Merge "Accept SEGV_MTESERR in CrasherTest.mte_async test."
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 15b5813..5da1b40 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -98,6 +98,7 @@
"libbase_headers",
"libdebuggerd_common_headers",
"bionic_libc_platform_headers",
+ "gwp_asan_headers",
],
whole_static_libs: [
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 70f333e..442392d 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -265,10 +265,12 @@
}
static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo,
- std::unique_ptr<unwindstack::Regs>* regs, ProcessInfo* process_info) {
+ std::unique_ptr<unwindstack::Regs>* regs, ProcessInfo* process_info,
+ bool* recoverable_gwp_asan_crash) {
std::aligned_storage<sizeof(CrashInfo) + 1, alignof(CrashInfo)>::type buf;
CrashInfo* crash_info = reinterpret_cast<CrashInfo*>(&buf);
ssize_t rc = TEMP_FAILURE_RETRY(read(fd.get(), &buf, sizeof(buf)));
+ *recoverable_gwp_asan_crash = false;
if (rc == -1) {
PLOG(FATAL) << "failed to read target ucontext";
} else {
@@ -304,6 +306,7 @@
process_info->scudo_region_info = crash_info->data.d.scudo_region_info;
process_info->scudo_ring_buffer = crash_info->data.d.scudo_ring_buffer;
process_info->scudo_ring_buffer_size = crash_info->data.d.scudo_ring_buffer_size;
+ *recoverable_gwp_asan_crash = crash_info->data.d.recoverable_gwp_asan_crash;
FALLTHROUGH_INTENDED;
case 1:
case 2:
@@ -468,6 +471,7 @@
std::map<pid_t, ThreadInfo> thread_info;
siginfo_t siginfo;
std::string error;
+ bool recoverable_gwp_asan_crash = false;
{
ATRACE_NAME("ptrace");
@@ -519,7 +523,8 @@
if (thread == g_target_thread) {
// Read the thread's registers along with the rest of the crash info out of the pipe.
- ReadCrashInfo(input_pipe, &siginfo, &info.registers, &process_info);
+ ReadCrashInfo(input_pipe, &siginfo, &info.registers, &process_info,
+ &recoverable_gwp_asan_crash);
info.siginfo = &siginfo;
info.signo = info.siginfo->si_signo;
@@ -646,7 +651,7 @@
}
}
- if (fatal_signal) {
+ if (fatal_signal && !recoverable_gwp_asan_crash) {
// Don't try to notify ActivityManager if it just crashed, or we might hang until timeout.
if (thread_info[target_process].thread_name != "system_server") {
activity_manager_notify(target_process, signo, amfd_data);
@@ -660,7 +665,7 @@
"* Process %d has been suspended while crashing.\n"
"* To attach the debugger, run this on the host:\n"
"*\n"
- "* gdbclient.py -p %d\n"
+ "* lldbclient.py -p %d\n"
"*\n"
"***********************************************************",
target_process, target_process);
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index 4eb7382..4043a6e 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -159,7 +159,8 @@
}
noinline void fprintf_null() {
- fprintf(nullptr, "oops");
+ FILE* sneaky_null = nullptr;
+ fprintf(sneaky_null, "oops");
}
noinline void readdir_null() {
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 9d9238f..517f2df 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -36,6 +36,7 @@
#include <string>
#include <thread>
+#include <android/dlext.h>
#include <android/fdsan.h>
#include <android/set_abort_message.h>
#include <bionic/malloc.h>
@@ -64,6 +65,7 @@
#include "crash_test.h"
#include "debuggerd/handler.h"
+#include "gtest/gtest.h"
#include "libdebuggerd/utility.h"
#include "protocol.h"
#include "tombstoned/tombstoned.h"
@@ -110,19 +112,6 @@
ASSERT_MATCH(result, \
R"(#\d\d pc [0-9a-f]+\s+ \S+ (\(offset 0x[0-9a-f]+\) )?\()" frame_name R"(\+)");
-// Enable GWP-ASan at the start of this process. GWP-ASan is enabled using
-// process sampling, so we need to ensure we force GWP-ASan on.
-__attribute__((constructor)) static void enable_gwp_asan() {
- android_mallopt_gwp_asan_options_t opts;
- // No, we're not an app, but let's turn ourselves on without sampling.
- // Technically, if someone's using the *.default_app sysprops, they'll adjust
- // our settings, but I don't think this will be common on a device that's
- // running debuggerd_tests.
- opts.desire = android_mallopt_gwp_asan_options_t::Action::TURN_ON_FOR_APP;
- opts.program_name = "";
- android_mallopt(M_INITIALIZE_GWP_ASAN, &opts, sizeof(android_mallopt_gwp_asan_options_t));
-}
-
static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, unique_fd* output_fd,
InterceptStatus* status, DebuggerdDumpType intercept_type) {
intercept_fd->reset(socket_local_client(kTombstonedInterceptSocketName,
@@ -468,76 +457,6 @@
}
#endif
-// Number of iterations required to reliably guarantee a GWP-ASan crash.
-// GWP-ASan's sample rate is not truly nondeterministic, it initialises a
-// thread-local counter at 2*SampleRate, and decrements on each malloc(). Once
-// the counter reaches zero, we provide a sampled allocation. Then, double that
-// figure to allow for left/right allocation alignment, as this is done randomly
-// without bias.
-#define GWP_ASAN_ITERATIONS_TO_ENSURE_CRASH (0x20000)
-
-struct GwpAsanTestParameters {
- size_t alloc_size;
- bool free_before_access;
- int access_offset;
- std::string cause_needle; // Needle to be found in the "Cause: [GWP-ASan]" line.
-};
-
-struct GwpAsanCrasherTest : CrasherTest, testing::WithParamInterface<GwpAsanTestParameters> {};
-
-GwpAsanTestParameters gwp_asan_tests[] = {
- {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 0, "Use After Free, 0 bytes into a 7-byte allocation"},
- {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 1, "Use After Free, 1 byte into a 7-byte allocation"},
- {/* alloc_size */ 7, /* free_before_access */ false, /* access_offset */ 16, "Buffer Overflow, 9 bytes right of a 7-byte allocation"},
- {/* alloc_size */ 16, /* free_before_access */ false, /* access_offset */ -1, "Buffer Underflow, 1 byte left of a 16-byte allocation"},
-};
-
-INSTANTIATE_TEST_SUITE_P(GwpAsanTests, GwpAsanCrasherTest, testing::ValuesIn(gwp_asan_tests));
-
-TEST_P(GwpAsanCrasherTest, gwp_asan_uaf) {
- if (mte_supported()) {
- // Skip this test on MTE hardware, as MTE will reliably catch these errors
- // instead of GWP-ASan.
- GTEST_SKIP() << "Skipped on MTE.";
- }
- // Skip this test on HWASan, which will reliably catch test errors as well.
- SKIP_WITH_HWASAN;
-
- GwpAsanTestParameters params = GetParam();
- LogcatCollector logcat_collector;
-
- int intercept_result;
- unique_fd output_fd;
- StartProcess([¶ms]() {
- for (unsigned i = 0; i < GWP_ASAN_ITERATIONS_TO_ENSURE_CRASH; ++i) {
- volatile char* p = reinterpret_cast<volatile char*>(malloc(params.alloc_size));
- if (params.free_before_access) free(static_cast<void*>(const_cast<char*>(p)));
- p[params.access_offset] = 42;
- if (!params.free_before_access) free(static_cast<void*>(const_cast<char*>(p)));
- }
- });
-
- StartIntercept(&output_fd);
- FinishCrasher();
- AssertDeath(SIGSEGV);
- FinishIntercept(&intercept_result);
-
- ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
-
- std::vector<std::string> log_sources(2);
- ConsumeFd(std::move(output_fd), &log_sources[0]);
- logcat_collector.Collect(&log_sources[1]);
-
- for (const auto& result : log_sources) {
- ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))");
- ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle);
- if (params.free_before_access) {
- ASSERT_MATCH(result, R"(deallocated by thread .*\n.*#00 pc)");
- }
- ASSERT_MATCH(result, R"((^|\s)allocated by thread .*\n.*#00 pc)");
- }
-}
-
struct SizeParamCrasherTest : CrasherTest, testing::WithParamInterface<size_t> {};
INSTANTIATE_TEST_SUITE_P(Sizes, SizeParamCrasherTest, testing::Values(0, 16, 131072));
@@ -1278,7 +1197,11 @@
static const char* const kDebuggerdSeccompPolicy =
"/system/etc/seccomp_policy/crash_dump." ABI_STRING ".policy";
-static pid_t seccomp_fork_impl(void (*prejail)()) {
+static void setup_jail(minijail* jail) {
+ if (!jail) {
+ LOG(FATAL) << "failed to create minijail";
+ }
+
std::string policy;
if (!android::base::ReadFileToString(kDebuggerdSeccompPolicy, &policy)) {
PLOG(FATAL) << "failed to read policy file";
@@ -1305,15 +1228,15 @@
PLOG(FATAL) << "failed to seek tmp_fd";
}
- ScopedMinijail jail{minijail_new()};
- if (!jail) {
- LOG(FATAL) << "failed to create minijail";
- }
+ minijail_no_new_privs(jail);
+ minijail_log_seccomp_filter_failures(jail);
+ minijail_use_seccomp_filter(jail);
+ minijail_parse_seccomp_filters_from_fd(jail, tmp_fd.release());
+}
- minijail_no_new_privs(jail.get());
- minijail_log_seccomp_filter_failures(jail.get());
- minijail_use_seccomp_filter(jail.get());
- minijail_parse_seccomp_filters_from_fd(jail.get(), tmp_fd.release());
+static pid_t seccomp_fork_impl(void (*prejail)()) {
+ ScopedMinijail jail{minijail_new()};
+ setup_jail(jail.get());
pid_t result = fork();
if (result == -1) {
@@ -1627,6 +1550,156 @@
AssertDeath(SIGABRT);
}
+struct GwpAsanTestParameters {
+ size_t alloc_size;
+ bool free_before_access;
+ int access_offset;
+ std::string cause_needle; // Needle to be found in the "Cause: [GWP-ASan]" line.
+};
+
+struct GwpAsanCrasherTest
+ : CrasherTest,
+ testing::WithParamInterface<
+ std::tuple<GwpAsanTestParameters, /* recoverable */ bool, /* seccomp */ bool>> {};
+
+GwpAsanTestParameters gwp_asan_tests[] = {
+ {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 0,
+ "Use After Free, 0 bytes into a 7-byte allocation"},
+ {/* alloc_size */ 15, /* free_before_access */ true, /* access_offset */ 1,
+ "Use After Free, 1 byte into a 15-byte allocation"},
+ {/* alloc_size */ 4096, /* free_before_access */ false, /* access_offset */ 4098,
+ "Buffer Overflow, 2 bytes right of a 4096-byte allocation"},
+ {/* alloc_size */ 4096, /* free_before_access */ false, /* access_offset */ -1,
+ "Buffer Underflow, 1 byte left of a 4096-byte allocation"},
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ GwpAsanTests, GwpAsanCrasherTest,
+ testing::Combine(testing::ValuesIn(gwp_asan_tests),
+ /* recoverable */ testing::Bool(),
+ /* seccomp */ testing::Bool()),
+ [](const testing::TestParamInfo<
+ std::tuple<GwpAsanTestParameters, /* recoverable */ bool, /* seccomp */ bool>>& info) {
+ const GwpAsanTestParameters& params = std::get<0>(info.param);
+ std::string name = params.free_before_access ? "UseAfterFree" : "Overflow";
+ name += testing::PrintToString(params.alloc_size);
+ name += "Alloc";
+ if (params.access_offset < 0) {
+ name += "Left";
+ name += testing::PrintToString(params.access_offset * -1);
+ } else {
+ name += "Right";
+ name += testing::PrintToString(params.access_offset);
+ }
+ name += "Bytes";
+ if (std::get<1>(info.param)) name += "Recoverable";
+ if (std::get<2>(info.param)) name += "Seccomp";
+ return name;
+ });
+
+TEST_P(GwpAsanCrasherTest, run_gwp_asan_test) {
+ if (mte_supported()) {
+ // Skip this test on MTE hardware, as MTE will reliably catch these errors
+ // instead of GWP-ASan.
+ GTEST_SKIP() << "Skipped on MTE.";
+ }
+ // Skip this test on HWASan, which will reliably catch test errors as well.
+ SKIP_WITH_HWASAN;
+
+ GwpAsanTestParameters params = std::get<0>(GetParam());
+ bool recoverable = std::get<1>(GetParam());
+ LogcatCollector logcat_collector;
+
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([&recoverable]() {
+ const char* env[] = {"GWP_ASAN_SAMPLE_RATE=1", "GWP_ASAN_PROCESS_SAMPLING=1",
+ "GWP_ASAN_MAX_ALLOCS=40000", nullptr, nullptr};
+ if (recoverable) {
+ env[3] = "GWP_ASAN_RECOVERABLE=true";
+ }
+ std::string test_name = ::testing::UnitTest::GetInstance()->current_test_info()->name();
+ test_name = std::regex_replace(test_name, std::regex("run_gwp_asan_test"),
+ "DISABLED_run_gwp_asan_test");
+ std::string test_filter = "--gtest_filter=*";
+ test_filter += test_name;
+ std::string this_binary = android::base::GetExecutablePath();
+ const char* args[] = {this_binary.c_str(), "--gtest_also_run_disabled_tests",
+ test_filter.c_str(), nullptr};
+ // We check the crash report from a debuggerd handler and from logcat. The
+ // echo from stdout/stderr of the subprocess trips up atest, because it
+ // doesn't like that two tests started in a row without the first one
+ // finishing (even though the second one is in a subprocess).
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ execve(this_binary.c_str(), const_cast<char**>(args), const_cast<char**>(env));
+ });
+
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ if (recoverable) {
+ AssertDeath(0);
+ } else {
+ AssertDeath(SIGSEGV);
+ }
+ FinishIntercept(&intercept_result);
+
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::vector<std::string> log_sources(2);
+ ConsumeFd(std::move(output_fd), &log_sources[0]);
+ logcat_collector.Collect(&log_sources[1]);
+
+ // seccomp forces the fallback handler, which doesn't print GWP-ASan debugging
+ // information. Make sure the recovery still works, but the report won't be
+ // hugely useful, it looks like a regular SEGV.
+ bool seccomp = std::get<2>(GetParam());
+ if (!seccomp) {
+ for (const auto& result : log_sources) {
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))");
+ ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle);
+ if (params.free_before_access) {
+ ASSERT_MATCH(result, R"(deallocated by thread .*\n.*#00 pc)");
+ }
+ ASSERT_MATCH(result, R"((^|\s)allocated by thread .*\n.*#00 pc)");
+ }
+ }
+}
+
+TEST_P(GwpAsanCrasherTest, DISABLED_run_gwp_asan_test) {
+ GwpAsanTestParameters params = std::get<0>(GetParam());
+ bool seccomp = std::get<2>(GetParam());
+ if (seccomp) {
+ ScopedMinijail jail{minijail_new()};
+ setup_jail(jail.get());
+ minijail_enter(jail.get());
+ }
+
+ // Use 'volatile' to prevent a very clever compiler eliminating the store.
+ char* volatile p = reinterpret_cast<char* volatile>(malloc(params.alloc_size));
+ if (params.free_before_access) free(static_cast<void*>(const_cast<char*>(p)));
+ p[params.access_offset] = 42;
+ if (!params.free_before_access) free(static_cast<void*>(const_cast<char*>(p)));
+
+ bool recoverable = std::get<1>(GetParam());
+ ASSERT_TRUE(recoverable); // Non-recoverable should have crashed.
+
+ // As we're in recoverable mode, trigger another 2x use-after-frees (ensuring
+ // we end with at least one in a different slot), make sure the process still
+ // doesn't crash.
+ p = reinterpret_cast<char* volatile>(malloc(params.alloc_size));
+ char* volatile p2 = reinterpret_cast<char* volatile>(malloc(params.alloc_size));
+ free(static_cast<void*>(const_cast<char*>(p)));
+ free(static_cast<void*>(const_cast<char*>(p2)));
+ *p = 42;
+ *p2 = 42;
+
+ // Under clang coverage (which is a default TEST_MAPPING presubmit target), the
+ // recoverable+seccomp tests fail because the minijail prevents some atexit syscalls that clang
+ // coverage does. Thus, skip the atexit handlers.
+ _exit(0);
+}
+
TEST_F(CrasherTest, fdsan_warning_abort_message) {
int intercept_result;
unique_fd output_fd;
@@ -1909,7 +1982,7 @@
ASSERT_MATCH(result, R"(Cause: stack pointer[^\n]*stack overflow.\n)");
}
-static bool CopySharedLibrary(const char* tmp_dir, std::string* tmp_so_name) {
+static std::string GetTestLibraryPath() {
std::string test_lib(testing::internal::GetArgvs()[0]);
auto const value = test_lib.find_last_of('/');
if (value == std::string::npos) {
@@ -1917,7 +1990,62 @@
} else {
test_lib = test_lib.substr(0, value + 1) + "./";
}
- test_lib += "libcrash_test.so";
+ return test_lib + "libcrash_test.so";
+}
+
+static void CreateEmbeddedLibrary(int out_fd) {
+ std::string test_lib(GetTestLibraryPath());
+ android::base::unique_fd fd(open(test_lib.c_str(), O_RDONLY | O_CLOEXEC));
+ ASSERT_NE(fd.get(), -1);
+ off_t file_size = lseek(fd, 0, SEEK_END);
+ ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0);
+ std::vector<uint8_t> contents(file_size);
+ ASSERT_TRUE(android::base::ReadFully(fd, contents.data(), contents.size()));
+
+ // Put the shared library data at a pagesize() offset.
+ ASSERT_EQ(lseek(out_fd, 4 * getpagesize(), SEEK_CUR), 4 * getpagesize());
+ ASSERT_EQ(static_cast<size_t>(write(out_fd, contents.data(), contents.size())), contents.size());
+}
+
+TEST_F(CrasherTest, non_zero_offset_in_library) {
+ int intercept_result;
+ unique_fd output_fd;
+ TemporaryFile tf;
+ CreateEmbeddedLibrary(tf.fd);
+ StartProcess([&tf]() {
+ android_dlextinfo extinfo{};
+ extinfo.flags = ANDROID_DLEXT_USE_LIBRARY_FD | ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET;
+ extinfo.library_fd = tf.fd;
+ extinfo.library_fd_offset = 4 * getpagesize();
+ void* handle = android_dlopen_ext(tf.path, RTLD_NOW, &extinfo);
+ if (handle == nullptr) {
+ _exit(1);
+ }
+ void (*crash_func)() = reinterpret_cast<void (*)()>(dlsym(handle, "crash"));
+ if (crash_func == nullptr) {
+ _exit(1);
+ }
+ crash_func();
+ });
+
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+ FinishIntercept(&intercept_result);
+
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ // Verify the crash includes an offset value in the backtrace.
+ std::string match_str = android::base::StringPrintf("%s\\!libcrash_test.so \\(offset 0x%x\\)",
+ tf.path, 4 * getpagesize());
+ ASSERT_MATCH(result, match_str);
+}
+
+static bool CopySharedLibrary(const char* tmp_dir, std::string* tmp_so_name) {
+ std::string test_lib(GetTestLibraryPath());
*tmp_so_name = std::string(tmp_dir) + "/libcrash_test.so";
std::string cp_cmd = android::base::StringPrintf("cp %s %s", test_lib.c_str(), tmp_dir);
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index 7120d73..baa5bfb 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -395,6 +395,7 @@
ASSERT_SAME_OFFSET(scudo_region_info, scudo_region_info);
ASSERT_SAME_OFFSET(scudo_ring_buffer, scudo_ring_buffer);
ASSERT_SAME_OFFSET(scudo_ring_buffer_size, scudo_ring_buffer_size);
+ ASSERT_SAME_OFFSET(recoverable_gwp_asan_crash, recoverable_gwp_asan_crash);
#undef ASSERT_SAME_OFFSET
iovs[3] = {.iov_base = &thread_info->process_info,
@@ -565,17 +566,37 @@
process_info = g_callbacks.get_process_info();
}
+ // GWP-ASan catches use-after-free and heap-buffer-overflow by using PROT_NONE
+ // guard pages, which lead to SEGV. Normally, debuggerd prints a bug report
+ // and the process terminates, but in some cases, we actually want to print
+ // the bug report and let the signal handler return, and restart the process.
+ // In order to do that, we need to disable GWP-ASan's guard pages. The
+ // following callbacks handle this case.
+ gwp_asan_callbacks_t gwp_asan_callbacks = g_callbacks.get_gwp_asan_callbacks();
+ if (signal_number == SIGSEGV && signal_has_si_addr(info) &&
+ gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery &&
+ gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report &&
+ gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report &&
+ gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery(info->si_addr)) {
+ gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report(info->si_addr);
+ process_info.recoverable_gwp_asan_crash = true;
+ }
+
// If sival_int is ~0, it means that the fallback handler has been called
// once before and this function is being called again to dump the stack
// of a specific thread. It is possible that the prctl call might return 1,
// then return 0 in subsequent calls, so check the sival_int to determine if
// the fallback handler should be called first.
- if (si_val == kDebuggerdFallbackSivalUintptrRequestDump ||
- prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) == 1) {
+ bool no_new_privs = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) == 1;
+ if (si_val == kDebuggerdFallbackSivalUintptrRequestDump || no_new_privs) {
// This check might be racy if another thread sets NO_NEW_PRIVS, but this should be unlikely,
// you can only set NO_NEW_PRIVS to 1, and the effect should be at worst a single missing
// ANR trace.
debuggerd_fallback_handler(info, ucontext, process_info.abort_msg);
+ if (no_new_privs && process_info.recoverable_gwp_asan_crash) {
+ gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report(info->si_addr);
+ return;
+ }
resend_signal(info);
return;
}
@@ -649,6 +670,9 @@
// If the signal is fatal, don't unlock the mutex to prevent other crashing threads from
// starting to dump right before our death.
pthread_mutex_unlock(&crash_mutex);
+ } else if (process_info.recoverable_gwp_asan_crash) {
+ gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report(info->si_addr);
+ pthread_mutex_unlock(&crash_mutex);
}
#ifdef __aarch64__
else if (info->si_signo == SIGSEGV &&
@@ -727,3 +751,52 @@
debuggerd_register_handlers(&action);
}
+
+// When debuggerd's signal handler is the first handler called, it's great at
+// handling the recoverable GWP-ASan mode. For apps, sigchain (from libart) is
+// always the first signal handler, and so the following function is what
+// sigchain must call before processing the signal. This allows for processing
+// of a potentially recoverable GWP-ASan crash. If the signal requires GWP-ASan
+// recovery, then dump a report (via the regular debuggerd hanndler), and patch
+// up the allocator, and allow the process to continue (indicated by returning
+// 'true'). If the crash has nothing to do with GWP-ASan, or recovery isn't
+// possible, return 'false'.
+bool debuggerd_handle_signal(int signal_number, siginfo_t* info, void* context) {
+ if (signal_number != SIGSEGV || !signal_has_si_addr(info)) return false;
+
+ gwp_asan_callbacks_t gwp_asan_callbacks = g_callbacks.get_gwp_asan_callbacks();
+ if (gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery == nullptr ||
+ gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report == nullptr ||
+ gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report == nullptr ||
+ !gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery(info->si_addr)) {
+ return false;
+ }
+
+ // Only dump a crash report for the first GWP-ASan crash. ActivityManager
+ // doesn't like it when an app crashes multiple times, and is even more strict
+ // about an app crashing multiple times in a short time period. While the app
+ // won't crash fully when we do GWP-ASan recovery, ActivityManager still gets
+ // the information about the crash through the DropBoxManager service. If an
+ // app has multiple back-to-back GWP-ASan crashes, this would lead to the app
+ // being killed, which defeats the purpose of having the recoverable mode. To
+ // mitigate against this, only generate a debuggerd crash report for the first
+ // GWP-ASan crash encountered. We still need to do the patching up of the
+ // allocator though, so do that.
+ static pthread_mutex_t first_crash_mutex = PTHREAD_MUTEX_INITIALIZER;
+ pthread_mutex_lock(&first_crash_mutex);
+ static bool first_crash = true;
+
+ if (first_crash) {
+ // `debuggerd_signal_handler` will call
+ // `debuggerd_gwp_asan_(pre|post)_crash_report`, so no need to manually call
+ // them here.
+ debuggerd_signal_handler(signal_number, info, context);
+ first_crash = false;
+ } else {
+ gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report(info->si_addr);
+ gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report(info->si_addr);
+ }
+
+ pthread_mutex_unlock(&first_crash_mutex);
+ return true;
+}
diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h
index 1f9f4e2..ebb5372 100644
--- a/debuggerd/include/debuggerd/handler.h
+++ b/debuggerd/include/debuggerd/handler.h
@@ -35,7 +35,7 @@
// When updating this data structure, CrashInfoDataDynamic and the code in
// ReadCrashInfo() must also be updated.
-struct debugger_process_info {
+struct __attribute__((packed)) debugger_process_info {
void* abort_msg;
void* fdsan_table;
const gwp_asan::AllocatorState* gwp_asan_state;
@@ -44,16 +44,28 @@
const char* scudo_region_info;
const char* scudo_ring_buffer;
size_t scudo_ring_buffer_size;
+ bool recoverable_gwp_asan_crash;
};
+// GWP-ASan calbacks to support the recoverable mode. Separate from the
+// debuggerd_callbacks_t because these values aren't available at debuggerd_init
+// time, and have to be synthesized on request.
+typedef struct {
+ bool (*debuggerd_needs_gwp_asan_recovery)(void* fault_addr);
+ void (*debuggerd_gwp_asan_pre_crash_report)(void* fault_addr);
+ void (*debuggerd_gwp_asan_post_crash_report)(void* fault_addr);
+} gwp_asan_callbacks_t;
+
// These callbacks are called in a signal handler, and thus must be async signal safe.
// If null, the callbacks will not be called.
typedef struct {
debugger_process_info (*get_process_info)();
+ gwp_asan_callbacks_t (*get_gwp_asan_callbacks)();
void (*post_dump)();
} debuggerd_callbacks_t;
void debuggerd_init(debuggerd_callbacks_t* callbacks);
+bool debuggerd_handle_signal(int signal_number, siginfo_t* info, void* context);
// DEBUGGER_ACTION_DUMP_TOMBSTONE and DEBUGGER_ACTION_DUMP_BACKTRACE are both
// triggered via BIONIC_SIGNAL_DEBUGGER. The debugger_action_t is sent via si_value
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index a05bcec..e4d68f8 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -176,8 +176,14 @@
build_id = StringPrintf(" (BuildId: %s)", frame.build_id().c_str());
}
- CB(should_log, " #%02d pc %0*" PRIx64 " %s%s%s", index++, pointer_width(tombstone) * 2,
- frame.rel_pc(), frame.file_name().c_str(), function.c_str(), build_id.c_str());
+ std::string line =
+ StringPrintf(" #%02d pc %0*" PRIx64 " %s", index++, pointer_width(tombstone) * 2,
+ frame.rel_pc(), frame.file_name().c_str());
+ if (frame.file_map_offset() != 0) {
+ line += StringPrintf(" (offset 0x%" PRIx64 ")", frame.file_map_offset());
+ }
+ line += function + build_id;
+ CB(should_log, "%s", line.c_str());
}
}
diff --git a/debuggerd/protocol.h b/debuggerd/protocol.h
index e7cb218..b60cf5b 100644
--- a/debuggerd/protocol.h
+++ b/debuggerd/protocol.h
@@ -99,6 +99,7 @@
uintptr_t scudo_region_info;
uintptr_t scudo_ring_buffer;
size_t scudo_ring_buffer_size;
+ bool recoverable_gwp_asan_crash;
};
struct __attribute__((__packed__)) CrashInfo {
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index a801900..3b786e8 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -283,14 +283,18 @@
srcs: [
"bootimg_utils.cpp",
+ "fastboot_driver.cpp",
"fastboot.cpp",
+ "filesystem.cpp",
"fs.cpp",
"socket.cpp",
+ "storage.cpp",
+ "super_flash_helper.cpp",
"tcp.cpp",
"udp.cpp",
"util.cpp",
"vendor_boot_img_utils.cpp",
- "fastboot_driver.cpp",
+ "task.cpp",
],
// Only version the final binaries
@@ -372,14 +376,19 @@
defaults: ["fastboot_host_defaults"],
srcs: [
+ "fastboot_driver_test.cpp",
"fastboot_test.cpp",
"socket_mock.cpp",
"socket_test.cpp",
+ "super_flash_helper_test.cpp",
"tcp_test.cpp",
"udp_test.cpp",
],
- static_libs: ["libfastboot"],
+ static_libs: [
+ "libfastboot",
+ "libgmock",
+ ],
target: {
windows: {
@@ -390,6 +399,14 @@
enabled: false,
},
},
+
+ test_suites: ["general-tests"],
+
+ data: [
+ "testdata/super.img",
+ "testdata/super_empty.img",
+ "testdata/system.img",
+ ],
}
cc_test_host {
diff --git a/fastboot/README.md b/fastboot/README.md
index d3b6c1a..63db5c3 100644
--- a/fastboot/README.md
+++ b/fastboot/README.md
@@ -29,20 +29,27 @@
2. Client response with a single packet no greater than 256 bytes.
The first four bytes of the response are "OKAY", "FAIL", "DATA",
- or "INFO". Additional bytes may contain an (ascii) informative
+ "INFO" or "TEXT". Additional bytes may contain an (ascii) informative
message.
a. INFO -> the remaining 252 bytes are an informative message
(providing progress or diagnostic messages). They should
- be displayed and then step #2 repeats
+ be displayed and then step #2 repeats. The print format is:
+ "(bootloader) " + InfoMessagePayload + '\n'
- b. FAIL -> the requested command failed. The remaining 252 bytes
+ b. TEXT -> the remaining 252 bytes are arbitrary. They should
+ be displayed and then step #2 repeats.
+ It differs from info in that no formatting is applied.
+ The payload is printed as-is with no newline at the end.
+ Payload is expected to be NULL terminated.
+
+ c. FAIL -> the requested command failed. The remaining 252 bytes
of the response (if present) provide a textual failure message
to present to the user. Stop.
- c. OKAY -> the requested command completed successfully. Go to #5
+ d. OKAY -> the requested command completed successfully. Go to #5
- d. DATA -> the requested command is ready for the data phase.
+ e. DATA -> the requested command is ready for the data phase.
A DATA response packet will be 12 bytes long, in the form of
DATA00000000 where the 8 digit hexadecimal number represents
the total data size to transfer.
@@ -54,15 +61,17 @@
in the "DATA" response above.
4. Client responds with a single packet no greater than 256 bytes.
- The first four bytes of the response are "OKAY", "FAIL", or "INFO".
- Similar to #2:
+ The first four bytes of the response are "OKAY", "FAIL",
+ "INFO" or "TEXT". Similar to #2:
- a. INFO -> display the remaining 252 bytes and return to #4
+ a. INFO -> display the formatted remaining 252 bytes and return to #4
- b. FAIL -> display the remaining 252 bytes (if present) as a failure
+ b. TEXT -> display the unformatted remaining 252 bytes and return to #4
+
+ c. FAIL -> display the remaining 252 bytes (if present) as a failure
reason and consider the command failed. Stop.
- c. OKAY -> success. Go to #5
+ d. OKAY -> success. Go to #5
5. Success. Stop.
diff --git a/fastboot/TEST_MAPPING b/fastboot/TEST_MAPPING
new file mode 100644
index 0000000..10f3d82
--- /dev/null
+++ b/fastboot/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "fastboot_test"
+ }
+ ]
+}
diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index 5afeb4f..6b6a982 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -70,11 +70,14 @@
using HidlFastboot = android::hardware::fastboot::V1_1::IFastboot;
using aidl::android::hardware::fastboot::FastbootShim;
auto service_name = IFastboot::descriptor + "/default"s;
- ndk::SpAIBinder binder(AServiceManager_getService(service_name.c_str()));
- std::shared_ptr<IFastboot> fastboot = IFastboot::fromBinder(binder);
- if (fastboot != nullptr) {
- LOG(INFO) << "Using AIDL fastboot service";
- return fastboot;
+ if (AServiceManager_isDeclared(service_name.c_str())) {
+ ndk::SpAIBinder binder(AServiceManager_waitForService(service_name.c_str()));
+ std::shared_ptr<IFastboot> fastboot = IFastboot::fromBinder(binder);
+ if (fastboot != nullptr) {
+ LOG(INFO) << "Found and using AIDL fastboot service";
+ return fastboot;
+ }
+ LOG(WARNING) << "AIDL fastboot service is declared, but it cannot be retrieved.";
}
LOG(INFO) << "Unable to get AIDL fastboot service, trying HIDL...";
android::sp<HidlFastboot> hidl_fastboot = HidlFastboot::getService();
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 9676f87..430ff14 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -44,8 +44,12 @@
#include <unistd.h>
#include <chrono>
+#include <fstream>
#include <functional>
+#include <iostream>
+#include <memory>
#include <regex>
+#include <sstream>
#include <string>
#include <thread>
#include <utility>
@@ -53,6 +57,7 @@
#include <android-base/endian.h>
#include <android-base/file.h>
+#include <android-base/logging.h>
#include <android-base/macros.h>
#include <android-base/parseint.h>
#include <android-base/parsenetaddress.h>
@@ -62,6 +67,7 @@
#include <build/version.h>
#include <libavb/libavb.h>
#include <liblp/liblp.h>
+#include <liblp/super_layout_builder.h>
#include <platform_tools_version.h>
#include <sparse/sparse.h>
#include <ziparchive/zip_archive.h>
@@ -71,6 +77,9 @@
#include "diagnose_usb.h"
#include "fastboot_driver.h"
#include "fs.h"
+#include "storage.h"
+#include "super_flash_helper.h"
+#include "task.h"
#include "tcp.h"
#include "transport.h"
#include "udp.h"
@@ -113,31 +122,12 @@
struct fastboot_buffer {
enum fb_buffer_type type;
- void* data;
+ std::vector<SparsePtr> files;
int64_t sz;
unique_fd fd;
int64_t image_size;
};
-enum class ImageType {
- // Must be flashed for device to boot into the kernel.
- BootCritical,
- // Normal partition to be flashed during "flashall".
- Normal,
- // Partition that is never flashed during "flashall".
- Extra
-};
-
-struct Image {
- std::string nickname;
- std::string img_name;
- std::string sig_name;
- std::string part_name;
- bool optional_if_no_image;
- ImageType type;
- bool IsSecondary() const { return nickname.empty(); }
-};
-
static std::vector<Image> images = {
// clang-format off
{ "boot", "boot.img", "boot.sig", "boot", false, ImageType::BootCritical },
@@ -211,7 +201,7 @@
return std::string(dir) + "/" + img_name;
}
-static std::string find_item(const std::string& item) {
+std::string find_item(const std::string& item) {
for (size_t i = 0; i < images.size(); ++i) {
if (!images[i].nickname.empty() && item == images[i].nickname) {
return find_item_given_name(images[i].img_name);
@@ -246,12 +236,8 @@
fprintf(stderr, "(bootloader) %s\n", info.c_str());
}
-static int64_t get_file_size(borrowed_fd fd) {
- struct stat sb;
- if (fstat(fd.get(), &sb) == -1) {
- die("could not get file size");
- }
- return sb.st_size;
+static void TextMessage(const std::string& text) {
+ fprintf(stderr, "%s", text.c_str());
}
bool ReadFileToVector(const std::string& file, std::vector<char>* out) {
@@ -279,8 +265,36 @@
return 0;
}
-static int match_fastboot(usb_ifc_info* info) {
- return match_fastboot_with_serial(info, serial);
+static ifc_match_func match_fastboot(const char* local_serial = serial) {
+ return [local_serial](usb_ifc_info* info) -> int {
+ return match_fastboot_with_serial(info, local_serial);
+ };
+}
+
+// output compatible with "adb devices"
+static void PrintDevice(const char* local_serial, const char* status = nullptr,
+ const char* details = nullptr) {
+ if (local_serial == nullptr || strlen(local_serial) == 0) {
+ return;
+ }
+
+ if (g_long_listing) {
+ printf("%-22s", local_serial);
+ } else {
+ printf("%s\t", local_serial);
+ }
+
+ if (status != nullptr && strlen(status) > 0) {
+ printf(" %s", status);
+ }
+
+ if (g_long_listing) {
+ if (details != nullptr && strlen(details) > 0) {
+ printf(" %s", details);
+ }
+ }
+
+ putchar('\n');
}
static int list_devices_callback(usb_ifc_info* info) {
@@ -296,91 +310,235 @@
if (!serial[0]) {
serial = "????????????";
}
- // output compatible with "adb devices"
- if (!g_long_listing) {
- printf("%s\t%s", serial.c_str(), interface.c_str());
- } else {
- printf("%-22s %s", serial.c_str(), interface.c_str());
- if (strlen(info->device_path) > 0) printf(" %s", info->device_path);
- }
- putchar('\n');
+
+ PrintDevice(serial.c_str(), interface.c_str(), info->device_path);
}
return -1;
}
-// Opens a new Transport connected to a device. If |serial| is non-null it will be used to identify
-// a specific device, otherwise the first USB device found will be used.
+Result<NetworkSerial, FastbootError> ParseNetworkSerial(const std::string& serial) {
+ Socket::Protocol protocol;
+ const char* net_address = nullptr;
+ int port = 0;
+
+ if (android::base::StartsWith(serial, "tcp:")) {
+ protocol = Socket::Protocol::kTcp;
+ net_address = serial.c_str() + strlen("tcp:");
+ port = tcp::kDefaultPort;
+ } else if (android::base::StartsWith(serial, "udp:")) {
+ protocol = Socket::Protocol::kUdp;
+ net_address = serial.c_str() + strlen("udp:");
+ port = udp::kDefaultPort;
+ } else {
+ return Error<FastbootError>(FastbootError::Type::NETWORK_SERIAL_WRONG_PREFIX)
+ << "protocol prefix ('tcp:' or 'udp:') is missed: " << serial << ". "
+ << "Expected address format:\n"
+ << "<protocol>:<address>:<port> (tcp:localhost:5554)";
+ }
+
+ std::string error;
+ std::string host;
+ if (!android::base::ParseNetAddress(net_address, &host, &port, nullptr, &error)) {
+ return Error<FastbootError>(FastbootError::Type::NETWORK_SERIAL_WRONG_ADDRESS)
+ << "invalid network address '" << net_address << "': " << error;
+ }
+
+ return NetworkSerial{protocol, host, port};
+}
+
+// Opens a new Transport connected to the particular device.
+// arguments:
//
-// If |serial| is non-null but invalid, this exits.
-// Otherwise it blocks until the target is available.
+// local_serial - device to connect (can be a network or usb serial name)
+// wait_for_device - flag indicates whether we need to wait for device
+// announce - flag indicates whether we need to print error to stdout in case
+// we cannot connect to the device
//
// The returned Transport is a singleton, so multiple calls to this function will return the same
// object, and the caller should not attempt to delete the returned Transport.
-static Transport* open_device() {
- bool announce = true;
-
- Socket::Protocol protocol = Socket::Protocol::kTcp;
- std::string host;
- int port = 0;
- if (serial != nullptr) {
- const char* net_address = nullptr;
-
- if (android::base::StartsWith(serial, "tcp:")) {
- protocol = Socket::Protocol::kTcp;
- port = tcp::kDefaultPort;
- net_address = serial + strlen("tcp:");
- } else if (android::base::StartsWith(serial, "udp:")) {
- protocol = Socket::Protocol::kUdp;
- port = udp::kDefaultPort;
- net_address = serial + strlen("udp:");
- }
-
- if (net_address != nullptr) {
- std::string error;
- if (!android::base::ParseNetAddress(net_address, &host, &port, nullptr, &error)) {
- die("invalid network address '%s': %s\n", net_address, error.c_str());
- }
- }
- }
+static Transport* open_device(const char* local_serial, bool wait_for_device = true,
+ bool announce = true) {
+ const Result<NetworkSerial, FastbootError> network_serial = ParseNetworkSerial(local_serial);
Transport* transport = nullptr;
while (true) {
- if (!host.empty()) {
+ if (network_serial.ok()) {
std::string error;
- if (protocol == Socket::Protocol::kTcp) {
- transport = tcp::Connect(host, port, &error).release();
- } else if (protocol == Socket::Protocol::kUdp) {
- transport = udp::Connect(host, port, &error).release();
+ if (network_serial->protocol == Socket::Protocol::kTcp) {
+ transport = tcp::Connect(network_serial->address, network_serial->port, &error)
+ .release();
+ } else if (network_serial->protocol == Socket::Protocol::kUdp) {
+ transport = udp::Connect(network_serial->address, network_serial->port, &error)
+ .release();
}
if (transport == nullptr && announce) {
- fprintf(stderr, "error: %s\n", error.c_str());
+ LOG(ERROR) << "error: " << error;
}
+ } else if (network_serial.error().code() ==
+ FastbootError::Type::NETWORK_SERIAL_WRONG_PREFIX) {
+ // WRONG_PREFIX is special because it happens when user wants to communicate with USB
+ // device
+ transport = usb_open(match_fastboot(local_serial));
} else {
- transport = usb_open(match_fastboot);
+ Expect(network_serial);
}
if (transport != nullptr) {
return transport;
}
+ if (!wait_for_device) {
+ return nullptr;
+ }
+
if (announce) {
announce = false;
- fprintf(stderr, "< waiting for %s >\n", serial ? serial : "any device");
+ LOG(ERROR) << "< waiting for " << local_serial << ">";
}
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
+static Transport* NetworkDeviceConnected(bool print = false) {
+ Transport* transport = nullptr;
+ Transport* result = nullptr;
+
+ ConnectedDevicesStorage storage;
+ std::set<std::string> devices;
+ {
+ FileLock lock = storage.Lock();
+ devices = storage.ReadDevices(lock);
+ }
+
+ for (const std::string& device : devices) {
+ transport = open_device(device.c_str(), false, false);
+
+ if (print) {
+ PrintDevice(device.c_str(), transport == nullptr ? "offline" : "fastboot");
+ }
+
+ if (transport != nullptr) {
+ result = transport;
+ }
+ }
+
+ return result;
+}
+
+// Detects the fastboot connected device to open a new Transport.
+// Detecting logic:
+//
+// if serial is provided - try to connect to this particular usb/network device
+// othervise:
+// 1. Check connected usb devices and return the last connected one
+// 2. Check connected network devices and return the last connected one
+// 2. If nothing is connected - wait for any device by repeating p. 1 and 2
+//
+// The returned Transport is a singleton, so multiple calls to this function will return the same
+// object, and the caller should not attempt to delete the returned Transport.
+static Transport* open_device() {
+ if (serial != nullptr) {
+ return open_device(serial);
+ }
+
+ bool announce = true;
+ Transport* transport = nullptr;
+ while (true) {
+ transport = usb_open(match_fastboot(nullptr));
+ if (transport != nullptr) {
+ return transport;
+ }
+
+ transport = NetworkDeviceConnected();
+ if (transport != nullptr) {
+ return transport;
+ }
+
+ if (announce) {
+ announce = false;
+ LOG(ERROR) << "< waiting for any device >";
+ }
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+}
+
+static int Connect(int argc, char* argv[]) {
+ if (argc != 1) {
+ LOG(FATAL) << "connect command requires to receive only 1 argument. Usage:" << std::endl
+ << "fastboot connect [tcp:|udp:host:port]";
+ }
+
+ const char* local_serial = *argv;
+ Expect(ParseNetworkSerial(local_serial));
+
+ const Transport* transport = open_device(local_serial, false);
+ if (transport == nullptr) {
+ return 1;
+ }
+
+ ConnectedDevicesStorage storage;
+ {
+ FileLock lock = storage.Lock();
+ std::set<std::string> devices = storage.ReadDevices(lock);
+ devices.insert(local_serial);
+ storage.WriteDevices(lock, devices);
+ }
+
+ return 0;
+}
+
+static int Disconnect(const char* local_serial) {
+ Expect(ParseNetworkSerial(local_serial));
+
+ ConnectedDevicesStorage storage;
+ {
+ FileLock lock = storage.Lock();
+ std::set<std::string> devices = storage.ReadDevices(lock);
+ devices.erase(local_serial);
+ storage.WriteDevices(lock, devices);
+ }
+
+ return 0;
+}
+
+static int Disconnect() {
+ ConnectedDevicesStorage storage;
+ {
+ FileLock lock = storage.Lock();
+ storage.Clear(lock);
+ }
+
+ return 0;
+}
+
+static int Disconnect(int argc, char* argv[]) {
+ switch (argc) {
+ case 0: {
+ return Disconnect();
+ }
+ case 1: {
+ return Disconnect(*argv);
+ }
+ default:
+ LOG(FATAL) << "disconnect command can receive only 0 or 1 arguments. Usage:"
+ << std::endl
+ << "fastboot disconnect # disconnect all devices" << std::endl
+ << "fastboot disconnect [tcp:|udp:host:port] # disconnect device";
+ }
+
+ return 0;
+}
+
static void list_devices() {
// We don't actually open a USB device here,
// just getting our callback called so we can
// list all the connected devices.
usb_open(list_devices_callback);
+ NetworkDeviceConnected(/* print */ true);
}
-
-static void syntax_error(const char* fmt, ...) {
+void syntax_error(const char* fmt, ...) {
fprintf(stderr, "fastboot: usage: ");
va_list ap;
@@ -824,27 +982,32 @@
fprintf(stderr, "--------------------------------------------\n");
}
-static struct sparse_file** load_sparse_files(int fd, int64_t max_size) {
- struct sparse_file* s = sparse_file_import_auto(fd, false, true);
- if (!s) die("cannot sparse read file");
-
+static std::vector<SparsePtr> resparse_file(sparse_file* s, int64_t max_size) {
if (max_size <= 0 || max_size > std::numeric_limits<uint32_t>::max()) {
die("invalid max size %" PRId64, max_size);
}
- int files = sparse_file_resparse(s, max_size, nullptr, 0);
+ const int files = sparse_file_resparse(s, max_size, nullptr, 0);
if (files < 0) die("Failed to resparse");
- sparse_file** out_s =
- reinterpret_cast<sparse_file**>(calloc(sizeof(struct sparse_file*), files + 1));
- if (!out_s) die("Failed to allocate sparse file array");
+ auto temp = std::make_unique<sparse_file*[]>(files);
+ const int rv = sparse_file_resparse(s, max_size, temp.get(), files);
+ if (rv < 0) die("Failed to resparse");
- files = sparse_file_resparse(s, max_size, out_s, files);
- if (files < 0) die("Failed to resparse");
-
+ std::vector<SparsePtr> out_s;
+ for (int i = 0; i < files; i++) {
+ out_s.emplace_back(temp[i], sparse_file_destroy);
+ }
return out_s;
}
+static std::vector<SparsePtr> load_sparse_files(int fd, int64_t max_size) {
+ SparsePtr s(sparse_file_import_auto(fd, false, true), sparse_file_destroy);
+ if (!s) die("cannot sparse read file");
+
+ return resparse_file(s.get(), max_size);
+}
+
static uint64_t get_uint_var(const char* var_name) {
std::string value_str;
if (fb->GetVar(var_name, &value_str) != fastboot::SUCCESS || value_str.empty()) {
@@ -903,15 +1066,13 @@
int64_t limit = get_sparse_limit(sz);
buf->fd = std::move(fd);
if (limit) {
- sparse_file** s = load_sparse_files(buf->fd.get(), limit);
- if (s == nullptr) {
+ buf->files = load_sparse_files(buf->fd.get(), limit);
+ if (buf->files.empty()) {
return false;
}
buf->type = FB_BUFFER_SPARSE;
- buf->data = s;
} else {
buf->type = FB_BUFFER_FD;
- buf->data = nullptr;
buf->sz = sz;
}
@@ -922,7 +1083,9 @@
unique_fd fd(TEMP_FAILURE_RETRY(open(fname, O_RDONLY | O_BINARY)));
if (fd == -1) {
- return false;
+ auto path = find_item_given_name(fname);
+ fd = unique_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_BINARY)));
+ if (fd == -1) return false;
}
struct stat s;
@@ -996,6 +1159,8 @@
fb->GetVar("partition-type:vbmeta_b", &partition_type) == fastboot::SUCCESS;
}
+// Note: this only works in userspace fastboot. In the bootloader, use
+// should_flash_in_userspace().
static bool is_logical(const std::string& partition) {
std::string value;
return fb->GetVar("is-logical:" + partition, &value) == fastboot::SUCCESS && value == "yes";
@@ -1078,9 +1243,16 @@
lseek(buf->fd.get(), 0, SEEK_SET);
}
-static void flash_buf(const std::string& partition, struct fastboot_buffer* buf) {
- sparse_file** s;
+static void flash_partition_files(const std::string& partition,
+ const std::vector<SparsePtr>& files) {
+ for (size_t i = 0; i < files.size(); i++) {
+ sparse_file* s = files[i].get();
+ int64_t sz = sparse_file_len(s, true, false);
+ fb->FlashPartition(partition, s, sz, i + 1, files.size());
+ }
+}
+static void flash_buf(const std::string& partition, struct fastboot_buffer* buf) {
if (partition == "boot" || partition == "boot_a" || partition == "boot_b" ||
partition == "init_boot" || partition == "init_boot_a" || partition == "init_boot_b" ||
partition == "recovery" || partition == "recovery_a" || partition == "recovery_b") {
@@ -1103,18 +1275,7 @@
switch (buf->type) {
case FB_BUFFER_SPARSE: {
- std::vector<std::pair<sparse_file*, int64_t>> sparse_files;
- s = reinterpret_cast<sparse_file**>(buf->data);
- while (*s) {
- int64_t sz = sparse_file_len(*s, true, false);
- sparse_files.emplace_back(*s, sz);
- ++s;
- }
-
- for (size_t i = 0; i < sparse_files.size(); ++i) {
- const auto& pair = sparse_files[i];
- fb->FlashPartition(partition, pair.first, pair.second, i + 1, sparse_files.size());
- }
+ flash_partition_files(partition, buf->files);
break;
}
case FB_BUFFER_FD:
@@ -1242,9 +1403,8 @@
* partition names. If force_slot is true, it will fail if a slot is specified, and the given
* partition does not support slots.
*/
-static void do_for_partitions(const std::string& part, const std::string& slot,
- const std::function<void(const std::string&)>& func,
- bool force_slot) {
+void do_for_partitions(const std::string& part, const std::string& slot,
+ const std::function<void(const std::string&)>& func, bool force_slot) {
std::string has_slot;
// |part| can be vendor_boot:default. Query has-slot on the first token only.
auto part_tokens = android::base::Split(part, ":");
@@ -1340,7 +1500,7 @@
return partition;
}
-static void do_flash(const char* pname, const char* fname) {
+void do_flash(const char* pname, const char* fname) {
verbose("Do flash %s %s", pname, fname);
struct fastboot_buffer buf;
@@ -1369,19 +1529,19 @@
}
}
-static bool is_userspace_fastboot() {
+bool is_userspace_fastboot() {
std::string value;
return fb->GetVar("is-userspace", &value) == fastboot::SUCCESS && value == "yes";
}
-static void reboot_to_userspace_fastboot() {
+void reboot_to_userspace_fastboot() {
fb->RebootTo("fastboot");
auto* old_transport = fb->set_transport(nullptr);
delete old_transport;
// Give the current connection time to close.
- std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+ std::this_thread::sleep_for(std::chrono::seconds(1));
fb->set_transport(open_device());
@@ -1402,45 +1562,34 @@
}
}
-class ImageSource {
- public:
- virtual ~ImageSource(){};
- virtual bool ReadFile(const std::string& name, std::vector<char>* out) const = 0;
- virtual unique_fd OpenFile(const std::string& name) const = 0;
-};
-
class FlashAllTool {
public:
- FlashAllTool(const ImageSource& source, const std::string& slot_override, bool skip_secondary,
- bool wipe, bool force_flash);
+ FlashAllTool(FlashingPlan* fp);
void Flash();
private:
void CheckRequirements();
- void DetermineSecondarySlot();
+ void DetermineSlot();
void CollectImages();
void FlashImages(const std::vector<std::pair<const Image*, std::string>>& images);
void FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf);
void UpdateSuperPartition();
+ bool OptimizedFlashSuper();
- const ImageSource& source_;
- std::string slot_override_;
- bool skip_secondary_;
- bool wipe_;
- bool force_flash_;
- std::string secondary_slot_;
- std::vector<std::pair<const Image*, std::string>> boot_images_;
- std::vector<std::pair<const Image*, std::string>> os_images_;
+ // If the image uses the default slot, or the user specified "all", then
+ // the paired string will be empty. If the image requests a specific slot
+ // (for example, system_other) it is specified instead.
+ using ImageEntry = std::pair<const Image*, std::string>;
+
+ std::string GetPartitionName(const ImageEntry& entry);
+
+ std::vector<ImageEntry> boot_images_;
+ std::vector<ImageEntry> os_images_;
+ FlashingPlan* fp_;
};
-FlashAllTool::FlashAllTool(const ImageSource& source, const std::string& slot_override,
- bool skip_secondary, bool wipe, bool force_flash)
- : source_(source),
- slot_override_(slot_override),
- skip_secondary_(skip_secondary),
- wipe_(wipe),
- force_flash_(force_flash) {}
+FlashAllTool::FlashAllTool(FlashingPlan* fp) : fp_(fp) {}
void FlashAllTool::Flash() {
DumpInfo();
@@ -1448,13 +1597,13 @@
// Change the slot first, so we boot into the correct recovery image when
// using fastbootd.
- if (slot_override_ == "all") {
+ if (fp_->slot == "all") {
set_active("a");
} else {
- set_active(slot_override_);
+ set_active(fp_->slot);
}
- DetermineSecondarySlot();
+ DetermineSlot();
CollectImages();
CancelSnapshotIfNeeded();
@@ -1463,57 +1612,131 @@
// or in bootloader fastboot.
FlashImages(boot_images_);
- // Sync the super partition. This will reboot to userspace fastboot if needed.
- UpdateSuperPartition();
+ if (!OptimizedFlashSuper()) {
+ // Sync the super partition. This will reboot to userspace fastboot if needed.
+ UpdateSuperPartition();
- // Resize any logical partition to 0, so each partition is reset to 0
- // extents, and will achieve more optimal allocation.
- for (const auto& [image, slot] : os_images_) {
- auto resize_partition = [](const std::string& partition) -> void {
- if (is_logical(partition)) {
- fb->ResizePartition(partition, "0");
- }
- };
- do_for_partitions(image->part_name, slot, resize_partition, false);
+ // Resize any logical partition to 0, so each partition is reset to 0
+ // extents, and will achieve more optimal allocation.
+ for (const auto& [image, slot] : os_images_) {
+ auto resize_partition = [](const std::string& partition) -> void {
+ if (is_logical(partition)) {
+ fb->ResizePartition(partition, "0");
+ }
+ };
+ do_for_partitions(image->part_name, slot, resize_partition, false);
+ }
}
// Flash OS images, resizing logical partitions as needed.
FlashImages(os_images_);
}
-void FlashAllTool::CheckRequirements() {
- std::vector<char> contents;
- if (!source_.ReadFile("android-info.txt", &contents)) {
- die("could not read android-info.txt");
+bool FlashAllTool::OptimizedFlashSuper() {
+ if (!supports_AB()) {
+ LOG(VERBOSE) << "Cannot optimize flashing super on non-AB device";
+ return false;
}
- ::CheckRequirements({contents.data(), contents.size()}, force_flash_);
+ if (fp_->slot == "all") {
+ LOG(VERBOSE) << "Cannot optimize flashing super for all slots";
+ return false;
+ }
+
+ // Does this device use dynamic partitions at all?
+ unique_fd fd = fp_->source->OpenFile("super_empty.img");
+ if (fd < 0) {
+ LOG(VERBOSE) << "could not open super_empty.img";
+ return false;
+ }
+
+ // Try to find whether there is a super partition.
+ std::string super_name;
+ if (fb->GetVar("super-partition-name", &super_name) != fastboot::SUCCESS) {
+ super_name = "super";
+ }
+ std::string partition_size_str;
+ if (fb->GetVar("partition-size:" + super_name, &partition_size_str) != fastboot::SUCCESS) {
+ LOG(VERBOSE) << "Cannot optimize super flashing: could not determine super partition";
+ return false;
+ }
+
+ SuperFlashHelper helper(*fp_->source);
+ if (!helper.Open(fd)) {
+ return false;
+ }
+
+ for (const auto& entry : os_images_) {
+ auto partition = GetPartitionName(entry);
+ auto image = entry.first;
+
+ if (!helper.AddPartition(partition, image->img_name, image->optional_if_no_image)) {
+ return false;
+ }
+ }
+
+ auto s = helper.GetSparseLayout();
+ if (!s) {
+ return false;
+ }
+
+ std::vector<SparsePtr> files;
+ if (int limit = get_sparse_limit(sparse_file_len(s.get(), false, false))) {
+ files = resparse_file(s.get(), limit);
+ } else {
+ files.emplace_back(std::move(s));
+ }
+
+ // Send the data to the device.
+ flash_partition_files(super_name, files);
+
+ // Remove images that we already flashed, just in case we have non-dynamic OS images.
+ auto remove_if_callback = [&, this](const ImageEntry& entry) -> bool {
+ return helper.WillFlash(GetPartitionName(entry));
+ };
+ os_images_.erase(std::remove_if(os_images_.begin(), os_images_.end(), remove_if_callback),
+ os_images_.end());
+ return true;
}
-void FlashAllTool::DetermineSecondarySlot() {
- if (skip_secondary_) {
+void FlashAllTool::CheckRequirements() {
+ std::vector<char> contents;
+ if (!fp_->source->ReadFile("android-info.txt", &contents)) {
+ die("could not read android-info.txt");
+ }
+ ::CheckRequirements({contents.data(), contents.size()}, fp_->force_flash);
+}
+
+void FlashAllTool::DetermineSlot() {
+ if (fp_->slot.empty()) {
+ fp_->current_slot = get_current_slot();
+ } else {
+ fp_->current_slot = fp_->slot;
+ }
+
+ if (fp_->skip_secondary) {
return;
}
- if (slot_override_ != "" && slot_override_ != "all") {
- secondary_slot_ = get_other_slot(slot_override_);
+ if (fp_->slot != "" && fp_->slot != "all") {
+ fp_->secondary_slot = get_other_slot(fp_->slot);
} else {
- secondary_slot_ = get_other_slot();
+ fp_->secondary_slot = get_other_slot();
}
- if (secondary_slot_ == "") {
+ if (fp_->secondary_slot == "") {
if (supports_AB()) {
fprintf(stderr, "Warning: Could not determine slot for secondary images. Ignoring.\n");
}
- skip_secondary_ = true;
+ fp_->skip_secondary = true;
}
}
void FlashAllTool::CollectImages() {
for (size_t i = 0; i < images.size(); ++i) {
- std::string slot = slot_override_;
+ std::string slot = fp_->slot;
if (images[i].IsSecondary()) {
- if (skip_secondary_) {
+ if (fp_->skip_secondary) {
continue;
}
- slot = secondary_slot_;
+ slot = fp_->secondary_slot;
}
if (images[i].type == ImageType::BootCritical) {
boot_images_.emplace_back(&images[i], slot);
@@ -1526,7 +1749,7 @@
void FlashAllTool::FlashImages(const std::vector<std::pair<const Image*, std::string>>& images) {
for (const auto& [image, slot] : images) {
fastboot_buffer buf;
- unique_fd fd = source_.OpenFile(image->img_name);
+ unique_fd fd = fp_->source->OpenFile(image->img_name);
if (fd < 0 || !load_buf_fd(std::move(fd), &buf)) {
if (image->optional_if_no_image) {
continue;
@@ -1540,7 +1763,7 @@
void FlashAllTool::FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf) {
auto flash = [&, this](const std::string& partition_name) {
std::vector<char> signature_data;
- if (source_.ReadFile(image.sig_name, &signature_data)) {
+ if (fp_->source->ReadFile(image.sig_name, &signature_data)) {
fb->Download("signature", signature_data);
fb->RawCommand("signature", "installing signature");
}
@@ -1554,7 +1777,7 @@
}
void FlashAllTool::UpdateSuperPartition() {
- unique_fd fd = source_.OpenFile("super_empty.img");
+ unique_fd fd = fp_->source->OpenFile("super_empty.img");
if (fd < 0) {
return;
}
@@ -1569,7 +1792,7 @@
fb->Download(super_name, fd, get_file_size(fd));
std::string command = "update-super:" + super_name;
- if (wipe_) {
+ if (fp_->wants_wipe) {
command += ":wipe";
}
fb->RawCommand(command, "Updating super partition");
@@ -1588,6 +1811,20 @@
}
}
+std::string FlashAllTool::GetPartitionName(const ImageEntry& entry) {
+ auto slot = entry.second;
+ if (slot.empty()) {
+ slot = fp_->current_slot;
+ }
+ if (slot.empty()) {
+ return entry.first->part_name;
+ }
+ if (slot == "all") {
+ LOG(FATAL) << "Cannot retrieve a singular name when using all slots";
+ }
+ return entry.first->part_name + "_" + slot;
+}
+
class ZipImageSource final : public ImageSource {
public:
explicit ZipImageSource(ZipArchiveHandle zip) : zip_(zip) {}
@@ -1606,15 +1843,16 @@
return unzip_to_file(zip_, name.c_str());
}
-static void do_update(const char* filename, const std::string& slot_override, bool skip_secondary,
- bool force_flash) {
+static void do_update(const char* filename, FlashingPlan* fp) {
ZipArchiveHandle zip;
int error = OpenArchive(filename, &zip);
if (error != 0) {
die("failed to open zip file '%s': %s", filename, ErrorCodeString(error));
}
-
- FlashAllTool tool(ZipImageSource(zip), slot_override, skip_secondary, false, force_flash);
+ ZipImageSource zp = ZipImageSource(zip);
+ fp->source = &zp;
+ fp->wants_wipe = false;
+ FlashAllTool tool(fp);
tool.Flash();
CloseArchive(zip);
@@ -1639,9 +1877,10 @@
return unique_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_BINARY)));
}
-static void do_flashall(const std::string& slot_override, bool skip_secondary, bool wipe,
- bool force_flash) {
- FlashAllTool tool(LocalImageSource(), slot_override, skip_secondary, wipe, force_flash);
+static void do_flashall(FlashingPlan* fp) {
+ LocalImageSource s = LocalImageSource();
+ fp->source = &s;
+ FlashAllTool tool(fp);
tool.Flash();
}
@@ -1770,7 +2009,7 @@
}
}
-static bool should_flash_in_userspace(const std::string& partition_name) {
+bool should_flash_in_userspace(const std::string& partition_name) {
if (!get_android_product_out()) {
return false;
}
@@ -1782,20 +2021,7 @@
if (!metadata) {
return false;
}
- for (const auto& partition : metadata->partitions) {
- auto candidate = android::fs_mgr::GetPartitionName(partition);
- if (partition.attributes & LP_PARTITION_ATTR_SLOT_SUFFIXED) {
- // On retrofit devices, we don't know if, or whether, the A or B
- // slot has been flashed for dynamic partitions. Instead we add
- // both names to the list as a conservative guess.
- if (candidate + "_a" == partition_name || candidate + "_b" == partition_name) {
- return true;
- }
- } else if (candidate == partition_name) {
- return true;
- }
- }
- return false;
+ return should_flash_in_userspace(*metadata.get(), partition_name);
}
static bool wipe_super(const android::fs_mgr::LpMetadata& metadata, const std::string& slot,
@@ -1868,16 +2094,29 @@
}
}
+static void FastbootLogger(android::base::LogId /* id */, android::base::LogSeverity severity,
+ const char* /* tag */, const char* /* file */, unsigned int /* line */,
+ const char* message) {
+ switch (severity) {
+ case android::base::INFO:
+ fprintf(stdout, "%s\n", message);
+ break;
+ case android::base::ERROR:
+ fprintf(stderr, "%s\n", message);
+ break;
+ default:
+ verbose("%s\n", message);
+ }
+}
+
+static void FastbootAborter(const char* message) {
+ die("%s", message);
+}
+
int FastBootTool::Main(int argc, char* argv[]) {
- bool wants_wipe = false;
- bool wants_reboot = false;
- bool wants_reboot_bootloader = false;
- bool wants_reboot_recovery = false;
- bool wants_reboot_fastboot = false;
- bool skip_reboot = false;
- bool wants_set_active = false;
- bool skip_secondary = false;
- bool force_flash = false;
+ android::base::InitLogging(argv, FastbootLogger, FastbootAborter);
+ std::unique_ptr<FlashingPlan> fp = std::make_unique<FlashingPlan>();
+
unsigned fs_options = 0;
int longindex;
std::string slot_override;
@@ -1930,7 +2169,7 @@
} else if (name == "disable-verity") {
g_disable_verity = true;
} else if (name == "force") {
- force_flash = true;
+ fp->force_flash = true;
} else if (name == "fs-options") {
fs_options = ParseFsOption(optarg);
} else if (name == "header-version") {
@@ -1949,9 +2188,9 @@
} else if (name == "ramdisk-offset") {
g_boot_img_hdr.ramdisk_addr = strtoul(optarg, 0, 16);
} else if (name == "skip-reboot") {
- skip_reboot = true;
+ fp->skip_reboot = true;
} else if (name == "skip-secondary") {
- skip_secondary = true;
+ fp->skip_secondary = true;
} else if (name == "slot") {
slot_override = optarg;
} else if (name == "dtb-offset") {
@@ -1972,7 +2211,7 @@
} else {
switch (c) {
case 'a':
- wants_set_active = true;
+ fp->wants_set_active = true;
if (optarg) next_active = optarg;
break;
case 'h':
@@ -1992,7 +2231,7 @@
set_verbose();
break;
case 'w':
- wants_wipe = true;
+ fp->wants_wipe = true;
break;
case '?':
return 1;
@@ -2005,13 +2244,25 @@
argc -= optind;
argv += optind;
- if (argc == 0 && !wants_wipe && !wants_set_active) syntax_error("no command");
+ if (argc == 0 && !fp->wants_wipe && !fp->wants_set_active) syntax_error("no command");
if (argc > 0 && !strcmp(*argv, "devices")) {
list_devices();
return 0;
}
+ if (argc > 0 && !strcmp(*argv, "connect")) {
+ argc -= optind;
+ argv += optind;
+ return Connect(argc, argv);
+ }
+
+ if (argc > 0 && !strcmp(*argv, "disconnect")) {
+ argc -= optind;
+ argv += optind;
+ return Disconnect(argc, argv);
+ }
+
if (argc > 0 && !strcmp(*argv, "help")) {
return show_help();
}
@@ -2024,16 +2275,19 @@
.prolog = Status,
.epilog = Epilog,
.info = InfoMessage,
+ .text = TextMessage,
};
+
fastboot::FastBootDriver fastboot_driver(transport, driver_callbacks, false);
fb = &fastboot_driver;
+ fp->fb = &fastboot_driver;
const double start = now();
if (slot_override != "") slot_override = verify_slot(slot_override);
if (next_active != "") next_active = verify_slot(next_active, false);
- if (wants_set_active) {
+ if (fp->wants_set_active) {
if (next_active == "") {
if (slot_override == "") {
std::string current_slot;
@@ -2041,14 +2295,14 @@
if (current_slot[0] == '_') current_slot.erase(0, 1);
next_active = verify_slot(current_slot, false);
} else {
- wants_set_active = false;
+ fp->wants_set_active = false;
}
} else {
next_active = verify_slot(slot_override, false);
}
}
}
-
+ std::unique_ptr<Task> reboot_task = nullptr;
std::vector<std::string> args(argv, argv + argc);
while (!args.empty()) {
std::string command = next_arg(&args);
@@ -2100,30 +2354,19 @@
fb->Download("signature", data);
fb->RawCommand("signature", "installing signature");
} else if (command == FB_CMD_REBOOT) {
- wants_reboot = true;
-
if (args.size() == 1) {
- std::string what = next_arg(&args);
- if (what == "bootloader") {
- wants_reboot = false;
- wants_reboot_bootloader = true;
- } else if (what == "recovery") {
- wants_reboot = false;
- wants_reboot_recovery = true;
- } else if (what == "fastboot") {
- wants_reboot = false;
- wants_reboot_fastboot = true;
- } else {
- syntax_error("unknown reboot target %s", what.c_str());
- }
+ std::string reboot_target = next_arg(&args);
+ reboot_task = std::make_unique<RebootTask>(fp.get(), reboot_target);
+ } else {
+ reboot_task = std::make_unique<RebootTask>(fp.get());
}
if (!args.empty()) syntax_error("junk after reboot command");
} else if (command == FB_CMD_REBOOT_BOOTLOADER) {
- wants_reboot_bootloader = true;
+ reboot_task = std::make_unique<RebootTask>(fp.get(), "bootloader");
} else if (command == FB_CMD_REBOOT_RECOVERY) {
- wants_reboot_recovery = true;
+ reboot_task = std::make_unique<RebootTask>(fp.get(), "recovery");
} else if (command == FB_CMD_REBOOT_FASTBOOT) {
- wants_reboot_fastboot = true;
+ reboot_task = std::make_unique<RebootTask>(fp.get(), "fastboot");
} else if (command == FB_CMD_CONTINUE) {
fb->Continue();
} else if (command == FB_CMD_BOOT) {
@@ -2137,7 +2380,6 @@
fb->Boot();
} else if (command == FB_CMD_FLASH) {
std::string pname = next_arg(&args);
-
std::string fname;
if (!args.empty()) {
fname = next_arg(&args);
@@ -2145,21 +2387,8 @@
fname = find_item(pname);
}
if (fname.empty()) die("cannot determine image filename for '%s'", pname.c_str());
-
- auto flash = [&](const std::string& partition) {
- if (should_flash_in_userspace(partition) && !is_userspace_fastboot() &&
- !force_flash) {
- die("The partition you are trying to flash is dynamic, and "
- "should be flashed via fastbootd. Please run:\n"
- "\n"
- " fastboot reboot fastboot\n"
- "\n"
- "And try again. If you are intentionally trying to "
- "overwrite a fixed partition, use --force.");
- }
- do_flash(partition.c_str(), fname.c_str());
- };
- do_for_partitions(pname, slot_override, flash, true);
+ FlashTask task(slot_override, pname, fname);
+ task.Run();
} else if (command == "flash:raw") {
std::string partition = next_arg(&args);
std::string kernel = next_arg(&args);
@@ -2177,11 +2406,12 @@
if (slot_override == "all") {
fprintf(stderr,
"Warning: slot set to 'all'. Secondary slots will not be flashed.\n");
- do_flashall(slot_override, true, wants_wipe, force_flash);
+ fp->skip_secondary = true;
+ do_flashall(fp.get());
} else {
- do_flashall(slot_override, skip_secondary, wants_wipe, force_flash);
+ do_flashall(fp.get());
}
- wants_reboot = true;
+ reboot_task = std::make_unique<RebootTask>(fp.get());
} else if (command == "update") {
bool slot_all = (slot_override == "all");
if (slot_all) {
@@ -2192,8 +2422,8 @@
if (!args.empty()) {
filename = next_arg(&args);
}
- do_update(filename.c_str(), slot_override, skip_secondary || slot_all, force_flash);
- wants_reboot = true;
+ do_update(filename.c_str(), fp.get());
+ reboot_task = std::make_unique<RebootTask>(fp.get());
} else if (command == FB_CMD_SET_ACTIVE) {
std::string slot = verify_slot(next_arg(&args), false);
fb->SetActive(slot);
@@ -2265,9 +2495,8 @@
syntax_error("unknown command %s", command.c_str());
}
}
-
- if (wants_wipe) {
- if (force_flash) {
+ if (fp->wants_wipe) {
+ if (fp->force_flash) {
CancelSnapshotIfNeeded();
}
std::vector<std::string> partitions = {"userdata", "cache", "metadata"};
@@ -2281,22 +2510,12 @@
fb_perform_format(partition, 1, partition_type, "", fs_options);
}
}
- if (wants_set_active) {
+ if (fp->wants_set_active) {
fb->SetActive(next_active);
}
- if (wants_reboot && !skip_reboot) {
- fb->Reboot();
- fb->WaitForDisconnect();
- } else if (wants_reboot_bootloader) {
- fb->RebootTo("bootloader");
- fb->WaitForDisconnect();
- } else if (wants_reboot_recovery) {
- fb->RebootTo("recovery");
- fb->WaitForDisconnect();
- } else if (wants_reboot_fastboot) {
- reboot_to_userspace_fastboot();
+ if (reboot_task && !fp->skip_reboot) {
+ reboot_task->Run();
}
-
fprintf(stderr, "Finished. Total time: %.3fs\n", (now() - start));
auto* old_transport = fb->set_transport(nullptr);
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index c23793a..09666aa 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -25,9 +25,18 @@
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
+#pragma once
+
+#include <string>
+#include "fastboot_driver.h"
+#include "util.h"
#include <bootimg.h>
+#include "result.h"
+#include "socket.h"
+#include "util.h"
+
class FastBootTool {
public:
int Main(int argc, char* argv[]);
@@ -36,3 +45,59 @@
void ParseOsVersion(boot_img_hdr_v1*, const char*);
unsigned ParseFsOption(const char*);
};
+
+enum class ImageType {
+ // Must be flashed for device to boot into the kernel.
+ BootCritical,
+ // Normal partition to be flashed during "flashall".
+ Normal,
+ // Partition that is never flashed during "flashall".
+ Extra
+};
+
+struct Image {
+ std::string nickname;
+ std::string img_name;
+ std::string sig_name;
+ std::string part_name;
+ bool optional_if_no_image;
+ ImageType type;
+ bool IsSecondary() const { return nickname.empty(); }
+};
+
+using ImageEntry = std::pair<const Image*, std::string>;
+
+struct FlashingPlan {
+ // If the image uses the default slot, or the user specified "all", then
+ // the paired string will be empty. If the image requests a specific slot
+ // (for example, system_other) it is specified instead.
+ ImageSource* source;
+ bool wants_wipe = false;
+ bool skip_reboot = false;
+ bool wants_set_active = false;
+ bool skip_secondary = false;
+ bool force_flash = false;
+
+ std::string slot;
+ std::string current_slot;
+ std::string secondary_slot;
+ fastboot::FastBootDriver* fb;
+
+};
+
+bool should_flash_in_userspace(const std::string& partition_name);
+bool is_userspace_fastboot();
+void do_flash(const char* pname, const char* fname);
+void do_for_partitions(const std::string& part, const std::string& slot,
+ const std::function<void(const std::string&)>& func, bool force_slot);
+std::string find_item(const std::string& item);
+void reboot_to_userspace_fastboot();
+void syntax_error(const char* fmt, ...);
+
+struct NetworkSerial {
+ Socket::Protocol protocol;
+ std::string address;
+ int port;
+};
+
+Result<NetworkSerial, FastbootError> ParseNetworkSerial(const std::string& serial);
\ No newline at end of file
diff --git a/fastboot/fastboot_driver.cpp b/fastboot/fastboot_driver.cpp
index 99a4873..9770ab2 100644
--- a/fastboot/fastboot_driver.cpp
+++ b/fastboot/fastboot_driver.cpp
@@ -64,6 +64,7 @@
prolog_(std::move(driver_callbacks.prolog)),
epilog_(std::move(driver_callbacks.epilog)),
info_(std::move(driver_callbacks.info)),
+ text_(std::move(driver_callbacks.text)),
disable_checks_(no_checks) {}
FastBootDriver::~FastBootDriver() {
@@ -498,6 +499,10 @@
error_ = android::base::StringPrintf("remote: '%s'", status + strlen("FAIL"));
set_response(input.substr(strlen("FAIL")));
return DEVICE_FAIL;
+ } else if (android::base::StartsWith(input, "TEXT")) {
+ text_(input.substr(strlen("TEXT")));
+ // Reset timeout as many more TEXT may come
+ start = std::chrono::steady_clock::now();
} else if (android::base::StartsWith(input, "DATA")) {
std::string tmp = input.substr(strlen("DATA"));
uint32_t num = strtol(tmp.c_str(), 0, 16);
diff --git a/fastboot/fastboot_driver.h b/fastboot/fastboot_driver.h
index bccd668..f60c9f1 100644
--- a/fastboot/fastboot_driver.h
+++ b/fastboot/fastboot_driver.h
@@ -32,6 +32,7 @@
#include <string>
#include <vector>
+#include <android-base/endian.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
@@ -59,6 +60,7 @@
std::function<void(const std::string&)> prolog = [](const std::string&) {};
std::function<void(int)> epilog = [](int) {};
std::function<void(const std::string&)> info = [](const std::string&) {};
+ std::function<void(const std::string&)> text = [](const std::string&) {};
};
class FastBootDriver {
@@ -168,6 +170,7 @@
std::function<void(const std::string&)> prolog_;
std::function<void(int)> epilog_;
std::function<void(const std::string&)> info_;
+ std::function<void(const std::string&)> text_;
bool disable_checks_;
};
diff --git a/fastboot/fastboot_driver_test.cpp b/fastboot/fastboot_driver_test.cpp
new file mode 100644
index 0000000..6f6cf8c
--- /dev/null
+++ b/fastboot/fastboot_driver_test.cpp
@@ -0,0 +1,95 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "fastboot_driver.h"
+
+#include <optional>
+
+#include <gtest/gtest.h>
+#include "mock_transport.h"
+
+using namespace ::testing;
+using namespace fastboot;
+
+class DriverTest : public ::testing::Test {
+ protected:
+ InSequence s_;
+};
+
+TEST_F(DriverTest, GetVar) {
+ MockTransport transport;
+ FastBootDriver driver(&transport);
+
+ EXPECT_CALL(transport, Write(_, _))
+ .With(AllArgs(RawData("getvar:version")))
+ .WillOnce(ReturnArg<1>());
+ EXPECT_CALL(transport, Read(_, _)).WillOnce(Invoke(CopyData("OKAY0.4")));
+
+ std::string output;
+ ASSERT_EQ(driver.GetVar("version", &output), SUCCESS) << driver.Error();
+ ASSERT_EQ(output, "0.4");
+}
+
+TEST_F(DriverTest, InfoMessage) {
+ MockTransport transport;
+ FastBootDriver driver(&transport);
+
+ EXPECT_CALL(transport, Write(_, _))
+ .With(AllArgs(RawData("oem dmesg")))
+ .WillOnce(ReturnArg<1>());
+ EXPECT_CALL(transport, Read(_, _)).WillOnce(Invoke(CopyData("INFOthis is an info line")));
+ EXPECT_CALL(transport, Read(_, _)).WillOnce(Invoke(CopyData("OKAY")));
+
+ std::vector<std::string> info;
+ ASSERT_EQ(driver.RawCommand("oem dmesg", "", nullptr, &info), SUCCESS) << driver.Error();
+ ASSERT_EQ(info.size(), size_t(1));
+ ASSERT_EQ(info[0], "this is an info line");
+}
+
+TEST_F(DriverTest, TextMessage) {
+ MockTransport transport;
+ std::string text;
+
+ DriverCallbacks callbacks{[](const std::string&) {}, [](int) {}, [](const std::string&) {},
+ [&text](const std::string& extra_text) { text += extra_text; }};
+
+ FastBootDriver driver(&transport, callbacks);
+
+ EXPECT_CALL(transport, Write(_, _))
+ .With(AllArgs(RawData("oem trusty runtest trusty.hwaes.bench")))
+ .WillOnce(ReturnArg<1>());
+ EXPECT_CALL(transport, Read(_, _)).WillOnce(Invoke(CopyData("TEXTthis is a text line")));
+ EXPECT_CALL(transport, Read(_, _))
+ .WillOnce(Invoke(
+ CopyData("TEXT, albeit very long and split over multiple TEXT messages.")));
+ EXPECT_CALL(transport, Read(_, _))
+ .WillOnce(Invoke(CopyData("TEXT Indeed we can do that now with a TEXT message whenever "
+ "we feel like it.")));
+ EXPECT_CALL(transport, Read(_, _))
+ .WillOnce(Invoke(CopyData("TEXT Isn't that truly super cool?")));
+
+ EXPECT_CALL(transport, Read(_, _)).WillOnce(Invoke(CopyData("OKAY")));
+
+ std::vector<std::string> info;
+ ASSERT_EQ(driver.RawCommand("oem trusty runtest trusty.hwaes.bench", "", nullptr, &info),
+ SUCCESS)
+ << driver.Error();
+ ASSERT_EQ(text,
+ "this is a text line"
+ ", albeit very long and split over multiple TEXT messages."
+ " Indeed we can do that now with a TEXT message whenever we feel like it."
+ " Isn't that truly super cool?");
+}
diff --git a/fastboot/fastboot_test.cpp b/fastboot/fastboot_test.cpp
index 9c3ab6e..1863e95 100644
--- a/fastboot/fastboot_test.cpp
+++ b/fastboot/fastboot_test.cpp
@@ -16,6 +16,7 @@
#include "fastboot.h"
+#include <android-base/logging.h>
#include <gtest/gtest.h>
TEST(FastBoot, ParseOsPatchLevel) {
@@ -201,3 +202,59 @@
// No spaces allowed before between require-for-product and :.
ParseRequirementLineTestMalformed("require-for-product :");
}
+
+static void ParseNetworkSerialTest(const std::string& description, const std::string& serial,
+ const std::string& expected_address,
+ const Socket::Protocol expected_protocol,
+ const int expected_port) {
+ const Result<NetworkSerial, FastbootError> parsed = ParseNetworkSerial(serial);
+
+ ASSERT_RESULT_OK(parsed) << description;
+
+ const NetworkSerial network_serial = parsed.value();
+ EXPECT_EQ(network_serial.address, expected_address) << description;
+ EXPECT_EQ(network_serial.protocol, expected_protocol) << description;
+ EXPECT_EQ(network_serial.port, expected_port) << description;
+}
+
+static void ParseNetworkSerialNegativeTest(const std::string& description,
+ const std::string& serial,
+ const FastbootError::Type expected_error) {
+ const Result<NetworkSerial, FastbootError> parsed = ParseNetworkSerial(serial);
+
+ EXPECT_FALSE(parsed.ok()) << description;
+ EXPECT_EQ(parsed.error().code(), expected_error) << description;
+}
+
+TEST(FastBoot, ParseNetworkSerial) {
+ ParseNetworkSerialTest("tcp IPv4 parsed", "tcp:192.168.1.0", "192.168.1.0",
+ Socket::Protocol::kTcp, 5554);
+
+ ParseNetworkSerialTest("udp IPv4 parsed", "udp:192.168.1.0", "192.168.1.0",
+ Socket::Protocol::kUdp, 5554);
+
+ ParseNetworkSerialTest("port parsed", "udp:192.168.1.0:9999", "192.168.1.0",
+ Socket::Protocol::kUdp, 9999);
+
+ ParseNetworkSerialTest("IPv6 parsed", "tcp:2001:db8:3333:4444:5555:6666:7777:8888",
+ "2001:db8:3333:4444:5555:6666:7777:8888", Socket::Protocol::kTcp, 5554);
+
+ ParseNetworkSerialTest("empty IPv6 parsed", "tcp:::", "::", Socket::Protocol::kTcp, 5554);
+
+ ParseNetworkSerialNegativeTest("wrong prefix", "tcpa:192.168.1.0",
+ FastbootError::Type::NETWORK_SERIAL_WRONG_PREFIX);
+
+ ParseNetworkSerialNegativeTest("no prefix", "192.168.1.0",
+ FastbootError::Type::NETWORK_SERIAL_WRONG_PREFIX);
+
+ ParseNetworkSerialNegativeTest("wrong port", "tcp:192.168.1.0:-1",
+ FastbootError::Type::NETWORK_SERIAL_WRONG_ADDRESS);
+}
+
+int main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ android::base::InitLogging(argv);
+ android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/fastboot/filesystem.cpp b/fastboot/filesystem.cpp
new file mode 100644
index 0000000..94fde8e
--- /dev/null
+++ b/fastboot/filesystem.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef _WIN32
+#include <android-base/utf8.h>
+#include <direct.h>
+#include <shlobj.h>
+#else
+#include <pwd.h>
+#endif
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "filesystem.h"
+
+namespace {
+
+int LockFile(int fd) {
+#ifdef _WIN32
+ HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
+ OVERLAPPED overlapped = {};
+ const BOOL locked =
+ LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD, &overlapped);
+ return locked ? 0 : -1;
+#else
+ return flock(fd, LOCK_EX);
+#endif
+}
+
+} // namespace
+
+// inspired by adb implementation:
+// cs.android.com/android/platform/superproject/+/master:packages/modules/adb/adb_utils.cpp;l=275
+std::string GetHomeDirPath() {
+#ifdef _WIN32
+ WCHAR path[MAX_PATH];
+ const HRESULT hr = SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, path);
+ if (FAILED(hr)) {
+ return {};
+ }
+ std::string home_str;
+ if (!android::base::WideToUTF8(path, &home_str)) {
+ return {};
+ }
+ return home_str;
+#else
+ if (const char* const home = getenv("HOME")) {
+ return home;
+ }
+
+ struct passwd pwent;
+ struct passwd* result;
+ int pwent_max = sysconf(_SC_GETPW_R_SIZE_MAX);
+ if (pwent_max == -1) {
+ pwent_max = 16384;
+ }
+ std::vector<char> buf(pwent_max);
+ int rc = getpwuid_r(getuid(), &pwent, buf.data(), buf.size(), &result);
+ if (rc == 0 && result) {
+ return result->pw_dir;
+ }
+#endif
+
+ return {};
+}
+
+bool FileExists(const std::string& path) {
+ return access(path.c_str(), F_OK) == 0;
+}
+
+bool EnsureDirectoryExists(const std::string& directory_path) {
+ const int result =
+#ifdef _WIN32
+ _mkdir(directory_path.c_str());
+#else
+ mkdir(directory_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+#endif
+
+ return result == 0 || errno == EEXIST;
+}
+
+FileLock::FileLock(const std::string& path) : fd_(open(path.c_str(), O_CREAT | O_WRONLY, 0644)) {
+ if (LockFile(fd_.get()) != 0) {
+ LOG(FATAL) << "Failed to acquire a lock on " << path;
+ }
+}
\ No newline at end of file
diff --git a/fastboot/filesystem.h b/fastboot/filesystem.h
new file mode 100644
index 0000000..5f41fbc
--- /dev/null
+++ b/fastboot/filesystem.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/unique_fd.h>
+
+#include <string>
+
+using android::base::unique_fd;
+
+// TODO(b/175635923): remove after enabling libc++fs for windows
+const char kPathSeparator =
+#ifdef _WIN32
+ '\\';
+#else
+ '/';
+#endif
+
+std::string GetHomeDirPath();
+bool EnsureDirectoryExists(const std::string& directory_path);
+
+class FileLock {
+ public:
+ FileLock() = delete;
+ FileLock(const std::string& path);
+
+ private:
+ unique_fd fd_;
+};
\ No newline at end of file
diff --git a/fastboot/mock_transport.h b/fastboot/mock_transport.h
new file mode 100644
index 0000000..cc3840c
--- /dev/null
+++ b/fastboot/mock_transport.h
@@ -0,0 +1,67 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#pragma once
+
+#include <string.h>
+
+#include <algorithm>
+#include <string_view>
+
+#include <gmock/gmock.h>
+#include "transport.h"
+
+class MockTransport : public Transport {
+ public:
+ MOCK_METHOD(ssize_t, Read, (void* data, size_t len), (override));
+ MOCK_METHOD(ssize_t, Write, (const void* data, size_t len), (override));
+ MOCK_METHOD(int, Close, (), (override));
+ MOCK_METHOD(int, Reset, (), (override));
+};
+
+class RawDataMatcher {
+ public:
+ explicit RawDataMatcher(const char* data) : data_(data) {}
+ explicit RawDataMatcher(std::string_view data) : data_(data) {}
+
+ bool MatchAndExplain(std::tuple<const void*, size_t> args,
+ ::testing::MatchResultListener*) const {
+ const void* expected_data = std::get<0>(args);
+ size_t expected_len = std::get<1>(args);
+ if (expected_len != data_.size()) {
+ return false;
+ }
+ return memcmp(expected_data, data_.data(), expected_len) == 0;
+ }
+ void DescribeTo(std::ostream* os) const { *os << "raw data is"; }
+ void DescribeNegationTo(std::ostream* os) const { *os << "raw data is not"; }
+
+ private:
+ std::string_view data_;
+};
+
+template <typename T>
+static inline ::testing::PolymorphicMatcher<RawDataMatcher> RawData(T data) {
+ return ::testing::MakePolymorphicMatcher(RawDataMatcher(data));
+}
+
+static inline auto CopyData(const char* source) {
+ return [source](void* buffer, size_t size) -> ssize_t {
+ size_t to_copy = std::min(size, strlen(source));
+ memcpy(buffer, source, to_copy);
+ return to_copy;
+ };
+};
diff --git a/fastboot/result.h b/fastboot/result.h
new file mode 100644
index 0000000..dfa5e10
--- /dev/null
+++ b/fastboot/result.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <android-base/result.h>
+
+#include "util.h"
+
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+using android::base::ResultError;
+
+class FastbootError {
+ public:
+ enum Type { NETWORK_SERIAL_WRONG_PREFIX = 1, NETWORK_SERIAL_WRONG_ADDRESS = 2 };
+
+ FastbootError(Type&& type) : type_(std::forward<Type>(type)) {}
+
+ Type value() const { return type_; }
+ operator Type() const { return value(); }
+ std::string print() const { return ""; }
+
+ private:
+ Type type_;
+};
+
+template <typename T, typename U>
+inline T Expect(Result<T, U> r) {
+ if (r.ok()) {
+ return r.value();
+ }
+
+ die(r.error().message());
+
+ return r.value();
+}
\ No newline at end of file
diff --git a/fastboot/storage.cpp b/fastboot/storage.cpp
new file mode 100644
index 0000000..d6e00cf
--- /dev/null
+++ b/fastboot/storage.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
+#include <fstream>
+
+#include "storage.h"
+#include "util.h"
+
+ConnectedDevicesStorage::ConnectedDevicesStorage() {
+ const std::string home_path = GetHomeDirPath();
+ if (home_path.empty()) {
+ return;
+ }
+
+ const std::string home_fastboot_path = home_path + kPathSeparator + ".fastboot";
+
+ if (!EnsureDirectoryExists(home_fastboot_path)) {
+ LOG(FATAL) << "Cannot create directory: " << home_fastboot_path;
+ }
+
+ // We're using a separate file for locking because the Windows LockFileEx does not
+ // permit opening a file stream for the locked file, even within the same process. So,
+ // we have to use fd or handle API to manipulate the storage files, which makes it
+ // nearly impossible to fully rewrite a file content without having to recreate it.
+ // Unfortunately, this is not an option during holding a lock.
+ devices_path_ = home_fastboot_path + kPathSeparator + "devices";
+ devices_lock_path_ = home_fastboot_path + kPathSeparator + "devices.lock";
+}
+
+void ConnectedDevicesStorage::WriteDevices(const FileLock&, const std::set<std::string>& devices) {
+ std::ofstream devices_stream(devices_path_);
+ std::copy(devices.begin(), devices.end(),
+ std::ostream_iterator<std::string>(devices_stream, "\n"));
+}
+
+std::set<std::string> ConnectedDevicesStorage::ReadDevices(const FileLock&) {
+ std::ifstream devices_stream(devices_path_);
+ std::istream_iterator<std::string> start(devices_stream), end;
+ std::set<std::string> devices(start, end);
+ return devices;
+}
+
+void ConnectedDevicesStorage::Clear(const FileLock&) {
+ if (!android::base::RemoveFileIfExists(devices_path_)) {
+ LOG(FATAL) << "Failed to clear connected device list: " << devices_path_;
+ }
+}
+
+FileLock ConnectedDevicesStorage::Lock() const {
+ return FileLock(devices_lock_path_);
+}
\ No newline at end of file
diff --git a/fastboot/storage.h b/fastboot/storage.h
new file mode 100644
index 0000000..0cc3d86
--- /dev/null
+++ b/fastboot/storage.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <set>
+#include <string>
+
+#include "filesystem.h"
+
+class ConnectedDevicesStorage {
+ public:
+ ConnectedDevicesStorage();
+ void WriteDevices(const FileLock&, const std::set<std::string>& devices);
+ std::set<std::string> ReadDevices(const FileLock&);
+ void Clear(const FileLock&);
+
+ FileLock Lock() const;
+
+ private:
+ std::string devices_path_;
+ std::string devices_lock_path_;
+};
\ No newline at end of file
diff --git a/fastboot/super_flash_helper.cpp b/fastboot/super_flash_helper.cpp
new file mode 100644
index 0000000..b617ce8
--- /dev/null
+++ b/fastboot/super_flash_helper.cpp
@@ -0,0 +1,125 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "super_flash_helper.h"
+
+#include <android-base/logging.h>
+
+#include "util.h"
+
+using android::base::borrowed_fd;
+using android::base::unique_fd;
+using android::fs_mgr::SuperImageExtent;
+
+SuperFlashHelper::SuperFlashHelper(const ImageSource& source) : source_(source) {}
+
+bool SuperFlashHelper::Open(borrowed_fd fd) {
+ if (!builder_.Open(fd)) {
+ LOG(VERBOSE) << "device does not support optimized super flashing";
+ return false;
+ }
+
+ base_metadata_ = builder_.Export();
+ return !!base_metadata_;
+}
+
+bool SuperFlashHelper::IncludeInSuper(const std::string& partition) {
+ return should_flash_in_userspace(*base_metadata_.get(), partition);
+}
+
+bool SuperFlashHelper::AddPartition(const std::string& partition, const std::string& image_name,
+ bool optional) {
+ if (!IncludeInSuper(partition)) {
+ return true;
+ }
+ auto iter = image_fds_.find(image_name);
+ if (iter == image_fds_.end()) {
+ unique_fd fd = source_.OpenFile(image_name);
+ if (fd < 0) {
+ if (!optional) {
+ LOG(VERBOSE) << "could not find partition image: " << image_name;
+ return false;
+ }
+ return true;
+ }
+ if (is_sparse_file(fd)) {
+ LOG(VERBOSE) << "cannot optimize dynamic partitions with sparse images";
+ return false;
+ }
+ iter = image_fds_.emplace(image_name, std::move(fd)).first;
+ }
+
+ if (!builder_.AddPartition(partition, image_name, get_file_size(iter->second))) {
+ return false;
+ }
+
+ will_flash_.emplace(partition);
+ return true;
+}
+
+SparsePtr SuperFlashHelper::GetSparseLayout() {
+ // Cache extents since the sparse ptr depends on data pointers.
+ if (extents_.empty()) {
+ extents_ = builder_.GetImageLayout();
+ if (extents_.empty()) {
+ LOG(VERBOSE) << "device does not support optimized super flashing";
+ return {nullptr, nullptr};
+ }
+ }
+
+ unsigned int block_size = base_metadata_->geometry.logical_block_size;
+ int64_t flashed_size = extents_.back().offset + extents_.back().size;
+ SparsePtr s(sparse_file_new(block_size, flashed_size), sparse_file_destroy);
+
+ for (const auto& extent : extents_) {
+ if (extent.offset / block_size > UINT_MAX) {
+ // Super image is too big to send via sparse files (>8TB).
+ LOG(VERBOSE) << "super image is too big to flash";
+ return {nullptr, nullptr};
+ }
+ unsigned int block = extent.offset / block_size;
+
+ int rv = 0;
+ switch (extent.type) {
+ case SuperImageExtent::Type::DONTCARE:
+ break;
+ case SuperImageExtent::Type::ZERO:
+ rv = sparse_file_add_fill(s.get(), 0, extent.size, block);
+ break;
+ case SuperImageExtent::Type::DATA:
+ rv = sparse_file_add_data(s.get(), extent.blob->data(), extent.size, block);
+ break;
+ case SuperImageExtent::Type::PARTITION: {
+ auto iter = image_fds_.find(extent.image_name);
+ if (iter == image_fds_.end()) {
+ LOG(FATAL) << "image added but not found: " << extent.image_name;
+ return {nullptr, nullptr};
+ }
+ rv = sparse_file_add_fd(s.get(), iter->second.get(), extent.image_offset,
+ extent.size, block);
+ break;
+ }
+ default:
+ LOG(VERBOSE) << "unrecognized extent type in super image layout";
+ return {nullptr, nullptr};
+ }
+ if (rv) {
+ LOG(VERBOSE) << "sparse failure building super image layout";
+ return {nullptr, nullptr};
+ }
+ }
+ return s;
+}
diff --git a/fastboot/super_flash_helper.h b/fastboot/super_flash_helper.h
new file mode 100644
index 0000000..29c15d0
--- /dev/null
+++ b/fastboot/super_flash_helper.h
@@ -0,0 +1,56 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <android-base/unique_fd.h>
+#include <liblp/liblp.h>
+#include <liblp/super_layout_builder.h>
+
+#include "util.h"
+
+class SuperFlashHelper final {
+ public:
+ explicit SuperFlashHelper(const ImageSource& source);
+
+ bool Open(android::base::borrowed_fd fd);
+ bool IncludeInSuper(const std::string& partition);
+ bool AddPartition(const std::string& partition, const std::string& image_name, bool optional);
+
+ // Note: the SparsePtr if non-null should not outlive SuperFlashHelper, since
+ // it depends on open fds and data pointers.
+ SparsePtr GetSparseLayout();
+
+ bool WillFlash(const std::string& partition) const {
+ return will_flash_.find(partition) != will_flash_.end();
+ }
+
+ private:
+ const ImageSource& source_;
+ android::fs_mgr::SuperLayoutBuilder builder_;
+ std::unique_ptr<android::fs_mgr::LpMetadata> base_metadata_;
+ std::vector<android::fs_mgr::SuperImageExtent> extents_;
+
+ // Cache open image fds. This keeps them alive while we flash the sparse
+ // file.
+ std::unordered_map<std::string, android::base::unique_fd> image_fds_;
+ std::unordered_set<std::string> will_flash_;
+};
diff --git a/fastboot/super_flash_helper_test.cpp b/fastboot/super_flash_helper_test.cpp
new file mode 100644
index 0000000..82b8aa5
--- /dev/null
+++ b/fastboot/super_flash_helper_test.cpp
@@ -0,0 +1,88 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "super_flash_helper.h"
+
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+#include <sparse/sparse.h>
+
+using android::base::unique_fd;
+
+unique_fd OpenTestFile(const std::string& file, int flags) {
+ std::string path = "testdata/" + file;
+
+ unique_fd fd(open(path.c_str(), flags));
+ if (fd >= 0) {
+ return fd;
+ }
+
+ path = android::base::GetExecutableDirectory() + "/" + path;
+ return unique_fd{open(path.c_str(), flags)};
+}
+
+class TestImageSource final : public ImageSource {
+ public:
+ bool ReadFile(const std::string&, std::vector<char>*) const override {
+ // Not used here.
+ return false;
+ }
+ unique_fd OpenFile(const std::string& name) const override {
+ return OpenTestFile(name, O_RDONLY | O_CLOEXEC);
+ }
+};
+
+TEST(SuperFlashHelper, ImageEquality) {
+ auto super_empty_fd = OpenTestFile("super_empty.img", O_RDONLY);
+ ASSERT_GE(super_empty_fd, 0);
+
+ TestImageSource source;
+ SuperFlashHelper helper(source);
+ ASSERT_TRUE(helper.Open(super_empty_fd));
+ ASSERT_TRUE(helper.AddPartition("system_a", "system.img", false));
+
+ auto sparse_file = helper.GetSparseLayout();
+ ASSERT_NE(sparse_file, nullptr);
+
+ TemporaryFile fb_super;
+ ASSERT_GE(fb_super.fd, 0);
+ ASSERT_EQ(sparse_file_write(sparse_file.get(), fb_super.fd, false, false, false), 0);
+
+ auto real_super_fd = OpenTestFile("super.img", O_RDONLY);
+ ASSERT_GE(real_super_fd, 0);
+
+ std::string expected(get_file_size(real_super_fd), '\0');
+ ASSERT_FALSE(expected.empty());
+ ASSERT_TRUE(android::base::ReadFully(real_super_fd, expected.data(), expected.size()));
+
+ std::string actual(get_file_size(fb_super.fd), '\0');
+ ASSERT_FALSE(actual.empty());
+ ASSERT_EQ(lseek(fb_super.fd, 0, SEEK_SET), 0);
+ ASSERT_TRUE(android::base::ReadFully(fb_super.fd, actual.data(), actual.size()));
+
+ // The helper doesn't add any extra zeroes to the image, whereas lpmake does, to
+ // pad to the entire super size.
+ ASSERT_LE(actual.size(), expected.size());
+ for (size_t i = 0; i < actual.size(); i++) {
+ ASSERT_EQ(actual[i], expected[i]) << "byte mismatch at position " << i;
+ }
+ for (size_t i = actual.size(); i < expected.size(); i++) {
+ ASSERT_EQ(expected[i], 0) << "byte mismatch at position " << i;
+ }
+}
diff --git a/fastboot/task.cpp b/fastboot/task.cpp
new file mode 100644
index 0000000..a4aa637
--- /dev/null
+++ b/fastboot/task.cpp
@@ -0,0 +1,68 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#include "task.h"
+#include "fastboot.h"
+#include "util.h"
+
+#include "fastboot.h"
+#include "util.h"
+
+FlashTask::FlashTask(const std::string& _slot, const std::string& _pname)
+ : pname_(_pname), fname_(find_item(_pname)), slot_(_slot) {
+ if (fname_.empty()) die("cannot determine image filename for '%s'", pname_.c_str());
+}
+FlashTask::FlashTask(const std::string& _slot, const std::string& _pname, const std::string& _fname)
+ : pname_(_pname), fname_(_fname), slot_(_slot) {}
+
+void FlashTask::Run() {
+ auto flash = [&](const std::string& partition) {
+ if (should_flash_in_userspace(partition) && !is_userspace_fastboot()) {
+ die("The partition you are trying to flash is dynamic, and "
+ "should be flashed via fastbootd. Please run:\n"
+ "\n"
+ " fastboot reboot fastboot\n"
+ "\n"
+ "And try again. If you are intentionally trying to "
+ "overwrite a fixed partition, use --force.");
+ }
+ do_flash(partition.c_str(), fname_.c_str());
+ };
+ do_for_partitions(pname_, slot_, flash, true);
+}
+
+RebootTask::RebootTask(FlashingPlan* _fp) : fp_(_fp){};
+RebootTask::RebootTask(FlashingPlan* _fp, const std::string& _reboot_target)
+ : reboot_target_(_reboot_target), fp_(_fp){};
+
+void RebootTask::Run() {
+ if ((reboot_target_ == "userspace" || reboot_target_ == "fastboot")) {
+ if (!is_userspace_fastboot()) {
+ reboot_to_userspace_fastboot();
+ fp_->fb->WaitForDisconnect();
+ }
+ } else if (reboot_target_ == "recovery") {
+ fp_->fb->RebootTo("recovery");
+ fp_->fb->WaitForDisconnect();
+ } else if (reboot_target_ == "bootloader") {
+ fp_->fb->RebootTo("bootloader");
+ fp_->fb->WaitForDisconnect();
+ } else if (reboot_target_ == "") {
+ fp_->fb->Reboot();
+ fp_->fb->WaitForDisconnect();
+ } else {
+ syntax_error("unknown reboot target %s", reboot_target_.c_str());
+ }
+}
diff --git a/fastboot/task.h b/fastboot/task.h
new file mode 100644
index 0000000..d8b9e21
--- /dev/null
+++ b/fastboot/task.h
@@ -0,0 +1,55 @@
+//
+// 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 <sstream>
+#include <string>
+
+#include "fastboot.h"
+#include "fastboot_driver.h"
+
+class Task {
+ public:
+ Task() = default;
+ virtual void Run() = 0;
+ virtual ~Task() = default;
+};
+
+class FlashTask : public Task {
+ public:
+ FlashTask(const std::string& _slot, const std::string& _pname);
+ FlashTask(const std::string& _slot, const std::string& _pname, const std::string& _fname);
+
+ void Run() override;
+ ~FlashTask() {}
+
+ private:
+ const std::string pname_;
+ const std::string fname_;
+ const std::string slot_;
+};
+
+class RebootTask : public Task {
+ public:
+ RebootTask(FlashingPlan* _fp);
+ RebootTask(FlashingPlan* _fp, const std::string& _reboot_target);
+ void Run() override;
+ ~RebootTask() {}
+
+ private:
+ const std::string reboot_target_ = "";
+ FlashingPlan* fp_;
+};
\ No newline at end of file
diff --git a/fastboot/testdata/make_super_images.sh b/fastboot/testdata/make_super_images.sh
new file mode 100644
index 0000000..71c54ee
--- /dev/null
+++ b/fastboot/testdata/make_super_images.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+set -e
+set -x
+
+lpmake \
+ --device-size=auto \
+ --metadata-size=4096 \
+ --metadata-slots=3 \
+ --partition=system_a:readonly:0 \
+ --alignment=16384 \
+ --output=super_empty.img
+
+lpmake \
+ --device-size=auto \
+ --metadata-size=4096 \
+ --metadata-slots=3 \
+ --partition=system_a:readonly:0 \
+ --alignment=16384 \
+ --output=super.img \
+ --image=system_a=system.img
diff --git a/fastboot/testdata/super.img b/fastboot/testdata/super.img
new file mode 100644
index 0000000..be13d36
--- /dev/null
+++ b/fastboot/testdata/super.img
Binary files differ
diff --git a/fastboot/testdata/super_empty.img b/fastboot/testdata/super_empty.img
new file mode 100644
index 0000000..4b25869
--- /dev/null
+++ b/fastboot/testdata/super_empty.img
Binary files differ
diff --git a/fastboot/testdata/system.img b/fastboot/testdata/system.img
new file mode 100644
index 0000000..b360610
--- /dev/null
+++ b/fastboot/testdata/system.img
Binary files differ
diff --git a/fastboot/usb.h b/fastboot/usb.h
index e5f56e2..69581ab 100644
--- a/fastboot/usb.h
+++ b/fastboot/usb.h
@@ -28,6 +28,8 @@
#pragma once
+#include <functional>
+
#include "transport.h"
struct usb_ifc_info {
@@ -61,7 +63,7 @@
virtual int Reset() = 0;
};
-typedef int (*ifc_match_func)(usb_ifc_info *ifc);
+typedef std::function<int(usb_ifc_info*)> ifc_match_func;
// 0 is non blocking
UsbTransport* usb_open(ifc_match_func callback, uint32_t timeout_ms = 0);
diff --git a/fastboot/usb_osx.cpp b/fastboot/usb_osx.cpp
index a4b9307..5b9e5c8 100644
--- a/fastboot/usb_osx.cpp
+++ b/fastboot/usb_osx.cpp
@@ -456,8 +456,7 @@
}
if (h.success) {
- handle->reset(new usb_handle);
- memcpy(handle->get(), &h, sizeof(usb_handle));
+ handle->reset(new usb_handle(h));
ret = 0;
break;
}
diff --git a/fastboot/util.cpp b/fastboot/util.cpp
index 900d6ea..ded54a5 100644
--- a/fastboot/util.cpp
+++ b/fastboot/util.cpp
@@ -30,11 +30,13 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-
+#include <sys/stat.h>
#include <sys/time.h>
#include "util.h"
+using android::base::borrowed_fd;
+
static bool g_verbose = false;
double now() {
@@ -73,3 +75,34 @@
}
fprintf(stderr, "\n");
}
+
+bool should_flash_in_userspace(const android::fs_mgr::LpMetadata& metadata,
+ const std::string& partition_name) {
+ for (const auto& partition : metadata.partitions) {
+ auto candidate = android::fs_mgr::GetPartitionName(partition);
+ if (partition.attributes & LP_PARTITION_ATTR_SLOT_SUFFIXED) {
+ // On retrofit devices, we don't know if, or whether, the A or B
+ // slot has been flashed for dynamic partitions. Instead we add
+ // both names to the list as a conservative guess.
+ if (candidate + "_a" == partition_name || candidate + "_b" == partition_name) {
+ return true;
+ }
+ } else if (candidate == partition_name) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool is_sparse_file(borrowed_fd fd) {
+ SparsePtr s(sparse_file_import(fd.get(), false, false), sparse_file_destroy);
+ return !!s;
+}
+
+int64_t get_file_size(borrowed_fd fd) {
+ struct stat sb;
+ if (fstat(fd.get(), &sb) == -1) {
+ die("could not get file size");
+ }
+ return sb.st_size;
+}
diff --git a/fastboot/util.h b/fastboot/util.h
index c719df2..290d0d5 100644
--- a/fastboot/util.h
+++ b/fastboot/util.h
@@ -4,8 +4,14 @@
#include <stdlib.h>
#include <string>
+#include <vector>
+#include <android-base/unique_fd.h>
#include <bootimg.h>
+#include <liblp/liblp.h>
+#include <sparse/sparse.h>
+
+using SparsePtr = std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)>;
/* util stuff */
double now();
@@ -19,3 +25,15 @@
void verbose(const char* fmt, ...) __attribute__((__format__(__printf__, 1, 2)));
void die(const std::string& str) __attribute__((__noreturn__));
+
+bool should_flash_in_userspace(const android::fs_mgr::LpMetadata& metadata,
+ const std::string& partition_name);
+bool is_sparse_file(android::base::borrowed_fd fd);
+int64_t get_file_size(android::base::borrowed_fd fd);
+
+class ImageSource {
+ public:
+ virtual ~ImageSource(){};
+ virtual bool ReadFile(const std::string& name, std::vector<char>* out) const = 0;
+ virtual android::base::unique_fd OpenFile(const std::string& name) const = 0;
+};
diff --git a/fastboot/vendor_boot_img_utils_test.cpp b/fastboot/vendor_boot_img_utils_test.cpp
index 1563b89..467c6e9 100644
--- a/fastboot/vendor_boot_img_utils_test.cpp
+++ b/fastboot/vendor_boot_img_utils_test.cpp
@@ -73,8 +73,8 @@
// Seek to beginning then read the whole file.
Result<std::string> ReadStartOfFdToString(borrowed_fd fd, std::filesystem::path path) {
- if (lseek64(fd.get(), 0, SEEK_SET) != 0)
- return ErrnoError() << "lseek64(" << path << ", 0, SEEK_SET)";
+ if (lseek(fd.get(), 0, SEEK_SET) != 0)
+ return ErrnoError() << "lseek(" << path << ", 0, SEEK_SET)";
std::string content;
if (!android::base::ReadFdToString(fd, &content)) return ErrnoError() << "read(" << path << ")";
return content;
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 1c1ab48..742cdfa 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -1176,6 +1176,10 @@
return false;
}
+ // dm-bow will not load if size is not a multiple of 4096
+ // rounding down does not hurt, since ext4 will only use full blocks
+ size &= ~7;
+
android::dm::DmTable table;
auto bowTarget =
std::make_unique<android::dm::DmTargetBow>(0, size, entry->blk_device);
diff --git a/fs_mgr/fs_mgr_priv.h b/fs_mgr/fs_mgr_priv.h
index 46f54cc..46cdb62 100644
--- a/fs_mgr/fs_mgr_priv.h
+++ b/fs_mgr/fs_mgr_priv.h
@@ -33,7 +33,7 @@
*/
#define FS_MGR_CHECK(x) CHECK(x) << "in libfs_mgr "
-#define FS_MGR_TAG "[libfs_mgr]"
+#define FS_MGR_TAG "[libfs_mgr] "
// Logs a message to kernel
#define LINFO LOG(INFO) << FS_MGR_TAG
diff --git a/fs_mgr/libfiemap/metadata.cpp b/fs_mgr/libfiemap/metadata.cpp
index b0dfb5c..22b8afb 100644
--- a/fs_mgr/libfiemap/metadata.cpp
+++ b/fs_mgr/libfiemap/metadata.cpp
@@ -30,6 +30,7 @@
namespace fiemap {
using namespace android::fs_mgr;
+using android::base::unique_fd;
static constexpr uint32_t kMaxMetadataSize = 256 * 1024;
@@ -109,10 +110,18 @@
if (exported->partitions.empty() && android::base::RemoveFileIfExists(metadata_file)) {
return true;
}
- if (!WriteToImageFile(metadata_file, *exported.get())) {
+
+ unique_fd fd(open(metadata_file.c_str(), O_CREAT | O_RDWR | O_TRUNC | O_CLOEXEC | O_BINARY | O_SYNC, 0644));
+ if (fd < 0) {
+ LOG(ERROR) << "open failed: " << metadata_file;
+ return false;
+ }
+
+ if (!WriteToImageFile(fd, *exported.get())) {
LOG(ERROR) << "Unable to save new metadata";
return false;
}
+
return true;
}
diff --git a/fs_mgr/libfs_avb/avb_ops.cpp b/fs_mgr/libfs_avb/avb_ops.cpp
index 46072bb..a119bfc 100644
--- a/fs_mgr/libfs_avb/avb_ops.cpp
+++ b/fs_mgr/libfs_avb/avb_ops.cpp
@@ -26,8 +26,10 @@
#include <errno.h>
#include <fcntl.h>
+#include <linux/fs.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/ioctl.h>
#include <sys/stat.h>
#include <string>
@@ -96,13 +98,11 @@
return AVB_IO_RESULT_OK;
}
-static AvbIOResult no_op_get_size_of_partition(AvbOps* ops ATTRIBUTE_UNUSED,
- const char* partition ATTRIBUTE_UNUSED,
- uint64_t* out_size_num_byte) {
- // The function is for bootloader to load entire content of AVB HASH partitions.
- // In user-space, returns 0 as we only need to set up AVB HASHTHREE partitions.
- *out_size_num_byte = 0;
- return AVB_IO_RESULT_OK;
+static AvbIOResult get_size_of_partition(AvbOps* ops ATTRIBUTE_UNUSED,
+ const char* partition ATTRIBUTE_UNUSED,
+ uint64_t* out_size_num_byte) {
+ return FsManagerAvbOps::GetInstanceFromAvbOps(ops)->GetSizeOfPartition(partition,
+ out_size_num_byte);
}
// Converts a partition name (with ab_suffix) to the corresponding mount point.
@@ -131,7 +131,7 @@
avb_ops_.validate_vbmeta_public_key = no_op_validate_vbmeta_public_key;
avb_ops_.read_is_device_unlocked = no_op_read_is_device_unlocked;
avb_ops_.get_unique_guid_for_partition = no_op_get_unique_guid_for_partition;
- avb_ops_.get_size_of_partition = no_op_get_size_of_partition;
+ avb_ops_.get_size_of_partition = get_size_of_partition;
// Sets user_data for GetInstanceFromAvbOps() to convert it back to FsManagerAvbOps.
avb_ops_.user_data = this;
@@ -167,13 +167,8 @@
return "";
}
-
-AvbIOResult FsManagerAvbOps::ReadFromPartition(const char* partition, int64_t offset,
- size_t num_bytes, void* buffer,
- size_t* out_num_read) {
+std::string FsManagerAvbOps::GetPartitionPath(const char* partition) {
std::string path = "/dev/block/by-name/"s + partition;
-
- // Ensures the device path (a symlink created by init) is ready to access.
if (!WaitForFile(path, 1s)) {
LERROR << "Device path not found: " << path;
// Falls back to logical path if the physical path is not found.
@@ -182,8 +177,36 @@
// the bootloader failed to read a physical partition, it will failed to boot
// the HLOS and we won't reach the code here.
path = GetLogicalPath(partition);
- if (path.empty() || !WaitForFile(path, 1s)) return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
- LINFO << "Fallback to use logical device path: " << path;
+ if (path.empty() || !WaitForFile(path, 1s)) return "";
+ }
+ return path;
+}
+
+AvbIOResult FsManagerAvbOps::GetSizeOfPartition(const char* partition,
+ uint64_t* out_size_num_byte) {
+ const auto path = GetPartitionPath(partition);
+ if (path.empty()) {
+ return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+ }
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
+ if (fd < 0) {
+ PERROR << "Failed to open " << path;
+ return AVB_IO_RESULT_ERROR_IO;
+ }
+ int err = ioctl(fd, BLKGETSIZE64, out_size_num_byte);
+ if (err) {
+ *out_size_num_byte = 0;
+ return AVB_IO_RESULT_ERROR_IO;
+ }
+ return AVB_IO_RESULT_OK;
+}
+
+AvbIOResult FsManagerAvbOps::ReadFromPartition(const char* partition, int64_t offset,
+ size_t num_bytes, void* buffer,
+ size_t* out_num_read) {
+ std::string path = GetPartitionPath(partition);
+ if (path.empty()) {
+ return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
}
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
diff --git a/fs_mgr/libfs_avb/avb_ops.h b/fs_mgr/libfs_avb/avb_ops.h
index b39812d..12686a6 100644
--- a/fs_mgr/libfs_avb/avb_ops.h
+++ b/fs_mgr/libfs_avb/avb_ops.h
@@ -56,12 +56,14 @@
AvbIOResult ReadFromPartition(const char* partition, int64_t offset, size_t num_bytes,
void* buffer, size_t* out_num_read);
+ AvbIOResult GetSizeOfPartition(const char* partition, uint64_t* out_size_num_byte);
AvbSlotVerifyResult AvbSlotVerify(const std::string& ab_suffix, AvbSlotVerifyFlags flags,
std::vector<VBMetaData>* out_vbmeta_images);
private:
std::string GetLogicalPath(const std::string& partition_name);
+ std::string GetPartitionPath(const char* partition_name);
AvbOps avb_ops_;
Fstab fstab_;
};
diff --git a/fs_mgr/libfs_avb/util.h b/fs_mgr/libfs_avb/util.h
index 427ab7c..29d1e9c 100644
--- a/fs_mgr/libfs_avb/util.h
+++ b/fs_mgr/libfs_avb/util.h
@@ -31,7 +31,7 @@
using android::base::ErrnoError;
using android::base::Result;
-#define FS_AVB_TAG "[libfs_avb]"
+#define FS_AVB_TAG "[libfs_avb] "
// Logs a message to kernel
#define LINFO LOG(INFO) << FS_AVB_TAG
diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp
index 4b81c2c..996ffd7 100644
--- a/fs_mgr/liblp/Android.bp
+++ b/fs_mgr/liblp/Android.bp
@@ -39,6 +39,7 @@
],
srcs: [
"builder.cpp",
+ "super_layout_builder.cpp",
"images.cpp",
"partition_opener.cpp",
"property_fetcher.cpp",
@@ -62,17 +63,6 @@
export_include_dirs: ["include"],
}
-filegroup {
- name: "liblp_test_srcs",
- srcs: [
- "builder_test.cpp",
- "device_test.cpp",
- "io_test.cpp",
- "test_partition_opener.cpp",
- "utility_test.cpp",
- ],
-}
-
cc_defaults {
name: "liblp_test_defaults",
defaults: ["fs_mgr_defaults"],
@@ -82,22 +72,39 @@
static_libs: [
"libcutils",
"libgmock",
- "libfs_mgr",
"liblp",
"libcrypto_static",
] + liblp_lib_deps,
header_libs: [
"libstorage_literals_headers",
],
+ target: {
+ android: {
+ srcs: [
+ "device_test.cpp",
+ "io_test.cpp",
+ ],
+ static_libs: [
+ "libfs_mgr",
+ ],
+ }
+ },
stl: "libc++_static",
- srcs: [":liblp_test_srcs"],
+ srcs: [
+ "builder_test.cpp",
+ "super_layout_builder_test.cpp",
+ "test_partition_opener.cpp",
+ "utility_test.cpp",
+ ],
}
cc_test {
name: "liblp_test",
defaults: ["liblp_test_defaults"],
- test_config: "liblp_test.xml",
test_suites: ["device-tests"],
+ auto_gen_config: true,
+ require_root: true,
+ host_supported: true
}
cc_test {
diff --git a/fs_mgr/liblp/images.cpp b/fs_mgr/liblp/images.cpp
index 0b1e522..02b64ac 100644
--- a/fs_mgr/liblp/images.cpp
+++ b/fs_mgr/liblp/images.cpp
@@ -312,6 +312,11 @@
bool ImageBuilder::AddPartitionImage(const LpMetadataPartition& partition,
const std::string& file) {
+ if (partition.num_extents == 0) {
+ LERROR << "Partition size is zero: " << GetPartitionName(partition);
+ return false;
+ }
+
// Track which extent we're processing.
uint32_t extent_index = partition.first_extent_index;
diff --git a/fs_mgr/liblp/include/liblp/super_layout_builder.h b/fs_mgr/liblp/include/liblp/super_layout_builder.h
new file mode 100644
index 0000000..d920855
--- /dev/null
+++ b/fs_mgr/liblp/include/liblp/super_layout_builder.h
@@ -0,0 +1,104 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#pragma once
+
+#include <stdint.h>
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include <android-base/unique_fd.h>
+#include <liblp/builder.h>
+
+namespace android {
+namespace fs_mgr {
+
+struct SuperImageExtent {
+ enum class Type { INVALID, DATA, PARTITION, ZERO, DONTCARE };
+
+ SuperImageExtent(const SuperImageExtent& other) = default;
+ SuperImageExtent(SuperImageExtent&& other) = default;
+ SuperImageExtent(uint64_t offset, uint64_t size, Type type)
+ : offset(offset), size(size), type(type) {}
+
+ SuperImageExtent(uint64_t offset, std::shared_ptr<std::string> blob)
+ : SuperImageExtent(offset, blob->size(), Type::DATA) {
+ this->blob = blob;
+ }
+
+ SuperImageExtent(uint64_t offset, uint64_t size, const std::string& image_name,
+ uint64_t image_offset)
+ : SuperImageExtent(offset, size, Type::PARTITION) {
+ this->image_name = image_name;
+ this->image_offset = image_offset;
+ }
+
+ SuperImageExtent& operator=(const SuperImageExtent& other) = default;
+ SuperImageExtent& operator=(SuperImageExtent&& other) = default;
+
+ bool operator<(const SuperImageExtent& other) const { return offset < other.offset; }
+ bool operator==(const SuperImageExtent& other) const;
+
+ // Location, size, and type of the extent.
+ uint64_t offset = 0;
+ uint64_t size = 0;
+ Type type = Type::INVALID;
+
+ // If type == DATA, this contains the bytes to write.
+ std::shared_ptr<std::string> blob;
+ // If type == PARTITION, this contains the partition image name and
+ // offset within that file.
+ std::string image_name;
+ uint64_t image_offset = 0;
+};
+
+// The SuperLayoutBuilder allows building a sparse view of a super image. This
+// is useful for efficient flashing, eg to bypass fastbootd and directly flash
+// super without physically building and storing the image.
+class SuperLayoutBuilder final {
+ public:
+ // Open a super_empty.img, return false on failure. This must be called to
+ // initialize the tool. If it returns false, either the image failed to
+ // parse, or the tool is not compatible with how the device is configured
+ // (in which case fastbootd should be preferred).
+ [[nodiscard]] bool Open(android::base::borrowed_fd fd);
+ [[nodiscard]] bool Open(const void* data, size_t bytes);
+ [[nodiscard]] bool Open(const LpMetadata& metadata);
+
+ // Add a partition's image and size to the work list. If false is returned,
+ // there was either a duplicate partition or not enough space in super.
+ bool AddPartition(const std::string& partition_name, const std::string& image_name,
+ uint64_t partition_size);
+
+ // Return the list of extents describing the super image. If this list is
+ // empty, then there was an unrecoverable error in building the list.
+ std::vector<SuperImageExtent> GetImageLayout();
+
+ // Return the current metadata.
+ std::unique_ptr<LpMetadata> Export() const { return builder_->Export(); }
+
+ private:
+ std::unique_ptr<MetadataBuilder> builder_;
+ std::unordered_map<std::string, std::string> image_map_;
+};
+
+std::ostream& operator<<(std::ostream& stream, const SuperImageExtent& extent);
+
+} // namespace fs_mgr
+} // namespace android
diff --git a/fs_mgr/liblp/liblp_test.xml b/fs_mgr/liblp/liblp_test.xml
deleted file mode 100644
index 98414b1..0000000
--- a/fs_mgr/liblp/liblp_test.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<configuration description="Config for liblp_test">
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
- <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
- <option name="cleanup" value="true" />
- <option name="push" value="liblp_test->/data/local/tmp/liblp_test" />
- </target_preparer>
- <test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/local/tmp" />
- <option name="module-name" value="liblp_test" />
- </test>
-</configuration>
diff --git a/fs_mgr/liblp/super_layout_builder.cpp b/fs_mgr/liblp/super_layout_builder.cpp
new file mode 100644
index 0000000..37f28e1
--- /dev/null
+++ b/fs_mgr/liblp/super_layout_builder.cpp
@@ -0,0 +1,241 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#include <liblp/super_layout_builder.h>
+
+#include <liblp/liblp.h>
+
+#include "images.h"
+#include "utility.h"
+#include "writer.h"
+
+using android::base::borrowed_fd;
+using android::base::unique_fd;
+
+namespace android {
+namespace fs_mgr {
+
+bool SuperLayoutBuilder::Open(borrowed_fd fd) {
+ auto metadata = ReadFromImageFile(fd.get());
+ if (!metadata) {
+ return false;
+ }
+ return Open(*metadata.get());
+}
+
+bool SuperLayoutBuilder::Open(const void* data, size_t size) {
+ auto metadata = ReadFromImageBlob(data, size);
+ if (!metadata) {
+ return false;
+ }
+ return Open(*metadata.get());
+}
+
+bool SuperLayoutBuilder::Open(const LpMetadata& metadata) {
+ for (const auto& partition : metadata.partitions) {
+ if (partition.attributes & LP_PARTITION_ATTR_SLOT_SUFFIXED) {
+ // Retrofit devices are not supported.
+ return false;
+ }
+ if (!(partition.attributes & LP_PARTITION_ATTR_READONLY)) {
+ // Writable partitions are not supported.
+ return false;
+ }
+ }
+ if (!metadata.extents.empty()) {
+ // Partitions that already have extents are not supported (should
+ // never be true of super_empty.img).
+ return false;
+ }
+ if (metadata.block_devices.size() != 1) {
+ // Only one "super" is supported.
+ return false;
+ }
+
+ builder_ = MetadataBuilder::New(metadata);
+ return !!builder_;
+}
+
+bool SuperLayoutBuilder::AddPartition(const std::string& partition_name,
+ const std::string& image_name, uint64_t partition_size) {
+ auto p = builder_->FindPartition(partition_name);
+ if (!p) {
+ return false;
+ }
+ if (!builder_->ResizePartition(p, partition_size)) {
+ return false;
+ }
+ image_map_.emplace(partition_name, image_name);
+ return true;
+}
+
+// Fill the space between each extent, if any, with either a fill or dontcare
+// extent. The caller constructs a sample extent to re-use.
+static bool AddGapExtents(std::vector<SuperImageExtent>* extents, SuperImageExtent::Type gap_type) {
+ std::vector<SuperImageExtent> old = std::move(*extents);
+ std::sort(old.begin(), old.end());
+
+ *extents = {};
+
+ uint64_t current_offset = 0;
+ for (const auto& extent : old) {
+ // Check for overlapping extents - this would be a serious error.
+ if (current_offset > extent.offset) {
+ LOG(INFO) << "Overlapping extents detected; cannot layout temporary super image";
+ return false;
+ }
+
+ if (extent.offset != current_offset) {
+ uint64_t gap_size = extent.offset - current_offset;
+ extents->emplace_back(current_offset, gap_size, gap_type);
+ current_offset = extent.offset;
+ }
+
+ extents->emplace_back(extent);
+ current_offset += extent.size;
+ }
+ return true;
+}
+
+std::vector<SuperImageExtent> SuperLayoutBuilder::GetImageLayout() {
+ auto metadata = builder_->Export();
+ if (!metadata) {
+ return {};
+ }
+
+ std::vector<SuperImageExtent> extents;
+
+ // Write the primary and backup copies of geometry.
+ std::string geometry_bytes = SerializeGeometry(metadata->geometry);
+ auto blob = std::make_shared<std::string>(std::move(geometry_bytes));
+
+ extents.emplace_back(0, GetPrimaryGeometryOffset(), SuperImageExtent::Type::ZERO);
+ extents.emplace_back(GetPrimaryGeometryOffset(), blob);
+ extents.emplace_back(GetBackupGeometryOffset(), blob);
+
+ // Write the primary and backup copies of each metadata slot. When flashing,
+ // all metadata copies are the same, even for different slots.
+ std::string metadata_bytes = SerializeMetadata(*metadata.get());
+
+ // Align metadata size to 4KB. This makes the layout easily compatible with
+ // libsparse.
+ static constexpr size_t kSparseAlignment = 4096;
+ size_t metadata_aligned_bytes;
+ if (!AlignTo(metadata_bytes.size(), kSparseAlignment, &metadata_aligned_bytes)) {
+ LOG(ERROR) << "Unable to align metadata size " << metadata_bytes.size() << " to "
+ << kSparseAlignment;
+ return {};
+ }
+ metadata_bytes.resize(metadata_aligned_bytes, '\0');
+
+ // However, alignment can cause larger-than-supported metadata blocks. Fall
+ // back to fastbootd/update-super.
+ if (metadata_bytes.size() > metadata->geometry.metadata_max_size) {
+ LOG(VERBOSE) << "Aligned metadata size " << metadata_bytes.size()
+ << " is larger than maximum metadata size "
+ << metadata->geometry.metadata_max_size;
+ return {};
+ }
+
+ blob = std::make_shared<std::string>(std::move(metadata_bytes));
+ for (uint32_t i = 0; i < metadata->geometry.metadata_slot_count; i++) {
+ int64_t metadata_primary = GetPrimaryMetadataOffset(metadata->geometry, i);
+ int64_t metadata_backup = GetBackupMetadataOffset(metadata->geometry, i);
+ extents.emplace_back(metadata_primary, blob);
+ extents.emplace_back(metadata_backup, blob);
+ }
+
+ // Add extents for each partition.
+ for (const auto& partition : metadata->partitions) {
+ auto partition_name = GetPartitionName(partition);
+ auto image_name_iter = image_map_.find(partition_name);
+ if (image_name_iter == image_map_.end()) {
+ if (partition.num_extents != 0) {
+ LOG(ERROR) << "Partition " << partition_name
+ << " has extents but no image filename";
+ return {};
+ }
+ continue;
+ }
+ const auto& image_name = image_name_iter->second;
+
+ uint64_t image_offset = 0;
+ for (uint32_t i = 0; i < partition.num_extents; i++) {
+ const auto& e = metadata->extents[partition.first_extent_index + i];
+
+ if (e.target_type != LP_TARGET_TYPE_LINEAR) {
+ // Any type other than LINEAR isn't understood here. We don't even
+ // bother with ZERO, which is never generated.
+ LOG(INFO) << "Unknown extent type from liblp: " << e.target_type;
+ return {};
+ }
+
+ size_t size = e.num_sectors * LP_SECTOR_SIZE;
+ uint64_t super_offset = e.target_data * LP_SECTOR_SIZE;
+ extents.emplace_back(super_offset, size, image_name, image_offset);
+
+ image_offset += size;
+ }
+ }
+
+ if (!AddGapExtents(&extents, SuperImageExtent::Type::DONTCARE)) {
+ return {};
+ }
+ return extents;
+}
+
+bool SuperImageExtent::operator==(const SuperImageExtent& other) const {
+ if (offset != other.offset) {
+ return false;
+ }
+ if (size != other.size) {
+ return false;
+ }
+ if (type != other.type) {
+ return false;
+ }
+ switch (type) {
+ case Type::DATA:
+ return *blob == *other.blob;
+ case Type::PARTITION:
+ return image_name == other.image_name && image_offset == other.image_offset;
+ default:
+ return true;
+ }
+}
+
+std::ostream& operator<<(std::ostream& stream, const SuperImageExtent& extent) {
+ stream << "extent:" << extent.offset << ":" << extent.size << ":";
+ switch (extent.type) {
+ case SuperImageExtent::Type::DATA:
+ stream << "data";
+ break;
+ case SuperImageExtent::Type::PARTITION:
+ stream << "partition:" << extent.image_name << ":" << extent.image_offset;
+ break;
+ case SuperImageExtent::Type::ZERO:
+ stream << "zero";
+ break;
+ case SuperImageExtent::Type::DONTCARE:
+ stream << "dontcare";
+ break;
+ default:
+ stream << "invalid";
+ }
+ return stream;
+}
+
+} // namespace fs_mgr
+} // namespace android
diff --git a/fs_mgr/liblp/super_layout_builder_test.cpp b/fs_mgr/liblp/super_layout_builder_test.cpp
new file mode 100644
index 0000000..714b6b4
--- /dev/null
+++ b/fs_mgr/liblp/super_layout_builder_test.cpp
@@ -0,0 +1,141 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <liblp/builder.h>
+#include <liblp/super_layout_builder.h>
+#include <storage_literals/storage_literals.h>
+
+#include "images.h"
+#include "writer.h"
+
+using namespace android::fs_mgr;
+using namespace android::storage_literals;
+
+TEST(SuperImageTool, Layout) {
+ auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2);
+ ASSERT_NE(builder, nullptr);
+
+ Partition* p = builder->AddPartition("system_a", LP_PARTITION_ATTR_READONLY);
+ ASSERT_NE(p, nullptr);
+
+ auto metadata = builder->Export();
+ ASSERT_NE(metadata, nullptr);
+
+ SuperLayoutBuilder tool;
+ ASSERT_TRUE(tool.Open(*metadata.get()));
+ ASSERT_TRUE(tool.AddPartition("system_a", "system.img", 16_KiB));
+
+ // Get a copy of the metadata we'd expect if flashing.
+ ASSERT_TRUE(builder->ResizePartition(p, 16_KiB));
+ metadata = builder->Export();
+ ASSERT_NE(metadata, nullptr);
+
+ auto geometry_blob = std::make_shared<std::string>(SerializeGeometry(metadata->geometry));
+ auto metadata_blob = std::make_shared<std::string>(SerializeMetadata(*metadata.get()));
+ metadata_blob->resize(4_KiB, '\0');
+
+ auto extents = tool.GetImageLayout();
+ ASSERT_EQ(extents.size(), 12);
+ EXPECT_EQ(extents[0], SuperImageExtent(0, 4096, SuperImageExtent::Type::ZERO));
+ EXPECT_EQ(extents[1], SuperImageExtent(4096, geometry_blob));
+ EXPECT_EQ(extents[2], SuperImageExtent(8192, geometry_blob));
+ EXPECT_EQ(extents[3], SuperImageExtent(12288, metadata_blob));
+ EXPECT_EQ(extents[4], SuperImageExtent(16384, 4096, SuperImageExtent::Type::DONTCARE));
+ EXPECT_EQ(extents[5], SuperImageExtent(20480, metadata_blob));
+ EXPECT_EQ(extents[6], SuperImageExtent(24576, 4096, SuperImageExtent::Type::DONTCARE));
+ EXPECT_EQ(extents[7], SuperImageExtent(28672, metadata_blob));
+ EXPECT_EQ(extents[8], SuperImageExtent(32768, 4096, SuperImageExtent::Type::DONTCARE));
+ EXPECT_EQ(extents[9], SuperImageExtent(36864, metadata_blob));
+ EXPECT_EQ(extents[10], SuperImageExtent(40960, 4096, SuperImageExtent::Type::DONTCARE));
+ EXPECT_EQ(extents[11], SuperImageExtent(45056, 16384, "system.img", 0));
+}
+
+TEST(SuperImageTool, NoWritablePartitions) {
+ auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2);
+ ASSERT_NE(builder, nullptr);
+
+ Partition* p = builder->AddPartition("system_a", 0);
+ ASSERT_NE(p, nullptr);
+
+ auto metadata = builder->Export();
+ ASSERT_NE(metadata, nullptr);
+
+ SuperLayoutBuilder tool;
+ ASSERT_FALSE(tool.Open(*metadata.get()));
+}
+
+TEST(SuperImageTool, NoRetrofit) {
+ auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2);
+ ASSERT_NE(builder, nullptr);
+
+ Partition* p = builder->AddPartition("system_a", LP_PARTITION_ATTR_READONLY);
+ ASSERT_NE(p, nullptr);
+
+ auto metadata = builder->Export();
+ ASSERT_NE(metadata, nullptr);
+
+ // Add an extra block device.
+ metadata->block_devices.emplace_back(metadata->block_devices[0]);
+
+ SuperLayoutBuilder tool;
+ ASSERT_FALSE(tool.Open(*metadata.get()));
+}
+
+TEST(SuperImageTool, NoRetrofit2) {
+ auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2);
+ ASSERT_NE(builder, nullptr);
+
+ Partition* p = builder->AddPartition(
+ "system_a", LP_PARTITION_ATTR_READONLY | LP_PARTITION_ATTR_SLOT_SUFFIXED);
+ ASSERT_NE(p, nullptr);
+
+ auto metadata = builder->Export();
+ ASSERT_NE(metadata, nullptr);
+
+ SuperLayoutBuilder tool;
+ ASSERT_FALSE(tool.Open(*metadata.get()));
+}
+
+TEST(SuperImageTool, NoFixedPartitions) {
+ auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2);
+ ASSERT_NE(builder, nullptr);
+
+ Partition* p = builder->AddPartition("system_a", LP_PARTITION_ATTR_READONLY);
+ ASSERT_NE(p, nullptr);
+ ASSERT_TRUE(builder->ResizePartition(p, 4_KiB));
+
+ auto metadata = builder->Export();
+ ASSERT_NE(metadata, nullptr);
+
+ SuperLayoutBuilder tool;
+ ASSERT_FALSE(tool.Open(*metadata.get()));
+}
+
+TEST(SuperImageTool, LargeAlignedMetadata) {
+ auto builder = MetadataBuilder::New(4_MiB, 512, 2);
+ ASSERT_NE(builder, nullptr);
+
+ auto metadata = builder->Export();
+ ASSERT_NE(metadata, nullptr);
+
+ SuperLayoutBuilder tool;
+ ASSERT_TRUE(tool.Open(*metadata.get()));
+
+ auto extents = tool.GetImageLayout();
+ ASSERT_TRUE(extents.empty());
+}
diff --git a/fs_mgr/liblp/utility.h b/fs_mgr/liblp/utility.h
index aa3a6a0..32a59a5 100644
--- a/fs_mgr/liblp/utility.h
+++ b/fs_mgr/liblp/utility.h
@@ -30,7 +30,7 @@
#include "liblp/liblp.h"
-#define LP_TAG "[liblp]"
+#define LP_TAG "[liblp] "
#define LWARN LOG(WARNING) << LP_TAG
#define LINFO LOG(INFO) << LP_TAG
#define LERROR LOG(ERROR) << LP_TAG
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index b3763ae..fa04c43 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -108,6 +108,12 @@
// Estimated COW size from OTA manifest.
uint64 estimated_cow_size = 12;
+
+ // Enable multi-threaded compression
+ bool enable_threading = 13;
+
+ // Enable batching for COW writes
+ bool batched_writes = 14;
}
// Next: 8
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
index 3932fad..56b48f0 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
@@ -298,13 +298,12 @@
return false;
}
- bool ret = OpenForWrite();
-
- if (ret) {
- InitWorkers();
+ if (!OpenForWrite()) {
+ return false;
}
- return ret;
+ InitWorkers();
+ return true;
}
bool CowWriter::InitializeAppend(android::base::unique_fd&& fd, uint64_t label) {
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.h b/fs_mgr/libsnapshot/partition_cow_creator.h
index 949e6c5..bd5c8cb 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.h
+++ b/fs_mgr/libsnapshot/partition_cow_creator.h
@@ -60,6 +60,12 @@
bool using_snapuserd = false;
std::string compression_algorithm;
+ // True if multi-threaded compression should be enabled
+ bool enable_threading;
+
+ // True if COW writes should be batched in memory
+ bool batched_writes;
+
struct Return {
SnapshotStatus snapshot_status;
std::vector<Interval> cow_partition_usable_regions;
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 961db02..f655522 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -400,6 +400,12 @@
status->set_metadata_sectors(0);
status->set_using_snapuserd(cow_creator->using_snapuserd);
status->set_compression_algorithm(cow_creator->compression_algorithm);
+ if (cow_creator->enable_threading) {
+ status->set_enable_threading(cow_creator->enable_threading);
+ }
+ if (cow_creator->batched_writes) {
+ status->set_batched_writes(cow_creator->batched_writes);
+ }
if (!WriteSnapshotStatus(lock, *status)) {
PLOG(ERROR) << "Could not write snapshot status: " << status->name();
@@ -3210,6 +3216,8 @@
vabc_disable_reason = "recovery";
} else if (!cow_format_support) {
vabc_disable_reason = "cow format not supported";
+ } else if (!KernelSupportsCompressedSnapshots()) {
+ vabc_disable_reason = "kernel missing userspace block device support";
}
if (!vabc_disable_reason.empty()) {
@@ -3248,6 +3256,12 @@
.using_snapuserd = using_snapuserd,
.compression_algorithm = compression_algorithm,
};
+ if (dap_metadata.vabc_feature_set().has_threaded()) {
+ cow_creator.enable_threading = dap_metadata.vabc_feature_set().threaded();
+ }
+ if (dap_metadata.vabc_feature_set().has_batch_writes()) {
+ cow_creator.batched_writes = dap_metadata.vabc_feature_set().batch_writes();
+ }
auto ret = CreateUpdateSnapshotsInternal(lock.get(), manifest, &cow_creator, &created_devices,
&all_snapshot_status);
@@ -3635,6 +3649,8 @@
CowOptions cow_options;
cow_options.compression = status.compression_algorithm();
cow_options.max_blocks = {status.device_size() / cow_options.block_size};
+ cow_options.batch_write = status.batched_writes();
+ cow_options.num_compress_threads = status.enable_threading() ? 2 : 0;
// Disable scratch space for vts tests
if (device()->IsTestDevice()) {
cow_options.scratch_space = false;
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 4014380..460d49d 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -124,6 +124,10 @@
SKIP_IF_NON_VIRTUAL_AB();
SetupProperties();
+ if (!DeviceSupportsMode()) {
+ GTEST_SKIP() << "Mode not supported on this device";
+ }
+
InitializeState();
CleanupTestArtifacts();
FormatFakeSuper();
@@ -159,7 +163,13 @@
IPropertyFetcher::OverrideForTesting(std::move(fetcher));
if (GetLegacyCompressionEnabledProperty() || CanUseUserspaceSnapshots()) {
- snapuserd_required_ = true;
+ // If we're asked to test the device's actual configuration, then it
+ // may be misconfigured, so check for kernel support as libsnapshot does.
+ if (FLAGS_force_mode.empty()) {
+ snapuserd_required_ = KernelSupportsCompressedSnapshots();
+ } else {
+ snapuserd_required_ = true;
+ }
}
}
@@ -176,6 +186,16 @@
LOG(INFO) << "Teardown complete for test: " << test_name_;
}
+ bool DeviceSupportsMode() {
+ if (FLAGS_force_mode.empty()) {
+ return true;
+ }
+ if (snapuserd_required_ && !KernelSupportsCompressedSnapshots()) {
+ return false;
+ }
+ return true;
+ }
+
void InitializeState() {
ASSERT_TRUE(sm->EnsureImageManager());
image_manager_ = sm->image_manager();
@@ -193,6 +213,11 @@
// get an accurate list to remove.
lock_ = nullptr;
+ // If there is no image manager, the test was skipped.
+ if (!image_manager_) {
+ return;
+ }
+
std::vector<std::string> snapshots = {"test-snapshot", "test_partition_a",
"test_partition_b"};
for (const auto& snapshot : snapshots) {
@@ -946,6 +971,11 @@
SKIP_IF_NON_VIRTUAL_AB();
SnapshotTest::SetUp();
+ if (!image_manager_) {
+ // Test was skipped.
+ return;
+ }
+
Cleanup();
// Cleanup() changes slot suffix, so initialize it again.
@@ -2669,44 +2699,49 @@
"Merge"s;
});
-// Test behavior of ImageManager::Create on low space scenario. These tests assumes image manager
-// uses /data as backup device.
-class ImageManagerTest : public SnapshotTest, public WithParamInterface<uint64_t> {
+class ImageManagerTest : public SnapshotTest {
protected:
void SetUp() override {
SKIP_IF_NON_VIRTUAL_AB();
SnapshotTest::SetUp();
- userdata_ = std::make_unique<LowSpaceUserdata>();
- ASSERT_TRUE(userdata_->Init(GetParam()));
}
void TearDown() override {
RETURN_IF_NON_VIRTUAL_AB();
-
+ CleanUp();
+ }
+ void CleanUp() {
+ if (!image_manager_) {
+ return;
+ }
EXPECT_TRUE(!image_manager_->BackingImageExists(kImageName) ||
image_manager_->DeleteBackingImage(kImageName));
}
+
static constexpr const char* kImageName = "my_image";
- std::unique_ptr<LowSpaceUserdata> userdata_;
};
-TEST_P(ImageManagerTest, CreateImageNoSpace) {
- uint64_t to_allocate = userdata_->free_space() + userdata_->bsize();
- auto res = image_manager_->CreateBackingImage(kImageName, to_allocate,
- IImageManager::CREATE_IMAGE_DEFAULT);
- ASSERT_FALSE(res) << "Should not be able to create image with size = " << to_allocate
- << " bytes because only " << userdata_->free_space() << " bytes are free";
- ASSERT_EQ(FiemapStatus::ErrorCode::NO_SPACE, res.error_code()) << res.string();
-}
-
-std::vector<uint64_t> ImageManagerTestParams() {
- std::vector<uint64_t> ret;
+TEST_F(ImageManagerTest, CreateImageNoSpace) {
+ bool at_least_one_failure = false;
for (uint64_t size = 1_MiB; size <= 512_MiB; size *= 2) {
- ret.push_back(size);
- }
- return ret;
-}
+ auto userdata = std::make_unique<LowSpaceUserdata>();
+ ASSERT_TRUE(userdata->Init(size));
-INSTANTIATE_TEST_SUITE_P(ImageManagerTest, ImageManagerTest, ValuesIn(ImageManagerTestParams()));
+ uint64_t to_allocate = userdata->free_space() + userdata->bsize();
+
+ auto res = image_manager_->CreateBackingImage(kImageName, to_allocate,
+ IImageManager::CREATE_IMAGE_DEFAULT);
+ if (!res) {
+ at_least_one_failure = true;
+ } else {
+ ASSERT_EQ(res.error_code(), FiemapStatus::ErrorCode::NO_SPACE) << res.string();
+ }
+
+ CleanUp();
+ }
+
+ ASSERT_TRUE(at_least_one_failure)
+ << "We should have failed to allocate at least one over-sized image";
+}
bool Mkdir(const std::string& path) {
if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
diff --git a/fs_mgr/libsnapshot/snapshot_writer_test.cpp b/fs_mgr/libsnapshot/snapshot_writer_test.cpp
index da48eb9..a03632b 100644
--- a/fs_mgr/libsnapshot/snapshot_writer_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_writer_test.cpp
@@ -33,8 +33,8 @@
TemporaryFile cow_device_file{};
android::snapshot::CowOptions options{.block_size = BLOCK_SIZE};
android::snapshot::CompressedSnapshotWriter snapshot_writer{options};
- snapshot_writer.SetCowDevice(android::base::unique_fd{cow_device_file.fd});
- snapshot_writer.Initialize();
+ ASSERT_TRUE(snapshot_writer.SetCowDevice(android::base::unique_fd{cow_device_file.fd}));
+ ASSERT_TRUE(snapshot_writer.Initialize());
std::vector<unsigned char> buffer;
buffer.resize(BLOCK_SIZE);
std::fill(buffer.begin(), buffer.end(), 123);
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
index fb2251e..010beb3 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
@@ -47,6 +47,8 @@
bool ValidateConnection();
std::string GetDaemonAliveIndicatorPath();
+ void WaitForServiceToTerminate(std::chrono::milliseconds timeout_ms);
+
public:
explicit SnapuserdClient(android::base::unique_fd&& sockfd);
SnapuserdClient(){};
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
index 695b581..3bed3a4 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
@@ -94,6 +94,21 @@
return client;
}
+void SnapuserdClient::WaitForServiceToTerminate(std::chrono::milliseconds timeout_ms) {
+ auto start = std::chrono::steady_clock::now();
+ while (android::base::GetProperty("init.svc.snapuserd", "") == "running") {
+ auto now = std::chrono::steady_clock::now();
+ auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
+ if (elapsed >= timeout_ms) {
+ LOG(ERROR) << "Timed out - Snapuserd service did not stop - Forcefully terminating the "
+ "service";
+ android::base::SetProperty("ctl.stop", "snapuserd");
+ return;
+ }
+ std::this_thread::sleep_for(100ms);
+ }
+}
+
bool SnapuserdClient::ValidateConnection() {
if (!Sendmsg("query")) {
return false;
@@ -238,6 +253,8 @@
LOG(ERROR) << "Failed to detach snapuserd.";
return false;
}
+
+ WaitForServiceToTerminate(3s);
return true;
}
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
index d670f1e..1421403 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -99,9 +99,9 @@
bool valid_;
};
-class SnapuserTest final {
+class SnapuserdTest : public ::testing::Test {
public:
- bool Setup();
+ bool SetupDefault();
bool SetupOrderedOps();
bool SetupOrderedOpsInverted();
bool SetupCopyOverlap_1();
@@ -118,6 +118,10 @@
static const uint64_t kSectorSize = 512;
+ protected:
+ void SetUp() override {}
+ void TearDown() override { Shutdown(); }
+
private:
void SetupImpl();
@@ -172,7 +176,7 @@
return fd;
}
-void SnapuserTest::Shutdown() {
+void SnapuserdTest::Shutdown() {
ASSERT_TRUE(dmuser_dev_->Destroy());
auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
@@ -181,36 +185,36 @@
ASSERT_TRUE(client_->DetachSnapuserd());
}
-bool SnapuserTest::Setup() {
+bool SnapuserdTest::SetupDefault() {
SetupImpl();
return setup_ok_;
}
-bool SnapuserTest::SetupOrderedOps() {
+bool SnapuserdTest::SetupOrderedOps() {
CreateBaseDevice();
CreateCowDeviceOrderedOps();
return SetupDaemon();
}
-bool SnapuserTest::SetupOrderedOpsInverted() {
+bool SnapuserdTest::SetupOrderedOpsInverted() {
CreateBaseDevice();
CreateCowDeviceOrderedOpsInverted();
return SetupDaemon();
}
-bool SnapuserTest::SetupCopyOverlap_1() {
+bool SnapuserdTest::SetupCopyOverlap_1() {
CreateBaseDevice();
CreateCowDeviceWithCopyOverlap_1();
return SetupDaemon();
}
-bool SnapuserTest::SetupCopyOverlap_2() {
+bool SnapuserdTest::SetupCopyOverlap_2() {
CreateBaseDevice();
CreateCowDeviceWithCopyOverlap_2();
return SetupDaemon();
}
-bool SnapuserTest::SetupDaemon() {
+bool SnapuserdTest::SetupDaemon() {
SetDeviceControlName();
StartSnapuserdDaemon();
@@ -224,7 +228,7 @@
return setup_ok_;
}
-void SnapuserTest::StartSnapuserdDaemon() {
+void SnapuserdTest::StartSnapuserdDaemon() {
pid_t pid = fork();
ASSERT_GE(pid, 0);
if (pid == 0) {
@@ -238,7 +242,7 @@
}
}
-void SnapuserTest::CreateBaseDevice() {
+void SnapuserdTest::CreateBaseDevice() {
unique_fd rnd_fd;
total_base_size_ = (size_ * 5);
@@ -261,7 +265,7 @@
ASSERT_TRUE(base_loop_->valid());
}
-void SnapuserTest::ReadSnapshotDeviceAndValidate() {
+void SnapuserdTest::ReadSnapshotDeviceAndValidate() {
unique_fd fd(open(dmuser_dev_->path().c_str(), O_RDONLY));
ASSERT_GE(fd, 0);
std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size_);
@@ -292,7 +296,7 @@
ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 4), size_), 0);
}
-void SnapuserTest::CreateCowDeviceWithCopyOverlap_2() {
+void SnapuserdTest::CreateCowDeviceWithCopyOverlap_2() {
std::string path = android::base::GetExecutableDirectory();
cow_system_ = std::make_unique<TemporaryFile>(path);
@@ -344,7 +348,7 @@
}
}
-void SnapuserTest::CreateCowDeviceWithCopyOverlap_1() {
+void SnapuserdTest::CreateCowDeviceWithCopyOverlap_1() {
std::string path = android::base::GetExecutableDirectory();
cow_system_ = std::make_unique<TemporaryFile>(path);
@@ -387,7 +391,7 @@
true);
}
-void SnapuserTest::CreateCowDeviceOrderedOpsInverted() {
+void SnapuserdTest::CreateCowDeviceOrderedOpsInverted() {
unique_fd rnd_fd;
loff_t offset = 0;
@@ -450,7 +454,7 @@
}
}
-void SnapuserTest::CreateCowDeviceOrderedOps() {
+void SnapuserdTest::CreateCowDeviceOrderedOps() {
unique_fd rnd_fd;
loff_t offset = 0;
@@ -511,7 +515,7 @@
}
}
-void SnapuserTest::CreateCowDevice() {
+void SnapuserdTest::CreateCowDevice() {
unique_fd rnd_fd;
loff_t offset = 0;
@@ -601,13 +605,13 @@
}
}
-void SnapuserTest::InitCowDevice() {
+void SnapuserdTest::InitCowDevice() {
uint64_t num_sectors = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
base_loop_->device(), base_loop_->device());
ASSERT_NE(num_sectors, 0);
}
-void SnapuserTest::SetDeviceControlName() {
+void SnapuserdTest::SetDeviceControlName() {
system_device_name_.clear();
system_device_ctrl_name_.clear();
@@ -619,7 +623,7 @@
system_device_ctrl_name_ = system_device_name_ + "-ctrl";
}
-void SnapuserTest::CreateDmUserDevice() {
+void SnapuserdTest::CreateDmUserDevice() {
unique_fd fd(TEMP_FAILURE_RETRY(open(base_loop_->device().c_str(), O_RDONLY | O_CLOEXEC)));
ASSERT_TRUE(fd > 0);
@@ -641,12 +645,12 @@
ASSERT_TRUE(android::fs_mgr::WaitForFile(misc_device, 10s));
}
-void SnapuserTest::InitDaemon() {
+void SnapuserdTest::InitDaemon() {
bool ok = client_->AttachDmUser(system_device_ctrl_name_);
ASSERT_TRUE(ok);
}
-void SnapuserTest::CheckMergeCompletion() {
+void SnapuserdTest::CheckMergeCompletion() {
while (true) {
double percentage = client_->GetMergePercent();
if ((int)percentage == 100) {
@@ -657,7 +661,7 @@
}
}
-void SnapuserTest::SetupImpl() {
+void SnapuserdTest::SetupImpl() {
CreateBaseDevice();
CreateCowDevice();
@@ -672,26 +676,26 @@
setup_ok_ = true;
}
-bool SnapuserTest::Merge() {
+bool SnapuserdTest::Merge() {
StartMerge();
CheckMergeCompletion();
merge_ok_ = true;
return merge_ok_;
}
-void SnapuserTest::StartMerge() {
+void SnapuserdTest::StartMerge() {
bool ok = client_->InitiateMerge(system_device_ctrl_name_);
ASSERT_TRUE(ok);
}
-void SnapuserTest::ValidateMerge() {
+void SnapuserdTest::ValidateMerge() {
merged_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, merged_buffer_.get(), total_base_size_, 0),
true);
ASSERT_EQ(memcmp(merged_buffer_.get(), orig_buffer_.get(), total_base_size_), 0);
}
-void SnapuserTest::SimulateDaemonRestart() {
+void SnapuserdTest::SimulateDaemonRestart() {
Shutdown();
std::this_thread::sleep_for(500ms);
SetDeviceControlName();
@@ -701,7 +705,7 @@
InitDaemon();
}
-void SnapuserTest::MergeInterruptRandomly(int max_duration) {
+void SnapuserdTest::MergeInterruptRandomly(int max_duration) {
std::srand(std::time(nullptr));
StartMerge();
@@ -716,7 +720,7 @@
ASSERT_TRUE(Merge());
}
-void SnapuserTest::MergeInterruptFixed(int duration) {
+void SnapuserdTest::MergeInterruptFixed(int duration) {
StartMerge();
for (int i = 0; i < 25; i++) {
@@ -729,7 +733,7 @@
ASSERT_TRUE(Merge());
}
-void SnapuserTest::MergeInterrupt() {
+void SnapuserdTest::MergeInterrupt() {
// Interrupt merge at various intervals
StartMerge();
std::this_thread::sleep_for(250ms);
@@ -758,104 +762,93 @@
ASSERT_TRUE(Merge());
}
-TEST(Snapuserd_Test, Snapshot_IO_TEST) {
- SnapuserTest harness;
- ASSERT_TRUE(harness.Setup());
+TEST_F(SnapuserdTest, Snapshot_IO_TEST) {
+ ASSERT_TRUE(SetupDefault());
// I/O before merge
- harness.ReadSnapshotDeviceAndValidate();
- ASSERT_TRUE(harness.Merge());
- harness.ValidateMerge();
+ ReadSnapshotDeviceAndValidate();
+ ASSERT_TRUE(Merge());
+ ValidateMerge();
// I/O after merge - daemon should read directly
// from base device
- harness.ReadSnapshotDeviceAndValidate();
- harness.Shutdown();
+ ReadSnapshotDeviceAndValidate();
+ Shutdown();
}
-TEST(Snapuserd_Test, Snapshot_MERGE_IO_TEST) {
- SnapuserTest harness;
- ASSERT_TRUE(harness.Setup());
+TEST_F(SnapuserdTest, Snapshot_MERGE_IO_TEST) {
+ ASSERT_TRUE(SetupDefault());
// Issue I/O before merge begins
- std::async(std::launch::async, &SnapuserTest::ReadSnapshotDeviceAndValidate, &harness);
+ std::async(std::launch::async, &SnapuserdTest::ReadSnapshotDeviceAndValidate, this);
// Start the merge
- ASSERT_TRUE(harness.Merge());
- harness.ValidateMerge();
- harness.Shutdown();
+ ASSERT_TRUE(Merge());
+ ValidateMerge();
+ Shutdown();
}
-TEST(Snapuserd_Test, Snapshot_MERGE_IO_TEST_1) {
- SnapuserTest harness;
- ASSERT_TRUE(harness.Setup());
+TEST_F(SnapuserdTest, Snapshot_MERGE_IO_TEST_1) {
+ ASSERT_TRUE(SetupDefault());
// Start the merge
- harness.StartMerge();
+ StartMerge();
// Issue I/O in parallel when merge is in-progress
- std::async(std::launch::async, &SnapuserTest::ReadSnapshotDeviceAndValidate, &harness);
- harness.CheckMergeCompletion();
- harness.ValidateMerge();
- harness.Shutdown();
+ std::async(std::launch::async, &SnapuserdTest::ReadSnapshotDeviceAndValidate, this);
+ CheckMergeCompletion();
+ ValidateMerge();
+ Shutdown();
}
-TEST(Snapuserd_Test, Snapshot_Merge_Resume) {
- SnapuserTest harness;
- ASSERT_TRUE(harness.Setup());
- harness.MergeInterrupt();
- harness.ValidateMerge();
- harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_Merge_Resume) {
+ ASSERT_TRUE(SetupDefault());
+ MergeInterrupt();
+ ValidateMerge();
+ Shutdown();
}
-TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_1) {
- SnapuserTest harness;
- ASSERT_TRUE(harness.SetupCopyOverlap_1());
- ASSERT_TRUE(harness.Merge());
- harness.ValidateMerge();
- harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_COPY_Overlap_TEST_1) {
+ ASSERT_TRUE(SetupCopyOverlap_1());
+ ASSERT_TRUE(Merge());
+ ValidateMerge();
+ Shutdown();
}
-TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_2) {
- SnapuserTest harness;
- ASSERT_TRUE(harness.SetupCopyOverlap_2());
- ASSERT_TRUE(harness.Merge());
- harness.ValidateMerge();
- harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_COPY_Overlap_TEST_2) {
+ ASSERT_TRUE(SetupCopyOverlap_2());
+ ASSERT_TRUE(Merge());
+ ValidateMerge();
+ Shutdown();
}
-TEST(Snapuserd_Test, Snapshot_COPY_Overlap_Merge_Resume_TEST) {
- SnapuserTest harness;
- ASSERT_TRUE(harness.SetupCopyOverlap_1());
- harness.MergeInterrupt();
- harness.ValidateMerge();
- harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_COPY_Overlap_Merge_Resume_TEST) {
+ ASSERT_TRUE(SetupCopyOverlap_1());
+ MergeInterrupt();
+ ValidateMerge();
+ Shutdown();
}
-TEST(Snapuserd_Test, Snapshot_Merge_Crash_Fixed_Ordered) {
- SnapuserTest harness;
- ASSERT_TRUE(harness.SetupOrderedOps());
- harness.MergeInterruptFixed(300);
- harness.ValidateMerge();
- harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_Merge_Crash_Fixed_Ordered) {
+ ASSERT_TRUE(SetupOrderedOps());
+ MergeInterruptFixed(300);
+ ValidateMerge();
+ Shutdown();
}
-TEST(Snapuserd_Test, Snapshot_Merge_Crash_Random_Ordered) {
- SnapuserTest harness;
- ASSERT_TRUE(harness.SetupOrderedOps());
- harness.MergeInterruptRandomly(500);
- harness.ValidateMerge();
- harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_Merge_Crash_Random_Ordered) {
+ ASSERT_TRUE(SetupOrderedOps());
+ MergeInterruptRandomly(500);
+ ValidateMerge();
+ Shutdown();
}
-TEST(Snapuserd_Test, Snapshot_Merge_Crash_Fixed_Inverted) {
- SnapuserTest harness;
- ASSERT_TRUE(harness.SetupOrderedOpsInverted());
- harness.MergeInterruptFixed(50);
- harness.ValidateMerge();
- harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_Merge_Crash_Fixed_Inverted) {
+ ASSERT_TRUE(SetupOrderedOpsInverted());
+ MergeInterruptFixed(50);
+ ValidateMerge();
+ Shutdown();
}
-TEST(Snapuserd_Test, Snapshot_Merge_Crash_Random_Inverted) {
- SnapuserTest harness;
- ASSERT_TRUE(harness.SetupOrderedOpsInverted());
- harness.MergeInterruptRandomly(50);
- harness.ValidateMerge();
- harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_Merge_Crash_Random_Inverted) {
+ ASSERT_TRUE(SetupOrderedOpsInverted());
+ MergeInterruptRandomly(50);
+ ValidateMerge();
+ Shutdown();
}
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
index b05123a..9f1d676 100644
--- a/fs_mgr/libsnapshot/test_helpers.cpp
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -227,7 +227,7 @@
return AssertionFailure() << "Temp file allocated to " << big_file_->path << ", not in "
<< kUserDataDevice;
}
- uint64_t next_consume = std::min(available_space_ - max_free_space,
+ uint64_t next_consume = std::min(std::max(available_space_, max_free_space) - max_free_space,
(uint64_t)std::numeric_limits<off_t>::max());
off_t allocated = 0;
while (next_consume > 0 && free_space_ > max_free_space) {
diff --git a/fs_mgr/libsnapshot/update_engine/update_metadata.proto b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
index 69d72e1..cc12d1d 100644
--- a/fs_mgr/libsnapshot/update_engine/update_metadata.proto
+++ b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
@@ -71,11 +71,18 @@
repeated string partition_names = 3;
}
+message VABCFeatureSet {
+ optional bool threaded = 1;
+ optional bool batch_writes = 2;
+}
+
message DynamicPartitionMetadata {
repeated DynamicPartitionGroup groups = 1;
optional bool vabc_enabled = 3;
optional string vabc_compression_param = 4;
optional uint32 cow_version = 5;
+ // A collection of knobs to tune Virtual AB Compression
+ optional VABCFeatureSet vabc_feature_set = 6;
}
message DeltaArchiveManifest {
diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index a98bf0e..1ffa89c 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -29,6 +29,7 @@
#include <fs_mgr/roots.h>
#include <liblp/property_fetcher.h>
+using android::dm::DeviceMapper;
using android::dm::kSectorSize;
using android::fiemap::FiemapStatus;
using android::fs_mgr::EnsurePathMounted;
@@ -251,7 +252,10 @@
LOG(INFO) << "Userspace snapshots disabled for testing";
return false;
}
-
+ if (!KernelSupportsCompressedSnapshots()) {
+ LOG(ERROR) << "Userspace snapshots requested, but no kernel support is available.";
+ return false;
+ }
return true;
}
@@ -278,5 +282,10 @@
return fetcher->GetBoolProperty("snapuserd.test.dm.snapshots", false);
}
+bool KernelSupportsCompressedSnapshots() {
+ auto& dm = DeviceMapper::Instance();
+ return dm.GetTargetByName("user", nullptr);
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index 8c4c7c6..370f3c4 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -127,6 +127,8 @@
void AppendExtent(google::protobuf::RepeatedPtrField<chromeos_update_engine::Extent>* extents,
uint64_t start_block, uint64_t num_blocks);
+bool KernelSupportsCompressedSnapshots();
+
bool GetLegacyCompressionEnabledProperty();
bool GetUserspaceSnapshotsEnabledProperty();
bool GetIouringEnabledProperty();
diff --git a/fs_mgr/libvbmeta/utility.h b/fs_mgr/libvbmeta/utility.h
index 91db0ad..ab9828d 100644
--- a/fs_mgr/libvbmeta/utility.h
+++ b/fs_mgr/libvbmeta/utility.h
@@ -19,7 +19,7 @@
#include <android-base/logging.h>
#include <android-base/result.h>
-#define VBMETA_TAG "[libvbmeta]"
+#define VBMETA_TAG "[libvbmeta] "
#define LWARN LOG(WARNING) << VBMETA_TAG
#define LINFO LOG(INFO) << VBMETA_TAG
#define LERROR LOG(ERROR) << VBMETA_TAG
diff --git a/gatekeeperd/gatekeeperd.cpp b/gatekeeperd/gatekeeperd.cpp
index 76fcd55..eb43a33 100644
--- a/gatekeeperd/gatekeeperd.cpp
+++ b/gatekeeperd/gatekeeperd.cpp
@@ -151,7 +151,7 @@
void clear_sid(uint32_t userId) {
char filename[21];
snprintf(filename, sizeof(filename), "%u", userId);
- if (remove(filename) < 0) {
+ if (remove(filename) < 0 && errno != ENOENT) {
ALOGE("%s: could not remove file [%s], attempting 0 write", __func__, strerror(errno));
store_sid(userId, 0);
}
diff --git a/healthd/BatteryMonitor.cpp b/healthd/BatteryMonitor.cpp
index 4ea452a..66e1e63 100644
--- a/healthd/BatteryMonitor.cpp
+++ b/healthd/BatteryMonitor.cpp
@@ -138,6 +138,7 @@
mBatteryDevicePresent(false),
mBatteryFixedCapacity(0),
mBatteryFixedTemperature(0),
+ mBatteryHealthStatus(BatteryMonitor::BH_UNKNOWN),
mHealthInfo(std::make_unique<HealthInfo>()) {
initHealthInfo(mHealthInfo.get());
}
@@ -230,6 +231,23 @@
return *ret;
}
+BatteryHealth getBatteryHealthStatus(int status) {
+ BatteryHealth value;
+
+ if (status == BatteryMonitor::BH_NOMINAL)
+ value = BatteryHealth::GOOD;
+ else if (status == BatteryMonitor::BH_MARGINAL)
+ value = BatteryHealth::FAIR;
+ else if (status == BatteryMonitor::BH_NEEDS_REPLACEMENT)
+ value = BatteryHealth::DEAD;
+ else if (status == BatteryMonitor::BH_FAILED)
+ value = BatteryHealth::UNSPECIFIED_FAILURE;
+ else
+ value = BatteryHealth::UNKNOWN;
+
+ return value;
+}
+
BatteryChargingPolicy getBatteryChargingPolicy(const char* chargingPolicy) {
static SysfsStringEnumMap<BatteryChargingPolicy> batteryChargingPolicyMap[] = {
{"0", BatteryChargingPolicy::INVALID}, {"1", BatteryChargingPolicy::DEFAULT},
@@ -374,8 +392,12 @@
mHealthInfo->batteryFullChargeDesignCapacityUah =
getIntField(mHealthdConfig->batteryFullChargeDesignCapacityUahPath);
+ if (!mHealthdConfig->batteryHealthStatusPath.isEmpty())
+ mBatteryHealthStatus = getIntField(mHealthdConfig->batteryHealthStatusPath);
+
if (!mHealthdConfig->batteryStateOfHealthPath.isEmpty())
- mHealthInfo->batteryStateOfHealth = getIntField(mHealthdConfig->batteryStateOfHealthPath);
+ mHealthInfo->batteryHealthData->batteryStateOfHealth =
+ getIntField(mHealthdConfig->batteryStateOfHealthPath);
if (!mHealthdConfig->batteryManufacturingDatePath.isEmpty())
mHealthInfo->batteryHealthData->batteryManufacturingDateSeconds =
@@ -397,8 +419,13 @@
if (readFromFile(mHealthdConfig->batteryStatusPath, &buf) > 0)
mHealthInfo->batteryStatus = getBatteryStatus(buf.c_str());
- if (readFromFile(mHealthdConfig->batteryHealthPath, &buf) > 0)
- mHealthInfo->batteryHealth = getBatteryHealth(buf.c_str());
+ // Backward compatible with android.hardware.health V1
+ if (mBatteryHealthStatus < BatteryMonitor::BH_MARGINAL) {
+ if (readFromFile(mHealthdConfig->batteryHealthPath, &buf) > 0)
+ mHealthInfo->batteryHealth = getBatteryHealth(buf.c_str());
+ } else {
+ mHealthInfo->batteryHealth = getBatteryHealthStatus(mBatteryHealthStatus);
+ }
if (readFromFile(mHealthdConfig->batteryTechnologyPath, &buf) > 0)
mHealthInfo->batteryTechnology = String8(buf.c_str());
@@ -565,6 +592,10 @@
if (!mHealthdConfig->batteryFirstUsageDatePath.isEmpty())
return getIntField(mHealthdConfig->batteryFirstUsageDatePath);
}
+ if (id == BATTERY_PROP_STATE_OF_HEALTH) {
+ if (!mHealthdConfig->batteryStateOfHealthPath.isEmpty())
+ return getIntField(mHealthdConfig->batteryStateOfHealthPath);
+ }
return 0;
}
@@ -643,6 +674,11 @@
ret = OK;
break;
+ case BATTERY_PROP_STATE_OF_HEALTH:
+ val->valueInt64 = getBatteryHealthData(BATTERY_PROP_STATE_OF_HEALTH);
+ ret = OK;
+ break;
+
default:
break;
}
@@ -878,6 +914,12 @@
}
}
+ if (mHealthdConfig->batteryHealthStatusPath.isEmpty()) {
+ path.clear();
+ path.appendFormat("%s/%s/health_status", POWER_SUPPLY_SYSFS_PATH, name);
+ if (access(path, R_OK) == 0) mHealthdConfig->batteryHealthStatusPath = path;
+ }
+
if (mHealthdConfig->batteryManufacturingDatePath.isEmpty()) {
path.clear();
path.appendFormat("%s/%s/manufacturing_date", POWER_SUPPLY_SYSFS_PATH, name);
@@ -957,6 +999,8 @@
KLOG_WARNING(LOG_TAG, "batteryFullChargeDesignCapacityUahPath. not found\n");
if (mHealthdConfig->batteryStateOfHealthPath.isEmpty())
KLOG_WARNING(LOG_TAG, "batteryStateOfHealthPath not found\n");
+ if (mHealthdConfig->batteryHealthStatusPath.isEmpty())
+ KLOG_WARNING(LOG_TAG, "batteryHealthStatusPath not found\n");
if (mHealthdConfig->batteryManufacturingDatePath.isEmpty())
KLOG_WARNING(LOG_TAG, "batteryManufacturingDatePath not found\n");
if (mHealthdConfig->batteryFirstUsageDatePath.isEmpty())
diff --git a/healthd/include/healthd/BatteryMonitor.h b/healthd/include/healthd/BatteryMonitor.h
index 4af2a87..7b4f46c 100644
--- a/healthd/include/healthd/BatteryMonitor.h
+++ b/healthd/include/healthd/BatteryMonitor.h
@@ -56,6 +56,14 @@
ANDROID_POWER_SUPPLY_TYPE_DOCK
};
+ enum BatteryHealthStatus {
+ BH_UNKNOWN = -1,
+ BH_NOMINAL,
+ BH_MARGINAL,
+ BH_NEEDS_REPLACEMENT,
+ BH_FAILED,
+ };
+
BatteryMonitor();
~BatteryMonitor();
void init(struct healthd_config *hc);
@@ -85,6 +93,7 @@
bool mBatteryDevicePresent;
int mBatteryFixedCapacity;
int mBatteryFixedTemperature;
+ int mBatteryHealthStatus;
std::unique_ptr<aidl::android::hardware::health::HealthInfo> mHealthInfo;
};
diff --git a/healthd/include/healthd/healthd.h b/healthd/include/healthd/healthd.h
index e1c357c..688e458 100644
--- a/healthd/include/healthd/healthd.h
+++ b/healthd/include/healthd/healthd.h
@@ -73,6 +73,7 @@
android::String8 batteryChargeTimeToFullNowPath;
android::String8 batteryFullChargeDesignCapacityUahPath;
android::String8 batteryStateOfHealthPath;
+ android::String8 batteryHealthStatusPath;
android::String8 batteryManufacturingDatePath;
android::String8 batteryFirstUsageDatePath;
android::String8 chargingStatePath;
diff --git a/init/OWNERS b/init/OWNERS
index 4604d06..68b9396 100644
--- a/init/OWNERS
+++ b/init/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 1312227
dvander@google.com
jiyong@google.com
diff --git a/init/action.cpp b/init/action.cpp
index 1e998ae..18f6360 100644
--- a/init/action.cpp
+++ b/init/action.cpp
@@ -30,7 +30,7 @@
Result<void> RunBuiltinFunction(const BuiltinFunction& function,
const std::vector<std::string>& args, const std::string& context) {
- auto builtin_arguments = BuiltinArguments(context);
+ BuiltinArguments builtin_arguments{.context = context};
builtin_arguments.args.resize(args.size());
builtin_arguments.args[0] = args[0];
@@ -69,7 +69,7 @@
}
Result<void> Command::CheckCommand() const {
- auto builtin_arguments = BuiltinArguments("host_init_verifier");
+ BuiltinArguments builtin_arguments{.context = "host_init_verifier"};
builtin_arguments.args.resize(args_.size());
builtin_arguments.args[0] = args_[0];
diff --git a/init/builtin_arguments.h b/init/builtin_arguments.h
index 1742b78..890a216 100644
--- a/init/builtin_arguments.h
+++ b/init/builtin_arguments.h
@@ -24,10 +24,6 @@
namespace init {
struct BuiltinArguments {
- BuiltinArguments(const std::string& context) : context(context) {}
- BuiltinArguments(std::vector<std::string> args, const std::string& context)
- : args(std::move(args)), context(context) {}
-
const std::string& operator[](std::size_t i) const { return args[i]; }
auto begin() const { return args.begin(); }
auto end() const { return args.end(); }
diff --git a/init/builtins.cpp b/init/builtins.cpp
index a89813e..bc23972 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -1074,7 +1074,7 @@
static Result<void> do_restorecon_recursive(const BuiltinArguments& args) {
std::vector<std::string> non_const_args(args.args);
non_const_args.insert(std::next(non_const_args.begin()), "--recursive");
- return do_restorecon({std::move(non_const_args), args.context});
+ return do_restorecon({.args = std::move(non_const_args), .context = args.context});
}
static Result<void> do_loglevel(const BuiltinArguments& args) {
diff --git a/init/check_builtins.cpp b/init/check_builtins.cpp
index 481fa31..461ed22 100644
--- a/init/check_builtins.cpp
+++ b/init/check_builtins.cpp
@@ -85,7 +85,7 @@
}
Result<void> check_exec_reboot_on_failure(const BuiltinArguments& args) {
- BuiltinArguments remaining_args(args.context);
+ BuiltinArguments remaining_args{.context = args.context};
remaining_args.args = std::vector<std::string>(args.begin() + 1, args.end());
remaining_args.args[0] = args[0];
diff --git a/init/init.cpp b/init/init.cpp
index 4262191..be1ebee 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -51,6 +51,7 @@
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include <android-base/thread_annotations.h>
#include <fs_avb/fs_avb.h>
#include <fs_mgr_vendor_overlay.h>
#include <keyutils.h>
@@ -211,16 +212,16 @@
}
private:
- void ResetWaitForPropLocked() {
+ void ResetWaitForPropLocked() EXCLUSIVE_LOCKS_REQUIRED(lock_) {
wait_prop_name_.clear();
wait_prop_value_.clear();
waiting_for_prop_.reset();
}
std::mutex lock_;
- std::unique_ptr<Timer> waiting_for_prop_{nullptr};
- std::string wait_prop_name_;
- std::string wait_prop_value_;
+ GUARDED_BY(lock_) std::unique_ptr<Timer> waiting_for_prop_{nullptr};
+ GUARDED_BY(lock_) std::string wait_prop_name_;
+ GUARDED_BY(lock_) std::string wait_prop_value_;
} prop_waiter_state;
@@ -246,48 +247,21 @@
WakeMainInitThread();
}
- std::optional<std::string> CheckShutdown() {
+ std::optional<std::string> CheckShutdown() __attribute__((warn_unused_result)) {
auto lock = std::lock_guard{shutdown_command_lock_};
if (do_shutdown_ && !IsShuttingDown()) {
+ do_shutdown_ = false;
return shutdown_command_;
}
return {};
}
- bool do_shutdown() const { return do_shutdown_; }
- void set_do_shutdown(bool value) { do_shutdown_ = value; }
-
private:
std::mutex shutdown_command_lock_;
- std::string shutdown_command_;
+ std::string shutdown_command_ GUARDED_BY(shutdown_command_lock_);
bool do_shutdown_ = false;
} shutdown_state;
-static void UnwindMainThreadStack() {
- unwindstack::AndroidLocalUnwinder unwinder;
- unwindstack::AndroidUnwinderData data;
- if (!unwinder.Unwind(data)) {
- LOG(ERROR) << __FUNCTION__
- << "sys.powerctl: Failed to unwind callstack: " << data.GetErrorString();
- }
- for (const auto& frame : data.frames) {
- LOG(ERROR) << "sys.powerctl: " << unwinder.FormatFrame(frame);
- }
-}
-
-void DebugRebootLogging() {
- LOG(INFO) << "sys.powerctl: do_shutdown: " << shutdown_state.do_shutdown()
- << " IsShuttingDown: " << IsShuttingDown();
- if (shutdown_state.do_shutdown()) {
- LOG(ERROR) << "sys.powerctl set while a previous shutdown command has not been handled";
- UnwindMainThreadStack();
- }
- if (IsShuttingDown()) {
- LOG(ERROR) << "sys.powerctl set while init is already shutting down";
- UnwindMainThreadStack();
- }
-}
-
void DumpState() {
ServiceList::GetInstance().DumpState();
ActionManager::GetInstance().DumpState();
@@ -952,6 +926,8 @@
InitKernelLogging(argv);
LOG(INFO) << "init second stage started!";
+ SelinuxSetupKernelLogging();
+
// Update $PATH in the case the second stage init is newer than first stage init, where it is
// first set.
if (setenv("PATH", _PATH_DEFPATH, 1) != 0) {
@@ -1012,7 +988,6 @@
MountExtraFilesystems();
// Now set up SELinux for second stage.
- SelinuxSetupKernelLogging();
SelabelInitialize();
SelinuxRestoreContext();
@@ -1107,36 +1082,43 @@
// Restore prio before main loop
setpriority(PRIO_PROCESS, 0, 0);
while (true) {
- // By default, sleep until something happens.
- std::optional<std::chrono::milliseconds> epoll_timeout;
+ // By default, sleep until something happens. Do not convert far_future into
+ // std::chrono::milliseconds because that would trigger an overflow. The unit of boot_clock
+ // is 1ns.
+ const boot_clock::time_point far_future = boot_clock::time_point::max();
+ boot_clock::time_point next_action_time = far_future;
auto shutdown_command = shutdown_state.CheckShutdown();
if (shutdown_command) {
LOG(INFO) << "Got shutdown_command '" << *shutdown_command
<< "' Calling HandlePowerctlMessage()";
HandlePowerctlMessage(*shutdown_command);
- shutdown_state.set_do_shutdown(false);
}
if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
am.ExecuteOneCommand();
+ // If there's more work to do, wake up again immediately.
+ if (am.HasMoreCommands()) {
+ next_action_time = boot_clock::now();
+ }
}
+ // Since the above code examined pending actions, no new actions must be
+ // queued by the code between this line and the Epoll::Wait() call below
+ // without calling WakeMainInitThread().
if (!IsShuttingDown()) {
auto next_process_action_time = HandleProcessActions();
// If there's a process that needs restarting, wake up in time for that.
if (next_process_action_time) {
- epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
- *next_process_action_time - boot_clock::now());
- if (epoll_timeout < 0ms) epoll_timeout = 0ms;
+ next_action_time = std::min(next_action_time, *next_process_action_time);
}
}
- if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
- // If there's more work to do, wake up again immediately.
- if (am.HasMoreCommands()) epoll_timeout = 0ms;
+ std::optional<std::chrono::milliseconds> epoll_timeout;
+ if (next_action_time != far_future) {
+ epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
+ std::max(next_action_time - boot_clock::now(), 0ns));
}
-
auto epoll_result = epoll.Wait(epoll_timeout);
if (!epoll_result.ok()) {
LOG(ERROR) << epoll_result.error();
diff --git a/init/init.h b/init/init.h
index 063632a..9c7e918 100644
--- a/init/init.h
+++ b/init/init.h
@@ -42,8 +42,6 @@
void PropertyChanged(const std::string& name, const std::string& value);
bool QueueControlMessage(const std::string& message, const std::string& name, pid_t pid, int fd);
-void DebugRebootLogging();
-
int SecondStageMain(int argc, char** argv);
int StopServicesFromApex(const std::string& apex_name);
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 1e69ede..305bf95 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <fstream>
#include <functional>
#include <string_view>
#include <thread>
@@ -22,6 +23,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
#include <android/api-level.h>
#include <gtest/gtest.h>
#include <selinux/selinux.h>
@@ -44,6 +46,7 @@
using android::base::GetIntProperty;
using android::base::GetProperty;
using android::base::SetProperty;
+using android::base::StringPrintf;
using android::base::StringReplace;
using android::base::WaitForProperty;
using namespace std::literals;
@@ -643,34 +646,32 @@
ASSERT_LE(curr_limit.rlim_max, max_limit);
}
-static std::vector<const char*> ConvertToArgv(const std::vector<std::string>& args) {
- std::vector<const char*> argv;
- argv.reserve(args.size() + 1);
- for (const auto& arg : args) {
- if (argv.empty()) {
- LOG(DEBUG) << arg;
- } else {
- LOG(DEBUG) << " " << arg;
+void CloseAllFds() {
+ DIR* dir;
+ struct dirent* ent;
+ int fd;
+
+ if ((dir = opendir("/proc/self/fd"))) {
+ while ((ent = readdir(dir))) {
+ if (sscanf(ent->d_name, "%d", &fd) == 1) {
+ close(fd);
+ }
}
- argv.emplace_back(arg.data());
+ closedir(dir);
}
- argv.emplace_back(nullptr);
- return argv;
}
-pid_t ForkExecvpAsync(const std::vector<std::string>& args) {
- auto argv = ConvertToArgv(args);
-
+pid_t ForkExecvpAsync(const char* argv[]) {
pid_t pid = fork();
if (pid == 0) {
- close(STDIN_FILENO);
- close(STDOUT_FILENO);
- close(STDERR_FILENO);
+ // Child process.
+ CloseAllFds();
- execvp(argv[0], const_cast<char**>(argv.data()));
+ execvp(argv[0], const_cast<char**>(argv));
PLOG(ERROR) << "exec in ForkExecvpAsync init test";
_exit(EXIT_FAILURE);
}
+ // Parent process.
if (pid == -1) {
PLOG(ERROR) << "fork in ForkExecvpAsync init test";
return -1;
@@ -678,7 +679,23 @@
return pid;
}
+pid_t TracerPid(pid_t pid) {
+ static constexpr std::string_view prefix{"TracerPid:"};
+ std::ifstream is(StringPrintf("/proc/%d/status", pid));
+ std::string line;
+ while (std::getline(is, line)) {
+ if (line.find(prefix) == 0) {
+ return atoi(line.substr(prefix.length()).c_str());
+ }
+ }
+ return -1;
+}
+
TEST(init, GentleKill) {
+ if (getuid() != 0) {
+ GTEST_SKIP() << "Must be run as root.";
+ return;
+ }
std::string init_script = R"init(
service test_gentle_kill /system/bin/sleep 1000
disabled
@@ -705,18 +722,15 @@
logfile.DoNotRemove();
ASSERT_TRUE(logfile.fd != -1);
- std::vector<std::string> cmd;
- cmd.push_back("system/bin/strace");
- cmd.push_back("-o");
- cmd.push_back(logfile.path);
- cmd.push_back("-e");
- cmd.push_back("signal");
- cmd.push_back("-p");
- cmd.push_back(std::to_string(pid));
- pid_t strace_pid = ForkExecvpAsync(cmd);
+ std::string pid_str = std::to_string(pid);
+ const char* argv[] = {"/system/bin/strace", "-o", logfile.path, "-e", "signal", "-p",
+ pid_str.c_str(), nullptr};
+ pid_t strace_pid = ForkExecvpAsync(argv);
- // Give strace a moment to connect
- std::this_thread::sleep_for(1s);
+ // Give strace the chance to connect
+ while (TracerPid(pid) == 0) {
+ std::this_thread::sleep_for(10ms);
+ }
service->Stop();
int status;
@@ -724,8 +738,7 @@
std::string logs;
android::base::ReadFdToString(logfile.fd, &logs);
- int pos = logs.find("killed by SIGTERM");
- ASSERT_NE(pos, (int)std::string::npos);
+ ASSERT_NE(logs.find("killed by SIGTERM"), std::string::npos);
}
class TestCaseLogger : public ::testing::EmptyTestEventListener {
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 87ffdb9..8da6982 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -550,9 +550,6 @@
}
LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
<< process_log_string;
- if (!value.empty()) {
- DebugRebootLogging();
- }
if (value == "reboot,userspace" && !is_userspace_reboot_supported().value_or(false)) {
*error = "Userspace reboot is not supported by this device";
return {PROP_ERROR_INVALID_VALUE};
diff --git a/init/selinux.cpp b/init/selinux.cpp
index ea308aa..062ed39 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -629,7 +629,7 @@
}
Result<void> SepolicyFsVerityCheck() {
- return Error() << "TODO implementent support for fsverity SEPolicy.";
+ return Error() << "TODO implement support for fsverity SEPolicy.";
}
Result<void> SepolicyCheckSignature(const std::string& dir) {
@@ -897,29 +897,31 @@
continue;
}
- auto system_entry = GetEntryForMountPoint(&fstab, "/system");
- if (!system_entry) {
- LOG(ERROR) << "Could not find mount entry for /system";
- break;
- }
- if (!system_entry->fs_mgr_flags.logical) {
- LOG(INFO) << "Skipping mount of " << name << ", system is not dynamic.";
- break;
- }
+ auto system_entries = GetEntriesForMountPoint(&fstab, "/system");
+ for (auto& system_entry : system_entries) {
+ if (!system_entry) {
+ LOG(ERROR) << "Could not find mount entry for /system";
+ break;
+ }
+ if (!system_entry->fs_mgr_flags.logical) {
+ LOG(INFO) << "Skipping mount of " << name << ", system is not dynamic.";
+ break;
+ }
- auto entry = *system_entry;
- auto partition_name = name + fs_mgr_get_slot_suffix();
- auto replace_name = "system"s + fs_mgr_get_slot_suffix();
+ auto entry = *system_entry;
+ auto partition_name = name + fs_mgr_get_slot_suffix();
+ auto replace_name = "system"s + fs_mgr_get_slot_suffix();
- entry.mount_point = "/"s + name;
- entry.blk_device =
+ entry.mount_point = "/"s + name;
+ entry.blk_device =
android::base::StringReplace(entry.blk_device, replace_name, partition_name, false);
- if (!fs_mgr_update_logical_partition(&entry)) {
- LOG(ERROR) << "Could not update logical partition";
- continue;
- }
+ if (!fs_mgr_update_logical_partition(&entry)) {
+ LOG(ERROR) << "Could not update logical partition";
+ continue;
+ }
- extra_fstab.emplace_back(std::move(entry));
+ extra_fstab.emplace_back(std::move(entry));
+ }
}
SkipMountingPartitions(&extra_fstab, true /* verbose */);
diff --git a/init/service.cpp b/init/service.cpp
index 87d9c3a..8456d1e 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -447,6 +447,15 @@
return {};
}
+ // On newer kernels, /dev/console will always exist because
+ // "console=ttynull" is hard-coded in CONFIG_CMDLINE. This new boot
+ // property should be set via "androidboot.serialconsole=0" to explicitly
+ // disable services requiring the console. For older kernels and boot
+ // images, not setting this at all will fall back to the old behavior
+ if (GetProperty("ro.boot.serialconsole", "") == "0") {
+ return {};
+ }
+
if (proc_attr_.console.empty()) {
proc_attr_.console = "/dev/" + GetProperty("ro.boot.console", "console");
}
@@ -755,6 +764,9 @@
NotifyStateChange("running");
reboot_on_failure.Disable();
+
+ LOG(INFO) << "... started service '" << name_ << "' has pid " << pid_;
+
return {};
}
diff --git a/libcutils/qtaguid.cpp b/libcutils/qtaguid.cpp
index a987b85..f847782 100644
--- a/libcutils/qtaguid.cpp
+++ b/libcutils/qtaguid.cpp
@@ -22,76 +22,60 @@
#include <dlfcn.h>
#include <errno.h>
-#include <fcntl.h>
#include <inttypes.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
#include <log/log.h>
-class netdHandler {
- public:
+struct netdHandler {
int (*netdTagSocket)(int, uint32_t, uid_t);
int (*netdUntagSocket)(int);
};
-int stubTagSocket(int, uint32_t, uid_t) {
+static int stubTagSocket(int, uint32_t, uid_t) {
return -EREMOTEIO;
}
-int stubUntagSocket(int) {
+static int stubUntagSocket(int) {
return -EREMOTEIO;
}
-netdHandler initHandler(void) {
- netdHandler handler = {stubTagSocket, stubUntagSocket};
+static netdHandler initHandler(void) {
+ const netdHandler stubHandler = { stubTagSocket, stubUntagSocket };
void* netdClientHandle = dlopen("libnetd_client.so", RTLD_NOW);
if (!netdClientHandle) {
ALOGE("Failed to open libnetd_client.so: %s", dlerror());
- return handler;
+ return stubHandler;
}
+ netdHandler handler;
handler.netdTagSocket = (int (*)(int, uint32_t, uid_t))dlsym(netdClientHandle, "tagSocket");
if (!handler.netdTagSocket) {
ALOGE("load netdTagSocket handler failed: %s", dlerror());
+ return stubHandler;
}
handler.netdUntagSocket = (int (*)(int))dlsym(netdClientHandle, "untagSocket");
if (!handler.netdUntagSocket) {
ALOGE("load netdUntagSocket handler failed: %s", dlerror());
+ return stubHandler;
}
return handler;
}
// The language guarantees that this object will be initialized in a thread-safe way.
-static netdHandler& getHandler() {
- static netdHandler instance = initHandler();
+static const netdHandler& getHandler() {
+ static const netdHandler instance = initHandler();
return instance;
}
int qtaguid_tagSocket(int sockfd, int tag, uid_t uid) {
- // Check the socket fd passed to us is still valid before we load the netd
- // client. Pass a already closed socket fd to netd client may let netd open
- // the unix socket with the same fd number and pass it to server for
- // tagging.
- // TODO: move the check into netdTagSocket.
- int res = fcntl(sockfd, F_GETFD);
- if (res < 0) return res;
-
ALOGV("Tagging socket %d with tag %u for uid %d", sockfd, tag, uid);
return getHandler().netdTagSocket(sockfd, tag, uid);
}
int qtaguid_untagSocket(int sockfd) {
- // Similiar to tag socket. We need a check before untag to make sure untag a closed socket fail
- // as expected.
- // TODO: move the check into netdTagSocket.
- int res = fcntl(sockfd, F_GETFD);
- if (res < 0) return res;
-
ALOGV("Untagging socket %d", sockfd);
return getHandler().netdUntagSocket(sockfd);
}
diff --git a/libcutils/sched_policy_test.cpp b/libcutils/sched_policy_test.cpp
index b9e2832..50bd6d0 100644
--- a/libcutils/sched_policy_test.cpp
+++ b/libcutils/sched_policy_test.cpp
@@ -75,9 +75,11 @@
}
ASSERT_EQ(0, set_sched_policy(0, SP_BACKGROUND));
+ ASSERT_EQ(0, set_cpuset_policy(0, SP_BACKGROUND));
AssertPolicy(SP_BACKGROUND);
ASSERT_EQ(0, set_sched_policy(0, SP_FOREGROUND));
+ ASSERT_EQ(0, set_cpuset_policy(0, SP_FOREGROUND));
AssertPolicy(SP_FOREGROUND);
}
diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp
index 468d796..ce7f10b 100644
--- a/libprocessgroup/cgroup_map.cpp
+++ b/libprocessgroup/cgroup_map.cpp
@@ -229,12 +229,17 @@
auto controller_count = ACgroupFile_getControllerCount();
for (uint32_t i = 0; i < controller_count; ++i) {
const ACgroupController* controller = ACgroupFile_getController(i);
- if (ACgroupController_getFlags(controller) &
- CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
+ const uint32_t flags = ACgroupController_getFlags(controller);
+ if (flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
std::string str("+");
str.append(ACgroupController_getName(controller));
if (!WriteStringToFile(str, path + "/cgroup.subtree_control")) {
- return -errno;
+ if (flags & CGROUPRC_CONTROLLER_FLAG_OPTIONAL) {
+ PLOG(WARNING) << "Activation of cgroup controller " << str
+ << " failed in path " << path;
+ } else {
+ return -errno;
+ }
}
}
}
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 1da69ba..38f19ff 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -372,6 +372,7 @@
std::set<pid_t> pgids;
pgids.emplace(initialPid);
std::set<pid_t> pids;
+ int processes = 0;
std::unique_ptr<FILE, decltype(&fclose)> fd(nullptr, fclose);
@@ -390,6 +391,7 @@
pid_t pid;
bool file_is_empty = true;
while (fscanf(fd.get(), "%d\n", &pid) == 1 && pid >= 0) {
+ processes++;
file_is_empty = false;
if (pid == 0) {
// Should never happen... but if it does, trying to kill this
@@ -406,31 +408,25 @@
pids.emplace(pid);
}
}
- if (file_is_empty) {
- // This happens when process is already dead
- return 0;
- }
-
- // Erase all pids that will be killed when we kill the process groups.
- for (auto it = pids.begin(); it != pids.end();) {
- pid_t pgid = getpgid(*it);
- if (pgids.count(pgid) == 1) {
- it = pids.erase(it);
- } else {
- ++it;
+ if (!file_is_empty) {
+ // Erase all pids that will be killed when we kill the process groups.
+ for (auto it = pids.begin(); it != pids.end();) {
+ pid_t pgid = getpgid(*it);
+ if (pgids.count(pgid) == 1) {
+ it = pids.erase(it);
+ } else {
+ ++it;
+ }
}
}
}
- int processes = 0;
// Kill all process groups.
for (const auto pgid : pgids) {
LOG(VERBOSE) << "Killing process group " << -pgid << " in uid " << uid
<< " as part of process cgroup " << initialPid;
- if (kill(-pgid, signal) == 0) {
- processes++;
- } else if (errno != ESRCH) {
+ if (kill(-pgid, signal) == -1 && errno != ESRCH) {
PLOG(WARNING) << "kill(" << -pgid << ", " << signal << ") failed";
}
}
@@ -440,9 +436,7 @@
LOG(VERBOSE) << "Killing pid " << pid << " in uid " << uid << " as part of process cgroup "
<< initialPid;
- if (kill(pid, signal) == 0) {
- processes++;
- } else if (errno != ESRCH) {
+ if (kill(pid, signal) == -1 && errno != ESRCH) {
PLOG(WARNING) << "kill(" << pid << ", " << signal << ") failed";
}
}
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index 304248a..fbeedf9 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -254,86 +254,64 @@
// To avoid issues in sdk_mac build
#if defined(__ANDROID__)
-static bool SetupCgroup(const CgroupDescriptor& descriptor) {
+static bool IsOptionalController(const format::CgroupController* controller) {
+ return controller->flags() & CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
+}
+
+static bool MountV2CgroupController(const CgroupDescriptor& descriptor) {
const format::CgroupController* controller = descriptor.controller();
- int result;
- if (controller->version() == 2) {
- result = 0;
- if (!strcmp(controller->name(), CGROUPV2_CONTROLLER_NAME)) {
- // /sys/fs/cgroup is created by cgroup2 with specific selinux permissions,
- // try to create again in case the mount point is changed
- if (!Mkdir(controller->path(), 0, "", "")) {
- LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
- return false;
- }
+ // /sys/fs/cgroup is created by cgroup2 with specific selinux permissions,
+ // try to create again in case the mount point is changed
+ if (!Mkdir(controller->path(), 0, "", "")) {
+ LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
+ return false;
+ }
- // The memory_recursiveprot mount option has been introduced by kernel commit
- // 8a931f801340 ("mm: memcontrol: recursive memory.low protection"; v5.7). Try first to
- // mount with that option enabled. If mounting fails because the kernel is too old,
- // retry without that mount option.
- if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
- "memory_recursiveprot") < 0) {
- LOG(INFO) << "Mounting memcg with memory_recursiveprot failed. Retrying without.";
- if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
- nullptr) < 0) {
- PLOG(ERROR) << "Failed to mount cgroup v2";
- }
- }
-
- // selinux permissions change after mounting, so it's ok to change mode and owner now
- if (!ChangeDirModeAndOwner(controller->path(), descriptor.mode(), descriptor.uid(),
- descriptor.gid())) {
- LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
- result = -1;
- }
- } else {
- if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
- LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
- return false;
- }
-
- if (controller->flags() & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
- std::string str = std::string("+") + controller->name();
- std::string path = std::string(controller->path()) + "/cgroup.subtree_control";
-
- if (!base::WriteStringToFile(str, path)) {
- LOG(ERROR) << "Failed to activate controller " << controller->name();
- return false;
- }
- }
- }
- } else {
- // mkdir <path> [mode] [owner] [group]
- if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
- LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
- return false;
- }
-
- // Unfortunately historically cpuset controller was mounted using a mount command
- // different from all other controllers. This results in controller attributes not
- // to be prepended with controller name. For example this way instead of
- // /dev/cpuset/cpuset.cpus the attribute becomes /dev/cpuset/cpus which is what
- // the system currently expects.
- if (!strcmp(controller->name(), "cpuset")) {
- // mount cpuset none /dev/cpuset nodev noexec nosuid
- result = mount("none", controller->path(), controller->name(),
- MS_NODEV | MS_NOEXEC | MS_NOSUID, nullptr);
- } else {
- // mount cgroup none <path> nodev noexec nosuid <controller>
- result = mount("none", controller->path(), "cgroup", MS_NODEV | MS_NOEXEC | MS_NOSUID,
- controller->name());
+ // The memory_recursiveprot mount option has been introduced by kernel commit
+ // 8a931f801340 ("mm: memcontrol: recursive memory.low protection"; v5.7). Try first to
+ // mount with that option enabled. If mounting fails because the kernel is too old,
+ // retry without that mount option.
+ if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
+ "memory_recursiveprot") < 0) {
+ LOG(INFO) << "Mounting memcg with memory_recursiveprot failed. Retrying without.";
+ if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
+ nullptr) < 0) {
+ PLOG(ERROR) << "Failed to mount cgroup v2";
+ return IsOptionalController(controller);
}
}
- if (result < 0) {
- bool optional = controller->flags() & CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
+ // selinux permissions change after mounting, so it's ok to change mode and owner now
+ if (!ChangeDirModeAndOwner(controller->path(), descriptor.mode(), descriptor.uid(),
+ descriptor.gid())) {
+ PLOG(ERROR) << "Change of ownership or mode failed for controller " << controller->name();
+ return IsOptionalController(controller);
+ }
- if (optional && errno == EINVAL) {
- // Optional controllers are allowed to fail to mount if kernel does not support them
- LOG(INFO) << "Optional " << controller->name() << " cgroup controller is not mounted";
- } else {
- PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup";
+ return true;
+}
+
+static bool ActivateV2CgroupController(const CgroupDescriptor& descriptor) {
+ const format::CgroupController* controller = descriptor.controller();
+
+ if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
+ LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
+ return false;
+ }
+
+ if (controller->flags() & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
+ std::string str = "+";
+ str += controller->name();
+ std::string path = controller->path();
+ path += "/cgroup.subtree_control";
+
+ if (!base::WriteStringToFile(str, path)) {
+ if (IsOptionalController(controller)) {
+ PLOG(INFO) << "Failed to activate optional controller " << controller->name();
+ return true;
+ }
+ PLOG(ERROR) << "Failed to activate controller " << controller->name();
return false;
}
}
@@ -341,6 +319,55 @@
return true;
}
+static bool MountV1CgroupController(const CgroupDescriptor& descriptor) {
+ const format::CgroupController* controller = descriptor.controller();
+
+ // mkdir <path> [mode] [owner] [group]
+ if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
+ LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
+ return false;
+ }
+
+ // Unfortunately historically cpuset controller was mounted using a mount command
+ // different from all other controllers. This results in controller attributes not
+ // to be prepended with controller name. For example this way instead of
+ // /dev/cpuset/cpuset.cpus the attribute becomes /dev/cpuset/cpus which is what
+ // the system currently expects.
+ int res;
+ if (!strcmp(controller->name(), "cpuset")) {
+ // mount cpuset none /dev/cpuset nodev noexec nosuid
+ res = mount("none", controller->path(), controller->name(),
+ MS_NODEV | MS_NOEXEC | MS_NOSUID, nullptr);
+ } else {
+ // mount cgroup none <path> nodev noexec nosuid <controller>
+ res = mount("none", controller->path(), "cgroup", MS_NODEV | MS_NOEXEC | MS_NOSUID,
+ controller->name());
+ }
+ if (res != 0) {
+ if (IsOptionalController(controller)) {
+ PLOG(INFO) << "Failed to mount optional controller " << controller->name();
+ return true;
+ }
+ PLOG(ERROR) << "Failed to mount controller " << controller->name();
+ return false;
+ }
+ return true;
+}
+
+static bool SetupCgroup(const CgroupDescriptor& descriptor) {
+ const format::CgroupController* controller = descriptor.controller();
+
+ if (controller->version() == 2) {
+ if (!strcmp(controller->name(), CGROUPV2_CONTROLLER_NAME)) {
+ return MountV2CgroupController(descriptor);
+ } else {
+ return ActivateV2CgroupController(descriptor);
+ }
+ } else {
+ return MountV1CgroupController(descriptor);
+ }
+}
+
#else
// Stubs for non-Android targets.
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 35adf36..4db7372 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -815,11 +815,11 @@
profile->EnableResourceCaching(ProfileAction::RCT_PROCESS);
}
if (!profile->ExecuteForProcess(uid, pid)) {
- PLOG(WARNING) << "Failed to apply " << name << " process profile";
+ LOG(WARNING) << "Failed to apply " << name << " process profile";
success = false;
}
} else {
- PLOG(WARNING) << "Failed to find " << name << " process profile";
+ LOG(WARNING) << "Failed to find " << name << " process profile";
success = false;
}
}
@@ -836,11 +836,11 @@
profile->EnableResourceCaching(ProfileAction::RCT_TASK);
}
if (!profile->ExecuteForTask(tid)) {
- PLOG(WARNING) << "Failed to apply " << name << " task profile";
+ LOG(WARNING) << "Failed to apply " << name << " task profile";
success = false;
}
} else {
- PLOG(WARNING) << "Failed to find " << name << " task profile";
+ LOG(WARNING) << "Failed to find " << name << " task profile";
success = false;
}
}
diff --git a/libstats/expresslog/Android.bp b/libstats/expresslog/Android.bp
index 9cdc2c3..004f8b9 100644
--- a/libstats/expresslog/Android.bp
+++ b/libstats/expresslog/Android.bp
@@ -18,11 +18,17 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-cc_library {
- name: "libexpresslog",
+cc_defaults {
+ name: "expresslog_defaults",
srcs: [
"Counter.cpp",
+ "Histogram.cpp",
],
+}
+
+cc_library {
+ name: "libexpresslog",
+ defaults: ["expresslog_defaults"],
cflags: [
"-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
"-Wall",
@@ -37,6 +43,7 @@
],
shared_libs: [
"libbase",
+ "liblog",
"libstatssocket",
],
export_include_dirs: ["include"],
@@ -69,3 +76,38 @@
"libstatssocket",
],
}
+
+cc_test {
+ name: "expresslog_test",
+ defaults: ["expresslog_defaults"],
+ test_suites: [
+ "general-tests",
+ ],
+ srcs: [
+ "tests/Histogram_test.cpp",
+ ],
+ local_include_dirs: [
+ "include",
+ ],
+ cflags: [
+ "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
+ "-Wall",
+ "-Wextra",
+ "-Wunused",
+ "-Wpedantic",
+ "-Werror",
+ ],
+ header_libs: [
+ "libtextclassifier_hash_headers",
+ ],
+ static_libs: [
+ "libgmock",
+ "libbase",
+ "liblog",
+ "libstatslog_express",
+ "libtextclassifier_hash_static",
+ ],
+ shared_libs: [
+ "libstatssocket",
+ ]
+}
diff --git a/libstats/expresslog/Histogram.cpp b/libstats/expresslog/Histogram.cpp
new file mode 100644
index 0000000..cb29a00
--- /dev/null
+++ b/libstats/expresslog/Histogram.cpp
@@ -0,0 +1,75 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "include/Histogram.h"
+
+#define LOG_TAG "tex"
+
+#include <log/log.h>
+#include <statslog_express.h>
+#include <string.h>
+#include <utils/hash/farmhash.h>
+
+namespace android {
+namespace expresslog {
+
+std::shared_ptr<Histogram::UniformOptions> Histogram::UniformOptions::create(
+ int binCount, float minValue, float exclusiveMaxValue) {
+ if (binCount < 1) {
+ ALOGE("Bin count should be positive number");
+ return nullptr;
+ }
+
+ if (exclusiveMaxValue <= minValue) {
+ ALOGE("Bins range invalid (maxValue < minValue)");
+ return nullptr;
+ }
+
+ return std::shared_ptr<UniformOptions>(
+ new UniformOptions(binCount, minValue, exclusiveMaxValue));
+}
+
+Histogram::UniformOptions::UniformOptions(int binCount, float minValue, float exclusiveMaxValue)
+ : // Implicitly add 2 for the extra undeflow & overflow bins
+ mBinCount(binCount + 2),
+ mMinValue(minValue),
+ mExclusiveMaxValue(exclusiveMaxValue),
+ mBinSize((exclusiveMaxValue - minValue) / binCount) {
+}
+
+int Histogram::UniformOptions::getBinForSample(float sample) const {
+ if (sample < mMinValue) {
+ // goes to underflow
+ return 0;
+ } else if (sample >= mExclusiveMaxValue) {
+ // goes to overflow
+ return mBinCount - 1;
+ }
+ return (int)((sample - mMinValue) / mBinSize + 1);
+}
+
+Histogram::Histogram(const char* metricName, std::shared_ptr<BinOptions> binOptions)
+ : mMetricIdHash(farmhash::Fingerprint64(metricName, strlen(metricName))),
+ mBinOptions(std::move(binOptions)) {
+}
+
+void Histogram::logSample(float sample) const {
+ const int binIndex = mBinOptions->getBinForSample(sample);
+ stats_write(EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash, /*count*/ 1, binIndex);
+}
+
+} // namespace expresslog
+} // namespace android
diff --git a/libstats/expresslog/include/Histogram.h b/libstats/expresslog/include/Histogram.h
new file mode 100644
index 0000000..8fdc1b6
--- /dev/null
+++ b/libstats/expresslog/include/Histogram.h
@@ -0,0 +1,81 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#pragma once
+#include <stdint.h>
+
+#include <memory>
+
+namespace android {
+namespace expresslog {
+
+/** Histogram encapsulates StatsD write API calls */
+class Histogram final {
+public:
+ class BinOptions {
+ public:
+ virtual ~BinOptions() = default;
+ /**
+ * Returns bins count to be used by a Histogram
+ *
+ * @return bins count used to initialize Options, including overflow & underflow bins
+ */
+ virtual int getBinsCount() const = 0;
+
+ /**
+ * @return zero based index
+ * Calculates bin index for the input sample value
+ * index == 0 stands for underflow
+ * index == getBinsCount() - 1 stands for overflow
+ */
+ virtual int getBinForSample(float sample) const = 0;
+ };
+
+ /** Used by Histogram to map data sample to corresponding bin for uniform bins */
+ class UniformOptions : public BinOptions {
+ public:
+ static std::shared_ptr<UniformOptions> create(int binCount, float minValue,
+ float exclusiveMaxValue);
+
+ int getBinsCount() const override {
+ return mBinCount;
+ }
+
+ int getBinForSample(float sample) const override;
+
+ private:
+ UniformOptions(int binCount, float minValue, float exclusiveMaxValue);
+
+ const int mBinCount;
+ const float mMinValue;
+ const float mExclusiveMaxValue;
+ const float mBinSize;
+ };
+
+ Histogram(const char* metricName, std::shared_ptr<BinOptions> binOptions);
+
+ /**
+ * Logs increment sample count for automatically calculated bin
+ */
+ void logSample(float sample) const;
+
+private:
+ const int64_t mMetricIdHash;
+ const std::shared_ptr<BinOptions> mBinOptions;
+};
+
+} // namespace expresslog
+} // namespace android
diff --git a/libstats/expresslog/tests/Histogram_test.cpp b/libstats/expresslog/tests/Histogram_test.cpp
new file mode 100644
index 0000000..813c997
--- /dev/null
+++ b/libstats/expresslog/tests/Histogram_test.cpp
@@ -0,0 +1,128 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "Histogram.h"
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace expresslog {
+
+#ifdef __ANDROID__
+TEST(UniformOptions, getBinsCount) {
+ const std::shared_ptr<Histogram::UniformOptions> options1(
+ Histogram::UniformOptions::create(1, 100, 1000));
+ ASSERT_EQ(3, options1->getBinsCount());
+
+ const std::shared_ptr<Histogram::UniformOptions> options10(
+ Histogram::UniformOptions::create(10, 100, 1000));
+ ASSERT_EQ(12, options10->getBinsCount());
+}
+
+TEST(UniformOptions, constructZeroBinsCount) {
+ const std::shared_ptr<Histogram::UniformOptions> options(
+ Histogram::UniformOptions::create(0, 100, 1000));
+ ASSERT_EQ(nullptr, options);
+}
+
+TEST(UniformOptions, constructNegativeBinsCount) {
+ const std::shared_ptr<Histogram::UniformOptions> options(
+ Histogram::UniformOptions::create(-1, 100, 1000));
+ ASSERT_EQ(nullptr, options);
+}
+
+TEST(UniformOptions, constructMaxValueLessThanMinValue) {
+ const std::shared_ptr<Histogram::UniformOptions> options(
+ Histogram::UniformOptions::create(10, 1000, 100));
+ ASSERT_EQ(nullptr, options);
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual1) {
+ const std::shared_ptr<Histogram::UniformOptions> options(
+ Histogram::UniformOptions::create(10, 1, 11));
+ for (int i = 0, bins = options->getBinsCount(); i < bins; i++) {
+ ASSERT_EQ(i, options->getBinForSample(i));
+ }
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual2) {
+ const std::shared_ptr<Histogram::UniformOptions> options(
+ Histogram::UniformOptions::create(10, 1, 21));
+ for (int i = 0, bins = options->getBinsCount(); i < bins; i++) {
+ ASSERT_EQ(i, options->getBinForSample(i * 2));
+ ASSERT_EQ(i, options->getBinForSample(i * 2 - 1));
+ }
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual5) {
+ const std::shared_ptr<Histogram::UniformOptions> options(
+ Histogram::UniformOptions::create(2, 0, 10));
+ ASSERT_EQ(4, options->getBinsCount());
+ for (int i = 0; i < 2; i++) {
+ for (int sample = 0; sample < 5; sample++) {
+ ASSERT_EQ(i + 1, options->getBinForSample(i * 5 + sample));
+ }
+ }
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual10) {
+ const std::shared_ptr<Histogram::UniformOptions> options(
+ Histogram::UniformOptions::create(10, 1, 101));
+ ASSERT_EQ(0, options->getBinForSample(0));
+ ASSERT_EQ(options->getBinsCount() - 2, options->getBinForSample(100));
+ ASSERT_EQ(options->getBinsCount() - 1, options->getBinForSample(101));
+
+ const float binSize = (101 - 1) / 10.f;
+ for (int i = 1, bins = options->getBinsCount() - 1; i < bins; i++) {
+ ASSERT_EQ(i, options->getBinForSample(i * binSize));
+ }
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual90) {
+ const int binCount = 10;
+ const int minValue = 100;
+ const int maxValue = 100000;
+
+ const std::shared_ptr<Histogram::UniformOptions> options(
+ Histogram::UniformOptions::create(binCount, minValue, maxValue));
+
+ // logging underflow sample
+ ASSERT_EQ(0, options->getBinForSample(minValue - 1));
+
+ // logging overflow sample
+ ASSERT_EQ(binCount + 1, options->getBinForSample(maxValue));
+ ASSERT_EQ(binCount + 1, options->getBinForSample(maxValue + 1));
+
+ // logging min edge sample
+ ASSERT_EQ(1, options->getBinForSample(minValue));
+
+ // logging max edge sample
+ ASSERT_EQ(binCount, options->getBinForSample(maxValue - 1));
+
+ // logging single valid sample per bin
+ const int binSize = (maxValue - minValue) / binCount;
+
+ for (int i = 0; i < binCount; i++) {
+ ASSERT_EQ(i + 1, options->getBinForSample(minValue + binSize * i));
+ }
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+} // namespace expresslog
+} // namespace android
diff --git a/libutils/ProcessCallStack_fuzz.cpp b/libutils/ProcessCallStack_fuzz.cpp
index 30136cd..552a11e 100644
--- a/libutils/ProcessCallStack_fuzz.cpp
+++ b/libutils/ProcessCallStack_fuzz.cpp
@@ -44,7 +44,7 @@
dataProvider->ConsumeRandomLengthString(MAX_NAME_SIZE).append(std::to_string(i));
std::thread th = std::thread(loop);
pthread_setname_np(th.native_handle(), threadName.c_str());
- threads.push_back(move(th));
+ threads.push_back(std::move(th));
}
// Collect thread information
diff --git a/libutils/RefBase_fuzz.cpp b/libutils/RefBase_fuzz.cpp
index 69288b3..8291be9 100644
--- a/libutils/RefBase_fuzz.cpp
+++ b/libutils/RefBase_fuzz.cpp
@@ -177,7 +177,7 @@
uint8_t opCount = dataProvider->ConsumeIntegralInRange<uint8_t>(1, kMaxOperations);
std::vector<uint8_t> threadOperations = dataProvider->ConsumeBytes<uint8_t>(opCount);
std::thread tmpThread = std::thread(loop, threadOperations);
- threads.push_back(move(tmpThread));
+ threads.push_back(std::move(tmpThread));
}
for (auto& th : threads) {
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 3dd269a..3362872 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -222,6 +222,8 @@
LOCAL_SRC_FILES := $(LOCAL_MODULE)
LOCAL_MODULE_PATH := $(PRODUCT_OUT)
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
include $(BUILD_PREBUILT)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/rootdir/init.rc b/rootdir/init.rc
index db5113f..68191bb 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -925,6 +925,10 @@
mkdir /data/system/dropbox 0700 system system
mkdir /data/system/heapdump 0700 system system
mkdir /data/system/users 0775 system system
+ # Mkdir and set SELinux security contexts for shutdown-checkpoints.
+ # TODO(b/270286197): remove these after couple releases.
+ mkdir /data/system/shutdown-checkpoints 0700 system system
+ restorecon_recursive /data/system/shutdown-checkpoints
# Create the parent directories of the user CE and DE storage directories.
# These parent directories must use encryption=None, since each of their
@@ -993,6 +997,7 @@
# Create directories for statsd
mkdir /data/misc/stats-active-metric/ 0770 statsd system
mkdir /data/misc/stats-data/ 0770 statsd system
+ mkdir /data/misc/stats-data/restricted-data 0770 statsd system
mkdir /data/misc/stats-metadata/ 0770 statsd system
mkdir /data/misc/stats-service/ 0770 statsd system
mkdir /data/misc/train-info/ 0770 statsd system
@@ -1288,11 +1293,13 @@
shutdown critical
on property:ro.debuggable=1
- # Give writes to anyone for the trace folder on debug builds.
+ # Give writes to the same group for the trace folder on debug builds,
+ # it's further protected by selinux policy.
# The folder is used to store method traces.
chmod 0773 /data/misc/trace
- # Give reads to anyone for the window trace folder on debug builds.
- chmod 0775 /data/misc/wmtrace
+ # Give writes and reads to anyone for the window trace folder on debug builds,
+ # it's further protected by selinux policy.
+ chmod 0777 /data/misc/wmtrace
# Give reads to anyone for the accessibility trace folder on debug builds.
chmod 0775 /data/misc/a11ytrace
diff --git a/shell_and_utilities/README.md b/shell_and_utilities/README.md
index ca522b7..9a733bb 100644
--- a/shell_and_utilities/README.md
+++ b/shell_and_utilities/README.md
@@ -18,8 +18,11 @@
in Marshmallow we changed direction and started the move to toybox.
Not everything is provided by toybox, though. For the bzip2 command-line tools
-we use the ones that are part of the bzip2 distribution. The awk added in
-Android P is Brian Kernighan's "one true" awk.
+we use the ones that are part of the bzip2 distribution.
+The awk added in Android P is the
+["one true" awk](https://github.com/onetrueawk/awk).
+The bc added in Android Q is
+[Gavin Howard's bc](https://github.com/gavinhoward/bc).
The lists below show what tools were provided and where they came from in
each release starting with Gingerbread. This doesn't tell the full story,
@@ -34,6 +37,40 @@
full list for a release by running `toybox` directly.
+## Android 14 ("U")
+
+BSD: fsck\_msdos newfs\_msdos
+
+bzip2: bzcat bzip2 bunzip2
+
+gavinhoward/bc: bc
+
+one-true-awk: awk
+
+toolbox: getevent getprop setprop start stop
+
+toybox ([0.8.9](http://landley.net/toybox/#10-01-2023)-ish):
+[ acpi base64 basename blkdiscard blkid blockdev **brctl** cal cat chattr
+chcon chgrp chmod chown chroot chrt cksum clear cmp comm cp cpio cut
+date dd devmem df diff dirname dmesg dos2unix du echo egrep env expand
+expr fallocate false fgrep file find flock fmt free freeramdisk fsfreeze
+fsync getconf getenforce getfattr getopt grep groups gunzip gzip head
+help hostname hwclock i2cdetect i2cdump i2cget i2cset iconv id ifconfig
+inotifyd insmod install ionice iorenice iotop kill killall ln load\_policy
+log **logger** logname losetup ls lsattr lsmod lsof lspci lsusb makedevs
+md5sum microcom mkdir mkfifo mknod mkswap mktemp modinfo modprobe
+more mount mountpoint mv nbd-client nc netcat netstat nice nl nohup
+nproc nsenter od partprobe paste patch pgrep pidof ping ping6 pivot\_root
+pkill pmap printenv printf prlimit ps pwd pwdx readelf readlink realpath
+renice restorecon rev rfkill rm rmdir rmmod rtcwake runcon sed sendevent
+seq setenforce setfattr setsid sha1sum sha224sum sha256sum sha384sum
+sha512sum sleep sort split stat strings stty swapoff swapon sync sysctl
+tac tail tar taskset tee test time timeout top touch tr traceroute
+traceroute6 true truncate tty tunctl uclampset ulimit umount uname
+uniq unix2dos unlink unshare uptime usleep uudecode uuencode uuidgen
+vconfig vi vmstat watch wc which whoami xargs xxd yes zcat
+
+
## Android 13 ("T")
BSD: fsck\_msdos newfs\_msdos
@@ -46,7 +83,8 @@
toolbox: getevent getprop setprop start stop
-toybox (0.8.6-ish): [ acpi base64 basename blkdiscard blkid blockdev cal cat chattr chcon
+toybox ([0.8.6](http://landley.net/toybox/#30-11-2021)-ish):
+[ acpi base64 basename blkdiscard blkid blockdev cal cat chattr chcon
chgrp chmod chown chroot chrt cksum clear cmp comm cp cpio cut date
dd devmem df diff dirname dmesg dos2unix du echo egrep env expand
expr fallocate false fgrep file find flock fmt free freeramdisk fsfreeze
@@ -79,7 +117,8 @@
toolbox: getevent getprop setprop start stop
-toybox (0.8.4-ish): **[** acpi base64 basename **blkdiscard** blkid blockdev cal cat chattr chcon
+toybox ([0.8.4](http://landley.net/toybox/#24-10-2020)-ish):
+**[** acpi base64 basename **blkdiscard** blkid blockdev cal cat chattr chcon
chgrp chmod chown chroot chrt cksum clear cmp comm cp cpio cut date
dd devmem df diff dirname dmesg dos2unix du echo egrep env expand
expr fallocate false fgrep file find flock fmt free freeramdisk fsfreeze
@@ -112,7 +151,8 @@
toolbox: getevent getprop setprop start stop
-toybox (0.8.3-ish): acpi base64 basename blkid blockdev cal cat chattr chcon chgrp chmod
+toybox ([0.8.3](http://landley.net/toybox/#11-05-2020)-ish):
+acpi base64 basename blkid blockdev cal cat chattr chcon chgrp chmod
chown chroot chrt cksum clear cmp comm cp cpio cut date dd **devmem**
df diff dirname dmesg dos2unix du echo egrep env expand expr fallocate
false fgrep file find flock fmt free freeramdisk fsfreeze **fsync** getconf
@@ -143,7 +183,8 @@
toolbox: getevent getprop
-toybox (0.8.0-ish): acpi base64 basename **bc** **blkid** blockdev cal cat **chattr** chcon chgrp
+toybox ([0.8.0](http://landley.net/toybox/#08-02-2019)-ish):
+acpi base64 basename **bc** **blkid** blockdev cal cat **chattr** chcon chgrp
chmod chown chroot chrt cksum clear cmp comm cp cpio cut date dd df
diff dirname dmesg dos2unix du echo **egrep** env expand expr fallocate
false **fgrep** file find flock fmt free **freeramdisk** **fsfreeze** **getconf**
@@ -174,7 +215,8 @@
toolbox: getevent getprop newfs\_msdos
-toybox (0.7.6-ish): acpi base64 basename blockdev cal cat chcon chgrp chmod chown
+toybox ([0.7.6](http://landley.net/toybox/#24-02-2018)-ish):
+acpi base64 basename blockdev cal cat chcon chgrp chmod chown
chroot chrt cksum clear cmp comm cp cpio cut date df diff dirname dmesg
dos2unix du echo env expand expr fallocate false file find flock **fmt** free
getenforce groups gunzip gzip head hostname hwclock id ifconfig inotifyd
@@ -198,7 +240,8 @@
toolbox: getevent newfs\_msdos
-toybox (0.7.3-ish): acpi base64 basename blockdev cal cat chcon chgrp chmod chown
+toybox ([0.7.3](http://landley.net/toybox/#21-02-2017)-ish):
+acpi base64 basename blockdev cal cat chcon chgrp chmod chown
chroot chrt cksum clear cmp comm cp cpio cut date df **diff** dirname dmesg
dos2unix du echo env expand expr fallocate false **file** find flock free
getenforce getprop groups **gunzip** **gzip** head hostname hwclock id ifconfig
@@ -221,7 +264,8 @@
toolbox: getevent iftop ioctl log nandread newfs\_msdos ps prlimit
sendevent start stop top
-toybox (0.7.0-ish): acpi **base64** basename blockdev bzcat cal cat chcon chgrp chmod
+toybox ([0.7.0](http://landley.net/toybox/#02-02-2016)-ish):
+acpi **base64** basename blockdev bzcat cal cat chcon chgrp chmod
chown chroot cksum clear comm cmp cp cpio cut date **df** dirname dmesg
dos2unix **du** echo env expand expr fallocate false find **flock** free
getenforce getprop groups head hostname hwclock id ifconfig inotifyd
@@ -242,7 +286,8 @@
toolbox: df getevent iftop ioctl ionice log ls lsof mount nandread
newfs\_msdos ps prlimit renice sendevent start stop top uptime watchprops
-toybox (0.5.2-ish): acpi basename blockdev bzcat cal cat chcon chgrp chmod chown
+toybox ([0.5.2](http://landley.net/toybox/#25-02-2015)-ish):
+acpi basename blockdev bzcat cal cat chcon chgrp chmod chown
chroot cksum clear comm cmp cp cpio cut date dirname dmesg dos2unix echo
env expand expr fallocate false find free getenforce getprop groups
head hostname hwclock id ifconfig inotifyd insmod kill load\_policy ln
diff --git a/storaged/storaged_diskstats.cpp b/storaged/storaged_diskstats.cpp
index 1eae5a1..c315409 100644
--- a/storaged/storaged_diskstats.cpp
+++ b/storaged/storaged_diskstats.cpp
@@ -312,7 +312,7 @@
{
struct disk_perf perf = get_disk_perf(&mAccumulate_pub);
log_debug_disk_perf(&perf, "regular");
- log_event_disk_stats(&mAccumulate, "regular");
+ log_event_disk_stats(&mAccumulate_pub, "regular");
// Reset global structures
memset(&mAccumulate_pub, 0, sizeof(struct disk_stats));
}
diff --git a/toolbox/modprobe.cpp b/toolbox/modprobe.cpp
index 711586a..17f8156 100644
--- a/toolbox/modprobe.cpp
+++ b/toolbox/modprobe.cpp
@@ -23,8 +23,11 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
+#include <android-base/stringprintf.h>
#include <modprobe/modprobe.h>
+#include <sys/utsname.h>
+
namespace {
enum modprobe_mode {
@@ -37,9 +40,8 @@
void print_usage(void) {
LOG(INFO) << "Usage:";
LOG(INFO);
- // -d option is required on Android
- LOG(INFO) << " modprobe [options] -d DIR [--all=FILE|MODULE]...";
- LOG(INFO) << " modprobe [options] -d DIR MODULE [symbol=value]...";
+ LOG(INFO) << " modprobe [options] [-d DIR] [--all=FILE|MODULE]...";
+ LOG(INFO) << " modprobe [options] [-d DIR] MODULE [symbol=value]...";
LOG(INFO);
LOG(INFO) << "Options:";
LOG(INFO) << " --all=FILE: FILE to acquire module names from";
@@ -189,6 +191,12 @@
}
}
+ if (mod_dirs.empty()) {
+ utsname uts;
+ uname(&uts);
+ mod_dirs.emplace_back(android::base::StringPrintf("/lib/modules/%s", uts.release));
+ }
+
LOG(DEBUG) << "mode is " << mode;
LOG(DEBUG) << "mod_dirs is: " << android::base::Join(mod_dirs, " ");
LOG(DEBUG) << "modules is: " << android::base::Join(modules, " ");
diff --git a/trusty/gatekeeper/Android.bp b/trusty/gatekeeper/Android.bp
index 81f012f..0b43754 100644
--- a/trusty/gatekeeper/Android.bp
+++ b/trusty/gatekeeper/Android.bp
@@ -24,11 +24,10 @@
}
cc_binary {
- name: "android.hardware.gatekeeper@1.0-service.trusty",
- defaults: ["hidl_defaults"],
+ name: "android.hardware.gatekeeper-service.trusty",
vendor: true,
relative_install_path: "hw",
- init_rc: ["android.hardware.gatekeeper@1.0-service.trusty.rc"],
+ init_rc: ["android.hardware.gatekeeper-service.trusty.rc"],
srcs: [
"service.cpp",
@@ -42,16 +41,21 @@
"-Werror",
],
+ static_libs: [
+ "libgflags",
+ ],
+
shared_libs: [
- "android.hardware.gatekeeper@1.0",
+ "android.hardware.gatekeeper-V1-ndk",
"libbase",
- "libhidlbase",
+ "libbinder_ndk",
"libgatekeeper",
+ "libhardware",
"libutils",
"liblog",
"libcutils",
"libtrusty",
],
- vintf_fragments: ["android.hardware.gatekeeper@1.0-service.trusty.xml"],
+ vintf_fragments: ["android.hardware.gatekeeper-service.trusty.xml"],
}
diff --git a/trusty/gatekeeper/android.hardware.gatekeeper-service.trusty.rc b/trusty/gatekeeper/android.hardware.gatekeeper-service.trusty.rc
new file mode 100644
index 0000000..66ecbd1
--- /dev/null
+++ b/trusty/gatekeeper/android.hardware.gatekeeper-service.trusty.rc
@@ -0,0 +1,4 @@
+service vendor.gatekeeper_default /vendor/bin/hw/android.hardware.gatekeeper-service.trusty
+ class hal
+ user system
+ group system
diff --git a/trusty/gatekeeper/android.hardware.gatekeeper@1.0-service.trusty.xml b/trusty/gatekeeper/android.hardware.gatekeeper-service.trusty.xml
similarity index 60%
rename from trusty/gatekeeper/android.hardware.gatekeeper@1.0-service.trusty.xml
rename to trusty/gatekeeper/android.hardware.gatekeeper-service.trusty.xml
index 19714a8..c35421e 100644
--- a/trusty/gatekeeper/android.hardware.gatekeeper@1.0-service.trusty.xml
+++ b/trusty/gatekeeper/android.hardware.gatekeeper-service.trusty.xml
@@ -1,10 +1,9 @@
<manifest version="1.0" type="device">
- <hal format="hidl">
+ <hal format="aidl">
<name>android.hardware.gatekeeper</name>
- <transport>hwbinder</transport>
- <version>1.0</version>
+ <version>1</version>
<interface>
- <name>IGatekeeper</name>
+ <name>IGatekeeper</name>
<instance>default</instance>
</interface>
</hal>
diff --git a/trusty/gatekeeper/android.hardware.gatekeeper@1.0-service.trusty.rc b/trusty/gatekeeper/android.hardware.gatekeeper@1.0-service.trusty.rc
deleted file mode 100644
index 5413a6c..0000000
--- a/trusty/gatekeeper/android.hardware.gatekeeper@1.0-service.trusty.rc
+++ /dev/null
@@ -1,4 +0,0 @@
-service vendor.gatekeeper-1-0 /vendor/bin/hw/android.hardware.gatekeeper@1.0-service.trusty
- class hal
- user system
- group system
diff --git a/trusty/gatekeeper/service.cpp b/trusty/gatekeeper/service.cpp
index c5ee488..d09804f 100644
--- a/trusty/gatekeeper/service.cpp
+++ b/trusty/gatekeeper/service.cpp
@@ -13,27 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#define LOG_TAG "android.hardware.gatekeeper@1.0-service.trusty"
+#define LOG_TAG "android.hardware.gatekeeper-service.trusty"
#include <android-base/logging.h>
-#include <android/hardware/gatekeeper/1.0/IGatekeeper.h>
-
-#include <hidl/LegacySupport.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
#include "trusty_gatekeeper.h"
-// Generated HIDL files
-using android::hardware::gatekeeper::V1_0::IGatekeeper;
-using gatekeeper::TrustyGateKeeperDevice;
+using aidl::android::hardware::gatekeeper::TrustyGateKeeperDevice;
int main() {
- ::android::hardware::configureRpcThreadpool(1, true /* willJoinThreadpool */);
- android::sp<TrustyGateKeeperDevice> gatekeeper(new TrustyGateKeeperDevice());
- auto status = gatekeeper->registerAsService();
- if (status != android::OK) {
- LOG(FATAL) << "Could not register service for Gatekeeper 1.0 (trusty) (" << status << ")";
- }
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
- android::hardware::joinRpcThreadpool();
+ std::shared_ptr<TrustyGateKeeperDevice> gatekeeper =
+ ndk::SharedRefBase::make<TrustyGateKeeperDevice>();
+
+ const std::string instance = std::string() + TrustyGateKeeperDevice::descriptor + "/default";
+ binder_status_t status =
+ AServiceManager_addService(gatekeeper->asBinder().get(), instance.c_str());
+ CHECK_EQ(status, STATUS_OK);
+
+ ABinderProcess_joinThreadPool();
+
return -1; // Should never get here.
}
diff --git a/trusty/gatekeeper/trusty_gatekeeper.cpp b/trusty/gatekeeper/trusty_gatekeeper.cpp
index ec4f81b..d0647df 100644
--- a/trusty/gatekeeper/trusty_gatekeeper.cpp
+++ b/trusty/gatekeeper/trusty_gatekeeper.cpp
@@ -16,28 +16,26 @@
#define LOG_TAG "TrustyGateKeeper"
-#include <android-base/logging.h>
+#include <endian.h>
#include <limits>
+#include <android-base/logging.h>
+#include <gatekeeper/password_handle.h>
+#include <hardware/hw_auth_token.h>
+
+#include "gatekeeper_ipc.h"
#include "trusty_gatekeeper.h"
#include "trusty_gatekeeper_ipc.h"
-#include "gatekeeper_ipc.h"
-using ::android::hardware::hidl_vec;
-using ::android::hardware::Return;
-using ::android::hardware::gatekeeper::V1_0::GatekeeperStatusCode;
-using ::gatekeeper::EnrollRequest;
-using ::gatekeeper::EnrollResponse;
+namespace aidl::android::hardware::gatekeeper {
+
using ::gatekeeper::ERROR_INVALID;
-using ::gatekeeper::ERROR_MEMORY_ALLOCATION_FAILED;
using ::gatekeeper::ERROR_NONE;
using ::gatekeeper::ERROR_RETRY;
using ::gatekeeper::SizedBuffer;
using ::gatekeeper::VerifyRequest;
using ::gatekeeper::VerifyResponse;
-namespace gatekeeper {
-
constexpr const uint32_t SEND_BUF_SIZE = 8192;
constexpr const uint32_t RECV_BUF_SIZE = 8192;
@@ -54,89 +52,101 @@
trusty_gatekeeper_disconnect();
}
-SizedBuffer hidl_vec2sized_buffer(const hidl_vec<uint8_t>& vec) {
+SizedBuffer vec2sized_buffer(const std::vector<uint8_t>& vec) {
if (vec.size() == 0 || vec.size() > std::numeric_limits<uint32_t>::max()) return {};
auto buffer = new uint8_t[vec.size()];
std::copy(vec.begin(), vec.end(), buffer);
return {buffer, static_cast<uint32_t>(vec.size())};
}
-Return<void> TrustyGateKeeperDevice::enroll(uint32_t uid,
- const hidl_vec<uint8_t>& currentPasswordHandle,
- const hidl_vec<uint8_t>& currentPassword,
- const hidl_vec<uint8_t>& desiredPassword,
- enroll_cb _hidl_cb) {
+void sizedBuffer2AidlHWToken(SizedBuffer& buffer,
+ android::hardware::security::keymint::HardwareAuthToken* aidlToken) {
+ const hw_auth_token_t* authToken =
+ reinterpret_cast<const hw_auth_token_t*>(buffer.Data<uint8_t>());
+ aidlToken->challenge = authToken->challenge;
+ aidlToken->userId = authToken->user_id;
+ aidlToken->authenticatorId = authToken->authenticator_id;
+ // these are in network order: translate to host
+ aidlToken->authenticatorType =
+ static_cast<android::hardware::security::keymint::HardwareAuthenticatorType>(
+ be32toh(authToken->authenticator_type));
+ aidlToken->timestamp.milliSeconds = be64toh(authToken->timestamp);
+ aidlToken->mac.insert(aidlToken->mac.begin(), std::begin(authToken->hmac),
+ std::end(authToken->hmac));
+}
+
+::ndk::ScopedAStatus TrustyGateKeeperDevice::enroll(
+ int32_t uid, const std::vector<uint8_t>& currentPasswordHandle,
+ const std::vector<uint8_t>& currentPassword, const std::vector<uint8_t>& desiredPassword,
+ GatekeeperEnrollResponse* rsp) {
if (error_ != 0) {
- _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
- return {};
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
}
if (desiredPassword.size() == 0) {
- _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
- return {};
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
}
- EnrollRequest request(uid, hidl_vec2sized_buffer(currentPasswordHandle),
- hidl_vec2sized_buffer(desiredPassword),
- hidl_vec2sized_buffer(currentPassword));
+ EnrollRequest request(uid, vec2sized_buffer(currentPasswordHandle),
+ vec2sized_buffer(desiredPassword), vec2sized_buffer(currentPassword));
EnrollResponse response;
auto error = Send(request, &response);
if (error != ERROR_NONE) {
- _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
} else if (response.error == ERROR_RETRY) {
- _hidl_cb({GatekeeperStatusCode::ERROR_RETRY_TIMEOUT, response.retry_timeout, {}});
+ *rsp = {ERROR_RETRY_TIMEOUT, static_cast<int32_t>(response.retry_timeout), 0, {}};
+ return ndk::ScopedAStatus::ok();
} else if (response.error != ERROR_NONE) {
- _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
} else {
- hidl_vec<uint8_t> new_handle(response.enrolled_password_handle.Data<uint8_t>(),
- response.enrolled_password_handle.Data<uint8_t>() +
- response.enrolled_password_handle.size());
- _hidl_cb({GatekeeperStatusCode::STATUS_OK, response.retry_timeout, new_handle});
+ const ::gatekeeper::password_handle_t* password_handle =
+ response.enrolled_password_handle.Data<::gatekeeper::password_handle_t>();
+ *rsp = {STATUS_OK,
+ 0,
+ static_cast<int64_t>(password_handle->user_id),
+ {response.enrolled_password_handle.Data<uint8_t>(),
+ (response.enrolled_password_handle.Data<uint8_t>() +
+ response.enrolled_password_handle.size())}};
}
- return {};
+ return ndk::ScopedAStatus::ok();
}
-Return<void> TrustyGateKeeperDevice::verify(
- uint32_t uid, uint64_t challenge,
- const ::android::hardware::hidl_vec<uint8_t>& enrolledPasswordHandle,
- const ::android::hardware::hidl_vec<uint8_t>& providedPassword, verify_cb _hidl_cb) {
+::ndk::ScopedAStatus TrustyGateKeeperDevice::verify(
+ int32_t uid, int64_t challenge, const std::vector<uint8_t>& enrolledPasswordHandle,
+ const std::vector<uint8_t>& providedPassword, GatekeeperVerifyResponse* rsp) {
if (error_ != 0) {
- _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
- return {};
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
}
if (enrolledPasswordHandle.size() == 0) {
- _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
- return {};
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
}
- VerifyRequest request(uid, challenge, hidl_vec2sized_buffer(enrolledPasswordHandle),
- hidl_vec2sized_buffer(providedPassword));
+ VerifyRequest request(uid, challenge, vec2sized_buffer(enrolledPasswordHandle),
+ vec2sized_buffer(providedPassword));
VerifyResponse response;
auto error = Send(request, &response);
if (error != ERROR_NONE) {
- _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
} else if (response.error == ERROR_RETRY) {
- _hidl_cb({GatekeeperStatusCode::ERROR_RETRY_TIMEOUT, response.retry_timeout, {}});
+ *rsp = {ERROR_RETRY_TIMEOUT, static_cast<int32_t>(response.retry_timeout), {}};
+ return ndk::ScopedAStatus::ok();
} else if (response.error != ERROR_NONE) {
- _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
} else {
- hidl_vec<uint8_t> auth_token(
- response.auth_token.Data<uint8_t>(),
- response.auth_token.Data<uint8_t>() + response.auth_token.size());
-
- _hidl_cb({response.request_reenroll ? GatekeeperStatusCode::STATUS_REENROLL
- : GatekeeperStatusCode::STATUS_OK,
- response.retry_timeout, auth_token});
+ // On Success, return GatekeeperVerifyResponse with Success Status, timeout{0} and
+ // valid HardwareAuthToken.
+ *rsp = {response.request_reenroll ? STATUS_REENROLL : STATUS_OK, 0, {}};
+ // Convert the hw_auth_token_t to HardwareAuthToken in the response.
+ sizedBuffer2AidlHWToken(response.auth_token, &rsp->hardwareAuthToken);
}
- return {};
+ return ndk::ScopedAStatus::ok();
}
-Return<void> TrustyGateKeeperDevice::deleteUser(uint32_t uid, deleteUser_cb _hidl_cb) {
+::ndk::ScopedAStatus TrustyGateKeeperDevice::deleteUser(int32_t uid) {
if (error_ != 0) {
- _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
- return {};
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
}
DeleteUserRequest request(uid);
@@ -144,21 +154,19 @@
auto error = Send(request, &response);
if (error != ERROR_NONE) {
- _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
} else if (response.error == ERROR_NOT_IMPLEMENTED) {
- _hidl_cb({GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED, 0, {}});
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_NOT_IMPLEMENTED));
} else if (response.error != ERROR_NONE) {
- _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
} else {
- _hidl_cb({GatekeeperStatusCode::STATUS_OK, response.retry_timeout, {}});
+ return ndk::ScopedAStatus::ok();
}
- return {};
}
-Return<void> TrustyGateKeeperDevice::deleteAllUsers(deleteAllUsers_cb _hidl_cb) {
+::ndk::ScopedAStatus TrustyGateKeeperDevice::deleteAllUsers() {
if (error_ != 0) {
- _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
- return {};
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
}
DeleteAllUsersRequest request;
@@ -166,16 +174,14 @@
auto error = Send(request, &response);
if (error != ERROR_NONE) {
- _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
} else if (response.error == ERROR_NOT_IMPLEMENTED) {
- _hidl_cb({GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED, 0, {}});
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_NOT_IMPLEMENTED));
} else if (response.error != ERROR_NONE) {
- _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
} else {
- _hidl_cb({GatekeeperStatusCode::STATUS_OK, response.retry_timeout, {}});
+ return ndk::ScopedAStatus::ok();
}
-
- return {};
}
gatekeeper_error_t TrustyGateKeeperDevice::Send(uint32_t command, const GateKeeperMessage& request,
@@ -201,4 +207,4 @@
return response->Deserialize(payload, payload + response_size);
}
-};
+} // namespace aidl::android::hardware::gatekeeper
diff --git a/trusty/gatekeeper/trusty_gatekeeper.h b/trusty/gatekeeper/trusty_gatekeeper.h
index 420dd7a..5cb5d4b 100644
--- a/trusty/gatekeeper/trusty_gatekeeper.h
+++ b/trusty/gatekeeper/trusty_gatekeeper.h
@@ -17,18 +17,30 @@
#ifndef TRUSTY_GATEKEEPER_H
#define TRUSTY_GATEKEEPER_H
-#include <android/hardware/gatekeeper/1.0/IGatekeeper.h>
-#include <hidl/Status.h>
-
#include <memory>
+#include <aidl/android/hardware/gatekeeper/BnGatekeeper.h>
+
#include <gatekeeper/gatekeeper_messages.h>
#include "gatekeeper_ipc.h"
-namespace gatekeeper {
+namespace aidl::android::hardware::gatekeeper {
-class TrustyGateKeeperDevice : public ::android::hardware::gatekeeper::V1_0::IGatekeeper {
+using aidl::android::hardware::gatekeeper::GatekeeperEnrollResponse;
+using aidl::android::hardware::gatekeeper::GatekeeperVerifyResponse;
+using ::gatekeeper::DeleteAllUsersRequest;
+using ::gatekeeper::DeleteAllUsersResponse;
+using ::gatekeeper::DeleteUserRequest;
+using ::gatekeeper::DeleteUserResponse;
+using ::gatekeeper::EnrollRequest;
+using ::gatekeeper::EnrollResponse;
+using ::gatekeeper::gatekeeper_error_t;
+using ::gatekeeper::GateKeeperMessage;
+using ::gatekeeper::VerifyRequest;
+using ::gatekeeper::VerifyResponse;
+
+class TrustyGateKeeperDevice : public BnGatekeeper {
public:
explicit TrustyGateKeeperDevice();
~TrustyGateKeeperDevice();
@@ -40,11 +52,10 @@
* Returns: 0 on success or an error code less than 0 on error.
* On error, enrolled_password_handle will not be allocated.
*/
- ::android::hardware::Return<void> enroll(
- uint32_t uid, const ::android::hardware::hidl_vec<uint8_t>& currentPasswordHandle,
- const ::android::hardware::hidl_vec<uint8_t>& currentPassword,
- const ::android::hardware::hidl_vec<uint8_t>& desiredPassword,
- enroll_cb _hidl_cb) override;
+ ::ndk::ScopedAStatus enroll(int32_t uid, const std::vector<uint8_t>& currentPasswordHandle,
+ const std::vector<uint8_t>& currentPassword,
+ const std::vector<uint8_t>& desiredPassword,
+ GatekeeperEnrollResponse* _aidl_return) override;
/**
* Verifies provided_password matches enrolled_password_handle.
@@ -59,25 +70,24 @@
* Returns: 0 on success or an error code less than 0 on error
* On error, verification token will not be allocated
*/
- ::android::hardware::Return<void> verify(
- uint32_t uid, uint64_t challenge,
- const ::android::hardware::hidl_vec<uint8_t>& enrolledPasswordHandle,
- const ::android::hardware::hidl_vec<uint8_t>& providedPassword,
- verify_cb _hidl_cb) override;
+ ::ndk::ScopedAStatus verify(int32_t uid, int64_t challenge,
+ const std::vector<uint8_t>& enrolledPasswordHandle,
+ const std::vector<uint8_t>& providedPassword,
+ GatekeeperVerifyResponse* _aidl_return) override;
- ::android::hardware::Return<void> deleteUser(uint32_t uid, deleteUser_cb _hidl_cb) override;
+ ::ndk::ScopedAStatus deleteAllUsers() override;
- ::android::hardware::Return<void> deleteAllUsers(deleteAllUsers_cb _hidl_cb) override;
+ ::ndk::ScopedAStatus deleteUser(int32_t uid) override;
private:
gatekeeper_error_t Send(uint32_t command, const GateKeeperMessage& request,
GateKeeperMessage* response);
- gatekeeper_error_t Send(const EnrollRequest& request, EnrollResponse *response) {
+ gatekeeper_error_t Send(const EnrollRequest& request, EnrollResponse* response) {
return Send(GK_ENROLL, request, response);
}
- gatekeeper_error_t Send(const VerifyRequest& request, VerifyResponse *response) {
+ gatekeeper_error_t Send(const VerifyRequest& request, VerifyResponse* response) {
return Send(GK_VERIFY, request, response);
}
@@ -93,7 +103,6 @@
int error_;
};
-} // namespace gatekeeper
+} // namespace aidl::android::hardware::gatekeeper
#endif
-
diff --git a/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h b/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h
index f767d40..09f696b 100644
--- a/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h
+++ b/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h
@@ -76,6 +76,7 @@
KM_CLEAR_ATTESTATION_CERT_CHAIN = (0xa000 << KEYMASTER_REQ_SHIFT),
KM_SET_WRAPPED_ATTESTATION_KEY = (0xb000 << KEYMASTER_REQ_SHIFT),
KM_SET_ATTESTATION_IDS = (0xc000 << KEYMASTER_REQ_SHIFT),
+ KM_SET_ATTESTATION_IDS_KM3 = (0xc001 << KEYMASTER_REQ_SHIFT),
KM_CONFIGURE_BOOT_PATCHLEVEL = (0xd000 << KEYMASTER_REQ_SHIFT),
};
diff --git a/trusty/libtrusty/tipc-test/tipc_test.c b/trusty/libtrusty/tipc-test/tipc_test.c
index eb0acb5..81c9881 100644
--- a/trusty/libtrusty/tipc-test/tipc_test.c
+++ b/trusty/libtrusty/tipc-test/tipc_test.c
@@ -596,6 +596,7 @@
TEST_PASSED = 0,
TEST_FAILED = 1,
TEST_MESSAGE = 2,
+ TEST_TEXT = 3,
};
int fd;
@@ -625,7 +626,7 @@
break;
} else if (rx_buf[0] == TEST_FAILED) {
break;
- } else if (rx_buf[0] == TEST_MESSAGE) {
+ } else if (rx_buf[0] == TEST_MESSAGE || rx_buf[0] == TEST_TEXT) {
write(STDOUT_FILENO, rx_buf + 1, ret - 1);
} else {
fprintf(stderr, "%s: Bad message header: %d\n", __func__, rx_buf[0]);
diff --git a/trusty/storage/proxy/Android.bp b/trusty/storage/proxy/Android.bp
index e952ee0..2e97ee0 100644
--- a/trusty/storage/proxy/Android.bp
+++ b/trusty/storage/proxy/Android.bp
@@ -28,6 +28,7 @@
"rpmb.c",
"storage.c",
"proxy.c",
+ "watchdog.cpp",
],
shared_libs: [
diff --git a/trusty/storage/proxy/proxy.c b/trusty/storage/proxy/proxy.c
index 4f77fa2..c89c5b6 100644
--- a/trusty/storage/proxy/proxy.c
+++ b/trusty/storage/proxy/proxy.c
@@ -31,6 +31,7 @@
#include "log.h"
#include "rpmb.h"
#include "storage.h"
+#include "watchdog.h"
#define REQ_BUFFER_SIZE 4096
static uint8_t req_buffer[REQ_BUFFER_SIZE + 1];
@@ -73,6 +74,8 @@
static int handle_req(struct storage_msg* msg, const void* req, size_t req_len) {
int rc;
+ struct watcher* watcher = watch_start("request", msg);
+
if ((msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) && msg->cmd != STORAGE_RPMB_SEND &&
msg->cmd != STORAGE_FILE_WRITE) {
/*
@@ -81,14 +84,14 @@
*/
ALOGE("cmd 0x%x: post commit option is not implemented\n", msg->cmd);
msg->result = STORAGE_ERR_UNIMPLEMENTED;
- return ipc_respond(msg, NULL, 0);
+ goto err_response;
}
if (msg->flags & STORAGE_MSG_FLAG_PRE_COMMIT) {
- rc = storage_sync_checkpoint();
+ rc = storage_sync_checkpoint(watcher);
if (rc < 0) {
msg->result = STORAGE_ERR_SYNC_FAILURE;
- return ipc_respond(msg, NULL, 0);
+ goto err_response;
}
}
@@ -99,61 +102,65 @@
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);
+ goto err_response;
} else if (is_checkpoint_active) {
ALOGE("Checkpoint in progress, dropping write ...\n");
msg->result = STORAGE_ERR_GENERIC;
- return ipc_respond(msg, NULL, 0);
+ goto err_response;
}
}
switch (msg->cmd) {
case STORAGE_FILE_DELETE:
- rc = storage_file_delete(msg, req, req_len);
+ rc = storage_file_delete(msg, req, req_len, watcher);
break;
case STORAGE_FILE_OPEN:
- rc = storage_file_open(msg, req, req_len);
+ rc = storage_file_open(msg, req, req_len, watcher);
break;
case STORAGE_FILE_CLOSE:
- rc = storage_file_close(msg, req, req_len);
+ rc = storage_file_close(msg, req, req_len, watcher);
break;
case STORAGE_FILE_WRITE:
- rc = storage_file_write(msg, req, req_len);
+ rc = storage_file_write(msg, req, req_len, watcher);
break;
case STORAGE_FILE_READ:
- rc = storage_file_read(msg, req, req_len);
+ rc = storage_file_read(msg, req, req_len, watcher);
break;
case STORAGE_FILE_GET_SIZE:
- rc = storage_file_get_size(msg, req, req_len);
+ rc = storage_file_get_size(msg, req, req_len, watcher);
break;
case STORAGE_FILE_SET_SIZE:
- rc = storage_file_set_size(msg, req, req_len);
+ rc = storage_file_set_size(msg, req, req_len, watcher);
break;
case STORAGE_FILE_GET_MAX_SIZE:
- rc = storage_file_get_max_size(msg, req, req_len);
+ rc = storage_file_get_max_size(msg, req, req_len, watcher);
break;
case STORAGE_RPMB_SEND:
- rc = rpmb_send(msg, req, req_len);
+ rc = rpmb_send(msg, req, req_len, watcher);
break;
default:
ALOGE("unhandled command 0x%x\n", msg->cmd);
msg->result = STORAGE_ERR_UNIMPLEMENTED;
- rc = 1;
+ goto err_response;
}
- if (rc > 0) {
- /* still need to send response */
- rc = ipc_respond(msg, NULL, 0);
- }
+ /* response was sent in handler */
+ goto finish;
+
+err_response:
+ rc = ipc_respond(msg, NULL, 0);
+
+finish:
+ watch_finish(watcher);
return rc;
}
diff --git a/trusty/storage/proxy/rpmb.c b/trusty/storage/proxy/rpmb.c
index b1b8232..22a85a7 100644
--- a/trusty/storage/proxy/rpmb.c
+++ b/trusty/storage/proxy/rpmb.c
@@ -321,7 +321,8 @@
return SCSI_RES_ERR;
}
-static int send_mmc_rpmb_req(int mmc_fd, const struct storage_rpmb_send_req* req) {
+static int send_mmc_rpmb_req(int mmc_fd, const struct storage_rpmb_send_req* req,
+ struct watcher* watcher) {
union {
struct mmc_ioc_multi_cmd multi;
uint8_t raw[sizeof(struct mmc_ioc_multi_cmd) + sizeof(struct mmc_ioc_cmd) * 3];
@@ -375,14 +376,17 @@
cmd++;
}
+ watch_progress(watcher, "rpmb mmc ioctl");
rc = ioctl(mmc_fd, MMC_IOC_MULTI_CMD, &mmc.multi);
+ watch_progress(watcher, "rpmb mmc ioctl done");
if (rc < 0) {
ALOGE("%s: mmc ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
}
return rc;
}
-static int send_ufs_rpmb_req(int sg_fd, const struct storage_rpmb_send_req* req) {
+static int send_ufs_rpmb_req(int sg_fd, const struct storage_rpmb_send_req* req,
+ struct watcher* watcher) {
int rc;
int wl_rc;
const uint8_t* write_buf = req->payload;
@@ -410,7 +414,9 @@
set_sg_io_hdr(&io_hdr, SG_DXFER_TO_DEV, sizeof(out_cdb), sizeof(sense_buffer),
req->reliable_write_size, (void*)write_buf, (unsigned char*)&out_cdb,
sense_buffer);
+ watch_progress(watcher, "rpmb ufs reliable write");
rc = ioctl(sg_fd, SG_IO, &io_hdr);
+ watch_progress(watcher, "rpmb ufs reliable write done");
if (rc < 0) {
ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
goto err_op;
@@ -435,7 +441,9 @@
set_sg_io_hdr(&io_hdr, SG_DXFER_TO_DEV, sizeof(out_cdb), sizeof(sense_buffer),
req->write_size, (void*)write_buf, (unsigned char*)&out_cdb,
sense_buffer);
+ watch_progress(watcher, "rpmb ufs write");
rc = ioctl(sg_fd, SG_IO, &io_hdr);
+ watch_progress(watcher, "rpmb ufs write done");
if (rc < 0) {
ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
goto err_op;
@@ -450,7 +458,9 @@
sg_io_hdr_t io_hdr;
set_sg_io_hdr(&io_hdr, SG_DXFER_FROM_DEV, sizeof(in_cdb), sizeof(sense_buffer),
req->read_size, read_buf, (unsigned char*)&in_cdb, sense_buffer);
+ watch_progress(watcher, "rpmb ufs read");
rc = ioctl(sg_fd, SG_IO, &io_hdr);
+ watch_progress(watcher, "rpmb ufs read done");
if (rc < 0) {
ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
}
@@ -487,7 +497,7 @@
return rc;
}
-int rpmb_send(struct storage_msg* msg, const void* r, size_t req_len) {
+int rpmb_send(struct storage_msg* msg, const void* r, size_t req_len, struct watcher* watcher) {
int rc;
const struct storage_rpmb_send_req* req = r;
@@ -523,13 +533,13 @@
}
if (dev_type == MMC_RPMB) {
- rc = send_mmc_rpmb_req(rpmb_fd, req);
+ rc = send_mmc_rpmb_req(rpmb_fd, req, watcher);
if (rc < 0) {
msg->result = STORAGE_ERR_GENERIC;
goto err_response;
}
} else if (dev_type == UFS_RPMB) {
- rc = send_ufs_rpmb_req(rpmb_fd, req);
+ rc = send_ufs_rpmb_req(rpmb_fd, req, watcher);
if (rc < 0) {
ALOGE("send_ufs_rpmb_req failed: %d, %s\n", rc, strerror(errno));
msg->result = STORAGE_ERR_GENERIC;
diff --git a/trusty/storage/proxy/rpmb.h b/trusty/storage/proxy/rpmb.h
index f4e1b51..04bdf9a 100644
--- a/trusty/storage/proxy/rpmb.h
+++ b/trusty/storage/proxy/rpmb.h
@@ -18,8 +18,10 @@
#include <stdint.h>
#include <trusty/interface/storage.h>
+#include "watchdog.h"
+
enum dev_type { UNKNOWN_RPMB, MMC_RPMB, VIRT_RPMB, UFS_RPMB, SOCK_RPMB };
int rpmb_open(const char* rpmb_devname, enum dev_type dev_type);
-int rpmb_send(struct storage_msg* msg, const void* r, size_t req_len);
+int rpmb_send(struct storage_msg* msg, const void* r, size_t req_len, struct watcher* watcher);
void rpmb_close(void);
diff --git a/trusty/storage/proxy/storage.c b/trusty/storage/proxy/storage.c
index a96ddcb..2299481 100644
--- a/trusty/storage/proxy/storage.c
+++ b/trusty/storage/proxy/storage.c
@@ -31,6 +31,7 @@
#include "ipc.h"
#include "log.h"
#include "storage.h"
+#include "watchdog.h"
#define FD_TBL_SIZE 64
#define MAX_READ_SIZE 4096
@@ -180,9 +181,8 @@
return rcnt;
}
-int storage_file_delete(struct storage_msg *msg,
- const void *r, size_t req_len)
-{
+int storage_file_delete(struct storage_msg* msg, const void* r, size_t req_len,
+ struct watcher* watcher) {
char *path = NULL;
const struct storage_file_delete_req *req = r;
@@ -208,6 +208,7 @@
goto err_response;
}
+ watch_progress(watcher, "unlinking file");
rc = unlink(path);
if (rc < 0) {
rc = errno;
@@ -231,8 +232,9 @@
return ipc_respond(msg, NULL, 0);
}
-static void sync_parent(const char* path) {
+static void sync_parent(const char* path, struct watcher* watcher) {
int parent_fd;
+ watch_progress(watcher, "syncing parent");
char* parent_path = dirname(path);
parent_fd = TEMP_FAILURE_RETRY(open(parent_path, O_RDONLY));
if (parent_fd >= 0) {
@@ -242,9 +244,11 @@
ALOGE("%s: failed to open parent directory \"%s\" for sync: %s\n", __func__, parent_path,
strerror(errno));
}
+ watch_progress(watcher, "done syncing parent");
}
-int storage_file_open(struct storage_msg* msg, const void* r, size_t req_len) {
+int storage_file_open(struct storage_msg* msg, const void* r, size_t req_len,
+ struct watcher* watcher) {
char* path = NULL;
const struct storage_file_open_req *req = r;
struct storage_file_open_resp resp = {0};
@@ -306,7 +310,7 @@
char* parent_path = dirname(path);
rc = mkdir(parent_path, S_IRWXU);
if (rc == 0) {
- sync_parent(parent_path);
+ sync_parent(parent_path, watcher);
} else if (errno != EEXIST) {
ALOGE("%s: Could not create parent directory \"%s\": %s\n", __func__, parent_path,
strerror(errno));
@@ -347,7 +351,7 @@
}
if (open_flags & O_CREAT) {
- sync_parent(path);
+ sync_parent(path, watcher);
}
free(path);
@@ -375,9 +379,8 @@
return ipc_respond(msg, NULL, 0);
}
-int storage_file_close(struct storage_msg *msg,
- const void *r, size_t req_len)
-{
+int storage_file_close(struct storage_msg* msg, const void* r, size_t req_len,
+ struct watcher* watcher) {
const struct storage_file_close_req *req = r;
if (req_len != sizeof(*req)) {
@@ -390,7 +393,9 @@
int fd = remove_fd(req->handle);
ALOGV("%s: handle = %u: fd = %u\n", __func__, req->handle, fd);
+ watch_progress(watcher, "fsyncing before file close");
int rc = fsync(fd);
+ watch_progress(watcher, "done fsyncing before file close");
if (rc < 0) {
rc = errno;
ALOGE("%s: fsync failed for fd=%u: %s\n",
@@ -414,10 +419,8 @@
return ipc_respond(msg, NULL, 0);
}
-
-int storage_file_write(struct storage_msg *msg,
- const void *r, size_t req_len)
-{
+int storage_file_write(struct storage_msg* msg, const void* r, size_t req_len,
+ struct watcher* watcher) {
int rc;
const struct storage_file_write_req *req = r;
@@ -429,17 +432,20 @@
}
int fd = lookup_fd(req->handle, true);
+ watch_progress(watcher, "writing");
if (write_with_retry(fd, &req->data[0], req_len - sizeof(*req),
req->offset) < 0) {
+ watch_progress(watcher, "writing done w/ error");
rc = errno;
ALOGW("%s: error writing file (fd=%d): %s\n",
__func__, fd, strerror(errno));
msg->result = translate_errno(rc);
goto err_response;
}
+ watch_progress(watcher, "writing done");
if (msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) {
- rc = storage_sync_checkpoint();
+ rc = storage_sync_checkpoint(watcher);
if (rc < 0) {
msg->result = STORAGE_ERR_SYNC_FAILURE;
goto err_response;
@@ -452,10 +458,8 @@
return ipc_respond(msg, NULL, 0);
}
-
-int storage_file_read(struct storage_msg *msg,
- const void *r, size_t req_len)
-{
+int storage_file_read(struct storage_msg* msg, const void* r, size_t req_len,
+ struct watcher* watcher) {
int rc;
const struct storage_file_read_req *req = r;
@@ -474,8 +478,10 @@
}
int fd = lookup_fd(req->handle, false);
+ watch_progress(watcher, "reading");
ssize_t read_res = read_with_retry(fd, read_rsp.hdr.data, req->size,
(off_t)req->offset);
+ watch_progress(watcher, "reading done");
if (read_res < 0) {
rc = errno;
ALOGW("%s: error reading file (fd=%d): %s\n",
@@ -491,10 +497,8 @@
return ipc_respond(msg, NULL, 0);
}
-
-int storage_file_get_size(struct storage_msg *msg,
- const void *r, size_t req_len)
-{
+int storage_file_get_size(struct storage_msg* msg, const void* r, size_t req_len,
+ struct watcher* watcher) {
const struct storage_file_get_size_req *req = r;
struct storage_file_get_size_resp resp = {0};
@@ -507,7 +511,9 @@
struct stat stat;
int fd = lookup_fd(req->handle, false);
+ watch_progress(watcher, "fstat");
int rc = fstat(fd, &stat);
+ watch_progress(watcher, "fstat done");
if (rc < 0) {
rc = errno;
ALOGE("%s: error stat'ing file (fd=%d): %s\n",
@@ -524,10 +530,8 @@
return ipc_respond(msg, NULL, 0);
}
-
-int storage_file_set_size(struct storage_msg *msg,
- const void *r, size_t req_len)
-{
+int storage_file_set_size(struct storage_msg* msg, const void* r, size_t req_len,
+ struct watcher* watcher) {
const struct storage_file_set_size_req *req = r;
if (req_len != sizeof(*req)) {
@@ -538,7 +542,9 @@
}
int fd = lookup_fd(req->handle, true);
+ watch_progress(watcher, "ftruncate");
int rc = TEMP_FAILURE_RETRY(ftruncate(fd, req->size));
+ watch_progress(watcher, "ftruncate done");
if (rc < 0) {
rc = errno;
ALOGE("%s: error truncating file (fd=%d): %s\n",
@@ -553,7 +559,8 @@
return ipc_respond(msg, NULL, 0);
}
-int storage_file_get_max_size(struct storage_msg* msg, const void* r, size_t req_len) {
+int storage_file_get_max_size(struct storage_msg* msg, const void* r, size_t req_len,
+ struct watcher* watcher) {
const struct storage_file_get_max_size_req* req = r;
struct storage_file_get_max_size_resp resp = {0};
uint64_t max_size = 0;
@@ -566,7 +573,9 @@
struct stat stat;
int fd = lookup_fd(req->handle, false);
+ watch_progress(watcher, "fstat to get max size");
int rc = fstat(fd, &stat);
+ watch_progress(watcher, "fstat to get max size done");
if (rc < 0) {
ALOGE("%s: error stat'ing file (fd=%d): %s\n", __func__, fd, strerror(errno));
goto err_response;
@@ -606,10 +615,10 @@
return 0;
}
-int storage_sync_checkpoint(void)
-{
+int storage_sync_checkpoint(struct watcher* watcher) {
int rc;
+ watch_progress(watcher, "sync fd table");
/* sync fd table and reset it to clean state first */
for (uint fd = 0; fd < FD_TBL_SIZE; fd++) {
if (fd_state[fd] == SS_DIRTY) {
@@ -634,10 +643,12 @@
* because our fd table is large enough to handle the few open files we
* use.
*/
- sync();
- fs_state = SS_CLEAN;
+ watch_progress(watcher, "all fs sync");
+ sync();
+ fs_state = SS_CLEAN;
}
+ watch_progress(watcher, "done syncing");
+
return 0;
}
-
diff --git a/trusty/storage/proxy/storage.h b/trusty/storage/proxy/storage.h
index 77bfa13..f29fdf2 100644
--- a/trusty/storage/proxy/storage.h
+++ b/trusty/storage/proxy/storage.h
@@ -18,30 +18,33 @@
#include <stdint.h>
#include <trusty/interface/storage.h>
-int storage_file_delete(struct storage_msg *msg,
- const void *req, size_t req_len);
+/* Defined in watchdog.h */
+struct watcher;
-int storage_file_open(struct storage_msg *msg,
- const void *req, size_t req_len);
+int storage_file_delete(struct storage_msg* msg, const void* req, size_t req_len,
+ struct watcher* watcher);
-int storage_file_close(struct storage_msg *msg,
- const void *req, size_t req_len);
+int storage_file_open(struct storage_msg* msg, const void* req, size_t req_len,
+ struct watcher* watcher);
-int storage_file_write(struct storage_msg *msg,
- const void *req, size_t req_len);
+int storage_file_close(struct storage_msg* msg, const void* req, size_t req_len,
+ struct watcher* watcher);
-int storage_file_read(struct storage_msg *msg,
- const void *req, size_t req_len);
+int storage_file_write(struct storage_msg* msg, const void* req, size_t req_len,
+ struct watcher* watcher);
-int storage_file_get_size(struct storage_msg *msg,
- const void *req, size_t req_len);
+int storage_file_read(struct storage_msg* msg, const void* req, size_t req_len,
+ struct watcher* watcher);
-int storage_file_set_size(struct storage_msg *msg,
- const void *req, size_t req_len);
+int storage_file_get_size(struct storage_msg* msg, const void* req, size_t req_len,
+ struct watcher* watcher);
-int storage_file_get_max_size(struct storage_msg* msg, const void* req, size_t req_len);
+int storage_file_set_size(struct storage_msg* msg, const void* req, size_t req_len,
+ struct watcher* watcher);
-int storage_init(const char *dirname);
+int storage_file_get_max_size(struct storage_msg* msg, const void* req, size_t req_len,
+ struct watcher* watcher);
-int storage_sync_checkpoint(void);
+int storage_init(const char* dirname);
+int storage_sync_checkpoint(struct watcher* watcher);
diff --git a/trusty/storage/proxy/watchdog.cpp b/trusty/storage/proxy/watchdog.cpp
new file mode 100644
index 0000000..6c09e26
--- /dev/null
+++ b/trusty/storage/proxy/watchdog.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "watchdog.h"
+
+#include <chrono>
+#include <cstdint>
+#include <optional>
+#include <thread>
+#include <vector>
+
+#include <android-base/logging.h>
+
+struct watcher {
+ watcher(const char* id, const struct storage_msg* request);
+ void SetState(const char* new_state);
+ void LogTimeout();
+ void LogFinished();
+
+ const char* id_;
+ uint32_t cmd_;
+ uint32_t op_id_;
+ uint32_t flags_;
+ const char* state_;
+
+ using clock = std::chrono::high_resolution_clock;
+ clock::time_point start_;
+ clock::time_point state_change_;
+ std::chrono::milliseconds Elapsed(clock::time_point end);
+
+ bool triggered_;
+};
+
+watcher::watcher(const char* id, const struct storage_msg* request)
+ : id_(id), state_(nullptr), triggered_(false) {
+ cmd_ = request->cmd;
+ op_id_ = request->op_id;
+ flags_ = request->flags;
+
+ start_ = clock::now();
+ state_change_ = start_;
+}
+
+void watcher::SetState(const char* new_state) {
+ state_ = new_state;
+ state_change_ = clock::now();
+}
+
+void watcher::LogTimeout() {
+ if (!triggered_) {
+ triggered_ = true;
+ LOG(ERROR) << "Storageproxyd watchdog triggered: " << id_ << " cmd: " << cmd_
+ << " op_id: " << op_id_ << " flags: " << flags_;
+ }
+ if (state_) {
+ LOG(ERROR) << "...elapsed: " << Elapsed(clock::now()).count() << "ms (" << state_ << " "
+ << Elapsed(state_change_).count() << "ms)";
+ } else {
+ LOG(ERROR) << "...elapsed: " << Elapsed(clock::now()).count() << "ms";
+ }
+}
+
+void watcher::LogFinished() {
+ if (triggered_) {
+ LOG(ERROR) << "...completed: " << Elapsed(clock::now()).count() << "ms";
+ }
+}
+
+std::chrono::milliseconds watcher::Elapsed(watcher::clock::time_point end) {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(end - start_);
+}
+
+namespace {
+
+class Watchdog {
+ private:
+ static constexpr std::chrono::milliseconds kDefaultTimeoutMs = std::chrono::milliseconds(500);
+ static constexpr std::chrono::milliseconds kMaxTimeoutMs = std::chrono::seconds(10);
+
+ public:
+ Watchdog() : watcher_(), done_(false) {}
+ ~Watchdog();
+ struct watcher* RegisterWatch(const char* id, const struct storage_msg* request);
+ void AddProgress(struct watcher* watcher, const char* state);
+ void UnRegisterWatch(struct watcher* watcher);
+
+ private:
+ // Syncronizes access to watcher_ and watcher_change_ between the main
+ // thread and watchdog loop thread. watcher_ may only be modified by the
+ // main thread; the watchdog loop is read-only.
+ std::mutex watcher_mutex_;
+ std::unique_ptr<struct watcher> watcher_;
+ std::condition_variable watcher_change_;
+
+ std::thread watchdog_thread_;
+ bool done_;
+
+ void WatchdogLoop();
+ void LogWatchdogTriggerLocked();
+};
+
+Watchdog gWatchdog;
+
+} // Anonymous namespace
+
+// Assumes that caller is single-threaded. If we want to use this from a
+// multi-threaded context we need to ensure that the watchdog thread is
+// initialized safely once and accessing an existing watcher is done while the
+// watcher lock is held.
+struct watcher* Watchdog::RegisterWatch(const char* id, const struct storage_msg* request) {
+ if (!watchdog_thread_.joinable()) {
+ watchdog_thread_ = std::thread(&Watchdog::WatchdogLoop, this);
+ }
+ if (watcher_) {
+ LOG(ERROR) << "Replacing registered watcher " << watcher_->id_;
+ UnRegisterWatch(watcher_.get());
+ }
+
+ struct watcher* ret = nullptr;
+ {
+ std::unique_lock<std::mutex> watcherLock(watcher_mutex_);
+ watcher_ = std::make_unique<struct watcher>(id, request);
+ ret = watcher_.get();
+ }
+ watcher_change_.notify_one();
+ return ret;
+}
+
+void Watchdog::UnRegisterWatch(struct watcher* watcher) {
+ {
+ std::lock_guard<std::mutex> watcherLock(watcher_mutex_);
+ if (!watcher_) {
+ LOG(ERROR) << "Cannot unregister watcher, no watcher registered";
+ return;
+ }
+ if (watcher_.get() != watcher) {
+ LOG(ERROR) << "Unregistering watcher that doesn't match current watcher";
+ }
+ watcher_->LogFinished();
+ watcher_.reset(nullptr);
+ }
+ watcher_change_.notify_one();
+}
+
+void Watchdog::AddProgress(struct watcher* watcher, const char* state) {
+ std::lock_guard<std::mutex> watcherLock(watcher_mutex_);
+ if (watcher_.get() != watcher) {
+ LOG(ERROR) << "Watcher was not registered, cannot log progress: " << state;
+ return;
+ }
+ watcher->SetState(state);
+}
+
+void Watchdog::WatchdogLoop() {
+ std::unique_lock<std::mutex> lock(watcher_mutex_);
+ std::chrono::milliseconds timeout = kDefaultTimeoutMs;
+
+ while (!done_) {
+ // wait for a watch to be registered
+ watcher_change_.wait(lock, [this] { return !!watcher_; });
+
+ // wait for the timeout or unregistration
+ timeout = kDefaultTimeoutMs;
+ do {
+ if (!watcher_change_.wait_for(lock, timeout, [this] { return !watcher_; })) {
+ watcher_->LogTimeout();
+ timeout = std::min(timeout * 2, kMaxTimeoutMs);
+ }
+ } while (!!watcher_);
+ }
+}
+
+Watchdog::~Watchdog() {
+ {
+ std::lock_guard<std::mutex> watcherLock(watcher_mutex_);
+ watcher_.reset(nullptr);
+ done_ = true;
+ }
+ watcher_change_.notify_one();
+ if (watchdog_thread_.joinable()) {
+ watchdog_thread_.join();
+ }
+}
+
+struct watcher* watch_start(const char* id, const struct storage_msg* request) {
+ return gWatchdog.RegisterWatch(id, request);
+}
+
+void watch_progress(struct watcher* watcher, const char* state) {
+ gWatchdog.AddProgress(watcher, state);
+}
+
+void watch_finish(struct watcher* watcher) {
+ gWatchdog.UnRegisterWatch(watcher);
+}
diff --git a/trusty/storage/proxy/watchdog.h b/trusty/storage/proxy/watchdog.h
new file mode 100644
index 0000000..9162fcb
--- /dev/null
+++ b/trusty/storage/proxy/watchdog.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "storage.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct watcher;
+
+/**
+ * watch_start() - Create a watcher for a storage request
+ * @id: Identifier string to distinguish watchers
+ * @request: Incoming request from Trusty storage service
+ *
+ * Create a watcher that will start logging if not finished before a timeout.
+ * Only one watcher may be active at a time, and this function may only be
+ * called from a single thread.
+ */
+struct watcher* watch_start(const char* id, const struct storage_msg* request);
+
+/**
+ * watch_progress() - Note progress on servicing the current request
+ * @watcher: Current watcher, created by watch()
+ *
+ * Sets the current progress state of the watcher, to allow for more granular
+ * reporting of what exactly is stuck if the timeout is reached.
+ */
+void watch_progress(struct watcher* watcher, const char* state);
+
+/**
+ * watch_finish() - Finish watching and unregister the watch
+ * @watcher: Current watcher, created by watch(). Takes ownership of this pointer.
+ *
+ * Finish the current watch task. This function takes ownership of the watcher
+ * and destroys it, so @watcher must not be used again after calling this
+ * function.
+ */
+void watch_finish(struct watcher* watcher);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/trusty/trusty-base.mk b/trusty/trusty-base.mk
index 7b4aa26..5a3a320 100644
--- a/trusty/trusty-base.mk
+++ b/trusty/trusty-base.mk
@@ -37,7 +37,7 @@
PRODUCT_PACKAGES += \
$(LOCAL_KEYMINT_PRODUCT_PACKAGE) \
- android.hardware.gatekeeper@1.0-service.trusty \
+ android.hardware.gatekeeper-service.trusty \
trusty_apploader \
RemoteProvisioner
diff --git a/trusty/utils/trusty-ut-ctrl/ut-ctrl.c b/trusty/utils/trusty-ut-ctrl/ut-ctrl.c
index 9e72af3..6cc6670 100644
--- a/trusty/utils/trusty-ut-ctrl/ut-ctrl.c
+++ b/trusty/utils/trusty-ut-ctrl/ut-ctrl.c
@@ -94,6 +94,7 @@
TEST_PASSED = 0,
TEST_FAILED = 1,
TEST_MESSAGE = 2,
+ TEST_TEXT = 3,
};
static int run_trusty_unitest(const char* utapp) {
@@ -121,7 +122,7 @@
break;
} else if (rx_buf[0] == TEST_FAILED) {
break;
- } else if (rx_buf[0] == TEST_MESSAGE) {
+ } else if (rx_buf[0] == TEST_MESSAGE || rx_buf[0] == TEST_TEXT) {
write(STDOUT_FILENO, rx_buf + 1, rc - 1);
} else {
fprintf(stderr, "%s: Bad message header: %d\n", __func__, rx_buf[0]);