Merge "Add a human readable description of the tagged_addr_ctrl value to tombstones."
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index d6ebb0d..2c878f0 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -439,6 +439,30 @@
{"reboot,forcedsilent", 191},
{"reboot,forcednonsilent", 192},
{"reboot,thermal,tj", 193},
+ {"reboot,emergency", 194},
+ {"reboot,factory", 195},
+ {"reboot,fastboot", 196},
+ {"reboot,gsa,hard", 197},
+ {"reboot,gsa,soft", 198},
+ {"reboot,master_dc,fault_n", 199},
+ {"reboot,master_dc,reset", 200},
+ {"reboot,ocp", 201},
+ {"reboot,pin", 202},
+ {"reboot,rom_recovery", 203},
+ {"reboot,uvlo", 204},
+ {"reboot,uvlo,pmic,if", 205},
+ {"reboot,uvlo,pmic,main", 206},
+ {"reboot,uvlo,pmic,sub", 207},
+ {"reboot,warm", 208},
+ {"watchdog,aoc", 209},
+ {"watchdog,apc", 210},
+ {"watchdog,apc,bl,debug,early", 211},
+ {"watchdog,apc,bl,early", 212},
+ {"watchdog,apc,early", 213},
+ {"watchdog,apm", 214},
+ {"watchdog,gsa,hard", 215},
+ {"watchdog,gsa,soft", 216},
+ {"watchdog,pmucal", 217},
};
// Converts a string value representing the reason the system booted to an
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 16cbbb0..edcea44 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -180,7 +180,6 @@
"libdebuggerd/backtrace.cpp",
"libdebuggerd/gwp_asan.cpp",
"libdebuggerd/open_files_list.cpp",
- "libdebuggerd/scudo.cpp",
"libdebuggerd/tombstone.cpp",
"libdebuggerd/tombstone_proto.cpp",
"libdebuggerd/tombstone_proto_to_text.cpp",
@@ -193,9 +192,6 @@
include_dirs: [
// Needed for private/bionic_fdsan.h
"bionic/libc",
-
- // Needed for scudo/interface.h
- "external/scudo/standalone/include",
],
header_libs: [
"bionic_libc_platform_headers",
@@ -210,11 +206,13 @@
"libcutils",
"liblog",
],
+ runtime_libs: [
+ "libdexfile", // libdexfile_support dependency
+ ],
whole_static_libs: [
"libasync_safe",
"gwp_asan_crash_handler",
- "libscudo",
"libtombstone_proto",
"libprocinfo",
"libprotobuf-cpp-lite",
@@ -225,11 +223,17 @@
exclude_static_libs: [
"libdexfile_support",
],
+ exclude_runtime_libs: [
+ "libdexfile",
+ ],
},
vendor_ramdisk: {
exclude_static_libs: [
"libdexfile_support",
],
+ exclude_runtime_libs: [
+ "libdexfile",
+ ],
},
},
@@ -237,6 +241,13 @@
debuggable: {
cflags: ["-DROOT_POSSIBLE"],
},
+
+ malloc_not_svelte: {
+ cflags: ["-DUSE_SCUDO"],
+ whole_static_libs: ["libscudo"],
+ srcs: ["libdebuggerd/scudo.cpp"],
+ header_libs: ["scudo_headers"],
+ },
},
}
@@ -308,10 +319,6 @@
"gwp_asan_headers",
],
- include_dirs: [
- "external/scudo/standalone/include",
- ],
-
local_include_dirs: [
"libdebuggerd",
],
diff --git a/debuggerd/crash_test.cpp b/debuggerd/crash_test.cpp
index c15145f..ce0f91f 100644
--- a/debuggerd/crash_test.cpp
+++ b/debuggerd/crash_test.cpp
@@ -16,6 +16,13 @@
#include <stdint.h>
-extern "C" void crash() {
+#include "crash_test.h"
+
+extern "C" {
+
+JITDescriptor __dex_debug_descriptor = {.version = 1};
+
+void crash() {
*reinterpret_cast<volatile char*>(0xdead) = '1';
}
+}
diff --git a/debuggerd/crash_test.h b/debuggerd/crash_test.h
new file mode 100644
index 0000000..2a8bea3
--- /dev/null
+++ b/debuggerd/crash_test.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+// Only support V1 of these structures.
+// See https://sourceware.org/gdb/onlinedocs/gdb/JIT-Interface.html
+// for information on the JIT Compilation Interface.
+// Also, see libunwindstack/GlobalDebugImpl.h for the full definition of
+// these structures.
+struct JITCodeEntry {
+ uintptr_t next;
+ uintptr_t prev;
+ uintptr_t symfile_addr;
+ uint64_t symfile_size;
+};
+
+struct JITDescriptor {
+ uint32_t version;
+ uint32_t action_flag;
+ uintptr_t relevant_entry;
+ uintptr_t first_entry;
+};
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 75f9921..e555793 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -57,6 +57,7 @@
#include <libminijail.h>
#include <scoped_minijail.h>
+#include "crash_test.h"
#include "debuggerd/handler.h"
#include "libdebuggerd/utility.h"
#include "protocol.h"
@@ -1890,3 +1891,299 @@
ConsumeFd(std::move(output_fd), &result);
ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal");
}
+
+static std::string format_pointer(uintptr_t ptr) {
+#if defined(__LP64__)
+ return android::base::StringPrintf("%08x'%08x", static_cast<uint32_t>(ptr >> 32),
+ static_cast<uint32_t>(ptr & 0xffffffff));
+#else
+ return android::base::StringPrintf("%08x", static_cast<uint32_t>(ptr & 0xffffffff));
+#endif
+}
+
+static std::string format_pointer(void* ptr) {
+ return format_pointer(reinterpret_cast<uintptr_t>(ptr));
+}
+
+static std::string format_full_pointer(uintptr_t ptr) {
+#if defined(__LP64__)
+ return android::base::StringPrintf("%016" PRIx64, ptr);
+#else
+ return android::base::StringPrintf("%08x", ptr);
+#endif
+}
+
+static std::string format_full_pointer(void* ptr) {
+ return format_full_pointer(reinterpret_cast<uintptr_t>(ptr));
+}
+
+__attribute__((__noinline__)) int crash_call(uintptr_t ptr) {
+ int* crash_ptr = reinterpret_cast<int*>(ptr);
+ *crash_ptr = 1;
+ return *crash_ptr;
+}
+
+// Verify that a fault address before the first map is properly handled.
+TEST_F(CrasherTest, fault_address_before_first_map) {
+ StartProcess([]() {
+ ASSERT_EQ(0, crash_call(0x1024));
+ _exit(0);
+ });
+
+ unique_fd output_fd;
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+
+ int intercept_result;
+ FinishIntercept(&intercept_result);
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x1024)");
+
+ ASSERT_MATCH(result, R"(\nmemory map \(.*\):\n)");
+
+ std::string match_str = android::base::StringPrintf(
+ R"(memory map .*:\n--->Fault address falls at %s before any mapped regions\n )",
+ format_pointer(0x1024).c_str());
+ ASSERT_MATCH(result, match_str);
+}
+
+// Verify that a fault address after the last map is properly handled.
+TEST_F(CrasherTest, fault_address_after_last_map) {
+ uintptr_t crash_uptr = untag_address(UINTPTR_MAX - 15);
+ StartProcess([crash_uptr]() {
+ ASSERT_EQ(0, crash_call(crash_uptr));
+ _exit(0);
+ });
+
+ unique_fd output_fd;
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+
+ int intercept_result;
+ FinishIntercept(&intercept_result);
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ std::string match_str = R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr )";
+ match_str += android::base::StringPrintf("0x%" PRIxPTR, crash_uptr);
+ ASSERT_MATCH(result, match_str);
+
+ ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->)\n)");
+
+ // Assumes that the open files section comes after the map section.
+ // If that assumption changes, the regex below needs to change.
+ match_str = android::base::StringPrintf(
+ R"(\n--->Fault address falls at %s after any mapped regions\n\nopen files:)",
+ format_pointer(crash_uptr).c_str());
+ ASSERT_MATCH(result, match_str);
+}
+
+// Verify that a fault address between maps is properly handled.
+TEST_F(CrasherTest, fault_address_between_maps) {
+ // Create a map before the fork so it will be present in the child.
+ void* start_ptr =
+ mmap(nullptr, 3 * getpagesize(), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ ASSERT_NE(MAP_FAILED, start_ptr);
+ // Unmap the page in the middle.
+ void* middle_ptr =
+ reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(start_ptr) + getpagesize());
+ ASSERT_EQ(0, munmap(middle_ptr, getpagesize()));
+
+ StartProcess([middle_ptr]() {
+ ASSERT_EQ(0, crash_call(reinterpret_cast<uintptr_t>(middle_ptr)));
+ _exit(0);
+ });
+
+ // Unmap the two maps.
+ ASSERT_EQ(0, munmap(start_ptr, getpagesize()));
+ void* end_ptr =
+ reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(start_ptr) + 2 * getpagesize());
+ ASSERT_EQ(0, munmap(end_ptr, getpagesize()));
+
+ unique_fd output_fd;
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+
+ int intercept_result;
+ FinishIntercept(&intercept_result);
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ std::string match_str = R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr )";
+ match_str += android::base::StringPrintf("%p", middle_ptr);
+ ASSERT_MATCH(result, match_str);
+
+ ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->)\n)");
+
+ match_str = android::base::StringPrintf(
+ R"( %s.*\n--->Fault address falls at %s between mapped regions\n %s)",
+ format_pointer(start_ptr).c_str(), format_pointer(middle_ptr).c_str(),
+ format_pointer(end_ptr).c_str());
+ ASSERT_MATCH(result, match_str);
+}
+
+// Verify that a fault address happens in the correct map.
+TEST_F(CrasherTest, fault_address_in_map) {
+ // Create a map before the fork so it will be present in the child.
+ void* ptr = mmap(nullptr, getpagesize(), 0, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ ASSERT_NE(MAP_FAILED, ptr);
+
+ StartProcess([ptr]() {
+ ASSERT_EQ(0, crash_call(reinterpret_cast<uintptr_t>(ptr)));
+ _exit(0);
+ });
+
+ ASSERT_EQ(0, munmap(ptr, getpagesize()));
+
+ unique_fd output_fd;
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+
+ int intercept_result;
+ FinishIntercept(&intercept_result);
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ std::string match_str = R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\), fault addr )";
+ match_str += android::base::StringPrintf("%p", ptr);
+ ASSERT_MATCH(result, match_str);
+
+ ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->)\n)");
+
+ match_str = android::base::StringPrintf(R"(\n--->%s.*\n)", format_pointer(ptr).c_str());
+ ASSERT_MATCH(result, match_str);
+}
+
+static constexpr uint32_t kDexData[] = {
+ 0x0a786564, 0x00383330, 0xc98b3ab8, 0xf3749d94, 0xaecca4d8, 0xffc7b09a, 0xdca9ca7f, 0x5be5deab,
+ 0x00000220, 0x00000070, 0x12345678, 0x00000000, 0x00000000, 0x0000018c, 0x00000008, 0x00000070,
+ 0x00000004, 0x00000090, 0x00000002, 0x000000a0, 0x00000000, 0x00000000, 0x00000003, 0x000000b8,
+ 0x00000001, 0x000000d0, 0x00000130, 0x000000f0, 0x00000122, 0x0000012a, 0x00000132, 0x00000146,
+ 0x00000151, 0x00000154, 0x00000158, 0x0000016d, 0x00000001, 0x00000002, 0x00000004, 0x00000006,
+ 0x00000004, 0x00000002, 0x00000000, 0x00000005, 0x00000002, 0x0000011c, 0x00000000, 0x00000000,
+ 0x00010000, 0x00000007, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000,
+ 0x00000003, 0x00000000, 0x0000017e, 0x00000000, 0x00010001, 0x00000001, 0x00000173, 0x00000004,
+ 0x00021070, 0x000e0000, 0x00010001, 0x00000000, 0x00000178, 0x00000001, 0x0000000e, 0x00000001,
+ 0x3c060003, 0x74696e69, 0x4c06003e, 0x6e69614d, 0x4c12003b, 0x6176616a, 0x6e616c2f, 0x624f2f67,
+ 0x7463656a, 0x4d09003b, 0x2e6e6961, 0x6176616a, 0x00560100, 0x004c5602, 0x6a4c5b13, 0x2f617661,
+ 0x676e616c, 0x7274532f, 0x3b676e69, 0x616d0400, 0x01006e69, 0x000e0700, 0x07000103, 0x0000000e,
+ 0x81000002, 0x01f00480, 0x02880901, 0x0000000c, 0x00000000, 0x00000001, 0x00000000, 0x00000001,
+ 0x00000008, 0x00000070, 0x00000002, 0x00000004, 0x00000090, 0x00000003, 0x00000002, 0x000000a0,
+ 0x00000005, 0x00000003, 0x000000b8, 0x00000006, 0x00000001, 0x000000d0, 0x00002001, 0x00000002,
+ 0x000000f0, 0x00001001, 0x00000001, 0x0000011c, 0x00002002, 0x00000008, 0x00000122, 0x00002003,
+ 0x00000002, 0x00000173, 0x00002000, 0x00000001, 0x0000017e, 0x00001000, 0x00000001, 0x0000018c,
+};
+
+TEST_F(CrasherTest, verify_dex_pc_with_function_name) {
+ StartProcess([]() {
+ TemporaryDir td;
+ std::string tmp_so_name;
+ if (!CopySharedLibrary(td.path, &tmp_so_name)) {
+ _exit(1);
+ }
+
+ // In order to cause libunwindstack to look for this __dex_debug_descriptor
+ // move the library to which has a basename of libart.so.
+ std::string art_so_name = android::base::Dirname(tmp_so_name) + "/libart.so";
+ ASSERT_EQ(0, rename(tmp_so_name.c_str(), art_so_name.c_str()));
+ void* handle = dlopen(art_so_name.c_str(), RTLD_NOW | RTLD_LOCAL);
+ if (handle == nullptr) {
+ _exit(1);
+ }
+
+ void* ptr =
+ mmap(nullptr, sizeof(kDexData), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ ASSERT_TRUE(ptr != MAP_FAILED);
+ memcpy(ptr, kDexData, sizeof(kDexData));
+ prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ptr, sizeof(kDexData), "dex");
+
+ JITCodeEntry dex_entry = {.symfile_addr = reinterpret_cast<uintptr_t>(ptr),
+ .symfile_size = sizeof(kDexData)};
+
+ JITDescriptor* dex_debug =
+ reinterpret_cast<JITDescriptor*>(dlsym(handle, "__dex_debug_descriptor"));
+ ASSERT_TRUE(dex_debug != nullptr);
+ dex_debug->version = 1;
+ dex_debug->action_flag = 0;
+ dex_debug->relevant_entry = 0;
+ dex_debug->first_entry = reinterpret_cast<uintptr_t>(&dex_entry);
+
+ // This sets the magic dex pc value for register 0, using the value
+ // of register 1 + 0x102.
+ asm(".cfi_escape "
+ "0x16 /* DW_CFA_val_expression */, 0, 0x0a /* size */,"
+ "0x0c /* DW_OP_const4u */, 0x44, 0x45, 0x58, 0x31, /* magic = 'DEX1' */"
+ "0x13 /* DW_OP_drop */,"
+ "0x92 /* DW_OP_bregx */, 1, 0x82, 0x02 /* 2-byte SLEB128 */");
+
+ // For each different architecture, set register one to the dex ptr mmap
+ // created above. Then do a nullptr dereference to force a crash.
+#if defined(__arm__)
+ asm volatile(
+ "mov r1, %[base]\n"
+ "mov r2, 0\n"
+ "str r3, [r2]\n"
+ : [base] "+r"(ptr)
+ :
+ : "r1", "r2", "r3", "memory");
+#elif defined(__aarch64__)
+ asm volatile(
+ "mov x1, %[base]\n"
+ "mov x2, 0\n"
+ "str x3, [x2]\n"
+ : [base] "+r"(ptr)
+ :
+ : "x1", "x2", "x3", "memory");
+#elif defined(__i386__)
+ asm volatile(
+ "mov %[base], %%ecx\n"
+ "movl $0, %%edi\n"
+ "movl 0(%%edi), %%edx\n"
+ : [base] "+r"(ptr)
+ :
+ : "edi", "ecx", "edx", "memory");
+#elif defined(__x86_64__)
+ asm volatile(
+ "mov %[base], %%rdx\n"
+ "movq 0, %%rdi\n"
+ "movq 0(%%rdi), %%rcx\n"
+ : [base] "+r"(ptr)
+ :
+ : "rcx", "rdx", "rdi", "memory");
+#else
+#error "Unsupported architecture"
+#endif
+ _exit(0);
+ });
+
+ unique_fd output_fd;
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+
+ int intercept_result;
+ FinishIntercept(&intercept_result);
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ // Verify the process crashed properly.
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0)");
+
+ // Now verify that the dex_pc frame includes a proper function name.
+ ASSERT_MATCH(result, R"( \[anon:dex\] \(Main\.\<init\>\+2)");
+}
diff --git a/debuggerd/libdebuggerd/test/UnwinderMock.h b/debuggerd/libdebuggerd/test/UnwinderMock.h
index 8f84346..1e3c559 100644
--- a/debuggerd/libdebuggerd/test/UnwinderMock.h
+++ b/debuggerd/libdebuggerd/test/UnwinderMock.h
@@ -16,6 +16,8 @@
#pragma once
+#include <memory>
+
#include <unwindstack/MapInfo.h>
#include <unwindstack/Maps.h>
#include <unwindstack/Unwinder.h>
@@ -31,7 +33,7 @@
}
void MockSetBuildID(uint64_t offset, const std::string& build_id) {
- unwindstack::MapInfo* map_info = GetMaps()->Find(offset);
+ std::shared_ptr<unwindstack::MapInfo> map_info = GetMaps()->Find(offset);
if (map_info != nullptr) {
map_info->SetBuildID(std::string(build_id));
}
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index a4a3c9e..20539b0 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -36,6 +36,7 @@
#include <string>
#include <android-base/file.h>
+#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
@@ -57,10 +58,13 @@
#include "libdebuggerd/backtrace.h"
#include "libdebuggerd/gwp_asan.h"
#include "libdebuggerd/open_files_list.h"
-#include "libdebuggerd/scudo.h"
#include "libdebuggerd/utility.h"
#include "util.h"
+#if defined(USE_SCUDO)
+#include "libdebuggerd/scudo.h"
+#endif
+
#include "gwp_asan/common.h"
#include "gwp_asan/crash_handler.h"
@@ -103,7 +107,7 @@
// In this case, the sp will be in either an invalid map if triggered
// on the main thread, or in a guard map if in another thread, which
// will be the first case or second case from below.
- unwindstack::MapInfo* map_info = maps->Find(sp);
+ auto map_info = maps->Find(sp);
if (map_info == nullptr) {
return "stack pointer is in a non-existent map; likely due to stack overflow.";
} else if ((map_info->flags() & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) {
@@ -115,8 +119,26 @@
return "";
}
-static void dump_probable_cause(log_t* log, const siginfo_t* si, unwindstack::Maps* maps,
- unwindstack::Regs* regs) {
+static void dump_probable_cause(log_t* log, unwindstack::Unwinder* unwinder,
+ const ProcessInfo& process_info, const ThreadInfo& main_thread) {
+#if defined(USE_SCUDO)
+ ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info);
+ if (scudo_crash_data.CrashIsMine()) {
+ scudo_crash_data.DumpCause(log, unwinder);
+ return;
+ }
+#endif
+
+ GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info,
+ main_thread);
+ if (gwp_asan_crash_data.CrashIsMine()) {
+ gwp_asan_crash_data.DumpCause(log);
+ return;
+ }
+
+ unwindstack::Maps* maps = unwinder->GetMaps();
+ unwindstack::Regs* regs = main_thread.registers.get();
+ const siginfo_t* si = main_thread.siginfo;
std::string cause;
if (si->si_signo == SIGSEGV && si->si_code == SEGV_MAPERR) {
if (si->si_addr < reinterpret_cast<void*>(4096)) {
@@ -136,7 +158,7 @@
}
} else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) {
uint64_t fault_addr = reinterpret_cast<uint64_t>(si->si_addr);
- unwindstack::MapInfo* map_info = maps->Find(fault_addr);
+ 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 {
@@ -375,7 +397,7 @@
regs->IterateRegisters([log, maps, memory](const char* reg_name, uint64_t reg_value) {
std::string label{"memory near "s + reg_name};
if (maps) {
- unwindstack::MapInfo* map_info = maps->Find(untag_address(reg_value));
+ auto map_info = maps->Find(untag_address(reg_value));
if (map_info != nullptr && !map_info->name().empty()) {
label += " (" + map_info->name() + ")";
}
@@ -396,22 +418,11 @@
dump_signal_info(log, thread_info, process_info, unwinder->GetProcessMemory().get());
}
- std::unique_ptr<GwpAsanCrashData> gwp_asan_crash_data;
- std::unique_ptr<ScudoCrashData> scudo_crash_data;
if (primary_thread) {
- gwp_asan_crash_data = std::make_unique<GwpAsanCrashData>(unwinder->GetProcessMemory().get(),
- process_info, thread_info);
- scudo_crash_data =
- std::make_unique<ScudoCrashData>(unwinder->GetProcessMemory().get(), process_info);
- }
+ // The main thread must have a valid siginfo.
+ CHECK(thread_info.siginfo != nullptr);
+ dump_probable_cause(log, unwinder, process_info, thread_info);
- if (primary_thread && gwp_asan_crash_data->CrashIsMine()) {
- gwp_asan_crash_data->DumpCause(log);
- } else if (thread_info.siginfo && !(primary_thread && scudo_crash_data->CrashIsMine())) {
- dump_probable_cause(log, thread_info.siginfo, unwinder->GetMaps(), thread_info.registers.get());
- }
-
- if (primary_thread) {
dump_abort_message(log, unwinder->GetProcessMemory().get(), process_info.abort_msg_address);
}
@@ -433,16 +444,17 @@
}
if (primary_thread) {
- if (gwp_asan_crash_data->HasDeallocationTrace()) {
- gwp_asan_crash_data->DumpDeallocationTrace(log, unwinder);
+ GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info,
+ thread_info);
+
+ if (gwp_asan_crash_data.HasDeallocationTrace()) {
+ gwp_asan_crash_data.DumpDeallocationTrace(log, unwinder);
}
- if (gwp_asan_crash_data->HasAllocationTrace()) {
- gwp_asan_crash_data->DumpAllocationTrace(log, unwinder);
+ if (gwp_asan_crash_data.HasAllocationTrace()) {
+ gwp_asan_crash_data.DumpAllocationTrace(log, unwinder);
}
- scudo_crash_data->DumpCause(log, unwinder);
-
unwindstack::Maps* maps = unwinder->GetMaps();
dump_memory_and_code(log, maps, unwinder->GetProcessMemory().get(),
thread_info.registers.get());
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index 0c93c90..b1c4ef3 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -18,7 +18,9 @@
#include "libdebuggerd/tombstone.h"
#include "libdebuggerd/gwp_asan.h"
+#if defined(USE_SCUDO)
#include "libdebuggerd/scudo.h"
+#endif
#include <errno.h>
#include <fcntl.h>
@@ -28,6 +30,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
+#include <sys/sysinfo.h>
#include <time.h>
#include <memory>
@@ -37,6 +40,7 @@
#include <async_safe/log.h>
#include <android-base/file.h>
+#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
@@ -99,7 +103,7 @@
// In this case, the sp will be in either an invalid map if triggered
// on the main thread, or in a guard map if in another thread, which
// will be the first case or second case from below.
- unwindstack::MapInfo* map_info = maps->Find(sp);
+ std::shared_ptr<unwindstack::MapInfo> map_info = maps->Find(sp);
if (map_info == nullptr) {
return "stack pointer is in a non-existent map; likely due to stack overflow.";
} else if ((map_info->flags() & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) {
@@ -185,11 +189,13 @@
static void dump_probable_cause(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
const ProcessInfo& process_info, const ThreadInfo& main_thread) {
+#if defined(USE_SCUDO)
ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info);
if (scudo_crash_data.CrashIsMine()) {
scudo_crash_data.AddCauseProtos(tombstone, unwinder);
return;
}
+#endif
GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info,
main_thread);
@@ -220,7 +226,7 @@
cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps);
}
} else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) {
- unwindstack::MapInfo* map_info = maps->Find(fault_addr);
+ auto map_info = maps->Find(fault_addr);
if (map_info != nullptr && map_info->flags() == PROT_EXEC) {
cause = "execute-only (no-read) memory access error; likely due to data in .text.";
} else {
@@ -336,8 +342,8 @@
f->set_file_map_offset(frame.map_elf_start_offset);
- unwindstack::MapInfo* map_info = maps->Find(frame.map_start);
- if (map_info) {
+ auto map_info = maps->Find(frame.map_start);
+ if (map_info.get() != nullptr) {
f->set_build_id(map_info->GetPrintableBuildID());
}
}
@@ -364,7 +370,7 @@
MemoryDump dump;
dump.set_register_name(name);
- unwindstack::MapInfo* map_info = maps->Find(untag_address(value));
+ std::shared_ptr<unwindstack::MapInfo> map_info = maps->Find(untag_address(value));
if (map_info) {
dump.set_mapping_name(map_info->name());
}
@@ -596,14 +602,6 @@
}
}
-static std::optional<uint64_t> read_uptime_secs() {
- std::string uptime;
- if (!android::base::ReadFileToString("/proc/uptime", &uptime)) {
- return {};
- }
- return strtoll(uptime.c_str(), nullptr, 10);
-}
-
void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
const ProcessInfo& process_info, const OpenFilesList* open_files) {
@@ -614,27 +612,25 @@
result.set_revision(android::base::GetProperty("ro.revision", "unknown"));
result.set_timestamp(get_timestamp());
- std::optional<uint64_t> system_uptime = read_uptime_secs();
- if (system_uptime) {
- android::procinfo::ProcessInfo proc_info;
- std::string error;
- if (android::procinfo::GetProcessInfo(target_thread, &proc_info, &error)) {
- uint64_t starttime = proc_info.starttime / sysconf(_SC_CLK_TCK);
- result.set_process_uptime(*system_uptime - starttime);
- } else {
- async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read process info: %s",
- error.c_str());
- }
- } else {
- async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read /proc/uptime: %s",
- strerror(errno));
- }
-
const ThreadInfo& main_thread = threads.at(target_thread);
result.set_pid(main_thread.pid);
result.set_tid(main_thread.tid);
result.set_uid(main_thread.uid);
result.set_selinux_label(main_thread.selinux_label);
+ // The main thread must have a valid siginfo.
+ CHECK(main_thread.siginfo != nullptr);
+
+ struct sysinfo si;
+ sysinfo(&si);
+ android::procinfo::ProcessInfo proc_info;
+ std::string error;
+ if (android::procinfo::GetProcessInfo(main_thread.pid, &proc_info, &error)) {
+ uint64_t starttime = proc_info.starttime / sysconf(_SC_CLK_TCK);
+ result.set_process_uptime(si.uptime - starttime);
+ } else {
+ async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read process info: %s",
+ error.c_str());
+ }
auto cmd_line = result.mutable_command_line();
for (const auto& arg : main_thread.command_line) {
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index 0cba362..5a13f84 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -363,8 +363,13 @@
print_thread_memory_dump(callback, tombstone, thread);
CBS("");
- CBS("memory map (%d %s):", tombstone.memory_mappings().size(),
- tombstone.memory_mappings().size() == 1 ? "entry" : "entries");
+
+ // No memory maps to print.
+ if (tombstone.memory_mappings().empty()) {
+ CBS("No memory maps found");
+ return;
+ }
+
int word_size = pointer_width(tombstone);
const auto format_pointer = [word_size](uint64_t ptr) -> std::string {
if (word_size == 8) {
@@ -376,8 +381,41 @@
return StringPrintf("%0*" PRIx64, word_size * 2, ptr);
};
+ std::string memory_map_header =
+ StringPrintf("memory map (%d %s):", tombstone.memory_mappings().size(),
+ tombstone.memory_mappings().size() == 1 ? "entry" : "entries");
+
+ bool has_fault_address = signal_info.has_fault_address();
+ uint64_t fault_address = untag_address(signal_info.fault_address());
+ bool preamble_printed = false;
+ bool printed_fault_address_marker = false;
for (const auto& map : tombstone.memory_mappings()) {
+ if (!preamble_printed) {
+ preamble_printed = true;
+ if (has_fault_address) {
+ if (fault_address < map.begin_address()) {
+ memory_map_header +=
+ StringPrintf("\n--->Fault address falls at %s before any mapped regions",
+ format_pointer(fault_address).c_str());
+ printed_fault_address_marker = true;
+ } else {
+ memory_map_header += " (fault address prefixed with --->)";
+ }
+ }
+ CBS("%s", memory_map_header.c_str());
+ }
+
std::string line = " ";
+ if (has_fault_address && !printed_fault_address_marker) {
+ if (fault_address < map.begin_address()) {
+ printed_fault_address_marker = true;
+ CBS("--->Fault address falls at %s between mapped regions",
+ format_pointer(fault_address).c_str());
+ } else if (fault_address >= map.begin_address() && fault_address < map.end_address()) {
+ printed_fault_address_marker = true;
+ line = "--->";
+ }
+ }
StringAppendF(&line, "%s-%s", format_pointer(map.begin_address()).c_str(),
format_pointer(map.end_address() - 1).c_str());
StringAppendF(&line, " %s%s%s", map.read() ? "r" : "-", map.write() ? "w" : "-",
@@ -399,6 +437,11 @@
CBS("%s", line.c_str());
}
+
+ if (has_fault_address && !printed_fault_address_marker) {
+ CBS("--->Fault address falls at %s after any mapped regions",
+ format_pointer(fault_address).c_str());
+ }
}
void print_logs(CallbackType callback, const Tombstone& tombstone, int tail) {
diff --git a/debuggerd/seccomp_policy/crash_dump.arm.policy b/debuggerd/seccomp_policy/crash_dump.arm.policy
index 4eac0e9..8fd03c4 100644
--- a/debuggerd/seccomp_policy/crash_dump.arm.policy
+++ b/debuggerd/seccomp_policy/crash_dump.arm.policy
@@ -20,6 +20,7 @@
faccessat: 1
recvmsg: 1
recvfrom: 1
+sysinfo: 1
process_vm_readv: 1
tgkill: 1
rt_sigprocmask: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.arm64.policy b/debuggerd/seccomp_policy/crash_dump.arm64.policy
index 21887ab..858a338 100644
--- a/debuggerd/seccomp_policy/crash_dump.arm64.policy
+++ b/debuggerd/seccomp_policy/crash_dump.arm64.policy
@@ -19,6 +19,7 @@
faccessat: 1
recvmsg: 1
recvfrom: 1
+sysinfo: 1
process_vm_readv: 1
tgkill: 1
rt_sigprocmask: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.policy.def b/debuggerd/seccomp_policy/crash_dump.policy.def
index 90843fc..152697c 100644
--- a/debuggerd/seccomp_policy/crash_dump.policy.def
+++ b/debuggerd/seccomp_policy/crash_dump.policy.def
@@ -25,6 +25,7 @@
faccessat: 1
recvmsg: 1
recvfrom: 1
+sysinfo: 1
process_vm_readv: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.x86.policy b/debuggerd/seccomp_policy/crash_dump.x86.policy
index 4eac0e9..8fd03c4 100644
--- a/debuggerd/seccomp_policy/crash_dump.x86.policy
+++ b/debuggerd/seccomp_policy/crash_dump.x86.policy
@@ -20,6 +20,7 @@
faccessat: 1
recvmsg: 1
recvfrom: 1
+sysinfo: 1
process_vm_readv: 1
tgkill: 1
rt_sigprocmask: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.x86_64.policy b/debuggerd/seccomp_policy/crash_dump.x86_64.policy
index 1585cc6..281e231 100644
--- a/debuggerd/seccomp_policy/crash_dump.x86_64.policy
+++ b/debuggerd/seccomp_policy/crash_dump.x86_64.policy
@@ -19,6 +19,7 @@
faccessat: 1
recvmsg: 1
recvfrom: 1
+sysinfo: 1
process_vm_readv: 1
tgkill: 1
rt_sigprocmask: 1
diff --git a/debuggerd/tombstoned/tombstoned.cpp b/debuggerd/tombstoned/tombstoned.cpp
index 05d8050..50558f7 100644
--- a/debuggerd/tombstoned/tombstoned.cpp
+++ b/debuggerd/tombstoned/tombstoned.cpp
@@ -449,7 +449,7 @@
}
if (crash->output.text.fd == -1) {
- LOG(WARNING) << "missing output fd";
+ LOG(WARNING) << "skipping tombstone file creation due to intercept";
return;
}
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 2c70778..339f392 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -408,3 +408,9 @@
":fastboot_test_vendor_boot_v4_with_frag"
],
}
+
+cc_library_headers {
+ name: "fastboot_headers",
+ host_supported: true,
+ export_include_dirs: ["."],
+}
diff --git a/fastboot/Android.mk b/fastboot/Android.mk
index 0e918a3..322fe5c 100644
--- a/fastboot/Android.mk
+++ b/fastboot/Android.mk
@@ -18,10 +18,10 @@
# Package fastboot-related executables.
#
-my_dist_files := $(SOONG_HOST_OUT_EXECUTABLES)/mke2fs
-my_dist_files += $(SOONG_HOST_OUT_EXECUTABLES)/e2fsdroid
-my_dist_files += $(SOONG_HOST_OUT_EXECUTABLES)/make_f2fs
-my_dist_files += $(SOONG_HOST_OUT_EXECUTABLES)/make_f2fs_casefold
-my_dist_files += $(SOONG_HOST_OUT_EXECUTABLES)/sload_f2fs
+my_dist_files := $(HOST_OUT_EXECUTABLES)/mke2fs
+my_dist_files += $(HOST_OUT_EXECUTABLES)/e2fsdroid
+my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs
+my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs_casefold
+my_dist_files += $(HOST_OUT_EXECUTABLES)/sload_f2fs
$(call dist-for-goals,dist_files sdk win_sdk,$(my_dist_files))
my_dist_files :=
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index 0a72812..4042531 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -725,7 +725,7 @@
return false;
}
- if (!OpenPartition(device_, partition_name_, &handle_, true /* read */)) {
+ if (!OpenPartition(device_, partition_name_, &handle_, O_RDONLY)) {
ret_ = device_->WriteFail(
android::base::StringPrintf("Cannot open %s", partition_name_.c_str()));
return false;
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 9b5d2cd..3f9bcdc 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -16,6 +16,7 @@
#include "flashing.h"
#include <fcntl.h>
+#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
@@ -77,9 +78,20 @@
int FlashRawDataChunk(int fd, const char* data, size_t len) {
size_t ret = 0;
+ const size_t max_write_size = 1048576;
+ void* aligned_buffer;
+
+ if (posix_memalign(&aligned_buffer, 4096, max_write_size)) {
+ PLOG(ERROR) << "Failed to allocate write buffer";
+ return -ENOMEM;
+ }
+
+ auto aligned_buffer_unique_ptr = std::unique_ptr<void, decltype(&free)>{aligned_buffer, free};
+
while (ret < len) {
- int this_len = std::min(static_cast<size_t>(1048576UL * 8), len - ret);
- int this_ret = write(fd, data, this_len);
+ int this_len = std::min(max_write_size, len - ret);
+ memcpy(aligned_buffer_unique_ptr.get(), data, this_len);
+ int this_ret = write(fd, aligned_buffer_unique_ptr.get(), this_len);
if (this_ret < 0) {
PLOG(ERROR) << "Failed to flash data of len " << len;
return -1;
@@ -147,7 +159,7 @@
int Flash(FastbootDevice* device, const std::string& partition_name) {
PartitionHandle handle;
- if (!OpenPartition(device, partition_name, &handle)) {
+ if (!OpenPartition(device, partition_name, &handle, O_WRONLY | O_DIRECT)) {
return -ENOENT;
}
diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp
index 07ad902..97b5ad4 100644
--- a/fastboot/device/utility.cpp
+++ b/fastboot/device/utility.cpp
@@ -78,7 +78,7 @@
} // namespace
bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
- bool read) {
+ int flags) {
// We prioritize logical partitions over physical ones, and do this
// consistently for other partition operations (like getvar:partition-size).
if (LogicalPartitionExists(device, name)) {
@@ -90,7 +90,6 @@
return false;
}
- int flags = (read ? O_RDONLY : O_WRONLY);
flags |= (O_EXCL | O_CLOEXEC | O_BINARY);
unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), flags)));
if (fd < 0) {
diff --git a/fastboot/device/utility.h b/fastboot/device/utility.h
index c2646d7..1d81b7a 100644
--- a/fastboot/device/utility.h
+++ b/fastboot/device/utility.h
@@ -76,9 +76,11 @@
bool LogicalPartitionExists(FastbootDevice* device, const std::string& name,
bool* is_zero_length = nullptr);
-// If read, partition is readonly. Else it is write only.
+// Partition is O_WRONLY by default, caller should pass O_RDONLY for reading.
+// Caller may pass additional flags if needed. (O_EXCL | O_CLOEXEC | O_BINARY)
+// will be logically ORed internally.
bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
- bool read = false);
+ int flags = O_WRONLY);
bool GetSlotNumber(const std::string& slot, android::hardware::boot::V1_0::Slot* number);
std::vector<std::string> ListPartitions(FastbootDevice* device);
diff --git a/fastboot/fs.cpp b/fastboot/fs.cpp
index 458a7a1..d268a50 100644
--- a/fastboot/fs.cpp
+++ b/fastboot/fs.cpp
@@ -143,6 +143,13 @@
mke2fs_args.push_back("512");
}
+ if (fsOptions & (1 << FS_OPT_CASEFOLD)) {
+ mke2fs_args.push_back("-O");
+ mke2fs_args.push_back("casefold");
+ mke2fs_args.push_back("-E");
+ mke2fs_args.push_back("encoding=utf8");
+ }
+
mke2fs_args.push_back(fileName);
std::string size_str = std::to_string(partSize / block_size);
diff --git a/fastboot/fuzzer/Android.bp b/fastboot/fuzzer/Android.bp
new file mode 100644
index 0000000..fcd3bd6
--- /dev/null
+++ b/fastboot/fuzzer/Android.bp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_fuzz {
+ name: "fastboot_fuzzer",
+ host_supported: true,
+ device_supported: false,
+ srcs: [
+ "fastboot_fuzzer.cpp",
+ "socket_mock_fuzz.cpp",
+ ],
+ header_libs: [
+ "bootimg_headers",
+ "fastboot_headers",
+ ],
+ static_libs: [
+ "libext4_utils",
+ "libcrypto",
+ "libfastboot",
+ "libbuildversion",
+ "libbase",
+ "libziparchive",
+ "libsparse",
+ "libutils",
+ "liblog",
+ "libz",
+ "libdiagnose_usb",
+ "libbase",
+ "libcutils",
+ "libgtest",
+ "libgtest_main",
+ "libbase",
+ "libadb_host",
+ "liblp",
+ "liblog",
+ ],
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 533764,
+ },
+}
diff --git a/fastboot/fuzzer/README.md b/fastboot/fuzzer/README.md
new file mode 100644
index 0000000..10b06ea
--- /dev/null
+++ b/fastboot/fuzzer/README.md
@@ -0,0 +1,51 @@
+# Fuzzer for libfastboot
+
+## Plugin Design Considerations
+The fuzzer plugin for libfastboot is designed based on the understanding of the
+source code and tries to achieve the following:
+
+##### Maximize code coverage
+The configuration parameters are not hardcoded, but instead selected based on
+incoming data. This ensures more code paths are reached by the fuzzer.
+
+libfastboot supports the following parameters:
+1. Year (parameter name: `year`)
+2. Month (parameter name: `month`)
+3. Day (parameter name: `day`)
+4. Version (parameter name: `version`)
+5. Fs Option (parameter name: `fsOption`)
+
+| Parameter| Valid Values| Configured Value|
+|------------- |-------------| ----- |
+| `year` | `2000` to `2127` | Value obtained from FuzzedDataProvider|
+| `month` | `1` to `12` | Value obtained from FuzzedDataProvider|
+| `day` | `1` to `31` | Value obtained from FuzzedDataProvider|
+| `version` | `0` to `127` | Value obtained from FuzzedDataProvider|
+| `fsOption` | 0. `casefold` 1. `projid` 2. `compress` | Value obtained from FuzzedDataProvider|
+
+##### Maximize utilization of input data
+The plugin feeds the entire input data to the module.
+This ensures that the plugin tolerates any kind of input (empty, huge,
+malformed, etc) and doesnt `exit()` on any input and thereby increasing the
+chance of identifying vulnerabilities.
+
+## Build
+
+This describes steps to build fastboot_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) fastboot_fuzzer_fuzzer
+```
+#### Steps to run
+To run on host
+```
+ $ $ANDROID_HOST_OUT/fuzz/${TARGET_ARCH}/fastboot_fuzzer/fastboot_fuzzer CORPUS_DIR
+```
+
+## References:
+ * http://llvm.org/docs/LibFuzzer.html
+ * https://github.com/google/oss-fuzz
diff --git a/fastboot/fuzzer/fastboot_fuzzer.cpp b/fastboot/fuzzer/fastboot_fuzzer.cpp
new file mode 100644
index 0000000..60940fe
--- /dev/null
+++ b/fastboot/fuzzer/fastboot_fuzzer.cpp
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#include <android-base/file.h>
+#include "fastboot.h"
+#include "socket.h"
+#include "socket_mock_fuzz.h"
+#include "tcp.h"
+#include "udp.h"
+#include "vendor_boot_img_utils.h"
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+using namespace std;
+
+const size_t kYearMin = 2000;
+const size_t kYearMax = 2127;
+const size_t kMonthMin = 1;
+const size_t kMonthMax = 12;
+const size_t kDayMin = 1;
+const size_t kDayMax = 31;
+const size_t kVersionMin = 0;
+const size_t kVersionMax = 127;
+const size_t kMaxStringSize = 100;
+const size_t kMinTimeout = 10;
+const size_t kMaxTimeout = 3000;
+const uint16_t kValidUdpPacketSize = 512;
+const uint16_t kMinUdpPackets = 1;
+const uint16_t kMaxUdpPackets = 10;
+
+const string kValidTcpHandshakeString = "FB01";
+const string kInvalidTcpHandshakeString = "FB00";
+const string kValidRamdiskName = "default";
+const string kVendorBootFile = "/tmp/vendorBootFile";
+const string kRamdiskFile = "/tmp/ramdiskFile";
+const char* kFsOptionsArray[] = {"casefold", "projid", "compress"};
+
+class FastbootFuzzer {
+ public:
+ void Process(const uint8_t* data, size_t size);
+
+ private:
+ void InvokeParseApi();
+ void InvokeSocket();
+ void InvokeTcp();
+ void InvokeUdp();
+ void InvokeVendorBootImgUtils(const uint8_t* data, size_t size);
+ bool MakeConnectedSockets(Socket::Protocol protocol, unique_ptr<Socket>* server,
+ unique_ptr<Socket>* client, const string& hostname);
+ unique_ptr<FuzzedDataProvider> fdp_ = nullptr;
+};
+
+void FastbootFuzzer::InvokeParseApi() {
+ boot_img_hdr_v1 hdr = {};
+ FastBootTool fastBoot;
+
+ int32_t year = fdp_->ConsumeIntegralInRange<int32_t>(kYearMin, kYearMax);
+ int32_t month = fdp_->ConsumeIntegralInRange<int32_t>(kMonthMin, kMonthMax);
+ int32_t day = fdp_->ConsumeIntegralInRange<int32_t>(kDayMin, kDayMax);
+ string date = to_string(year) + "-" + to_string(month) + "-" + to_string(day);
+ fastBoot.ParseOsPatchLevel(&hdr, date.c_str());
+
+ int32_t major = fdp_->ConsumeIntegralInRange<int32_t>(kVersionMin, kVersionMax);
+ int32_t minor = fdp_->ConsumeIntegralInRange<int32_t>(kVersionMin, kVersionMax);
+ int32_t patch = fdp_->ConsumeIntegralInRange<int32_t>(kVersionMin, kVersionMax);
+ string version = to_string(major) + "." + to_string(minor) + "." + to_string(patch);
+ fastBoot.ParseOsVersion(&hdr, version.c_str());
+
+ fastBoot.ParseFsOption(fdp_->PickValueInArray(kFsOptionsArray));
+}
+
+bool FastbootFuzzer::MakeConnectedSockets(Socket::Protocol protocol, unique_ptr<Socket>* server,
+ unique_ptr<Socket>* client,
+ const string& hostname = "localhost") {
+ *server = Socket::NewServer(protocol, 0);
+ if (*server == nullptr) {
+ return false;
+ }
+ *client = Socket::NewClient(protocol, hostname, (*server)->GetLocalPort(), nullptr);
+ if (*client == nullptr) {
+ return false;
+ }
+ if (protocol == Socket::Protocol::kTcp) {
+ *server = (*server)->Accept();
+ if (*server == nullptr) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void FastbootFuzzer::InvokeSocket() {
+ unique_ptr<Socket> server, client;
+
+ for (Socket::Protocol protocol : {Socket::Protocol::kUdp, Socket::Protocol::kTcp}) {
+ if (MakeConnectedSockets(protocol, &server, &client)) {
+ string message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+ client->Send(message.c_str(), message.length());
+ string received(message.length(), '\0');
+ if (fdp_->ConsumeBool()) {
+ client->Close();
+ }
+ if (fdp_->ConsumeBool()) {
+ server->Close();
+ }
+ server->ReceiveAll(&received[0], received.length(),
+ /* timeout_ms */
+ fdp_->ConsumeIntegralInRange<size_t>(kMinTimeout, kMaxTimeout));
+ server->Close();
+ client->Close();
+ }
+ }
+}
+
+void FastbootFuzzer::InvokeTcp() {
+ /* Using a raw SocketMockFuzz* here because ownership shall be passed to the Transport object */
+ SocketMockFuzz* tcp_mock = new SocketMockFuzz;
+ tcp_mock->ExpectSend(fdp_->ConsumeBool() ? kValidTcpHandshakeString
+ : kInvalidTcpHandshakeString);
+ tcp_mock->AddReceive(fdp_->ConsumeBool() ? kValidTcpHandshakeString
+ : kInvalidTcpHandshakeString);
+
+ string error;
+ unique_ptr<Transport> transport = tcp::internal::Connect(unique_ptr<Socket>(tcp_mock), &error);
+
+ if (transport.get()) {
+ string write_message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+ if (fdp_->ConsumeBool()) {
+ tcp_mock->ExpectSend(write_message);
+ } else {
+ tcp_mock->ExpectSendFailure(write_message);
+ }
+ string read_message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+ if (fdp_->ConsumeBool()) {
+ tcp_mock->AddReceive(read_message);
+ } else {
+ tcp_mock->AddReceiveFailure();
+ }
+
+ transport->Write(write_message.data(), write_message.length());
+
+ string buffer(read_message.length(), '\0');
+ transport->Read(&buffer[0], buffer.length());
+
+ transport->Close();
+ }
+}
+
+static string PacketValue(uint16_t value) {
+ return string{static_cast<char>(value >> 8), static_cast<char>(value)};
+}
+
+static string ErrorPacket(uint16_t sequence, const string& message = "",
+ char flags = udp::internal::kFlagNone) {
+ return string{udp::internal::kIdError, flags} + PacketValue(sequence) + message;
+}
+
+static string InitPacket(uint16_t sequence, uint16_t version, uint16_t max_packet_size) {
+ return string{udp::internal::kIdInitialization, udp::internal::kFlagNone} +
+ PacketValue(sequence) + PacketValue(version) + PacketValue(max_packet_size);
+}
+
+static string QueryPacket(uint16_t sequence, uint16_t new_sequence) {
+ return string{udp::internal::kIdDeviceQuery, udp::internal::kFlagNone} + PacketValue(sequence) +
+ PacketValue(new_sequence);
+}
+
+static string QueryPacket(uint16_t sequence) {
+ return string{udp::internal::kIdDeviceQuery, udp::internal::kFlagNone} + PacketValue(sequence);
+}
+
+static string FastbootPacket(uint16_t sequence, const string& data = "",
+ char flags = udp::internal::kFlagNone) {
+ return string{udp::internal::kIdFastboot, flags} + PacketValue(sequence) + data;
+}
+
+void FastbootFuzzer::InvokeUdp() {
+ /* Using a raw SocketMockFuzz* here because ownership shall be passed to the Transport object */
+ SocketMockFuzz* udp_mock = new SocketMockFuzz;
+ uint16_t starting_sequence = fdp_->ConsumeIntegral<uint16_t>();
+ int32_t device_max_packet_size = fdp_->ConsumeBool() ? kValidUdpPacketSize
+ : fdp_->ConsumeIntegralInRange<uint16_t>(
+ 0, kValidUdpPacketSize - 1);
+ udp_mock->ExpectSend(QueryPacket(0));
+ udp_mock->AddReceive(QueryPacket(0, starting_sequence));
+ udp_mock->ExpectSend(InitPacket(starting_sequence, udp::internal::kProtocolVersion,
+ udp::internal::kHostMaxPacketSize));
+ udp_mock->AddReceive(
+ InitPacket(starting_sequence, udp::internal::kProtocolVersion, device_max_packet_size));
+
+ string error;
+ unique_ptr<Transport> transport = udp::internal::Connect(unique_ptr<Socket>(udp_mock), &error);
+ bool is_transport_initialized = transport != nullptr && error.empty();
+
+ if (is_transport_initialized) {
+ uint16_t num_packets =
+ fdp_->ConsumeIntegralInRange<uint16_t>(kMinUdpPackets, kMaxUdpPackets);
+
+ for (uint16_t i = 0; i < num_packets; ++i) {
+ string write_message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+ string read_message = fdp_->ConsumeRandomLengthString(kMaxStringSize);
+ if (fdp_->ConsumeBool()) {
+ udp_mock->ExpectSend(FastbootPacket(i, write_message));
+ } else {
+ udp_mock->ExpectSend(ErrorPacket(i, write_message));
+ }
+
+ if (fdp_->ConsumeBool()) {
+ udp_mock->AddReceive(FastbootPacket(i, read_message));
+ } else {
+ udp_mock->AddReceive(ErrorPacket(i, read_message));
+ }
+ transport->Write(write_message.data(), write_message.length());
+ string buffer(read_message.length(), '\0');
+ transport->Read(&buffer[0], buffer.length());
+ }
+ transport->Close();
+ }
+}
+
+void FastbootFuzzer::InvokeVendorBootImgUtils(const uint8_t* data, size_t size) {
+ int32_t vendor_boot_fd = open(kVendorBootFile.c_str(), O_CREAT | O_RDWR, 0644);
+ if (vendor_boot_fd < 0) {
+ return;
+ }
+ int32_t ramdisk_fd = open(kRamdiskFile.c_str(), O_CREAT | O_RDWR, 0644);
+ if (ramdisk_fd < 0) {
+ return;
+ }
+ write(vendor_boot_fd, data, size);
+ write(ramdisk_fd, data, size);
+ string ramdisk_name = fdp_->ConsumeBool() ? kValidRamdiskName
+ : fdp_->ConsumeRandomLengthString(kMaxStringSize);
+ string content_vendor_boot_fd = {};
+ string content_ramdisk_fd = {};
+ lseek(vendor_boot_fd, 0, SEEK_SET);
+ lseek(ramdisk_fd, 0, SEEK_SET);
+ android::base::ReadFdToString(vendor_boot_fd, &content_vendor_boot_fd);
+ android::base::ReadFdToString(ramdisk_fd, &content_ramdisk_fd);
+ uint64_t vendor_boot_size =
+ fdp_->ConsumeBool() ? content_vendor_boot_fd.size() : fdp_->ConsumeIntegral<uint64_t>();
+ uint64_t ramdisk_size =
+ fdp_->ConsumeBool() ? content_ramdisk_fd.size() : fdp_->ConsumeIntegral<uint64_t>();
+ (void)replace_vendor_ramdisk(vendor_boot_fd, vendor_boot_size, ramdisk_name, ramdisk_fd,
+ ramdisk_size);
+ close(vendor_boot_fd);
+ close(ramdisk_fd);
+}
+
+void FastbootFuzzer::Process(const uint8_t* data, size_t size) {
+ fdp_ = make_unique<FuzzedDataProvider>(data, size);
+ InvokeParseApi();
+ InvokeSocket();
+ InvokeTcp();
+ InvokeUdp();
+ InvokeVendorBootImgUtils(data, size);
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ FastbootFuzzer fastbootFuzzer;
+ fastbootFuzzer.Process(data, size);
+ return 0;
+}
diff --git a/fastboot/fuzzer/socket_mock_fuzz.cpp b/fastboot/fuzzer/socket_mock_fuzz.cpp
new file mode 100644
index 0000000..df96eb0
--- /dev/null
+++ b/fastboot/fuzzer/socket_mock_fuzz.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "socket_mock_fuzz.h"
+
+SocketMockFuzz::SocketMockFuzz() : Socket(INVALID_SOCKET) {}
+
+SocketMockFuzz::~SocketMockFuzz() {}
+
+bool SocketMockFuzz::Send(const void* data, size_t length) {
+ if (events_.empty()) {
+ return false;
+ }
+
+ if (events_.front().type != EventType::kSend) {
+ return false;
+ }
+
+ std::string message(reinterpret_cast<const char*>(data), length);
+ if (events_.front().message != message) {
+ return false;
+ }
+
+ bool return_value = events_.front().status;
+ events_.pop();
+ return return_value;
+}
+
+// Mock out multi-buffer send to be one large send, since that's what it should looks like from
+// the user's perspective.
+bool SocketMockFuzz::Send(std::vector<cutils_socket_buffer_t> buffers) {
+ std::string data;
+ for (const auto& buffer : buffers) {
+ data.append(reinterpret_cast<const char*>(buffer.data), buffer.length);
+ }
+ return Send(data.data(), data.size());
+}
+
+ssize_t SocketMockFuzz::Receive(void* data, size_t length, int /*timeout_ms*/) {
+ if (events_.empty()) {
+ return -1;
+ }
+
+ const Event& event = events_.front();
+ if (event.type != EventType::kReceive) {
+ return -1;
+ }
+
+ const std::string& message = event.message;
+ if (message.length() > length) {
+ return -1;
+ }
+
+ receive_timed_out_ = event.status;
+ ssize_t return_value = message.length();
+
+ // Empty message indicates failure.
+ if (message.empty()) {
+ return_value = -1;
+ } else {
+ memcpy(data, message.data(), message.length());
+ }
+
+ events_.pop();
+ return return_value;
+}
+
+int SocketMockFuzz::Close() {
+ return 0;
+}
+
+std::unique_ptr<Socket> SocketMockFuzz::Accept() {
+ if (events_.empty()) {
+ return nullptr;
+ }
+
+ if (events_.front().type != EventType::kAccept) {
+ return nullptr;
+ }
+
+ std::unique_ptr<Socket> sock = std::move(events_.front().sock);
+ events_.pop();
+ return sock;
+}
+
+void SocketMockFuzz::ExpectSend(std::string message) {
+ events_.push(Event(EventType::kSend, std::move(message), true, nullptr));
+}
+
+void SocketMockFuzz::ExpectSendFailure(std::string message) {
+ events_.push(Event(EventType::kSend, std::move(message), false, nullptr));
+}
+
+void SocketMockFuzz::AddReceive(std::string message) {
+ events_.push(Event(EventType::kReceive, std::move(message), false, nullptr));
+}
+
+void SocketMockFuzz::AddReceiveTimeout() {
+ events_.push(Event(EventType::kReceive, "", true, nullptr));
+}
+
+void SocketMockFuzz::AddReceiveFailure() {
+ events_.push(Event(EventType::kReceive, "", false, nullptr));
+}
+
+void SocketMockFuzz::AddAccept(std::unique_ptr<Socket> sock) {
+ events_.push(Event(EventType::kAccept, "", false, std::move(sock)));
+}
+
+SocketMockFuzz::Event::Event(EventType _type, std::string _message, ssize_t _status,
+ std::unique_ptr<Socket> _sock)
+ : type(_type), message(_message), status(_status), sock(std::move(_sock)) {}
diff --git a/fastboot/fuzzer/socket_mock_fuzz.h b/fastboot/fuzzer/socket_mock_fuzz.h
new file mode 100644
index 0000000..67bd0d6
--- /dev/null
+++ b/fastboot/fuzzer/socket_mock_fuzz.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#pragma once
+
+#include <memory>
+#include <queue>
+#include <string>
+
+#include <android-base/macros.h>
+
+#include "socket.h"
+
+class SocketMockFuzz : public Socket {
+ public:
+ SocketMockFuzz();
+ ~SocketMockFuzz() override;
+
+ bool Send(const void* data, size_t length) override;
+ bool Send(std::vector<cutils_socket_buffer_t> buffers) override;
+ ssize_t Receive(void* data, size_t length, int timeout_ms) override;
+ int Close() override;
+ virtual std::unique_ptr<Socket> Accept();
+
+ // Adds an expectation for Send().
+ void ExpectSend(std::string message);
+
+ // Adds an expectation for Send() that returns false.
+ void ExpectSendFailure(std::string message);
+
+ // Adds data to provide for Receive().
+ void AddReceive(std::string message);
+
+ // Adds a Receive() timeout after which ReceiveTimedOut() will return true.
+ void AddReceiveTimeout();
+
+ // Adds a Receive() failure after which ReceiveTimedOut() will return false.
+ void AddReceiveFailure();
+
+ // Adds a Socket to return from Accept().
+ void AddAccept(std::unique_ptr<Socket> sock);
+
+ private:
+ enum class EventType { kSend, kReceive, kAccept };
+
+ struct Event {
+ Event(EventType _type, std::string _message, ssize_t _status,
+ std::unique_ptr<Socket> _sock);
+
+ EventType type;
+ std::string message;
+ bool status; // Return value for Send() or timeout status for Receive().
+ std::unique_ptr<Socket> sock;
+ };
+
+ std::queue<Event> events_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketMockFuzz);
+};
diff --git a/fs_mgr/Android.bp b/fs_mgr/Android.bp
index 7ae526f..5872dda 100644
--- a/fs_mgr/Android.bp
+++ b/fs_mgr/Android.bp
@@ -215,7 +215,6 @@
static_libs: [
"libavb_user",
"libgsid",
- "libutils",
"libvold_binder",
],
shared_libs: [
@@ -230,6 +229,7 @@
"liblog",
"liblp",
"libselinux",
+ "libutils",
],
header_libs: [
"libcutils_headers",
@@ -246,28 +246,12 @@
"-UALLOW_ADBD_DISABLE_VERITY",
"-DALLOW_ADBD_DISABLE_VERITY=1",
],
- },
- },
- required: [
- "clean_scratch_files",
- ],
-}
-
-cc_binary {
- name: "clean_scratch_files",
- defaults: ["fs_mgr_defaults"],
- shared_libs: [
- "libbase",
- "libfs_mgr_binder",
- ],
- srcs: [
- "clean_scratch_files.cpp",
- ],
- product_variables: {
- debuggable: {
init_rc: [
"clean_scratch_files.rc",
],
},
},
+ symlinks: [
+ "clean_scratch_files",
+ ],
}
diff --git a/fs_mgr/OWNERS b/fs_mgr/OWNERS
index cf353a1..c6f9054 100644
--- a/fs_mgr/OWNERS
+++ b/fs_mgr/OWNERS
@@ -1,2 +1,4 @@
+# Bug component: 30545
bowgotsai@google.com
dvander@google.com
+elsk@google.com
diff --git a/fs_mgr/clean_scratch_files.cpp b/fs_mgr/clean_scratch_files.cpp
deleted file mode 100644
index 42fe35a..0000000
--- a/fs_mgr/clean_scratch_files.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <fs_mgr_overlayfs.h>
-
-int main() {
- android::fs_mgr::CleanupOldScratchFiles();
- return 0;
-}
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 5f15aad..07e1e6b 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -2119,6 +2119,9 @@
PERROR << "Cannot open " << loop_device;
return false;
}
+ if (!LoopControl::SetAutoClearStatus(loop_fd.get())) {
+ PERROR << "Failed set LO_FLAGS_AUTOCLEAR for " << loop_device;
+ }
if (!LoopControl::EnableDirectIo(loop_fd.get())) {
return false;
}
@@ -2231,16 +2234,16 @@
return false;
}
-std::string fs_mgr_get_hashtree_algorithm(const android::fs_mgr::FstabEntry& entry) {
+std::optional<HashtreeInfo> fs_mgr_get_hashtree_info(const android::fs_mgr::FstabEntry& entry) {
if (!entry.fs_mgr_flags.verify && !entry.fs_mgr_flags.avb) {
- return "";
+ return {};
}
DeviceMapper& dm = DeviceMapper::Instance();
std::string device = GetVerityDeviceName(entry);
std::vector<DeviceMapper::TargetInfo> table;
if (dm.GetState(device) == DmDeviceState::INVALID || !dm.GetTableInfo(device, &table)) {
- return "";
+ return {};
}
for (const auto& target : table) {
if (strcmp(target.spec.target_type, "verity") != 0) {
@@ -2256,14 +2259,15 @@
std::vector<std::string> tokens = android::base::Split(target.data, " \t\r\n");
if (tokens[0] != "0" && tokens[0] != "1") {
LOG(WARNING) << "Unrecognized device mapper version in " << target.data;
- return "";
+ return {};
}
- // Hashtree algorithm is the 8th token in the output
- return android::base::Trim(tokens[7]);
+ // Hashtree algorithm & root digest are the 8th & 9th token in the output.
+ return HashtreeInfo{.algorithm = android::base::Trim(tokens[7]),
+ .root_digest = android::base::Trim(tokens[8])};
}
- return "";
+ return {};
}
bool fs_mgr_verity_is_check_at_most_once(const android::fs_mgr::FstabEntry& entry) {
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 9b6c3dd..609bd11 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -130,10 +130,12 @@
if (auto equal_sign = flag.find('='); equal_sign != std::string::npos) {
const auto arg = flag.substr(equal_sign + 1);
if (entry->fs_type == "f2fs" && StartsWith(flag, "reserve_root=")) {
- if (!ParseInt(arg, &entry->reserved_size)) {
+ off64_t size_in_4k_blocks;
+ if (!ParseInt(arg, &size_in_4k_blocks, static_cast<off64_t>(0),
+ std::numeric_limits<off64_t>::max() >> 12)) {
LWARNING << "Warning: reserve_root= flag malformed: " << arg;
} else {
- entry->reserved_size <<= 12;
+ entry->reserved_size = size_in_4k_blocks << 12;
}
} else if (StartsWith(flag, "lowerdir=")) {
entry->lowerdir = std::move(arg);
@@ -442,7 +444,81 @@
return "";
}
-bool ReadFstabFile(FILE* fstab_file, bool proc_mounts, Fstab* fstab_out) {
+/* Extracts <device>s from the by-name symlinks specified in a fstab:
+ * /dev/block/<type>/<device>/by-name/<partition>
+ *
+ * <type> can be: platform, pci or vbd.
+ *
+ * For example, given the following entries in the input fstab:
+ * /dev/block/platform/soc/1da4000.ufshc/by-name/system
+ * /dev/block/pci/soc.0/f9824900.sdhci/by-name/vendor
+ * it returns a set { "soc/1da4000.ufshc", "soc.0/f9824900.sdhci" }.
+ */
+std::set<std::string> ExtraBootDevices(const Fstab& fstab) {
+ std::set<std::string> boot_devices;
+
+ for (const auto& entry : fstab) {
+ std::string blk_device = entry.blk_device;
+ // Skips blk_device that doesn't conform to the format.
+ if (!android::base::StartsWith(blk_device, "/dev/block") ||
+ android::base::StartsWith(blk_device, "/dev/block/by-name") ||
+ android::base::StartsWith(blk_device, "/dev/block/bootdevice/by-name")) {
+ continue;
+ }
+ // Skips non-by_name blk_device.
+ // /dev/block/<type>/<device>/by-name/<partition>
+ // ^ slash_by_name
+ auto slash_by_name = blk_device.find("/by-name");
+ if (slash_by_name == std::string::npos) continue;
+ blk_device.erase(slash_by_name); // erases /by-name/<partition>
+
+ // Erases /dev/block/, now we have <type>/<device>
+ blk_device.erase(0, std::string("/dev/block/").size());
+
+ // <type>/<device>
+ // ^ first_slash
+ auto first_slash = blk_device.find('/');
+ if (first_slash == std::string::npos) continue;
+
+ auto boot_device = blk_device.substr(first_slash + 1);
+ if (!boot_device.empty()) boot_devices.insert(std::move(boot_device));
+ }
+
+ return boot_devices;
+}
+
+FstabEntry BuildDsuUserdataFstabEntry() {
+ constexpr uint32_t kFlags = MS_NOATIME | MS_NOSUID | MS_NODEV;
+
+ FstabEntry userdata = {
+ .blk_device = "userdata_gsi",
+ .mount_point = "/data",
+ .fs_type = "ext4",
+ .flags = kFlags,
+ .reserved_size = 128 * 1024 * 1024,
+ };
+ userdata.fs_mgr_flags.wait = true;
+ userdata.fs_mgr_flags.check = true;
+ userdata.fs_mgr_flags.logical = true;
+ userdata.fs_mgr_flags.quota = true;
+ userdata.fs_mgr_flags.late_mount = true;
+ userdata.fs_mgr_flags.formattable = true;
+ return userdata;
+}
+
+bool EraseFstabEntry(Fstab* fstab, const std::string& mount_point) {
+ auto iter = std::remove_if(fstab->begin(), fstab->end(),
+ [&](const auto& entry) { return entry.mount_point == mount_point; });
+ if (iter != fstab->end()) {
+ fstab->erase(iter, fstab->end());
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+bool ReadFstabFromFp(FILE* fstab_file, bool proc_mounts, Fstab* fstab_out) {
ssize_t len;
size_t alloc_len = 0;
char *line = NULL;
@@ -528,80 +604,6 @@
return false;
}
-/* Extracts <device>s from the by-name symlinks specified in a fstab:
- * /dev/block/<type>/<device>/by-name/<partition>
- *
- * <type> can be: platform, pci or vbd.
- *
- * For example, given the following entries in the input fstab:
- * /dev/block/platform/soc/1da4000.ufshc/by-name/system
- * /dev/block/pci/soc.0/f9824900.sdhci/by-name/vendor
- * it returns a set { "soc/1da4000.ufshc", "soc.0/f9824900.sdhci" }.
- */
-std::set<std::string> ExtraBootDevices(const Fstab& fstab) {
- std::set<std::string> boot_devices;
-
- for (const auto& entry : fstab) {
- std::string blk_device = entry.blk_device;
- // Skips blk_device that doesn't conform to the format.
- if (!android::base::StartsWith(blk_device, "/dev/block") ||
- android::base::StartsWith(blk_device, "/dev/block/by-name") ||
- android::base::StartsWith(blk_device, "/dev/block/bootdevice/by-name")) {
- continue;
- }
- // Skips non-by_name blk_device.
- // /dev/block/<type>/<device>/by-name/<partition>
- // ^ slash_by_name
- auto slash_by_name = blk_device.find("/by-name");
- if (slash_by_name == std::string::npos) continue;
- blk_device.erase(slash_by_name); // erases /by-name/<partition>
-
- // Erases /dev/block/, now we have <type>/<device>
- blk_device.erase(0, std::string("/dev/block/").size());
-
- // <type>/<device>
- // ^ first_slash
- auto first_slash = blk_device.find('/');
- if (first_slash == std::string::npos) continue;
-
- auto boot_device = blk_device.substr(first_slash + 1);
- if (!boot_device.empty()) boot_devices.insert(std::move(boot_device));
- }
-
- return boot_devices;
-}
-
-FstabEntry BuildDsuUserdataFstabEntry() {
- constexpr uint32_t kFlags = MS_NOATIME | MS_NOSUID | MS_NODEV;
-
- FstabEntry userdata = {
- .blk_device = "userdata_gsi",
- .mount_point = "/data",
- .fs_type = "ext4",
- .flags = kFlags,
- .reserved_size = 128 * 1024 * 1024,
- };
- userdata.fs_mgr_flags.wait = true;
- userdata.fs_mgr_flags.check = true;
- userdata.fs_mgr_flags.logical = true;
- userdata.fs_mgr_flags.quota = true;
- userdata.fs_mgr_flags.late_mount = true;
- userdata.fs_mgr_flags.formattable = true;
- return userdata;
-}
-
-bool EraseFstabEntry(Fstab* fstab, const std::string& mount_point) {
- auto iter = std::remove_if(fstab->begin(), fstab->end(),
- [&](const auto& entry) { return entry.mount_point == mount_point; });
- if (iter != fstab->end()) {
- fstab->erase(iter, fstab->end());
- return true;
- }
- return false;
-}
-
-} // namespace
-
void TransformFstabForDsu(Fstab* fstab, const std::string& dsu_slot,
const std::vector<std::string>& dsu_partitions) {
static constexpr char kDsuKeysDir[] = "/avb";
@@ -707,7 +709,7 @@
bool is_proc_mounts = path == "/proc/mounts";
Fstab fstab;
- if (!ReadFstabFile(fstab_file.get(), is_proc_mounts, &fstab)) {
+ if (!ReadFstabFromFp(fstab_file.get(), is_proc_mounts, &fstab)) {
LERROR << __FUNCTION__ << "(): failed to load fstab from : '" << path << "'";
return false;
}
@@ -762,7 +764,7 @@
return false;
}
- if (!ReadFstabFile(fstab_file.get(), false, fstab)) {
+ if (!ReadFstabFromFp(fstab_file.get(), false, fstab)) {
if (verbose) {
LERROR << __FUNCTION__ << "(): failed to load fstab from kernel:" << std::endl
<< fstab_buf;
diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index 5411aca..deaf5f7 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -42,6 +42,8 @@
#include <libavb_user/libavb_user.h>
#include <libgsi/libgsid.h>
+using namespace std::literals;
+
namespace {
[[noreturn]] void usage(int exit_status) {
@@ -142,6 +144,7 @@
BINDER_ERROR,
CHECKPOINTING,
GSID_ERROR,
+ CLEAN_SCRATCH_FILES,
};
static int do_remount(int argc, char* argv[]) {
@@ -163,6 +166,7 @@
{"help", no_argument, nullptr, 'h'},
{"reboot", no_argument, nullptr, 'R'},
{"verbose", no_argument, nullptr, 'v'},
+ {"clean_scratch_files", no_argument, nullptr, 'C'},
{0, 0, nullptr, 0},
};
for (int opt; (opt = ::getopt_long(argc, argv, "hRT:v", longopts, nullptr)) != -1;) {
@@ -183,6 +187,8 @@
case 'v':
verbose = true;
break;
+ case 'C':
+ return CLEAN_SCRATCH_FILES;
default:
LOG(ERROR) << "Bad Argument -" << char(opt);
usage(BADARG);
@@ -476,13 +482,24 @@
return retval;
}
+static int do_clean_scratch_files() {
+ android::fs_mgr::CleanupOldScratchFiles();
+ return 0;
+}
+
int main(int argc, char* argv[]) {
android::base::InitLogging(argv, MyLogger);
+ if (argc > 0 && android::base::Basename(argv[0]) == "clean_scratch_files"s) {
+ return do_clean_scratch_files();
+ }
int result = do_remount(argc, argv);
if (result == MUST_REBOOT) {
LOG(INFO) << "Now reboot your device for settings to take effect";
+ result = 0;
} else if (result == REMOUNT_SUCCESS) {
printf("remount succeeded\n");
+ } else if (result == CLEAN_SCRATCH_FILES) {
+ return do_clean_scratch_files();
} else {
printf("remount failed\n");
}
diff --git a/fs_mgr/fuzz/Android.bp b/fs_mgr/fuzz/Android.bp
new file mode 100644
index 0000000..f0afd28
--- /dev/null
+++ b/fs_mgr/fuzz/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_fuzz {
+ name: "libfstab_fuzzer",
+ srcs: [
+ "fs_mgr_fstab_fuzzer.cpp",
+ ],
+ static_libs: [
+ "libfstab",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+
+ dictionary: "fstab.dict",
+ fuzz_config: {
+ cc: [
+ "yochiang@google.com",
+ ],
+ },
+}
diff --git a/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp b/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp
new file mode 100644
index 0000000..1fddbf8
--- /dev/null
+++ b/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <cstdio>
+
+#include <fstab/fstab.h>
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ std::unique_ptr<FILE, decltype(&fclose)> fstab_file(
+ fmemopen(static_cast<void*>(const_cast<uint8_t*>(data)), size, "r"), fclose);
+ if (fstab_file == nullptr) {
+ return 0;
+ }
+ android::fs_mgr::Fstab fstab;
+ android::fs_mgr::ReadFstabFromFp(fstab_file.get(), /* proc_mounts= */ false, &fstab);
+ return 0;
+}
diff --git a/fs_mgr/fuzz/fstab.dict b/fs_mgr/fuzz/fstab.dict
new file mode 100644
index 0000000..84dddf7
--- /dev/null
+++ b/fs_mgr/fuzz/fstab.dict
@@ -0,0 +1,70 @@
+"#"
+"="
+","
+"f2fs"
+
+# mount flags
+"noatime"
+"noexec"
+"nosuid"
+"nodev"
+"nodiratime"
+"ro"
+"rw"
+"sync"
+"remount"
+"bind"
+"rec"
+"unbindable"
+"private"
+"slave"
+"shared"
+"defaults"
+
+# fs_mgr flags
+"wait"
+"check"
+"nonremovable"
+"recoveryonly"
+"noemulatedsd"
+"notrim"
+"verify"
+"formattable"
+"slotselect"
+"latemount"
+"nofail"
+"verifyatboot"
+"quota"
+"avb"
+"logical"
+"checkpoint=block"
+"checkpoint=fs"
+"first_stage_mount"
+"slotselect_other"
+"fsverity"
+"metadata_csum"
+"fscompress"
+"overlayfs_remove_missing_lowerdir"
+
+# fs_mgr flags that expect an argument
+"reserve_root="
+"lowerdir="
+"encryptable="
+"voldmanaged="
+"length="
+"swapprio="
+"zramsize="
+"forceencrypt="
+"fileencryption="
+"forcefdeorfbe="
+"max_comp_streams="
+"reservedsize="
+"readahead_size_kb="
+"eraseblk="
+"logicalblk="
+"avb_keys="
+"avb="
+"keydirectory="
+"metadata_encryption="
+"sysfs_path="
+"zram_backingdev_size="
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 4d3ecc9..21c9989 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -22,6 +22,7 @@
#include <linux/dm-ioctl.h>
#include <functional>
+#include <optional>
#include <string>
#include <fstab/fstab.h>
@@ -68,6 +69,13 @@
bool userdata_mounted;
};
+struct HashtreeInfo {
+ // The hash algorithm used to build the merkle tree.
+ std::string algorithm;
+ // The root digest of the merkle tree.
+ std::string root_digest;
+};
+
// 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
@@ -88,9 +96,9 @@
bool fs_mgr_load_verity_state(int* mode);
// Returns true if verity is enabled on this particular FstabEntry.
bool fs_mgr_is_verity_enabled(const android::fs_mgr::FstabEntry& entry);
-// Returns the hash algorithm used to build the hashtree of this particular FstabEntry. Returns an
-// empty string if the input isn't a dm-verity entry, or if there is an error.
-std::string fs_mgr_get_hashtree_algorithm(const android::fs_mgr::FstabEntry& entry);
+// Returns the verity hashtree information of this particular FstabEntry. Returns std::nullopt
+// if the input isn't a dm-verity entry, or if there is an error.
+std::optional<HashtreeInfo> fs_mgr_get_hashtree_info(const android::fs_mgr::FstabEntry& entry);
bool fs_mgr_swapon_all(const android::fs_mgr::Fstab& fstab);
bool fs_mgr_update_logical_partition(android::fs_mgr::FstabEntry* entry);
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index 9a4ed46..d0f32a3 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -99,6 +99,9 @@
// Unless explicitly requested, a lookup on mount point should always return the 1st one.
using Fstab = std::vector<FstabEntry>;
+// Exported for testability. Regular users should use ReadFstabFromfile().
+bool ReadFstabFromFp(FILE* fstab_file, bool proc_mounts, Fstab* fstab_out);
+
bool ReadFstabFromFile(const std::string& path, Fstab* fstab);
bool ReadFstabFromDt(Fstab* fstab, bool verbose = true);
bool ReadDefaultFstab(Fstab* fstab);
diff --git a/fs_mgr/libdm/Android.bp b/fs_mgr/libdm/Android.bp
index 428a7f4..2bb9035 100644
--- a/fs_mgr/libdm/Android.bp
+++ b/fs_mgr/libdm/Android.bp
@@ -86,7 +86,9 @@
name: "vts_libdm_test",
defaults: ["libdm_test_defaults"],
test_suites: ["vts"],
- test_min_api_level: 29,
+ test_options: {
+ min_shipping_api_level: 29,
+ },
}
cc_fuzz {
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 7e01b85..332fcf5 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -55,7 +55,33 @@
// that prefix.
std::optional<std::string> ExtractBlockDeviceName(const std::string& path);
-class DeviceMapper final {
+// This interface is for testing purposes. See DeviceMapper proper for what these methods do.
+class IDeviceMapper {
+ public:
+ virtual ~IDeviceMapper() {}
+
+ struct TargetInfo {
+ struct dm_target_spec spec;
+ std::string data;
+ TargetInfo() {}
+ TargetInfo(const struct dm_target_spec& spec, const std::string& data)
+ : spec(spec), data(data) {}
+
+ bool IsOverflowSnapshot() const;
+ };
+
+ virtual bool CreateDevice(const std::string& name, const DmTable& table, std::string* path,
+ const std::chrono::milliseconds& timeout_ms) = 0;
+ virtual DmDeviceState GetState(const std::string& name) const = 0;
+ virtual bool LoadTableAndActivate(const std::string& name, const DmTable& table) = 0;
+ virtual bool GetTableInfo(const std::string& name, std::vector<TargetInfo>* table) = 0;
+ virtual bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) = 0;
+ virtual bool GetDmDevicePathByName(const std::string& name, std::string* path) = 0;
+ virtual bool GetDeviceString(const std::string& name, std::string* dev) = 0;
+ virtual bool DeleteDeviceIfExists(const std::string& name) = 0;
+};
+
+class DeviceMapper final : public IDeviceMapper {
public:
class DmBlockDevice final {
public:
@@ -95,7 +121,7 @@
// Removes a device mapper device with the given name.
// Returns 'true' on success, false otherwise.
bool DeleteDevice(const std::string& name);
- bool DeleteDeviceIfExists(const std::string& name);
+ bool DeleteDeviceIfExists(const std::string& name) override;
// Removes a device mapper device with the given name and waits for |timeout_ms| milliseconds
// for the corresponding block device to be deleted.
bool DeleteDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms);
@@ -114,7 +140,7 @@
// Returns the current state of the underlying device mapper device
// with given name.
// One of INVALID, SUSPENDED or ACTIVE.
- DmDeviceState GetState(const std::string& name) const;
+ DmDeviceState GetState(const std::string& name) const override;
// Puts the given device to the specified status, which must be either:
// - SUSPENDED: suspend the device, or
@@ -158,7 +184,7 @@
// not |path| is available. It is the caller's responsibility to ensure
// there are no races.
bool CreateDevice(const std::string& name, const DmTable& table, std::string* path,
- const std::chrono::milliseconds& timeout_ms);
+ const std::chrono::milliseconds& timeout_ms) override;
// Create a device and activate the given table, without waiting to acquire
// a valid path. If the caller will use GetDmDevicePathByName(), it should
@@ -170,7 +196,7 @@
// process. A device with the given name must already exist.
//
// Returns 'true' on success, false otherwise.
- bool LoadTableAndActivate(const std::string& name, const DmTable& table);
+ bool LoadTableAndActivate(const std::string& name, const DmTable& table) override;
// Returns true if a list of available device mapper targets registered in the kernel was
// successfully read and stored in 'targets'. Returns 'false' otherwise.
@@ -216,7 +242,7 @@
// Returns a major:minor string for the named device-mapper node, that can
// be used as inputs to DmTargets that take a block device.
- bool GetDeviceString(const std::string& name, std::string* dev);
+ bool GetDeviceString(const std::string& name, std::string* dev) override;
// The only way to create a DeviceMapper object.
static DeviceMapper& Instance();
@@ -231,20 +257,11 @@
// contain one TargetInfo for each target in the table. If the device does
// not exist, or there were too many targets, the call will fail and return
// false.
- struct TargetInfo {
- struct dm_target_spec spec;
- std::string data;
- TargetInfo() {}
- TargetInfo(const struct dm_target_spec& spec, const std::string& data)
- : spec(spec), data(data) {}
-
- bool IsOverflowSnapshot() const;
- };
- bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table);
+ bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) override;
// Identical to GetTableStatus, except also retrives the active table for the device
// mapper device from the kernel.
- bool GetTableInfo(const std::string& name, std::vector<TargetInfo>* table);
+ bool GetTableInfo(const std::string& name, std::vector<TargetInfo>* table) override;
static std::string GetTargetType(const struct dm_target_spec& spec);
diff --git a/fs_mgr/libdm/include/libdm/loop_control.h b/fs_mgr/libdm/include/libdm/loop_control.h
index ad53c11..f519054 100644
--- a/fs_mgr/libdm/include/libdm/loop_control.h
+++ b/fs_mgr/libdm/include/libdm/loop_control.h
@@ -46,6 +46,9 @@
// Enable Direct I/O on a loop device. This requires kernel 4.9+.
static bool EnableDirectIo(int fd);
+ // Set LO_FLAGS_AUTOCLEAR on a loop device.
+ static bool SetAutoClearStatus(int fd);
+
LoopControl(const LoopControl&) = delete;
LoopControl& operator=(const LoopControl&) = delete;
LoopControl& operator=(LoopControl&&) = default;
diff --git a/fs_mgr/libdm/loop_control.cpp b/fs_mgr/libdm/loop_control.cpp
index 2e40a18..32d5f38 100644
--- a/fs_mgr/libdm/loop_control.cpp
+++ b/fs_mgr/libdm/loop_control.cpp
@@ -133,6 +133,16 @@
return true;
}
+bool LoopControl::SetAutoClearStatus(int fd) {
+ struct loop_info64 info = {};
+
+ info.lo_flags |= LO_FLAGS_AUTOCLEAR;
+ if (ioctl(fd, LOOP_SET_STATUS64, &info)) {
+ return false;
+ }
+ return true;
+}
+
LoopDevice::LoopDevice(android::base::borrowed_fd fd, const std::chrono::milliseconds& timeout_ms,
bool auto_close)
: fd_(fd), owned_fd_(-1) {
diff --git a/fs_mgr/libfiemap/Android.bp b/fs_mgr/libfiemap/Android.bp
index d16b8d6..5deba65 100644
--- a/fs_mgr/libfiemap/Android.bp
+++ b/fs_mgr/libfiemap/Android.bp
@@ -90,7 +90,9 @@
test_suites: ["vts", "device-tests"],
auto_gen_config: true,
- test_min_api_level: 29,
+ test_options: {
+ min_shipping_api_level: 29,
+ },
require_root: true,
}
diff --git a/fs_mgr/libfiemap/fiemap_writer.cpp b/fs_mgr/libfiemap/fiemap_writer.cpp
index 8acb885..275388e 100644
--- a/fs_mgr/libfiemap/fiemap_writer.cpp
+++ b/fs_mgr/libfiemap/fiemap_writer.cpp
@@ -498,24 +498,6 @@
return IsFilePinned(fd, file_path, sfs.f_type);
}
-static bool CountFiemapExtents(int file_fd, const std::string& file_path, uint32_t* num_extents) {
- struct fiemap fiemap = {};
- fiemap.fm_start = 0;
- fiemap.fm_length = UINT64_MAX;
- fiemap.fm_flags = FIEMAP_FLAG_SYNC;
- fiemap.fm_extent_count = 0;
-
- if (ioctl(file_fd, FS_IOC_FIEMAP, &fiemap)) {
- PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
- return false;
- }
-
- if (num_extents) {
- *num_extents = fiemap.fm_mapped_extents;
- }
- return true;
-}
-
static bool IsValidExtent(const fiemap_extent* extent, std::string_view file_path) {
if (extent->fe_flags & kUnsupportedExtentFlags) {
LOG(ERROR) << "Extent at location " << extent->fe_logical << " of file " << file_path
@@ -530,12 +512,16 @@
}
static bool FiemapToExtents(struct fiemap* fiemap, std::vector<struct fiemap_extent>* extents,
- uint32_t num_extents, std::string_view file_path) {
- if (num_extents == 0) return false;
-
+ std::string_view file_path) {
+ uint32_t num_extents = fiemap->fm_mapped_extents;
+ if (num_extents == 0) {
+ LOG(ERROR) << "File " << file_path << " has zero extent";
+ return false;
+ }
const struct fiemap_extent* last_extent = &fiemap->fm_extents[num_extents - 1];
if (!IsLastExtent(last_extent)) {
- LOG(ERROR) << "FIEMAP did not return a final extent for file: " << file_path;
+ LOG(ERROR) << "FIEMAP did not return a final extent for file: " << file_path
+ << " num_extents=" << num_extents << " max_extents=" << kMaxExtents;
return false;
}
@@ -577,21 +563,7 @@
static bool ReadFiemap(int file_fd, const std::string& file_path,
std::vector<struct fiemap_extent>* extents) {
- uint32_t num_extents;
- if (!CountFiemapExtents(file_fd, file_path, &num_extents)) {
- return false;
- }
- if (num_extents == 0) {
- LOG(ERROR) << "File " << file_path << " has zero extents";
- return false;
- }
- if (num_extents > kMaxExtents) {
- LOG(ERROR) << "File has " << num_extents << ", maximum is " << kMaxExtents << ": "
- << file_path;
- return false;
- }
-
- uint64_t fiemap_size = sizeof(struct fiemap) + num_extents * sizeof(struct fiemap_extent);
+ uint64_t fiemap_size = sizeof(struct fiemap) + kMaxExtents * sizeof(struct fiemap_extent);
auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, fiemap_size), free);
if (buffer == nullptr) {
LOG(ERROR) << "Failed to allocate memory for fiemap";
@@ -603,19 +575,13 @@
fiemap->fm_length = UINT64_MAX;
// make sure file is synced to disk before we read the fiemap
fiemap->fm_flags = FIEMAP_FLAG_SYNC;
- fiemap->fm_extent_count = num_extents;
+ fiemap->fm_extent_count = kMaxExtents;
if (ioctl(file_fd, FS_IOC_FIEMAP, fiemap)) {
PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
return false;
}
- if (fiemap->fm_mapped_extents != num_extents) {
- LOG(ERROR) << "FIEMAP returned unexpected extent count (" << num_extents
- << " expected, got " << fiemap->fm_mapped_extents << ") for file: " << file_path;
- return false;
- }
-
- return FiemapToExtents(fiemap, extents, num_extents, file_path);
+ return FiemapToExtents(fiemap, extents, file_path);
}
static bool ReadFibmap(int file_fd, const std::string& file_path,
diff --git a/fs_mgr/libfiemap/fiemap_writer_test.cpp b/fs_mgr/libfiemap/fiemap_writer_test.cpp
index b31c78d..c65481b 100644
--- a/fs_mgr/libfiemap/fiemap_writer_test.cpp
+++ b/fs_mgr/libfiemap/fiemap_writer_test.cpp
@@ -258,6 +258,13 @@
EXPECT_EQ(memcmp(actual.data(), data.data(), data.size()), 0);
}
+TEST_F(FiemapWriterTest, CheckEmptyFile) {
+ // Can't get any fiemap_extent out of a zero-sized file.
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 0);
+ EXPECT_EQ(fptr, nullptr);
+ EXPECT_EQ(access(testfile.c_str(), F_OK), -1);
+}
+
TEST_F(SplitFiemapTest, Create) {
auto ptr = SplitFiemap::Create(testfile, 1024 * 768, 1024 * 32);
ASSERT_NE(ptr, nullptr);
diff --git a/fs_mgr/libfiemap/image_manager.cpp b/fs_mgr/libfiemap/image_manager.cpp
index dcbbc54..2c14c8a 100644
--- a/fs_mgr/libfiemap/image_manager.cpp
+++ b/fs_mgr/libfiemap/image_manager.cpp
@@ -696,7 +696,12 @@
bool ok = true;
for (const auto& partition : metadata->partitions) {
if (partition.attributes & LP_PARTITION_ATTR_DISABLED) {
- ok &= DeleteBackingImage(GetPartitionName(partition));
+ const auto name = GetPartitionName(partition);
+ if (!DeleteBackingImage(name)) {
+ ok = false;
+ } else {
+ LOG(INFO) << "Removed disabled partition image: " << name;
+ }
}
}
return ok;
diff --git a/fs_mgr/libfs_avb/Android.bp b/fs_mgr/libfs_avb/Android.bp
index 0cbd9db..a0ad208 100644
--- a/fs_mgr/libfs_avb/Android.bp
+++ b/fs_mgr/libfs_avb/Android.bp
@@ -152,6 +152,7 @@
static_libs: [
"libavb",
"libdm",
+ "libext2_uuid",
"libfs_avb",
"libfstab",
],
diff --git a/fs_mgr/libfs_avb/tests/avb_util_test.cpp b/fs_mgr/libfs_avb/tests/avb_util_test.cpp
index 0288d85..6f874a6 100644
--- a/fs_mgr/libfs_avb/tests/avb_util_test.cpp
+++ b/fs_mgr/libfs_avb/tests/avb_util_test.cpp
@@ -779,7 +779,7 @@
nullptr /* out_public_key_data */, &verify_result);
ASSERT_EQ(0, close(hash_modified_fd.release()));
EXPECT_NE(nullptr, vbmeta);
- EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta));
+ // EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); // b/187303962.
EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, verify_result);
// Modifies the auxiliary data block.
@@ -795,7 +795,7 @@
nullptr /* out_public_key_data */, &verify_result);
ASSERT_EQ(0, close(aux_modified_fd.release()));
EXPECT_NE(nullptr, vbmeta);
- EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta));
+ // EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); // b/187303962.
EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, verify_result);
// Resets previous modification by setting offset to -1, and checks the verification can pass.
@@ -807,7 +807,7 @@
nullptr /* out_public_key_data */, &verify_result);
ASSERT_EQ(0, close(ok_fd.release()));
EXPECT_NE(nullptr, vbmeta);
- EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta));
+ // EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); // b/187303962.
EXPECT_EQ(VBMetaVerifyResult::kSuccess, verify_result);
}
diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp
index fc2d8a1..4b81c2c 100644
--- a/fs_mgr/liblp/Android.bp
+++ b/fs_mgr/liblp/Android.bp
@@ -105,7 +105,9 @@
defaults: ["liblp_test_defaults"],
test_suites: ["vts"],
auto_gen_config: true,
- test_min_api_level: 29,
+ test_options: {
+ min_shipping_api_level: 29,
+ },
require_root: true,
}
diff --git a/fs_mgr/liblp/OWNERS b/fs_mgr/liblp/OWNERS
new file mode 100644
index 0000000..6a95eb2
--- /dev/null
+++ b/fs_mgr/liblp/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 391836
+dvander@google.com
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index df00f45..5ab2ce2 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -251,7 +251,9 @@
"vts",
"device-tests"
],
- test_min_api_level: 29,
+ test_options: {
+ min_shipping_api_level: 29,
+ },
auto_gen_config: true,
require_root: true,
}
@@ -407,7 +409,9 @@
test_suites: [
"device-tests"
],
- test_min_api_level: 30,
+ test_options: {
+ min_shipping_api_level: 30,
+ },
auto_gen_config: true,
require_root: false,
host_supported: true,
diff --git a/fs_mgr/libsnapshot/OWNERS b/fs_mgr/libsnapshot/OWNERS
index 801c446..37319fe 100644
--- a/fs_mgr/libsnapshot/OWNERS
+++ b/fs_mgr/libsnapshot/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 30545
balsini@google.com
dvander@google.com
elsk@google.com
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index 6066309..ba4044f 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -1171,18 +1171,18 @@
ASSERT_TRUE(writer.Initialize(cow_->fd));
ASSERT_TRUE(writer.AddSequenceData(6, sequence));
- ASSERT_TRUE(writer.AddCopy(6, 3));
+ ASSERT_TRUE(writer.AddCopy(6, 13));
ASSERT_TRUE(writer.AddZeroBlocks(12, 1));
ASSERT_TRUE(writer.AddZeroBlocks(8, 1));
ASSERT_TRUE(writer.AddZeroBlocks(11, 1));
- ASSERT_TRUE(writer.AddCopy(3, 5));
- ASSERT_TRUE(writer.AddCopy(2, 1));
+ ASSERT_TRUE(writer.AddCopy(3, 15));
+ ASSERT_TRUE(writer.AddCopy(2, 11));
ASSERT_TRUE(writer.AddZeroBlocks(4, 1));
ASSERT_TRUE(writer.AddZeroBlocks(9, 1));
- ASSERT_TRUE(writer.AddCopy(5, 6));
+ ASSERT_TRUE(writer.AddCopy(5, 16));
ASSERT_TRUE(writer.AddZeroBlocks(1, 1));
- ASSERT_TRUE(writer.AddCopy(10, 2));
- ASSERT_TRUE(writer.AddCopy(7, 4));
+ ASSERT_TRUE(writer.AddCopy(10, 12));
+ ASSERT_TRUE(writer.AddCopy(7, 14));
ASSERT_TRUE(writer.Finalize());
// New block in cow order is 6, 12, 8, 11, 3, 2, 4, 9, 5, 1, 10, 7
@@ -1219,12 +1219,12 @@
ASSERT_TRUE(writer.Initialize(cow_->fd));
- ASSERT_TRUE(writer.AddCopy(2, 1));
- ASSERT_TRUE(writer.AddCopy(10, 2));
- ASSERT_TRUE(writer.AddCopy(6, 3));
- ASSERT_TRUE(writer.AddCopy(7, 4));
- ASSERT_TRUE(writer.AddCopy(3, 5));
- ASSERT_TRUE(writer.AddCopy(5, 6));
+ ASSERT_TRUE(writer.AddCopy(2, 11));
+ ASSERT_TRUE(writer.AddCopy(10, 12));
+ ASSERT_TRUE(writer.AddCopy(6, 13));
+ ASSERT_TRUE(writer.AddCopy(7, 14));
+ ASSERT_TRUE(writer.AddCopy(3, 15));
+ ASSERT_TRUE(writer.AddCopy(5, 16));
ASSERT_TRUE(writer.AddZeroBlocks(12, 1));
ASSERT_TRUE(writer.AddZeroBlocks(8, 1));
ASSERT_TRUE(writer.AddZeroBlocks(11, 1));
@@ -1260,6 +1260,39 @@
ASSERT_TRUE(iter->Done());
}
+TEST_F(CowTest, InvalidMergeOrderTest) {
+ CowOptions options;
+ options.cluster_ops = 5;
+ options.num_merge_ops = 1;
+ std::string data = "This is some data, believe it";
+ data.resize(options.block_size, '\0');
+ auto writer = std::make_unique<CowWriter>(options);
+ CowReader reader;
+
+ ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+ ASSERT_TRUE(writer->AddCopy(3, 2));
+ ASSERT_TRUE(writer->AddCopy(2, 1));
+ ASSERT_TRUE(writer->AddLabel(1));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+ ASSERT_TRUE(reader.VerifyMergeOps());
+
+ ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+ ASSERT_TRUE(writer->AddCopy(4, 2));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+ ASSERT_FALSE(reader.VerifyMergeOps());
+
+ writer = std::make_unique<CowWriter>(options);
+ ASSERT_TRUE(writer->Initialize(cow_->fd));
+ ASSERT_TRUE(writer->AddCopy(2, 1));
+ ASSERT_TRUE(writer->AddXorBlocks(3, &data, data.size(), 1, 1));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+ ASSERT_FALSE(reader.VerifyMergeOps());
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/cow_format.cpp b/fs_mgr/libsnapshot/cow_format.cpp
index 8e6bec7..94c4109 100644
--- a/fs_mgr/libsnapshot/cow_format.cpp
+++ b/fs_mgr/libsnapshot/cow_format.cpp
@@ -56,7 +56,10 @@
os << (int)op.compression << "?, ";
os << "data_length:" << op.data_length << ",\t";
os << "new_block:" << op.new_block << ",\t";
- os << "source:" << op.source << ")";
+ os << "source:" << op.source;
+ if (op.type == kCowXorOp)
+ os << " (block:" << op.source / BLOCK_SZ << " offset:" << op.source % BLOCK_SZ << ")";
+ os << ")";
return os;
}
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 773d978..20030b9 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -34,11 +34,12 @@
namespace android {
namespace snapshot {
-CowReader::CowReader()
+CowReader::CowReader(ReaderFlags reader_flag)
: fd_(-1),
header_(),
fd_size_(0),
- merge_op_blocks_(std::make_shared<std::vector<uint32_t>>()) {}
+ merge_op_blocks_(std::make_shared<std::vector<uint32_t>>()),
+ reader_flag_(reader_flag) {}
static void SHA256(const void*, size_t, uint8_t[]) {
#if 0
@@ -58,6 +59,7 @@
cow->last_label_ = last_label_;
cow->ops_ = ops_;
cow->merge_op_blocks_ = merge_op_blocks_;
+ cow->merge_op_start_ = merge_op_start_;
cow->block_map_ = block_map_;
cow->num_total_data_ops_ = num_total_data_ops_;
cow->num_ordered_ops_to_merge_ = num_ordered_ops_to_merge_;
@@ -230,7 +232,7 @@
memcpy(&footer_->op, ¤t_op, sizeof(footer->op));
off_t offs = lseek(fd_.get(), pos, SEEK_SET);
if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
- PLOG(ERROR) << "lseek next op failed";
+ PLOG(ERROR) << "lseek next op failed " << offs;
return false;
}
if (!android::base::ReadFully(fd_, &footer->data, sizeof(footer->data))) {
@@ -250,7 +252,7 @@
// Position for next cluster read
off_t offs = lseek(fd_.get(), pos, SEEK_SET);
if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
- PLOG(ERROR) << "lseek next op failed";
+ PLOG(ERROR) << "lseek next op failed " << offs;
return false;
}
ops_buffer->resize(current_op_num);
@@ -414,7 +416,7 @@
//==============================================================
bool CowReader::PrepMergeOps() {
auto merge_op_blocks = std::make_shared<std::vector<uint32_t>>();
- std::set<int, std::greater<int>> other_ops;
+ std::vector<int> other_ops;
auto seq_ops_set = std::unordered_set<uint32_t>();
auto block_map = std::make_shared<std::unordered_map<uint32_t, int>>();
size_t num_seqs = 0;
@@ -445,7 +447,7 @@
if (!has_seq_ops_ && IsOrderedOp(current_op)) {
merge_op_blocks->emplace_back(current_op.new_block);
} else if (seq_ops_set.count(current_op.new_block) == 0) {
- other_ops.insert(current_op.new_block);
+ other_ops.push_back(current_op.new_block);
}
block_map->insert({current_op.new_block, i});
}
@@ -461,6 +463,18 @@
} else {
num_ordered_ops_to_merge_ = 0;
}
+
+ // Sort the vector in increasing order if merging in user-space as
+ // we can batch merge them when iterating from forward.
+ //
+ // dm-snapshot-merge requires decreasing order as we iterate the blocks
+ // in reverse order.
+ if (reader_flag_ == ReaderFlags::USERSPACE_MERGE) {
+ std::sort(other_ops.begin(), other_ops.end());
+ } else {
+ std::sort(other_ops.begin(), other_ops.end(), std::greater<int>());
+ }
+
merge_op_blocks->reserve(merge_op_blocks->size() + other_ops.size());
for (auto block : other_ops) {
merge_op_blocks->emplace_back(block);
@@ -468,8 +482,7 @@
num_total_data_ops_ = merge_op_blocks->size();
if (header_.num_merge_ops > 0) {
- merge_op_blocks->erase(merge_op_blocks->begin(),
- merge_op_blocks->begin() + header_.num_merge_ops);
+ merge_op_start_ = header_.num_merge_ops;
}
block_map_ = block_map;
@@ -477,6 +490,44 @@
return true;
}
+bool CowReader::VerifyMergeOps() {
+ auto itr = GetMergeOpIter(true);
+ std::unordered_map<uint64_t, CowOperation> overwritten_blocks;
+ while (!itr->Done()) {
+ CowOperation op = itr->Get();
+ uint64_t block;
+ bool offset;
+ if (op.type == kCowCopyOp) {
+ block = op.source;
+ offset = false;
+ } else if (op.type == kCowXorOp) {
+ block = op.source / BLOCK_SZ;
+ offset = (op.source % BLOCK_SZ) != 0;
+ } else {
+ itr->Next();
+ continue;
+ }
+
+ CowOperation* overwrite = nullptr;
+ if (overwritten_blocks.count(block)) {
+ overwrite = &overwritten_blocks[block];
+ LOG(ERROR) << "Invalid Sequence! Block needed for op:\n"
+ << op << "\noverwritten by previously merged op:\n"
+ << *overwrite;
+ }
+ if (offset && overwritten_blocks.count(block + 1)) {
+ overwrite = &overwritten_blocks[block + 1];
+ LOG(ERROR) << "Invalid Sequence! Block needed for op:\n"
+ << op << "\noverwritten by previously merged op:\n"
+ << *overwrite;
+ }
+ if (overwrite != nullptr) return false;
+ overwritten_blocks[op.new_block] = op;
+ itr->Next();
+ }
+ return true;
+}
+
bool CowReader::GetHeader(CowHeader* header) {
*header = header_;
return true;
@@ -530,7 +581,8 @@
public:
explicit CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
- std::shared_ptr<std::unordered_map<uint32_t, int>> map);
+ std::shared_ptr<std::unordered_map<uint32_t, int>> map,
+ uint64_t start);
bool Done() override;
const CowOperation& Get() override;
@@ -541,20 +593,67 @@
std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
std::shared_ptr<std::unordered_map<uint32_t, int>> map_;
std::vector<uint32_t>::reverse_iterator block_riter_;
+ uint64_t start_;
};
-CowRevMergeOpIter::CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
- std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
- std::shared_ptr<std::unordered_map<uint32_t, int>> map) {
+class CowMergeOpIter final : public ICowOpIter {
+ public:
+ explicit CowMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
+ std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
+ std::shared_ptr<std::unordered_map<uint32_t, int>> map, uint64_t start);
+
+ bool Done() override;
+ const CowOperation& Get() override;
+ void Next() override;
+
+ private:
+ std::shared_ptr<std::vector<CowOperation>> ops_;
+ std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
+ std::shared_ptr<std::unordered_map<uint32_t, int>> map_;
+ std::vector<uint32_t>::iterator block_iter_;
+ uint64_t start_;
+};
+
+CowMergeOpIter::CowMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
+ std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
+ std::shared_ptr<std::unordered_map<uint32_t, int>> map,
+ uint64_t start) {
ops_ = ops;
merge_op_blocks_ = merge_op_blocks;
map_ = map;
+ start_ = start;
+
+ block_iter_ = merge_op_blocks->begin() + start;
+}
+
+bool CowMergeOpIter::Done() {
+ return block_iter_ == merge_op_blocks_->end();
+}
+
+void CowMergeOpIter::Next() {
+ CHECK(!Done());
+ block_iter_++;
+}
+
+const CowOperation& CowMergeOpIter::Get() {
+ CHECK(!Done());
+ return ops_->data()[map_->at(*block_iter_)];
+}
+
+CowRevMergeOpIter::CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
+ std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
+ std::shared_ptr<std::unordered_map<uint32_t, int>> map,
+ uint64_t start) {
+ ops_ = ops;
+ merge_op_blocks_ = merge_op_blocks;
+ map_ = map;
+ start_ = start;
block_riter_ = merge_op_blocks->rbegin();
}
bool CowRevMergeOpIter::Done() {
- return block_riter_ == merge_op_blocks_->rend();
+ return block_riter_ == merge_op_blocks_->rend() - start_;
}
void CowRevMergeOpIter::Next() {
@@ -571,8 +670,14 @@
return std::make_unique<CowOpIter>(ops_);
}
-std::unique_ptr<ICowOpIter> CowReader::GetRevMergeOpIter() {
- return std::make_unique<CowRevMergeOpIter>(ops_, merge_op_blocks_, block_map_);
+std::unique_ptr<ICowOpIter> CowReader::GetRevMergeOpIter(bool ignore_progress) {
+ return std::make_unique<CowRevMergeOpIter>(ops_, merge_op_blocks_, block_map_,
+ ignore_progress ? 0 : merge_op_start_);
+}
+
+std::unique_ptr<ICowOpIter> CowReader::GetMergeOpIter(bool ignore_progress) {
+ return std::make_unique<CowMergeOpIter>(ops_, merge_op_blocks_, block_map_,
+ ignore_progress ? 0 : merge_op_start_);
}
bool CowReader::GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read) {
diff --git a/fs_mgr/libsnapshot/device_info.cpp b/fs_mgr/libsnapshot/device_info.cpp
index 14ce0ee..a6d96ed 100644
--- a/fs_mgr/libsnapshot/device_info.cpp
+++ b/fs_mgr/libsnapshot/device_info.cpp
@@ -139,5 +139,9 @@
}
}
+android::dm::IDeviceMapper& DeviceInfo::GetDeviceMapper() {
+ return android::dm::DeviceMapper::Instance();
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/device_info.h b/fs_mgr/libsnapshot/device_info.h
index 7999c99..8aefb85 100644
--- a/fs_mgr/libsnapshot/device_info.h
+++ b/fs_mgr/libsnapshot/device_info.h
@@ -40,6 +40,7 @@
bool IsRecovery() const override;
std::unique_ptr<IImageManager> OpenImageManager() const override;
bool IsFirstStageInit() const override;
+ android::dm::IDeviceMapper& GetDeviceMapper() override;
void set_first_stage_init(bool value) { first_stage_init_ = value; }
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index c15682a..9f4ddbb 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -26,8 +26,8 @@
static constexpr uint32_t kCowVersionManifest = 2;
-static constexpr uint32_t BLOCK_SZ = 4096;
-static constexpr uint32_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
+static constexpr size_t BLOCK_SZ = 4096;
+static constexpr size_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
// This header appears as the first sequence of bytes in the COW. All fields
// in the layout are little-endian encoded. The on-disk layout is:
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index 0786e82..d5b4335 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -68,6 +68,7 @@
// Return the file footer.
virtual bool GetFooter(CowFooter* footer) = 0;
+ virtual bool VerifyMergeOps() = 0;
// Return the last valid label
virtual bool GetLastLabel(uint64_t* label) = 0;
@@ -75,8 +76,11 @@
// Return an iterator for retrieving CowOperation entries.
virtual std::unique_ptr<ICowOpIter> GetOpIter() = 0;
+ // Return an iterator for retrieving CowOperation entries in reverse merge order
+ virtual std::unique_ptr<ICowOpIter> GetRevMergeOpIter(bool ignore_progress) = 0;
+
// Return an iterator for retrieving CowOperation entries in merge order
- virtual std::unique_ptr<ICowOpIter> GetRevMergeOpIter() = 0;
+ virtual std::unique_ptr<ICowOpIter> GetMergeOpIter(bool ignore_progress) = 0;
// Get decoded bytes from the data section, handling any decompression.
// All retrieved data is passed to the sink.
@@ -98,9 +102,14 @@
virtual void Next() = 0;
};
-class CowReader : public ICowReader {
+class CowReader final : public ICowReader {
public:
- CowReader();
+ enum class ReaderFlags {
+ DEFAULT = 0,
+ USERSPACE_MERGE = 1,
+ };
+
+ CowReader(ReaderFlags reader_flag = ReaderFlags::DEFAULT);
~CowReader() { owned_fd_ = {}; }
// Parse the COW, optionally, up to the given label. If no label is
@@ -109,6 +118,7 @@
bool Parse(android::base::borrowed_fd fd, std::optional<uint64_t> label = {});
bool InitForMerge(android::base::unique_fd&& fd);
+ bool VerifyMergeOps() override;
bool GetHeader(CowHeader* header) override;
bool GetFooter(CowFooter* footer) override;
@@ -120,7 +130,8 @@
// whose lifetime depends on the CowOpIter object; the return
// value of these will never be null.
std::unique_ptr<ICowOpIter> GetOpIter() override;
- std::unique_ptr<ICowOpIter> GetRevMergeOpIter() override;
+ std::unique_ptr<ICowOpIter> GetRevMergeOpIter(bool ignore_progress = false) override;
+ std::unique_ptr<ICowOpIter> GetMergeOpIter(bool ignore_progress = false) override;
bool ReadData(const CowOperation& op, IByteSink* sink) override;
@@ -139,6 +150,8 @@
// Creates a clone of the current CowReader without the file handlers
std::unique_ptr<CowReader> CloneCowReader();
+ void UpdateMergeOpsCompleted(int num_merge_ops) { header_.num_merge_ops += num_merge_ops; }
+
private:
bool ParseOps(std::optional<uint64_t> label);
bool PrepMergeOps();
@@ -152,11 +165,13 @@
std::optional<uint64_t> last_label_;
std::shared_ptr<std::vector<CowOperation>> ops_;
std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
+ uint64_t merge_op_start_;
std::shared_ptr<std::unordered_map<uint32_t, int>> block_map_;
uint64_t num_total_data_ops_;
uint64_t num_ordered_ops_to_merge_;
bool has_seq_ops_;
std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
+ ReaderFlags reader_flag_;
};
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h
index 3655c01..b0be5a5 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h
@@ -44,6 +44,7 @@
// Open the writer in write mode (no append).
MOCK_METHOD(bool, Initialize, (), (override));
+ MOCK_METHOD(bool, VerifyMergeOps, (), (override, const, noexcept));
// Open the writer in append mode, with the last label to resume
// from. See CowWriter::InitializeAppend.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index e60da31..a49b026 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -110,6 +110,7 @@
virtual bool IsTestDevice() const { return false; }
virtual bool IsFirstStageInit() const = 0;
virtual std::unique_ptr<IImageManager> OpenImageManager() const = 0;
+ virtual android::dm::IDeviceMapper& GetDeviceMapper() = 0;
// Helper method for implementing OpenImageManager.
std::unique_ptr<IImageManager> OpenImageManager(const std::string& gsid_dir) const;
@@ -399,6 +400,7 @@
FRIEND_TEST(SnapshotTest, MergeFailureCode);
FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
FRIEND_TEST(SnapshotTest, UpdateBootControlHal);
+ FRIEND_TEST(SnapshotUpdateTest, AddPartition);
FRIEND_TEST(SnapshotUpdateTest, DaemonTransition);
FRIEND_TEST(SnapshotUpdateTest, DataWipeAfterRollback);
FRIEND_TEST(SnapshotUpdateTest, DataWipeRollbackInRecovery);
@@ -406,6 +408,7 @@
FRIEND_TEST(SnapshotUpdateTest, FullUpdateFlow);
FRIEND_TEST(SnapshotUpdateTest, MergeCannotRemoveCow);
FRIEND_TEST(SnapshotUpdateTest, MergeInRecovery);
+ FRIEND_TEST(SnapshotUpdateTest, QueryStatusError);
FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow);
FRIEND_TEST(SnapshotUpdateTest, SpaceSwapUpdate);
friend class SnapshotTest;
@@ -610,6 +613,14 @@
MergeFailureCode CheckMergeConsistency(LockedFile* lock, const std::string& name,
const SnapshotStatus& update_status);
+ // Get status or table information about a device-mapper node with a single target.
+ enum class TableQuery {
+ Table,
+ Status,
+ };
+ bool GetSingleTarget(const std::string& dm_name, TableQuery query,
+ android::dm::DeviceMapper::TargetInfo* target);
+
// Interact with status files under /metadata/ota/snapshots.
bool WriteSnapshotStatus(LockedFile* lock, const SnapshotStatus& status);
bool ReadSnapshotStatus(LockedFile* lock, const std::string& name, SnapshotStatus* status);
@@ -772,9 +783,9 @@
bool DeleteDeviceIfExists(const std::string& name,
const std::chrono::milliseconds& timeout_ms = {});
- std::string gsid_dir_;
- std::string metadata_dir_;
+ android::dm::IDeviceMapper& dm_;
std::unique_ptr<IDeviceInfo> device_;
+ std::string metadata_dir_;
std::unique_ptr<IImageManager> images_;
bool use_first_stage_snapuserd_ = false;
bool in_factory_data_reset_ = false;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
index b09e1ae..545f117 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
@@ -47,6 +47,7 @@
virtual bool InitializeAppend(uint64_t label) = 0;
virtual std::unique_ptr<FileDescriptor> OpenReader() = 0;
+ virtual bool VerifyMergeOps() const noexcept = 0;
protected:
android::base::borrowed_fd GetSourceFd();
@@ -58,7 +59,7 @@
};
// Send writes to a COW or a raw device directly, based on a threshold.
-class CompressedSnapshotWriter : public ISnapshotWriter {
+class CompressedSnapshotWriter final : public ISnapshotWriter {
public:
CompressedSnapshotWriter(const CowOptions& options);
@@ -70,6 +71,7 @@
bool Finalize() override;
uint64_t GetCowSize() override;
std::unique_ptr<FileDescriptor> OpenReader() override;
+ bool VerifyMergeOps() const noexcept;
protected:
bool EmitCopy(uint64_t new_block, uint64_t old_block) override;
@@ -81,13 +83,14 @@
bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
private:
+ std::unique_ptr<CowReader> OpenCowReader() const;
android::base::unique_fd cow_device_;
std::unique_ptr<CowWriter> cow_;
};
// Write directly to a dm-snapshot device.
-class OnlineKernelSnapshotWriter : public ISnapshotWriter {
+class OnlineKernelSnapshotWriter final : public ISnapshotWriter {
public:
OnlineKernelSnapshotWriter(const CowOptions& options);
@@ -101,6 +104,10 @@
uint64_t GetCowSize() override { return cow_size_; }
std::unique_ptr<FileDescriptor> OpenReader() override;
+ // Online kernel snapshot writer doesn't care about merge sequences.
+ // So ignore.
+ bool VerifyMergeOps() const noexcept override { return true; }
+
protected:
bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index 4e7ccf1..c3b40dc 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -99,6 +99,12 @@
std::unique_ptr<IImageManager> OpenImageManager() const override {
return IDeviceInfo::OpenImageManager("ota/test");
}
+ android::dm::IDeviceMapper& GetDeviceMapper() override {
+ if (dm_) {
+ return *dm_;
+ }
+ return android::dm::DeviceMapper::Instance();
+ }
bool IsSlotUnbootable(uint32_t slot) { return unbootable_slots_.count(slot) != 0; }
@@ -108,6 +114,8 @@
}
void set_recovery(bool value) { recovery_ = value; }
void set_first_stage_init(bool value) { first_stage_init_ = value; }
+ void set_dm(android::dm::IDeviceMapper* dm) { dm_ = dm; }
+
MergeStatus merge_status() const { return merge_status_; }
private:
@@ -117,6 +125,45 @@
bool recovery_ = false;
bool first_stage_init_ = false;
std::unordered_set<uint32_t> unbootable_slots_;
+ android::dm::IDeviceMapper* dm_ = nullptr;
+};
+
+class DeviceMapperWrapper : public android::dm::IDeviceMapper {
+ using DmDeviceState = android::dm::DmDeviceState;
+ using DmTable = android::dm::DmTable;
+
+ public:
+ DeviceMapperWrapper() : impl_(android::dm::DeviceMapper::Instance()) {}
+ explicit DeviceMapperWrapper(android::dm::IDeviceMapper& impl) : impl_(impl) {}
+
+ virtual bool CreateDevice(const std::string& name, const DmTable& table, std::string* path,
+ const std::chrono::milliseconds& timeout_ms) override {
+ return impl_.CreateDevice(name, table, path, timeout_ms);
+ }
+ virtual DmDeviceState GetState(const std::string& name) const override {
+ return impl_.GetState(name);
+ }
+ virtual bool LoadTableAndActivate(const std::string& name, const DmTable& table) {
+ return impl_.LoadTableAndActivate(name, table);
+ }
+ virtual bool GetTableInfo(const std::string& name, std::vector<TargetInfo>* table) {
+ return impl_.GetTableInfo(name, table);
+ }
+ virtual bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) {
+ return impl_.GetTableStatus(name, table);
+ }
+ virtual bool GetDmDevicePathByName(const std::string& name, std::string* path) {
+ return impl_.GetDmDevicePathByName(name, path);
+ }
+ virtual bool GetDeviceString(const std::string& name, std::string* dev) {
+ return impl_.GetDeviceString(name, dev);
+ }
+ virtual bool DeleteDeviceIfExists(const std::string& name) {
+ return impl_.DeleteDeviceIfExists(name);
+ }
+
+ private:
+ android::dm::IDeviceMapper& impl_;
};
class SnapshotTestPropertyFetcher : public android::fs_mgr::testing::MockPropertyFetcher {
diff --git a/fs_mgr/libsnapshot/inspect_cow.cpp b/fs_mgr/libsnapshot/inspect_cow.cpp
index ed86c87..548ba00 100644
--- a/fs_mgr/libsnapshot/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/inspect_cow.cpp
@@ -16,8 +16,10 @@
#include <stdio.h>
#include <unistd.h>
+#include <iomanip>
#include <iostream>
#include <string>
+#include <vector>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
@@ -39,17 +41,26 @@
LOG(ERROR) << "Usage: inspect_cow [-sd] <COW_FILE>";
LOG(ERROR) << "\t -s Run Silent";
LOG(ERROR) << "\t -d Attempt to decompress";
- LOG(ERROR) << "\t -b Show data for failed decompress\n";
- LOG(ERROR) << "\t -m Show ops in reverse merge order\n";
+ LOG(ERROR) << "\t -b Show data for failed decompress";
+ LOG(ERROR) << "\t -l Show ops";
+ LOG(ERROR) << "\t -m Show ops in reverse merge order";
+ LOG(ERROR) << "\t -n Show ops in merge order";
+ LOG(ERROR) << "\t -a Include merged ops in any merge order listing";
+ LOG(ERROR) << "\t -o Shows sequence op block order";
+ LOG(ERROR) << "\t -v Verifies merge order has no conflicts\n";
}
-enum OpIter { Normal, RevMerge };
+enum OpIter { Normal, RevMerge, Merge };
struct Options {
bool silent;
bool decompress;
+ bool show_ops;
bool show_bad;
+ bool show_seq;
+ bool verify_sequence;
OpIter iter_type;
+ bool include_merged;
};
// Sink that always appends to the end of a string.
@@ -126,18 +137,28 @@
}
}
+ if (opt.verify_sequence) {
+ if (reader.VerifyMergeOps()) {
+ std::cout << "\nMerge sequence is consistent.\n";
+ } else {
+ std::cout << "\nMerge sequence is inconsistent!\n";
+ }
+ }
+
std::unique_ptr<ICowOpIter> iter;
if (opt.iter_type == Normal) {
iter = reader.GetOpIter();
} else if (opt.iter_type == RevMerge) {
- iter = reader.GetRevMergeOpIter();
+ iter = reader.GetRevMergeOpIter(opt.include_merged);
+ } else if (opt.iter_type == Merge) {
+ iter = reader.GetMergeOpIter(opt.include_merged);
}
StringSink sink;
bool success = true;
while (!iter->Done()) {
const CowOperation& op = iter->Get();
- if (!opt.silent) std::cout << op << "\n";
+ if (!opt.silent && opt.show_ops) std::cout << op << "\n";
if (opt.decompress && op.type == kCowReplaceOp && op.compression != kCowCompressNone) {
if (!reader.ReadData(op, &sink)) {
@@ -148,6 +169,24 @@
sink.Reset();
}
+ if (op.type == kCowSequenceOp && opt.show_seq) {
+ size_t read;
+ std::vector<uint32_t> merge_op_blocks;
+ size_t seq_len = op.data_length / sizeof(uint32_t);
+ merge_op_blocks.resize(seq_len);
+ if (!reader.GetRawBytes(op.source, merge_op_blocks.data(), op.data_length, &read)) {
+ PLOG(ERROR) << "Failed to read sequence op!";
+ return false;
+ }
+ if (!opt.silent) {
+ std::cout << "Sequence for " << op << " is :\n";
+ for (size_t i = 0; i < seq_len; i++) {
+ std::cout << std::setfill('0') << std::setw(6) << merge_op_blocks[i] << ", ";
+ if ((i + 1) % 10 == 0 || i + 1 == seq_len) std::cout << "\n";
+ }
+ }
+ }
+
iter->Next();
}
@@ -164,7 +203,9 @@
opt.decompress = false;
opt.show_bad = false;
opt.iter_type = android::snapshot::Normal;
- while ((ch = getopt(argc, argv, "sdbm")) != -1) {
+ opt.verify_sequence = false;
+ opt.include_merged = false;
+ while ((ch = getopt(argc, argv, "sdbmnolva")) != -1) {
switch (ch) {
case 's':
opt.silent = true;
@@ -178,6 +219,21 @@
case 'm':
opt.iter_type = android::snapshot::RevMerge;
break;
+ case 'n':
+ opt.iter_type = android::snapshot::Merge;
+ break;
+ case 'o':
+ opt.show_seq = true;
+ break;
+ case 'l':
+ opt.show_ops = true;
+ break;
+ case 'v':
+ opt.verify_sequence = true;
+ break;
+ case 'a':
+ opt.include_merged = true;
+ break;
default:
android::snapshot::usage();
}
diff --git a/fs_mgr/libsnapshot/scripts/Android.bp b/fs_mgr/libsnapshot/scripts/Android.bp
index 3e09cc3..829f5bc 100644
--- a/fs_mgr/libsnapshot/scripts/Android.bp
+++ b/fs_mgr/libsnapshot/scripts/Android.bp
@@ -14,6 +14,11 @@
// limitations under the License.
//
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
python_binary_host {
name: "dump_snapshot_proto",
main: "dump_snapshot_proto.py",
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index c3db32e..3d8ae29 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -114,9 +114,8 @@
return sm;
}
-SnapshotManager::SnapshotManager(IDeviceInfo* device) : device_(device) {
- metadata_dir_ = device_->GetMetadataDir();
-}
+SnapshotManager::SnapshotManager(IDeviceInfo* device)
+ : dm_(device->GetDeviceMapper()), device_(device), metadata_dir_(device_->GetMetadataDir()) {}
static std::string GetCowName(const std::string& snapshot_name) {
return snapshot_name + "-cow";
@@ -402,8 +401,6 @@
const std::chrono::milliseconds& timeout_ms, std::string* path) {
CHECK(lock);
- auto& dm = DeviceMapper::Instance();
-
// Use an extra decoration for first-stage init, so we can transition
// to a new table entry in second-stage.
std::string misc_name = name;
@@ -423,7 +420,7 @@
DmTable table;
table.Emplace<DmTargetUser>(0, base_sectors, misc_name);
- if (!dm.CreateDevice(name, table, path, timeout_ms)) {
+ if (!dm_.CreateDevice(name, table, path, timeout_ms)) {
return false;
}
if (!WaitForDevice(*path, timeout_ms)) {
@@ -490,8 +487,6 @@
uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize;
- auto& dm = DeviceMapper::Instance();
-
// Note that merging is a global state. We do track whether individual devices
// have completed merging, but the start of the merge process is considered
// atomic.
@@ -518,10 +513,17 @@
break;
}
+ if (mode == SnapshotStorageMode::Persistent && status.state() == SnapshotState::MERGING) {
+ LOG(ERROR) << "Snapshot: " << name
+ << " has snapshot status Merging but mode set to Persistent."
+ << " Changing mode to Snapshot-Merge.";
+ mode = SnapshotStorageMode::Merge;
+ }
+
DmTable table;
table.Emplace<DmTargetSnapshot>(0, snapshot_sectors, base_device, cow_device, mode,
kSnapshotChunkSize);
- if (!dm.CreateDevice(name, table, dev_path, timeout_ms)) {
+ if (!dm_.CreateDevice(name, table, dev_path, timeout_ms)) {
LOG(ERROR) << "Could not create snapshot device: " << name;
return false;
}
@@ -652,7 +654,6 @@
auto other_suffix = device_->GetOtherSlotSuffix();
- auto& dm = DeviceMapper::Instance();
for (const auto& snapshot : snapshots) {
if (android::base::EndsWith(snapshot, other_suffix)) {
// Allow the merge to continue, but log this unexpected case.
@@ -664,7 +665,7 @@
// the same time. This is a fairly serious error. We could forcefully
// map everything here, but it should have been mapped during first-
// stage init.
- if (dm.GetState(snapshot) == DmDeviceState::INVALID) {
+ if (dm_.GetState(snapshot) == DmDeviceState::INVALID) {
LOG(ERROR) << "Cannot begin merge; device " << snapshot << " is not mapped.";
return false;
}
@@ -797,10 +798,8 @@
}
MergeFailureCode SnapshotManager::RewriteSnapshotDeviceTable(const std::string& name) {
- auto& dm = DeviceMapper::Instance();
-
std::vector<DeviceMapper::TargetInfo> old_targets;
- if (!dm.GetTableInfo(name, &old_targets)) {
+ if (!dm_.GetTableInfo(name, &old_targets)) {
LOG(ERROR) << "Could not read snapshot device table: " << name;
return MergeFailureCode::GetTableInfo;
}
@@ -818,7 +817,7 @@
DmTable table;
table.Emplace<DmTargetSnapshot>(0, old_targets[0].spec.length, base_device, cow_device,
SnapshotStorageMode::Merge, kSnapshotChunkSize);
- if (!dm.LoadTableAndActivate(name, table)) {
+ if (!dm_.LoadTableAndActivate(name, table)) {
LOG(ERROR) << "Could not swap device-mapper tables on snapshot device " << name;
return MergeFailureCode::ActivateNewTable;
}
@@ -826,24 +825,18 @@
return MergeFailureCode::Ok;
}
-enum class TableQuery {
- Table,
- Status,
-};
-
-static bool GetSingleTarget(const std::string& dm_name, TableQuery query,
- DeviceMapper::TargetInfo* target) {
- auto& dm = DeviceMapper::Instance();
- if (dm.GetState(dm_name) == DmDeviceState::INVALID) {
+bool SnapshotManager::GetSingleTarget(const std::string& dm_name, TableQuery query,
+ DeviceMapper::TargetInfo* target) {
+ if (dm_.GetState(dm_name) == DmDeviceState::INVALID) {
return false;
}
std::vector<DeviceMapper::TargetInfo> targets;
bool result;
if (query == TableQuery::Status) {
- result = dm.GetTableStatus(dm_name, &targets);
+ result = dm_.GetTableStatus(dm_name, &targets);
} else {
- result = dm.GetTableInfo(dm_name, &targets);
+ result = dm_.GetTableInfo(dm_name, &targets);
}
if (!result) {
LOG(ERROR) << "Could not query device: " << dm_name;
@@ -886,6 +879,10 @@
if (target_type) {
*target_type = DeviceMapper::GetTargetType(target.spec);
}
+ if (!status->error.empty()) {
+ LOG(ERROR) << "Snapshot: " << dm_name << " returned error code: " << status->error;
+ return false;
+ }
return true;
}
@@ -1169,11 +1166,9 @@
return MergeFailureCode::Ok;
}
- auto& dm = DeviceMapper::Instance();
-
std::string cow_image_name = GetMappedCowDeviceName(name, status);
std::string cow_image_path;
- if (!dm.GetDmDevicePathByName(cow_image_name, &cow_image_path)) {
+ if (!dm_.GetDmDevicePathByName(cow_image_name, &cow_image_path)) {
LOG(ERROR) << "Failed to get path for cow device: " << cow_image_name;
return MergeFailureCode::GetCowPathConsistencyCheck;
}
@@ -1349,8 +1344,6 @@
bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
const SnapshotStatus& status) {
- auto& dm = DeviceMapper::Instance();
-
// Verify we have a snapshot-merge device.
DeviceMapper::TargetInfo target;
if (!GetSingleTarget(name, TableQuery::Table, &target)) {
@@ -1389,7 +1382,7 @@
return false;
}
- if (!dm.LoadTableAndActivate(name, table)) {
+ if (!dm_.LoadTableAndActivate(name, table)) {
return false;
}
@@ -1452,7 +1445,7 @@
std::vector<std::string>* snapuserd_argv) {
LOG(INFO) << "Performing transition for snapuserd.";
- // Don't use EnsuerSnapuserdConnected() because this is called from init,
+ // Don't use EnsureSnapuserdConnected() because this is called from init,
// and attempting to do so will deadlock.
if (!snapuserd_client_ && transition != InitTransition::SELINUX_DETACH) {
snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, 10s);
@@ -1462,8 +1455,6 @@
}
}
- auto& dm = DeviceMapper::Instance();
-
auto lock = LockExclusive();
if (!lock) return false;
@@ -1477,7 +1468,7 @@
size_t ok_cows = 0;
for (const auto& snapshot : snapshots) {
std::string user_cow_name = GetDmUserCowName(snapshot);
- if (dm.GetState(user_cow_name) == DmDeviceState::INVALID) {
+ if (dm_.GetState(user_cow_name) == DmDeviceState::INVALID) {
continue;
}
@@ -1504,13 +1495,20 @@
DmTable table;
table.Emplace<DmTargetUser>(0, target.spec.length, misc_name);
- if (!dm.LoadTableAndActivate(user_cow_name, table)) {
+ if (!dm_.LoadTableAndActivate(user_cow_name, table)) {
LOG(ERROR) << "Unable to swap tables for " << misc_name;
continue;
}
+ std::string source_device_name;
+ if (snapshot_status.old_partition_size() > 0) {
+ source_device_name = GetSourceDeviceName(snapshot);
+ } else {
+ source_device_name = GetBaseDeviceName(snapshot);
+ }
+
std::string source_device;
- if (!dm.GetDmDevicePathByName(GetSourceDeviceName(snapshot), &source_device)) {
+ if (!dm_.GetDmDevicePathByName(source_device_name, &source_device)) {
LOG(ERROR) << "Could not get device path for " << GetSourceDeviceName(snapshot);
continue;
}
@@ -1518,7 +1516,7 @@
std::string cow_image_name = GetMappedCowDeviceName(snapshot, snapshot_status);
std::string cow_image_device;
- if (!dm.GetDmDevicePathByName(cow_image_name, &cow_image_device)) {
+ if (!dm_.GetDmDevicePathByName(cow_image_name, &cow_image_device)) {
LOG(ERROR) << "Could not get device path for " << cow_image_name;
continue;
}
@@ -1693,8 +1691,7 @@
// snapshot, but it's on the wrong slot. We can't unmap an active
// partition. If this is not really a snapshot, skip the unmap
// step.
- auto& dm = DeviceMapper::Instance();
- if (dm.GetState(name) == DmDeviceState::INVALID || !IsSnapshotDevice(name)) {
+ if (dm_.GetState(name) == DmDeviceState::INVALID || !IsSnapshotDevice(name)) {
LOG(ERROR) << "Detected snapshot " << name << " on " << current_slot << " slot"
<< " for source partition; removing without unmap.";
should_unmap = false;
@@ -2031,14 +2028,13 @@
// Create the base device for the snapshot, or if there is no snapshot, the
// device itself. This device consists of the real blocks in the super
// partition that this logical partition occupies.
- auto& dm = DeviceMapper::Instance();
std::string base_path;
if (!CreateLogicalPartition(params, &base_path)) {
LOG(ERROR) << "Could not create logical partition " << params.GetPartitionName()
<< " as device " << params.GetDeviceName();
return false;
}
- created_devices.EmplaceBack<AutoUnmapDevice>(&dm, params.GetDeviceName());
+ created_devices.EmplaceBack<AutoUnmapDevice>(&dm_, params.GetDeviceName());
if (paths) {
paths->target_device = base_path;
@@ -2052,7 +2048,7 @@
// We don't have ueventd in first-stage init, so use device major:minor
// strings instead.
std::string base_device;
- if (!dm.GetDeviceString(params.GetDeviceName(), &base_device)) {
+ if (!dm_.GetDeviceString(params.GetDeviceName(), &base_device)) {
LOG(ERROR) << "Could not determine major/minor for: " << params.GetDeviceName();
return false;
}
@@ -2087,14 +2083,18 @@
if (live_snapshot_status->compression_enabled()) {
// Get the source device (eg the view of the partition from before it was resized).
std::string source_device_path;
- if (!MapSourceDevice(lock, params.GetPartitionName(), remaining_time,
- &source_device_path)) {
- LOG(ERROR) << "Could not map source device for: " << cow_name;
- return false;
- }
+ if (live_snapshot_status->old_partition_size() > 0) {
+ if (!MapSourceDevice(lock, params.GetPartitionName(), remaining_time,
+ &source_device_path)) {
+ LOG(ERROR) << "Could not map source device for: " << cow_name;
+ return false;
+ }
- auto source_device = GetSourceDeviceName(params.GetPartitionName());
- created_devices.EmplaceBack<AutoUnmapDevice>(&dm, source_device);
+ auto source_device = GetSourceDeviceName(params.GetPartitionName());
+ created_devices.EmplaceBack<AutoUnmapDevice>(&dm_, source_device);
+ } else {
+ source_device_path = base_path;
+ }
if (!WaitForDevice(source_device_path, remaining_time)) {
return false;
@@ -2118,7 +2118,7 @@
<< params.GetPartitionName();
return false;
}
- created_devices.EmplaceBack<AutoUnmapDevice>(&dm, name);
+ created_devices.EmplaceBack<AutoUnmapDevice>(&dm_, name);
remaining_time = GetRemainingTime(params.timeout_ms, begin);
if (remaining_time.count() < 0) return false;
@@ -2184,8 +2184,6 @@
std::string cow_image_name = GetCowImageDeviceName(partition_name);
*cow_name = GetCowName(partition_name);
- auto& dm = DeviceMapper::Instance();
-
// Map COW image if necessary.
if (snapshot_status.cow_file_size() > 0) {
if (!EnsureImageManager()) return false;
@@ -2236,11 +2234,11 @@
// We have created the DmTable now. Map it.
std::string cow_path;
- if (!dm.CreateDevice(*cow_name, table, &cow_path, remaining_time)) {
+ if (!dm_.CreateDevice(*cow_name, table, &cow_path, remaining_time)) {
LOG(ERROR) << "Could not create COW device: " << *cow_name;
return false;
}
- created_devices->EmplaceBack<AutoUnmapDevice>(&dm, *cow_name);
+ created_devices->EmplaceBack<AutoUnmapDevice>(&dm_, *cow_name);
LOG(INFO) << "Mapped COW device for " << params.GetPartitionName() << " at " << cow_path;
return true;
}
@@ -2267,10 +2265,8 @@
}
bool SnapshotManager::UnmapDmUserDevice(const std::string& snapshot_name) {
- auto& dm = DeviceMapper::Instance();
-
auto dm_user_name = GetDmUserCowName(snapshot_name);
- if (dm.GetState(dm_user_name) == DmDeviceState::INVALID) {
+ if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) {
return true;
}
@@ -3490,7 +3486,6 @@
return false;
}
- auto& dm = DeviceMapper::Instance();
for (const auto& snapshot : snapshots) {
SnapshotStatus status;
if (!ReadSnapshotStatus(lock, snapshot, &status)) {
@@ -3501,7 +3496,7 @@
}
std::vector<DeviceMapper::TargetInfo> targets;
- if (!dm.GetTableStatus(snapshot, &targets)) {
+ if (!dm_.GetTableStatus(snapshot, &targets)) {
LOG(ERROR) << "Could not read snapshot device table: " << snapshot;
return false;
}
@@ -3594,11 +3589,9 @@
// isn't running yet.
bool SnapshotManager::GetMappedImageDevicePath(const std::string& device_name,
std::string* device_path) {
- auto& dm = DeviceMapper::Instance();
-
// Try getting the device string if it is a device mapper device.
- if (dm.GetState(device_name) != DmDeviceState::INVALID) {
- return dm.GetDmDevicePathByName(device_name, device_path);
+ if (dm_.GetState(device_name) != DmDeviceState::INVALID) {
+ return dm_.GetDmDevicePathByName(device_name, device_path);
}
// Otherwise, get path from IImageManager.
@@ -3607,10 +3600,9 @@
bool SnapshotManager::GetMappedImageDeviceStringOrPath(const std::string& device_name,
std::string* device_string_or_mapped_path) {
- auto& dm = DeviceMapper::Instance();
// Try getting the device string if it is a device mapper device.
- if (dm.GetState(device_name) != DmDeviceState::INVALID) {
- return dm.GetDeviceString(device_name, device_string_or_mapped_path);
+ if (dm_.GetState(device_name) != DmDeviceState::INVALID) {
+ return dm_.GetDeviceString(device_name, device_string_or_mapped_path);
}
// Otherwise, get path from IImageManager.
@@ -3726,10 +3718,9 @@
bool SnapshotManager::DeleteDeviceIfExists(const std::string& name,
const std::chrono::milliseconds& timeout_ms) {
- auto& dm = DeviceMapper::Instance();
auto start = std::chrono::steady_clock::now();
while (true) {
- if (dm.DeleteDeviceIfExists(name)) {
+ if (dm_.DeleteDeviceIfExists(name)) {
return true;
}
auto now = std::chrono::steady_clock::now();
@@ -3742,7 +3733,7 @@
// Try to diagnose why this failed. First get the actual device path.
std::string full_path;
- if (!dm.GetDmDevicePathByName(name, &full_path)) {
+ if (!dm_.GetDmDevicePathByName(name, &full_path)) {
LOG(ERROR) << "Unable to diagnose DM_DEV_REMOVE failure.";
return false;
}
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
index 0096f85..acee2f4 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
@@ -139,7 +139,7 @@
auto& dm = DeviceMapper::Instance();
std::vector<DeviceMapper::TargetInfo> table;
if (!dm.GetTableInfo(dev_name, &table)) {
- PCHECK(errno == ENODEV);
+ PCHECK(errno == ENODEV || errno == ENXIO);
return {};
}
return table;
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
index 3ed27c8..c1a5af7 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
@@ -101,7 +101,8 @@
: env_(env),
data_(&data),
partition_opener_(std::move(partition_opener)),
- metadata_dir_(metadata_dir) {}
+ metadata_dir_(metadata_dir),
+ dm_(android::dm::DeviceMapper::Instance()) {}
// Following APIs are mocked.
std::string GetMetadataDir() const override { return metadata_dir_; }
@@ -125,6 +126,7 @@
}
bool IsRecovery() const override { return data_->is_recovery(); }
bool IsFirstStageInit() const override { return false; }
+ android::dm::IDeviceMapper& GetDeviceMapper() override { return dm_; }
std::unique_ptr<IImageManager> OpenImageManager() const {
return env_->CheckCreateFakeImageManager();
}
@@ -137,6 +139,7 @@
std::unique_ptr<TestPartitionOpener> partition_opener_;
std::string metadata_dir_;
bool switched_slot_ = false;
+ android::dm::DeviceMapper& dm_;
bool CurrentSlotIsA() const { return data_->slot_suffix_is_a() != switched_slot_; }
};
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index b2203fe..d78ba0a 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -56,6 +56,7 @@
using android::base::unique_fd;
using android::dm::DeviceMapper;
using android::dm::DmDeviceState;
+using android::dm::IDeviceMapper;
using android::fiemap::FiemapStatus;
using android::fiemap::IImageManager;
using android::fs_mgr::BlockDeviceInfo;
@@ -911,6 +912,11 @@
ASSERT_TRUE(hash.has_value());
hashes_[name] = *hash;
}
+
+ // OTA client blindly unmaps all partitions that are possibly mapped.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
+ }
}
void TearDown() override {
RETURN_IF_NON_VIRTUAL_AB();
@@ -925,6 +931,14 @@
MountMetadata();
for (const auto& suffix : {"_a", "_b"}) {
test_device->set_slot_suffix(suffix);
+
+ // Cheat our way out of merge failed states.
+ if (sm->ProcessUpdateState() == UpdateState::MergeFailed) {
+ ASSERT_TRUE(AcquireLock());
+ ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::None));
+ lock_ = {};
+ }
+
EXPECT_TRUE(sm->CancelUpdate()) << suffix;
}
EXPECT_TRUE(UnmapAll());
@@ -963,7 +977,7 @@
}
AssertionResult UnmapAll() {
- for (const auto& name : {"sys", "vnd", "prd"}) {
+ for (const auto& name : {"sys", "vnd", "prd", "dlkm"}) {
if (!dm_.DeleteDeviceIfExists(name + "_a"s)) {
return AssertionFailure() << "Cannot unmap " << name << "_a";
}
@@ -1097,11 +1111,6 @@
// Also test UnmapUpdateSnapshot unmaps everything.
// Also test first stage mount and merge after this.
TEST_F(SnapshotUpdateTest, FullUpdateFlow) {
- // OTA client blindly unmaps all partitions that are possibly mapped.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
- }
-
// Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs
// fit in super, but not |prd|.
constexpr uint64_t partition_size = 3788_KiB;
@@ -1189,11 +1198,6 @@
GTEST_SKIP() << "Compression-only test";
}
- // OTA client blindly unmaps all partitions that are possibly mapped.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
- }
-
// Execute the update.
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
@@ -1239,11 +1243,6 @@
GTEST_SKIP() << "Skipping Virtual A/B Compression test";
}
- // OTA client blindly unmaps all partitions that are possibly mapped.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
- }
-
auto old_sys_size = GetSize(sys_);
auto old_prd_size = GetSize(prd_);
@@ -1630,11 +1629,6 @@
ASSERT_NE(nullptr, metadata);
ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0));
- // OTA client blindly unmaps all partitions that are possibly mapped.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
- }
-
// Add operations for sys. The whole device is written.
AddOperation(sys_);
@@ -2073,6 +2067,75 @@
ASSERT_LT(res.required_size(), 40_MiB);
}
+TEST_F(SnapshotUpdateTest, AddPartition) {
+ group_->add_partition_names("dlkm");
+
+ auto dlkm = manifest_.add_partitions();
+ dlkm->set_partition_name("dlkm");
+ dlkm->set_estimate_cow_size(2_MiB);
+ SetSize(dlkm, 3_MiB);
+
+ // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs
+ // fit in super, but not |prd|.
+ constexpr uint64_t partition_size = 3788_KiB;
+ SetSize(sys_, partition_size);
+ SetSize(vnd_, partition_size);
+ SetSize(prd_, partition_size);
+ SetSize(dlkm, partition_size);
+
+ AddOperationForPartitions({sys_, vnd_, prd_, dlkm});
+
+ // Execute the update.
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+ // Write some data to target partitions.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b", "dlkm_b"}) {
+ ASSERT_TRUE(WriteSnapshotAndHash(name));
+ }
+
+ // Assert that source partitions aren't affected.
+ for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
+ ASSERT_TRUE(IsPartitionUnchanged(name));
+ }
+
+ ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+
+ // Simulate shutting down the device.
+ ASSERT_TRUE(UnmapAll());
+
+ // After reboot, init does first stage mount.
+ auto init = NewManagerForFirstStageMount("_b");
+ ASSERT_NE(init, nullptr);
+
+ ASSERT_TRUE(init->EnsureSnapuserdConnected());
+ init->set_use_first_stage_snapuserd(true);
+
+ ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+ ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+
+ // Check that the target partitions have the same content.
+ std::vector<std::string> partitions = {"sys_b", "vnd_b", "prd_b", "dlkm_b"};
+ for (const auto& name : partitions) {
+ ASSERT_TRUE(IsPartitionUnchanged(name));
+ }
+
+ ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE));
+ for (const auto& name : partitions) {
+ ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete(name + "-user-cow-init"));
+ }
+
+ // Initiate the merge and wait for it to be completed.
+ ASSERT_TRUE(init->InitiateMerge());
+ ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
+
+ // Check that the target partitions have the same content after the merge.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b", "dlkm_b"}) {
+ ASSERT_TRUE(IsPartitionUnchanged(name))
+ << "Content of " << name << " changes after the merge";
+ }
+}
+
class AutoKill final {
public:
explicit AutoKill(pid_t pid) : pid_(pid) {}
@@ -2175,6 +2238,60 @@
ASSERT_TRUE(sm->BeginUpdate());
}
+TEST_F(SnapshotUpdateTest, QueryStatusError) {
+ // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs
+ // fit in super, but not |prd|.
+ constexpr uint64_t partition_size = 3788_KiB;
+ SetSize(sys_, partition_size);
+
+ AddOperationForPartitions({sys_});
+
+ // Execute the update.
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+ ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
+ ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+ ASSERT_TRUE(UnmapAll());
+
+ class DmStatusFailure final : public DeviceMapperWrapper {
+ public:
+ bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) override {
+ if (!DeviceMapperWrapper::GetTableStatus(name, table)) {
+ return false;
+ }
+ if (name == "sys_b" && !table->empty()) {
+ auto& info = table->at(0);
+ if (DeviceMapper::GetTargetType(info.spec) == "snapshot-merge") {
+ info.data = "Merge failed";
+ }
+ }
+ return true;
+ }
+ };
+ DmStatusFailure wrapper;
+
+ // After reboot, init does first stage mount.
+ auto info = new TestDeviceInfo(fake_super, "_b");
+ info->set_dm(&wrapper);
+
+ auto init = NewManagerForFirstStageMount(info);
+ ASSERT_NE(init, nullptr);
+
+ ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+ ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+
+ // Initiate the merge and wait for it to be completed.
+ ASSERT_TRUE(init->InitiateMerge());
+ ASSERT_EQ(UpdateState::MergeFailed, init->ProcessUpdateState());
+
+ // Simulate a reboot that tries the merge again, with the non-failing dm.
+ ASSERT_TRUE(UnmapAll());
+ init = NewManagerForFirstStageMount("_b");
+ ASSERT_NE(init, nullptr);
+ ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+ ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
+}
+
class FlashAfterUpdateTest : public SnapshotUpdateTest,
public WithParamInterface<std::tuple<uint32_t, bool>> {
public:
@@ -2191,11 +2308,6 @@
};
TEST_P(FlashAfterUpdateTest, FlashSlotAfterUpdate) {
- // OTA client blindly unmaps all partitions that are possibly mapped.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
- }
-
// Execute the update.
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
diff --git a/fs_mgr/libsnapshot/snapshot_writer.cpp b/fs_mgr/libsnapshot/snapshot_writer.cpp
index 3eda08e..48b7d80 100644
--- a/fs_mgr/libsnapshot/snapshot_writer.cpp
+++ b/fs_mgr/libsnapshot/snapshot_writer.cpp
@@ -67,7 +67,7 @@
return cow_->GetCowSize();
}
-std::unique_ptr<FileDescriptor> CompressedSnapshotWriter::OpenReader() {
+std::unique_ptr<CowReader> CompressedSnapshotWriter::OpenCowReader() const {
unique_fd cow_fd(dup(cow_device_.get()));
if (cow_fd < 0) {
PLOG(ERROR) << "dup COW device";
@@ -79,6 +79,20 @@
LOG(ERROR) << "Unable to read COW";
return nullptr;
}
+ return cow;
+}
+
+bool CompressedSnapshotWriter::VerifyMergeOps() const noexcept {
+ auto cow_reader = OpenCowReader();
+ if (cow_reader == nullptr) {
+ LOG(ERROR) << "Couldn't open CowReader";
+ return false;
+ }
+ return cow_reader->VerifyMergeOps();
+}
+
+std::unique_ptr<FileDescriptor> CompressedSnapshotWriter::OpenReader() {
+ auto cow = OpenCowReader();
auto reader = std::make_unique<CompressedSnapshotReader>();
if (!reader->SetCow(std::move(cow))) {
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 47268d4..c9b0512 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -56,11 +56,18 @@
"fs_mgr_defaults",
],
srcs: [
- "snapuserd_server.cpp",
- "snapuserd.cpp",
+ "dm-snapshot-merge/snapuserd_server.cpp",
+ "dm-snapshot-merge/snapuserd.cpp",
+ "dm-snapshot-merge/snapuserd_worker.cpp",
+ "dm-snapshot-merge/snapuserd_readahead.cpp",
"snapuserd_daemon.cpp",
- "snapuserd_worker.cpp",
- "snapuserd_readahead.cpp",
+ "snapuserd_buffer.cpp",
+ "user-space-merge/snapuserd_core.cpp",
+ "user-space-merge/snapuserd_dm_user.cpp",
+ "user-space-merge/snapuserd_merge.cpp",
+ "user-space-merge/snapuserd_readahead.cpp",
+ "user-space-merge/snapuserd_transitions.cpp",
+ "user-space-merge/snapuserd_server.cpp",
],
cflags: [
@@ -101,9 +108,10 @@
"fs_mgr_defaults",
],
srcs: [
- "cow_snapuserd_test.cpp",
- "snapuserd.cpp",
- "snapuserd_worker.cpp",
+ "dm-snapshot-merge/cow_snapuserd_test.cpp",
+ "dm-snapshot-merge/snapuserd.cpp",
+ "dm-snapshot-merge/snapuserd_worker.cpp",
+ "snapuserd_buffer.cpp",
],
cflags: [
"-Wall",
@@ -128,7 +136,9 @@
"libstorage_literals_headers",
"libfiemap_headers",
],
- test_min_api_level: 30,
+ test_options: {
+ min_shipping_api_level: 30,
+ },
auto_gen_config: true,
require_root: false,
}
diff --git a/fs_mgr/libsnapshot/snapuserd/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
similarity index 93%
rename from fs_mgr/libsnapshot/snapuserd/cow_snapuserd_test.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
index f4aef44..b86a802 100644
--- a/fs_mgr/libsnapshot/snapuserd/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
@@ -33,6 +33,7 @@
#include <libdm/dm.h>
#include <libdm/loop_control.h>
#include <libsnapshot/cow_writer.h>
+#include <snapuserd/snapuserd_buffer.h>
#include <snapuserd/snapuserd_client.h>
#include <storage_literals/storage_literals.h>
@@ -108,6 +109,7 @@
void MergeInterruptFixed(int duration);
void MergeInterruptRandomly(int max_duration);
void ReadDmUserBlockWithoutDaemon();
+ void ReadLastBlock();
std::string snapshot_dev() const { return snapshot_dev_->path(); }
@@ -256,6 +258,73 @@
}
}
+void CowSnapuserdTest::ReadLastBlock() {
+ unique_fd rnd_fd;
+ total_base_size_ = BLOCK_SZ * 2;
+
+ base_fd_ = CreateTempFile("base_device", total_base_size_);
+ ASSERT_GE(base_fd_, 0);
+
+ rnd_fd.reset(open("/dev/random", O_RDONLY));
+ ASSERT_TRUE(rnd_fd > 0);
+
+ std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(BLOCK_SZ);
+
+ for (size_t j = 0; j < ((total_base_size_) / BLOCK_SZ); j++) {
+ ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), BLOCK_SZ, 0), true);
+ ASSERT_EQ(android::base::WriteFully(base_fd_, random_buffer.get(), BLOCK_SZ), true);
+ }
+
+ ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0);
+
+ base_loop_ = std::make_unique<LoopDevice>(base_fd_, 10s);
+ ASSERT_TRUE(base_loop_->valid());
+
+ std::string path = android::base::GetExecutableDirectory();
+ cow_system_ = std::make_unique<TemporaryFile>(path);
+
+ std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(total_base_size_);
+ loff_t offset = 0;
+
+ // Fill random data
+ for (size_t j = 0; j < (total_base_size_ / BLOCK_SZ); j++) {
+ ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, BLOCK_SZ, 0),
+ true);
+
+ offset += BLOCK_SZ;
+ }
+
+ CowOptions options;
+ options.compression = "gz";
+ CowWriter writer(options);
+
+ ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+ ASSERT_TRUE(writer.AddRawBlocks(0, random_buffer_1_.get(), BLOCK_SZ));
+ ASSERT_TRUE(writer.AddRawBlocks(1, (char*)random_buffer_1_.get() + BLOCK_SZ, BLOCK_SZ));
+
+ ASSERT_TRUE(writer.Finalize());
+
+ SetDeviceControlName();
+
+ StartSnapuserdDaemon();
+ InitCowDevice();
+
+ CreateDmUserDevice();
+ InitDaemon();
+
+ CreateSnapshotDevice();
+
+ unique_fd snapshot_fd(open(snapshot_dev_->path().c_str(), O_RDONLY));
+ ASSERT_TRUE(snapshot_fd > 0);
+
+ std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(BLOCK_SZ);
+
+ offset = 7680;
+ ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), 512, offset), true);
+ ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)random_buffer_1_.get() + offset, 512), 0);
+}
+
void CowSnapuserdTest::CreateBaseDevice() {
unique_fd rnd_fd;
@@ -1143,6 +1212,12 @@
harness.Shutdown();
}
+TEST(Snapuserd_Test, Snapshot_END_IO_TEST) {
+ CowSnapuserdTest harness;
+ harness.ReadLastBlock();
+ harness.Shutdown();
+}
+
TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_1) {
CowSnapuserdTest harness;
ASSERT_TRUE(harness.SetupCopyOverlap_1());
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
similarity index 99%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
index 2abf431..5f4d706 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
@@ -691,7 +691,7 @@
}
void Snapuserd::ReadBlocksToCache(const std::string& dm_block_device,
- const std::string partition_name, off_t offset, size_t size) {
+ const std::string& partition_name, off_t offset, size_t size) {
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
if (fd.get() == -1) {
SNAP_PLOG(ERROR) << "Error reading " << dm_block_device
@@ -726,7 +726,7 @@
<< " offset: " << offset;
}
-void Snapuserd::ReadBlocks(const std::string partition_name, const std::string& dm_block_device) {
+void Snapuserd::ReadBlocks(const std::string& partition_name, const std::string& dm_block_device) {
SNAP_LOG(DEBUG) << "Reading partition: " << partition_name
<< " Block-Device: " << dm_block_device;
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd.h b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
similarity index 90%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd.h
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
index b30041d..47b9b22 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd.h
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
@@ -42,6 +42,7 @@
#include <libdm/dm.h>
#include <libsnapshot/cow_reader.h>
#include <libsnapshot/cow_writer.h>
+#include <snapuserd/snapuserd_buffer.h>
#include <snapuserd/snapuserd_kernel.h>
namespace android {
@@ -89,39 +90,6 @@
READ_AHEAD_FAILURE,
};
-class BufferSink : public IByteSink {
- public:
- void Initialize(size_t size);
- void* GetBufPtr() { return buffer_.get(); }
- void Clear() { memset(GetBufPtr(), 0, buffer_size_); }
- void* GetPayloadBuffer(size_t size);
- void* GetBuffer(size_t requested, size_t* actual) override;
- void UpdateBufferOffset(size_t size) { buffer_offset_ += size; }
- struct dm_user_header* GetHeaderPtr();
- bool ReturnData(void*, size_t) override { return true; }
- void ResetBufferOffset() { buffer_offset_ = 0; }
- void* GetPayloadBufPtr();
-
- private:
- std::unique_ptr<uint8_t[]> buffer_;
- loff_t buffer_offset_;
- size_t buffer_size_;
-};
-
-class XorSink : public IByteSink {
- public:
- void Initialize(BufferSink* sink, size_t size);
- void Reset();
- void* GetBuffer(size_t requested, size_t* actual) override;
- bool ReturnData(void* buffer, size_t len) override;
-
- private:
- BufferSink* bufsink_;
- std::unique_ptr<uint8_t[]> buffer_;
- size_t buffer_size_;
- size_t returned_;
-};
-
class Snapuserd;
class ReadAheadThread {
@@ -316,8 +284,8 @@
bool IsBlockAligned(int read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
struct BufferState* GetBufferState();
- void ReadBlocks(const std::string partition_name, const std::string& dm_block_device);
- void ReadBlocksToCache(const std::string& dm_block_device, const std::string partition_name,
+ void ReadBlocks(const std::string& partition_name, const std::string& dm_block_device);
+ void ReadBlocksToCache(const std::string& dm_block_device, const std::string& partition_name,
off_t offset, size_t size);
std::string cow_device_;
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
similarity index 98%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd_readahead.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
index b868eed..3bb7a0a 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
@@ -194,10 +194,12 @@
std::vector<uint64_t>& blocks) {
int num_ops = *pending_ops;
int nr_consecutive = 0;
+ CHECK_NE(source_offset, nullptr);
if (!RAIterDone() && num_ops) {
// Get the first block with offset
const CowOperation* cow_op = GetRAOpIter();
+ CHECK_NE(cow_op, nullptr);
*source_offset = cow_op->source;
if (cow_op->type == kCowCopyOp) {
*source_offset *= BLOCK_SZ;
@@ -216,11 +218,12 @@
*/
while (!RAIterDone() && num_ops) {
const CowOperation* op = GetRAOpIter();
+ CHECK_NE(op, nullptr);
uint64_t next_offset = op->source;
- if (cow_op->type == kCowCopyOp) {
+ if (op->type == kCowCopyOp) {
next_offset *= BLOCK_SZ;
}
- if (next_offset != (*source_offset - nr_consecutive * BLOCK_SZ)) {
+ if (next_offset + nr_consecutive * BLOCK_SZ != *source_offset) {
break;
}
nr_consecutive += 1;
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp
similarity index 99%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp
index 2f87557..9ddc963 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.cpp
@@ -31,7 +31,7 @@
#include <android-base/scopeguard.h>
#include <fs_mgr/file_wait.h>
#include <snapuserd/snapuserd_client.h>
-#include "snapuserd.h"
+
#include "snapuserd_server.h"
#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.h
similarity index 100%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd_server.h
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_server.h
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
similarity index 93%
rename from fs_mgr/libsnapshot/snapuserd/snapuserd_worker.cpp
rename to fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
index cdf9fe7..0e9f0f1 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
@@ -32,78 +32,6 @@
#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
-void BufferSink::Initialize(size_t size) {
- buffer_size_ = size;
- buffer_offset_ = 0;
- buffer_ = std::make_unique<uint8_t[]>(size);
-}
-
-void* BufferSink::GetPayloadBuffer(size_t size) {
- if ((buffer_size_ - buffer_offset_) < size) return nullptr;
-
- char* buffer = reinterpret_cast<char*>(GetBufPtr());
- struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
- return (char*)msg->payload.buf + buffer_offset_;
-}
-
-void* BufferSink::GetBuffer(size_t requested, size_t* actual) {
- void* buf = GetPayloadBuffer(requested);
- if (!buf) {
- *actual = 0;
- return nullptr;
- }
- *actual = requested;
- return buf;
-}
-
-struct dm_user_header* BufferSink::GetHeaderPtr() {
- if (!(sizeof(struct dm_user_header) <= buffer_size_)) {
- return nullptr;
- }
- char* buf = reinterpret_cast<char*>(GetBufPtr());
- struct dm_user_header* header = (struct dm_user_header*)(&(buf[0]));
- return header;
-}
-
-void* BufferSink::GetPayloadBufPtr() {
- char* buffer = reinterpret_cast<char*>(GetBufPtr());
- struct dm_user_message* msg = reinterpret_cast<struct dm_user_message*>(&(buffer[0]));
- return msg->payload.buf;
-}
-
-void XorSink::Initialize(BufferSink* sink, size_t size) {
- bufsink_ = sink;
- buffer_size_ = size;
- returned_ = 0;
- buffer_ = std::make_unique<uint8_t[]>(size);
-}
-
-void XorSink::Reset() {
- returned_ = 0;
-}
-
-void* XorSink::GetBuffer(size_t requested, size_t* actual) {
- if (requested > buffer_size_) {
- *actual = buffer_size_;
- } else {
- *actual = requested;
- }
- return buffer_.get();
-}
-
-bool XorSink::ReturnData(void* buffer, size_t len) {
- uint8_t* xor_data = reinterpret_cast<uint8_t*>(buffer);
- uint8_t* buff = reinterpret_cast<uint8_t*>(bufsink_->GetPayloadBuffer(len + returned_));
- if (buff == nullptr) {
- return false;
- }
- for (size_t i = 0; i < len; i++) {
- buff[returned_ + i] ^= xor_data[i];
- }
- returned_ += len;
- return true;
-}
-
WorkerThread::WorkerThread(const std::string& cow_device, const std::string& backing_device,
const std::string& control_device, const std::string& misc_name,
std::shared_ptr<Snapuserd> snapuserd) {
@@ -350,16 +278,36 @@
it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr),
Snapuserd::compare);
- if (!(it != chunk_vec.end())) {
- SNAP_LOG(ERROR) << "ReadData: Sector " << sector << " not found in chunk_vec";
- return -1;
+ bool read_end_of_device = false;
+ if (it == chunk_vec.end()) {
+ // |-------|-------|-------|
+ // 0 1 2 3
+ //
+ // Block 0 - op 1
+ // Block 1 - op 2
+ // Block 2 - op 3
+ //
+ // chunk_vec will have block 0, 1, 2 which maps to relavant COW ops.
+ //
+ // Each block is 4k bytes. Thus, the last block will span 8 sectors
+ // ranging till block 3 (However, block 3 won't be in chunk_vec as
+ // it doesn't have any mapping to COW ops. Now, if we get an I/O request for a sector
+ // spanning between block 2 and block 3, we need to step back
+ // and get hold of the last element.
+ //
+ // Additionally, dm-snapshot makes sure that I/O request beyond block 3
+ // will not be routed to the daemon. Hence, it is safe to assume that
+ // if a sector is not available in the chunk_vec, the I/O falls in the
+ // end of region.
+ it = std::prev(chunk_vec.end());
+ read_end_of_device = true;
}
// We didn't find the required sector; hence find the previous sector
// as lower_bound will gives us the value greater than
// the requested sector
if (it->first != sector) {
- if (it != chunk_vec.begin()) {
+ if (it != chunk_vec.begin() && !read_end_of_device) {
--it;
}
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
new file mode 100644
index 0000000..2e4cac6
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <linux/types.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <iostream>
+
+#include <libsnapshot/cow_reader.h>
+
+namespace android {
+namespace snapshot {
+
+class BufferSink : public IByteSink {
+ public:
+ void Initialize(size_t size);
+ void* GetBufPtr() { return buffer_.get(); }
+ void Clear() { memset(GetBufPtr(), 0, buffer_size_); }
+ void* GetPayloadBuffer(size_t size);
+ void* GetBuffer(size_t requested, size_t* actual) override;
+ void UpdateBufferOffset(size_t size) { buffer_offset_ += size; }
+ struct dm_user_header* GetHeaderPtr();
+ bool ReturnData(void*, size_t) override { return true; }
+ void ResetBufferOffset() { buffer_offset_ = 0; }
+ void* GetPayloadBufPtr();
+
+ private:
+ std::unique_ptr<uint8_t[]> buffer_;
+ loff_t buffer_offset_;
+ size_t buffer_size_;
+};
+
+class XorSink : public IByteSink {
+ public:
+ void Initialize(BufferSink* sink, size_t size);
+ void Reset();
+ void* GetBuffer(size_t requested, size_t* actual) override;
+ bool ReturnData(void* buffer, size_t len) override;
+
+ private:
+ BufferSink* bufsink_;
+ std::unique_ptr<uint8_t[]> buffer_;
+ size_t buffer_size_;
+ size_t returned_;
+};
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
index aeecf41..6ed55af 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
@@ -79,6 +79,15 @@
// Returns true if the snapuserd instance supports bridging a socket to second-stage init.
bool SupportsSecondStageSocketHandoff();
+
+ // Returns true if the merge is started(or resumed from crash).
+ bool InitiateMerge(const std::string& misc_name);
+
+ // Returns Merge completion percentage
+ double GetMergePercent();
+
+ // Return the status of the snapshot
+ std::string QuerySnapshotStatus(const std::string& misc_name);
};
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_buffer.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_buffer.cpp
new file mode 100644
index 0000000..ab763ab
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_buffer.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <snapuserd/snapuserd_buffer.h>
+#include <snapuserd/snapuserd_kernel.h>
+
+namespace android {
+namespace snapshot {
+
+void BufferSink::Initialize(size_t size) {
+ buffer_size_ = size;
+ buffer_offset_ = 0;
+ buffer_ = std::make_unique<uint8_t[]>(size);
+}
+
+void* BufferSink::GetPayloadBuffer(size_t size) {
+ if ((buffer_size_ - buffer_offset_) < size) return nullptr;
+
+ char* buffer = reinterpret_cast<char*>(GetBufPtr());
+ struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
+ return (char*)msg->payload.buf + buffer_offset_;
+}
+
+void* BufferSink::GetBuffer(size_t requested, size_t* actual) {
+ void* buf = GetPayloadBuffer(requested);
+ if (!buf) {
+ *actual = 0;
+ return nullptr;
+ }
+ *actual = requested;
+ return buf;
+}
+
+struct dm_user_header* BufferSink::GetHeaderPtr() {
+ if (!(sizeof(struct dm_user_header) <= buffer_size_)) {
+ return nullptr;
+ }
+ char* buf = reinterpret_cast<char*>(GetBufPtr());
+ struct dm_user_header* header = (struct dm_user_header*)(&(buf[0]));
+ return header;
+}
+
+void* BufferSink::GetPayloadBufPtr() {
+ char* buffer = reinterpret_cast<char*>(GetBufPtr());
+ struct dm_user_message* msg = reinterpret_cast<struct dm_user_message*>(&(buffer[0]));
+ return msg->payload.buf;
+}
+
+void XorSink::Initialize(BufferSink* sink, size_t size) {
+ bufsink_ = sink;
+ buffer_size_ = size;
+ returned_ = 0;
+ buffer_ = std::make_unique<uint8_t[]>(size);
+}
+
+void XorSink::Reset() {
+ returned_ = 0;
+}
+
+void* XorSink::GetBuffer(size_t requested, size_t* actual) {
+ if (requested > buffer_size_) {
+ *actual = buffer_size_;
+ } else {
+ *actual = requested;
+ }
+ return buffer_.get();
+}
+
+bool XorSink::ReturnData(void* buffer, size_t len) {
+ uint8_t* xor_data = reinterpret_cast<uint8_t*>(buffer);
+ uint8_t* buff = reinterpret_cast<uint8_t*>(bufsink_->GetPayloadBuffer(len + returned_));
+ if (buff == nullptr) {
+ return false;
+ }
+ for (size_t i = 0; i < len; i++) {
+ buff[returned_ + i] ^= xor_data[i];
+ }
+ returned_ += len;
+ return true;
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
index 1ea05a3..e345269 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
@@ -231,5 +231,35 @@
return true;
}
+bool SnapuserdClient::InitiateMerge(const std::string& misc_name) {
+ std::string msg = "initiate_merge," + misc_name;
+ if (!Sendmsg(msg)) {
+ LOG(ERROR) << "Failed to send message " << msg << " to snapuserd";
+ return false;
+ }
+ std::string response = Receivemsg();
+ return response == "success";
+}
+
+double SnapuserdClient::GetMergePercent() {
+ std::string msg = "merge_percent";
+ if (!Sendmsg(msg)) {
+ LOG(ERROR) << "Failed to send message " << msg << " to snapuserd";
+ return false;
+ }
+ std::string response = Receivemsg();
+
+ return std::stod(response);
+}
+
+std::string SnapuserdClient::QuerySnapshotStatus(const std::string& misc_name) {
+ std::string msg = "getstatus," + misc_name;
+ if (!Sendmsg(msg)) {
+ LOG(ERROR) << "Failed to send message " << msg << " to snapuserd";
+ return "snapshot-merge-failed";
+ }
+ return Receivemsg();
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index e05822e..912884f 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -21,8 +21,6 @@
#include <gflags/gflags.h>
#include <snapuserd/snapuserd_client.h>
-#include "snapuserd_server.h"
-
DEFINE_string(socket, android::snapshot::kSnapuserdSocket, "Named socket or socket path.");
DEFINE_bool(no_socket, false,
"If true, no socket is used. Each additional argument is an INIT message.");
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
index b660ba2..fbf57d9 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
@@ -19,7 +19,7 @@
#include <string>
#include <vector>
-#include "snapuserd_server.h"
+#include "dm-snapshot-merge/snapuserd_server.h"
namespace android {
namespace snapshot {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
new file mode 100644
index 0000000..57e47e7
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -0,0 +1,517 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd_core.h"
+
+#include <android-base/strings.h>
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+SnapshotHandler::SnapshotHandler(std::string misc_name, std::string cow_device,
+ std::string backing_device, std::string base_path_merge) {
+ misc_name_ = std::move(misc_name);
+ cow_device_ = std::move(cow_device);
+ backing_store_device_ = std::move(backing_device);
+ control_device_ = "/dev/dm-user/" + misc_name_;
+ base_path_merge_ = std::move(base_path_merge);
+}
+
+bool SnapshotHandler::InitializeWorkers() {
+ for (int i = 0; i < NUM_THREADS_PER_PARTITION; i++) {
+ std::unique_ptr<Worker> wt =
+ std::make_unique<Worker>(cow_device_, backing_store_device_, control_device_,
+ misc_name_, base_path_merge_, GetSharedPtr());
+ if (!wt->Init()) {
+ SNAP_LOG(ERROR) << "Thread initialization failed";
+ return false;
+ }
+
+ worker_threads_.push_back(std::move(wt));
+ }
+
+ merge_thread_ = std::make_unique<Worker>(cow_device_, backing_store_device_, control_device_,
+ misc_name_, base_path_merge_, GetSharedPtr());
+
+ read_ahead_thread_ = std::make_unique<ReadAhead>(cow_device_, backing_store_device_, misc_name_,
+ GetSharedPtr());
+ return true;
+}
+
+std::unique_ptr<CowReader> SnapshotHandler::CloneReaderForWorker() {
+ return reader_->CloneCowReader();
+}
+
+void SnapshotHandler::UpdateMergeCompletionPercentage() {
+ struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+ merge_completion_percentage_ = (ch->num_merge_ops * 100.0) / reader_->get_num_total_data_ops();
+
+ SNAP_LOG(DEBUG) << "Merge-complete %: " << merge_completion_percentage_
+ << " num_merge_ops: " << ch->num_merge_ops
+ << " total-ops: " << reader_->get_num_total_data_ops();
+}
+
+bool SnapshotHandler::CommitMerge(int num_merge_ops) {
+ struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+ ch->num_merge_ops += num_merge_ops;
+
+ if (scratch_space_) {
+ if (ra_thread_) {
+ struct BufferState* ra_state = GetBufferState();
+ ra_state->read_ahead_state = kCowReadAheadInProgress;
+ }
+
+ int ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
+ if (ret < 0) {
+ SNAP_PLOG(ERROR) << "msync header failed: " << ret;
+ return false;
+ }
+ } else {
+ reader_->UpdateMergeOpsCompleted(num_merge_ops);
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ if (lseek(cow_fd_.get(), 0, SEEK_SET) < 0) {
+ SNAP_PLOG(ERROR) << "lseek failed";
+ return false;
+ }
+
+ if (!android::base::WriteFully(cow_fd_, &header, sizeof(CowHeader))) {
+ SNAP_PLOG(ERROR) << "Write to header failed";
+ return false;
+ }
+
+ if (fsync(cow_fd_.get()) < 0) {
+ SNAP_PLOG(ERROR) << "fsync failed";
+ return false;
+ }
+ }
+
+ // Update the merge completion - this is used by update engine
+ // to track the completion. No need to take a lock. It is ok
+ // even if there is a miss on reading a latest updated value.
+ // Subsequent polling will eventually converge to completion.
+ UpdateMergeCompletionPercentage();
+
+ return true;
+}
+
+void SnapshotHandler::PrepareReadAhead() {
+ struct BufferState* ra_state = GetBufferState();
+ // Check if the data has to be re-constructed from COW device
+ if (ra_state->read_ahead_state == kCowReadAheadDone) {
+ populate_data_from_cow_ = true;
+ } else {
+ populate_data_from_cow_ = false;
+ }
+
+ NotifyRAForMergeReady();
+}
+
+void SnapshotHandler::CheckMergeCompletionStatus() {
+ if (!merge_initiated_) {
+ SNAP_LOG(INFO) << "Merge was not initiated. Total-data-ops: "
+ << reader_->get_num_total_data_ops();
+ return;
+ }
+
+ struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+
+ SNAP_LOG(INFO) << "Merge-status: Total-Merged-ops: " << ch->num_merge_ops
+ << " Total-data-ops: " << reader_->get_num_total_data_ops();
+}
+
+bool SnapshotHandler::ReadMetadata() {
+ reader_ = std::make_unique<CowReader>(CowReader::ReaderFlags::USERSPACE_MERGE);
+ CowHeader header;
+ CowOptions options;
+
+ SNAP_LOG(DEBUG) << "ReadMetadata: Parsing cow file";
+
+ if (!reader_->Parse(cow_fd_)) {
+ SNAP_LOG(ERROR) << "Failed to parse";
+ return false;
+ }
+
+ if (!reader_->GetHeader(&header)) {
+ SNAP_LOG(ERROR) << "Failed to get header";
+ return false;
+ }
+
+ if (!(header.block_size == BLOCK_SZ)) {
+ SNAP_LOG(ERROR) << "Invalid header block size found: " << header.block_size;
+ return false;
+ }
+
+ SNAP_LOG(INFO) << "Merge-ops: " << header.num_merge_ops;
+
+ if (!MmapMetadata()) {
+ SNAP_LOG(ERROR) << "mmap failed";
+ return false;
+ }
+
+ UpdateMergeCompletionPercentage();
+
+ // Initialize the iterator for reading metadata
+ std::unique_ptr<ICowOpIter> cowop_iter = reader_->GetMergeOpIter();
+
+ int num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ);
+ int ra_index = 0;
+
+ size_t copy_ops = 0, replace_ops = 0, zero_ops = 0, xor_ops = 0;
+
+ while (!cowop_iter->Done()) {
+ const CowOperation* cow_op = &cowop_iter->Get();
+
+ if (cow_op->type == kCowCopyOp) {
+ copy_ops += 1;
+ } else if (cow_op->type == kCowReplaceOp) {
+ replace_ops += 1;
+ } else if (cow_op->type == kCowZeroOp) {
+ zero_ops += 1;
+ } else if (cow_op->type == kCowXorOp) {
+ xor_ops += 1;
+ }
+
+ chunk_vec_.push_back(std::make_pair(ChunkToSector(cow_op->new_block), cow_op));
+
+ if (IsOrderedOp(*cow_op)) {
+ ra_thread_ = true;
+ block_to_ra_index_[cow_op->new_block] = ra_index;
+ num_ra_ops_per_iter -= 1;
+
+ if ((ra_index + 1) - merge_blk_state_.size() == 1) {
+ std::unique_ptr<MergeGroupState> blk_state = std::make_unique<MergeGroupState>(
+ MERGE_GROUP_STATE::GROUP_MERGE_PENDING, 0);
+
+ merge_blk_state_.push_back(std::move(blk_state));
+ }
+
+ // Move to next RA block
+ if (num_ra_ops_per_iter == 0) {
+ num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ);
+ ra_index += 1;
+ }
+ }
+ cowop_iter->Next();
+ }
+
+ chunk_vec_.shrink_to_fit();
+
+ // Sort the vector based on sectors as we need this during un-aligned access
+ std::sort(chunk_vec_.begin(), chunk_vec_.end(), compare);
+
+ PrepareReadAhead();
+
+ SNAP_LOG(INFO) << "Merged-ops: " << header.num_merge_ops
+ << " Total-data-ops: " << reader_->get_num_total_data_ops()
+ << " Unmerged-ops: " << chunk_vec_.size() << " Copy-ops: " << copy_ops
+ << " Zero-ops: " << zero_ops << " Replace-ops: " << replace_ops
+ << " Xor-ops: " << xor_ops;
+
+ return true;
+}
+
+bool SnapshotHandler::MmapMetadata() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
+
+ if (header.major_version >= 2 && header.buffer_size > 0) {
+ scratch_space_ = true;
+ }
+
+ if (scratch_space_) {
+ mapped_addr_ = mmap(NULL, total_mapped_addr_length_, PROT_READ | PROT_WRITE, MAP_SHARED,
+ cow_fd_.get(), 0);
+ } else {
+ mapped_addr_ = mmap(NULL, total_mapped_addr_length_, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+ ch->num_merge_ops = header.num_merge_ops;
+ }
+
+ if (mapped_addr_ == MAP_FAILED) {
+ SNAP_LOG(ERROR) << "mmap metadata failed";
+ return false;
+ }
+
+ return true;
+}
+
+void SnapshotHandler::UnmapBufferRegion() {
+ int ret = munmap(mapped_addr_, total_mapped_addr_length_);
+ if (ret < 0) {
+ SNAP_PLOG(ERROR) << "munmap failed";
+ }
+}
+
+bool SnapshotHandler::InitCowDevice() {
+ cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
+ if (cow_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
+ return false;
+ }
+
+ unique_fd fd(TEMP_FAILURE_RETRY(open(base_path_merge_.c_str(), O_RDONLY | O_CLOEXEC)));
+ if (fd < 0) {
+ SNAP_LOG(ERROR) << "Cannot open block device";
+ return false;
+ }
+
+ uint64_t dev_sz = get_block_device_size(fd.get());
+ if (!dev_sz) {
+ SNAP_LOG(ERROR) << "Failed to find block device size: " << base_path_merge_;
+ return false;
+ }
+
+ num_sectors_ = dev_sz >> SECTOR_SHIFT;
+
+ return ReadMetadata();
+}
+
+void SnapshotHandler::ReadBlocksToCache(const std::string& dm_block_device,
+ const std::string& partition_name, off_t offset,
+ size_t size) {
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
+ if (fd.get() == -1) {
+ SNAP_PLOG(ERROR) << "Error reading " << dm_block_device
+ << " partition-name: " << partition_name;
+ return;
+ }
+
+ size_t remain = size;
+ off_t file_offset = offset;
+ // We pick 4M I/O size based on the fact that the current
+ // update_verifier has a similar I/O size.
+ size_t read_sz = 1024 * BLOCK_SZ;
+ std::vector<uint8_t> buf(read_sz);
+
+ while (remain > 0) {
+ size_t to_read = std::min(remain, read_sz);
+
+ if (!android::base::ReadFullyAtOffset(fd.get(), buf.data(), to_read, file_offset)) {
+ SNAP_PLOG(ERROR) << "Failed to read block from block device: " << dm_block_device
+ << " at offset: " << file_offset
+ << " partition-name: " << partition_name << " total-size: " << size
+ << " remain_size: " << remain;
+ return;
+ }
+
+ file_offset += to_read;
+ remain -= to_read;
+ }
+
+ SNAP_LOG(INFO) << "Finished reading block-device: " << dm_block_device
+ << " partition: " << partition_name << " size: " << size
+ << " offset: " << offset;
+}
+
+void SnapshotHandler::ReadBlocks(const std::string partition_name,
+ const std::string& dm_block_device) {
+ SNAP_LOG(DEBUG) << "Reading partition: " << partition_name
+ << " Block-Device: " << dm_block_device;
+
+ uint64_t dev_sz = 0;
+
+ unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY | O_CLOEXEC)));
+ if (fd < 0) {
+ SNAP_LOG(ERROR) << "Cannot open block device";
+ return;
+ }
+
+ dev_sz = get_block_device_size(fd.get());
+ if (!dev_sz) {
+ SNAP_PLOG(ERROR) << "Could not determine block device size: " << dm_block_device;
+ return;
+ }
+
+ int num_threads = 2;
+ size_t num_blocks = dev_sz >> BLOCK_SHIFT;
+ size_t num_blocks_per_thread = num_blocks / num_threads;
+ size_t read_sz_per_thread = num_blocks_per_thread << BLOCK_SHIFT;
+ off_t offset = 0;
+
+ for (int i = 0; i < num_threads; i++) {
+ std::async(std::launch::async, &SnapshotHandler::ReadBlocksToCache, this, dm_block_device,
+ partition_name, offset, read_sz_per_thread);
+
+ offset += read_sz_per_thread;
+ }
+}
+
+/*
+ * Entry point to launch threads
+ */
+bool SnapshotHandler::Start() {
+ std::vector<std::future<bool>> threads;
+ std::future<bool> ra_thread_status;
+
+ if (ra_thread_) {
+ ra_thread_status =
+ std::async(std::launch::async, &ReadAhead::RunThread, read_ahead_thread_.get());
+
+ SNAP_LOG(INFO) << "Read-ahead thread started...";
+ }
+
+ // Launch worker threads
+ for (int i = 0; i < worker_threads_.size(); i++) {
+ threads.emplace_back(
+ std::async(std::launch::async, &Worker::RunThread, worker_threads_[i].get()));
+ }
+
+ bool second_stage_init = true;
+
+ // We don't want to read the blocks during first stage init.
+ if (android::base::EndsWith(misc_name_, "-init") || is_socket_present_) {
+ second_stage_init = false;
+ }
+
+ if (second_stage_init) {
+ SNAP_LOG(INFO) << "Reading blocks to cache....";
+ auto& dm = DeviceMapper::Instance();
+ auto dm_block_devices = dm.FindDmPartitions();
+ if (dm_block_devices.empty()) {
+ SNAP_LOG(ERROR) << "No dm-enabled block device is found.";
+ } else {
+ auto parts = android::base::Split(misc_name_, "-");
+ std::string partition_name = parts[0];
+
+ const char* suffix_b = "_b";
+ const char* suffix_a = "_a";
+
+ partition_name.erase(partition_name.find_last_not_of(suffix_b) + 1);
+ partition_name.erase(partition_name.find_last_not_of(suffix_a) + 1);
+
+ if (dm_block_devices.find(partition_name) == dm_block_devices.end()) {
+ SNAP_LOG(ERROR) << "Failed to find dm block device for " << partition_name;
+ } else {
+ ReadBlocks(partition_name, dm_block_devices.at(partition_name));
+ }
+ }
+ } else {
+ SNAP_LOG(INFO) << "Not reading block device into cache";
+ }
+
+ std::future<bool> merge_thread =
+ std::async(std::launch::async, &Worker::RunMergeThread, merge_thread_.get());
+
+ bool ret = true;
+ for (auto& t : threads) {
+ ret = t.get() && ret;
+ }
+
+ // Worker threads are terminated by this point - this can only happen:
+ //
+ // 1: If dm-user device is destroyed
+ // 2: We had an I/O failure when reading root partitions
+ //
+ // In case (1), this would be a graceful shutdown. In this case, merge
+ // thread and RA thread should have already terminated by this point. We will be
+ // destroying the dm-user device only _after_ merge is completed.
+ //
+ // In case (2), if merge thread had started, then it will be
+ // continuing to merge; however, since we had an I/O failure and the
+ // I/O on root partitions are no longer served, we will terminate the
+ // merge
+
+ NotifyIOTerminated();
+
+ bool read_ahead_retval = false;
+
+ SNAP_LOG(INFO) << "Snapshot I/O terminated. Waiting for merge thread....";
+ bool merge_thread_status = merge_thread.get();
+
+ if (ra_thread_) {
+ read_ahead_retval = ra_thread_status.get();
+ }
+
+ SNAP_LOG(INFO) << "Worker threads terminated with ret: " << ret
+ << " Merge-thread with ret: " << merge_thread_status
+ << " RA-thread with ret: " << read_ahead_retval;
+ return ret;
+}
+
+uint64_t SnapshotHandler::GetBufferMetadataOffset() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ return (header.header_size + sizeof(BufferState));
+}
+
+/*
+ * Metadata for read-ahead is 16 bytes. For a 2 MB region, we will
+ * end up with 8k (2 PAGE) worth of metadata. Thus, a 2MB buffer
+ * region is split into:
+ *
+ * 1: 8k metadata
+ * 2: Scratch space
+ *
+ */
+size_t SnapshotHandler::GetBufferMetadataSize() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+ size_t buffer_size = header.buffer_size;
+
+ // If there is no scratch space, then just use the
+ // anonymous memory
+ if (buffer_size == 0) {
+ buffer_size = BUFFER_REGION_DEFAULT_SIZE;
+ }
+
+ return ((buffer_size * sizeof(struct ScratchMetadata)) / BLOCK_SZ);
+}
+
+size_t SnapshotHandler::GetBufferDataOffset() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ return (header.header_size + GetBufferMetadataSize());
+}
+
+/*
+ * (2MB - 8K = 2088960 bytes) will be the buffer region to hold the data.
+ */
+size_t SnapshotHandler::GetBufferDataSize() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+ size_t buffer_size = header.buffer_size;
+
+ // If there is no scratch space, then just use the
+ // anonymous memory
+ if (buffer_size == 0) {
+ buffer_size = BUFFER_REGION_DEFAULT_SIZE;
+ }
+
+ return (buffer_size - GetBufferMetadataSize());
+}
+
+struct BufferState* SnapshotHandler::GetBufferState() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ struct BufferState* ra_state =
+ reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
+ return ra_state;
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
new file mode 100644
index 0000000..13b56fa
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -0,0 +1,358 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <linux/types.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+
+#include <condition_variable>
+#include <cstring>
+#include <future>
+#include <iostream>
+#include <limits>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
+#include <libdm/dm.h>
+#include <libsnapshot/cow_reader.h>
+#include <libsnapshot/cow_writer.h>
+#include <snapuserd/snapuserd_buffer.h>
+#include <snapuserd/snapuserd_kernel.h>
+
+namespace android {
+namespace snapshot {
+
+using android::base::unique_fd;
+using namespace std::chrono_literals;
+
+static constexpr size_t PAYLOAD_SIZE = (1UL << 20);
+static_assert(PAYLOAD_SIZE >= BLOCK_SZ);
+
+static constexpr int NUM_THREADS_PER_PARTITION = 1;
+
+#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
+#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
+
+enum class MERGE_IO_TRANSITION {
+ MERGE_READY,
+ MERGE_BEGIN,
+ MERGE_FAILED,
+ MERGE_COMPLETE,
+ IO_TERMINATED,
+ READ_AHEAD_FAILURE,
+};
+
+class SnapshotHandler;
+
+enum class MERGE_GROUP_STATE {
+ GROUP_MERGE_PENDING,
+ GROUP_MERGE_RA_READY,
+ GROUP_MERGE_IN_PROGRESS,
+ GROUP_MERGE_COMPLETED,
+ GROUP_MERGE_FAILED,
+ GROUP_INVALID,
+};
+
+struct MergeGroupState {
+ MERGE_GROUP_STATE merge_state_;
+ // Ref count I/O when group state
+ // is in "GROUP_MERGE_PENDING"
+ size_t num_ios_in_progress;
+ std::mutex m_lock;
+ std::condition_variable m_cv;
+
+ MergeGroupState(MERGE_GROUP_STATE state, size_t n_ios)
+ : merge_state_(state), num_ios_in_progress(n_ios) {}
+};
+
+class ReadAhead {
+ public:
+ ReadAhead(const std::string& cow_device, const std::string& backing_device,
+ const std::string& misc_name, std::shared_ptr<SnapshotHandler> snapuserd);
+ bool RunThread();
+
+ private:
+ void InitializeRAIter();
+ bool RAIterDone();
+ void RAIterNext();
+ const CowOperation* GetRAOpIter();
+
+ void InitializeBuffer();
+ bool InitReader();
+ bool InitializeFds();
+
+ void CloseFds() { backing_store_fd_ = {}; }
+
+ bool ReadAheadIOStart();
+ int PrepareNextReadAhead(uint64_t* source_offset, int* pending_ops,
+ std::vector<uint64_t>& blocks,
+ std::vector<const CowOperation*>& xor_op_vec);
+ bool ReconstructDataFromCow();
+ void CheckOverlap(const CowOperation* cow_op);
+
+ void* read_ahead_buffer_;
+ void* metadata_buffer_;
+
+ std::unique_ptr<ICowOpIter> cowop_iter_;
+
+ std::string cow_device_;
+ std::string backing_store_device_;
+ std::string misc_name_;
+
+ unique_fd cow_fd_;
+ unique_fd backing_store_fd_;
+
+ std::shared_ptr<SnapshotHandler> snapuserd_;
+ std::unique_ptr<CowReader> reader_;
+
+ std::unordered_set<uint64_t> dest_blocks_;
+ std::unordered_set<uint64_t> source_blocks_;
+ bool overlap_;
+ BufferSink bufsink_;
+};
+
+class Worker {
+ public:
+ Worker(const std::string& cow_device, const std::string& backing_device,
+ const std::string& control_device, const std::string& misc_name,
+ const std::string& base_path_merge, std::shared_ptr<SnapshotHandler> snapuserd);
+ bool RunThread();
+ bool RunMergeThread();
+ bool Init();
+
+ private:
+ // Initialization
+ void InitializeBufsink();
+ bool InitializeFds();
+ bool InitReader();
+ void CloseFds() {
+ ctrl_fd_ = {};
+ backing_store_fd_ = {};
+ base_path_merge_fd_ = {};
+ }
+
+ // Functions interacting with dm-user
+ bool ReadDmUserHeader();
+ bool WriteDmUserPayload(size_t size, bool header_response);
+ bool DmuserReadRequest();
+
+ // IO Path
+ bool ProcessIORequest();
+ bool IsBlockAligned(size_t size) { return ((size & (BLOCK_SZ - 1)) == 0); }
+
+ bool ReadDataFromBaseDevice(sector_t sector, size_t read_size);
+ bool ReadFromSourceDevice(const CowOperation* cow_op);
+
+ bool ReadAlignedSector(sector_t sector, size_t sz, bool header_response);
+ bool ReadUnalignedSector(sector_t sector, size_t size);
+ int ReadUnalignedSector(sector_t sector, size_t size,
+ std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it);
+ bool RespondIOError(bool header_response);
+
+ // Processing COW operations
+ bool ProcessCowOp(const CowOperation* cow_op);
+ bool ProcessReplaceOp(const CowOperation* cow_op);
+ bool ProcessZeroOp();
+
+ // Handles Copy and Xor
+ bool ProcessCopyOp(const CowOperation* cow_op);
+ bool ProcessXorOp(const CowOperation* cow_op);
+ bool ProcessOrderedOp(const CowOperation* cow_op);
+
+ // Merge related ops
+ bool Merge();
+ bool MergeOrderedOps(const std::unique_ptr<ICowOpIter>& cowop_iter);
+ bool MergeReplaceZeroOps(const std::unique_ptr<ICowOpIter>& cowop_iter);
+ int PrepareMerge(uint64_t* source_offset, int* pending_ops,
+ const std::unique_ptr<ICowOpIter>& cowop_iter,
+ std::vector<const CowOperation*>* replace_zero_vec = nullptr);
+
+ sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
+ chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
+
+ std::unique_ptr<CowReader> reader_;
+ BufferSink bufsink_;
+ XorSink xorsink_;
+
+ std::string cow_device_;
+ std::string backing_store_device_;
+ std::string control_device_;
+ std::string misc_name_;
+ std::string base_path_merge_;
+
+ unique_fd cow_fd_;
+ unique_fd backing_store_fd_;
+ unique_fd base_path_merge_fd_;
+ unique_fd ctrl_fd_;
+
+ std::shared_ptr<SnapshotHandler> snapuserd_;
+};
+
+class SnapshotHandler : public std::enable_shared_from_this<SnapshotHandler> {
+ public:
+ SnapshotHandler(std::string misc_name, std::string cow_device, std::string backing_device,
+ std::string base_path_merge);
+ bool InitCowDevice();
+ bool Start();
+
+ const std::string& GetControlDevicePath() { return control_device_; }
+ const std::string& GetMiscName() { return misc_name_; }
+ const uint64_t& GetNumSectors() { return num_sectors_; }
+ const bool& IsAttached() const { return attached_; }
+ void AttachControlDevice() { attached_ = true; }
+
+ void CheckMergeCompletionStatus();
+ bool CommitMerge(int num_merge_ops);
+
+ void CloseFds() { cow_fd_ = {}; }
+ void FreeResources() {
+ worker_threads_.clear();
+ read_ahead_thread_ = nullptr;
+ merge_thread_ = nullptr;
+ }
+
+ bool InitializeWorkers();
+ std::unique_ptr<CowReader> CloneReaderForWorker();
+ std::shared_ptr<SnapshotHandler> GetSharedPtr() { return shared_from_this(); }
+
+ std::vector<std::pair<sector_t, const CowOperation*>>& GetChunkVec() { return chunk_vec_; }
+
+ static bool compare(std::pair<sector_t, const CowOperation*> p1,
+ std::pair<sector_t, const CowOperation*> p2) {
+ return p1.first < p2.first;
+ }
+
+ void UnmapBufferRegion();
+ bool MmapMetadata();
+
+ // Read-ahead related functions
+ void* GetMappedAddr() { return mapped_addr_; }
+ void PrepareReadAhead();
+ std::unordered_map<uint64_t, void*>& GetReadAheadMap() { return read_ahead_buffer_map_; }
+
+ // State transitions for merge
+ void InitiateMerge();
+ void WaitForMergeComplete();
+ bool WaitForMergeBegin();
+ void NotifyRAForMergeReady();
+ bool WaitForMergeReady();
+ void MergeFailed();
+ bool IsIOTerminated();
+ void MergeCompleted();
+ void NotifyIOTerminated();
+ bool ReadAheadIOCompleted(bool sync);
+ void ReadAheadIOFailed();
+
+ bool ShouldReconstructDataFromCow() { return populate_data_from_cow_; }
+ void FinishReconstructDataFromCow() { populate_data_from_cow_ = false; }
+ // Return the snapshot status
+ std::string GetMergeStatus();
+
+ // RA related functions
+ uint64_t GetBufferMetadataOffset();
+ size_t GetBufferMetadataSize();
+ size_t GetBufferDataOffset();
+ size_t GetBufferDataSize();
+
+ // Total number of blocks to be merged in a given read-ahead buffer region
+ void SetMergedBlockCountForNextCommit(int x) { total_ra_blocks_merged_ = x; }
+ int GetTotalBlocksToMerge() { return total_ra_blocks_merged_; }
+ void SetSocketPresent(bool socket) { is_socket_present_ = socket; }
+ bool MergeInitiated() { return merge_initiated_; }
+ double GetMergePercentage() { return merge_completion_percentage_; }
+
+ // Merge Block State Transitions
+ void SetMergeCompleted(size_t block_index);
+ void SetMergeInProgress(size_t block_index);
+ void SetMergeFailed(size_t block_index);
+ void NotifyIOCompletion(uint64_t new_block);
+ bool GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block, void* buffer);
+ MERGE_GROUP_STATE ProcessMergingBlock(uint64_t new_block, void* buffer);
+
+ private:
+ bool ReadMetadata();
+ sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
+ chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
+ bool IsBlockAligned(int read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
+ struct BufferState* GetBufferState();
+ void UpdateMergeCompletionPercentage();
+
+ void ReadBlocks(const std::string partition_name, const std::string& dm_block_device);
+ void ReadBlocksToCache(const std::string& dm_block_device, const std::string& partition_name,
+ off_t offset, size_t size);
+
+ // COW device
+ std::string cow_device_;
+ // Source device
+ std::string backing_store_device_;
+ // dm-user control device
+ std::string control_device_;
+ std::string misc_name_;
+ // Base device for merging
+ std::string base_path_merge_;
+
+ unique_fd cow_fd_;
+
+ uint64_t num_sectors_;
+
+ std::unique_ptr<CowReader> reader_;
+
+ // chunk_vec stores the pseudo mapping of sector
+ // to COW operations.
+ std::vector<std::pair<sector_t, const CowOperation*>> chunk_vec_;
+
+ std::mutex lock_;
+ std::condition_variable cv;
+
+ void* mapped_addr_;
+ size_t total_mapped_addr_length_;
+
+ std::vector<std::unique_ptr<Worker>> worker_threads_;
+ // Read-ahead related
+ bool populate_data_from_cow_ = false;
+ bool ra_thread_ = false;
+ int total_ra_blocks_merged_ = 0;
+ MERGE_IO_TRANSITION io_state_;
+ std::unique_ptr<ReadAhead> read_ahead_thread_;
+ std::unordered_map<uint64_t, void*> read_ahead_buffer_map_;
+
+ // user-space-merging
+ std::unordered_map<uint64_t, int> block_to_ra_index_;
+
+ // Merge Block state
+ std::vector<std::unique_ptr<MergeGroupState>> merge_blk_state_;
+
+ std::unique_ptr<Worker> merge_thread_;
+ double merge_completion_percentage_;
+
+ bool merge_initiated_ = false;
+ bool attached_ = false;
+ bool is_socket_present_;
+ bool scratch_space_ = false;
+};
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
new file mode 100644
index 0000000..bfbacf9
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
@@ -0,0 +1,645 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd_core.h"
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+Worker::Worker(const std::string& cow_device, const std::string& backing_device,
+ const std::string& control_device, const std::string& misc_name,
+ const std::string& base_path_merge, std::shared_ptr<SnapshotHandler> snapuserd) {
+ cow_device_ = cow_device;
+ backing_store_device_ = backing_device;
+ control_device_ = control_device;
+ misc_name_ = misc_name;
+ base_path_merge_ = base_path_merge;
+ snapuserd_ = snapuserd;
+}
+
+bool Worker::InitializeFds() {
+ backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
+ if (backing_store_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
+ return false;
+ }
+
+ cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
+ if (cow_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
+ return false;
+ }
+
+ ctrl_fd_.reset(open(control_device_.c_str(), O_RDWR));
+ if (ctrl_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Unable to open " << control_device_;
+ return false;
+ }
+
+ // Base device used by merge thread
+ base_path_merge_fd_.reset(open(base_path_merge_.c_str(), O_RDWR));
+ if (base_path_merge_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed: " << base_path_merge_;
+ return false;
+ }
+
+ return true;
+}
+
+bool Worker::InitReader() {
+ reader_ = snapuserd_->CloneReaderForWorker();
+
+ if (!reader_->InitForMerge(std::move(cow_fd_))) {
+ return false;
+ }
+ return true;
+}
+
+// Start the replace operation. This will read the
+// internal COW format and if the block is compressed,
+// it will be de-compressed.
+bool Worker::ProcessReplaceOp(const CowOperation* cow_op) {
+ if (!reader_->ReadData(*cow_op, &bufsink_)) {
+ SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
+ return false;
+ }
+
+ return true;
+}
+
+bool Worker::ReadFromSourceDevice(const CowOperation* cow_op) {
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ReadFromBaseDevice: Failed to get payload buffer";
+ return false;
+ }
+ SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block
+ << " Source: " << cow_op->source;
+ uint64_t offset = cow_op->source;
+ if (cow_op->type == kCowCopyOp) {
+ offset *= BLOCK_SZ;
+ }
+ if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ, offset)) {
+ std::string op;
+ if (cow_op->type == kCowCopyOp)
+ op = "Copy-op";
+ else {
+ op = "Xor-op";
+ }
+ SNAP_PLOG(ERROR) << op << " failed. Read from backing store: " << backing_store_device_
+ << "at block :" << offset / BLOCK_SZ << " offset:" << offset % BLOCK_SZ;
+ return false;
+ }
+
+ return true;
+}
+
+// Start the copy operation. This will read the backing
+// block device which is represented by cow_op->source.
+bool Worker::ProcessCopyOp(const CowOperation* cow_op) {
+ if (!ReadFromSourceDevice(cow_op)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool Worker::ProcessXorOp(const CowOperation* cow_op) {
+ if (!ReadFromSourceDevice(cow_op)) {
+ return false;
+ }
+ xorsink_.Reset();
+ if (!reader_->ReadData(*cow_op, &xorsink_)) {
+ SNAP_LOG(ERROR) << "ProcessXorOp failed for block " << cow_op->new_block;
+ return false;
+ }
+
+ return true;
+}
+
+bool Worker::ProcessZeroOp() {
+ // Zero out the entire block
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ProcessZeroOp: Failed to get payload buffer";
+ return false;
+ }
+
+ memset(buffer, 0, BLOCK_SZ);
+ return true;
+}
+
+bool Worker::ProcessOrderedOp(const CowOperation* cow_op) {
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ProcessOrderedOp: Failed to get payload buffer";
+ return false;
+ }
+
+ MERGE_GROUP_STATE state = snapuserd_->ProcessMergingBlock(cow_op->new_block, buffer);
+
+ switch (state) {
+ case MERGE_GROUP_STATE::GROUP_MERGE_COMPLETED: {
+ // Merge is completed for this COW op; just read directly from
+ // the base device
+ SNAP_LOG(DEBUG) << "Merge-completed: Reading from base device sector: "
+ << (cow_op->new_block >> SECTOR_SHIFT)
+ << " Block-number: " << cow_op->new_block;
+ if (!ReadDataFromBaseDevice(ChunkToSector(cow_op->new_block), BLOCK_SZ)) {
+ SNAP_LOG(ERROR) << "ReadDataFromBaseDevice at sector: "
+ << (cow_op->new_block >> SECTOR_SHIFT) << " after merge-complete.";
+ return false;
+ }
+ return true;
+ }
+ case MERGE_GROUP_STATE::GROUP_MERGE_PENDING: {
+ bool ret;
+ if (cow_op->type == kCowCopyOp) {
+ ret = ProcessCopyOp(cow_op);
+ } else {
+ ret = ProcessXorOp(cow_op);
+ }
+
+ // I/O is complete - decrement the refcount irrespective of the return
+ // status
+ snapuserd_->NotifyIOCompletion(cow_op->new_block);
+ return ret;
+ }
+ // We already have the data in the buffer retrieved from RA thread.
+ // Nothing to process further.
+ case MERGE_GROUP_STATE::GROUP_MERGE_RA_READY: {
+ [[fallthrough]];
+ }
+ case MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS: {
+ return true;
+ }
+ default: {
+ // All other states, fail the I/O viz (GROUP_MERGE_FAILED and GROUP_INVALID)
+ return false;
+ }
+ }
+
+ return false;
+}
+
+bool Worker::ProcessCowOp(const CowOperation* cow_op) {
+ if (cow_op == nullptr) {
+ SNAP_LOG(ERROR) << "ProcessCowOp: Invalid cow_op";
+ return false;
+ }
+
+ switch (cow_op->type) {
+ case kCowReplaceOp: {
+ return ProcessReplaceOp(cow_op);
+ }
+
+ case kCowZeroOp: {
+ return ProcessZeroOp();
+ }
+
+ case kCowCopyOp:
+ [[fallthrough]];
+ case kCowXorOp: {
+ return ProcessOrderedOp(cow_op);
+ }
+
+ default: {
+ SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
+ }
+ }
+ return false;
+}
+
+void Worker::InitializeBufsink() {
+ // Allocate the buffer which is used to communicate between
+ // daemon and dm-user. The buffer comprises of header and a fixed payload.
+ // If the dm-user requests a big IO, the IO will be broken into chunks
+ // of PAYLOAD_SIZE.
+ size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
+ bufsink_.Initialize(buf_size);
+}
+
+bool Worker::Init() {
+ InitializeBufsink();
+ xorsink_.Initialize(&bufsink_, BLOCK_SZ);
+
+ if (!InitializeFds()) {
+ return false;
+ }
+
+ if (!InitReader()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool Worker::RunThread() {
+ SNAP_LOG(INFO) << "Processing snapshot I/O requests....";
+ // Start serving IO
+ while (true) {
+ if (!ProcessIORequest()) {
+ break;
+ }
+ }
+
+ CloseFds();
+ reader_->CloseCowFd();
+
+ return true;
+}
+
+// Read Header from dm-user misc device. This gives
+// us the sector number for which IO is issued by dm-snapshot device
+bool Worker::ReadDmUserHeader() {
+ if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) {
+ if (errno != ENOTBLK) {
+ SNAP_PLOG(ERROR) << "Control-read failed";
+ }
+
+ SNAP_PLOG(DEBUG) << "ReadDmUserHeader failed....";
+ return false;
+ }
+
+ return true;
+}
+
+// Send the payload/data back to dm-user misc device.
+bool Worker::WriteDmUserPayload(size_t size, bool header_response) {
+ size_t payload_size = size;
+ void* buf = bufsink_.GetPayloadBufPtr();
+ if (header_response) {
+ payload_size += sizeof(struct dm_user_header);
+ buf = bufsink_.GetBufPtr();
+ }
+
+ if (!android::base::WriteFully(ctrl_fd_, buf, payload_size)) {
+ SNAP_PLOG(ERROR) << "Write to dm-user failed size: " << payload_size;
+ return false;
+ }
+
+ return true;
+}
+
+bool Worker::ReadDataFromBaseDevice(sector_t sector, size_t read_size) {
+ CHECK(read_size <= BLOCK_SZ);
+
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ReadFromBaseDevice: Failed to get payload buffer";
+ return false;
+ }
+
+ loff_t offset = sector << SECTOR_SHIFT;
+ if (!android::base::ReadFullyAtOffset(base_path_merge_fd_, buffer, read_size, offset)) {
+ SNAP_PLOG(ERROR) << "ReadDataFromBaseDevice failed. fd: " << base_path_merge_fd_
+ << "at sector :" << sector << " size: " << read_size;
+ return false;
+ }
+
+ return true;
+}
+
+bool Worker::ReadAlignedSector(sector_t sector, size_t sz, bool header_response) {
+ struct dm_user_header* header = bufsink_.GetHeaderPtr();
+ size_t remaining_size = sz;
+ std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
+ bool io_error = false;
+ int ret = 0;
+
+ do {
+ // Process 1MB payload at a time
+ size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
+
+ header->type = DM_USER_RESP_SUCCESS;
+ size_t total_bytes_read = 0;
+ io_error = false;
+ bufsink_.ResetBufferOffset();
+
+ while (read_size) {
+ // We need to check every 4k block to verify if it is
+ // present in the mapping.
+ size_t size = std::min(BLOCK_SZ, read_size);
+
+ auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
+ std::make_pair(sector, nullptr), SnapshotHandler::compare);
+ bool not_found = (it == chunk_vec.end() || it->first != sector);
+
+ if (not_found) {
+ // Block not found in map - which means this block was not
+ // changed as per the OTA. Just route the I/O to the base
+ // device.
+ if (!ReadDataFromBaseDevice(sector, size)) {
+ SNAP_LOG(ERROR) << "ReadDataFromBaseDevice failed";
+ header->type = DM_USER_RESP_ERROR;
+ }
+
+ ret = size;
+ } else {
+ // We found the sector in mapping. Check the type of COW OP and
+ // process it.
+ if (!ProcessCowOp(it->second)) {
+ SNAP_LOG(ERROR) << "ProcessCowOp failed";
+ header->type = DM_USER_RESP_ERROR;
+ }
+
+ ret = BLOCK_SZ;
+ }
+
+ // Just return the header if it is an error
+ if (header->type == DM_USER_RESP_ERROR) {
+ if (!RespondIOError(header_response)) {
+ return false;
+ }
+
+ io_error = true;
+ break;
+ }
+
+ read_size -= ret;
+ total_bytes_read += ret;
+ sector += (ret >> SECTOR_SHIFT);
+ bufsink_.UpdateBufferOffset(ret);
+ }
+
+ if (!io_error) {
+ if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+ return false;
+ }
+
+ SNAP_LOG(DEBUG) << "WriteDmUserPayload success total_bytes_read: " << total_bytes_read
+ << " header-response: " << header_response
+ << " remaining_size: " << remaining_size;
+ header_response = false;
+ remaining_size -= total_bytes_read;
+ }
+ } while (remaining_size > 0 && !io_error);
+
+ return true;
+}
+
+int Worker::ReadUnalignedSector(
+ sector_t sector, size_t size,
+ std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it) {
+ size_t skip_sector_size = 0;
+
+ SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size
+ << " Aligned sector: " << it->first;
+
+ if (!ProcessCowOp(it->second)) {
+ SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size
+ << " Aligned sector: " << it->first;
+ return -1;
+ }
+
+ int num_sectors_skip = sector - it->first;
+
+ if (num_sectors_skip > 0) {
+ skip_sector_size = num_sectors_skip << SECTOR_SHIFT;
+ char* buffer = reinterpret_cast<char*>(bufsink_.GetBufPtr());
+ struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
+
+ if (skip_sector_size == BLOCK_SZ) {
+ SNAP_LOG(ERROR) << "Invalid un-aligned IO request at sector: " << sector
+ << " Base-sector: " << it->first;
+ return -1;
+ }
+
+ memmove(msg->payload.buf, (char*)msg->payload.buf + skip_sector_size,
+ (BLOCK_SZ - skip_sector_size));
+ }
+
+ bufsink_.ResetBufferOffset();
+ return std::min(size, (BLOCK_SZ - skip_sector_size));
+}
+
+bool Worker::ReadUnalignedSector(sector_t sector, size_t size) {
+ struct dm_user_header* header = bufsink_.GetHeaderPtr();
+ header->type = DM_USER_RESP_SUCCESS;
+ bufsink_.ResetBufferOffset();
+ std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
+
+ auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr),
+ SnapshotHandler::compare);
+
+ // |-------|-------|-------|
+ // 0 1 2 3
+ //
+ // Block 0 - op 1
+ // Block 1 - op 2
+ // Block 2 - op 3
+ //
+ // chunk_vec will have block 0, 1, 2 which maps to relavant COW ops.
+ //
+ // Each block is 4k bytes. Thus, the last block will span 8 sectors
+ // ranging till block 3 (However, block 3 won't be in chunk_vec as
+ // it doesn't have any mapping to COW ops. Now, if we get an I/O request for a sector
+ // spanning between block 2 and block 3, we need to step back
+ // and get hold of the last element.
+ //
+ // Additionally, we need to make sure that the requested sector is
+ // indeed within the range of the final sector. It is perfectly valid
+ // to get an I/O request for block 3 and beyond which are not mapped
+ // to any COW ops. In that case, we just need to read from the base
+ // device.
+ bool merge_complete = false;
+ bool header_response = true;
+ if (it == chunk_vec.end()) {
+ if (chunk_vec.size() > 0) {
+ // I/O request beyond the last mapped sector
+ it = std::prev(chunk_vec.end());
+ } else {
+ // This can happen when a partition merge is complete but snapshot
+ // state in /metadata is not yet deleted; during this window if the
+ // device is rebooted, subsequent attempt will mount the snapshot.
+ // However, since the merge was completed we wouldn't have any
+ // mapping to COW ops thus chunk_vec will be empty. In that case,
+ // mark this as merge_complete and route the I/O to the base device.
+ merge_complete = true;
+ }
+ } else if (it->first != sector) {
+ if (it != chunk_vec.begin()) {
+ --it;
+ }
+ } else {
+ return ReadAlignedSector(sector, size, header_response);
+ }
+
+ loff_t requested_offset = sector << SECTOR_SHIFT;
+
+ loff_t final_offset = 0;
+ if (!merge_complete) {
+ final_offset = it->first << SECTOR_SHIFT;
+ }
+
+ // Since a COW op span 4k block size, we need to make sure that the requested
+ // offset is within the 4k region. Consider the following case:
+ //
+ // |-------|-------|-------|
+ // 0 1 2 3
+ //
+ // Block 0 - op 1
+ // Block 1 - op 2
+ //
+ // We have an I/O request for a sector between block 2 and block 3. However,
+ // we have mapping to COW ops only for block 0 and block 1. Thus, the
+ // requested offset in this case is beyond the last mapped COW op size (which
+ // is block 1 in this case).
+
+ size_t total_bytes_read = 0;
+ size_t remaining_size = size;
+ int ret = 0;
+ if (!merge_complete && (requested_offset >= final_offset) &&
+ (requested_offset - final_offset) < BLOCK_SZ) {
+ // Read the partial un-aligned data
+ ret = ReadUnalignedSector(sector, remaining_size, it);
+ if (ret < 0) {
+ SNAP_LOG(ERROR) << "ReadUnalignedSector failed for sector: " << sector
+ << " size: " << size << " it->sector: " << it->first;
+ return RespondIOError(header_response);
+ }
+
+ remaining_size -= ret;
+ total_bytes_read += ret;
+ sector += (ret >> SECTOR_SHIFT);
+
+ // Send the data back
+ if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+ return false;
+ }
+
+ header_response = false;
+ // If we still have pending data to be processed, this will be aligned I/O
+ if (remaining_size) {
+ return ReadAlignedSector(sector, remaining_size, header_response);
+ }
+ } else {
+ // This is all about handling I/O request to be routed to base device
+ // as the I/O is not mapped to any of the COW ops.
+ loff_t aligned_offset = requested_offset;
+ // Align to nearest 4k
+ aligned_offset += BLOCK_SZ - 1;
+ aligned_offset &= ~(BLOCK_SZ - 1);
+ // Find the diff of the aligned offset
+ size_t diff_size = aligned_offset - requested_offset;
+ CHECK(diff_size <= BLOCK_SZ);
+ if (remaining_size < diff_size) {
+ if (!ReadDataFromBaseDevice(sector, remaining_size)) {
+ return RespondIOError(header_response);
+ }
+ total_bytes_read += remaining_size;
+
+ if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+ return false;
+ }
+ } else {
+ if (!ReadDataFromBaseDevice(sector, diff_size)) {
+ return RespondIOError(header_response);
+ }
+
+ total_bytes_read += diff_size;
+
+ if (!WriteDmUserPayload(total_bytes_read, header_response)) {
+ return false;
+ }
+
+ remaining_size -= diff_size;
+ size_t num_sectors_read = (diff_size >> SECTOR_SHIFT);
+ sector += num_sectors_read;
+ CHECK(IsBlockAligned(sector << SECTOR_SHIFT));
+ header_response = false;
+
+ // If we still have pending data to be processed, this will be aligned I/O
+ return ReadAlignedSector(sector, remaining_size, header_response);
+ }
+ }
+
+ return true;
+}
+
+bool Worker::RespondIOError(bool header_response) {
+ struct dm_user_header* header = bufsink_.GetHeaderPtr();
+ header->type = DM_USER_RESP_ERROR;
+ // This is an issue with the dm-user interface. There
+ // is no way to propagate the I/O error back to dm-user
+ // if we have already communicated the header back. Header
+ // is responded once at the beginning; however I/O can
+ // be processed in chunks. If we encounter an I/O error
+ // somewhere in the middle of the processing, we can't communicate
+ // this back to dm-user.
+ //
+ // TODO: Fix the interface
+ CHECK(header_response);
+
+ if (!WriteDmUserPayload(0, header_response)) {
+ return false;
+ }
+
+ // There is no need to process further as we have already seen
+ // an I/O error
+ return true;
+}
+
+bool Worker::DmuserReadRequest() {
+ struct dm_user_header* header = bufsink_.GetHeaderPtr();
+
+ // Unaligned I/O request
+ if (!IsBlockAligned(header->sector << SECTOR_SHIFT)) {
+ return ReadUnalignedSector(header->sector, header->len);
+ }
+
+ return ReadAlignedSector(header->sector, header->len, true);
+}
+
+bool Worker::ProcessIORequest() {
+ struct dm_user_header* header = bufsink_.GetHeaderPtr();
+
+ if (!ReadDmUserHeader()) {
+ return false;
+ }
+
+ SNAP_LOG(DEBUG) << "Daemon: msg->seq: " << std::dec << header->seq;
+ SNAP_LOG(DEBUG) << "Daemon: msg->len: " << std::dec << header->len;
+ SNAP_LOG(DEBUG) << "Daemon: msg->sector: " << std::dec << header->sector;
+ SNAP_LOG(DEBUG) << "Daemon: msg->type: " << std::dec << header->type;
+ SNAP_LOG(DEBUG) << "Daemon: msg->flags: " << std::dec << header->flags;
+
+ switch (header->type) {
+ case DM_USER_REQ_MAP_READ: {
+ if (!DmuserReadRequest()) {
+ return false;
+ }
+ break;
+ }
+
+ case DM_USER_REQ_MAP_WRITE: {
+ // TODO: We should not get any write request
+ // to dm-user as we mount all partitions
+ // as read-only. Need to verify how are TRIM commands
+ // handled during mount.
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
new file mode 100644
index 0000000..47fc7db
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd_core.h"
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+int Worker::PrepareMerge(uint64_t* source_offset, int* pending_ops,
+ const std::unique_ptr<ICowOpIter>& cowop_iter,
+ std::vector<const CowOperation*>* replace_zero_vec) {
+ int num_ops = *pending_ops;
+ int nr_consecutive = 0;
+ bool checkOrderedOp = (replace_zero_vec == nullptr);
+
+ do {
+ if (!cowop_iter->Done() && num_ops) {
+ const CowOperation* cow_op = &cowop_iter->Get();
+ if (checkOrderedOp && !IsOrderedOp(*cow_op)) {
+ break;
+ }
+
+ *source_offset = cow_op->new_block * BLOCK_SZ;
+ if (!checkOrderedOp) {
+ replace_zero_vec->push_back(cow_op);
+ }
+
+ cowop_iter->Next();
+ num_ops -= 1;
+ nr_consecutive = 1;
+
+ while (!cowop_iter->Done() && num_ops) {
+ const CowOperation* op = &cowop_iter->Get();
+ if (checkOrderedOp && !IsOrderedOp(*op)) {
+ break;
+ }
+
+ uint64_t next_offset = op->new_block * BLOCK_SZ;
+ if (next_offset != (*source_offset + nr_consecutive * BLOCK_SZ)) {
+ break;
+ }
+
+ if (!checkOrderedOp) {
+ replace_zero_vec->push_back(op);
+ }
+
+ nr_consecutive += 1;
+ num_ops -= 1;
+ cowop_iter->Next();
+ }
+ }
+ } while (0);
+
+ return nr_consecutive;
+}
+
+bool Worker::MergeReplaceZeroOps(const std::unique_ptr<ICowOpIter>& cowop_iter) {
+ // Flush every 2048 ops. Since all ops are independent and there is no
+ // dependency between COW ops, we will flush the data and the number
+ // of ops merged in COW file for every 2048 ops. If there is a crash,
+ // we will end up replaying some of the COW ops which were already merged.
+ // That is ok.
+ //
+ // Why 2048 ops ? We can probably increase this to bigger value but just
+ // need to ensure that merge makes forward progress if there are
+ // crashes repeatedly which is highly unlikely.
+ int total_ops_merged_per_commit = (PAYLOAD_SIZE / BLOCK_SZ) * 8;
+ int num_ops_merged = 0;
+
+ while (!cowop_iter->Done()) {
+ int num_ops = PAYLOAD_SIZE / BLOCK_SZ;
+ std::vector<const CowOperation*> replace_zero_vec;
+ uint64_t source_offset;
+
+ int linear_blocks = PrepareMerge(&source_offset, &num_ops, cowop_iter, &replace_zero_vec);
+ if (linear_blocks == 0) {
+ // Merge complete
+ CHECK(cowop_iter->Done());
+ break;
+ }
+
+ for (size_t i = 0; i < replace_zero_vec.size(); i++) {
+ const CowOperation* cow_op = replace_zero_vec[i];
+ if (cow_op->type == kCowReplaceOp) {
+ if (!ProcessReplaceOp(cow_op)) {
+ SNAP_LOG(ERROR) << "Merge - ReplaceOp failed for block: " << cow_op->new_block;
+ return false;
+ }
+ } else {
+ CHECK(cow_op->type == kCowZeroOp);
+ if (!ProcessZeroOp()) {
+ SNAP_LOG(ERROR) << "Merge ZeroOp failed.";
+ return false;
+ }
+ }
+
+ bufsink_.UpdateBufferOffset(BLOCK_SZ);
+ }
+
+ size_t io_size = linear_blocks * BLOCK_SZ;
+
+ // Merge - Write the contents back to base device
+ int ret = pwrite(base_path_merge_fd_.get(), bufsink_.GetPayloadBufPtr(), io_size,
+ source_offset);
+ if (ret < 0 || ret != io_size) {
+ SNAP_LOG(ERROR)
+ << "Merge: ReplaceZeroOps: Failed to write to backing device while merging "
+ << " at offset: " << source_offset << " io_size: " << io_size;
+ return false;
+ }
+
+ num_ops_merged += linear_blocks;
+
+ if (num_ops_merged == total_ops_merged_per_commit) {
+ // Flush the data
+ if (fsync(base_path_merge_fd_.get()) < 0) {
+ SNAP_LOG(ERROR) << "Merge: ReplaceZeroOps: Failed to fsync merged data";
+ return false;
+ }
+
+ // Track the merge completion
+ if (!snapuserd_->CommitMerge(num_ops_merged)) {
+ SNAP_LOG(ERROR) << " Failed to commit the merged block in the header";
+ return false;
+ }
+
+ num_ops_merged = 0;
+ }
+
+ bufsink_.ResetBufferOffset();
+
+ if (snapuserd_->IsIOTerminated()) {
+ SNAP_LOG(ERROR)
+ << "MergeReplaceZeroOps: Worker threads terminated - shutting down merge";
+ return false;
+ }
+ }
+
+ // Any left over ops not flushed yet.
+ if (num_ops_merged) {
+ // Flush the data
+ if (fsync(base_path_merge_fd_.get()) < 0) {
+ SNAP_LOG(ERROR) << "Merge: ReplaceZeroOps: Failed to fsync merged data";
+ return false;
+ }
+
+ if (!snapuserd_->CommitMerge(num_ops_merged)) {
+ SNAP_LOG(ERROR) << " Failed to commit the merged block in the header";
+ return false;
+ }
+
+ num_ops_merged = 0;
+ }
+
+ return true;
+}
+
+bool Worker::MergeOrderedOps(const std::unique_ptr<ICowOpIter>& cowop_iter) {
+ void* mapped_addr = snapuserd_->GetMappedAddr();
+ void* read_ahead_buffer =
+ static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
+ size_t block_index = 0;
+
+ SNAP_LOG(INFO) << "MergeOrderedOps started....";
+
+ while (!cowop_iter->Done()) {
+ const CowOperation* cow_op = &cowop_iter->Get();
+ if (!IsOrderedOp(*cow_op)) {
+ break;
+ }
+
+ SNAP_LOG(DEBUG) << "Waiting for merge begin...";
+ // Wait for RA thread to notify that the merge window
+ // is ready for merging.
+ if (!snapuserd_->WaitForMergeBegin()) {
+ snapuserd_->SetMergeFailed(block_index);
+ return false;
+ }
+
+ snapuserd_->SetMergeInProgress(block_index);
+
+ loff_t offset = 0;
+ int num_ops = snapuserd_->GetTotalBlocksToMerge();
+ SNAP_LOG(DEBUG) << "Merging copy-ops of size: " << num_ops;
+ while (num_ops) {
+ uint64_t source_offset;
+
+ int linear_blocks = PrepareMerge(&source_offset, &num_ops, cowop_iter);
+ if (linear_blocks == 0) {
+ break;
+ }
+
+ size_t io_size = (linear_blocks * BLOCK_SZ);
+ // Write to the base device. Data is already in the RA buffer. Note
+ // that XOR ops is already handled by the RA thread. We just write
+ // the contents out.
+ int ret = pwrite(base_path_merge_fd_.get(), (char*)read_ahead_buffer + offset, io_size,
+ source_offset);
+ if (ret < 0 || ret != io_size) {
+ SNAP_LOG(ERROR) << "Failed to write to backing device while merging "
+ << " at offset: " << source_offset << " io_size: " << io_size;
+ snapuserd_->SetMergeFailed(block_index);
+ return false;
+ }
+
+ offset += io_size;
+ num_ops -= linear_blocks;
+ }
+
+ // Verify all ops are merged
+ CHECK(num_ops == 0);
+
+ // Flush the data
+ if (fsync(base_path_merge_fd_.get()) < 0) {
+ SNAP_LOG(ERROR) << " Failed to fsync merged data";
+ snapuserd_->SetMergeFailed(block_index);
+ return false;
+ }
+
+ // Merge is done and data is on disk. Update the COW Header about
+ // the merge completion
+ if (!snapuserd_->CommitMerge(snapuserd_->GetTotalBlocksToMerge())) {
+ SNAP_LOG(ERROR) << " Failed to commit the merged block in the header";
+ snapuserd_->SetMergeFailed(block_index);
+ return false;
+ }
+
+ SNAP_LOG(DEBUG) << "Block commit of size: " << snapuserd_->GetTotalBlocksToMerge();
+ // Mark the block as merge complete
+ snapuserd_->SetMergeCompleted(block_index);
+
+ // Notify RA thread that the merge thread is ready to merge the next
+ // window
+ snapuserd_->NotifyRAForMergeReady();
+
+ // Get the next block
+ block_index += 1;
+ }
+
+ return true;
+}
+
+bool Worker::Merge() {
+ std::unique_ptr<ICowOpIter> cowop_iter = reader_->GetMergeOpIter();
+
+ // Start with Copy and Xor ops
+ if (!MergeOrderedOps(cowop_iter)) {
+ SNAP_LOG(ERROR) << "Merge failed for ordered ops";
+ snapuserd_->MergeFailed();
+ return false;
+ }
+
+ SNAP_LOG(INFO) << "MergeOrderedOps completed...";
+
+ // Replace and Zero ops
+ if (!MergeReplaceZeroOps(cowop_iter)) {
+ SNAP_LOG(ERROR) << "Merge failed for replace/zero ops";
+ snapuserd_->MergeFailed();
+ return false;
+ }
+
+ snapuserd_->MergeCompleted();
+
+ return true;
+}
+
+bool Worker::RunMergeThread() {
+ SNAP_LOG(DEBUG) << "Waiting for merge begin...";
+ if (!snapuserd_->WaitForMergeBegin()) {
+ SNAP_LOG(ERROR) << "Merge terminated early...";
+ return true;
+ }
+
+ SNAP_LOG(INFO) << "Merge starting..";
+
+ if (!Init()) {
+ SNAP_LOG(ERROR) << "Merge thread initialization failed...";
+ return false;
+ }
+
+ if (!Merge()) {
+ return false;
+ }
+
+ CloseFds();
+ reader_->CloseCowFd();
+
+ SNAP_LOG(INFO) << "Merge finish";
+
+ return true;
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
new file mode 100644
index 0000000..0bcf26e
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd_core.h"
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+ReadAhead::ReadAhead(const std::string& cow_device, const std::string& backing_device,
+ const std::string& misc_name, std::shared_ptr<SnapshotHandler> snapuserd) {
+ cow_device_ = cow_device;
+ backing_store_device_ = backing_device;
+ misc_name_ = misc_name;
+ snapuserd_ = snapuserd;
+}
+
+void ReadAhead::CheckOverlap(const CowOperation* cow_op) {
+ uint64_t source_block = cow_op->source;
+ uint64_t source_offset = 0;
+ if (cow_op->type == kCowXorOp) {
+ source_block /= BLOCK_SZ;
+ source_offset = cow_op->source % BLOCK_SZ;
+ }
+ if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block) ||
+ (source_offset > 0 && source_blocks_.count(source_block + 1))) {
+ overlap_ = true;
+ }
+
+ dest_blocks_.insert(source_block);
+ if (source_offset > 0) {
+ dest_blocks_.insert(source_block + 1);
+ }
+ source_blocks_.insert(cow_op->new_block);
+}
+
+int ReadAhead::PrepareNextReadAhead(uint64_t* source_offset, int* pending_ops,
+ std::vector<uint64_t>& blocks,
+ std::vector<const CowOperation*>& xor_op_vec) {
+ int num_ops = *pending_ops;
+ int nr_consecutive = 0;
+
+ bool is_ops_present = (!RAIterDone() && num_ops);
+
+ if (!is_ops_present) {
+ return nr_consecutive;
+ }
+
+ // Get the first block with offset
+ const CowOperation* cow_op = GetRAOpIter();
+ *source_offset = cow_op->source;
+
+ if (cow_op->type == kCowCopyOp) {
+ *source_offset *= BLOCK_SZ;
+ } else if (cow_op->type == kCowXorOp) {
+ xor_op_vec.push_back(cow_op);
+ }
+
+ RAIterNext();
+ num_ops -= 1;
+ nr_consecutive = 1;
+ blocks.push_back(cow_op->new_block);
+
+ if (!overlap_) {
+ CheckOverlap(cow_op);
+ }
+
+ /*
+ * Find number of consecutive blocks
+ */
+ while (!RAIterDone() && num_ops) {
+ const CowOperation* op = GetRAOpIter();
+ uint64_t next_offset = op->source;
+
+ if (cow_op->type == kCowCopyOp) {
+ next_offset *= BLOCK_SZ;
+ }
+
+ // Check for consecutive blocks
+ if (next_offset != (*source_offset + nr_consecutive * BLOCK_SZ)) {
+ break;
+ }
+
+ if (op->type == kCowXorOp) {
+ xor_op_vec.push_back(op);
+ }
+
+ nr_consecutive += 1;
+ num_ops -= 1;
+ blocks.push_back(op->new_block);
+ RAIterNext();
+
+ if (!overlap_) {
+ CheckOverlap(op);
+ }
+ }
+
+ return nr_consecutive;
+}
+
+bool ReadAhead::ReconstructDataFromCow() {
+ std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+ loff_t metadata_offset = 0;
+ loff_t start_data_offset = snapuserd_->GetBufferDataOffset();
+ int num_ops = 0;
+ int total_blocks_merged = 0;
+
+ while (true) {
+ struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
+ (char*)metadata_buffer_ + metadata_offset);
+
+ // Done reading metadata
+ if (bm->new_block == 0 && bm->file_offset == 0) {
+ break;
+ }
+
+ loff_t buffer_offset = bm->file_offset - start_data_offset;
+ void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + buffer_offset);
+ read_ahead_buffer_map[bm->new_block] = bufptr;
+ num_ops += 1;
+ total_blocks_merged += 1;
+
+ metadata_offset += sizeof(struct ScratchMetadata);
+ }
+
+ // We are done re-constructing the mapping; however, we need to make sure
+ // all the COW operations to-be merged are present in the re-constructed
+ // mapping.
+ while (!RAIterDone()) {
+ const CowOperation* op = GetRAOpIter();
+ if (read_ahead_buffer_map.find(op->new_block) != read_ahead_buffer_map.end()) {
+ num_ops -= 1;
+ RAIterNext();
+ continue;
+ }
+
+ // Verify that we have covered all the ops which were re-constructed
+ // from COW device - These are the ops which are being
+ // re-constructed after crash.
+ if (!(num_ops == 0)) {
+ SNAP_LOG(ERROR) << "ReconstructDataFromCow failed. Not all ops recoverd "
+ << " Pending ops: " << num_ops;
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ break;
+ }
+
+ snapuserd_->SetMergedBlockCountForNextCommit(total_blocks_merged);
+
+ snapuserd_->FinishReconstructDataFromCow();
+
+ if (!snapuserd_->ReadAheadIOCompleted(true)) {
+ SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ SNAP_LOG(INFO) << "ReconstructDataFromCow success";
+ return true;
+}
+
+bool ReadAhead::ReadAheadIOStart() {
+ // Check if the data has to be constructed from the COW file.
+ // This will be true only once during boot up after a crash
+ // during merge.
+ if (snapuserd_->ShouldReconstructDataFromCow()) {
+ return ReconstructDataFromCow();
+ }
+
+ std::vector<uint64_t> blocks;
+
+ int num_ops = (snapuserd_->GetBufferDataSize()) / BLOCK_SZ;
+ loff_t buffer_offset = 0;
+ int total_blocks_merged = 0;
+ overlap_ = false;
+ dest_blocks_.clear();
+ source_blocks_.clear();
+ std::vector<const CowOperation*> xor_op_vec;
+
+ auto ra_temp_buffer = std::make_unique<uint8_t[]>(snapuserd_->GetBufferDataSize());
+
+ // Number of ops to be merged in this window. This is a fixed size
+ // except for the last window wherein the number of ops can be less
+ // than the size of the RA window.
+ while (num_ops) {
+ uint64_t source_offset;
+
+ int linear_blocks = PrepareNextReadAhead(&source_offset, &num_ops, blocks, xor_op_vec);
+ if (linear_blocks == 0) {
+ // No more blocks to read
+ SNAP_LOG(DEBUG) << " Read-ahead completed....";
+ break;
+ }
+
+ size_t io_size = (linear_blocks * BLOCK_SZ);
+
+ // Read from the base device consecutive set of blocks in one shot
+ if (!android::base::ReadFullyAtOffset(backing_store_fd_,
+ (char*)ra_temp_buffer.get() + buffer_offset, io_size,
+ source_offset)) {
+ SNAP_PLOG(ERROR) << "Ordered-op failed. Read from backing store: "
+ << backing_store_device_ << "at block :" << source_offset / BLOCK_SZ
+ << " offset :" << source_offset % BLOCK_SZ
+ << " buffer_offset : " << buffer_offset << " io_size : " << io_size
+ << " buf-addr : " << read_ahead_buffer_;
+
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ buffer_offset += io_size;
+ total_blocks_merged += linear_blocks;
+ num_ops -= linear_blocks;
+ }
+
+ // Done with merging ordered ops
+ if (RAIterDone() && total_blocks_merged == 0) {
+ return true;
+ }
+
+ loff_t metadata_offset = 0;
+
+ auto ra_temp_meta_buffer = std::make_unique<uint8_t[]>(snapuserd_->GetBufferMetadataSize());
+
+ struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
+ (char*)ra_temp_meta_buffer.get() + metadata_offset);
+
+ bm->new_block = 0;
+ bm->file_offset = 0;
+
+ loff_t file_offset = snapuserd_->GetBufferDataOffset();
+
+ loff_t offset = 0;
+ CHECK(blocks.size() == total_blocks_merged);
+
+ size_t xor_index = 0;
+ for (size_t block_index = 0; block_index < blocks.size(); block_index++) {
+ void* bufptr = static_cast<void*>((char*)ra_temp_buffer.get() + offset);
+ uint64_t new_block = blocks[block_index];
+
+ if (xor_index < xor_op_vec.size()) {
+ const CowOperation* xor_op = xor_op_vec[xor_index];
+
+ // Check if this block is an XOR op
+ if (xor_op->new_block == new_block) {
+ // Read the xor'ed data from COW
+ if (!reader_->ReadData(*xor_op, &bufsink_)) {
+ SNAP_LOG(ERROR)
+ << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block;
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ // Pointer to the data read from base device
+ uint8_t* buffer = reinterpret_cast<uint8_t*>(bufptr);
+ // Get the xor'ed data read from COW device
+ uint8_t* xor_data = reinterpret_cast<uint8_t*>(bufsink_.GetPayloadBufPtr());
+
+ // Retrieve the original data
+ for (size_t byte_offset = 0; byte_offset < BLOCK_SZ; byte_offset++) {
+ buffer[byte_offset] ^= xor_data[byte_offset];
+ }
+
+ // Move to next XOR op
+ xor_index += 1;
+ }
+ }
+
+ offset += BLOCK_SZ;
+ // Track the metadata blocks which are stored in scratch space
+ bm = reinterpret_cast<struct ScratchMetadata*>((char*)ra_temp_meta_buffer.get() +
+ metadata_offset);
+
+ bm->new_block = new_block;
+ bm->file_offset = file_offset;
+
+ metadata_offset += sizeof(struct ScratchMetadata);
+ file_offset += BLOCK_SZ;
+ }
+
+ // Verify if all the xor blocks were scanned to retrieve the original data
+ CHECK(xor_index == xor_op_vec.size());
+
+ // This is important - explicitly set the contents to zero. This is used
+ // when re-constructing the data after crash. This indicates end of
+ // reading metadata contents when re-constructing the data
+ bm = reinterpret_cast<struct ScratchMetadata*>((char*)ra_temp_meta_buffer.get() +
+ metadata_offset);
+ bm->new_block = 0;
+ bm->file_offset = 0;
+
+ // Wait for the merge to finish for the previous RA window. We shouldn't
+ // be touching the scratch space until merge is complete of previous RA
+ // window. If there is a crash during this time frame, merge should resume
+ // based on the contents of the scratch space.
+ if (!snapuserd_->WaitForMergeReady()) {
+ return false;
+ }
+
+ // Copy the data to scratch space
+ memcpy(metadata_buffer_, ra_temp_meta_buffer.get(), snapuserd_->GetBufferMetadataSize());
+ memcpy(read_ahead_buffer_, ra_temp_buffer.get(), total_blocks_merged * BLOCK_SZ);
+
+ offset = 0;
+ std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+ read_ahead_buffer_map.clear();
+
+ for (size_t block_index = 0; block_index < blocks.size(); block_index++) {
+ void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + offset);
+ uint64_t new_block = blocks[block_index];
+
+ read_ahead_buffer_map[new_block] = bufptr;
+ offset += BLOCK_SZ;
+ }
+
+ snapuserd_->SetMergedBlockCountForNextCommit(total_blocks_merged);
+
+ // Flush the data only if we have a overlapping blocks in the region
+ // Notify the Merge thread to resume merging this window
+ if (!snapuserd_->ReadAheadIOCompleted(overlap_)) {
+ SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ return true;
+}
+
+bool ReadAhead::RunThread() {
+ if (!InitializeFds()) {
+ return false;
+ }
+
+ InitializeBuffer();
+
+ if (!InitReader()) {
+ return false;
+ }
+
+ InitializeRAIter();
+
+ while (!RAIterDone()) {
+ if (!ReadAheadIOStart()) {
+ break;
+ }
+ }
+
+ CloseFds();
+ reader_->CloseCowFd();
+ SNAP_LOG(INFO) << " ReadAhead thread terminating....";
+ return true;
+}
+
+// Initialization
+bool ReadAhead::InitializeFds() {
+ backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
+ if (backing_store_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
+ return false;
+ }
+
+ cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
+ if (cow_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
+ return false;
+ }
+
+ return true;
+}
+
+bool ReadAhead::InitReader() {
+ reader_ = snapuserd_->CloneReaderForWorker();
+
+ if (!reader_->InitForMerge(std::move(cow_fd_))) {
+ return false;
+ }
+ return true;
+}
+
+void ReadAhead::InitializeRAIter() {
+ cowop_iter_ = reader_->GetMergeOpIter();
+}
+
+bool ReadAhead::RAIterDone() {
+ if (cowop_iter_->Done()) {
+ return true;
+ }
+
+ const CowOperation* cow_op = GetRAOpIter();
+
+ if (!IsOrderedOp(*cow_op)) {
+ return true;
+ }
+
+ return false;
+}
+
+void ReadAhead::RAIterNext() {
+ cowop_iter_->Next();
+}
+
+const CowOperation* ReadAhead::GetRAOpIter() {
+ const CowOperation* cow_op = &cowop_iter_->Get();
+ return cow_op;
+}
+
+void ReadAhead::InitializeBuffer() {
+ void* mapped_addr = snapuserd_->GetMappedAddr();
+ // Map the scratch space region into memory
+ metadata_buffer_ =
+ static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferMetadataOffset());
+ read_ahead_buffer_ = static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
+ // For xor ops
+ bufsink_.Initialize(PAYLOAD_SIZE);
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
similarity index 66%
copy from fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
copy to fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index 2f87557..a4fd5a0 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -31,7 +31,6 @@
#include <android-base/scopeguard.h>
#include <fs_mgr/file_wait.h>
#include <snapuserd/snapuserd_client.h>
-#include "snapuserd.h"
#include "snapuserd_server.h"
#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
@@ -45,26 +44,29 @@
using android::base::borrowed_fd;
using android::base::unique_fd;
-DaemonOperations SnapuserdServer::Resolveop(std::string& input) {
- if (input == "init") return DaemonOperations::INIT;
- if (input == "start") return DaemonOperations::START;
- if (input == "stop") return DaemonOperations::STOP;
- if (input == "query") return DaemonOperations::QUERY;
- if (input == "delete") return DaemonOperations::DELETE;
- if (input == "detach") return DaemonOperations::DETACH;
- if (input == "supports") return DaemonOperations::SUPPORTS;
+DaemonOps SnapuserServer::Resolveop(std::string& input) {
+ if (input == "init") return DaemonOps::INIT;
+ if (input == "start") return DaemonOps::START;
+ if (input == "stop") return DaemonOps::STOP;
+ if (input == "query") return DaemonOps::QUERY;
+ if (input == "delete") return DaemonOps::DELETE;
+ if (input == "detach") return DaemonOps::DETACH;
+ if (input == "supports") return DaemonOps::SUPPORTS;
+ if (input == "initiate_merge") return DaemonOps::INITIATE;
+ if (input == "merge_percent") return DaemonOps::PERCENTAGE;
+ if (input == "getstatus") return DaemonOps::GETSTATUS;
- return DaemonOperations::INVALID;
+ return DaemonOps::INVALID;
}
-SnapuserdServer::~SnapuserdServer() {
+SnapuserServer::~SnapuserServer() {
// Close any client sockets that were added via AcceptClient().
for (size_t i = 1; i < watched_fds_.size(); i++) {
close(watched_fds_[i].fd);
}
}
-std::string SnapuserdServer::GetDaemonStatus() {
+std::string SnapuserServer::GetDaemonStatus() {
std::string msg = "";
if (IsTerminating())
@@ -75,8 +77,8 @@
return msg;
}
-void SnapuserdServer::Parsemsg(std::string const& msg, const char delim,
- std::vector<std::string>& out) {
+void SnapuserServer::Parsemsg(std::string const& msg, const char delim,
+ std::vector<std::string>& out) {
std::stringstream ss(msg);
std::string s;
@@ -85,15 +87,15 @@
}
}
-void SnapuserdServer::ShutdownThreads() {
- StopThreads();
+void SnapuserServer::ShutdownThreads() {
+ terminating_ = true;
JoinAllThreads();
}
-DmUserHandler::DmUserHandler(std::shared_ptr<Snapuserd> snapuserd)
+DmUserHandler::DmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd)
: snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
-bool SnapuserdServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
+bool SnapuserServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
if (ret < 0) {
PLOG(ERROR) << "Snapuserd:server: send() failed";
@@ -107,7 +109,7 @@
return true;
}
-bool SnapuserdServer::Recv(android::base::borrowed_fd fd, std::string* data) {
+bool SnapuserServer::Recv(android::base::borrowed_fd fd, std::string* data) {
char msg[MAX_PACKET_SIZE];
ssize_t rv = TEMP_FAILURE_RETRY(recv(fd.get(), msg, sizeof(msg), 0));
if (rv < 0) {
@@ -118,25 +120,25 @@
return true;
}
-bool SnapuserdServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
+bool SnapuserServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
const char delim = ',';
std::vector<std::string> out;
Parsemsg(str, delim, out);
- DaemonOperations op = Resolveop(out[0]);
+ DaemonOps op = Resolveop(out[0]);
switch (op) {
- case DaemonOperations::INIT: {
+ case DaemonOps::INIT: {
// Message format:
- // init,<misc_name>,<cow_device_path>,<backing_device>
+ // init,<misc_name>,<cow_device_path>,<backing_device>,<base_path_merge>
//
// Reads the metadata and send the number of sectors
- if (out.size() != 4) {
+ if (out.size() != 5) {
LOG(ERROR) << "Malformed init message, " << out.size() << " parts";
return Sendmsg(fd, "fail");
}
- auto handler = AddHandler(out[1], out[2], out[3]);
+ auto handler = AddHandler(out[1], out[2], out[3], out[4]);
if (!handler) {
return Sendmsg(fd, "fail");
}
@@ -144,7 +146,7 @@
auto retval = "success," + std::to_string(handler->snapuserd()->GetNumSectors());
return Sendmsg(fd, retval);
}
- case DaemonOperations::START: {
+ case DaemonOps::START: {
// Message format:
// start,<misc_name>
//
@@ -169,7 +171,7 @@
}
return Sendmsg(fd, "success");
}
- case DaemonOperations::STOP: {
+ case DaemonOps::STOP: {
// Message format: stop
//
// Stop all the threads gracefully and then shutdown the
@@ -178,7 +180,7 @@
ShutdownThreads();
return true;
}
- case DaemonOperations::QUERY: {
+ case DaemonOps::QUERY: {
// Message format: query
//
// As part of transition, Second stage daemon will be
@@ -190,23 +192,41 @@
// be ready to receive control message.
return Sendmsg(fd, GetDaemonStatus());
}
- case DaemonOperations::DELETE: {
+ case DaemonOps::DELETE: {
// Message format:
// delete,<misc_name>
if (out.size() != 2) {
LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
return Sendmsg(fd, "fail");
}
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ auto iter = FindHandler(&lock, out[1]);
+ if (iter == dm_users_.end()) {
+ // After merge is completed, we swap dm-user table with
+ // the underlying dm-linear base device. Hence, worker
+ // threads would have terminted and was removed from
+ // the list.
+ LOG(DEBUG) << "Could not find handler: " << out[1];
+ return Sendmsg(fd, "success");
+ }
+
+ if (!(*iter)->ThreadTerminated()) {
+ (*iter)->snapuserd()->NotifyIOTerminated();
+ }
+ }
if (!RemoveAndJoinHandler(out[1])) {
return Sendmsg(fd, "fail");
}
return Sendmsg(fd, "success");
}
- case DaemonOperations::DETACH: {
+ case DaemonOps::DETACH: {
+ std::lock_guard<std::mutex> lock(lock_);
+ TerminateMergeThreads(&lock);
terminating_ = true;
return true;
}
- case DaemonOperations::SUPPORTS: {
+ case DaemonOps::SUPPORTS: {
if (out.size() != 2) {
LOG(ERROR) << "Malformed supports message, " << out.size() << " parts";
return Sendmsg(fd, "fail");
@@ -216,6 +236,52 @@
}
return Sendmsg(fd, "fail");
}
+ case DaemonOps::INITIATE: {
+ if (out.size() != 2) {
+ LOG(ERROR) << "Malformed initiate-merge message, " << out.size() << " parts";
+ return Sendmsg(fd, "fail");
+ }
+ if (out[0] == "initiate_merge") {
+ std::lock_guard<std::mutex> lock(lock_);
+ auto iter = FindHandler(&lock, out[1]);
+ if (iter == dm_users_.end()) {
+ LOG(ERROR) << "Could not find handler: " << out[1];
+ return Sendmsg(fd, "fail");
+ }
+
+ if (!StartMerge(*iter)) {
+ return Sendmsg(fd, "fail");
+ }
+
+ return Sendmsg(fd, "success");
+ }
+ return Sendmsg(fd, "fail");
+ }
+ case DaemonOps::PERCENTAGE: {
+ std::lock_guard<std::mutex> lock(lock_);
+ double percentage = GetMergePercentage(&lock);
+
+ return Sendmsg(fd, std::to_string(percentage));
+ }
+ case DaemonOps::GETSTATUS: {
+ // Message format:
+ // getstatus,<misc_name>
+ if (out.size() != 2) {
+ LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
+ return Sendmsg(fd, "snapshot-merge-failed");
+ }
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ auto iter = FindHandler(&lock, out[1]);
+ if (iter == dm_users_.end()) {
+ LOG(ERROR) << "Could not find handler: " << out[1];
+ return Sendmsg(fd, "snapshot-merge-failed");
+ }
+
+ std::string merge_status = GetMergeStatus(*iter);
+ return Sendmsg(fd, merge_status);
+ }
+ }
default: {
LOG(ERROR) << "Received unknown message type from client";
Sendmsg(fd, "fail");
@@ -224,7 +290,7 @@
}
}
-void SnapuserdServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
+void SnapuserServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
handler->snapuserd()->SetSocketPresent(is_socket_present_);
@@ -241,6 +307,8 @@
{
std::lock_guard<std::mutex> lock(lock_);
+ num_partitions_merge_complete_ += 1;
+ handler->SetThreadTerminated();
auto iter = FindHandler(&lock, handler->misc_name());
if (iter == dm_users_.end()) {
// RemoveAndJoinHandler() already removed us from the list, and is
@@ -265,10 +333,11 @@
// WaitForDelete() is called, the handler is either in the list, or
// it's not and its resources are guaranteed to be freed.
handler->FreeResources();
+ dm_users_.erase(iter);
}
}
-bool SnapuserdServer::Start(const std::string& socketname) {
+bool SnapuserServer::Start(const std::string& socketname) {
bool start_listening = true;
sockfd_.reset(android_get_control_socket(socketname.c_str()));
@@ -284,7 +353,7 @@
return StartWithSocket(start_listening);
}
-bool SnapuserdServer::StartWithSocket(bool start_listening) {
+bool SnapuserServer::StartWithSocket(bool start_listening) {
if (start_listening && listen(sockfd_.get(), 4) < 0) {
PLOG(ERROR) << "listen socket failed";
return false;
@@ -305,7 +374,7 @@
return true;
}
-bool SnapuserdServer::Run() {
+bool SnapuserServer::Run() {
LOG(INFO) << "Now listening on snapuserd socket";
while (!IsTerminating()) {
@@ -337,7 +406,7 @@
return true;
}
-void SnapuserdServer::JoinAllThreads() {
+void SnapuserServer::JoinAllThreads() {
// Acquire the thread list within the lock.
std::vector<std::shared_ptr<DmUserHandler>> dm_users;
{
@@ -352,14 +421,14 @@
}
}
-void SnapuserdServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
+void SnapuserServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
struct pollfd p = {};
p.fd = fd.get();
p.events = events;
watched_fds_.emplace_back(std::move(p));
}
-void SnapuserdServer::AcceptClient() {
+void SnapuserServer::AcceptClient() {
int fd = TEMP_FAILURE_RETRY(accept4(sockfd_.get(), nullptr, nullptr, SOCK_CLOEXEC));
if (fd < 0) {
PLOG(ERROR) << "accept4 failed";
@@ -369,7 +438,7 @@
AddWatchedFd(fd, POLLIN);
}
-bool SnapuserdServer::HandleClient(android::base::borrowed_fd fd, int revents) {
+bool SnapuserServer::HandleClient(android::base::borrowed_fd fd, int revents) {
if (revents & POLLHUP) {
LOG(DEBUG) << "Snapuserd client disconnected";
return false;
@@ -386,16 +455,18 @@
return true;
}
-void SnapuserdServer::Interrupt() {
+void SnapuserServer::Interrupt() {
// Force close the socket so poll() fails.
sockfd_ = {};
SetTerminating();
}
-std::shared_ptr<DmUserHandler> SnapuserdServer::AddHandler(const std::string& misc_name,
- const std::string& cow_device_path,
- const std::string& backing_device) {
- auto snapuserd = std::make_shared<Snapuserd>(misc_name, cow_device_path, backing_device);
+std::shared_ptr<DmUserHandler> SnapuserServer::AddHandler(const std::string& misc_name,
+ const std::string& cow_device_path,
+ const std::string& backing_device,
+ const std::string& base_path_merge) {
+ auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
+ base_path_merge);
if (!snapuserd->InitCowDevice()) {
LOG(ERROR) << "Failed to initialize Snapuserd";
return nullptr;
@@ -418,7 +489,7 @@
return handler;
}
-bool SnapuserdServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
+bool SnapuserServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
if (handler->snapuserd()->IsAttached()) {
LOG(ERROR) << "Handler already attached";
return false;
@@ -426,12 +497,22 @@
handler->snapuserd()->AttachControlDevice();
- handler->thread() = std::thread(std::bind(&SnapuserdServer::RunThread, this, handler));
+ handler->thread() = std::thread(std::bind(&SnapuserServer::RunThread, this, handler));
return true;
}
-auto SnapuserdServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
- const std::string& misc_name) -> HandlerList::iterator {
+bool SnapuserServer::StartMerge(const std::shared_ptr<DmUserHandler>& handler) {
+ if (!handler->snapuserd()->IsAttached()) {
+ LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
+ return false;
+ }
+
+ handler->snapuserd()->InitiateMerge();
+ return true;
+}
+
+auto SnapuserServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+ const std::string& misc_name) -> HandlerList::iterator {
CHECK(proof_of_lock);
for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
@@ -442,7 +523,51 @@
return dm_users_.end();
}
-bool SnapuserdServer::RemoveAndJoinHandler(const std::string& misc_name) {
+void SnapuserServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
+ CHECK(proof_of_lock);
+
+ for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+ if (!(*iter)->ThreadTerminated()) {
+ (*iter)->snapuserd()->NotifyIOTerminated();
+ }
+ }
+}
+
+std::string SnapuserServer::GetMergeStatus(const std::shared_ptr<DmUserHandler>& handler) {
+ return handler->snapuserd()->GetMergeStatus();
+}
+
+double SnapuserServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock) {
+ CHECK(proof_of_lock);
+ double percentage = 0.0;
+ int n = 0;
+
+ for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+ auto& th = (*iter)->thread();
+ if (th.joinable()) {
+ // Merge percentage by individual partitions wherein merge is still
+ // in-progress
+ percentage += (*iter)->snapuserd()->GetMergePercentage();
+ n += 1;
+ }
+ }
+
+ // Calculate final merge including those partitions where merge was already
+ // completed - num_partitions_merge_complete_ will track them when each
+ // thread exists in RunThread.
+ int total_partitions = n + num_partitions_merge_complete_;
+
+ if (total_partitions) {
+ percentage = ((num_partitions_merge_complete_ * 100.0) + percentage) / total_partitions;
+ }
+
+ LOG(DEBUG) << "Merge %: " << percentage
+ << " num_partitions_merge_complete_: " << num_partitions_merge_complete_
+ << " total_partitions: " << total_partitions << " n: " << n;
+ return percentage;
+}
+
+bool SnapuserServer::RemoveAndJoinHandler(const std::string& misc_name) {
std::shared_ptr<DmUserHandler> handler;
{
std::lock_guard<std::mutex> lock(lock_);
@@ -463,7 +588,7 @@
return true;
}
-bool SnapuserdServer::WaitForSocket() {
+bool SnapuserServer::WaitForSocket() {
auto scope_guard = android::base::make_scope_guard([this]() -> void { JoinAllThreads(); });
auto socket_path = ANDROID_SOCKET_DIR "/"s + kSnapuserdSocketProxy;
@@ -517,7 +642,7 @@
return Run();
}
-bool SnapuserdServer::RunForSocketHandoff() {
+bool SnapuserServer::RunForSocketHandoff() {
unique_fd proxy_fd(android_get_control_socket(kSnapuserdSocketProxy));
if (proxy_fd < 0) {
PLOG(FATAL) << "Proxy could not get android control socket " << kSnapuserdSocketProxy;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
new file mode 100644
index 0000000..e93621c
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -0,0 +1,142 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <poll.h>
+
+#include <cstdio>
+#include <cstring>
+#include <functional>
+#include <future>
+#include <iostream>
+#include <mutex>
+#include <sstream>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include "snapuserd_core.h"
+
+namespace android {
+namespace snapshot {
+
+static constexpr uint32_t MAX_PACKET_SIZE = 512;
+
+enum class DaemonOps {
+ INIT,
+ START,
+ QUERY,
+ STOP,
+ DELETE,
+ DETACH,
+ SUPPORTS,
+ INITIATE,
+ PERCENTAGE,
+ GETSTATUS,
+ INVALID,
+};
+
+class DmUserHandler {
+ public:
+ explicit DmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd);
+
+ void FreeResources() {
+ // Each worker thread holds a reference to snapuserd.
+ // Clear them so that all the resources
+ // held by snapuserd is released
+ if (snapuserd_) {
+ snapuserd_->FreeResources();
+ snapuserd_ = nullptr;
+ }
+ }
+ const std::shared_ptr<SnapshotHandler>& snapuserd() const { return snapuserd_; }
+ std::thread& thread() { return thread_; }
+
+ const std::string& misc_name() const { return misc_name_; }
+ bool ThreadTerminated() { return thread_terminated_; }
+ void SetThreadTerminated() { thread_terminated_ = true; }
+
+ private:
+ std::thread thread_;
+ std::shared_ptr<SnapshotHandler> snapuserd_;
+ std::string misc_name_;
+ bool thread_terminated_ = false;
+};
+
+class SnapuserServer {
+ private:
+ android::base::unique_fd sockfd_;
+ bool terminating_;
+ volatile bool received_socket_signal_ = false;
+ std::vector<struct pollfd> watched_fds_;
+ bool is_socket_present_ = false;
+ int num_partitions_merge_complete_ = 0;
+
+ std::mutex lock_;
+
+ using HandlerList = std::vector<std::shared_ptr<DmUserHandler>>;
+ HandlerList dm_users_;
+
+ void AddWatchedFd(android::base::borrowed_fd fd, int events);
+ void AcceptClient();
+ bool HandleClient(android::base::borrowed_fd fd, int revents);
+ bool Recv(android::base::borrowed_fd fd, std::string* data);
+ bool Sendmsg(android::base::borrowed_fd fd, const std::string& msg);
+ bool Receivemsg(android::base::borrowed_fd fd, const std::string& str);
+
+ void ShutdownThreads();
+ bool RemoveAndJoinHandler(const std::string& control_device);
+ DaemonOps Resolveop(std::string& input);
+ std::string GetDaemonStatus();
+ void Parsemsg(std::string const& msg, const char delim, std::vector<std::string>& out);
+
+ bool IsTerminating() { return terminating_; }
+
+ void RunThread(std::shared_ptr<DmUserHandler> handler);
+ void JoinAllThreads();
+ bool StartWithSocket(bool start_listening);
+
+ // Find a DmUserHandler within a lock.
+ HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+ const std::string& misc_name);
+
+ double GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock);
+ void TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock);
+
+ public:
+ SnapuserServer() { terminating_ = false; }
+ ~SnapuserServer();
+
+ bool Start(const std::string& socketname);
+ bool Run();
+ void Interrupt();
+ bool RunForSocketHandoff();
+ bool WaitForSocket();
+
+ std::shared_ptr<DmUserHandler> AddHandler(const std::string& misc_name,
+ const std::string& cow_device_path,
+ const std::string& backing_device,
+ const std::string& base_path_merge);
+ bool StartHandler(const std::shared_ptr<DmUserHandler>& handler);
+ bool StartMerge(const std::shared_ptr<DmUserHandler>& handler);
+ std::string GetMergeStatus(const std::shared_ptr<DmUserHandler>& handler);
+
+ void SetTerminating() { terminating_ = true; }
+ void ReceivedSocketSignal() { received_socket_signal_ = true; }
+};
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
new file mode 100644
index 0000000..6c91fde
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
@@ -0,0 +1,647 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd_core.h"
+
+/*
+ * Readahead is used to optimize the merge of COPY and XOR Ops.
+ *
+ * We create a scratch space of 2MB to store the read-ahead data in the COW
+ * device.
+ *
+ * +-----------------------+
+ * | Header (fixed) |
+ * +-----------------------+
+ * | Scratch space | <-- 2MB
+ * +-----------------------+
+ *
+ * Scratch space is as follows:
+ *
+ * +-----------------------+
+ * | Metadata | <- 4k page
+ * +-----------------------+
+ * | Metadata | <- 4k page
+ * +-----------------------+
+ * | |
+ * | Read-ahead data |
+ * | |
+ * +-----------------------+
+ *
+ *
+ * * ===================================================================
+ *
+ * Example:
+ *
+ * We have 6 copy operations to be executed in OTA. Update-engine
+ * will write to COW file as follows:
+ *
+ * Op-1: 20 -> 23
+ * Op-2: 19 -> 22
+ * Op-3: 18 -> 21
+ * Op-4: 17 -> 20
+ * Op-5: 16 -> 19
+ * Op-6: 15 -> 18
+ *
+ * Read-ahead thread will read all the 6 source blocks and store the data in the
+ * scratch space. Metadata will contain the destination block numbers. Thus,
+ * scratch space will look something like this:
+ *
+ * +--------------+
+ * | Block 23 |
+ * | offset - 1 |
+ * +--------------+
+ * | Block 22 |
+ * | offset - 2 |
+ * +--------------+
+ * | Block 21 |
+ * | offset - 3 |
+ * +--------------+
+ * ...
+ * ...
+ * +--------------+
+ * | Data-Block 20| <-- offset - 1
+ * +--------------+
+ * | Data-Block 19| <-- offset - 2
+ * +--------------+
+ * | Data-Block 18| <-- offset - 3
+ * +--------------+
+ * ...
+ * ...
+ *
+ * ====================================================================
+ *
+ *
+ * Read-ahead thread will process the COW Ops in fixed set. Consider
+ * the following example:
+ *
+ * +--------------------------+
+ * |op-1|op-2|op-3|....|op-510|
+ * +--------------------------+
+ *
+ * <------ One RA Block ------>
+ *
+ * RA thread will read 510 ordered COW ops at a time and will store
+ * the data in the scratch space.
+ *
+ * RA thread and Merge thread will go lock-step wherein RA thread
+ * will make sure that 510 COW operation data are read upfront
+ * and is in memory. Thus, when merge thread will pick up the data
+ * directly from memory and write it back to base device.
+ *
+ *
+ * +--------------------------+------------------------------------+
+ * |op-1|op-2|op-3|....|op-510|op-511|op-512|op-513........|op-1020|
+ * +--------------------------+------------------------------------+
+ *
+ * <------Merge 510 Blocks----><-Prepare 510 blocks for merge by RA->
+ * ^ ^
+ * | |
+ * Merge thread RA thread
+ *
+ * Both Merge and RA thread will strive to work in parallel.
+ *
+ * ===========================================================================
+ *
+ * State transitions and communication between RA thread and Merge thread:
+ *
+ * Merge Thread RA Thread
+ * ----------------------------------------------------------------------------
+ *
+ * | |
+ * WAIT for RA Block N READ one RA Block (N)
+ * for merge |
+ * | |
+ * | |
+ * <--------------MERGE BEGIN--------READ Block N done(copy to scratch)
+ * | |
+ * | |
+ * Merge Begin Block N READ one RA BLock (N+1)
+ * | |
+ * | |
+ * | READ done. Wait for merge complete
+ * | |
+ * | WAIT
+ * | |
+ * Merge done Block N |
+ * ----------------MERGE READY-------------->|
+ * WAIT for RA Block N+1 Copy RA Block (N+1)
+ * for merge to scratch space
+ * | |
+ * <---------------MERGE BEGIN---------BLOCK N+1 Done
+ * | |
+ * | |
+ * Merge Begin Block N+1 READ one RA BLock (N+2)
+ * | |
+ * | |
+ * | READ done. Wait for merge complete
+ * | |
+ * | WAIT
+ * | |
+ * Merge done Block N+1 |
+ * ----------------MERGE READY-------------->|
+ * WAIT for RA Block N+2 Copy RA Block (N+2)
+ * for merge to scratch space
+ * | |
+ * <---------------MERGE BEGIN---------BLOCK N+2 Done
+ */
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+// This is invoked once primarily by update-engine to initiate
+// the merge
+void SnapshotHandler::InitiateMerge() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ merge_initiated_ = true;
+
+ // If there are only REPLACE ops to be merged, then we need
+ // to explicitly set the state to MERGE_BEGIN as there
+ // is no read-ahead thread
+ if (!ra_thread_) {
+ io_state_ = MERGE_IO_TRANSITION::MERGE_BEGIN;
+ }
+ }
+ cv.notify_all();
+}
+
+// Invoked by Merge thread - Waits on RA thread to resume merging. Will
+// be waken up RA thread.
+bool SnapshotHandler::WaitForMergeBegin() {
+ {
+ std::unique_lock<std::mutex> lock(lock_);
+ while (!MergeInitiated()) {
+ cv.wait(lock);
+
+ if (io_state_ == MERGE_IO_TRANSITION::READ_AHEAD_FAILURE ||
+ io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED) {
+ return false;
+ }
+ }
+
+ while (!(io_state_ == MERGE_IO_TRANSITION::MERGE_BEGIN ||
+ io_state_ == MERGE_IO_TRANSITION::READ_AHEAD_FAILURE ||
+ io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED)) {
+ cv.wait(lock);
+ }
+
+ if (io_state_ == MERGE_IO_TRANSITION::READ_AHEAD_FAILURE ||
+ io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED) {
+ return false;
+ }
+
+ return true;
+ }
+}
+
+// Invoked by RA thread - Flushes the RA block to scratch space if necessary
+// and then notifies the merge thread to resume merging
+bool SnapshotHandler::ReadAheadIOCompleted(bool sync) {
+ if (sync) {
+ // Flush the entire buffer region
+ int ret = msync(mapped_addr_, total_mapped_addr_length_, MS_SYNC);
+ if (ret < 0) {
+ PLOG(ERROR) << "msync failed after ReadAheadIOCompleted: " << ret;
+ return false;
+ }
+
+ // Metadata and data are synced. Now, update the state.
+ // We need to update the state after flushing data; if there is a crash
+ // when read-ahead IO is in progress, the state of data in the COW file
+ // is unknown. kCowReadAheadDone acts as a checkpoint wherein the data
+ // in the scratch space is good and during next reboot, read-ahead thread
+ // can safely re-construct the data.
+ struct BufferState* ra_state = GetBufferState();
+ ra_state->read_ahead_state = kCowReadAheadDone;
+
+ ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
+ if (ret < 0) {
+ PLOG(ERROR) << "msync failed to flush Readahead completion state...";
+ return false;
+ }
+ }
+
+ // Notify the merge thread to resume merging
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ if (io_state_ != MERGE_IO_TRANSITION::IO_TERMINATED &&
+ io_state_ != MERGE_IO_TRANSITION::MERGE_FAILED) {
+ io_state_ = MERGE_IO_TRANSITION::MERGE_BEGIN;
+ }
+ }
+
+ cv.notify_all();
+ return true;
+}
+
+// Invoked by RA thread - Waits for merge thread to finish merging
+// RA Block N - RA thread would be ready will with Block N+1 but
+// will wait to merge thread to finish Block N. Once Block N
+// is merged, RA thread will be woken up by Merge thread and will
+// flush the data of Block N+1 to scratch space
+bool SnapshotHandler::WaitForMergeReady() {
+ {
+ std::unique_lock<std::mutex> lock(lock_);
+ while (!(io_state_ == MERGE_IO_TRANSITION::MERGE_READY ||
+ io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED ||
+ io_state_ == MERGE_IO_TRANSITION::MERGE_COMPLETE ||
+ io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED)) {
+ cv.wait(lock);
+ }
+
+ // Check if merge failed
+ if (io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED ||
+ io_state_ == MERGE_IO_TRANSITION::MERGE_COMPLETE ||
+ io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED) {
+ return false;
+ }
+ return true;
+ }
+}
+
+// Invoked by Merge thread - Notify RA thread about Merge completion
+// for Block N and wake up
+void SnapshotHandler::NotifyRAForMergeReady() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ if (io_state_ != MERGE_IO_TRANSITION::IO_TERMINATED &&
+ io_state_ != MERGE_IO_TRANSITION::READ_AHEAD_FAILURE) {
+ io_state_ = MERGE_IO_TRANSITION::MERGE_READY;
+ }
+ }
+
+ cv.notify_all();
+}
+
+// The following transitions are mostly in the failure paths
+void SnapshotHandler::MergeFailed() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = MERGE_IO_TRANSITION::MERGE_FAILED;
+ }
+
+ cv.notify_all();
+}
+
+void SnapshotHandler::MergeCompleted() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = MERGE_IO_TRANSITION::MERGE_COMPLETE;
+ }
+
+ cv.notify_all();
+}
+
+// This is invoked by worker threads.
+//
+// Worker threads are terminated either by two scenarios:
+//
+// 1: If dm-user device is destroyed
+// 2: We had an I/O failure when reading root partitions
+//
+// In case (1), this would be a graceful shutdown. In this case, merge
+// thread and RA thread should have _already_ terminated by this point. We will be
+// destroying the dm-user device only _after_ merge is completed.
+//
+// In case (2), if merge thread had started, then it will be
+// continuing to merge; however, since we had an I/O failure and the
+// I/O on root partitions are no longer served, we will terminate the
+// merge.
+//
+// This functions is about handling case (2)
+void SnapshotHandler::NotifyIOTerminated() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = MERGE_IO_TRANSITION::IO_TERMINATED;
+ }
+
+ cv.notify_all();
+}
+
+bool SnapshotHandler::IsIOTerminated() {
+ std::lock_guard<std::mutex> lock(lock_);
+ return (io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED);
+}
+
+// Invoked by RA thread
+void SnapshotHandler::ReadAheadIOFailed() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = MERGE_IO_TRANSITION::READ_AHEAD_FAILURE;
+ }
+
+ cv.notify_all();
+}
+
+void SnapshotHandler::WaitForMergeComplete() {
+ std::unique_lock<std::mutex> lock(lock_);
+ while (!(io_state_ == MERGE_IO_TRANSITION::MERGE_COMPLETE ||
+ io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED ||
+ io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED)) {
+ cv.wait(lock);
+ }
+}
+
+std::string SnapshotHandler::GetMergeStatus() {
+ bool merge_not_initiated = false;
+ bool merge_failed = false;
+
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ if (!MergeInitiated()) {
+ merge_not_initiated = true;
+ }
+
+ if (io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED) {
+ merge_failed = true;
+ }
+ }
+
+ struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+ bool merge_complete = (ch->num_merge_ops == reader_->get_num_total_data_ops());
+
+ if (merge_not_initiated) {
+ // Merge was not initiated yet; however, we have merge completion
+ // recorded in the COW Header. This can happen if the device was
+ // rebooted during merge. During next reboot, libsnapshot will
+ // query the status and if the merge is completed, then snapshot-status
+ // file will be deleted
+ if (merge_complete) {
+ return "snapshot-merge-complete";
+ }
+
+ // Return the state as "snapshot". If the device was rebooted during
+ // merge, we will return the status as "snapshot". This is ok, as
+ // libsnapshot will explicitly resume the merge. This is slightly
+ // different from kernel snapshot wherein once the snapshot was switched
+ // to merge target, during next boot, we immediately switch to merge
+ // target. We don't do that here because, during first stage init, we
+ // don't want to initiate the merge. The problem is that we have daemon
+ // transition between first and second stage init. If the merge was
+ // started, then we will have to quiesce the merge before switching
+ // the dm tables. Instead, we just wait until second stage daemon is up
+ // before resuming the merge.
+ return "snapshot";
+ }
+
+ if (merge_failed) {
+ return "snapshot-merge-failed";
+ }
+
+ // Merge complete
+ if (merge_complete) {
+ return "snapshot-merge-complete";
+ }
+
+ // Merge is in-progress
+ return "snapshot-merge";
+}
+
+//========== End of Read-ahead state transition functions ====================
+
+/*
+ * Root partitions are mounted off dm-user and the I/O's are served
+ * by snapuserd worker threads.
+ *
+ * When there is an I/O request to be served by worker threads, we check
+ * if the corresponding sector is "changed" due to OTA by doing a lookup.
+ * If the lookup succeeds then the sector has been changed and that can
+ * either fall into 4 COW operations viz: COPY, XOR, REPLACE and ZERO.
+ *
+ * For the case of REPLACE and ZERO ops, there is not much of a concern
+ * as there is no dependency between blocks. Hence all the I/O request
+ * mapped to these two COW operations will be served by reading the COW device.
+ *
+ * However, COPY and XOR ops are tricky. Since the merge operations are
+ * in-progress, we cannot just go and read from the source device. We need
+ * to be in sync with the state of the merge thread before serving the I/O.
+ *
+ * Given that we know merge thread processes a set of COW ops called as RA
+ * Blocks - These set of COW ops are fixed size wherein each Block comprises
+ * of 510 COW ops.
+ *
+ * +--------------------------+
+ * |op-1|op-2|op-3|....|op-510|
+ * +--------------------------+
+ *
+ * <------ Merge Group Block N ------>
+ *
+ * Thus, a Merge Group Block N, will fall into one of these states and will
+ * transition the states in the following order:
+ *
+ * 1: GROUP_MERGE_PENDING
+ * 2: GROUP_MERGE_RA_READY
+ * 2: GROUP_MERGE_IN_PROGRESS
+ * 3: GROUP_MERGE_COMPLETED
+ * 4: GROUP_MERGE_FAILED
+ *
+ * Let's say that we have the I/O request from dm-user whose sector gets mapped
+ * to a COPY operation with op-10 in the above "Merge Group Block N".
+ *
+ * 1: If the Group is in "GROUP_MERGE_PENDING" state:
+ *
+ * Just read the data from source block based on COW op->source field. Note,
+ * that we will take a ref count on "Block N". This ref count will prevent
+ * merge thread to begin merging if there are any pending I/Os. Once the I/O
+ * is completed, ref count on "Group N" is decremented. Merge thread will
+ * resume merging "Group N" if there are no pending I/Os.
+ *
+ * 2: If the Group is in "GROUP_MERGE_IN_PROGRESS" or "GROUP_MERGE_RA_READY" state:
+ *
+ * When the merge thread is ready to process a "Group", it will first move
+ * the state to GROUP_MERGE_PENDING -> GROUP_MERGE_RA_READY. From this point
+ * onwards, I/O will be served from Read-ahead buffer. However, merge thread
+ * cannot start merging this "Group" immediately. If there were any in-flight
+ * I/O requests, merge thread should wait and allow those I/O's to drain.
+ * Once all the in-flight I/O's are completed, merge thread will move the
+ * state from "GROUP_MERGE_RA_READY" -> "GROUP_MERGE_IN_PROGRESS". I/O will
+ * be continued to serve from Read-ahead buffer during the entire duration
+ * of the merge.
+ *
+ * See SetMergeInProgress().
+ *
+ * 3: If the Group is in "GROUP_MERGE_COMPLETED" state:
+ *
+ * This is straightforward. We just read the data directly from "Base"
+ * device. We should not be reading the COW op->source field.
+ *
+ * 4: If the Block is in "GROUP_MERGE_FAILED" state:
+ *
+ * Terminate the I/O with an I/O error as we don't know which "op" in the
+ * "Group" failed.
+ *
+ * Transition ensures that the I/O from root partitions are never made to
+ * wait and are processed immediately. Thus the state transition for any
+ * "Group" is:
+ *
+ * GROUP_MERGE_PENDING
+ * |
+ * |
+ * v
+ * GROUP_MERGE_RA_READY
+ * |
+ * |
+ * v
+ * GROUP_MERGE_IN_PROGRESS
+ * |
+ * |----------------------------(on failure)
+ * | |
+ * v v
+ * GROUP_MERGE_COMPLETED GROUP_MERGE_FAILED
+ *
+ */
+
+// Invoked by Merge thread
+void SnapshotHandler::SetMergeCompleted(size_t ra_index) {
+ MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+ {
+ std::lock_guard<std::mutex> lock(blk_state->m_lock);
+
+ CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS);
+ CHECK(blk_state->num_ios_in_progress == 0);
+
+ // Merge is complete - All I/O henceforth should be read directly
+ // from base device
+ blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_COMPLETED;
+ }
+}
+
+// Invoked by Merge thread. This is called just before the beginning
+// of merging a given Block of 510 ops. If there are any in-flight I/O's
+// from dm-user then wait for them to complete.
+void SnapshotHandler::SetMergeInProgress(size_t ra_index) {
+ MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+ {
+ std::unique_lock<std::mutex> lock(blk_state->m_lock);
+
+ CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_PENDING);
+
+ // First set the state to RA_READY so that in-flight I/O will drain
+ // and any new I/O will start reading from RA buffer
+ blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_RA_READY;
+
+ // Wait if there are any in-flight I/O's - we cannot merge at this point
+ while (!(blk_state->num_ios_in_progress == 0)) {
+ blk_state->m_cv.wait(lock);
+ }
+
+ blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS;
+ }
+}
+
+// Invoked by Merge thread on failure
+void SnapshotHandler::SetMergeFailed(size_t ra_index) {
+ MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+ {
+ std::unique_lock<std::mutex> lock(blk_state->m_lock);
+
+ blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_FAILED;
+ }
+}
+
+// Invoked by worker threads when I/O is complete on a "MERGE_PENDING"
+// Block. If there are no more in-flight I/Os, wake up merge thread
+// to resume merging.
+void SnapshotHandler::NotifyIOCompletion(uint64_t new_block) {
+ auto it = block_to_ra_index_.find(new_block);
+ CHECK(it != block_to_ra_index_.end()) << " invalid block: " << new_block;
+
+ bool pending_ios = true;
+
+ int ra_index = it->second;
+ MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+ {
+ std::unique_lock<std::mutex> lock(blk_state->m_lock);
+
+ CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_PENDING);
+ blk_state->num_ios_in_progress -= 1;
+ if (blk_state->num_ios_in_progress == 0) {
+ pending_ios = false;
+ }
+ }
+
+ // Give a chance to merge-thread to resume merge
+ // as there are no pending I/O.
+ if (!pending_ios) {
+ blk_state->m_cv.notify_all();
+ }
+}
+
+bool SnapshotHandler::GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block,
+ void* buffer) {
+ if (!lock->owns_lock()) {
+ SNAP_LOG(ERROR) << "GetRABuffer - Lock not held";
+ return false;
+ }
+ std::unordered_map<uint64_t, void*>::iterator it = read_ahead_buffer_map_.find(block);
+
+ if (it == read_ahead_buffer_map_.end()) {
+ SNAP_LOG(ERROR) << "Block: " << block << " not found in RA buffer";
+ return false;
+ }
+
+ memcpy(buffer, it->second, BLOCK_SZ);
+ return true;
+}
+
+// Invoked by worker threads in the I/O path. This is called when a sector
+// is mapped to a COPY/XOR COW op.
+MERGE_GROUP_STATE SnapshotHandler::ProcessMergingBlock(uint64_t new_block, void* buffer) {
+ auto it = block_to_ra_index_.find(new_block);
+ if (it == block_to_ra_index_.end()) {
+ return MERGE_GROUP_STATE::GROUP_INVALID;
+ }
+
+ int ra_index = it->second;
+ MergeGroupState* blk_state = merge_blk_state_[ra_index].get();
+ {
+ std::unique_lock<std::mutex> lock(blk_state->m_lock);
+
+ MERGE_GROUP_STATE state = blk_state->merge_state_;
+ switch (state) {
+ case MERGE_GROUP_STATE::GROUP_MERGE_PENDING: {
+ blk_state->num_ios_in_progress += 1; // ref count
+ [[fallthrough]];
+ }
+ case MERGE_GROUP_STATE::GROUP_MERGE_COMPLETED: {
+ [[fallthrough]];
+ }
+ case MERGE_GROUP_STATE::GROUP_MERGE_FAILED: {
+ return state;
+ }
+ // Fetch the data from RA buffer.
+ case MERGE_GROUP_STATE::GROUP_MERGE_RA_READY: {
+ [[fallthrough]];
+ }
+ case MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS: {
+ if (!GetRABuffer(&lock, new_block, buffer)) {
+ return MERGE_GROUP_STATE::GROUP_INVALID;
+ }
+ return state;
+ }
+ default: {
+ return MERGE_GROUP_STATE::GROUP_INVALID;
+ }
+ }
+ }
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index 671de9d..e97afed 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -57,14 +57,14 @@
// Automatically unmap a device upon deletion.
struct AutoUnmapDevice : AutoDevice {
// On destruct, delete |name| from device mapper.
- AutoUnmapDevice(android::dm::DeviceMapper* dm, const std::string& name)
+ AutoUnmapDevice(android::dm::IDeviceMapper* dm, const std::string& name)
: AutoDevice(name), dm_(dm) {}
AutoUnmapDevice(AutoUnmapDevice&& other) = default;
~AutoUnmapDevice();
private:
DISALLOW_COPY_AND_ASSIGN(AutoUnmapDevice);
- android::dm::DeviceMapper* dm_ = nullptr;
+ android::dm::IDeviceMapper* dm_ = nullptr;
};
// Automatically unmap an image upon deletion.
diff --git a/healthd/Android.bp b/healthd/Android.bp
index ec47f68..eaa8e5b 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -106,6 +106,7 @@
cc_library_static {
name: "libhealthd_draw",
+ vendor_available: true,
export_include_dirs: ["."],
static_libs: [
"libcharger_sysprop",
@@ -117,24 +118,34 @@
header_libs: ["libbatteryservice_headers"],
srcs: ["healthd_draw.cpp"],
+
+ target: {
+ vendor: {
+ exclude_static_libs: [
+ "libcharger_sysprop",
+ ],
+ },
+ },
}
cc_library_static {
- name: "libhealthd_charger",
- local_include_dirs: ["include"],
- export_include_dirs: [".", "include"],
+ name: "libhealthd_charger_ui",
+ vendor_available: true,
+ export_include_dirs: [
+ "include",
+ "include_charger",
+ ],
static_libs: [
- "android.hardware.health@1.0-convert",
+ "android.hardware.health-V1-ndk",
+ "android.hardware.health-translate-ndk",
"libcharger_sysprop",
"libhealthd_draw",
"libhealthloop",
- "libhealth2impl",
"libminui",
],
shared_libs: [
- "android.hardware.health@2.1",
"libbase",
"libcutils",
"liblog",
@@ -143,14 +154,60 @@
"libutils",
],
+ header_libs: [
+ "libhealthd_headers",
+ ],
+
+ export_static_lib_headers: [
+ "android.hardware.health-V1-ndk",
+ ],
+
srcs: [
"healthd_mode_charger.cpp",
"AnimationParser.cpp",
],
+
+ target: {
+ vendor: {
+ exclude_static_libs: [
+ "libcharger_sysprop",
+ ],
+ },
+ },
+}
+
+cc_library_static {
+ name: "libhealthd_charger",
+ export_include_dirs: [
+ "include",
+ "include_charger",
+ ],
+
+ static_libs: [
+ "android.hardware.health@1.0-convert",
+ "libcharger_sysprop",
+ "libhealth2impl",
+ "libhealthd_charger_ui",
+ ],
+
+ shared_libs: [
+ "android.hardware.health@2.1",
+ "libbase",
+ "libcutils",
+ "liblog",
+ "libutils",
+ ],
+
+ srcs: [
+ "healthd_mode_charger_hidl.cpp",
+ ],
}
cc_defaults {
name: "charger_defaults",
+ local_include_dirs: [
+ "include_charger",
+ ],
cflags: [
"-Wall",
@@ -174,6 +231,7 @@
static_libs: [
// common
"android.hardware.health@1.0-convert",
+ "android.hardware.health-V1-ndk",
"libbatterymonitor",
"libcharger_sysprop",
"libhealthd_charger_nops",
@@ -183,6 +241,7 @@
// system charger only
"libhealthd_draw",
"libhealthd_charger",
+ "libhealthd_charger_ui",
"libminui",
"libsuspend",
],
@@ -209,6 +268,7 @@
exclude_static_libs: [
"libhealthd_draw",
"libhealthd_charger",
+ "libhealthd_charger_ui",
"libminui",
"libsuspend",
],
@@ -265,3 +325,29 @@
"system_core_charger_res_images_battery_scale.png",
],
}
+
+// /vendor/etc/res/images/charger/battery_fail.png
+prebuilt_etc {
+ name: "system_core_charger_res_images_battery_fail.png_default_vendor",
+ src: "images/battery_fail.png",
+ relative_install_path: "res/images/charger/default",
+ vendor: true,
+ filename: "battery_fail.png",
+}
+
+// /vendor/etc/res/images/charger/battery_scale.png
+prebuilt_etc {
+ name: "system_core_charger_res_images_battery_scale.png_default_vendor",
+ src: "images/battery_scale.png",
+ relative_install_path: "res/images/charger/default",
+ vendor: true,
+ filename: "battery_scale.png",
+}
+
+phony {
+ name: "charger_res_images_vendor",
+ required: [
+ "system_core_charger_res_images_battery_fail.png_default_vendor",
+ "system_core_charger_res_images_battery_scale.png_default_vendor",
+ ],
+}
diff --git a/healthd/charger.cpp b/healthd/charger.cpp
index d03978d..73e04fe 100644
--- a/healthd/charger.cpp
+++ b/healthd/charger.cpp
@@ -15,9 +15,9 @@
*/
#include <android-base/logging.h>
+#include <charger.sysprop.h>
-#include "charger.sysprop.h"
-#include "healthd_mode_charger.h"
+#include "healthd_mode_charger_hidl.h"
#include "healthd_mode_charger_nops.h"
#ifndef CHARGER_FORCE_NO_UI
diff --git a/healthd/charger_test.cpp b/healthd/charger_test.cpp
index e0bde68..dc5c459 100644
--- a/healthd/charger_test.cpp
+++ b/healthd/charger_test.cpp
@@ -31,7 +31,7 @@
#include <health/utils.h>
#include <health2impl/Health.h>
-#include "healthd_mode_charger.h"
+#include "healthd_mode_charger_hidl.h"
using android::hardware::health::InitHealthdConfig;
using android::hardware::health::V2_1::HealthInfo;
@@ -153,7 +153,7 @@
sp<IHealth> passthrough = new TestHealth(std::move(config));
std::thread bgThread([=] {
- android::Charger charger(passthrough);
+ android::ChargerHidl charger(passthrough);
charger.StartLoop();
});
diff --git a/healthd/healthd_draw.cpp b/healthd/healthd_draw.cpp
index 50eee19..4484fa6 100644
--- a/healthd/healthd_draw.cpp
+++ b/healthd/healthd_draw.cpp
@@ -18,19 +18,30 @@
#include <batteryservice/BatteryService.h>
#include <cutils/klog.h>
-#include "charger.sysprop.h"
#include "healthd_draw.h"
+#if !defined(__ANDROID_VNDK__)
+#include "charger.sysprop.h"
+#endif
+
#define LOGE(x...) KLOG_ERROR("charger", x);
#define LOGW(x...) KLOG_WARNING("charger", x);
#define LOGV(x...) KLOG_DEBUG("charger", x);
static bool get_split_screen() {
+#if !defined(__ANDROID_VNDK__)
return android::sysprop::ChargerProperties::draw_split_screen().value_or(false);
+#else
+ return false;
+#endif
}
static int get_split_offset() {
+#if !defined(__ANDROID_VNDK__)
int64_t value = android::sysprop::ChargerProperties::draw_split_offset().value_or(0);
+#else
+ int64_t value = 0;
+#endif
if (value < static_cast<int64_t>(std::numeric_limits<int>::min())) {
LOGW("draw_split_offset = %" PRId64 " overflow for an int; resetting to %d.\n", value,
std::numeric_limits<int>::min());
@@ -46,14 +57,6 @@
HealthdDraw::HealthdDraw(animation* anim)
: kSplitScreen(get_split_screen()), kSplitOffset(get_split_offset()) {
- int ret = gr_init();
-
- if (ret < 0) {
- LOGE("gr_init failed\n");
- graphics_available = false;
- return;
- }
-
graphics_available = true;
sys_font = gr_sys_font();
if (sys_font == nullptr) {
@@ -235,3 +238,11 @@
LOGW("Charging, level unknown\n");
}
}
+
+std::unique_ptr<HealthdDraw> HealthdDraw::Create(animation *anim) {
+ if (gr_init() < 0) {
+ LOGE("gr_init failed\n");
+ return nullptr;
+ }
+ return std::unique_ptr<HealthdDraw>(new HealthdDraw(anim));
+}
diff --git a/healthd/healthd_draw.h b/healthd/healthd_draw.h
index 7c847bd..0b48ce8 100644
--- a/healthd/healthd_draw.h
+++ b/healthd/healthd_draw.h
@@ -26,8 +26,6 @@
class HealthdDraw {
public:
- // Configures font using given animation.
- HealthdDraw(animation* anim);
virtual ~HealthdDraw();
// Redraws screen.
@@ -36,6 +34,8 @@
// Blanks screen if true, unblanks if false.
virtual void blank_screen(bool blank);
+ static std::unique_ptr<HealthdDraw> Create(animation *anim);
+
protected:
virtual void clear_screen();
@@ -76,6 +76,10 @@
// true if minui init'ed OK, false if minui init failed
bool graphics_available;
+
+ private:
+ // Configures font using given animation.
+ HealthdDraw(animation* anim);
};
#endif // HEALTHD_DRAW_H
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index e95efc0..0f9779c 100644
--- a/healthd/healthd_mode_charger.cpp
+++ b/healthd/healthd_mode_charger.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "healthd_mode_charger.h"
+#include <charger/healthd_mode_charger.h>
#include <dirent.h>
#include <errno.h>
@@ -50,27 +50,20 @@
#include <suspend/autosuspend.h>
#include "AnimationParser.h"
-#include "charger.sysprop.h"
-#include "charger_utils.h"
#include "healthd_draw.h"
-#include <android/hardware/health/2.0/IHealthInfoCallback.h>
-#include <health/utils.h>
-#include <health2impl/HalHealthLoop.h>
-#include <health2impl/Health.h>
+#include <aidl/android/hardware/health/BatteryStatus.h>
+#include <health/HealthLoop.h>
#include <healthd/healthd.h>
+#if !defined(__ANDROID_VNDK__)
+#include "charger.sysprop.h"
+#endif
+
using std::string_literals::operator""s;
using namespace android;
-using android::hardware::Return;
-using android::hardware::health::GetHealthServiceOrDefault;
+using aidl::android::hardware::health::BatteryStatus;
using android::hardware::health::HealthLoop;
-using android::hardware::health::V1_0::BatteryStatus;
-using android::hardware::health::V2_0::Result;
-using android::hardware::health::V2_1::IHealth;
-using IHealth_2_0 = android::hardware::health::V2_0::IHealth;
-using HealthInfo_1_0 = android::hardware::health::V1_0::HealthInfo;
-using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;
// main healthd loop
extern int healthd_main(void);
@@ -106,6 +99,13 @@
namespace android {
+#if defined(__ANDROID_VNDK__)
+static constexpr const char* vendor_animation_desc_path =
+ "/vendor/etc/res/values/charger/animation.txt";
+static constexpr const char* vendor_animation_root = "/vendor/etc/res/images/";
+static constexpr const char* vendor_default_animation_root = "/vendor/etc/res/images/default/";
+#else
+
// Legacy animation resources are loaded from this directory.
static constexpr const char* legacy_animation_root = "/res/images/";
@@ -119,6 +119,7 @@
"/product/etc/res/values/charger/animation.txt";
static constexpr const char* product_animation_root = "/product/etc/res/images/";
static constexpr const char* animation_desc_path = "/res/values/charger/animation.txt";
+#endif
static const animation BASE_ANIMATION = {
.text_clock =
@@ -199,8 +200,8 @@
};
}
-Charger::Charger(const sp<IHealth>& service)
- : HalHealthLoop("charger", service), batt_anim_(BASE_ANIMATION) {}
+Charger::Charger(ChargerConfigurationInterface* configuration)
+ : batt_anim_(BASE_ANIMATION), configuration_(configuration) {}
Charger::~Charger() {}
@@ -218,9 +219,7 @@
char* ptr;
size_t len;
- LOGW("\n");
LOGW("*************** LAST KMSG ***************\n");
- LOGW("\n");
const char* kmsg[] = {
// clang-format off
"/sys/fs/pstore/console-ramoops-0",
@@ -263,20 +262,21 @@
}
out:
- LOGW("\n");
LOGW("************* END LAST KMSG *************\n");
- LOGW("\n");
}
-static int request_suspend(bool enable) {
- if (!android::sysprop::ChargerProperties::enable_suspend().value_or(false)) {
+int Charger::RequestEnableSuspend() {
+ if (!configuration_->ChargerEnableSuspend()) {
return 0;
}
+ return autosuspend_enable();
+}
- if (enable)
- return autosuspend_enable();
- else
- return autosuspend_disable();
+int Charger::RequestDisableSuspend() {
+ if (!configuration_->ChargerEnableSuspend()) {
+ return 0;
+ }
+ return autosuspend_disable();
}
static void kick_animation(animation* anim) {
@@ -295,7 +295,7 @@
if (!batt_anim_.run || now < next_screen_transition_) return;
// If battery level is not ready, keep checking in the defined time
- if (health_info_.batteryLevel == 0 && health_info_.batteryStatus == BatteryStatus::UNKNOWN) {
+ if (health_info_.battery_level == 0 && health_info_.battery_status == BatteryStatus::UNKNOWN) {
if (wait_batt_level_timestamp_ == 0) {
// Set max delay time and skip drawing screen
wait_batt_level_timestamp_ = now + MAX_BATT_LEVEL_WAIT_TIME;
@@ -309,28 +309,28 @@
}
if (healthd_draw_ == nullptr) {
- std::optional<bool> out_screen_on;
- service()->shouldKeepScreenOn([&](Result res, bool screen_on) {
- if (res == Result::SUCCESS) {
- *out_screen_on = screen_on;
- }
- });
+ std::optional<bool> out_screen_on = configuration_->ChargerShouldKeepScreenOn();
if (out_screen_on.has_value()) {
if (!*out_screen_on) {
LOGV("[%" PRId64 "] leave screen off\n", now);
batt_anim_.run = false;
next_screen_transition_ = -1;
- if (charger_online()) request_suspend(true);
+ if (configuration_->ChargerIsOnline()) {
+ RequestEnableSuspend();
+ }
return;
}
}
- healthd_draw_.reset(new HealthdDraw(&batt_anim_));
+ healthd_draw_ = HealthdDraw::Create(&batt_anim_);
+ if (healthd_draw_ == nullptr) return;
+#if !defined(__ANDROID_VNDK__)
if (android::sysprop::ChargerProperties::disable_init_blank().value_or(false)) {
healthd_draw_->blank_screen(true);
screen_blanked_ = true;
}
+#endif
}
/* animation is over, blank screen and leave */
@@ -340,7 +340,9 @@
healthd_draw_->blank_screen(true);
screen_blanked_ = true;
LOGV("[%" PRId64 "] animation done\n", now);
- if (charger_online()) request_suspend(true);
+ if (configuration_->ChargerIsOnline()) {
+ RequestEnableSuspend();
+ }
return;
}
@@ -354,9 +356,9 @@
/* animation starting, set up the animation */
if (batt_anim_.cur_frame == 0) {
LOGV("[%" PRId64 "] animation starting\n", now);
- batt_anim_.cur_level = health_info_.batteryLevel;
- batt_anim_.cur_status = (int)health_info_.batteryStatus;
- if (health_info_.batteryLevel >= 0 && batt_anim_.num_frames != 0) {
+ batt_anim_.cur_level = health_info_.battery_level;
+ batt_anim_.cur_status = (int)health_info_.battery_status;
+ if (health_info_.battery_level >= 0 && batt_anim_.num_frames != 0) {
/* find first frame given current battery level */
for (int i = 0; i < batt_anim_.num_frames; i++) {
if (batt_anim_.cur_level >= batt_anim_.frames[i].min_level &&
@@ -366,7 +368,7 @@
}
}
- if (charger_online()) {
+ if (configuration_->ChargerIsOnline()) {
// repeat the first frame first_frame_repeats times
disp_time = batt_anim_.frames[batt_anim_.cur_frame].disp_time *
batt_anim_.first_frame_repeats;
@@ -397,7 +399,7 @@
/* advance frame cntr to the next valid frame only if we are charging
* if necessary, advance cycle cntr, and reset frame cntr
*/
- if (charger_online()) {
+ if (configuration_->ChargerIsOnline()) {
batt_anim_.cur_frame++;
while (batt_anim_.cur_frame < batt_anim_.num_frames &&
@@ -495,13 +497,13 @@
* rather than on key release
*/
kick_animation(&batt_anim_);
- request_suspend(false);
+ RequestDisableSuspend();
}
} else {
/* if the power key got released, force screen state cycle */
if (key->pending) {
kick_animation(&batt_anim_);
- request_suspend(false);
+ RequestDisableSuspend();
}
}
}
@@ -519,8 +521,8 @@
int timer_shutdown = UNPLUGGED_SHUTDOWN_TIME;
if (!have_battery_state_) return;
- if (!charger_online()) {
- request_suspend(false);
+ if (!configuration_->ChargerIsOnline()) {
+ RequestDisableSuspend();
if (next_pwr_check_ == -1) {
/* Last cycle would have stopped at the extreme top of battery-icon
* Need to show the correct level corresponding to capacity.
@@ -550,7 +552,7 @@
* Reset & kick animation to show complete animation cycles
* when charger connected again.
*/
- request_suspend(false);
+ RequestDisableSuspend();
next_screen_transition_ = now - 1;
reset_animation(&batt_anim_);
kick_animation(&batt_anim_);
@@ -560,7 +562,7 @@
}
}
-void Charger::Heartbeat() {
+void Charger::OnHeartbeat() {
// charger* charger = &charger_state;
int64_t now = curr_time_ms();
@@ -573,22 +575,18 @@
UpdateScreenState(now);
}
-void Charger::OnHealthInfoChanged(const HealthInfo_2_1& health_info) {
- set_charger_online(health_info);
-
+void Charger::OnHealthInfoChanged(const ChargerHealthInfo& health_info) {
if (!have_battery_state_) {
have_battery_state_ = true;
next_screen_transition_ = curr_time_ms() - 1;
- request_suspend(false);
+ RequestDisableSuspend();
reset_animation(&batt_anim_);
kick_animation(&batt_anim_);
}
- health_info_ = health_info.legacy.legacy;
-
- AdjustWakealarmPeriods(charger_online());
+ health_info_ = health_info;
}
-int Charger::PrepareToWait(void) {
+int Charger::OnPrepareToWait(void) {
int64_t now = curr_time_ms();
int64_t next_event = INT64_MAX;
int64_t timeout;
@@ -629,6 +627,16 @@
bool parse_success;
std::string content;
+
+#if defined(__ANDROID_VNDK__)
+ if (base::ReadFileToString(vendor_animation_desc_path, &content)) {
+ parse_success = parse_animation_desc(content, &batt_anim_);
+ batt_anim_.set_resource_root(vendor_animation_root);
+ } else {
+ LOGW("Could not open animation description at %s\n", vendor_animation_desc_path);
+ parse_success = false;
+ }
+#else
if (base::ReadFileToString(product_animation_desc_path, &content)) {
parse_success = parse_animation_desc(content, &batt_anim_);
batt_anim_.set_resource_root(product_animation_root);
@@ -644,17 +652,26 @@
LOGW("Could not open animation description at %s\n", animation_desc_path);
parse_success = false;
}
+#endif
+
+#if defined(__ANDROID_VNDK__)
+ auto default_animation_root = vendor_default_animation_root;
+#else
+ auto default_animation_root = system_animation_root;
+#endif
if (!parse_success) {
- LOGW("Could not parse animation description. Using default animation.\n");
+ LOGW("Could not parse animation description. "
+ "Using default animation with resources at %s\n",
+ default_animation_root);
batt_anim_ = BASE_ANIMATION;
- batt_anim_.animation_file.assign(system_animation_root + "charger/battery_scale.png"s);
+ batt_anim_.animation_file.assign(default_animation_root + "charger/battery_scale.png"s);
InitDefaultAnimationFrames();
batt_anim_.frames = owned_frames_.data();
batt_anim_.num_frames = owned_frames_.size();
}
if (batt_anim_.fail_file.empty()) {
- batt_anim_.fail_file.assign(system_animation_root + "charger/battery_fail.png"s);
+ batt_anim_.fail_file.assign(default_animation_root + "charger/battery_fail.png"s);
}
LOGV("Animation Description:\n");
@@ -675,7 +692,7 @@
}
}
-void Charger::Init(struct healthd_config* config) {
+void Charger::OnInit(struct healthd_config* config) {
int ret;
int i;
int epollfd;
@@ -688,16 +705,18 @@
std::bind(&Charger::InputCallback, this, std::placeholders::_1, std::placeholders::_2));
if (!ret) {
epollfd = ev_get_epollfd();
- RegisterEvent(epollfd, &charger_event_handler, EVENT_WAKEUP_FD);
+ configuration_->ChargerRegisterEvent(epollfd, &charger_event_handler, EVENT_WAKEUP_FD);
}
InitAnimation();
ret = CreateDisplaySurface(batt_anim_.fail_file, &surf_unknown_);
if (ret < 0) {
+#if !defined(__ANDROID_VNDK__)
LOGE("Cannot load custom battery_fail image. Reverting to built in: %d\n", ret);
ret = CreateDisplaySurface((system_animation_root + "charger/battery_fail.png"s).c_str(),
&surf_unknown_);
+#endif
if (ret < 0) {
LOGE("Cannot load built in battery_fail image\n");
surf_unknown_ = NULL;
@@ -733,7 +752,7 @@
wait_batt_level_timestamp_ = 0;
// Retrieve healthd_config from the existing health HAL.
- HalHealthLoop::Init(config);
+ configuration_->ChargerInitConfig(config);
boot_min_cap_ = config->boot_min_cap;
}
@@ -776,25 +795,3 @@
}
} // namespace android
-
-int healthd_charger_main(int argc, char** argv) {
- int ch;
-
- while ((ch = getopt(argc, argv, "cr")) != -1) {
- switch (ch) {
- case 'c':
- // -c is now a noop
- break;
- case 'r':
- // -r is now a noop
- break;
- case '?':
- default:
- LOGE("Unrecognized charger option: %c\n", optopt);
- exit(1);
- }
- }
-
- Charger charger(GetHealthServiceOrDefault());
- return charger.StartLoop();
-}
diff --git a/healthd/healthd_mode_charger.h b/healthd/healthd_mode_charger.h
deleted file mode 100644
index 6f9ae8c..0000000
--- a/healthd/healthd_mode_charger.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <linux/input.h>
-
-#include <memory>
-#include <vector>
-
-#include <android/hardware/health/2.0/IHealthInfoCallback.h>
-#include <android/hardware/health/2.1/IHealth.h>
-#include <health2impl/HalHealthLoop.h>
-
-#include "animation.h"
-
-class GRSurface;
-class HealthdDraw;
-
-namespace android {
-struct key_state {
- bool pending;
- bool down;
- int64_t timestamp;
-};
-
-class Charger : public ::android::hardware::health::V2_1::implementation::HalHealthLoop {
- public:
- using HealthInfo_1_0 = android::hardware::health::V1_0::HealthInfo;
- using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;
-
- Charger(const sp<android::hardware::health::V2_1::IHealth>& service);
- ~Charger();
-
- protected:
- // HealthLoop overrides.
- void Heartbeat() override;
- int PrepareToWait() override;
- void Init(struct healthd_config* config) override;
- // HalHealthLoop overrides
- void OnHealthInfoChanged(const HealthInfo_2_1& health_info) override;
-
- // Allowed to be mocked for testing.
- virtual int CreateDisplaySurface(const std::string& name, GRSurface** surface);
- virtual int CreateMultiDisplaySurface(const std::string& name, int* frames, int* fps,
- GRSurface*** surface);
-
- private:
- void InitDefaultAnimationFrames();
- void UpdateScreenState(int64_t now);
- int SetKeyCallback(int code, int value);
- void UpdateInputState(input_event* ev);
- void SetNextKeyCheck(key_state* key, int64_t timeout);
- void ProcessKey(int code, int64_t now);
- void HandleInputState(int64_t now);
- void HandlePowerSupplyState(int64_t now);
- int InputCallback(int fd, unsigned int epevents);
- void InitAnimation();
-
- bool have_battery_state_ = false;
- bool screen_blanked_ = false;
- int64_t next_screen_transition_ = 0;
- int64_t next_key_check_ = 0;
- int64_t next_pwr_check_ = 0;
- int64_t wait_batt_level_timestamp_ = 0;
-
- key_state keys_[KEY_MAX + 1] = {};
-
- animation batt_anim_;
- GRSurface* surf_unknown_ = nullptr;
- int boot_min_cap_ = 0;
-
- HealthInfo_1_0 health_info_ = {};
- std::unique_ptr<HealthdDraw> healthd_draw_;
- std::vector<animation::frame> owned_frames_;
-};
-} // namespace android
-
-int healthd_charger_main(int argc, char** argv);
diff --git a/healthd/healthd_mode_charger_hidl.cpp b/healthd/healthd_mode_charger_hidl.cpp
new file mode 100644
index 0000000..3a33c02
--- /dev/null
+++ b/healthd/healthd_mode_charger_hidl.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "healthd_mode_charger_hidl.h"
+
+#include <android/hardware/health/2.0/types.h>
+#include <charger.sysprop.h>
+#include <cutils/klog.h>
+
+#include "charger_utils.h"
+
+using android::hardware::health::GetHealthServiceOrDefault;
+using android::hardware::health::V2_0::Result;
+
+namespace android {
+
+ChargerHidl::ChargerHidl(const sp<android::hardware::health::V2_1::IHealth>& service)
+ : HalHealthLoop("charger", service), charger_(std::make_unique<Charger>(this)) {}
+
+void ChargerHidl::OnHealthInfoChanged(const HealthInfo_2_1& health_info) {
+ set_charger_online(health_info);
+
+ charger_->OnHealthInfoChanged(ChargerHealthInfo{
+ .battery_level = health_info.legacy.legacy.batteryLevel,
+ .battery_status = static_cast<::aidl::android::hardware::health::BatteryStatus>(
+ health_info.legacy.legacy.batteryStatus),
+ });
+
+ AdjustWakealarmPeriods(charger_online());
+}
+
+std::optional<bool> ChargerHidl::ChargerShouldKeepScreenOn() {
+ std::optional<bool> out_screen_on;
+ service()->shouldKeepScreenOn([&](Result res, bool screen_on) {
+ if (res == Result::SUCCESS) {
+ *out_screen_on = screen_on;
+ }
+ });
+ return out_screen_on;
+}
+
+bool ChargerHidl::ChargerEnableSuspend() {
+ return android::sysprop::ChargerProperties::enable_suspend().value_or(false);
+}
+
+} // namespace android
+
+int healthd_charger_main(int argc, char** argv) {
+ int ch;
+
+ while ((ch = getopt(argc, argv, "cr")) != -1) {
+ switch (ch) {
+ case 'c':
+ // -c is now a noop
+ break;
+ case 'r':
+ // -r is now a noop
+ break;
+ case '?':
+ default:
+ KLOG_ERROR("charger", "Unrecognized charger option: %c\n", optopt);
+ exit(1);
+ }
+ }
+
+ android::ChargerHidl charger(GetHealthServiceOrDefault());
+ return charger.StartLoop();
+}
diff --git a/healthd/healthd_mode_charger_hidl.h b/healthd/healthd_mode_charger_hidl.h
new file mode 100644
index 0000000..0149d07
--- /dev/null
+++ b/healthd/healthd_mode_charger_hidl.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <health2impl/HalHealthLoop.h>
+
+#include <charger/healthd_mode_charger.h>
+
+namespace android {
+
+// An implementation of Charger backed by HIDL implementation. Uses HIDL health
+// HAL's HalHealthLoop.
+class ChargerHidl : public ::android::ChargerConfigurationInterface,
+ public ::android::hardware::health::V2_1::implementation::HalHealthLoop {
+ using HalHealthLoop = ::android::hardware::health::V2_1::implementation::HalHealthLoop;
+ using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;
+
+ public:
+ explicit ChargerHidl(const sp<android::hardware::health::V2_1::IHealth>& service);
+ std::optional<bool> ChargerShouldKeepScreenOn() override;
+ bool ChargerIsOnline() override { return HalHealthLoop::charger_online(); }
+ void ChargerInitConfig(healthd_config* config) override { return HalHealthLoop::Init(config); }
+ int ChargerRegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) override {
+ return HalHealthLoop::RegisterEvent(fd, func, wakeup);
+ }
+ bool ChargerEnableSuspend() override;
+ // HealthLoop overrides
+ void Heartbeat() override { charger_->OnHeartbeat(); }
+ int PrepareToWait() override { return charger_->OnPrepareToWait(); }
+ void Init(struct healthd_config* config) override { charger_->OnInit(config); }
+ // HalHealthLoop overrides
+ void OnHealthInfoChanged(const HealthInfo_2_1& health_info) override;
+
+ private:
+ sp<android::hardware::health::V2_1::IHealth> service_;
+ std::unique_ptr<Charger> charger_;
+};
+
+} // namespace android
+
+int healthd_charger_main(int argc, char** argv);
diff --git a/healthd/healthd_mode_charger_test.cpp b/healthd/healthd_mode_charger_test.cpp
index f444f66..b7aace3 100644
--- a/healthd/healthd_mode_charger_test.cpp
+++ b/healthd/healthd_mode_charger_test.cpp
@@ -23,11 +23,12 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
+#include <android/hardware/health/2.1/IHealth.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <health/utils.h>
-#include "healthd_mode_charger.h"
+#include "healthd_mode_charger_hidl.h"
using android::hardware::Return;
using android::hardware::health::InitHealthdConfig;
@@ -102,12 +103,12 @@
MOCK_METHOD(Return<void>, shouldKeepScreenOn, (shouldKeepScreenOn_cb _hidl_cb));
};
-class TestCharger : public Charger {
+class TestCharger : public ChargerHidl {
public:
// Inherit constructor.
- using Charger::Charger;
+ using ChargerHidl::ChargerHidl;
// Expose protected functions to be used in tests.
- void Init(struct healthd_config* config) override { Charger::Init(config); }
+ void Init(struct healthd_config* config) override { ChargerHidl::Init(config); }
MOCK_METHOD(int, CreateDisplaySurface, (const std::string& name, GRSurface** surface));
MOCK_METHOD(int, CreateMultiDisplaySurface,
(const std::string& name, int* frames, int* fps, GRSurface*** surface));
diff --git a/healthd/include_charger/charger/healthd_mode_charger.h b/healthd/include_charger/charger/healthd_mode_charger.h
new file mode 100644
index 0000000..216e5ad
--- /dev/null
+++ b/healthd/include_charger/charger/healthd_mode_charger.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <linux/input.h>
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include <aidl/android/hardware/health/BatteryStatus.h>
+#include <health/HealthLoop.h>
+#include <healthd/healthd.h>
+
+#include "animation.h"
+
+class GRSurface;
+class HealthdDraw;
+
+namespace android {
+struct key_state {
+ bool pending;
+ bool down;
+ int64_t timestamp;
+};
+
+// Health info that interests charger
+struct ChargerHealthInfo {
+ int32_t battery_level;
+ aidl::android::hardware::health::BatteryStatus battery_status;
+};
+
+// Configuration interface for charger. This includes:
+// - HalHealthLoop APIs that interests charger.
+// - configuration values that used to be provided by sysprops
+class ChargerConfigurationInterface {
+ public:
+ virtual ~ChargerConfigurationInterface() = default;
+ // HalHealthLoop related APIs
+ virtual std::optional<bool> ChargerShouldKeepScreenOn() = 0;
+ virtual bool ChargerIsOnline() = 0;
+ virtual void ChargerInitConfig(healthd_config* config) = 0;
+ using BoundFunction =
+ std::function<void(android::hardware::health::HealthLoop*, uint32_t /* epevents */)>;
+ virtual int ChargerRegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) = 0;
+
+ // Other configuration values
+ virtual bool ChargerEnableSuspend() = 0;
+};
+
+// charger UI
+class Charger {
+ public:
+ explicit Charger(ChargerConfigurationInterface* configuration);
+ virtual ~Charger();
+
+ // Hooks for ChargerConfigurationInterface
+ void OnHeartbeat();
+ int OnPrepareToWait();
+ // |cookie| is passed to ChargerConfigurationInterface::ChargerInitConfig
+ void OnInit(struct healthd_config* config);
+ void OnHealthInfoChanged(const ChargerHealthInfo& health_info);
+
+ protected:
+ // Allowed to be mocked for testing.
+ virtual int CreateDisplaySurface(const std::string& name, GRSurface** surface);
+ virtual int CreateMultiDisplaySurface(const std::string& name, int* frames, int* fps,
+ GRSurface*** surface);
+
+ private:
+ void InitDefaultAnimationFrames();
+ void UpdateScreenState(int64_t now);
+ int SetKeyCallback(int code, int value);
+ void UpdateInputState(input_event* ev);
+ void SetNextKeyCheck(key_state* key, int64_t timeout);
+ void ProcessKey(int code, int64_t now);
+ void HandleInputState(int64_t now);
+ void HandlePowerSupplyState(int64_t now);
+ int InputCallback(int fd, unsigned int epevents);
+ void InitAnimation();
+ int RequestEnableSuspend();
+ int RequestDisableSuspend();
+
+ bool have_battery_state_ = false;
+ bool screen_blanked_ = false;
+ int64_t next_screen_transition_ = 0;
+ int64_t next_key_check_ = 0;
+ int64_t next_pwr_check_ = 0;
+ int64_t wait_batt_level_timestamp_ = 0;
+
+ key_state keys_[KEY_MAX + 1] = {};
+
+ animation batt_anim_;
+ GRSurface* surf_unknown_ = nullptr;
+ int boot_min_cap_ = 0;
+
+ ChargerHealthInfo health_info_ = {};
+ std::unique_ptr<HealthdDraw> healthd_draw_;
+ std::vector<animation::frame> owned_frames_;
+
+ ChargerConfigurationInterface* configuration_;
+};
+} // namespace android
diff --git a/init/Android.bp b/init/Android.bp
index 5d09687..66427dc 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -89,7 +89,19 @@
"host_init_verifier.cpp",
]
-cc_defaults {
+soong_config_module_type {
+ name: "libinit_cc_defaults",
+ module_type: "cc_defaults",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT",
+ ],
+ properties: [
+ "cflags",
+ ],
+}
+
+libinit_cc_defaults {
name: "init_defaults",
sanitize: {
misc_undefined: ["signed-integer-overflow"],
@@ -109,6 +121,7 @@
"-DDUMP_ON_UMOUNT_FAILURE=0",
"-DSHUTDOWN_ZERO_TIMEOUT=0",
"-DINIT_FULL_SOURCES",
+ "-DINSTALL_DEBUG_POLICY_TO_SYSTEM_EXT=0",
],
product_variables: {
debuggable: {
@@ -137,6 +150,14 @@
cppflags: ["-DUSER_MODE_LINUX"],
},
},
+ soong_config_variables: {
+ PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT: {
+ cflags: [
+ "-UINSTALL_DEBUG_POLICY_TO_SYSTEM_EXT",
+ "-DINSTALL_DEBUG_POLICY_TO_SYSTEM_EXT=1",
+ ],
+ },
+ },
static_libs: [
"libavb",
"libc++fs",
@@ -300,7 +321,6 @@
"first_stage_mount.cpp",
"reboot_utils.cpp",
"selabel.cpp",
- "selinux.cpp",
"service_utils.cpp",
"snapuserd_transition.cpp",
"switch_root.cpp",
@@ -315,23 +335,16 @@
"libfec",
"libfec_rs",
"libsquashfs_utils",
- "liblogwrap",
- "libext4_utils",
"libcrypto_utils",
- "libsparse",
"libavb",
- "libkeyutils",
"liblp",
"libcutils",
"libbase",
"liblog",
"libcrypto_static",
- "libdl",
- "libz",
"libselinux",
"libcap",
"libgsi",
- "libcom.android.sysprop.apex",
"liblzma",
"libunwindstack_no_dex",
"libbacktrace_no_dex",
@@ -435,6 +448,7 @@
srcs: [
"devices_test.cpp",
+ "epoll_test.cpp",
"firmware_handler_test.cpp",
"init_test.cpp",
"keychords_test.cpp",
@@ -442,6 +456,7 @@
"persistent_properties_test.cpp",
"property_service_test.cpp",
"property_type_test.cpp",
+ "reboot_test.cpp",
"rlimit_parser_test.cpp",
"service_test.cpp",
"subcontext_test.cpp",
diff --git a/init/README.md b/init/README.md
index f447ab2..6c29b07 100644
--- a/init/README.md
+++ b/init/README.md
@@ -77,6 +77,43 @@
conflict resolution when multiple services are added to the system, as
each one will go into a separate file.
+Versioned RC files within APEXs
+-------------------------------
+
+With the arrival of mainline on Android Q, the individual mainline
+modules carry their own init.rc files within their boundaries. Init
+processes these files according to the naming pattern `/apex/*/etc/*rc`.
+
+Because APEX modules must run on more than one release of Android,
+they may require different parameters as part of the services they
+define. This is achieved, starting in Android T, by incorporating
+the SDK version information in the name of the init file. The suffix
+is changed from `.rc` to `.#rc` where # is the first SDK where that
+RC file is accepted. An init file specific to SDK=31 might be named
+`init.31rc`. With this scheme, an APEX may include multiple init files. An
+example is appropriate.
+
+For an APEX module with the following files in /apex/sample-module/apex/etc/:
+
+ 1. init.rc
+ 2. init.32rc
+ 4. init.35rc
+
+The selection rule chooses the highest `.#rc` value that does not
+exceed the SDK of the currently running system. The unadorned `.rc`
+is interpreted as sdk=0.
+
+When this APEX is installed on a device with SDK <=31, the system will
+process init.rc. When installed on a device running SDK 32, 33, or 34,
+it will use init.32rc. When installed on a device running SDKs >= 35,
+it will choose init.35rc
+
+This versioning scheme is used only for the init files within APEX
+modules; it does not apply to the init files stored in /system/etc/init,
+/vendor/etc/init, or other directories.
+
+This naming scheme is available after Android S.
+
Actions
-------
Actions are named sequences of commands. Actions have a trigger which
@@ -693,10 +730,13 @@
fstab.${ro.hardware} or fstab.${ro.hardware.platform} will be scanned for
under /odm/etc, /vendor/etc, or / at runtime, in that order.
-`verity_update_state <mount-point>`
+`verity_update_state`
> Internal implementation detail used to update dm-verity state and
set the partition._mount-point_.verified properties used by adb remount
- because fs\_mgr can't set them directly itself.
+ because fs\_mgr can't set them directly itself. This is required since
+ Android 12, because CtsNativeVerifiedBootTestCases will read property
+ "partition.${partition}.verified.hash_alg" to check that sha1 is not used.
+ See https://r.android.com/1546980 for more details.
`wait <path> [ <timeout> ]`
> Poll for the existence of the given file and return when found,
diff --git a/init/TEST_MAPPING b/init/TEST_MAPPING
index 905d41e..03b9eaa 100644
--- a/init/TEST_MAPPING
+++ b/init/TEST_MAPPING
@@ -5,6 +5,9 @@
},
{
"name": "init_kill_services_test"
+ },
+ {
+ "name": "MicrodroidHostTestCases"
}
]
}
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 7633d9c..763a147 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -28,6 +28,7 @@
#include <net/if.h>
#include <sched.h>
#include <signal.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -42,6 +43,7 @@
#include <sys/wait.h>
#include <unistd.h>
+#include <map>
#include <memory>
#include <ApexProperties.sysprop.h>
@@ -894,9 +896,11 @@
std::string partition = entry.mount_point == "/" ? "system" : Basename(entry.mount_point);
SetProperty("partition." + partition + ".verified", std::to_string(mode));
- std::string hash_alg = fs_mgr_get_hashtree_algorithm(entry);
- if (!hash_alg.empty()) {
- SetProperty("partition." + partition + ".verified.hash_alg", hash_alg);
+ auto hashtree_info = fs_mgr_get_hashtree_info(entry);
+ if (hashtree_info) {
+ SetProperty("partition." + partition + ".verified.hash_alg", hashtree_info->algorithm);
+ SetProperty("partition." + partition + ".verified.root_digest",
+ hashtree_info->root_digest);
}
}
@@ -1311,7 +1315,7 @@
static Result<void> parse_apex_configs() {
glob_t glob_result;
- static constexpr char glob_pattern[] = "/apex/*/etc/*.rc";
+ static constexpr char glob_pattern[] = "/apex/*/etc/*rc";
const int ret = glob(glob_pattern, GLOB_MARK, nullptr, &glob_result);
if (ret != 0 && ret != GLOB_NOMATCH) {
globfree(&glob_result);
@@ -1328,17 +1332,66 @@
if (paths.size() >= 3 && paths[2].find('@') != std::string::npos) {
continue;
}
+ // Filter directories
+ if (path.back() == '/') {
+ continue;
+ }
configs.push_back(path);
}
globfree(&glob_result);
- bool success = true;
+ // Compare all files /apex/path.#rc and /apex/path.rc with the same "/apex/path" prefix,
+ // choosing the one with the highest # that doesn't exceed the system's SDK.
+ // (.rc == .0rc for ranking purposes)
+ //
+ int active_sdk = android::base::GetIntProperty("ro.build.version.sdk", INT_MAX);
+
+ std::map<std::string, std::pair<std::string, int>> script_map;
+
for (const auto& c : configs) {
- if (c.back() == '/') {
- // skip if directory
+ int sdk = 0;
+ const std::vector<std::string> parts = android::base::Split(c, ".");
+ std::string base;
+ if (parts.size() < 2) {
continue;
}
- success &= parser.ParseConfigFile(c);
+
+ // parts[size()-1], aka the suffix, should be "rc" or "#rc"
+ // any other pattern gets discarded
+
+ const auto& suffix = parts[parts.size() - 1];
+ if (suffix == "rc") {
+ sdk = 0;
+ } else {
+ char trailer[9] = {0};
+ int r = sscanf(suffix.c_str(), "%d%8s", &sdk, trailer);
+ if (r != 2) {
+ continue;
+ }
+ if (strlen(trailer) > 2 || strcmp(trailer, "rc") != 0) {
+ continue;
+ }
+ }
+
+ if (sdk < 0 || sdk > active_sdk) {
+ continue;
+ }
+
+ base = parts[0];
+ for (unsigned int i = 1; i < parts.size() - 1; i++) {
+ base = base + "." + parts[i];
+ }
+
+ // is this preferred over what we already have
+ auto it = script_map.find(base);
+ if (it == script_map.end() || it->second.second < sdk) {
+ script_map[base] = std::make_pair(c, sdk);
+ }
+ }
+
+ bool success = true;
+ for (const auto& m : script_map) {
+ success &= parser.ParseConfigFile(m.second.first);
}
ServiceList::GetInstance().MarkServicesUpdate();
if (success) {
diff --git a/init/epoll.cpp b/init/epoll.cpp
index 17d63fa..74d8aac 100644
--- a/init/epoll.cpp
+++ b/init/epoll.cpp
@@ -38,11 +38,12 @@
return {};
}
-Result<void> Epoll::RegisterHandler(int fd, std::function<void()> handler, uint32_t events) {
+Result<void> Epoll::RegisterHandler(int fd, Handler handler, uint32_t events) {
if (!events) {
return Error() << "Must specify events";
}
- auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(handler));
+ auto sp = std::make_shared<decltype(handler)>(std::move(handler));
+ auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(sp));
if (!inserted) {
return Error() << "Cannot specify two epoll handlers for a given FD";
}
@@ -69,7 +70,7 @@
return {};
}
-Result<std::vector<std::function<void()>*>> Epoll::Wait(
+Result<std::vector<std::shared_ptr<Epoll::Handler>>> Epoll::Wait(
std::optional<std::chrono::milliseconds> timeout) {
int timeout_ms = -1;
if (timeout && timeout->count() < INT_MAX) {
@@ -81,9 +82,10 @@
if (num_events == -1) {
return ErrnoError() << "epoll_wait failed";
}
- std::vector<std::function<void()>*> pending_functions;
+ std::vector<std::shared_ptr<Handler>> pending_functions;
for (int i = 0; i < num_events; ++i) {
- pending_functions.emplace_back(reinterpret_cast<std::function<void()>*>(ev[i].data.ptr));
+ auto sp = *reinterpret_cast<std::shared_ptr<Handler>*>(ev[i].data.ptr);
+ pending_functions.emplace_back(std::move(sp));
}
return pending_functions;
diff --git a/init/epoll.h b/init/epoll.h
index c32a661..0df5289 100644
--- a/init/epoll.h
+++ b/init/epoll.h
@@ -22,6 +22,7 @@
#include <chrono>
#include <functional>
#include <map>
+#include <memory>
#include <optional>
#include <vector>
@@ -36,15 +37,17 @@
public:
Epoll();
+ typedef std::function<void()> Handler;
+
Result<void> Open();
- Result<void> RegisterHandler(int fd, std::function<void()> handler, uint32_t events = EPOLLIN);
+ Result<void> RegisterHandler(int fd, Handler handler, uint32_t events = EPOLLIN);
Result<void> UnregisterHandler(int fd);
- Result<std::vector<std::function<void()>*>> Wait(
+ Result<std::vector<std::shared_ptr<Handler>>> Wait(
std::optional<std::chrono::milliseconds> timeout);
private:
android::base::unique_fd epoll_fd_;
- std::map<int, std::function<void()>> epoll_handlers_;
+ std::map<int, std::shared_ptr<Handler>> epoll_handlers_;
};
} // namespace init
diff --git a/init/epoll_test.cpp b/init/epoll_test.cpp
new file mode 100644
index 0000000..9236cd5
--- /dev/null
+++ b/init/epoll_test.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "epoll.h"
+
+#include <sys/unistd.h>
+
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+
+namespace android {
+namespace init {
+
+std::unordered_set<void*> sValidObjects;
+
+class CatchDtor final {
+ public:
+ CatchDtor() { sValidObjects.emplace(this); }
+ CatchDtor(const CatchDtor&) { sValidObjects.emplace(this); }
+ ~CatchDtor() {
+ auto iter = sValidObjects.find(this);
+ if (iter != sValidObjects.end()) {
+ sValidObjects.erase(iter);
+ }
+ }
+};
+
+TEST(epoll, UnregisterHandler) {
+ Epoll epoll;
+ ASSERT_RESULT_OK(epoll.Open());
+
+ int fds[2];
+ ASSERT_EQ(pipe(fds), 0);
+
+ CatchDtor catch_dtor;
+ bool handler_invoked;
+ auto handler = [&, catch_dtor]() -> void {
+ auto result = epoll.UnregisterHandler(fds[0]);
+ ASSERT_EQ(result.ok(), !handler_invoked);
+ handler_invoked = true;
+ ASSERT_NE(sValidObjects.find((void*)&catch_dtor), sValidObjects.end());
+ };
+
+ epoll.RegisterHandler(fds[0], std::move(handler));
+
+ uint8_t byte = 0xee;
+ ASSERT_TRUE(android::base::WriteFully(fds[1], &byte, sizeof(byte)));
+
+ auto results = epoll.Wait({});
+ ASSERT_RESULT_OK(results);
+ ASSERT_EQ(results->size(), size_t(1));
+
+ for (const auto& function : *results) {
+ (*function)();
+ (*function)();
+ }
+ ASSERT_TRUE(handler_invoked);
+}
+
+} // namespace init
+} // namespace android
diff --git a/init/first_stage_console.cpp b/init/first_stage_console.cpp
index e2ea0ab..67cac19 100644
--- a/init/first_stage_console.cpp
+++ b/init/first_stage_console.cpp
@@ -85,7 +85,10 @@
void StartConsole(const std::string& cmdline) {
bool console = KernelConsolePresent(cmdline);
+ // Use a simple sigchld handler -- first_stage_console doesn't need to track or log zombies
+ const struct sigaction chld_act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDWAIT };
+ sigaction(SIGCHLD, &chld_act, nullptr);
pid_t pid = fork();
if (pid != 0) {
int status;
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 78e5b60..c7b7b0c 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -330,14 +330,21 @@
// If "/force_debuggable" is present, the second-stage init will use a userdebug
// sepolicy and load adb_debug.prop to allow adb root, if the device is unlocked.
if (access("/force_debuggable", F_OK) == 0) {
+ constexpr const char adb_debug_prop_src[] = "/adb_debug.prop";
+ constexpr const char userdebug_plat_sepolicy_cil_src[] = "/userdebug_plat_sepolicy.cil";
std::error_code ec; // to invoke the overloaded copy_file() that won't throw.
- if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) ||
- !fs::copy_file("/userdebug_plat_sepolicy.cil", kDebugRamdiskSEPolicy, ec)) {
- LOG(ERROR) << "Failed to setup debug ramdisk";
- } else {
- // setenv for second-stage init to read above kDebugRamdisk* files.
- setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
+ if (access(adb_debug_prop_src, F_OK) == 0 &&
+ !fs::copy_file(adb_debug_prop_src, kDebugRamdiskProp, ec)) {
+ LOG(WARNING) << "Can't copy " << adb_debug_prop_src << " to " << kDebugRamdiskProp
+ << ": " << ec.message();
}
+ if (access(userdebug_plat_sepolicy_cil_src, F_OK) == 0 &&
+ !fs::copy_file(userdebug_plat_sepolicy_cil_src, kDebugRamdiskSEPolicy, ec)) {
+ LOG(WARNING) << "Can't copy " << userdebug_plat_sepolicy_cil_src << " to "
+ << kDebugRamdiskSEPolicy << ": " << ec.message();
+ }
+ // setenv for second-stage init to read above kDebugRamdisk* files.
+ setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
}
if (ForceNormalBoot(cmdline, bootconfig)) {
diff --git a/init/init.cpp b/init/init.cpp
index bde8e04..e3596cb 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -27,6 +27,7 @@
#include <sys/mount.h>
#include <sys/signalfd.h>
#include <sys/types.h>
+#include <sys/utsname.h>
#include <unistd.h>
#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
@@ -554,6 +555,19 @@
}
}
+/// Set ro.kernel.version property to contain the major.minor pair as returned
+/// by uname(2).
+static void SetKernelVersion() {
+ struct utsname uts;
+ unsigned int major, minor;
+
+ if ((uname(&uts) != 0) || (sscanf(uts.release, "%u.%u", &major, &minor) != 2)) {
+ LOG(ERROR) << "Could not parse the kernel version from uname";
+ return;
+ }
+ SetProperty("ro.kernel.version", android::base::StringPrintf("%u.%u", major, minor));
+}
+
static void HandleSigtermSignal(const signalfd_siginfo& siginfo) {
if (siginfo.ssi_pid != 0) {
// Drop any userspace SIGTERM requests.
@@ -858,6 +872,7 @@
export_oem_lock_status();
MountHandler mount_handler(&epoll);
SetUsbController();
+ SetKernelVersion();
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
Action::set_function_map(&function_map);
diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp
index 593e5ae..bce1cc3 100644
--- a/init/mount_namespace.cpp
+++ b/init/mount_namespace.cpp
@@ -82,11 +82,6 @@
return updatable;
}
-static bool IsMicrodroid() {
- static bool is_microdroid = android::base::GetProperty("ro.hardware", "") == "microdroid";
- return is_microdroid;
-}
-
// In case we have two sets of APEXes (non-updatable, updatable), we need two separate mount
// namespaces.
static bool NeedsTwoMountNamespaces() {
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 2d67bf5..70e26ec 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -100,6 +100,7 @@
constexpr auto LEGACY_ID_PROP = "ro.build.legacy.id";
constexpr auto VBMETA_DIGEST_PROP = "ro.boot.vbmeta.digest";
constexpr auto DIGEST_SIZE_USED = 8;
+constexpr auto API_LEVEL_CURRENT = 10000;
static bool persistent_properties_loaded = false;
@@ -1017,6 +1018,39 @@
}
}
+static int read_api_level_props(const std::vector<std::string>& api_level_props) {
+ int api_level = API_LEVEL_CURRENT;
+ for (const auto& api_level_prop : api_level_props) {
+ api_level = android::base::GetIntProperty(api_level_prop, API_LEVEL_CURRENT);
+ if (api_level != API_LEVEL_CURRENT) {
+ break;
+ }
+ }
+ return api_level;
+}
+
+static void property_initialize_ro_vendor_api_level() {
+ // ro.vendor.api_level shows the api_level that the vendor images (vendor, odm, ...) are
+ // required to support.
+ constexpr auto VENDOR_API_LEVEL_PROP = "ro.vendor.api_level";
+
+ // Api level properties of the board. The order of the properties must be kept.
+ std::vector<std::string> BOARD_API_LEVEL_PROPS = {
+ "ro.board.api_level", "ro.board.first_api_level", "ro.vendor.build.version.sdk"};
+ // Api level properties of the device. The order of the properties must be kept.
+ std::vector<std::string> DEVICE_API_LEVEL_PROPS = {"ro.product.first_api_level",
+ "ro.build.version.sdk"};
+
+ int api_level = std::min(read_api_level_props(BOARD_API_LEVEL_PROPS),
+ read_api_level_props(DEVICE_API_LEVEL_PROPS));
+ std::string error;
+ uint32_t res = PropertySet(VENDOR_API_LEVEL_PROP, std::to_string(api_level), &error);
+ if (res != PROP_SUCCESS) {
+ LOG(ERROR) << "Failed to set " << VENDOR_API_LEVEL_PROP << " with " << api_level << ": "
+ << error << "(" << res << ")";
+ }
+}
+
void PropertyLoadBootDefaults() {
// We read the properties and their values into a map, in order to always allow properties
// loaded in the later property files to override the properties in loaded in the earlier
@@ -1102,6 +1136,7 @@
property_derive_build_fingerprint();
property_derive_legacy_build_fingerprint();
property_initialize_ro_cpu_abilist();
+ property_initialize_ro_vendor_api_level();
update_sys_usb_config();
}
@@ -1140,10 +1175,8 @@
LoadPropertyInfoFromFile("/system_ext/etc/selinux/system_ext_property_contexts",
&property_infos);
}
- if (!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
- &property_infos)) {
- // Fallback to nonplat_* if vendor_* doesn't exist.
- LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts",
+ if (access("/vendor/etc/selinux/vendor_property_contexts", R_OK) != -1) {
+ LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
&property_infos);
}
if (access("/product/etc/selinux/product_property_contexts", R_OK) != -1) {
@@ -1158,10 +1191,7 @@
return;
}
LoadPropertyInfoFromFile("/system_ext_property_contexts", &property_infos);
- if (!LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos)) {
- // Fallback to nonplat_* if vendor_* doesn't exist.
- LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
- }
+ LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos);
LoadPropertyInfoFromFile("/product_property_contexts", &property_infos);
LoadPropertyInfoFromFile("/odm_property_contexts", &property_infos);
}
diff --git a/init/property_service_test.cpp b/init/property_service_test.cpp
index ac6b7b2..5f34cc4 100644
--- a/init/property_service_test.cpp
+++ b/init/property_service_test.cpp
@@ -99,7 +99,7 @@
std::string vbmeta_digest = GetProperty("ro.boot.vbmeta.digest", "");
ASSERT_GE(vbmeta_digest.size(), 8u);
- std::string build_id = GetProperty("ro.boot.build.id", "");
+ std::string build_id = GetProperty("ro.build.id", "");
// Check that the build id is constructed with the prefix of vbmeta digest
std::string expected_build_id = legacy_build_id + "." + vbmeta_digest.substr(0, 8);
ASSERT_EQ(expected_build_id, build_id);
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 1681627..6aa9912 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -550,8 +550,8 @@
// Like StopServices, but also logs all the services that failed to stop after the provided timeout.
// Returns number of violators.
-static int StopServicesAndLogViolations(const std::set<std::string>& services,
- std::chrono::milliseconds timeout, bool terminate) {
+int StopServicesAndLogViolations(const std::set<std::string>& services,
+ std::chrono::milliseconds timeout, bool terminate) {
StopServices(services, timeout, terminate);
int still_running = 0;
for (const auto& s : ServiceList::GetInstance()) {
diff --git a/init/reboot.h b/init/reboot.h
index 81c3edc..551a114 100644
--- a/init/reboot.h
+++ b/init/reboot.h
@@ -17,11 +17,17 @@
#ifndef _INIT_REBOOT_H
#define _INIT_REBOOT_H
+#include <chrono>
+#include <set>
#include <string>
namespace android {
namespace init {
+// Like StopServices, but also logs all the services that failed to stop after the provided timeout.
+// Returns number of violators.
+int StopServicesAndLogViolations(const std::set<std::string>& services,
+ std::chrono::milliseconds timeout, bool terminate);
// Parses and handles a setprop sys.powerctl message.
void HandlePowerctlMessage(const std::string& command);
diff --git a/init/reboot_test.cpp b/init/reboot_test.cpp
new file mode 100644
index 0000000..b3d038d
--- /dev/null
+++ b/init/reboot_test.cpp
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "reboot.h"
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string_view>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+#include <selinux/selinux.h>
+
+#include "builtin_arguments.h"
+#include "builtins.h"
+#include "parser.h"
+#include "service_list.h"
+#include "service_parser.h"
+#include "subcontext.h"
+#include "util.h"
+
+using namespace std::literals;
+
+using android::base::GetProperty;
+using android::base::Join;
+using android::base::SetProperty;
+using android::base::Split;
+using android::base::StringReplace;
+using android::base::WaitForProperty;
+using android::base::WriteStringToFd;
+
+namespace android {
+namespace init {
+
+class RebootTest : public ::testing::Test {
+ public:
+ RebootTest() {
+ std::vector<std::string> names = GetServiceNames();
+ if (!names.empty()) {
+ ADD_FAILURE() << "Expected empty ServiceList but found: [" << Join(names, ',') << "]";
+ }
+ }
+
+ ~RebootTest() {
+ std::vector<std::string> names = GetServiceNames();
+ for (const auto& name : names) {
+ auto s = ServiceList::GetInstance().FindService(name);
+ auto pid = s->pid();
+ ServiceList::GetInstance().RemoveService(*s);
+ if (pid > 0) {
+ kill(pid, SIGTERM);
+ kill(pid, SIGKILL);
+ }
+ }
+ }
+
+ private:
+ std::vector<std::string> GetServiceNames() const {
+ std::vector<std::string> names;
+ for (const auto& s : ServiceList::GetInstance()) {
+ names.push_back(s->name());
+ }
+ return names;
+ }
+};
+
+std::string GetSecurityContext() {
+ char* ctx;
+ if (getcon(&ctx) == -1) {
+ ADD_FAILURE() << "Failed to call getcon : " << strerror(errno);
+ }
+ std::string result = std::string(ctx);
+ freecon(ctx);
+ return result;
+}
+
+void AddTestService(const std::string& name) {
+ static constexpr std::string_view kScriptTemplate = R"init(
+service $name /system/bin/yes
+ user shell
+ group shell
+ seclabel $selabel
+)init";
+
+ std::string script = StringReplace(StringReplace(kScriptTemplate, "$name", name, false),
+ "$selabel", GetSecurityContext(), false);
+ ServiceList& service_list = ServiceList::GetInstance();
+ Parser parser;
+ parser.AddSectionParser("service",
+ std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+
+ TemporaryFile tf;
+ ASSERT_TRUE(tf.fd != -1);
+ ASSERT_TRUE(WriteStringToFd(script, tf.fd));
+ ASSERT_TRUE(parser.ParseConfig(tf.path));
+}
+
+TEST_F(RebootTest, StopServicesSIGTERM) {
+ if (getuid() != 0) {
+ GTEST_SKIP() << "Skipping test, must be run as root.";
+ return;
+ }
+
+ AddTestService("A");
+ AddTestService("B");
+
+ auto service_a = ServiceList::GetInstance().FindService("A");
+ ASSERT_NE(nullptr, service_a);
+ auto service_b = ServiceList::GetInstance().FindService("B");
+ ASSERT_NE(nullptr, service_b);
+
+ ASSERT_RESULT_OK(service_a->Start());
+ ASSERT_TRUE(service_a->IsRunning());
+ ASSERT_RESULT_OK(service_b->Start());
+ ASSERT_TRUE(service_b->IsRunning());
+
+ std::unique_ptr<Service> oneshot_service;
+ {
+ auto result = Service::MakeTemporaryOneshotService(
+ {"exec", GetSecurityContext(), "--", "/system/bin/yes"});
+ ASSERT_RESULT_OK(result);
+ oneshot_service = std::move(*result);
+ }
+ std::string oneshot_service_name = oneshot_service->name();
+ oneshot_service->Start();
+ ASSERT_TRUE(oneshot_service->IsRunning());
+ ServiceList::GetInstance().AddService(std::move(oneshot_service));
+
+ EXPECT_EQ(0, StopServicesAndLogViolations({"A", "B", oneshot_service_name}, 10s,
+ /* terminate= */ true));
+ EXPECT_FALSE(service_a->IsRunning());
+ EXPECT_FALSE(service_b->IsRunning());
+ // Oneshot services are deleted from the ServiceList after they are destroyed.
+ auto oneshot_service_after_stop = ServiceList::GetInstance().FindService(oneshot_service_name);
+ EXPECT_EQ(nullptr, oneshot_service_after_stop);
+}
+
+TEST_F(RebootTest, StopServicesSIGKILL) {
+ if (getuid() != 0) {
+ GTEST_SKIP() << "Skipping test, must be run as root.";
+ return;
+ }
+
+ AddTestService("A");
+ AddTestService("B");
+
+ auto service_a = ServiceList::GetInstance().FindService("A");
+ ASSERT_NE(nullptr, service_a);
+ auto service_b = ServiceList::GetInstance().FindService("B");
+ ASSERT_NE(nullptr, service_b);
+
+ ASSERT_RESULT_OK(service_a->Start());
+ ASSERT_TRUE(service_a->IsRunning());
+ ASSERT_RESULT_OK(service_b->Start());
+ ASSERT_TRUE(service_b->IsRunning());
+
+ std::unique_ptr<Service> oneshot_service;
+ {
+ auto result = Service::MakeTemporaryOneshotService(
+ {"exec", GetSecurityContext(), "--", "/system/bin/yes"});
+ ASSERT_RESULT_OK(result);
+ oneshot_service = std::move(*result);
+ }
+ std::string oneshot_service_name = oneshot_service->name();
+ oneshot_service->Start();
+ ASSERT_TRUE(oneshot_service->IsRunning());
+ ServiceList::GetInstance().AddService(std::move(oneshot_service));
+
+ EXPECT_EQ(0, StopServicesAndLogViolations({"A", "B", oneshot_service_name}, 10s,
+ /* terminate= */ false));
+ EXPECT_FALSE(service_a->IsRunning());
+ EXPECT_FALSE(service_b->IsRunning());
+ // Oneshot services are deleted from the ServiceList after they are destroyed.
+ auto oneshot_service_after_stop = ServiceList::GetInstance().FindService(oneshot_service_name);
+ EXPECT_EQ(nullptr, oneshot_service_after_stop);
+}
+
+} // namespace init
+} // namespace android
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 42d3023..28cd012 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -27,7 +27,7 @@
// file located at /sepolicy and is directly loaded into the kernel SELinux subsystem.
// The split policy is for supporting treble devices. It splits the SEPolicy across files on
-// /system/etc/selinux (the 'plat' portion of the policy) and /vendor/etc/selinux (the 'nonplat'
+// /system/etc/selinux (the 'plat' portion of the policy) and /vendor/etc/selinux (the 'vendor'
// portion of the policy). This is necessary to allow the system image to be updated independently
// of the vendor image, while maintaining contributions from both partitions in the SEPolicy. This
// is especially important for VTS testing, where the SEPolicy on the Google System Image may not be
@@ -295,28 +295,44 @@
return access(plat_policy_cil_file, R_OK) != -1;
}
+std::optional<const char*> GetUserdebugPlatformPolicyFile() {
+ // See if we need to load userdebug_plat_sepolicy.cil instead of plat_sepolicy.cil.
+ const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
+ if (force_debuggable_env && "true"s == force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
+ const std::vector<const char*> debug_policy_candidates = {
+#if INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT == 1
+ "/system_ext/etc/selinux/userdebug_plat_sepolicy.cil",
+#endif
+ kDebugRamdiskSEPolicy,
+ };
+ for (const char* debug_policy : debug_policy_candidates) {
+ if (access(debug_policy, F_OK) == 0) {
+ return debug_policy;
+ }
+ }
+ }
+ return std::nullopt;
+}
+
struct PolicyFile {
unique_fd fd;
std::string path;
};
bool OpenSplitPolicy(PolicyFile* policy_file) {
- // IMPLEMENTATION NOTE: Split policy consists of three CIL files:
+ // IMPLEMENTATION NOTE: Split policy consists of three or more CIL files:
// * platform -- policy needed due to logic contained in the system image,
- // * non-platform -- policy needed due to logic contained in the vendor image,
+ // * vendor -- policy needed due to logic contained in the vendor image,
// * mapping -- mapping policy which helps preserve forward-compatibility of non-platform policy
// with newer versions of platform policy.
- //
+ // * (optional) policy needed due to logic on product, system_ext, or odm images.
// secilc is invoked to compile the above three policy files into a single monolithic policy
// file. This file is then loaded into the kernel.
- // See if we need to load userdebug_plat_sepolicy.cil instead of plat_sepolicy.cil.
- const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
- bool use_userdebug_policy =
- ((force_debuggable_env && "true"s == force_debuggable_env) &&
- AvbHandle::IsDeviceUnlocked() && access(kDebugRamdiskSEPolicy, F_OK) == 0);
+ const auto userdebug_plat_sepolicy = GetUserdebugPlatformPolicyFile();
+ const bool use_userdebug_policy = userdebug_plat_sepolicy.has_value();
if (use_userdebug_policy) {
- LOG(WARNING) << "Using userdebug system sepolicy";
+ LOG(INFO) << "Using userdebug system sepolicy " << *userdebug_plat_sepolicy;
}
// Load precompiled policy from vendor image, if a matching policy is found there. The policy
@@ -388,17 +404,14 @@
product_mapping_file.clear();
}
- // vendor_sepolicy.cil and plat_pub_versioned.cil are the new design to replace
- // nonplat_sepolicy.cil.
- std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil");
std::string vendor_policy_cil_file("/vendor/etc/selinux/vendor_sepolicy.cil");
-
if (access(vendor_policy_cil_file.c_str(), F_OK) == -1) {
- // For backward compatibility.
- // TODO: remove this after no device is using nonplat_sepolicy.cil.
- vendor_policy_cil_file = "/vendor/etc/selinux/nonplat_sepolicy.cil";
- plat_pub_versioned_cil_file.clear();
- } else if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) {
+ LOG(ERROR) << "Missing " << vendor_policy_cil_file;
+ return false;
+ }
+
+ std::string plat_pub_versioned_cil_file("/vendor/etc/selinux/plat_pub_versioned.cil");
+ if (access(plat_pub_versioned_cil_file.c_str(), F_OK) == -1) {
LOG(ERROR) << "Missing " << plat_pub_versioned_cil_file;
return false;
}
@@ -413,7 +426,7 @@
// clang-format off
std::vector<const char*> compile_args {
"/system/bin/secilc",
- use_userdebug_policy ? kDebugRamdiskSEPolicy: plat_policy_cil_file,
+ use_userdebug_policy ? *userdebug_plat_sepolicy : plat_policy_cil_file,
"-m", "-M", "true", "-G", "-N",
"-c", version_as_string.c_str(),
plat_mapping_file.c_str(),
diff --git a/init/subcontext.cpp b/init/subcontext.cpp
index f1fbffe..6eaa80f 100644
--- a/init/subcontext.cpp
+++ b/init/subcontext.cpp
@@ -44,6 +44,7 @@
#endif
using android::base::GetExecutablePath;
+using android::base::GetProperty;
using android::base::Join;
using android::base::Socketpair;
using android::base::Split;
@@ -337,6 +338,11 @@
}
void InitializeSubcontext() {
+ if (IsMicrodroid()) {
+ LOG(INFO) << "Not using subcontext for microdroid";
+ return;
+ }
+
if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_P__) {
subcontext.reset(
new Subcontext(std::vector<std::string>{"/vendor", "/odm"}, kVendorContext));
@@ -351,6 +357,9 @@
}
bool SubcontextChildReap(pid_t pid) {
+ if (!subcontext) {
+ return false;
+ }
if (subcontext->pid() == pid) {
if (!subcontext_terminated_by_shutdown) {
subcontext->Restart();
diff --git a/init/util.cpp b/init/util.cpp
index 9f7bfdb..d1e518b 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -757,5 +757,10 @@
is_default_mount_namespace_ready = true;
}
+bool IsMicrodroid() {
+ static bool is_microdroid = android::base::GetProperty("ro.hardware", "") == "microdroid";
+ return is_microdroid;
+}
+
} // namespace init
} // namespace android
diff --git a/init/util.h b/init/util.h
index daba852..bf53675 100644
--- a/init/util.h
+++ b/init/util.h
@@ -103,5 +103,7 @@
bool IsDefaultMountNamespaceReady();
void SetDefaultMountNamespaceReady();
+
+bool IsMicrodroid();
} // namespace init
} // namespace android
diff --git a/libcutils/include/cutils/list.h b/libcutils/include/cutils/list.h
index dfdc53b..7eb8725 100644
--- a/libcutils/include/cutils/list.h
+++ b/libcutils/include/cutils/list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2013 The Android Open Source Project
+ * Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef _CUTILS_LIST_H_
-#define _CUTILS_LIST_H_
+#pragma once
#include <stddef.h>
@@ -38,9 +37,6 @@
.prev = &(name), \
}
-#define list_for_each(node, list) \
- for ((node) = (list)->next; (node) != (list); (node) = (node)->next)
-
#define list_for_each_reverse(node, list) \
for ((node) = (list)->prev; (node) != (list); (node) = (node)->prev)
@@ -49,6 +45,10 @@
(node) != (list); \
(node) = (n), (n) = (node)->next)
+#define list_for_each(node, list) \
+ for (struct listnode* __n = ((node) = (list)->next)->next; (node) != (list); \
+ (node) = __n, __n = (node)->next)
+
static inline void list_init(struct listnode *node)
{
node->next = node;
@@ -84,5 +84,3 @@
#ifdef __cplusplus
};
#endif /* __cplusplus */
-
-#endif
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index c471fa0..e65fe92 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -130,6 +130,7 @@
#define AID_VIRTUALIZATIONSERVICE 1081 /* VirtualizationService daemon */
#define AID_ARTD 1082 /* ART Service daemon */
#define AID_UWB 1083 /* UWB subsystem */
+#define AID_THREAD_NETWORK 1084 /* Thread Network subsystem */
/* Changes to this file must be made in AOSP, *not* in internal branches. */
#define AID_SHELL 2000 /* adb and debug shell user */
diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp
index 0734f25..352847a 100644
--- a/libprocessgroup/cgroup_map.cpp
+++ b/libprocessgroup/cgroup_map.cpp
@@ -34,6 +34,7 @@
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <cgroup_map.h>
#include <json/reader.h>
@@ -41,6 +42,7 @@
#include <processgroup/processgroup.h>
using android::base::GetBoolProperty;
+using android::base::StartsWith;
using android::base::StringPrintf;
using android::base::unique_fd;
using android::base::WriteStringToFile;
@@ -204,6 +206,24 @@
return CgroupController(nullptr);
}
+CgroupController CgroupMap::FindControllerByPath(const std::string& path) const {
+ if (!loaded_) {
+ LOG(ERROR) << "CgroupMap::FindControllerByPath called for [" << getpid()
+ << "] failed, RC file was not initialized properly";
+ return CgroupController(nullptr);
+ }
+
+ auto controller_count = ACgroupFile_getControllerCount();
+ for (uint32_t i = 0; i < controller_count; ++i) {
+ const ACgroupController* controller = ACgroupFile_getController(i);
+ if (StartsWith(path, ACgroupController_getPath(controller))) {
+ return CgroupController(controller);
+ }
+ }
+
+ return CgroupController(nullptr);
+}
+
int CgroupMap::ActivateControllers(const std::string& path) const {
if (__builtin_available(android 30, *)) {
auto controller_count = ACgroupFile_getControllerCount();
diff --git a/libprocessgroup/cgroup_map.h b/libprocessgroup/cgroup_map.h
index 22d717b..5cdf8b2 100644
--- a/libprocessgroup/cgroup_map.h
+++ b/libprocessgroup/cgroup_map.h
@@ -62,6 +62,7 @@
static CgroupMap& GetInstance();
CgroupController FindController(const std::string& name) const;
+ CgroupController FindControllerByPath(const std::string& path) const;
int ActivateControllers(const std::string& path) const;
private:
diff --git a/libprocessgroup/cgrouprc/include/android/cgrouprc.h b/libprocessgroup/cgrouprc/include/android/cgrouprc.h
index 100d60e..e704a36 100644
--- a/libprocessgroup/cgrouprc/include/android/cgrouprc.h
+++ b/libprocessgroup/cgrouprc/include/android/cgrouprc.h
@@ -16,6 +16,7 @@
#pragma once
+#include <sys/cdefs.h>
#include <stdint.h>
__BEGIN_DECLS
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index fa2642d..be34f95 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -26,6 +26,7 @@
static constexpr const char* CGROUPV2_CONTROLLER_NAME = "cgroup2";
bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path);
+bool CgroupGetControllerFromPath(const std::string& path, std::string* cgroup_name);
bool CgroupGetAttributePath(const std::string& attr_name, std::string* path);
bool CgroupGetAttributePathForTask(const std::string& attr_name, int tid, std::string* path);
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index faf945c..0320b02 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -69,6 +69,20 @@
return true;
}
+bool CgroupGetControllerFromPath(const std::string& path, std::string* cgroup_name) {
+ auto controller = CgroupMap::GetInstance().FindControllerByPath(path);
+
+ if (!controller.HasValue()) {
+ return false;
+ }
+
+ if (cgroup_name) {
+ *cgroup_name = controller.name();
+ }
+
+ return true;
+}
+
bool CgroupGetAttributePath(const std::string& attr_name, std::string* path) {
const TaskProfiles& tp = TaskProfiles::GetInstance();
const ProfileAttribute* attr = tp.GetAttribute(attr_name);
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 449a505..45d3c7c 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -183,7 +183,19 @@
}
]
},
-
+ {
+ "Name": "Dex2oatPerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "cpu",
+ "Path": "dex2oat"
+ }
+ }
+ ]
+ },
{
"Name": "CpuPolicySpread",
"Actions": [
@@ -638,7 +650,7 @@
},
{
"Name": "Dex2OatBootComplete",
- "Profiles": [ "SCHED_SP_BACKGROUND" ]
+ "Profiles": [ "Dex2oatPerformance", "LowIoPriority", "TimerSlackHigh" ]
}
]
}
diff --git a/libprocessgroup/profiles/task_profiles_28.json b/libprocessgroup/profiles/task_profiles_28.json
index 9f83785..e7be548 100644
--- a/libprocessgroup/profiles/task_profiles_28.json
+++ b/libprocessgroup/profiles/task_profiles_28.json
@@ -40,6 +40,19 @@
]
},
{
+ "Name": "ServicePerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "schedtune",
+ "Path": "background"
+ }
+ }
+ ]
+ },
+ {
"Name": "HighPerformance",
"Actions": [
{
@@ -104,7 +117,19 @@
}
]
},
-
+ {
+ "Name": "Dex2oatPerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "schedtune",
+ "Path": "background"
+ }
+ }
+ ]
+ },
{
"Name": "CpuPolicySpread",
"Actions": [
diff --git a/libprocessgroup/profiles/task_profiles_29.json b/libprocessgroup/profiles/task_profiles_29.json
index 9f83785..6174c8d 100644
--- a/libprocessgroup/profiles/task_profiles_29.json
+++ b/libprocessgroup/profiles/task_profiles_29.json
@@ -53,6 +53,19 @@
]
},
{
+ "Name": "ServicePerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "schedtune",
+ "Path": "background"
+ }
+ }
+ ]
+ },
+ {
"Name": "MaxPerformance",
"Actions": [
{
@@ -104,7 +117,19 @@
}
]
},
-
+ {
+ "Name": "Dex2oatPerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "schedtune",
+ "Path": "background"
+ }
+ }
+ ]
+ },
{
"Name": "CpuPolicySpread",
"Actions": [
diff --git a/libprocessgroup/profiles/task_profiles_30.json b/libprocessgroup/profiles/task_profiles_30.json
index 9f83785..e7be548 100644
--- a/libprocessgroup/profiles/task_profiles_30.json
+++ b/libprocessgroup/profiles/task_profiles_30.json
@@ -40,6 +40,19 @@
]
},
{
+ "Name": "ServicePerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "schedtune",
+ "Path": "background"
+ }
+ }
+ ]
+ },
+ {
"Name": "HighPerformance",
"Actions": [
{
@@ -104,7 +117,19 @@
}
]
},
-
+ {
+ "Name": "Dex2oatPerformance",
+ "Actions": [
+ {
+ "Name": "JoinCgroup",
+ "Params":
+ {
+ "Controller": "schedtune",
+ "Path": "background"
+ }
+ }
+ ]
+ },
{
"Name": "CpuPolicySpread",
"Actions": [
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index cf74e65..e935f99 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -194,22 +194,39 @@
fd_.reset(FDS_NOT_CACHED);
}
-bool SetCgroupAction::AddTidToCgroup(int tid, int fd) {
+bool SetCgroupAction::AddTidToCgroup(int tid, int fd, const char* controller_name) {
if (tid <= 0) {
return true;
}
std::string value = std::to_string(tid);
- if (TEMP_FAILURE_RETRY(write(fd, value.c_str(), value.length())) < 0) {
- // If the thread is in the process of exiting, don't flag an error
- if (errno != ESRCH) {
- PLOG(ERROR) << "AddTidToCgroup failed to write '" << value << "'; fd=" << fd;
- return false;
- }
+ if (TEMP_FAILURE_RETRY(write(fd, value.c_str(), value.length())) == value.length()) {
+ return true;
}
- return true;
+ // If the thread is in the process of exiting, don't flag an error
+ if (errno == ESRCH) {
+ return true;
+ }
+
+ // ENOSPC is returned when cpuset cgroup that we are joining has no online cpus
+ if (errno == ENOSPC && !strcmp(controller_name, "cpuset")) {
+ // This is an abnormal case happening only in testing, so report it only once
+ static bool empty_cpuset_reported = false;
+
+ if (empty_cpuset_reported) {
+ return true;
+ }
+
+ LOG(ERROR) << "Failed to add task '" << value
+ << "' into cpuset because all cpus in that cpuset are offline";
+ empty_cpuset_reported = true;
+ } else {
+ PLOG(ERROR) << "AddTidToCgroup failed to write '" << value << "'; fd=" << fd;
+ }
+
+ return false;
}
bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
@@ -219,7 +236,7 @@
PLOG(WARNING) << "Failed to open " << procs_path;
return false;
}
- if (!AddTidToCgroup(pid, tmp_fd)) {
+ if (!AddTidToCgroup(pid, tmp_fd, controller()->name())) {
LOG(ERROR) << "Failed to add task into cgroup";
return false;
}
@@ -231,7 +248,7 @@
std::lock_guard<std::mutex> lock(fd_mutex_);
if (IsFdValid()) {
// fd is cached, reuse it
- if (!AddTidToCgroup(tid, fd_)) {
+ if (!AddTidToCgroup(tid, fd_, controller()->name())) {
LOG(ERROR) << "Failed to add task into cgroup";
return false;
}
@@ -256,7 +273,7 @@
PLOG(WARNING) << "Failed to open " << tasks_path << ": " << strerror(errno);
return false;
}
- if (!AddTidToCgroup(tid, tmp_fd)) {
+ if (!AddTidToCgroup(tid, tmp_fd, controller()->name())) {
LOG(ERROR) << "Failed to add task into cgroup";
return false;
}
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 25a84b0..97c38f4 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -134,7 +134,7 @@
mutable std::mutex fd_mutex_;
static bool IsAppDependentPath(const std::string& path);
- static bool AddTidToCgroup(int tid, int fd);
+ static bool AddTidToCgroup(int tid, int fd, const char* controller_name);
bool IsFdValid() const { return fd_ > FDS_INACCESSIBLE; }
};
diff --git a/libprocessgroup/tools/Android.bp b/libprocessgroup/tools/Android.bp
new file mode 100644
index 0000000..91418e1
--- /dev/null
+++ b/libprocessgroup/tools/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+ name: "settaskprofile",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ srcs: ["settaskprofile.cpp"],
+ shared_libs: [
+ "libprocessgroup",
+ ],
+}
diff --git a/libprocessgroup/tools/settaskprofile.cpp b/libprocessgroup/tools/settaskprofile.cpp
new file mode 100644
index 0000000..f83944a
--- /dev/null
+++ b/libprocessgroup/tools/settaskprofile.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+
+#include <iostream>
+
+#include <processgroup/processgroup.h>
+
+[[noreturn]] static void usage(int exit_status) {
+ std::cerr << "Usage: " << getprogname() << " <tid> <profile> [... profileN]" << std::endl
+ << " tid Thread ID to apply the profiles to." << std::endl
+ << " profile Name of the profile to apply." << std::endl
+ << "Applies listed profiles to the thread with specified ID." << std::endl
+ << "Profiles are applied in the order specified in the command line." << std::endl
+ << "If applying a profile fails, remaining profiles are ignored." << std::endl;
+ exit(exit_status);
+}
+
+int main(int argc, char* argv[]) {
+ if (argc < 3) {
+ usage(EXIT_FAILURE);
+ }
+
+ int tid = atoi(argv[1]);
+ if (tid == 0) {
+ std::cerr << "Invalid thread id" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ for (int i = 2; i < argc; i++) {
+ if (!SetTaskProfiles(tid, {argv[i]})) {
+ std::cerr << "Failed to apply " << argv[i] << " profile" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ std::cout << "Profile " << argv[i] << " is applied successfully!" << std::endl;
+ }
+
+ return 0;
+}
diff --git a/libstats/pull_rust/Android.bp b/libstats/pull_rust/Android.bp
index 2a89e29..f07e32b 100644
--- a/libstats/pull_rust/Android.bp
+++ b/libstats/pull_rust/Android.bp
@@ -57,3 +57,13 @@
"libstatspull_bindgen",
],
}
+
+rust_test {
+ name: "libstatspull_bindgen_test",
+ srcs: [":libstatspull_bindgen"],
+ crate_name: "statspull_bindgen_test",
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ clippy_lints: "none",
+ lints: "none",
+}
diff --git a/libsuspend/Android.bp b/libsuspend/Android.bp
index 671de4d..144b4b6 100644
--- a/libsuspend/Android.bp
+++ b/libsuspend/Android.bp
@@ -6,6 +6,7 @@
cc_library {
name: "libsuspend",
+ vendor_available: true,
srcs: [
"autosuspend.c",
"autosuspend_wakeup_count.cpp",
diff --git a/libsysutils/src/NetlinkEvent.cpp b/libsysutils/src/NetlinkEvent.cpp
index 3b6cfd8..515cc10 100644
--- a/libsysutils/src/NetlinkEvent.cpp
+++ b/libsysutils/src/NetlinkEvent.cpp
@@ -31,14 +31,41 @@
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/personality.h>
#include <sys/socket.h>
#include <sys/types.h>
+#include <sys/utsname.h>
+
+#include <android-base/parseint.h>
+#include <log/log.h>
+#include <sysutils/NetlinkEvent.h>
+
+using android::base::ParseInt;
/* From kernel's net/netfilter/xt_quota2.c */
const int LOCAL_QLOG_NL_EVENT = 112;
const int LOCAL_NFLOG_PACKET = NFNL_SUBSYS_ULOG << 8 | NFULNL_MSG_PACKET;
-/* From deprecated ipt_ULOG.h to parse QLOG_NL_EVENT. */
+/******************************************************************************
+ * WARNING: HERE BE DRAGONS! *
+ * *
+ * This is here to provide for compatibility with both 32 and 64-bit kernels *
+ * from 32-bit userspace. *
+ * *
+ * The kernel definition of this struct uses types (like long) that are not *
+ * the same across 32-bit and 64-bit builds, and there is no compatibility *
+ * layer to fix it up before it reaches userspace. *
+ * As such we need to detect the bit-ness of the kernel and deal with it. *
+ * *
+ ******************************************************************************/
+
+/*
+ * This is the verbatim kernel declaration from net/netfilter/xt_quota2.c,
+ * it is *NOT* of a well defined layout and is included here for compile
+ * time assertions only.
+ *
+ * It got there from deprecated ipt_ULOG.h to parse QLOG_NL_EVENT.
+ */
#define ULOG_MAC_LEN 80
#define ULOG_PREFIX_LEN 32
typedef struct ulog_packet_msg {
@@ -55,11 +82,117 @@
unsigned char payload[0];
} ulog_packet_msg_t;
-#include <android-base/parseint.h>
-#include <log/log.h>
-#include <sysutils/NetlinkEvent.h>
+// On Linux int is always 32 bits, while sizeof(long) == sizeof(void*),
+// thus long on a 32-bit Linux kernel is 32-bits, like int always is
+typedef int long32;
+typedef unsigned int ulong32;
+static_assert(sizeof(long32) == 4);
+static_assert(sizeof(ulong32) == 4);
-using android::base::ParseInt;
+// Here's the same structure definition with the assumption the kernel
+// is compiled for 32-bits.
+typedef struct {
+ ulong32 mark;
+ long32 timestamp_sec;
+ long32 timestamp_usec;
+ unsigned int hook;
+ char indev_name[IFNAMSIZ];
+ char outdev_name[IFNAMSIZ];
+ ulong32 data_len;
+ char prefix[ULOG_PREFIX_LEN];
+ unsigned char mac_len;
+ unsigned char mac[ULOG_MAC_LEN];
+ unsigned char payload[0];
+} ulog_packet_msg32_t;
+
+// long on a 64-bit kernel is 64-bits with 64-bit alignment,
+// while long long is 64-bit but may have 32-bit aligment.
+typedef long long __attribute__((__aligned__(8))) long64;
+typedef unsigned long long __attribute__((__aligned__(8))) ulong64;
+static_assert(sizeof(long64) == 8);
+static_assert(sizeof(ulong64) == 8);
+
+// Here's the same structure definition with the assumption the kernel
+// is compiled for 64-bits.
+typedef struct {
+ ulong64 mark;
+ long64 timestamp_sec;
+ long64 timestamp_usec;
+ unsigned int hook;
+ char indev_name[IFNAMSIZ];
+ char outdev_name[IFNAMSIZ];
+ ulong64 data_len;
+ char prefix[ULOG_PREFIX_LEN];
+ unsigned char mac_len;
+ unsigned char mac[ULOG_MAC_LEN];
+ unsigned char payload[0];
+} ulog_packet_msg64_t;
+
+// One expects the 32-bit version to be smaller than the 64-bit version.
+static_assert(sizeof(ulog_packet_msg32_t) < sizeof(ulog_packet_msg64_t));
+// And either way the 'native' version should match either the 32 or 64 bit one.
+static_assert(sizeof(ulog_packet_msg_t) == sizeof(ulog_packet_msg32_t) ||
+ sizeof(ulog_packet_msg_t) == sizeof(ulog_packet_msg64_t));
+
+// In practice these sizes are always simply (for both x86 and arm):
+static_assert(sizeof(ulog_packet_msg32_t) == 168);
+static_assert(sizeof(ulog_packet_msg64_t) == 192);
+
+// Figure out the bitness of userspace.
+// Trivial and known at compile time.
+static bool isUserspace64bit(void) {
+ return sizeof(long) == 8;
+}
+
+// Figure out the bitness of the kernel.
+static bool isKernel64Bit(void) {
+ // a 64-bit userspace requires a 64-bit kernel
+ if (isUserspace64bit()) return true;
+
+ static bool init = false;
+ static bool cache = false;
+ if (init) return cache;
+
+ // Retrieve current personality - on Linux this system call *cannot* fail.
+ int p = personality(0xffffffff);
+ // But if it does just assume kernel and userspace (which is 32-bit) match...
+ if (p == -1) return false;
+
+ // This will effectively mask out the bottom 8 bits, and switch to 'native'
+ // personality, and then return the previous personality of this thread
+ // (likely PER_LINUX or PER_LINUX32) with any extra options unmodified.
+ int q = personality((p & ~PER_MASK) | PER_LINUX);
+ // Per man page this theoretically could error out with EINVAL,
+ // but kernel code analysis suggests setting PER_LINUX cannot fail.
+ // Either way, assume kernel and userspace (which is 32-bit) match...
+ if (q != p) return false;
+
+ struct utsname u;
+ (void)uname(&u); // only possible failure is EFAULT, but u is on stack.
+
+ // Switch back to previous personality.
+ // Theoretically could fail with EINVAL on arm64 with no 32-bit support,
+ // but then we wouldn't have fetched 'p' from the kernel in the first place.
+ // Either way there's nothing meaningul we can do in case of error.
+ // Since PER_LINUX32 vs PER_LINUX only affects uname.machine it doesn't
+ // really hurt us either. We're really just switching back to be 'clean'.
+ (void)personality(p);
+
+ // Possible values of utsname.machine observed on x86_64 desktop (arm via qemu):
+ // x86_64 i686 aarch64 armv7l
+ // additionally observed on arm device:
+ // armv8l
+ // presumably also might just be possible:
+ // i386 i486 i586
+ // and there might be other weird arm32 cases.
+ // We note that the 64 is present in both 64-bit archs,
+ // and in general is likely to be present in only 64-bit archs.
+ cache = !!strstr(u.machine, "64");
+ init = true;
+ return cache;
+}
+
+/******************************************************************************/
NetlinkEvent::NetlinkEvent() {
mAction = Action::kUnknown;
@@ -280,13 +413,22 @@
* Parse a QLOG_NL_EVENT message.
*/
bool NetlinkEvent::parseUlogPacketMessage(const struct nlmsghdr *nh) {
- const char *devname;
- ulog_packet_msg_t *pm = (ulog_packet_msg_t *) NLMSG_DATA(nh);
- if (!checkRtNetlinkLength(nh, sizeof(*pm)))
- return false;
+ const char* alert;
+ const char* devname;
- devname = pm->indev_name[0] ? pm->indev_name : pm->outdev_name;
- asprintf(&mParams[0], "ALERT_NAME=%s", pm->prefix);
+ if (isKernel64Bit()) {
+ ulog_packet_msg64_t* pm64 = (ulog_packet_msg64_t*)NLMSG_DATA(nh);
+ if (!checkRtNetlinkLength(nh, sizeof(*pm64))) return false;
+ alert = pm64->prefix;
+ devname = pm64->indev_name[0] ? pm64->indev_name : pm64->outdev_name;
+ } else {
+ ulog_packet_msg32_t* pm32 = (ulog_packet_msg32_t*)NLMSG_DATA(nh);
+ if (!checkRtNetlinkLength(nh, sizeof(*pm32))) return false;
+ alert = pm32->prefix;
+ devname = pm32->indev_name[0] ? pm32->indev_name : pm32->outdev_name;
+ }
+
+ asprintf(&mParams[0], "ALERT_NAME=%s", alert);
asprintf(&mParams[1], "INTERFACE=%s", devname);
mSubsystem = strdup("qlog");
mAction = Action::kChange;
diff --git a/libutils/Looper.cpp b/libutils/Looper.cpp
index 14e3e35..292425a 100644
--- a/libutils/Looper.cpp
+++ b/libutils/Looper.cpp
@@ -20,6 +20,16 @@
namespace android {
+namespace {
+
+constexpr uint64_t WAKE_EVENT_FD_SEQ = 1;
+
+epoll_event createEpollEvent(uint32_t events, uint64_t seq) {
+ return {.events = events, .data = {.u64 = seq}};
+}
+
+} // namespace
+
// --- WeakMessageHandler ---
WeakMessageHandler::WeakMessageHandler(const wp<MessageHandler>& handler) :
@@ -64,7 +74,7 @@
mSendingMessage(false),
mPolling(false),
mEpollRebuildRequired(false),
- mNextRequestSeq(0),
+ mNextRequestSeq(WAKE_EVENT_FD_SEQ + 1),
mResponseIndex(0),
mNextMessageUptime(LLONG_MAX) {
mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
@@ -137,22 +147,17 @@
mEpollFd.reset();
}
- // Allocate the new epoll instance and register the wake pipe.
+ // Allocate the new epoll instance and register the WakeEventFd.
mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
- struct epoll_event eventItem;
- memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
- eventItem.events = EPOLLIN;
- eventItem.data.fd = mWakeEventFd.get();
- int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
+ epoll_event wakeEvent = createEpollEvent(EPOLLIN, WAKE_EVENT_FD_SEQ);
+ int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &wakeEvent);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
strerror(errno));
- for (size_t i = 0; i < mRequests.size(); i++) {
- const Request& request = mRequests.valueAt(i);
- struct epoll_event eventItem;
- request.initEventItem(&eventItem);
+ for (const auto& [seq, request] : mRequests) {
+ epoll_event eventItem = createEpollEvent(request.getEpollEvents(), seq);
int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, request.fd, &eventItem);
if (epollResult < 0) {
@@ -276,26 +281,28 @@
#endif
for (int i = 0; i < eventCount; i++) {
- int fd = eventItems[i].data.fd;
+ const SequenceNumber seq = eventItems[i].data.u64;
uint32_t epollEvents = eventItems[i].events;
- if (fd == mWakeEventFd.get()) {
+ if (seq == WAKE_EVENT_FD_SEQ) {
if (epollEvents & EPOLLIN) {
awoken();
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
}
} else {
- ssize_t requestIndex = mRequests.indexOfKey(fd);
- if (requestIndex >= 0) {
+ const auto& request_it = mRequests.find(seq);
+ if (request_it != mRequests.end()) {
+ const auto& request = request_it->second;
int events = 0;
if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
- pushResponse(events, mRequests.valueAt(requestIndex));
+ mResponses.push({.seq = seq, .events = events, .request = request});
} else {
- ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
- "no longer registered.", epollEvents, fd);
+ ALOGW("Ignoring unexpected epoll events 0x%x for sequence number %" PRIu64
+ " that is no longer registered.",
+ epollEvents, seq);
}
}
}
@@ -354,7 +361,8 @@
// we need to be a little careful when removing the file descriptor afterwards.
int callbackResult = response.request.callback->handleEvent(fd, events, data);
if (callbackResult == 0) {
- removeFd(fd, response.request.seq);
+ AutoMutex _l(mLock);
+ removeSequenceNumberLocked(response.seq);
}
// Clear the callback reference in the response structure promptly because we
@@ -416,13 +424,6 @@
TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}
-void Looper::pushResponse(int events, const Request& request) {
- Response response;
- response.events = events;
- response.request = request;
- mResponses.push(response);
-}
-
int Looper::addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data) {
return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : nullptr, data);
}
@@ -449,27 +450,27 @@
{ // acquire lock
AutoMutex _l(mLock);
+ // There is a sequence number reserved for the WakeEventFd.
+ if (mNextRequestSeq == WAKE_EVENT_FD_SEQ) mNextRequestSeq++;
+ const SequenceNumber seq = mNextRequestSeq++;
Request request;
request.fd = fd;
request.ident = ident;
request.events = events;
- request.seq = mNextRequestSeq++;
request.callback = callback;
request.data = data;
- if (mNextRequestSeq == -1) mNextRequestSeq = 0; // reserve sequence number -1
- struct epoll_event eventItem;
- request.initEventItem(&eventItem);
-
- ssize_t requestIndex = mRequests.indexOfKey(fd);
- if (requestIndex < 0) {
+ epoll_event eventItem = createEpollEvent(request.getEpollEvents(), seq);
+ auto seq_it = mSequenceNumberByFd.find(fd);
+ if (seq_it == mSequenceNumberByFd.end()) {
int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, fd, &eventItem);
if (epollResult < 0) {
ALOGE("Error adding epoll events for fd %d: %s", fd, strerror(errno));
return -1;
}
- mRequests.add(fd, request);
+ mRequests.emplace(seq, request);
+ mSequenceNumberByFd.emplace(fd, seq);
} else {
int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_MOD, fd, &eventItem);
if (epollResult < 0) {
@@ -486,7 +487,7 @@
// set from scratch because it may contain an old file handle that we are
// now unable to remove since its file descriptor is no longer valid.
// No such problem would have occurred if we were using the poll system
- // call instead, but that approach carries others disadvantages.
+ // call instead, but that approach carries other disadvantages.
#if DEBUG_CALLBACKS
ALOGD("%p ~ addFd - EPOLL_CTL_MOD failed due to file descriptor "
"being recycled, falling back on EPOLL_CTL_ADD: %s",
@@ -504,71 +505,69 @@
return -1;
}
}
- mRequests.replaceValueAt(requestIndex, request);
+ const SequenceNumber oldSeq = seq_it->second;
+ mRequests.erase(oldSeq);
+ mRequests.emplace(seq, request);
+ seq_it->second = seq;
}
} // release lock
return 1;
}
int Looper::removeFd(int fd) {
- return removeFd(fd, -1);
+ AutoMutex _l(mLock);
+ const auto& it = mSequenceNumberByFd.find(fd);
+ if (it == mSequenceNumberByFd.end()) {
+ return 0;
+ }
+ return removeSequenceNumberLocked(it->second);
}
-int Looper::removeFd(int fd, int seq) {
+int Looper::removeSequenceNumberLocked(SequenceNumber seq) {
#if DEBUG_CALLBACKS
- ALOGD("%p ~ removeFd - fd=%d, seq=%d", this, fd, seq);
+ ALOGD("%p ~ removeFd - fd=%d, seq=%u", this, fd, seq);
#endif
- { // acquire lock
- AutoMutex _l(mLock);
- ssize_t requestIndex = mRequests.indexOfKey(fd);
- if (requestIndex < 0) {
- return 0;
- }
+ const auto& request_it = mRequests.find(seq);
+ if (request_it == mRequests.end()) {
+ return 0;
+ }
+ const int fd = request_it->second.fd;
- // Check the sequence number if one was given.
- if (seq != -1 && mRequests.valueAt(requestIndex).seq != seq) {
+ // Always remove the FD from the request map even if an error occurs while
+ // updating the epoll set so that we avoid accidentally leaking callbacks.
+ mRequests.erase(request_it);
+ mSequenceNumberByFd.erase(fd);
+
+ int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_DEL, fd, nullptr);
+ if (epollResult < 0) {
+ if (errno == EBADF || errno == ENOENT) {
+ // Tolerate EBADF or ENOENT because it means that the file descriptor was closed
+ // before its callback was unregistered. This error may occur naturally when a
+ // callback has the side-effect of closing the file descriptor before returning and
+ // unregistering itself.
+ //
+ // Unfortunately due to kernel limitations we need to rebuild the epoll
+ // set from scratch because it may contain an old file handle that we are
+ // now unable to remove since its file descriptor is no longer valid.
+ // No such problem would have occurred if we were using the poll system
+ // call instead, but that approach carries other disadvantages.
#if DEBUG_CALLBACKS
- ALOGD("%p ~ removeFd - sequence number mismatch, oldSeq=%d",
- this, mRequests.valueAt(requestIndex).seq);
+ ALOGD("%p ~ removeFd - EPOLL_CTL_DEL failed due to file descriptor "
+ "being closed: %s",
+ this, strerror(errno));
#endif
- return 0;
+ scheduleEpollRebuildLocked();
+ } else {
+ // Some other error occurred. This is really weird because it means
+ // our list of callbacks got out of sync with the epoll set somehow.
+ // We defensively rebuild the epoll set to avoid getting spurious
+ // notifications with nowhere to go.
+ ALOGE("Error removing epoll events for fd %d: %s", fd, strerror(errno));
+ scheduleEpollRebuildLocked();
+ return -1;
}
-
- // Always remove the FD from the request map even if an error occurs while
- // updating the epoll set so that we avoid accidentally leaking callbacks.
- mRequests.removeItemsAt(requestIndex);
-
- int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_DEL, fd, nullptr);
- if (epollResult < 0) {
- if (seq != -1 && (errno == EBADF || errno == ENOENT)) {
- // Tolerate EBADF or ENOENT when the sequence number is known because it
- // means that the file descriptor was closed before its callback was
- // unregistered. This error may occur naturally when a callback has the
- // side-effect of closing the file descriptor before returning and
- // unregistering itself.
- //
- // Unfortunately due to kernel limitations we need to rebuild the epoll
- // set from scratch because it may contain an old file handle that we are
- // now unable to remove since its file descriptor is no longer valid.
- // No such problem would have occurred if we were using the poll system
- // call instead, but that approach carries others disadvantages.
-#if DEBUG_CALLBACKS
- ALOGD("%p ~ removeFd - EPOLL_CTL_DEL failed due to file descriptor "
- "being closed: %s", this, strerror(errno));
-#endif
- scheduleEpollRebuildLocked();
- } else {
- // Some other error occurred. This is really weird because it means
- // our list of callbacks got out of sync with the epoll set somehow.
- // We defensively rebuild the epoll set to avoid getting spurious
- // notifications with nowhere to go.
- ALOGE("Error removing epoll events for fd %d: %s", fd, strerror(errno));
- scheduleEpollRebuildLocked();
- return -1;
- }
- }
- } // release lock
+ }
return 1;
}
@@ -656,14 +655,11 @@
return mPolling;
}
-void Looper::Request::initEventItem(struct epoll_event* eventItem) const {
- int epollEvents = 0;
+uint32_t Looper::Request::getEpollEvents() const {
+ uint32_t epollEvents = 0;
if (events & EVENT_INPUT) epollEvents |= EPOLLIN;
if (events & EVENT_OUTPUT) epollEvents |= EPOLLOUT;
-
- memset(eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
- eventItem->events = epollEvents;
- eventItem->data.fd = fd;
+ return epollEvents;
}
MessageHandler::~MessageHandler() { }
diff --git a/libutils/Looper_test.cpp b/libutils/Looper_test.cpp
index 34f424b..c859f9c 100644
--- a/libutils/Looper_test.cpp
+++ b/libutils/Looper_test.cpp
@@ -8,6 +8,9 @@
#include <utils/Looper.h>
#include <utils/StopWatch.h>
#include <utils/Timers.h>
+#include <thread>
+#include <unordered_map>
+#include <utility>
#include "Looper_test_pipe.h"
#include <utils/threads.h>
@@ -710,4 +713,123 @@
<< "no more messages to handle";
}
+class LooperEventCallback : public LooperCallback {
+ public:
+ using Callback = std::function<int(int fd, int events)>;
+ explicit LooperEventCallback(Callback callback) : mCallback(std::move(callback)) {}
+ int handleEvent(int fd, int events, void* /*data*/) override { return mCallback(fd, events); }
+
+ private:
+ Callback mCallback;
+};
+
+// A utility class that allows for pipes to be added and removed from the looper, and polls the
+// looper from a different thread.
+class ThreadedLooperUtil {
+ public:
+ explicit ThreadedLooperUtil(const sp<Looper>& looper) : mLooper(looper), mRunning(true) {
+ mThread = std::thread([this]() {
+ while (mRunning) {
+ static constexpr std::chrono::milliseconds POLL_TIMEOUT(500);
+ mLooper->pollOnce(POLL_TIMEOUT.count());
+ }
+ });
+ }
+
+ ~ThreadedLooperUtil() {
+ mRunning = false;
+ mThread.join();
+ }
+
+ // Create a new pipe, and return the write end of the pipe and the id used to track the pipe.
+ // The read end of the pipe is added to the looper.
+ std::pair<int /*id*/, base::unique_fd> createPipe() {
+ int pipeFd[2];
+ if (pipe(pipeFd)) {
+ ADD_FAILURE() << "pipe() failed.";
+ return {};
+ }
+ const int readFd = pipeFd[0];
+ const int writeFd = pipeFd[1];
+
+ int id;
+ { // acquire lock
+ std::scoped_lock l(mLock);
+
+ id = mNextId++;
+ mFds.emplace(id, readFd);
+
+ auto removeCallback = [this, id, readFd](int fd, int events) {
+ EXPECT_EQ(readFd, fd) << "Received callback for incorrect fd.";
+ if ((events & Looper::EVENT_HANGUP) == 0) {
+ return 1; // Not a hangup, keep the callback.
+ }
+ removePipe(id);
+ return 0; // Remove the callback.
+ };
+
+ mLooper->addFd(readFd, 0, Looper::EVENT_INPUT,
+ new LooperEventCallback(std::move(removeCallback)), nullptr);
+ } // release lock
+
+ return {id, base::unique_fd(writeFd)};
+ }
+
+ // Remove the pipe with the given id.
+ void removePipe(int id) {
+ std::scoped_lock l(mLock);
+ if (mFds.find(id) == mFds.end()) {
+ return;
+ }
+ mLooper->removeFd(mFds[id].get());
+ mFds.erase(id);
+ }
+
+ // Check if the pipe with the given id exists and has not been removed.
+ bool hasPipe(int id) {
+ std::scoped_lock l(mLock);
+ return mFds.find(id) != mFds.end();
+ }
+
+ private:
+ sp<Looper> mLooper;
+ std::atomic<bool> mRunning;
+ std::thread mThread;
+
+ std::mutex mLock;
+ std::unordered_map<int, base::unique_fd> mFds GUARDED_BY(mLock);
+ int mNextId GUARDED_BY(mLock) = 0;
+};
+
+TEST_F(LooperTest, MultiThreaded_NoUnexpectedFdRemoval) {
+ ThreadedLooperUtil util(mLooper);
+
+ // Iterate repeatedly to try to recreate a flaky instance.
+ for (int i = 0; i < 1000; i++) {
+ auto [firstPipeId, firstPipeFd] = util.createPipe();
+ const int firstFdNumber = firstPipeFd.get();
+
+ // Close the first pipe's fd, causing a fd hangup.
+ firstPipeFd.reset();
+
+ // Request to remove the pipe from this test thread. This causes a race for pipe removal
+ // between the hangup in the looper's thread and this remove request from the test thread.
+ util.removePipe(firstPipeId);
+
+ // Create the second pipe. Since the fds for the first pipe are closed, this pipe should
+ // have the same fd numbers as the first pipe because the lowest unused fd number is used.
+ const auto [secondPipeId, fd] = util.createPipe();
+ EXPECT_EQ(firstFdNumber, fd.get())
+ << "The first and second fds must match for the purposes of this test.";
+
+ // Wait for unexpected hangup to occur.
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+
+ ASSERT_TRUE(util.hasPipe(secondPipeId)) << "The second pipe was removed unexpectedly.";
+
+ util.removePipe(secondPipeId);
+ }
+ SUCCEED() << "No unexpectedly removed fds.";
+}
+
} // namespace android
diff --git a/libutils/RefBase.cpp b/libutils/RefBase.cpp
index b57e287..0518927 100644
--- a/libutils/RefBase.cpp
+++ b/libutils/RefBase.cpp
@@ -170,7 +170,7 @@
: mStrong(INITIAL_STRONG_VALUE)
, mWeak(0)
, mBase(base)
- , mFlags(0)
+ , mFlags(OBJECT_LIFETIME_STRONG)
{
}
@@ -189,7 +189,7 @@
: mStrong(INITIAL_STRONG_VALUE)
, mWeak(0)
, mBase(base)
- , mFlags(0)
+ , mFlags(OBJECT_LIFETIME_STRONG)
, mStrongRefs(NULL)
, mWeakRefs(NULL)
, mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT)
diff --git a/libutils/String16.cpp b/libutils/String16.cpp
index c42cada..68642d8 100644
--- a/libutils/String16.cpp
+++ b/libutils/String16.cpp
@@ -199,99 +199,59 @@
return NO_MEMORY;
}
-status_t String16::append(const String16& other)
-{
- const size_t myLen = size();
- const size_t otherLen = other.size();
- if (myLen == 0) {
- setTo(other);
- return OK;
- } else if (otherLen == 0) {
- return OK;
- }
-
- if (myLen >= SIZE_MAX / sizeof(char16_t) - otherLen) {
- android_errorWriteLog(0x534e4554, "73826242");
- abort();
- }
-
- SharedBuffer* buf =
- static_cast<SharedBuffer*>(editResize((myLen + otherLen + 1) * sizeof(char16_t)));
- if (buf) {
- char16_t* str = (char16_t*)buf->data();
- memcpy(str+myLen, other, (otherLen+1)*sizeof(char16_t));
- mString = str;
- return OK;
- }
- return NO_MEMORY;
+status_t String16::append(const String16& other) {
+ return append(other.string(), other.size());
}
-status_t String16::append(const char16_t* chrs, size_t otherLen)
-{
+status_t String16::append(const char16_t* chrs, size_t otherLen) {
const size_t myLen = size();
- if (myLen == 0) {
- setTo(chrs, otherLen);
- return OK;
- } else if (otherLen == 0) {
- return OK;
- }
- if (myLen >= SIZE_MAX / sizeof(char16_t) - otherLen) {
- android_errorWriteLog(0x534e4554, "73826242");
- abort();
- }
+ if (myLen == 0) return setTo(chrs, otherLen);
- SharedBuffer* buf =
- static_cast<SharedBuffer*>(editResize((myLen + otherLen + 1) * sizeof(char16_t)));
- if (buf) {
- char16_t* str = (char16_t*)buf->data();
- memcpy(str+myLen, chrs, otherLen*sizeof(char16_t));
- str[myLen+otherLen] = 0;
- mString = str;
- return OK;
- }
- return NO_MEMORY;
+ if (otherLen == 0) return OK;
+
+ size_t size = myLen;
+ if (__builtin_add_overflow(size, otherLen, &size) ||
+ __builtin_add_overflow(size, 1, &size) ||
+ __builtin_mul_overflow(size, sizeof(char16_t), &size)) return NO_MEMORY;
+
+ SharedBuffer* buf = static_cast<SharedBuffer*>(editResize(size));
+ if (!buf) return NO_MEMORY;
+
+ char16_t* str = static_cast<char16_t*>(buf->data());
+ memcpy(str + myLen, chrs, otherLen * sizeof(char16_t));
+ str[myLen + otherLen] = 0;
+ mString = str;
+ return OK;
}
-status_t String16::insert(size_t pos, const char16_t* chrs)
-{
+status_t String16::insert(size_t pos, const char16_t* chrs) {
return insert(pos, chrs, strlen16(chrs));
}
-status_t String16::insert(size_t pos, const char16_t* chrs, size_t len)
-{
+status_t String16::insert(size_t pos, const char16_t* chrs, size_t otherLen) {
const size_t myLen = size();
- if (myLen == 0) {
- return setTo(chrs, len);
- return OK;
- } else if (len == 0) {
- return OK;
- }
+
+ if (myLen == 0) return setTo(chrs, otherLen);
+
+ if (otherLen == 0) return OK;
if (pos > myLen) pos = myLen;
- #if 0
- printf("Insert in to %s: pos=%d, len=%d, myLen=%d, chrs=%s\n",
- String8(*this).string(), pos,
- len, myLen, String8(chrs, len).string());
- #endif
+ size_t size = myLen;
+ if (__builtin_add_overflow(size, otherLen, &size) ||
+ __builtin_add_overflow(size, 1, &size) ||
+ __builtin_mul_overflow(size, sizeof(char16_t), &size)) return NO_MEMORY;
- SharedBuffer* buf =
- static_cast<SharedBuffer*>(editResize((myLen + len + 1) * sizeof(char16_t)));
- if (buf) {
- char16_t* str = (char16_t*)buf->data();
- if (pos < myLen) {
- memmove(str+pos+len, str+pos, (myLen-pos)*sizeof(char16_t));
- }
- memcpy(str+pos, chrs, len*sizeof(char16_t));
- str[myLen+len] = 0;
- mString = str;
- #if 0
- printf("Result (%d chrs): %s\n", size(), String8(*this).string());
- #endif
- return OK;
- }
- return NO_MEMORY;
+ SharedBuffer* buf = static_cast<SharedBuffer*>(editResize(size));
+ if (!buf) return NO_MEMORY;
+
+ char16_t* str = static_cast<char16_t*>(buf->data());
+ if (pos < myLen) memmove(str + pos + otherLen, str + pos, (myLen - pos) * sizeof(char16_t));
+ memcpy(str + pos, chrs, otherLen * sizeof(char16_t));
+ str[myLen + otherLen] = 0;
+ mString = str;
+ return OK;
}
ssize_t String16::findFirst(char16_t c) const
diff --git a/libutils/String16_test.cpp b/libutils/String16_test.cpp
index 7d7230e..c6e6f74 100644
--- a/libutils/String16_test.cpp
+++ b/libutils/String16_test.cpp
@@ -19,7 +19,7 @@
#include <gtest/gtest.h>
-namespace android {
+using namespace android;
::testing::AssertionResult Char16_tStringEquals(const char16_t* a, const char16_t* b) {
if (strcmp16(a, b) != 0) {
@@ -224,4 +224,36 @@
EXPECT_STR16EQ(another, u"abcdef");
}
-} // namespace android
+TEST(String16Test, append) {
+ String16 s;
+ EXPECT_EQ(OK, s.append(String16(u"foo")));
+ EXPECT_STR16EQ(u"foo", s);
+ EXPECT_EQ(OK, s.append(String16(u"bar")));
+ EXPECT_STR16EQ(u"foobar", s);
+ EXPECT_EQ(OK, s.append(u"baz", 0));
+ EXPECT_STR16EQ(u"foobar", s);
+ EXPECT_EQ(NO_MEMORY, s.append(u"baz", SIZE_MAX));
+ EXPECT_STR16EQ(u"foobar", s);
+}
+
+TEST(String16Test, insert) {
+ String16 s;
+
+ // Inserting into the empty string inserts at the start.
+ EXPECT_EQ(OK, s.insert(123, u"foo"));
+ EXPECT_STR16EQ(u"foo", s);
+
+ // Inserting zero characters at any position is okay, but won't expand the string.
+ EXPECT_EQ(OK, s.insert(123, u"foo", 0));
+ EXPECT_STR16EQ(u"foo", s);
+
+ // Inserting past the end of a non-empty string appends.
+ EXPECT_EQ(OK, s.insert(123, u"bar"));
+ EXPECT_STR16EQ(u"foobar", s);
+
+ EXPECT_EQ(OK, s.insert(3, u"!"));
+ EXPECT_STR16EQ(u"foo!bar", s);
+
+ EXPECT_EQ(NO_MEMORY, s.insert(3, u"", SIZE_MAX));
+ EXPECT_STR16EQ(u"foo!bar", s);
+}
diff --git a/libutils/String8.cpp b/libutils/String8.cpp
index 8511da9..419b2de 100644
--- a/libutils/String8.cpp
+++ b/libutils/String8.cpp
@@ -313,8 +313,8 @@
if (n > 0) {
size_t oldLength = length();
- if ((size_t)n > SIZE_MAX - 1 ||
- oldLength > SIZE_MAX - (size_t)n - 1) {
+ if (n > std::numeric_limits<size_t>::max() - 1 ||
+ oldLength > std::numeric_limits<size_t>::max() - n - 1) {
return NO_MEMORY;
}
char* buf = lockBuffer(oldLength + n);
@@ -327,21 +327,23 @@
return result;
}
-status_t String8::real_append(const char* other, size_t otherLen)
-{
+status_t String8::real_append(const char* other, size_t otherLen) {
const size_t myLen = bytes();
- SharedBuffer* buf = SharedBuffer::bufferFromData(mString)
- ->editResize(myLen+otherLen+1);
- if (buf) {
- char* str = (char*)buf->data();
- mString = str;
- str += myLen;
- memcpy(str, other, otherLen);
- str[otherLen] = '\0';
- return OK;
+ SharedBuffer* buf;
+ size_t newLen;
+ if (__builtin_add_overflow(myLen, otherLen, &newLen) ||
+ __builtin_add_overflow(newLen, 1, &newLen) ||
+ (buf = SharedBuffer::bufferFromData(mString)->editResize(newLen)) == nullptr) {
+ return NO_MEMORY;
}
- return NO_MEMORY;
+
+ char* str = (char*)buf->data();
+ mString = str;
+ str += myLen;
+ memcpy(str, other, otherLen);
+ str[otherLen] = '\0';
+ return OK;
}
char* String8::lockBuffer(size_t size)
diff --git a/libutils/String8_test.cpp b/libutils/String8_test.cpp
index 9efcc6f..1356cd0 100644
--- a/libutils/String8_test.cpp
+++ b/libutils/String8_test.cpp
@@ -15,13 +15,14 @@
*/
#define LOG_TAG "String8_test"
+
#include <utils/Log.h>
#include <utils/String8.h>
#include <utils/String16.h>
#include <gtest/gtest.h>
-namespace android {
+using namespace android;
class String8Test : public testing::Test {
protected:
@@ -101,4 +102,15 @@
String8 valid = String8(String16(tmp));
EXPECT_STREQ(valid, "abcdef");
}
+
+TEST_F(String8Test, append) {
+ String8 s;
+ EXPECT_EQ(OK, s.append("foo"));
+ EXPECT_STREQ("foo", s);
+ EXPECT_EQ(OK, s.append("bar"));
+ EXPECT_STREQ("foobar", s);
+ EXPECT_EQ(OK, s.append("baz", 0));
+ EXPECT_STREQ("foobar", s);
+ EXPECT_EQ(NO_MEMORY, s.append("baz", SIZE_MAX));
+ EXPECT_STREQ("foobar", s);
}
diff --git a/libutils/Threads.cpp b/libutils/Threads.cpp
index 540dcf4..6e293c7 100644
--- a/libutils/Threads.cpp
+++ b/libutils/Threads.cpp
@@ -86,8 +86,10 @@
// A new thread will be in its parent's sched group by default,
// so we just need to handle the background case.
+ // currently set to system_background group which is different
+ // from background group for app.
if (prio >= ANDROID_PRIORITY_BACKGROUND) {
- SetTaskProfiles(0, {"SCHED_SP_BACKGROUND"}, true);
+ SetTaskProfiles(0, {"SCHED_SP_SYSTEM"}, true);
}
if (name) {
@@ -313,7 +315,7 @@
}
if (pri >= ANDROID_PRIORITY_BACKGROUND) {
- rc = SetTaskProfiles(tid, {"SCHED_SP_BACKGROUND"}, true) ? 0 : -1;
+ rc = SetTaskProfiles(tid, {"SCHED_SP_SYSTEM"}, true) ? 0 : -1;
} else if (curr_pri >= ANDROID_PRIORITY_BACKGROUND) {
SchedPolicy policy = SP_FOREGROUND;
// Change to the sched policy group of the process.
diff --git a/libutils/include/utils/Looper.h b/libutils/include/utils/Looper.h
index 466fbb7..b387d68 100644
--- a/libutils/include/utils/Looper.h
+++ b/libutils/include/utils/Looper.h
@@ -17,15 +17,16 @@
#ifndef UTILS_LOOPER_H
#define UTILS_LOOPER_H
-#include <utils/threads.h>
#include <utils/RefBase.h>
-#include <utils/KeyedVector.h>
#include <utils/Timers.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
#include <sys/epoll.h>
#include <android-base/unique_fd.h>
+#include <unordered_map>
#include <utility>
namespace android {
@@ -421,18 +422,20 @@
static sp<Looper> getForThread();
private:
- struct Request {
- int fd;
- int ident;
- int events;
- int seq;
- sp<LooperCallback> callback;
- void* data;
+ using SequenceNumber = uint64_t;
- void initEventItem(struct epoll_event* eventItem) const;
- };
+ struct Request {
+ int fd;
+ int ident;
+ int events;
+ sp<LooperCallback> callback;
+ void* data;
+
+ uint32_t getEpollEvents() const;
+ };
struct Response {
+ SequenceNumber seq;
int events;
Request request;
};
@@ -463,9 +466,14 @@
android::base::unique_fd mEpollFd; // guarded by mLock but only modified on the looper thread
bool mEpollRebuildRequired; // guarded by mLock
- // Locked list of file descriptor monitoring requests.
- KeyedVector<int, Request> mRequests; // guarded by mLock
- int mNextRequestSeq;
+ // Locked maps of fds and sequence numbers monitoring requests.
+ // Both maps must be kept in sync at all times.
+ std::unordered_map<SequenceNumber, Request> mRequests; // guarded by mLock
+ std::unordered_map<int /*fd*/, SequenceNumber> mSequenceNumberByFd; // guarded by mLock
+
+ // The sequence number to use for the next fd that is added to the looper.
+ // The sequence number 0 is reserved for the WakeEventFd.
+ SequenceNumber mNextRequestSeq; // guarded by mLock
// This state is only used privately by pollOnce and does not require a lock since
// it runs on a single thread.
@@ -474,9 +482,8 @@
nsecs_t mNextMessageUptime; // set to LLONG_MAX when none
int pollInner(int timeoutMillis);
- int removeFd(int fd, int seq);
+ int removeSequenceNumberLocked(SequenceNumber seq); // requires mLock
void awoken();
- void pushResponse(int events, const Request& request);
void rebuildEpollLocked();
void scheduleEpollRebuildLocked();
diff --git a/llkd/libllkd.cpp b/llkd/libllkd.cpp
index c4c58ee..42602e9 100644
--- a/llkd/libllkd.cpp
+++ b/llkd/libllkd.cpp
@@ -1283,8 +1283,7 @@
llkEnableSysrqT &= !llkLowRam;
if (debuggable) {
llkEnableSysrqT |= llkCheckEng(LLK_ENABLE_SYSRQ_T_PROPERTY);
- if (!LLK_ENABLE_DEFAULT) { // NB: default is currently true ...
- llkEnable |= llkCheckEng(LLK_ENABLE_PROPERTY);
+ if (!LLK_ENABLE_DEFAULT) {
khtEnable |= llkCheckEng(KHT_ENABLE_PROPERTY);
}
}
diff --git a/llkd/llkd-debuggable.rc b/llkd/llkd-debuggable.rc
index 8697e9a..8355e9d 100644
--- a/llkd/llkd-debuggable.rc
+++ b/llkd/llkd-debuggable.rc
@@ -1,5 +1,5 @@
on property:ro.debuggable=1
- setprop llk.enable ${ro.llk.enable:-1}
+ setprop llk.enable ${ro.llk.enable:-0}
setprop khungtask.enable ${ro.khungtask.enable:-1}
on property:ro.llk.enable=eng
diff --git a/llkd/tests/llkd_test.cpp b/llkd/tests/llkd_test.cpp
index 475512c..8eb9b00 100644
--- a/llkd/tests/llkd_test.cpp
+++ b/llkd/tests/llkd_test.cpp
@@ -69,13 +69,9 @@
seconds llkdSleepPeriod(char state) {
auto default_eng = android::base::GetProperty(LLK_ENABLE_PROPERTY, "eng") == "eng";
auto default_enable = LLK_ENABLE_DEFAULT;
- if (!LLK_ENABLE_DEFAULT && default_eng &&
- android::base::GetBoolProperty("ro.debuggable", false)) {
- default_enable = true;
- }
default_enable = android::base::GetBoolProperty(LLK_ENABLE_PROPERTY, default_enable);
if (default_eng) {
- GTEST_LOG_INFO << LLK_ENABLE_PROPERTY " defaults to \"eng\" thus "
+ GTEST_LOG_INFO << LLK_ENABLE_PROPERTY " defaults to "
<< (default_enable ? "true" : "false") << "\n";
}
// Hail Mary hope is unconfigured.
@@ -108,10 +104,6 @@
rest();
}
default_enable = LLK_ENABLE_DEFAULT;
- if (!LLK_ENABLE_DEFAULT && (android::base::GetProperty(LLK_ENABLE_PROPERTY, "eng") == "eng") &&
- android::base::GetBoolProperty("ro.debuggable", false)) {
- default_enable = true;
- }
default_enable = android::base::GetBoolProperty(LLK_ENABLE_PROPERTY, default_enable);
if (default_enable) {
execute("start llkd-1");
diff --git a/property_service/libpropertyinfoparser/Android.bp b/property_service/libpropertyinfoparser/Android.bp
index 6861456..87646f9 100644
--- a/property_service/libpropertyinfoparser/Android.bp
+++ b/property_service/libpropertyinfoparser/Android.bp
@@ -18,7 +18,11 @@
"-Werror",
],
stl: "none",
- system_shared_libs: [],
- header_libs: ["libc_headers"],
+ target: {
+ bionic: {
+ system_shared_libs: [],
+ header_libs: ["libc_headers"],
+ },
+ },
export_include_dirs: ["include"],
}
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 9371617..27fa059 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -155,6 +155,7 @@
mkdir /dev/cpuctl/rt
mkdir /dev/cpuctl/system
mkdir /dev/cpuctl/system-background
+ mkdir /dev/cpuctl/dex2oat
chown system system /dev/cpuctl
chown system system /dev/cpuctl/foreground
chown system system /dev/cpuctl/background
@@ -162,6 +163,7 @@
chown system system /dev/cpuctl/rt
chown system system /dev/cpuctl/system
chown system system /dev/cpuctl/system-background
+ chown system system /dev/cpuctl/dex2oat
chown system system /dev/cpuctl/tasks
chown system system /dev/cpuctl/foreground/tasks
chown system system /dev/cpuctl/background/tasks
@@ -169,6 +171,7 @@
chown system system /dev/cpuctl/rt/tasks
chown system system /dev/cpuctl/system/tasks
chown system system /dev/cpuctl/system-background/tasks
+ chown system system /dev/cpuctl/dex2oat/tasks
chmod 0664 /dev/cpuctl/tasks
chmod 0664 /dev/cpuctl/foreground/tasks
chmod 0664 /dev/cpuctl/background/tasks
@@ -176,6 +179,7 @@
chmod 0664 /dev/cpuctl/rt/tasks
chmod 0664 /dev/cpuctl/system/tasks
chmod 0664 /dev/cpuctl/system-background/tasks
+ chmod 0664 /dev/cpuctl/dex2oat/tasks
# Create a cpu group for NNAPI HAL processes
mkdir /dev/cpuctl/nnapi-hal
@@ -591,7 +595,20 @@
# Load trusted keys from dm-verity protected partitions
exec -- /system/bin/fsverity_init --load-verified-keys
-on late-fs && property:ro.product.cpu.abilist64=*
+# Only enable the bootreceiver tracing instance for kernels 5.10 and above.
+on late-fs && property:ro.kernel.version=4.9
+ setprop bootreceiver.enable 0
+on late-fs && property:ro.kernel.version=4.14
+ setprop bootreceiver.enable 0
+on late-fs && property:ro.kernel.version=4.19
+ setprop bootreceiver.enable 0
+on late-fs && property:ro.kernel.version=5.4
+ setprop bootreceiver.enable 0
+on late-fs
+ # Bootreceiver tracing instance is enabled by default.
+ setprop bootreceiver.enable ${bootreceiver.enable:-1}
+
+on property:ro.product.cpu.abilist64=* && property:bootreceiver.enable=1
# Set up a tracing instance for system_server to monitor error_report_end events.
# These are sent by kernel tools like KASAN and KFENCE when a memory corruption
# is detected. This is only needed for 64-bit systems.
@@ -776,11 +793,13 @@
# Create directories to push tests to for each linker namespace.
# Create the subdirectories in case the first test is run as root
# so it doesn't end up owned by root.
- mkdir /data/local/tests 0700 shell shell
- mkdir /data/local/tests/product 0700 shell shell
- mkdir /data/local/tests/system 0700 shell shell
- mkdir /data/local/tests/unrestricted 0700 shell shell
- mkdir /data/local/tests/vendor 0700 shell shell
+ # Set directories to be executable by any process so that debuggerd,
+ # aka crash_dump, can read any executables/shared libraries.
+ mkdir /data/local/tests 0701 shell shell
+ mkdir /data/local/tests/product 0701 shell shell
+ mkdir /data/local/tests/system 0701 shell shell
+ mkdir /data/local/tests/unrestricted 0701 shell shell
+ mkdir /data/local/tests/vendor 0701 shell shell
# create dalvik-cache, so as to enforce our permissions
mkdir /data/dalvik-cache 0771 root root encryption=Require
@@ -1082,6 +1101,9 @@
# Define default initial receive window size in segments.
setprop net.tcp_def_init_rwnd 60
+ # Update dm-verity state and set partition.*.verified properties.
+ verity_update_state
+
# Start standard binderized HAL daemons
class_start hal
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index 97e8d8e..d85f6ed 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -26,6 +26,7 @@
"mkshrc",
"newfs_msdos",
"reboot",
+ "settaskprofile",
"sh",
"simpleperf",
"simpleperf_app_runner",
diff --git a/storaged/Android.bp b/storaged/Android.bp
index 9d5cb48..b557dee 100644
--- a/storaged/Android.bp
+++ b/storaged/Android.bp
@@ -32,6 +32,7 @@
"libprotobuf-cpp-lite",
"libutils",
"libz",
+ "packagemanager_aidl-cpp",
],
cflags: [
diff --git a/trusty/apploader/apploader.cpp b/trusty/apploader/apploader.cpp
index e4d9b39..c72af40 100644
--- a/trusty/apploader/apploader.cpp
+++ b/trusty/apploader/apploader.cpp
@@ -245,6 +245,8 @@
tipc_fd = tipc_connect(dev_name, APPLOADER_PORT);
if (tipc_fd < 0) {
LOG(ERROR) << "Failed to connect to Trusty app loader: " << strerror(-tipc_fd);
+ // print this to stderr too to avoid silently exiting when run as non-root
+ fprintf(stderr, "Failed to connect to Trusty app loader: %s\n", strerror(-tipc_fd));
rc = tipc_fd;
goto err_tipc_connect;
}
diff --git a/trusty/keymaster/Android.bp b/trusty/keymaster/Android.bp
index 155363c..99d9e56 100644
--- a/trusty/keymaster/Android.bp
+++ b/trusty/keymaster/Android.bp
@@ -117,9 +117,11 @@
"libkeymint",
"liblog",
"libtrusty",
+ "libutils",
],
required: [
"android.hardware.hardware_keystore.xml",
+ "RemoteProvisioner",
],
}
@@ -141,6 +143,7 @@
"libtrusty",
"libhardware",
"libkeymaster_messages",
+ "libutils",
"libxml2",
],
export_include_dirs: ["include"],
@@ -168,6 +171,7 @@
"libtrusty",
"libhardware",
"libkeymaster_messages",
+ "libutils",
"libxml2",
],
cflags: [
diff --git a/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp b/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp
index 2d44009..db1a9f4 100644
--- a/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp
+++ b/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp
@@ -19,6 +19,7 @@
// TODO: make this generic in libtrusty
#include <errno.h>
+#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
@@ -33,11 +34,15 @@
#include <trusty_keymaster/ipc/keymaster_ipc.h>
#include <trusty_keymaster/ipc/trusty_keymaster_ipc.h>
+#include <utils/Timers.h>
#define TRUSTY_DEVICE_NAME "/dev/trusty-ipc-dev0"
static int handle_ = -1;
+static const int timeout_ms = 10 * 1000;
+static const int max_timeout_ms = 60 * 1000;
+
int trusty_keymaster_connect() {
int rc = tipc_connect(TRUSTY_DEVICE_NAME, KEYMASTER_PORT);
if (rc < 0) {
@@ -84,7 +89,38 @@
msg->cmd = cmd;
memcpy(msg->payload, in, in_size);
+ nsecs_t start_time_ns = systemTime(SYSTEM_TIME_MONOTONIC);
+ bool timed_out = false;
+ int poll_timeout_ms = timeout_ms;
+ while (true) {
+ struct pollfd pfd;
+ pfd.fd = handle_;
+ pfd.events = POLLOUT;
+ pfd.revents = 0;
+
+ int p = poll(&pfd, 1, poll_timeout_ms);
+ if (p == 0) {
+ ALOGW("write for cmd %d is taking more than %lld nsecs", cmd,
+ (long long)(systemTime(SYSTEM_TIME_MONOTONIC) - start_time_ns));
+ timed_out = true;
+ poll_timeout_ms *= 2;
+ if (poll_timeout_ms > max_timeout_ms) {
+ poll_timeout_ms = max_timeout_ms;
+ }
+ continue;
+ } else if (p < 0) {
+ ALOGE("write poll error: %d", errno);
+ } else if (pfd.revents != POLLOUT) {
+ ALOGW("unexpected poll() result: %d", pfd.revents);
+ }
+ break;
+ }
+
ssize_t rc = write(handle_, msg, msg_size);
+ if (timed_out) {
+ ALOGW("write for cmd %d finished after %lld nsecs", cmd,
+ (long long)(systemTime(SYSTEM_TIME_MONOTONIC) - start_time_ns));
+ }
free(msg);
if (rc < 0) {
@@ -122,8 +158,37 @@
return -EOVERFLOW;
}
iov[1] = {.iov_base = write_pos, .iov_len = buffer_size};
+ start_time_ns = systemTime(SYSTEM_TIME_MONOTONIC);
+ timed_out = false;
+ poll_timeout_ms = timeout_ms;
+ while (true) {
+ struct pollfd pfd;
+ pfd.fd = handle_;
+ pfd.events = POLLIN;
+ pfd.revents = 0;
+ int p = poll(&pfd, 1, poll_timeout_ms);
+ if (p == 0) {
+ ALOGW("readv for cmd %d is taking more than %lld nsecs", cmd,
+ (long long)(systemTime(SYSTEM_TIME_MONOTONIC) - start_time_ns));
+ timed_out = true;
+ poll_timeout_ms *= 2;
+ if (poll_timeout_ms > max_timeout_ms) {
+ poll_timeout_ms = max_timeout_ms;
+ }
+ continue;
+ } else if (p < 0) {
+ ALOGE("read poll error: %d", errno);
+ } else if (pfd.revents != POLLIN) {
+ ALOGW("unexpected poll() result: %d", pfd.revents);
+ }
+ break;
+ }
rc = readv(handle_, iov, 2);
+ if (timed_out) {
+ ALOGW("readv for cmd %d finished after %lld nsecs", cmd,
+ (long long)(systemTime(SYSTEM_TIME_MONOTONIC) - start_time_ns));
+ }
if (rc < 0) {
ALOGE("failed to retrieve response for cmd (%d) to %s: %s\n", cmd, KEYMASTER_PORT,
strerror(errno));
diff --git a/trusty/keymaster/keymint/service.cpp b/trusty/keymaster/keymint/service.cpp
index 4060278..d5a77fb 100644
--- a/trusty/keymaster/keymint/service.cpp
+++ b/trusty/keymaster/keymint/service.cpp
@@ -31,7 +31,7 @@
template <typename T, class... Args>
std::shared_ptr<T> addService(Args&&... args) {
- std::shared_ptr<T> service = std::make_shared<T>(std::forward<Args>(args)...);
+ std::shared_ptr<T> service = ndk::SharedRefBase::make<T>(std::forward<Args>(args)...);
auto instanceName = std::string(T::descriptor) + "/default";
LOG(ERROR) << "Adding service instance: " << instanceName;
auto status = AServiceManager_addService(service->asBinder().get(), instanceName.c_str());
diff --git a/trusty/libtrusty/tipc-test/tipc_test.c b/trusty/libtrusty/tipc-test/tipc_test.c
index 29c6f93..eb0acb5 100644
--- a/trusty/libtrusty/tipc-test/tipc_test.c
+++ b/trusty/libtrusty/tipc-test/tipc_test.c
@@ -45,36 +45,40 @@
static const char *main_ctrl_name = "com.android.ipc-unittest.ctrl";
static const char* receiver_name = "com.android.trusty.memref.receiver";
-static const char *_sopts = "hsvD:t:r:m:b:";
+static const char* _sopts = "hsvDS:t:r:m:b:";
+/* clang-format off */
static const struct option _lopts[] = {
- {"help", no_argument, 0, 'h'},
- {"silent", no_argument, 0, 's'},
- {"variable",no_argument, 0, 'v'},
- {"dev", required_argument, 0, 'D'},
- {"repeat", required_argument, 0, 'r'},
- {"burst", required_argument, 0, 'b'},
- {"msgsize", required_argument, 0, 'm'},
- {0, 0, 0, 0}
+ {"help", no_argument, 0, 'h'},
+ {"silent", no_argument, 0, 's'},
+ {"variable",no_argument, 0, 'v'},
+ {"dev", required_argument, 0, 'D'},
+ {"srv", required_argument, 0, 'S'},
+ {"repeat", required_argument, 0, 'r'},
+ {"burst", required_argument, 0, 'b'},
+ {"msgsize", required_argument, 0, 'm'},
+ {0, 0, 0, 0}
};
+/* clang-format on */
-static const char *usage =
-"Usage: %s [options]\n"
-"\n"
-"options:\n"
-" -h, --help prints this message and exit\n"
-" -D, --dev name device name\n"
-" -t, --test name test to run\n"
-" -r, --repeat cnt repeat count\n"
-" -m, --msgsize size max message size\n"
-" -v, --variable variable message size\n"
-" -s, --silent silent\n"
-"\n"
-;
+static const char* usage =
+ "Usage: %s [options]\n"
+ "\n"
+ "options:\n"
+ " -h, --help prints this message and exit\n"
+ " -D, --dev name device name\n"
+ " -S, --srv name service name\n"
+ " -t, --test name test to run\n"
+ " -r, --repeat cnt repeat count\n"
+ " -b, --burst cnt burst count\n"
+ " -m, --msgsize size max message size\n"
+ " -v, --variable variable message size\n"
+ " -s, --silent silent\n"
+ "\n";
static const char* usage_long =
"\n"
"The following tests are available:\n"
- " connect - connect to datasink service\n"
+ " connect - connect to specified service, defaults to echo+datasink\n"
" connect_foo - connect to non existing service\n"
" burst_write - send messages to datasink service\n"
" echo - send/receive messages to echo service\n"
@@ -96,798 +100,774 @@
static uint opt_msgburst = 32;
static bool opt_variable = false;
static bool opt_silent = false;
+static char* srv_name = NULL;
static void print_usage_and_exit(const char *prog, int code, bool verbose)
{
- fprintf (stderr, usage, prog);
- if (verbose)
- fprintf (stderr, "%s", usage_long);
- exit(code);
+ fprintf(stderr, usage, prog);
+ if (verbose) fprintf(stderr, "%s", usage_long);
+ exit(code);
}
static void parse_options(int argc, char **argv)
{
- int c;
- int oidx = 0;
+ int c;
+ int oidx = 0;
- while (1)
- {
- c = getopt_long (argc, argv, _sopts, _lopts, &oidx);
- if (c == -1)
- break; /* done */
+ while (1) {
+ c = getopt_long(argc, argv, _sopts, _lopts, &oidx);
+ if (c == -1) break; /* done */
- switch (c) {
+ switch (c) {
+ case 'D':
+ dev_name = strdup(optarg);
+ break;
- case 'D':
- dev_name = strdup(optarg);
- break;
+ case 'S':
+ srv_name = strdup(optarg);
+ break;
- case 't':
- test_name = strdup(optarg);
- break;
+ case 't':
+ test_name = strdup(optarg);
+ break;
- case 'v':
- opt_variable = true;
- break;
+ case 'v':
+ opt_variable = true;
+ break;
- case 'r':
- opt_repeat = atoi(optarg);
- break;
+ case 'r':
+ opt_repeat = atoi(optarg);
+ break;
- case 'm':
- opt_msgsize = atoi(optarg);
- break;
+ case 'm':
+ opt_msgsize = atoi(optarg);
+ break;
- case 'b':
- opt_msgburst = atoi(optarg);
- break;
+ case 'b':
+ opt_msgburst = atoi(optarg);
+ break;
- case 's':
- opt_silent = true;
- break;
+ case 's':
+ opt_silent = true;
+ break;
- case 'h':
- print_usage_and_exit(argv[0], EXIT_SUCCESS, true);
- break;
+ case 'h':
+ print_usage_and_exit(argv[0], EXIT_SUCCESS, true);
+ break;
- default:
- print_usage_and_exit(argv[0], EXIT_FAILURE, false);
- }
- }
+ default:
+ print_usage_and_exit(argv[0], EXIT_FAILURE, false);
+ }
+ }
}
static int connect_test(uint repeat)
{
- uint i;
- int echo_fd;
- int dsink_fd;
+ uint i;
+ int echo_fd;
+ int dsink_fd;
+ int custom_fd;
- if (!opt_silent) {
- printf("%s: repeat = %u\n", __func__, repeat);
- }
+ if (!opt_silent) {
+ printf("%s: repeat = %u\n", __func__, repeat);
+ }
- for (i = 0; i < repeat; i++) {
- echo_fd = tipc_connect(dev_name, echo_name);
- if (echo_fd < 0) {
- fprintf(stderr, "Failed to connect to '%s' service\n",
- "echo");
- }
- dsink_fd = tipc_connect(dev_name, datasink_name);
- if (dsink_fd < 0) {
- fprintf(stderr, "Failed to connect to '%s' service\n",
- "datasink");
- }
+ for (i = 0; i < repeat; i++) {
+ if (srv_name) {
+ custom_fd = tipc_connect(dev_name, srv_name);
+ if (custom_fd < 0) {
+ fprintf(stderr, "Failed to connect to '%s' service\n", srv_name);
+ }
+ if (custom_fd >= 0) {
+ tipc_close(custom_fd);
+ }
+ } else {
+ echo_fd = tipc_connect(dev_name, echo_name);
+ if (echo_fd < 0) {
+ fprintf(stderr, "Failed to connect to '%s' service\n", "echo");
+ }
+ dsink_fd = tipc_connect(dev_name, datasink_name);
+ if (dsink_fd < 0) {
+ fprintf(stderr, "Failed to connect to '%s' service\n", "datasink");
+ }
- if (echo_fd >= 0) {
- tipc_close(echo_fd);
- }
- if (dsink_fd >= 0) {
- tipc_close(dsink_fd);
- }
- }
+ if (echo_fd >= 0) {
+ tipc_close(echo_fd);
+ }
+ if (dsink_fd >= 0) {
+ tipc_close(dsink_fd);
+ }
+ }
+ }
- if (!opt_silent) {
- printf("%s: done\n", __func__);
- }
+ if (!opt_silent) {
+ printf("%s: done\n", __func__);
+ }
- return 0;
+ return 0;
}
static int connect_foo(uint repeat)
{
- uint i;
- int fd;
+ uint i;
+ int fd;
- if (!opt_silent) {
- printf("%s: repeat = %u\n", __func__, repeat);
- }
+ if (!opt_silent) {
+ printf("%s: repeat = %u\n", __func__, repeat);
+ }
- for (i = 0; i < repeat; i++) {
- fd = tipc_connect(dev_name, "foo");
- if (fd >= 0) {
- fprintf(stderr, "succeeded to connect to '%s' service\n",
- "foo");
- tipc_close(fd);
- }
- }
+ for (i = 0; i < repeat; i++) {
+ fd = tipc_connect(dev_name, "foo");
+ if (fd >= 0) {
+ fprintf(stderr, "succeeded to connect to '%s' service\n", "foo");
+ tipc_close(fd);
+ }
+ }
- if (!opt_silent) {
- printf("%s: done\n", __func__);
- }
+ if (!opt_silent) {
+ printf("%s: done\n", __func__);
+ }
- return 0;
+ return 0;
}
static int closer1_test(uint repeat)
{
- uint i;
- int fd;
+ uint i;
+ int fd;
- if (!opt_silent) {
- printf("%s: repeat = %u\n", __func__, repeat);
- }
+ if (!opt_silent) {
+ printf("%s: repeat = %u\n", __func__, repeat);
+ }
- for (i = 0; i < repeat; i++) {
- fd = tipc_connect(dev_name, closer1_name);
- if (fd < 0) {
- fprintf(stderr, "Failed to connect to '%s' service\n",
- "closer1");
- continue;
- }
- if (!opt_silent) {
- printf("%s: connected\n", __func__);
- }
- tipc_close(fd);
- }
+ for (i = 0; i < repeat; i++) {
+ fd = tipc_connect(dev_name, closer1_name);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to connect to '%s' service\n", "closer1");
+ continue;
+ }
+ if (!opt_silent) {
+ printf("%s: connected\n", __func__);
+ }
+ tipc_close(fd);
+ }
- if (!opt_silent) {
- printf("%s: done\n", __func__);
- }
+ if (!opt_silent) {
+ printf("%s: done\n", __func__);
+ }
- return 0;
+ return 0;
}
static int closer2_test(uint repeat)
{
- uint i;
- int fd;
+ uint i;
+ int fd;
- if (!opt_silent) {
- printf("%s: repeat = %u\n", __func__, repeat);
- }
+ if (!opt_silent) {
+ printf("%s: repeat = %u\n", __func__, repeat);
+ }
- for (i = 0; i < repeat; i++) {
- fd = tipc_connect(dev_name, closer2_name);
- if (fd < 0) {
- if (!opt_silent) {
- printf("failed to connect to '%s' service\n", "closer2");
- }
- } else {
- /* this should always fail */
- fprintf(stderr, "connected to '%s' service\n", "closer2");
- tipc_close(fd);
- }
- }
+ for (i = 0; i < repeat; i++) {
+ fd = tipc_connect(dev_name, closer2_name);
+ if (fd < 0) {
+ if (!opt_silent) {
+ printf("failed to connect to '%s' service\n", "closer2");
+ }
+ } else {
+ /* this should always fail */
+ fprintf(stderr, "connected to '%s' service\n", "closer2");
+ tipc_close(fd);
+ }
+ }
- if (!opt_silent) {
- printf("%s: done\n", __func__);
- }
+ if (!opt_silent) {
+ printf("%s: done\n", __func__);
+ }
- return 0;
+ return 0;
}
static int closer3_test(uint repeat)
{
- uint i, j;
- ssize_t rc;
- int fd[4];
- char buf[64];
+ uint i, j;
+ ssize_t rc;
+ int fd[4];
+ char buf[64];
- if (!opt_silent) {
- printf("%s: repeat = %u\n", __func__, repeat);
- }
+ if (!opt_silent) {
+ printf("%s: repeat = %u\n", __func__, repeat);
+ }
- for (i = 0; i < repeat; i++) {
+ for (i = 0; i < repeat; i++) {
+ /* open 4 connections to closer3 service */
+ for (j = 0; j < 4; j++) {
+ fd[j] = tipc_connect(dev_name, closer3_name);
+ if (fd[j] < 0) {
+ fprintf(stderr, "fd[%d]: failed to connect to '%s' service\n", j, "closer3");
+ } else {
+ if (!opt_silent) {
+ printf("%s: fd[%d]=%d: connected\n", __func__, j, fd[j]);
+ }
+ memset(buf, i + j, sizeof(buf));
+ rc = write(fd[j], buf, sizeof(buf));
+ if (rc != sizeof(buf)) {
+ if (!opt_silent) {
+ printf("%s: fd[%d]=%d: write returned = %zd\n", __func__, j, fd[j], rc);
+ }
+ perror("closer3_test: write");
+ }
+ }
+ }
- /* open 4 connections to closer3 service */
- for (j = 0; j < 4; j++) {
- fd[j] = tipc_connect(dev_name, closer3_name);
- if (fd[j] < 0) {
- fprintf(stderr, "fd[%d]: failed to connect to '%s' service\n", j, "closer3");
- } else {
- if (!opt_silent) {
- printf("%s: fd[%d]=%d: connected\n", __func__, j, fd[j]);
- }
- memset(buf, i + j, sizeof(buf));
- rc = write(fd[j], buf, sizeof(buf));
- if (rc != sizeof(buf)) {
- if (!opt_silent) {
- printf("%s: fd[%d]=%d: write returned = %zd\n",
- __func__, j, fd[j], rc);
- }
- perror("closer3_test: write");
- }
- }
- }
+ /* sleep a bit */
+ sleep(1);
- /* sleep a bit */
- sleep(1);
+ /* It is expected that they will be closed by remote */
+ for (j = 0; j < 4; j++) {
+ if (fd[j] < 0) continue;
+ rc = write(fd[j], buf, sizeof(buf));
+ if (rc != sizeof(buf)) {
+ if (!opt_silent) {
+ printf("%s: fd[%d]=%d: write returned = %zd\n", __func__, j, fd[j], rc);
+ }
+ perror("closer3_test: write");
+ }
+ }
- /* It is expected that they will be closed by remote */
- for (j = 0; j < 4; j++) {
- if (fd[j] < 0)
- continue;
- rc = write(fd[j], buf, sizeof(buf));
- if (rc != sizeof(buf)) {
- if (!opt_silent) {
- printf("%s: fd[%d]=%d: write returned = %zd\n",
- __func__, j, fd[j], rc);
- }
- perror("closer3_test: write");
- }
- }
+ /* then they have to be closed by remote */
+ for (j = 0; j < 4; j++) {
+ if (fd[j] >= 0) {
+ tipc_close(fd[j]);
+ }
+ }
+ }
- /* then they have to be closed by remote */
- for (j = 0; j < 4; j++) {
- if (fd[j] >= 0) {
- tipc_close(fd[j]);
- }
- }
- }
+ if (!opt_silent) {
+ printf("%s: done\n", __func__);
+ }
- if (!opt_silent) {
- printf("%s: done\n", __func__);
- }
-
- return 0;
+ return 0;
}
static int echo_test(uint repeat, uint msgsz, bool var)
{
- uint i;
- ssize_t rc;
- size_t msg_len;
- int echo_fd =-1;
- char tx_buf[msgsz];
- char rx_buf[msgsz];
+ uint i;
+ ssize_t rc;
+ size_t msg_len;
+ int echo_fd = -1;
+ char tx_buf[msgsz];
+ char rx_buf[msgsz];
- if (!opt_silent) {
- printf("%s: repeat %u: msgsz %u: variable %s\n",
- __func__, repeat, msgsz, var ? "true" : "false");
- }
+ if (!opt_silent) {
+ printf("%s: repeat %u: msgsz %u: variable %s\n", __func__, repeat, msgsz,
+ var ? "true" : "false");
+ }
- echo_fd = tipc_connect(dev_name, echo_name);
- if (echo_fd < 0) {
- fprintf(stderr, "Failed to connect to service\n");
- return echo_fd;
- }
+ echo_fd = tipc_connect(dev_name, echo_name);
+ if (echo_fd < 0) {
+ fprintf(stderr, "Failed to connect to service\n");
+ return echo_fd;
+ }
- for (i = 0; i < repeat; i++) {
+ for (i = 0; i < repeat; i++) {
+ msg_len = msgsz;
+ if (opt_variable && msgsz) {
+ msg_len = rand() % msgsz;
+ }
- msg_len = msgsz;
- if (opt_variable && msgsz) {
- msg_len = rand() % msgsz;
- }
+ memset(tx_buf, i + 1, msg_len);
- memset(tx_buf, i + 1, msg_len);
+ rc = write(echo_fd, tx_buf, msg_len);
+ if ((size_t)rc != msg_len) {
+ perror("echo_test: write");
+ break;
+ }
- rc = write(echo_fd, tx_buf, msg_len);
- if ((size_t)rc != msg_len) {
- perror("echo_test: write");
- break;
- }
+ rc = read(echo_fd, rx_buf, msg_len);
+ if (rc < 0) {
+ perror("echo_test: read");
+ break;
+ }
- rc = read(echo_fd, rx_buf, msg_len);
- if (rc < 0) {
- perror("echo_test: read");
- break;
- }
+ if ((size_t)rc != msg_len) {
+ fprintf(stderr, "data truncated (%zu vs. %zu)\n", rc, msg_len);
+ continue;
+ }
- if ((size_t)rc != msg_len) {
- fprintf(stderr, "data truncated (%zu vs. %zu)\n",
- rc, msg_len);
- continue;
- }
+ if (memcmp(tx_buf, rx_buf, (size_t)rc)) {
+ fprintf(stderr, "data mismatch\n");
+ continue;
+ }
+ }
- if (memcmp(tx_buf, rx_buf, (size_t) rc)) {
- fprintf(stderr, "data mismatch\n");
- continue;
- }
- }
+ tipc_close(echo_fd);
- tipc_close(echo_fd);
+ if (!opt_silent) {
+ printf("%s: done\n", __func__);
+ }
- if (!opt_silent) {
- printf("%s: done\n",__func__);
- }
-
- return 0;
+ return 0;
}
static int burst_write_test(uint repeat, uint msgburst, uint msgsz, bool var)
{
- int fd;
- uint i, j;
- ssize_t rc;
- size_t msg_len;
- char tx_buf[msgsz];
+ int fd;
+ uint i, j;
+ ssize_t rc;
+ size_t msg_len;
+ char tx_buf[msgsz];
- if (!opt_silent) {
- printf("%s: repeat %u: burst %u: msgsz %u: variable %s\n",
- __func__, repeat, msgburst, msgsz,
- var ? "true" : "false");
- }
+ if (!opt_silent) {
+ printf("%s: repeat %u: burst %u: msgsz %u: variable %s\n", __func__, repeat, msgburst,
+ msgsz, var ? "true" : "false");
+ }
- for (i = 0; i < repeat; i++) {
+ for (i = 0; i < repeat; i++) {
+ fd = tipc_connect(dev_name, datasink_name);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to connect to '%s' service\n", "datasink");
+ break;
+ }
- fd = tipc_connect(dev_name, datasink_name);
- if (fd < 0) {
- fprintf(stderr, "Failed to connect to '%s' service\n",
- "datasink");
- break;
- }
+ for (j = 0; j < msgburst; j++) {
+ msg_len = msgsz;
+ if (var && msgsz) {
+ msg_len = rand() % msgsz;
+ }
- for (j = 0; j < msgburst; j++) {
- msg_len = msgsz;
- if (var && msgsz) {
- msg_len = rand() % msgsz;
- }
+ memset(tx_buf, i + 1, msg_len);
+ rc = write(fd, tx_buf, msg_len);
+ if ((size_t)rc != msg_len) {
+ perror("burst_test: write");
+ break;
+ }
+ }
- memset(tx_buf, i + 1, msg_len);
- rc = write(fd, tx_buf, msg_len);
- if ((size_t)rc != msg_len) {
- perror("burst_test: write");
- break;
- }
- }
+ tipc_close(fd);
+ }
- tipc_close(fd);
- }
+ if (!opt_silent) {
+ printf("%s: done\n", __func__);
+ }
- if (!opt_silent) {
- printf("%s: done\n",__func__);
- }
-
- return 0;
+ return 0;
}
static int _wait_for_msg(int fd, uint msgsz, int timeout)
{
- int rc;
- fd_set rfds;
- uint msgcnt = 0;
- char rx_buf[msgsz];
- struct timeval tv;
+ int rc;
+ fd_set rfds;
+ uint msgcnt = 0;
+ char rx_buf[msgsz];
+ struct timeval tv;
- if (!opt_silent) {
- printf("waiting (%d) for msg\n", timeout);
- }
+ if (!opt_silent) {
+ printf("waiting (%d) for msg\n", timeout);
+ }
- FD_ZERO(&rfds);
- FD_SET(fd, &rfds);
+ FD_ZERO(&rfds);
+ FD_SET(fd, &rfds);
- tv.tv_sec = timeout;
- tv.tv_usec = 0;
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
- for(;;) {
- rc = select(fd+1, &rfds, NULL, NULL, &tv);
+ for (;;) {
+ rc = select(fd + 1, &rfds, NULL, NULL, &tv);
- if (rc == 0) {
- if (!opt_silent) {
- printf("select timedout\n");
- }
- break;
- }
+ if (rc == 0) {
+ if (!opt_silent) {
+ printf("select timedout\n");
+ }
+ break;
+ }
- if (rc == -1) {
- perror("select_test: select");
- return rc;
- }
+ if (rc == -1) {
+ perror("select_test: select");
+ return rc;
+ }
- rc = read(fd, rx_buf, sizeof(rx_buf));
- if (rc < 0) {
- perror("select_test: read");
- return rc;
- } else {
- if (rc > 0) {
- msgcnt++;
- }
- }
- }
+ rc = read(fd, rx_buf, sizeof(rx_buf));
+ if (rc < 0) {
+ perror("select_test: read");
+ return rc;
+ } else {
+ if (rc > 0) {
+ msgcnt++;
+ }
+ }
+ }
- if (!opt_silent) {
- printf("got %u messages\n", msgcnt);
- }
+ if (!opt_silent) {
+ printf("got %u messages\n", msgcnt);
+ }
- return 0;
+ return 0;
}
static int select_test(uint repeat, uint msgburst, uint msgsz)
{
- int fd;
- uint i, j;
- ssize_t rc;
- char tx_buf[msgsz];
+ int fd;
+ uint i, j;
+ ssize_t rc;
+ char tx_buf[msgsz];
- if (!opt_silent) {
- printf("%s: repeat %u\n", __func__, repeat);
- }
+ if (!opt_silent) {
+ printf("%s: repeat %u\n", __func__, repeat);
+ }
- fd = tipc_connect(dev_name, echo_name);
- if (fd < 0) {
- fprintf(stderr, "Failed to connect to '%s' service\n",
- "echo");
- return fd;
- }
+ fd = tipc_connect(dev_name, echo_name);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to connect to '%s' service\n", "echo");
+ return fd;
+ }
- for (i = 0; i < repeat; i++) {
+ for (i = 0; i < repeat; i++) {
+ _wait_for_msg(fd, msgsz, 1);
- _wait_for_msg(fd, msgsz, 1);
+ if (!opt_silent) {
+ printf("sending burst: %u msg\n", msgburst);
+ }
- if (!opt_silent) {
- printf("sending burst: %u msg\n", msgburst);
- }
+ for (j = 0; j < msgburst; j++) {
+ memset(tx_buf, i + j, msgsz);
+ rc = write(fd, tx_buf, msgsz);
+ if ((size_t)rc != msgsz) {
+ perror("burst_test: write");
+ break;
+ }
+ }
+ }
- for (j = 0; j < msgburst; j++) {
- memset(tx_buf, i + j, msgsz);
- rc = write(fd, tx_buf, msgsz);
- if ((size_t)rc != msgsz) {
- perror("burst_test: write");
- break;
- }
- }
- }
+ tipc_close(fd);
- tipc_close(fd);
+ if (!opt_silent) {
+ printf("%s: done\n", __func__);
+ }
- if (!opt_silent) {
- printf("%s: done\n",__func__);
- }
-
- return 0;
+ return 0;
}
static int blocked_read_test(uint repeat)
{
- int fd;
- uint i;
- ssize_t rc;
- char rx_buf[512];
+ int fd;
+ uint i;
+ ssize_t rc;
+ char rx_buf[512];
- if (!opt_silent) {
- printf("%s: repeat %u\n", __func__, repeat);
- }
+ if (!opt_silent) {
+ printf("%s: repeat %u\n", __func__, repeat);
+ }
- fd = tipc_connect(dev_name, echo_name);
- if (fd < 0) {
- fprintf(stderr, "Failed to connect to '%s' service\n",
- "echo");
- return fd;
- }
+ fd = tipc_connect(dev_name, echo_name);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to connect to '%s' service\n", "echo");
+ return fd;
+ }
- for (i = 0; i < repeat; i++) {
- rc = read(fd, rx_buf, sizeof(rx_buf));
- if (rc < 0) {
- perror("select_test: read");
- break;
- } else {
- if (!opt_silent) {
- printf("got %zd bytes\n", rc);
- }
- }
- }
+ for (i = 0; i < repeat; i++) {
+ rc = read(fd, rx_buf, sizeof(rx_buf));
+ if (rc < 0) {
+ perror("select_test: read");
+ break;
+ } else {
+ if (!opt_silent) {
+ printf("got %zd bytes\n", rc);
+ }
+ }
+ }
- tipc_close(fd);
+ tipc_close(fd);
- if (!opt_silent) {
- printf("%s: done\n",__func__);
- }
+ if (!opt_silent) {
+ printf("%s: done\n", __func__);
+ }
- return 0;
+ return 0;
}
static int ta2ta_ipc_test(void)
{
- enum test_message_header {
- TEST_PASSED = 0,
- TEST_FAILED = 1,
- TEST_MESSAGE = 2,
- };
+ enum test_message_header {
+ TEST_PASSED = 0,
+ TEST_FAILED = 1,
+ TEST_MESSAGE = 2,
+ };
- int fd;
- int ret;
- unsigned char rx_buf[256];
+ int fd;
+ int ret;
+ unsigned char rx_buf[256];
- if (!opt_silent) {
- printf("%s:\n", __func__);
- }
+ if (!opt_silent) {
+ printf("%s:\n", __func__);
+ }
- fd = tipc_connect(dev_name, main_ctrl_name);
- if (fd < 0) {
- fprintf(stderr, "Failed to connect to '%s' service\n",
- "main_ctrl");
- return fd;
- }
+ fd = tipc_connect(dev_name, main_ctrl_name);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to connect to '%s' service\n", "main_ctrl");
+ return fd;
+ }
- /* Wait for tests to complete and read status */
- while (true) {
- ret = read(fd, rx_buf, sizeof(rx_buf));
- if (ret <= 0 || ret >= (int)sizeof(rx_buf)) {
- fprintf(stderr, "%s: Read failed: %d\n", __func__, ret);
- tipc_close(fd);
- return -1;
- }
+ /* Wait for tests to complete and read status */
+ while (true) {
+ ret = read(fd, rx_buf, sizeof(rx_buf));
+ if (ret <= 0 || ret >= (int)sizeof(rx_buf)) {
+ fprintf(stderr, "%s: Read failed: %d\n", __func__, ret);
+ tipc_close(fd);
+ return -1;
+ }
- if (rx_buf[0] == TEST_PASSED) {
- break;
- } else if (rx_buf[0] == TEST_FAILED) {
- break;
- } else if (rx_buf[0] == TEST_MESSAGE) {
- write(STDOUT_FILENO, rx_buf + 1, ret - 1);
- } else {
- fprintf(stderr, "%s: Bad message header: %d\n",
- __func__, rx_buf[0]);
- break;
- }
- }
+ if (rx_buf[0] == TEST_PASSED) {
+ break;
+ } else if (rx_buf[0] == TEST_FAILED) {
+ break;
+ } else if (rx_buf[0] == TEST_MESSAGE) {
+ write(STDOUT_FILENO, rx_buf + 1, ret - 1);
+ } else {
+ fprintf(stderr, "%s: Bad message header: %d\n", __func__, rx_buf[0]);
+ break;
+ }
+ }
- tipc_close(fd);
+ tipc_close(fd);
- return rx_buf[0] == TEST_PASSED ? 0 : -1;
+ return rx_buf[0] == TEST_PASSED ? 0 : -1;
}
typedef struct uuid
{
- uint32_t time_low;
- uint16_t time_mid;
- uint16_t time_hi_and_version;
- uint8_t clock_seq_and_node[8];
+ uint32_t time_low;
+ uint16_t time_mid;
+ uint16_t time_hi_and_version;
+ uint8_t clock_seq_and_node[8];
} uuid_t;
static void print_uuid(const char *dev, uuid_t *uuid)
{
- printf("%s:", dev);
- printf("uuid: %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
- uuid->time_low,
- uuid->time_mid,
- uuid->time_hi_and_version,
- uuid->clock_seq_and_node[0],
- uuid->clock_seq_and_node[1],
- uuid->clock_seq_and_node[2],
- uuid->clock_seq_and_node[3],
- uuid->clock_seq_and_node[4],
- uuid->clock_seq_and_node[5],
- uuid->clock_seq_and_node[6],
- uuid->clock_seq_and_node[7]
- );
+ printf("%s:", dev);
+ printf("uuid: %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", uuid->time_low,
+ uuid->time_mid, uuid->time_hi_and_version, uuid->clock_seq_and_node[0],
+ uuid->clock_seq_and_node[1], uuid->clock_seq_and_node[2], uuid->clock_seq_and_node[3],
+ uuid->clock_seq_and_node[4], uuid->clock_seq_and_node[5], uuid->clock_seq_and_node[6],
+ uuid->clock_seq_and_node[7]);
}
static int dev_uuid_test(void)
{
- int fd;
- ssize_t rc;
- uuid_t uuid;
+ int fd;
+ ssize_t rc;
+ uuid_t uuid;
- fd = tipc_connect(dev_name, uuid_name);
- if (fd < 0) {
- fprintf(stderr, "Failed to connect to '%s' service\n",
- "uuid");
- return fd;
- }
+ fd = tipc_connect(dev_name, uuid_name);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to connect to '%s' service\n", "uuid");
+ return fd;
+ }
- /* wait for test to complete */
- rc = read(fd, &uuid, sizeof(uuid));
- if (rc < 0) {
- perror("dev_uuid_test: read");
- } else if (rc != sizeof(uuid)) {
- fprintf(stderr, "unexpected uuid size (%d vs. %d)\n",
- (int)rc, (int)sizeof(uuid));
- } else {
- print_uuid(dev_name, &uuid);
- }
+ /* wait for test to complete */
+ rc = read(fd, &uuid, sizeof(uuid));
+ if (rc < 0) {
+ perror("dev_uuid_test: read");
+ } else if (rc != sizeof(uuid)) {
+ fprintf(stderr, "unexpected uuid size (%d vs. %d)\n", (int)rc, (int)sizeof(uuid));
+ } else {
+ print_uuid(dev_name, &uuid);
+ }
- tipc_close(fd);
+ tipc_close(fd);
- return 0;
+ return 0;
}
static int ta_access_test(void)
{
- int fd;
+ int fd;
- if (!opt_silent) {
- printf("%s:\n", __func__);
- }
+ if (!opt_silent) {
+ printf("%s:\n", __func__);
+ }
- fd = tipc_connect(dev_name, ta_only_name);
- if (fd >= 0) {
- fprintf(stderr, "Succeed to connect to '%s' service\n",
- "ta_only");
- tipc_close(fd);
- }
+ fd = tipc_connect(dev_name, ta_only_name);
+ if (fd >= 0) {
+ fprintf(stderr, "Succeed to connect to '%s' service\n", "ta_only");
+ tipc_close(fd);
+ }
- fd = tipc_connect(dev_name, ns_only_name);
- if (fd < 0) {
- fprintf(stderr, "Failed to connect to '%s' service\n",
- "ns_only");
- return fd;
- }
- tipc_close(fd);
+ fd = tipc_connect(dev_name, ns_only_name);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to connect to '%s' service\n", "ns_only");
+ return fd;
+ }
+ tipc_close(fd);
- if (!opt_silent) {
- printf("%s: done\n",__func__);
- }
+ if (!opt_silent) {
+ printf("%s: done\n", __func__);
+ }
- return 0;
+ return 0;
}
static int writev_test(uint repeat, uint msgsz, bool var)
{
- uint i;
- ssize_t rc;
- size_t msg_len;
- int echo_fd = -1;
- char tx0_buf[msgsz];
- char tx1_buf[msgsz];
- char rx_buf [msgsz];
- struct iovec iovs[2]= {{tx0_buf, 0}, {tx1_buf, 0}};
+ uint i;
+ ssize_t rc;
+ size_t msg_len;
+ int echo_fd = -1;
+ char tx0_buf[msgsz];
+ char tx1_buf[msgsz];
+ char rx_buf[msgsz];
+ struct iovec iovs[2] = {{tx0_buf, 0}, {tx1_buf, 0}};
- if (!opt_silent) {
- printf("%s: repeat %u: msgsz %u: variable %s\n",
- __func__, repeat, msgsz, var ? "true" : "false");
- }
+ if (!opt_silent) {
+ printf("%s: repeat %u: msgsz %u: variable %s\n", __func__, repeat, msgsz,
+ var ? "true" : "false");
+ }
- echo_fd = tipc_connect(dev_name, echo_name);
- if (echo_fd < 0) {
- fprintf(stderr, "Failed to connect to service\n");
- return echo_fd;
- }
+ echo_fd = tipc_connect(dev_name, echo_name);
+ if (echo_fd < 0) {
+ fprintf(stderr, "Failed to connect to service\n");
+ return echo_fd;
+ }
- for (i = 0; i < repeat; i++) {
+ for (i = 0; i < repeat; i++) {
+ msg_len = msgsz;
+ if (opt_variable && msgsz) {
+ msg_len = rand() % msgsz;
+ }
- msg_len = msgsz;
- if (opt_variable && msgsz) {
- msg_len = rand() % msgsz;
- }
+ iovs[0].iov_len = msg_len / 3;
+ iovs[1].iov_len = msg_len - iovs[0].iov_len;
- iovs[0].iov_len = msg_len / 3;
- iovs[1].iov_len = msg_len - iovs[0].iov_len;
+ memset(tx0_buf, i + 1, iovs[0].iov_len);
+ memset(tx1_buf, i + 2, iovs[1].iov_len);
+ memset(rx_buf, i + 3, sizeof(rx_buf));
- memset(tx0_buf, i + 1, iovs[0].iov_len);
- memset(tx1_buf, i + 2, iovs[1].iov_len);
- memset(rx_buf, i + 3, sizeof(rx_buf));
+ rc = writev(echo_fd, iovs, 2);
+ if (rc < 0) {
+ perror("writev_test: writev");
+ break;
+ }
- rc = writev(echo_fd, iovs, 2);
- if (rc < 0) {
- perror("writev_test: writev");
- break;
- }
+ if ((size_t)rc != msg_len) {
+ fprintf(stderr, "%s: %s: data size mismatch (%zd vs. %zd)\n", __func__, "writev",
+ (size_t)rc, msg_len);
+ break;
+ }
- if ((size_t)rc != msg_len) {
- fprintf(stderr,
- "%s: %s: data size mismatch (%zd vs. %zd)\n",
- __func__, "writev", (size_t)rc, msg_len);
- break;
- }
+ rc = read(echo_fd, rx_buf, sizeof(rx_buf));
+ if (rc < 0) {
+ perror("writev_test: read");
+ break;
+ }
- rc = read(echo_fd, rx_buf, sizeof(rx_buf));
- if (rc < 0) {
- perror("writev_test: read");
- break;
- }
+ if ((size_t)rc != msg_len) {
+ fprintf(stderr, "%s: %s: data size mismatch (%zd vs. %zd)\n", __func__, "read",
+ (size_t)rc, msg_len);
+ break;
+ }
- if ((size_t)rc != msg_len) {
- fprintf(stderr,
- "%s: %s: data size mismatch (%zd vs. %zd)\n",
- __func__, "read", (size_t)rc, msg_len);
- break;
- }
+ if (memcmp(tx0_buf, rx_buf, iovs[0].iov_len)) {
+ fprintf(stderr, "%s: data mismatch: buf 0\n", __func__);
+ break;
+ }
- if (memcmp(tx0_buf, rx_buf, iovs[0].iov_len)) {
- fprintf(stderr, "%s: data mismatch: buf 0\n", __func__);
- break;
- }
+ if (memcmp(tx1_buf, rx_buf + iovs[0].iov_len, iovs[1].iov_len)) {
+ fprintf(stderr, "%s: data mismatch, buf 1\n", __func__);
+ break;
+ }
+ }
- if (memcmp(tx1_buf, rx_buf + iovs[0].iov_len, iovs[1].iov_len)) {
- fprintf(stderr, "%s: data mismatch, buf 1\n", __func__);
- break;
- }
- }
+ tipc_close(echo_fd);
- tipc_close(echo_fd);
+ if (!opt_silent) {
+ printf("%s: done\n", __func__);
+ }
- if (!opt_silent) {
- printf("%s: done\n",__func__);
- }
-
- return 0;
+ return 0;
}
static int readv_test(uint repeat, uint msgsz, bool var)
{
- uint i;
- ssize_t rc;
- size_t msg_len;
- int echo_fd = -1;
- char tx_buf [msgsz];
- char rx0_buf[msgsz];
- char rx1_buf[msgsz];
- struct iovec iovs[2]= {{rx0_buf, 0}, {rx1_buf, 0}};
+ uint i;
+ ssize_t rc;
+ size_t msg_len;
+ int echo_fd = -1;
+ char tx_buf[msgsz];
+ char rx0_buf[msgsz];
+ char rx1_buf[msgsz];
+ struct iovec iovs[2] = {{rx0_buf, 0}, {rx1_buf, 0}};
- if (!opt_silent) {
- printf("%s: repeat %u: msgsz %u: variable %s\n",
- __func__, repeat, msgsz, var ? "true" : "false");
- }
+ if (!opt_silent) {
+ printf("%s: repeat %u: msgsz %u: variable %s\n", __func__, repeat, msgsz,
+ var ? "true" : "false");
+ }
- echo_fd = tipc_connect(dev_name, echo_name);
- if (echo_fd < 0) {
- fprintf(stderr, "Failed to connect to service\n");
- return echo_fd;
- }
+ echo_fd = tipc_connect(dev_name, echo_name);
+ if (echo_fd < 0) {
+ fprintf(stderr, "Failed to connect to service\n");
+ return echo_fd;
+ }
- for (i = 0; i < repeat; i++) {
+ for (i = 0; i < repeat; i++) {
+ msg_len = msgsz;
+ if (opt_variable && msgsz) {
+ msg_len = rand() % msgsz;
+ }
- msg_len = msgsz;
- if (opt_variable && msgsz) {
- msg_len = rand() % msgsz;
- }
+ iovs[0].iov_len = msg_len / 3;
+ iovs[1].iov_len = msg_len - iovs[0].iov_len;
- iovs[0].iov_len = msg_len / 3;
- iovs[1].iov_len = msg_len - iovs[0].iov_len;
+ memset(tx_buf, i + 1, sizeof(tx_buf));
+ memset(rx0_buf, i + 2, iovs[0].iov_len);
+ memset(rx1_buf, i + 3, iovs[1].iov_len);
- memset(tx_buf, i + 1, sizeof(tx_buf));
- memset(rx0_buf, i + 2, iovs[0].iov_len);
- memset(rx1_buf, i + 3, iovs[1].iov_len);
+ rc = write(echo_fd, tx_buf, msg_len);
+ if (rc < 0) {
+ perror("readv_test: write");
+ break;
+ }
- rc = write(echo_fd, tx_buf, msg_len);
- if (rc < 0) {
- perror("readv_test: write");
- break;
- }
+ if ((size_t)rc != msg_len) {
+ fprintf(stderr, "%s: %s: data size mismatch (%zd vs. %zd)\n", __func__, "write",
+ (size_t)rc, msg_len);
+ break;
+ }
- if ((size_t)rc != msg_len) {
- fprintf(stderr,
- "%s: %s: data size mismatch (%zd vs. %zd)\n",
- __func__, "write", (size_t)rc, msg_len);
- break;
- }
+ rc = readv(echo_fd, iovs, 2);
+ if (rc < 0) {
+ perror("readv_test: readv");
+ break;
+ }
- rc = readv(echo_fd, iovs, 2);
- if (rc < 0) {
- perror("readv_test: readv");
- break;
- }
+ if ((size_t)rc != msg_len) {
+ fprintf(stderr, "%s: %s: data size mismatch (%zd vs. %zd)\n", __func__, "write",
+ (size_t)rc, msg_len);
+ break;
+ }
- if ((size_t)rc != msg_len) {
- fprintf(stderr,
- "%s: %s: data size mismatch (%zd vs. %zd)\n",
- __func__, "write", (size_t)rc, msg_len);
- break;
- }
+ if (memcmp(rx0_buf, tx_buf, iovs[0].iov_len)) {
+ fprintf(stderr, "%s: data mismatch: buf 0\n", __func__);
+ break;
+ }
- if (memcmp(rx0_buf, tx_buf, iovs[0].iov_len)) {
- fprintf(stderr, "%s: data mismatch: buf 0\n", __func__);
- break;
- }
+ if (memcmp(rx1_buf, tx_buf + iovs[0].iov_len, iovs[1].iov_len)) {
+ fprintf(stderr, "%s: data mismatch, buf 1\n", __func__);
+ break;
+ }
+ }
- if (memcmp(rx1_buf, tx_buf + iovs[0].iov_len, iovs[1].iov_len)) {
- fprintf(stderr, "%s: data mismatch, buf 1\n", __func__);
- break;
- }
- }
+ tipc_close(echo_fd);
- tipc_close(echo_fd);
+ if (!opt_silent) {
+ printf("%s: done\n", __func__);
+ }
- if (!opt_silent) {
- printf("%s: done\n",__func__);
- }
-
- return 0;
+ return 0;
}
static int send_fd_test(void) {
@@ -964,51 +944,51 @@
int main(int argc, char **argv)
{
- int rc = 0;
+ int rc = 0;
- if (argc <= 1) {
- print_usage_and_exit(argv[0], EXIT_FAILURE, false);
- }
+ if (argc <= 1) {
+ print_usage_and_exit(argv[0], EXIT_FAILURE, false);
+ }
- parse_options(argc, argv);
+ parse_options(argc, argv);
- if (!dev_name) {
- dev_name = TIPC_DEFAULT_DEVNAME;
- }
+ if (!dev_name) {
+ dev_name = TIPC_DEFAULT_DEVNAME;
+ }
- if (!test_name) {
- fprintf(stderr, "need a Test to run\n");
- print_usage_and_exit(argv[0], EXIT_FAILURE, true);
- }
+ if (!test_name) {
+ fprintf(stderr, "need a Test to run\n");
+ print_usage_and_exit(argv[0], EXIT_FAILURE, true);
+ }
- if (strcmp(test_name, "connect") == 0) {
- rc = connect_test(opt_repeat);
- } else if (strcmp(test_name, "connect_foo") == 0) {
- rc = connect_foo(opt_repeat);
- } else if (strcmp(test_name, "burst_write") == 0) {
- rc = burst_write_test(opt_repeat, opt_msgburst, opt_msgsize, opt_variable);
- } else if (strcmp(test_name, "select") == 0) {
- rc = select_test(opt_repeat, opt_msgburst, opt_msgsize);
- } else if (strcmp(test_name, "blocked_read") == 0) {
- rc = blocked_read_test(opt_repeat);
- } else if (strcmp(test_name, "closer1") == 0) {
- rc = closer1_test(opt_repeat);
- } else if (strcmp(test_name, "closer2") == 0) {
- rc = closer2_test(opt_repeat);
- } else if (strcmp(test_name, "closer3") == 0) {
- rc = closer3_test(opt_repeat);
- } else if (strcmp(test_name, "echo") == 0) {
- rc = echo_test(opt_repeat, opt_msgsize, opt_variable);
- } else if(strcmp(test_name, "ta2ta-ipc") == 0) {
- rc = ta2ta_ipc_test();
- } else if (strcmp(test_name, "dev-uuid") == 0) {
- rc = dev_uuid_test();
- } else if (strcmp(test_name, "ta-access") == 0) {
- rc = ta_access_test();
- } else if (strcmp(test_name, "writev") == 0) {
- rc = writev_test(opt_repeat, opt_msgsize, opt_variable);
- } else if (strcmp(test_name, "readv") == 0) {
- rc = readv_test(opt_repeat, opt_msgsize, opt_variable);
+ if (strcmp(test_name, "connect") == 0) {
+ rc = connect_test(opt_repeat);
+ } else if (strcmp(test_name, "connect_foo") == 0) {
+ rc = connect_foo(opt_repeat);
+ } else if (strcmp(test_name, "burst_write") == 0) {
+ rc = burst_write_test(opt_repeat, opt_msgburst, opt_msgsize, opt_variable);
+ } else if (strcmp(test_name, "select") == 0) {
+ rc = select_test(opt_repeat, opt_msgburst, opt_msgsize);
+ } else if (strcmp(test_name, "blocked_read") == 0) {
+ rc = blocked_read_test(opt_repeat);
+ } else if (strcmp(test_name, "closer1") == 0) {
+ rc = closer1_test(opt_repeat);
+ } else if (strcmp(test_name, "closer2") == 0) {
+ rc = closer2_test(opt_repeat);
+ } else if (strcmp(test_name, "closer3") == 0) {
+ rc = closer3_test(opt_repeat);
+ } else if (strcmp(test_name, "echo") == 0) {
+ rc = echo_test(opt_repeat, opt_msgsize, opt_variable);
+ } else if (strcmp(test_name, "ta2ta-ipc") == 0) {
+ rc = ta2ta_ipc_test();
+ } else if (strcmp(test_name, "dev-uuid") == 0) {
+ rc = dev_uuid_test();
+ } else if (strcmp(test_name, "ta-access") == 0) {
+ rc = ta_access_test();
+ } else if (strcmp(test_name, "writev") == 0) {
+ rc = writev_test(opt_repeat, opt_msgsize, opt_variable);
+ } else if (strcmp(test_name, "readv") == 0) {
+ rc = readv_test(opt_repeat, opt_msgsize, opt_variable);
} else if (strcmp(test_name, "send-fd") == 0) {
rc = send_fd_test();
} else {
diff --git a/trusty/storage/interface/include/trusty/interface/storage.h b/trusty/storage/interface/include/trusty/interface/storage.h
index b196d88..3f1dcb8 100644
--- a/trusty/storage/interface/include/trusty/interface/storage.h
+++ b/trusty/storage/interface/include/trusty/interface/storage.h
@@ -112,26 +112,30 @@
/**
* enum storage_msg_flag - protocol-level flags in struct storage_msg
- * @STORAGE_MSG_FLAG_BATCH: if set, command belongs to a batch transaction.
- * No response will be sent by the server until
- * it receives a command with this flag unset, at
- * which point a cummulative result for all messages
- * sent with STORAGE_MSG_FLAG_BATCH will be sent.
- * This is only supported by the non-secure disk proxy
- * server.
- * @STORAGE_MSG_FLAG_PRE_COMMIT: if set, indicates that server need to commit
- * pending changes before processing this message.
- * @STORAGE_MSG_FLAG_POST_COMMIT: if set, indicates that server need to commit
- * pending changes after processing this message.
- * @STORAGE_MSG_FLAG_TRANSACT_COMPLETE: if set, indicates that server need to commit
- * current transaction after processing this message.
- * It is an alias for STORAGE_MSG_FLAG_POST_COMMIT.
+ * @STORAGE_MSG_FLAG_BATCH: if set, command belongs to a batch transaction.
+ * No response will be sent by the server until
+ * it receives a command with this flag unset, at
+ * which point a cumulative result for all messages
+ * sent with STORAGE_MSG_FLAG_BATCH will be sent.
+ * This is only supported by the non-secure disk proxy
+ * server.
+ * @STORAGE_MSG_FLAG_PRE_COMMIT: if set, indicates that server need to commit
+ * pending changes before processing this message.
+ * @STORAGE_MSG_FLAG_POST_COMMIT: if set, indicates that server need to commit
+ * pending changes after processing this message.
+ * @STORAGE_MSG_FLAG_TRANSACT_COMPLETE: if set, indicates that server need to commit
+ * current transaction after processing this message.
+ * It is an alias for STORAGE_MSG_FLAG_POST_COMMIT.
+ * @STORAGE_MSG_FLAG_PRE_COMMIT_CHECKPOINT: if set, indicates that server needs to ensure
+ * that there is not a pending checkpoint for
+ * userdata before processing this message.
*/
enum storage_msg_flag {
- STORAGE_MSG_FLAG_BATCH = 0x1,
- STORAGE_MSG_FLAG_PRE_COMMIT = 0x2,
- STORAGE_MSG_FLAG_POST_COMMIT = 0x4,
- STORAGE_MSG_FLAG_TRANSACT_COMPLETE = STORAGE_MSG_FLAG_POST_COMMIT,
+ STORAGE_MSG_FLAG_BATCH = 0x1,
+ STORAGE_MSG_FLAG_PRE_COMMIT = 0x2,
+ STORAGE_MSG_FLAG_POST_COMMIT = 0x4,
+ STORAGE_MSG_FLAG_TRANSACT_COMPLETE = STORAGE_MSG_FLAG_POST_COMMIT,
+ STORAGE_MSG_FLAG_PRE_COMMIT_CHECKPOINT = 0x8,
};
/*
diff --git a/trusty/storage/proxy/Android.bp b/trusty/storage/proxy/Android.bp
index d67089f..38d8685 100644
--- a/trusty/storage/proxy/Android.bp
+++ b/trusty/storage/proxy/Android.bp
@@ -23,6 +23,7 @@
vendor: true,
srcs: [
+ "checkpoint_handling.cpp",
"ipc.c",
"rpmb.c",
"storage.c",
@@ -30,12 +31,14 @@
],
shared_libs: [
+ "libbase",
"liblog",
"libhardware_legacy",
],
header_libs: ["libcutils_headers"],
static_libs: [
+ "libfstab",
"libtrustystorageinterface",
"libtrusty",
],
diff --git a/trusty/storage/proxy/checkpoint_handling.cpp b/trusty/storage/proxy/checkpoint_handling.cpp
new file mode 100644
index 0000000..6c2fd36
--- /dev/null
+++ b/trusty/storage/proxy/checkpoint_handling.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "checkpoint_handling.h"
+#include "log.h"
+
+#include <fstab/fstab.h>
+#include <cstring>
+#include <string>
+
+namespace {
+
+bool checkpointingDoneForever = false;
+
+} // namespace
+
+int is_data_checkpoint_active(bool* active) {
+ if (!active) {
+ ALOGE("active out parameter is null");
+ return 0;
+ }
+
+ *active = false;
+
+ if (checkpointingDoneForever) {
+ return 0;
+ }
+
+ android::fs_mgr::Fstab procMounts;
+ bool success = android::fs_mgr::ReadFstabFromFile("/proc/mounts", &procMounts);
+ if (!success) {
+ ALOGE("Could not parse /proc/mounts\n");
+ /* Really bad. Tell the caller to abort the write. */
+ return -1;
+ }
+
+ android::fs_mgr::FstabEntry* dataEntry =
+ android::fs_mgr::GetEntryForMountPoint(&procMounts, "/data");
+ if (dataEntry == NULL) {
+ ALOGE("/data is not mounted yet\n");
+ return 0;
+ }
+
+ /* We can't handle e.g., ext4. Nothing we can do about it for now. */
+ if (dataEntry->fs_type != "f2fs") {
+ ALOGW("Checkpoint status not supported for filesystem %s\n", dataEntry->fs_type.c_str());
+ checkpointingDoneForever = true;
+ return 0;
+ }
+
+ /*
+ * The data entry looks like "... blah,checkpoint=disable:0,blah ...".
+ * checkpoint=disable means checkpointing is on (yes, arguably reversed).
+ */
+ size_t checkpointPos = dataEntry->fs_options.find("checkpoint=disable");
+ if (checkpointPos == std::string::npos) {
+ /* Assumption is that once checkpointing turns off, it stays off */
+ checkpointingDoneForever = true;
+ } else {
+ *active = true;
+ }
+
+ return 0;
+}
diff --git a/trusty/storage/proxy/checkpoint_handling.h b/trusty/storage/proxy/checkpoint_handling.h
new file mode 100644
index 0000000..f1bf27c
--- /dev/null
+++ b/trusty/storage/proxy/checkpoint_handling.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * is_data_checkpoint_active() - Check for an active, uncommitted checkpoint of
+ * /data. If a checkpoint is active, storage should not commit any
+ * rollback-protected writes to /data.
+ * @active: Out parameter that will be set to the result of the check.
+ *
+ * Return: 0 if active was set and is valid, non-zero otherwise.
+ */
+int is_data_checkpoint_active(bool* active);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/trusty/storage/proxy/proxy.c b/trusty/storage/proxy/proxy.c
index e230941..c690a28 100644
--- a/trusty/storage/proxy/proxy.c
+++ b/trusty/storage/proxy/proxy.c
@@ -26,6 +26,7 @@
#include <cutils/android_filesystem_config.h>
+#include "checkpoint_handling.h"
#include "ipc.h"
#include "log.h"
#include "rpmb.h"
@@ -130,6 +131,21 @@
}
}
+ if (msg->flags & STORAGE_MSG_FLAG_PRE_COMMIT_CHECKPOINT) {
+ bool is_checkpoint_active = false;
+
+ rc = is_data_checkpoint_active(&is_checkpoint_active);
+ if (rc != 0) {
+ ALOGE("is_data_checkpoint_active failed in an unexpected way. Aborting.\n");
+ msg->result = STORAGE_ERR_GENERIC;
+ return ipc_respond(msg, NULL, 0);
+ } else if (is_checkpoint_active) {
+ ALOGE("Checkpoint in progress, dropping write ...\n");
+ msg->result = STORAGE_ERR_GENERIC;
+ return ipc_respond(msg, NULL, 0);
+ }
+ }
+
switch (msg->cmd) {
case STORAGE_FILE_DELETE:
rc = storage_file_delete(msg, req, req_len);
diff --git a/trusty/storage/proxy/rpmb.c b/trusty/storage/proxy/rpmb.c
index b59fb67..f059935 100644
--- a/trusty/storage/proxy/rpmb.c
+++ b/trusty/storage/proxy/rpmb.c
@@ -16,7 +16,10 @@
#include <errno.h>
#include <fcntl.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_proto.h>
#include <scsi/sg.h>
+#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -55,6 +58,17 @@
#define MMC_BLOCK_SIZE 512
/*
+ * Number of retry attempts when an RPMB authenticated write triggers a UNIT
+ * ATTENTION
+ */
+#define UFS_RPMB_WRITE_RETRY_COUNT 1
+/*
+ * Number of retry attempts when an RPMB read operation triggers a UNIT
+ * ATTENTION
+ */
+#define UFS_RPMB_READ_RETRY_COUNT 3
+
+/*
* There should be no timeout for security protocol ioctl call, so we choose a
* large number for timeout.
* 20000 millisecs == 20 seconds
@@ -104,21 +118,62 @@
static const char* UFS_WAKE_LOCK_NAME = "ufs_seq_wakelock";
-#ifdef RPMB_DEBUG
-
-static void print_buf(const char* prefix, const uint8_t* buf, size_t size) {
+/**
+ * log_buf - Log a byte buffer to the android log.
+ * @priority: One of ANDROID_LOG_* priority levels from android_LogPriority in
+ * android/log.h
+ * @prefix: A null-terminated string that identifies this buffer. Must be less
+ * than 128 bytes.
+ * @buf: Buffer to dump.
+ * @size: Length of @buf in bytes.
+ */
+#define LOG_BUF_SIZE 256
+static int log_buf(int priority, const char* prefix, const uint8_t* buf, size_t size) {
+ int rc;
size_t i;
+ char line[LOG_BUF_SIZE] = {0};
+ char* cur = line;
- printf("%s @%p [%zu]", prefix, buf, size);
- for (i = 0; i < size; i++) {
- if (i && i % 32 == 0) printf("\n%*s", (int)strlen(prefix), "");
- printf(" %02x", buf[i]);
+ rc = snprintf(line, LOG_BUF_SIZE, "%s @%p [%zu]", prefix, buf, size);
+ if (rc < 0 || rc >= LOG_BUF_SIZE) {
+ goto err;
}
- printf("\n");
- fflush(stdout);
-}
+ cur += rc;
+ for (i = 0; i < size; i++) {
+ if (i % 32 == 0) {
+ /*
+ * Flush the line out to the log after we have printed 32 bytes
+ * (also flushes the header line on the first iteration and sets up
+ * for printing the buffer itself)
+ */
+ LOG_PRI(priority, LOG_TAG, "%s", line);
+ memset(line, 0, LOG_BUF_SIZE);
+ cur = line;
+ /* Shift output over by the length of the prefix */
+ rc = snprintf(line, LOG_BUF_SIZE, "%*s", (int)strlen(prefix), "");
+ if (rc < 0 || rc >= LOG_BUF_SIZE) {
+ goto err;
+ }
+ cur += rc;
+ }
+ rc = snprintf(cur, LOG_BUF_SIZE - (cur - line), "%02x ", buf[i]);
+ if (rc < 0 || rc >= LOG_BUF_SIZE - (cur - line)) {
+ goto err;
+ }
+ cur += rc;
+ }
+ LOG_PRI(priority, LOG_TAG, "%s", line);
-#endif
+ return 0;
+
+err:
+ if (rc < 0) {
+ return rc;
+ } else {
+ ALOGE("log_buf prefix was too long");
+ return -1;
+ }
+}
static void set_sg_io_hdr(sg_io_hdr_t* io_hdrp, int dxfer_direction, unsigned char cmd_len,
unsigned char mx_sb_len, unsigned int dxfer_len, void* dxferp,
@@ -135,6 +190,137 @@
io_hdrp->timeout = TIMEOUT;
}
+/**
+ * enum scsi_result - Results of checking the SCSI status and sense buffer
+ *
+ * @SCSI_RES_OK: SCSI status and sense are good
+ * @SCSI_RES_ERR: SCSI status or sense contain an unhandled error
+ * @SCSI_RES_RETRY: SCSI sense buffer contains a status that indicates that the
+ * command should be retried
+ */
+enum scsi_result {
+ SCSI_RES_OK = 0,
+ SCSI_RES_ERR,
+ SCSI_RES_RETRY,
+};
+
+static enum scsi_result check_scsi_sense(const uint8_t* sense_buf, size_t len) {
+ uint8_t response_code = 0;
+ uint8_t sense_key = 0;
+ uint8_t additional_sense_code = 0;
+ uint8_t additional_sense_code_qualifier = 0;
+ uint8_t additional_length = 0;
+
+ if (!sense_buf || len == 0) {
+ ALOGE("Invalid SCSI sense buffer, length: %zu\n", len);
+ return SCSI_RES_ERR;
+ }
+
+ response_code = 0x7f & sense_buf[0];
+
+ if (response_code < 0x70 || response_code > 0x73) {
+ ALOGE("Invalid SCSI sense response code: %hhu\n", response_code);
+ return SCSI_RES_ERR;
+ }
+
+ if (response_code >= 0x72) {
+ /* descriptor format, SPC-6 4.4.2 */
+ if (len > 1) {
+ sense_key = 0xf & sense_buf[1];
+ }
+ if (len > 2) {
+ additional_sense_code = sense_buf[2];
+ }
+ if (len > 3) {
+ additional_sense_code_qualifier = sense_buf[3];
+ }
+ if (len > 7) {
+ additional_length = sense_buf[7];
+ }
+ } else {
+ /* fixed format, SPC-6 4.4.3 */
+ if (len > 2) {
+ sense_key = 0xf & sense_buf[2];
+ }
+ if (len > 7) {
+ additional_length = sense_buf[7];
+ }
+ if (len > 12) {
+ additional_sense_code = sense_buf[12];
+ }
+ if (len > 13) {
+ additional_sense_code_qualifier = sense_buf[13];
+ }
+ }
+
+ switch (sense_key) {
+ case NO_SENSE:
+ case 0x0f: /* COMPLETED, not present in kernel headers */
+ ALOGD("SCSI success with sense data: key=%hhu, asc=%hhu, ascq=%hhu\n", sense_key,
+ additional_sense_code, additional_sense_code_qualifier);
+ return SCSI_RES_OK;
+ case UNIT_ATTENTION:
+ ALOGD("UNIT ATTENTION with sense data: key=%hhu, asc=%hhu, ascq=%hhu\n", sense_key,
+ additional_sense_code, additional_sense_code_qualifier);
+ if (additional_sense_code == 0x29) {
+ /* POWER ON or RESET condition */
+ return SCSI_RES_RETRY;
+ }
+
+ /* treat this UNIT ATTENTION as an error if we don't recognize it */
+ break;
+ }
+
+ ALOGE("Unexpected SCSI sense data: key=%hhu, asc=%hhu, ascq=%hhu\n", sense_key,
+ additional_sense_code, additional_sense_code_qualifier);
+ log_buf(ANDROID_LOG_ERROR, "sense buffer: ", sense_buf, len);
+ return SCSI_RES_ERR;
+}
+
+static enum scsi_result check_sg_io_hdr(const sg_io_hdr_t* io_hdrp) {
+ if (io_hdrp->status == 0 && io_hdrp->host_status == 0 && io_hdrp->driver_status == 0) {
+ return SCSI_RES_OK;
+ }
+
+ if (io_hdrp->status & 0x01) {
+ ALOGE("SG_IO received unknown status, LSB is set: %hhu", io_hdrp->status);
+ }
+
+ if (io_hdrp->masked_status != GOOD && io_hdrp->sb_len_wr > 0) {
+ enum scsi_result scsi_res = check_scsi_sense(io_hdrp->sbp, io_hdrp->sb_len_wr);
+ if (scsi_res == SCSI_RES_RETRY) {
+ return SCSI_RES_RETRY;
+ } else if (scsi_res != SCSI_RES_OK) {
+ ALOGE("Unexpected SCSI sense. masked_status: %hhu, host_status: %hu, driver_status: "
+ "%hu\n",
+ io_hdrp->masked_status, io_hdrp->host_status, io_hdrp->driver_status);
+ return scsi_res;
+ }
+ }
+
+ switch (io_hdrp->masked_status) {
+ case GOOD:
+ break;
+ case CHECK_CONDITION:
+ /* handled by check_sg_sense above */
+ break;
+ default:
+ ALOGE("SG_IO failed with masked_status: %hhu, host_status: %hu, driver_status: %hu\n",
+ io_hdrp->masked_status, io_hdrp->host_status, io_hdrp->driver_status);
+ return SCSI_RES_ERR;
+ }
+
+ if (io_hdrp->host_status != 0) {
+ ALOGE("SG_IO failed with host_status: %hu, driver_status: %hu\n", io_hdrp->host_status,
+ io_hdrp->driver_status);
+ }
+
+ if (io_hdrp->resid != 0) {
+ ALOGE("SG_IO resid was non-zero: %d\n", io_hdrp->resid);
+ }
+ return SCSI_RES_ERR;
+}
+
static int send_mmc_rpmb_req(int mmc_fd, const struct storage_rpmb_send_req* req) {
struct {
struct mmc_ioc_multi_cmd multi;
@@ -153,7 +339,7 @@
mmc_ioc_cmd_set_data((*cmd), write_buf);
#ifdef RPMB_DEBUG
ALOGI("opcode: 0x%x, write_flag: 0x%x\n", cmd->opcode, cmd->write_flag);
- print_buf("request: ", write_buf, req->reliable_write_size);
+ log_buf(ANDROID_LOG_INFO, "request: ", write_buf, req->reliable_write_size);
#endif
write_buf += req->reliable_write_size;
mmc.multi.num_of_cmds++;
@@ -169,7 +355,7 @@
mmc_ioc_cmd_set_data((*cmd), write_buf);
#ifdef RPMB_DEBUG
ALOGI("opcode: 0x%x, write_flag: 0x%x\n", cmd->opcode, cmd->write_flag);
- print_buf("request: ", write_buf, req->write_size);
+ log_buf(ANDROID_LOG_INFO, "request: ", write_buf, req->write_size);
#endif
write_buf += req->write_size;
mmc.multi.num_of_cmds++;
@@ -207,6 +393,8 @@
struct sec_proto_cdb out_cdb = {0xB5, 0xEC, 0x00, 0x01, 0x00, 0x00, 0, 0x00, 0x00};
unsigned char sense_buffer[32];
+ bool is_request_write = req->reliable_write_size > 0;
+
wl_rc = acquire_wake_lock(PARTIAL_WAKE_LOCK, UFS_WAKE_LOCK_NAME);
if (wl_rc < 0) {
ALOGE("%s: failed to acquire wakelock: %d, %s\n", __func__, wl_rc, strerror(errno));
@@ -215,30 +403,44 @@
if (req->reliable_write_size) {
/* Prepare SECURITY PROTOCOL OUT command. */
- out_cdb.length = __builtin_bswap32(req->reliable_write_size);
sg_io_hdr_t io_hdr;
- set_sg_io_hdr(&io_hdr, SG_DXFER_TO_DEV, sizeof(out_cdb), sizeof(sense_buffer),
- req->reliable_write_size, (void*)write_buf, (unsigned char*)&out_cdb,
- sense_buffer);
- rc = ioctl(sg_fd, SG_IO, &io_hdr);
- if (rc < 0) {
- ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
- goto err_op;
- }
+ int retry_count = UFS_RPMB_WRITE_RETRY_COUNT;
+ do {
+ out_cdb.length = __builtin_bswap32(req->reliable_write_size);
+ set_sg_io_hdr(&io_hdr, SG_DXFER_TO_DEV, sizeof(out_cdb), sizeof(sense_buffer),
+ req->reliable_write_size, (void*)write_buf, (unsigned char*)&out_cdb,
+ sense_buffer);
+ rc = ioctl(sg_fd, SG_IO, &io_hdr);
+ if (rc < 0) {
+ ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
+ goto err_op;
+ }
+ } while (check_sg_io_hdr(&io_hdr) == SCSI_RES_RETRY && retry_count-- > 0);
write_buf += req->reliable_write_size;
}
if (req->write_size) {
/* Prepare SECURITY PROTOCOL OUT command. */
- out_cdb.length = __builtin_bswap32(req->write_size);
sg_io_hdr_t io_hdr;
- set_sg_io_hdr(&io_hdr, SG_DXFER_TO_DEV, sizeof(out_cdb), sizeof(sense_buffer),
- req->write_size, (void*)write_buf, (unsigned char*)&out_cdb, sense_buffer);
- rc = ioctl(sg_fd, SG_IO, &io_hdr);
- if (rc < 0) {
- ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
- goto err_op;
- }
+ /*
+ * We don't retry write response request messages (is_request_write ==
+ * true) because a unit attention condition between the write and
+ * requesting a response means that the device was reset and we can't
+ * get a response to our original write. We can only retry this SG_IO
+ * call when it is the first call in our sequence.
+ */
+ int retry_count = is_request_write ? 0 : UFS_RPMB_READ_RETRY_COUNT;
+ do {
+ out_cdb.length = __builtin_bswap32(req->write_size);
+ set_sg_io_hdr(&io_hdr, SG_DXFER_TO_DEV, sizeof(out_cdb), sizeof(sense_buffer),
+ req->write_size, (void*)write_buf, (unsigned char*)&out_cdb,
+ sense_buffer);
+ rc = ioctl(sg_fd, SG_IO, &io_hdr);
+ if (rc < 0) {
+ ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
+ goto err_op;
+ }
+ } while (check_sg_io_hdr(&io_hdr) == SCSI_RES_RETRY && retry_count-- > 0);
write_buf += req->write_size;
}
@@ -252,6 +454,7 @@
if (rc < 0) {
ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
}
+ check_sg_io_hdr(&io_hdr);
}
err_op:
@@ -353,7 +556,7 @@
goto err_response;
}
#ifdef RPMB_DEBUG
- if (req->read_size) print_buf("response: ", read_buf, req->read_size);
+ if (req->read_size) log_buf(ANDROID_LOG_INFO, "response: ", read_buf, req->read_size);
#endif
if (msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) {
diff --git a/trusty/trusty-base.mk b/trusty/trusty-base.mk
index 6cd381f..21ea7ae 100644
--- a/trusty/trusty-base.mk
+++ b/trusty/trusty-base.mk
@@ -23,7 +23,7 @@
# HAL loading of gatekeeper.trusty.
PRODUCT_PACKAGES += \
- android.hardware.keymaster@4.0-service.trusty \
+ android.hardware.security.keymint-service.trusty \
android.hardware.gatekeeper@1.0-service.trusty \
trusty_apploader