diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp index d402bf17b..d476d36a9 100644 --- a/bootstat/bootstat.cpp +++ b/bootstat/bootstat.cpp @@ -499,7 +499,7 @@ int32_t BootReasonStrToEnum(const std::string& boot_reason) { } // Canonical list of supported primary reboot reasons. -const std::vector knownReasons = { +const std::vector knownReasons = { // clang-format off // kernel "watchdog", diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp index 0c5543ed9..c365cac52 100644 --- a/debuggerd/Android.bp +++ b/debuggerd/Android.bp @@ -359,6 +359,7 @@ cc_test { "libdebuggerd/test/dump_memory_test.cpp", "libdebuggerd/test/elf_fake.cpp", "libdebuggerd/test/log_fake.cpp", + "libdebuggerd/test/mte_stack_record_test.cpp", "libdebuggerd/test/open_files_list_test.cpp", "libdebuggerd/test/tombstone_proto_to_text_test.cpp", ], diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp index af1bb8181..632f6f1c9 100644 --- a/debuggerd/client/debuggerd_client.cpp +++ b/debuggerd/client/debuggerd_client.cpp @@ -138,7 +138,7 @@ bool debuggerd_trigger_dump(pid_t tid, DebuggerdDumpType dump_type, unsigned int auto remaining = end - std::chrono::steady_clock::now(); if (remaining < decltype(remaining)::zero()) { - log_error(output_fd, 0, "timeout expired"); + log_error(output_fd, 0, "timeout expired (update_timeout)"); return false; } @@ -254,7 +254,7 @@ bool debuggerd_trigger_dump(pid_t tid, DebuggerdDumpType dump_type, unsigned int if (timeout_ms <= 0) { remaining_ms = -1; } else if (remaining_ms < 0) { - log_error(output_fd, 0, "timeout expired"); + log_error(output_fd, 0, "timeout expired before poll"); return false; } @@ -271,7 +271,7 @@ bool debuggerd_trigger_dump(pid_t tid, DebuggerdDumpType dump_type, unsigned int return false; } } else if (rc == 0) { - log_error(output_fd, 0, "timeout expired"); + log_error(output_fd, 0, "poll timeout expired"); return false; } diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp index 8dd2b0d73..c9235eeff 100644 --- a/debuggerd/crash_dump.cpp +++ b/debuggerd/crash_dump.cpp @@ -662,6 +662,15 @@ int main(int argc, char** argv) { info.pac_enabled_keys = -1; } +#if defined(__aarch64__) + struct iovec tls_iov = { + &info.tls, + sizeof(info.tls), + }; + if (ptrace(PTRACE_GETREGSET, thread, NT_ARM_TLS, reinterpret_cast(&tls_iov)) == -1) { + info.tls = 0; + } +#endif 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, &recoverable_crash); diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp index baddf6578..e33cea5c8 100644 --- a/debuggerd/debuggerd_test.cpp +++ b/debuggerd/debuggerd_test.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -69,7 +70,6 @@ #include "crash_test.h" #include "debuggerd/handler.h" #include "gtest/gtest.h" -#include "libdebuggerd/utility.h" #include "protocol.h" #include "tombstoned/tombstoned.h" #include "util.h" @@ -741,6 +741,8 @@ TEST_F(CrasherTest, mte_multiple_causes) { } #if defined(__aarch64__) +constexpr size_t kTagGranuleSize = 16; + static uintptr_t CreateTagMapping() { // Some of the MTE tag dump tests assert that there is an inaccessible page to the left and right // of the PROT_MTE page, so map three pages and set the two guard pages to PROT_NONE. @@ -1771,6 +1773,75 @@ TEST_F(CrasherTest, seccomp_crash_logcat) { AssertDeath(SIGABRT); } +extern "C" void malloc_enable(); +extern "C" void malloc_disable(); + +TEST_F(CrasherTest, seccomp_tombstone_no_allocation) { + int intercept_result; + unique_fd output_fd; + + static const auto dump_type = kDebuggerdTombstone; + StartProcess( + []() { + std::thread a(foo); + std::thread b(bar); + + std::this_thread::sleep_for(100ms); + + // Disable allocations to verify that nothing in the fallback + // signal handler does an allocation. + malloc_disable(); + raise_debugger_signal(dump_type); + _exit(0); + }, + &seccomp_fork); + + StartIntercept(&output_fd, dump_type); + FinishCrasher(); + AssertDeath(0); + FinishIntercept(&intercept_result); + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + + std::string result; + ConsumeFd(std::move(output_fd), &result); + ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal"); + ASSERT_BACKTRACE_FRAME(result, "foo"); + ASSERT_BACKTRACE_FRAME(result, "bar"); +} + +TEST_F(CrasherTest, seccomp_backtrace_no_allocation) { + int intercept_result; + unique_fd output_fd; + + static const auto dump_type = kDebuggerdNativeBacktrace; + StartProcess( + []() { + std::thread a(foo); + std::thread b(bar); + + std::this_thread::sleep_for(100ms); + + // Disable allocations to verify that nothing in the fallback + // signal handler does an allocation. + malloc_disable(); + raise_debugger_signal(dump_type); + _exit(0); + }, + &seccomp_fork); + + StartIntercept(&output_fd, dump_type); + FinishCrasher(); + AssertDeath(0); + FinishIntercept(&intercept_result); + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + + std::string result; + ConsumeFd(std::move(output_fd), &result); + ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal"); + ASSERT_BACKTRACE_FRAME(result, "foo"); + ASSERT_BACKTRACE_FRAME(result, "bar"); +} + TEST_F(CrasherTest, competing_tracer) { int intercept_result; unique_fd output_fd; diff --git a/debuggerd/handler/debuggerd_fallback.cpp b/debuggerd/handler/debuggerd_fallback.cpp index 4721391ef..8ab5f253f 100644 --- a/debuggerd/handler/debuggerd_fallback.cpp +++ b/debuggerd/handler/debuggerd_fallback.cpp @@ -48,50 +48,69 @@ using android::base::unique_fd; extern "C" bool __linker_enable_fallback_allocator(); extern "C" void __linker_disable_fallback_allocator(); -// This is incredibly sketchy to do inside of a signal handler, especially when libbacktrace -// uses the C++ standard library throughout, but this code runs in the linker, so we'll be using -// the linker's malloc instead of the libc one. Switch it out for a replacement, just in case. -// -// This isn't the default method of dumping because it can fail in cases such as address space -// exhaustion. +// This file implements a fallback path for processes that do not allow the +// normal fork and exec of crash_dump to handle crashes/unwinds. +// The issue is that all of this happens from within a signal handler, which +// can cause problems since this code uses the linker allocator which is not +// thread safe. In order to avoid any problems allocating, the code calls +// a function to switch to use a fallback allocator in the linker that will +// only be used for the current thread. All of the libunwindstack code does +// allocations using C++ stl, but should be fine since the code runs in the +// linker and should use the fallback handler. + +// This method can still fail if the virtual space is exhausted on a 32 bit +// process or mmap failing due to hitting the maximum number of maps (65535 +// total maps) on a 64 bit process. + +// Class to handle automatically turning on and off the fallback allocator. +class ScopedUseFallbackAllocator { + public: + ScopedUseFallbackAllocator() { Enable(); } + + ~ScopedUseFallbackAllocator() { Disable(); } + + bool Enable() { + if (!enabled_) { + enabled_ = __linker_enable_fallback_allocator(); + if (!enabled_) { + async_safe_format_log(ANDROID_LOG_ERROR, "libc", + "Unable to enable fallback allocator, already in use."); + } + } + return enabled_; + } + + void Disable() { + if (enabled_) { + __linker_disable_fallback_allocator(); + enabled_ = false; + } + } + + bool enabled() { return enabled_; } + + private: + bool enabled_ = false; +}; + static void debuggerd_fallback_trace(int output_fd, ucontext_t* ucontext) { - if (!__linker_enable_fallback_allocator()) { - async_safe_format_log(ANDROID_LOG_ERROR, "libc", "fallback allocator already in use"); - return; - } + std::unique_ptr regs; - { - std::unique_ptr regs; + ThreadInfo thread; + thread.pid = getpid(); + thread.tid = gettid(); + thread.thread_name = get_thread_name(gettid()); + thread.registers.reset( + unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(), ucontext)); - ThreadInfo thread; - thread.pid = getpid(); - thread.tid = gettid(); - thread.thread_name = get_thread_name(gettid()); - thread.registers.reset( - unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(), ucontext)); - - // Do not use the thread cache here because it will call pthread_key_create - // which doesn't work in linker code. See b/189803009. - // Use a normal cached object because the thread is stopped, and there - // is no chance of data changing between reads. - auto process_memory = unwindstack::Memory::CreateProcessMemoryCached(getpid()); - // TODO: Create this once and store it in a global? - unwindstack::AndroidLocalUnwinder unwinder(process_memory); - dump_backtrace_thread(output_fd, &unwinder, thread); - } - __linker_disable_fallback_allocator(); -} - -static void debuggerd_fallback_tombstone(int output_fd, int proto_fd, ucontext_t* ucontext, - siginfo_t* siginfo, void* abort_message) { - if (!__linker_enable_fallback_allocator()) { - async_safe_format_log(ANDROID_LOG_ERROR, "libc", "fallback allocator already in use"); - return; - } - - engrave_tombstone_ucontext(output_fd, proto_fd, reinterpret_cast(abort_message), - siginfo, ucontext); - __linker_disable_fallback_allocator(); + // Do not use the thread cache here because it will call pthread_key_create + // which doesn't work in linker code. See b/189803009. + // Use a normal cached object because the thread is stopped, and there + // is no chance of data changing between reads. + auto process_memory = unwindstack::Memory::CreateProcessMemoryCached(getpid()); + // TODO: Create this once and store it in a global? + unwindstack::AndroidLocalUnwinder unwinder(process_memory); + dump_backtrace_thread(output_fd, &unwinder, thread); } static bool forward_output(int src_fd, int dst_fd, pid_t expected_tid) { @@ -154,6 +173,11 @@ static std::pair unpack_thread_fd(uint64_t value) { } static void trace_handler(siginfo_t* info, ucontext_t* ucontext) { + ScopedUseFallbackAllocator allocator; + if (!allocator.enabled()) { + return; + } + static std::atomic trace_output(pack_thread_fd(-1, -1)); if (info->si_value.sival_ptr == kDebuggerdFallbackSivalPtrRequestDump) { @@ -181,6 +205,11 @@ static void trace_handler(siginfo_t* info, ucontext_t* ucontext) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to write to output fd"); } + // Stop using the fallback allocator before the close. This will prevent + // a race condition where the thread backtracing all of the threads tries + // to re-acquire the fallback allocator. + allocator.Disable(); + close(fd); return; } @@ -210,10 +239,15 @@ static void trace_handler(siginfo_t* info, ucontext_t* ucontext) { // Send a signal to all of our siblings, asking them to dump their stack. pid_t current_tid = gettid(); - if (!iterate_tids(current_tid, [&output_fd, ¤t_tid](pid_t tid) { + if (!iterate_tids(current_tid, [&allocator, &output_fd, ¤t_tid](pid_t tid) { if (current_tid == tid) { return; } + + if (!allocator.enabled()) { + return; + } + // Use a pipe, to be able to detect situations where the thread gracefully exits before // receiving our signal. unique_fd pipe_read, pipe_write; @@ -233,22 +267,29 @@ static void trace_handler(siginfo_t* info, ucontext_t* ucontext) { return; } + // Disable our use of the fallback allocator while the target thread + // is getting the backtrace. + allocator.Disable(); + siginfo_t siginfo = {}; siginfo.si_code = SI_QUEUE; siginfo.si_value.sival_ptr = kDebuggerdFallbackSivalPtrRequestDump; siginfo.si_pid = getpid(); siginfo.si_uid = getuid(); - if (syscall(__NR_rt_tgsigqueueinfo, getpid(), tid, BIONIC_SIGNAL_DEBUGGER, &siginfo) != 0) { + if (syscall(__NR_rt_tgsigqueueinfo, getpid(), tid, BIONIC_SIGNAL_DEBUGGER, &siginfo) == 0) { + if (!forward_output(pipe_read.get(), output_fd.get(), tid)) { + async_safe_format_log(ANDROID_LOG_ERROR, "libc", + "timeout expired while waiting for thread %d to dump", tid); + } + } else { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to send trace signal to %d: %s", tid, strerror(errno)); - return; } - bool success = forward_output(pipe_read.get(), output_fd.get(), tid); - if (!success) { - async_safe_format_log(ANDROID_LOG_ERROR, "libc", - "timeout expired while waiting for thread %d to dump", tid); + // The thread should be finished now, so try and re-enable the fallback allocator. + if (!allocator.Enable()) { + return; } // Regardless of whether the poll succeeds, check to see if the thread took fd ownership. @@ -260,14 +301,15 @@ static void trace_handler(siginfo_t* info, ucontext_t* ucontext) { close(fd); } } - - return; })) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to open /proc/%d/task: %s", current_tid, strerror(errno)); } - dump_backtrace_footer(output_fd.get()); + if (allocator.enabled()) { + dump_backtrace_footer(output_fd.get()); + } + tombstoned_notify_completion(tombstone_socket.get()); } @@ -295,7 +337,13 @@ static void crash_handler(siginfo_t* info, ucontext_t* ucontext, void* abort_mes unique_fd tombstone_socket, output_fd, proto_fd; bool tombstoned_connected = tombstoned_connect(getpid(), &tombstone_socket, &output_fd, &proto_fd, kDebuggerdTombstoneProto); - debuggerd_fallback_tombstone(output_fd.get(), proto_fd.get(), ucontext, info, abort_message); + { + ScopedUseFallbackAllocator allocator; + if (allocator.enabled()) { + engrave_tombstone_ucontext(output_fd.get(), proto_fd.get(), + reinterpret_cast(abort_message), info, ucontext); + } + } if (tombstoned_connected) { tombstoned_notify_completion(tombstone_socket.get()); } diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp index e26746b60..ddc3244f1 100644 --- a/debuggerd/handler/debuggerd_handler.cpp +++ b/debuggerd/handler/debuggerd_handler.cpp @@ -36,10 +36,12 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -115,6 +117,59 @@ static bool is_permissive_mte() { (permissive_env && ParseBool(permissive_env) == ParseBoolResult::kTrue); } +static bool parse_uint_with_error_reporting(const char* s, const char* name, int* v) { + if (android::base::ParseInt(s, v) && *v >= 0) { + return true; + } + async_safe_format_log(ANDROID_LOG_ERROR, "libc", "invalid %s: %s", name, s); + return false; +} + +// We cannot use base::GetIntProperty, because that internally uses +// std::string, which allocates. +static bool property_parse_int(const char* name, int* out) { + const prop_info* pi = __system_property_find(name); + if (!pi) return false; + struct cookie_t { + int* out; + bool empty; + } cookie{out, true}; + __system_property_read_callback( + pi, + [](void* raw_cookie, const char* name, const char* value, uint32_t) { + // Property is set to empty value, ignoring. + if (!*value) return; + cookie_t* cookie = reinterpret_cast(raw_cookie); + if (parse_uint_with_error_reporting(value, name, cookie->out)) cookie->empty = false; + }, + &cookie); + return !cookie.empty; +} + +static int permissive_mte_renable_timer() { + if (char* env = getenv("MTE_PERMISSIVE_REENABLE_TIME_CPUMS")) { + int v; + if (parse_uint_with_error_reporting(env, "MTE_PERMISSIVE_REENABLE_TIME_CPUMS", &v)) return v; + } + + char process_sysprop_name[512]; + async_safe_format_buffer(process_sysprop_name, sizeof(process_sysprop_name), + "persist.sys.mte.permissive_reenable_timer.process.%s", getprogname()); + int v; + if (property_parse_int(process_sysprop_name, &v)) return v; + if (property_parse_int("persist.sys.mte.permissive_reenable_timer.default", &v)) return v; + char process_deviceconf_sysprop_name[512]; + async_safe_format_buffer( + process_deviceconf_sysprop_name, sizeof(process_deviceconf_sysprop_name), + "persist.device_config.memory_safety_native.permissive_reenable_timer.process.%s", + getprogname()); + if (property_parse_int(process_deviceconf_sysprop_name, &v)) return v; + if (property_parse_int( + "persist.device_config.memory_safety_native.permissive_reenable_timer.default", &v)) + return v; + return 0; +} + static inline void futex_wait(volatile void* ftx, int value) { syscall(__NR_futex, ftx, FUTEX_WAIT, value, nullptr, nullptr, 0); } @@ -599,12 +654,40 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c if (tagged_addr_ctrl < 0) { fatal_errno("failed to PR_GET_TAGGED_ADDR_CTRL"); } + int previous = tagged_addr_ctrl & PR_MTE_TCF_MASK; tagged_addr_ctrl = (tagged_addr_ctrl & ~PR_MTE_TCF_MASK) | PR_MTE_TCF_NONE; if (prctl(PR_SET_TAGGED_ADDR_CTRL, tagged_addr_ctrl, 0, 0, 0) < 0) { fatal_errno("failed to PR_SET_TAGGED_ADDR_CTRL"); } - async_safe_format_log(ANDROID_LOG_ERROR, "libc", - "MTE ERROR DETECTED BUT RUNNING IN PERMISSIVE MODE. CONTINUING."); + if (int reenable_timer = permissive_mte_renable_timer()) { + async_safe_format_log(ANDROID_LOG_ERROR, "libc", + "MTE ERROR DETECTED BUT RUNNING IN PERMISSIVE MODE. CONTINUING WITH " + "MTE DISABLED FOR %d MS OF CPU TIME.", + reenable_timer); + timer_t timerid{}; + struct sigevent sev {}; + sev.sigev_signo = BIONIC_ENABLE_MTE; + sev.sigev_notify = SIGEV_THREAD_ID; + sev.sigev_value.sival_int = previous; + sev.sigev_notify_thread_id = __gettid(); + // This MUST be CLOCK_THREAD_CPUTIME_ID. If we used CLOCK_MONOTONIC we could get stuck + // in an endless loop of re-running the same instruction, calling this signal handler, + // and re-enabling MTE before we had a chance to re-run the instruction. + if (timer_create(CLOCK_THREAD_CPUTIME_ID, &sev, &timerid) == -1) { + fatal_errno("timer_create() failed"); + } + struct itimerspec its {}; + its.it_value.tv_sec = reenable_timer / 1000; + its.it_value.tv_nsec = (reenable_timer % 1000) * 1000000; + + if (timer_settime(timerid, 0, &its, nullptr) == -1) { + fatal_errno("timer_settime() failed"); + } + } else { + async_safe_format_log( + ANDROID_LOG_ERROR, "libc", + "MTE ERROR DETECTED BUT RUNNING IN PERMISSIVE MODE. CONTINUING WITH MTE DISABLED."); + } pthread_mutex_unlock(&crash_mutex); } @@ -755,7 +838,6 @@ void debuggerd_init(debuggerd_callbacks_t* callbacks) { // Use the alternate signal stack if available so we can catch stack overflows. action.sa_flags |= SA_ONSTACK; -#define SA_EXPOSE_TAGBITS 0x00000800 // Request that the kernel set tag bits in the fault address. This is necessary for diagnosing MTE // faults. action.sa_flags |= SA_EXPOSE_TAGBITS; diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h index dfdfabdff..074b0957a 100644 --- a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h +++ b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h @@ -73,5 +73,8 @@ bool tombstone_proto_to_text( void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame); void set_human_readable_cause(Cause* cause, uint64_t fault_addr); - +#if defined(__aarch64__) +void dump_stack_history(unwindstack::AndroidUnwinder* unwinder, uintptr_t target_tls, + StackHistoryBuffer& shb_ob, bool nounwind = false); +#endif #endif // _DEBUGGERD_TOMBSTONE_H diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/types.h b/debuggerd/libdebuggerd/include/libdebuggerd/types.h index c799f2448..f7fc2a3b7 100644 --- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h +++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h @@ -41,6 +41,9 @@ struct ThreadInfo { siginfo_t* siginfo = nullptr; std::unique_ptr guest_registers; +#if defined(__aarch64__) + uintptr_t tls; // This is currently used for MTE stack history buffer. +#endif }; // This struct is written into a pipe from inside the crashing process. diff --git a/debuggerd/libdebuggerd/test/host_signal_fixup.h b/debuggerd/libdebuggerd/test/host_signal_fixup.h deleted file mode 100644 index 762bae5fb..000000000 --- a/debuggerd/libdebuggerd/test/host_signal_fixup.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _DEBUGGERD_TEST_HOST_SIGNAL_FIXUP_H -#define _DEBUGGERD_TEST_HOST_SIGNAL_FIXUP_H - -#include - -#if !defined(__BIONIC__) - -// In order to compile parts of debuggerd for the host, we need to -// define these values. - -#if !defined(NSIGILL) -#define NSIGILL ILL_BADSTK -#endif - -#if !defined(BUS_MCEERR_AR) -#define BUS_MCEERR_AR 4 -#endif -#if !defined(BUS_MCEERR_AO) -#define BUS_MCEERR_AO 5 -#endif -#if !defined(NSIGBUS) -#define NSIGBUS BUS_MCEERR_AO -#endif - -#if !defined(NSIGFPE) -#define NSIGFPE FPE_FLTSUB -#endif - -#if !defined(NSIGSEGV) -#define NSIGSEGV SEGV_ACCERR -#endif - -#if !defined(TRAP_BRANCH) -#define TRAP_BRANCH 3 -#endif -#if !defined(TRAP_HWBKPT) -#define TRAP_HWBKPT 4 -#endif -#if !defined(NSIGTRAP) -#define NSIGTRAP TRAP_HWBKPT -#endif - -#if !defined(SI_DETHREAD) -#define SI_DETHREAD (-7) -#endif - -#endif - -#endif // _DEBUGGERD_TEST_HOST_SIGNAL_FIXUP_H diff --git a/debuggerd/libdebuggerd/test/mte_stack_record_test.cpp b/debuggerd/libdebuggerd/test/mte_stack_record_test.cpp new file mode 100644 index 000000000..4b788f3b7 --- /dev/null +++ b/debuggerd/libdebuggerd/test/mte_stack_record_test.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#if defined(__aarch64__) + +#include +#include + +#include + +#include "bionic/mte.h" +#include "bionic/page.h" +#include "unwindstack/AndroidUnwinder.h" +#include "unwindstack/Memory.h" + +#include +#include "gtest/gtest.h" + +#include "libdebuggerd/tombstone.h" + +struct ScopedUnmap { + void* ptr; + size_t size; + ~ScopedUnmap() { munmap(ptr, size); } +}; + +class MteStackHistoryTest : public ::testing::TestWithParam { + void SetUp() override { +#if !defined(__aarch64__) + GTEST_SKIP(); +#endif + SKIP_WITH_HWASAN; + unwinder.emplace(); + unwindstack::ErrorData E; + ASSERT_TRUE(unwinder->Initialize(E)); + } + + protected: + // std::optional so we don't construct it for the SKIP cases. + std::optional unwinder; +}; + +TEST(MteStackHistoryUnwindTest, TestOne) { +#if !defined(__aarch64__) + GTEST_SKIP(); +#endif + SKIP_WITH_HWASAN; + size_t size = stack_mte_ringbuffer_size(0); + char* data = static_cast(stack_mte_ringbuffer_allocate(0, nullptr)); + ScopedUnmap s{data, size}; + + uintptr_t taggedfp = (1ULL << 56) | 1; + uintptr_t pc = reinterpret_cast(&memcpy); + memcpy(data, &pc, sizeof(pc)); + memcpy(data + 8, &taggedfp, sizeof(taggedfp)); + + // The MTE TLS is at TLS - 3, so we allocate 3 placeholders. + void* tls[4] = {data + 16}; + + unwindstack::AndroidLocalUnwinder unwinder; + unwindstack::ErrorData E; + unwinder.Initialize(E); + StackHistoryBuffer shb; + dump_stack_history(&unwinder, reinterpret_cast(&tls[3]), shb, /* nounwind= */ false); + ASSERT_EQ(shb.entries_size(), 1); + const StackHistoryBufferEntry& e = shb.entries(0); + EXPECT_EQ(e.addr().pc(), pc); + EXPECT_EQ(e.addr().file_name(), "/apex/com.android.runtime/lib64/bionic/libc.so"); + EXPECT_EQ(e.fp(), 1ULL); + EXPECT_EQ(e.tag(), 1ULL); +} + +TEST_P(MteStackHistoryTest, TestEmpty) { + int size_cls = GetParam(); + size_t size = stack_mte_ringbuffer_size(size_cls); + void* data = stack_mte_ringbuffer_allocate(size_cls, nullptr); + ScopedUnmap s{data, size}; + // The MTE TLS is at TLS - 3, so we allocate 3 placeholders. + void* tls[4] = {data}; + + StackHistoryBuffer shb; + dump_stack_history(&*unwinder, reinterpret_cast(&tls[3]), shb, /* nounwind= */ true); + EXPECT_EQ(shb.entries_size(), 0); +} + +TEST_P(MteStackHistoryTest, TestFull) { + int size_cls = GetParam(); + size_t size = stack_mte_ringbuffer_size(size_cls); + char* data = static_cast(stack_mte_ringbuffer_allocate(size_cls, nullptr)); + ScopedUnmap s{data, size}; + uintptr_t itr = 1; + for (char* d = data; d < &data[size]; d += 16) { + uintptr_t taggedfp = ((itr & 15) << 56) | itr; + uintptr_t pc = itr; + memcpy(d, &pc, sizeof(pc)); + memcpy(d + 8, &taggedfp, sizeof(taggedfp)); + ++itr; + } + // The MTE TLS is at TLS - 3, so we allocate 3 placeholders. + // Because the buffer is full, and we point at one past the last inserted element, + // due to wrap-around we point at the beginning of the buffer. + void* tls[4] = {data}; + + StackHistoryBuffer shb; + dump_stack_history(&*unwinder, reinterpret_cast(&tls[3]), shb, /* nounwind= */ true); + EXPECT_EQ(static_cast(shb.entries_size()), size / 16); + for (const auto& entry : shb.entries()) { + EXPECT_EQ(entry.addr().pc(), --itr); + EXPECT_EQ(entry.addr().pc(), entry.fp()); + EXPECT_EQ(entry.addr().pc() & 15, entry.tag()); + } +} + +TEST_P(MteStackHistoryTest, TestHalfFull) { + int size_cls = GetParam(); + size_t size = stack_mte_ringbuffer_size(size_cls); + size_t half_size = size / 2; + + char* data = static_cast(stack_mte_ringbuffer_allocate(size_cls, nullptr)); + ScopedUnmap s{data, size}; + + uintptr_t itr = 1; + for (char* d = data; d < &data[half_size]; d += 16) { + uintptr_t taggedfp = ((itr & 15) << 56) | itr; + uintptr_t pc = itr; + memcpy(d, &pc, sizeof(pc)); + memcpy(d + 8, &taggedfp, sizeof(taggedfp)); + ++itr; + } + // The MTE TLS is at TLS - 3, so we allocate 3 placeholders. + void* tls[4] = {&data[half_size]}; + + StackHistoryBuffer shb; + dump_stack_history(&*unwinder, reinterpret_cast(&tls[3]), shb, /* nounwind= */ true); + EXPECT_EQ(static_cast(shb.entries_size()), half_size / 16); + for (const auto& entry : shb.entries()) { + EXPECT_EQ(entry.addr().pc(), --itr); + EXPECT_EQ(entry.addr().pc(), entry.fp()); + EXPECT_EQ(entry.addr().pc() & 15, entry.tag()); + } +} + +INSTANTIATE_TEST_SUITE_P(MteStackHistoryTestInstance, MteStackHistoryTest, testing::Range(0, 8)); + +#endif diff --git a/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp b/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp index a4c08a450..4fd264301 100644 --- a/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp +++ b/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp @@ -134,3 +134,31 @@ TEST_F(TombstoneProtoToTextTest, crash_detail_bytes) { ProtoToString(); EXPECT_MATCH(text_, R"(CRASH_DETAIL_NAME: 'helloworld\\1\\255\\3')"); } + +TEST_F(TombstoneProtoToTextTest, stack_record) { + auto* cause = tombstone_->add_causes(); + cause->set_human_readable("stack tag-mismatch on thread 123"); + auto* stack = tombstone_->mutable_stack_history_buffer(); + stack->set_tid(123); + { + auto* shb_entry = stack->add_entries(); + shb_entry->set_fp(0x1); + shb_entry->set_tag(0xb); + auto* addr = shb_entry->mutable_addr(); + addr->set_rel_pc(0x567); + addr->set_file_name("foo.so"); + addr->set_build_id("ABC123"); + } + { + auto* shb_entry = stack->add_entries(); + shb_entry->set_fp(0x2); + shb_entry->set_tag(0xc); + auto* addr = shb_entry->mutable_addr(); + addr->set_rel_pc(0x678); + addr->set_file_name("bar.so"); + } + ProtoToString(); + EXPECT_MATCH(text_, "stack tag-mismatch on thread 123"); + EXPECT_MATCH(text_, "stack_record fp:0x1 tag:0xb pc:foo\\.so\\+0x567 \\(BuildId: ABC123\\)"); + EXPECT_MATCH(text_, "stack_record fp:0x2 tag:0xc pc:bar\\.so\\+0x678"); +} diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp index 46d0cea26..53af4581a 100644 --- a/debuggerd/libdebuggerd/tombstone_proto.cpp +++ b/debuggerd/libdebuggerd/tombstone_proto.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -49,9 +50,11 @@ #include #include -#include -#include #include +#include +#include +#include +#include #include #include #include @@ -202,8 +205,117 @@ void set_human_readable_cause(Cause* cause, uint64_t fault_addr) { error_type_str, diff, byte_suffix, location_str, heap_object.size(), heap_object.address())); } +#if defined(__aarch64__) +void dump_stack_history(unwindstack::AndroidUnwinder* unwinder, uintptr_t target_tls, + StackHistoryBuffer& shb_obj, bool nounwind) { + auto process_memory = unwinder->GetProcessMemory(); + target_tls += sizeof(void*) * TLS_SLOT_STACK_MTE; + uintptr_t stack_mte; + if (!process_memory->ReadFully(target_tls, &stack_mte, sizeof(stack_mte))) { + async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, + "dump_stack_history: failed to read TLS_SLOT_STACK_MTE: %m"); + return; + } + if (stack_mte == 0) { + async_safe_format_log(ANDROID_LOG_DEBUG, LOG_TAG, + "dump_stack_history: stack history buffer is null"); + return; + } + uintptr_t untagged_stack_mte = untag_address(stack_mte); + uintptr_t buf_size = stack_mte_ringbuffer_size_from_pointer(stack_mte); + if (buf_size == 0) { + async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "dump_stack_history: empty size"); + return; + } + uintptr_t buf_start = untagged_stack_mte & ~(buf_size - 1ULL); + std::vector buf(buf_size); + if (!process_memory->ReadFully(buf_start, buf.data(), buf.size())) { + async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, + "dump_stack_history: failed to read stack history: %m"); + return; + } + uintptr_t original_off = untagged_stack_mte - buf_start; + if (original_off % 16 || original_off > buf_size) { + async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, + "dump_stack_history: invalid offset: %" PRIuPTR, original_off); + return; + } + + // The original_off is the next slot that would have been written, so the last + // slot that was written is the previous one. + for (uintptr_t idx = 16; idx <= buf_size; idx += 16) { + int64_t off = original_off - idx; + if (off < 0) off += buf_size; + uintptr_t pc, taggedfp; + memcpy(&pc, &(buf[off]), sizeof(pc)); + memcpy(&taggedfp, &(buf[off + sizeof(pc)]), sizeof(taggedfp)); + + if (pc == 0) break; + uintptr_t fp = untag_address(taggedfp); + uintptr_t tag = taggedfp >> 56; + + unwindstack::FrameData frame_data; + + if (nounwind) { + frame_data.pc = pc; + } else { + // +4 is to counteract the "pc adjustment" in BuildFrameFromPcOnly. + // BuildFrameFromPcOnly assumes we are unwinding, so it needs to correct for that + // the PC is the return address. That is not the case here. + // It doesn't really matter, because either should be in the correct function, but + // this is more correct (and consistent with the nounwind case). + frame_data = unwinder->BuildFrameFromPcOnly(pc); + frame_data.pc += 4; + frame_data.rel_pc += 4; + } + + StackHistoryBufferEntry* entry = shb_obj.add_entries(); + fill_in_backtrace_frame(entry->mutable_addr(), frame_data); + entry->set_fp(fp); + entry->set_tag(tag); + } +} + +static pid_t get_containing_thread(unwindstack::MapInfo* map_info, pid_t main_tid) { + if (map_info == nullptr) return 0; + + std::string name = map_info->name(); + if (name == "[stack]") { + return main_tid; + } + int tid; + if (sscanf(name.c_str(), "[anon:stack_and_tls:%d", &tid) != 1) { + return 0; + } + return tid; +} + +static std::optional maybe_stack_mte_cause( + Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder, const ThreadInfo& target_thread, + [[maybe_unused]] const std::map& threads, uint64_t fault_addr) { + unwindstack::Maps* maps = unwinder->GetMaps(); + auto map_info = maps->Find(untag_address(fault_addr)); + pid_t tid = get_containing_thread(map_info.get(), target_thread.tid); + if (!tid) { + return std::nullopt; + } + auto it = threads.find(tid); + if (it != threads.end()) { + StackHistoryBuffer* shb = tombstone->mutable_stack_history_buffer(); + shb->set_tid(tid); + dump_stack_history(unwinder, it->second.tls, *shb); + } else { + async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, + "dump_probable_cause: unknown target thread %d", tid); + } + return StringPrintf("stack tag-mismatch on thread %u", tid); +} + +#endif + static void dump_probable_cause(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder, - const ProcessInfo& process_info, const ThreadInfo& target_thread) { + const ProcessInfo& process_info, const ThreadInfo& target_thread, + [[maybe_unused]] const std::map& threads) { #if defined(USE_SCUDO) ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info); if (scudo_crash_data.CrashIsMine()) { @@ -244,10 +356,21 @@ static void dump_probable_cause(Tombstone* tombstone, unwindstack::AndroidUnwind auto map_info = maps->Find(fault_addr); if (map_info != nullptr && map_info->flags() == PROT_EXEC) { cause = "execute-only (no-read) memory access error; likely due to data in .text."; + } else if (fault_addr == target_thread.registers->pc() && + map_info != nullptr && (map_info->flags() & PROT_EXEC) == 0) { + cause = "trying to execute non-executable memory."; } else { cause = get_stack_overflow_cause(fault_addr, target_thread.registers->sp(), maps); } - } else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) { + } +#if defined(__aarch64__) && defined(SEGV_MTESERR) + else if (si->si_signo == SIGSEGV && si->si_code == SEGV_MTESERR) { + // If this was a heap MTE crash, it would have been handled by scudo. Checking whether it + // is a stack one. + cause = maybe_stack_mte_cause(tombstone, unwinder, target_thread, threads, fault_addr); + } +#endif + else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) { cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING, si->si_syscall); } @@ -788,7 +911,7 @@ void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::AndroidUnwinder* } } - dump_probable_cause(&result, unwinder, process_info, target_thread); + dump_probable_cause(&result, unwinder, process_info, target_thread, threads); dump_mappings(&result, unwinder->GetMaps(), unwinder->GetProcessMemory()); diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp index 87350ce76..d7f768a67 100644 --- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp +++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp @@ -511,6 +511,19 @@ static void print_main_thread(CallbackType callback, const Tombstone& tombstone, "order of likelihood."); } + if (tombstone.has_stack_history_buffer()) { + for (const StackHistoryBufferEntry& shbe : tombstone.stack_history_buffer().entries()) { + std::string stack_record_str = StringPrintf( + "stack_record fp:0x%" PRIx64 " tag:0x%" PRIx64 " pc:%s+0x%" PRIx64, shbe.fp(), shbe.tag(), + shbe.addr().file_name().c_str(), shbe.addr().rel_pc()); + if (!shbe.addr().build_id().empty()) { + StringAppendF(&stack_record_str, " (BuildId: %s)", shbe.addr().build_id().c_str()); + } + + CBL("%s", stack_record_str.c_str()); + } + } + for (const Cause& cause : tombstone.causes()) { if (tombstone.causes_size() > 1) { CBS(""); diff --git a/debuggerd/proto/tombstone.proto b/debuggerd/proto/tombstone.proto index d9d15657b..0c28d1f5e 100644 --- a/debuggerd/proto/tombstone.proto +++ b/debuggerd/proto/tombstone.proto @@ -22,6 +22,21 @@ message CrashDetail { reserved 3 to 999; } +message StackHistoryBufferEntry { + BacktraceFrame addr = 1; + uint64 fp = 2; + uint64 tag = 3; + + reserved 4 to 999; +} + +message StackHistoryBuffer { + uint64 tid = 1; + repeated StackHistoryBufferEntry entries = 2; + + reserved 3 to 999; +} + message Tombstone { Architecture arch = 1; Architecture guest_arch = 24; @@ -54,7 +69,9 @@ message Tombstone { uint32 page_size = 22; bool has_been_16kb_mode = 23; - reserved 26 to 999; + StackHistoryBuffer stack_history_buffer = 26; + + reserved 27 to 999; } enum Architecture { diff --git a/debuggerd/test_permissive_mte/mte_crash.cpp b/debuggerd/test_permissive_mte/mte_crash.cpp index 97ad73fbc..75b70edb8 100644 --- a/debuggerd/test_permissive_mte/mte_crash.cpp +++ b/debuggerd/test_permissive_mte/mte_crash.cpp @@ -20,5 +20,14 @@ int main(int, char**) { volatile char* f = (char*)malloc(1); printf("%c\n", f[17]); +#ifdef __aarch64__ + if (getenv("MTE_PERMISSIVE_REENABLE_TIME_CPUMS")) { + // Burn some cycles because the MTE_PERMISSIVE_REENABLE_TIME_CPUMS is based on CPU clock. + for (int i = 0; i < 1000000000; ++i) { + asm("isb"); + } + printf("%c\n", f[17]); + } +#endif return 0; } diff --git a/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java b/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java index 0203adcd4..544299dd1 100644 --- a/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java +++ b/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java @@ -97,6 +97,34 @@ public class PermissiveMteTest extends BaseHostJUnit4Test { } assertThat(numberTombstones).isEqualTo(1); } + + @Test + public void testReenableCrash() throws Exception { + CommandResult result = + getDevice().executeShellV2Command("MTE_PERMISSIVE=1 MTE_PERMISSIVE_REENABLE_TIME_CPUMS=1 " + + "/data/local/tmp/mte_crash testReenableCrash " + + mUUID); + assertThat(result.getExitCode()).isEqualTo(0); + int numberTombstones = 0; + String[] tombstones = getDevice().getChildren("/data/tombstones"); + for (String tombstone : tombstones) { + if (!tombstone.endsWith(".pb")) { + continue; + } + String tombstonePath = "/data/tombstones/" + tombstone; + Tombstone tombstoneProto = parseTombstone(tombstonePath); + if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains(mUUID))) { + continue; + } + if (!tombstoneProto.getCommandLineList().stream().anyMatch( + x -> x.contains("testReenableCrash"))) { + continue; + } + numberTombstones++; + } + assertThat(numberTombstones).isEqualTo(2); + } + @Test public void testCrashProperty() throws Exception { String prevValue = getDevice().getProperty("persist.sys.mte.permissive"); diff --git a/debuggerd/tombstoned/tombstoned.cpp b/debuggerd/tombstoned/tombstoned.cpp index fa67d46e6..2c7237934 100644 --- a/debuggerd/tombstoned/tombstoned.cpp +++ b/debuggerd/tombstoned/tombstoned.cpp @@ -158,7 +158,7 @@ class CrashQueue { } } - return std::move(result); + return result; } std::optional get_output(DebuggerdDumpType dump_type) { diff --git a/fastboot/Android.bp b/fastboot/Android.bp index 774af284c..bfe0768f8 100644 --- a/fastboot/Android.bp +++ b/fastboot/Android.bp @@ -186,6 +186,7 @@ cc_binary { "libprotobuf-cpp-lite", "libsparse", "libutils", + "libselinux", ], static_libs: [ diff --git a/fastboot/Android.mk b/fastboot/Android.mk deleted file mode 100644 index cde0cb28e..000000000 --- a/fastboot/Android.mk +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (C) 2007 Google Inc. -# -# 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. - -LOCAL_PATH:= $(call my-dir) - -# -# Package fastboot-related executables. -# - -my_dist_files := $(HOST_OUT_EXECUTABLES)/mke2fs -my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs -my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs_casefold -$(call dist-for-goals,dist_files sdk,$(my_dist_files)) -my_dist_files := diff --git a/fastboot/fuzzy_fastboot/transport_sniffer.cpp b/fastboot/fuzzy_fastboot/transport_sniffer.cpp index b55ffd31a..0aef35005 100644 --- a/fastboot/fuzzy_fastboot/transport_sniffer.cpp +++ b/fastboot/fuzzy_fastboot/transport_sniffer.cpp @@ -90,7 +90,7 @@ std::string TransportSniffer::CreateTrace() { // and be printed as a string, or just a raw byte-buffer const auto msg = [&ret, no_print](const std::vector& buf) { ret += android::base::StringPrintf("(%lu bytes): ", buf.size()); - std::vector::iterator iter = buf.end(); + std::vector::const_iterator iter = buf.end(); const unsigned max_chars = 50; if (buf.size() > max_chars) { iter = buf.begin() + max_chars; diff --git a/fastboot/vendor_boot_img_utils_test.cpp b/fastboot/vendor_boot_img_utils_test.cpp index 467c6e9d9..81072705d 100644 --- a/fastboot/vendor_boot_img_utils_test.cpp +++ b/fastboot/vendor_boot_img_utils_test.cpp @@ -163,7 +163,7 @@ struct TestFileHandle { protected: // |rel_path| is the relative path under test data directory. TestFileHandle(const std::filesystem::path& rel_path) - : abs_path_(std::move(std::filesystem::path(GetExecutableDirectory()) / rel_path)) {} + : abs_path_(std::filesystem::path(GetExecutableDirectory()) / rel_path) {} // Given |read_fd|, the readonly fd on the test file, return an fd that's suitable for client // to use. Implementation is responsible for managing the lifetime of the returned fd. virtual android::base::Result Transform(const std::filesystem::path& abs_path, diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING index 32e8b88e1..192232d6c 100644 --- a/fs_mgr/TEST_MAPPING +++ b/fs_mgr/TEST_MAPPING @@ -33,6 +33,9 @@ } ], "kernel-presubmit": [ + { + "name": "adb-remount-sh" + }, { "name": "libdm_test" }, diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp index d53891094..38aab8f00 100644 --- a/fs_mgr/fs_mgr.cpp +++ b/fs_mgr/fs_mgr.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -37,10 +38,8 @@ #include #include -#include #include #include -#include #include #include #include @@ -65,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -81,7 +81,7 @@ #define F2FS_FSCK_BIN "/system/bin/fsck.f2fs" #define MKSWAP_BIN "/system/bin/mkswap" #define TUNE2FS_BIN "/system/bin/tune2fs" -#define RESIZE2FS_BIN "/system/bin/resize2fs" +#define RESIZE2FS_BIN "/system/bin/resize2fs" #ifdef __ANDROID_RECOVERY__ #define FSCK_LOG_FILE "/dev/null" @@ -141,8 +141,8 @@ enum FsStatFlags { static void log_fs_stat(const std::string& blk_device, int fs_stat) { std::string msg = android::base::StringPrintf("\nfs_stat,%s,0x%x\n", blk_device.c_str(), fs_stat); - android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(FSCK_LOG_FILE, O_WRONLY | O_CLOEXEC | - O_APPEND | O_CREAT, 0664))); + android::base::unique_fd fd(TEMP_FAILURE_RETRY( + open(FSCK_LOG_FILE, O_WRONLY | O_CLOEXEC | O_APPEND | O_CREAT, 0664))); if (fd == -1 || !android::base::WriteStringToFd(msg, fd)) { LWARNING << __FUNCTION__ << "() cannot log " << msg; } @@ -219,10 +219,6 @@ static void check_fs(const std::string& blk_device, const std::string& fs_type, */ if (!(*fs_stat & FS_STAT_FULL_MOUNT_FAILED)) { // already tried if full mount failed errno = 0; - if (fs_type == "ext4") { - // This option is only valid with ext4 - tmpmnt_opts += ",nomblk_io_submit"; - } ret = mount(blk_device.c_str(), target.c_str(), fs_type.c_str(), tmpmnt_flags, tmpmnt_opts.c_str()); PINFO << __FUNCTION__ << "(): mount(" << blk_device << "," << target << "," << fs_type @@ -600,7 +596,7 @@ static void tune_metadata_csum(const std::string& blk_device, const FstabEntry& // Must give `-T now` to prevent last_fsck_time from growing too large, // otherwise, tune2fs won't enable metadata_csum. - const char* tune2fs_args[] = {TUNE2FS_BIN, "-O", "metadata_csum,64bit,extent", + const char* tune2fs_args[] = {TUNE2FS_BIN, "-O", "metadata_csum,64bit,extent", "-T", "now", blk_device.c_str()}; const char* resize2fs_args[] = {RESIZE2FS_BIN, "-b", blk_device.c_str()}; @@ -936,7 +932,8 @@ static bool should_use_metadata_encryption(const FstabEntry& entry) { // attempted_idx: On return, will indicate which fstab entry // succeeded. In case of failure, it will be the start_idx. // Sets errno to match the 1st mount failure on failure. -static bool mount_with_alternatives(Fstab& fstab, int start_idx, int* end_idx, int* attempted_idx) { +static bool mount_with_alternatives(Fstab& fstab, int start_idx, bool interrupted, int* end_idx, + int* attempted_idx) { unsigned long i; int mount_errno = 0; bool mounted = false; @@ -955,6 +952,13 @@ static bool mount_with_alternatives(Fstab& fstab, int start_idx, int* end_idx, i continue; } + if (interrupted) { + LINFO << __FUNCTION__ << "(): skipping fstab mountpoint=" << fstab[i].mount_point + << " rec[" << i << "].fs_type=" << fstab[i].fs_type + << " (previously interrupted during encryption step)"; + continue; + } + // fstab[start_idx].blk_device is already updated to /dev/dm- by // AVB related functions. Copy it from start_idx to the current index i. if ((i != start_idx) && fstab[i].fs_mgr_flags.logical && @@ -1422,19 +1426,59 @@ static bool IsMountPointMounted(const std::string& mount_point) { return GetEntryForMountPoint(&fstab, mount_point) != nullptr; } +std::string fs_mgr_metadata_encryption_in_progress_file_name(const FstabEntry& entry) { + return entry.metadata_key_dir + "/in_progress"; +} + +bool WasMetadataEncryptionInterrupted(const FstabEntry& entry) { + if (!should_use_metadata_encryption(entry)) return false; + return access(fs_mgr_metadata_encryption_in_progress_file_name(entry).c_str(), R_OK) == 0; +} + +static FstabEntry* LocateFormattableEntry(FstabEntry* const begin, FstabEntry* const end) { + if (begin == end) { + return nullptr; + } + const bool dev_option_enabled = + android::base::GetBoolProperty("ro.product.build.16k_page.enabled", false); + FstabEntry* f2fs_entry = nullptr; + for (auto iter = begin; iter != end && iter->blk_device == begin->blk_device; iter++) { + if (iter->fs_mgr_flags.formattable) { + if (getpagesize() != 4096 && is_f2fs(iter->fs_type) && dev_option_enabled) { + f2fs_entry = iter; + continue; + } + if (f2fs_entry) { + LOG(INFO) << "Skipping F2FS format for block device " << iter->blk_device << " @ " + << iter->mount_point + << " in non-4K mode for dev option enabled devices, " + "as these devices need to toggle between 4K/16K mode, and F2FS does " + "not support page_size != block_size configuration."; + } + return iter; + } + } + if (f2fs_entry) { + LOG(INFO) << "Using F2FS for " << f2fs_entry->blk_device << " @ " << f2fs_entry->mount_point + << " even though we are in non-4K mode. Device might require a data wipe after " + "going back to 4K mode, as F2FS does not support page_size != block_size"; + } + return f2fs_entry; +} + // When multiple fstab records share the same mount_point, it will try to mount each // one in turn, and ignore any duplicates after a first successful mount. // Returns -1 on error, and FS_MGR_MNTALL_* otherwise. -MountAllResult fs_mgr_mount_all(Fstab* fstab, int mount_mode) { +int fs_mgr_mount_all(Fstab* fstab, int mount_mode) { int encryptable = FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE; int error_count = 0; CheckpointManager checkpoint_manager; AvbUniquePtr avb_handle(nullptr); bool wiped = false; - bool userdata_mounted = false; + if (fstab->empty()) { - return {FS_MGR_MNTALL_FAIL, userdata_mounted}; + return FS_MGR_MNTALL_FAIL; } bool scratch_can_be_mounted = true; @@ -1513,7 +1557,7 @@ MountAllResult fs_mgr_mount_all(Fstab* fstab, int mount_mode) { if (!avb_handle) { LERROR << "Failed to open AvbHandle"; set_type_property(encryptable); - return {FS_MGR_MNTALL_FAIL, userdata_mounted}; + return FS_MGR_MNTALL_FAIL; } } if (avb_handle->SetUpAvbHashtree(¤t_entry, true /* wait_for_verity_dev */) == @@ -1532,11 +1576,13 @@ MountAllResult fs_mgr_mount_all(Fstab* fstab, int mount_mode) { } } - int last_idx_inspected; - int top_idx = i; + int last_idx_inspected = -1; + const int top_idx = i; int attempted_idx = -1; - bool mret = mount_with_alternatives(*fstab, i, &last_idx_inspected, &attempted_idx); + bool encryption_interrupted = WasMetadataEncryptionInterrupted(current_entry); + bool mret = mount_with_alternatives(*fstab, i, encryption_interrupted, &last_idx_inspected, + &attempted_idx); auto& attempted_entry = (*fstab)[attempted_idx]; i = last_idx_inspected; int mount_errno = errno; @@ -1547,7 +1593,7 @@ MountAllResult fs_mgr_mount_all(Fstab* fstab, int mount_mode) { if (status == FS_MGR_MNTALL_FAIL) { // Fatal error - no point continuing. - return {status, userdata_mounted}; + return status; } if (status != FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) { @@ -1562,11 +1608,12 @@ MountAllResult fs_mgr_mount_all(Fstab* fstab, int mount_mode) { attempted_entry.mount_point, wiped ? "true" : "false", attempted_entry.fs_type, attempted_entry.fs_mgr_flags.is_zoned ? "true" : "false", + std::to_string(attempted_entry.length), android::base::Join(attempted_entry.user_devices, ' ')}, nullptr)) { LERROR << "Encryption failed"; set_type_property(encryptable); - return {FS_MGR_MNTALL_FAIL, userdata_mounted}; + return FS_MGR_MNTALL_FAIL; } } } @@ -1580,17 +1627,23 @@ MountAllResult fs_mgr_mount_all(Fstab* fstab, int mount_mode) { // Success! Go get the next one. continue; } - + auto formattable_entry = + LocateFormattableEntry(fstab->data() + top_idx, fstab->data() + fstab->size()); // Mounting failed, understand why and retry. wiped = partition_wiped(current_entry.blk_device.c_str()); if (mount_errno != EBUSY && mount_errno != EACCES && - current_entry.fs_mgr_flags.formattable && wiped) { + current_entry.fs_mgr_flags.formattable && (wiped || encryption_interrupted)) { // current_entry and attempted_entry point at the same partition, but sometimes // at two different lines in the fstab. Use current_entry for formatting // as that is the preferred one. - LERROR << __FUNCTION__ << "(): " << realpath(current_entry.blk_device) - << " is wiped and " << current_entry.mount_point << " " << current_entry.fs_type - << " is formattable. Format it."; + if (wiped) + LERROR << __FUNCTION__ << "(): " << realpath(current_entry.blk_device) + << " is wiped and " << current_entry.mount_point << " " + << current_entry.fs_type << " is formattable. Format it."; + if (encryption_interrupted) + LERROR << __FUNCTION__ << "(): " << realpath(current_entry.blk_device) + << " was interrupted during encryption and " << current_entry.mount_point + << " " << current_entry.fs_type << " is formattable. Format it."; checkpoint_manager.Revert(¤t_entry); @@ -1603,11 +1656,12 @@ MountAllResult fs_mgr_mount_all(Fstab* fstab, int mount_mode) { encryptable = FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED; set_type_property(encryptable); - if (!call_vdc({"cryptfs", "encryptFstab", current_entry.blk_device, - current_entry.mount_point, "true" /* shouldFormat */, - current_entry.fs_type, - current_entry.fs_mgr_flags.is_zoned ? "true" : "false", - android::base::Join(current_entry.user_devices, ' ')}, + if (!call_vdc({"cryptfs", "encryptFstab", formattable_entry->blk_device, + formattable_entry->mount_point, "true" /* shouldFormat */, + formattable_entry->fs_type, + formattable_entry->fs_mgr_flags.is_zoned ? "true" : "false", + std::to_string(formattable_entry->length), + android::base::Join(formattable_entry->user_devices, ' ')}, nullptr)) { LERROR << "Encryption failed"; } else { @@ -1616,7 +1670,7 @@ MountAllResult fs_mgr_mount_all(Fstab* fstab, int mount_mode) { } } - if (fs_mgr_do_format(current_entry) == 0) { + if (fs_mgr_do_format(*formattable_entry) == 0) { // Let's replay the mount actions. i = top_idx - 1; continue; @@ -1629,7 +1683,7 @@ MountAllResult fs_mgr_mount_all(Fstab* fstab, int mount_mode) { } // mount(2) returned an error, handle the encryptable/formattable case. - if (mount_errno != EBUSY && mount_errno != EACCES && + if (mount_errno != EBUSY && mount_errno != EACCES && !encryption_interrupted && should_use_metadata_encryption(attempted_entry)) { if (!call_vdc({"cryptfs", "mountFstab", attempted_entry.blk_device, attempted_entry.mount_point, @@ -1647,13 +1701,13 @@ MountAllResult fs_mgr_mount_all(Fstab* fstab, int mount_mode) { // Use StringPrintf to output "(null)" instead. if (attempted_entry.fs_mgr_flags.no_fail) { PERROR << android::base::StringPrintf( - "Ignoring failure to mount an un-encryptable or wiped " + "Ignoring failure to mount an un-encryptable, interrupted, or wiped " "partition on %s at %s options: %s", attempted_entry.blk_device.c_str(), attempted_entry.mount_point.c_str(), attempted_entry.fs_options.c_str()); } else { PERROR << android::base::StringPrintf( - "Failed to mount an un-encryptable or wiped partition " + "Failed to mount an un-encryptable, interrupted, or wiped partition " "on %s at %s options: %s", attempted_entry.blk_device.c_str(), attempted_entry.mount_point.c_str(), attempted_entry.fs_options.c_str()); @@ -1679,9 +1733,9 @@ MountAllResult fs_mgr_mount_all(Fstab* fstab, int mount_mode) { set_type_property(encryptable); if (error_count) { - return {FS_MGR_MNTALL_FAIL, userdata_mounted}; + return FS_MGR_MNTALL_FAIL; } else { - return {encryptable, userdata_mounted}; + return encryptable; } } @@ -1718,190 +1772,6 @@ int fs_mgr_umount_all(android::fs_mgr::Fstab* fstab) { return ret; } -static std::chrono::milliseconds GetMillisProperty(const std::string& name, - std::chrono::milliseconds default_value) { - auto value = GetUintProperty(name, static_cast(default_value.count())); - return std::chrono::milliseconds(std::move(value)); -} - -static bool fs_mgr_unmount_all_data_mounts(const std::string& data_block_device) { - LINFO << __FUNCTION__ << "(): about to umount everything on top of " << data_block_device; - Timer t; - auto timeout = GetMillisProperty("init.userspace_reboot.userdata_remount.timeoutmillis", 5s); - while (true) { - bool umount_done = true; - Fstab proc_mounts; - if (!ReadFstabFromFile("/proc/mounts", &proc_mounts)) { - LERROR << __FUNCTION__ << "(): Can't read /proc/mounts"; - return false; - } - // Now proceed with other bind mounts on top of /data. - for (const auto& entry : proc_mounts) { - std::string block_device; - if (StartsWith(entry.blk_device, "/dev/block") && - !Realpath(entry.blk_device, &block_device)) { - PWARNING << __FUNCTION__ << "(): failed to realpath " << entry.blk_device; - block_device = entry.blk_device; - } - if (data_block_device == block_device) { - if (umount2(entry.mount_point.c_str(), 0) != 0) { - PERROR << __FUNCTION__ << "(): Failed to umount " << entry.mount_point; - umount_done = false; - } - } - } - if (umount_done) { - LINFO << __FUNCTION__ << "(): Unmounting /data took " << t; - return true; - } - if (t.duration() > timeout) { - LERROR << __FUNCTION__ << "(): Timed out unmounting all mounts on " - << data_block_device; - Fstab remaining_mounts; - if (!ReadFstabFromFile("/proc/mounts", &remaining_mounts)) { - LERROR << __FUNCTION__ << "(): Can't read /proc/mounts"; - } else { - LERROR << __FUNCTION__ << "(): Following mounts remaining"; - for (const auto& e : remaining_mounts) { - LERROR << __FUNCTION__ << "(): mount point: " << e.mount_point - << " block device: " << e.blk_device; - } - } - return false; - } - std::this_thread::sleep_for(50ms); - } -} - -static bool UnwindDmDeviceStack(const std::string& block_device, - std::vector* dm_stack) { - if (!StartsWith(block_device, "/dev/block/")) { - LWARNING << block_device << " is not a block device"; - return false; - } - std::string current = block_device; - DeviceMapper& dm = DeviceMapper::Instance(); - while (true) { - dm_stack->push_back(current); - if (!dm.IsDmBlockDevice(current)) { - break; - } - auto parent = dm.GetParentBlockDeviceByPath(current); - if (!parent) { - return false; - } - current = *parent; - } - return true; -} - -FstabEntry* fs_mgr_get_mounted_entry_for_userdata(Fstab* fstab, - const std::string& data_block_device) { - std::vector dm_stack; - if (!UnwindDmDeviceStack(data_block_device, &dm_stack)) { - LERROR << "Failed to unwind dm-device stack for " << data_block_device; - return nullptr; - } - for (auto& entry : *fstab) { - if (entry.mount_point != "/data") { - continue; - } - std::string block_device; - if (entry.fs_mgr_flags.logical) { - if (!fs_mgr_update_logical_partition(&entry)) { - LERROR << "Failed to update logic partition " << entry.blk_device; - continue; - } - block_device = entry.blk_device; - } else if (!Realpath(entry.blk_device, &block_device)) { - PWARNING << "Failed to realpath " << entry.blk_device; - block_device = entry.blk_device; - } - if (std::find(dm_stack.begin(), dm_stack.end(), block_device) != dm_stack.end()) { - return &entry; - } - } - LERROR << "Didn't find entry that was used to mount /data onto " << data_block_device; - return nullptr; -} - -// TODO(b/143970043): return different error codes based on which step failed. -int fs_mgr_remount_userdata_into_checkpointing(Fstab* fstab) { - Fstab proc_mounts; - if (!ReadFstabFromFile("/proc/mounts", &proc_mounts)) { - LERROR << "Can't read /proc/mounts"; - return -1; - } - auto mounted_entry = GetEntryForMountPoint(&proc_mounts, "/data"); - if (mounted_entry == nullptr) { - LERROR << "/data is not mounted"; - return -1; - } - std::string block_device; - if (!Realpath(mounted_entry->blk_device, &block_device)) { - PERROR << "Failed to realpath " << mounted_entry->blk_device; - return -1; - } - auto fstab_entry = fs_mgr_get_mounted_entry_for_userdata(fstab, block_device); - if (fstab_entry == nullptr) { - LERROR << "Can't find /data in fstab"; - return -1; - } - bool force_umount = GetBoolProperty("sys.init.userdata_remount.force_umount", false); - if (force_umount) { - LINFO << "Will force an umount of userdata even if it's not required"; - } - if (!force_umount && !SupportsCheckpoint(fstab_entry)) { - LINFO << "Userdata doesn't support checkpointing. Nothing to do"; - return 0; - } - CheckpointManager checkpoint_manager; - if (!force_umount && !checkpoint_manager.NeedsCheckpoint()) { - LINFO << "Checkpointing not needed. Don't remount"; - return 0; - } - if (!force_umount && fstab_entry->fs_mgr_flags.checkpoint_fs) { - // Userdata is f2fs, simply remount it. - if (!checkpoint_manager.Update(fstab_entry)) { - LERROR << "Failed to remount userdata in checkpointing mode"; - return -1; - } - if (mount(block_device.c_str(), fstab_entry->mount_point.c_str(), "none", - MS_REMOUNT | fstab_entry->flags, fstab_entry->fs_options.c_str()) != 0) { - PERROR << "Failed to remount userdata in checkpointing mode"; - return -1; - } - } else { - LINFO << "Unmounting /data before remounting into checkpointing mode"; - if (!fs_mgr_unmount_all_data_mounts(block_device)) { - LERROR << "Failed to umount /data"; - return -1; - } - DeviceMapper& dm = DeviceMapper::Instance(); - while (dm.IsDmBlockDevice(block_device)) { - auto next_device = dm.GetParentBlockDeviceByPath(block_device); - auto name = dm.GetDmDeviceNameByPath(block_device); - if (!name) { - LERROR << "Failed to get dm-name for " << block_device; - return -1; - } - LINFO << "Deleting " << block_device << " named " << *name; - if (!dm.DeleteDevice(*name, 3s)) { - return -1; - } - if (!next_device) { - LERROR << "Failed to find parent device for " << block_device; - } - block_device = *next_device; - } - LINFO << "Remounting /data"; - // TODO(b/143970043): remove this hack after fs_mgr_mount_all is refactored. - auto result = fs_mgr_mount_all(fstab, MOUNT_MODE_ONLY_USERDATA); - return result.code == FS_MGR_MNTALL_FAIL ? -1 : 0; - } - return 0; -} - // wrapper to __mount() and expects a fully prepared fstab_rec, // unlike fs_mgr_do_mount which does more things with avb / verity etc. int fs_mgr_do_mount_one(const FstabEntry& entry, const std::string& alt_mount_point) { @@ -1916,12 +1786,12 @@ int fs_mgr_do_mount_one(const FstabEntry& entry, const std::string& alt_mount_po int ret = prepare_fs_for_mount(entry.blk_device, entry, mount_point); // Wiped case doesn't require to try __mount below. if (ret & FS_STAT_INVALID_MAGIC) { - return FS_MGR_DOMNT_FAILED; + return FS_MGR_DOMNT_FAILED; } ret = __mount(entry.blk_device, mount_point, entry); if (ret) { - ret = (errno == EBUSY) ? FS_MGR_DOMNT_BUSY : FS_MGR_DOMNT_FAILED; + ret = (errno == EBUSY) ? FS_MGR_DOMNT_BUSY : FS_MGR_DOMNT_FAILED; } return ret; @@ -2073,11 +1943,45 @@ static bool InstallZramDevice(const std::string& device) { return true; } +/* + * Zram backing device can be created as long as /data has at least `size` + * free space, though we may want to leave some extra space for the remaining + * boot process and other system activities. + */ +static bool ZramBackingDeviceSizeAvailable(off64_t size) { + constexpr const char* data_path = "/data"; + uint64_t min_free_mb = + android::base::GetUintProperty("ro.zram_backing_device_min_free_mb", 0); + + // No min_free property. Skip the available size check. + if (min_free_mb == 0) return true; + + struct statvfs vst; + if (statvfs(data_path, &vst) < 0) { + PERROR << "Cannot check available space: " << data_path; + return false; + } + + uint64_t size_free = static_cast(vst.f_bfree) * vst.f_frsize; + uint64_t size_required = size + (min_free_mb * 1024 * 1024); + if (size_required > size_free) { + PERROR << "Free space is not enough for zram backing device: " << size_required << " > " + << size_free; + return false; + } + return true; +} + static bool PrepareZramBackingDevice(off64_t size) { constexpr const char* file_path = "/data/per_boot/zram_swap"; if (size == 0) return true; + // Check available space + if (!ZramBackingDeviceSizeAvailable(size)) { + PERROR << "No space for target path: " << file_path; + return false; + } // Prepare target path unique_fd target_fd(TEMP_FAILURE_RETRY(open(file_path, O_RDWR | O_CREAT | O_CLOEXEC, 0600))); if (target_fd.get() == -1) { @@ -2086,6 +1990,7 @@ static bool PrepareZramBackingDevice(off64_t size) { } if (fallocate(target_fd.get(), 0, 0, size) < 0) { PERROR << "Cannot truncate target path: " << file_path; + unlink(file_path); return false; } diff --git a/fs_mgr/fs_mgr_overlayfs_mount.cpp b/fs_mgr/fs_mgr_overlayfs_mount.cpp index a1ec63b69..b63b9e7aa 100644 --- a/fs_mgr/fs_mgr_overlayfs_mount.cpp +++ b/fs_mgr/fs_mgr_overlayfs_mount.cpp @@ -74,7 +74,7 @@ bool fs_mgr_is_dsu_running() { return android::gsi::IsGsiRunning(); } -std::vector OverlayMountPoints() { +std::vector OverlayMountPoints() { // Never fallback to legacy cache mount point if within a DSU system, // because running a DSU system implies the device supports dynamic // partitions, which means legacy cache mustn't be used. @@ -412,6 +412,8 @@ static bool fs_mgr_overlayfs_mount_one(const FstabEntry& fstab_entry) { bool retval = true; bool move_dir_shared = true; bool parent_shared = true; + bool parent_have_parent = false; + bool parent_made_private = false; bool root_shared = true; bool root_made_private = false; @@ -443,6 +445,10 @@ static bool fs_mgr_overlayfs_mount_one(const FstabEntry& fstab_entry) { if (entry.mount_point == "/") { root_shared = entry.shared_flag; } + // Ignore "/" as we don't overlay "/" directly. + if (entry.mount_point != "/") { + parent_have_parent |= android::base::StartsWith(mount_point, entry.mount_point + "/"); + } } // Precondition is that kMoveMountTempDir is MS_PRIVATE, otherwise don't try to move any @@ -453,11 +459,13 @@ static bool fs_mgr_overlayfs_mount_one(const FstabEntry& fstab_entry) { // Need to make the original mountpoint MS_PRIVATE, so that the overlayfs can be MS_MOVE. // This could happen if its parent mount is remounted later. - if (!fs_mgr_overlayfs_set_shared_mount(mount_point, false)) { - // If failed to set "/system" mount type, it might be due to "/system" not being a valid - // mountpoint after switch root. Retry with "/" in this case. - if (errno == EINVAL && mount_point == "/system") { - root_made_private = fs_mgr_overlayfs_set_shared_mount("/", false); + if (parent_have_parent) { + parent_made_private |= fs_mgr_overlayfs_set_shared_mount(mount_point, false); + if (!parent_made_private && errno == EINVAL && mount_point == "/system") { + // If failed to set "/system" mount type, it might be due to "/system" not being a valid + // mountpoint after switch root. Retry with "/" in this case. + parent_made_private |= fs_mgr_overlayfs_set_shared_mount("/", false); + root_made_private |= parent_made_private; } } @@ -496,6 +504,15 @@ static bool fs_mgr_overlayfs_mount_one(const FstabEntry& fstab_entry) { continue; } } + if (!parent_made_private) { + parent_made_private |= fs_mgr_overlayfs_set_shared_mount(mount_point, false); + if (!parent_made_private && errno == EINVAL && mount_point == "/system") { + // If failed to set "/system" mount type, it might be due to "/system" not being a + // valid mountpoint after switch root. Retry with "/" in this case. + parent_made_private |= fs_mgr_overlayfs_set_shared_mount("/", false); + root_made_private |= parent_made_private; + } + } if (new_entry.shared_flag) { new_entry.shared_flag = fs_mgr_overlayfs_set_shared_mount(new_entry.mount_point, false); @@ -524,7 +541,7 @@ static bool fs_mgr_overlayfs_mount_one(const FstabEntry& fstab_entry) { rmdir(entry.dir.c_str()); } // If the original (overridden) mount was MS_SHARED, then set the overlayfs mount to MS_SHARED. - if (parent_shared) { + if (parent_shared && parent_made_private) { fs_mgr_overlayfs_set_shared_mount(mount_point, true); } if (root_shared && root_made_private) { diff --git a/fs_mgr/fs_mgr_overlayfs_mount.h b/fs_mgr/fs_mgr_overlayfs_mount.h index 98b9007b5..f941ab1d6 100644 --- a/fs_mgr/fs_mgr_overlayfs_mount.h +++ b/fs_mgr/fs_mgr_overlayfs_mount.h @@ -54,7 +54,7 @@ const std::string fs_mgr_mount_point(const std::string& mount_point); bool OverlayfsSetupAllowed(bool verbose = false); bool MountScratch(const std::string& device_path, bool readonly = false); bool fs_mgr_overlayfs_umount_scratch(); -std::vector OverlayMountPoints(); +std::vector OverlayMountPoints(); bool fs_mgr_overlayfs_already_mounted(const std::string& mount_point, bool overlay_only = true); bool fs_mgr_wants_overlayfs(android::fs_mgr::FstabEntry* entry); android::fs_mgr::Fstab fs_mgr_overlayfs_candidate_list(const android::fs_mgr::Fstab& fstab); diff --git a/fs_mgr/fs_mgr_priv.h b/fs_mgr/fs_mgr_priv.h index 7e4d5e54d..84e4924fb 100644 --- a/fs_mgr/fs_mgr_priv.h +++ b/fs_mgr/fs_mgr_priv.h @@ -80,10 +80,8 @@ using namespace std::chrono_literals; -bool fs_mgr_set_blk_ro(const std::string& blockdev, bool readonly = true); bool fs_mgr_is_device_unlocked(); -bool fs_mgr_is_ext4(const std::string& blk_device); bool fs_mgr_is_f2fs(const std::string& blk_device); bool fs_mgr_filesystem_available(const std::string& filesystem); diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp index 43ea2455d..d4a741240 100644 --- a/fs_mgr/fs_mgr_remount.cpp +++ b/fs_mgr/fs_mgr_remount.cpp @@ -171,11 +171,12 @@ bool VerifyCheckpointing() { } if (show_help) { show_help = false; - std::cerr << "WARNING: Userdata checkpoint is in progress. To force end checkpointing, " - "call 'vdc checkpoint commitChanges'. This can lead to data corruption if " - "rolled back." + std::cerr << "WARNING: Userdata checkpoint is in progress. " + "To forcibly end checkpointing, " + "call 'vdc checkpoint commitChanges'. " + "This can lead to data corruption if rolled back." << std::endl; - LOG(INFO) << "Waiting for checkpoint to complete and then continue remount."; + LOG(INFO) << "Waiting for checkpoint to complete before remounting..."; } std::this_thread::sleep_for(4s); } diff --git a/fs_mgr/fs_mgr_vendor_overlay.cpp b/fs_mgr/fs_mgr_vendor_overlay.cpp index 6b742e55e..668728384 100644 --- a/fs_mgr/fs_mgr_vendor_overlay.cpp +++ b/fs_mgr/fs_mgr_vendor_overlay.cpp @@ -36,7 +36,7 @@ namespace { // The order of the list means the priority to show the files in the directory. // The last one has the highest priority. -const std::vector kVendorOverlaySourceDirs = { +const std::vector kVendorOverlaySourceDirs = { "/system/vendor_overlay/", "/product/vendor_overlay/", }; diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h index bc4a7a6c2..9cfa93f78 100644 --- a/fs_mgr/include/fs_mgr.h +++ b/fs_mgr/include/fs_mgr.h @@ -58,13 +58,8 @@ enum mount_mode { #define FS_MGR_MNTALL_DEV_NEEDS_RECOVERY 4 #define FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE 0 #define FS_MGR_MNTALL_FAIL (-1) - -struct MountAllResult { - // One of the FS_MGR_MNTALL_* returned code defined above. - int code; - // Whether userdata was mounted as a result of |fs_mgr_mount_all| call. - bool userdata_mounted; -}; +// fs_mgr_mount_all() updates fstab entries that reference device-mapper. +int fs_mgr_mount_all(android::fs_mgr::Fstab* fstab, int mount_mode); struct HashtreeInfo { // The hash algorithm used to build the merkle tree. @@ -75,13 +70,6 @@ struct HashtreeInfo { bool check_at_most_once; }; -// fs_mgr_mount_all() updates fstab entries that reference device-mapper. -// Returns a |MountAllResult|. The first element is one of the FS_MNG_MNTALL_* return codes -// defined above, and the second element tells whether this call to fs_mgr_mount_all was responsible -// for mounting userdata. Later is required for init to correctly enqueue fs-related events as part -// of userdata remount during userspace reboot. -MountAllResult fs_mgr_mount_all(android::fs_mgr::Fstab* fstab, int mount_mode); - #define FS_MGR_DOMNT_FAILED (-1) #define FS_MGR_DOMNT_BUSY (-2) #define FS_MGR_DOMNT_SUCCESS 0 @@ -116,6 +104,12 @@ int fs_mgr_setup_verity(android::fs_mgr::FstabEntry* fstab, bool wait_for_verity // returned. Otherwise, it will use the current slot. std::string fs_mgr_get_super_partition_name(int slot = -1); +// Set readonly for the block device +bool fs_mgr_set_blk_ro(const std::string& blockdev, bool readonly = true); + +// Check if the block device has ext4 filesystem +bool fs_mgr_is_ext4(const std::string& blk_device); + enum FsMgrUmountStatus : int { SUCCESS = 0, ERROR_UNKNOWN = 1 << 0, @@ -127,11 +121,6 @@ enum FsMgrUmountStatus : int { // it destroys verity devices from device mapper after the device is unmounted. int fs_mgr_umount_all(android::fs_mgr::Fstab* fstab); -// Finds a entry in |fstab| that was used to mount a /data on |data_block_device|. -android::fs_mgr::FstabEntry* fs_mgr_get_mounted_entry_for_userdata( - android::fs_mgr::Fstab* fstab, const std::string& data_block_device); -int fs_mgr_remount_userdata_into_checkpointing(android::fs_mgr::Fstab* fstab); - // Finds the dm_bow device on which this block device is stacked, or returns // empty string std::string fs_mgr_find_bow_device(const std::string& block_device); @@ -144,3 +133,7 @@ bool fs_mgr_create_canonical_mount_point(const std::string& mount_point); // Unlike fs_mgr_overlayfs, mount overlayfs without upperdir and workdir, so the // filesystem cannot be remount read-write. bool fs_mgr_mount_overlayfs_fstab_entry(const android::fs_mgr::FstabEntry& entry); + +// File name used to track if encryption was interrupted, leading to a known bad fs state +std::string fs_mgr_metadata_encryption_in_progress_file_name( + const android::fs_mgr::FstabEntry& entry); diff --git a/fs_mgr/include/fs_mgr/roots.h b/fs_mgr/include/fs_mgr/roots.h index 65c59cfe6..e19f9ad53 100644 --- a/fs_mgr/include/fs_mgr/roots.h +++ b/fs_mgr/include/fs_mgr/roots.h @@ -29,6 +29,8 @@ namespace fs_mgr { // first match or nullptr. FstabEntry* GetEntryForPath(Fstab* fstab, const std::string& path); +std::vector GetEntriesForPath(Fstab* fstab, const std::string& path); + // Make sure that the volume 'path' is on is mounted. // * If 'mount_point' is nullptr, use mount point in fstab. Caller can call // fs_mgr_ensure_path_unmounted() with the same 'path' argument to unmount. diff --git a/fs_mgr/libfiemap/fiemap_writer.cpp b/fs_mgr/libfiemap/fiemap_writer.cpp index 06e210eca..d61e0071b 100644 --- a/fs_mgr/libfiemap/fiemap_writer.cpp +++ b/fs_mgr/libfiemap/fiemap_writer.cpp @@ -60,6 +60,7 @@ static_assert(sizeof(off_t) == sizeof(uint64_t)); static inline void cleanup(const std::string& file_path, bool created) { if (created) { unlink(file_path.c_str()); + sync(); } } diff --git a/fs_mgr/libfs_avb/avb_ops.cpp b/fs_mgr/libfs_avb/avb_ops.cpp index cc1977619..7f0083997 100644 --- a/fs_mgr/libfs_avb/avb_ops.cpp +++ b/fs_mgr/libfs_avb/avb_ops.cpp @@ -34,6 +34,7 @@ #include +#include #include #include #include diff --git a/fs_mgr/libfs_avb/avb_util.cpp b/fs_mgr/libfs_avb/avb_util.cpp index 90b65ce4c..37c9eabb8 100644 --- a/fs_mgr/libfs_avb/avb_util.cpp +++ b/fs_mgr/libfs_avb/avb_util.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include diff --git a/fs_mgr/libfs_avb/fs_avb.cpp b/fs_mgr/libfs_avb/fs_avb.cpp index be48de6cc..4e6aaf500 100644 --- a/fs_mgr/libfs_avb/fs_avb.cpp +++ b/fs_mgr/libfs_avb/fs_avb.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include diff --git a/fs_mgr/libfs_avb/fs_avb_util.cpp b/fs_mgr/libfs_avb/fs_avb_util.cpp index 532622694..a74aa2587 100644 --- a/fs_mgr/libfs_avb/fs_avb_util.cpp +++ b/fs_mgr/libfs_avb/fs_avb_util.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include diff --git a/fs_mgr/libfs_avb/tests/avb_util_test.cpp b/fs_mgr/libfs_avb/tests/avb_util_test.cpp index ee83cda38..85eeeb075 100644 --- a/fs_mgr/libfs_avb/tests/avb_util_test.cpp +++ b/fs_mgr/libfs_avb/tests/avb_util_test.cpp @@ -16,10 +16,11 @@ #include +#include + +#include #include #include -#include -#include #include #include "avb_util.h" @@ -70,7 +71,7 @@ void AvbUtilTest::SetVBMetaFlags(const base::FilePath& image_path, uint32_t flag std::string image_file_name = image_path.RemoveExtension().BaseName().value(); bool is_vbmeta_partition = - base::StartsWith(image_file_name, "vbmeta", base::CompareCase::INSENSITIVE_ASCII); + android::base::StartsWithIgnoreCase(image_file_name, "vbmeta"); android::base::unique_fd fd(open(image_path.value().c_str(), O_RDWR | O_CLOEXEC)); EXPECT_TRUE(fd > 0); @@ -603,7 +604,7 @@ bool AvbUtilTest::CompareVBMeta(const base::FilePath& avb_image_path, std::string image_file_name = avb_image_path.RemoveExtension().BaseName().value(); base::FilePath extracted_vbmeta_path; - if (base::StartsWith(image_file_name, "vbmeta", base::CompareCase::INSENSITIVE_ASCII)) { + if (android::base::StartsWithIgnoreCase(image_file_name, "vbmeta")) { extracted_vbmeta_path = avb_image_path; // no need to extract if it's a vbmeta image. } else { extracted_vbmeta_path = ExtractVBMetaImage(avb_image_path, image_file_name + "-vbmeta.img"); @@ -727,7 +728,10 @@ void AvbUtilTest::ModifyFile(const base::FilePath& file_path, size_t offset, ssi // Introduces a new modification. if (length > 0) { - int modify_location = base::RandInt(offset, offset + length - 1); + // mersenne_twister_engine seeded with the default seed source. + static std::mt19937 gen(std::random_device{}()); + std::uniform_int_distribution<> rand_distribution(offset, offset + length - 1); + int modify_location = rand_distribution(gen); file_content[modify_location] ^= 0x80; last_file_path = file_path.value(); last_modified_location = modify_location; diff --git a/fs_mgr/libfs_avb/tests/basic_test.cpp b/fs_mgr/libfs_avb/tests/basic_test.cpp index d49affb42..e0d465e17 100644 --- a/fs_mgr/libfs_avb/tests/basic_test.cpp +++ b/fs_mgr/libfs_avb/tests/basic_test.cpp @@ -20,7 +20,6 @@ #include #include -#include namespace fs_avb_host_test { diff --git a/fs_mgr/libfs_avb/tests/fs_avb_test.cpp b/fs_mgr/libfs_avb/tests/fs_avb_test.cpp index 2c819a929..5f153f507 100644 --- a/fs_mgr/libfs_avb/tests/fs_avb_test.cpp +++ b/fs_mgr/libfs_avb/tests/fs_avb_test.cpp @@ -18,8 +18,8 @@ #include #include +#include #include -#include #include #include @@ -49,7 +49,7 @@ void PublicFsAvbTest::ModifyVBMetaHeaderFlags(const base::FilePath& vbmeta_image // Only support modifying the flags in vbmeta*.img. std::string image_file_name = vbmeta_image_path.RemoveExtension().BaseName().value(); - ASSERT_TRUE(base::StartsWith(image_file_name, "vbmeta", base::CompareCase::INSENSITIVE_ASCII)); + ASSERT_TRUE(android::base::StartsWithIgnoreCase(image_file_name, "vbmeta")); android::base::unique_fd fd(open(vbmeta_image_path.value().c_str(), O_RDWR | O_CLOEXEC)); EXPECT_TRUE(fd > 0); diff --git a/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp b/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp index 1c95cf0fe..28fdbc286 100644 --- a/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp +++ b/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp @@ -19,8 +19,9 @@ #include #include +#include +#include #include -#include namespace fs_avb_host_test { @@ -64,9 +65,7 @@ std::string BaseFsAvbTest::CalcVBMetaDigest(const std::string& file_name, std::string vbmeta_digest_data; EXPECT_TRUE(base::ReadFileToString(vbmeta_digest_path, &vbmeta_digest_data)); // Returns the trimmed digest. - std::string trimmed_digest_data; - base::TrimString(vbmeta_digest_data, " \t\n", &trimmed_digest_data); - return trimmed_digest_data; + return android::base::Trim(vbmeta_digest_data); } base::FilePath BaseFsAvbTest::GenerateVBMetaImage( @@ -91,7 +90,7 @@ base::FilePath BaseFsAvbTest::GenerateVBMetaImage( // --chain_partitions std::string chain_partition_options; for (const auto& partition : chain_partitions) { - chain_partition_options += base::StringPrintf( + chain_partition_options += android::base::StringPrintf( " --chain_partition %s:%u:%s", partition.partition_name.c_str(), partition.rollback_index_location, partition.key_blob_path.value().c_str()); } diff --git a/fs_mgr/libfs_avb/tests/fs_avb_test_util.h b/fs_mgr/libfs_avb/tests/fs_avb_test_util.h index ab1980bb9..fbda0e16b 100644 --- a/fs_mgr/libfs_avb/tests/fs_avb_test_util.h +++ b/fs_mgr/libfs_avb/tests/fs_avb_test_util.h @@ -26,9 +26,9 @@ #include #include +#include #include #include -#include #include #include @@ -37,7 +37,7 @@ // the command exits normally with exit status |expected_exit_status|. #define EXPECT_COMMAND(expected_exit_status, command_format, ...) \ do { \ - int rc = system(base::StringPrintf(command_format, ##__VA_ARGS__).c_str()); \ + int rc = system(android::base::StringPrintf(command_format, ##__VA_ARGS__).c_str()); \ EXPECT_TRUE(WIFEXITED(rc)); \ EXPECT_EQ(WEXITSTATUS(rc), expected_exit_status); \ } while (0); diff --git a/fs_mgr/libfs_avb/util.cpp b/fs_mgr/libfs_avb/util.cpp index 7783d04e6..79c7b9494 100644 --- a/fs_mgr/libfs_avb/util.cpp +++ b/fs_mgr/libfs_avb/util.cpp @@ -22,6 +22,7 @@ #include +#include #include #include #include diff --git a/fs_mgr/libfs_avb/util.h b/fs_mgr/libfs_avb/util.h index 29d1e9c54..570456046 100644 --- a/fs_mgr/libfs_avb/util.h +++ b/fs_mgr/libfs_avb/util.h @@ -20,12 +20,6 @@ #include #include -#ifdef HOST_TEST -#include -#else -#include -#endif - #include using android::base::ErrnoError; diff --git a/fs_mgr/libfstab/fstab.cpp b/fs_mgr/libfstab/fstab.cpp index 35d5696aa..45ffdc8d0 100644 --- a/fs_mgr/libfstab/fstab.cpp +++ b/fs_mgr/libfstab/fstab.cpp @@ -39,6 +39,10 @@ #include "fstab_priv.h" #include "logging_macros.h" +#if !defined(MS_LAZYTIME) +#define MS_LAZYTIME (1 << 25) +#endif + using android::base::EndsWith; using android::base::ParseByteCount; using android::base::ParseInt; @@ -74,6 +78,7 @@ FlagList kMountFlagsList[] = { {"private", MS_PRIVATE}, {"slave", MS_SLAVE}, {"shared", MS_SHARED}, + {"lazytime", MS_LAZYTIME}, {"defaults", 0}, }; @@ -521,6 +526,24 @@ std::vector GetEntriesByPred(FstabPtr fstab, const Pred& pre } // namespace +// Return the path to the recovery fstab file. There may be multiple fstab files; +// the one that is returned will be the first that exists of recovery.fstab., +// recovery.fstab., and recovery.fstab.. +std::string GetRecoveryFstabPath() { + for (const char* prop : {"fstab_suffix", "hardware", "hardware.platform"}) { + std::string suffix; + + if (!fs_mgr_get_boot_config(prop, &suffix)) continue; + + std::string fstab_path = "/etc/recovery.fstab." + suffix; + if (access(fstab_path.c_str(), F_OK) == 0) { + return fstab_path; + } + } + + return "/etc/recovery.fstab"; +} + // Return the path to the fstab file. There may be multiple fstab files; the // one that is returned will be the first that exists of fstab., // fstab., and fstab.. The fstab is searched for @@ -530,7 +553,7 @@ std::vector GetEntriesByPred(FstabPtr fstab, const Pred& pre // the system/etc directory is supported too and is the preferred location. std::string GetFstabPath() { if (InRecovery()) { - return "/etc/recovery.fstab"; + return GetRecoveryFstabPath(); } for (const char* prop : {"fstab_suffix", "hardware", "hardware.platform"}) { std::string suffix; diff --git a/fs_mgr/libfstab/fstab_priv.h b/fs_mgr/libfstab/fstab_priv.h index 5105da001..73cb17518 100644 --- a/fs_mgr/libfstab/fstab_priv.h +++ b/fs_mgr/libfstab/fstab_priv.h @@ -39,9 +39,6 @@ std::string GetFstabPath(); void ImportBootconfigFromString(const std::string& bootconfig, const std::function& fn); -bool GetBootconfigFromString(const std::string& bootconfig, const std::string& key, - std::string* out); - void ImportKernelCmdlineFromString(const std::string& cmdline, const std::function& fn); diff --git a/fs_mgr/libfstab/include/fstab/fstab.h b/fs_mgr/libfstab/include/fstab/fstab.h index d01753b9a..368804fc4 100644 --- a/fs_mgr/libfstab/include/fstab/fstab.h +++ b/fs_mgr/libfstab/include/fstab/fstab.h @@ -154,5 +154,8 @@ bool GetKernelCmdline(const std::string& key, std::string* out); // Return the "other" slot for the given slot suffix. std::string OtherSlotSuffix(const std::string& suffix); +bool GetBootconfigFromString(const std::string& bootconfig, const std::string& key, + std::string* out); + } // namespace fs_mgr } // namespace android diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp index 39b5b7653..50efb03bd 100644 --- a/fs_mgr/libsnapshot/Android.bp +++ b/fs_mgr/libsnapshot/Android.bp @@ -91,6 +91,7 @@ filegroup { "partition_cow_creator.cpp", "return.cpp", "utility.cpp", + "scratch_super.cpp", ], } @@ -110,6 +111,9 @@ cc_library_static { static_libs: [ "libfs_mgr_binder", ], + whole_static_libs: [ + "libselinux", + ], } cc_library { @@ -128,6 +132,9 @@ cc_library { static_libs: [ "libsnapshot_cow", ], + whole_static_libs: [ + "libselinux", + ], } cc_library_static { @@ -142,6 +149,7 @@ cc_library_static { ], static_libs: [ "libfs_mgr", + "libselinux", ], } @@ -159,6 +167,9 @@ cc_library_static { static_libs: [ "libfs_mgr", ], + whole_static_libs: [ + "libselinux", + ], } cc_defaults { @@ -241,6 +252,7 @@ cc_library_static { "libfs_mgr", "libgmock", "libgtest", + "libselinux", ], } @@ -283,7 +295,6 @@ cc_defaults { ], auto_gen_config: true, require_root: true, - compile_multilib: "first", } cc_test { @@ -294,8 +305,17 @@ cc_test { ], test_suites: [ "vts", - "device-tests", + "general-tests", ], + compile_multilib: "both", + multilib: { + lib32: { + suffix: "32", + }, + lib64: { + suffix: "64", + }, + }, test_options: { min_shipping_api_level: 30, }, @@ -311,11 +331,18 @@ cc_test { "-DLIBSNAPSHOT_TEST_VAB_LEGACY", ], test_suites: [ - "device-tests", + "general-tests", ], + compile_multilib: "64", test_options: { // Legacy VAB launched in Android R. min_shipping_api_level: 30, + test_runner_options: [ + { + name: "force-no-test-error", + value: "false", + }, + ], }, } @@ -345,6 +372,7 @@ cc_binary { ], srcs: [ "snapshotctl.cpp", + "scratch_super.cpp", ], static_libs: [ "libbrotli", @@ -414,7 +442,7 @@ cc_test { "libstorage_literals_headers", ], test_suites: [ - "device-tests", + "general-tests", ], test_options: { min_shipping_api_level: 30, diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto index 2e948dda7..62f99013e 100644 --- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto +++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto @@ -229,6 +229,10 @@ message SnapshotUpdateStatus { // Enable direct reads from source device bool o_direct = 12; + + // Number of cow operations to be merged at once + uint32 cow_op_merge_size = 13; + } // Next: 10 diff --git a/fs_mgr/libsnapshot/device_info.cpp b/fs_mgr/libsnapshot/device_info.cpp index 0ab61033f..19f3e0293 100644 --- a/fs_mgr/libsnapshot/device_info.cpp +++ b/fs_mgr/libsnapshot/device_info.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "device_info.h" +#include "scratch_super.h" #include #include @@ -37,8 +38,24 @@ constexpr bool kIsRecovery = true; constexpr bool kIsRecovery = false; #endif +DeviceInfo::DeviceInfo() { + std::string scratch_device = android::snapshot::GetScratchOtaMetadataPartition(); + if (!scratch_device.empty()) { + std::string scratch_metadata = + android::snapshot::MapScratchOtaMetadataPartition(scratch_device); + if (!scratch_metadata.empty()) { + SetMetadataDir(scratch_metadata); + SetTempMetadata(); + } + } +} + std::string DeviceInfo::GetMetadataDir() const { - return "/metadata/ota"s; + return metadata_dir_; +} + +void DeviceInfo::SetMetadataDir(const std::string& value) { + metadata_dir_ = value; } std::string DeviceInfo::GetSlotSuffix() const { @@ -104,6 +121,24 @@ bool DeviceInfo::IsFirstStageInit() const { return first_stage_init_; } +bool DeviceInfo::SetActiveBootSlot([[maybe_unused]] unsigned int slot) { +#ifdef LIBSNAPSHOT_USE_HAL + if (!EnsureBootHal()) { + return false; + } + + CommandResult result = boot_control_->SetActiveBootSlot(slot); + if (!result.success) { + LOG(ERROR) << "Error setting slot " << slot << " active: " << result.errMsg; + return false; + } + return true; +#else + LOG(ERROR) << "HAL support not enabled."; + return false; +#endif +} + bool DeviceInfo::SetSlotAsUnbootable([[maybe_unused]] unsigned int slot) { #ifdef LIBSNAPSHOT_USE_HAL if (!EnsureBootHal()) { diff --git a/fs_mgr/libsnapshot/device_info.h b/fs_mgr/libsnapshot/device_info.h index d06f1be7c..e93ec4960 100644 --- a/fs_mgr/libsnapshot/device_info.h +++ b/fs_mgr/libsnapshot/device_info.h @@ -29,6 +29,7 @@ class DeviceInfo final : public SnapshotManager::IDeviceInfo { using MergeStatus = ::aidl::android::hardware::boot::MergeStatus; public: + DeviceInfo(); std::string GetMetadataDir() const override; std::string GetSlotSuffix() const override; std::string GetOtherSlotSuffix() const override; @@ -36,19 +37,25 @@ class DeviceInfo final : public SnapshotManager::IDeviceInfo { std::string GetSuperDevice(uint32_t slot) const override; bool IsOverlayfsSetup() const override; bool SetBootControlMergeStatus(MergeStatus status) override; + bool SetActiveBootSlot(unsigned int slot) override; bool SetSlotAsUnbootable(unsigned int slot) override; bool IsRecovery() const override; std::unique_ptr OpenImageManager() const override; bool IsFirstStageInit() const override; android::dm::IDeviceMapper& GetDeviceMapper() override; - + void SetMetadataDir(const std::string& value); void set_first_stage_init(bool value) { first_stage_init_ = value; } + bool IsTempMetadata() const override { return temp_metadata_; } + void SetTempMetadata() { temp_metadata_ = true; } private: bool EnsureBootHal(); android::fs_mgr::PartitionOpener opener_; bool first_stage_init_ = false; + // Default value + std::string metadata_dir_ = "/metadata/ota"; + bool temp_metadata_ = false; #ifdef LIBSNAPSHOT_USE_HAL std::unique_ptr<::android::hal::BootControlClient> boot_control_; #endif diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h index 573a85b24..ca1ac1e6b 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h @@ -29,6 +29,7 @@ class MockDeviceInfo : public SnapshotManager::IDeviceInfo { MOCK_METHOD(const android::fs_mgr::IPartitionOpener&, GetPartitionOpener, (), (const)); MOCK_METHOD(bool, IsOverlayfsSetup, (), (const, override)); MOCK_METHOD(bool, SetBootControlMergeStatus, (MergeStatus status), (override)); + MOCK_METHOD(bool, SetActiveBootSlot, (unsigned int slot), (override)); MOCK_METHOD(bool, SetSlotAsUnbootable, (unsigned int slot), (override)); MOCK_METHOD(bool, IsRecovery, (), (const, override)); MOCK_METHOD(bool, IsFirstStageInit, (), (const, override)); diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h index 1ec863444..7ae55db31 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h @@ -104,12 +104,14 @@ class ISnapshotManager { virtual const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const = 0; virtual bool IsOverlayfsSetup() const = 0; virtual bool SetBootControlMergeStatus(MergeStatus status) = 0; + virtual bool SetActiveBootSlot(unsigned int slot) = 0; virtual bool SetSlotAsUnbootable(unsigned int slot) = 0; virtual bool IsRecovery() const = 0; virtual bool IsTestDevice() const { return false; } virtual bool IsFirstStageInit() const = 0; virtual std::unique_ptr OpenImageManager() const = 0; virtual android::dm::IDeviceMapper& GetDeviceMapper() = 0; + virtual bool IsTempMetadata() const = 0; // Helper method for implementing OpenImageManager. std::unique_ptr OpenImageManager(const std::string& gsid_dir) const; @@ -328,6 +330,10 @@ class SnapshotManager final : public ISnapshotManager { // might be needed to perform first-stage mounts. static bool IsSnapshotManagerNeeded(); + // Map the temp OTA metadata partition from super + static bool MapTempOtaMetadataPartitionIfNeeded( + const std::function& init); + // Helper function for second stage init to restorecon on the rollback indicator. static std::string GetGlobalRollbackIndicatorPath(); @@ -675,6 +681,8 @@ class SnapshotManager final : public ISnapshotManager { std::string GetBootSnapshotsWithoutSlotSwitchPath(); std::string GetSnapuserdFromSystemPath(); + bool HasForwardMergeIndicator(); + const LpMetadata* ReadOldPartitionMetadata(LockedFile* lock); bool MapAllPartitions(LockedFile* lock, const std::string& super_device, uint32_t slot, @@ -785,11 +793,8 @@ class SnapshotManager final : public ISnapshotManager { bool UpdateForwardMergeIndicator(bool wipe); // Helper for HandleImminentDataWipe. - // Call ProcessUpdateState and handle states with special rules before data wipe. Specifically, - // if |allow_forward_merge| and allow-forward-merge indicator exists, initiate merge if - // necessary. - UpdateState ProcessUpdateStateOnDataWipe(bool allow_forward_merge, - const std::function& callback); + // Call ProcessUpdateState and handle states with special rules before data wipe. + UpdateState ProcessUpdateStateOnDataWipe(const std::function& callback); // Return device string of a mapped image, or if it is not available, the mapped image path. bool GetMappedImageDeviceStringOrPath(const std::string& device_name, @@ -831,6 +836,8 @@ class SnapshotManager final : public ISnapshotManager { // Check if direct reads are enabled for the source image bool UpdateUsesODirect(LockedFile* lock); + // Get value of maximum cow op merge size + uint32_t GetUpdateCowOpMergeSize(LockedFile* lock); // Wrapper around libdm, with diagnostics. bool DeleteDeviceIfExists(const std::string& name, const std::chrono::milliseconds& timeout_ms = {}); @@ -846,7 +853,6 @@ class SnapshotManager final : public ISnapshotManager { std::string metadata_dir_; std::unique_ptr images_; bool use_first_stage_snapuserd_ = false; - bool in_factory_data_reset_ = false; std::function uevent_regen_callback_; std::unique_ptr snapuserd_client_; std::unique_ptr old_partition_metadata_; diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h index 90813fe79..1cd66515e 100644 --- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h +++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h @@ -30,6 +30,8 @@ #include #include +#include "utility.h" + namespace android { namespace snapshot { @@ -77,7 +79,7 @@ class TestDeviceInfo : public SnapshotManager::IDeviceInfo { : TestDeviceInfo(fake_super) { set_slot_suffix(slot_suffix); } - std::string GetMetadataDir() const override { return "/metadata/ota/test"s; } + std::string GetMetadataDir() const override { return metadata_dir_; } std::string GetSlotSuffix() const override { return slot_suffix_; } std::string GetOtherSlotSuffix() const override { return slot_suffix_ == "_a" ? "_b" : "_a"; } std::string GetSuperDevice([[maybe_unused]] uint32_t slot) const override { return "super"; } @@ -90,6 +92,7 @@ class TestDeviceInfo : public SnapshotManager::IDeviceInfo { } bool IsOverlayfsSetup() const override { return false; } bool IsRecovery() const override { return recovery_; } + bool SetActiveBootSlot([[maybe_unused]] unsigned int slot) override { return true; } bool SetSlotAsUnbootable(unsigned int slot) override { unbootable_slots_.insert(slot); return true; @@ -117,6 +120,7 @@ class TestDeviceInfo : public SnapshotManager::IDeviceInfo { void set_dm(android::dm::IDeviceMapper* dm) { dm_ = dm; } MergeStatus merge_status() const { return merge_status_; } + bool IsTempMetadata() const override { return temp_metadata_; } private: std::string slot_suffix_ = "_a"; @@ -126,6 +130,8 @@ class TestDeviceInfo : public SnapshotManager::IDeviceInfo { bool first_stage_init_ = false; std::unordered_set unbootable_slots_; android::dm::IDeviceMapper* dm_ = nullptr; + std::string metadata_dir_ = "/metadata/ota/test"; + bool temp_metadata_ = false; }; class DeviceMapperWrapper : public android::dm::IDeviceMapper { @@ -234,5 +240,21 @@ bool IsVirtualAbEnabled(); #define RETURN_IF_NON_VIRTUAL_AB() RETURN_IF_NON_VIRTUAL_AB_MSG("") +#define SKIP_IF_VENDOR_ON_ANDROID_S() \ + do { \ + if (IsVendorFromAndroid12()) \ + GTEST_SKIP() << "Skip test as Vendor partition is on Android S"; \ + } while (0) + +#define RETURN_IF_VENDOR_ON_ANDROID_S_MSG(msg) \ + do { \ + if (IsVendorFromAndroid12()) { \ + std::cerr << (msg); \ + return; \ + } \ + } while (0) + +#define RETURN_IF_VENDOR_ON_ANDROID_S() RETURN_IF_VENDOR_ON_ANDROID_S_MSG("") + } // namespace snapshot } // namespace android diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h index 871ed273f..9e7cf7a2c 100644 --- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h +++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h @@ -82,7 +82,7 @@ class CowWriterV3 : public CowWriterBase { CowOperationType type); size_t GetCompressionFactor(const size_t blocks_to_compress, CowOperationType type) const; - constexpr bool IsBlockAligned(const size_t size) { + constexpr bool IsBlockAligned(const uint64_t size) { // These are the only block size supported. Block size beyond 256k // may impact random read performance post OTA boot. const size_t values[] = {4_KiB, 8_KiB, 16_KiB, 32_KiB, 64_KiB, 128_KiB, 256_KiB}; diff --git a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp index a4a2c1a68..8356c0c24 100644 --- a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp +++ b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp @@ -250,8 +250,8 @@ TEST_F(PartitionCowCreatorTest, CompressionEnabled) { .target_partition = system_b, .current_metadata = builder_a.get(), .current_suffix = "_a", - .using_snapuserd = true, - .update = &update}; + .update = &update, + .using_snapuserd = true}; auto ret = creator.Run(); ASSERT_TRUE(ret.has_value()); @@ -276,8 +276,8 @@ TEST_F(PartitionCowCreatorTest, CompressionWithNoManifest) { .target_partition = system_b, .current_metadata = builder_a.get(), .current_suffix = "_a", - .using_snapuserd = true, - .update = nullptr}; + .update = nullptr, + .using_snapuserd = true}; auto ret = creator.Run(); ASSERT_FALSE(ret.has_value()); diff --git a/fs_mgr/libsnapshot/scratch_super.cpp b/fs_mgr/libsnapshot/scratch_super.cpp new file mode 100644 index 000000000..93c4bbd99 --- /dev/null +++ b/fs_mgr/libsnapshot/scratch_super.cpp @@ -0,0 +1,416 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "device_info.h" +#include "scratch_super.h" + +using namespace std::literals; +using namespace android::dm; +using namespace android::fs_mgr; +using namespace android::storage_literals; + +namespace android { +namespace snapshot { + +static bool UmountScratch() { + auto ota_dir = std::string(kOtaMetadataMount) + "/" + "ota"; + std::error_code ec; + + if (std::filesystem::remove_all(ota_dir, ec) == static_cast(-1)) { + LOG(ERROR) << "Failed to remove OTA directory: " << ec.message(); + return false; + } + + if (umount(kOtaMetadataMount) != 0) { + PLOG(ERROR) << "UmountScratch failed"; + return false; + } + + LOG(INFO) << "umount scratch_super success"; + return true; +} + +bool CleanupScratchOtaMetadataIfPresent(const ISnapshotManager::IDeviceInfo* info) { + if (!UmountScratch()) { + return false; + } + + std::unique_ptr builder; + const auto partition_name = android::base::Basename(kOtaMetadataMount); + const std::vector slots = {0, 1}; + + if (info == nullptr) { + info = new android::snapshot::DeviceInfo(); + } + + std::string super_device; + if (info->IsTestDevice()) { + super_device = "super"; + } else { + super_device = kPhysicalDevice + fs_mgr_get_super_partition_name(); + } + const auto& opener = info->GetPartitionOpener(); + std::string slot_suffix = info->GetSlotSuffix(); + // Walk both the slots and clean up metadata related to scratch space from + // both the slots. + for (auto slot : slots) { + std::unique_ptr builder = MetadataBuilder::New(opener, super_device, slot); + if (!builder) { + return false; + } + + if (builder->FindPartition(partition_name) != nullptr) { + builder->RemovePartition(partition_name); + auto metadata = builder->Export(); + if (!metadata) { + return false; + } + if (!UpdatePartitionTable(info->GetPartitionOpener(), super_device, *metadata.get(), + slot)) { + LOG(ERROR) << "UpdatePartitionTable failed for slot: " << slot; + return false; + } + if (DestroyLogicalPartition(partition_name)) { + LOG(INFO) << "CleanupScratchOtaMetadata success for slot: " << slot; + } + } + } + + return true; +} + +static bool SetupOTADirs() { + if (setfscreatecon(android::snapshot::kOtaMetadataFileContext)) { + PLOG(ERROR) << "setfscreatecon failed: " << android::snapshot::kOtaMetadataFileContext; + return false; + } + const auto ota_dir = std::string(kOtaMetadataMount) + "/" + "ota"; + if (mkdir(ota_dir.c_str(), 0755) != 0 && errno != EEXIST) { + PLOG(ERROR) << "mkdir " << ota_dir; + return false; + } + + const auto snapshot_dir = ota_dir + "/" + "snapshots"; + if (mkdir(snapshot_dir.c_str(), 0755) != 0 && errno != EEXIST) { + PLOG(ERROR) << "mkdir " << snapshot_dir; + return false; + } + if (setfscreatecon(nullptr)) { + PLOG(ERROR) << "setfscreatecon null"; + return false; + } + return true; +} + +static bool MountScratch(const std::string& device_path) { + if (access(device_path.c_str(), R_OK | W_OK)) { + LOG(ERROR) << "Path does not exist or is not readwrite: " << device_path; + return false; + } + + std::string filesystem_candidate; + if (fs_mgr_is_ext4(device_path)) { + filesystem_candidate = "ext4"; + } else { + LOG(ERROR) << "Scratch partition is not ext4"; + return false; + } + if (setfscreatecon(android::snapshot::kOtaMetadataFileContext)) { + PLOG(ERROR) << "setfscreatecon failed: " << android::snapshot::kOtaMetadataFileContext; + return false; + } + if (mkdir(kOtaMetadataMount, 0755) && (errno != EEXIST)) { + PLOG(ERROR) << "create " << kOtaMetadataMount; + return false; + } + + android::fs_mgr::FstabEntry entry; + entry.blk_device = device_path; + entry.mount_point = kOtaMetadataMount; + entry.flags = MS_NOATIME; + entry.flags |= MS_SYNCHRONOUS; + entry.fs_options = "nodiscard"; + fs_mgr_set_blk_ro(device_path, false); + entry.fs_mgr_flags.check = true; + + bool mounted = false; + entry.fs_type = filesystem_candidate.c_str(); + if (fs_mgr_do_mount_one(entry) == 0) { + mounted = true; + } + + if (setfscreatecon(nullptr)) { + PLOG(ERROR) << "setfscreatecon null"; + return false; + } + if (!mounted) { + rmdir(kOtaMetadataMount); + return false; + } + + return true; +} + +static bool MakeScratchFilesystem(const std::string& scratch_device) { + std::string fs_type; + std::string command; + if (!access(kMkExt4, X_OK)) { + fs_type = "ext4"; + command = kMkExt4 + " -F -b 4096 -t ext4 -m 0 -O has_journal -M "s + kOtaMetadataMount; + } else { + LOG(ERROR) << "No supported mkfs command or filesystem driver available, supported " + "filesystems " + "are: f2fs, ext4"; + return false; + } + command += " " + scratch_device + " >/dev/null 2>/dev/null GetPartitionOpener(); + std::string slot_suffix = info->GetSlotSuffix(); + int slot = SlotNumberForSlotSuffix(slot_suffix); + std::unique_ptr builder = MetadataBuilder::New(opener, super_device, slot); + + if (!builder) { + LOG(ERROR) << "open " << super_device << " failed"; + return false; + } + + auto partition = builder->FindPartition(partition_name); + partition_exists = partition != nullptr; + if (partition_exists) { + LOG(ERROR) << "Partition exists in super metadata"; + return false; + } + + partition = builder->AddPartition(partition_name, LP_PARTITION_ATTR_NONE); + if (!partition) { + LOG(ERROR) << "AddPartition failed " << partition_name; + return false; + } + + auto free_space = builder->AllocatableSpace() - builder->UsedSpace(); + if (free_space < kOtaMetadataPartitionSize) { + LOG(ERROR) << "No space in super partition. Free space: " << free_space + << " Requested space: " << kOtaMetadataPartitionSize; + return false; + } + + LOG(INFO) << "CreateDynamicScratch: free_space: " << free_space + << " scratch_size: " << kOtaMetadataPartitionSize << " slot_number: " << slot; + + if (!builder->ResizePartition(partition, kOtaMetadataPartitionSize)) { + LOG(ERROR) << "ResizePartition failed: " << partition_name << " free_space: " << free_space + << " scratch_size: " << kOtaMetadataPartitionSize; + return false; + } + + auto metadata = builder->Export(); + CreateLogicalPartitionParams params; + + if (!metadata || + !UpdatePartitionTable(info->GetPartitionOpener(), super_device, *metadata.get(), slot)) { + LOG(ERROR) << "UpdatePartitionTable failed: " << partition_name; + return false; + } + params = { + .block_device = super_device, + .metadata_slot = slot, + .partition_name = partition_name, + .force_writable = true, + .timeout_ms = 10s, + .partition_opener = &info->GetPartitionOpener(), + }; + + if (!CreateLogicalPartition(params, scratch_device)) { + LOG(ERROR) << "CreateLogicalPartition failed"; + return false; + } + + LOG(INFO) << "Scratch device created successfully: " << *scratch_device << " slot: " << slot; + return true; +} + +bool IsScratchOtaMetadataOnSuper() { + auto partition_name = android::base::Basename(kOtaMetadataMount); + auto source_slot = fs_mgr_get_slot_suffix(); + auto source_slot_number = SlotNumberForSlotSuffix(source_slot); + + const auto super_device = + kPhysicalDevice + fs_mgr_get_super_partition_name(!source_slot_number); + + auto metadata = android::fs_mgr::ReadMetadata(super_device, !source_slot_number); + if (!metadata) { + return false; + } + auto partition = android::fs_mgr::FindPartition(*metadata.get(), partition_name); + if (!partition) { + return false; + } + + auto& dm = DeviceMapper::Instance(); + if (dm.GetState(partition_name) == DmDeviceState::ACTIVE) { + LOG(INFO) << "Partition: " << partition_name << " is active"; + return true; + } + + CreateLogicalPartitionParams params = { + .block_device = super_device, + .metadata = metadata.get(), + .partition = partition, + }; + + std::string scratch_path; + if (!CreateLogicalPartition(params, &scratch_path)) { + LOG(ERROR) << "Could not create logical partition: " << partition_name; + return false; + } + LOG(INFO) << "Scratch device: " << scratch_path << " created successfully"; + + return true; +} + +std::string GetScratchOtaMetadataPartition() { + std::string device; + auto& dm = DeviceMapper::Instance(); + auto partition_name = android::base::Basename(kOtaMetadataMount); + + bool invalid_partition = (dm.GetState(partition_name) == DmDeviceState::INVALID); + if (!invalid_partition && dm.GetDmDevicePathByName(partition_name, &device)) { + return device; + } + return ""; +} + +static bool ScratchAlreadyMounted(const std::string& mount_point) { + android::fs_mgr::Fstab fstab; + if (!ReadFstabFromProcMounts(&fstab)) { + return false; + } + for (const auto& entry : GetEntriesForMountPoint(&fstab, mount_point)) { + if (entry->fs_type == "ext4") { + return true; + } + } + return false; +} + +std::string MapScratchOtaMetadataPartition(const std::string& scratch_device) { + if (!ScratchAlreadyMounted(kOtaMetadataMount)) { + if (!MountScratch(scratch_device)) { + return ""; + } + } + + auto ota_dir = std::string(kOtaMetadataMount) + "/" + "ota"; + if (access(ota_dir.c_str(), F_OK) != 0) { + return ""; + } + return ota_dir; +} + +// Entry point to create a scratch device on super partition +// This will create a 1MB space in super. The space will be +// from the current active slot. Ext4 filesystem will be created +// on this scratch device and all the OTA related directories +// will be created. +bool CreateScratchOtaMetadataOnSuper(const ISnapshotManager::IDeviceInfo* info) { + std::string scratch_device; + + if (!CreateDynamicScratch(info, &scratch_device)) { + LOG(ERROR) << "CreateDynamicScratch failed"; + return false; + } + if (!MakeScratchFilesystem(scratch_device)) { + LOG(ERROR) << "MakeScratchFilesystem failed"; + return false; + } + if (!MountScratch(scratch_device)) { + LOG(ERROR) << "MountScratch failed"; + return false; + } + if (!SetupOTADirs()) { + LOG(ERROR) << "SetupOTADirs failed"; + return false; + } + return true; +} + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/scratch_super.h b/fs_mgr/libsnapshot/scratch_super.h new file mode 100644 index 000000000..3e6fe702f --- /dev/null +++ b/fs_mgr/libsnapshot/scratch_super.h @@ -0,0 +1,33 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +namespace android { +namespace snapshot { + +constexpr char kMkExt4[] = "/system/bin/mke2fs"; +constexpr char kOtaMetadataFileContext[] = "u:object_r:ota_metadata_file:s0"; +constexpr char kOtaMetadataMount[] = "/mnt/scratch_ota_metadata_super"; +const size_t kOtaMetadataPartitionSize = uint64_t(1 * 1024 * 1024); +constexpr char kPhysicalDevice[] = "/dev/block/by-name/"; + +bool IsScratchOtaMetadataOnSuper(); +std::string GetScratchOtaMetadataPartition(); +std::string MapScratchOtaMetadataPartition(const std::string& device); +bool CreateScratchOtaMetadataOnSuper(const ISnapshotManager::IDeviceInfo* info = nullptr); +bool CleanupScratchOtaMetadataIfPresent(const ISnapshotManager::IDeviceInfo* info = nullptr); + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp index e4a6153a8..6c3bedd86 100644 --- a/fs_mgr/libsnapshot/snapshot.cpp +++ b/fs_mgr/libsnapshot/snapshot.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -47,6 +48,7 @@ #include #include "device_info.h" #include "partition_cow_creator.h" +#include "scratch_super.h" #include "snapshot_metadata_updater.h" #include "utility.h" @@ -116,7 +118,11 @@ std::unique_ptr SnapshotManager::New(IDeviceInfo* info) { info = new DeviceInfo(); } - return std::unique_ptr(new SnapshotManager(info)); + auto sm = std::unique_ptr(new SnapshotManager(info)); + if (info->IsTempMetadata()) { + LOG(INFO) << "Using temp metadata from super"; + } + return sm; } std::unique_ptr SnapshotManager::NewForFirstStageMount(IDeviceInfo* info) { @@ -1109,6 +1115,13 @@ UpdateState SnapshotManager::ProcessUpdateState(const std::function& cal if (result.state == UpdateState::MergeFailed) { AcknowledgeMergeFailure(result.failure_code); } + + if (result.state == UpdateState::MergeCompleted) { + if (device_->IsTempMetadata()) { + CleanupScratchOtaMetadataIfPresent(); + } + } + if (result.state != UpdateState::Merging) { // Either there is no merge, or the merge was finished, so no need // to keep waiting. @@ -1708,6 +1721,10 @@ bool SnapshotManager::PerformInitTransition(InitTransition transition, if (UpdateUsesODirect(lock.get())) { snapuserd_argv->emplace_back("-o_direct"); } + uint cow_op_merge_size = GetUpdateCowOpMergeSize(lock.get()); + if (cow_op_merge_size != 0) { + snapuserd_argv->emplace_back("-cow_op_merge_size=" + std::to_string(cow_op_merge_size)); + } } size_t num_cows = 0; @@ -2130,6 +2147,11 @@ bool SnapshotManager::UpdateUsesODirect(LockedFile* lock) { return update_status.o_direct(); } +uint32_t SnapshotManager::GetUpdateCowOpMergeSize(LockedFile* lock) { + SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock); + return update_status.cow_op_merge_size(); +} + bool SnapshotManager::MarkSnapuserdFromSystem() { auto path = GetSnapuserdFromSystemPath(); @@ -2300,7 +2322,27 @@ bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector* } bool SnapshotManager::IsSnapshotManagerNeeded() { - return access(kBootIndicatorPath, F_OK) == 0; + if (access(kBootIndicatorPath, F_OK) == 0) { + return true; + } + + if (IsScratchOtaMetadataOnSuper()) { + return true; + } + + return false; +} + +bool SnapshotManager::MapTempOtaMetadataPartitionIfNeeded( + const std::function& init) { + auto device = android::snapshot::GetScratchOtaMetadataPartition(); + if (!device.empty()) { + init(device); + if (android::snapshot::MapScratchOtaMetadataPartition(device).empty()) { + return false; + } + } + return true; } std::string SnapshotManager::GetGlobalRollbackIndicatorPath() { @@ -2387,6 +2429,12 @@ bool SnapshotManager::MapAllPartitions(LockedFile* lock, const std::string& supe continue; } + if (GetPartitionName(partition) == + android::base::Basename(android::snapshot::kOtaMetadataMount)) { + LOG(INFO) << "Partition: " << GetPartitionName(partition) << " skipping"; + continue; + } + CreateLogicalPartitionParams params = { .block_device = super_device, .metadata = metadata.get(), @@ -2892,10 +2940,12 @@ bool SnapshotManager::UnmapAllSnapshots() { } bool SnapshotManager::UnmapAllSnapshots(LockedFile* lock) { + LOG(INFO) << "Lock acquired for " << __FUNCTION__; std::vector snapshots; if (!ListSnapshots(lock, &snapshots)) { return false; } + LOG(INFO) << "Found " << snapshots.size() << " partitions with snapshots"; for (const auto& snapshot : snapshots) { if (!UnmapPartitionWithSnapshot(lock, snapshot)) { @@ -2903,6 +2953,7 @@ bool SnapshotManager::UnmapAllSnapshots(LockedFile* lock) { return false; } } + LOG(INFO) << "Unmapped " << snapshots.size() << " partitions with snapshots"; // Terminate the daemon and release the snapuserd_client_ object. // If we need to re-connect with the daemon, EnsureSnapuserdConnected() @@ -2918,6 +2969,7 @@ bool SnapshotManager::UnmapAllSnapshots(LockedFile* lock) { auto SnapshotManager::OpenFile(const std::string& file, int lock_flags) -> std::unique_ptr { + const auto start = std::chrono::system_clock::now(); unique_fd fd(open(file.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW)); if (fd < 0) { PLOG(ERROR) << "Open failed: " << file; @@ -2930,6 +2982,11 @@ auto SnapshotManager::OpenFile(const std::string& file, // For simplicity, we want to CHECK that lock_mode == LOCK_EX, in some // calls, so strip extra flags. int lock_mode = lock_flags & (LOCK_EX | LOCK_SH); + const auto end = std::chrono::system_clock::now(); + const auto duration_ms = std::chrono::duration_cast(end - start); + if (duration_ms >= 1000ms) { + LOG(INFO) << "Taking lock on " << file << " took " << duration_ms.count() << "ms"; + } return std::make_unique(file, std::move(fd), lock_mode); } @@ -3082,6 +3139,7 @@ bool SnapshotManager::WriteUpdateState(LockedFile* lock, UpdateState state, status.set_io_uring_enabled(old_status.io_uring_enabled()); status.set_legacy_snapuserd(old_status.legacy_snapuserd()); status.set_o_direct(old_status.o_direct()); + status.set_cow_op_merge_size(old_status.cow_op_merge_size()); } return WriteSnapshotUpdateStatus(lock, status); } @@ -3464,6 +3522,8 @@ Return SnapshotManager::CreateUpdateSnapshots(const DeltaArchiveManifest& manife status.set_legacy_snapuserd(true); LOG(INFO) << "Setting legacy_snapuserd to true"; } + status.set_cow_op_merge_size( + android::base::GetUintProperty("ro.virtual_ab.cow_op_merge_size", 0)); } else if (legacy_compression) { LOG(INFO) << "Virtual A/B using legacy snapuserd"; } else { @@ -3899,6 +3959,7 @@ bool SnapshotManager::Dump(std::ostream& os) { ss << "Using userspace snapshots: " << update_status.userspace_snapshots() << std::endl; ss << "Using io_uring: " << update_status.io_uring_enabled() << std::endl; ss << "Using o_direct: " << update_status.o_direct() << std::endl; + ss << "Cow op merge size (0 for uncapped): " << update_status.cow_op_merge_size() << std::endl; ss << "Using XOR compression: " << GetXorCompressionEnabledProperty() << std::endl; ss << "Current slot: " << device_->GetSlotSuffix() << std::endl; ss << "Boot indicator: booting from " << GetCurrentSlot() << " slot" << std::endl; @@ -3982,44 +4043,90 @@ bool SnapshotManager::HandleImminentDataWipe(const std::function& callba // We allow the wipe to continue, because if we can't mount /metadata, // it is unlikely the device would have booted anyway. If there is no // metadata partition, then the device predates Virtual A/B. + LOG(INFO) << "/metadata not found; allowing wipe."; return true; } - // Check this early, so we don't accidentally start trying to populate - // the state file in recovery. Note we don't call GetUpdateState since - // we want errors in acquiring the lock to be propagated, instead of - // returning UpdateState::None. - auto state_file = GetStateFilePath(); - if (access(state_file.c_str(), F_OK) != 0 && errno == ENOENT) { - return true; - } - - auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); - auto super_path = device_->GetSuperDevice(slot_number); - if (!CreateLogicalAndSnapshotPartitions(super_path, 20s)) { - LOG(ERROR) << "Unable to map partitions to complete merge."; - return false; - } - - auto process_callback = [&]() -> bool { - if (callback) { - callback(); + // This could happen if /metadata mounted but there is no filesystem + // structure. Weird, but we have to assume there's no OTA pending, and + // thus we let the wipe proceed. + UpdateState state; + { + auto lock = LockExclusive(); + if (!lock) { + LOG(ERROR) << "Unable to determine update state; allowing wipe."; + return true; } - return true; - }; - in_factory_data_reset_ = true; - UpdateState state = - ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback); - in_factory_data_reset_ = false; - - if (state == UpdateState::MergeFailed) { - return false; + state = ReadUpdateState(lock.get()); + LOG(INFO) << "Update state before wipe: " << state << "; slot: " << GetCurrentSlot() + << "; suffix: " << device_->GetSlotSuffix(); } - // Nothing should be depending on partitions now, so unmap them all. - if (!UnmapAllPartitionsInRecovery()) { - LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash."; + bool try_merge = false; + switch (state) { + case UpdateState::None: + case UpdateState::Initiated: + LOG(INFO) << "Wipe is not impacted by update state; allowing wipe."; + break; + case UpdateState::Unverified: + if (GetCurrentSlot() != Slot::Target) { + LOG(INFO) << "Wipe is not impacted by rolled back update; allowing wipe"; + break; + } + if (!HasForwardMergeIndicator()) { + auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); + auto other_slot_number = SlotNumberForSlotSuffix(device_->GetOtherSlotSuffix()); + + // We're not allowed to forward merge, so forcefully rollback the + // slot switch. + LOG(INFO) << "Allowing wipe due to lack of forward merge indicator; reverting to " + "old slot since update will be deleted."; + device_->SetSlotAsUnbootable(slot_number); + device_->SetActiveBootSlot(other_slot_number); + break; + } + + // Forward merge indicator means we have to mount snapshots and try to merge. + LOG(INFO) << "Forward merge indicator is present."; + try_merge = true; + break; + case UpdateState::Merging: + case UpdateState::MergeFailed: + try_merge = true; + break; + case UpdateState::MergeNeedsReboot: + case UpdateState::Cancelled: + LOG(INFO) << "Unexpected update state in recovery; allowing wipe."; + break; + default: + break; + } + + if (try_merge) { + auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); + auto super_path = device_->GetSuperDevice(slot_number); + if (!CreateLogicalAndSnapshotPartitions(super_path, 20s)) { + LOG(ERROR) << "Unable to map partitions to complete merge."; + return false; + } + + auto process_callback = [&]() -> bool { + if (callback) { + callback(); + } + return true; + }; + + state = ProcessUpdateStateOnDataWipe(process_callback); + if (state == UpdateState::MergeFailed) { + return false; + } + + // Nothing should be depending on partitions now, so unmap them all. + if (!UnmapAllPartitionsInRecovery()) { + LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash."; + } } if (state != UpdateState::None) { @@ -4065,58 +4172,40 @@ bool SnapshotManager::FinishMergeInRecovery() { return true; } -UpdateState SnapshotManager::ProcessUpdateStateOnDataWipe(bool allow_forward_merge, - const std::function& callback) { - auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); - UpdateState state = ProcessUpdateState(callback); - LOG(INFO) << "Update state in recovery: " << state; - switch (state) { - case UpdateState::MergeFailed: - LOG(ERROR) << "Unrecoverable merge failure detected."; - return state; - case UpdateState::Unverified: { - // If an OTA was just applied but has not yet started merging: - // - // - if forward merge is allowed, initiate merge and call - // ProcessUpdateState again. - // - // - if forward merge is not allowed, we - // have no choice but to revert slots, because the current slot will - // immediately become unbootable. Rather than wait for the device - // to reboot N times until a rollback, we proactively disable the - // new slot instead. - // - // Since the rollback is inevitable, we don't treat a HAL failure - // as an error here. - auto slot = GetCurrentSlot(); - if (slot == Slot::Target) { - if (allow_forward_merge && - access(GetForwardMergeIndicatorPath().c_str(), F_OK) == 0) { - LOG(INFO) << "Forward merge allowed, initiating merge now."; - - if (!InitiateMerge()) { - LOG(ERROR) << "Failed to initiate merge on data wipe."; - return UpdateState::MergeFailed; - } - return ProcessUpdateStateOnDataWipe(false /* allow_forward_merge */, callback); +UpdateState SnapshotManager::ProcessUpdateStateOnDataWipe(const std::function& callback) { + while (true) { + UpdateState state = ProcessUpdateState(callback); + LOG(INFO) << "Processed updated state in recovery: " << state; + switch (state) { + case UpdateState::MergeFailed: + LOG(ERROR) << "Unrecoverable merge failure detected."; + return state; + case UpdateState::Unverified: { + // Unverified was already handled earlier, in HandleImminentDataWipe, + // but it will fall through here if a forward merge is required. + // + // If InitiateMerge fails, we early return. If it succeeds, then we + // are guaranteed that the next call to ProcessUpdateState will not + // return Unverified. + if (!InitiateMerge()) { + LOG(ERROR) << "Failed to initiate merge on data wipe."; + return UpdateState::MergeFailed; } - - LOG(ERROR) << "Reverting to old slot since update will be deleted."; - device_->SetSlotAsUnbootable(slot_number); - } else { - LOG(INFO) << "Booting from " << slot << " slot, no action is taken."; + continue; } - break; + case UpdateState::MergeNeedsReboot: + // We shouldn't get here, because nothing is depending on + // logical partitions. + LOG(ERROR) << "Unexpected merge-needs-reboot state in recovery."; + return state; + default: + return state; } - case UpdateState::MergeNeedsReboot: - // We shouldn't get here, because nothing is depending on - // logical partitions. - LOG(ERROR) << "Unexpected merge-needs-reboot state in recovery."; - break; - default: - break; } - return state; +} + +bool SnapshotManager::HasForwardMergeIndicator() { + return access(GetForwardMergeIndicatorPath().c_str(), F_OK) == 0; } bool SnapshotManager::EnsureNoOverflowSnapshot(LockedFile* lock) { diff --git a/fs_mgr/libsnapshot/snapshot_stats.cpp b/fs_mgr/libsnapshot/snapshot_stats.cpp index 9b6eb2c62..8e9d9c5f3 100644 --- a/fs_mgr/libsnapshot/snapshot_stats.cpp +++ b/fs_mgr/libsnapshot/snapshot_stats.cpp @@ -25,7 +25,7 @@ namespace snapshot { SnapshotMergeStats* SnapshotMergeStats::GetInstance(SnapshotManager& parent) { static SnapshotMergeStats g_instance(parent.GetMergeStateFilePath()); - CHECK(g_instance.path_ == parent.GetMergeStateFilePath()); + CHECK_EQ(g_instance.path_, parent.GetMergeStateFilePath()); return &g_instance; } diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp index 3299ec5c9..46c3a35e9 100644 --- a/fs_mgr/libsnapshot/snapshot_test.cpp +++ b/fs_mgr/libsnapshot/snapshot_test.cpp @@ -47,6 +47,7 @@ #include #include #include "partition_cow_creator.h" +#include "scratch_super.h" #include "utility.h" // Mock classes are not used. Header included to ensure mocked class definition aligns with the @@ -122,6 +123,7 @@ class SnapshotTest : public ::testing::Test { LOG(INFO) << "Starting test: " << test_name_; SKIP_IF_NON_VIRTUAL_AB(); + SKIP_IF_VENDOR_ON_ANDROID_S(); SetupProperties(); if (!DeviceSupportsMode()) { @@ -168,6 +170,7 @@ class SnapshotTest : public ::testing::Test { void TearDown() override { RETURN_IF_NON_VIRTUAL_AB(); + RETURN_IF_VENDOR_ON_ANDROID_S(); LOG(INFO) << "Tearing down SnapshotTest test: " << test_name_; @@ -1015,6 +1018,7 @@ class SnapshotUpdateTest : public SnapshotTest { public: void SetUp() override { SKIP_IF_NON_VIRTUAL_AB(); + SKIP_IF_VENDOR_ON_ANDROID_S(); SnapshotTest::SetUp(); if (!image_manager_) { @@ -1097,6 +1101,7 @@ class SnapshotUpdateTest : public SnapshotTest { } void TearDown() override { RETURN_IF_NON_VIRTUAL_AB(); + RETURN_IF_VENDOR_ON_ANDROID_S(); LOG(INFO) << "Tearing down SnapshotUpdateTest test: " << test_name_; @@ -1338,6 +1343,15 @@ class SnapshotUpdateTest : public SnapshotTest { DynamicPartitionGroup* group_ = nullptr; }; +TEST_F(SnapshotUpdateTest, SuperOtaMetadataTest) { + auto info = new TestDeviceInfo(fake_super); + ASSERT_TRUE(CreateScratchOtaMetadataOnSuper(info)); + std::string scratch_device = GetScratchOtaMetadataPartition(); + ASSERT_NE(scratch_device, ""); + ASSERT_NE(MapScratchOtaMetadataPartition(scratch_device), ""); + ASSERT_TRUE(CleanupScratchOtaMetadataIfPresent(info)); +} + // Test full update flow executed by update_engine. Some partitions uses super empty space, // some uses images, and some uses both. // Also test UnmapUpdateSnapshot unmaps everything. @@ -1978,6 +1992,8 @@ TEST_F(MetadataMountedTest, Android) { } TEST_F(MetadataMountedTest, Recovery) { + GTEST_SKIP() << "b/350715463"; + test_device->set_recovery(true); metadata_dir_ = test_device->GetMetadataDir(); @@ -2096,10 +2112,10 @@ TEST_F(SnapshotUpdateTest, DataWipeRollbackInRecovery) { test_device->set_recovery(true); auto new_sm = NewManagerForFirstStageMount(test_device); + EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::Unverified); ASSERT_TRUE(new_sm->HandleImminentDataWipe()); // Manually mount metadata so that we can call GetUpdateState() below. MountMetadata(); - EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None); EXPECT_TRUE(test_device->IsSlotUnbootable(1)); EXPECT_FALSE(test_device->IsSlotUnbootable(0)); } @@ -2121,6 +2137,7 @@ TEST_F(SnapshotUpdateTest, DataWipeAfterRollback) { test_device->set_recovery(true); auto new_sm = NewManagerForFirstStageMount(test_device); + EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::Unverified); ASSERT_TRUE(new_sm->HandleImminentDataWipe()); EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None); EXPECT_FALSE(test_device->IsSlotUnbootable(0)); @@ -2129,10 +2146,6 @@ TEST_F(SnapshotUpdateTest, DataWipeAfterRollback) { // Test update package that requests data wipe. TEST_F(SnapshotUpdateTest, DataWipeRequiredInPackage) { - if (ShouldSkipLegacyMerging()) { - GTEST_SKIP() << "Skipping legacy merge in test"; - } - AddOperationForPartitions(); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); @@ -2151,6 +2164,7 @@ TEST_F(SnapshotUpdateTest, DataWipeRequiredInPackage) { test_device->set_recovery(true); auto new_sm = NewManagerForFirstStageMount(test_device); + EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::Unverified); ASSERT_TRUE(new_sm->HandleImminentDataWipe()); // Manually mount metadata so that we can call GetUpdateState() below. MountMetadata(); @@ -2172,10 +2186,6 @@ TEST_F(SnapshotUpdateTest, DataWipeRequiredInPackage) { // Test update package that requests data wipe. TEST_F(SnapshotUpdateTest, DataWipeWithStaleSnapshots) { - if (ShouldSkipLegacyMerging()) { - GTEST_SKIP() << "Skipping legacy merge in test"; - } - AddOperationForPartitions(); // Execute the update. @@ -2216,6 +2226,7 @@ TEST_F(SnapshotUpdateTest, DataWipeWithStaleSnapshots) { test_device->set_recovery(true); auto new_sm = NewManagerForFirstStageMount(test_device); + EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::Unverified); ASSERT_TRUE(new_sm->HandleImminentDataWipe()); // Manually mount metadata so that we can call GetUpdateState() below. MountMetadata(); @@ -2659,6 +2670,7 @@ TEST_F(SnapshotTest, FlagCheck) { status.set_o_direct(true); status.set_io_uring_enabled(true); status.set_userspace_snapshots(true); + status.set_cow_op_merge_size(16); sm->WriteSnapshotUpdateStatus(lock_.get(), status); // Ensure a connection to the second-stage daemon, but use the first-stage @@ -2680,6 +2692,8 @@ TEST_F(SnapshotTest, FlagCheck) { snapuserd_argv.end()); ASSERT_TRUE(std::find(snapuserd_argv.begin(), snapuserd_argv.end(), "-user_snapshot") != snapuserd_argv.end()); + ASSERT_TRUE(std::find(snapuserd_argv.begin(), snapuserd_argv.end(), "-cow_op_merge_size=16") != + snapuserd_argv.end()); } class FlashAfterUpdateTest : public SnapshotUpdateTest, @@ -2885,6 +2899,8 @@ void SnapshotTestEnvironment::SetUp() { void SnapshotTestEnvironment::TearDown() { RETURN_IF_NON_VIRTUAL_AB(); + RETURN_IF_VENDOR_ON_ANDROID_S(); + if (super_images_ != nullptr) { DeleteBackingImage(super_images_.get(), "fake-super"); } diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp index 0158d4d9c..97a8cb210 100644 --- a/fs_mgr/libsnapshot/snapshotctl.cpp +++ b/fs_mgr/libsnapshot/snapshotctl.cpp @@ -16,7 +16,6 @@ #include #include - #include #include #include @@ -46,6 +45,7 @@ #include #include "partition_cow_creator.h" +#include "scratch_super.h" #ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG #include @@ -57,6 +57,8 @@ using namespace android::storage_literals; using android::base::LogdLogger; using android::base::StderrLogger; using android::base::TeeLogger; +using namespace android::dm; +using namespace android::fs_mgr; using android::fs_mgr::CreateLogicalPartitionParams; using android::fs_mgr::FindPartition; using android::fs_mgr::GetPartitionSize; @@ -97,7 +99,7 @@ namespace snapshot { #ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG class MapSnapshots { public: - MapSnapshots(std::string path = ""); + MapSnapshots(std::string path = "", bool metadata_super = false); bool CreateSnapshotDevice(std::string& partition_name, std::string& patch); bool InitiateThreadedSnapshotWrite(std::string& pname, std::string& snapshot_patch); bool FinishSnapshotWrites(); @@ -122,15 +124,12 @@ class MapSnapshots { std::vector patchfiles_; chromeos_update_engine::DeltaArchiveManifest manifest_; + bool metadata_super_ = false; }; -MapSnapshots::MapSnapshots(std::string path) { - sm_ = SnapshotManager::New(); - if (!sm_) { - std::cout << "Failed to create snapshotmanager"; - exit(1); - } +MapSnapshots::MapSnapshots(std::string path, bool metadata_super) { snapshot_dir_path_ = path + "/"; + metadata_super_ = metadata_super; } std::string MapSnapshots::GetGroupName(const android::fs_mgr::LpMetadata& pt, @@ -150,6 +149,12 @@ std::string MapSnapshots::GetGroupName(const android::fs_mgr::LpMetadata& pt, } bool MapSnapshots::PrepareUpdate() { + if (metadata_super_ && !CreateScratchOtaMetadataOnSuper()) { + LOG(ERROR) << "Failed to create OTA metadata on super"; + return false; + } + sm_ = SnapshotManager::New(); + auto source_slot = fs_mgr_get_slot_suffix(); auto source_slot_number = SlotNumberForSlotSuffix(source_slot); auto super_source = fs_mgr_get_super_partition_name(source_slot_number); @@ -234,7 +239,17 @@ bool MapSnapshots::PrepareUpdate() { bool MapSnapshots::GetCowDevicePath(std::string partition_name, std::string* cow_path) { auto& dm = android::dm::DeviceMapper::Instance(); - std::string cow_device = partition_name + "-cow"; + + std::string cow_device = partition_name + "-cow-img"; + if (metadata_super_) { + // If COW device exists on /data, then data wipe cannot be done. + if (dm.GetDmDevicePathByName(cow_device, cow_path)) { + LOG(ERROR) << "COW device exists on /data: " << *cow_path; + return false; + } + } + + cow_device = partition_name + "-cow"; if (dm.GetDmDevicePathByName(cow_device, cow_path)) { return true; } @@ -321,6 +336,12 @@ bool MapSnapshots::ApplyUpdate() { } bool MapSnapshots::BeginUpdate() { + if (metadata_super_ && !CreateScratchOtaMetadataOnSuper()) { + LOG(ERROR) << "Failed to create OTA metadata on super"; + return false; + } + sm_ = SnapshotManager::New(); + lock_ = sm_->LockExclusive(); std::vector snapshots; sm_->ListSnapshots(lock_.get(), &snapshots); @@ -470,10 +491,12 @@ bool MapSnapshots::FinishSnapshotWrites() { } bool MapSnapshots::UnmapCowImagePath(std::string& name) { + sm_ = SnapshotManager::New(); return sm_->UnmapCowImage(name); } bool MapSnapshots::DeleteSnapshots() { + sm_ = SnapshotManager::New(); lock_ = sm_->LockExclusive(); if (!sm_->RemoveAllUpdateState(lock_.get())) { LOG(ERROR) << "Remove All Update State failed"; @@ -583,13 +606,19 @@ bool ApplyUpdate(int argc, char** argv) { } if (argc < 3) { - std::cerr << " apply-update " + std::cerr << " apply-update {-w}" " Apply the snapshots to the COW block device\n"; return false; } std::string path = std::string(argv[2]); - MapSnapshots cow(path); + bool metadata_on_super = false; + if (argc == 4) { + if (std::string(argv[3]) == "-w") { + metadata_on_super = true; + } + } + MapSnapshots cow(path, metadata_on_super); if (!cow.ApplyUpdate()) { return false; } @@ -607,7 +636,7 @@ bool MapPrecreatedSnapshots(int argc, char** argv) { } if (argc < 3) { - std::cerr << " map-snapshots " + std::cerr << " map-snapshots {-w}" " Map all snapshots based on patches present in the directory\n"; return false; } @@ -638,7 +667,14 @@ bool MapPrecreatedSnapshots(int argc, char** argv) { } } - MapSnapshots cow(path); + bool metadata_on_super = false; + if (argc == 4) { + if (std::string(argv[3]) == "-w") { + metadata_on_super = true; + } + } + + MapSnapshots cow(path, metadata_on_super); if (!cow.BeginUpdate()) { LOG(ERROR) << "BeginUpdate failed"; return false; diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp index eb5443c1c..8d0bf7d6b 100644 --- a/fs_mgr/libsnapshot/snapuserd/Android.bp +++ b/fs_mgr/libsnapshot/snapuserd/Android.bp @@ -42,6 +42,7 @@ cc_library_static { static_libs: [ "libcutils_sockets", "libfs_mgr_file_wait", + "libdm", ], shared_libs: [ "libbase", @@ -169,7 +170,7 @@ cc_binary { recovery_available: true, } -// This target will install to /system/bin/snapuserd_ramdisk +// This target will install to /system/bin/snapuserd_ramdisk // It will also create a symblink on /system/bin/snapuserd that point to // /system/bin/snapuserd_ramdisk . // This way, init can check if generic ramdisk copy exists. @@ -238,9 +239,19 @@ cc_defaults { test_options: { min_shipping_api_level: 30, }, + + compile_multilib: "both", + multilib: { + lib32: { + suffix: "32", + }, + lib64: { + suffix: "64", + }, + }, + auto_gen_config: true, require_root: true, - compile_multilib: "first", } cc_test { @@ -248,8 +259,16 @@ cc_test { defaults: ["snapuserd_test_defaults"], host_supported: true, test_suites: [ - "device-tests", + "general-tests", ], + test_options: { + test_runner_options: [ + { + name: "force-no-test-error", + value: "false", + }, + ], + }, } // vts tests cannot be host_supported. @@ -259,6 +278,10 @@ cc_test { test_suites: [ "vts", ], + test_options: { + // VABC mandatory in Android T per VSR. + min_shipping_api_level: 32, + }, } cc_binary_host { diff --git a/fs_mgr/libsnapshot/snapuserd/dm_user_block_server.cpp b/fs_mgr/libsnapshot/snapuserd/dm_user_block_server.cpp index e98833502..4599ad3b3 100644 --- a/fs_mgr/libsnapshot/snapuserd/dm_user_block_server.cpp +++ b/fs_mgr/libsnapshot/snapuserd/dm_user_block_server.cpp @@ -27,11 +27,12 @@ using android::base::unique_fd; DmUserBlockServer::DmUserBlockServer(const std::string& misc_name, unique_fd&& ctrl_fd, Delegate* delegate, size_t buffer_size) : misc_name_(misc_name), ctrl_fd_(std::move(ctrl_fd)), delegate_(delegate) { - buffer_.Initialize(buffer_size); + buffer_.Initialize(sizeof(struct dm_user_header), buffer_size); } bool DmUserBlockServer::ProcessRequests() { - struct dm_user_header* header = buffer_.GetHeaderPtr(); + struct dm_user_header* header = + reinterpret_cast(buffer_.GetHeaderPtr()); if (!android::base::ReadFully(ctrl_fd_, header, sizeof(*header))) { if (errno != ENOTBLK) { SNAP_PLOG(ERROR) << "Control-read failed"; @@ -90,7 +91,8 @@ bool DmUserBlockServer::SendBufferedIo() { } void DmUserBlockServer::SendError() { - struct dm_user_header* header = buffer_.GetHeaderPtr(); + struct dm_user_header* header = + reinterpret_cast(buffer_.GetHeaderPtr()); header->type = DM_USER_RESP_ERROR; // This is an issue with the dm-user interface. There // is no way to propagate the I/O error back to dm-user diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/dm_user_block_server.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/dm_user_block_server.h index f1f8da143..35c6bfbb2 100644 --- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/dm_user_block_server.h +++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/dm_user_block_server.h @@ -20,6 +20,7 @@ #include #include +#include namespace android { namespace snapshot { diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h index c5ca2b120..cc7c48c96 100644 --- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h +++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h @@ -27,13 +27,17 @@ namespace snapshot { class BufferSink final { public: - void Initialize(size_t size); + // Do not reserve any space of header by default + void Initialize(size_t size) { return Initialize(0, size); }; + // This allows to set const header_size_ to be used if caller needs it + // for example, while working with dm_user + void Initialize(size_t header_size, size_t size); void* GetBufPtr() { return buffer_.get(); } void Clear() { memset(GetBufPtr(), 0, buffer_size_); } void* GetPayloadBuffer(size_t size); void* GetBuffer(size_t requested, size_t* actual); void UpdateBufferOffset(size_t size) { buffer_offset_ += size; } - struct dm_user_header* GetHeaderPtr(); + void* GetHeaderPtr(); void ResetBufferOffset() { buffer_offset_ = 0; } void* GetPayloadBufPtr(); loff_t GetPayloadBytesWritten() { return buffer_offset_; } @@ -56,6 +60,7 @@ class BufferSink final { std::unique_ptr buffer_; loff_t buffer_offset_; size_t buffer_size_; + size_t header_size_; }; } // namespace snapshot diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h index 7ab75dc79..14291b214 100644 --- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h +++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h @@ -92,15 +92,5 @@ struct dm_user_header { __u64 len; } __attribute__((packed)); -struct dm_user_payload { - __u8 buf[]; -}; - -// Message comprising both header and payload -struct dm_user_message { - struct dm_user_header header; - struct dm_user_payload payload; -}; - } // namespace snapshot } // namespace android diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_buffer.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_buffer.cpp index 490c0e63a..51b249034 100644 --- a/fs_mgr/libsnapshot/snapuserd/snapuserd_buffer.cpp +++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_buffer.cpp @@ -22,8 +22,9 @@ namespace android { namespace snapshot { -void BufferSink::Initialize(size_t size) { - buffer_size_ = size + sizeof(struct dm_user_header); +void BufferSink::Initialize(size_t header_size, size_t size) { + header_size_ = header_size; + buffer_size_ = size + header_size; buffer_offset_ = 0; buffer_ = std::make_unique(buffer_size_); } @@ -41,11 +42,11 @@ void* BufferSink::AcquireBuffer(size_t size, size_t to_write) { void* BufferSink::GetPayloadBuffer(size_t size) { char* buffer = reinterpret_cast(GetBufPtr()); - struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0])); - if ((buffer_size_ - buffer_offset_ - sizeof(msg->header)) < size) { + + if ((buffer_size_ - buffer_offset_ - header_size_) < size) { return nullptr; } - return (char*)msg->payload.buf + buffer_offset_; + return (char*)(&buffer[0] + header_size_ + buffer_offset_); } void* BufferSink::GetBuffer(size_t requested, size_t* actual) { @@ -58,19 +59,18 @@ void* BufferSink::GetBuffer(size_t requested, size_t* actual) { return buf; } -struct dm_user_header* BufferSink::GetHeaderPtr() { - if (!(sizeof(struct dm_user_header) <= buffer_size_)) { +void* BufferSink::GetHeaderPtr() { + // If no sufficient space or header not reserved + if (!(header_size_ <= buffer_size_) || !header_size_) { return nullptr; } char* buf = reinterpret_cast(GetBufPtr()); - struct dm_user_header* header = (struct dm_user_header*)(&(buf[0])); - return header; + return (void*)(&(buf[0])); } void* BufferSink::GetPayloadBufPtr() { char* buffer = reinterpret_cast(GetBufPtr()); - struct dm_user_message* msg = reinterpret_cast(&(buffer[0])); - return msg->payload.buf; + return &buffer[header_size_]; } } // namespace snapshot diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp index 789c98071..ddefb9f91 100644 --- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp +++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include namespace android { @@ -333,7 +334,21 @@ bool SnapuserdClient::QueryUpdateVerification() { } std::string SnapuserdClient::GetDaemonAliveIndicatorPath() { - return "/metadata/ota/" + std::string(kDaemonAliveIndicator); + std::string metadata_dir; + std::string temp_metadata_mnt = "/mnt/scratch_ota_metadata_super"; + + auto& dm = ::android::dm::DeviceMapper::Instance(); + auto partition_name = android::base::Basename(temp_metadata_mnt); + + bool invalid_partition = (dm.GetState(partition_name) == dm::DmDeviceState::INVALID); + std::string temp_device; + if (!invalid_partition && dm.GetDmDevicePathByName(partition_name, &temp_device)) { + metadata_dir = temp_metadata_mnt + "/" + "ota/"; + } else { + metadata_dir = "/metadata/ota/"; + } + + return metadata_dir + std::string(kDaemonAliveIndicator); } bool SnapuserdClient::IsTransitionedDaemonReady() { diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp index 67e9e52ea..dd2dd5659 100644 --- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp +++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp @@ -30,6 +30,7 @@ DEFINE_bool(socket_handoff, false, DEFINE_bool(user_snapshot, false, "If true, user-space snapshots are used"); DEFINE_bool(io_uring, false, "If true, io_uring feature is enabled"); DEFINE_bool(o_direct, false, "If true, enable direct reads on source device"); +DEFINE_int32(cow_op_merge_size, 0, "number of operations to be processed at once"); namespace android { namespace snapshot { @@ -106,7 +107,6 @@ bool Daemon::StartServerForUserspaceSnapshots(int arg_start, int argc, char** ar } return user_server_.Run(); } - for (int i = arg_start; i < argc; i++) { auto parts = android::base::Split(argv[i], ","); @@ -114,8 +114,8 @@ bool Daemon::StartServerForUserspaceSnapshots(int arg_start, int argc, char** ar LOG(ERROR) << "Malformed message, expected at least four sub-arguments."; return false; } - auto handler = - user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3], FLAGS_o_direct); + auto handler = user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3], + FLAGS_o_direct, FLAGS_cow_op_merge_size); if (!handler || !user_server_.StartHandler(parts[0])) { return false; } diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp index c85331b34..ef4ba93fe 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp @@ -41,7 +41,7 @@ Extractor::Extractor(const std::string& base_path, const std::string& cow_path) bool Extractor::Init() { auto opener = factory_.CreateTestOpener(control_name_); handler_ = std::make_shared(control_name_, cow_path_, base_path_, base_path_, - opener, 1, false, false, false); + opener, 1, false, false, false, 0); if (!handler_->InitCowDevice()) { return false; } diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp index ea11f0e6e..fdd9cce0c 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp @@ -53,10 +53,10 @@ std::shared_ptr SnapshotHandlerManager::AddHandler( const std::string& misc_name, const std::string& cow_device_path, const std::string& backing_device, const std::string& base_path_merge, std::shared_ptr opener, int num_worker_threads, bool use_iouring, - bool o_direct) { + bool o_direct, uint32_t cow_op_merge_size) { auto snapuserd = std::make_shared( misc_name, cow_device_path, backing_device, base_path_merge, opener, num_worker_threads, - use_iouring, perform_verification_, o_direct); + use_iouring, perform_verification_, o_direct, cow_op_merge_size); if (!snapuserd->InitCowDevice()) { LOG(ERROR) << "Failed to initialize Snapuserd"; return nullptr; diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h index f23f07eb5..ecf5d5c38 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h @@ -52,13 +52,11 @@ class ISnapshotHandlerManager { virtual ~ISnapshotHandlerManager() {} // Add a new snapshot handler but do not start serving requests yet. - virtual std::shared_ptr AddHandler(const std::string& misc_name, - const std::string& cow_device_path, - const std::string& backing_device, - const std::string& base_path_merge, - std::shared_ptr opener, - int num_worker_threads, bool use_iouring, - bool o_direct) = 0; + virtual std::shared_ptr AddHandler( + const std::string& misc_name, const std::string& cow_device_path, + const std::string& backing_device, const std::string& base_path_merge, + std::shared_ptr opener, int num_worker_threads, bool use_iouring, + bool o_direct, uint32_t cow_op_merge_size) = 0; // Start serving requests on a snapshot handler. virtual bool StartHandler(const std::string& misc_name) = 0; @@ -98,7 +96,7 @@ class SnapshotHandlerManager final : public ISnapshotHandlerManager { const std::string& base_path_merge, std::shared_ptr opener, int num_worker_threads, bool use_iouring, - bool o_direct) override; + bool o_direct, uint32_t cow_op_merge_size) override; bool StartHandler(const std::string& misc_name) override; bool DeleteHandler(const std::string& misc_name) override; bool InitiateMerge(const std::string& misc_name) override; diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp index bd7eaca74..e2c58741a 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp @@ -17,6 +17,8 @@ #include #include +#include + #include "merge_worker.h" #include "snapuserd_core.h" #include "utility.h" @@ -30,12 +32,18 @@ using android::base::unique_fd; MergeWorker::MergeWorker(const std::string& cow_device, const std::string& misc_name, const std::string& base_path_merge, - std::shared_ptr snapuserd) - : Worker(cow_device, misc_name, base_path_merge, snapuserd) {} + std::shared_ptr snapuserd, uint32_t cow_op_merge_size) + : Worker(cow_device, misc_name, base_path_merge, snapuserd), + cow_op_merge_size_(cow_op_merge_size) {} int MergeWorker::PrepareMerge(uint64_t* source_offset, int* pending_ops, std::vector* replace_zero_vec) { int num_ops = *pending_ops; + // 0 indicates ro.virtual_ab.cow_op_merge_size was not set in the build + if (cow_op_merge_size_ != 0) { + num_ops = std::min(cow_op_merge_size_, static_cast(*pending_ops)); + } + int nr_consecutive = 0; bool checkOrderedOp = (replace_zero_vec == nullptr); size_t num_blocks = 1; @@ -179,8 +187,8 @@ bool MergeWorker::MergeReplaceZeroOps() { bufsink_.ResetBufferOffset(); if (snapuserd_->IsIOTerminated()) { - SNAP_LOG(ERROR) - << "MergeReplaceZeroOps: MergeWorker threads terminated - shutting down merge"; + SNAP_LOG(ERROR) << "MergeReplaceZeroOps: MergeWorker threads terminated - shutting " + "down merge"; return false; } } @@ -577,8 +585,10 @@ bool MergeWorker::Run() { SNAP_LOG(ERROR) << "Merge terminated early..."; return true; } + auto merge_thread_priority = android::base::GetUintProperty( + "ro.virtual_ab.merge_thread_priority", ANDROID_PRIORITY_BACKGROUND); - if (!SetThreadPriority(ANDROID_PRIORITY_BACKGROUND)) { + if (!SetThreadPriority(merge_thread_priority)) { SNAP_PLOG(ERROR) << "Failed to set thread priority"; } diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.h index 478d4c8a2..a19352dad 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.h +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.h @@ -23,7 +23,8 @@ namespace snapshot { class MergeWorker : public Worker { public: MergeWorker(const std::string& cow_device, const std::string& misc_name, - const std::string& base_path_merge, std::shared_ptr snapuserd); + const std::string& base_path_merge, std::shared_ptr snapuserd, + uint32_t cow_op_merge_size); bool Run(); private: @@ -53,6 +54,7 @@ class MergeWorker : public Worker { // syscalls and fallback to synchronous I/O, we // don't want huge queue depth int queue_depth_ = 8; + uint32_t cow_op_merge_size_ = 0; }; } // namespace snapshot diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp index d40b6d11d..ef311d475 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp @@ -14,11 +14,14 @@ * limitations under the License. */ +#include + #include #include #include "read_worker.h" #include "snapuserd_core.h" +#include "user-space-merge/worker.h" #include "utility.h" namespace android { @@ -259,8 +262,10 @@ bool ReadWorker::Run() { SNAP_LOG(INFO) << "Processing snapshot I/O requests...."; pthread_setname_np(pthread_self(), "ReadWorker"); + auto worker_thread_priority = android::base::GetUintProperty( + "ro.virtual_ab.worker_thread_priority", ANDROID_PRIORITY_NORMAL); - if (!SetThreadPriority(ANDROID_PRIORITY_NORMAL)) { + if (!SetThreadPriority(worker_thread_priority)) { SNAP_PLOG(ERROR) << "Failed to set thread priority"; } diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h index 04b2736e9..378d80979 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h @@ -57,7 +57,7 @@ class ReadWorker : public Worker, public IBlockServer::Delegate { bool ReadFromSourceDevice(const CowOperation* cow_op, void* buffer); bool ReadDataFromBaseDevice(sector_t sector, void* buffer, size_t read_size); - constexpr bool IsBlockAligned(size_t size) { return ((size & (BLOCK_SZ - 1)) == 0); } + constexpr bool IsBlockAligned(uint64_t size) { return ((size & (BLOCK_SZ - 1)) == 0); } constexpr sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; } constexpr chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; } diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp index 05ba047b5..7c9a64ee4 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp @@ -36,7 +36,8 @@ using android::base::unique_fd; SnapshotHandler::SnapshotHandler(std::string misc_name, std::string cow_device, std::string backing_device, std::string base_path_merge, std::shared_ptr opener, int num_worker_threads, - bool use_iouring, bool perform_verification, bool o_direct) { + bool use_iouring, bool perform_verification, bool o_direct, + uint32_t cow_op_merge_size) { misc_name_ = std::move(misc_name); cow_device_ = std::move(cow_device); backing_store_device_ = std::move(backing_device); @@ -46,6 +47,7 @@ SnapshotHandler::SnapshotHandler(std::string misc_name, std::string cow_device, is_io_uring_enabled_ = use_iouring; perform_verification_ = perform_verification; o_direct_ = o_direct; + cow_op_merge_size_ = cow_op_merge_size; } bool SnapshotHandler::InitializeWorkers() { @@ -60,12 +62,11 @@ bool SnapshotHandler::InitializeWorkers() { worker_threads_.push_back(std::move(wt)); } - merge_thread_ = std::make_unique(cow_device_, misc_name_, base_path_merge_, - GetSharedPtr()); + GetSharedPtr(), cow_op_merge_size_); read_ahead_thread_ = std::make_unique(cow_device_, backing_store_device_, misc_name_, - GetSharedPtr()); + GetSharedPtr(), cow_op_merge_size_); update_verify_ = std::make_unique(misc_name_); diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h index 9b7238a5d..c7de9951f 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h @@ -104,7 +104,8 @@ class SnapshotHandler : public std::enable_shared_from_this { public: SnapshotHandler(std::string misc_name, std::string cow_device, std::string backing_device, std::string base_path_merge, std::shared_ptr opener, - int num_workers, bool use_iouring, bool perform_verification, bool o_direct); + int num_workers, bool use_iouring, bool perform_verification, bool o_direct, + uint32_t cow_op_merge_size); bool InitCowDevice(); bool Start(); @@ -247,7 +248,7 @@ class SnapshotHandler : public std::enable_shared_from_this { bool resume_merge_ = false; bool merge_complete_ = false; bool o_direct_ = false; - + uint32_t cow_op_merge_size_ = 0; std::unique_ptr update_verify_; std::shared_ptr block_server_opener_; }; diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp index 2baf20ddd..6b1ed0cd7 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp @@ -18,6 +18,7 @@ #include +#include "android-base/properties.h" #include "snapuserd_core.h" #include "utility.h" @@ -29,11 +30,13 @@ using namespace android::dm; using android::base::unique_fd; ReadAhead::ReadAhead(const std::string& cow_device, const std::string& backing_device, - const std::string& misc_name, std::shared_ptr snapuserd) { + const std::string& misc_name, std::shared_ptr snapuserd, + uint32_t cow_op_merge_size) { cow_device_ = cow_device; backing_store_device_ = backing_device; misc_name_ = misc_name; snapuserd_ = snapuserd; + cow_op_merge_size_ = cow_op_merge_size; } void ReadAhead::CheckOverlap(const CowOperation* cow_op) { @@ -62,8 +65,11 @@ int ReadAhead::PrepareNextReadAhead(uint64_t* source_offset, int* pending_ops, std::vector& blocks, std::vector& xor_op_vec) { int num_ops = *pending_ops; - int nr_consecutive = 0; + if (cow_op_merge_size_ != 0) { + num_ops = std::min(static_cast(cow_op_merge_size_), *pending_ops); + } + int nr_consecutive = 0; bool is_ops_present = (!RAIterDone() && num_ops); if (!is_ops_present) { diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.h index d3ba1265b..4885c9608 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.h +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.h @@ -35,7 +35,8 @@ class SnapshotHandler; class ReadAhead { public: ReadAhead(const std::string& cow_device, const std::string& backing_device, - const std::string& misc_name, std::shared_ptr snapuserd); + const std::string& misc_name, std::shared_ptr snapuserd, + uint32_t cow_op_merge_size); bool RunThread(); private: @@ -106,6 +107,7 @@ class ReadAhead { // syscalls and fallback to synchronous I/O, we // don't want huge queue depth int queue_depth_ = 8; + uint32_t cow_op_merge_size_; std::unique_ptr ring_; }; diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp index 0b881b683..013df350b 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -35,9 +36,6 @@ #include #include "snapuserd_server.h" -#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_ -#include - namespace android { namespace snapshot { @@ -347,7 +345,8 @@ std::shared_ptr UserSnapshotServer::AddHandler(const std::string& const std::string& cow_device_path, const std::string& backing_device, const std::string& base_path_merge, - const bool o_direct) { + const bool o_direct, + uint32_t cow_op_merge_size) { // We will need multiple worker threads only during // device boot after OTA. For all other purposes, // one thread is sufficient. We don't want to consume @@ -369,7 +368,8 @@ std::shared_ptr UserSnapshotServer::AddHandler(const std::string& auto opener = block_server_factory_->CreateOpener(misc_name); return handlers_->AddHandler(misc_name, cow_device_path, backing_device, base_path_merge, - opener, num_worker_threads, io_uring_enabled_, o_direct); + opener, num_worker_threads, io_uring_enabled_, o_direct, + cow_op_merge_size); } bool UserSnapshotServer::WaitForSocket() { diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h index d9cf97f9d..ceea36ae4 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h @@ -87,7 +87,8 @@ class UserSnapshotServer { const std::string& cow_device_path, const std::string& backing_device, const std::string& base_path_merge, - bool o_direct = false); + bool o_direct = false, + uint32_t cow_op_merge_size = 0); bool StartHandler(const std::string& misc_name); void SetTerminating() { terminating_ = true; } diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp index 56f7b5911..4dfb9bf4e 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp @@ -67,6 +67,7 @@ struct TestParam { std::string compression; int block_size; int num_threads; + uint32_t cow_op_merge_size; }; class SnapuserdTestBase : public ::testing::TestWithParam { @@ -79,6 +80,8 @@ class SnapuserdTestBase : public ::testing::TestWithParam { std::unique_ptr CreateCowDeviceInternal(); std::unique_ptr CreateV3Cow(); + unique_fd GetCowFd() { return unique_fd{dup(cow_system_->fd)}; } + std::unique_ptr harness_; size_t size_ = 10_MiB; int total_base_size_ = 0; @@ -101,7 +104,9 @@ void SnapuserdTestBase::SetUp() { #endif } -void SnapuserdTestBase::TearDown() {} +void SnapuserdTestBase::TearDown() { + cow_system_ = nullptr; +} void SnapuserdTestBase::CreateBaseDevice() { total_base_size_ = (size_ * 5); @@ -132,10 +137,7 @@ std::unique_ptr SnapuserdTestBase::CreateCowDeviceInternal() { CowOptions options; options.compression = "gz"; - unique_fd fd(cow_system_->fd); - cow_system_->fd = -1; - - return CreateCowWriter(2, options, std::move(fd)); + return CreateCowWriter(2, options, GetCowFd()); } std::unique_ptr SnapuserdTestBase::CreateV3Cow() { @@ -151,10 +153,7 @@ std::unique_ptr SnapuserdTestBase::CreateV3Cow() { std::string path = android::base::GetExecutableDirectory(); cow_system_ = std::make_unique(path); - unique_fd fd(cow_system_->fd); - cow_system_->fd = -1; - - return CreateCowWriter(3, options, std::move(fd)); + return CreateCowWriter(3, options, GetCowFd()); } void SnapuserdTestBase::CreateCowDevice() { @@ -710,9 +709,9 @@ void SnapuserdTest::InitCowDevice() { auto opener = factory->CreateOpener(system_device_ctrl_name_); handlers_->DisableVerification(); const TestParam params = GetParam(); - auto handler = handlers_->AddHandler(system_device_ctrl_name_, cow_system_->path, - base_dev_->GetPath(), base_dev_->GetPath(), opener, 1, - params.io_uring, params.o_direct); + auto handler = handlers_->AddHandler( + system_device_ctrl_name_, cow_system_->path, base_dev_->GetPath(), base_dev_->GetPath(), + opener, 1, params.io_uring, params.o_direct, params.cow_op_merge_size); ASSERT_NE(handler, nullptr); ASSERT_NE(handler->snapuserd(), nullptr); #ifdef __ANDROID__ @@ -1229,9 +1228,9 @@ void HandlerTest::InitializeDevice() { ASSERT_NE(opener_, nullptr); const TestParam params = GetParam(); - handler_ = std::make_shared(system_device_ctrl_name_, cow_system_->path, - base_dev_->GetPath(), base_dev_->GetPath(), - opener_, 1, false, false, params.o_direct); + handler_ = std::make_shared( + system_device_ctrl_name_, cow_system_->path, base_dev_->GetPath(), base_dev_->GetPath(), + opener_, 1, false, false, params.o_direct, params.cow_op_merge_size); ASSERT_TRUE(handler_->InitCowDevice()); ASSERT_TRUE(handler_->InitializeWorkers()); @@ -1509,6 +1508,7 @@ std::vector GetVariableBlockTestConfigs() { param.num_threads = thread; param.io_uring = io_uring; param.o_direct = false; + param.cow_op_merge_size = 0; testParams.push_back(std::move(param)); } } @@ -1530,6 +1530,14 @@ INSTANTIATE_TEST_SUITE_P(Io, HandlerTest, ::testing::ValuesIn(GetTestConfigs())) int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); +#ifdef __ANDROID__ + if (!android::snapshot::CanUseUserspaceSnapshots() || + android::snapshot::IsVendorFromAndroid12()) { + std::cerr << "snapuserd_test not supported on this device\n"; + return 0; + } +#endif + gflags::ParseCommandLineFlags(&argc, &argv, false); return RUN_ALL_TESTS(); diff --git a/fs_mgr/libsnapshot/snapuserd/utility.cpp b/fs_mgr/libsnapshot/snapuserd/utility.cpp index 684ca3d7b..b44f5abd2 100644 --- a/fs_mgr/libsnapshot/snapuserd/utility.cpp +++ b/fs_mgr/libsnapshot/snapuserd/utility.cpp @@ -14,11 +14,14 @@ #include "utility.h" +#include #include #include #include #include +#include +#include #include #include @@ -27,6 +30,7 @@ namespace android { namespace snapshot { using android::base::unique_fd; +using android::dm::DeviceMapper; bool SetThreadPriority([[maybe_unused]] int priority) { #ifdef __ANDROID__ @@ -61,5 +65,38 @@ bool KernelSupportsIoUring() { return major > 5 || (major == 5 && minor >= 6); } +bool GetUserspaceSnapshotsEnabledProperty() { + return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false); +} + +bool KernelSupportsCompressedSnapshots() { + auto& dm = DeviceMapper::Instance(); + return dm.GetTargetByName("user", nullptr); +} + +bool IsVendorFromAndroid12() { + const std::string UNKNOWN = "unknown"; + const std::string vendor_release = + android::base::GetProperty("ro.vendor.build.version.release_or_codename", UNKNOWN); + + if (vendor_release.find("12") != std::string::npos) { + return true; + } + return false; +} + +bool CanUseUserspaceSnapshots() { + if (!GetUserspaceSnapshotsEnabledProperty()) { + LOG(INFO) << "Virtual A/B - Userspace snapshots disabled"; + return false; + } + + if (!KernelSupportsCompressedSnapshots()) { + LOG(ERROR) << "Userspace snapshots requested, but no kernel support is available."; + return false; + } + return true; +} + } // namespace snapshot } // namespace android diff --git a/fs_mgr/libsnapshot/snapuserd/utility.h b/fs_mgr/libsnapshot/snapuserd/utility.h index c3c3cbae9..50be41837 100644 --- a/fs_mgr/libsnapshot/snapuserd/utility.h +++ b/fs_mgr/libsnapshot/snapuserd/utility.h @@ -24,5 +24,10 @@ bool SetThreadPriority(int priority); bool SetProfiles(std::initializer_list profiles); bool KernelSupportsIoUring(); +bool GetUserspaceSnapshotsEnabledProperty(); +bool KernelSupportsCompressedSnapshots(); +bool CanUseUserspaceSnapshots(); +bool IsVendorFromAndroid12(); + } // namespace snapshot } // namespace android diff --git a/fs_mgr/tests/AndroidTest.xml b/fs_mgr/tests/AndroidTest.xml index de835b3ad..1c06ebd33 100644 --- a/fs_mgr/tests/AndroidTest.xml +++ b/fs_mgr/tests/AndroidTest.xml @@ -16,6 +16,7 @@