Merge "Add a small testcase for snapshot writer."
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/cpio/Android.bp b/cpio/Android.bp
index 16af079..cd2a624 100644
--- a/cpio/Android.bp
+++ b/cpio/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/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 144faee..93725b9 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -274,7 +274,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));
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 43b2ddd..2c70778 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -183,7 +183,7 @@
],
static_libs: [
- "libgtest_prod",
+ "libc++fs",
"libhealthhalutils",
"libsnapshot_cow",
"libsnapshot_nobinder",
@@ -192,6 +192,7 @@
header_libs: [
"avb_headers",
+ "libgtest_prod_headers",
"libsnapshot_headers",
"libstorage_literals_headers",
],
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 333ca50..ee0aa58 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -27,6 +27,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <android-base/strings.h>
#include <ext4_utils/ext4_utils.h>
#include <fs_mgr_overlayfs.h>
@@ -162,7 +163,9 @@
partition_name == "boot_b")) {
CopyAVBFooter(&data, block_device_size);
}
- WipeOverlayfsForPartition(device, partition_name);
+ if (android::base::GetProperty("ro.system.build.type", "") != "user") {
+ WipeOverlayfsForPartition(device, partition_name);
+ }
int result = FlashBlockDevice(handle.fd(), data);
sync();
return result;
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 700d4bd..e5319a5 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -411,6 +411,13 @@
" gsi wipe|disable Wipe or disable a GSI installation (fastbootd only).\n"
" wipe-super [SUPER_EMPTY] Wipe the super partition. This will reset it to\n"
" contain an empty set of default dynamic partitions.\n"
+ " create-logical-partition NAME SIZE\n"
+ " Create a logical partition with the given name and\n"
+ " size, in the super partition.\n"
+ " delete-logical-partition NAME\n"
+ " Delete a logical partition with the given name.\n"
+ " resize-logical-partition NAME SIZE\n"
+ " Change the size of the named logical partition.\n"
" snapshot-update cancel On devices that support snapshot-based updates, cancel\n"
" an in-progress update. This may make the device\n"
" unbootable until it is reflashed.\n"
@@ -673,7 +680,7 @@
return fd;
}
-static void CheckRequirement(const std::string& cur_product, const std::string& var,
+static bool CheckRequirement(const std::string& cur_product, const std::string& var,
const std::string& product, bool invert,
const std::vector<std::string>& options) {
Status("Checking '" + var + "'");
@@ -685,7 +692,7 @@
double split = now();
fprintf(stderr, "IGNORE, product is %s required only for %s [%7.3fs]\n",
cur_product.c_str(), product.c_str(), (split - start));
- return;
+ return true;
}
}
@@ -694,7 +701,7 @@
fprintf(stderr, "FAILED\n\n");
fprintf(stderr, "Could not getvar for '%s' (%s)\n\n", var.c_str(),
fb->Error().c_str());
- die("requirements not met!");
+ return false;
}
bool match = false;
@@ -714,7 +721,7 @@
if (match) {
double split = now();
fprintf(stderr, "OKAY [%7.3fs]\n", (split - start));
- return;
+ return true;
}
fprintf(stderr, "FAILED\n\n");
@@ -724,7 +731,7 @@
fprintf(stderr, " or '%s'", it->c_str());
}
fprintf(stderr, ".\n\n");
- die("requirements not met!");
+ return false;
}
bool ParseRequirementLine(const std::string& line, std::string* name, std::string* product,
@@ -788,7 +795,7 @@
}
}
-static void CheckRequirements(const std::string& data) {
+static void CheckRequirements(const std::string& data, bool force_flash) {
std::string cur_product;
if (fb->GetVar("product", &cur_product) != fastboot::SUCCESS) {
fprintf(stderr, "getvar:product FAILED (%s)\n", fb->Error().c_str());
@@ -812,7 +819,14 @@
if (name == "partition-exists") {
HandlePartitionExists(options);
} else {
- CheckRequirement(cur_product, name, product, invert, options);
+ bool met = CheckRequirement(cur_product, name, product, invert, options);
+ if (!met) {
+ if (!force_flash) {
+ die("requirements not met!");
+ } else {
+ fprintf(stderr, "requirements not met! but proceeding due to --force\n");
+ }
+ }
}
}
}
@@ -1405,7 +1419,8 @@
class FlashAllTool {
public:
- FlashAllTool(const ImageSource& source, const std::string& slot_override, bool skip_secondary, bool wipe);
+ FlashAllTool(const ImageSource& source, const std::string& slot_override, bool skip_secondary,
+ bool wipe, bool force_flash);
void Flash();
@@ -1421,16 +1436,19 @@
std::string slot_override_;
bool skip_secondary_;
bool wipe_;
+ bool force_flash_;
std::string secondary_slot_;
std::vector<std::pair<const Image*, std::string>> boot_images_;
std::vector<std::pair<const Image*, std::string>> os_images_;
};
-FlashAllTool::FlashAllTool(const ImageSource& source, const std::string& slot_override, bool skip_secondary, bool wipe)
+FlashAllTool::FlashAllTool(const ImageSource& source, const std::string& slot_override,
+ bool skip_secondary, bool wipe, bool force_flash)
: source_(source),
slot_override_(slot_override),
skip_secondary_(skip_secondary),
- wipe_(wipe)
+ wipe_(wipe),
+ force_flash_(force_flash)
{
}
@@ -1478,7 +1496,7 @@
if (!source_.ReadFile("android-info.txt", &contents)) {
die("could not read android-info.txt");
}
- ::CheckRequirements({contents.data(), contents.size()});
+ ::CheckRequirements({contents.data(), contents.size()}, force_flash_);
}
void FlashAllTool::DetermineSecondarySlot() {
@@ -1598,14 +1616,15 @@
return unzip_to_file(zip_, name.c_str());
}
-static void do_update(const char* filename, const std::string& slot_override, bool skip_secondary) {
+static void do_update(const char* filename, const std::string& slot_override, bool skip_secondary,
+ bool force_flash) {
ZipArchiveHandle zip;
int error = OpenArchive(filename, &zip);
if (error != 0) {
die("failed to open zip file '%s': %s", filename, ErrorCodeString(error));
}
- FlashAllTool tool(ZipImageSource(zip), slot_override, skip_secondary, false);
+ FlashAllTool tool(ZipImageSource(zip), slot_override, skip_secondary, false, force_flash);
tool.Flash();
CloseArchive(zip);
@@ -1630,8 +1649,9 @@
return unique_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_BINARY)));
}
-static void do_flashall(const std::string& slot_override, bool skip_secondary, bool wipe) {
- FlashAllTool tool(LocalImageSource(), slot_override, skip_secondary, wipe);
+static void do_flashall(const std::string& slot_override, bool skip_secondary, bool wipe,
+ bool force_flash) {
+ FlashAllTool tool(LocalImageSource(), slot_override, skip_secondary, wipe, force_flash);
tool.Flash();
}
@@ -2179,9 +2199,9 @@
} else if (command == "flashall") {
if (slot_override == "all") {
fprintf(stderr, "Warning: slot set to 'all'. Secondary slots will not be flashed.\n");
- do_flashall(slot_override, true, wants_wipe);
+ do_flashall(slot_override, true, wants_wipe, force_flash);
} else {
- do_flashall(slot_override, skip_secondary, wants_wipe);
+ do_flashall(slot_override, skip_secondary, wants_wipe, force_flash);
}
wants_reboot = true;
} else if (command == "update") {
@@ -2193,7 +2213,7 @@
if (!args.empty()) {
filename = next_arg(&args);
}
- do_update(filename.c_str(), slot_override, skip_secondary || slot_all);
+ do_update(filename.c_str(), slot_override, skip_secondary || slot_all, force_flash);
wants_reboot = true;
} else if (command == FB_CMD_SET_ACTIVE) {
std::string slot = verify_slot(next_arg(&args), false);
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 6952cdf..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);
@@ -647,6 +647,46 @@
return sb == cpu_to_le32(F2FS_SUPER_MAGIC);
}
+static void SetReadAheadSize(const std::string& entry_block_device, off64_t size_kb) {
+ std::string block_device;
+ if (!Realpath(entry_block_device, &block_device)) {
+ PERROR << "Failed to realpath " << entry_block_device;
+ return;
+ }
+
+ static constexpr std::string_view kDevBlockPrefix("/dev/block/");
+ if (!android::base::StartsWith(block_device, kDevBlockPrefix)) {
+ LWARNING << block_device << " is not a block device";
+ return;
+ }
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+ while (true) {
+ std::string block_name = block_device;
+ if (android::base::StartsWith(block_device, kDevBlockPrefix)) {
+ block_name = block_device.substr(kDevBlockPrefix.length());
+ }
+ std::string sys_partition =
+ android::base::StringPrintf("/sys/class/block/%s/partition", block_name.c_str());
+ struct stat info;
+ if (lstat(sys_partition.c_str(), &info) == 0) {
+ // it has a partition like "sda12".
+ block_name += "/..";
+ }
+ std::string sys_ra = android::base::StringPrintf("/sys/class/block/%s/queue/read_ahead_kb",
+ block_name.c_str());
+ std::string size = android::base::StringPrintf("%llu", (long long)size_kb);
+ android::base::WriteStringToFile(size, sys_ra.c_str());
+ LINFO << "Set readahead_kb: " << size << " on " << sys_ra;
+
+ auto parent = dm.GetParentBlockDeviceByPath(block_device);
+ if (!parent) {
+ return;
+ }
+ block_device = *parent;
+ }
+}
+
//
// Prepare the filesystem on the given block device to be mounted.
//
@@ -667,6 +707,11 @@
}
mkdir(mount_point.c_str(), 0755);
+ // Don't need to return error, since it's a salt
+ if (entry.readahead_size_kb != -1) {
+ SetReadAheadSize(blk_device, entry.readahead_size_kb);
+ }
+
int fs_stat = 0;
if (is_extfs(entry.fs_type)) {
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index ad48dd1..42bf356 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -255,6 +255,13 @@
} else {
entry->reserved_size = static_cast<off64_t>(size);
}
+ } else if (StartsWith(flag, "readahead_size_kb=")) {
+ int val;
+ if (ParseInt(arg, &val, 0, 16 * 1024)) {
+ entry->readahead_size_kb = val;
+ } else {
+ LWARNING << "Warning: readahead_size_kb= flag malformed (0 ~ 16MB): " << arg;
+ }
} else if (StartsWith(flag, "eraseblk=")) {
// The erase block size flag is followed by an = and the flash erase block size. Get it,
// check that it is a power of 2 and at least 4096, and return it.
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 1134f14..cb09383 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -116,6 +116,8 @@
void MapScratchPartitionIfNeeded(Fstab*, const std::function<bool(const std::set<std::string>&)>&) {
}
+void CleanupOldScratchFiles() {}
+
void TeardownAllOverlayForMountPoint(const std::string&) {}
} // namespace fs_mgr
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index 8ecf41b..2704e47 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -47,6 +47,7 @@
int max_comp_streams = 0;
off64_t zram_size = 0;
off64_t reserved_size = 0;
+ off64_t readahead_size_kb = -1;
std::string encryption_options;
off64_t erase_blk_size = 0;
off64_t logical_blk_size = 0;
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/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/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index e28cfd4..adfb56c 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -265,6 +265,7 @@
"android.hardware.boot@1.0",
"android.hardware.boot@1.1",
"libbrotli",
+ "libc++fs",
"libfs_mgr",
"libgsi",
"libgmock",
@@ -298,6 +299,7 @@
],
static_libs: [
"libbrotli",
+ "libc++fs",
"libfstab",
"libsnapshot",
"libsnapshot_cow",
@@ -327,6 +329,7 @@
"power_test.cpp",
],
static_libs: [
+ "libc++fs",
"libsnapshot",
"update_metadata-protos",
],
@@ -356,6 +359,7 @@
static_libs: [
"libbase",
"libbrotli",
+ "libc++fs",
"libchrome",
"libcrypto_static",
"libcutils",
@@ -418,6 +422,7 @@
"snapuserd.cpp",
"snapuserd_daemon.cpp",
"snapuserd_worker.cpp",
+ "snapuserd_readahead.cpp",
],
cflags: [
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_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index 5d63220..b75b154 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -25,6 +25,10 @@
#include <libsnapshot/cow_reader.h>
#include <libsnapshot/cow_writer.h>
+using testing::AssertionFailure;
+using testing::AssertionResult;
+using testing::AssertionSuccess;
+
namespace android {
namespace snapshot {
@@ -781,6 +785,202 @@
ASSERT_TRUE(reader.Parse(cow_->fd));
}
+AssertionResult WriteDataBlock(CowWriter* writer, uint64_t new_block, std::string data) {
+ data.resize(writer->options().block_size, '\0');
+ if (!writer->AddRawBlocks(new_block, data.data(), data.size())) {
+ return AssertionFailure() << "Failed to add raw block";
+ }
+ return AssertionSuccess();
+}
+
+AssertionResult CompareDataBlock(CowReader* reader, const CowOperation& op,
+ const std::string& data) {
+ CowHeader header;
+ reader->GetHeader(&header);
+
+ std::string cmp = data;
+ cmp.resize(header.block_size, '\0');
+
+ StringSink sink;
+ if (!reader->ReadData(op, &sink)) {
+ return AssertionFailure() << "Failed to read data block";
+ }
+ if (cmp != sink.stream()) {
+ return AssertionFailure() << "Data blocks did not match, expected " << cmp << ", got "
+ << sink.stream();
+ }
+
+ return AssertionSuccess();
+}
+
+TEST_F(CowTest, ResumeMidCluster) {
+ CowOptions options;
+ options.cluster_ops = 7;
+ auto writer = std::make_unique<CowWriter>(options);
+ ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+ ASSERT_TRUE(writer->AddLabel(1));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ writer = std::make_unique<CowWriter>(options);
+ ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+ ASSERT_TRUE(writer->AddLabel(2));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ CowReader reader;
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+
+ auto iter = reader.GetOpIter();
+ size_t num_replace = 0;
+ size_t max_in_cluster = 0;
+ size_t num_in_cluster = 0;
+ size_t num_clusters = 0;
+ while (!iter->Done()) {
+ const auto& op = iter->Get();
+
+ num_in_cluster++;
+ max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+
+ if (op.type == kCowReplaceOp) {
+ num_replace++;
+
+ ASSERT_EQ(op.new_block, num_replace);
+ ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
+ } else if (op.type == kCowClusterOp) {
+ num_in_cluster = 0;
+ num_clusters++;
+ }
+
+ iter->Next();
+ }
+ ASSERT_EQ(num_replace, 8);
+ ASSERT_EQ(max_in_cluster, 7);
+ ASSERT_EQ(num_clusters, 2);
+}
+
+TEST_F(CowTest, ResumeEndCluster) {
+ CowOptions options;
+ int cluster_ops = 5;
+ options.cluster_ops = cluster_ops;
+ auto writer = std::make_unique<CowWriter>(options);
+ ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+ ASSERT_TRUE(writer->AddLabel(1));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ writer = std::make_unique<CowWriter>(options);
+ ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+ ASSERT_TRUE(writer->AddLabel(2));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ CowReader reader;
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+
+ auto iter = reader.GetOpIter();
+ size_t num_replace = 0;
+ size_t max_in_cluster = 0;
+ size_t num_in_cluster = 0;
+ size_t num_clusters = 0;
+ while (!iter->Done()) {
+ const auto& op = iter->Get();
+
+ num_in_cluster++;
+ max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+
+ if (op.type == kCowReplaceOp) {
+ num_replace++;
+
+ ASSERT_EQ(op.new_block, num_replace);
+ ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
+ } else if (op.type == kCowClusterOp) {
+ num_in_cluster = 0;
+ num_clusters++;
+ }
+
+ iter->Next();
+ }
+ ASSERT_EQ(num_replace, 8);
+ ASSERT_EQ(max_in_cluster, cluster_ops);
+ ASSERT_EQ(num_clusters, 3);
+}
+
+TEST_F(CowTest, DeleteMidCluster) {
+ CowOptions options;
+ options.cluster_ops = 7;
+ auto writer = std::make_unique<CowWriter>(options);
+ ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+ ASSERT_TRUE(writer->AddLabel(1));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+ ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ writer = std::make_unique<CowWriter>(options);
+ ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+ ASSERT_TRUE(writer->Finalize());
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ CowReader reader;
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+
+ auto iter = reader.GetOpIter();
+ size_t num_replace = 0;
+ size_t max_in_cluster = 0;
+ size_t num_in_cluster = 0;
+ size_t num_clusters = 0;
+ while (!iter->Done()) {
+ const auto& op = iter->Get();
+
+ num_in_cluster++;
+ max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+ if (op.type == kCowReplaceOp) {
+ num_replace++;
+
+ ASSERT_EQ(op.new_block, num_replace);
+ ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
+ } else if (op.type == kCowClusterOp) {
+ num_in_cluster = 0;
+ num_clusters++;
+ }
+
+ iter->Next();
+ }
+ ASSERT_EQ(num_replace, 3);
+ ASSERT_EQ(max_in_cluster, 5); // 3 data, 1 label, 1 cluster op
+ ASSERT_EQ(num_clusters, 1);
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 7199b38..35a02e6 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,13 +369,7 @@
// Replace-op-4, Zero-op-9, Replace-op-5 }
//==============================================================
- for (uint64_t i = 0; i < ops_->size(); i++) {
- auto& current_op = ops_->data()[i];
- if (current_op.type != kCowCopyOp) {
- break;
- }
- num_copy_ops += 1;
- }
+ num_copy_ops = FindNumCopyops();
std::sort(ops_.get()->begin() + num_copy_ops, ops_.get()->end(),
[](CowOperation& op1, CowOperation& op2) -> bool {
@@ -377,6 +380,23 @@
CHECK(ops_->size() >= header_.num_merge_ops);
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) {
+ break;
+ }
+ num_copy_ops += 1;
+ }
+
+ return num_copy_ops;
}
bool CowReader::GetHeader(CowHeader* header) {
@@ -470,7 +490,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..313eb64 100644
--- a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
@@ -96,6 +96,7 @@
class CowSnapuserdTest final {
public:
bool Setup();
+ bool SetupCopyOverlap();
bool Merge();
void ValidateMerge();
void ReadSnapshotDeviceAndValidate();
@@ -114,6 +115,7 @@
void StartMerge();
void CreateCowDevice();
+ void CreateCowDeviceWithCopyOverlap();
void CreateBaseDevice();
void InitCowDevice();
void SetDeviceControlName();
@@ -191,6 +193,24 @@
return setup_ok_;
}
+bool CowSnapuserdTest::SetupCopyOverlap() {
+ CreateBaseDevice();
+ CreateCowDeviceWithCopyOverlap();
+
+ 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 +275,49 @@
ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0);
}
+void CowSnapuserdTest::CreateCowDeviceWithCopyOverlap() {
+ 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;
@@ -707,17 +770,17 @@
de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
ASSERT_EQ(de->old_chunk, 21);
- ASSERT_EQ(de->new_chunk, 537);
+ ASSERT_EQ(de->new_chunk, 536);
offset += sizeof(struct disk_exception);
de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
ASSERT_EQ(de->old_chunk, 22);
- ASSERT_EQ(de->new_chunk, 538);
+ ASSERT_EQ(de->new_chunk, 537);
offset += sizeof(struct disk_exception);
de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
ASSERT_EQ(de->old_chunk, 23);
- ASSERT_EQ(de->new_chunk, 539);
+ ASSERT_EQ(de->new_chunk, 538);
offset += sizeof(struct disk_exception);
// End of metadata
@@ -757,6 +820,23 @@
harness.ValidateMerge();
harness.Shutdown();
}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST) {
+ CowSnapuserdTest harness;
+ ASSERT_TRUE(harness.SetupCopyOverlap());
+ ASSERT_TRUE(harness.Merge());
+ harness.ValidateMerge();
+ harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_Merge_Resume_TEST) {
+ CowSnapuserdTest harness;
+ ASSERT_TRUE(harness.SetupCopyOverlap());
+ 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 59f6d6f..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;
}
@@ -232,15 +251,11 @@
// Free reader so we own the descriptor position again.
reader = nullptr;
- // Remove excess data
- if (!Truncate(next_op_pos_)) {
- return false;
- }
if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
PLOG(ERROR) << "lseek failed";
return false;
}
- return true;
+ return EmitClusterIfNeeded();
}
bool CowWriter::EmitCopy(uint64_t new_block, uint64_t old_block) {
@@ -319,6 +334,14 @@
return WriteOperation(op);
}
+bool CowWriter::EmitClusterIfNeeded() {
+ // If there isn't room for another op and the cluster end op, end the current cluster
+ if (cluster_size_ && cluster_size_ < current_cluster_size_ + 2 * sizeof(CowOperation)) {
+ if (!EmitCluster()) return false;
+ }
+ return true;
+}
+
std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length) {
switch (compression_) {
case kCowCompressGz: {
@@ -379,6 +402,21 @@
auto continue_num_ops = footer_.op.num_ops;
bool extra_cluster = false;
+ // Blank out extra ops, in case we're in append mode and dropped ops.
+ if (cluster_size_) {
+ auto unused_cluster_space = cluster_size_ - current_cluster_size_;
+ std::string clr;
+ clr.resize(unused_cluster_space, '\0');
+ if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
+ PLOG(ERROR) << "Failed to seek to footer position.";
+ return false;
+ }
+ if (!android::base::WriteFully(fd_, clr.data(), clr.size())) {
+ PLOG(ERROR) << "clearing unused cluster area failed";
+ return false;
+ }
+ }
+
// Footer should be at the end of a file, so if there is data after the current block, end it
// and start a new cluster.
if (cluster_size_ && current_data_size_ > 0) {
@@ -403,6 +441,17 @@
return false;
}
+ // Remove excess data, if we're in append mode and threw away more data
+ // than we wrote before.
+ off_t offs = lseek(fd_.get(), 0, SEEK_CUR);
+ if (offs < 0) {
+ PLOG(ERROR) << "Failed to lseek to find current position";
+ return false;
+ }
+ if (!Truncate(offs)) {
+ return false;
+ }
+
// Reposition for additional Writing
if (extra_cluster) {
current_cluster_size_ = continue_cluster_size;
@@ -445,12 +494,7 @@
if (!WriteRawData(data, size)) return false;
}
AddOperation(op);
- // If there isn't room for another op and the cluster end op, end the current cluster
- if (cluster_size_ && op.type != kCowClusterOp &&
- cluster_size_ < current_cluster_size_ + 2 * sizeof(op)) {
- if (!EmitCluster()) return false;
- }
- return true;
+ return EmitClusterIfNeeded();
}
void CowWriter::AddOperation(const CowOperation& op) {
@@ -492,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 6ffd5d8..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;
@@ -115,6 +116,7 @@
private:
bool EmitCluster();
+ bool EmitClusterIfNeeded();
void SetupHeaders();
bool ParseOptions();
bool OpenForWrite();
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 7e74fac..195b6f2 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,6 +386,7 @@
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);
@@ -493,6 +499,9 @@
// Unmap a COW image device previously mapped with MapCowImage().
bool UnmapCowImage(const std::string& name);
+ // Unmap a COW and remove it from a MetadataBuilder.
+ void UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata);
+
// Unmap and remove all known snapshots.
bool RemoveAllSnapshots(LockedFile* lock);
@@ -529,7 +538,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;
@@ -538,12 +548,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();
@@ -570,14 +580,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.
@@ -738,6 +756,10 @@
// Helper of UpdateUsesCompression
bool UpdateUsesCompression(LockedFile* lock);
+ // Wrapper around libdm, with diagnostics.
+ bool DeleteDeviceIfExists(const std::string& name,
+ const std::chrono::milliseconds& timeout_ms = {});
+
std::string gsid_dir_;
std::string metadata_dir_;
std::unique_ptr<IDeviceInfo> device_;
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 a0a1e4f..8f3926a 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -21,6 +21,7 @@
#include <sys/types.h>
#include <sys/unistd.h>
+#include <filesystem>
#include <optional>
#include <thread>
#include <unordered_set>
@@ -587,8 +588,7 @@
bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) {
CHECK(lock);
- auto& dm = DeviceMapper::Instance();
- if (!dm.DeleteDeviceIfExists(name)) {
+ if (!DeleteDeviceIfExists(name)) {
LOG(ERROR) << "Could not delete snapshot device: " << name;
return false;
}
@@ -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,
@@ -1252,25 +1266,6 @@
return true;
}
-static bool DeleteDmDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms) {
- auto start = std::chrono::steady_clock::now();
- auto& dm = DeviceMapper::Instance();
- while (true) {
- if (dm.DeleteDeviceIfExists(name)) {
- break;
- }
- auto now = std::chrono::steady_clock::now();
- auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
- if (elapsed >= timeout_ms) {
- LOG(ERROR) << "DeleteDevice timeout: " << name;
- return false;
- }
- std::this_thread::sleep_for(400ms);
- }
-
- return true;
-}
-
bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
const SnapshotStatus& status) {
auto& dm = DeviceMapper::Instance();
@@ -1326,11 +1321,11 @@
UnmapDmUserDevice(name);
}
auto base_name = GetBaseDeviceName(name);
- if (!dm.DeleteDeviceIfExists(base_name)) {
+ if (!DeleteDeviceIfExists(base_name)) {
LOG(ERROR) << "Unable to delete base device for snapshot: " << base_name;
}
- if (!DeleteDmDevice(GetSourceDeviceName(name), 4000ms)) {
+ if (!DeleteDeviceIfExists(GetSourceDeviceName(name), 4000ms)) {
LOG(ERROR) << "Unable to delete source device for snapshot: " << GetSourceDeviceName(name);
}
@@ -2083,15 +2078,14 @@
return false;
}
- auto& dm = DeviceMapper::Instance();
auto base_name = GetBaseDeviceName(target_partition_name);
- if (!dm.DeleteDeviceIfExists(base_name)) {
+ if (!DeleteDeviceIfExists(base_name)) {
LOG(ERROR) << "Cannot delete base device: " << base_name;
return false;
}
auto source_name = GetSourceDeviceName(target_partition_name);
- if (!dm.DeleteDeviceIfExists(source_name)) {
+ if (!DeleteDeviceIfExists(source_name)) {
LOG(ERROR) << "Cannot delete source device: " << source_name;
return false;
}
@@ -2181,7 +2175,7 @@
return false;
}
- if (!DeleteDmDevice(GetCowName(name), 4000ms)) {
+ if (!DeleteDeviceIfExists(GetCowName(name), 4000ms)) {
LOG(ERROR) << "Cannot unmap: " << GetCowName(name);
return false;
}
@@ -2202,7 +2196,7 @@
return true;
}
- if (!dm.DeleteDeviceIfExists(dm_user_name)) {
+ if (!DeleteDeviceIfExists(dm_user_name)) {
LOG(ERROR) << "Cannot unmap " << dm_user_name;
return false;
}
@@ -2434,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)) {
@@ -2593,11 +2592,10 @@
return true;
}
-static void UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata) {
- auto& dm = DeviceMapper::Instance();
+void SnapshotManager::UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata) {
std::vector<std::string> to_delete;
for (auto* existing_cow_partition : current_metadata->ListPartitionsInGroup(kCowGroupName)) {
- if (!dm.DeleteDeviceIfExists(existing_cow_partition->name())) {
+ if (!DeleteDeviceIfExists(existing_cow_partition->name())) {
LOG(WARNING) << existing_cow_partition->name()
<< " cannot be unmapped and its space cannot be reclaimed";
continue;
@@ -2694,8 +2692,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) {
@@ -2962,7 +2970,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();
@@ -3071,6 +3085,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.
@@ -3626,5 +3644,82 @@
stats->set_estimated_cow_size_bytes(estimated_cow_size);
}
+bool SnapshotManager::DeleteDeviceIfExists(const std::string& name,
+ const std::chrono::milliseconds& timeout_ms) {
+ auto& dm = DeviceMapper::Instance();
+ auto start = std::chrono::steady_clock::now();
+ while (true) {
+ if (dm.DeleteDeviceIfExists(name)) {
+ return true;
+ }
+ auto now = std::chrono::steady_clock::now();
+ auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
+ if (elapsed >= timeout_ms) {
+ break;
+ }
+ std::this_thread::sleep_for(400ms);
+ }
+
+ // Try to diagnose why this failed. First get the actual device path.
+ std::string full_path;
+ if (!dm.GetDmDevicePathByName(name, &full_path)) {
+ LOG(ERROR) << "Unable to diagnose DM_DEV_REMOVE failure.";
+ return false;
+ }
+
+ // Check for child dm-devices.
+ std::string block_name = android::base::Basename(full_path);
+ std::string sysfs_holders = "/sys/class/block/" + block_name + "/holders";
+
+ std::error_code ec;
+ std::filesystem::directory_iterator dir_iter(sysfs_holders, ec);
+ if (auto begin = std::filesystem::begin(dir_iter); begin != std::filesystem::end(dir_iter)) {
+ LOG(ERROR) << "Child device-mapper device still mapped: " << begin->path();
+ return false;
+ }
+
+ // Check for mounted partitions.
+ android::fs_mgr::Fstab fstab;
+ android::fs_mgr::ReadFstabFromFile("/proc/mounts", &fstab);
+ for (const auto& entry : fstab) {
+ if (android::base::Basename(entry.blk_device) == block_name) {
+ LOG(ERROR) << "Partition still mounted: " << entry.mount_point;
+ return false;
+ }
+ }
+
+ // Check for detached mounted partitions.
+ for (const auto& fs : std::filesystem::directory_iterator("/sys/fs", ec)) {
+ std::string fs_type = android::base::Basename(fs.path().c_str());
+ if (!(fs_type == "ext4" || fs_type == "f2fs")) {
+ continue;
+ }
+
+ std::string path = fs.path().c_str() + "/"s + block_name;
+ if (access(path.c_str(), F_OK) == 0) {
+ LOG(ERROR) << "Block device was lazily unmounted and is still in-use: " << full_path
+ << "; possibly open file descriptor or attached loop device.";
+ return false;
+ }
+ }
+
+ LOG(ERROR) << "Device-mapper device " << name << "(" << full_path << ")"
+ << " still in use."
+ << " Probably a file descriptor was leaked or held open, or a loop device is"
+ << " attached.";
+ 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..45db7a4 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
diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp
index 5ef9e29..2ccc750 100644
--- a/fs_mgr/libsnapshot/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -47,28 +47,202 @@
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) {
+ CHECK(lock->owns_lock());
+ 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 +267,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();
}
@@ -175,8 +349,10 @@
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 +434,15 @@
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 +478,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,14 +555,10 @@
if (diff != 1) {
break;
}
- if (dest_blocks.count(cow_op->new_block) || map.count(cow_op->source) > 0) {
- break;
- }
}
metadata_found = true;
pending_copy_ops -= 1;
map[cow_op->new_block] = cow_op;
- dest_blocks.insert(cow_op->source);
prev_id = cow_op->new_block;
cowop_riter_->Next();
} while (!cowop_riter_->Done() && pending_copy_ops);
@@ -426,6 +579,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: "
@@ -453,9 +609,17 @@
}
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();
prev_id.reset();
}
@@ -470,6 +634,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 +649,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 +704,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 +736,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..09ee2f2
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd_readahead.cpp
@@ -0,0 +1,460 @@
+/*
+ * 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.
+ CHECK(num_ops == 0);
+ 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;
+ CHECK(offset == buffer_offset);
+ CHECK((file_offset - snapuserd_->GetBufferDataOffset()) == offset);
+ }
+
+ 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..ff8a259 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;
@@ -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_);
diff --git a/fs_mgr/libsnapshot/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd_worker.cpp
index 1002569..9f42ab8 100644
--- a/fs_mgr/libsnapshot/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_worker.cpp
@@ -135,14 +135,11 @@
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.
+ 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,6 +150,31 @@
return true;
}
+bool WorkerThread::GetReadAheadPopulatedBuffer(const CowOperation* cow_op) {
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ CHECK(buffer != nullptr);
+
+ 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);
@@ -386,8 +408,10 @@
}
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.
@@ -411,6 +435,23 @@
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.
@@ -442,6 +483,8 @@
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);
@@ -452,13 +495,24 @@
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);
+ 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)) {
- 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;
@@ -613,12 +667,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 5887641..83174ef 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,8 +321,7 @@
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(
@@ -316,88 +345,69 @@
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(
@@ -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,9 +436,9 @@
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;
@@ -439,9 +449,9 @@
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;
@@ -451,25 +461,25 @@
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 +501,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 +571,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 +595,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 +636,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(2U, fstab.size());
+ ASSERT_LE(2U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -652,7 +662,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(2U, fstab.size());
+ ASSERT_LE(2U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -682,7 +692,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(6U, fstab.size());
+ ASSERT_LE(6U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -728,7 +738,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 +761,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 +785,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 +807,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(2U, fstab.size());
+ ASSERT_LE(2U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -825,7 +835,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(4U, fstab.size());
+ ASSERT_LE(4U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -863,7 +873,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(4U, fstab.size());
+ ASSERT_LE(4U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -901,7 +911,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(4U, fstab.size());
+ ASSERT_LE(4U, fstab.size());
FstabEntry::FsMgrFlags flags = {};
@@ -938,7 +948,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 +977,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 +999,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 +1016,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 +1037,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 +1063,7 @@
Fstab fstab;
EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
- ASSERT_EQ(4U, fstab.size());
+ ASSERT_LE(4U, fstab.size());
auto entry = fstab.begin();
@@ -1097,3 +1107,59 @@
ASSERT_NE(nullptr, fs_mgr_get_mounted_entry_for_userdata(&fstab, block_device))
<< "/data wasn't mounted from default fstab";
}
+
+TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Readahead_Size_KB) {
+ TemporaryFile tf;
+ ASSERT_TRUE(tf.fd != -1);
+ std::string fstab_contents = R"fs(
+source none0 swap defaults readahead_size_kb=blah
+source none1 swap defaults readahead_size_kb=128
+source none2 swap defaults readahead_size_kb=5%
+source none3 swap defaults readahead_size_kb=5kb
+source none4 swap defaults readahead_size_kb=16385
+source none5 swap defaults readahead_size_kb=-128
+source none6 swap defaults readahead_size_kb=0
+)fs";
+ ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
+
+ Fstab fstab;
+ EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
+ ASSERT_LE(7U, fstab.size());
+
+ FstabEntry::FsMgrFlags flags = {};
+
+ auto entry = fstab.begin();
+ EXPECT_EQ("none0", entry->mount_point);
+ EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+ EXPECT_EQ(-1, entry->readahead_size_kb);
+ entry++;
+
+ EXPECT_EQ("none1", entry->mount_point);
+ EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+ EXPECT_EQ(128, entry->readahead_size_kb);
+ entry++;
+
+ EXPECT_EQ("none2", entry->mount_point);
+ EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+ EXPECT_EQ(-1, entry->readahead_size_kb);
+ entry++;
+
+ EXPECT_EQ("none3", entry->mount_point);
+ EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+ EXPECT_EQ(-1, entry->readahead_size_kb);
+ entry++;
+
+ EXPECT_EQ("none4", entry->mount_point);
+ EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+ EXPECT_EQ(-1, entry->readahead_size_kb);
+ entry++;
+
+ EXPECT_EQ("none5", entry->mount_point);
+ EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+ EXPECT_EQ(-1, entry->readahead_size_kb);
+ entry++;
+
+ EXPECT_EQ("none6", entry->mount_point);
+ EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+ EXPECT_EQ(0, entry->readahead_size_kb);
+}
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/first_stage_init.cpp b/init/first_stage_init.cpp
index 5cde167..84cda98 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -327,28 +327,22 @@
LOG(INFO) << "Copied ramdisk prop to " << dest;
}
- 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
- // target directory to itself here.
- if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
- LOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
- }
- SwitchRoot("/first_stage_ramdisk");
- }
-
- std::string force_debuggable("/force_debuggable");
+ // 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.
+ bool found_debuggable = false;
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");
+ if (access("/force_debuggable", F_OK) == 0) {
+ found_debuggable = true;
+ } else if (access("/first_stage_ramdisk/force_debuggable", F_OK) == 0) {
+ // Fallback to legacy debug resource paths.
+ // TODO(b/186485355): removes the fallback path once it is not needed.
+ found_debuggable = true;
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) {
+
+ if (found_debuggable) {
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)) {
@@ -359,6 +353,16 @@
}
}
+ 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
+ // target directory to itself here.
+ if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
+ LOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
+ }
+ SwitchRoot("/first_stage_ramdisk");
+ }
+
if (!DoFirstStageMount(!created_devices)) {
LOG(FATAL) << "Failed to mount required partitions early ...";
}
diff --git a/init/init.cpp b/init/init.cpp
index 7264b22..a7325ca 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -849,21 +849,6 @@
auto is_installed = android::gsi::IsGsiInstalled() ? "1" : "0";
SetProperty(gsi::kGsiInstalledProp, is_installed);
- /*
- * For debug builds of S launching devices, init mounts debugfs for
- * enabling vendor debug data collection setup at boot time. Init will unmount it on
- * boot-complete after vendor code has performed the required initializations
- * during boot. Dumpstate will then mount debugfs in order to read data
- * from the same using the dumpstate HAL during bugreport creation.
- * Dumpstate will also unmount debugfs after bugreport creation.
- * first_api_level comparison is done here instead
- * of init.rc since init.rc parser does not support >/< operators.
- */
- auto api_level = android::base::GetIntProperty("ro.product.first_api_level", 0);
- bool is_debuggable = android::base::GetBoolProperty("ro.debuggable", false);
- auto mount_debugfs = (is_debuggable && (api_level >= 31)) ? "1" : "0";
- SetProperty("init.mount_debugfs", mount_debugfs);
-
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
diff --git a/init/property_service.cpp b/init/property_service.cpp
index c2eb73c..fe3490d 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -1187,8 +1187,6 @@
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 =
@@ -1220,13 +1218,20 @@
});
}
+// 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/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 2d3e06e..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;
@@ -372,6 +372,12 @@
system_ext_mapping_file.clear();
}
+ std::string system_ext_compat_cil_file("/system_ext/etc/selinux/mapping/" + vend_plat_vers +
+ ".compat.cil");
+ if (access(system_ext_compat_cil_file.c_str(), F_OK) == -1) {
+ system_ext_compat_cil_file.clear();
+ }
+
std::string product_policy_cil_file("/product/etc/selinux/product_sepolicy.cil");
if (access(product_policy_cil_file.c_str(), F_OK) == -1) {
product_policy_cil_file.clear();
@@ -426,6 +432,9 @@
if (!system_ext_mapping_file.empty()) {
compile_args.push_back(system_ext_mapping_file.c_str());
}
+ if (!system_ext_compat_cil_file.empty()) {
+ compile_args.push_back(system_ext_compat_cil_file.c_str());
+ }
if (!product_policy_cil_file.empty()) {
compile_args.push_back(product_policy_cil_file.c_str());
}
diff --git a/init/service.cpp b/init/service.cpp
index 836dc47..c3069f5 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -460,7 +460,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/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/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 5b57bdd..bd94621 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -70,11 +70,11 @@
"Name": "Frozen",
"Actions": [
{
- "Name": "JoinCgroup",
+ "Name": "SetAttribute",
"Params":
{
- "Controller": "freezer",
- "Path": ""
+ "Name": "FreezerState",
+ "Value": "1"
}
}
]
@@ -83,11 +83,11 @@
"Name": "Unfrozen",
"Actions": [
{
- "Name": "JoinCgroup",
+ "Name": "SetAttribute",
"Params":
{
- "Controller": "freezer",
- "Path": "../"
+ "Name": "FreezerState",
+ "Value": "0"
}
}
]
@@ -558,32 +558,6 @@
}
]
},
- {
- "Name": "FreezerDisabled",
- "Actions": [
- {
- "Name": "SetAttribute",
- "Params":
- {
- "Name": "FreezerState",
- "Value": "0"
- }
- }
- ]
- },
- {
- "Name": "FreezerEnabled",
- "Actions": [
- {
- "Name": "SetAttribute",
- "Params":
- {
- "Name": "FreezerState",
- "Value": "1"
- }
- }
- ]
- }
],
"AggregateProfiles": [
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/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index f13a681..db00a49 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -518,10 +518,10 @@
std::string attr_filepath = params_val["FilePath"].asString();
std::string attr_value = params_val["Value"].asString();
if (!attr_filepath.empty() && !attr_value.empty()) {
- const Json::Value& logfailures = params_val["LogFailures"];
- bool attr_logfailures = logfailures.isNull() || logfailures.asBool();
+ std::string attr_logfailures = params_val["LogFailures"].asString();
+ bool logfailures = attr_logfailures.empty() || attr_logfailures == "true";
profile->Add(std::make_unique<WriteFileAction>(attr_filepath, attr_value,
- attr_logfailures));
+ logfailures));
} else if (attr_filepath.empty()) {
LOG(WARNING) << "WriteFile: invalid parameter: "
<< "empty filepath";
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/RefBase_test.cpp b/libutils/RefBase_test.cpp
index dcc469e..93f9654 100644
--- a/libutils/RefBase_test.cpp
+++ b/libutils/RefBase_test.cpp
@@ -242,12 +242,12 @@
}
TEST(RefBase, AssertWeakRefExistsSuccess) {
- // uses some other refcounting method, or non at all
bool isDeleted;
sp<Foo> foo = sp<Foo>::make(&isDeleted);
wp<Foo> weakFoo = foo;
EXPECT_EQ(weakFoo, wp<Foo>::fromExisting(foo.get()));
+ EXPECT_EQ(weakFoo.unsafe_get(), wp<Foo>::fromExisting(foo.get()).unsafe_get());
EXPECT_FALSE(isDeleted);
foo = nullptr;
@@ -255,7 +255,7 @@
}
TEST(RefBase, AssertWeakRefExistsDeath) {
- // uses some other refcounting method, or non at all
+ // uses some other refcounting method, or none at all
bool isDeleted;
Foo* foo = new Foo(&isDeleted);
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/String16.cpp b/libutils/String16.cpp
index 70bf5a0..e3e5f11 100644
--- a/libutils/String16.cpp
+++ b/libutils/String16.cpp
@@ -390,28 +390,6 @@
return static_cast<size_t>(*(p - 1));
}
-status_t String16::makeLower()
-{
- const size_t N = size();
- const char16_t* str = string();
- char16_t* edited = nullptr;
- for (size_t i=0; i<N; i++) {
- const char16_t v = str[i];
- if (v >= 'A' && v <= 'Z') {
- if (!edited) {
- SharedBuffer* buf = static_cast<SharedBuffer*>(edit());
- if (!buf) {
- return NO_MEMORY;
- }
- edited = (char16_t*)buf->data();
- mString = str = edited;
- }
- edited[i] = tolower((char)v);
- }
- }
- return OK;
-}
-
status_t String16::replaceAll(char16_t replaceThis, char16_t withThis)
{
const size_t N = size();
diff --git a/libutils/String16_fuzz.cpp b/libutils/String16_fuzz.cpp
index 63c2800..defa0f5 100644
--- a/libutils/String16_fuzz.cpp
+++ b/libutils/String16_fuzz.cpp
@@ -34,11 +34,6 @@
str1.size();
}),
- // Casing
- ([](FuzzedDataProvider&, android::String16 str1, android::String16) -> void {
- str1.makeLower();
- }),
-
// Comparison
([](FuzzedDataProvider&, android::String16 str1, android::String16 str2) -> void {
str1.startsWith(str2);
diff --git a/libutils/String16_test.cpp b/libutils/String16_test.cpp
index 2505f44..c2e9b02 100644
--- a/libutils/String16_test.cpp
+++ b/libutils/String16_test.cpp
@@ -97,13 +97,6 @@
EXPECT_STR16EQ(u" m", tmp);
}
-TEST(String16Test, MakeLower) {
- String16 tmp("Verify Me!");
- tmp.makeLower();
- EXPECT_EQ(10U, tmp.size());
- EXPECT_STR16EQ(u"verify me!", tmp);
-}
-
TEST(String16Test, ReplaceAll) {
String16 tmp("Verify verify Verify");
tmp.replaceAll(u'r', u'!');
@@ -176,14 +169,6 @@
EXPECT_FALSE(tmp.isStaticString());
}
-TEST(String16Test, StaticStringMakeLower) {
- StaticString16 tmp(u"Verify me!");
- tmp.makeLower();
- EXPECT_EQ(10U, tmp.size());
- EXPECT_STR16EQ(u"verify me!", tmp);
- EXPECT_FALSE(tmp.isStaticString());
-}
-
TEST(String16Test, StaticStringReplaceAll) {
StaticString16 tmp(u"Verify verify Verify");
tmp.replaceAll(u'r', u'!');
diff --git a/libutils/String8.cpp b/libutils/String8.cpp
index 3dc2026..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))
@@ -415,50 +415,15 @@
void String8::toLower()
{
- toLower(0, size());
-}
+ const size_t length = size();
+ if (length == 0) return;
-void String8::toLower(size_t start, size_t length)
-{
- const size_t len = size();
- if (start >= len) {
- return;
- }
- if (start+length > len) {
- length = len-start;
- }
- char* buf = lockBuffer(len);
- buf += start;
- while (length > 0) {
+ char* buf = lockBuffer(length);
+ for (size_t i = length; i > 0; --i) {
*buf = static_cast<char>(tolower(*buf));
buf++;
- length--;
}
- unlockBuffer(len);
-}
-
-void String8::toUpper()
-{
- toUpper(0, size());
-}
-
-void String8::toUpper(size_t start, size_t length)
-{
- const size_t len = size();
- if (start >= len) {
- return;
- }
- if (start+length > len) {
- length = len-start;
- }
- char* buf = lockBuffer(len);
- buf += start;
- while (length > 0) {
- *buf = static_cast<char>(toupper(*buf));
- buf++;
- length--;
- }
- unlockBuffer(len);
+ unlockBuffer(length);
}
// ---------------------------------------------------------------------------
diff --git a/libutils/String8_fuzz.cpp b/libutils/String8_fuzz.cpp
index b02683c..a45d675 100644
--- a/libutils/String8_fuzz.cpp
+++ b/libutils/String8_fuzz.cpp
@@ -42,9 +42,6 @@
// Casing
[](FuzzedDataProvider*, android::String8* str1, android::String8*) -> void {
- str1->toUpper();
- },
- [](FuzzedDataProvider*, android::String8* str1, android::String8*) -> void {
str1->toLower();
},
[](FuzzedDataProvider*, android::String8* str1, android::String8* str2) -> void {
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/RefBase.h b/libutils/include/utils/RefBase.h
index 5a5bd56..e07f574 100644
--- a/libutils/include/utils/RefBase.h
+++ b/libutils/include/utils/RefBase.h
@@ -416,13 +416,16 @@
wp(std::nullptr_t) : wp() {}
#else
wp(T* other); // NOLINT(implicit)
+ template <typename U>
+ wp(U* other); // NOLINT(implicit)
+ wp& operator=(T* other);
+ template <typename U>
+ wp& operator=(U* other);
#endif
+
wp(const wp<T>& other);
explicit wp(const sp<T>& other);
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
- template<typename U> wp(U* other); // NOLINT(implicit)
-#endif
template<typename U> wp(const sp<U>& other); // NOLINT(implicit)
template<typename U> wp(const wp<U>& other); // NOLINT(implicit)
@@ -430,15 +433,9 @@
// Assignment
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
- wp& operator = (T* other);
-#endif
wp& operator = (const wp<T>& other);
wp& operator = (const sp<T>& other);
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
- template<typename U> wp& operator = (U* other);
-#endif
template<typename U> wp& operator = (const wp<U>& other);
template<typename U> wp& operator = (const sp<U>& other);
@@ -547,6 +544,7 @@
refs->incWeakRequireWeak(other);
wp<T> ret;
+ ret.m_ptr = other;
ret.m_refs = refs;
return ret;
}
@@ -558,6 +556,31 @@
{
m_refs = other ? m_refs = other->createWeak(this) : nullptr;
}
+
+template <typename T>
+template <typename U>
+wp<T>::wp(U* other) : m_ptr(other) {
+ m_refs = other ? other->createWeak(this) : nullptr;
+}
+
+template <typename T>
+wp<T>& wp<T>::operator=(T* other) {
+ weakref_type* newRefs = other ? other->createWeak(this) : nullptr;
+ if (m_ptr) m_refs->decWeak(this);
+ m_ptr = other;
+ m_refs = newRefs;
+ return *this;
+}
+
+template <typename T>
+template <typename U>
+wp<T>& wp<T>::operator=(U* other) {
+ weakref_type* newRefs = other ? other->createWeak(this) : 0;
+ if (m_ptr) m_refs->decWeak(this);
+ m_ptr = other;
+ m_refs = newRefs;
+ return *this;
+}
#endif
template<typename T>
@@ -574,15 +597,6 @@
m_refs = m_ptr ? m_ptr->createWeak(this) : nullptr;
}
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
-template<typename T> template<typename U>
-wp<T>::wp(U* other)
- : m_ptr(other)
-{
- m_refs = other ? other->createWeak(this) : nullptr;
-}
-#endif
-
template<typename T> template<typename U>
wp<T>::wp(const wp<U>& other)
: m_ptr(other.m_ptr)
@@ -608,19 +622,6 @@
if (m_ptr) m_refs->decWeak(this);
}
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
-template<typename T>
-wp<T>& wp<T>::operator = (T* other)
-{
- weakref_type* newRefs =
- other ? other->createWeak(this) : nullptr;
- if (m_ptr) m_refs->decWeak(this);
- m_ptr = other;
- m_refs = newRefs;
- return *this;
-}
-#endif
-
template<typename T>
wp<T>& wp<T>::operator = (const wp<T>& other)
{
@@ -645,19 +646,6 @@
return *this;
}
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
-template<typename T> template<typename U>
-wp<T>& wp<T>::operator = (U* other)
-{
- weakref_type* newRefs =
- other ? other->createWeak(this) : 0;
- if (m_ptr) m_refs->decWeak(this);
- m_ptr = other;
- m_refs = newRefs;
- return *this;
-}
-#endif
-
template<typename T> template<typename U>
wp<T>& wp<T>::operator = (const wp<U>& other)
{
diff --git a/libutils/include/utils/String16.h b/libutils/include/utils/String16.h
index 1a4b47e..5ce48c6 100644
--- a/libutils/include/utils/String16.h
+++ b/libutils/include/utils/String16.h
@@ -85,8 +85,6 @@
bool contains(const char16_t* chrs) const;
- status_t makeLower();
-
status_t replaceAll(char16_t replaceThis,
char16_t withThis);
diff --git a/libutils/include/utils/String8.h b/libutils/include/utils/String8.h
index 0bcb716..cee5dc6 100644
--- a/libutils/include/utils/String8.h
+++ b/libutils/include/utils/String8.h
@@ -130,9 +130,6 @@
bool removeAll(const char* other);
void toLower();
- void toLower(size_t start, size_t numChars);
- void toUpper();
- void toUpper(size_t start, size_t numChars);
/*
diff --git a/libutils/include/utils/StrongPointer.h b/libutils/include/utils/StrongPointer.h
index 1f07052..bb1941b 100644
--- a/libutils/include/utils/StrongPointer.h
+++ b/libutils/include/utils/StrongPointer.h
@@ -62,31 +62,34 @@
sp(std::nullptr_t) : sp() {}
#else
sp(T* other); // NOLINT(implicit)
+ template <typename U>
+ sp(U* other); // NOLINT(implicit)
+ sp& operator=(T* other);
+ template <typename U>
+ sp& operator=(U* other);
#endif
+
sp(const sp<T>& other);
sp(sp<T>&& other) noexcept;
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
- template<typename U> sp(U* other); // NOLINT(implicit)
-#endif
template<typename U> sp(const sp<U>& other); // NOLINT(implicit)
template<typename U> sp(sp<U>&& other); // NOLINT(implicit)
+ // Cast a strong pointer directly from one type to another. Constructors
+ // allow changing types, but only if they are pointer-compatible. This does
+ // a static_cast internally.
+ template <typename U>
+ static inline sp<T> cast(const sp<U>& other);
+
~sp();
// Assignment
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
- sp& operator = (T* other);
-#endif
sp& operator = (const sp<T>& other);
sp& operator=(sp<T>&& other) noexcept;
template<typename U> sp& operator = (const sp<U>& other);
template<typename U> sp& operator = (sp<U>&& other);
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
- template<typename U> sp& operator = (U* other);
-#endif
//! Special optimization for use by ProcessState (and nobody else).
void force_set(T* other);
@@ -241,6 +244,28 @@
other->incStrong(this);
}
}
+
+template <typename T>
+template <typename U>
+sp<T>::sp(U* other) : m_ptr(other) {
+ if (other) {
+ check_not_on_stack(other);
+ (static_cast<T*>(other))->incStrong(this);
+ }
+}
+
+template <typename T>
+sp<T>& sp<T>::operator=(T* other) {
+ T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
+ if (other) {
+ check_not_on_stack(other);
+ other->incStrong(this);
+ }
+ if (oldPtr) oldPtr->decStrong(this);
+ if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
+ m_ptr = other;
+ return *this;
+}
#endif
template<typename T>
@@ -255,17 +280,6 @@
other.m_ptr = nullptr;
}
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
-template<typename T> template<typename U>
-sp<T>::sp(U* other)
- : m_ptr(other) {
- if (other) {
- check_not_on_stack(other);
- (static_cast<T*>(other))->incStrong(this);
- }
-}
-#endif
-
template<typename T> template<typename U>
sp<T>::sp(const sp<U>& other)
: m_ptr(other.m_ptr) {
@@ -279,6 +293,12 @@
other.m_ptr = nullptr;
}
+template <typename T>
+template <typename U>
+sp<T> sp<T>::cast(const sp<U>& other) {
+ return sp<T>::fromExisting(static_cast<T*>(other.get()));
+}
+
template<typename T>
sp<T>::~sp() {
if (m_ptr)
@@ -307,21 +327,6 @@
return *this;
}
-#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
-template<typename T>
-sp<T>& sp<T>::operator =(T* other) {
- T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
- if (other) {
- check_not_on_stack(other);
- other->incStrong(this);
- }
- if (oldPtr) oldPtr->decStrong(this);
- if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
- m_ptr = other;
- return *this;
-}
-#endif
-
template<typename T> template<typename U>
sp<T>& sp<T>::operator =(const sp<U>& other) {
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
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/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 6a80808..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 {
@@ -35,3 +38,11 @@
src: "etc/linker.config.json",
installable: false,
}
+
+// TODO(b/185211376) Scope the native APIs that microdroid will provide to the app payload
+prebuilt_etc {
+ name: "public.libraries.android.txt",
+ src: "etc/public.libraries.android.txt",
+ filename: "public.libraries.txt",
+ installable: false,
+}
\ No newline at end of file
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 83cb6ff..a22ef6f 100644
--- a/rootdir/etc/linker.config.json
+++ b/rootdir/etc/linker.config.json
@@ -1,8 +1,7 @@
{
"requireLibs": [
- // Keep in sync with the "platform" namespace in art/build/apex/ld.config.txt.
- "libdexfile_external.so",
- "libdexfiled_external.so",
+ "libdexfile.so",
+ "libdexfiled.so",
"libnativebridge.so",
"libnativehelper.so",
"libnativeloader.so",
@@ -19,6 +18,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 9a30ead..4ec5d33 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,7 +665,30 @@
# 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
@@ -676,13 +697,10 @@
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
@@ -755,6 +772,8 @@
mkdir /data/misc/snapshotctl_log 0755 root root
# create location to store pre-reboot information
mkdir /data/misc/prereboot 0700 system system
+ # directory used for on-device refresh metrics file.
+ mkdir /data/misc/odrefresh 0777 system system
# directory used for on-device signing key blob
mkdir /data/misc/odsign 0700 root root
@@ -903,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.
@@ -914,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
@@ -985,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:init.mount_debugfs=1
- umount /sys/kernel/debug
-
on boot
# basic network init
ifup lo
@@ -1231,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 ""
@@ -1263,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:init.mount_debugfs=1
- 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/rootdir/ueventd.rc b/rootdir/ueventd.rc
index 65e29c1..56e774b 100644
--- a/rootdir/ueventd.rc
+++ b/rootdir/ueventd.rc
@@ -67,6 +67,10 @@
# CDMA radio interface MUX
/dev/ppp 0660 radio vpn
+# Virtualisation is managed by Virt Manager
+/dev/kvm 0600 virtmanager root
+/dev/vhost-vsock 0600 virtmanager root
+
# sysfs properties
/sys/devices/platform/trusty.* trusty_version 0440 root log
/sys/devices/virtual/input/input* enable 0660 root input
diff --git a/trusty/fuzz/include/trusty/fuzz/utils.h b/trusty/fuzz/include/trusty/fuzz/utils.h
index bca84e9..c906412 100644
--- a/trusty/fuzz/include/trusty/fuzz/utils.h
+++ b/trusty/fuzz/include/trusty/fuzz/utils.h
@@ -34,6 +34,7 @@
android::base::Result<void> Connect();
android::base::Result<void> Read(void* buf, size_t len);
android::base::Result<void> Write(const void* buf, size_t len);
+ void Disconnect();
android::base::Result<int> GetRawFd();
diff --git a/trusty/fuzz/tipc_fuzzer.cpp b/trusty/fuzz/tipc_fuzzer.cpp
index 3258944..f265ced 100644
--- a/trusty/fuzz/tipc_fuzzer.cpp
+++ b/trusty/fuzz/tipc_fuzzer.cpp
@@ -41,6 +41,7 @@
#error "Binary file name must be parameterized using -DTRUSTY_APP_FILENAME."
#endif
+static TrustyApp kTrustyApp(TIPC_DEV, TRUSTY_APP_PORT);
static std::unique_ptr<CoverageRecord> record;
extern "C" int LLVMFuzzerInitialize(int* /* argc */, char*** /* argv */) {
@@ -52,8 +53,7 @@
}
/* Make sure lazy-loaded TAs have started and connected to coverage service. */
- TrustyApp ta(TIPC_DEV, TRUSTY_APP_PORT);
- auto ret = ta.Connect();
+ auto ret = kTrustyApp.Connect();
if (!ret.ok()) {
std::cerr << ret.error() << std::endl;
exit(-1);
@@ -79,22 +79,18 @@
ExtraCounters counters(record.get());
counters.Reset();
- TrustyApp ta(TIPC_DEV, TRUSTY_APP_PORT);
- auto ret = ta.Connect();
+ auto ret = kTrustyApp.Write(data, size);
+ if (ret.ok()) {
+ ret = kTrustyApp.Read(&buf, sizeof(buf));
+ }
+
+ // Reconnect to ensure that the service is still up
+ kTrustyApp.Disconnect();
+ ret = kTrustyApp.Connect();
if (!ret.ok()) {
std::cerr << ret.error() << std::endl;
android::trusty::fuzz::Abort();
}
- ret = ta.Write(data, size);
- if (!ret.ok()) {
- return -1;
- }
-
- ret = ta.Read(&buf, sizeof(buf));
- if (!ret.ok()) {
- return -1;
- }
-
- return 0;
+ return ret.ok() ? 0 : -1;
}
diff --git a/trusty/fuzz/utils.cpp b/trusty/fuzz/utils.cpp
index 3526337..bb096be 100644
--- a/trusty/fuzz/utils.cpp
+++ b/trusty/fuzz/utils.cpp
@@ -127,6 +127,10 @@
return ta_fd_;
}
+void TrustyApp::Disconnect() {
+ ta_fd_.reset();
+}
+
void Abort() {
PrintTrustyLog();
exit(-1);
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