Merge "Test that out-of-bounds UAF is not detected with MTE."
diff --git a/bootstat/Android.bp b/bootstat/Android.bp
index 545a06f..ca59ef3 100644
--- a/bootstat/Android.bp
+++ b/bootstat/Android.bp
@@ -35,7 +35,7 @@
"libcutils",
"liblog",
],
- static_libs: ["libgtest_prod"],
+ header_libs: ["libgtest_prod_headers"],
}
// bootstat static library
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 7b6f6c0..198e4de 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -276,6 +276,12 @@
],
}
+cc_test_library {
+ name: "libcrash_test",
+ defaults: ["debuggerd_defaults"],
+ srcs: ["crash_test.cpp"],
+}
+
cc_test {
name: "debuggerd_test",
defaults: ["debuggerd_defaults"],
@@ -341,6 +347,10 @@
},
},
+ data: [
+ ":libcrash_test",
+ ],
+
test_suites: ["device-tests"],
}
diff --git a/debuggerd/crash_test.cpp b/debuggerd/crash_test.cpp
new file mode 100644
index 0000000..c15145f
--- /dev/null
+++ b/debuggerd/crash_test.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+
+extern "C" void crash() {
+ *reinterpret_cast<volatile char*>(0xdead) = '1';
+}
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 193bf6b..5b04111 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -15,6 +15,7 @@
*/
#include <dirent.h>
+#include <dlfcn.h>
#include <err.h>
#include <fcntl.h>
#include <malloc.h>
@@ -30,6 +31,7 @@
#include <chrono>
#include <regex>
+#include <string>
#include <thread>
#include <android/fdsan.h>
@@ -274,7 +276,7 @@
}
if (signo == 0) {
- ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_TRUE(WIFEXITED(status)) << "Terminated due to unexpected signal " << WTERMSIG(status);
ASSERT_EQ(0, WEXITSTATUS(signo));
} else {
ASSERT_FALSE(WIFEXITED(status));
@@ -1542,6 +1544,60 @@
ASSERT_MATCH(result, R"(Cause: stack pointer[^\n]*stack overflow.\n)");
}
+static bool CopySharedLibrary(const char* tmp_dir, std::string* tmp_so_name) {
+ std::string test_lib(testing::internal::GetArgvs()[0]);
+ auto const value = test_lib.find_last_of('/');
+ if (value == std::string::npos) {
+ test_lib = "./";
+ } else {
+ test_lib = test_lib.substr(0, value + 1) + "./";
+ }
+ test_lib += "libcrash_test.so";
+
+ *tmp_so_name = std::string(tmp_dir) + "/libcrash_test.so";
+ std::string cp_cmd = android::base::StringPrintf("cp %s %s", test_lib.c_str(), tmp_dir);
+
+ // Copy the shared so to a tempory directory.
+ return system(cp_cmd.c_str()) == 0;
+}
+
+TEST_F(CrasherTest, unreadable_elf) {
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([]() {
+ TemporaryDir td;
+ std::string tmp_so_name;
+ if (!CopySharedLibrary(td.path, &tmp_so_name)) {
+ _exit(1);
+ }
+ void* handle = dlopen(tmp_so_name.c_str(), RTLD_NOW);
+ if (handle == nullptr) {
+ _exit(1);
+ }
+ // Delete the original shared library so that we get the warning
+ // about unreadable elf files.
+ if (unlink(tmp_so_name.c_str()) == -1) {
+ _exit(1);
+ }
+ void (*crash_func)() = reinterpret_cast<void (*)()>(dlsym(handle, "crash"));
+ if (crash_func == nullptr) {
+ _exit(1);
+ }
+ crash_func();
+ });
+
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+ FinishIntercept(&intercept_result);
+
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+ ASSERT_MATCH(result, R"(NOTE: Function names and BuildId information is missing )");
+}
+
TEST(tombstoned, proto) {
const pid_t self = getpid();
unique_fd tombstoned_socket, text_fd, proto_fd;
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index 7657001..d4a35b3 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -395,6 +395,15 @@
unwinder->LastErrorAddress());
}
} else {
+ if (unwinder->elf_from_memory_not_file()) {
+ auto backtrace_note = thread.mutable_backtrace_note();
+ *backtrace_note->Add() =
+ "Function names and BuildId information is missing for some frames due";
+ *backtrace_note->Add() =
+ "to unreadable libraries. For unwinds of apps, only shared libraries";
+ *backtrace_note->Add() = "found under the lib/ directory are readable.";
+ *backtrace_note->Add() = "On this device, run setenforce 0 to make the libraries readable.";
+ }
unwinder->SetDisplayBuildID(true);
for (const auto& frame : unwinder->frames()) {
BacktraceFrame* f = thread.add_current_backtrace();
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index 020b0a5..b780b22 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -171,6 +171,10 @@
const Thread& thread, bool should_log) {
CBS("");
CB(should_log, "backtrace:");
+ if (!thread.backtrace_note().empty()) {
+ CB(should_log, " NOTE: %s",
+ android::base::Join(thread.backtrace_note(), "\n NOTE: ").c_str());
+ }
print_backtrace(callback, tombstone, thread.current_backtrace(), should_log);
}
diff --git a/debuggerd/proto/tombstone.proto b/debuggerd/proto/tombstone.proto
index 294e4e5..22fc30e 100644
--- a/debuggerd/proto/tombstone.proto
+++ b/debuggerd/proto/tombstone.proto
@@ -119,11 +119,12 @@
int32 id = 1;
string name = 2;
repeated Register registers = 3;
+ repeated string backtrace_note = 7;
repeated BacktraceFrame current_backtrace = 4;
repeated MemoryDump memory_dump = 5;
int64 tagged_addr_ctrl = 6;
- reserved 7 to 999;
+ reserved 8 to 999;
}
message BacktraceFrame {
diff --git a/debuggerd/seccomp_policy/crash_dump.arm64.policy b/debuggerd/seccomp_policy/crash_dump.arm64.policy
index 1585cc6..21887ab 100644
--- a/debuggerd/seccomp_policy/crash_dump.arm64.policy
+++ b/debuggerd/seccomp_policy/crash_dump.arm64.policy
@@ -24,7 +24,7 @@
rt_sigprocmask: 1
rt_sigaction: 1
rt_tgsigqueueinfo: 1
-prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41
+prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 || arg0 == PR_PAC_RESET_KEYS
madvise: 1
mprotect: arg2 in 0x1|0x2
munmap: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.policy.def b/debuggerd/seccomp_policy/crash_dump.policy.def
index cd5aad4..90843fc 100644
--- a/debuggerd/seccomp_policy/crash_dump.policy.def
+++ b/debuggerd/seccomp_policy/crash_dump.policy.def
@@ -34,7 +34,12 @@
rt_tgsigqueueinfo: 1
#define PR_SET_VMA 0x53564d41
+#if defined(__aarch64__)
+// PR_PAC_RESET_KEYS happens on aarch64 in pthread_create path.
+prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA || arg0 == PR_PAC_RESET_KEYS
+#else
prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA
+#endif
#if 0
libminijail on vendor partitions older than P does not have constants from <sys/mman.h>.
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index ce702a0..2c70778 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -184,7 +184,6 @@
static_libs: [
"libc++fs",
- "libgtest_prod",
"libhealthhalutils",
"libsnapshot_cow",
"libsnapshot_nobinder",
@@ -193,6 +192,7 @@
header_libs: [
"avb_headers",
+ "libgtest_prod_headers",
"libsnapshot_headers",
"libstorage_literals_headers",
],
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index a349408..84709b6 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -17,6 +17,9 @@
},
{
"name": "libsnapshot_fuzzer_test"
+ },
+ {
+ "name": "cow_api_test"
}
]
}
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index bbbb7e8..ea9d333 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -264,12 +264,12 @@
F2FS_FSCK_BIN, "-f", "-c", "10000", "--debug-cache", blk_device.c_str()};
if (should_force_check(*fs_stat)) {
- LINFO << "Running " << F2FS_FSCK_BIN << " -f -c 10000 --debug-cache"
+ LINFO << "Running " << F2FS_FSCK_BIN << " -f -c 10000 --debug-cache "
<< realpath(blk_device);
ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_forced_argv), f2fs_fsck_forced_argv,
&status, false, LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE);
} else {
- LINFO << "Running " << F2FS_FSCK_BIN << " -a -c 10000 --debug-cache"
+ LINFO << "Running " << F2FS_FSCK_BIN << " -a -c 10000 --debug-cache "
<< realpath(blk_device);
ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_argv), f2fs_fsck_argv, &status, false,
LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE);
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 42bf356..853b24d 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -298,6 +298,8 @@
if (!ParseByteCount(arg, &entry->zram_backingdev_size)) {
LWARNING << "Warning: zram_backingdev_size= flag malformed: " << arg;
}
+ } else if (StartsWith(flag, "lowerdir=")) {
+ entry->lowerdir = arg;
} else {
LWARNING << "Warning: unknown flag: " << flag;
}
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 1134f14..9a94d79 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -92,6 +92,10 @@
return false;
}
+bool fs_mgr_overlayfs_mount_fstab_entry(const std::string&, const std::string&) {
+ return false;
+}
+
std::vector<std::string> fs_mgr_overlayfs_required_devices(Fstab*) {
return {};
}
@@ -116,6 +120,8 @@
void MapScratchPartitionIfNeeded(Fstab*, const std::function<bool(const std::set<std::string>&)>&) {
}
+void CleanupOldScratchFiles() {}
+
void TeardownAllOverlayForMountPoint(const std::string&) {}
} // namespace fs_mgr
@@ -1293,6 +1299,18 @@
}
}
+bool fs_mgr_overlayfs_mount_fstab_entry(const std::string& lowers,
+ const std::string& mount_point) {
+ if (fs_mgr_overlayfs_invalid()) return false;
+
+ std::string aux = "lowerdir=" + lowers + ",override_creds=off";
+ auto rc = mount("overlay", mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME, aux.c_str());
+
+ if (rc == 0) return true;
+
+ return false;
+}
+
bool fs_mgr_overlayfs_mount_all(Fstab* fstab) {
auto ret = false;
if (fs_mgr_overlayfs_invalid()) return ret;
diff --git a/fs_mgr/include/fs_mgr_overlayfs.h b/fs_mgr/include/fs_mgr_overlayfs.h
index d45e2de..ac95ef5 100644
--- a/fs_mgr/include/fs_mgr_overlayfs.h
+++ b/fs_mgr/include/fs_mgr_overlayfs.h
@@ -27,6 +27,7 @@
android::fs_mgr::Fstab fs_mgr_overlayfs_candidate_list(const android::fs_mgr::Fstab& fstab);
bool fs_mgr_overlayfs_mount_all(android::fs_mgr::Fstab* fstab);
+bool fs_mgr_overlayfs_mount_fstab_entry (const std::string& lowers, const std::string& mount_point);
std::vector<std::string> fs_mgr_overlayfs_required_devices(android::fs_mgr::Fstab* fstab);
bool fs_mgr_overlayfs_setup(const char* backing = nullptr, const char* mount_point = nullptr,
bool* change = nullptr, bool force = true);
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index 2704e47..f33768b 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -55,6 +55,7 @@
std::string vbmeta_partition;
uint64_t zram_backingdev_size = 0;
std::string avb_keys;
+ std::string lowerdir;
struct FsMgrFlags {
bool wait : 1;
diff --git a/fs_mgr/libdm/dm_target.cpp b/fs_mgr/libdm/dm_target.cpp
index ef46eb9..b0639e6 100644
--- a/fs_mgr/libdm/dm_target.cpp
+++ b/fs_mgr/libdm/dm_target.cpp
@@ -95,7 +95,9 @@
}
void DmTargetVerity::SetVerityMode(const std::string& mode) {
- if (mode != "restart_on_corruption" && mode != "ignore_corruption") {
+ if (mode != "panic_on_corruption" &&
+ mode != "restart_on_corruption" &&
+ mode != "ignore_corruption") {
LOG(ERROR) << "Unknown verity mode: " << mode;
valid_ = false;
return;
diff --git a/fs_mgr/libfiemap/binder.cpp b/fs_mgr/libfiemap/binder.cpp
index c8516ab..31a57a8 100644
--- a/fs_mgr/libfiemap/binder.cpp
+++ b/fs_mgr/libfiemap/binder.cpp
@@ -224,8 +224,9 @@
return false;
}
-std::unique_ptr<IImageManager> IImageManager::Open(
- const std::string& dir, const std::chrono::milliseconds& /*timeout_ms*/) {
+std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir,
+ const std::chrono::milliseconds& /*timeout_ms*/,
+ const DeviceInfo&) {
android::sp<IGsiService> service = android::gsi::GetGsiService();
android::sp<IImageService> manager;
diff --git a/fs_mgr/libfiemap/image_manager.cpp b/fs_mgr/libfiemap/image_manager.cpp
index 44f659b..dcbbc54 100644
--- a/fs_mgr/libfiemap/image_manager.cpp
+++ b/fs_mgr/libfiemap/image_manager.cpp
@@ -55,7 +55,8 @@
static constexpr char kTestImageMetadataDir[] = "/metadata/gsi/test";
static constexpr char kOtaTestImageMetadataDir[] = "/metadata/gsi/ota/test";
-std::unique_ptr<ImageManager> ImageManager::Open(const std::string& dir_prefix) {
+std::unique_ptr<ImageManager> ImageManager::Open(const std::string& dir_prefix,
+ const DeviceInfo& device_info) {
auto metadata_dir = "/metadata/gsi/" + dir_prefix;
auto data_dir = "/data/gsi/" + dir_prefix;
auto install_dir_file = gsi::DsuInstallDirFile(gsi::GetDsuSlot(dir_prefix));
@@ -63,17 +64,28 @@
if (ReadFileToString(install_dir_file, &path)) {
data_dir = path;
}
- return Open(metadata_dir, data_dir);
+ return Open(metadata_dir, data_dir, device_info);
}
std::unique_ptr<ImageManager> ImageManager::Open(const std::string& metadata_dir,
- const std::string& data_dir) {
- return std::unique_ptr<ImageManager>(new ImageManager(metadata_dir, data_dir));
+ const std::string& data_dir,
+ const DeviceInfo& device_info) {
+ return std::unique_ptr<ImageManager>(new ImageManager(metadata_dir, data_dir, device_info));
}
-ImageManager::ImageManager(const std::string& metadata_dir, const std::string& data_dir)
- : metadata_dir_(metadata_dir), data_dir_(data_dir) {
+ImageManager::ImageManager(const std::string& metadata_dir, const std::string& data_dir,
+ const DeviceInfo& device_info)
+ : metadata_dir_(metadata_dir), data_dir_(data_dir), device_info_(device_info) {
partition_opener_ = std::make_unique<android::fs_mgr::PartitionOpener>();
+
+ // Allow overriding whether ImageManager thinks it's in recovery, for testing.
+#ifdef __ANDROID_RECOVERY__
+ device_info_.is_recovery = {true};
+#else
+ if (!device_info_.is_recovery.has_value()) {
+ device_info_.is_recovery = {false};
+ }
+#endif
}
std::string ImageManager::GetImageHeaderPath(const std::string& name) {
@@ -261,10 +273,11 @@
return false;
}
-#if defined __ANDROID_RECOVERY__
- LOG(ERROR) << "Cannot remove images backed by /data in recovery";
- return false;
-#else
+ if (device_info_.is_recovery.value()) {
+ LOG(ERROR) << "Cannot remove images backed by /data in recovery";
+ return false;
+ }
+
std::string message;
auto header_file = GetImageHeaderPath(name);
if (!SplitFiemap::RemoveSplitFiles(header_file, &message)) {
@@ -278,7 +291,6 @@
LOG(ERROR) << "Error removing " << status_file << ": " << message;
}
return RemoveImageMetadata(metadata_dir_, name);
-#endif
}
// Create a block device for an image file, using its extents in its
@@ -521,6 +533,9 @@
// filesystem. This should only happen on devices with no encryption, or
// devices with FBE and no metadata encryption. For these cases it suffices
// to perform normal file writes to /data/gsi (which is unencrypted).
+ //
+ // Note: this is not gated on DeviceInfo, because the recovery-specific path
+ // must only be used in actual recovery.
std::string block_device;
bool can_use_devicemapper;
if (!FiemapWriter::GetBlockDeviceForFile(image_header, &block_device, &can_use_devicemapper)) {
diff --git a/fs_mgr/libfiemap/include/libfiemap/image_manager.h b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
index 7e30509..3c87000 100644
--- a/fs_mgr/libfiemap/include/libfiemap/image_manager.h
+++ b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
@@ -21,6 +21,7 @@
#include <chrono>
#include <functional>
#include <memory>
+#include <optional>
#include <set>
#include <string>
@@ -37,11 +38,17 @@
virtual ~IImageManager() {}
+ // Helper for dependency injection.
+ struct DeviceInfo {
+ std::optional<bool> is_recovery;
+ };
+
// When linking to libfiemap_binder, the Open() call will use binder.
// Otherwise, the Open() call will use the ImageManager implementation
- // below.
+ // below. In binder mode, device_info is ignored.
static std::unique_ptr<IImageManager> Open(const std::string& dir_prefix,
- const std::chrono::milliseconds& timeout_ms);
+ const std::chrono::milliseconds& timeout_ms,
+ const DeviceInfo& device_info = {});
// Flags for CreateBackingImage().
static constexpr int CREATE_IMAGE_DEFAULT = 0x0;
@@ -131,11 +138,13 @@
// Return an ImageManager for the given metadata and data directories. Both
// directories must already exist.
static std::unique_ptr<ImageManager> Open(const std::string& metadata_dir,
- const std::string& data_dir);
+ const std::string& data_dir,
+ const DeviceInfo& device_info = {});
// Helper function that derives the metadata and data dirs given a single
// prefix.
- static std::unique_ptr<ImageManager> Open(const std::string& dir_prefix);
+ static std::unique_ptr<ImageManager> Open(const std::string& dir_prefix,
+ const DeviceInfo& device_info = {});
// Methods that must be implemented from IImageManager.
FiemapStatus CreateBackingImage(const std::string& name, uint64_t size, int flags,
@@ -166,7 +175,8 @@
FiemapStatus ZeroFillNewImage(const std::string& name, uint64_t bytes);
private:
- ImageManager(const std::string& metadata_dir, const std::string& data_dir);
+ ImageManager(const std::string& metadata_dir, const std::string& data_dir,
+ const DeviceInfo& device_info);
std::string GetImageHeaderPath(const std::string& name);
std::string GetStatusFilePath(const std::string& image_name);
bool MapWithLoopDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
@@ -187,6 +197,7 @@
std::string metadata_dir_;
std::string data_dir_;
std::unique_ptr<IPartitionOpener> partition_opener_;
+ DeviceInfo device_info_;
};
// RAII helper class for mapping and opening devices with an ImageManager.
diff --git a/fs_mgr/libfiemap/passthrough.cpp b/fs_mgr/libfiemap/passthrough.cpp
index 1ccd9a0..d521804 100644
--- a/fs_mgr/libfiemap/passthrough.cpp
+++ b/fs_mgr/libfiemap/passthrough.cpp
@@ -20,9 +20,10 @@
namespace fiemap {
std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir_prefix,
- const std::chrono::milliseconds& timeout_ms) {
+ const std::chrono::milliseconds& timeout_ms,
+ const DeviceInfo& device_info) {
(void)timeout_ms;
- return ImageManager::Open(dir_prefix);
+ return ImageManager::Open(dir_prefix, device_info);
}
} // namespace fiemap
diff --git a/fs_mgr/libfs_avb/Android.bp b/fs_mgr/libfs_avb/Android.bp
index c1c181a..6892025 100644
--- a/fs_mgr/libfs_avb/Android.bp
+++ b/fs_mgr/libfs_avb/Android.bp
@@ -78,6 +78,7 @@
shared_libs: [
"libbase",
"libchrome",
+ "libcrypto",
],
target: {
darwin: {
@@ -107,9 +108,6 @@
static_libs: [
"libfs_avb_test_util",
],
- shared_libs: [
- "libcrypto",
- ],
compile_multilib: "first",
data: [
":avbtool",
diff --git a/fs_mgr/libfs_avb/avb_util.cpp b/fs_mgr/libfs_avb/avb_util.cpp
index 2288674..31494c1 100644
--- a/fs_mgr/libfs_avb/avb_util.cpp
+++ b/fs_mgr/libfs_avb/avb_util.cpp
@@ -61,7 +61,9 @@
// Converts veritymode to the format used in kernel.
std::string dm_verity_mode;
- if (verity_mode == "enforcing") {
+ if (verity_mode == "panicking") {
+ dm_verity_mode = "panic_on_corruption";
+ } else if (verity_mode == "enforcing") {
dm_verity_mode = "restart_on_corruption";
} else if (verity_mode == "logging") {
dm_verity_mode = "ignore_corruption";
diff --git a/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp b/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
index 17f4c4e..1c95cf0 100644
--- a/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
+++ b/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
@@ -122,6 +122,7 @@
const size_t padding_size) {
VBMetaImage vbmeta_image;
vbmeta_image.path = test_dir_.Append(output_file_name);
+ GTEST_LOG_(INFO) << "ExtractVBMetaImage: " << image_path << " to " << output_file_name;
EXPECT_COMMAND(0,
"avbtool extract_vbmeta_image"
" --image %s"
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 3cb4123..104484d 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -251,6 +251,7 @@
"snapshot_metadata_updater_test.cpp",
"snapshot_reader_test.cpp",
"snapshot_test.cpp",
+ "snapshot_writer_test.cpp",
],
shared_libs: [
"libbinder",
@@ -420,7 +421,8 @@
"snapuserd_server.cpp",
"snapuserd.cpp",
"snapuserd_daemon.cpp",
- "snapuserd_worker.cpp",
+ "snapuserd_worker.cpp",
+ "snapuserd_readahead.cpp",
],
cflags: [
@@ -477,6 +479,9 @@
"libgtest",
"libsnapshot_cow",
],
+ test_suites: [
+ "device-tests"
+ ],
test_min_api_level: 30,
auto_gen_config: true,
require_root: false,
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index 1ebc29f..92aa55c 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -139,7 +139,28 @@
Cancelled = 7;
};
-// Next: 7
+// Next 14:
+//
+// To understand the source of each failure, read snapshot.cpp. To handle new
+// sources of failure, avoid reusing an existing code; add a new code instead.
+enum MergeFailureCode {
+ Ok = 0;
+ ReadStatus = 1;
+ GetTableInfo = 2;
+ UnknownTable = 3;
+ GetTableParams = 4;
+ ActivateNewTable = 5;
+ AcquireLock = 6;
+ ListSnapshots = 7;
+ WriteStatus = 8;
+ UnknownTargetType = 9;
+ QuerySnapshotStatus = 10;
+ ExpectedMergeTarget = 11;
+ UnmergedSectorsAfterCompletion = 12;
+ UnexpectedMergeState = 13;
+};
+
+// Next: 8
message SnapshotUpdateStatus {
UpdateState state = 1;
@@ -160,9 +181,12 @@
// Merge phase (if state == MERGING).
MergePhase merge_phase = 6;
+
+ // Merge failure code, filled if state == MergeFailed.
+ MergeFailureCode merge_failure_code = 7;
}
-// Next: 9
+// Next: 10
message SnapshotMergeReport {
// Status of the update after the merge attempts.
UpdateState state = 1;
@@ -188,4 +212,7 @@
// Time from sys.boot_completed to merge start, in milliseconds.
uint32 boot_complete_to_merge_start_time_ms = 8;
+
+ // Merge failure code, filled if state == MergeFailed.
+ MergeFailureCode merge_failure_code = 9;
}
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 7199b38..2349e4a 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -94,11 +94,6 @@
<< "Expected: " << kCowMagicNumber;
return false;
}
- if (header_.header_size != sizeof(CowHeader)) {
- LOG(ERROR) << "Header size unknown, read " << header_.header_size << ", expected "
- << sizeof(CowHeader);
- return false;
- }
if (header_.footer_size != sizeof(CowFooter)) {
LOG(ERROR) << "Footer size unknown, read " << header_.footer_size << ", expected "
<< sizeof(CowFooter);
@@ -123,8 +118,7 @@
return false;
}
- if ((header_.major_version != kCowVersionMajor) ||
- (header_.minor_version != kCowVersionMinor)) {
+ if ((header_.major_version > kCowVersionMajor) || (header_.minor_version != kCowVersionMinor)) {
LOG(ERROR) << "Header version mismatch";
LOG(ERROR) << "Major version: " << header_.major_version
<< "Expected: " << kCowVersionMajor;
@@ -137,10 +131,25 @@
}
bool CowReader::ParseOps(std::optional<uint64_t> label) {
- uint64_t pos = lseek(fd_.get(), sizeof(header_), SEEK_SET);
- if (pos != sizeof(header_)) {
- PLOG(ERROR) << "lseek ops failed";
- return false;
+ uint64_t pos;
+
+ // Skip the scratch space
+ if (header_.major_version >= 2 && (header_.buffer_size > 0)) {
+ LOG(DEBUG) << " Scratch space found of size: " << header_.buffer_size;
+ size_t init_offset = header_.header_size + header_.buffer_size;
+ pos = lseek(fd_.get(), init_offset, SEEK_SET);
+ if (pos != init_offset) {
+ PLOG(ERROR) << "lseek ops failed";
+ return false;
+ }
+ } else {
+ pos = lseek(fd_.get(), header_.header_size, SEEK_SET);
+ if (pos != header_.header_size) {
+ PLOG(ERROR) << "lseek ops failed";
+ return false;
+ }
+ // Reading a v1 version of COW which doesn't have buffer_size.
+ header_.buffer_size = 0;
}
auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
@@ -360,6 +369,24 @@
// Replace-op-4, Zero-op-9, Replace-op-5 }
//==============================================================
+ num_copy_ops = FindNumCopyops();
+
+ std::sort(ops_.get()->begin() + num_copy_ops, ops_.get()->end(),
+ [](CowOperation& op1, CowOperation& op2) -> bool {
+ return op1.new_block > op2.new_block;
+ });
+
+ if (header_.num_merge_ops > 0) {
+ ops_->erase(ops_.get()->begin(), ops_.get()->begin() + header_.num_merge_ops);
+ }
+
+ num_copy_ops = FindNumCopyops();
+ set_copy_ops(num_copy_ops);
+}
+
+uint64_t CowReader::FindNumCopyops() {
+ uint64_t num_copy_ops = 0;
+
for (uint64_t i = 0; i < ops_->size(); i++) {
auto& current_op = ops_->data()[i];
if (current_op.type != kCowCopyOp) {
@@ -368,15 +395,7 @@
num_copy_ops += 1;
}
- std::sort(ops_.get()->begin() + num_copy_ops, ops_.get()->end(),
- [](CowOperation& op1, CowOperation& op2) -> bool {
- return op1.new_block > op2.new_block;
- });
-
- if (header_.num_merge_ops > 0) {
- CHECK(ops_->size() >= header_.num_merge_ops);
- ops_->erase(ops_.get()->begin(), ops_.get()->begin() + header_.num_merge_ops);
- }
+ return num_copy_ops;
}
bool CowReader::GetHeader(CowHeader* header) {
@@ -470,7 +489,7 @@
bool CowReader::GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read) {
// Validate the offset, taking care to acknowledge possible overflow of offset+len.
- if (offset < sizeof(header_) || offset >= fd_size_ - sizeof(CowFooter) || len >= fd_size_ ||
+ if (offset < header_.header_size || offset >= fd_size_ - sizeof(CowFooter) || len >= fd_size_ ||
offset + len > fd_size_ - sizeof(CowFooter)) {
LOG(ERROR) << "invalid data offset: " << offset << ", " << len << " bytes";
return false;
diff --git a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
index 045d9db..3888eb1 100644
--- a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
@@ -96,6 +96,8 @@
class CowSnapuserdTest final {
public:
bool Setup();
+ bool SetupCopyOverlap_1();
+ bool SetupCopyOverlap_2();
bool Merge();
void ValidateMerge();
void ReadSnapshotDeviceAndValidate();
@@ -114,6 +116,9 @@
void StartMerge();
void CreateCowDevice();
+ void CreateCowDeviceWithCopyOverlap_1();
+ void CreateCowDeviceWithCopyOverlap_2();
+ bool SetupDaemon();
void CreateBaseDevice();
void InitCowDevice();
void SetDeviceControlName();
@@ -191,6 +196,33 @@
return setup_ok_;
}
+bool CowSnapuserdTest::SetupCopyOverlap_1() {
+ CreateBaseDevice();
+ CreateCowDeviceWithCopyOverlap_1();
+ return SetupDaemon();
+}
+
+bool CowSnapuserdTest::SetupCopyOverlap_2() {
+ CreateBaseDevice();
+ CreateCowDeviceWithCopyOverlap_2();
+ return SetupDaemon();
+}
+
+bool CowSnapuserdTest::SetupDaemon() {
+ SetDeviceControlName();
+
+ StartSnapuserdDaemon();
+ InitCowDevice();
+
+ CreateDmUserDevice();
+ InitDaemon();
+
+ CreateSnapshotDevice();
+ setup_ok_ = true;
+
+ return setup_ok_;
+}
+
void CowSnapuserdTest::StartSnapuserdDaemon() {
pid_t pid = fork();
ASSERT_GE(pid, 0);
@@ -255,6 +287,101 @@
ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0);
}
+void CowSnapuserdTest::CreateCowDeviceWithCopyOverlap_2() {
+ std::string path = android::base::GetExecutableDirectory();
+ cow_system_ = std::make_unique<TemporaryFile>(path);
+
+ CowOptions options;
+ options.compression = "gz";
+ CowWriter writer(options);
+
+ ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+ size_t num_blocks = size_ / options.block_size;
+ size_t x = num_blocks;
+ size_t blk_src_copy = 0;
+
+ // Create overlapping copy operations
+ while (1) {
+ ASSERT_TRUE(writer.AddCopy(blk_src_copy, blk_src_copy + 1));
+ x -= 1;
+ if (x == 1) {
+ break;
+ }
+ blk_src_copy += 1;
+ }
+
+ // Flush operations
+ ASSERT_TRUE(writer.Finalize());
+
+ // Construct the buffer required for validation
+ orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+ // Read the entire base device
+ ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+ true);
+
+ // Merged operations required for validation
+ int block_size = 4096;
+ x = num_blocks;
+ loff_t src_offset = block_size;
+ loff_t dest_offset = 0;
+
+ while (1) {
+ memmove((char*)orig_buffer_.get() + dest_offset, (char*)orig_buffer_.get() + src_offset,
+ block_size);
+ x -= 1;
+ if (x == 1) {
+ break;
+ }
+ src_offset += block_size;
+ dest_offset += block_size;
+ }
+}
+
+void CowSnapuserdTest::CreateCowDeviceWithCopyOverlap_1() {
+ std::string path = android::base::GetExecutableDirectory();
+ cow_system_ = std::make_unique<TemporaryFile>(path);
+
+ CowOptions options;
+ options.compression = "gz";
+ CowWriter writer(options);
+
+ ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+ size_t num_blocks = size_ / options.block_size;
+ size_t x = num_blocks;
+ size_t blk_src_copy = num_blocks - 1;
+
+ // Create overlapping copy operations
+ while (1) {
+ ASSERT_TRUE(writer.AddCopy(blk_src_copy + 1, blk_src_copy));
+ x -= 1;
+ if (x == 0) {
+ ASSERT_EQ(blk_src_copy, 0);
+ break;
+ }
+ blk_src_copy -= 1;
+ }
+
+ // Flush operations
+ ASSERT_TRUE(writer.Finalize());
+
+ // Construct the buffer required for validation
+ orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+ // Read the entire base device
+ ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+ true);
+
+ // Merged operations
+ ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), options.block_size, 0),
+ true);
+ ASSERT_EQ(android::base::ReadFullyAtOffset(
+ base_fd_, (char*)orig_buffer_.get() + options.block_size, size_, 0),
+ true);
+}
+
void CowSnapuserdTest::CreateCowDevice() {
unique_fd rnd_fd;
loff_t offset = 0;
@@ -757,6 +884,31 @@
harness.ValidateMerge();
harness.Shutdown();
}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_1) {
+ CowSnapuserdTest harness;
+ ASSERT_TRUE(harness.SetupCopyOverlap_1());
+ ASSERT_TRUE(harness.Merge());
+ harness.ValidateMerge();
+ harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_2) {
+ CowSnapuserdTest harness;
+ ASSERT_TRUE(harness.SetupCopyOverlap_2());
+ ASSERT_TRUE(harness.Merge());
+ harness.ValidateMerge();
+ harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_Merge_Resume_TEST) {
+ CowSnapuserdTest harness;
+ ASSERT_TRUE(harness.SetupCopyOverlap_1());
+ harness.MergeInterrupt();
+ harness.ValidateMerge();
+ harness.Shutdown();
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index 645ae9d..51c00a9 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -94,6 +94,7 @@
header_.block_size = options_.block_size;
header_.num_merge_ops = 0;
header_.cluster_ops = options_.cluster_ops;
+ header_.buffer_size = 0;
footer_ = {};
footer_.op.data_length = 64;
footer_.op.type = kCowFooterOp;
@@ -139,12 +140,6 @@
return true;
}
-void CowWriter::InitializeMerge(borrowed_fd fd, CowHeader* header) {
- fd_ = fd;
- memcpy(&header_, header, sizeof(CowHeader));
- merge_in_progress_ = true;
-}
-
bool CowWriter::Initialize(unique_fd&& fd) {
owned_fd_ = std::move(fd);
return Initialize(borrowed_fd{owned_fd_});
@@ -172,7 +167,7 @@
}
void CowWriter::InitPos() {
- next_op_pos_ = sizeof(header_);
+ next_op_pos_ = sizeof(header_) + header_.buffer_size;
cluster_size_ = header_.cluster_ops * sizeof(CowOperation);
if (header_.cluster_ops) {
next_data_pos_ = next_op_pos_ + cluster_size_;
@@ -196,6 +191,10 @@
return false;
}
+ if (options_.scratch_space) {
+ header_.buffer_size = BUFFER_REGION_DEFAULT_SIZE;
+ }
+
// Headers are not complete, but this ensures the file is at the right
// position.
if (!android::base::WriteFully(fd_, &header_, sizeof(header_))) {
@@ -203,7 +202,27 @@
return false;
}
+ if (options_.scratch_space) {
+ // Initialize the scratch space
+ std::string data(header_.buffer_size, 0);
+ if (!android::base::WriteFully(fd_, data.data(), header_.buffer_size)) {
+ PLOG(ERROR) << "writing scratch space failed";
+ return false;
+ }
+ }
+
+ if (!Sync()) {
+ LOG(ERROR) << "Header sync failed";
+ return false;
+ }
+
+ if (lseek(fd_.get(), sizeof(header_) + header_.buffer_size, SEEK_SET) < 0) {
+ PLOG(ERROR) << "lseek failed";
+ return false;
+ }
+
InitPos();
+
return true;
}
@@ -517,24 +536,6 @@
return true;
}
-bool CowWriter::CommitMerge(int merged_ops) {
- CHECK(merge_in_progress_);
- header_.num_merge_ops += merged_ops;
-
- if (lseek(fd_.get(), 0, SEEK_SET) < 0) {
- PLOG(ERROR) << "lseek failed";
- return false;
- }
-
- if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&header_),
- sizeof(header_))) {
- PLOG(ERROR) << "WriteFully failed";
- return false;
- }
-
- return Sync();
-}
-
bool CowWriter::Truncate(off_t length) {
if (is_dev_null_ || is_block_device_) {
return true;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 797b8ef..000e5e1 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -21,15 +21,22 @@
namespace snapshot {
static constexpr uint64_t kCowMagicNumber = 0x436f77634f572121ULL;
-static constexpr uint32_t kCowVersionMajor = 1;
+static constexpr uint32_t kCowVersionMajor = 2;
static constexpr uint32_t kCowVersionMinor = 0;
+static constexpr uint32_t kCowVersionManifest = 2;
+
+static constexpr uint32_t BLOCK_SZ = 4096;
+static constexpr uint32_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
+
// This header appears as the first sequence of bytes in the COW. All fields
// in the layout are little-endian encoded. The on-disk layout is:
//
// +-----------------------+
// | Header (fixed) |
// +-----------------------+
+// | Scratch space |
+// +-----------------------+
// | Operation (variable) |
// | Data (variable) |
// +-----------------------+
@@ -68,6 +75,9 @@
// Tracks merge operations completed
uint64_t num_merge_ops;
+
+ // Scratch space used during merge
+ uint32_t buffer_size;
} __attribute__((packed));
// This structure is the same size of a normal Operation, but is repurposed for the footer.
@@ -144,11 +154,31 @@
static constexpr uint8_t kCowCompressGz = 1;
static constexpr uint8_t kCowCompressBrotli = 2;
+static constexpr uint8_t kCowReadAheadNotStarted = 0;
+static constexpr uint8_t kCowReadAheadInProgress = 1;
+static constexpr uint8_t kCowReadAheadDone = 2;
+
struct CowFooter {
CowFooterOperation op;
CowFooterData data;
} __attribute__((packed));
+struct ScratchMetadata {
+ // Block of data in the image that operation modifies
+ // and read-ahead thread stores the modified data
+ // in the scratch space
+ uint64_t new_block;
+ // Offset within the file to read the data
+ uint64_t file_offset;
+} __attribute__((packed));
+
+struct BufferState {
+ uint8_t read_ahead_state;
+} __attribute__((packed));
+
+// 2MB Scratch space used for read-ahead
+static constexpr uint64_t BUFFER_REGION_DEFAULT_SIZE = (1ULL << 21);
+
std::ostream& operator<<(std::ostream& os, CowOperation const& arg);
int64_t GetNextOpOffset(const CowOperation& op, uint32_t cluster_size);
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index 552fd96..9ebcfd9 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -141,18 +141,21 @@
bool GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read);
- void UpdateMergeProgress(uint64_t merge_ops) { header_.num_merge_ops += merge_ops; }
-
void InitializeMerge();
void set_total_data_ops(uint64_t size) { total_data_ops_ = size; }
uint64_t total_data_ops() { return total_data_ops_; }
+ void set_copy_ops(uint64_t size) { copy_ops_ = size; }
+
+ uint64_t total_copy_ops() { return copy_ops_; }
+
void CloseCowFd() { owned_fd_ = {}; }
private:
bool ParseOps(std::optional<uint64_t> label);
+ uint64_t FindNumCopyops();
android::base::unique_fd owned_fd_;
android::base::borrowed_fd fd_;
@@ -162,6 +165,7 @@
std::optional<uint64_t> last_label_;
std::shared_ptr<std::vector<CowOperation>> ops_;
uint64_t total_data_ops_;
+ uint64_t copy_ops_;
};
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index a9efad8..f43ea68 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -36,6 +36,8 @@
// Number of CowOperations in a cluster. 0 for no clustering. Cannot be 1.
uint32_t cluster_ops = 200;
+
+ bool scratch_space = true;
};
// Interface for writing to a snapuserd COW. All operations are ordered; merges
@@ -100,13 +102,12 @@
bool InitializeAppend(android::base::unique_fd&&, uint64_t label);
bool InitializeAppend(android::base::borrowed_fd fd, uint64_t label);
- void InitializeMerge(android::base::borrowed_fd fd, CowHeader* header);
- bool CommitMerge(int merged_ops);
-
bool Finalize() override;
uint64_t GetCowSize() override;
+ uint32_t GetCowVersion() { return header_.major_version; }
+
protected:
virtual bool EmitCopy(uint64_t new_block, uint64_t old_block) override;
virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index 1cb966b..94d5055 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -27,6 +27,7 @@
MOCK_METHOD(bool, CancelUpdate, (), (override));
MOCK_METHOD(bool, FinishedSnapshotWrites, (bool wipe), (override));
MOCK_METHOD(void, UpdateCowStats, (ISnapshotMergeStats * stats), (override));
+ MOCK_METHOD(MergeFailureCode, ReadMergeFailureCode, (), (override));
MOCK_METHOD(bool, InitiateMerge, (), (override));
MOCK_METHOD(UpdateState, ProcessUpdateState,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
index ac2c787..067f99c 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
@@ -34,11 +34,13 @@
MOCK_METHOD(void, set_estimated_cow_size_bytes, (uint64_t), (override));
MOCK_METHOD(void, set_boot_complete_time_ms, (uint32_t), (override));
MOCK_METHOD(void, set_boot_complete_to_merge_start_time_ms, (uint32_t), (override));
+ MOCK_METHOD(void, set_merge_failure_code, (MergeFailureCode), (override));
MOCK_METHOD(uint64_t, cow_file_size, (), (override));
MOCK_METHOD(uint64_t, total_cow_size_bytes, (), (override));
MOCK_METHOD(uint64_t, estimated_cow_size_bytes, (), (override));
MOCK_METHOD(uint32_t, boot_complete_time_ms, (), (override));
MOCK_METHOD(uint32_t, boot_complete_to_merge_start_time_ms, (), (override));
+ MOCK_METHOD(MergeFailureCode, merge_failure_code, (), (override));
MOCK_METHOD(std::unique_ptr<Result>, Finish, (), (override));
using ISnapshotMergeStats::Result;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 126e1a0..d2fdfd6 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -167,6 +167,10 @@
virtual UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
const std::function<bool()>& before_cancel = {}) = 0;
+ // If ProcessUpdateState() returned MergeFailed, this returns the appropriate
+ // code. Otherwise, MergeFailureCode::Ok is returned.
+ virtual MergeFailureCode ReadMergeFailureCode() = 0;
+
// Find the status of the current update, if any.
//
// |progress| depends on the returned status:
@@ -332,6 +336,7 @@
bool CancelUpdate() override;
bool FinishedSnapshotWrites(bool wipe) override;
void UpdateCowStats(ISnapshotMergeStats* stats) override;
+ MergeFailureCode ReadMergeFailureCode() override;
bool InitiateMerge() override;
UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
const std::function<bool()>& before_cancel = {}) override;
@@ -381,11 +386,13 @@
FRIEND_TEST(SnapshotTest, MapPartialSnapshot);
FRIEND_TEST(SnapshotTest, MapSnapshot);
FRIEND_TEST(SnapshotTest, Merge);
+ FRIEND_TEST(SnapshotTest, MergeFailureCode);
FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
FRIEND_TEST(SnapshotTest, UpdateBootControlHal);
FRIEND_TEST(SnapshotUpdateTest, DaemonTransition);
FRIEND_TEST(SnapshotUpdateTest, DataWipeAfterRollback);
FRIEND_TEST(SnapshotUpdateTest, DataWipeRollbackInRecovery);
+ FRIEND_TEST(SnapshotUpdateTest, DataWipeWithStaleSnapshots);
FRIEND_TEST(SnapshotUpdateTest, FullUpdateFlow);
FRIEND_TEST(SnapshotUpdateTest, MergeCannotRemoveCow);
FRIEND_TEST(SnapshotUpdateTest, MergeInRecovery);
@@ -532,7 +539,8 @@
// Interact with /metadata/ota/state.
UpdateState ReadUpdateState(LockedFile* file);
SnapshotUpdateStatus ReadSnapshotUpdateStatus(LockedFile* file);
- bool WriteUpdateState(LockedFile* file, UpdateState state);
+ bool WriteUpdateState(LockedFile* file, UpdateState state,
+ MergeFailureCode failure_code = MergeFailureCode::Ok);
bool WriteSnapshotUpdateStatus(LockedFile* file, const SnapshotUpdateStatus& status);
std::string GetStateFilePath() const;
@@ -541,12 +549,12 @@
std::string GetMergeStateFilePath() const;
// Helpers for merging.
- bool MergeSecondPhaseSnapshots(LockedFile* lock);
- bool SwitchSnapshotToMerge(LockedFile* lock, const std::string& name);
- bool RewriteSnapshotDeviceTable(const std::string& dm_name);
+ MergeFailureCode MergeSecondPhaseSnapshots(LockedFile* lock);
+ MergeFailureCode SwitchSnapshotToMerge(LockedFile* lock, const std::string& name);
+ MergeFailureCode RewriteSnapshotDeviceTable(const std::string& dm_name);
bool MarkSnapshotMergeCompleted(LockedFile* snapshot_lock, const std::string& snapshot_name);
void AcknowledgeMergeSuccess(LockedFile* lock);
- void AcknowledgeMergeFailure();
+ void AcknowledgeMergeFailure(MergeFailureCode failure_code);
MergePhase DecideMergePhase(const SnapshotStatus& status);
std::unique_ptr<LpMetadata> ReadCurrentMetadata();
@@ -573,14 +581,22 @@
const SnapshotStatus& status);
bool CollapseSnapshotDevice(const std::string& name, const SnapshotStatus& status);
+ struct MergeResult {
+ explicit MergeResult(UpdateState state,
+ MergeFailureCode failure_code = MergeFailureCode::Ok)
+ : state(state), failure_code(failure_code) {}
+ UpdateState state;
+ MergeFailureCode failure_code;
+ };
+
// Only the following UpdateStates are used here:
// UpdateState::Merging
// UpdateState::MergeCompleted
// UpdateState::MergeFailed
// UpdateState::MergeNeedsReboot
- UpdateState CheckMergeState(const std::function<bool()>& before_cancel);
- UpdateState CheckMergeState(LockedFile* lock, const std::function<bool()>& before_cancel);
- UpdateState CheckTargetMergeState(LockedFile* lock, const std::string& name,
+ MergeResult CheckMergeState(const std::function<bool()>& before_cancel);
+ MergeResult CheckMergeState(LockedFile* lock, const std::function<bool()>& before_cancel);
+ MergeResult CheckTargetMergeState(LockedFile* lock, const std::string& name,
const SnapshotUpdateStatus& update_status);
// Interact with status files under /metadata/ota/snapshots.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
index e617d7a..4ce5077 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
@@ -34,11 +34,13 @@
virtual void set_estimated_cow_size_bytes(uint64_t bytes) = 0;
virtual void set_boot_complete_time_ms(uint32_t ms) = 0;
virtual void set_boot_complete_to_merge_start_time_ms(uint32_t ms) = 0;
+ virtual void set_merge_failure_code(MergeFailureCode code) = 0;
virtual uint64_t cow_file_size() = 0;
virtual uint64_t total_cow_size_bytes() = 0;
virtual uint64_t estimated_cow_size_bytes() = 0;
virtual uint32_t boot_complete_time_ms() = 0;
virtual uint32_t boot_complete_to_merge_start_time_ms() = 0;
+ virtual MergeFailureCode merge_failure_code() = 0;
// Called when merge ends. Properly clean up permanent storage.
class Result {
@@ -70,6 +72,8 @@
uint32_t boot_complete_time_ms() override;
void set_boot_complete_to_merge_start_time_ms(uint32_t ms) override;
uint32_t boot_complete_to_merge_start_time_ms() override;
+ void set_merge_failure_code(MergeFailureCode code) override;
+ MergeFailureCode merge_failure_code() override;
std::unique_ptr<Result> Finish() override;
private:
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
index cc75db8..a7cd939 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -29,6 +29,7 @@
bool CancelUpdate() override;
bool FinishedSnapshotWrites(bool wipe) override;
void UpdateCowStats(ISnapshotMergeStats* stats) override;
+ MergeFailureCode ReadMergeFailureCode() override;
bool InitiateMerge() override;
UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
const std::function<bool()>& before_cancel = {}) override;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
index 2b6c8ef..6bb7a39 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
@@ -47,9 +47,6 @@
static constexpr uint32_t CHUNK_SIZE = 8;
static constexpr uint32_t CHUNK_SHIFT = (__builtin_ffs(CHUNK_SIZE) - 1);
-static constexpr uint32_t BLOCK_SZ = 4096;
-static constexpr uint32_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
-
#define DIV_ROUND_UP(n, d) (((n) + (d)-1) / (d))
// This structure represents the kernel COW header.
diff --git a/fs_mgr/libsnapshot/inspect_cow.cpp b/fs_mgr/libsnapshot/inspect_cow.cpp
index 453b5c6..4a84fba 100644
--- a/fs_mgr/libsnapshot/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/inspect_cow.cpp
@@ -38,7 +38,8 @@
static void usage(void) {
LOG(ERROR) << "Usage: inspect_cow [-sd] <COW_FILE>";
LOG(ERROR) << "\t -s Run Silent";
- LOG(ERROR) << "\t -d Attempt to decompress\n";
+ LOG(ERROR) << "\t -d Attempt to decompress";
+ LOG(ERROR) << "\t -b Show data for failed decompress\n";
}
// Sink that always appends to the end of a string.
@@ -59,7 +60,25 @@
std::string stream_;
};
-static bool Inspect(const std::string& path, bool silent, bool decompress) {
+static void ShowBad(CowReader& reader, const struct CowOperation& op) {
+ size_t count;
+ auto buffer = std::make_unique<uint8_t[]>(op.data_length);
+
+ if (!reader.GetRawBytes(op.source, buffer.get(), op.data_length, &count)) {
+ std::cerr << "Failed to read at all!\n";
+ } else {
+ std::cout << "The Block data is:\n";
+ for (int i = 0; i < op.data_length; i++) {
+ std::cout << std::hex << (int)buffer[i];
+ }
+ std::cout << std::dec << "\n\n";
+ if (op.data_length >= sizeof(CowOperation)) {
+ std::cout << "The start, as an op, would be " << *(CowOperation*)buffer.get() << "\n";
+ }
+ }
+}
+
+static bool Inspect(const std::string& path, bool silent, bool decompress, bool show_bad) {
android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
if (fd < 0) {
PLOG(ERROR) << "open failed: " << path;
@@ -107,6 +126,7 @@
if (!reader.ReadData(op, &sink)) {
std::cerr << "Failed to decompress for :" << op << "\n";
success = false;
+ if (show_bad) ShowBad(reader, op);
}
sink.Reset();
}
@@ -124,7 +144,8 @@
int ch;
bool silent = false;
bool decompress = false;
- while ((ch = getopt(argc, argv, "sd")) != -1) {
+ bool show_bad = false;
+ while ((ch = getopt(argc, argv, "sdb")) != -1) {
switch (ch) {
case 's':
silent = true;
@@ -132,6 +153,9 @@
case 'd':
decompress = true;
break;
+ case 'b':
+ show_bad = true;
+ break;
default:
android::snapshot::usage();
}
@@ -143,7 +167,7 @@
return 1;
}
- if (!android::snapshot::Inspect(argv[optind], silent, decompress)) {
+ if (!android::snapshot::Inspect(argv[optind], silent, decompress, show_bad)) {
return 1;
}
return 0;
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index c504355..245742e 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -746,32 +746,35 @@
return false;
}
- bool rewrote_all = true;
+ auto reported_code = MergeFailureCode::Ok;
for (const auto& snapshot : *merge_group) {
// If this fails, we have no choice but to continue. Everything must
// be merged. This is not an ideal state to be in, but it is safe,
// because we the next boot will try again.
- if (!SwitchSnapshotToMerge(lock.get(), snapshot)) {
+ auto code = SwitchSnapshotToMerge(lock.get(), snapshot);
+ if (code != MergeFailureCode::Ok) {
LOG(ERROR) << "Failed to switch snapshot to a merge target: " << snapshot;
- rewrote_all = false;
+ if (reported_code == MergeFailureCode::Ok) {
+ reported_code = code;
+ }
}
}
// If we couldn't switch everything to a merge target, pre-emptively mark
// this merge as failed. It will get acknowledged when WaitForMerge() is
// called.
- if (!rewrote_all) {
- WriteUpdateState(lock.get(), UpdateState::MergeFailed);
+ if (reported_code != MergeFailureCode::Ok) {
+ WriteUpdateState(lock.get(), UpdateState::MergeFailed, reported_code);
}
// Return true no matter what, because a merge was initiated.
return true;
}
-bool SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string& name) {
+MergeFailureCode SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string& name) {
SnapshotStatus status;
if (!ReadSnapshotStatus(lock, name, &status)) {
- return false;
+ return MergeFailureCode::ReadStatus;
}
if (status.state() != SnapshotState::CREATED) {
LOG(WARNING) << "Snapshot " << name
@@ -780,8 +783,8 @@
// After this, we return true because we technically did switch to a merge
// target. Everything else we do here is just informational.
- if (!RewriteSnapshotDeviceTable(name)) {
- return false;
+ if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
+ return code;
}
status.set_state(SnapshotState::MERGING);
@@ -795,26 +798,26 @@
if (!WriteSnapshotStatus(lock, status)) {
LOG(ERROR) << "Could not update status file for snapshot: " << name;
}
- return true;
+ return MergeFailureCode::Ok;
}
-bool SnapshotManager::RewriteSnapshotDeviceTable(const std::string& name) {
+MergeFailureCode SnapshotManager::RewriteSnapshotDeviceTable(const std::string& name) {
auto& dm = DeviceMapper::Instance();
std::vector<DeviceMapper::TargetInfo> old_targets;
if (!dm.GetTableInfo(name, &old_targets)) {
LOG(ERROR) << "Could not read snapshot device table: " << name;
- return false;
+ return MergeFailureCode::GetTableInfo;
}
if (old_targets.size() != 1 || DeviceMapper::GetTargetType(old_targets[0].spec) != "snapshot") {
LOG(ERROR) << "Unexpected device-mapper table for snapshot: " << name;
- return false;
+ return MergeFailureCode::UnknownTable;
}
std::string base_device, cow_device;
if (!DmTargetSnapshot::GetDevicesFromParams(old_targets[0].data, &base_device, &cow_device)) {
LOG(ERROR) << "Could not derive underlying devices for snapshot: " << name;
- return false;
+ return MergeFailureCode::GetTableParams;
}
DmTable table;
@@ -822,10 +825,10 @@
SnapshotStorageMode::Merge, kSnapshotChunkSize);
if (!dm.LoadTableAndActivate(name, table)) {
LOG(ERROR) << "Could not swap device-mapper tables on snapshot device " << name;
- return false;
+ return MergeFailureCode::ActivateNewTable;
}
LOG(INFO) << "Successfully switched snapshot device to a merge target: " << name;
- return true;
+ return MergeFailureCode::Ok;
}
enum class TableQuery {
@@ -897,20 +900,20 @@
UpdateState SnapshotManager::ProcessUpdateState(const std::function<bool()>& callback,
const std::function<bool()>& before_cancel) {
while (true) {
- UpdateState state = CheckMergeState(before_cancel);
- LOG(INFO) << "ProcessUpdateState handling state: " << state;
+ auto result = CheckMergeState(before_cancel);
+ LOG(INFO) << "ProcessUpdateState handling state: " << result.state;
- if (state == UpdateState::MergeFailed) {
- AcknowledgeMergeFailure();
+ if (result.state == UpdateState::MergeFailed) {
+ AcknowledgeMergeFailure(result.failure_code);
}
- if (state != UpdateState::Merging) {
+ if (result.state != UpdateState::Merging) {
// Either there is no merge, or the merge was finished, so no need
// to keep waiting.
- return state;
+ return result.state;
}
if (callback && !callback()) {
- return state;
+ return result.state;
}
// This wait is not super time sensitive, so we have a relatively
@@ -919,36 +922,36 @@
}
}
-UpdateState SnapshotManager::CheckMergeState(const std::function<bool()>& before_cancel) {
+auto SnapshotManager::CheckMergeState(const std::function<bool()>& before_cancel) -> MergeResult {
auto lock = LockExclusive();
if (!lock) {
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, MergeFailureCode::AcquireLock);
}
- UpdateState state = CheckMergeState(lock.get(), before_cancel);
- LOG(INFO) << "CheckMergeState for snapshots returned: " << state;
+ auto result = CheckMergeState(lock.get(), before_cancel);
+ LOG(INFO) << "CheckMergeState for snapshots returned: " << result.state;
- if (state == UpdateState::MergeCompleted) {
+ if (result.state == UpdateState::MergeCompleted) {
// Do this inside the same lock. Failures get acknowledged without the
// lock, because flock() might have failed.
AcknowledgeMergeSuccess(lock.get());
- } else if (state == UpdateState::Cancelled) {
+ } else if (result.state == UpdateState::Cancelled) {
if (!device_->IsRecovery() && !RemoveAllUpdateState(lock.get(), before_cancel)) {
LOG(ERROR) << "Failed to remove all update state after acknowleding cancelled update.";
}
}
- return state;
+ return result;
}
-UpdateState SnapshotManager::CheckMergeState(LockedFile* lock,
- const std::function<bool()>& before_cancel) {
+auto SnapshotManager::CheckMergeState(LockedFile* lock, const std::function<bool()>& before_cancel)
+ -> MergeResult {
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
switch (update_status.state()) {
case UpdateState::None:
case UpdateState::MergeCompleted:
// Harmless races are allowed between two callers of WaitForMerge,
// so in both of these cases we just propagate the state.
- return update_status.state();
+ return MergeResult(update_status.state());
case UpdateState::Merging:
case UpdateState::MergeNeedsReboot:
@@ -963,26 +966,26 @@
// via the merge poll below, but if we never started a merge, we
// need to also check here.
if (HandleCancelledUpdate(lock, before_cancel)) {
- return UpdateState::Cancelled;
+ return MergeResult(UpdateState::Cancelled);
}
- return update_status.state();
+ return MergeResult(update_status.state());
default:
- return update_status.state();
+ return MergeResult(update_status.state());
}
std::vector<std::string> snapshots;
if (!ListSnapshots(lock, &snapshots)) {
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ListSnapshots);
}
auto other_suffix = device_->GetOtherSlotSuffix();
bool cancelled = false;
- bool failed = false;
bool merging = false;
bool needs_reboot = false;
bool wrong_phase = false;
+ MergeFailureCode failure_code = MergeFailureCode::Ok;
for (const auto& snapshot : snapshots) {
if (android::base::EndsWith(snapshot, other_suffix)) {
// This will have triggered an error message in InitiateMerge already.
@@ -990,12 +993,15 @@
continue;
}
- UpdateState snapshot_state = CheckTargetMergeState(lock, snapshot, update_status);
- LOG(INFO) << "CheckTargetMergeState for " << snapshot << " returned: " << snapshot_state;
+ auto result = CheckTargetMergeState(lock, snapshot, update_status);
+ LOG(INFO) << "CheckTargetMergeState for " << snapshot << " returned: " << result.state;
- switch (snapshot_state) {
+ switch (result.state) {
case UpdateState::MergeFailed:
- failed = true;
+ // Take the first failure code in case other failures compound.
+ if (failure_code == MergeFailureCode::Ok) {
+ failure_code = result.failure_code;
+ }
break;
case UpdateState::Merging:
merging = true;
@@ -1013,8 +1019,10 @@
break;
default:
LOG(ERROR) << "Unknown merge status for \"" << snapshot << "\": "
- << "\"" << snapshot_state << "\"";
- failed = true;
+ << "\"" << result.state << "\"";
+ if (failure_code == MergeFailureCode::Ok) {
+ failure_code = MergeFailureCode::UnexpectedMergeState;
+ }
break;
}
}
@@ -1023,24 +1031,25 @@
// Note that we handle "Merging" before we handle anything else. We
// want to poll until *nothing* is merging if we can, so everything has
// a chance to get marked as completed or failed.
- return UpdateState::Merging;
+ return MergeResult(UpdateState::Merging);
}
- if (failed) {
+ if (failure_code != MergeFailureCode::Ok) {
// Note: since there are many drop-out cases for failure, we acknowledge
// it in WaitForMerge rather than here and elsewhere.
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, failure_code);
}
if (wrong_phase) {
// If we got here, no other partitions are being merged, and nothing
// failed to merge. It's safe to move to the next merge phase.
- if (!MergeSecondPhaseSnapshots(lock)) {
- return UpdateState::MergeFailed;
+ auto code = MergeSecondPhaseSnapshots(lock);
+ if (code != MergeFailureCode::Ok) {
+ return MergeResult(UpdateState::MergeFailed, code);
}
- return UpdateState::Merging;
+ return MergeResult(UpdateState::Merging);
}
if (needs_reboot) {
WriteUpdateState(lock, UpdateState::MergeNeedsReboot);
- return UpdateState::MergeNeedsReboot;
+ return MergeResult(UpdateState::MergeNeedsReboot);
}
if (cancelled) {
// This is an edge case, that we handle as correctly as we sensibly can.
@@ -1048,16 +1057,17 @@
// removed the snapshot as a result. The exact state of the update is
// undefined now, but this can only happen on an unlocked device where
// partitions can be flashed without wiping userdata.
- return UpdateState::Cancelled;
+ return MergeResult(UpdateState::Cancelled);
}
- return UpdateState::MergeCompleted;
+ return MergeResult(UpdateState::MergeCompleted);
}
-UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string& name,
- const SnapshotUpdateStatus& update_status) {
+auto SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string& name,
+ const SnapshotUpdateStatus& update_status)
+ -> MergeResult {
SnapshotStatus snapshot_status;
if (!ReadSnapshotStatus(lock, name, &snapshot_status)) {
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ReadStatus);
}
std::unique_ptr<LpMetadata> current_metadata;
@@ -1070,7 +1080,7 @@
if (!current_metadata ||
GetMetadataPartitionState(*current_metadata, name) != MetadataPartitionState::Updated) {
DeleteSnapshot(lock, name);
- return UpdateState::Cancelled;
+ return MergeResult(UpdateState::Cancelled);
}
// During a check, we decided the merge was complete, but we were unable to
@@ -1081,11 +1091,11 @@
if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
// NB: It's okay if this fails now, we gave cleanup our best effort.
OnSnapshotMergeComplete(lock, name, snapshot_status);
- return UpdateState::MergeCompleted;
+ return MergeResult(UpdateState::MergeCompleted);
}
LOG(ERROR) << "Expected snapshot or snapshot-merge for device: " << name;
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
}
// This check is expensive so it is only enabled for debugging.
@@ -1095,29 +1105,30 @@
std::string target_type;
DmTargetSnapshot::Status status;
if (!QuerySnapshotStatus(name, &target_type, &status)) {
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
}
if (target_type == "snapshot" &&
DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
update_status.merge_phase() == MergePhase::FIRST_PHASE) {
// The snapshot is not being merged because it's in the wrong phase.
- return UpdateState::None;
+ return MergeResult(UpdateState::None);
}
if (target_type != "snapshot-merge") {
// We can get here if we failed to rewrite the target type in
// InitiateMerge(). If we failed to create the target in first-stage
// init, boot would not succeed.
LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
}
// These two values are equal when merging is complete.
if (status.sectors_allocated != status.metadata_sectors) {
if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete.";
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed,
+ MergeFailureCode::UnmergedSectorsAfterCompletion);
}
- return UpdateState::Merging;
+ return MergeResult(UpdateState::Merging);
}
// Merging is done. First, update the status file to indicate the merge
@@ -1130,18 +1141,18 @@
// snapshot device for this partition.
snapshot_status.set_state(SnapshotState::MERGE_COMPLETED);
if (!WriteSnapshotStatus(lock, snapshot_status)) {
- return UpdateState::MergeFailed;
+ return MergeResult(UpdateState::MergeFailed, MergeFailureCode::WriteStatus);
}
if (!OnSnapshotMergeComplete(lock, name, snapshot_status)) {
- return UpdateState::MergeNeedsReboot;
+ return MergeResult(UpdateState::MergeNeedsReboot);
}
- return UpdateState::MergeCompleted;
+ return MergeResult(UpdateState::MergeCompleted, MergeFailureCode::Ok);
}
-bool SnapshotManager::MergeSecondPhaseSnapshots(LockedFile* lock) {
+MergeFailureCode SnapshotManager::MergeSecondPhaseSnapshots(LockedFile* lock) {
std::vector<std::string> snapshots;
if (!ListSnapshots(lock, &snapshots)) {
- return UpdateState::MergeFailed;
+ return MergeFailureCode::ListSnapshots;
}
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
@@ -1150,24 +1161,27 @@
update_status.set_merge_phase(MergePhase::SECOND_PHASE);
if (!WriteSnapshotUpdateStatus(lock, update_status)) {
- return false;
+ return MergeFailureCode::WriteStatus;
}
- bool rewrote_all = true;
+ MergeFailureCode result = MergeFailureCode::Ok;
for (const auto& snapshot : snapshots) {
SnapshotStatus snapshot_status;
if (!ReadSnapshotStatus(lock, snapshot, &snapshot_status)) {
- return UpdateState::MergeFailed;
+ return MergeFailureCode::ReadStatus;
}
if (DecideMergePhase(snapshot_status) != MergePhase::SECOND_PHASE) {
continue;
}
- if (!SwitchSnapshotToMerge(lock, snapshot)) {
+ auto code = SwitchSnapshotToMerge(lock, snapshot);
+ if (code != MergeFailureCode::Ok) {
LOG(ERROR) << "Failed to switch snapshot to a second-phase merge target: " << snapshot;
- rewrote_all = false;
+ if (result == MergeFailureCode::Ok) {
+ result = code;
+ }
}
}
- return rewrote_all;
+ return result;
}
std::string SnapshotManager::GetSnapshotBootIndicatorPath() {
@@ -1199,7 +1213,7 @@
RemoveAllUpdateState(lock);
}
-void SnapshotManager::AcknowledgeMergeFailure() {
+void SnapshotManager::AcknowledgeMergeFailure(MergeFailureCode failure_code) {
// Log first, so worst case, we always have a record of why the calls below
// were being made.
LOG(ERROR) << "Merge could not be completed and will be marked as failed.";
@@ -1216,7 +1230,7 @@
return;
}
- WriteUpdateState(lock.get(), UpdateState::MergeFailed);
+ WriteUpdateState(lock.get(), UpdateState::MergeFailed, failure_code);
}
bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
@@ -2414,10 +2428,15 @@
return status;
}
-bool SnapshotManager::WriteUpdateState(LockedFile* lock, UpdateState state) {
+bool SnapshotManager::WriteUpdateState(LockedFile* lock, UpdateState state,
+ MergeFailureCode failure_code) {
SnapshotUpdateStatus status;
status.set_state(state);
+ if (state == UpdateState::MergeFailed) {
+ status.set_merge_failure_code(failure_code);
+ }
+
// If we're transitioning between two valid states (eg, we're not beginning
// or ending an OTA), then make sure to propagate the compression bit.
if (!(state == UpdateState::Initiated || state == UpdateState::None)) {
@@ -2564,7 +2583,11 @@
}
bool SnapshotManager::ForceLocalImageManager() {
- images_ = android::fiemap::ImageManager::Open(gsid_dir_);
+ android::fiemap::ImageManager::DeviceInfo device_info = {
+ .is_recovery = {device_->IsRecovery()},
+ };
+
+ images_ = android::fiemap::ImageManager::Open(gsid_dir_, device_info);
if (!images_) {
LOG(ERROR) << "Could not open ImageManager";
return false;
@@ -2673,8 +2696,18 @@
AutoDeviceList created_devices;
const auto& dap_metadata = manifest.dynamic_partition_metadata();
- bool use_compression =
- IsCompressionEnabled() && dap_metadata.vabc_enabled() && !device_->IsRecovery();
+ CowOptions options;
+ CowWriter writer(options);
+ bool cow_format_support = true;
+ if (dap_metadata.cow_version() < writer.GetCowVersion()) {
+ cow_format_support = false;
+ }
+
+ LOG(INFO) << " dap_metadata.cow_version(): " << dap_metadata.cow_version()
+ << " writer.GetCowVersion(): " << writer.GetCowVersion();
+
+ bool use_compression = IsCompressionEnabled() && dap_metadata.vabc_enabled() &&
+ !device_->IsRecovery() && cow_format_support;
std::string compression_algorithm;
if (use_compression) {
@@ -2941,7 +2974,13 @@
return Return::Error();
}
- CowWriter writer(CowOptions{.compression = it->second.compression_algorithm()});
+ CowOptions options;
+ if (device()->IsTestDevice()) {
+ options.scratch_space = false;
+ }
+ options.compression = it->second.compression_algorithm();
+
+ CowWriter writer(options);
if (!writer.Initialize(fd) || !writer.Finalize()) {
LOG(ERROR) << "Could not initialize COW device for " << target_partition->name();
return Return::Error();
@@ -3050,6 +3089,10 @@
CowOptions cow_options;
cow_options.compression = status.compression_algorithm();
cow_options.max_blocks = {status.device_size() / cow_options.block_size};
+ // Disable scratch space for vts tests
+ if (device()->IsTestDevice()) {
+ cow_options.scratch_space = false;
+ }
// Currently we don't support partial snapshots, since partition_cow_creator
// never creates this scenario.
@@ -3671,5 +3714,16 @@
return false;
}
+MergeFailureCode SnapshotManager::ReadMergeFailureCode() {
+ auto lock = LockExclusive();
+ if (!lock) return MergeFailureCode::AcquireLock;
+
+ SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get());
+ if (status.state() != UpdateState::MergeFailed) {
+ return MergeFailureCode::Ok;
+ }
+ return status.merge_failure_code();
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_reader_test.cpp b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
index 4202d22..9373059 100644
--- a/fs_mgr/libsnapshot/snapshot_reader_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
@@ -150,6 +150,7 @@
CowOptions options;
options.compression = "gz";
options.max_blocks = {kBlockCount};
+ options.scratch_space = false;
unique_fd cow_fd(dup(cow_->fd));
ASSERT_GE(cow_fd, 0);
diff --git a/fs_mgr/libsnapshot/snapshot_stats.cpp b/fs_mgr/libsnapshot/snapshot_stats.cpp
index 7fcfcea..4a93d65 100644
--- a/fs_mgr/libsnapshot/snapshot_stats.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stats.cpp
@@ -130,6 +130,14 @@
return report_.boot_complete_to_merge_start_time_ms();
}
+void SnapshotMergeStats::set_merge_failure_code(MergeFailureCode code) {
+ report_.set_merge_failure_code(code);
+}
+
+MergeFailureCode SnapshotMergeStats::merge_failure_code() {
+ return report_.merge_failure_code();
+}
+
class SnapshotMergeStatsResultImpl : public SnapshotMergeStats::Result {
public:
SnapshotMergeStatsResultImpl(const SnapshotMergeReport& report,
diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp
index 43825cc..1a9eda5 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -135,6 +135,8 @@
uint32_t boot_complete_time_ms() override { return 0; }
void set_boot_complete_to_merge_start_time_ms(uint32_t) override {}
uint32_t boot_complete_to_merge_start_time_ms() override { return 0; }
+ void set_merge_failure_code(MergeFailureCode) override {}
+ MergeFailureCode merge_failure_code() { return MergeFailureCode::Ok; }
};
ISnapshotMergeStats* SnapshotManagerStub::GetSnapshotMergeStatsInstance() {
@@ -163,4 +165,9 @@
LOG(ERROR) << __FUNCTION__ << " should never be called.";
}
+auto SnapshotManagerStub::ReadMergeFailureCode() -> MergeFailureCode {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return MergeFailureCode::Ok;
+}
+
} // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 8fae00b..9226b7f 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <libsnapshot/cow_format.h>
#include <libsnapshot/snapshot.h>
#include <fcntl.h>
@@ -327,6 +328,7 @@
auto dynamic_partition_metadata = manifest.mutable_dynamic_partition_metadata();
dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+ dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
auto group = dynamic_partition_metadata->add_groups();
group->set_name("group");
@@ -676,6 +678,18 @@
ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING);
}
+TEST_F(SnapshotTest, MergeFailureCode) {
+ ASSERT_TRUE(AcquireLock());
+
+ ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeFailed,
+ MergeFailureCode::ListSnapshots));
+ ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING);
+
+ SnapshotUpdateStatus status = sm->ReadSnapshotUpdateStatus(lock_.get());
+ ASSERT_EQ(status.state(), UpdateState::MergeFailed);
+ ASSERT_EQ(status.merge_failure_code(), MergeFailureCode::ListSnapshots);
+}
+
enum class Request { UNKNOWN, LOCK_SHARED, LOCK_EXCLUSIVE, UNLOCK, EXIT };
std::ostream& operator<<(std::ostream& os, Request request) {
switch (request) {
@@ -841,6 +855,7 @@
auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata();
dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+ dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
// Create a fake update package metadata.
// Not using full name "system", "vendor", "product" because these names collide with the
@@ -1845,6 +1860,67 @@
}
}
+// Test update package that requests data wipe.
+TEST_F(SnapshotUpdateTest, DataWipeWithStaleSnapshots) {
+ AddOperationForPartitions();
+
+ // Execute the update.
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+ // Write some data to target partitions.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ ASSERT_TRUE(WriteSnapshotAndHash(name)) << name;
+ }
+
+ // Create a stale snapshot that should not exist.
+ {
+ ASSERT_TRUE(AcquireLock());
+
+ PartitionCowCreator cow_creator = {
+ .compression_enabled = IsCompressionEnabled(),
+ .compression_algorithm = IsCompressionEnabled() ? "gz" : "none",
+ };
+ SnapshotStatus status;
+ status.set_name("sys_a");
+ status.set_device_size(1_MiB);
+ status.set_snapshot_size(2_MiB);
+ status.set_cow_partition_size(2_MiB);
+
+ ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status));
+ lock_ = nullptr;
+
+ ASSERT_TRUE(sm->EnsureImageManager());
+ ASSERT_TRUE(sm->image_manager()->CreateBackingImage("sys_a", 1_MiB, 0));
+ }
+
+ ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */));
+
+ // Simulate shutting down the device.
+ ASSERT_TRUE(UnmapAll());
+
+ // Simulate a reboot into recovery.
+ auto test_device = new TestDeviceInfo(fake_super, "_b");
+ test_device->set_recovery(true);
+ auto new_sm = NewManagerForFirstStageMount(test_device);
+
+ ASSERT_TRUE(new_sm->HandleImminentDataWipe());
+ // Manually mount metadata so that we can call GetUpdateState() below.
+ MountMetadata();
+ EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None);
+ ASSERT_FALSE(test_device->IsSlotUnbootable(1));
+ ASSERT_FALSE(test_device->IsSlotUnbootable(0));
+
+ // Now reboot into new slot.
+ test_device = new TestDeviceInfo(fake_super, "_b");
+ auto init = NewManagerForFirstStageMount(test_device);
+ ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+ // Verify that we are on the downgraded build.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ ASSERT_TRUE(IsPartitionUnchanged(name)) << name;
+ }
+}
+
TEST_F(SnapshotUpdateTest, Hashtree) {
constexpr auto partition_size = 4_MiB;
constexpr auto data_size = 3_MiB;
@@ -2232,9 +2308,27 @@
void TearDown() override;
private:
+ bool CreateFakeSuper();
+
std::unique_ptr<IImageManager> super_images_;
};
+bool SnapshotTestEnvironment::CreateFakeSuper() {
+ // Create and map the fake super partition.
+ static constexpr int kImageFlags =
+ IImageManager::CREATE_IMAGE_DEFAULT | IImageManager::CREATE_IMAGE_ZERO_FILL;
+ if (!super_images_->CreateBackingImage("fake-super", kSuperSize, kImageFlags)) {
+ LOG(ERROR) << "Could not create fake super partition";
+ return false;
+ }
+ if (!super_images_->MapImageDevice("fake-super", 10s, &fake_super)) {
+ LOG(ERROR) << "Could not map fake super partition";
+ return false;
+ }
+ test_device->set_fake_super(fake_super);
+ return true;
+}
+
void SnapshotTestEnvironment::SetUp() {
// b/163082876: GTEST_SKIP in Environment will make atest report incorrect results. Until
// that is fixed, don't call GTEST_SKIP here, but instead call GTEST_SKIP in individual test
@@ -2260,27 +2354,36 @@
sm = SnapshotManager::New(test_device);
ASSERT_NE(nullptr, sm) << "Could not create snapshot manager";
+ // Use a separate image manager for our fake super partition.
+ super_images_ = IImageManager::Open("ota/test/super", 10s);
+ ASSERT_NE(nullptr, super_images_) << "Could not create image manager";
+
+ // Map the old image if one exists so we can safely unmap everything that
+ // depends on it.
+ bool recreate_fake_super;
+ if (super_images_->BackingImageExists("fake-super")) {
+ if (super_images_->IsImageMapped("fake-super")) {
+ ASSERT_TRUE(super_images_->GetMappedImageDevice("fake-super", &fake_super));
+ } else {
+ ASSERT_TRUE(super_images_->MapImageDevice("fake-super", 10s, &fake_super));
+ }
+ test_device->set_fake_super(fake_super);
+ recreate_fake_super = true;
+ } else {
+ ASSERT_TRUE(CreateFakeSuper());
+ recreate_fake_super = false;
+ }
+
// Clean up previous run.
MetadataMountedTest().TearDown();
SnapshotUpdateTest().Cleanup();
SnapshotTest().Cleanup();
- // Use a separate image manager for our fake super partition.
- super_images_ = IImageManager::Open("ota/test/super", 10s);
- ASSERT_NE(nullptr, super_images_) << "Could not create image manager";
-
- // Clean up any old copy.
- DeleteBackingImage(super_images_.get(), "fake-super");
-
- // Create and map the fake super partition.
- static constexpr int kImageFlags =
- IImageManager::CREATE_IMAGE_DEFAULT | IImageManager::CREATE_IMAGE_ZERO_FILL;
- ASSERT_TRUE(super_images_->CreateBackingImage("fake-super", kSuperSize, kImageFlags))
- << "Could not create fake super partition";
-
- ASSERT_TRUE(super_images_->MapImageDevice("fake-super", 10s, &fake_super))
- << "Could not map fake super partition";
- test_device->set_fake_super(fake_super);
+ if (recreate_fake_super) {
+ // Clean up any old copy.
+ DeleteBackingImage(super_images_.get(), "fake-super");
+ ASSERT_TRUE(CreateFakeSuper());
+ }
}
void SnapshotTestEnvironment::TearDown() {
diff --git a/fs_mgr/libsnapshot/snapshot_writer.cpp b/fs_mgr/libsnapshot/snapshot_writer.cpp
index 8e379e4..080f3b7 100644
--- a/fs_mgr/libsnapshot/snapshot_writer.cpp
+++ b/fs_mgr/libsnapshot/snapshot_writer.cpp
@@ -90,7 +90,9 @@
}
const auto& cow_options = options();
- reader->SetBlockDeviceSize(*cow_options.max_blocks * cow_options.block_size);
+ if (cow_options.max_blocks) {
+ reader->SetBlockDeviceSize(*cow_options.max_blocks * cow_options.block_size);
+ }
return reader;
}
diff --git a/fs_mgr/libsnapshot/snapshot_writer_test.cpp b/fs_mgr/libsnapshot/snapshot_writer_test.cpp
new file mode 100644
index 0000000..da48eb9
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapshot_writer_test.cpp
@@ -0,0 +1,62 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <libsnapshot/snapshot.h>
+
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+#include <libsnapshot/snapshot_writer.h>
+#include <payload_consumer/file_descriptor.h>
+
+namespace android::snapshot {
+class CompressedSnapshotWriterTest : public ::testing::Test {
+ public:
+ static constexpr size_t BLOCK_SIZE = 4096;
+};
+
+TEST_F(CompressedSnapshotWriterTest, ReadAfterWrite) {
+ TemporaryFile cow_device_file{};
+ android::snapshot::CowOptions options{.block_size = BLOCK_SIZE};
+ android::snapshot::CompressedSnapshotWriter snapshot_writer{options};
+ snapshot_writer.SetCowDevice(android::base::unique_fd{cow_device_file.fd});
+ snapshot_writer.Initialize();
+ std::vector<unsigned char> buffer;
+ buffer.resize(BLOCK_SIZE);
+ std::fill(buffer.begin(), buffer.end(), 123);
+
+ ASSERT_TRUE(snapshot_writer.AddRawBlocks(0, buffer.data(), buffer.size()));
+ ASSERT_TRUE(snapshot_writer.Finalize());
+ auto cow_reader = snapshot_writer.OpenReader();
+ ASSERT_NE(cow_reader, nullptr);
+ ASSERT_TRUE(snapshot_writer.AddRawBlocks(1, buffer.data(), buffer.size()));
+ ASSERT_TRUE(snapshot_writer.AddRawBlocks(2, buffer.data(), buffer.size()));
+ ASSERT_TRUE(snapshot_writer.Finalize());
+ // After wrigin some data, if we call OpenReader() again, writes should
+ // be visible to the newly opened reader. update_engine relies on this
+ // behavior for verity writes.
+ cow_reader = snapshot_writer.OpenReader();
+ ASSERT_NE(cow_reader, nullptr);
+ std::vector<unsigned char> read_back;
+ read_back.resize(buffer.size());
+ cow_reader->Seek(BLOCK_SIZE, SEEK_SET);
+ const auto bytes_read = cow_reader->Read(read_back.data(), read_back.size());
+ ASSERT_EQ((size_t)(bytes_read), BLOCK_SIZE);
+ ASSERT_EQ(read_back, buffer);
+}
+
+} // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp
index 5ef9e29..03c2ef6 100644
--- a/fs_mgr/libsnapshot/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -47,28 +47,205 @@
worker_threads_.push_back(std::move(wt));
}
+
+ read_ahead_thread_ = std::make_unique<ReadAheadThread>(cow_device_, backing_store_device_,
+ misc_name_, GetSharedPtr());
return true;
}
bool Snapuserd::CommitMerge(int num_merge_ops) {
- {
- std::lock_guard<std::mutex> lock(lock_);
- CowHeader header;
+ struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+ ch->num_merge_ops += num_merge_ops;
- reader_->GetHeader(&header);
- header.num_merge_ops += num_merge_ops;
- reader_->UpdateMergeProgress(num_merge_ops);
- if (!writer_->CommitMerge(num_merge_ops)) {
- SNAP_LOG(ERROR) << "CommitMerge failed... merged_ops_cur_iter: " << num_merge_ops
- << " Total-merged-ops: " << header.num_merge_ops;
- return false;
- }
- merge_initiated_ = true;
+ if (read_ahead_feature_ && read_ahead_ops_.size() > 0) {
+ struct BufferState* ra_state = GetBufferState();
+ ra_state->read_ahead_state = kCowReadAheadInProgress;
}
+ int ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
+ if (ret < 0) {
+ PLOG(ERROR) << "msync header failed: " << ret;
+ return false;
+ }
+
+ merge_initiated_ = true;
+
return true;
}
+void Snapuserd::PrepareReadAhead() {
+ if (!read_ahead_feature_) {
+ return;
+ }
+
+ struct BufferState* ra_state = GetBufferState();
+ // Check if the data has to be re-constructed from COW device
+ if (ra_state->read_ahead_state == kCowReadAheadDone) {
+ populate_data_from_cow_ = true;
+ } else {
+ populate_data_from_cow_ = false;
+ }
+
+ StartReadAhead();
+}
+
+bool Snapuserd::GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block, void* buffer) {
+ if (!lock->owns_lock()) {
+ SNAP_LOG(ERROR) << "GetRABuffer - Lock not held";
+ return false;
+ }
+ std::unordered_map<uint64_t, void*>::iterator it = read_ahead_buffer_map_.find(block);
+
+ // This will be true only for IO's generated as part of reading a root
+ // filesystem. IO's related to merge should always be in read-ahead cache.
+ if (it == read_ahead_buffer_map_.end()) {
+ return false;
+ }
+
+ // Theoretically, we can send the data back from the read-ahead buffer
+ // all the way to the kernel without memcpy. However, if the IO is
+ // un-aligned, the wrapper function will need to touch the read-ahead
+ // buffers and transitions will be bit more complicated.
+ memcpy(buffer, it->second, BLOCK_SZ);
+ return true;
+}
+
+// ========== State transition functions for read-ahead operations ===========
+
+bool Snapuserd::GetReadAheadPopulatedBuffer(uint64_t block, void* buffer) {
+ if (!read_ahead_feature_) {
+ return false;
+ }
+
+ {
+ std::unique_lock<std::mutex> lock(lock_);
+ if (io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE) {
+ return false;
+ }
+
+ if (io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS) {
+ return GetRABuffer(&lock, block, buffer);
+ }
+ }
+
+ {
+ // Read-ahead thread IO is in-progress. Wait for it to complete
+ std::unique_lock<std::mutex> lock(lock_);
+ while (!(io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE ||
+ io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS)) {
+ cv.wait(lock);
+ }
+
+ return GetRABuffer(&lock, block, buffer);
+ }
+}
+
+// This is invoked by read-ahead thread waiting for merge IO's
+// to complete
+bool Snapuserd::WaitForMergeToComplete() {
+ {
+ std::unique_lock<std::mutex> lock(lock_);
+ while (!(io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_BEGIN ||
+ io_state_ == READ_AHEAD_IO_TRANSITION::IO_TERMINATED)) {
+ cv.wait(lock);
+ }
+
+ if (io_state_ == READ_AHEAD_IO_TRANSITION::IO_TERMINATED) {
+ return false;
+ }
+
+ io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_IN_PROGRESS;
+ return true;
+ }
+}
+
+// This is invoked during the launch of worker threads. We wait
+// for read-ahead thread to by fully up before worker threads
+// are launched; else we will have a race between worker threads
+// and read-ahead thread specifically during re-construction.
+bool Snapuserd::WaitForReadAheadToStart() {
+ {
+ std::unique_lock<std::mutex> lock(lock_);
+ while (!(io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS ||
+ io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE)) {
+ cv.wait(lock);
+ }
+
+ if (io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE) {
+ return false;
+ }
+
+ return true;
+ }
+}
+
+// Invoked by worker threads when a sequence of merge operation
+// is complete notifying read-ahead thread to make forward
+// progress.
+void Snapuserd::StartReadAhead() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_BEGIN;
+ }
+
+ cv.notify_one();
+}
+
+void Snapuserd::MergeCompleted() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = READ_AHEAD_IO_TRANSITION::IO_TERMINATED;
+ }
+
+ cv.notify_one();
+}
+
+bool Snapuserd::ReadAheadIOCompleted(bool sync) {
+ if (sync) {
+ // Flush the entire buffer region
+ int ret = msync(mapped_addr_, total_mapped_addr_length_, MS_SYNC);
+ if (ret < 0) {
+ PLOG(ERROR) << "msync failed after ReadAheadIOCompleted: " << ret;
+ return false;
+ }
+
+ // Metadata and data are synced. Now, update the state.
+ // We need to update the state after flushing data; if there is a crash
+ // when read-ahead IO is in progress, the state of data in the COW file
+ // is unknown. kCowReadAheadDone acts as a checkpoint wherein the data
+ // in the scratch space is good and during next reboot, read-ahead thread
+ // can safely re-construct the data.
+ struct BufferState* ra_state = GetBufferState();
+ ra_state->read_ahead_state = kCowReadAheadDone;
+
+ ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
+ if (ret < 0) {
+ PLOG(ERROR) << "msync failed to flush Readahead completion state...";
+ return false;
+ }
+ }
+
+ // Notify the worker threads
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS;
+ }
+
+ cv.notify_all();
+ return true;
+}
+
+void Snapuserd::ReadAheadIOFailed() {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE;
+ }
+
+ cv.notify_all();
+}
+
+//========== End of state transition functions ====================
+
bool Snapuserd::IsChunkIdMetadata(chunk_t chunk) {
uint32_t stride = exceptions_per_area_ + 1;
lldiv_t divresult = lldiv(chunk, stride);
@@ -93,9 +270,9 @@
return;
}
- CowHeader header;
- reader_->GetHeader(&header);
- SNAP_LOG(INFO) << "Merge-status: Total-Merged-ops: " << header.num_merge_ops
+ struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+
+ SNAP_LOG(INFO) << "Merge-status: Total-Merged-ops: " << ch->num_merge_ops
<< " Total-data-ops: " << reader_->total_data_ops();
}
@@ -170,13 +347,18 @@
return false;
}
- CHECK(header.block_size == BLOCK_SZ);
+ if (!(header.block_size == BLOCK_SZ)) {
+ SNAP_LOG(ERROR) << "Invalid header block size found: " << header.block_size;
+ return false;
+ }
reader_->InitializeMerge();
SNAP_LOG(DEBUG) << "Merge-ops: " << header.num_merge_ops;
- writer_ = std::make_unique<CowWriter>(options);
- writer_->InitializeMerge(cow_fd_.get(), &header);
+ if (!MmapMetadata()) {
+ SNAP_LOG(ERROR) << "mmap failed";
+ return false;
+ }
// Initialize the iterator for reading metadata
cowop_riter_ = reader_->GetRevOpIter();
@@ -258,13 +440,16 @@
data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
}
+ int num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ);
std::optional<chunk_t> prev_id = {};
std::map<uint64_t, const CowOperation*> map;
std::set<uint64_t> dest_blocks;
size_t pending_copy_ops = exceptions_per_area_ - num_ops;
- SNAP_LOG(INFO) << " Processing copy-ops at Area: " << vec_.size()
- << " Number of replace/zero ops completed in this area: " << num_ops
- << " Pending copy ops for this area: " << pending_copy_ops;
+ uint64_t total_copy_ops = reader_->total_copy_ops();
+
+ SNAP_LOG(DEBUG) << " Processing copy-ops at Area: " << vec_.size()
+ << " Number of replace/zero ops completed in this area: " << num_ops
+ << " Pending copy ops for this area: " << pending_copy_ops;
while (!cowop_riter_->Done()) {
do {
const CowOperation* cow_op = &cowop_riter_->Get();
@@ -300,41 +485,20 @@
// Op-6: 15 -> 18
//
// Note that the blocks numbers are contiguous. Hence, all 6 copy
- // operations can potentially be batch merged. However, that will be
+ // operations can be batch merged. However, that will be
// problematic if we have a crash as block 20, 19, 18 would have
// been overwritten and hence subsequent recovery may end up with
// a silent data corruption when op-1, op-2 and op-3 are
// re-executed.
//
- // We will split these 6 operations into two batches viz:
- //
- // Batch-1:
- // ===================
- // Op-1: 20 -> 23
- // Op-2: 19 -> 22
- // Op-3: 18 -> 21
- // ===================
- //
- // Batch-2:
- // ==================
- // Op-4: 17 -> 20
- // Op-5: 16 -> 19
- // Op-6: 15 -> 18
- // ==================
- //
- // Now, merge sequence will look like:
- //
- // 1: Merge Batch-1 { op-1, op-2, op-3 }
- // 2: Update Metadata in COW File that op-1, op-2, op-3 merge is
- // done.
- // 3: Merge Batch-2
- // 4: Update Metadata in COW File that op-4, op-5, op-6 merge is
- // done.
- //
- // Note, that the order of block operations are still the same.
- // However, we have two batch merge operations. Any crash between
- // either of this sequence should be safe as each of these
- // batches are self-contained.
+ // To address the above problem, read-ahead thread will
+ // read all the 6 source blocks, cache them in the scratch
+ // space of the COW file. During merge, read-ahead
+ // thread will serve the blocks from the read-ahead cache.
+ // If there is a crash during merge; on subsequent reboot,
+ // read-ahead thread will recover the data from the
+ // scratch space and re-construct it thereby there
+ // is no loss of data.
//
//===========================================================
//
@@ -398,6 +562,7 @@
if (diff != 1) {
break;
}
+
if (dest_blocks.count(cow_op->new_block) || map.count(cow_op->source) > 0) {
break;
}
@@ -426,6 +591,9 @@
offset += sizeof(struct disk_exception);
num_ops += 1;
copy_ops++;
+ if (read_ahead_feature_) {
+ read_ahead_ops_.push_back(it->second);
+ }
SNAP_LOG(DEBUG) << num_ops << ":"
<< " Copy-op: "
@@ -448,11 +616,24 @@
SNAP_LOG(DEBUG) << "ReadMetadata() completed; Number of Areas: " << vec_.size();
}
- CHECK(pending_copy_ops == 0);
+ if (!(pending_copy_ops == 0)) {
+ SNAP_LOG(ERROR)
+ << "Invalid pending_copy_ops: expected: 0 found: " << pending_copy_ops;
+ return false;
+ }
pending_copy_ops = exceptions_per_area_;
}
data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
+ total_copy_ops -= 1;
+ /*
+ * Split the number of ops based on the size of read-ahead buffer
+ * region. We need to ensure that kernel doesn't issue IO on blocks
+ * which are not read by the read-ahead thread.
+ */
+ if (read_ahead_feature_ && (total_copy_ops % num_ra_ops_per_iter == 0)) {
+ data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
+ }
}
map.clear();
dest_blocks.clear();
@@ -470,6 +651,7 @@
chunk_vec_.shrink_to_fit();
vec_.shrink_to_fit();
+ read_ahead_ops_.shrink_to_fit();
// Sort the vector based on sectors as we need this during un-aligned access
std::sort(chunk_vec_.begin(), chunk_vec_.end(), compare);
@@ -484,9 +666,41 @@
// Total number of sectors required for creating dm-user device
num_sectors_ = ChunkToSector(data_chunk_id);
merge_initiated_ = false;
+ PrepareReadAhead();
+
return true;
}
+bool Snapuserd::MmapMetadata() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ if (header.major_version >= 2 && header.buffer_size > 0) {
+ total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
+ read_ahead_feature_ = true;
+ } else {
+ // mmap the first 4k page - older COW format
+ total_mapped_addr_length_ = BLOCK_SZ;
+ read_ahead_feature_ = false;
+ }
+
+ mapped_addr_ = mmap(NULL, total_mapped_addr_length_, PROT_READ | PROT_WRITE, MAP_SHARED,
+ cow_fd_.get(), 0);
+ if (mapped_addr_ == MAP_FAILED) {
+ SNAP_LOG(ERROR) << "mmap metadata failed";
+ return false;
+ }
+
+ return true;
+}
+
+void Snapuserd::UnmapBufferRegion() {
+ int ret = munmap(mapped_addr_, total_mapped_addr_length_);
+ if (ret < 0) {
+ SNAP_PLOG(ERROR) << "munmap failed";
+ }
+}
+
void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
unsigned int, const char* message) {
if (severity == android::base::ERROR) {
@@ -507,11 +721,28 @@
}
/*
- * Entry point to launch worker threads
+ * Entry point to launch threads
*/
bool Snapuserd::Start() {
std::vector<std::future<bool>> threads;
+ std::future<bool> ra_thread;
+ bool rathread = (read_ahead_feature_ && (read_ahead_ops_.size() > 0));
+ // Start the read-ahead thread and wait
+ // for it as the data has to be re-constructed
+ // from COW device.
+ if (rathread) {
+ ra_thread = std::async(std::launch::async, &ReadAheadThread::RunThread,
+ read_ahead_thread_.get());
+ if (!WaitForReadAheadToStart()) {
+ SNAP_LOG(ERROR) << "Failed to start Read-ahead thread...";
+ return false;
+ }
+
+ SNAP_LOG(INFO) << "Read-ahead thread started...";
+ }
+
+ // Launch worker threads
for (int i = 0; i < worker_threads_.size(); i++) {
threads.emplace_back(
std::async(std::launch::async, &WorkerThread::RunThread, worker_threads_[i].get()));
@@ -522,8 +753,69 @@
ret = t.get() && ret;
}
+ if (rathread) {
+ // Notify the read-ahead thread that all worker threads
+ // are done. We need this explicit notification when
+ // there is an IO failure or there was a switch
+ // of dm-user table; thus, forcing the read-ahead
+ // thread to wake up.
+ MergeCompleted();
+ ret = ret && ra_thread.get();
+ }
+
return ret;
}
+uint64_t Snapuserd::GetBufferMetadataOffset() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ size_t size = header.header_size + sizeof(BufferState);
+ return size;
+}
+
+/*
+ * Metadata for read-ahead is 16 bytes. For a 2 MB region, we will
+ * end up with 8k (2 PAGE) worth of metadata. Thus, a 2MB buffer
+ * region is split into:
+ *
+ * 1: 8k metadata
+ *
+ */
+size_t Snapuserd::GetBufferMetadataSize() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ size_t metadata_bytes = (header.buffer_size * sizeof(struct ScratchMetadata)) / BLOCK_SZ;
+ return metadata_bytes;
+}
+
+size_t Snapuserd::GetBufferDataOffset() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ return (header.header_size + GetBufferMetadataSize());
+}
+
+/*
+ * (2MB - 8K = 2088960 bytes) will be the buffer region to hold the data.
+ */
+size_t Snapuserd::GetBufferDataSize() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ size_t size = header.buffer_size - GetBufferMetadataSize();
+ return size;
+}
+
+struct BufferState* Snapuserd::GetBufferState() {
+ CowHeader header;
+ reader_->GetHeader(&header);
+
+ struct BufferState* ra_state =
+ reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
+ return ra_state;
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/snapuserd.h
index 87c5528..0a5ab50 100644
--- a/fs_mgr/libsnapshot/snapuserd.h
+++ b/fs_mgr/libsnapshot/snapuserd.h
@@ -17,8 +17,10 @@
#include <linux/types.h>
#include <stdint.h>
#include <stdlib.h>
+#include <sys/mman.h>
#include <bitset>
+#include <condition_variable>
#include <csignal>
#include <cstring>
#include <future>
@@ -29,6 +31,7 @@
#include <string>
#include <thread>
#include <unordered_map>
+#include <unordered_set>
#include <vector>
#include <android-base/file.h>
@@ -56,6 +59,35 @@
*/
static constexpr int NUM_THREADS_PER_PARTITION = 4;
+/*
+ * State transitions between worker threads and read-ahead
+ * threads.
+ *
+ * READ_AHEAD_BEGIN: Worker threads initiates the read-ahead
+ * thread to begin reading the copy operations
+ * for each bounded region.
+ *
+ * READ_AHEAD_IN_PROGRESS: When read ahead thread is in-flight
+ * and reading the copy operations.
+ *
+ * IO_IN_PROGRESS: Merge operation is in-progress by worker threads.
+ *
+ * IO_TERMINATED: When all the worker threads are done, request the
+ * read-ahead thread to terminate
+ *
+ * READ_AHEAD_FAILURE: If there are any IO failures when read-ahead
+ * thread is reading from COW device.
+ *
+ * The transition of each states is described in snapuserd_readahead.cpp
+ */
+enum class READ_AHEAD_IO_TRANSITION {
+ READ_AHEAD_BEGIN,
+ READ_AHEAD_IN_PROGRESS,
+ IO_IN_PROGRESS,
+ IO_TERMINATED,
+ READ_AHEAD_FAILURE,
+};
+
class BufferSink : public IByteSink {
public:
void Initialize(size_t size);
@@ -76,6 +108,47 @@
class Snapuserd;
+class ReadAheadThread {
+ public:
+ ReadAheadThread(const std::string& cow_device, const std::string& backing_device,
+ const std::string& misc_name, std::shared_ptr<Snapuserd> snapuserd);
+ bool RunThread();
+
+ private:
+ void InitializeIter();
+ bool IterDone();
+ void IterNext();
+ const CowOperation* GetIterOp();
+ void InitializeBuffer();
+
+ bool InitializeFds();
+ void CloseFds() {
+ cow_fd_ = {};
+ backing_store_fd_ = {};
+ }
+
+ bool ReadAheadIOStart();
+ void PrepareReadAhead(uint64_t* source_block, int* pending_ops, std::vector<uint64_t>& blocks);
+ bool ReconstructDataFromCow();
+ void CheckOverlap(const CowOperation* cow_op);
+
+ void* read_ahead_buffer_;
+ void* metadata_buffer_;
+ std::vector<const CowOperation*>::reverse_iterator read_ahead_iter_;
+ std::string cow_device_;
+ std::string backing_store_device_;
+ std::string misc_name_;
+
+ unique_fd cow_fd_;
+ unique_fd backing_store_fd_;
+
+ std::shared_ptr<Snapuserd> snapuserd_;
+
+ std::unordered_set<uint64_t> dest_blocks_;
+ std::unordered_set<uint64_t> source_blocks_;
+ bool overlap_;
+};
+
class WorkerThread {
public:
WorkerThread(const std::string& cow_device, const std::string& backing_device,
@@ -116,12 +189,16 @@
bool ProcessCopyOp(const CowOperation* cow_op);
bool ProcessZeroOp();
+ bool ReadFromBaseDevice(const CowOperation* cow_op);
+ bool GetReadAheadPopulatedBuffer(const CowOperation* cow_op);
+
// Merge related functions
bool ProcessMergeComplete(chunk_t chunk, void* buffer);
loff_t GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
int* unmerged_exceptions);
+
int GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
- int unmerged_exceptions);
+ int unmerged_exceptions, bool* copy_op, bool* commit);
sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
@@ -158,7 +235,10 @@
bool CommitMerge(int num_merge_ops);
void CloseFds() { cow_fd_ = {}; }
- void FreeResources() { worker_threads_.clear(); }
+ void FreeResources() {
+ worker_threads_.clear();
+ read_ahead_thread_ = nullptr;
+ }
size_t GetMetadataAreaSize() { return vec_.size(); }
void* GetExceptionBuffer(size_t i) { return vec_[i].get(); }
@@ -173,16 +253,47 @@
return p1.first < p2.first;
}
- private:
- std::vector<std::unique_ptr<WorkerThread>> worker_threads_;
+ void UnmapBufferRegion();
+ bool MmapMetadata();
+ // Read-ahead related functions
+ std::vector<const CowOperation*>& GetReadAheadOpsVec() { return read_ahead_ops_; }
+ std::unordered_map<uint64_t, void*>& GetReadAheadMap() { return read_ahead_buffer_map_; }
+ void* GetMappedAddr() { return mapped_addr_; }
+ bool IsReadAheadFeaturePresent() { return read_ahead_feature_; }
+ void PrepareReadAhead();
+ void StartReadAhead();
+ void MergeCompleted();
+ bool ReadAheadIOCompleted(bool sync);
+ void ReadAheadIOFailed();
+ bool WaitForMergeToComplete();
+ bool GetReadAheadPopulatedBuffer(uint64_t block, void* buffer);
+ bool ReconstructDataFromCow() { return populate_data_from_cow_; }
+ void ReconstructDataFromCowFinish() { populate_data_from_cow_ = false; }
+ bool WaitForReadAheadToStart();
+
+ uint64_t GetBufferMetadataOffset();
+ size_t GetBufferMetadataSize();
+ size_t GetBufferDataOffset();
+ size_t GetBufferDataSize();
+
+ // Final block to be merged in a given read-ahead buffer region
+ void SetFinalBlockMerged(uint64_t x) { final_block_merged_ = x; }
+ uint64_t GetFinalBlockMerged() { return final_block_merged_; }
+ // Total number of blocks to be merged in a given read-ahead buffer region
+ void SetTotalRaBlocksMerged(int x) { total_ra_blocks_merged_ = x; }
+ int GetTotalRaBlocksMerged() { return total_ra_blocks_merged_; }
+
+ private:
bool IsChunkIdMetadata(chunk_t chunk);
chunk_t GetNextAllocatableChunkId(chunk_t chunk_id);
+ bool GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block, void* buffer);
bool ReadMetadata();
sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
bool IsBlockAligned(int read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
+ struct BufferState* GetBufferState();
std::string cow_device_;
std::string backing_store_device_;
@@ -197,7 +308,6 @@
std::unique_ptr<ICowOpIter> cowop_iter_;
std::unique_ptr<ICowOpReverseIter> cowop_riter_;
std::unique_ptr<CowReader> reader_;
- std::unique_ptr<CowWriter> writer_;
// Vector of disk exception which is a
// mapping of old-chunk to new-chunk
@@ -208,6 +318,21 @@
std::vector<std::pair<sector_t, const CowOperation*>> chunk_vec_;
std::mutex lock_;
+ std::condition_variable cv;
+
+ void* mapped_addr_;
+ size_t total_mapped_addr_length_;
+
+ std::vector<std::unique_ptr<WorkerThread>> worker_threads_;
+ // Read-ahead related
+ std::unordered_map<uint64_t, void*> read_ahead_buffer_map_;
+ std::vector<const CowOperation*> read_ahead_ops_;
+ bool populate_data_from_cow_ = false;
+ bool read_ahead_feature_;
+ uint64_t final_block_merged_;
+ int total_ra_blocks_merged_ = 0;
+ READ_AHEAD_IO_TRANSITION io_state_;
+ std::unique_ptr<ReadAheadThread> read_ahead_thread_;
bool merge_initiated_ = false;
bool attached_ = false;
diff --git a/fs_mgr/libsnapshot/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd_client.cpp
index 16d02e4..41ab344 100644
--- a/fs_mgr/libsnapshot/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_client.cpp
@@ -113,7 +113,7 @@
bool SnapuserdClient::Sendmsg(const std::string& msg) {
LOG(DEBUG) << "Sendmsg: msg " << msg << " sockfd: " << sockfd_;
- ssize_t numBytesSent = TEMP_FAILURE_RETRY(send(sockfd_, msg.data(), msg.size(), 0));
+ ssize_t numBytesSent = TEMP_FAILURE_RETRY(send(sockfd_, msg.data(), msg.size(), MSG_NOSIGNAL));
if (numBytesSent < 0) {
PLOG(ERROR) << "Send failed";
return false;
diff --git a/fs_mgr/libsnapshot/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd_readahead.cpp
new file mode 100644
index 0000000..16d5919
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd_readahead.cpp
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd.h"
+
+#include <csignal>
+#include <optional>
+#include <set>
+
+#include <libsnapshot/snapuserd_client.h>
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
+#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
+
+/*
+ * Merging a copy operation involves the following flow:
+ *
+ * 1: dm-snapshot layer requests merge for a 4k block. dm-user sends the request
+ * to the daemon
+ * 2: daemon reads the source block
+ * 3: daemon copies the source data
+ * 4: IO completion sent back to dm-user (a switch from user space to kernel)
+ * 5: dm-snapshot merges the data to base device
+ * 6: dm-snapshot sends the merge-completion IO to dm-user
+ * 7: dm-user re-directs the merge completion IO to daemon (one more switch)
+ * 8: daemon updates the COW file about the completed merge request (a write syscall) and followed
+ * by a fysnc. 9: Send the IO completion back to dm-user
+ *
+ * The above sequence is a significant overhead especially when merging one 4k
+ * block at a time.
+ *
+ * Read-ahead layer will optimize the above path by reading the data from base
+ * device in the background so that merging thread can retrieve the data from
+ * the read-ahead cache. Additionally, syncing of merged data is deferred to
+ * read-ahead thread threadby the IO path is not bottlenecked.
+ *
+ * We create a scratch space of 2MB to store the read-ahead data in the COW
+ * device.
+ *
+ * +-----------------------+
+ * | Header (fixed) |
+ * +-----------------------+
+ * | Scratch space | <-- 2MB
+ * +-----------------------+
+ *
+ * Scratch space is as follows:
+ *
+ * +-----------------------+
+ * | Metadata | <- 4k page
+ * +-----------------------+
+ * | Metadata | <- 4k page
+ * +-----------------------+
+ * | |
+ * | Read-ahead data |
+ * | |
+ * +-----------------------+
+ *
+ * State transitions and communication between read-ahead thread and worker
+ * thread during merge:
+ * =====================================================================
+ *
+ * Worker Threads Read-Ahead thread
+ * ------------------------------------------------------------------
+ *
+ * |
+ * |
+ * --> -----------------READ_AHEAD_BEGIN------------->|
+ * | | | READ_AHEAD_IN_PROGRESS
+ * | WAIT |
+ * | | |
+ * | |<-----------------IO_IN_PROGRESS---------------
+ * | | |
+ * | | IO_IN_PRGRESS WAIT
+ * | | |
+ * |<--| |
+ * | |
+ * ------------------IO_TERMINATED--------------->|
+ * END
+ *
+ *
+ * ===================================================================
+ *
+ * Example:
+ *
+ * We have 6 copy operations to be executed in OTA and there is a overlap. Update-engine
+ * will write to COW file as follows:
+ *
+ * Op-1: 20 -> 23
+ * Op-2: 19 -> 22
+ * Op-3: 18 -> 21
+ * Op-4: 17 -> 20
+ * Op-5: 16 -> 19
+ * Op-6: 15 -> 18
+ *
+ * Read-ahead thread will read all the 6 source blocks and store the data in the
+ * scratch space. Metadata will contain the destination block numbers. Thus,
+ * scratch space will look something like this:
+ *
+ * +--------------+
+ * | Block 23 |
+ * | offset - 1 |
+ * +--------------+
+ * | Block 22 |
+ * | offset - 2 |
+ * +--------------+
+ * | Block 21 |
+ * | offset - 3 |
+ * +--------------+
+ * ...
+ * ...
+ * +--------------+
+ * | Data-Block 20| <-- offset - 1
+ * +--------------+
+ * | Data-Block 19| <-- offset - 2
+ * +--------------+
+ * | Data-Block 18| <-- offset - 3
+ * +--------------+
+ * ...
+ * ...
+ *
+ * ====================================================================
+ * IO Path:
+ *
+ * Read-ahead will serve the data to worker threads during merge only
+ * after metadata and data are persisted to the scratch space. Worker
+ * threads during merge will always retrieve the data from cache; if the
+ * cache is not populated, it will wait for the read-ahead thread to finish.
+ * Furthermore, the number of operations merged will by synced to the header
+ * only when all the blocks in the read-ahead cache are merged. In the above
+ * case, when all 6 operations are merged, COW Header is updated with
+ * num_merge_ops = 6.
+ *
+ * Merge resume after crash:
+ *
+ * Let's say we have a crash after 5 operations are merged. i.e. after
+ * Op-5: 16->19 is completed but before the Op-6 is merged. Thus, COW Header
+ * num_merge_ops will be 0 as the all the ops were not merged yet. During next
+ * reboot, read-ahead thread will re-construct the data in-memory from the
+ * scratch space; when merge resumes, Op-1 will be re-exectued. However,
+ * data will be served from read-ahead cache safely even though, block 20
+ * was over-written by Op-4.
+ *
+ */
+
+ReadAheadThread::ReadAheadThread(const std::string& cow_device, const std::string& backing_device,
+ const std::string& misc_name,
+ std::shared_ptr<Snapuserd> snapuserd) {
+ cow_device_ = cow_device;
+ backing_store_device_ = backing_device;
+ misc_name_ = misc_name;
+ snapuserd_ = snapuserd;
+}
+
+void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
+ if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(cow_op->source)) {
+ overlap_ = true;
+ }
+
+ dest_blocks_.insert(cow_op->source);
+ source_blocks_.insert(cow_op->new_block);
+}
+
+void ReadAheadThread::PrepareReadAhead(uint64_t* source_block, int* pending_ops,
+ std::vector<uint64_t>& blocks) {
+ int num_ops = *pending_ops;
+ int nr_consecutive = 0;
+
+ if (!IterDone() && num_ops) {
+ // Get the first block
+ const CowOperation* cow_op = GetIterOp();
+ *source_block = cow_op->source;
+ IterNext();
+ num_ops -= 1;
+ nr_consecutive = 1;
+ blocks.push_back(cow_op->new_block);
+
+ if (!overlap_) {
+ CheckOverlap(cow_op);
+ }
+
+ /*
+ * Find number of consecutive blocks working backwards.
+ */
+ while (!IterDone() && num_ops) {
+ const CowOperation* op = GetIterOp();
+ if (op->source != (*source_block - nr_consecutive)) {
+ break;
+ }
+ nr_consecutive += 1;
+ num_ops -= 1;
+ blocks.push_back(op->new_block);
+ IterNext();
+
+ if (!overlap_) {
+ CheckOverlap(op);
+ }
+ }
+ }
+}
+
+bool ReadAheadThread::ReconstructDataFromCow() {
+ std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+ read_ahead_buffer_map.clear();
+ loff_t metadata_offset = 0;
+ loff_t start_data_offset = snapuserd_->GetBufferDataOffset();
+ int num_ops = 0;
+ int total_blocks_merged = 0;
+
+ while (true) {
+ struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
+ (char*)metadata_buffer_ + metadata_offset);
+
+ // Done reading metadata
+ if (bm->new_block == 0 && bm->file_offset == 0) {
+ break;
+ }
+
+ loff_t buffer_offset = bm->file_offset - start_data_offset;
+ void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + buffer_offset);
+ read_ahead_buffer_map[bm->new_block] = bufptr;
+ num_ops += 1;
+ total_blocks_merged += 1;
+
+ metadata_offset += sizeof(struct ScratchMetadata);
+ }
+
+ // We are done re-constructing the mapping; however, we need to make sure
+ // all the COW operations to-be merged are present in the re-constructed
+ // mapping.
+ while (!IterDone()) {
+ const CowOperation* op = GetIterOp();
+ if (read_ahead_buffer_map.find(op->new_block) != read_ahead_buffer_map.end()) {
+ num_ops -= 1;
+ snapuserd_->SetFinalBlockMerged(op->new_block);
+ IterNext();
+ } else {
+ // Verify that we have covered all the ops which were re-constructed
+ // from COW device - These are the ops which are being
+ // re-constructed after crash.
+ if (!(num_ops == 0)) {
+ SNAP_LOG(ERROR) << "ReconstructDataFromCow failed. Not all ops recoverd "
+ << " Pending ops: " << num_ops;
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+ break;
+ }
+ }
+
+ snapuserd_->SetTotalRaBlocksMerged(total_blocks_merged);
+
+ snapuserd_->ReconstructDataFromCowFinish();
+
+ if (!snapuserd_->ReadAheadIOCompleted(true)) {
+ SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ SNAP_LOG(INFO) << "ReconstructDataFromCow success";
+ return true;
+}
+
+bool ReadAheadThread::ReadAheadIOStart() {
+ // Check if the data has to be constructed from the COW file.
+ // This will be true only once during boot up after a crash
+ // during merge.
+ if (snapuserd_->ReconstructDataFromCow()) {
+ return ReconstructDataFromCow();
+ }
+
+ std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+ read_ahead_buffer_map.clear();
+
+ int num_ops = (snapuserd_->GetBufferDataSize()) / BLOCK_SZ;
+ loff_t metadata_offset = 0;
+
+ struct ScratchMetadata* bm =
+ reinterpret_cast<struct ScratchMetadata*>((char*)metadata_buffer_ + metadata_offset);
+
+ bm->new_block = 0;
+ bm->file_offset = 0;
+
+ std::vector<uint64_t> blocks;
+
+ loff_t buffer_offset = 0;
+ loff_t offset = 0;
+ loff_t file_offset = snapuserd_->GetBufferDataOffset();
+ int total_blocks_merged = 0;
+ overlap_ = false;
+ dest_blocks_.clear();
+ source_blocks_.clear();
+
+ while (true) {
+ uint64_t source_block;
+ int linear_blocks;
+
+ PrepareReadAhead(&source_block, &num_ops, blocks);
+ linear_blocks = blocks.size();
+ if (linear_blocks == 0) {
+ // No more blocks to read
+ SNAP_LOG(DEBUG) << " Read-ahead completed....";
+ break;
+ }
+
+ // Get the first block in the consecutive set of blocks
+ source_block = source_block + 1 - linear_blocks;
+ size_t io_size = (linear_blocks * BLOCK_SZ);
+ num_ops -= linear_blocks;
+ total_blocks_merged += linear_blocks;
+
+ // Mark the block number as the one which will
+ // be the final block to be merged in this entire region.
+ // Read-ahead thread will get
+ // notified when this block is merged to make
+ // forward progress
+ snapuserd_->SetFinalBlockMerged(blocks.back());
+
+ while (linear_blocks) {
+ uint64_t new_block = blocks.back();
+ blocks.pop_back();
+ // Assign the mapping
+ void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + offset);
+ read_ahead_buffer_map[new_block] = bufptr;
+ offset += BLOCK_SZ;
+
+ bm = reinterpret_cast<struct ScratchMetadata*>((char*)metadata_buffer_ +
+ metadata_offset);
+ bm->new_block = new_block;
+ bm->file_offset = file_offset;
+
+ metadata_offset += sizeof(struct ScratchMetadata);
+ file_offset += BLOCK_SZ;
+
+ linear_blocks -= 1;
+ }
+
+ // Read from the base device consecutive set of blocks in one shot
+ if (!android::base::ReadFullyAtOffset(backing_store_fd_,
+ (char*)read_ahead_buffer_ + buffer_offset, io_size,
+ source_block * BLOCK_SZ)) {
+ SNAP_PLOG(ERROR) << "Copy-op failed. Read from backing store: " << backing_store_device_
+ << "at block :" << source_block << " buffer_offset : " << buffer_offset
+ << " io_size : " << io_size << " buf-addr : " << read_ahead_buffer_;
+
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ // This is important - explicitly set the contents to zero. This is used
+ // when re-constructing the data after crash. This indicates end of
+ // reading metadata contents when re-constructing the data
+ bm = reinterpret_cast<struct ScratchMetadata*>((char*)metadata_buffer_ + metadata_offset);
+ bm->new_block = 0;
+ bm->file_offset = 0;
+
+ buffer_offset += io_size;
+ }
+
+ snapuserd_->SetTotalRaBlocksMerged(total_blocks_merged);
+
+ // Flush the data only if we have a overlapping blocks in the region
+ if (!snapuserd_->ReadAheadIOCompleted(overlap_)) {
+ SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
+ snapuserd_->ReadAheadIOFailed();
+ return false;
+ }
+
+ return true;
+}
+
+bool ReadAheadThread::RunThread() {
+ if (!InitializeFds()) {
+ return false;
+ }
+
+ InitializeIter();
+ InitializeBuffer();
+
+ while (!IterDone()) {
+ if (!ReadAheadIOStart()) {
+ return false;
+ }
+
+ bool status = snapuserd_->WaitForMergeToComplete();
+
+ if (status && !snapuserd_->CommitMerge(snapuserd_->GetTotalRaBlocksMerged())) {
+ return false;
+ }
+
+ if (!status) break;
+ }
+
+ CloseFds();
+ SNAP_LOG(INFO) << " ReadAhead thread terminating....";
+ return true;
+}
+
+// Initialization
+bool ReadAheadThread::InitializeFds() {
+ backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
+ if (backing_store_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
+ return false;
+ }
+
+ cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
+ if (cow_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
+ return false;
+ }
+
+ return true;
+}
+
+void ReadAheadThread::InitializeIter() {
+ std::vector<const CowOperation*>& read_ahead_ops = snapuserd_->GetReadAheadOpsVec();
+ read_ahead_iter_ = read_ahead_ops.rbegin();
+}
+
+bool ReadAheadThread::IterDone() {
+ std::vector<const CowOperation*>& read_ahead_ops = snapuserd_->GetReadAheadOpsVec();
+ return read_ahead_iter_ == read_ahead_ops.rend();
+}
+
+void ReadAheadThread::IterNext() {
+ read_ahead_iter_++;
+}
+
+const CowOperation* ReadAheadThread::GetIterOp() {
+ return *read_ahead_iter_;
+}
+
+void ReadAheadThread::InitializeBuffer() {
+ void* mapped_addr = snapuserd_->GetMappedAddr();
+ // Map the scratch space region into memory
+ metadata_buffer_ =
+ static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferMetadataOffset());
+ read_ahead_buffer_ = static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd_server.cpp
index 64332d1..8339690 100644
--- a/fs_mgr/libsnapshot/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_server.cpp
@@ -81,7 +81,7 @@
: snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
bool SnapuserdServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
- ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), 0));
+ ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
if (ret < 0) {
PLOG(ERROR) << "Snapuserd:server: send() failed";
return false;
@@ -191,7 +191,7 @@
}
case DaemonOperations::DETACH: {
terminating_ = true;
- return Sendmsg(fd, "success");
+ return true;
}
default: {
LOG(ERROR) << "Received unknown message type from client";
@@ -209,10 +209,11 @@
}
handler->snapuserd()->CloseFds();
+ handler->snapuserd()->CheckMergeCompletionStatus();
+ handler->snapuserd()->UnmapBufferRegion();
auto misc_name = handler->misc_name();
LOG(INFO) << "Handler thread about to exit: " << misc_name;
- handler->snapuserd()->CheckMergeCompletionStatus();
{
std::lock_guard<std::mutex> lock(lock_);
@@ -377,7 +378,10 @@
}
bool SnapuserdServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
- CHECK(!handler->snapuserd()->IsAttached());
+ if (handler->snapuserd()->IsAttached()) {
+ LOG(ERROR) << "Handler already attached";
+ return false;
+ }
handler->snapuserd()->AttachControlDevice();
diff --git a/fs_mgr/libsnapshot/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd_worker.cpp
index 1002569..7e0f493 100644
--- a/fs_mgr/libsnapshot/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_worker.cpp
@@ -57,7 +57,9 @@
}
struct dm_user_header* BufferSink::GetHeaderPtr() {
- CHECK(sizeof(struct dm_user_header) <= buffer_size_);
+ if (!(sizeof(struct dm_user_header) <= buffer_size_)) {
+ return nullptr;
+ }
char* buf = reinterpret_cast<char*>(GetBufPtr());
struct dm_user_header* header = (struct dm_user_header*)(&(buf[0]));
return header;
@@ -111,7 +113,6 @@
// the header, zero out the remaining block.
void WorkerThread::ConstructKernelCowHeader() {
void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
- CHECK(buffer != nullptr);
memset(buffer, 0, BLOCK_SZ);
@@ -135,14 +136,14 @@
return true;
}
-// Start the copy operation. This will read the backing
-// block device which is represented by cow_op->source.
-bool WorkerThread::ProcessCopyOp(const CowOperation* cow_op) {
+bool WorkerThread::ReadFromBaseDevice(const CowOperation* cow_op) {
void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
- CHECK(buffer != nullptr);
-
- // Issue a single 4K IO. However, this can be optimized
- // if the successive blocks are contiguous.
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ReadFromBaseDevice: Failed to get payload buffer";
+ return false;
+ }
+ SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block
+ << " Source: " << cow_op->source;
if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ,
cow_op->source * BLOCK_SZ)) {
SNAP_PLOG(ERROR) << "Copy-op failed. Read from backing store: " << backing_store_device_
@@ -153,17 +154,51 @@
return true;
}
+bool WorkerThread::GetReadAheadPopulatedBuffer(const CowOperation* cow_op) {
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "GetReadAheadPopulatedBuffer: Failed to get payload buffer";
+ return false;
+ }
+
+ if (!snapuserd_->GetReadAheadPopulatedBuffer(cow_op->new_block, buffer)) {
+ return false;
+ }
+
+ return true;
+}
+
+// Start the copy operation. This will read the backing
+// block device which is represented by cow_op->source.
+bool WorkerThread::ProcessCopyOp(const CowOperation* cow_op) {
+ if (!GetReadAheadPopulatedBuffer(cow_op)) {
+ SNAP_LOG(DEBUG) << " GetReadAheadPopulatedBuffer failed..."
+ << " new_block: " << cow_op->new_block;
+ if (!ReadFromBaseDevice(cow_op)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
bool WorkerThread::ProcessZeroOp() {
// Zero out the entire block
void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
- CHECK(buffer != nullptr);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ProcessZeroOp: Failed to get payload buffer";
+ return false;
+ }
memset(buffer, 0, BLOCK_SZ);
return true;
}
bool WorkerThread::ProcessCowOp(const CowOperation* cow_op) {
- CHECK(cow_op != nullptr);
+ if (cow_op == nullptr) {
+ SNAP_LOG(ERROR) << "ProcessCowOp: Invalid cow_op";
+ return false;
+ }
switch (cow_op->type) {
case kCowReplaceOp: {
@@ -194,7 +229,8 @@
<< " Aligned sector: " << it->first;
if (!ProcessCowOp(it->second)) {
- SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size;
+ SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size
+ << " Aligned sector: " << it->first;
return -1;
}
@@ -239,7 +275,10 @@
it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr),
Snapuserd::compare);
- CHECK(it != chunk_vec.end());
+ if (!(it != chunk_vec.end())) {
+ SNAP_LOG(ERROR) << "ReadData: Sector " << sector << " not found in chunk_vec";
+ return -1;
+ }
// We didn't find the required sector; hence find the previous sector
// as lower_bound will gives us the value greater than
@@ -312,7 +351,10 @@
}
void* buffer = bufsink_.GetPayloadBuffer(size);
- CHECK(buffer != nullptr);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ZerofillDiskExceptions: Failed to get payload buffer";
+ return false;
+ }
memset(buffer, 0, size);
return true;
@@ -342,10 +384,17 @@
if (divresult.quot < vec.size()) {
size = exceptions_per_area_ * sizeof(struct disk_exception);
- CHECK(read_size == size);
+ if (read_size != size) {
+ SNAP_LOG(ERROR) << "ReadDiskExceptions: read_size: " << read_size
+ << " does not match with size: " << size;
+ return false;
+ }
void* buffer = bufsink_.GetPayloadBuffer(size);
- CHECK(buffer != nullptr);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "ReadDiskExceptions: Failed to get payload buffer of size: " << size;
+ return false;
+ }
memcpy(buffer, vec[divresult.quot].get(), size);
} else {
@@ -368,8 +417,19 @@
// Unmerged op by the kernel
if (merged_de->old_chunk != 0 || merged_de->new_chunk != 0) {
- CHECK(merged_de->old_chunk == cow_de->old_chunk);
- CHECK(merged_de->new_chunk == cow_de->new_chunk);
+ if (!(merged_de->old_chunk == cow_de->old_chunk)) {
+ SNAP_LOG(ERROR) << "GetMergeStartOffset: merged_de->old_chunk: "
+ << merged_de->old_chunk
+ << "cow_de->old_chunk: " << cow_de->old_chunk;
+ return -1;
+ }
+
+ if (!(merged_de->new_chunk == cow_de->new_chunk)) {
+ SNAP_LOG(ERROR) << "GetMergeStartOffset: merged_de->new_chunk: "
+ << merged_de->new_chunk
+ << "cow_de->new_chunk: " << cow_de->new_chunk;
+ return -1;
+ }
offset += sizeof(struct disk_exception);
*unmerged_exceptions += 1;
@@ -379,15 +439,15 @@
break;
}
- CHECK(!(*unmerged_exceptions == exceptions_per_area_));
-
SNAP_LOG(DEBUG) << "Unmerged_Exceptions: " << *unmerged_exceptions << " Offset: " << offset;
return offset;
}
int WorkerThread::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
- int unmerged_exceptions) {
+ int unmerged_exceptions, bool* copy_op, bool* commit) {
int merged_ops_cur_iter = 0;
+ std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+ *copy_op = false;
std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
// Find the operations which are merged in this cycle.
@@ -397,8 +457,15 @@
struct disk_exception* cow_de =
reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
- CHECK(merged_de->new_chunk == 0);
- CHECK(merged_de->old_chunk == 0);
+ if (!(merged_de->new_chunk == 0)) {
+ SNAP_LOG(ERROR) << "GetNumberOfMergedOps: Invalid new-chunk: " << merged_de->new_chunk;
+ return -1;
+ }
+
+ if (!(merged_de->old_chunk == 0)) {
+ SNAP_LOG(ERROR) << "GetNumberOfMergedOps: Invalid old-chunk: " << merged_de->old_chunk;
+ return -1;
+ }
if (cow_de->new_chunk != 0) {
merged_ops_cur_iter += 1;
@@ -406,13 +473,36 @@
auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
std::make_pair(ChunkToSector(cow_de->new_chunk), nullptr),
Snapuserd::compare);
- CHECK(it != chunk_vec.end());
- CHECK(it->first == ChunkToSector(cow_de->new_chunk));
+
+ if (!(it != chunk_vec.end())) {
+ SNAP_LOG(ERROR) << "Sector not found: " << ChunkToSector(cow_de->new_chunk);
+ return -1;
+ }
+
+ if (!(it->first == ChunkToSector(cow_de->new_chunk))) {
+ SNAP_LOG(ERROR) << "Invalid sector: " << ChunkToSector(cow_de->new_chunk);
+ return -1;
+ }
const CowOperation* cow_op = it->second;
- CHECK(cow_op != nullptr);
+ if (snapuserd_->IsReadAheadFeaturePresent() && cow_op->type == kCowCopyOp) {
+ *copy_op = true;
+ // Every single copy operation has to come from read-ahead
+ // cache.
+ if (read_ahead_buffer_map.find(cow_op->new_block) == read_ahead_buffer_map.end()) {
+ SNAP_LOG(ERROR)
+ << " Block: " << cow_op->new_block << " not found in read-ahead cache"
+ << " Source: " << cow_op->source;
+ return -1;
+ }
+ // If this is a final block merged in the read-ahead buffer
+ // region, notify the read-ahead thread to make forward
+ // progress
+ if (cow_op->new_block == snapuserd_->GetFinalBlockMerged()) {
+ *commit = true;
+ }
+ }
- CHECK(cow_op->new_block == cow_de->old_chunk);
// zero out to indicate that operation is merged.
cow_de->old_chunk = 0;
cow_de->new_chunk = 0;
@@ -422,7 +512,6 @@
//
// If the op was merged in previous cycle, we don't have
// to count them.
- CHECK(cow_de->new_chunk == 0);
break;
} else {
SNAP_LOG(ERROR) << "Error in merge operation. Found invalid metadata: "
@@ -442,25 +531,53 @@
bool WorkerThread::ProcessMergeComplete(chunk_t chunk, void* buffer) {
uint32_t stride = exceptions_per_area_ + 1;
const std::vector<std::unique_ptr<uint8_t[]>>& vec = snapuserd_->GetMetadataVec();
+ bool copy_op = false;
+ bool commit = false;
// ChunkID to vector index
lldiv_t divresult = lldiv(chunk, stride);
- CHECK(divresult.quot < vec.size());
+
+ if (!(divresult.quot < vec.size())) {
+ SNAP_LOG(ERROR) << "ProcessMergeComplete: Invalid chunk: " << chunk
+ << " Metadata-Index: " << divresult.quot << " Area-size: " << vec.size();
+ return false;
+ }
+
SNAP_LOG(DEBUG) << "ProcessMergeComplete: chunk: " << chunk
<< " Metadata-Index: " << divresult.quot;
int unmerged_exceptions = 0;
loff_t offset = GetMergeStartOffset(buffer, vec[divresult.quot].get(), &unmerged_exceptions);
- int merged_ops_cur_iter =
- GetNumberOfMergedOps(buffer, vec[divresult.quot].get(), offset, unmerged_exceptions);
+ if (offset < 0) {
+ SNAP_LOG(ERROR) << "GetMergeStartOffset failed: unmerged_exceptions: "
+ << unmerged_exceptions;
+ return false;
+ }
+
+ int merged_ops_cur_iter = GetNumberOfMergedOps(buffer, vec[divresult.quot].get(), offset,
+ unmerged_exceptions, ©_op, &commit);
// There should be at least one operation merged in this cycle
- CHECK(merged_ops_cur_iter > 0);
- if (!snapuserd_->CommitMerge(merged_ops_cur_iter)) {
+ if (!(merged_ops_cur_iter > 0)) {
+ SNAP_LOG(ERROR) << "Merge operation failed: " << merged_ops_cur_iter;
return false;
}
+ if (copy_op) {
+ if (commit) {
+ // Push the flushing logic to read-ahead thread so that merge thread
+ // can make forward progress. Sync will happen in the background
+ snapuserd_->StartReadAhead();
+ }
+ } else {
+ // Non-copy ops and all ops in older COW format
+ if (!snapuserd_->CommitMerge(merged_ops_cur_iter)) {
+ SNAP_LOG(ERROR) << "CommitMerge failed...";
+ return false;
+ }
+ }
+
SNAP_LOG(DEBUG) << "Merge success: " << merged_ops_cur_iter << "chunk: " << chunk;
return true;
}
@@ -516,8 +633,12 @@
// REQ_PREFLUSH flag set. Snapuser daemon doesn't have anything
// to flush per se; hence, just respond back with a success message.
if (header->sector == 0) {
- CHECK(header->len == 0);
- header->type = DM_USER_RESP_SUCCESS;
+ if (!(header->len == 0)) {
+ header->type = DM_USER_RESP_ERROR;
+ } else {
+ header->type = DM_USER_RESP_SUCCESS;
+ }
+
if (!WriteDmUserPayload(0)) {
return false;
}
@@ -527,33 +648,37 @@
std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
size_t remaining_size = header->len;
size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
- CHECK(read_size == BLOCK_SZ) << "DmuserWriteRequest: read_size: " << read_size;
- CHECK(header->sector > 0);
chunk_t chunk = SectorToChunk(header->sector);
auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
std::make_pair(header->sector, nullptr), Snapuserd::compare);
bool not_found = (it == chunk_vec.end() || it->first != header->sector);
- CHECK(not_found);
- void* buffer = bufsink_.GetPayloadBuffer(read_size);
- CHECK(buffer != nullptr);
- header->type = DM_USER_RESP_SUCCESS;
+ if (not_found) {
+ void* buffer = bufsink_.GetPayloadBuffer(read_size);
+ if (buffer == nullptr) {
+ SNAP_LOG(ERROR) << "DmuserWriteRequest: Failed to get payload buffer of size: "
+ << read_size;
+ header->type = DM_USER_RESP_ERROR;
+ } else {
+ header->type = DM_USER_RESP_SUCCESS;
- if (!ReadDmUserPayload(buffer, read_size)) {
- SNAP_LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk
- << "Sector: " << header->sector;
- header->type = DM_USER_RESP_ERROR;
- }
+ if (!ReadDmUserPayload(buffer, read_size)) {
+ SNAP_LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk
+ << "Sector: " << header->sector;
+ header->type = DM_USER_RESP_ERROR;
+ }
- if (header->type == DM_USER_RESP_SUCCESS && !ProcessMergeComplete(chunk, buffer)) {
- SNAP_LOG(ERROR) << "ProcessMergeComplete failed for chunk id: " << chunk
- << "Sector: " << header->sector;
- header->type = DM_USER_RESP_ERROR;
+ if (header->type == DM_USER_RESP_SUCCESS && !ProcessMergeComplete(chunk, buffer)) {
+ SNAP_LOG(ERROR) << "ProcessMergeComplete failed for chunk id: " << chunk
+ << "Sector: " << header->sector;
+ header->type = DM_USER_RESP_ERROR;
+ }
+ }
} else {
- SNAP_LOG(DEBUG) << "ProcessMergeComplete success for chunk id: " << chunk
- << "Sector: " << header->sector;
+ SNAP_LOG(ERROR) << "DmuserWriteRequest: Invalid sector received: header->sector";
+ header->type = DM_USER_RESP_ERROR;
}
if (!WriteDmUserPayload(0)) {
@@ -582,7 +707,6 @@
// never see multiple IO requests. Additionally this IO
// will always be a single 4k.
if (header->sector == 0) {
- CHECK(read_size == BLOCK_SZ) << " Sector 0 read request of size: " << read_size;
ConstructKernelCowHeader();
SNAP_LOG(DEBUG) << "Kernel header constructed";
} else {
@@ -613,12 +737,21 @@
}
}
+ // Just return the header if it is an error
+ if (header->type == DM_USER_RESP_ERROR) {
+ ret = 0;
+ }
+
// Daemon will not be terminated if there is any error. We will
// just send the error back to dm-user.
if (!WriteDmUserPayload(ret)) {
return false;
}
+ if (header->type == DM_USER_RESP_ERROR) {
+ break;
+ }
+
remaining_size -= ret;
offset += ret;
} while (remaining_size > 0);
diff --git a/fs_mgr/libsnapshot/update_engine/update_metadata.proto b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
index f31ee31..69d72e1 100644
--- a/fs_mgr/libsnapshot/update_engine/update_metadata.proto
+++ b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
@@ -75,6 +75,7 @@
repeated DynamicPartitionGroup groups = 1;
optional bool vabc_enabled = 3;
optional string vabc_compression_param = 4;
+ optional uint32 cow_version = 5;
}
message DeltaArchiveManifest {
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index 9adb6bd..eb2919b 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -184,6 +184,36 @@
{"androidboot.space", "sha256 5248 androidboot.nospace = nope"},
};
+bool CompareFlags(FstabEntry::FsMgrFlags& lhs, FstabEntry::FsMgrFlags& rhs) {
+ // clang-format off
+ return lhs.wait == rhs.wait &&
+ lhs.check == rhs.check &&
+ lhs.crypt == rhs.crypt &&
+ lhs.nonremovable == rhs.nonremovable &&
+ lhs.vold_managed == rhs.vold_managed &&
+ lhs.recovery_only == rhs.recovery_only &&
+ lhs.verify == rhs.verify &&
+ lhs.force_crypt == rhs.force_crypt &&
+ lhs.no_emulated_sd == rhs.no_emulated_sd &&
+ lhs.no_trim == rhs.no_trim &&
+ lhs.file_encryption == rhs.file_encryption &&
+ lhs.formattable == rhs.formattable &&
+ lhs.slot_select == rhs.slot_select &&
+ lhs.force_fde_or_fbe == rhs.force_fde_or_fbe &&
+ lhs.late_mount == rhs.late_mount &&
+ lhs.no_fail == rhs.no_fail &&
+ lhs.verify_at_boot == rhs.verify_at_boot &&
+ lhs.quota == rhs.quota &&
+ lhs.avb == rhs.avb &&
+ lhs.logical == rhs.logical &&
+ lhs.checkpoint_blk == rhs.checkpoint_blk &&
+ lhs.checkpoint_fs == rhs.checkpoint_fs &&
+ lhs.first_stage_mount == rhs.first_stage_mount &&
+ lhs.slot_select_other == rhs.slot_select_other &&
+ lhs.fs_verity == rhs.fs_verity;
+ // clang-format on
+}
+
} // namespace
TEST(fs_mgr, fs_mgr_parse_cmdline) {
@@ -291,19 +321,18 @@
EXPECT_EQ(i, fstab.size());
}
-// TODO(124837435): enable it later when it can pass TreeHugger.
-TEST(fs_mgr, DISABLED_ReadFstabFromFile_MountOptions) {
+TEST(fs_mgr, ReadFstabFromFile_MountOptions) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
std::string fstab_contents = R"fs(
-source / ext4 ro,barrier=1 wait,slotselect,avb
+source / ext4 ro,barrier=1 wait,avb
source /metadata ext4 noatime,nosuid,nodev,discard wait,formattable
source /data f2fs noatime,nosuid,nodev,discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier latemount,wait,check,fileencryption=ice,keydirectory=/metadata/vold/metadata_encryption,quota,formattable,sysfs_path=/sys/devices/platform/soc/1d84000.ufshc,reservedsize=128M
source /misc emmc defaults defaults
-source /vendor/firmware_mnt vfat ro,shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,context=u:object_r:firmware_file:s0 wait,slotselect
+source /vendor/firmware_mnt vfat ro,shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,context=u:object_r:firmware_file:s0 wait
source auto vfat defaults voldmanaged=usb:auto
source none swap defaults zramsize=1073741824,max_comp_streams=8
@@ -316,94 +345,75 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(11U, fstab.size());
+ ASSERT_LE(11U, fstab.size());
- EXPECT_EQ("/", fstab[0].mount_point);
- EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), fstab[0].flags);
- EXPECT_EQ("barrier=1", fstab[0].fs_options);
+ FstabEntry* entry = GetEntryForMountPoint(&fstab, "/");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), entry->flags);
+ EXPECT_EQ("barrier=1", entry->fs_options);
- EXPECT_EQ("/metadata", fstab[1].mount_point);
- EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), fstab[1].flags);
- EXPECT_EQ("discard", fstab[1].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "/metadata");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), entry->flags);
+ EXPECT_EQ("discard", entry->fs_options);
- EXPECT_EQ("/data", fstab[2].mount_point);
- EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), fstab[2].flags);
- EXPECT_EQ("discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier", fstab[2].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "/data");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), entry->flags);
+ EXPECT_EQ("discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier", entry->fs_options);
- EXPECT_EQ("/misc", fstab[3].mount_point);
- EXPECT_EQ(0U, fstab[3].flags);
- EXPECT_EQ("", fstab[3].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "/misc");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(0U, entry->flags);
+ EXPECT_EQ("", entry->fs_options);
- EXPECT_EQ("/vendor/firmware_mnt", fstab[4].mount_point);
- EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), fstab[4].flags);
+ entry = GetEntryForMountPoint(&fstab, "/vendor/firmware_mnt");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), entry->flags);
EXPECT_EQ(
"shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,"
"context=u:object_r:firmware_file:s0",
- fstab[4].fs_options);
+ entry->fs_options);
- EXPECT_EQ("auto", fstab[5].mount_point);
- EXPECT_EQ(0U, fstab[5].flags);
- EXPECT_EQ("", fstab[5].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "auto");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(0U, entry->flags);
+ EXPECT_EQ("", entry->fs_options);
- EXPECT_EQ("none", fstab[6].mount_point);
- EXPECT_EQ(0U, fstab[6].flags);
- EXPECT_EQ("", fstab[6].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "none");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(0U, entry->flags);
+ EXPECT_EQ("", entry->fs_options);
- EXPECT_EQ("none2", fstab[7].mount_point);
- EXPECT_EQ(static_cast<unsigned long>(MS_NODIRATIME | MS_REMOUNT | MS_BIND), fstab[7].flags);
- EXPECT_EQ("", fstab[7].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "none2");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(static_cast<unsigned long>(MS_NODIRATIME | MS_REMOUNT | MS_BIND), entry->flags);
+ EXPECT_EQ("", entry->fs_options);
- EXPECT_EQ("none3", fstab[8].mount_point);
- EXPECT_EQ(static_cast<unsigned long>(MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE), fstab[8].flags);
- EXPECT_EQ("", fstab[8].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "none3");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(static_cast<unsigned long>(MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE), entry->flags);
+ EXPECT_EQ("", entry->fs_options);
- EXPECT_EQ("none4", fstab[9].mount_point);
- EXPECT_EQ(static_cast<unsigned long>(MS_NOEXEC | MS_SHARED | MS_REC), fstab[9].flags);
- EXPECT_EQ("", fstab[9].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "none4");
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(static_cast<unsigned long>(MS_NOEXEC | MS_SHARED | MS_REC), entry->flags);
+ EXPECT_EQ("", entry->fs_options);
- EXPECT_EQ("none5", fstab[10].mount_point);
- EXPECT_EQ(0U, fstab[10].flags); // rw is the same as defaults
- EXPECT_EQ("", fstab[10].fs_options);
+ entry = GetEntryForMountPoint(&fstab, "none5");
+ ASSERT_NE(nullptr, entry);
+ // rw is the default.
+ EXPECT_EQ(0U, entry->flags);
+ EXPECT_EQ("", entry->fs_options);
}
-static bool CompareFlags(FstabEntry::FsMgrFlags& lhs, FstabEntry::FsMgrFlags& rhs) {
- // clang-format off
- return lhs.wait == rhs.wait &&
- lhs.check == rhs.check &&
- lhs.crypt == rhs.crypt &&
- lhs.nonremovable == rhs.nonremovable &&
- lhs.vold_managed == rhs.vold_managed &&
- lhs.recovery_only == rhs.recovery_only &&
- lhs.verify == rhs.verify &&
- lhs.force_crypt == rhs.force_crypt &&
- lhs.no_emulated_sd == rhs.no_emulated_sd &&
- lhs.no_trim == rhs.no_trim &&
- lhs.file_encryption == rhs.file_encryption &&
- lhs.formattable == rhs.formattable &&
- lhs.slot_select == rhs.slot_select &&
- lhs.force_fde_or_fbe == rhs.force_fde_or_fbe &&
- lhs.late_mount == rhs.late_mount &&
- lhs.no_fail == rhs.no_fail &&
- lhs.verify_at_boot == rhs.verify_at_boot &&
- lhs.quota == rhs.quota &&
- lhs.avb == rhs.avb &&
- lhs.logical == rhs.logical &&
- lhs.checkpoint_blk == rhs.checkpoint_blk &&
- lhs.checkpoint_fs == rhs.checkpoint_fs &&
- lhs.first_stage_mount == rhs.first_stage_mount &&
- lhs.slot_select_other == rhs.slot_select_other &&
- lhs.fs_verity == rhs.fs_verity;
- // clang-format on
-}
-
-// TODO(124837435): enable it later when it can pass TreeHugger.
-TEST(fs_mgr, DISABLED_ReadFstabFromFile_FsMgrFlags) {
+TEST(fs_mgr, ReadFstabFromFile_FsMgrFlags) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
std::string fstab_contents = R"fs(
source none0 swap defaults wait,check,nonremovable,recoveryonly,verifyatboot,verify
-source none1 swap defaults avb,noemulatedsd,notrim,formattable,slotselect,nofail
-source none2 swap defaults first_stage_mount,latemount,quota,logical,slotselect_other
+source none1 swap defaults avb,noemulatedsd,notrim,formattable,nofail
+source none2 swap defaults first_stage_mount,latemount,quota,logical
source none3 swap defaults checkpoint=block
source none4 swap defaults checkpoint=fs
source none5 swap defaults defaults
@@ -412,10 +422,10 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(6U, fstab.size());
+ ASSERT_LE(6U, fstab.size());
- auto entry = fstab.begin();
- EXPECT_EQ("none0", entry->mount_point);
+ FstabEntry* entry = GetEntryForMountPoint(&fstab, "none0");
+ ASSERT_NE(nullptr, entry);
{
FstabEntry::FsMgrFlags flags = {};
flags.wait = true;
@@ -426,50 +436,48 @@
flags.verify = true;
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
}
- entry++;
- EXPECT_EQ("none1", entry->mount_point);
+ entry = GetEntryForMountPoint(&fstab, "none1");
+ ASSERT_NE(nullptr, entry);
{
FstabEntry::FsMgrFlags flags = {};
flags.avb = true;
flags.no_emulated_sd = true;
flags.no_trim = true;
flags.formattable = true;
- flags.slot_select = true;
flags.no_fail = true;
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
}
- entry++;
- EXPECT_EQ("none2", entry->mount_point);
+ entry = GetEntryForMountPoint(&fstab, "none2");
+ ASSERT_NE(nullptr, entry);
{
FstabEntry::FsMgrFlags flags = {};
flags.first_stage_mount = true;
flags.late_mount = true;
flags.quota = true;
flags.logical = true;
- flags.slot_select_other = true;
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
}
- entry++;
- EXPECT_EQ("none3", entry->mount_point);
+ entry = GetEntryForMountPoint(&fstab, "none3");
+ ASSERT_NE(nullptr, entry);
{
FstabEntry::FsMgrFlags flags = {};
flags.checkpoint_blk = true;
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
}
- entry++;
- EXPECT_EQ("none4", entry->mount_point);
+ entry = GetEntryForMountPoint(&fstab, "none4");
+ ASSERT_NE(nullptr, entry);
{
FstabEntry::FsMgrFlags flags = {};
flags.checkpoint_fs = true;
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
}
- entry++;
- EXPECT_EQ("none5", entry->mount_point);
+ entry = GetEntryForMountPoint(&fstab, "none5");
+ ASSERT_NE(nullptr, entry);
{
FstabEntry::FsMgrFlags flags = {};
EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
@@ -491,7 +499,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(3U, fstab.size());
+ ASSERT_LE(3U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("none0", entry->mount_point);
@@ -561,7 +569,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
flags.crypt = true;
@@ -585,7 +593,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(4U, fstab.size());
+ ASSERT_LE(4U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
flags.vold_managed = true;
@@ -626,7 +634,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(2U, fstab.size());
+ ASSERT_LE(2U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -652,7 +660,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(2U, fstab.size());
+ ASSERT_LE(2U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -682,7 +690,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(6U, fstab.size());
+ ASSERT_LE(6U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -728,7 +736,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("none0", entry->mount_point);
@@ -751,7 +759,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("none0", entry->mount_point);
@@ -775,7 +783,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
flags.file_encryption = true;
@@ -797,7 +805,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(2U, fstab.size());
+ ASSERT_LE(2U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -825,7 +833,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(4U, fstab.size());
+ ASSERT_LE(4U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -863,7 +871,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(4U, fstab.size());
+ ASSERT_LE(4U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -901,7 +909,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(4U, fstab.size());
+ ASSERT_LE(4U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -938,7 +946,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(2U, fstab.size());
+ ASSERT_LE(2U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("none0", entry->mount_point);
@@ -967,7 +975,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("none0", entry->mount_point);
@@ -989,7 +997,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("adiantum", entry->metadata_encryption);
@@ -1006,7 +1014,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("aes-256-xts:wrappedkey_v0", entry->metadata_encryption);
@@ -1027,7 +1035,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(1U, fstab.size());
+ ASSERT_LE(1U, fstab.size());
auto entry = fstab.begin();
EXPECT_EQ("none0", entry->mount_point);
@@ -1053,7 +1061,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(4U, fstab.size());
+ ASSERT_LE(4U, fstab.size());
auto entry = fstab.begin();
@@ -1114,7 +1122,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(7U, fstab.size());
+ ASSERT_LE(7U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
diff --git a/init/README.md b/init/README.md
index 4a262c9..75dc328 100644
--- a/init/README.md
+++ b/init/README.md
@@ -277,6 +277,8 @@
CLD_EXITED or an status other than '0', reboot the system with the target specified in
_target_. _target_ takes the same format as the parameter to sys.powerctl. This is particularly
intended to be used with the `exec_start` builtin for any must-have checks during boot.
+ A service being stopped by init (e.g. using the `stop` or `class_reset` commands) is not
+ considered a failure for the purpose of this setting.
`restart_period <seconds>`
> If a non-oneshot service exits, it will be restarted at its start time plus
diff --git a/init/README.ueventd.md b/init/README.ueventd.md
index 3ffca88..d22f68f 100644
--- a/init/README.ueventd.md
+++ b/init/README.ueventd.md
@@ -130,6 +130,10 @@
Will launch `/vendor/bin/led_coeffs.bin` as the system user instead of serving the default firmware
for `/devices/leds/red/firmware/coeffs.bin`.
+The `devpath` argument may include asterisks (`*`) to match multiple paths. For example, the string
+`/dev/*/red` will match `/dev/leds/red` as well as `/dev/lights/red`. The pattern matching follows
+the rules of the fnmatch() function.
+
Ueventd will provide the uevent `DEVPATH` and `FIRMWARE` to this external program on the environment
via environment variables with the same names. Ueventd will use the string written to stdout as the
new name of the firmware to load. It will still look for the new firmware in the list of firmware
diff --git a/init/firmware_handler.cpp b/init/firmware_handler.cpp
index ba7e6bd..bdc2922 100644
--- a/init/firmware_handler.cpp
+++ b/init/firmware_handler.cpp
@@ -17,6 +17,7 @@
#include "firmware_handler.h"
#include <fcntl.h>
+#include <fnmatch.h>
#include <glob.h>
#include <pwd.h>
#include <signal.h>
@@ -46,6 +47,20 @@
namespace android {
namespace init {
+namespace {
+bool PrefixMatch(const std::string& pattern, const std::string& path) {
+ return android::base::StartsWith(path, pattern);
+}
+
+bool FnMatch(const std::string& pattern, const std::string& path) {
+ return fnmatch(pattern.c_str(), path.c_str(), 0) == 0;
+}
+
+bool EqualMatch(const std::string& pattern, const std::string& path) {
+ return pattern == path;
+}
+} // namespace
+
static void LoadFirmware(const std::string& firmware, const std::string& root, int fw_fd,
size_t fw_size, int loading_fd, int data_fd) {
// Start transfer.
@@ -66,6 +81,22 @@
return access("/dev/.booting", F_OK) == 0;
}
+ExternalFirmwareHandler::ExternalFirmwareHandler(std::string devpath, uid_t uid,
+ std::string handler_path)
+ : devpath(std::move(devpath)), uid(uid), handler_path(std::move(handler_path)) {
+ auto wildcard_position = this->devpath.find('*');
+ if (wildcard_position != std::string::npos) {
+ if (wildcard_position == this->devpath.length() - 1) {
+ this->devpath.pop_back();
+ match = std::bind(PrefixMatch, this->devpath, std::placeholders::_1);
+ } else {
+ match = std::bind(FnMatch, this->devpath, std::placeholders::_1);
+ }
+ } else {
+ match = std::bind(EqualMatch, this->devpath, std::placeholders::_1);
+ }
+}
+
FirmwareHandler::FirmwareHandler(std::vector<std::string> firmware_directories,
std::vector<ExternalFirmwareHandler> external_firmware_handlers)
: firmware_directories_(std::move(firmware_directories)),
@@ -160,7 +191,7 @@
std::string FirmwareHandler::GetFirmwarePath(const Uevent& uevent) const {
for (const auto& external_handler : external_firmware_handlers_) {
- if (external_handler.devpath == uevent.path) {
+ if (external_handler.match(uevent.path)) {
LOG(INFO) << "Launching external firmware handler '" << external_handler.handler_path
<< "' for devpath: '" << uevent.path << "' firmware: '" << uevent.firmware
<< "'";
diff --git a/init/firmware_handler.h b/init/firmware_handler.h
index 8b758ae..3c35b1f 100644
--- a/init/firmware_handler.h
+++ b/init/firmware_handler.h
@@ -30,11 +30,13 @@
namespace init {
struct ExternalFirmwareHandler {
- ExternalFirmwareHandler(std::string devpath, uid_t uid, std::string handler_path)
- : devpath(std::move(devpath)), uid(uid), handler_path(std::move(handler_path)) {}
+ ExternalFirmwareHandler(std::string devpath, uid_t uid, std::string handler_path);
+
std::string devpath;
uid_t uid;
std::string handler_path;
+
+ std::function<bool(const std::string&)> match;
};
class FirmwareHandler : public UeventHandler {
diff --git a/init/firmware_handler_test.cpp b/init/firmware_handler_test.cpp
index 5124a6f..f6e75b0 100644
--- a/init/firmware_handler_test.cpp
+++ b/init/firmware_handler_test.cpp
@@ -103,6 +103,23 @@
return 0;
}
+TEST(firmware_handler, Matching) {
+ ExternalFirmwareHandler h("/dev/path/a.bin", getuid(), "/test");
+ ASSERT_TRUE(h.match("/dev/path/a.bin"));
+ ASSERT_FALSE(h.match("/dev/path/a.bi"));
+
+ h = ExternalFirmwareHandler("/dev/path/a.*", getuid(), "/test");
+ ASSERT_TRUE(h.match("/dev/path/a.bin"));
+ ASSERT_TRUE(h.match("/dev/path/a.bix"));
+ ASSERT_FALSE(h.match("/dev/path/b.bin"));
+
+ h = ExternalFirmwareHandler("/dev/*/a.bin", getuid(), "/test");
+ ASSERT_TRUE(h.match("/dev/path/a.bin"));
+ ASSERT_TRUE(h.match("/dev/other/a.bin"));
+ ASSERT_FALSE(h.match("/dev/other/c.bin"));
+ ASSERT_FALSE(h.match("/dev/path/b.bin"));
+}
+
} // namespace init
} // namespace android
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 5cde167..78e5b60 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -327,6 +327,19 @@
LOG(INFO) << "Copied ramdisk prop to " << dest;
}
+ // If "/force_debuggable" is present, the second-stage init will use a userdebug
+ // sepolicy and load adb_debug.prop to allow adb root, if the device is unlocked.
+ if (access("/force_debuggable", F_OK) == 0) {
+ std::error_code ec; // to invoke the overloaded copy_file() that won't throw.
+ if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) ||
+ !fs::copy_file("/userdebug_plat_sepolicy.cil", kDebugRamdiskSEPolicy, ec)) {
+ LOG(ERROR) << "Failed to setup debug ramdisk";
+ } else {
+ // setenv for second-stage init to read above kDebugRamdisk* files.
+ setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
+ }
+ }
+
if (ForceNormalBoot(cmdline, bootconfig)) {
mkdir("/first_stage_ramdisk", 0755);
// SwitchRoot() must be called with a mount point as the target, so we bind mount the
@@ -337,28 +350,6 @@
SwitchRoot("/first_stage_ramdisk");
}
- std::string force_debuggable("/force_debuggable");
- std::string adb_debug_prop("/adb_debug.prop");
- std::string userdebug_sepolicy("/userdebug_plat_sepolicy.cil");
- if (IsRecoveryMode()) {
- // Update these file paths since we didn't switch root
- force_debuggable.insert(0, "/first_stage_ramdisk");
- adb_debug_prop.insert(0, "/first_stage_ramdisk");
- userdebug_sepolicy.insert(0, "/first_stage_ramdisk");
- }
- // If this file is present, the second-stage init will use a userdebug sepolicy
- // and load adb_debug.prop to allow adb root, if the device is unlocked.
- if (access(force_debuggable.c_str(), F_OK) == 0) {
- std::error_code ec; // to invoke the overloaded copy_file() that won't throw.
- if (!fs::copy_file(adb_debug_prop, kDebugRamdiskProp, ec) ||
- !fs::copy_file(userdebug_sepolicy, kDebugRamdiskSEPolicy, ec)) {
- LOG(ERROR) << "Failed to setup debug ramdisk";
- } else {
- // setenv for second-stage init to read above kDebugRamdisk* files.
- setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
- }
- }
-
if (!DoFirstStageMount(!created_devices)) {
LOG(FATAL) << "Failed to mount required partitions early ...";
}
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index 3faf430..a733839 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -331,6 +331,12 @@
if (devices.empty()) {
return true;
}
+ // excluding overlays
+ for (auto iter = devices.begin(); iter != devices.end(); ) {
+ if (*iter=="overlay") iter = devices.erase(iter);
+ else iter++;
+ }
+
return block_dev_init_.InitDevices(std::move(devices));
}
@@ -542,6 +548,11 @@
continue;
}
+ if (current->fs_type == "overlay") {
+ ++current;
+ continue;
+ }
+
// Skip raw partition entries such as boot, dtbo, etc.
// Having emmc fstab entries allows us to probe current->vbmeta_partition
// in InitDevices() when they are AVB chained partitions.
@@ -591,6 +602,13 @@
};
MapScratchPartitionIfNeeded(&fstab_, init_devices);
+ for (auto current = fstab_.begin(); current != fstab_.end(); ) {
+ if (current->fs_type == "overlay") {
+ fs_mgr_overlayfs_mount_fstab_entry(current->lowerdir, current->mount_point);
+ }
+ ++current;
+ }
+
fs_mgr_overlayfs_mount_all(&fstab_);
return true;
diff --git a/init/property_service.cpp b/init/property_service.cpp
index c2eb73c..ff9da42 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -94,6 +94,12 @@
namespace android {
namespace init {
+constexpr auto FINGERPRINT_PROP = "ro.build.fingerprint";
+constexpr auto LEGACY_FINGERPRINT_PROP = "ro.build.legacy.fingerprint";
+constexpr auto ID_PROP = "ro.build.id";
+constexpr auto LEGACY_ID_PROP = "ro.build.legacy.id";
+constexpr auto VBMETA_DIGEST_PROP = "ro.boot.vbmeta.digest";
+constexpr auto DIGEST_SIZE_USED = 8;
static bool persistent_properties_loaded = false;
@@ -857,15 +863,33 @@
}
}
-// If the ro.build.fingerprint property has not been set, derive it from constituent pieces
-static void property_derive_build_fingerprint() {
- std::string build_fingerprint = GetProperty("ro.build.fingerprint", "");
- if (!build_fingerprint.empty()) {
+static void property_initialize_build_id() {
+ std::string build_id = GetProperty(ID_PROP, "");
+ if (!build_id.empty()) {
return;
}
+ std::string legacy_build_id = GetProperty(LEGACY_ID_PROP, "");
+ std::string vbmeta_digest = GetProperty(VBMETA_DIGEST_PROP, "");
+ if (vbmeta_digest.size() < DIGEST_SIZE_USED) {
+ LOG(ERROR) << "vbmeta digest size too small " << vbmeta_digest;
+ // Still try to set the id field in the unexpected case.
+ build_id = legacy_build_id;
+ } else {
+ // Derive the ro.build.id by appending the vbmeta digest to the base value.
+ build_id = legacy_build_id + "." + vbmeta_digest.substr(0, DIGEST_SIZE_USED);
+ }
+
+ std::string error;
+ auto res = PropertySet(ID_PROP, build_id, &error);
+ if (res != PROP_SUCCESS) {
+ LOG(ERROR) << "Failed to set " << ID_PROP << " to " << build_id;
+ }
+}
+
+static std::string ConstructBuildFingerprint(bool legacy) {
const std::string UNKNOWN = "unknown";
- build_fingerprint = GetProperty("ro.product.brand", UNKNOWN);
+ std::string build_fingerprint = GetProperty("ro.product.brand", UNKNOWN);
build_fingerprint += '/';
build_fingerprint += GetProperty("ro.product.name", UNKNOWN);
build_fingerprint += '/';
@@ -873,7 +897,10 @@
build_fingerprint += ':';
build_fingerprint += GetProperty("ro.build.version.release_or_codename", UNKNOWN);
build_fingerprint += '/';
- build_fingerprint += GetProperty("ro.build.id", UNKNOWN);
+
+ std::string build_id =
+ legacy ? GetProperty(LEGACY_ID_PROP, UNKNOWN) : GetProperty(ID_PROP, UNKNOWN);
+ build_fingerprint += build_id;
build_fingerprint += '/';
build_fingerprint += GetProperty("ro.build.version.incremental", UNKNOWN);
build_fingerprint += ':';
@@ -881,13 +908,49 @@
build_fingerprint += '/';
build_fingerprint += GetProperty("ro.build.tags", UNKNOWN);
- LOG(INFO) << "Setting property 'ro.build.fingerprint' to '" << build_fingerprint << "'";
+ return build_fingerprint;
+}
+
+// Derive the legacy build fingerprint if we overwrite the build id at runtime.
+static void property_derive_legacy_build_fingerprint() {
+ std::string legacy_build_fingerprint = GetProperty(LEGACY_FINGERPRINT_PROP, "");
+ if (!legacy_build_fingerprint.empty()) {
+ return;
+ }
+
+ // The device doesn't have a legacy build id, skipping the legacy fingerprint.
+ std::string legacy_build_id = GetProperty(LEGACY_ID_PROP, "");
+ if (legacy_build_id.empty()) {
+ return;
+ }
+
+ legacy_build_fingerprint = ConstructBuildFingerprint(true /* legacy fingerprint */);
+ LOG(INFO) << "Setting property '" << LEGACY_FINGERPRINT_PROP << "' to '"
+ << legacy_build_fingerprint << "'";
std::string error;
- uint32_t res = PropertySet("ro.build.fingerprint", build_fingerprint, &error);
+ uint32_t res = PropertySet(LEGACY_FINGERPRINT_PROP, legacy_build_fingerprint, &error);
if (res != PROP_SUCCESS) {
- LOG(ERROR) << "Error setting property 'ro.build.fingerprint': err=" << res << " (" << error
- << ")";
+ LOG(ERROR) << "Error setting property '" << LEGACY_FINGERPRINT_PROP << "': err=" << res
+ << " (" << error << ")";
+ }
+}
+
+// If the ro.build.fingerprint property has not been set, derive it from constituent pieces
+static void property_derive_build_fingerprint() {
+ std::string build_fingerprint = GetProperty("ro.build.fingerprint", "");
+ if (!build_fingerprint.empty()) {
+ return;
+ }
+
+ build_fingerprint = ConstructBuildFingerprint(false /* legacy fingerprint */);
+ LOG(INFO) << "Setting property '" << FINGERPRINT_PROP << "' to '" << build_fingerprint << "'";
+
+ std::string error;
+ uint32_t res = PropertySet(FINGERPRINT_PROP, build_fingerprint, &error);
+ if (res != PROP_SUCCESS) {
+ LOG(ERROR) << "Error setting property '" << FINGERPRINT_PROP << "': err=" << res << " ("
+ << error << ")";
}
}
@@ -1035,7 +1098,9 @@
}
property_initialize_ro_product_props();
+ property_initialize_build_id();
property_derive_build_fingerprint();
+ property_derive_legacy_build_fingerprint();
property_initialize_ro_cpu_abilist();
update_sys_usb_config();
@@ -1165,68 +1230,28 @@
constexpr auto ANDROIDBOOT_PREFIX = "androidboot."sv;
-// emulator specific, should be removed once emulator is migrated to
-// bootconfig, see b/182291166.
-static std::string RemapEmulatorPropertyName(const std::string_view qemu_key) {
- if (StartsWith(qemu_key, "dalvik."sv) || StartsWith(qemu_key, "opengles."sv) ||
- StartsWith(qemu_key, "config."sv)) {
- return std::string(qemu_key);
- } else if (qemu_key == "uirenderer"sv) {
- return "debug.hwui.renderer"s;
- } else if (qemu_key == "media.ccodec"sv) {
- return "debug.stagefright.ccodec"s;
- } else {
- return "qemu."s + std::string(qemu_key);
- }
-}
-
static void ProcessKernelCmdline() {
ImportKernelCmdline([&](const std::string& key, const std::string& value) {
- constexpr auto qemu_prefix = "qemu."sv;
-
if (StartsWith(key, ANDROIDBOOT_PREFIX)) {
InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value);
- } else if (StartsWith(key, qemu_prefix)) {
- InitPropertySet("ro.kernel." + key, value); // emulator specific, deprecated
-
- // emulator specific, should be retired once emulator migrates to
- // androidboot.
- const auto new_name =
- RemapEmulatorPropertyName(std::string_view(key).substr(qemu_prefix.size()));
- if (!new_name.empty()) {
- InitPropertySet("ro.boot." + new_name, value);
- }
- } else if (key == "qemu") {
- // emulator specific, should be retired once emulator migrates to
- // androidboot.
- InitPropertySet("ro.boot." + key, value);
- } else if (key == "android.bootanim" && value == "0") {
- // emulator specific, should be retired once emulator migrates to
- // androidboot.
- InitPropertySet("ro.boot.debug.sf.nobootanimation", "1");
- } else if (key == "android.checkjni") {
- // emulator specific, should be retired once emulator migrates to
- // androidboot.
- std::string value_bool;
- if (value == "0") {
- value_bool = "false";
- } else if (value == "1") {
- value_bool = "true";
- } else {
- value_bool = value;
- }
- InitPropertySet("ro.boot.dalvik.vm.checkjni", value_bool);
}
});
}
+// bootconfig does not allow to populate `key=value` simultaneously with
+// `key.subkey=value` which does not work with the existing code for
+// `hardware` (e.g. we want both `ro.boot.hardware=value` and
+// `ro.boot.hardware.sku=value`) and for `qemu` (Android Stidio Emulator
+// specific).
+static bool IsAllowedBootconfigKey(const std::string_view key) {
+ return (key == "hardware"sv) || (key == "qemu"sv);
+}
+
static void ProcessBootconfig() {
ImportBootconfig([&](const std::string& key, const std::string& value) {
if (StartsWith(key, ANDROIDBOOT_PREFIX)) {
InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value);
- } else if (key == "hardware") {
- // "hardware" in bootconfig replaces "androidboot.hardware" kernel
- // cmdline parameter
+ } else if (IsAllowedBootconfigKey(key)) {
InitPropertySet("ro.boot." + key, value);
}
});
diff --git a/init/property_service_test.cpp b/init/property_service_test.cpp
index c6dcfa2..ac6b7b2 100644
--- a/init/property_service_test.cpp
+++ b/init/property_service_test.cpp
@@ -23,6 +23,7 @@
#include <android-base/properties.h>
#include <android-base/scopeguard.h>
+#include <android-base/strings.h>
#include <gtest/gtest.h>
using android::base::GetProperty;
@@ -90,5 +91,39 @@
EXPECT_FALSE(SetProperty("sys.powerctl", "reboot,userspace"));
}
+TEST(property_service, check_fingerprint_with_legacy_build_id) {
+ std::string legacy_build_id = GetProperty("ro.build.legacy.id", "");
+ if (legacy_build_id.empty()) {
+ GTEST_SKIP() << "Skipping test, legacy build id isn't set.";
+ }
+
+ std::string vbmeta_digest = GetProperty("ro.boot.vbmeta.digest", "");
+ ASSERT_GE(vbmeta_digest.size(), 8u);
+ std::string build_id = GetProperty("ro.boot.build.id", "");
+ // Check that the build id is constructed with the prefix of vbmeta digest
+ std::string expected_build_id = legacy_build_id + "." + vbmeta_digest.substr(0, 8);
+ ASSERT_EQ(expected_build_id, build_id);
+ // Check that the fingerprint is constructed with the expected format.
+ std::string fingerprint = GetProperty("ro.build.fingerprint", "");
+ std::vector<std::string> fingerprint_fields = {
+ GetProperty("ro.product.brand", ""),
+ "/",
+ GetProperty("ro.product.name", ""),
+ "/",
+ GetProperty("ro.product.device", ""),
+ ":",
+ GetProperty("ro.build.version.release_or_codename", ""),
+ "/",
+ expected_build_id,
+ "/",
+ GetProperty("ro.build.version.incremental", ""),
+ ":",
+ GetProperty("ro.build.type", ""),
+ "/",
+ GetProperty("ro.build.tags", "")};
+
+ ASSERT_EQ(android::base::Join(fingerprint_fields, ""), fingerprint);
+}
+
} // namespace init
} // namespace android
diff --git a/init/reboot.cpp b/init/reboot.cpp
index d9acee5..ab0e48e 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -450,10 +450,22 @@
// zram is able to use backing device on top of a loopback device.
// In order to unmount /data successfully, we have to kill the loopback device first
-#define ZRAM_DEVICE "/dev/block/zram0"
-#define ZRAM_RESET "/sys/block/zram0/reset"
-#define ZRAM_BACK_DEV "/sys/block/zram0/backing_dev"
+#define ZRAM_DEVICE "/dev/block/zram0"
+#define ZRAM_RESET "/sys/block/zram0/reset"
+#define ZRAM_BACK_DEV "/sys/block/zram0/backing_dev"
+#define ZRAM_INITSTATE "/sys/block/zram0/initstate"
static Result<void> KillZramBackingDevice() {
+ std::string zram_initstate;
+ if (!android::base::ReadFileToString(ZRAM_INITSTATE, &zram_initstate)) {
+ return ErrnoError() << "Failed to read " << ZRAM_INITSTATE;
+ }
+
+ zram_initstate.erase(zram_initstate.length() - 1);
+ if (zram_initstate == "0") {
+ LOG(INFO) << "Zram has not been swapped on";
+ return {};
+ }
+
if (access(ZRAM_BACK_DEV, F_OK) != 0 && errno == ENOENT) {
LOG(INFO) << "No zram backing device configured";
return {};
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 35a96f9..42d3023 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -240,25 +240,25 @@
}
// Use precompiled sepolicy only when all corresponding hashes are equal.
- // plat_sepolicy is always checked, while system_ext and product are checked only when they
- // exist.
std::vector<std::pair<std::string, std::string>> sepolicy_hashes{
{"/system/etc/selinux/plat_sepolicy_and_mapping.sha256",
precompiled_sepolicy + ".plat_sepolicy_and_mapping.sha256"},
+ {"/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256",
+ precompiled_sepolicy + ".system_ext_sepolicy_and_mapping.sha256"},
+ {"/product/etc/selinux/product_sepolicy_and_mapping.sha256",
+ precompiled_sepolicy + ".product_sepolicy_and_mapping.sha256"},
};
- if (access("/system_ext/etc/selinux/system_ext_sepolicy.cil", F_OK) == 0) {
- sepolicy_hashes.emplace_back(
- "/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256",
- precompiled_sepolicy + ".system_ext_sepolicy_and_mapping.sha256");
- }
-
- if (access("/product/etc/selinux/product_sepolicy.cil", F_OK) == 0) {
- sepolicy_hashes.emplace_back("/product/etc/selinux/product_sepolicy_and_mapping.sha256",
- precompiled_sepolicy + ".product_sepolicy_and_mapping.sha256");
- }
-
for (const auto& [actual_id_path, precompiled_id_path] : sepolicy_hashes) {
+ // Both of them should exist or both of them shouldn't exist.
+ if (access(actual_id_path.c_str(), R_OK) != 0) {
+ if (access(precompiled_id_path.c_str(), R_OK) == 0) {
+ return Error() << precompiled_id_path << " exists but " << actual_id_path
+ << " doesn't";
+ }
+ continue;
+ }
+
std::string actual_id;
if (!ReadFirstLine(actual_id_path.c_str(), &actual_id)) {
return ErrnoError() << "Failed to read " << actual_id_path;
diff --git a/init/service.cpp b/init/service.cpp
index 836dc47..5af81bf 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -194,6 +194,8 @@
<< ") process group...";
int max_processes = 0;
int r;
+
+ flags_ |= SVC_STOPPING;
if (signal == SIGTERM) {
r = killProcessGroupOnce(proc_attr_.uid, pid_, signal, &max_processes);
} else {
@@ -277,7 +279,8 @@
f(siginfo);
}
- if ((siginfo.si_code != CLD_EXITED || siginfo.si_status != 0) && on_failure_reboot_target_) {
+ if ((siginfo.si_code != CLD_EXITED || siginfo.si_status != 0) && on_failure_reboot_target_ &&
+ !(flags_ & SVC_STOPPING)) {
LOG(ERROR) << "Service with 'reboot_on_failure' option failed, shutting down system.";
trigger_shutdown(*on_failure_reboot_target_);
}
@@ -287,7 +290,7 @@
if (flags_ & SVC_TEMPORARY) return;
pid_ = 0;
- flags_ &= (~SVC_RUNNING);
+ flags_ &= ~(SVC_RUNNING | SVC_STOPPING);
start_order_ = 0;
// Oneshot processes go into the disabled state on exit,
@@ -411,7 +414,8 @@
bool disabled = (flags_ & (SVC_DISABLED | SVC_RESET));
// Starting a service removes it from the disabled or reset state and
// immediately takes it out of the restarting state if it was in there.
- flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
+ flags_ &= (~(SVC_DISABLED | SVC_RESTARTING | SVC_RESET | SVC_RESTART | SVC_DISABLED_START |
+ SVC_STOPPING));
// Running processes require no additional work --- if they're in the
// process of exiting, we've ensured that they will immediately restart
@@ -460,7 +464,11 @@
scon = *result;
}
- if (!IsDefaultMountNamespaceReady() && name_ != "apexd") {
+ // APEXd is always started in the "current" namespace because it is the process to set up
+ // the current namespace.
+ const bool is_apexd = args_[0] == "/system/bin/apexd";
+
+ if (!IsDefaultMountNamespaceReady() && !is_apexd) {
// If this service is started before APEXes and corresponding linker configuration
// get available, mark it as pre-apexd one. Note that this marking is
// permanent. So for example, if the service is re-launched (e.g., due
diff --git a/init/service.h b/init/service.h
index 043555f..89b1f09 100644
--- a/init/service.h
+++ b/init/service.h
@@ -54,6 +54,7 @@
// should not be killed during shutdown
#define SVC_TEMPORARY 0x1000 // This service was started by 'exec' and should be removed from the
// service list once it is reaped.
+#define SVC_STOPPING 0x2000 // service is being stopped by init
#define NR_SVC_SUPP_GIDS 12 // twelve supplementary groups
diff --git a/init/ueventd_parser_test.cpp b/init/ueventd_parser_test.cpp
index 4e63ba5..c5aa9e3 100644
--- a/init/ueventd_parser_test.cpp
+++ b/init/ueventd_parser_test.cpp
@@ -154,6 +154,9 @@
external_firmware_handler devpath root handler_path
external_firmware_handler /devices/path/firmware/something001.bin system /vendor/bin/firmware_handler.sh
external_firmware_handler /devices/path/firmware/something002.bin radio "/vendor/bin/firmware_handler.sh --has --arguments"
+external_firmware_handler /devices/path/firmware/* root "/vendor/bin/firmware_handler.sh"
+external_firmware_handler /devices/path/firmware/something* system "/vendor/bin/firmware_handler.sh"
+external_firmware_handler /devices/path/*/firmware/something*.bin radio "/vendor/bin/firmware_handler.sh"
)";
auto external_firmware_handlers = std::vector<ExternalFirmwareHandler>{
@@ -172,6 +175,21 @@
AID_RADIO,
"/vendor/bin/firmware_handler.sh --has --arguments",
},
+ {
+ "/devices/path/firmware/",
+ AID_ROOT,
+ "/vendor/bin/firmware_handler.sh",
+ },
+ {
+ "/devices/path/firmware/something",
+ AID_SYSTEM,
+ "/vendor/bin/firmware_handler.sh",
+ },
+ {
+ "/devices/path/*/firmware/something*.bin",
+ AID_RADIO,
+ "/vendor/bin/firmware_handler.sh",
+ },
};
TestUeventdFile(ueventd_file, {{}, {}, {}, {}, external_firmware_handlers});
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 0f3763c..68b21c6 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -362,9 +362,10 @@
source_stem: "bindings",
local_include_dirs: ["include"],
bindgen_flags: [
- "--whitelist-function", "multiuser_get_app_id",
- "--whitelist-function", "multiuser_get_user_id",
- "--whitelist-function", "multiuser_get_uid",
- "--whitelist-var", "AID_USER_OFFSET",
+ "--allowlist-function", "multiuser_get_app_id",
+ "--allowlist-function", "multiuser_get_uid",
+ "--allowlist-function", "multiuser_get_user_id",
+ "--allowlist-var", "AID_KEYSTORE",
+ "--allowlist-var", "AID_USER_OFFSET",
],
}
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index 7489281..8f22d89 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -128,6 +128,8 @@
#define AID_EXT_OBB_RW 1079 /* GID for OBB directories on external storage */
#define AID_CONTEXT_HUB 1080 /* GID for access to the Context Hub */
#define AID_VIRTMANAGER 1081 /* VirtManager daemon */
+#define AID_ARTD 1082 /* ART Service daemon */
+#define AID_UWB 1083 /* UWB subsystem */
/* Changes to this file must be made in AOSP, *not* in internal branches. */
#define AID_SHELL 2000 /* adb and debug shell user */
diff --git a/libprocessgroup/cgrouprc/Android.bp b/libprocessgroup/cgrouprc/Android.bp
index 0cbe0cc..7522cfe 100644
--- a/libprocessgroup/cgrouprc/Android.bp
+++ b/libprocessgroup/cgrouprc/Android.bp
@@ -28,7 +28,9 @@
// defined below. The static library is built for tests.
vendor_available: false,
native_bridge_supported: true,
- llndk_stubs: "libcgrouprc.llndk",
+ llndk: {
+ symbol_file: "libcgrouprc.map.txt",
+ },
srcs: [
"cgroup_controller.cpp",
"cgroup_file.cpp",
@@ -59,12 +61,3 @@
},
},
}
-
-llndk_library {
- name: "libcgrouprc.llndk",
- symbol_file: "libcgrouprc.map.txt",
- native_bridge_supported: true,
- export_include_dirs: [
- "include",
- ],
-}
diff --git a/libprocessgroup/sched_policy.cpp b/libprocessgroup/sched_policy.cpp
index c51ee61..1a4196a 100644
--- a/libprocessgroup/sched_policy.cpp
+++ b/libprocessgroup/sched_policy.cpp
@@ -159,10 +159,9 @@
if (!controller.IsUsable()) return -1;
- if (!controller.GetTaskGroup(tid, &subgroup)) {
- LOG(ERROR) << "Failed to find cgroup for tid " << tid;
+ if (!controller.GetTaskGroup(tid, &subgroup))
return -1;
- }
+
return 0;
}
@@ -174,11 +173,16 @@
std::string group;
if (schedboost_enabled()) {
if ((getCGroupSubsys(tid, "schedtune", group) < 0) &&
- (getCGroupSubsys(tid, "cpu", group) < 0))
- return -1;
+ (getCGroupSubsys(tid, "cpu", group) < 0)) {
+ LOG(ERROR) << "Failed to find cpu cgroup for tid " << tid;
+ return -1;
+ }
}
if (group.empty() && cpusets_enabled()) {
- if (getCGroupSubsys(tid, "cpuset", group) < 0) return -1;
+ if (getCGroupSubsys(tid, "cpuset", group) < 0) {
+ LOG(ERROR) << "Failed to find cpuset cgroup for tid " << tid;
+ return -1;
+ }
}
// TODO: replace hardcoded directories
diff --git a/libstats/pull_rust/Android.bp b/libstats/pull_rust/Android.bp
index 354c7b3..2a89e29 100644
--- a/libstats/pull_rust/Android.bp
+++ b/libstats/pull_rust/Android.bp
@@ -25,10 +25,10 @@
source_stem: "bindings",
bindgen_flags: [
"--size_t-is-usize",
- "--whitelist-function=AStatsEventList_addStatsEvent",
- "--whitelist-function=AStatsEvent_.*",
- "--whitelist-function=AStatsManager_.*",
- "--whitelist-var=AStatsManager_.*",
+ "--allowlist-function=AStatsEventList_addStatsEvent",
+ "--allowlist-function=AStatsEvent_.*",
+ "--allowlist-function=AStatsManager_.*",
+ "--allowlist-var=AStatsManager_.*",
],
target: {
android: {
diff --git a/libstats/push_compat/Android.bp b/libstats/push_compat/Android.bp
index 4b2f40e..819066e 100644
--- a/libstats/push_compat/Android.bp
+++ b/libstats/push_compat/Android.bp
@@ -55,7 +55,7 @@
export_header_lib_headers: [
"libstatssocket_headers",
],
- static_libs: ["libgtest_prod"],
+ header_libs: ["libgtest_prod_headers"],
apex_available: ["com.android.resolv"],
min_sdk_version: "29",
}
diff --git a/libsync/Android.bp b/libsync/Android.bp
index 540a246..99c88cf 100644
--- a/libsync/Android.bp
+++ b/libsync/Android.bp
@@ -42,7 +42,9 @@
recovery_available: true,
native_bridge_supported: true,
defaults: ["libsync_defaults"],
- llndk_stubs: "libsync.llndk",
+ llndk: {
+ symbol_file: "libsync.map.txt",
+ },
stubs: {
symbol_file: "libsync.map.txt",
versions: [
@@ -51,12 +53,6 @@
},
}
-llndk_library {
- name: "libsync.llndk",
- symbol_file: "libsync.map.txt",
- export_include_dirs: ["include"],
-}
-
cc_test {
name: "sync-unit-tests",
shared_libs: ["libsync"],
diff --git a/libutils/Android.bp b/libutils/Android.bp
index 6201569..13e4c02 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -141,6 +141,7 @@
"Errors.cpp",
"FileMap.cpp",
"JenkinsHash.cpp",
+ "LightRefBase.cpp",
"NativeHandle.cpp",
"Printer.cpp",
"RefBase.cpp",
@@ -274,12 +275,6 @@
}
cc_fuzz {
- name: "libutils_fuzz_stopwatch",
- defaults: ["libutils_fuzz_defaults"],
- srcs: ["StopWatch_fuzz.cpp"],
-}
-
-cc_fuzz {
name: "libutils_fuzz_refbase",
defaults: ["libutils_fuzz_defaults"],
srcs: ["RefBase_fuzz.cpp"],
diff --git a/libutils/LightRefBase.cpp b/libutils/LightRefBase.cpp
new file mode 100644
index 0000000..e08ffec
--- /dev/null
+++ b/libutils/LightRefBase.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "LightRefBase"
+
+#include <utils/LightRefBase.h>
+
+#include <log/log.h>
+
+namespace android {
+
+void LightRefBase_reportIncStrongRequireStrongFailed(const void* thiz) {
+ LOG_ALWAYS_FATAL("incStrongRequireStrong() called on %p which isn't already owned", thiz);
+}
+
+} // namespace android
diff --git a/libutils/README b/libutils/README
deleted file mode 100644
index 01741e0..0000000
--- a/libutils/README
+++ /dev/null
@@ -1,289 +0,0 @@
-Android Utility Function Library
-================================
-
-
-If you need a feature that is native to Linux but not present on other
-platforms, construct a platform-dependent implementation that shares
-the Linux interface. That way the actual device runs as "light" as
-possible.
-
-If that isn't feasible, create a system-independent interface and hide
-the details.
-
-The ultimate goal is *not* to create a super-duper platform abstraction
-layer. The goal is to provide an optimized solution for Linux with
-reasonable implementations for other platforms.
-
-
-
-Resource overlay
-================
-
-
-Introduction
-------------
-
-Overlay packages are special .apk files which provide no code but
-additional resource values (and possibly new configurations) for
-resources in other packages. When an application requests resources,
-the system will return values from either the application's original
-package or any associated overlay package. Any redirection is completely
-transparent to the calling application.
-
-Resource values have the following precedence table, listed in
-descending precedence.
-
- * overlay package, matching config (eg res/values-en-land)
-
- * original package, matching config
-
- * overlay package, no config (eg res/values)
-
- * original package, no config
-
-During compilation, overlay packages are differentiated from regular
-packages by passing the -o flag to aapt.
-
-
-Background
-----------
-
-This section provides generic background material on resources in
-Android.
-
-
-How resources are bundled in .apk files
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Android .apk files are .zip files, usually housing .dex code,
-certificates and resources, though packages containing resources but
-no code are possible. Resources can be divided into the following
-categories; a `configuration' indicates a set of phone language, display
-density, network operator, etc.
-
- * assets: uncompressed, raw files packaged as part of an .apk and
- explicitly referenced by filename. These files are
- independent of configuration.
-
- * res/drawable: bitmap or xml graphics. Each file may have different
- values depending on configuration.
-
- * res/values: integers, strings, etc. Each resource may have different
- values depending on configuration.
-
-Resource meta information and information proper is stored in a binary
-format in a named file resources.arsc, bundled as part of the .apk.
-
-Resource IDs and lookup
-~~~~~~~~~~~~~~~~~~~~~~~
-During compilation, the aapt tool gathers application resources and
-generates a resources.arsc file. Each resource name is assigned an
-integer ID 0xppttiii (translated to a symbolic name via R.java), where
-
- * pp: corresponds to the package namespace (details below).
-
- * tt: corresponds to the resource type (string, int, etc). Every
- resource of the same type within the same package has the same
- tt value, but depending on available types, the actual numerical
- value may be different between packages.
-
- * iiii: sequential number, assigned in the order resources are found.
-
-Resource values are specified paired with a set of configuration
-constraints (the default being the empty set), eg res/values-sv-port
-which imposes restrictions on language (Swedish) and display orientation
-(portrait). During lookup, every constraint set is matched against the
-current configuration, and the value corresponding to the best matching
-constraint set is returned (ResourceTypes.{h,cpp}).
-
-Parsing of resources.arsc is handled by ResourceTypes.cpp; this utility
-is governed by AssetManager.cpp, which tracks loaded resources per
-process.
-
-Assets are looked up by path and filename in AssetManager.cpp. The path
-to resources in res/drawable are located by ResourceTypes.cpp and then
-handled like assets by AssetManager.cpp. Other resources are handled
-solely by ResourceTypes.cpp.
-
-Package ID as namespace
-~~~~~~~~~~~~~~~~~~~~~~~
-The pp part of a resource ID defines a namespace. Android currently
-defines two namespaces:
-
- * 0x01: system resources (pre-installed in framework-res.apk)
-
- * 0x7f: application resources (bundled in the application .apk)
-
-ResourceTypes.cpp supports package IDs between 0x01 and 0x7f
-(inclusive); values outside this range are invalid.
-
-Each running (Dalvik) process is assigned a unique instance of
-AssetManager, which in turn keeps a forest structure of loaded
-resource.arsc files. Normally, this forest is structured as follows,
-where mPackageMap is the internal vector employed in ResourceTypes.cpp.
-
-mPackageMap[0x00] -> system package
-mPackageMap[0x01] -> NULL
-mPackageMap[0x02] -> NULL
-...
-mPackageMap[0x7f - 2] -> NULL
-mPackageMap[0x7f - 1] -> application package
-
-
-
-The resource overlay extension
-------------------------------
-
-The resource overlay mechanism aims to (partly) shadow and extend
-existing resources with new values for defined and new configurations.
-Technically, this is achieved by adding resource-only packages (called
-overlay packages) to existing resource namespaces, like so:
-
-mPackageMap[0x00] -> system package -> system overlay package
-mPackageMap[0x01] -> NULL
-mPackageMap[0x02] -> NULL
-...
-mPackageMap[0x7f - 2] -> NULL
-mPackageMap[0x7f - 1] -> application package -> overlay 1 -> overlay 2
-
-The use of overlay resources is completely transparent to
-applications; no additional resource identifiers are introduced, only
-configuration/value pairs. Any number of overlay packages may be loaded
-at a time; overlay packages are agnostic to what they target -- both
-system and application resources are fair game.
-
-The package targeted by an overlay package is called the target or
-original package.
-
-Resource overlay operates on symbolic resources names. Hence, to
-override the string/str1 resources in a package, the overlay package
-would include a resource also named string/str1. The end user does not
-have to worry about the numeric resources IDs assigned by aapt, as this
-is resolved automatically by the system.
-
-As of this writing, the use of resource overlay has not been fully
-explored. Until it has, only OEMs are trusted to use resource overlay.
-For this reason, overlay packages must reside in /system/overlay.
-
-
-Resource ID mapping
-~~~~~~~~~~~~~~~~~~~
-Resource identifiers must be coherent within the same namespace (ie
-PackageGroup in ResourceTypes.cpp). Calling applications will refer to
-resources using the IDs defined in the original package, but there is no
-guarantee aapt has assigned the same ID to the corresponding resource in
-an overlay package. To translate between the two, a resource ID mapping
-{original ID -> overlay ID} is created during package installation
-(PackageManagerService.java) and used during resource lookup. The
-mapping is stored in /data/resource-cache, with a @idmap file name
-suffix.
-
-The idmap file format is documented in a separate section, below.
-
-
-Package management
-~~~~~~~~~~~~~~~~~~
-Packages are managed by the PackageManagerService. Addition and removal
-of packages are monitored via the inotify framework, exposed via
-android.os.FileObserver.
-
-During initialization of a Dalvik process, ActivityThread.java requests
-the process' AssetManager (by proxy, via AssetManager.java and JNI)
-to load a list of packages. This list includes overlay packages, if
-present.
-
-When a target package or a corresponding overlay package is installed,
-the target package's process is stopped and a new idmap is generated.
-This is similar to how applications are stopped when their packages are
-upgraded.
-
-
-Creating overlay packages
--------------------------
-
-Overlay packages should contain no code, define (some) resources with
-the same type and name as in the original package, and be compiled with
-the -o flag passed to aapt.
-
-The aapt -o flag instructs aapt to create an overlay package.
-Technically, this means the package will be assigned package id 0x00.
-
-There are no restrictions on overlay packages names, though the naming
-convention <original.package.name>.overlay.<name> is recommended.
-
-
-Example overlay package
-~~~~~~~~~~~~~~~~~~~~~~~
-
-To overlay the resource bool/b in package com.foo.bar, to be applied
-when the display is in landscape mode, create a new package with
-no source code and a single .xml file under res/values-land, with
-an entry for bool/b. Compile with aapt -o and place the results in
-/system/overlay by adding the following to Android.mk:
-
-LOCAL_AAPT_FLAGS := -o com.foo.bar
-LOCAL_MODULE_PATH := $(TARGET_OUT)/overlay
-
-
-The ID map (idmap) file format
-------------------------------
-
-The idmap format is designed for lookup performance. However, leading
-and trailing undefined overlay values are discarded to reduce the memory
-footprint.
-
-
-idmap grammar
-~~~~~~~~~~~~~
-All atoms (names in square brackets) are uint32_t integers. The
-idmap-magic constant spells "idmp" in ASCII. Offsets are given relative
-to the data_header, not to the beginning of the file.
-
-map := header data
-header := idmap-magic <crc32-original-pkg> <crc32-overlay-pkg>
-idmap-magic := <0x706d6469>
-data := data_header type_block+
-data_header := <m> header_block{m}
-header_block := <0> | <type_block_offset>
-type_block := <n> <id_offset> entry{n}
-entry := <resource_id_in_target_package>
-
-
-idmap example
-~~~~~~~~~~~~~
-Given a pair of target and overlay packages with CRC sums 0x216a8fe2
-and 0x6b9beaec, each defining the following resources
-
-Name Target package Overlay package
-string/str0 0x7f010000 -
-string/str1 0x7f010001 0x7f010000
-string/str2 0x7f010002 -
-string/str3 0x7f010003 0x7f010001
-string/str4 0x7f010004 -
-bool/bool0 0x7f020000 -
-integer/int0 0x7f030000 0x7f020000
-integer/int1 0x7f030001 -
-
-the corresponding resource map is
-
-0x706d6469 0x216a8fe2 0x6b9beaec 0x00000003 \
-0x00000004 0x00000000 0x00000009 0x00000003 \
-0x00000001 0x7f010000 0x00000000 0x7f010001 \
-0x00000001 0x00000000 0x7f020000
-
-or, formatted differently
-
-0x706d6469 # magic: all idmap files begin with this constant
-0x216a8fe2 # CRC32 of the resources.arsc file in the original package
-0x6b9beaec # CRC32 of the resources.arsc file in the overlay package
-0x00000003 # header; three types (string, bool, integer) in the target package
-0x00000004 # header_block for type 0 (string) is located at offset 4
-0x00000000 # no bool type exists in overlay package -> no header_block
-0x00000009 # header_block for type 2 (integer) is located at offset 9
-0x00000003 # header_block for string; overlay IDs span 3 elements
-0x00000001 # the first string in target package is entry 1 == offset
-0x7f010000 # target 0x7f01001 -> overlay 0x7f010000
-0x00000000 # str2 not defined in overlay package
-0x7f010001 # target 0x7f010003 -> overlay 0x7f010001
-0x00000001 # header_block for integer; overlay IDs span 1 element
-0x00000000 # offset == 0
-0x7f020000 # target 0x7f030000 -> overlay 0x7f020000
diff --git a/libutils/SharedBuffer_test.cpp b/libutils/SharedBuffer_test.cpp
index 3f960d2..1d6317f 100644
--- a/libutils/SharedBuffer_test.cpp
+++ b/libutils/SharedBuffer_test.cpp
@@ -32,10 +32,25 @@
EXPECT_DEATH(android::SharedBuffer::alloc(SIZE_MAX - sizeof(android::SharedBuffer)), "");
}
-TEST(SharedBufferTest, alloc_null) {
- // Big enough to fail, not big enough to abort.
+TEST(SharedBufferTest, alloc_max) {
SKIP_WITH_HWASAN; // hwasan has a 2GiB allocation limit.
- ASSERT_EQ(nullptr, android::SharedBuffer::alloc(SIZE_MAX / 2));
+
+ android::SharedBuffer* buf =
+ android::SharedBuffer::alloc(SIZE_MAX - sizeof(android::SharedBuffer) - 1);
+ if (buf != nullptr) {
+ EXPECT_NE(nullptr, buf->data());
+ buf->release();
+ }
+}
+
+TEST(SharedBufferTest, alloc_big) {
+ SKIP_WITH_HWASAN; // hwasan has a 2GiB allocation limit.
+
+ android::SharedBuffer* buf = android::SharedBuffer::alloc(SIZE_MAX / 2);
+ if (buf != nullptr) {
+ EXPECT_NE(nullptr, buf->data());
+ buf->release();
+ }
}
TEST(SharedBufferTest, alloc_zero_size) {
@@ -56,7 +71,13 @@
// Big enough to fail, not big enough to abort.
SKIP_WITH_HWASAN; // hwasan has a 2GiB allocation limit.
android::SharedBuffer* buf = android::SharedBuffer::alloc(10);
- ASSERT_EQ(nullptr, buf->editResize(SIZE_MAX / 2));
+ android::SharedBuffer* buf2 = buf->editResize(SIZE_MAX / 2);
+ if (buf2 == nullptr) {
+ buf->release();
+ } else {
+ EXPECT_NE(nullptr, buf2->data());
+ buf2->release();
+ }
}
TEST(SharedBufferTest, editResize_zero_size) {
diff --git a/libutils/StopWatch.cpp b/libutils/StopWatch.cpp
index d01865e..28e2d76 100644
--- a/libutils/StopWatch.cpp
+++ b/libutils/StopWatch.cpp
@@ -26,58 +26,26 @@
#include <utils/Log.h>
-/*****************************************************************************/
-
namespace android {
StopWatch::StopWatch(const char* name, int clock) : mName(name), mClock(clock) {
reset();
}
-StopWatch::~StopWatch()
-{
- nsecs_t elapsed = elapsedTime();
- const int n = mNumLaps;
- ALOGD("StopWatch %s (us): %" PRId64 " ", mName, ns2us(elapsed));
- for (int i=0 ; i<n ; i++) {
- const nsecs_t soFar = mLaps[i].soFar;
- const nsecs_t thisLap = mLaps[i].thisLap;
- ALOGD(" [%d: %" PRId64 ", %" PRId64, i, ns2us(soFar), ns2us(thisLap));
- }
+StopWatch::~StopWatch() {
+ ALOGD("StopWatch %s (us): %" PRId64 " ", name(), ns2us(elapsedTime()));
}
-const char* StopWatch::name() const
-{
+const char* StopWatch::name() const {
return mName;
}
-nsecs_t StopWatch::lap()
-{
- nsecs_t elapsed = elapsedTime();
- if (mNumLaps >= 8) {
- elapsed = 0;
- } else {
- const int n = mNumLaps;
- mLaps[n].soFar = elapsed;
- mLaps[n].thisLap = n ? (elapsed - mLaps[n-1].soFar) : elapsed;
- mNumLaps = n+1;
- }
- return elapsed;
-}
-
-nsecs_t StopWatch::elapsedTime() const
-{
+nsecs_t StopWatch::elapsedTime() const {
return systemTime(mClock) - mStartTime;
}
-void StopWatch::reset()
-{
- mNumLaps = 0;
+void StopWatch::reset() {
mStartTime = systemTime(mClock);
}
-
-/*****************************************************************************/
-
-}; // namespace android
-
+} // namespace android
diff --git a/libutils/StopWatch_fuzz.cpp b/libutils/StopWatch_fuzz.cpp
deleted file mode 100644
index 63d8a28..0000000
--- a/libutils/StopWatch_fuzz.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "fuzzer/FuzzedDataProvider.h"
-#include "utils/StopWatch.h"
-
-static constexpr int MAX_OPERATIONS = 100;
-static constexpr int MAX_NAME_LEN = 2048;
-
-static const std::vector<std::function<void(android::StopWatch)>> operations = {
- [](android::StopWatch stopWatch) -> void { stopWatch.reset(); },
- [](android::StopWatch stopWatch) -> void { stopWatch.lap(); },
- [](android::StopWatch stopWatch) -> void { stopWatch.elapsedTime(); },
- [](android::StopWatch stopWatch) -> void { stopWatch.name(); },
-};
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
- FuzzedDataProvider dataProvider(data, size);
- std::string nameStr = dataProvider.ConsumeRandomLengthString(MAX_NAME_LEN);
- int clockVal = dataProvider.ConsumeIntegral<int>();
- android::StopWatch stopWatch = android::StopWatch(nameStr.c_str(), clockVal);
- std::vector<uint8_t> opsToRun = dataProvider.ConsumeRemainingBytes<uint8_t>();
- int opsRun = 0;
- for (auto it : opsToRun) {
- if (opsRun++ >= MAX_OPERATIONS) {
- break;
- }
- it = it % operations.size();
- operations[it](stopWatch);
- }
- return 0;
-}
diff --git a/libutils/String8.cpp b/libutils/String8.cpp
index 2974aa3..195e122 100644
--- a/libutils/String8.cpp
+++ b/libutils/String8.cpp
@@ -25,6 +25,8 @@
#include <ctype.h>
+#include <string>
+
#include "SharedBuffer.h"
/*
@@ -163,9 +165,7 @@
}
String8::String8(const char32_t* o)
- : mString(allocFromUTF32(o, strlen32(o)))
-{
-}
+ : mString(allocFromUTF32(o, std::char_traits<char32_t>::length(o))) {}
String8::String8(const char32_t* o, size_t len)
: mString(allocFromUTF32(o, len))
diff --git a/libutils/StrongPointer_test.cpp b/libutils/StrongPointer_test.cpp
index 29f6bd4..f27c1f1 100644
--- a/libutils/StrongPointer_test.cpp
+++ b/libutils/StrongPointer_test.cpp
@@ -30,17 +30,34 @@
~SPFoo() {
*mDeleted = true;
}
-private:
+
+ private:
bool* mDeleted;
};
-TEST(StrongPointer, move) {
+class SPLightFoo : virtual public VirtualLightRefBase {
+ public:
+ explicit SPLightFoo(bool* deleted_check) : mDeleted(deleted_check) { *mDeleted = false; }
+
+ ~SPLightFoo() { *mDeleted = true; }
+
+ private:
+ bool* mDeleted;
+};
+
+template <typename T>
+class StrongPointer : public ::testing::Test {};
+
+using RefBaseTypes = ::testing::Types<SPFoo, SPLightFoo>;
+TYPED_TEST_CASE(StrongPointer, RefBaseTypes);
+
+TYPED_TEST(StrongPointer, move) {
bool isDeleted;
- sp<SPFoo> sp1 = sp<SPFoo>::make(&isDeleted);
- SPFoo* foo = sp1.get();
+ sp<TypeParam> sp1 = sp<TypeParam>::make(&isDeleted);
+ TypeParam* foo = sp1.get();
ASSERT_EQ(1, foo->getStrongCount());
{
- sp<SPFoo> sp2 = std::move(sp1);
+ sp<TypeParam> sp2 = std::move(sp1);
ASSERT_EQ(1, foo->getStrongCount()) << "std::move failed, incremented refcnt";
ASSERT_EQ(nullptr, sp1.get()) << "std::move failed, sp1 is still valid";
// The strong count isn't increasing, let's double check the old object
@@ -50,33 +67,42 @@
ASSERT_FALSE(isDeleted) << "deleted too early! still has a reference!";
{
// Now let's double check it deletes on time
- sp<SPFoo> sp2 = std::move(sp1);
+ sp<TypeParam> sp2 = std::move(sp1);
}
ASSERT_TRUE(isDeleted) << "foo was leaked!";
}
-TEST(StrongPointer, NullptrComparison) {
- sp<SPFoo> foo;
+TYPED_TEST(StrongPointer, NullptrComparison) {
+ sp<TypeParam> foo;
ASSERT_EQ(foo, nullptr);
ASSERT_EQ(nullptr, foo);
}
-TEST(StrongPointer, PointerComparison) {
+TYPED_TEST(StrongPointer, PointerComparison) {
bool isDeleted;
- sp<SPFoo> foo = sp<SPFoo>::make(&isDeleted);
+ sp<TypeParam> foo = sp<TypeParam>::make(&isDeleted);
ASSERT_EQ(foo.get(), foo);
ASSERT_EQ(foo, foo.get());
ASSERT_NE(nullptr, foo);
ASSERT_NE(foo, nullptr);
}
-TEST(StrongPointer, AssertStrongRefExists) {
- // uses some other refcounting method, or non at all
+TYPED_TEST(StrongPointer, Deleted) {
bool isDeleted;
- SPFoo* foo = new SPFoo(&isDeleted);
+ sp<TypeParam> foo = sp<TypeParam>::make(&isDeleted);
- // can only get a valid sp<> object when you construct it as an sp<> object
- EXPECT_DEATH(sp<SPFoo>::fromExisting(foo), "");
+ auto foo2 = sp<TypeParam>::fromExisting(foo.get());
+ EXPECT_FALSE(isDeleted);
+ foo = nullptr;
+ EXPECT_FALSE(isDeleted);
+ foo2 = nullptr;
+ EXPECT_TRUE(isDeleted);
+}
+
+TYPED_TEST(StrongPointer, AssertStrongRefExists) {
+ bool isDeleted;
+ TypeParam* foo = new TypeParam(&isDeleted);
+ EXPECT_DEATH(sp<TypeParam>::fromExisting(foo), "");
delete foo;
}
diff --git a/libutils/Unicode.cpp b/libutils/Unicode.cpp
index 843a81a..3ffcf7e 100644
--- a/libutils/Unicode.cpp
+++ b/libutils/Unicode.cpp
@@ -22,20 +22,6 @@
#include <log/log.h>
-#if defined(_WIN32)
-# undef nhtol
-# undef htonl
-# undef nhtos
-# undef htons
-
-# define ntohl(x) ( ((x) << 24) | (((x) >> 24) & 255) | (((x) << 8) & 0xff0000) | (((x) >> 8) & 0xff00) )
-# define htonl(x) ntohl(x)
-# define ntohs(x) ( (((x) << 8) & 0xff00) | (((x) >> 8) & 255) )
-# define htons(x) ntohs(x)
-#else
-# include <netinet/in.h>
-#endif
-
extern "C" {
static const char32_t kByteMask = 0x000000BF;
@@ -115,24 +101,6 @@
}
}
-size_t strlen32(const char32_t *s)
-{
- const char32_t *ss = s;
- while ( *ss )
- ss++;
- return ss-s;
-}
-
-size_t strnlen32(const char32_t *s, size_t maxlen)
-{
- const char32_t *ss = s;
- while ((maxlen > 0) && *ss) {
- ss++;
- maxlen--;
- }
- return ss-s;
-}
-
static inline int32_t utf32_at_internal(const char* cur, size_t *num_read)
{
const char first_char = *cur;
@@ -254,19 +222,6 @@
return d;
}
-char16_t *strcpy16(char16_t *dst, const char16_t *src)
-{
- char16_t *q = dst;
- const char16_t *p = src;
- char16_t ch;
-
- do {
- *q++ = ch = *p++;
- } while ( ch );
-
- return dst;
-}
-
size_t strlen16(const char16_t *s)
{
const char16_t *ss = s;
diff --git a/libutils/include/utils/LightRefBase.h b/libutils/include/utils/LightRefBase.h
index b04e5c1..40edf67 100644
--- a/libutils/include/utils/LightRefBase.h
+++ b/libutils/include/utils/LightRefBase.h
@@ -28,6 +28,8 @@
class ReferenceRenamer;
+void LightRefBase_reportIncStrongRequireStrongFailed(const void* thiz);
+
template <class T>
class LightRefBase
{
@@ -36,6 +38,11 @@
inline void incStrong(__attribute__((unused)) const void* id) const {
mCount.fetch_add(1, std::memory_order_relaxed);
}
+ inline void incStrongRequireStrong(__attribute__((unused)) const void* id) const {
+ if (0 == mCount.fetch_add(1, std::memory_order_relaxed)) {
+ LightRefBase_reportIncStrongRequireStrongFailed(this);
+ }
+ }
inline void decStrong(__attribute__((unused)) const void* id) const {
if (mCount.fetch_sub(1, std::memory_order_release) == 1) {
std::atomic_thread_fence(std::memory_order_acquire);
@@ -59,7 +66,6 @@
mutable std::atomic<int32_t> mCount;
};
-
// This is a wrapper around LightRefBase that simply enforces a virtual
// destructor to eliminate the template requirement of LightRefBase
class VirtualLightRefBase : public LightRefBase<VirtualLightRefBase> {
diff --git a/libutils/include/utils/StopWatch.h b/libutils/include/utils/StopWatch.h
index 9b14ac8..4e53eda 100644
--- a/libutils/include/utils/StopWatch.h
+++ b/libutils/include/utils/StopWatch.h
@@ -14,46 +14,30 @@
* limitations under the License.
*/
-#ifndef ANDROID_STOPWATCH_H
-#define ANDROID_STOPWATCH_H
+#pragma once
#include <stdint.h>
#include <sys/types.h>
#include <utils/Timers.h>
-// ---------------------------------------------------------------------------
-
namespace android {
-class StopWatch
-{
-public:
- StopWatch(const char* name, int clock = SYSTEM_TIME_MONOTONIC);
- ~StopWatch();
+class StopWatch {
+ public:
+ StopWatch(const char* name, int clock = SYSTEM_TIME_MONOTONIC);
+ ~StopWatch();
- const char* name() const;
- nsecs_t lap();
- nsecs_t elapsedTime() const;
+ const char* name() const;
+ nsecs_t elapsedTime() const;
- void reset();
+ void reset();
-private:
- const char* mName;
- int mClock;
-
- struct lap_t {
- nsecs_t soFar;
- nsecs_t thisLap;
- };
-
- nsecs_t mStartTime;
- lap_t mLaps[8];
- int mNumLaps;
+ private:
+ const char* mName;
+ int mClock;
+
+ nsecs_t mStartTime;
};
} // namespace android
-
-// ---------------------------------------------------------------------------
-
-#endif // ANDROID_STOPWATCH_H
diff --git a/libutils/include/utils/Unicode.h b/libutils/include/utils/Unicode.h
index 0087383..d60d5d6 100644
--- a/libutils/include/utils/Unicode.h
+++ b/libutils/include/utils/Unicode.h
@@ -27,7 +27,6 @@
int strncmp16(const char16_t *s1, const char16_t *s2, size_t n);
size_t strlen16(const char16_t *);
size_t strnlen16(const char16_t *, size_t);
-char16_t *strcpy16(char16_t *, const char16_t *);
char16_t *strstr16(const char16_t*, const char16_t*);
// Version of comparison that supports embedded NULs.
@@ -39,10 +38,6 @@
// equivalent result as strcmp16 (unlike strncmp16).
int strzcmp16(const char16_t *s1, size_t n1, const char16_t *s2, size_t n2);
-// Standard string functions on char32_t strings.
-size_t strlen32(const char32_t *);
-size_t strnlen32(const char32_t *, size_t);
-
/**
* Measure the length of a UTF-32 string in UTF-8. If the string is invalid
* such as containing a surrogate character, -1 will be returned.
diff --git a/libvndksupport/Android.bp b/libvndksupport/Android.bp
index 11c75f7..f800bf7 100644
--- a/libvndksupport/Android.bp
+++ b/libvndksupport/Android.bp
@@ -5,7 +5,9 @@
cc_library {
name: "libvndksupport",
native_bridge_supported: true,
- llndk_stubs: "libvndksupport.llndk",
+ llndk: {
+ symbol_file: "libvndksupport.map.txt",
+ },
srcs: ["linker.cpp"],
cflags: [
"-Wall",
@@ -23,10 +25,3 @@
versions: ["29"],
},
}
-
-llndk_library {
- name: "libvndksupport.llndk",
- native_bridge_supported: true,
- symbol_file: "libvndksupport.map.txt",
- export_include_dirs: ["include"],
-}
diff --git a/cpio/Android.bp b/mkbootfs/Android.bp
similarity index 75%
rename from cpio/Android.bp
rename to mkbootfs/Android.bp
index 16af079..cd2a624 100644
--- a/cpio/Android.bp
+++ b/mkbootfs/Android.bp
@@ -8,7 +8,11 @@
name: "mkbootfs",
srcs: ["mkbootfs.c"],
cflags: ["-Werror"],
- shared_libs: ["libcutils"],
+ static_libs: [
+ "libbase",
+ "libcutils",
+ "liblog",
+ ],
dist: {
targets: ["dist_files"],
},
diff --git a/cpio/mkbootfs.c b/mkbootfs/mkbootfs.c
similarity index 100%
rename from cpio/mkbootfs.c
rename to mkbootfs/mkbootfs.c
diff --git a/qemu_pipe/Android.bp b/qemu_pipe/Android.bp
deleted file mode 100644
index 42a69db..0000000
--- a/qemu_pipe/Android.bp
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2011 The Android Open Source Project
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_library_static {
- name: "libqemu_pipe",
- vendor_available: true,
- recovery_available: true,
- apex_available: [
- "com.android.adbd",
- // TODO(b/151398197) remove the below
- "//apex_available:platform",
- ],
- sanitize: {
- misc_undefined: ["integer"],
- },
- srcs: ["qemu_pipe.cpp"],
- local_include_dirs: ["include"],
- static_libs: ["libbase"],
- export_include_dirs: ["include"],
- cflags: ["-Werror"],
-}
diff --git a/qemu_pipe/OWNERS b/qemu_pipe/OWNERS
deleted file mode 100644
index d67a329..0000000
--- a/qemu_pipe/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-bohu@google.com
-lfy@google.com
-rkir@google.com
diff --git a/qemu_pipe/include/qemu_pipe.h b/qemu_pipe/include/qemu_pipe.h
deleted file mode 100644
index 0987498..0000000
--- a/qemu_pipe/include/qemu_pipe.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef ANDROID_CORE_INCLUDE_QEMU_PIPE_H
-#define ANDROID_CORE_INCLUDE_QEMU_PIPE_H
-
-#include <stddef.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-// Try to open a new Qemu fast-pipe. This function returns a file descriptor
-// that can be used to communicate with a named service managed by the
-// emulator.
-//
-// This file descriptor can be used as a standard pipe/socket descriptor.
-//
-// 'pipeName' is the name of the emulator service you want to connect to,
-// and should begin with 'pipe:' (e.g. 'pipe:camera' or 'pipe:opengles').
-// For backward compatibility, the 'pipe:' prefix can be omitted, and in
-// that case, qemu_pipe_open will add it for you.
-
-// On success, return a valid file descriptor, or -1/errno on failure. E.g.:
-//
-// EINVAL -> unknown/unsupported pipeName
-// ENOSYS -> fast pipes not available in this system.
-//
-// ENOSYS should never happen, except if you're trying to run within a
-// misconfigured emulator.
-//
-// You should be able to open several pipes to the same pipe service,
-// except for a few special cases (e.g. GSM modem), where EBUSY will be
-// returned if more than one client tries to connect to it.
-int qemu_pipe_open(const char* pipeName);
-
-// Send a framed message |buff| of |len| bytes through the |fd| descriptor.
-// This really adds a 4-hexchar prefix describing the payload size.
-// Returns 0 on success, and -1 on error.
-int qemu_pipe_frame_send(int fd, const void* buff, size_t len);
-
-// Read a frame message from |fd|, and store it into |buff| of |len| bytes.
-// If the framed message is larger than |len|, then this returns -1 and the
-// content is lost. Otherwise, this returns the size of the message. NOTE:
-// empty messages are possible in a framed wire protocol and do not mean
-// end-of-stream.
-int qemu_pipe_frame_recv(int fd, void* buff, size_t len);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* ANDROID_CORE_INCLUDE_QEMU_PIPE_H */
diff --git a/qemu_pipe/qemu_pipe.cpp b/qemu_pipe/qemu_pipe.cpp
deleted file mode 100644
index 03afb21..0000000
--- a/qemu_pipe/qemu_pipe.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "qemu_pipe.h"
-
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-#include <errno.h>
-#include <stdio.h>
-
-#include <android-base/file.h>
-
-using android::base::ReadFully;
-using android::base::WriteFully;
-
-// Define QEMU_PIPE_DEBUG if you want to print error messages when an error
-// occurs during pipe operations. The macro should simply take a printf-style
-// formatting string followed by optional arguments.
-#ifndef QEMU_PIPE_DEBUG
-# define QEMU_PIPE_DEBUG(...) (void)0
-#endif
-
-int qemu_pipe_open(const char* pipeName) {
- if (!pipeName) {
- errno = EINVAL;
- return -1;
- }
-
- int fd = TEMP_FAILURE_RETRY(open("/dev/qemu_pipe", O_RDWR));
- if (fd < 0) {
- QEMU_PIPE_DEBUG("%s: Could not open /dev/qemu_pipe: %s", __FUNCTION__,
- strerror(errno));
- return -1;
- }
-
- // Write the pipe name, *including* the trailing zero which is necessary.
- size_t pipeNameLen = strlen(pipeName);
- if (WriteFully(fd, pipeName, pipeNameLen + 1U)) {
- return fd;
- }
-
- // now, add 'pipe:' prefix and try again
- // Note: host side will wait for the trailing '\0' to start
- // service lookup.
- const char pipe_prefix[] = "pipe:";
- if (WriteFully(fd, pipe_prefix, strlen(pipe_prefix)) &&
- WriteFully(fd, pipeName, pipeNameLen + 1U)) {
- return fd;
- }
- QEMU_PIPE_DEBUG("%s: Could not write to %s pipe service: %s",
- __FUNCTION__, pipeName, strerror(errno));
- close(fd);
- return -1;
-}
-
-int qemu_pipe_frame_send(int fd, const void* buff, size_t len) {
- char header[5];
- snprintf(header, sizeof(header), "%04zx", len);
- if (!WriteFully(fd, header, 4)) {
- QEMU_PIPE_DEBUG("Can't write qemud frame header: %s", strerror(errno));
- return -1;
- }
- if (!WriteFully(fd, buff, len)) {
- QEMU_PIPE_DEBUG("Can't write qemud frame payload: %s", strerror(errno));
- return -1;
- }
- return 0;
-}
-
-int qemu_pipe_frame_recv(int fd, void* buff, size_t len) {
- char header[5];
- if (!ReadFully(fd, header, 4)) {
- QEMU_PIPE_DEBUG("Can't read qemud frame header: %s", strerror(errno));
- return -1;
- }
- header[4] = '\0';
- size_t size;
- if (sscanf(header, "%04zx", &size) != 1) {
- QEMU_PIPE_DEBUG("Malformed qemud frame header: [%.*s]", 4, header);
- return -1;
- }
- if (size > len) {
- QEMU_PIPE_DEBUG("Oversized qemud frame (% bytes, expected <= %)", size,
- len);
- return -1;
- }
- if (!ReadFully(fd, buff, size)) {
- QEMU_PIPE_DEBUG("Could not read qemud frame payload: %s",
- strerror(errno));
- return -1;
- }
- return size;
-}
diff --git a/rootdir/Android.bp b/rootdir/Android.bp
index 8189fa2..ae21633 100644
--- a/rootdir/Android.bp
+++ b/rootdir/Android.bp
@@ -20,7 +20,10 @@
name: "init.rc",
src: "init.rc",
sub_dir: "init/hw",
- required: ["fsverity_init"],
+ required: [
+ "fsverity_init",
+ "platform-bootclasspath",
+ ],
}
prebuilt_etc {
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 5503cc1..99d8f9a 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -56,7 +56,6 @@
LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
LOCAL_LICENSE_CONDITIONS := notice
LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
-LOCAL_REQUIRED_MODULES := etc_classpath
EXPORT_GLOBAL_ASAN_OPTIONS :=
ifneq ($(filter address,$(SANITIZE_TARGET)),)
@@ -186,21 +185,6 @@
endef
#######################################
-# /etc/classpath
-include $(CLEAR_VARS)
-LOCAL_MODULE := etc_classpath
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-LOCAL_MODULE_STEM := classpath
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE):
- @echo "Generate: $@"
- @mkdir -p $(dir $@)
- $(hide) echo "export BOOTCLASSPATH $(PRODUCT_BOOTCLASSPATH)" > $@
- $(hide) echo "export DEX2OATBOOTCLASSPATH $(PRODUCT_DEX2OAT_BOOTCLASSPATH)" >> $@
- $(hide) echo "export SYSTEMSERVERCLASSPATH $(PRODUCT_SYSTEM_SERVER_CLASSPATH)" >> $@
-
-#######################################
# sanitizer.libraries.txt
include $(CLEAR_VARS)
LOCAL_MODULE := sanitizer.libraries.txt
diff --git a/rootdir/etc/linker.config.json b/rootdir/etc/linker.config.json
index d9f5526..6b03a1d 100644
--- a/rootdir/etc/linker.config.json
+++ b/rootdir/etc/linker.config.json
@@ -1,13 +1,14 @@
{
"requireLibs": [
+ "libandroidicu.so",
"libdexfile.so",
"libdexfiled.so",
+ "libicu.so",
+ "libjdwp.so",
"libnativebridge.so",
"libnativehelper.so",
"libnativeloader.so",
"libsigchain.so",
- "libandroidicu.so",
- "libicu.so",
// TODO(b/122876336): Remove libpac.so once it's migrated to Webview
"libpac.so",
// TODO(b/120786417 or b/134659294): libicuuc.so
@@ -18,6 +19,7 @@
"libnetd_resolv.so",
// nn
"libneuralnetworks.so",
+ "libneuralnetworks_shim.so",
// statsd
"libstatspull.so",
"libstatssocket.so",
diff --git a/rootdir/init-debug.rc b/rootdir/init-debug.rc
index 435d4cb..77a80cd 100644
--- a/rootdir/init-debug.rc
+++ b/rootdir/init-debug.rc
@@ -6,3 +6,11 @@
on property:persist.mmc.cache_size=*
write /sys/block/mmcblk0/cache_size ${persist.mmc.cache_size}
+
+on early-init && property:ro.product.debugfs_restrictions.enabled=true
+ mount debugfs debugfs /sys/kernel/debug
+ chmod 0755 /sys/kernel/debug
+
+on property:sys.boot_completed=1 && property:ro.product.debugfs_restrictions.enabled=true && \
+ property:persist.dbg.keep_debugfs_mounted=""
+ umount /sys/kernel/debug
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 18efcde..1013d41 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -471,9 +471,6 @@
chmod 0664 /sys/module/lowmemorykiller/parameters/minfree
start lmkd
- # Set an initial boot level - start at 10 in case we need to add earlier ones.
- setprop keystore.boot_level 10
-
# Start essential services.
start servicemanager
start hwservicemanager
@@ -630,8 +627,6 @@
write /sys/kernel/tracing/instances/bootreceiver/events/error_report/error_report_end/enable 1
on post-fs-data
- # Boot level 30 - at this point daemons like apexd and odsign run
- setprop keystore.boot_level 30
mark_post_data
@@ -652,6 +647,9 @@
mkdir /data/bootchart 0755 shell shell encryption=Require
bootchart start
+ # Avoid predictable entropy pool. Carry over entropy from previous boot.
+ copy /data/system/entropy.dat /dev/urandom
+
mkdir /data/vendor 0771 root root encryption=Require
mkdir /data/vendor_ce 0771 root root encryption=None
mkdir /data/vendor_de 0771 root root encryption=None
@@ -667,22 +665,42 @@
# Make sure that apexd is started in the default namespace
enter_default_mount_ns
+ # set up keystore directory structure first so that we can end early boot
+ # and start apexd
+ mkdir /data/misc 01771 system misc encryption=Require
+ mkdir /data/misc/keystore 0700 keystore keystore
+ # work around b/183668221
+ restorecon /data/misc /data/misc/keystore
+
+ # Boot level 30
+ # odsign signing keys have MAX_BOOT_LEVEL=30
+ # This is currently the earliest boot level, but we start at 30
+ # to leave room for earlier levels.
+ setprop keystore.boot_level 30
+
+ # Now that /data is mounted and we have created /data/misc/keystore,
+ # we can tell keystore to stop allowing use of early-boot keys,
+ # and access its database for the first time to support creation and
+ # use of MAX_BOOT_LEVEL keys.
+ exec - system system -- /system/bin/vdc keymaster earlyBootEnded
+
# /data/apex is now available. Start apexd to scan and activate APEXes.
+ #
+ # To handle userspace reboots as well as devices that use FDE, make sure
+ # that apexd is started cleanly here (set apexd.status="") and that it is
+ # restarted if it's already running.
mkdir /data/apex 0755 root system encryption=None
mkdir /data/apex/active 0755 root system
mkdir /data/apex/backup 0700 root system
- mkdir /data/apex/decompressed 0700 root system encryption=Require
+ mkdir /data/apex/decompressed 0755 root system encryption=Require
mkdir /data/apex/hashtree 0700 root system
mkdir /data/apex/sessions 0700 root system
mkdir /data/app-staging 0751 system system encryption=DeleteIfNecessary
mkdir /data/apex/ota_reserved 0700 root system encryption=Require
- start apexd
+ setprop apexd.status ""
+ restart apexd
- # Avoid predictable entropy pool. Carry over entropy from previous boot.
- copy /data/system/entropy.dat /dev/urandom
-
- # create basic filesystem structure
- mkdir /data/misc 01771 system misc encryption=Require
+ # create rest of basic filesystem structure
mkdir /data/misc/recovery 0770 system log
copy /data/misc/recovery/ro.build.fingerprint /data/misc/recovery/ro.build.fingerprint.1
chmod 0440 /data/misc/recovery/ro.build.fingerprint.1
@@ -706,7 +724,6 @@
mkdir /data/misc/nfc 0770 nfc nfc
mkdir /data/misc/nfc/logs 0770 nfc nfc
mkdir /data/misc/credstore 0700 credstore credstore
- mkdir /data/misc/keystore 0700 keystore keystore
mkdir /data/misc/gatekeeper 0700 system system
mkdir /data/misc/keychain 0771 system system
mkdir /data/misc/net 0750 root shell
@@ -905,7 +922,6 @@
# Must start before 'odsign', as odsign depends on *CLASSPATH variables
exec_start derive_classpath
load_exports /data/system/environ/classpath
- rm /data/system/environ/classpath
# Start the on-device signing daemon, and wait for it to finish, to ensure
# ART artifacts are generated if needed.
@@ -916,14 +932,13 @@
# odsign to be done with the key
wait_for_prop odsign.key.done 1
- # After apexes are mounted, tell keymaster early boot has ended, so it will
- # stop allowing use of early-boot keys
- exec - system system -- /system/bin/vdc keymaster earlyBootEnded
-
# Lock the fs-verity keyring, so no more keys can be added
exec -- /system/bin/fsverity_init --lock
- setprop keystore.boot_level 40
+ # Bump the boot level to 1000000000; this prevents further on-device signing.
+ # This is a special value that shuts down the thread which listens for
+ # further updates.
+ setprop keystore.boot_level 1000000000
# Allow apexd to snapshot and restore device encrypted apex data in the case
# of a rollback. This should be done immediately after DE_user data keys
@@ -987,9 +1002,6 @@
write /proc/sys/vm/dirty_expire_centisecs 200
write /proc/sys/vm/dirty_background_ratio 5
-on property:sys.boot_completed=1 && property:ro.product.enforce_debugfs_restrictions=true
- umount /sys/kernel/debug
-
on boot
# basic network init
ifup lo
@@ -1233,7 +1245,6 @@
setprop dev.bootcomplete ""
setprop sys.init.updatable_crashing ""
setprop sys.init.updatable_crashing_process_name ""
- setprop apexd.status ""
setprop sys.user.0.ce_available ""
setprop sys.shutdown.requested ""
setprop service.bootanim.exit ""
@@ -1265,10 +1276,6 @@
on property:sys.boot_completed=1 && property:sys.init.userspace_reboot.in_progress=1
setprop sys.init.userspace_reboot.in_progress ""
-on early-init && property:ro.product.enforce_debugfs_restrictions=true
- mount debugfs debugfs /sys/kernel/debug
- chmod 0755 /sys/kernel/debug
-
# Migrate tasks again in case kernel threads are created during boot
on property:sys.boot_completed=1
copy_per_line /dev/cpuctl/tasks /dev/cpuctl/system/tasks
diff --git a/trusty/metrics/Android.bp b/trusty/metrics/Android.bp
new file mode 100644
index 0000000..e0533bc
--- /dev/null
+++ b/trusty/metrics/Android.bp
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 The Android Open-Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+ name: "libtrusty_metrics",
+ vendor: true,
+ srcs: [
+ "metrics.cpp",
+ ],
+ export_include_dirs: [
+ "include",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libtrusty",
+ ],
+}
+
+cc_test {
+ name: "libtrusty_metrics_test",
+ vendor: true,
+ srcs: [
+ "metrics_test.cpp",
+ ],
+ static_libs: [
+ "libtrusty_metrics",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "liblog",
+ "libtrusty",
+ ],
+ require_root: true,
+}
diff --git a/trusty/metrics/include/trusty/metrics/metrics.h b/trusty/metrics/include/trusty/metrics/metrics.h
new file mode 100644
index 0000000..6949e9b
--- /dev/null
+++ b/trusty/metrics/include/trusty/metrics/metrics.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <string>
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace trusty {
+namespace metrics {
+
+using android::base::Result;
+using android::base::unique_fd;
+
+class TrustyMetrics {
+ public:
+ /* Wait for next event with a given timeout. Negative timeout means infinite timeout. */
+ Result<void> WaitForEvent(int timeout_ms = -1);
+ /* Attempt to handle an event from Metrics TA in a non-blocking manner. */
+ Result<void> HandleEvent();
+ /* Expose TIPC channel so that client can integrate it into an event loop with other fds. */
+ int GetRawFd() { return metrics_fd_; };
+
+ protected:
+ TrustyMetrics(std::string tipc_dev) : tipc_dev_(std::move(tipc_dev)), metrics_fd_(-1) {}
+ virtual ~TrustyMetrics(){};
+
+ Result<void> Open();
+ virtual void HandleCrash(const std::string& app_id) = 0;
+ virtual void HandleEventDrop() = 0;
+
+ private:
+ std::string tipc_dev_;
+ unique_fd metrics_fd_;
+};
+
+} // namespace metrics
+} // namespace trusty
+} // namespace android
diff --git a/trusty/metrics/include/trusty/metrics/tipc.h b/trusty/metrics/include/trusty/metrics/tipc.h
new file mode 100644
index 0000000..66d0876
--- /dev/null
+++ b/trusty/metrics/include/trusty/metrics/tipc.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+/**
+ * DOC: Metrics
+ *
+ * Metrics interface provides a way for Android to get Trusty metrics data.
+ *
+ * Currently, only "push" model is supported. Clients are expected to connect to
+ * metrics service, listen for events, e.g. app crash events, and respond to
+ * every event with a &struct metrics_req.
+ *
+ * Communication is driven by metrics service, i.e. requests/responses are all
+ * sent from/to metrics service.
+ *
+ * Note that the type of the event is not known to the client ahead of time.
+ *
+ * In the future, if we need to have Android "pull" metrics data from Trusty,
+ * that can be done by introducing a separate port.
+ *
+ * This interface is shared between Android and Trusty. There is a copy in each
+ * repository. They must be kept in sync.
+ */
+
+#define METRICS_PORT "com.android.trusty.metrics"
+
+/**
+ * enum metrics_cmd - command identifiers for metrics interface
+ * @METRICS_CMD_RESP_BIT: message is a response
+ * @METRICS_CMD_REQ_SHIFT: number of bits used by @METRICS_CMD_RESP_BIT
+ * @METRICS_CMD_REPORT_EVENT_DROP: report gaps in the event stream
+ * @METRICS_CMD_REPORT_CRASH: report an app crash event
+ */
+enum metrics_cmd {
+ METRICS_CMD_RESP_BIT = 1,
+ METRICS_CMD_REQ_SHIFT = 1,
+
+ METRICS_CMD_REPORT_EVENT_DROP = (1 << METRICS_CMD_REQ_SHIFT),
+ METRICS_CMD_REPORT_CRASH = (2 << METRICS_CMD_REQ_SHIFT),
+};
+
+/**
+ * enum metrics_error - metrics error codes
+ * @METRICS_NO_ERROR: no error
+ * @METRICS_ERR_UNKNOWN_CMD: unknown or not implemented command
+ */
+enum metrics_error {
+ METRICS_NO_ERROR = 0,
+ METRICS_ERR_UNKNOWN_CMD = 1,
+};
+
+/**
+ * struct metrics_req - common structure for metrics requests
+ * @cmd: command identifier - one of &enum metrics_cmd
+ * @reserved: must be 0
+ */
+struct metrics_req {
+ uint32_t cmd;
+ uint32_t reserved;
+} __attribute__((__packed__));
+
+/**
+ * struct metrics_resp - common structure for metrics responses
+ * @cmd: command identifier - %METRICS_CMD_RESP_BIT or'ed with a cmd in
+ * one of &enum metrics_cmd
+ * @status: response status, one of &enum metrics_error
+ */
+struct metrics_resp {
+ uint32_t cmd;
+ uint32_t status;
+} __attribute__((__packed__));
+
+/**
+ * struct metrics_report_crash_req - arguments of %METRICS_CMD_REPORT_CRASH
+ * requests
+ * @app_id_len: length of app ID that follows this structure
+ */
+struct metrics_report_crash_req {
+ uint32_t app_id_len;
+} __attribute__((__packed__));
+
+#define METRICS_MAX_APP_ID_LEN 256
+
+#define METRICS_MAX_MSG_SIZE \
+ (sizeof(struct metrics_req) + sizeof(struct metrics_report_crash_req) + \
+ METRICS_MAX_APP_ID_LEN)
diff --git a/trusty/metrics/metrics.cpp b/trusty/metrics/metrics.cpp
new file mode 100644
index 0000000..3ac128a
--- /dev/null
+++ b/trusty/metrics/metrics.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 The Android Open Sourete Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "metrics"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <trusty/metrics/metrics.h>
+#include <trusty/metrics/tipc.h>
+#include <trusty/tipc.h>
+#include <unistd.h>
+
+namespace android {
+namespace trusty {
+namespace metrics {
+
+using android::base::ErrnoError;
+using android::base::Error;
+
+Result<void> TrustyMetrics::Open() {
+ int fd = tipc_connect(tipc_dev_.c_str(), METRICS_PORT);
+ if (fd < 0) {
+ return ErrnoError() << "failed to connect to Trusty metrics TA";
+ }
+
+ int flags = fcntl(fd, F_GETFL, 0);
+ if (flags < 0) {
+ return ErrnoError() << "failed F_GETFL";
+ }
+
+ int rc = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ if (rc < 0) {
+ return ErrnoError() << "failed F_SETFL";
+ }
+
+ metrics_fd_.reset(fd);
+ return {};
+}
+
+Result<void> TrustyMetrics::WaitForEvent(int timeout_ms) {
+ if (!metrics_fd_.ok()) {
+ return Error() << "connection to Metrics TA has not been initialized yet";
+ }
+
+ struct pollfd pfd = {
+ .fd = metrics_fd_,
+ .events = POLLIN,
+ };
+
+ int rc = poll(&pfd, 1, timeout_ms);
+ if (rc != 1) {
+ return ErrnoError() << "failed poll()";
+ }
+
+ if (!(pfd.revents & POLLIN)) {
+ return ErrnoError() << "channel not ready";
+ }
+
+ return {};
+}
+
+Result<void> TrustyMetrics::HandleEvent() {
+ if (!metrics_fd_.ok()) {
+ return Error() << "connection to Metrics TA has not been initialized yet";
+ }
+
+ uint8_t msg[METRICS_MAX_MSG_SIZE];
+
+ auto rc = read(metrics_fd_, msg, sizeof(msg));
+ if (rc < 0) {
+ return ErrnoError() << "failed to read metrics message";
+ }
+ size_t msg_len = rc;
+
+ if (msg_len < sizeof(metrics_req)) {
+ return Error() << "message too small: " << rc;
+ }
+ auto req = reinterpret_cast<metrics_req*>(msg);
+ size_t offset = sizeof(metrics_req);
+ uint32_t status = METRICS_NO_ERROR;
+
+ switch (req->cmd) {
+ case METRICS_CMD_REPORT_CRASH: {
+ if (msg_len < offset + sizeof(metrics_report_crash_req)) {
+ return Error() << "message too small: " << rc;
+ }
+ auto crash_args = reinterpret_cast<metrics_report_crash_req*>(msg + offset);
+ offset += sizeof(metrics_report_crash_req);
+
+ if (msg_len < offset + crash_args->app_id_len) {
+ return Error() << "message too small: " << rc;
+ }
+ auto app_id_ptr = reinterpret_cast<char*>(msg + offset);
+ std::string app_id(app_id_ptr, crash_args->app_id_len);
+
+ HandleCrash(app_id);
+ break;
+ }
+
+ case METRICS_CMD_REPORT_EVENT_DROP:
+ HandleEventDrop();
+ break;
+
+ default:
+ status = METRICS_ERR_UNKNOWN_CMD;
+ break;
+ }
+
+ metrics_resp resp = {
+ .cmd = req->cmd | METRICS_CMD_RESP_BIT,
+ .status = status,
+ };
+
+ rc = write(metrics_fd_, &resp, sizeof(resp));
+ if (rc < 0) {
+ return ErrnoError() << "failed to request next metrics event";
+ }
+
+ if (rc != (int)sizeof(resp)) {
+ return Error() << "unexpected number of bytes sent event: " << rc;
+ }
+
+ return {};
+}
+
+} // namespace metrics
+} // namespace trusty
+} // namespace android
diff --git a/trusty/metrics/metrics_test.cpp b/trusty/metrics/metrics_test.cpp
new file mode 100644
index 0000000..407ddf2
--- /dev/null
+++ b/trusty/metrics/metrics_test.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/unique_fd.h>
+#include <binder/IPCThreadState.h>
+#include <gtest/gtest.h>
+#include <poll.h>
+#include <trusty/metrics/metrics.h>
+#include <trusty/tipc.h>
+
+#define TIPC_DEV "/dev/trusty-ipc-dev0"
+#define CRASHER_PORT "com.android.trusty.metrics.test.crasher"
+
+namespace android {
+namespace trusty {
+namespace metrics {
+
+using android::base::unique_fd;
+
+static void TriggerCrash() {
+ size_t num_retries = 3;
+ int fd = -1;
+
+ for (size_t i = 0; i < num_retries; i++) {
+ /* It's possible to time out waiting for crasher TA to restart. */
+ fd = tipc_connect(TIPC_DEV, CRASHER_PORT);
+ if (fd >= 0) {
+ break;
+ }
+ }
+
+ unique_fd crasher(fd);
+ ASSERT_GE(crasher, 0);
+
+ int msg = 0;
+ int rc = write(crasher, &msg, sizeof(msg));
+ ASSERT_EQ(rc, sizeof(msg));
+}
+
+class TrustyMetricsTest : public TrustyMetrics, public ::testing::Test {
+ public:
+ TrustyMetricsTest() : TrustyMetrics(TIPC_DEV) {}
+
+ virtual void HandleCrash(const std::string& app_id) override { crashed_app_ = app_id; }
+
+ virtual void HandleEventDrop() override { event_drop_count_++; }
+
+ virtual void SetUp() override {
+ auto ret = Open();
+ ASSERT_TRUE(ret.ok()) << ret.error();
+ }
+
+ void WaitForAndHandleEvent() {
+ auto ret = WaitForEvent(30000 /* 30 second timeout */);
+ ASSERT_TRUE(ret.ok()) << ret.error();
+
+ ret = HandleEvent();
+ ASSERT_TRUE(ret.ok()) << ret.error();
+ }
+
+ std::string crashed_app_;
+ size_t event_drop_count_;
+};
+
+TEST_F(TrustyMetricsTest, Crash) {
+ TriggerCrash();
+ WaitForAndHandleEvent();
+
+ /* Check that correct TA crashed. */
+ ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
+}
+
+TEST_F(TrustyMetricsTest, PollSet) {
+ int binder_fd;
+ int rc = IPCThreadState::self()->setupPolling(&binder_fd);
+ ASSERT_EQ(rc, 0);
+ ASSERT_GE(binder_fd, 0);
+
+ TriggerCrash();
+
+ struct pollfd pfds[] = {
+ {
+ .fd = binder_fd,
+ .events = POLLIN,
+ },
+ {
+ .fd = GetRawFd(),
+ .events = POLLIN,
+ },
+ };
+
+ rc = poll(pfds, 2, 30000 /* 30 second timeout */);
+ /* We expect one event on the metrics fd. */
+ ASSERT_EQ(rc, 1);
+ ASSERT_TRUE(pfds[1].revents & POLLIN);
+
+ auto ret = HandleEvent();
+ ASSERT_TRUE(ret.ok()) << ret.error();
+
+ /* Check that correct TA crashed. */
+ ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
+}
+
+TEST_F(TrustyMetricsTest, EventDrop) {
+ /* We know the size of the internal event queue is less than this. */
+ size_t num_events = 3;
+
+ ASSERT_EQ(event_drop_count_, 0);
+
+ for (auto i = 0; i < num_events; i++) {
+ TriggerCrash();
+ }
+
+ for (auto i = 0; i < num_events; i++) {
+ WaitForAndHandleEvent();
+ if (event_drop_count_ > 0) {
+ break;
+ }
+ }
+
+ ASSERT_EQ(event_drop_count_, 1);
+}
+
+} // namespace metrics
+} // namespace trusty
+} // namespace android