Merge "Add libsigchain as dependency of platform."
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 68a43cf..c8612bf 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -303,6 +303,7 @@
process_info->gwp_asan_metadata = crash_info->data.d.gwp_asan_metadata;
process_info->scudo_stack_depot = crash_info->data.d.scudo_stack_depot;
process_info->scudo_region_info = crash_info->data.d.scudo_region_info;
+ process_info->scudo_ring_buffer = crash_info->data.d.scudo_ring_buffer;
FALLTHROUGH_INTENDED;
case 1:
case 2:
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 9e9557f..ab95768 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -392,7 +392,11 @@
}
#endif
-TEST_F(CrasherTest, mte_uaf) {
+struct SizeParamCrasherTest : CrasherTest, testing::WithParamInterface<size_t> {};
+
+INSTANTIATE_TEST_SUITE_P(Sizes, SizeParamCrasherTest, testing::Values(16, 131072));
+
+TEST_P(SizeParamCrasherTest, mte_uaf) {
#if defined(__aarch64__)
if (!mte_supported()) {
GTEST_SKIP() << "Requires MTE";
@@ -400,9 +404,9 @@
int intercept_result;
unique_fd output_fd;
- StartProcess([]() {
+ StartProcess([&]() {
SetTagCheckingLevelSync();
- volatile int* p = (volatile int*)malloc(16);
+ volatile int* p = (volatile int*)malloc(GetParam());
free((void *)p);
p[0] = 42;
});
@@ -417,8 +421,9 @@
std::string result;
ConsumeFd(std::move(output_fd), &result);
- ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
- ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a 16-byte allocation.*
+ ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
+ ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a )" +
+ std::to_string(GetParam()) + R"(-byte allocation.*
allocated by thread .*
#00 pc)");
@@ -429,7 +434,7 @@
#endif
}
-TEST_F(CrasherTest, mte_overflow) {
+TEST_P(SizeParamCrasherTest, mte_overflow) {
#if defined(__aarch64__)
if (!mte_supported()) {
GTEST_SKIP() << "Requires MTE";
@@ -437,10 +442,10 @@
int intercept_result;
unique_fd output_fd;
- StartProcess([]() {
+ StartProcess([&]() {
SetTagCheckingLevelSync();
- volatile int* p = (volatile int*)malloc(16);
- p[4] = 42;
+ volatile char* p = (volatile char*)malloc(GetParam());
+ p[GetParam()] = 42;
});
StartIntercept(&output_fd);
@@ -454,7 +459,8 @@
ConsumeFd(std::move(output_fd), &result);
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
- ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation.*
+ ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
+ std::to_string(GetParam()) + R"(-byte allocation.*
allocated by thread .*
#00 pc)");
@@ -463,7 +469,7 @@
#endif
}
-TEST_F(CrasherTest, mte_underflow) {
+TEST_P(SizeParamCrasherTest, mte_underflow) {
#if defined(__aarch64__)
if (!mte_supported()) {
GTEST_SKIP() << "Requires MTE";
@@ -471,9 +477,9 @@
int intercept_result;
unique_fd output_fd;
- StartProcess([]() {
+ StartProcess([&]() {
SetTagCheckingLevelSync();
- volatile int* p = (volatile int*)malloc(16);
+ volatile int* p = (volatile int*)malloc(GetParam());
p[-1] = 42;
});
@@ -488,7 +494,8 @@
ConsumeFd(std::move(output_fd), &result);
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
- ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a 16-byte allocation.*
+ ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a )" +
+ std::to_string(GetParam()) + R"(-byte allocation.*
allocated by thread .*
#00 pc)");
diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h
index 254ed4f..bc08327 100644
--- a/debuggerd/include/debuggerd/handler.h
+++ b/debuggerd/include/debuggerd/handler.h
@@ -42,6 +42,7 @@
const gwp_asan::AllocationMetadata* gwp_asan_metadata;
const char* scudo_stack_depot;
const char* scudo_region_info;
+ const char* scudo_ring_buffer;
};
// These callbacks are called in a signal handler, and thus must be async signal safe.
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/types.h b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
index d5b0735..dcb52f9 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
@@ -46,6 +46,7 @@
uintptr_t gwp_asan_metadata = 0;
uintptr_t scudo_stack_depot = 0;
uintptr_t scudo_region_info = 0;
+ uintptr_t scudo_ring_buffer = 0;
bool has_fault_address = false;
uintptr_t untagged_fault_address = 0;
diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp
index 141c3bd..1c3437f 100644
--- a/debuggerd/libdebuggerd/scudo.cpp
+++ b/debuggerd/libdebuggerd/scudo.cpp
@@ -43,6 +43,8 @@
__scudo_get_stack_depot_size());
auto region_info = AllocAndReadFully(process_memory, process_info.scudo_region_info,
__scudo_get_region_info_size());
+ auto ring_buffer = AllocAndReadFully(process_memory, process_info.scudo_ring_buffer,
+ __scudo_get_ring_buffer_size());
untagged_fault_addr_ = process_info.untagged_fault_address;
uintptr_t fault_page = untagged_fault_addr_ & ~(PAGE_SIZE - 1);
@@ -68,8 +70,8 @@
}
__scudo_get_error_info(&error_info_, process_info.maybe_tagged_fault_address, stack_depot.get(),
- region_info.get(), memory.get(), memory_tags.get(), memory_begin,
- memory_end - memory_begin);
+ region_info.get(), ring_buffer.get(), memory.get(), memory_tags.get(),
+ memory_begin, memory_end - memory_begin);
}
bool ScudoCrashData::CrashIsMine() const {
diff --git a/debuggerd/protocol.h b/debuggerd/protocol.h
index 53a76ea..f33b2f0 100644
--- a/debuggerd/protocol.h
+++ b/debuggerd/protocol.h
@@ -97,6 +97,7 @@
uintptr_t gwp_asan_metadata;
uintptr_t scudo_stack_depot;
uintptr_t scudo_region_info;
+ uintptr_t scudo_ring_buffer;
};
struct __attribute__((__packed__)) CrashInfo {
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 1efe793..6952cdf 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -2203,7 +2203,8 @@
// Devices upgrading to dynamic partitions are allowed to specify a super
// partition name. This includes cuttlefish, which is a non-A/B device.
std::string super_partition;
- if (fs_mgr_get_boot_config_from_kernel_cmdline("super_partition", &super_partition)) {
+ if (fs_mgr_get_boot_config_from_bootconfig_source("super_partition", &super_partition) ||
+ fs_mgr_get_boot_config_from_kernel_cmdline("super_partition", &super_partition)) {
if (fs_mgr_get_slot_suffix().empty()) {
return super_partition;
}
diff --git a/fs_mgr/fs_mgr_boot_config.cpp b/fs_mgr/fs_mgr_boot_config.cpp
index 75d1e0d..e3ef232 100644
--- a/fs_mgr/fs_mgr_boot_config.cpp
+++ b/fs_mgr/fs_mgr_boot_config.cpp
@@ -91,6 +91,12 @@
if (key == bootconfig_key) {
*out_val = value;
return true;
+ } else if (android_key == "hardware" && android_key == key) {
+ // bootconfig doesn't allow subkeys and values to coexist, so
+ // "androidboot.hardware" cannot be used. It is replaced in
+ // bootconfig with "hardware"
+ *out_val = value;
+ return true;
}
}
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 785a8e0..0c0862e 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -299,7 +299,8 @@
std::string InitAndroidDtDir() {
std::string android_dt_dir;
// The platform may specify a custom Android DT path in kernel cmdline
- if (!fs_mgr_get_boot_config_from_kernel_cmdline("android_dt_dir", &android_dt_dir)) {
+ if (!fs_mgr_get_boot_config_from_bootconfig_source("android_dt_dir", &android_dt_dir) &&
+ !fs_mgr_get_boot_config_from_kernel_cmdline("android_dt_dir", &android_dt_dir)) {
// Fall back to the standard procfs-based path
android_dt_dir = kDefaultAndroidDtDir;
}
@@ -842,9 +843,22 @@
}
std::set<std::string> GetBootDevices() {
- // First check the kernel commandline, then try the device tree otherwise
+ // First check bootconfig, then kernel commandline, then the device tree
std::string dt_file_name = get_android_dt_dir() + "/boot_devices";
std::string value;
+ if (fs_mgr_get_boot_config_from_bootconfig_source("boot_devices", &value) ||
+ fs_mgr_get_boot_config_from_bootconfig_source("boot_device", &value)) {
+ std::set<std::string> boot_devices;
+ // remove quotes and split by spaces
+ auto boot_device_strings = base::Split(base::StringReplace(value, "\"", "", true), " ");
+ for (std::string_view device : boot_device_strings) {
+ // trim the trailing comma, keep the rest.
+ base::ConsumeSuffix(&device, ",");
+ boot_devices.emplace(device);
+ }
+ return boot_devices;
+ }
+
if (fs_mgr_get_boot_config_from_kernel_cmdline("boot_devices", &value) ||
ReadDtFile(dt_file_name, &value)) {
auto boot_devices = Split(value, ",");
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 623293e..6cb2c51 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -383,11 +383,6 @@
<< " partition alignment is not sector-aligned.";
return false;
}
- if (device_info.alignment_offset > device_info.alignment) {
- LERROR << "Block device " << device_info.partition_name
- << " partition alignment offset is greater than its alignment.";
- return false;
- }
return true;
}
@@ -489,7 +484,7 @@
// Compute the first free sector, factoring in alignment.
uint64_t free_area_start = total_reserved;
bool ok;
- if (super.alignment || super.alignment_offset) {
+ if (super.alignment) {
ok = AlignTo(free_area_start, super.alignment, &free_area_start);
} else {
ok = AlignTo(free_area_start, logical_block_size, &free_area_start);
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index e4b617a..72827eb 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -176,10 +176,10 @@
ASSERT_NE(super_device, nullptr);
EXPECT_EQ(super_device->first_logical_sector, 1536);
- // Alignment offset without alignment doesn't mean anything.
+ // Alignment offset without alignment is ignored.
device_info.alignment = 0;
builder = MetadataBuilder::New(device_info, 1024, 2);
- ASSERT_EQ(builder, nullptr);
+ ASSERT_NE(builder, nullptr);
// Test a small alignment with an alignment offset.
device_info.alignment = 12 * 1024;
@@ -444,11 +444,6 @@
device_info.alignment = 131072;
builder = MetadataBuilder::New(device_info, kMetadataSize, 1);
EXPECT_EQ(builder, nullptr);
-
- device_info.alignment = 0;
- device_info.alignment_offset = 32768 - LP_SECTOR_SIZE;
- builder = MetadataBuilder::New(device_info, kMetadataSize, 1);
- EXPECT_EQ(builder, nullptr);
}
TEST_F(BuilderTest, UpdateBlockDeviceInfo) {
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index b808609..ea92d25 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -416,6 +416,7 @@
"snapuserd_server.cpp",
"snapuserd.cpp",
"snapuserd_daemon.cpp",
+ "snapuserd_worker.cpp",
],
cflags: [
@@ -554,6 +555,7 @@
srcs: [
"cow_snapuserd_test.cpp",
"snapuserd.cpp",
+ "snapuserd_worker.cpp",
],
cflags: [
"-Wall",
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index b4e92a2..e902fa4 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -46,7 +46,7 @@
SECOND_PHASE = 2;
}
-// Next: 12
+// Next: 13
message SnapshotStatus {
// Name of the snapshot. This is usually the name of the snapshotted
// logical partition; for example, "system_b".
@@ -105,6 +105,9 @@
// Compression algorithm (none, gz, or brotli).
string compression_algorithm = 11;
+
+ // Estimated COW size from OTA manifest.
+ uint64 estimated_cow_size = 12;
}
// Next: 8
@@ -159,7 +162,7 @@
MergePhase merge_phase = 6;
}
-// Next: 5
+// Next: 7
message SnapshotMergeReport {
// Status of the update after the merge attempts.
UpdateState state = 1;
@@ -173,4 +176,10 @@
// Whether compression/dm-user was used for any snapshots.
bool compression_enabled = 4;
+
+ // Total size used by COWs, including /data and the super partition.
+ uint64 total_cow_size_bytes = 5;
+
+ // Sum of the estimated COW fields in the OTA manifest.
+ uint64 estimated_cow_size_bytes = 6;
}
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index a96352a..5d63220 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -757,6 +757,30 @@
ASSERT_TRUE(iter->Done());
}
+TEST_F(CowTest, AppendAfterFinalize) {
+ CowOptions options;
+ options.cluster_ops = 0;
+ auto writer = std::make_unique<CowWriter>(options);
+ ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+ std::string data = "This is some data, believe it";
+ data.resize(options.block_size, '\0');
+ ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size()));
+ ASSERT_TRUE(writer->AddLabel(3));
+ ASSERT_TRUE(writer->Finalize());
+
+ std::string data2 = "More data!";
+ data2.resize(options.block_size, '\0');
+ ASSERT_TRUE(writer->AddRawBlocks(51, data2.data(), data2.size()));
+ ASSERT_TRUE(writer->Finalize());
+
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ // COW should be valid.
+ CowReader reader;
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index cf9f6ea..44a423c 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -42,6 +42,29 @@
#endif
}
+bool CowReader::InitForMerge(android::base::unique_fd&& fd) {
+ owned_fd_ = std::move(fd);
+ fd_ = owned_fd_.get();
+
+ auto pos = lseek(fd_.get(), 0, SEEK_END);
+ if (pos < 0) {
+ PLOG(ERROR) << "lseek end failed";
+ return false;
+ }
+ fd_size_ = pos;
+
+ if (lseek(fd_.get(), 0, SEEK_SET) < 0) {
+ PLOG(ERROR) << "lseek header failed";
+ return false;
+ }
+ if (!android::base::ReadFully(fd_, &header_, sizeof(header_))) {
+ PLOG(ERROR) << "read header failed";
+ return false;
+ }
+
+ return true;
+}
+
bool CowReader::Parse(android::base::unique_fd&& fd, std::optional<uint64_t> label) {
owned_fd_ = std::move(fd);
return Parse(android::base::borrowed_fd{owned_fd_}, label);
@@ -206,7 +229,8 @@
if (footer_) {
if (ops_buffer->size() != footer_->op.num_ops) {
- LOG(ERROR) << "num ops does not match";
+ LOG(ERROR) << "num ops does not match, expected " << footer_->op.num_ops << ", found "
+ << ops_buffer->size();
return false;
}
if (ops_buffer->size() * sizeof(CowOperation) != footer_->op.ops_size) {
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index 81edc79..59f6d6f 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -376,6 +376,7 @@
auto continue_data_pos = next_data_pos_;
auto continue_op_pos = next_op_pos_;
auto continue_size = ops_.size();
+ auto continue_num_ops = footer_.op.num_ops;
bool extra_cluster = false;
// Footer should be at the end of a file, so if there is data after the current block, end it
@@ -408,9 +409,9 @@
current_data_size_ = continue_data_size;
next_data_pos_ = continue_data_pos;
next_op_pos_ = continue_op_pos;
+ footer_.op.num_ops = continue_num_ops;
ops_.resize(continue_size);
}
-
return Sync();
}
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index 1de7473..552fd96 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -116,12 +116,15 @@
class CowReader : public ICowReader {
public:
CowReader();
+ ~CowReader() { owned_fd_ = {}; }
// Parse the COW, optionally, up to the given label. If no label is
// specified, the COW must have an intact footer.
bool Parse(android::base::unique_fd&& fd, std::optional<uint64_t> label = {});
bool Parse(android::base::borrowed_fd fd, std::optional<uint64_t> label = {});
+ bool InitForMerge(android::base::unique_fd&& fd);
+
bool GetHeader(CowHeader* header) override;
bool GetFooter(CowFooter* footer) override;
@@ -146,6 +149,8 @@
uint64_t total_data_ops() { return total_data_ops_; }
+ void CloseCowFd() { owned_fd_ = {}; }
+
private:
bool ParseOps(std::optional<uint64_t> label);
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index 1e420cb..1cb966b 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -26,7 +26,8 @@
MOCK_METHOD(bool, BeginUpdate, (), (override));
MOCK_METHOD(bool, CancelUpdate, (), (override));
MOCK_METHOD(bool, FinishedSnapshotWrites, (bool wipe), (override));
- MOCK_METHOD(bool, InitiateMerge, (uint64_t * cow_file_size), (override));
+ MOCK_METHOD(void, UpdateCowStats, (ISnapshotMergeStats * stats), (override));
+ MOCK_METHOD(bool, InitiateMerge, (), (override));
MOCK_METHOD(UpdateState, ProcessUpdateState,
(const std::function<bool()>& callback, const std::function<bool()>& before_cancel),
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index a79a86d..7e74fac 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -127,9 +127,14 @@
// may need to be merged before wiping.
virtual bool FinishedSnapshotWrites(bool wipe) = 0;
+ // Update an ISnapshotMergeStats object with statistics about COW usage.
+ // This should be called before the merge begins as otherwise snapshots
+ // may be deleted.
+ virtual void UpdateCowStats(ISnapshotMergeStats* stats) = 0;
+
// Initiate a merge on all snapshot devices. This should only be used after an
// update has been marked successful after booting.
- virtual bool InitiateMerge(uint64_t* cow_file_size = nullptr) = 0;
+ virtual bool InitiateMerge() = 0;
// Perform any necessary post-boot actions. This should be run soon after
// /data is mounted.
@@ -326,7 +331,8 @@
bool BeginUpdate() override;
bool CancelUpdate() override;
bool FinishedSnapshotWrites(bool wipe) override;
- bool InitiateMerge(uint64_t* cow_file_size = nullptr) override;
+ void UpdateCowStats(ISnapshotMergeStats* stats) override;
+ bool InitiateMerge() override;
UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
const std::function<bool()>& before_cancel = {}) override;
UpdateState GetUpdateState(double* progress = nullptr) override;
@@ -491,7 +497,8 @@
bool RemoveAllSnapshots(LockedFile* lock);
// List the known snapshot names.
- bool ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots);
+ bool ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots,
+ const std::string& suffix = "");
// Check for a cancelled or rolled back merge, returning true if such a
// condition was detected and handled.
@@ -679,6 +686,9 @@
friend std::ostream& operator<<(std::ostream& os, SnapshotManager::Slot slot);
Slot GetCurrentSlot();
+ // Return the suffix we expect snapshots to have.
+ std::string GetSnapshotSlotSuffix();
+
std::string ReadUpdateSourceSlotSuffix();
// Helper for RemoveAllSnapshots.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
index 96d2deb..3eeae64 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
@@ -30,7 +30,11 @@
virtual bool Start() = 0;
virtual void set_state(android::snapshot::UpdateState state, bool using_compression) = 0;
virtual void set_cow_file_size(uint64_t cow_file_size) = 0;
+ virtual void set_total_cow_size_bytes(uint64_t bytes) = 0;
+ virtual void set_estimated_cow_size_bytes(uint64_t bytes) = 0;
virtual uint64_t cow_file_size() = 0;
+ virtual uint64_t total_cow_size_bytes() = 0;
+ virtual uint64_t estimated_cow_size_bytes() = 0;
// Called when merge ends. Properly clean up permanent storage.
class Result {
@@ -54,6 +58,10 @@
void set_state(android::snapshot::UpdateState state, bool using_compression) override;
void set_cow_file_size(uint64_t cow_file_size) override;
uint64_t cow_file_size() override;
+ void set_total_cow_size_bytes(uint64_t bytes) override;
+ void set_estimated_cow_size_bytes(uint64_t bytes) override;
+ uint64_t total_cow_size_bytes() override;
+ uint64_t estimated_cow_size_bytes() 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 3365ceb..cc75db8 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -28,7 +28,8 @@
bool BeginUpdate() override;
bool CancelUpdate() override;
bool FinishedSnapshotWrites(bool wipe) override;
- bool InitiateMerge(uint64_t* cow_file_size = nullptr) override;
+ void UpdateCowStats(ISnapshotMergeStats* stats) override;
+ bool InitiateMerge() override;
UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
const std::function<bool()>& before_cancel = {}) override;
UpdateState GetUpdateState(double* progress = nullptr) override;
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
index 6002043..5569da0 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -202,6 +202,10 @@
ret.snapshot_status.set_device_size(target_partition->size());
ret.snapshot_status.set_snapshot_size(target_partition->size());
+ if (update && update->has_estimate_cow_size()) {
+ ret.snapshot_status.set_estimated_cow_size(update->estimate_cow_size());
+ }
+
if (ret.snapshot_status.snapshot_size() == 0) {
LOG(INFO) << "Not creating snapshot for partition " << ret.snapshot_status.name();
ret.snapshot_status.set_cow_partition_size(0);
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index ca4c265..bd1e284 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -230,6 +230,15 @@
return Slot::Target;
}
+std::string SnapshotManager::GetSnapshotSlotSuffix() {
+ switch (GetCurrentSlot()) {
+ case Slot::Target:
+ return device_->GetSlotSuffix();
+ default:
+ return device_->GetOtherSlotSuffix();
+ }
+}
+
static bool RemoveFileIfExists(const std::string& path) {
std::string message;
if (!android::base::RemoveFileIfExists(path, &message)) {
@@ -624,7 +633,7 @@
return true;
}
-bool SnapshotManager::InitiateMerge(uint64_t* cow_file_size) {
+bool SnapshotManager::InitiateMerge() {
auto lock = LockExclusive();
if (!lock) return false;
@@ -691,7 +700,6 @@
std::vector<std::string> first_merge_group;
- uint64_t total_cow_file_size = 0;
DmTargetSnapshot::Status initial_target_values = {};
for (const auto& snapshot : snapshots) {
DmTargetSnapshot::Status current_status;
@@ -706,7 +714,6 @@
if (!ReadSnapshotStatus(lock.get(), snapshot, &snapshot_status)) {
return false;
}
- total_cow_file_size += snapshot_status.cow_file_size();
compression_enabled |= snapshot_status.compression_enabled();
if (DecideMergePhase(snapshot_status) == MergePhase::FIRST_PHASE) {
@@ -714,10 +721,6 @@
}
}
- if (cow_file_size) {
- *cow_file_size = total_cow_file_size;
- }
-
SnapshotUpdateStatus initial_status;
initial_status.set_state(UpdateState::Merging);
initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
@@ -1262,7 +1265,7 @@
LOG(ERROR) << "DeleteDevice timeout: " << name;
return false;
}
- std::this_thread::sleep_for(250ms);
+ std::this_thread::sleep_for(400ms);
}
return true;
@@ -1732,7 +1735,8 @@
return update_status.compression_enabled();
}
-bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots) {
+bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots,
+ const std::string& suffix) {
CHECK(lock);
auto dir_path = metadata_dir_ + "/snapshots"s;
@@ -1745,7 +1749,12 @@
struct dirent* dp;
while ((dp = readdir(dir.get())) != nullptr) {
if (dp->d_type != DT_REG) continue;
- snapshots->emplace_back(dp->d_name);
+
+ std::string name(dp->d_name);
+ if (!suffix.empty() && !android::base::EndsWith(name, suffix)) {
+ continue;
+ }
+ snapshots->emplace_back(std::move(name));
}
return true;
}
@@ -3565,5 +3574,34 @@
return MergePhase::SECOND_PHASE;
}
+void SnapshotManager::UpdateCowStats(ISnapshotMergeStats* stats) {
+ auto lock = LockExclusive();
+ if (!lock) return;
+
+ std::vector<std::string> snapshots;
+ if (!ListSnapshots(lock.get(), &snapshots, GetSnapshotSlotSuffix())) {
+ LOG(ERROR) << "Could not list snapshots";
+ return;
+ }
+
+ uint64_t cow_file_size = 0;
+ uint64_t total_cow_size = 0;
+ uint64_t estimated_cow_size = 0;
+ for (const auto& snapshot : snapshots) {
+ SnapshotStatus status;
+ if (!ReadSnapshotStatus(lock.get(), snapshot, &status)) {
+ return;
+ }
+
+ cow_file_size += status.cow_file_size();
+ total_cow_size += status.cow_file_size() + status.cow_partition_size();
+ estimated_cow_size += status.estimated_cow_size();
+ }
+
+ stats->set_cow_file_size(cow_file_size);
+ stats->set_total_cow_size_bytes(total_cow_size);
+ stats->set_estimated_cow_size_bytes(estimated_cow_size);
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_stats.cpp b/fs_mgr/libsnapshot/snapshot_stats.cpp
index 513700d..35e2d92 100644
--- a/fs_mgr/libsnapshot/snapshot_stats.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stats.cpp
@@ -98,6 +98,22 @@
return report_.cow_file_size();
}
+void SnapshotMergeStats::set_total_cow_size_bytes(uint64_t bytes) {
+ report_.set_total_cow_size_bytes(bytes);
+}
+
+void SnapshotMergeStats::set_estimated_cow_size_bytes(uint64_t bytes) {
+ report_.set_estimated_cow_size_bytes(bytes);
+}
+
+uint64_t SnapshotMergeStats::total_cow_size_bytes() {
+ return report_.total_cow_size_bytes();
+}
+
+uint64_t SnapshotMergeStats::estimated_cow_size_bytes() {
+ return report_.estimated_cow_size_bytes();
+}
+
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 8a254c9..079e606 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -43,7 +43,7 @@
return false;
}
-bool SnapshotManagerStub::InitiateMerge(uint64_t*) {
+bool SnapshotManagerStub::InitiateMerge() {
LOG(ERROR) << __FUNCTION__ << " should never be called.";
return false;
}
@@ -127,6 +127,10 @@
void set_cow_file_size(uint64_t) override {}
uint64_t cow_file_size() override { return 0; }
std::unique_ptr<Result> Finish() override { return nullptr; }
+ void set_total_cow_size_bytes(uint64_t) override {}
+ void set_estimated_cow_size_bytes(uint64_t) override {}
+ uint64_t total_cow_size_bytes() override { return 0; }
+ uint64_t estimated_cow_size_bytes() override { return 0; }
};
ISnapshotMergeStats* SnapshotManagerStub::GetSnapshotMergeStatsInstance() {
@@ -151,4 +155,8 @@
return false;
}
+void SnapshotManagerStub::UpdateCowStats(ISnapshotMergeStats*) {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+}
+
} // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp
index 4c4a342..f1fcb70 100644
--- a/fs_mgr/libsnapshot/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -32,41 +32,6 @@
#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
-static constexpr size_t PAYLOAD_SIZE = (1UL << 20);
-
-static_assert(PAYLOAD_SIZE >= BLOCK_SZ);
-
-void BufferSink::Initialize(size_t size) {
- buffer_size_ = size;
- buffer_offset_ = 0;
- buffer_ = std::make_unique<uint8_t[]>(size);
-}
-
-void* BufferSink::GetPayloadBuffer(size_t size) {
- if ((buffer_size_ - buffer_offset_) < size) return nullptr;
-
- char* buffer = reinterpret_cast<char*>(GetBufPtr());
- struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
- return (char*)msg->payload.buf + buffer_offset_;
-}
-
-void* BufferSink::GetBuffer(size_t requested, size_t* actual) {
- void* buf = GetPayloadBuffer(requested);
- if (!buf) {
- *actual = 0;
- return nullptr;
- }
- *actual = requested;
- return buf;
-}
-
-struct dm_user_header* BufferSink::GetHeaderPtr() {
- CHECK(sizeof(struct dm_user_header) <= buffer_size_);
- char* buf = reinterpret_cast<char*>(GetBufPtr());
- struct dm_user_header* header = (struct dm_user_header*)(&(buf[0]));
- return header;
-}
-
Snapuserd::Snapuserd(const std::string& misc_name, const std::string& cow_device,
const std::string& backing_device) {
misc_name_ = misc_name;
@@ -75,356 +40,32 @@
control_device_ = "/dev/dm-user/" + misc_name;
}
-// Construct kernel COW header in memory
-// This header will be in sector 0. The IO
-// request will always be 4k. After constructing
-// the header, zero out the remaining block.
-void Snapuserd::ConstructKernelCowHeader() {
- void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
- CHECK(buffer != nullptr);
+bool Snapuserd::InitializeWorkers() {
+ for (int i = 0; i < NUM_THREADS_PER_PARTITION; i++) {
+ std::unique_ptr<WorkerThread> wt = std::make_unique<WorkerThread>(
+ cow_device_, backing_store_device_, control_device_, misc_name_, GetSharedPtr());
- memset(buffer, 0, BLOCK_SZ);
-
- struct disk_header* dh = reinterpret_cast<struct disk_header*>(buffer);
-
- dh->magic = SNAP_MAGIC;
- dh->valid = SNAPSHOT_VALID;
- dh->version = SNAPSHOT_DISK_VERSION;
- dh->chunk_size = CHUNK_SIZE;
-}
-
-// Start the replace operation. This will read the
-// internal COW format and if the block is compressed,
-// it will be de-compressed.
-bool Snapuserd::ProcessReplaceOp(const CowOperation* cow_op) {
- if (!reader_->ReadData(*cow_op, &bufsink_)) {
- SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
- return false;
+ worker_threads_.push_back(std::move(wt));
}
-
return true;
}
-// Start the copy operation. This will read the backing
-// block device which is represented by cow_op->source.
-bool Snapuserd::ProcessCopyOp(const CowOperation* cow_op) {
- void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
- CHECK(buffer != nullptr);
+bool Snapuserd::CommitMerge(int num_merge_ops) {
+ {
+ std::lock_guard<std::mutex> lock(lock_);
+ CowHeader header;
- // Issue a single 4K IO. However, this can be optimized
- // if the successive blocks are contiguous.
- 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_
- << "at block :" << cow_op->source;
- return false;
- }
-
- return true;
-}
-
-bool Snapuserd::ProcessZeroOp() {
- // Zero out the entire block
- void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
- CHECK(buffer != nullptr);
-
- memset(buffer, 0, BLOCK_SZ);
- return true;
-}
-
-bool Snapuserd::ProcessCowOp(const CowOperation* cow_op) {
- CHECK(cow_op != nullptr);
-
- switch (cow_op->type) {
- case kCowReplaceOp: {
- return ProcessReplaceOp(cow_op);
+ 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;
}
-
- case kCowZeroOp: {
- return ProcessZeroOp();
- }
-
- case kCowCopyOp: {
- return ProcessCopyOp(cow_op);
- }
-
- default: {
- SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
- }
- }
- return false;
-}
-
-int Snapuserd::ReadUnalignedSector(sector_t sector, size_t size,
- std::map<sector_t, const CowOperation*>::iterator& it) {
- size_t skip_sector_size = 0;
-
- SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size
- << " Aligned sector: " << it->second;
-
- if (!ProcessCowOp(it->second)) {
- SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size;
- return -1;
+ merge_initiated_ = true;
}
- int num_sectors_skip = sector - it->first;
-
- if (num_sectors_skip > 0) {
- skip_sector_size = num_sectors_skip << SECTOR_SHIFT;
- char* buffer = reinterpret_cast<char*>(bufsink_.GetBufPtr());
- struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
-
- memmove(msg->payload.buf, (char*)msg->payload.buf + skip_sector_size,
- (BLOCK_SZ - skip_sector_size));
- }
-
- bufsink_.ResetBufferOffset();
- return std::min(size, (BLOCK_SZ - skip_sector_size));
-}
-
-/*
- * Read the data for a given COW Operation.
- *
- * Kernel can issue IO at a sector granularity.
- * Hence, an IO may end up with reading partial
- * data from a COW operation or we may also
- * end up with interspersed request between
- * two COW operations.
- *
- */
-int Snapuserd::ReadData(sector_t sector, size_t size) {
- /*
- * chunk_map stores COW operation at 4k granularity.
- * If the requested IO with the sector falls on the 4k
- * boundary, then we can read the COW op directly without
- * any issue.
- *
- * However, if the requested sector is not 4K aligned,
- * then we will have the find the nearest COW operation
- * and chop the 4K block to fetch the requested sector.
- */
- std::map<sector_t, const CowOperation*>::iterator it = chunk_map_.find(sector);
- if (it == chunk_map_.end()) {
- it = chunk_map_.lower_bound(sector);
- if (it != chunk_map_.begin()) {
- --it;
- }
-
- /*
- * If the IO is spanned between two COW operations,
- * split the IO into two parts:
- *
- * 1: Read the first part from the single COW op
- * 2: Read the second part from the next COW op.
- *
- * Ex: Let's say we have a 1024 Bytes IO request.
- *
- * 0 COW OP-1 4096 COW OP-2 8192
- * |******************|*******************|
- * |*****|*****|
- * 3584 4608
- * <- 1024B - >
- *
- * We have two COW operations which are 4k blocks.
- * The IO is requested for 1024 Bytes which are spanned
- * between two COW operations. We will split this IO
- * into two parts:
- *
- * 1: IO of size 512B from offset 3584 bytes (COW OP-1)
- * 2: IO of size 512B from offset 4096 bytes (COW OP-2)
- */
- return ReadUnalignedSector(sector, size, it);
- }
-
- int num_ops = DIV_ROUND_UP(size, BLOCK_SZ);
- while (num_ops) {
- if (!ProcessCowOp(it->second)) {
- return -1;
- }
- num_ops -= 1;
- it++;
- // Update the buffer offset
- bufsink_.UpdateBufferOffset(BLOCK_SZ);
-
- SNAP_LOG(DEBUG) << "ReadData at sector: " << sector << " size: " << size;
- }
-
- // Reset the buffer offset
- bufsink_.ResetBufferOffset();
- return size;
-}
-
-/*
- * dm-snap does prefetch reads while reading disk-exceptions.
- * By default, prefetch value is set to 12; this means that
- * dm-snap will issue 12 areas wherein each area is a 4k page
- * of disk-exceptions.
- *
- * If during prefetch, if the chunk-id seen is beyond the
- * actual number of metadata page, fill the buffer with zero.
- * When dm-snap starts parsing the buffer, it will stop
- * reading metadata page once the buffer content is zero.
- */
-bool Snapuserd::ZerofillDiskExceptions(size_t read_size) {
- size_t size = exceptions_per_area_ * sizeof(struct disk_exception);
-
- if (read_size > size) {
- return false;
- }
-
- void* buffer = bufsink_.GetPayloadBuffer(size);
- CHECK(buffer != nullptr);
-
- memset(buffer, 0, size);
- return true;
-}
-
-/*
- * A disk exception is a simple mapping of old_chunk to new_chunk.
- * When dm-snapshot device is created, kernel requests these mapping.
- *
- * Each disk exception is of size 16 bytes. Thus a single 4k page can
- * have:
- *
- * exceptions_per_area_ = 4096/16 = 256. This entire 4k page
- * is considered a metadata page and it is represented by chunk ID.
- *
- * Convert the chunk ID to index into the vector which gives us
- * the metadata page.
- */
-bool Snapuserd::ReadDiskExceptions(chunk_t chunk, size_t read_size) {
- uint32_t stride = exceptions_per_area_ + 1;
- size_t size;
-
- // ChunkID to vector index
- lldiv_t divresult = lldiv(chunk, stride);
-
- if (divresult.quot < vec_.size()) {
- size = exceptions_per_area_ * sizeof(struct disk_exception);
-
- CHECK(read_size == size);
-
- void* buffer = bufsink_.GetPayloadBuffer(size);
- CHECK(buffer != nullptr);
-
- memcpy(buffer, vec_[divresult.quot].get(), size);
- } else {
- return ZerofillDiskExceptions(read_size);
- }
-
- return true;
-}
-
-loff_t Snapuserd::GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
- int* unmerged_exceptions) {
- loff_t offset = 0;
- *unmerged_exceptions = 0;
-
- while (*unmerged_exceptions <= exceptions_per_area_) {
- struct disk_exception* merged_de =
- reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
- struct disk_exception* cow_de =
- reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
-
- // Unmerged op by the kernel
- if (merged_de->old_chunk != 0 || merged_de->new_chunk != 0) {
- CHECK(merged_de->old_chunk == cow_de->old_chunk);
- CHECK(merged_de->new_chunk == cow_de->new_chunk);
-
- offset += sizeof(struct disk_exception);
- *unmerged_exceptions += 1;
- continue;
- }
-
- break;
- }
-
- CHECK(!(*unmerged_exceptions == exceptions_per_area_));
-
- SNAP_LOG(DEBUG) << "Unmerged_Exceptions: " << *unmerged_exceptions << " Offset: " << offset;
- return offset;
-}
-
-int Snapuserd::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
- int unmerged_exceptions) {
- int merged_ops_cur_iter = 0;
-
- // Find the operations which are merged in this cycle.
- while ((unmerged_exceptions + merged_ops_cur_iter) < exceptions_per_area_) {
- struct disk_exception* merged_de =
- reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
- struct disk_exception* cow_de =
- reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
-
- CHECK(merged_de->new_chunk == 0);
- CHECK(merged_de->old_chunk == 0);
-
- if (cow_de->new_chunk != 0) {
- merged_ops_cur_iter += 1;
- offset += sizeof(struct disk_exception);
- const CowOperation* cow_op = chunk_map_[ChunkToSector(cow_de->new_chunk)];
- CHECK(cow_op != nullptr);
-
- CHECK(cow_op->new_block == cow_de->old_chunk);
- // zero out to indicate that operation is merged.
- cow_de->old_chunk = 0;
- cow_de->new_chunk = 0;
- } else if (cow_de->old_chunk == 0) {
- // Already merged op in previous iteration or
- // This could also represent a partially filled area.
- //
- // If the op was merged in previous cycle, we don't have
- // to count them.
- CHECK(cow_de->new_chunk == 0);
- break;
- } else {
- SNAP_LOG(ERROR) << "Error in merge operation. Found invalid metadata: "
- << " merged_de-old-chunk: " << merged_de->old_chunk
- << " merged_de-new-chunk: " << merged_de->new_chunk
- << " cow_de-old-chunk: " << cow_de->old_chunk
- << " cow_de-new-chunk: " << cow_de->new_chunk
- << " unmerged_exceptions: " << unmerged_exceptions
- << " merged_ops_cur_iter: " << merged_ops_cur_iter
- << " offset: " << offset;
- return -1;
- }
- }
- return merged_ops_cur_iter;
-}
-
-bool Snapuserd::ProcessMergeComplete(chunk_t chunk, void* buffer) {
- uint32_t stride = exceptions_per_area_ + 1;
- CowHeader header;
-
- if (!reader_->GetHeader(&header)) {
- SNAP_LOG(ERROR) << "Failed to get header";
- return false;
- }
-
- // ChunkID to vector index
- lldiv_t divresult = lldiv(chunk, stride);
- CHECK(divresult.quot < vec_.size());
- SNAP_LOG(DEBUG) << "ProcessMergeComplete: chunk: " << chunk
- << " Metadata-Index: " << divresult.quot;
-
- int unmerged_exceptions = 0;
- loff_t offset = GetMergeStartOffset(buffer, vec_[divresult.quot].get(), &unmerged_exceptions);
-
- int merged_ops_cur_iter =
- GetNumberOfMergedOps(buffer, vec_[divresult.quot].get(), offset, unmerged_exceptions);
-
- // There should be at least one operation merged in this cycle
- CHECK(merged_ops_cur_iter > 0);
-
- header.num_merge_ops += merged_ops_cur_iter;
- reader_->UpdateMergeProgress(merged_ops_cur_iter);
- if (!writer_->CommitMerge(merged_ops_cur_iter)) {
- SNAP_LOG(ERROR) << "CommitMerge failed... merged_ops_cur_iter: " << merged_ops_cur_iter;
- return false;
- }
-
- SNAP_LOG(DEBUG) << "Merge success: " << merged_ops_cur_iter << "chunk: " << chunk;
- merge_initiated_ = true;
return true;
}
@@ -836,7 +477,6 @@
// Total number of sectors required for creating dm-user device
num_sectors_ = ChunkToSector(data_chunk_id);
- metadata_read_done_ = true;
merge_initiated_ = false;
return true;
}
@@ -850,37 +490,6 @@
}
}
-// Read Header from dm-user misc device. This gives
-// us the sector number for which IO is issued by dm-snapshot device
-bool Snapuserd::ReadDmUserHeader() {
- if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) {
- SNAP_PLOG(ERROR) << "Control-read failed";
- return false;
- }
-
- return true;
-}
-
-// Send the payload/data back to dm-user misc device.
-bool Snapuserd::WriteDmUserPayload(size_t size) {
- if (!android::base::WriteFully(ctrl_fd_, bufsink_.GetBufPtr(),
- sizeof(struct dm_user_header) + size)) {
- SNAP_PLOG(ERROR) << "Write to dm-user failed size: " << size;
- return false;
- }
-
- return true;
-}
-
-bool Snapuserd::ReadDmUserPayload(void* buffer, size_t size) {
- if (!android::base::ReadFully(ctrl_fd_, buffer, size)) {
- SNAP_PLOG(ERROR) << "ReadDmUserPayload failed size: " << size;
- return false;
- }
-
- return true;
-}
-
bool Snapuserd::InitCowDevice() {
cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
if (cow_fd_ < 0) {
@@ -888,186 +497,26 @@
return false;
}
- // Allocate the buffer which is used to communicate between
- // daemon and dm-user. The buffer comprises of header and a fixed payload.
- // If the dm-user requests a big IO, the IO will be broken into chunks
- // of PAYLOAD_SIZE.
- size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
- bufsink_.Initialize(buf_size);
-
return ReadMetadata();
}
-bool Snapuserd::InitBackingAndControlDevice() {
- 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;
+/*
+ * Entry point to launch worker threads
+ */
+bool Snapuserd::Start() {
+ std::vector<std::future<bool>> threads;
+
+ for (int i = 0; i < worker_threads_.size(); i++) {
+ threads.emplace_back(
+ std::async(std::launch::async, &WorkerThread::RunThread, worker_threads_[i].get()));
}
- ctrl_fd_.reset(open(control_device_.c_str(), O_RDWR));
- if (ctrl_fd_ < 0) {
- SNAP_PLOG(ERROR) << "Unable to open " << control_device_;
- return false;
+ bool ret = true;
+ for (auto& t : threads) {
+ ret = t.get() && ret;
}
- return true;
-}
-
-bool Snapuserd::DmuserWriteRequest() {
- struct dm_user_header* header = bufsink_.GetHeaderPtr();
-
- // device mapper has the capability to allow
- // targets to flush the cache when writes are completed. This
- // is controlled by each target by a flag "flush_supported".
- // This flag is set by dm-user. When flush is supported,
- // a number of zero-length bio's will be submitted to
- // the target for the purpose of flushing cache. It is the
- // responsibility of the target driver - which is dm-user in this
- // case, to remap these bio's to the underlying device. Since,
- // there is no underlying device for dm-user, this zero length
- // bio's gets routed to daemon.
- //
- // Flush operations are generated post merge by dm-snap by having
- // REQ_PREFLUSH flag set. Snapuser daemon doesn't have anything
- // to flush per se; hence, just respond back with a success message.
- if (header->sector == 0) {
- CHECK(header->len == 0);
- header->type = DM_USER_RESP_SUCCESS;
- if (!WriteDmUserPayload(0)) {
- return false;
- }
- return true;
- }
-
- size_t remaining_size = header->len;
- size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
- CHECK(read_size == BLOCK_SZ);
-
- CHECK(header->sector > 0);
- chunk_t chunk = SectorToChunk(header->sector);
- CHECK(chunk_map_.find(header->sector) == chunk_map_.end());
-
- void* buffer = bufsink_.GetPayloadBuffer(read_size);
- CHECK(buffer != nullptr);
- header->type = DM_USER_RESP_SUCCESS;
-
- if (!ReadDmUserPayload(buffer, read_size)) {
- SNAP_LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk
- << "Sector: " << header->sector;
- header->type = DM_USER_RESP_ERROR;
- }
-
- if (header->type == DM_USER_RESP_SUCCESS && !ProcessMergeComplete(chunk, buffer)) {
- SNAP_LOG(ERROR) << "ProcessMergeComplete failed for chunk id: " << chunk
- << "Sector: " << header->sector;
- header->type = DM_USER_RESP_ERROR;
- } else {
- SNAP_LOG(DEBUG) << "ProcessMergeComplete success for chunk id: " << chunk
- << "Sector: " << header->sector;
- }
-
- if (!WriteDmUserPayload(0)) {
- return false;
- }
-
- return true;
-}
-
-bool Snapuserd::DmuserReadRequest() {
- struct dm_user_header* header = bufsink_.GetHeaderPtr();
- size_t remaining_size = header->len;
- loff_t offset = 0;
- sector_t sector = header->sector;
- do {
- size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
-
- int ret = read_size;
- header->type = DM_USER_RESP_SUCCESS;
- chunk_t chunk = SectorToChunk(header->sector);
-
- // Request to sector 0 is always for kernel
- // representation of COW header. This IO should be only
- // once during dm-snapshot device creation. We should
- // never see multiple IO requests. Additionally this IO
- // will always be a single 4k.
- if (header->sector == 0) {
- CHECK(metadata_read_done_ == true);
- CHECK(read_size == BLOCK_SZ);
- ConstructKernelCowHeader();
- SNAP_LOG(DEBUG) << "Kernel header constructed";
- } else {
- if (!offset && (read_size == BLOCK_SZ) &&
- chunk_map_.find(header->sector) == chunk_map_.end()) {
- if (!ReadDiskExceptions(chunk, read_size)) {
- SNAP_LOG(ERROR) << "ReadDiskExceptions failed for chunk id: " << chunk
- << "Sector: " << header->sector;
- header->type = DM_USER_RESP_ERROR;
- } else {
- SNAP_LOG(DEBUG) << "ReadDiskExceptions success for chunk id: " << chunk
- << "Sector: " << header->sector;
- }
- } else {
- chunk_t num_sectors_read = (offset >> SECTOR_SHIFT);
- ret = ReadData(sector + num_sectors_read, read_size);
- if (ret < 0) {
- SNAP_LOG(ERROR) << "ReadData failed for chunk id: " << chunk
- << " Sector: " << (sector + num_sectors_read)
- << " size: " << read_size << " header-len: " << header->len;
- header->type = DM_USER_RESP_ERROR;
- } else {
- SNAP_LOG(DEBUG) << "ReadData success for chunk id: " << chunk
- << "Sector: " << header->sector;
- }
- }
- }
-
- // 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;
- }
-
- remaining_size -= ret;
- offset += ret;
- } while (remaining_size > 0);
-
- return true;
-}
-
-bool Snapuserd::Run() {
- struct dm_user_header* header = bufsink_.GetHeaderPtr();
-
- bufsink_.Clear();
-
- if (!ReadDmUserHeader()) {
- SNAP_LOG(ERROR) << "ReadDmUserHeader failed";
- return false;
- }
-
- SNAP_LOG(DEBUG) << "msg->seq: " << std::hex << header->seq;
- SNAP_LOG(DEBUG) << "msg->type: " << std::hex << header->type;
- SNAP_LOG(DEBUG) << "msg->flags: " << std::hex << header->flags;
- SNAP_LOG(DEBUG) << "msg->sector: " << std::hex << header->sector;
- SNAP_LOG(DEBUG) << "msg->len: " << std::hex << header->len;
-
- switch (header->type) {
- case DM_USER_REQ_MAP_READ: {
- if (!DmuserReadRequest()) {
- return false;
- }
- break;
- }
-
- case DM_USER_REQ_MAP_WRITE: {
- if (!DmuserWriteRequest()) {
- return false;
- }
- break;
- }
- }
-
- return true;
+ return ret;
}
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/snapuserd.h
index 518d08b..6ce64d8 100644
--- a/fs_mgr/libsnapshot/snapuserd.h
+++ b/fs_mgr/libsnapshot/snapuserd.h
@@ -18,13 +18,17 @@
#include <stdint.h>
#include <stdlib.h>
+#include <bitset>
#include <csignal>
#include <cstring>
+#include <future>
#include <iostream>
#include <limits>
#include <map>
+#include <mutex>
#include <string>
#include <thread>
+#include <unordered_map>
#include <vector>
#include <android-base/file.h>
@@ -40,6 +44,17 @@
namespace snapshot {
using android::base::unique_fd;
+using namespace std::chrono_literals;
+
+static constexpr size_t PAYLOAD_SIZE = (1UL << 20);
+static_assert(PAYLOAD_SIZE >= BLOCK_SZ);
+
+/*
+ * With 4 threads, we get optimal performance
+ * when update_verifier reads the partition during
+ * boot.
+ */
+static constexpr int NUM_THREADS_PER_PARTITION = 4;
class BufferSink : public IByteSink {
public:
@@ -59,53 +74,106 @@
size_t buffer_size_;
};
-class Snapuserd final {
+class Snapuserd;
+
+class WorkerThread {
public:
- Snapuserd(const std::string& misc_name, const std::string& cow_device,
- const std::string& backing_device);
- bool InitBackingAndControlDevice();
- bool InitCowDevice();
- bool Run();
- const std::string& GetControlDevicePath() { return control_device_; }
- const std::string& GetMiscName() { return misc_name_; }
- uint64_t GetNumSectors() { return num_sectors_; }
- bool IsAttached() const { return ctrl_fd_ >= 0; }
- void CheckMergeCompletionStatus();
- void CloseFds() {
- ctrl_fd_ = {};
- cow_fd_ = {};
- backing_store_fd_ = {};
- }
- size_t GetMetadataAreaSize() { return vec_.size(); }
- void* GetExceptionBuffer(size_t i) { return vec_[i].get(); }
+ WorkerThread(const std::string& cow_device, const std::string& backing_device,
+ const std::string& control_device, const std::string& misc_name,
+ std::shared_ptr<Snapuserd> snapuserd);
+ bool RunThread();
private:
+ // Initialization
+ void InitializeBufsink();
+ bool InitializeFds();
+ bool InitReader();
+ void CloseFds() {
+ ctrl_fd_ = {};
+ backing_store_fd_ = {};
+ }
+
+ // Functions interacting with dm-user
+ bool ReadDmUserHeader();
bool DmuserReadRequest();
bool DmuserWriteRequest();
-
- bool ReadDmUserHeader();
bool ReadDmUserPayload(void* buffer, size_t size);
bool WriteDmUserPayload(size_t size);
- void ConstructKernelCowHeader();
- bool ReadMetadata();
- bool ZerofillDiskExceptions(size_t read_size);
+
bool ReadDiskExceptions(chunk_t chunk, size_t size);
+ bool ZerofillDiskExceptions(size_t read_size);
+ void ConstructKernelCowHeader();
+
+ // IO Path
+ bool ProcessIORequest();
+ int ReadData(sector_t sector, size_t size);
int ReadUnalignedSector(sector_t sector, size_t size,
std::map<sector_t, const CowOperation*>::iterator& it);
- int ReadData(sector_t sector, size_t size);
- bool IsChunkIdMetadata(chunk_t chunk);
- chunk_t GetNextAllocatableChunkId(chunk_t chunk_id);
+ // Processing COW operations
bool ProcessCowOp(const CowOperation* cow_op);
bool ProcessReplaceOp(const CowOperation* cow_op);
bool ProcessCopyOp(const CowOperation* cow_op);
bool ProcessZeroOp();
+ // 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);
- bool ProcessMergeComplete(chunk_t chunk, void* buffer);
+
+ sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
+ chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
+
+ std::unique_ptr<CowReader> reader_;
+ BufferSink bufsink_;
+
+ std::string cow_device_;
+ std::string backing_store_device_;
+ std::string control_device_;
+ std::string misc_name_;
+
+ unique_fd cow_fd_;
+ unique_fd backing_store_fd_;
+ unique_fd ctrl_fd_;
+
+ std::shared_ptr<Snapuserd> snapuserd_;
+ uint32_t exceptions_per_area_;
+};
+
+class Snapuserd : public std::enable_shared_from_this<Snapuserd> {
+ public:
+ Snapuserd(const std::string& misc_name, const std::string& cow_device,
+ const std::string& backing_device);
+ bool InitCowDevice();
+ bool Start();
+ const std::string& GetControlDevicePath() { return control_device_; }
+ const std::string& GetMiscName() { return misc_name_; }
+ uint64_t GetNumSectors() { return num_sectors_; }
+ bool IsAttached() const { return attached_; }
+ void AttachControlDevice() { attached_ = true; }
+
+ void CheckMergeCompletionStatus();
+ bool CommitMerge(int num_merge_ops);
+
+ void CloseFds() { cow_fd_ = {}; }
+ size_t GetMetadataAreaSize() { return vec_.size(); }
+ void* GetExceptionBuffer(size_t i) { return vec_[i].get(); }
+
+ bool InitializeWorkers();
+ std::shared_ptr<Snapuserd> GetSharedPtr() { return shared_from_this(); }
+
+ std::map<sector_t, const CowOperation*>& GetChunkMap() { return chunk_map_; }
+ const std::vector<std::unique_ptr<uint8_t[]>>& GetMetadataVec() const { return vec_; }
+
+ private:
+ std::vector<std::unique_ptr<WorkerThread>> worker_threads_;
+
+ bool ReadMetadata();
+ bool IsChunkIdMetadata(chunk_t chunk);
+ chunk_t GetNextAllocatableChunkId(chunk_t chunk_id);
+
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); }
@@ -116,8 +184,6 @@
std::string misc_name_;
unique_fd cow_fd_;
- unique_fd backing_store_fd_;
- unique_fd ctrl_fd_;
uint32_t exceptions_per_area_;
uint64_t num_sectors_;
@@ -141,9 +207,10 @@
// in the chunk_map to find the nearest COW op.
std::map<sector_t, const CowOperation*> chunk_map_;
- bool metadata_read_done_ = false;
+ std::mutex lock_;
+
bool merge_initiated_ = false;
- BufferSink bufsink_;
+ bool attached_ = false;
};
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd_server.cpp
index 017de3b..167895e 100644
--- a/fs_mgr/libsnapshot/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_server.cpp
@@ -77,8 +77,8 @@
JoinAllThreads();
}
-DmUserHandler::DmUserHandler(std::unique_ptr<Snapuserd>&& snapuserd)
- : snapuserd_(std::move(snapuserd)), misc_name_(snapuserd_->GetMiscName()) {}
+DmUserHandler::DmUserHandler(std::shared_ptr<Snapuserd> snapuserd)
+ : 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));
@@ -204,10 +204,8 @@
void SnapuserdServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
- while (!StopRequested()) {
- if (!handler->snapuserd()->Run()) {
- break;
- }
+ if (!handler->snapuserd()->Start()) {
+ LOG(ERROR) << " Failed to launch all worker threads";
}
handler->snapuserd()->CloseFds();
@@ -349,13 +347,18 @@
std::shared_ptr<DmUserHandler> SnapuserdServer::AddHandler(const std::string& misc_name,
const std::string& cow_device_path,
const std::string& backing_device) {
- auto snapuserd = std::make_unique<Snapuserd>(misc_name, cow_device_path, backing_device);
+ auto snapuserd = std::make_shared<Snapuserd>(misc_name, cow_device_path, backing_device);
if (!snapuserd->InitCowDevice()) {
LOG(ERROR) << "Failed to initialize Snapuserd";
return nullptr;
}
- auto handler = std::make_shared<DmUserHandler>(std::move(snapuserd));
+ if (!snapuserd->InitializeWorkers()) {
+ LOG(ERROR) << "Failed to initialize workers";
+ return nullptr;
+ }
+
+ auto handler = std::make_shared<DmUserHandler>(snapuserd);
{
std::lock_guard<std::mutex> lock(lock_);
if (FindHandler(&lock, misc_name) != dm_users_.end()) {
@@ -370,10 +373,7 @@
bool SnapuserdServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
CHECK(!handler->snapuserd()->IsAttached());
- if (!handler->snapuserd()->InitBackingAndControlDevice()) {
- LOG(ERROR) << "Failed to initialize control device: " << handler->misc_name();
- return false;
- }
+ handler->snapuserd()->AttachControlDevice();
handler->thread() = std::thread(std::bind(&SnapuserdServer::RunThread, this, handler));
return true;
diff --git a/fs_mgr/libsnapshot/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd_server.h
index 7cbc2de..e9d575d 100644
--- a/fs_mgr/libsnapshot/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd_server.h
@@ -47,17 +47,17 @@
class DmUserHandler {
public:
- explicit DmUserHandler(std::unique_ptr<Snapuserd>&& snapuserd);
+ explicit DmUserHandler(std::shared_ptr<Snapuserd> snapuserd);
void FreeResources() { snapuserd_ = nullptr; }
- const std::unique_ptr<Snapuserd>& snapuserd() const { return snapuserd_; }
+ const std::shared_ptr<Snapuserd>& snapuserd() const { return snapuserd_; }
std::thread& thread() { return thread_; }
const std::string& misc_name() const { return misc_name_; }
private:
std::thread thread_;
- std::unique_ptr<Snapuserd> snapuserd_;
+ std::shared_ptr<Snapuserd> snapuserd_;
std::string misc_name_;
};
diff --git a/fs_mgr/libsnapshot/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd_worker.cpp
new file mode 100644
index 0000000..16f47fe
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd_worker.cpp
@@ -0,0 +1,675 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "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_ << ": "
+
+void BufferSink::Initialize(size_t size) {
+ buffer_size_ = size;
+ buffer_offset_ = 0;
+ buffer_ = std::make_unique<uint8_t[]>(size);
+}
+
+void* BufferSink::GetPayloadBuffer(size_t size) {
+ if ((buffer_size_ - buffer_offset_) < size) return nullptr;
+
+ char* buffer = reinterpret_cast<char*>(GetBufPtr());
+ struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
+ return (char*)msg->payload.buf + buffer_offset_;
+}
+
+void* BufferSink::GetBuffer(size_t requested, size_t* actual) {
+ void* buf = GetPayloadBuffer(requested);
+ if (!buf) {
+ *actual = 0;
+ return nullptr;
+ }
+ *actual = requested;
+ return buf;
+}
+
+struct dm_user_header* BufferSink::GetHeaderPtr() {
+ CHECK(sizeof(struct dm_user_header) <= buffer_size_);
+ char* buf = reinterpret_cast<char*>(GetBufPtr());
+ struct dm_user_header* header = (struct dm_user_header*)(&(buf[0]));
+ return header;
+}
+
+WorkerThread::WorkerThread(const std::string& cow_device, const std::string& backing_device,
+ const std::string& control_device, const std::string& misc_name,
+ std::shared_ptr<Snapuserd> snapuserd) {
+ cow_device_ = cow_device;
+ backing_store_device_ = backing_device;
+ control_device_ = control_device;
+ misc_name_ = misc_name;
+ snapuserd_ = snapuserd;
+ exceptions_per_area_ = (CHUNK_SIZE << SECTOR_SHIFT) / sizeof(struct disk_exception);
+}
+
+bool WorkerThread::InitializeFds() {
+ backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
+ if (backing_store_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
+ return false;
+ }
+
+ cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
+ if (cow_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
+ return false;
+ }
+
+ ctrl_fd_.reset(open(control_device_.c_str(), O_RDWR));
+ if (ctrl_fd_ < 0) {
+ SNAP_PLOG(ERROR) << "Unable to open " << control_device_;
+ return false;
+ }
+
+ return true;
+}
+
+bool WorkerThread::InitReader() {
+ reader_ = std::make_unique<CowReader>();
+ if (!reader_->InitForMerge(std::move(cow_fd_))) {
+ return false;
+ }
+
+ return true;
+}
+
+// Construct kernel COW header in memory
+// This header will be in sector 0. The IO
+// request will always be 4k. After constructing
+// the header, zero out the remaining block.
+void WorkerThread::ConstructKernelCowHeader() {
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ CHECK(buffer != nullptr);
+
+ memset(buffer, 0, BLOCK_SZ);
+
+ struct disk_header* dh = reinterpret_cast<struct disk_header*>(buffer);
+
+ dh->magic = SNAP_MAGIC;
+ dh->valid = SNAPSHOT_VALID;
+ dh->version = SNAPSHOT_DISK_VERSION;
+ dh->chunk_size = CHUNK_SIZE;
+}
+
+// Start the replace operation. This will read the
+// internal COW format and if the block is compressed,
+// it will be de-compressed.
+bool WorkerThread::ProcessReplaceOp(const CowOperation* cow_op) {
+ if (!reader_->ReadData(*cow_op, &bufsink_)) {
+ SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
+ return false;
+ }
+
+ return true;
+}
+
+// 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) {
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ CHECK(buffer != nullptr);
+
+ // Issue a single 4K IO. However, this can be optimized
+ // if the successive blocks are contiguous.
+ if (!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_
+ << "at block :" << cow_op->source;
+ return false;
+ }
+
+ return true;
+}
+
+bool WorkerThread::ProcessZeroOp() {
+ // Zero out the entire block
+ void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+ CHECK(buffer != nullptr);
+
+ memset(buffer, 0, BLOCK_SZ);
+ return true;
+}
+
+bool WorkerThread::ProcessCowOp(const CowOperation* cow_op) {
+ CHECK(cow_op != nullptr);
+
+ switch (cow_op->type) {
+ case kCowReplaceOp: {
+ return ProcessReplaceOp(cow_op);
+ }
+
+ case kCowZeroOp: {
+ return ProcessZeroOp();
+ }
+
+ case kCowCopyOp: {
+ return ProcessCopyOp(cow_op);
+ }
+
+ default: {
+ SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
+ }
+ }
+ return false;
+}
+
+int WorkerThread::ReadUnalignedSector(sector_t sector, size_t size,
+ std::map<sector_t, const CowOperation*>::iterator& it) {
+ size_t skip_sector_size = 0;
+
+ SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size
+ << " Aligned sector: " << it->second;
+
+ if (!ProcessCowOp(it->second)) {
+ SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size;
+ return -1;
+ }
+
+ int num_sectors_skip = sector - it->first;
+
+ if (num_sectors_skip > 0) {
+ skip_sector_size = num_sectors_skip << SECTOR_SHIFT;
+ char* buffer = reinterpret_cast<char*>(bufsink_.GetBufPtr());
+ struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
+
+ memmove(msg->payload.buf, (char*)msg->payload.buf + skip_sector_size,
+ (BLOCK_SZ - skip_sector_size));
+ }
+
+ bufsink_.ResetBufferOffset();
+ return std::min(size, (BLOCK_SZ - skip_sector_size));
+}
+
+/*
+ * Read the data for a given COW Operation.
+ *
+ * Kernel can issue IO at a sector granularity.
+ * Hence, an IO may end up with reading partial
+ * data from a COW operation or we may also
+ * end up with interspersed request between
+ * two COW operations.
+ *
+ */
+int WorkerThread::ReadData(sector_t sector, size_t size) {
+ std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+ /*
+ * chunk_map stores COW operation at 4k granularity.
+ * If the requested IO with the sector falls on the 4k
+ * boundary, then we can read the COW op directly without
+ * any issue.
+ *
+ * However, if the requested sector is not 4K aligned,
+ * then we will have the find the nearest COW operation
+ * and chop the 4K block to fetch the requested sector.
+ */
+ std::map<sector_t, const CowOperation*>::iterator it = chunk_map.find(sector);
+ if (it == chunk_map.end()) {
+ it = chunk_map.lower_bound(sector);
+ if (it != chunk_map.begin()) {
+ --it;
+ }
+
+ /*
+ * If the IO is spanned between two COW operations,
+ * split the IO into two parts:
+ *
+ * 1: Read the first part from the single COW op
+ * 2: Read the second part from the next COW op.
+ *
+ * Ex: Let's say we have a 1024 Bytes IO request.
+ *
+ * 0 COW OP-1 4096 COW OP-2 8192
+ * |******************|*******************|
+ * |*****|*****|
+ * 3584 4608
+ * <- 1024B - >
+ *
+ * We have two COW operations which are 4k blocks.
+ * The IO is requested for 1024 Bytes which are spanned
+ * between two COW operations. We will split this IO
+ * into two parts:
+ *
+ * 1: IO of size 512B from offset 3584 bytes (COW OP-1)
+ * 2: IO of size 512B from offset 4096 bytes (COW OP-2)
+ */
+ return ReadUnalignedSector(sector, size, it);
+ }
+
+ int num_ops = DIV_ROUND_UP(size, BLOCK_SZ);
+ while (num_ops) {
+ if (!ProcessCowOp(it->second)) {
+ return -1;
+ }
+ num_ops -= 1;
+ it++;
+ // Update the buffer offset
+ bufsink_.UpdateBufferOffset(BLOCK_SZ);
+
+ SNAP_LOG(DEBUG) << "ReadData at sector: " << sector << " size: " << size;
+ }
+
+ // Reset the buffer offset
+ bufsink_.ResetBufferOffset();
+ return size;
+}
+
+/*
+ * dm-snap does prefetch reads while reading disk-exceptions.
+ * By default, prefetch value is set to 12; this means that
+ * dm-snap will issue 12 areas wherein each area is a 4k page
+ * of disk-exceptions.
+ *
+ * If during prefetch, if the chunk-id seen is beyond the
+ * actual number of metadata page, fill the buffer with zero.
+ * When dm-snap starts parsing the buffer, it will stop
+ * reading metadata page once the buffer content is zero.
+ */
+bool WorkerThread::ZerofillDiskExceptions(size_t read_size) {
+ size_t size = exceptions_per_area_ * sizeof(struct disk_exception);
+
+ if (read_size > size) {
+ return false;
+ }
+
+ void* buffer = bufsink_.GetPayloadBuffer(size);
+ CHECK(buffer != nullptr);
+
+ memset(buffer, 0, size);
+ return true;
+}
+
+/*
+ * A disk exception is a simple mapping of old_chunk to new_chunk.
+ * When dm-snapshot device is created, kernel requests these mapping.
+ *
+ * Each disk exception is of size 16 bytes. Thus a single 4k page can
+ * have:
+ *
+ * exceptions_per_area_ = 4096/16 = 256. This entire 4k page
+ * is considered a metadata page and it is represented by chunk ID.
+ *
+ * Convert the chunk ID to index into the vector which gives us
+ * the metadata page.
+ */
+bool WorkerThread::ReadDiskExceptions(chunk_t chunk, size_t read_size) {
+ uint32_t stride = exceptions_per_area_ + 1;
+ size_t size;
+ const std::vector<std::unique_ptr<uint8_t[]>>& vec = snapuserd_->GetMetadataVec();
+
+ // ChunkID to vector index
+ lldiv_t divresult = lldiv(chunk, stride);
+
+ if (divresult.quot < vec.size()) {
+ size = exceptions_per_area_ * sizeof(struct disk_exception);
+
+ CHECK(read_size == size);
+
+ void* buffer = bufsink_.GetPayloadBuffer(size);
+ CHECK(buffer != nullptr);
+
+ memcpy(buffer, vec[divresult.quot].get(), size);
+ } else {
+ return ZerofillDiskExceptions(read_size);
+ }
+
+ return true;
+}
+
+loff_t WorkerThread::GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
+ int* unmerged_exceptions) {
+ loff_t offset = 0;
+ *unmerged_exceptions = 0;
+
+ while (*unmerged_exceptions <= exceptions_per_area_) {
+ struct disk_exception* merged_de =
+ reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
+ struct disk_exception* cow_de =
+ reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
+
+ // Unmerged op by the kernel
+ if (merged_de->old_chunk != 0 || merged_de->new_chunk != 0) {
+ CHECK(merged_de->old_chunk == cow_de->old_chunk);
+ CHECK(merged_de->new_chunk == cow_de->new_chunk);
+
+ offset += sizeof(struct disk_exception);
+ *unmerged_exceptions += 1;
+ continue;
+ }
+
+ break;
+ }
+
+ CHECK(!(*unmerged_exceptions == exceptions_per_area_));
+
+ SNAP_LOG(DEBUG) << "Unmerged_Exceptions: " << *unmerged_exceptions << " Offset: " << offset;
+ return offset;
+}
+
+int WorkerThread::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
+ int unmerged_exceptions) {
+ int merged_ops_cur_iter = 0;
+ std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+
+ // Find the operations which are merged in this cycle.
+ while ((unmerged_exceptions + merged_ops_cur_iter) < exceptions_per_area_) {
+ struct disk_exception* merged_de =
+ reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
+ struct disk_exception* cow_de =
+ reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
+
+ CHECK(merged_de->new_chunk == 0);
+ CHECK(merged_de->old_chunk == 0);
+
+ if (cow_de->new_chunk != 0) {
+ merged_ops_cur_iter += 1;
+ offset += sizeof(struct disk_exception);
+ const CowOperation* cow_op = chunk_map[ChunkToSector(cow_de->new_chunk)];
+ CHECK(cow_op != nullptr);
+
+ CHECK(cow_op->new_block == cow_de->old_chunk);
+ // zero out to indicate that operation is merged.
+ cow_de->old_chunk = 0;
+ cow_de->new_chunk = 0;
+ } else if (cow_de->old_chunk == 0) {
+ // Already merged op in previous iteration or
+ // This could also represent a partially filled area.
+ //
+ // If the op was merged in previous cycle, we don't have
+ // to count them.
+ CHECK(cow_de->new_chunk == 0);
+ break;
+ } else {
+ SNAP_LOG(ERROR) << "Error in merge operation. Found invalid metadata: "
+ << " merged_de-old-chunk: " << merged_de->old_chunk
+ << " merged_de-new-chunk: " << merged_de->new_chunk
+ << " cow_de-old-chunk: " << cow_de->old_chunk
+ << " cow_de-new-chunk: " << cow_de->new_chunk
+ << " unmerged_exceptions: " << unmerged_exceptions
+ << " merged_ops_cur_iter: " << merged_ops_cur_iter
+ << " offset: " << offset;
+ return -1;
+ }
+ }
+ return merged_ops_cur_iter;
+}
+
+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();
+
+ // ChunkID to vector index
+ lldiv_t divresult = lldiv(chunk, stride);
+ CHECK(divresult.quot < vec.size());
+ SNAP_LOG(DEBUG) << "ProcessMergeComplete: chunk: " << chunk
+ << " Metadata-Index: " << divresult.quot;
+
+ int unmerged_exceptions = 0;
+ loff_t offset = GetMergeStartOffset(buffer, vec[divresult.quot].get(), &unmerged_exceptions);
+
+ int merged_ops_cur_iter =
+ GetNumberOfMergedOps(buffer, vec[divresult.quot].get(), offset, unmerged_exceptions);
+
+ // 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;
+ }
+
+ SNAP_LOG(DEBUG) << "Merge success: " << merged_ops_cur_iter << "chunk: " << chunk;
+ return true;
+}
+
+// Read Header from dm-user misc device. This gives
+// us the sector number for which IO is issued by dm-snapshot device
+bool WorkerThread::ReadDmUserHeader() {
+ if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) {
+ if (errno != ENOTBLK) {
+ SNAP_PLOG(ERROR) << "Control-read failed";
+ }
+ return false;
+ }
+
+ return true;
+}
+
+// Send the payload/data back to dm-user misc device.
+bool WorkerThread::WriteDmUserPayload(size_t size) {
+ if (!android::base::WriteFully(ctrl_fd_, bufsink_.GetBufPtr(),
+ sizeof(struct dm_user_header) + size)) {
+ SNAP_PLOG(ERROR) << "Write to dm-user failed size: " << size;
+ return false;
+ }
+
+ return true;
+}
+
+bool WorkerThread::ReadDmUserPayload(void* buffer, size_t size) {
+ if (!android::base::ReadFully(ctrl_fd_, buffer, size)) {
+ SNAP_PLOG(ERROR) << "ReadDmUserPayload failed size: " << size;
+ return false;
+ }
+
+ return true;
+}
+
+bool WorkerThread::DmuserWriteRequest() {
+ struct dm_user_header* header = bufsink_.GetHeaderPtr();
+
+ // device mapper has the capability to allow
+ // targets to flush the cache when writes are completed. This
+ // is controlled by each target by a flag "flush_supported".
+ // This flag is set by dm-user. When flush is supported,
+ // a number of zero-length bio's will be submitted to
+ // the target for the purpose of flushing cache. It is the
+ // responsibility of the target driver - which is dm-user in this
+ // case, to remap these bio's to the underlying device. Since,
+ // there is no underlying device for dm-user, this zero length
+ // bio's gets routed to daemon.
+ //
+ // Flush operations are generated post merge by dm-snap by having
+ // REQ_PREFLUSH flag set. Snapuser daemon doesn't have anything
+ // to flush per se; hence, just respond back with a success message.
+ if (header->sector == 0) {
+ CHECK(header->len == 0);
+ header->type = DM_USER_RESP_SUCCESS;
+ if (!WriteDmUserPayload(0)) {
+ return false;
+ }
+ return true;
+ }
+
+ std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+ size_t remaining_size = header->len;
+ size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
+ CHECK(read_size == BLOCK_SZ) << "DmuserWriteRequest: read_size: " << read_size;
+
+ CHECK(header->sector > 0);
+ chunk_t chunk = SectorToChunk(header->sector);
+ CHECK(chunk_map.find(header->sector) == chunk_map.end());
+
+ void* buffer = bufsink_.GetPayloadBuffer(read_size);
+ CHECK(buffer != nullptr);
+ header->type = DM_USER_RESP_SUCCESS;
+
+ if (!ReadDmUserPayload(buffer, read_size)) {
+ SNAP_LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk
+ << "Sector: " << header->sector;
+ header->type = DM_USER_RESP_ERROR;
+ }
+
+ if (header->type == DM_USER_RESP_SUCCESS && !ProcessMergeComplete(chunk, buffer)) {
+ SNAP_LOG(ERROR) << "ProcessMergeComplete failed for chunk id: " << chunk
+ << "Sector: " << header->sector;
+ header->type = DM_USER_RESP_ERROR;
+ } else {
+ SNAP_LOG(DEBUG) << "ProcessMergeComplete success for chunk id: " << chunk
+ << "Sector: " << header->sector;
+ }
+
+ if (!WriteDmUserPayload(0)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool WorkerThread::DmuserReadRequest() {
+ struct dm_user_header* header = bufsink_.GetHeaderPtr();
+ size_t remaining_size = header->len;
+ loff_t offset = 0;
+ sector_t sector = header->sector;
+ std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+ do {
+ size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
+
+ int ret = read_size;
+ header->type = DM_USER_RESP_SUCCESS;
+ chunk_t chunk = SectorToChunk(header->sector);
+
+ // Request to sector 0 is always for kernel
+ // representation of COW header. This IO should be only
+ // once during dm-snapshot device creation. We should
+ // never see multiple IO requests. Additionally this IO
+ // will always be a single 4k.
+ if (header->sector == 0) {
+ CHECK(read_size == BLOCK_SZ) << " Sector 0 read request of size: " << read_size;
+ ConstructKernelCowHeader();
+ SNAP_LOG(DEBUG) << "Kernel header constructed";
+ } else {
+ if (!offset && (read_size == BLOCK_SZ) &&
+ chunk_map.find(header->sector) == chunk_map.end()) {
+ if (!ReadDiskExceptions(chunk, read_size)) {
+ SNAP_LOG(ERROR) << "ReadDiskExceptions failed for chunk id: " << chunk
+ << "Sector: " << header->sector;
+ header->type = DM_USER_RESP_ERROR;
+ } else {
+ SNAP_LOG(DEBUG) << "ReadDiskExceptions success for chunk id: " << chunk
+ << "Sector: " << header->sector;
+ }
+ } else {
+ chunk_t num_sectors_read = (offset >> SECTOR_SHIFT);
+ ret = ReadData(sector + num_sectors_read, read_size);
+ if (ret < 0) {
+ SNAP_LOG(ERROR) << "ReadData failed for chunk id: " << chunk
+ << " Sector: " << (sector + num_sectors_read)
+ << " size: " << read_size << " header-len: " << header->len;
+ header->type = DM_USER_RESP_ERROR;
+ } else {
+ SNAP_LOG(DEBUG) << "ReadData success for chunk id: " << chunk
+ << "Sector: " << header->sector;
+ }
+ }
+ }
+
+ // 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;
+ }
+
+ remaining_size -= ret;
+ offset += ret;
+ } while (remaining_size > 0);
+
+ return true;
+}
+
+void WorkerThread::InitializeBufsink() {
+ // Allocate the buffer which is used to communicate between
+ // daemon and dm-user. The buffer comprises of header and a fixed payload.
+ // If the dm-user requests a big IO, the IO will be broken into chunks
+ // of PAYLOAD_SIZE.
+ size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
+ bufsink_.Initialize(buf_size);
+}
+
+bool WorkerThread::RunThread() {
+ InitializeBufsink();
+
+ if (!InitializeFds()) {
+ return false;
+ }
+
+ if (!InitReader()) {
+ return false;
+ }
+
+ // Start serving IO
+ while (true) {
+ if (!ProcessIORequest()) {
+ break;
+ }
+ }
+
+ CloseFds();
+ reader_->CloseCowFd();
+
+ return true;
+}
+
+bool WorkerThread::ProcessIORequest() {
+ struct dm_user_header* header = bufsink_.GetHeaderPtr();
+
+ if (!ReadDmUserHeader()) {
+ return false;
+ }
+
+ SNAP_LOG(DEBUG) << "msg->seq: " << std::hex << header->seq;
+ SNAP_LOG(DEBUG) << "msg->type: " << std::hex << header->type;
+ SNAP_LOG(DEBUG) << "msg->flags: " << std::hex << header->flags;
+ SNAP_LOG(DEBUG) << "msg->sector: " << std::hex << header->sector;
+ SNAP_LOG(DEBUG) << "msg->len: " << std::hex << header->len;
+
+ switch (header->type) {
+ case DM_USER_REQ_MAP_READ: {
+ if (!DmuserReadRequest()) {
+ return false;
+ }
+ break;
+ }
+
+ case DM_USER_REQ_MAP_WRITE: {
+ if (!DmuserWriteRequest()) {
+ return false;
+ }
+ break;
+ }
+ }
+
+ return true;
+}
+
+} // namespace snapshot
+} // namespace android
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index 62a8d3b..5887641 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -121,12 +121,13 @@
const std::string bootconfig =
"androidboot.bootdevice = \" \"1d84000.ufshc\"\n"
+ "androidboot.boot_devices = \"dev1\", \"dev2,withcomma\", \"dev3\"\n"
"androidboot.baseband = \"sdy\"\n"
"androidboot.keymaster = \"1\"\n"
"androidboot.serialno = \"BLAHBLAHBLAH\"\n"
"androidboot.slot_suffix = \"_a\"\n"
"androidboot.hardware.platform = \"sdw813\"\n"
- "androidboot.hardware = \"foo\"\n"
+ "hardware = \"foo\"\n"
"androidboot.revision = \"EVT1.0\"\n"
"androidboot.bootloader = \"burp-0.1-7521\"\n"
"androidboot.hardware.sku = \"mary\"\n"
@@ -152,12 +153,13 @@
const std::vector<std::pair<std::string, std::string>> bootconfig_result_space = {
{"androidboot.bootdevice", "1d84000.ufshc"},
+ {"androidboot.boot_devices", "dev1, dev2,withcomma, dev3"},
{"androidboot.baseband", "sdy"},
{"androidboot.keymaster", "1"},
{"androidboot.serialno", "BLAHBLAHBLAH"},
{"androidboot.slot_suffix", "_a"},
{"androidboot.hardware.platform", "sdw813"},
- {"androidboot.hardware", "foo"},
+ {"hardware", "foo"},
{"androidboot.revision", "EVT1.0"},
{"androidboot.bootloader", "burp-0.1-7521"},
{"androidboot.hardware.sku", "mary"},
diff --git a/init/Android.bp b/init/Android.bp
index 3ff1767..1381c1d 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -143,6 +143,7 @@
"libcgrouprc_format",
"liblmkd_utils",
"libmodprobe",
+ "libprocinfo",
"libprotobuf-cpp-lite",
"libpropertyinfoserializer",
"libpropertyinfoparser",
@@ -308,6 +309,7 @@
"libsnapshot_cow",
"libsnapshot_init",
"update_metadata-protos",
+ "libprocinfo",
],
static_executable: true,
diff --git a/init/Android.mk b/init/Android.mk
index 65ee385..3c7d95a 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -130,6 +130,7 @@
libsnapshot_cow \
libsnapshot_init \
update_metadata-protos \
+ libprocinfo \
LOCAL_SANITIZE := signed-integer-overflow
# First stage init is weird: it may start without stdout/stderr, and no /proc.
diff --git a/init/builtins.cpp b/init/builtins.cpp
index dcc9582..035038f 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -1278,6 +1278,14 @@
return ErrnoError() << "failed to execute linkerconfig";
}
+ auto current_mount_ns = GetCurrentMountNamespace();
+ if (!current_mount_ns.ok()) {
+ return current_mount_ns.error();
+ }
+ if (*current_mount_ns == NS_DEFAULT) {
+ SetDefaultMountNamespaceReady();
+ }
+
LOG(INFO) << "linkerconfig generated " << linkerconfig_target
<< " with mounted APEX modules info";
diff --git a/init/first_stage_console.cpp b/init/first_stage_console.cpp
index 0f01166..e2ea0ab 100644
--- a/init/first_stage_console.cpp
+++ b/init/first_stage_console.cpp
@@ -105,8 +105,20 @@
_exit(127);
}
-int FirstStageConsole(const std::string& cmdline) {
- auto pos = cmdline.find("androidboot.first_stage_console=");
+int FirstStageConsole(const std::string& cmdline, const std::string& bootconfig) {
+ auto pos = bootconfig.find("androidboot.first_stage_console =");
+ if (pos != std::string::npos) {
+ int val = 0;
+ if (sscanf(bootconfig.c_str() + pos, "androidboot.first_stage_console = \"%d\"", &val) !=
+ 1) {
+ return FirstStageConsoleParam::DISABLED;
+ }
+ if (val <= FirstStageConsoleParam::MAX_PARAM_VALUE && val >= 0) {
+ return val;
+ }
+ }
+
+ pos = cmdline.find("androidboot.first_stage_console=");
if (pos != std::string::npos) {
int val = 0;
if (sscanf(cmdline.c_str() + pos, "androidboot.first_stage_console=%d", &val) != 1) {
diff --git a/init/first_stage_console.h b/init/first_stage_console.h
index d5744df..4a30d35 100644
--- a/init/first_stage_console.h
+++ b/init/first_stage_console.h
@@ -29,7 +29,7 @@
};
void StartConsole(const std::string& cmdline);
-int FirstStageConsole(const std::string& cmdline);
+int FirstStageConsole(const std::string& cmdline, const std::string& bootconfig);
} // namespace init
} // namespace android
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index ff75aa3..b2ab550 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -102,8 +102,9 @@
}
}
-bool ForceNormalBoot(const std::string& cmdline) {
- return cmdline.find("androidboot.force_normal_boot=1") != std::string::npos;
+bool ForceNormalBoot(const std::string& cmdline, const std::string& bootconfig) {
+ return bootconfig.find("androidboot.force_normal_boot = \"1\"") != std::string::npos ||
+ cmdline.find("androidboot.force_normal_boot=1") != std::string::npos;
}
} // namespace
@@ -211,6 +212,8 @@
android::base::ReadFileToString("/proc/cmdline", &cmdline);
// Don't expose the raw bootconfig to unprivileged processes.
chmod("/proc/bootconfig", 0440);
+ std::string bootconfig;
+ android::base::ReadFileToString("/proc/bootconfig", &bootconfig);
gid_t groups[] = {AID_READPROC};
CHECKCALL(setgroups(arraysize(groups), groups));
CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
@@ -278,11 +281,11 @@
old_root_dir.reset();
}
- auto want_console = ALLOW_FIRST_STAGE_CONSOLE ? FirstStageConsole(cmdline) : 0;
+ auto want_console = ALLOW_FIRST_STAGE_CONSOLE ? FirstStageConsole(cmdline, bootconfig) : 0;
boot_clock::time_point module_start_time = boot_clock::now();
int module_count = 0;
- if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline), want_console,
+ if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig), want_console,
module_count)) {
if (want_console != FirstStageConsoleParam::DISABLED) {
LOG(ERROR) << "Failed to load kernel modules, starting console";
@@ -324,7 +327,7 @@
LOG(INFO) << "Copied ramdisk prop to " << dest;
}
- if (ForceNormalBoot(cmdline)) {
+ 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.
diff --git a/init/init.cpp b/init/init.cpp
index 70d6809..7264b22 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -518,11 +518,9 @@
if (!android::base::GetBoolProperty("ro.oem_unlock_supported", false)) {
return;
}
- ImportKernelCmdline([](const std::string& key, const std::string& value) {
- if (key == "androidboot.verifiedbootstate") {
- SetProperty("ro.boot.flash.locked", value == "orange" ? "0" : "1");
- }
- });
+ SetProperty(
+ "ro.boot.flash.locked",
+ android::base::GetProperty("ro.boot.verifiedbootstate", "") == "orange" ? "0" : "1");
}
static Result<void> property_enable_triggers_action(const BuiltinArguments& args) {
diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp
index ec48cde..15252a6 100644
--- a/init/mount_namespace.cpp
+++ b/init/mount_namespace.cpp
@@ -301,5 +301,20 @@
return {};
}
+base::Result<MountNamespace> GetCurrentMountNamespace() {
+ std::string current_namespace_id = GetMountNamespaceId();
+ if (current_namespace_id == "") {
+ return Error() << "Failed to get current mount namespace ID";
+ }
+
+ if (current_namespace_id == bootstrap_ns_id) {
+ return NS_BOOTSTRAP;
+ } else if (current_namespace_id == default_ns_id) {
+ return NS_DEFAULT;
+ }
+
+ return Error() << "Failed to find current mount namespace";
+}
+
} // namespace init
} // namespace android
diff --git a/init/mount_namespace.h b/init/mount_namespace.h
index d4d6f82..5e3dab2 100644
--- a/init/mount_namespace.h
+++ b/init/mount_namespace.h
@@ -26,5 +26,7 @@
bool SetupMountNamespaces();
base::Result<void> SwitchToMountNamespaceIfNeeded(MountNamespace target_mount_namespace);
+base::Result<MountNamespace> GetCurrentMountNamespace();
+
} // namespace init
} // namespace android
diff --git a/init/property_service.cpp b/init/property_service.cpp
index b722702..382f430 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -44,6 +44,7 @@
#include <mutex>
#include <optional>
#include <queue>
+#include <string_view>
#include <thread>
#include <vector>
@@ -1162,28 +1163,29 @@
}
}
+constexpr auto ANDROIDBOOT_PREFIX = "androidboot."sv;
+
static void ProcessKernelCmdline() {
- bool for_emulator = false;
ImportKernelCmdline([&](const std::string& key, const std::string& value) {
- if (key == "qemu") {
- for_emulator = true;
- } else if (StartsWith(key, "androidboot.")) {
- InitPropertySet("ro.boot." + key.substr(12), value);
+ if (StartsWith(key, ANDROIDBOOT_PREFIX)) {
+ InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value);
+ } else if (StartsWith(key, "qemu."sv)) {
+ InitPropertySet("ro.kernel." + key, value);
+ } else if (key == "qemu") {
+ InitPropertySet("ro.kernel." + key, value); // emulator specific, deprecated
+ InitPropertySet("ro.boot." + key, value);
}
});
-
- if (for_emulator) {
- ImportKernelCmdline([&](const std::string& key, const std::string& value) {
- // In the emulator, export any kernel option with the "ro.kernel." prefix.
- InitPropertySet("ro.kernel." + key, value);
- });
- }
}
static void ProcessBootconfig() {
ImportBootconfig([&](const std::string& key, const std::string& value) {
- if (StartsWith(key, "androidboot.")) {
- InitPropertySet("ro.boot." + key.substr(12), 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
+ InitPropertySet("ro.boot." + key, value);
}
});
}
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 0336936..62c4586 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -63,6 +63,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
+#include <android-base/result.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <fs_avb/fs_avb.h>
@@ -92,7 +93,7 @@
enum EnforcingStatus { SELINUX_PERMISSIVE, SELINUX_ENFORCING };
-EnforcingStatus StatusFromCmdline() {
+EnforcingStatus StatusFromProperty() {
EnforcingStatus status = SELINUX_ENFORCING;
ImportKernelCmdline([&](const std::string& key, const std::string& value) {
@@ -101,12 +102,20 @@
}
});
+ if (status == SELINUX_ENFORCING) {
+ ImportBootconfig([&](const std::string& key, const std::string& value) {
+ if (key == "androidboot.selinux" && value == "permissive") {
+ status = SELINUX_PERMISSIVE;
+ }
+ });
+ }
+
return status;
}
bool IsEnforcing() {
if (ALLOW_PERMISSIVE_SELINUX) {
- return StatusFromCmdline() == SELINUX_ENFORCING;
+ return StatusFromProperty() == SELINUX_ENFORCING;
}
return true;
}
@@ -214,8 +223,8 @@
return true;
}
-bool FindPrecompiledSplitPolicy(std::string* file) {
- file->clear();
+Result<std::string> FindPrecompiledSplitPolicy() {
+ std::string precompiled_sepolicy;
// If there is an odm partition, precompiled_sepolicy will be in
// odm/etc/selinux. Otherwise it will be in vendor/etc/selinux.
static constexpr const char vendor_precompiled_sepolicy[] =
@@ -223,62 +232,49 @@
static constexpr const char odm_precompiled_sepolicy[] =
"/odm/etc/selinux/precompiled_sepolicy";
if (access(odm_precompiled_sepolicy, R_OK) == 0) {
- *file = odm_precompiled_sepolicy;
+ precompiled_sepolicy = odm_precompiled_sepolicy;
} else if (access(vendor_precompiled_sepolicy, R_OK) == 0) {
- *file = vendor_precompiled_sepolicy;
+ precompiled_sepolicy = vendor_precompiled_sepolicy;
} else {
- PLOG(INFO) << "No precompiled sepolicy";
- return false;
- }
- std::string actual_plat_id;
- if (!ReadFirstLine("/system/etc/selinux/plat_sepolicy_and_mapping.sha256", &actual_plat_id)) {
- PLOG(INFO) << "Failed to read "
- "/system/etc/selinux/plat_sepolicy_and_mapping.sha256";
- return false;
- }
- std::string actual_system_ext_id;
- if (!ReadFirstLine("/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256",
- &actual_system_ext_id)) {
- PLOG(INFO) << "Failed to read "
- "/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256";
- return false;
- }
- std::string actual_product_id;
- if (!ReadFirstLine("/product/etc/selinux/product_sepolicy_and_mapping.sha256",
- &actual_product_id)) {
- PLOG(INFO) << "Failed to read "
- "/product/etc/selinux/product_sepolicy_and_mapping.sha256";
- return false;
+ return ErrnoError() << "No precompiled sepolicy at " << vendor_precompiled_sepolicy;
}
- std::string precompiled_plat_id;
- std::string precompiled_plat_sha256 = *file + ".plat_sepolicy_and_mapping.sha256";
- if (!ReadFirstLine(precompiled_plat_sha256.c_str(), &precompiled_plat_id)) {
- PLOG(INFO) << "Failed to read " << precompiled_plat_sha256;
- file->clear();
- return false;
+ // 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"},
+ };
+
+ 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");
}
- std::string precompiled_system_ext_id;
- std::string precompiled_system_ext_sha256 = *file + ".system_ext_sepolicy_and_mapping.sha256";
- if (!ReadFirstLine(precompiled_system_ext_sha256.c_str(), &precompiled_system_ext_id)) {
- PLOG(INFO) << "Failed to read " << precompiled_system_ext_sha256;
- file->clear();
- return false;
+
+ 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");
}
- std::string precompiled_product_id;
- std::string precompiled_product_sha256 = *file + ".product_sepolicy_and_mapping.sha256";
- if (!ReadFirstLine(precompiled_product_sha256.c_str(), &precompiled_product_id)) {
- PLOG(INFO) << "Failed to read " << precompiled_product_sha256;
- file->clear();
- return false;
+
+ for (const auto& [actual_id_path, precompiled_id_path] : sepolicy_hashes) {
+ std::string actual_id;
+ if (!ReadFirstLine(actual_id_path.c_str(), &actual_id)) {
+ return ErrnoError() << "Failed to read " << actual_id_path;
+ }
+
+ std::string precompiled_id;
+ if (!ReadFirstLine(precompiled_id_path.c_str(), &precompiled_id)) {
+ return ErrnoError() << "Failed to read " << precompiled_id_path;
+ }
+
+ if (actual_id.empty() || actual_id != precompiled_id) {
+ return Error() << actual_id_path << " and " << precompiled_id_path << " differ";
+ }
}
- if (actual_plat_id.empty() || actual_plat_id != precompiled_plat_id ||
- actual_system_ext_id.empty() || actual_system_ext_id != precompiled_system_ext_id ||
- actual_product_id.empty() || actual_product_id != precompiled_product_id) {
- file->clear();
- return false;
- }
- return true;
+
+ return precompiled_sepolicy;
}
bool GetVendorMappingVersion(std::string* plat_vers) {
@@ -325,15 +321,18 @@
// Load precompiled policy from vendor image, if a matching policy is found there. The policy
// must match the platform policy on the system image.
- std::string precompiled_sepolicy_file;
// use_userdebug_policy requires compiling sepolicy with userdebug_plat_sepolicy.cil.
// Thus it cannot use the precompiled policy from vendor image.
- if (!use_userdebug_policy && FindPrecompiledSplitPolicy(&precompiled_sepolicy_file)) {
- unique_fd fd(open(precompiled_sepolicy_file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
- if (fd != -1) {
- policy_file->fd = std::move(fd);
- policy_file->path = std::move(precompiled_sepolicy_file);
- return true;
+ if (!use_userdebug_policy) {
+ if (auto res = FindPrecompiledSplitPolicy(); res.ok()) {
+ unique_fd fd(open(res->c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
+ if (fd != -1) {
+ policy_file->fd = std::move(fd);
+ policy_file->path = std::move(*res);
+ return true;
+ }
+ } else {
+ LOG(INFO) << res.error();
}
}
// No suitable precompiled policy could be loaded
diff --git a/init/service.cpp b/init/service.cpp
index cfb8284..836dc47 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -125,11 +125,6 @@
return execv(c_strings[0], c_strings.data()) == 0;
}
-static bool AreRuntimeApexesReady() {
- struct stat buf;
- return stat("/apex/com.android.runtime/", &buf) == 0;
-}
-
unsigned long Service::next_start_order_ = 1;
bool Service::is_exec_service_running_ = false;
@@ -312,7 +307,7 @@
#else
static bool is_apex_updatable = false;
#endif
- const bool is_process_updatable = !pre_apexd_ && is_apex_updatable;
+ const bool is_process_updatable = !use_bootstrap_ns_ && is_apex_updatable;
// If we crash > 4 times in 'fatal_crash_window_' minutes or before boot_completed,
// reboot into bootloader or set crashing property
@@ -465,12 +460,12 @@
scon = *result;
}
- if (!AreRuntimeApexesReady() && !pre_apexd_) {
- // If this service is started before the Runtime and ART APEXes get
- // available, mark it as pre-apexd one. Note that this marking is
+ if (!IsDefaultMountNamespaceReady() && name_ != "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
// to crash), it is still recognized as pre-apexd... for consistency.
- pre_apexd_ = true;
+ use_bootstrap_ns_ = true;
}
// For pre-apexd services, override mount namespace as "bootstrap" one before starting.
@@ -479,7 +474,7 @@
std::optional<MountNamespace> override_mount_namespace;
if (name_ == "ueventd") {
override_mount_namespace = NS_DEFAULT;
- } else if (pre_apexd_) {
+ } else if (use_bootstrap_ns_) {
override_mount_namespace = NS_BOOTSTRAP;
}
diff --git a/init/service.h b/init/service.h
index aee1e5d..043555f 100644
--- a/init/service.h
+++ b/init/service.h
@@ -207,7 +207,7 @@
std::vector<std::function<void(const siginfo_t& siginfo)>> reap_callbacks_;
- bool pre_apexd_ = false;
+ bool use_bootstrap_ns_ = false;
bool post_data_ = false;
diff --git a/init/snapuserd_transition.cpp b/init/snapuserd_transition.cpp
index 19b5c57..40467b7 100644
--- a/init/snapuserd_transition.cpp
+++ b/init/snapuserd_transition.cpp
@@ -24,6 +24,7 @@
#include <filesystem>
#include <string>
+#include <string_view>
#include <android-base/file.h>
#include <android-base/logging.h>
@@ -34,6 +35,7 @@
#include <libsnapshot/snapshot.h>
#include <libsnapshot/snapuserd_client.h>
#include <private/android_filesystem_config.h>
+#include <procinfo/process_map.h>
#include <selinux/android.h>
#include "block_dev_initializer.h"
@@ -157,6 +159,33 @@
});
}
+static void LockAllSystemPages() {
+ bool ok = true;
+ auto callback = [&](const android::procinfo::MapInfo& map) -> void {
+ if (!ok || android::base::StartsWith(map.name, "/dev/") ||
+ !android::base::StartsWith(map.name, "/")) {
+ return;
+ }
+ auto start = reinterpret_cast<const void*>(map.start);
+ auto len = map.end - map.start;
+ if (!len) {
+ return;
+ }
+ if (mlock(start, len) < 0) {
+ LOG(ERROR) << "mlock failed, " << start << " for " << len << " bytes.";
+ ok = false;
+ }
+ };
+
+ if (!android::procinfo::ReadProcessMaps(getpid(), callback) || !ok) {
+ LOG(FATAL) << "Could not process /proc/" << getpid() << "/maps file for init, "
+ << "falling back to mlockall().";
+ if (mlockall(MCL_CURRENT) < 0) {
+ LOG(FATAL) << "mlockall failed";
+ }
+ }
+}
+
void SnapuserdSelinuxHelper::StartTransition() {
LOG(INFO) << "Starting SELinux transition of snapuserd";
@@ -170,9 +199,7 @@
// We cannot access /system after the transition, so make sure init is
// pinned in memory.
- if (mlockall(MCL_CURRENT) < 0) {
- LOG(FATAL) << "mlockall failed";
- }
+ LockAllSystemPages();
argv_.emplace_back("snapuserd");
argv_.emplace_back("-no_socket");
diff --git a/init/util.cpp b/init/util.cpp
index e69b43f..a40d104 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -376,6 +376,15 @@
android_dt_dir = value;
}
});
+ // ..Or bootconfig
+ if (android_dt_dir == kDefaultAndroidDtDir) {
+ ImportBootconfig([&](const std::string& key, const std::string& value) {
+ if (key == "androidboot.android_dt_dir") {
+ android_dt_dir = value;
+ }
+ });
+ }
+
LOG(INFO) << "Using Android DT directory " << android_dt_dir;
return android_dt_dir;
}
@@ -735,5 +744,16 @@
return access("/system/bin/recovery", F_OK) == 0;
}
+// Check if default mount namespace is ready to be used with APEX modules
+static bool is_default_mount_namespace_ready = false;
+
+bool IsDefaultMountNamespaceReady() {
+ return is_default_mount_namespace_ready;
+}
+
+void SetDefaultMountNamespaceReady() {
+ is_default_mount_namespace_ready = true;
+}
+
} // namespace init
} // namespace android
diff --git a/init/util.h b/init/util.h
index 7745d77..daba852 100644
--- a/init/util.h
+++ b/init/util.h
@@ -100,5 +100,8 @@
void SetStdioToDevNull(char** argv);
void InitKernelLogging(char** argv);
bool IsRecoveryMode();
+
+bool IsDefaultMountNamespaceReady();
+void SetDefaultMountNamespaceReady();
} // namespace init
} // namespace android
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index 54eeeac..d69c038 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -35,6 +35,7 @@
#include <string>
#include <android-base/strings.h>
+#include <cutils/fs.h>
#include <log/log.h>
#include <private/android_filesystem_config.h>
diff --git a/libprocessgroup/cgrouprc/include/android/cgrouprc.h b/libprocessgroup/cgrouprc/include/android/cgrouprc.h
index 9a79954..100d60e 100644
--- a/libprocessgroup/cgrouprc/include/android/cgrouprc.h
+++ b/libprocessgroup/cgrouprc/include/android/cgrouprc.h
@@ -68,6 +68,7 @@
*/
#define CGROUPRC_CONTROLLER_FLAG_MOUNTED 0x1
#define CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION 0x2
+#define CGROUPRC_CONTROLLER_FLAG_OPTIONAL 0x4
/**
* Returns the flags bitmask of the given controller.
diff --git a/libprocessgroup/profiles/cgroups.json b/libprocessgroup/profiles/cgroups.json
index 962d2ba..0634220 100644
--- a/libprocessgroup/profiles/cgroups.json
+++ b/libprocessgroup/profiles/cgroups.json
@@ -26,7 +26,8 @@
"Path": "/dev/memcg",
"Mode": "0700",
"UID": "root",
- "GID": "system"
+ "GID": "system",
+ "Optional": true
}
],
"Cgroups2": {
diff --git a/libprocessgroup/profiles/cgroups.proto b/libprocessgroup/profiles/cgroups.proto
index 13adcae..f2de345 100644
--- a/libprocessgroup/profiles/cgroups.proto
+++ b/libprocessgroup/profiles/cgroups.proto
@@ -24,7 +24,7 @@
Cgroups2 cgroups2 = 2 [json_name = "Cgroups2"];
}
-// Next: 7
+// Next: 8
message Cgroup {
string controller = 1 [json_name = "Controller"];
string path = 2 [json_name = "Path"];
@@ -35,6 +35,7 @@
// when a boolean is specified as false, so leave unspecified in that case
// https://developers.google.com/protocol-buffers/docs/proto3#default
bool needs_activation = 6 [json_name = "NeedsActivation"];
+ bool is_optional = 7 [json_name = "Optional"];
}
// Next: 6
diff --git a/libprocessgroup/profiles/cgroups_30.json b/libprocessgroup/profiles/cgroups_30.json
index 17d4929..80a074b 100644
--- a/libprocessgroup/profiles/cgroups_30.json
+++ b/libprocessgroup/profiles/cgroups_30.json
@@ -5,7 +5,8 @@
"Path": "/dev/stune",
"Mode": "0755",
"UID": "system",
- "GID": "system"
+ "GID": "system",
+ "Optional": true
}
]
}
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index aa41acb..3121d24 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -161,6 +161,10 @@
controller_flags |= CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION;
}
+ if (cgroup["Optional"].isBool() && cgroup["Optional"].asBool()) {
+ controller_flags |= CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
+ }
+
CgroupDescriptor descriptor(
cgroups_version, name, path, std::strtoul(cgroup["Mode"].asString().c_str(), 0, 8),
cgroup["UID"].asString(), cgroup["GID"].asString(), controller_flags);
@@ -267,8 +271,6 @@
descriptor.gid())) {
LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
result = -1;
- } else {
- LOG(ERROR) << "restored ownership for " << controller->name() << " cgroup";
}
} else {
if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
@@ -310,8 +312,15 @@
}
if (result < 0) {
- PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup";
- return false;
+ bool optional = controller->flags() & CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
+
+ if (optional && errno == EINVAL) {
+ // Optional controllers are allowed to fail to mount if kernel does not support them
+ LOG(INFO) << "Optional " << controller->name() << " cgroup controller is not mounted";
+ } else {
+ PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup";
+ return false;
+ }
}
return true;
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 0e1e98b..58e161d 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -630,6 +630,9 @@
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
# Start checkpoint before we touch data
@@ -908,6 +911,8 @@
# Lock the fs-verity keyring, so no more keys can be added
exec -- /system/bin/fsverity_init --lock
+ setprop keystore.boot_level 40
+
# 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
# are loaded. APEXes should not access this data until this has been
diff --git a/trusty/fuzz/Android.bp b/trusty/fuzz/Android.bp
index d147767..5d0ff79 100644
--- a/trusty/fuzz/Android.bp
+++ b/trusty/fuzz/Android.bp
@@ -30,7 +30,6 @@
"-Werror",
],
fuzz_config: {
- fuzz_on_haiku_device: false,
fuzz_on_haiku_host: false,
},
}
diff --git a/trusty/fuzz/test/Android.bp b/trusty/fuzz/test/Android.bp
index 7d74913..e0bca55 100644
--- a/trusty/fuzz/test/Android.bp
+++ b/trusty/fuzz/test/Android.bp
@@ -24,5 +24,8 @@
"-DTRUSTY_APP_PORT=\"com.android.trusty.sancov.test.srv\"",
"-DTRUSTY_APP_UUID=\"77f68803-c514-43ba-bdce-3254531c3d24\"",
"-DTRUSTY_APP_FILENAME=\"srv.syms.elf\"",
- ]
+ ],
+ fuzz_config: {
+ fuzz_on_haiku_device: false,
+ },
}