From 3b874456fc75e388d7e0c35b401b841a8372161b Mon Sep 17 00:00:00 2001 From: Akilesh Kailash Date: Sun, 3 Oct 2021 09:41:13 +0000 Subject: [PATCH 1/3] libsnapshot: Integrate userspace snapshots APIs dm-user block device will be the snapshot device; thus, no more explicit call to MapSnapshot(). Additionally, block device name for dm-user will be the snapshot name so that mount works seamlessly. API's to query the snapshot status, merge progress has been integrated. Since daemon requires base device for merge, we pass additional parameter during initialization. Add a new virtual a/b property flag to enable/disable user-snapshots feature. Propagate this flag to init layer for first stage mount during boot process. Some minor cleanup and renaming of variables. Bug: 193863443 Test: 1: Full OTA on CF and pixel and verify the merge completion. Tested merge-resume path by rebooting device during merge. 2: Incremental OTA on CF and pixel Signed-off-by: Akilesh Kailash Change-Id: I5088f40a55807946cd044b3987678ead3696d996 --- .../android/snapshot/snapshot.proto | 3 + .../include/libsnapshot/mock_snapshot.h | 1 + .../include/libsnapshot/snapshot.h | 26 +- .../include/libsnapshot/snapshot_stub.h | 1 + fs_mgr/libsnapshot/snapshot.cpp | 645 ++++++++++++++---- fs_mgr/libsnapshot/snapshot_stub.cpp | 5 + fs_mgr/libsnapshot/snapshot_test.cpp | 16 +- .../include/snapuserd/snapuserd_client.h | 3 +- .../snapuserd/snapuserd_client.cpp | 12 +- .../snapuserd/snapuserd_daemon.cpp | 93 ++- .../libsnapshot/snapuserd/snapuserd_daemon.h | 8 +- .../user-space-merge/snapuserd_core.cpp | 2 +- .../user-space-merge/snapuserd_core.h | 6 +- .../user-space-merge/snapuserd_dm_user.cpp | 6 +- .../user-space-merge/snapuserd_merge.cpp | 5 +- .../user-space-merge/snapuserd_readahead.cpp | 2 +- .../user-space-merge/snapuserd_server.cpp | 76 +-- .../user-space-merge/snapuserd_server.h | 32 +- .../snapuserd_transitions.cpp | 1 - fs_mgr/libsnapshot/utility.cpp | 9 + fs_mgr/libsnapshot/utility.h | 5 +- init/first_stage_mount.cpp | 6 +- init/snapuserd_transition.cpp | 23 +- init/snapuserd_transition.h | 7 +- 24 files changed, 764 insertions(+), 229 deletions(-) diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto index e2abdbaba..532f66dda 100644 --- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto +++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto @@ -194,6 +194,9 @@ message SnapshotUpdateStatus { // Source build fingerprint. string source_build_fingerprint = 8; + + // user-space snapshots + bool userspace_snapshots = 9; } // Next: 10 diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h index ec58cca2e..ba62330f1 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h @@ -35,6 +35,7 @@ class MockSnapshotManager : public ISnapshotManager { (override)); MOCK_METHOD(UpdateState, GetUpdateState, (double* progress), (override)); MOCK_METHOD(bool, UpdateUsesCompression, (), (override)); + MOCK_METHOD(bool, UpdateUsesUserSnapshots, (), (override)); MOCK_METHOD(Return, CreateUpdateSnapshots, (const chromeos_update_engine::DeltaArchiveManifest& manifest), (override)); MOCK_METHOD(bool, MapUpdateSnapshot, diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h index a49b0261c..08c39205a 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h @@ -193,6 +193,9 @@ class ISnapshotManager { // UpdateState is None, or no snapshots have been created. virtual bool UpdateUsesCompression() = 0; + // Returns true if userspace snapshots is enabled for the current update. + virtual bool UpdateUsesUserSnapshots() = 0; + // Create necessary COW device / files for OTA clients. New logical partitions will be added to // group "cow" in target_metadata. Regions of partitions of current_metadata will be // "write-protected" and snapshotted. @@ -352,6 +355,7 @@ class SnapshotManager final : public ISnapshotManager { const std::function& before_cancel = {}) override; UpdateState GetUpdateState(double* progress = nullptr) override; bool UpdateUsesCompression() override; + bool UpdateUsesUserSnapshots() override; Return CreateUpdateSnapshots(const DeltaArchiveManifest& manifest) override; bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params, std::string* snapshot_path) override; @@ -387,6 +391,11 @@ class SnapshotManager final : public ISnapshotManager { // first-stage to decide whether to launch snapuserd. bool IsSnapuserdRequired(); + enum class SnapshotDriver { + DM_SNAPSHOT, + DM_USER, + }; + private: FRIEND_TEST(SnapshotTest, CleanFirstStageMount); FRIEND_TEST(SnapshotTest, CreateSnapshot); @@ -456,6 +465,8 @@ class SnapshotManager final : public ISnapshotManager { }; static std::unique_ptr OpenFile(const std::string& file, int lock_flags); + SnapshotDriver GetSnapshotDriver(LockedFile* lock); + // Create a new snapshot record. This creates the backing COW store and // persists information needed to map the device. The device can be mapped // with MapSnapshot(). @@ -491,8 +502,8 @@ class SnapshotManager final : public ISnapshotManager { // Create a dm-user device for a given snapshot. bool MapDmUserCow(LockedFile* lock, const std::string& name, const std::string& cow_file, - const std::string& base_device, const std::chrono::milliseconds& timeout_ms, - std::string* path); + const std::string& base_device, const std::string& base_path_merge, + const std::chrono::milliseconds& timeout_ms, std::string* path); // Map the source device used for dm-user. bool MapSourceDevice(LockedFile* lock, const std::string& name, @@ -591,7 +602,8 @@ class SnapshotManager final : public ISnapshotManager { // Internal callback for when merging is complete. bool OnSnapshotMergeComplete(LockedFile* lock, const std::string& name, const SnapshotStatus& status); - bool CollapseSnapshotDevice(const std::string& name, const SnapshotStatus& status); + bool CollapseSnapshotDevice(LockedFile* lock, const std::string& name, + const SnapshotStatus& status); struct MergeResult { explicit MergeResult(UpdateState state, @@ -689,7 +701,10 @@ class SnapshotManager final : public ISnapshotManager { bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name); // Unmap a dm-user device through snapuserd. - bool UnmapDmUserDevice(const std::string& snapshot_name); + bool UnmapDmUserDevice(const std::string& dm_user_name); + + // Unmap a dm-user device for user space snapshots + bool UnmapUserspaceSnapshotDevice(LockedFile* lock, const std::string& snapshot_name); // If there isn't a previous update, return true. |needs_merge| is set to false. // If there is a previous update but the device has not boot into it, tries to cancel the @@ -778,6 +793,8 @@ class SnapshotManager final : public ISnapshotManager { // Helper of UpdateUsesCompression bool UpdateUsesCompression(LockedFile* lock); + // Helper of UpdateUsesUsersnapshots + bool UpdateUsesUserSnapshots(LockedFile* lock); // Wrapper around libdm, with diagnostics. bool DeleteDeviceIfExists(const std::string& name, @@ -792,6 +809,7 @@ class SnapshotManager final : public ISnapshotManager { std::function uevent_regen_callback_; std::unique_ptr snapuserd_client_; std::unique_ptr old_partition_metadata_; + std::optional is_snapshot_userspace_; }; } // namespace snapshot diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h index 74b78c59b..318e5259d 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h @@ -35,6 +35,7 @@ class SnapshotManagerStub : public ISnapshotManager { const std::function& before_cancel = {}) override; UpdateState GetUpdateState(double* progress = nullptr) override; bool UpdateUsesCompression() override; + bool UpdateUsesUserSnapshots() override; Return CreateUpdateSnapshots( const chromeos_update_engine::DeltaArchiveManifest& manifest) override; bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params, diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp index 3d8ae29df..f4584d282 100644 --- a/fs_mgr/libsnapshot/snapshot.cpp +++ b/fs_mgr/libsnapshot/snapshot.cpp @@ -95,6 +95,7 @@ std::unique_ptr SnapshotManager::New(IDeviceInfo* info) { if (!info) { info = new DeviceInfo(); } + return std::unique_ptr(new SnapshotManager(info)); } @@ -121,8 +122,34 @@ static std::string GetCowName(const std::string& snapshot_name) { return snapshot_name + "-cow"; } -static std::string GetDmUserCowName(const std::string& snapshot_name) { - return snapshot_name + "-user-cow"; +SnapshotManager::SnapshotDriver SnapshotManager::GetSnapshotDriver(LockedFile* lock) { + if (UpdateUsesUserSnapshots(lock)) { + return SnapshotManager::SnapshotDriver::DM_USER; + } else { + return SnapshotManager::SnapshotDriver::DM_SNAPSHOT; + } +} + +static std::string GetDmUserCowName(const std::string& snapshot_name, + SnapshotManager::SnapshotDriver driver) { + // dm-user block device will act as a snapshot device. We identify it with + // the same partition name so that when partitions can be mounted off + // dm-user. + + switch (driver) { + case SnapshotManager::SnapshotDriver::DM_USER: { + return snapshot_name; + } + + case SnapshotManager::SnapshotDriver::DM_SNAPSHOT: { + return snapshot_name + "-user-cow"; + } + + default: { + LOG(ERROR) << "Invalid snapshot driver"; + return ""; + } + } } static std::string GetCowImageDeviceName(const std::string& snapshot_name) { @@ -398,9 +425,33 @@ Return SnapshotManager::CreateCowImage(LockedFile* lock, const std::string& name bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name, const std::string& cow_file, const std::string& base_device, + const std::string& base_path_merge, const std::chrono::milliseconds& timeout_ms, std::string* path) { CHECK(lock); + if (UpdateUsesUserSnapshots(lock)) { + SnapshotStatus status; + if (!ReadSnapshotStatus(lock, name, &status)) { + LOG(ERROR) << "MapDmUserCow: ReadSnapshotStatus failed..."; + return false; + } + + if (status.state() == SnapshotState::NONE || + status.state() == SnapshotState::MERGE_COMPLETED) { + LOG(ERROR) << "Should not create a snapshot device for " << name + << " after merging has completed."; + return false; + } + + SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock); + if (update_status.state() == UpdateState::MergeCompleted || + update_status.state() == UpdateState::MergeNeedsReboot) { + LOG(ERROR) << "Should not create a snapshot device for " << name + << " after global merging has completed."; + return false; + } + } + // Use an extra decoration for first-stage init, so we can transition // to a new table entry in second-stage. std::string misc_name = name; @@ -412,18 +463,41 @@ bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name, return false; } - uint64_t base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device); - if (base_sectors == 0) { - LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd"; - return false; + uint64_t base_sectors = 0; + if (!UpdateUsesUserSnapshots(lock)) { + base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device); + if (base_sectors == 0) { + LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd"; + return false; + } + } else { + // For userspace snapshots, the size of the base device is taken as the + // size of the dm-user block device. Since there is no pseudo mapping + // created in the daemon, we no longer need to rely on the daemon for + // sizing the dm-user block device. + unique_fd fd(TEMP_FAILURE_RETRY(open(base_path_merge.c_str(), O_RDONLY | O_CLOEXEC))); + if (fd < 0) { + LOG(ERROR) << "Cannot open block device: " << base_path_merge; + return false; + } + + uint64_t dev_sz = get_block_device_size(fd.get()); + if (!dev_sz) { + LOG(ERROR) << "Failed to find block device size: " << base_path_merge; + return false; + } + + base_sectors = dev_sz >> 9; } DmTable table; table.Emplace(0, base_sectors, misc_name); if (!dm_.CreateDevice(name, table, path, timeout_ms)) { + LOG(ERROR) << " dm-user: CreateDevice failed... "; return false; } if (!WaitForDevice(*path, timeout_ms)) { + LOG(ERROR) << " dm-user: timeout: Failed to create block device for: " << name; return false; } @@ -432,6 +506,15 @@ bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name, return false; } + if (UpdateUsesUserSnapshots(lock)) { + // Now that the dm-user device is created, initialize the daemon and + // spin up the worker threads. + if (!snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device, base_path_merge)) { + LOG(ERROR) << "InitDmUserCow failed"; + return false; + } + } + return snapuserd_client_->AttachDmUser(misc_name); } @@ -698,13 +781,15 @@ bool SnapshotManager::InitiateMerge() { DmTargetSnapshot::Status initial_target_values = {}; for (const auto& snapshot : snapshots) { - DmTargetSnapshot::Status current_status; - if (!QuerySnapshotStatus(snapshot, nullptr, ¤t_status)) { - return false; + if (!UpdateUsesUserSnapshots(lock.get())) { + DmTargetSnapshot::Status current_status; + if (!QuerySnapshotStatus(snapshot, nullptr, ¤t_status)) { + return false; + } + initial_target_values.sectors_allocated += current_status.sectors_allocated; + initial_target_values.total_sectors += current_status.total_sectors; + initial_target_values.metadata_sectors += current_status.metadata_sectors; } - initial_target_values.sectors_allocated += current_status.sectors_allocated; - initial_target_values.total_sectors += current_status.total_sectors; - initial_target_values.metadata_sectors += current_status.metadata_sectors; SnapshotStatus snapshot_status; if (!ReadSnapshotStatus(lock.get(), snapshot, &snapshot_status)) { @@ -719,11 +804,14 @@ bool SnapshotManager::InitiateMerge() { SnapshotUpdateStatus initial_status = ReadSnapshotUpdateStatus(lock.get()); initial_status.set_state(UpdateState::Merging); - initial_status.set_sectors_allocated(initial_target_values.sectors_allocated); - initial_status.set_total_sectors(initial_target_values.total_sectors); - initial_status.set_metadata_sectors(initial_target_values.metadata_sectors); initial_status.set_compression_enabled(compression_enabled); + if (!UpdateUsesUserSnapshots(lock.get())) { + initial_status.set_sectors_allocated(initial_target_values.sectors_allocated); + initial_status.set_total_sectors(initial_target_values.total_sectors); + initial_status.set_metadata_sectors(initial_target_values.metadata_sectors); + } + // If any partitions shrunk, we need to merge them before we merge any other // partitions (see b/177935716). Otherwise, a merge from another partition // may overwrite the source block of a copy operation. @@ -777,20 +865,36 @@ MergeFailureCode SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const << " has unexpected state: " << SnapshotState_Name(status.state()); } - // After this, we return true because we technically did switch to a merge - // target. Everything else we do here is just informational. - if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) { - return code; + if (UpdateUsesUserSnapshots(lock)) { + if (EnsureSnapuserdConnected()) { + // This is the point where we inform the daemon to initiate/resume + // the merge + if (!snapuserd_client_->InitiateMerge(name)) { + return MergeFailureCode::UnknownTable; + } + } else { + LOG(ERROR) << "Failed to connect to snapuserd daemon to initiate merge"; + return MergeFailureCode::UnknownTable; + } + } else { + // After this, we return true because we technically did switch to a merge + // target. Everything else we do here is just informational. + if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) { + return code; + } } status.set_state(SnapshotState::MERGING); - DmTargetSnapshot::Status dm_status; - if (!QuerySnapshotStatus(name, nullptr, &dm_status)) { - LOG(ERROR) << "Could not query merge status for snapshot: " << name; + if (!UpdateUsesUserSnapshots(lock)) { + DmTargetSnapshot::Status dm_status; + if (!QuerySnapshotStatus(name, nullptr, &dm_status)) { + LOG(ERROR) << "Could not query merge status for snapshot: " << name; + } + status.set_sectors_allocated(dm_status.sectors_allocated); + status.set_metadata_sectors(dm_status.metadata_sectors); } - status.set_sectors_allocated(dm_status.sectors_allocated); - status.set_metadata_sectors(dm_status.metadata_sectors); + if (!WriteSnapshotStatus(lock, status)) { LOG(ERROR) << "Could not update status file for snapshot: " << name; } @@ -856,9 +960,15 @@ bool SnapshotManager::IsSnapshotDevice(const std::string& dm_name, TargetInfo* t return false; } auto type = DeviceMapper::GetTargetType(snap_target.spec); - if (type != "snapshot" && type != "snapshot-merge") { - return false; + + // If this is not a user-snapshot device then it should either + // be a dm-snapshot or dm-snapshot-merge target + if (type != "user") { + if (type != "snapshot" && type != "snapshot-merge") { + return false; + } } + if (target) { *target = std::move(snap_target); } @@ -1094,34 +1204,86 @@ auto SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string& DCHECK((current_metadata = ReadCurrentMetadata()) && GetMetadataPartitionState(*current_metadata, name) == MetadataPartitionState::Updated); - std::string target_type; - DmTargetSnapshot::Status status; - if (!QuerySnapshotStatus(name, &target_type, &status)) { - return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus); - } - if (target_type == "snapshot" && - DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE && - update_status.merge_phase() == MergePhase::FIRST_PHASE) { - // The snapshot is not being merged because it's in the wrong phase. - return MergeResult(UpdateState::None); - } - if (target_type != "snapshot-merge") { - // We can get here if we failed to rewrite the target type in - // InitiateMerge(). If we failed to create the target in first-stage - // init, boot would not succeed. - LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type; - return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget); + if (UpdateUsesUserSnapshots(lock)) { + std::string merge_status; + if (EnsureSnapuserdConnected()) { + // Query the snapshot status from the daemon + merge_status = snapuserd_client_->QuerySnapshotStatus(name); + } else { + MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus); + } + + if (merge_status == "snapshot-merge-failed") { + return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType); + } + + // This is the case when device reboots during merge. Once the device boots, + // snapuserd daemon will not resume merge immediately in first stage init. + // This is slightly different as compared to dm-snapshot-merge; In this + // case, metadata file will have "MERGING" state whereas the daemon will be + // waiting to resume the merge. Thus, we resume the merge at this point. + if (merge_status == "snapshot" && snapshot_status.state() == SnapshotState::MERGING) { + if (!snapuserd_client_->InitiateMerge(name)) { + return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType); + } + return MergeResult(UpdateState::Merging); + } + + if (merge_status == "snapshot" && + DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE && + update_status.merge_phase() == MergePhase::FIRST_PHASE) { + // The snapshot is not being merged because it's in the wrong phase. + return MergeResult(UpdateState::None); + } + + if (merge_status == "snapshot-merge") { + if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) { + LOG(ERROR) << "Snapshot " << name + << " is merging after being marked merge-complete."; + return MergeResult(UpdateState::MergeFailed, + MergeFailureCode::UnmergedSectorsAfterCompletion); + } + return MergeResult(UpdateState::Merging); + } + + if (merge_status != "snapshot-merge-complete") { + LOG(ERROR) << "Snapshot " << name << " has incorrect status: " << merge_status; + return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget); + } + } else { + // dm-snapshot in the kernel + std::string target_type; + DmTargetSnapshot::Status status; + if (!QuerySnapshotStatus(name, &target_type, &status)) { + return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus); + } + if (target_type == "snapshot" && + DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE && + update_status.merge_phase() == MergePhase::FIRST_PHASE) { + // The snapshot is not being merged because it's in the wrong phase. + return MergeResult(UpdateState::None); + } + if (target_type != "snapshot-merge") { + // We can get here if we failed to rewrite the target type in + // InitiateMerge(). If we failed to create the target in first-stage + // init, boot would not succeed. + LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type; + return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget); + } + + // These two values are equal when merging is complete. + if (status.sectors_allocated != status.metadata_sectors) { + if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) { + LOG(ERROR) << "Snapshot " << name + << " is merging after being marked merge-complete."; + return MergeResult(UpdateState::MergeFailed, + MergeFailureCode::UnmergedSectorsAfterCompletion); + } + return MergeResult(UpdateState::Merging); + } } - // These two values are equal when merging is complete. - if (status.sectors_allocated != status.metadata_sectors) { - if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) { - LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete."; - return MergeResult(UpdateState::MergeFailed, - MergeFailureCode::UnmergedSectorsAfterCompletion); - } - return MergeResult(UpdateState::Merging); - } + // Merge is complete at this point auto code = CheckMergeConsistency(lock, name, snapshot_status); if (code != MergeFailureCode::Ok) { @@ -1311,30 +1473,40 @@ void SnapshotManager::AcknowledgeMergeFailure(MergeFailureCode failure_code) { bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::string& name, const SnapshotStatus& status) { - if (IsSnapshotDevice(name)) { - // We are extra-cautious here, to avoid deleting the wrong table. - std::string target_type; - DmTargetSnapshot::Status dm_status; - if (!QuerySnapshotStatus(name, &target_type, &dm_status)) { - return false; + if (!UpdateUsesUserSnapshots(lock)) { + if (IsSnapshotDevice(name)) { + // We are extra-cautious here, to avoid deleting the wrong table. + std::string target_type; + DmTargetSnapshot::Status dm_status; + if (!QuerySnapshotStatus(name, &target_type, &dm_status)) { + return false; + } + if (target_type != "snapshot-merge") { + LOG(ERROR) << "Unexpected target type " << target_type + << " for snapshot device: " << name; + return false; + } + if (dm_status.sectors_allocated != dm_status.metadata_sectors) { + LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name; + return false; + } + if (!CollapseSnapshotDevice(lock, name, status)) { + LOG(ERROR) << "Unable to collapse snapshot: " << name; + return false; + } } - if (target_type != "snapshot-merge") { - LOG(ERROR) << "Unexpected target type " << target_type - << " for snapshot device: " << name; - return false; - } - if (dm_status.sectors_allocated != dm_status.metadata_sectors) { - LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name; - return false; - } - if (!CollapseSnapshotDevice(name, status)) { + } else { + // Just collapse the device - no need to query again as we just did + // prior to calling this function + if (!CollapseSnapshotDevice(lock, name, status)) { LOG(ERROR) << "Unable to collapse snapshot: " << name; return false; } - // Note that collapsing is implicitly an Unmap, so we don't need to - // unmap the snapshot. } + // Note that collapsing is implicitly an Unmap, so we don't need to + // unmap the snapshot. + if (!DeleteSnapshot(lock, name)) { LOG(ERROR) << "Could not delete snapshot: " << name; return false; @@ -1342,23 +1514,26 @@ bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::strin return true; } -bool SnapshotManager::CollapseSnapshotDevice(const std::string& name, +bool SnapshotManager::CollapseSnapshotDevice(LockedFile* lock, const std::string& name, const SnapshotStatus& status) { - // Verify we have a snapshot-merge device. - DeviceMapper::TargetInfo target; - if (!GetSingleTarget(name, TableQuery::Table, &target)) { - return false; - } - if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") { - // This should be impossible, it was checked earlier. - LOG(ERROR) << "Snapshot device has invalid target type: " << name; - return false; - } + if (!UpdateUsesUserSnapshots(lock)) { + // Verify we have a snapshot-merge device. + DeviceMapper::TargetInfo target; + if (!GetSingleTarget(name, TableQuery::Table, &target)) { + return false; + } + if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") { + // This should be impossible, it was checked earlier. + LOG(ERROR) << "Snapshot device has invalid target type: " << name; + return false; + } - std::string base_device, cow_device; - if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) { - LOG(ERROR) << "Could not parse snapshot device " << name << " parameters: " << target.data; - return false; + std::string base_device, cow_device; + if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) { + LOG(ERROR) << "Could not parse snapshot device " << name + << " parameters: " << target.data; + return false; + } } uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize; @@ -1386,14 +1561,32 @@ bool SnapshotManager::CollapseSnapshotDevice(const std::string& name, return false; } - // Attempt to delete the snapshot device if one still exists. Nothing - // should be depending on the device, and device-mapper should have - // flushed remaining I/O. We could in theory replace with dm-zero (or - // re-use the table above), but for now it's better to know why this - // would fail. - if (status.compression_enabled()) { - UnmapDmUserDevice(name); + if (!UpdateUsesUserSnapshots(lock)) { + // Attempt to delete the snapshot device if one still exists. Nothing + // should be depending on the device, and device-mapper should have + // flushed remaining I/O. We could in theory replace with dm-zero (or + // re-use the table above), but for now it's better to know why this + // would fail. + // + // Furthermore, we should not be trying to unmap for userspace snapshot + // as unmap will fail since dm-user itself was a snapshot device prior + // to switching of tables. Unmap will fail as the device will be mounted + // by system partitions + if (status.compression_enabled()) { + auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock)); + UnmapDmUserDevice(dm_user_name); + } } + + // We can't delete base device immediately as daemon holds a reference. + // Make sure we wait for all the worker threads to terminate and release + // the reference + if (UpdateUsesUserSnapshots(lock) && EnsureSnapuserdConnected()) { + if (!snapuserd_client_->WaitForDeviceDelete(name)) { + LOG(ERROR) << "Failed to wait for " << name << " control device to delete"; + } + } + auto base_name = GetBaseDeviceName(name); if (!DeleteDeviceIfExists(base_name)) { LOG(ERROR) << "Unable to delete base device for snapshot: " << base_name; @@ -1464,10 +1657,15 @@ bool SnapshotManager::PerformInitTransition(InitTransition transition, return false; } + if (UpdateUsesUserSnapshots(lock.get()) && transition == InitTransition::SELINUX_DETACH) { + snapuserd_argv->emplace_back("-user_snapshot"); + } + size_t num_cows = 0; size_t ok_cows = 0; for (const auto& snapshot : snapshots) { - std::string user_cow_name = GetDmUserCowName(snapshot); + std::string user_cow_name = GetDmUserCowName(snapshot, GetSnapshotDriver(lock.get())); + if (dm_.GetState(user_cow_name) == DmDeviceState::INVALID) { continue; } @@ -1513,6 +1711,12 @@ bool SnapshotManager::PerformInitTransition(InitTransition transition, continue; } + std::string base_path_merge; + if (!dm_.GetDmDevicePathByName(GetBaseDeviceName(snapshot), &base_path_merge)) { + LOG(ERROR) << "Could not get device path for " << GetSourceDeviceName(snapshot); + continue; + } + std::string cow_image_name = GetMappedCowDeviceName(snapshot, snapshot_status); std::string cow_image_device; @@ -1529,8 +1733,14 @@ bool SnapshotManager::PerformInitTransition(InitTransition transition, } if (transition == InitTransition::SELINUX_DETACH) { - auto message = misc_name + "," + cow_image_device + "," + source_device; - snapuserd_argv->emplace_back(std::move(message)); + if (!UpdateUsesUserSnapshots(lock.get())) { + auto message = misc_name + "," + cow_image_device + "," + source_device; + snapuserd_argv->emplace_back(std::move(message)); + } else { + auto message = misc_name + "," + cow_image_device + "," + source_device + "," + + base_path_merge; + snapuserd_argv->emplace_back(std::move(message)); + } // Do not attempt to connect to the new snapuserd yet, it hasn't // been started. We do however want to wait for the misc device @@ -1539,8 +1749,15 @@ bool SnapshotManager::PerformInitTransition(InitTransition transition, continue; } - uint64_t base_sectors = - snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device); + uint64_t base_sectors; + if (!UpdateUsesUserSnapshots(lock.get())) { + base_sectors = + snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device); + } else { + base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, + source_device, base_path_merge); + } + if (base_sectors == 0) { // Unrecoverable as metadata reads from cow device failed LOG(FATAL) << "Failed to retrieve base_sectors from Snapuserd"; @@ -1775,30 +1992,36 @@ UpdateState SnapshotManager::GetUpdateState(double* progress) { return state; } - // Sum all the snapshot states as if the system consists of a single huge - // snapshots device, then compute the merge completion percentage of that - // device. - std::vector snapshots; - if (!ListSnapshots(lock.get(), &snapshots)) { - LOG(ERROR) << "Could not list snapshots"; - return state; + if (!UpdateUsesUserSnapshots(lock.get())) { + // Sum all the snapshot states as if the system consists of a single huge + // snapshots device, then compute the merge completion percentage of that + // device. + std::vector snapshots; + if (!ListSnapshots(lock.get(), &snapshots)) { + LOG(ERROR) << "Could not list snapshots"; + return state; + } + + DmTargetSnapshot::Status fake_snapshots_status = {}; + for (const auto& snapshot : snapshots) { + DmTargetSnapshot::Status current_status; + + if (!IsSnapshotDevice(snapshot)) continue; + if (!QuerySnapshotStatus(snapshot, nullptr, ¤t_status)) continue; + + fake_snapshots_status.sectors_allocated += current_status.sectors_allocated; + fake_snapshots_status.total_sectors += current_status.total_sectors; + fake_snapshots_status.metadata_sectors += current_status.metadata_sectors; + } + + *progress = DmTargetSnapshot::MergePercent(fake_snapshots_status, + update_status.sectors_allocated()); + } else { + if (EnsureSnapuserdConnected()) { + *progress = snapuserd_client_->GetMergePercent(); + } } - DmTargetSnapshot::Status fake_snapshots_status = {}; - for (const auto& snapshot : snapshots) { - DmTargetSnapshot::Status current_status; - - if (!IsSnapshotDevice(snapshot)) continue; - if (!QuerySnapshotStatus(snapshot, nullptr, ¤t_status)) continue; - - fake_snapshots_status.sectors_allocated += current_status.sectors_allocated; - fake_snapshots_status.total_sectors += current_status.total_sectors; - fake_snapshots_status.metadata_sectors += current_status.metadata_sectors; - } - - *progress = DmTargetSnapshot::MergePercent(fake_snapshots_status, - update_status.sectors_allocated()); - return state; } @@ -1813,6 +2036,38 @@ bool SnapshotManager::UpdateUsesCompression(LockedFile* lock) { return update_status.compression_enabled(); } +bool SnapshotManager::UpdateUsesUserSnapshots() { + // This and the following function is constantly + // invoked during snapshot merge. We want to avoid + // constantly reading from disk. Hence, store this + // value in memory. + // + // Furthermore, this value in the disk is set + // only when OTA is applied and doesn't change + // during merge phase. Hence, once we know that + // the value is read from disk the very first time, + // it is safe to read successive checks from memory. + if (is_snapshot_userspace_.has_value()) { + return is_snapshot_userspace_.value(); + } + + auto lock = LockShared(); + if (!lock) return false; + + return UpdateUsesUserSnapshots(lock.get()); +} + +bool SnapshotManager::UpdateUsesUserSnapshots(LockedFile* lock) { + // See UpdateUsesUserSnapshots() + if (is_snapshot_userspace_.has_value()) { + return is_snapshot_userspace_.value(); + } + + SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock); + is_snapshot_userspace_ = update_status.userspace_snapshots(); + return is_snapshot_userspace_.value(); +} + bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector* snapshots, const std::string& suffix) { CHECK(lock); @@ -2040,6 +2295,16 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock, paths->target_device = base_path; } + auto remaining_time = GetRemainingTime(params.timeout_ms, begin); + if (remaining_time.count() < 0) { + return false; + } + + // Wait for the base device to appear + if (!WaitForDevice(base_path, remaining_time)) { + return false; + } + if (!live_snapshot_status.has_value()) { created_devices.Release(); return true; @@ -2053,7 +2318,7 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock, return false; } - auto remaining_time = GetRemainingTime(params.timeout_ms, begin); + remaining_time = GetRemainingTime(params.timeout_ms, begin); if (remaining_time.count() < 0) return false; std::string cow_name; @@ -2109,10 +2374,10 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock, return false; } - auto name = GetDmUserCowName(params.GetPartitionName()); + auto name = GetDmUserCowName(params.GetPartitionName(), GetSnapshotDriver(lock)); std::string new_cow_device; - if (!MapDmUserCow(lock, name, cow_path, source_device_path, remaining_time, + if (!MapDmUserCow(lock, name, cow_path, source_device_path, base_path, remaining_time, &new_cow_device)) { LOG(ERROR) << "Could not map dm-user device for partition " << params.GetPartitionName(); @@ -2126,21 +2391,37 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock, cow_device = new_cow_device; } - std::string path; - if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time, - &path)) { - LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName(); - return false; - } - // No need to add params.GetPartitionName() to created_devices since it is immediately released. + // For userspace snapshots, dm-user block device itself will act as a + // snapshot device. There is one subtle difference - MapSnapshot will create + // either snapshot target or snapshot-merge target based on the underlying + // state of the snapshot device. If snapshot-merge target is created, merge + // will immediately start in the kernel. + // + // This is no longer true with respect to userspace snapshots. When dm-user + // block device is created, we just have the snapshots ready but daemon in + // the user-space will not start the merge. We have to explicitly inform the + // daemon to resume the merge. Check ProcessUpdateState() call stack. + if (!UpdateUsesUserSnapshots(lock)) { + std::string path; + if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time, + &path)) { + LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName(); + return false; + } + // No need to add params.GetPartitionName() to created_devices since it is immediately + // released. - if (paths) { - paths->snapshot_device = path; + if (paths) { + paths->snapshot_device = path; + } + LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path; + } else { + LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " + << cow_device; } created_devices.Release(); - LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path; return true; } @@ -2148,8 +2429,10 @@ bool SnapshotManager::UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name) { CHECK(lock); - if (!UnmapSnapshot(lock, target_partition_name)) { - return false; + if (!UpdateUsesUserSnapshots(lock)) { + if (!UnmapSnapshot(lock, target_partition_name)) { + return false; + } } if (!UnmapCowDevices(lock, target_partition_name)) { @@ -2247,8 +2530,17 @@ bool SnapshotManager::UnmapCowDevices(LockedFile* lock, const std::string& name) CHECK(lock); if (!EnsureImageManager()) return false; - if (UpdateUsesCompression(lock) && !UnmapDmUserDevice(name)) { - return false; + if (UpdateUsesCompression(lock)) { + if (UpdateUsesUserSnapshots(lock)) { + if (!UnmapUserspaceSnapshotDevice(lock, name)) { + return false; + } + } else { + auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock)); + if (!UnmapDmUserDevice(dm_user_name)) { + return false; + } + } } if (!DeleteDeviceIfExists(GetCowName(name), 4000ms)) { @@ -2264,8 +2556,7 @@ bool SnapshotManager::UnmapCowDevices(LockedFile* lock, const std::string& name) return true; } -bool SnapshotManager::UnmapDmUserDevice(const std::string& snapshot_name) { - auto dm_user_name = GetDmUserCowName(snapshot_name); +bool SnapshotManager::UnmapDmUserDevice(const std::string& dm_user_name) { if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) { return true; } @@ -2291,6 +2582,46 @@ bool SnapshotManager::UnmapDmUserDevice(const std::string& snapshot_name) { return true; } +bool SnapshotManager::UnmapUserspaceSnapshotDevice(LockedFile* lock, + const std::string& snapshot_name) { + auto dm_user_name = GetDmUserCowName(snapshot_name, GetSnapshotDriver(lock)); + if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) { + return true; + } + + CHECK(lock); + + SnapshotStatus snapshot_status; + + if (!ReadSnapshotStatus(lock, snapshot_name, &snapshot_status)) { + return false; + } + // If the merge is complete, then we switch dm tables which is equivalent + // to unmap; hence, we can't be deleting the device + // as the table would be mounted off partitions and will fail. + if (snapshot_status.state() != SnapshotState::MERGE_COMPLETED) { + if (!DeleteDeviceIfExists(dm_user_name)) { + LOG(ERROR) << "Cannot unmap " << dm_user_name; + return false; + } + } + + if (EnsureSnapuserdConnected()) { + if (!snapuserd_client_->WaitForDeviceDelete(dm_user_name)) { + LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete"; + return false; + } + } + + // Ensure the control device is gone so we don't run into ABA problems. + auto control_device = "/dev/dm-user/" + dm_user_name; + if (!android::fs_mgr::WaitForFileDeleted(control_device, 10s)) { + LOG(ERROR) << "Timed out waiting for " << control_device << " to unlink"; + return false; + } + return true; +} + bool SnapshotManager::MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) { auto lock = LockExclusive(); if (!lock) return false; @@ -2527,6 +2858,7 @@ bool SnapshotManager::WriteUpdateState(LockedFile* lock, UpdateState state, status.set_compression_enabled(old_status.compression_enabled()); status.set_source_build_fingerprint(old_status.source_build_fingerprint()); status.set_merge_phase(old_status.merge_phase()); + status.set_userspace_snapshots(old_status.userspace_snapshots()); } return WriteSnapshotUpdateStatus(lock, status); } @@ -2844,6 +3176,43 @@ Return SnapshotManager::CreateUpdateSnapshots(const DeltaArchiveManifest& manife SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get()); status.set_state(update_state); status.set_compression_enabled(cow_creator.compression_enabled); + if (cow_creator.compression_enabled) { + if (!device()->IsTestDevice()) { + // Userspace snapshots is enabled only if compression is enabled + status.set_userspace_snapshots(IsUserspaceSnapshotsEnabled()); + if (IsUserspaceSnapshotsEnabled()) { + is_snapshot_userspace_ = true; + LOG(INFO) << "User-space snapshots enabled"; + } else { + is_snapshot_userspace_ = false; + LOG(INFO) << "User-space snapshots disabled"; + } + + // Terminate stale daemon if any + std::unique_ptr snapuserd_client = + SnapuserdClient::Connect(kSnapuserdSocket, 10s); + if (snapuserd_client) { + snapuserd_client->DetachSnapuserd(); + snapuserd_client->CloseConnection(); + snapuserd_client = nullptr; + } + + // Clear the cached client if any + if (snapuserd_client_) { + snapuserd_client_->CloseConnection(); + snapuserd_client_ = nullptr; + } + } else { + status.set_userspace_snapshots(!IsDmSnapshotTestingEnabled()); + if (IsDmSnapshotTestingEnabled()) { + is_snapshot_userspace_ = false; + LOG(INFO) << "User-space snapshots disabled for testing"; + } else { + is_snapshot_userspace_ = true; + LOG(INFO) << "User-space snapshots enabled for testing"; + } + } + } if (!WriteSnapshotUpdateStatus(lock.get(), status)) { LOG(ERROR) << "Unable to write new update state"; return Return::Error(); diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp index a8d5b8a1a..4af5367ac 100644 --- a/fs_mgr/libsnapshot/snapshot_stub.cpp +++ b/fs_mgr/libsnapshot/snapshot_stub.cpp @@ -121,6 +121,11 @@ bool SnapshotManagerStub::UpdateUsesCompression() { return false; } +bool SnapshotManagerStub::UpdateUsesUserSnapshots() { + LOG(ERROR) << __FUNCTION__ << " should never be called."; + return false; +} + class SnapshotMergeStatsStub : public ISnapshotMergeStats { bool Start() override { return false; } void set_state(android::snapshot::UpdateState, bool) override {} diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp index d78ba0a83..f1d76e74d 100644 --- a/fs_mgr/libsnapshot/snapshot_test.cpp +++ b/fs_mgr/libsnapshot/snapshot_test.cpp @@ -45,6 +45,8 @@ #include "partition_cow_creator.h" #include "utility.h" +#include + // Mock classes are not used. Header included to ensure mocked class definition aligns with the // class itself. #include @@ -272,7 +274,7 @@ class SnapshotTest : public ::testing::Test { AssertionResult DeleteSnapshotDevice(const std::string& snapshot) { AssertionResult res = AssertionSuccess(); if (!(res = DeleteDevice(snapshot))) return res; - if (!sm->UnmapDmUserDevice(snapshot)) { + if (!sm->UnmapDmUserDevice(snapshot + "-user-cow")) { return AssertionFailure() << "Cannot delete dm-user device for " << snapshot; } if (!(res = DeleteDevice(snapshot + "-inner"))) return res; @@ -2559,5 +2561,15 @@ void SnapshotTestEnvironment::TearDown() { int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); ::testing::AddGlobalTestEnvironment(new ::android::snapshot::SnapshotTestEnvironment()); - return RUN_ALL_TESTS(); + + android::base::SetProperty("ctl.stop", "snapuserd"); + + if (!android::base::SetProperty("snapuserd.test.dm.snapshots", "1")) { + return testing::AssertionFailure() + << "Failed to disable property: virtual_ab.userspace.snapshots.enabled"; + } + + int ret = RUN_ALL_TESTS(); + android::base::SetProperty("snapuserd.test.dm.snapshots", "0"); + return ret; } diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h index 6ed55af2d..cebda1ccb 100644 --- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h +++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h @@ -63,7 +63,8 @@ class SnapuserdClient { // The misc_name must be the "misc_name" given to dm-user in step 2. // uint64_t InitDmUserCow(const std::string& misc_name, const std::string& cow_device, - const std::string& backing_device); + const std::string& backing_device, + const std::string& base_path_merge = ""); bool AttachDmUser(const std::string& misc_name); // Wait for snapuserd to disassociate with a dm-user control device. This diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp index e345269ae..7b1c7a3a6 100644 --- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp +++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp @@ -195,8 +195,16 @@ bool SnapuserdClient::AttachDmUser(const std::string& misc_name) { } uint64_t SnapuserdClient::InitDmUserCow(const std::string& misc_name, const std::string& cow_device, - const std::string& backing_device) { - std::vector parts = {"init", misc_name, cow_device, backing_device}; + const std::string& backing_device, + const std::string& base_path_merge) { + std::vector parts; + + if (base_path_merge.empty()) { + parts = {"init", misc_name, cow_device, backing_device}; + } else { + // For userspace snapshots + parts = {"init", misc_name, cow_device, backing_device, base_path_merge}; + } std::string msg = android::base::Join(parts, ","); if (!Sendmsg(msg)) { LOG(ERROR) << "Failed to send message " << msg << " to snapuserd daemon"; diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp index 912884fd3..ddb1f7999 100644 --- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp +++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp @@ -14,25 +14,95 @@ * limitations under the License. */ -#include "snapuserd_daemon.h" - #include +#include #include #include #include +#include "snapuserd_daemon.h" + DEFINE_string(socket, android::snapshot::kSnapuserdSocket, "Named socket or socket path."); DEFINE_bool(no_socket, false, "If true, no socket is used. Each additional argument is an INIT message."); DEFINE_bool(socket_handoff, false, "If true, perform a socket hand-off with an existing snapuserd instance, then exit."); +DEFINE_bool(user_snapshot, false, "If true, user-space snapshots are used"); namespace android { namespace snapshot { -bool Daemon::StartServer(int argc, char** argv) { +bool Daemon::IsUserspaceSnapshotsEnabled() { + return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false); +} + +bool Daemon::IsDmSnapshotTestingEnabled() { + return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false); +} + +bool Daemon::StartDaemon(int argc, char** argv) { int arg_start = gflags::ParseCommandLineFlags(&argc, &argv, true); + // Daemon launched from first stage init and during selinux transition + // will have the command line "-user_snapshot" flag set if the user-space + // snapshots are enabled. + // + // Daemon launched as a init service during "socket-handoff" and when OTA + // is applied will check for the property. This is ok as the system + // properties are valid at this point. We can't do this during first + // stage init and hence use the command line flags to get the information. + if (!IsDmSnapshotTestingEnabled() && (FLAGS_user_snapshot || IsUserspaceSnapshotsEnabled())) { + LOG(INFO) << "Starting daemon for user-space snapshots....."; + return StartServerForUserspaceSnapshots(arg_start, argc, argv); + } else { + LOG(INFO) << "Starting daemon for dm-snapshots....."; + return StartServerForDmSnapshot(arg_start, argc, argv); + } +} + +bool Daemon::StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv) { + sigfillset(&signal_mask_); + sigdelset(&signal_mask_, SIGINT); + sigdelset(&signal_mask_, SIGTERM); + sigdelset(&signal_mask_, SIGUSR1); + + // Masking signals here ensure that after this point, we won't handle INT/TERM + // until after we call into ppoll() + signal(SIGINT, Daemon::SignalHandler); + signal(SIGTERM, Daemon::SignalHandler); + signal(SIGPIPE, Daemon::SignalHandler); + signal(SIGUSR1, Daemon::SignalHandler); + + MaskAllSignalsExceptIntAndTerm(); + + if (FLAGS_socket_handoff) { + return user_server_.RunForSocketHandoff(); + } + if (!FLAGS_no_socket) { + if (!user_server_.Start(FLAGS_socket)) { + return false; + } + return user_server_.Run(); + } + + for (int i = arg_start; i < argc; i++) { + auto parts = android::base::Split(argv[i], ","); + if (parts.size() != 4) { + LOG(ERROR) << "Malformed message, expected three sub-arguments."; + return false; + } + auto handler = user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3]); + if (!handler || !user_server_.StartHandler(handler)) { + return false; + } + } + + // Skip the accept() call to avoid spurious log spam. The server will still + // run until all handlers have completed. + return user_server_.WaitForSocket(); +} + +bool Daemon::StartServerForDmSnapshot(int arg_start, int argc, char** argv) { sigfillset(&signal_mask_); sigdelset(&signal_mask_, SIGINT); sigdelset(&signal_mask_, SIGTERM); @@ -95,11 +165,19 @@ void Daemon::MaskAllSignals() { } void Daemon::Interrupt() { - server_.Interrupt(); + if (IsUserspaceSnapshotsEnabled()) { + user_server_.Interrupt(); + } else { + server_.Interrupt(); + } } void Daemon::ReceivedSocketSignal() { - server_.ReceivedSocketSignal(); + if (IsUserspaceSnapshotsEnabled()) { + user_server_.ReceivedSocketSignal(); + } else { + server_.ReceivedSocketSignal(); + } } void Daemon::SignalHandler(int signal) { @@ -133,9 +211,10 @@ int main(int argc, char** argv) { android::snapshot::Daemon& daemon = android::snapshot::Daemon::Instance(); - if (!daemon.StartServer(argc, argv)) { - LOG(ERROR) << "Snapuserd daemon failed to start."; + if (!daemon.StartDaemon(argc, argv)) { + LOG(ERROR) << "Snapuserd daemon failed to start"; exit(EXIT_FAILURE); } + return 0; } diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h index fbf57d943..cf3b917e5 100644 --- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h +++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h @@ -20,6 +20,7 @@ #include #include "dm-snapshot-merge/snapuserd_server.h" +#include "user-space-merge/snapuserd_server.h" namespace android { namespace snapshot { @@ -35,9 +36,13 @@ class Daemon { return instance; } - bool StartServer(int argc, char** argv); + bool StartServerForDmSnapshot(int arg_start, int argc, char** argv); + bool StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv); void Interrupt(); void ReceivedSocketSignal(); + bool IsUserspaceSnapshotsEnabled(); + bool IsDmSnapshotTestingEnabled(); + bool StartDaemon(int argc, char** argv); private: // Signal mask used with ppoll() @@ -47,6 +52,7 @@ class Daemon { void operator=(Daemon const&) = delete; SnapuserdServer server_; + UserSnapshotServer user_server_; void MaskAllSignalsExceptIntAndTerm(); void MaskAllSignals(); static void SignalHandler(int signal); diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp index 57e47e7ed..95d95cdb8 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp @@ -35,7 +35,7 @@ SnapshotHandler::SnapshotHandler(std::string misc_name, std::string cow_device, } bool SnapshotHandler::InitializeWorkers() { - for (int i = 0; i < NUM_THREADS_PER_PARTITION; i++) { + for (int i = 0; i < kNumWorkerThreads; i++) { std::unique_ptr wt = std::make_unique(cow_device_, backing_store_device_, control_device_, misc_name_, base_path_merge_, GetSharedPtr()); diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h index 13b56facb..195331691 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h @@ -48,10 +48,10 @@ namespace snapshot { using android::base::unique_fd; using namespace std::chrono_literals; -static constexpr size_t PAYLOAD_SIZE = (1UL << 20); -static_assert(PAYLOAD_SIZE >= BLOCK_SZ); +static constexpr size_t PAYLOAD_BUFFER_SZ = (1UL << 20); +static_assert(PAYLOAD_BUFFER_SZ >= BLOCK_SZ); -static constexpr int NUM_THREADS_PER_PARTITION = 1; +static constexpr int kNumWorkerThreads = 4; #define SNAP_LOG(level) LOG(level) << misc_name_ << ": " #define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": " diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp index bfbacf92e..1e300d2d4 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp @@ -231,8 +231,8 @@ void Worker::InitializeBufsink() { // Allocate the buffer which is used to communicate between // daemon and dm-user. The buffer comprises of header and a fixed payload. // If the dm-user requests a big IO, the IO will be broken into chunks - // of PAYLOAD_SIZE. - size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE; + // of PAYLOAD_BUFFER_SZ. + size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_BUFFER_SZ; bufsink_.Initialize(buf_size); } @@ -326,7 +326,7 @@ bool Worker::ReadAlignedSector(sector_t sector, size_t sz, bool header_response) do { // Process 1MB payload at a time - size_t read_size = std::min(PAYLOAD_SIZE, remaining_size); + size_t read_size = std::min(PAYLOAD_BUFFER_SZ, remaining_size); header->type = DM_USER_RESP_SUCCESS; size_t total_bytes_read = 0; diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp index 47fc7db50..fa055b730 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp @@ -81,11 +81,11 @@ bool Worker::MergeReplaceZeroOps(const std::unique_ptr& cowop_iter) // Why 2048 ops ? We can probably increase this to bigger value but just // need to ensure that merge makes forward progress if there are // crashes repeatedly which is highly unlikely. - int total_ops_merged_per_commit = (PAYLOAD_SIZE / BLOCK_SZ) * 8; + int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 8; int num_ops_merged = 0; while (!cowop_iter->Done()) { - int num_ops = PAYLOAD_SIZE / BLOCK_SZ; + int num_ops = PAYLOAD_BUFFER_SZ / BLOCK_SZ; std::vector replace_zero_vec; uint64_t source_offset; @@ -292,6 +292,7 @@ bool Worker::RunMergeThread() { if (!Init()) { SNAP_LOG(ERROR) << "Merge thread initialization failed..."; + snapuserd_->MergeFailed(); return false; } diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp index 0bcf26e78..40e7242cd 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp @@ -429,7 +429,7 @@ void ReadAhead::InitializeBuffer() { static_cast((char*)mapped_addr + snapuserd_->GetBufferMetadataOffset()); read_ahead_buffer_ = static_cast((char*)mapped_addr + snapuserd_->GetBufferDataOffset()); // For xor ops - bufsink_.Initialize(PAYLOAD_SIZE); + bufsink_.Initialize(PAYLOAD_BUFFER_SZ); } } // namespace snapshot diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp index a4fd5a035..a79e3e13a 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp @@ -44,7 +44,7 @@ using namespace std::string_literals; using android::base::borrowed_fd; using android::base::unique_fd; -DaemonOps SnapuserServer::Resolveop(std::string& input) { +DaemonOps UserSnapshotServer::Resolveop(std::string& input) { if (input == "init") return DaemonOps::INIT; if (input == "start") return DaemonOps::START; if (input == "stop") return DaemonOps::STOP; @@ -59,14 +59,14 @@ DaemonOps SnapuserServer::Resolveop(std::string& input) { return DaemonOps::INVALID; } -SnapuserServer::~SnapuserServer() { +UserSnapshotServer::~UserSnapshotServer() { // Close any client sockets that were added via AcceptClient(). for (size_t i = 1; i < watched_fds_.size(); i++) { close(watched_fds_[i].fd); } } -std::string SnapuserServer::GetDaemonStatus() { +std::string UserSnapshotServer::GetDaemonStatus() { std::string msg = ""; if (IsTerminating()) @@ -77,8 +77,8 @@ std::string SnapuserServer::GetDaemonStatus() { return msg; } -void SnapuserServer::Parsemsg(std::string const& msg, const char delim, - std::vector& out) { +void UserSnapshotServer::Parsemsg(std::string const& msg, const char delim, + std::vector& out) { std::stringstream ss(msg); std::string s; @@ -87,15 +87,15 @@ void SnapuserServer::Parsemsg(std::string const& msg, const char delim, } } -void SnapuserServer::ShutdownThreads() { +void UserSnapshotServer::ShutdownThreads() { terminating_ = true; JoinAllThreads(); } -DmUserHandler::DmUserHandler(std::shared_ptr snapuserd) +UserSnapshotDmUserHandler::UserSnapshotDmUserHandler(std::shared_ptr snapuserd) : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {} -bool SnapuserServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) { +bool UserSnapshotServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) { ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL)); if (ret < 0) { PLOG(ERROR) << "Snapuserd:server: send() failed"; @@ -109,8 +109,8 @@ bool SnapuserServer::Sendmsg(android::base::borrowed_fd fd, const std::string& m return true; } -bool SnapuserServer::Recv(android::base::borrowed_fd fd, std::string* data) { - char msg[MAX_PACKET_SIZE]; +bool UserSnapshotServer::Recv(android::base::borrowed_fd fd, std::string* data) { + char msg[kMaxPacketSize]; ssize_t rv = TEMP_FAILURE_RETRY(recv(fd.get(), msg, sizeof(msg), 0)); if (rv < 0) { PLOG(ERROR) << "recv failed"; @@ -120,7 +120,7 @@ bool SnapuserServer::Recv(android::base::borrowed_fd fd, std::string* data) { return true; } -bool SnapuserServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) { +bool UserSnapshotServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) { const char delim = ','; std::vector out; @@ -290,7 +290,7 @@ bool SnapuserServer::Receivemsg(android::base::borrowed_fd fd, const std::string } } -void SnapuserServer::RunThread(std::shared_ptr handler) { +void UserSnapshotServer::RunThread(std::shared_ptr handler) { LOG(INFO) << "Entering thread for handler: " << handler->misc_name(); handler->snapuserd()->SetSocketPresent(is_socket_present_); @@ -337,7 +337,7 @@ void SnapuserServer::RunThread(std::shared_ptr handler) { } } -bool SnapuserServer::Start(const std::string& socketname) { +bool UserSnapshotServer::Start(const std::string& socketname) { bool start_listening = true; sockfd_.reset(android_get_control_socket(socketname.c_str())); @@ -353,7 +353,7 @@ bool SnapuserServer::Start(const std::string& socketname) { return StartWithSocket(start_listening); } -bool SnapuserServer::StartWithSocket(bool start_listening) { +bool UserSnapshotServer::StartWithSocket(bool start_listening) { if (start_listening && listen(sockfd_.get(), 4) < 0) { PLOG(ERROR) << "listen socket failed"; return false; @@ -374,7 +374,7 @@ bool SnapuserServer::StartWithSocket(bool start_listening) { return true; } -bool SnapuserServer::Run() { +bool UserSnapshotServer::Run() { LOG(INFO) << "Now listening on snapuserd socket"; while (!IsTerminating()) { @@ -406,9 +406,9 @@ bool SnapuserServer::Run() { return true; } -void SnapuserServer::JoinAllThreads() { +void UserSnapshotServer::JoinAllThreads() { // Acquire the thread list within the lock. - std::vector> dm_users; + std::vector> dm_users; { std::lock_guard guard(lock_); dm_users = std::move(dm_users_); @@ -421,14 +421,14 @@ void SnapuserServer::JoinAllThreads() { } } -void SnapuserServer::AddWatchedFd(android::base::borrowed_fd fd, int events) { +void UserSnapshotServer::AddWatchedFd(android::base::borrowed_fd fd, int events) { struct pollfd p = {}; p.fd = fd.get(); p.events = events; watched_fds_.emplace_back(std::move(p)); } -void SnapuserServer::AcceptClient() { +void UserSnapshotServer::AcceptClient() { int fd = TEMP_FAILURE_RETRY(accept4(sockfd_.get(), nullptr, nullptr, SOCK_CLOEXEC)); if (fd < 0) { PLOG(ERROR) << "accept4 failed"; @@ -438,7 +438,7 @@ void SnapuserServer::AcceptClient() { AddWatchedFd(fd, POLLIN); } -bool SnapuserServer::HandleClient(android::base::borrowed_fd fd, int revents) { +bool UserSnapshotServer::HandleClient(android::base::borrowed_fd fd, int revents) { if (revents & POLLHUP) { LOG(DEBUG) << "Snapuserd client disconnected"; return false; @@ -455,16 +455,15 @@ bool SnapuserServer::HandleClient(android::base::borrowed_fd fd, int revents) { return true; } -void SnapuserServer::Interrupt() { +void UserSnapshotServer::Interrupt() { // Force close the socket so poll() fails. sockfd_ = {}; SetTerminating(); } -std::shared_ptr SnapuserServer::AddHandler(const std::string& misc_name, - const std::string& cow_device_path, - const std::string& backing_device, - const std::string& base_path_merge) { +std::shared_ptr UserSnapshotServer::AddHandler( + const std::string& misc_name, const std::string& cow_device_path, + const std::string& backing_device, const std::string& base_path_merge) { auto snapuserd = std::make_shared(misc_name, cow_device_path, backing_device, base_path_merge); if (!snapuserd->InitCowDevice()) { @@ -477,7 +476,7 @@ std::shared_ptr SnapuserServer::AddHandler(const std::string& mis return nullptr; } - auto handler = std::make_shared(snapuserd); + auto handler = std::make_shared(snapuserd); { std::lock_guard lock(lock_); if (FindHandler(&lock, misc_name) != dm_users_.end()) { @@ -489,7 +488,7 @@ std::shared_ptr SnapuserServer::AddHandler(const std::string& mis return handler; } -bool SnapuserServer::StartHandler(const std::shared_ptr& handler) { +bool UserSnapshotServer::StartHandler(const std::shared_ptr& handler) { if (handler->snapuserd()->IsAttached()) { LOG(ERROR) << "Handler already attached"; return false; @@ -497,11 +496,11 @@ bool SnapuserServer::StartHandler(const std::shared_ptr& handler) handler->snapuserd()->AttachControlDevice(); - handler->thread() = std::thread(std::bind(&SnapuserServer::RunThread, this, handler)); + handler->thread() = std::thread(std::bind(&UserSnapshotServer::RunThread, this, handler)); return true; } -bool SnapuserServer::StartMerge(const std::shared_ptr& handler) { +bool UserSnapshotServer::StartMerge(const std::shared_ptr& handler) { if (!handler->snapuserd()->IsAttached()) { LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started"; return false; @@ -511,8 +510,8 @@ bool SnapuserServer::StartMerge(const std::shared_ptr& handler) { return true; } -auto SnapuserServer::FindHandler(std::lock_guard* proof_of_lock, - const std::string& misc_name) -> HandlerList::iterator { +auto UserSnapshotServer::FindHandler(std::lock_guard* proof_of_lock, + const std::string& misc_name) -> HandlerList::iterator { CHECK(proof_of_lock); for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) { @@ -523,7 +522,7 @@ auto SnapuserServer::FindHandler(std::lock_guard* proof_of_lock, return dm_users_.end(); } -void SnapuserServer::TerminateMergeThreads(std::lock_guard* proof_of_lock) { +void UserSnapshotServer::TerminateMergeThreads(std::lock_guard* proof_of_lock) { CHECK(proof_of_lock); for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) { @@ -533,11 +532,12 @@ void SnapuserServer::TerminateMergeThreads(std::lock_guard* proof_of } } -std::string SnapuserServer::GetMergeStatus(const std::shared_ptr& handler) { +std::string UserSnapshotServer::GetMergeStatus( + const std::shared_ptr& handler) { return handler->snapuserd()->GetMergeStatus(); } -double SnapuserServer::GetMergePercentage(std::lock_guard* proof_of_lock) { +double UserSnapshotServer::GetMergePercentage(std::lock_guard* proof_of_lock) { CHECK(proof_of_lock); double percentage = 0.0; int n = 0; @@ -567,8 +567,8 @@ double SnapuserServer::GetMergePercentage(std::lock_guard* proof_of_ return percentage; } -bool SnapuserServer::RemoveAndJoinHandler(const std::string& misc_name) { - std::shared_ptr handler; +bool UserSnapshotServer::RemoveAndJoinHandler(const std::string& misc_name) { + std::shared_ptr handler; { std::lock_guard lock(lock_); @@ -588,7 +588,7 @@ bool SnapuserServer::RemoveAndJoinHandler(const std::string& misc_name) { return true; } -bool SnapuserServer::WaitForSocket() { +bool UserSnapshotServer::WaitForSocket() { auto scope_guard = android::base::make_scope_guard([this]() -> void { JoinAllThreads(); }); auto socket_path = ANDROID_SOCKET_DIR "/"s + kSnapuserdSocketProxy; @@ -642,7 +642,7 @@ bool SnapuserServer::WaitForSocket() { return Run(); } -bool SnapuserServer::RunForSocketHandoff() { +bool UserSnapshotServer::RunForSocketHandoff() { unique_fd proxy_fd(android_get_control_socket(kSnapuserdSocketProxy)); if (proxy_fd < 0) { PLOG(FATAL) << "Proxy could not get android control socket " << kSnapuserdSocketProxy; diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h index e93621ca2..c645456bc 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h @@ -33,7 +33,7 @@ namespace android { namespace snapshot { -static constexpr uint32_t MAX_PACKET_SIZE = 512; +static constexpr uint32_t kMaxPacketSize = 512; enum class DaemonOps { INIT, @@ -49,9 +49,9 @@ enum class DaemonOps { INVALID, }; -class DmUserHandler { +class UserSnapshotDmUserHandler { public: - explicit DmUserHandler(std::shared_ptr snapuserd); + explicit UserSnapshotDmUserHandler(std::shared_ptr snapuserd); void FreeResources() { // Each worker thread holds a reference to snapuserd. @@ -76,7 +76,7 @@ class DmUserHandler { bool thread_terminated_ = false; }; -class SnapuserServer { +class UserSnapshotServer { private: android::base::unique_fd sockfd_; bool terminating_; @@ -87,7 +87,7 @@ class SnapuserServer { std::mutex lock_; - using HandlerList = std::vector>; + using HandlerList = std::vector>; HandlerList dm_users_; void AddWatchedFd(android::base::borrowed_fd fd, int events); @@ -105,11 +105,11 @@ class SnapuserServer { bool IsTerminating() { return terminating_; } - void RunThread(std::shared_ptr handler); + void RunThread(std::shared_ptr handler); void JoinAllThreads(); bool StartWithSocket(bool start_listening); - // Find a DmUserHandler within a lock. + // Find a UserSnapshotDmUserHandler within a lock. HandlerList::iterator FindHandler(std::lock_guard* proof_of_lock, const std::string& misc_name); @@ -117,8 +117,8 @@ class SnapuserServer { void TerminateMergeThreads(std::lock_guard* proof_of_lock); public: - SnapuserServer() { terminating_ = false; } - ~SnapuserServer(); + UserSnapshotServer() { terminating_ = false; } + ~UserSnapshotServer(); bool Start(const std::string& socketname); bool Run(); @@ -126,13 +126,13 @@ class SnapuserServer { bool RunForSocketHandoff(); bool WaitForSocket(); - std::shared_ptr AddHandler(const std::string& misc_name, - const std::string& cow_device_path, - const std::string& backing_device, - const std::string& base_path_merge); - bool StartHandler(const std::shared_ptr& handler); - bool StartMerge(const std::shared_ptr& handler); - std::string GetMergeStatus(const std::shared_ptr& handler); + std::shared_ptr AddHandler(const std::string& misc_name, + const std::string& cow_device_path, + const std::string& backing_device, + const std::string& base_path_merge); + bool StartHandler(const std::shared_ptr& handler); + bool StartMerge(const std::shared_ptr& handler); + std::string GetMergeStatus(const std::shared_ptr& handler); void SetTerminating() { terminating_ = true; } void ReceivedSocketSignal() { received_socket_signal_ = true; } diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp index 6c91fde6b..6dec1e2e0 100644 --- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp @@ -570,7 +570,6 @@ void SnapshotHandler::NotifyIOCompletion(uint64_t new_block) { { std::unique_lock lock(blk_state->m_lock); - CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_PENDING); blk_state->num_ios_in_progress -= 1; if (blk_state->num_ios_in_progress == 0) { pending_ios = false; diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp index 4a2af1c10..89d614599 100644 --- a/fs_mgr/libsnapshot/utility.cpp +++ b/fs_mgr/libsnapshot/utility.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -187,6 +188,10 @@ bool IsCompressionEnabled() { return android::base::GetBoolProperty("ro.virtual_ab.compression.enabled", false); } +bool IsUserspaceSnapshotsEnabled() { + return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false); +} + std::string GetOtherPartitionName(const std::string& name) { auto suffix = android::fs_mgr::GetPartitionSlotSuffix(name); CHECK(suffix == "_a" || suffix == "_b"); @@ -195,5 +200,9 @@ std::string GetOtherPartitionName(const std::string& name) { return name.substr(0, name.size() - suffix.size()) + other_suffix; } +bool IsDmSnapshotTestingEnabled() { + return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false); +} + } // namespace snapshot } // namespace android diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h index e97afed22..a032b6869 100644 --- a/fs_mgr/libsnapshot/utility.h +++ b/fs_mgr/libsnapshot/utility.h @@ -131,8 +131,11 @@ void AppendExtent(google::protobuf::RepeatedPtrFieldIsSnapuserdRequired(); if (use_snapuserd_) { - LaunchFirstStageSnapuserd(); + if (sm->UpdateUsesUserSnapshots()) { + LaunchFirstStageSnapuserd(SnapshotDriver::DM_USER); + } else { + LaunchFirstStageSnapuserd(SnapshotDriver::DM_SNAPSHOT); + } } sm->SetUeventRegenCallback([this](const std::string& device) -> bool { diff --git a/init/snapuserd_transition.cpp b/init/snapuserd_transition.cpp index b8c2fd2f3..e11510ee8 100644 --- a/init/snapuserd_transition.cpp +++ b/init/snapuserd_transition.cpp @@ -58,7 +58,7 @@ static constexpr char kSnapuserdFirstStageInfoVar[] = "FIRST_STAGE_SNAPUSERD_INF static constexpr char kSnapuserdLabel[] = "u:object_r:snapuserd_exec:s0"; static constexpr char kSnapuserdSocketLabel[] = "u:object_r:snapuserd_socket:s0"; -void LaunchFirstStageSnapuserd() { +void LaunchFirstStageSnapuserd(SnapshotDriver driver) { SocketDescriptor socket_desc; socket_desc.name = android::snapshot::kSnapuserdSocket; socket_desc.type = SOCK_STREAM; @@ -80,12 +80,23 @@ void LaunchFirstStageSnapuserd() { } if (pid == 0) { socket->Publish(); - char arg0[] = "/system/bin/snapuserd"; - char* const argv[] = {arg0, nullptr}; - if (execv(arg0, argv) < 0) { - PLOG(FATAL) << "Cannot launch snapuserd; execv failed"; + + if (driver == SnapshotDriver::DM_USER) { + char arg0[] = "/system/bin/snapuserd"; + char arg1[] = "-user_snapshot"; + char* const argv[] = {arg0, arg1, nullptr}; + if (execv(arg0, argv) < 0) { + PLOG(FATAL) << "Cannot launch snapuserd; execv failed"; + } + _exit(127); + } else { + char arg0[] = "/system/bin/snapuserd"; + char* const argv[] = {arg0, nullptr}; + if (execv(arg0, argv) < 0) { + PLOG(FATAL) << "Cannot launch snapuserd; execv failed"; + } + _exit(127); } - _exit(127); } auto client = SnapuserdClient::Connect(android::snapshot::kSnapuserdSocket, 10s); diff --git a/init/snapuserd_transition.h b/init/snapuserd_transition.h index 62aee83f6..be22afd20 100644 --- a/init/snapuserd_transition.h +++ b/init/snapuserd_transition.h @@ -29,8 +29,13 @@ namespace android { namespace init { +enum class SnapshotDriver { + DM_SNAPSHOT, + DM_USER, +}; + // Fork and exec a new copy of snapuserd. -void LaunchFirstStageSnapuserd(); +void LaunchFirstStageSnapuserd(SnapshotDriver driver); class SnapuserdSelinuxHelper final { using SnapshotManager = android::snapshot::SnapshotManager; From 6e35cb89ffeb6cf34e104e3aff331080248f7a02 Mon Sep 17 00:00:00 2001 From: Akilesh Kailash Date: Thu, 18 Nov 2021 23:00:29 +0000 Subject: [PATCH 2/3] libsnapshot: Add vts_userspace_snapshot_test Toggle virtual_ab.userspace.snapshots.enabled to test both: 1: vts_libsnapshot_test - testing kernel dm-snapshot 2: vts_userspace_snapshot_test - testing user-space snapshot Bug: 193863443 Test: vts_userspace_snapshot_test Signed-off-by: Akilesh Kailash Change-Id: I873e7476d71feb9a2e12054e968443bd22ee817c --- fs_mgr/libsnapshot/Android.bp | 51 + .../libsnapshot/userspace_snapshot_test.cpp | 2519 +++++++++++++++++ 2 files changed, 2570 insertions(+) create mode 100644 fs_mgr/libsnapshot/userspace_snapshot_test.cpp diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp index 5ab2ce285..6b0293ada 100644 --- a/fs_mgr/libsnapshot/Android.bp +++ b/fs_mgr/libsnapshot/Android.bp @@ -258,11 +258,62 @@ cc_defaults { require_root: true, } +cc_defaults { + name: "userspace_snapshot_test_defaults", + defaults: ["libsnapshot_defaults"], + srcs: [ + "partition_cow_creator_test.cpp", + "snapshot_metadata_updater_test.cpp", + "snapshot_reader_test.cpp", + "userspace_snapshot_test.cpp", + "snapshot_writer_test.cpp", + ], + shared_libs: [ + "libbinder", + "libcrypto", + "libhidlbase", + "libprotobuf-cpp-lite", + "libutils", + "libz", + ], + static_libs: [ + "android.hardware.boot@1.0", + "android.hardware.boot@1.1", + "libbrotli", + "libc++fs", + "libfs_mgr_binder", + "libgsi", + "libgmock", + "liblp", + "libsnapshot", + "libsnapshot_cow", + "libsnapshot_test_helpers", + "libsparse", + ], + header_libs: [ + "libstorage_literals_headers", + ], + test_suites: [ + "vts", + "device-tests" + ], + test_options: { + min_shipping_api_level: 29, + }, + auto_gen_config: true, + require_root: true, +} + cc_test { name: "vts_libsnapshot_test", defaults: ["libsnapshot_test_defaults"], } +cc_test { + name: "vts_userspace_snapshot_test", + defaults: ["userspace_snapshot_test_defaults"], +} + cc_binary { name: "snapshotctl", srcs: [ diff --git a/fs_mgr/libsnapshot/userspace_snapshot_test.cpp b/fs_mgr/libsnapshot/userspace_snapshot_test.cpp new file mode 100644 index 000000000..abe67f64c --- /dev/null +++ b/fs_mgr/libsnapshot/userspace_snapshot_test.cpp @@ -0,0 +1,2519 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "partition_cow_creator.h" +#include "utility.h" + +#include + +// Mock classes are not used. Header included to ensure mocked class definition aligns with the +// class itself. +#include +#include + +namespace android { +namespace snapshot { + +using android::base::unique_fd; +using android::dm::DeviceMapper; +using android::dm::DmDeviceState; +using android::dm::IDeviceMapper; +using android::fiemap::FiemapStatus; +using android::fiemap::IImageManager; +using android::fs_mgr::BlockDeviceInfo; +using android::fs_mgr::CreateLogicalPartitionParams; +using android::fs_mgr::DestroyLogicalPartition; +using android::fs_mgr::EnsurePathMounted; +using android::fs_mgr::EnsurePathUnmounted; +using android::fs_mgr::Extent; +using android::fs_mgr::Fstab; +using android::fs_mgr::GetPartitionGroupName; +using android::fs_mgr::GetPartitionName; +using android::fs_mgr::Interval; +using android::fs_mgr::MetadataBuilder; +using android::fs_mgr::SlotSuffixForSlotNumber; +using chromeos_update_engine::DeltaArchiveManifest; +using chromeos_update_engine::DynamicPartitionGroup; +using chromeos_update_engine::PartitionUpdate; +using namespace ::testing; +using namespace android::storage_literals; +using namespace std::chrono_literals; +using namespace std::string_literals; + +// Global states. See test_helpers.h. +std::unique_ptr sm; +TestDeviceInfo* test_device = nullptr; +std::string fake_super; + +void MountMetadata(); + +class SnapshotTest : public ::testing::Test { + public: + SnapshotTest() : dm_(DeviceMapper::Instance()) {} + + // This is exposed for main. + void Cleanup() { + InitializeState(); + CleanupTestArtifacts(); + } + + protected: + void SetUp() override { + SKIP_IF_NON_VIRTUAL_AB(); + + SnapshotTestPropertyFetcher::SetUp(); + InitializeState(); + CleanupTestArtifacts(); + FormatFakeSuper(); + MountMetadata(); + ASSERT_TRUE(sm->BeginUpdate()); + } + + void TearDown() override { + RETURN_IF_NON_VIRTUAL_AB(); + + lock_ = nullptr; + + CleanupTestArtifacts(); + SnapshotTestPropertyFetcher::TearDown(); + } + + void InitializeState() { + ASSERT_TRUE(sm->EnsureImageManager()); + image_manager_ = sm->image_manager(); + + test_device->set_slot_suffix("_a"); + + sm->set_use_first_stage_snapuserd(false); + } + + void CleanupTestArtifacts() { + // Normally cancelling inside a merge is not allowed. Since these + // are tests, we don't care, destroy everything that might exist. + // Note we hardcode this list because of an annoying quirk: when + // completing a merge, the snapshot stops existing, so we can't + // get an accurate list to remove. + lock_ = nullptr; + + std::vector snapshots = {"test-snapshot", "test_partition_a", + "test_partition_b"}; + for (const auto& snapshot : snapshots) { + ASSERT_TRUE(DeleteSnapshotDevice(snapshot)); + DeleteBackingImage(image_manager_, snapshot + "-cow-img"); + + auto status_file = sm->GetSnapshotStatusFilePath(snapshot); + android::base::RemoveFileIfExists(status_file); + } + + // Remove stale partitions in fake super. + std::vector partitions = { + "base-device", + "test_partition_b", + "test_partition_b-base", + "test_partition_b-base", + }; + for (const auto& partition : partitions) { + DeleteDevice(partition); + } + + if (sm->GetUpdateState() != UpdateState::None) { + auto state_file = sm->GetStateFilePath(); + unlink(state_file.c_str()); + } + } + + bool AcquireLock() { + lock_ = sm->LockExclusive(); + return !!lock_; + } + + // This is so main() can instantiate this to invoke Cleanup. + virtual void TestBody() override {} + + void FormatFakeSuper() { + BlockDeviceInfo super_device("super", kSuperSize, 0, 0, 4096); + std::vector devices = {super_device}; + + auto builder = MetadataBuilder::New(devices, "super", 65536, 2); + ASSERT_NE(builder, nullptr); + + auto metadata = builder->Export(); + ASSERT_NE(metadata, nullptr); + + TestPartitionOpener opener(fake_super); + ASSERT_TRUE(FlashPartitionTable(opener, fake_super, *metadata.get())); + } + + // If |path| is non-null, the partition will be mapped after creation. + bool CreatePartition(const std::string& name, uint64_t size, std::string* path = nullptr, + const std::optional group = {}) { + TestPartitionOpener opener(fake_super); + auto builder = MetadataBuilder::New(opener, "super", 0); + if (!builder) return false; + + std::string partition_group = std::string(android::fs_mgr::kDefaultGroup); + if (group) { + partition_group = *group; + } + return CreatePartition(builder.get(), name, size, path, partition_group); + } + + bool CreatePartition(MetadataBuilder* builder, const std::string& name, uint64_t size, + std::string* path, const std::string& group) { + auto partition = builder->AddPartition(name, group, 0); + if (!partition) return false; + if (!builder->ResizePartition(partition, size)) { + return false; + } + + // Update the source slot. + auto metadata = builder->Export(); + if (!metadata) return false; + + TestPartitionOpener opener(fake_super); + if (!UpdatePartitionTable(opener, "super", *metadata.get(), 0)) { + return false; + } + + if (!path) return true; + + CreateLogicalPartitionParams params = { + .block_device = fake_super, + .metadata = metadata.get(), + .partition_name = name, + .force_writable = true, + .timeout_ms = 10s, + }; + return CreateLogicalPartition(params, path); + } + + AssertionResult MapUpdateSnapshot(const std::string& name, + std::unique_ptr* writer) { + TestPartitionOpener opener(fake_super); + CreateLogicalPartitionParams params{ + .block_device = fake_super, + .metadata_slot = 1, + .partition_name = name, + .timeout_ms = 10s, + .partition_opener = &opener, + }; + + auto old_partition = "/dev/block/mapper/" + GetOtherPartitionName(name); + auto result = sm->OpenSnapshotWriter(params, {old_partition}); + if (!result) { + return AssertionFailure() << "Cannot open snapshot for writing: " << name; + } + if (!result->Initialize()) { + return AssertionFailure() << "Cannot initialize snapshot for writing: " << name; + } + + if (writer) { + *writer = std::move(result); + } + return AssertionSuccess(); + } + + AssertionResult MapUpdateSnapshot(const std::string& name, std::string* path) { + TestPartitionOpener opener(fake_super); + CreateLogicalPartitionParams params{ + .block_device = fake_super, + .metadata_slot = 1, + .partition_name = name, + .timeout_ms = 10s, + .partition_opener = &opener, + }; + + auto result = sm->MapUpdateSnapshot(params, path); + if (!result) { + return AssertionFailure() << "Cannot open snapshot for writing: " << name; + } + return AssertionSuccess(); + } + + AssertionResult DeleteSnapshotDevice(const std::string& snapshot) { + AssertionResult res = AssertionSuccess(); + if (!(res = DeleteDevice(snapshot))) return res; + if (!sm->UnmapDmUserDevice(snapshot + "-user-cow")) { + return AssertionFailure() << "Cannot delete dm-user device for " << snapshot; + } + if (!(res = DeleteDevice(snapshot + "-inner"))) return res; + if (!(res = DeleteDevice(snapshot + "-cow"))) return res; + if (!image_manager_->UnmapImageIfExists(snapshot + "-cow-img")) { + return AssertionFailure() << "Cannot unmap image " << snapshot << "-cow-img"; + } + if (!(res = DeleteDevice(snapshot + "-base"))) return res; + if (!(res = DeleteDevice(snapshot + "-src"))) return res; + return AssertionSuccess(); + } + + AssertionResult DeleteDevice(const std::string& device) { + if (!dm_.DeleteDeviceIfExists(device)) { + return AssertionFailure() << "Can't delete " << device; + } + return AssertionSuccess(); + } + + AssertionResult CreateCowImage(const std::string& name) { + if (!sm->CreateCowImage(lock_.get(), name)) { + return AssertionFailure() << "Cannot create COW image " << name; + } + std::string cow_device; + auto map_res = MapCowImage(name, 10s, &cow_device); + if (!map_res) { + return map_res; + } + if (!InitializeKernelCow(cow_device)) { + return AssertionFailure() << "Cannot zero fill " << cow_device; + } + if (!sm->UnmapCowImage(name)) { + return AssertionFailure() << "Cannot unmap " << name << " after zero filling it"; + } + return AssertionSuccess(); + } + + AssertionResult MapCowImage(const std::string& name, + const std::chrono::milliseconds& timeout_ms, std::string* path) { + auto cow_image_path = sm->MapCowImage(name, timeout_ms); + if (!cow_image_path.has_value()) { + return AssertionFailure() << "Cannot map cow image " << name; + } + *path = *cow_image_path; + return AssertionSuccess(); + } + + // Prepare A/B slot for a partition named "test_partition". + AssertionResult PrepareOneSnapshot(uint64_t device_size, + std::unique_ptr* writer = nullptr) { + lock_ = nullptr; + + DeltaArchiveManifest manifest; + + auto dynamic_partition_metadata = manifest.mutable_dynamic_partition_metadata(); + dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled()); + dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor); + + auto group = dynamic_partition_metadata->add_groups(); + group->set_name("group"); + group->set_size(device_size * 2); + group->add_partition_names("test_partition"); + + auto pu = manifest.add_partitions(); + pu->set_partition_name("test_partition"); + pu->set_estimate_cow_size(device_size); + SetSize(pu, device_size); + + auto extent = pu->add_operations()->add_dst_extents(); + extent->set_start_block(0); + if (device_size) { + extent->set_num_blocks(device_size / manifest.block_size()); + } + + TestPartitionOpener opener(fake_super); + auto builder = MetadataBuilder::New(opener, "super", 0); + if (!builder) { + return AssertionFailure() << "Failed to open MetadataBuilder"; + } + builder->AddGroup("group_a", 16_GiB); + builder->AddGroup("group_b", 16_GiB); + if (!CreatePartition(builder.get(), "test_partition_a", device_size, nullptr, "group_a")) { + return AssertionFailure() << "Failed create test_partition_a"; + } + + if (!sm->CreateUpdateSnapshots(manifest)) { + return AssertionFailure() << "Failed to create update snapshots"; + } + + if (writer) { + auto res = MapUpdateSnapshot("test_partition_b", writer); + if (!res) { + return res; + } + } else if (!IsCompressionEnabled()) { + std::string ignore; + if (!MapUpdateSnapshot("test_partition_b", &ignore)) { + return AssertionFailure() << "Failed to map test_partition_b"; + } + } + if (!AcquireLock()) { + return AssertionFailure() << "Failed to acquire lock"; + } + return AssertionSuccess(); + } + + // Simulate a reboot into the new slot. + AssertionResult SimulateReboot() { + lock_ = nullptr; + if (!sm->FinishedSnapshotWrites(false)) { + return AssertionFailure() << "Failed to finish snapshot writes"; + } + if (!sm->UnmapUpdateSnapshot("test_partition_b")) { + return AssertionFailure() << "Failed to unmap COW for test_partition_b"; + } + if (!dm_.DeleteDeviceIfExists("test_partition_b")) { + return AssertionFailure() << "Failed to delete test_partition_b"; + } + if (!dm_.DeleteDeviceIfExists("test_partition_b-base")) { + return AssertionFailure() << "Failed to destroy test_partition_b-base"; + } + return AssertionSuccess(); + } + + std::unique_ptr NewManagerForFirstStageMount( + const std::string& slot_suffix = "_a") { + auto info = new TestDeviceInfo(fake_super, slot_suffix); + return NewManagerForFirstStageMount(info); + } + + std::unique_ptr NewManagerForFirstStageMount(TestDeviceInfo* info) { + info->set_first_stage_init(true); + auto init = SnapshotManager::NewForFirstStageMount(info); + if (!init) { + return nullptr; + } + init->SetUeventRegenCallback([](const std::string& device) -> bool { + return android::fs_mgr::WaitForFile(device, snapshot_timeout_); + }); + return init; + } + + static constexpr std::chrono::milliseconds snapshot_timeout_ = 5s; + DeviceMapper& dm_; + std::unique_ptr lock_; + android::fiemap::IImageManager* image_manager_ = nullptr; + std::string fake_super_; +}; + +TEST_F(SnapshotTest, CreateSnapshot) { + ASSERT_TRUE(AcquireLock()); + + PartitionCowCreator cow_creator; + cow_creator.compression_enabled = IsCompressionEnabled(); + if (cow_creator.compression_enabled) { + cow_creator.compression_algorithm = "gz"; + } else { + cow_creator.compression_algorithm = "none"; + } + + static const uint64_t kDeviceSize = 1024 * 1024; + SnapshotStatus status; + status.set_name("test-snapshot"); + status.set_device_size(kDeviceSize); + status.set_snapshot_size(kDeviceSize); + status.set_cow_file_size(kDeviceSize); + ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status)); + ASSERT_TRUE(CreateCowImage("test-snapshot")); + + std::vector snapshots; + ASSERT_TRUE(sm->ListSnapshots(lock_.get(), &snapshots)); + ASSERT_EQ(snapshots.size(), 1); + ASSERT_EQ(snapshots[0], "test-snapshot"); + + // Scope so delete can re-acquire the snapshot file lock. + { + SnapshotStatus status; + ASSERT_TRUE(sm->ReadSnapshotStatus(lock_.get(), "test-snapshot", &status)); + ASSERT_EQ(status.state(), SnapshotState::CREATED); + ASSERT_EQ(status.device_size(), kDeviceSize); + ASSERT_EQ(status.snapshot_size(), kDeviceSize); + ASSERT_EQ(status.compression_enabled(), cow_creator.compression_enabled); + ASSERT_EQ(status.compression_algorithm(), cow_creator.compression_algorithm); + } + + ASSERT_TRUE(sm->UnmapSnapshot(lock_.get(), "test-snapshot")); + ASSERT_TRUE(sm->UnmapCowImage("test-snapshot")); + ASSERT_TRUE(sm->DeleteSnapshot(lock_.get(), "test-snapshot")); +} + +TEST_F(SnapshotTest, MapSnapshot) { + ASSERT_TRUE(AcquireLock()); + + PartitionCowCreator cow_creator; + cow_creator.compression_enabled = IsCompressionEnabled(); + + static const uint64_t kDeviceSize = 1024 * 1024; + SnapshotStatus status; + status.set_name("test-snapshot"); + status.set_device_size(kDeviceSize); + status.set_snapshot_size(kDeviceSize); + status.set_cow_file_size(kDeviceSize); + ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status)); + ASSERT_TRUE(CreateCowImage("test-snapshot")); + + std::string base_device; + ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device)); + + std::string cow_device; + ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device)); + + std::string snap_device; + ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s, + &snap_device)); + ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-")); +} + +TEST_F(SnapshotTest, NoMergeBeforeReboot) { + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // Merge should fail, since the slot hasn't changed. + ASSERT_FALSE(sm->InitiateMerge()); +} + +TEST_F(SnapshotTest, CleanFirstStageMount) { + // If there's no update in progress, there should be no first-stage mount + // needed. + auto sm = NewManagerForFirstStageMount(); + ASSERT_NE(sm, nullptr); + ASSERT_FALSE(sm->NeedSnapshotsInFirstStageMount()); +} + +TEST_F(SnapshotTest, FirstStageMountAfterRollback) { + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // We didn't change the slot, so we shouldn't need snapshots. + auto sm = NewManagerForFirstStageMount(); + ASSERT_NE(sm, nullptr); + ASSERT_FALSE(sm->NeedSnapshotsInFirstStageMount()); + + auto indicator = sm->GetRollbackIndicatorPath(); + ASSERT_EQ(access(indicator.c_str(), R_OK), 0); +} + +TEST_F(SnapshotTest, Merge) { + ASSERT_TRUE(AcquireLock()); + + static const uint64_t kDeviceSize = 1024 * 1024; + + std::unique_ptr writer; + ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &writer)); + + // Release the lock. + lock_ = nullptr; + + std::string test_string = "This is a test string."; + test_string.resize(writer->options().block_size); + ASSERT_TRUE(writer->AddRawBlocks(0, test_string.data(), test_string.size())); + ASSERT_TRUE(writer->Finalize()); + writer = nullptr; + + // Done updating. + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + ASSERT_TRUE(sm->UnmapUpdateSnapshot("test_partition_b")); + + test_device->set_slot_suffix("_b"); + ASSERT_TRUE(sm->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + ASSERT_TRUE(sm->InitiateMerge()); + + // The device should have been switched to a snapshot-merge target. + DeviceMapper::TargetInfo target; + ASSERT_TRUE(sm->IsSnapshotDevice("test_partition_b", &target)); + ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user"); + + // We should not be able to cancel an update now. + ASSERT_FALSE(sm->CancelUpdate()); + + ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::MergeCompleted); + ASSERT_EQ(sm->GetUpdateState(), UpdateState::None); + + // The device should no longer be a snapshot or snapshot-merge. + ASSERT_FALSE(sm->IsSnapshotDevice("test_partition_b")); + + // Test that we can read back the string we wrote to the snapshot. Note + // that the base device is gone now. |snap_device| contains the correct + // partition. + unique_fd fd(open("/dev/block/mapper/test_partition_b", O_RDONLY | O_CLOEXEC)); + ASSERT_GE(fd, 0); + + std::string buffer(test_string.size(), '\0'); + ASSERT_TRUE(android::base::ReadFully(fd, buffer.data(), buffer.size())); + ASSERT_EQ(test_string, buffer); +} + +TEST_F(SnapshotTest, FirstStageMountAndMerge) { + ASSERT_TRUE(AcquireLock()); + + static const uint64_t kDeviceSize = 1024 * 1024; + ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize)); + ASSERT_TRUE(SimulateReboot()); + + auto init = NewManagerForFirstStageMount("_b"); + ASSERT_NE(init, nullptr); + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + ASSERT_TRUE(AcquireLock()); + + // Validate that we have a snapshot device. + SnapshotStatus status; + ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status)); + ASSERT_EQ(status.state(), SnapshotState::CREATED); + if (IsCompressionEnabled()) { + ASSERT_EQ(status.compression_algorithm(), "gz"); + } else { + ASSERT_EQ(status.compression_algorithm(), "none"); + } + + DeviceMapper::TargetInfo target; + ASSERT_TRUE(init->IsSnapshotDevice("test_partition_b", &target)); + ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user"); +} + +TEST_F(SnapshotTest, FlashSuperDuringUpdate) { + ASSERT_TRUE(AcquireLock()); + + static const uint64_t kDeviceSize = 1024 * 1024; + ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize)); + ASSERT_TRUE(SimulateReboot()); + + // Reflash the super partition. + FormatFakeSuper(); + ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize)); + + auto init = NewManagerForFirstStageMount("_b"); + ASSERT_NE(init, nullptr); + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + ASSERT_TRUE(AcquireLock()); + + SnapshotStatus status; + ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status)); + + // We should not get a snapshot device now. + DeviceMapper::TargetInfo target; + ASSERT_FALSE(init->IsSnapshotDevice("test_partition_b", &target)); + + // We should see a cancelled update as well. + lock_ = nullptr; + ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::Cancelled); +} + +TEST_F(SnapshotTest, FlashSuperDuringMerge) { + ASSERT_TRUE(AcquireLock()); + + static const uint64_t kDeviceSize = 1024 * 1024; + ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize)); + ASSERT_TRUE(SimulateReboot()); + + auto init = NewManagerForFirstStageMount("_b"); + ASSERT_NE(init, nullptr); + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + ASSERT_TRUE(init->InitiateMerge()); + + // Now, reflash super. Note that we haven't called ProcessUpdateState, so the + // status is still Merging. + ASSERT_TRUE(DeleteSnapshotDevice("test_partition_b")); + ASSERT_TRUE(init->image_manager()->UnmapImageIfExists("test_partition_b-cow-img")); + FormatFakeSuper(); + ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize)); + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + // Because the status is Merging, we must call ProcessUpdateState, which should + // detect a cancelled update. + ASSERT_EQ(init->ProcessUpdateState(), UpdateState::Cancelled); + ASSERT_EQ(init->GetUpdateState(), UpdateState::None); +} + +TEST_F(SnapshotTest, UpdateBootControlHal) { + ASSERT_TRUE(AcquireLock()); + + ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::None)); + ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE); + + ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Initiated)); + ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE); + + ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified)); + ASSERT_EQ(test_device->merge_status(), MergeStatus::SNAPSHOTTED); + + ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Merging)); + ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING); + + ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeNeedsReboot)); + ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE); + + ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeCompleted)); + ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE); + + ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeFailed)); + ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING); +} + +TEST_F(SnapshotTest, MergeFailureCode) { + ASSERT_TRUE(AcquireLock()); + + ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeFailed, + MergeFailureCode::ListSnapshots)); + ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING); + + SnapshotUpdateStatus status = sm->ReadSnapshotUpdateStatus(lock_.get()); + ASSERT_EQ(status.state(), UpdateState::MergeFailed); + ASSERT_EQ(status.merge_failure_code(), MergeFailureCode::ListSnapshots); +} + +enum class Request { UNKNOWN, LOCK_SHARED, LOCK_EXCLUSIVE, UNLOCK, EXIT }; +std::ostream& operator<<(std::ostream& os, Request request) { + switch (request) { + case Request::LOCK_SHARED: + return os << "Shared"; + case Request::LOCK_EXCLUSIVE: + return os << "Exclusive"; + case Request::UNLOCK: + return os << "Unlock"; + case Request::EXIT: + return os << "Exit"; + case Request::UNKNOWN: + [[fallthrough]]; + default: + return os << "Unknown"; + } +} + +class LockTestConsumer { + public: + AssertionResult MakeRequest(Request new_request) { + { + std::unique_lock ulock(mutex_); + requests_.push_back(new_request); + } + cv_.notify_all(); + return AssertionSuccess() << "Request " << new_request << " successful"; + } + + template + AssertionResult WaitFulfill(std::chrono::duration timeout) { + std::unique_lock ulock(mutex_); + if (cv_.wait_for(ulock, timeout, [this] { return requests_.empty(); })) { + return AssertionSuccess() << "All requests_ fulfilled."; + } + return AssertionFailure() << "Timeout waiting for fulfilling " << requests_.size() + << " request(s), first one is " + << (requests_.empty() ? Request::UNKNOWN : requests_.front()); + } + + void StartHandleRequestsInBackground() { + future_ = std::async(std::launch::async, &LockTestConsumer::HandleRequests, this); + } + + private: + void HandleRequests() { + static constexpr auto consumer_timeout = 3s; + + auto next_request = Request::UNKNOWN; + do { + // Peek next request. + { + std::unique_lock ulock(mutex_); + if (cv_.wait_for(ulock, consumer_timeout, [this] { return !requests_.empty(); })) { + next_request = requests_.front(); + } else { + next_request = Request::EXIT; + } + } + + // Handle next request. + switch (next_request) { + case Request::LOCK_SHARED: { + lock_ = sm->LockShared(); + } break; + case Request::LOCK_EXCLUSIVE: { + lock_ = sm->LockExclusive(); + } break; + case Request::EXIT: + [[fallthrough]]; + case Request::UNLOCK: { + lock_.reset(); + } break; + case Request::UNKNOWN: + [[fallthrough]]; + default: + break; + } + + // Pop next request. This thread is the only thread that + // pops from the front of the requests_ deque. + { + std::unique_lock ulock(mutex_); + if (next_request == Request::EXIT) { + requests_.clear(); + } else { + requests_.pop_front(); + } + } + cv_.notify_all(); + } while (next_request != Request::EXIT); + } + + std::mutex mutex_; + std::condition_variable cv_; + std::deque requests_; + std::unique_ptr lock_; + std::future future_; +}; + +class LockTest : public ::testing::Test { + public: + void SetUp() { + SKIP_IF_NON_VIRTUAL_AB(); + first_consumer.StartHandleRequestsInBackground(); + second_consumer.StartHandleRequestsInBackground(); + } + + void TearDown() { + RETURN_IF_NON_VIRTUAL_AB(); + EXPECT_TRUE(first_consumer.MakeRequest(Request::EXIT)); + EXPECT_TRUE(second_consumer.MakeRequest(Request::EXIT)); + } + + static constexpr auto request_timeout = 500ms; + LockTestConsumer first_consumer; + LockTestConsumer second_consumer; +}; + +TEST_F(LockTest, SharedShared) { + ASSERT_TRUE(first_consumer.MakeRequest(Request::LOCK_SHARED)); + ASSERT_TRUE(first_consumer.WaitFulfill(request_timeout)); + ASSERT_TRUE(second_consumer.MakeRequest(Request::LOCK_SHARED)); + ASSERT_TRUE(second_consumer.WaitFulfill(request_timeout)); +} + +using LockTestParam = std::pair; +class LockTestP : public LockTest, public ::testing::WithParamInterface {}; +TEST_P(LockTestP, Test) { + ASSERT_TRUE(first_consumer.MakeRequest(GetParam().first)); + ASSERT_TRUE(first_consumer.WaitFulfill(request_timeout)); + ASSERT_TRUE(second_consumer.MakeRequest(GetParam().second)); + ASSERT_FALSE(second_consumer.WaitFulfill(request_timeout)) + << "Should not be able to " << GetParam().second << " while separate thread " + << GetParam().first; + ASSERT_TRUE(first_consumer.MakeRequest(Request::UNLOCK)); + ASSERT_TRUE(second_consumer.WaitFulfill(request_timeout)) + << "Should be able to hold lock that is released by separate thread"; +} +INSTANTIATE_TEST_SUITE_P( + LockTest, LockTestP, + testing::Values(LockTestParam{Request::LOCK_EXCLUSIVE, Request::LOCK_EXCLUSIVE}, + LockTestParam{Request::LOCK_EXCLUSIVE, Request::LOCK_SHARED}, + LockTestParam{Request::LOCK_SHARED, Request::LOCK_EXCLUSIVE}), + [](const testing::TestParamInfo& info) { + std::stringstream ss; + ss << info.param.first << info.param.second; + return ss.str(); + }); + +class SnapshotUpdateTest : public SnapshotTest { + public: + void SetUp() override { + SKIP_IF_NON_VIRTUAL_AB(); + + SnapshotTest::SetUp(); + Cleanup(); + + // Cleanup() changes slot suffix, so initialize it again. + test_device->set_slot_suffix("_a"); + + opener_ = std::make_unique(fake_super); + + auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata(); + dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled()); + dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor); + + // Create a fake update package metadata. + // Not using full name "system", "vendor", "product" because these names collide with the + // mapped partitions on the running device. + // Each test modifies manifest_ slightly to indicate changes to the partition layout. + group_ = dynamic_partition_metadata->add_groups(); + group_->set_name("group"); + group_->set_size(kGroupSize); + group_->add_partition_names("sys"); + group_->add_partition_names("vnd"); + group_->add_partition_names("prd"); + sys_ = manifest_.add_partitions(); + sys_->set_partition_name("sys"); + sys_->set_estimate_cow_size(2_MiB); + SetSize(sys_, 3_MiB); + vnd_ = manifest_.add_partitions(); + vnd_->set_partition_name("vnd"); + vnd_->set_estimate_cow_size(2_MiB); + SetSize(vnd_, 3_MiB); + prd_ = manifest_.add_partitions(); + prd_->set_partition_name("prd"); + prd_->set_estimate_cow_size(2_MiB); + SetSize(prd_, 3_MiB); + + // Initialize source partition metadata using |manifest_|. + src_ = MetadataBuilder::New(*opener_, "super", 0); + ASSERT_NE(src_, nullptr); + ASSERT_TRUE(FillFakeMetadata(src_.get(), manifest_, "_a")); + // Add sys_b which is like system_other. + ASSERT_TRUE(src_->AddGroup("group_b", kGroupSize)); + auto partition = src_->AddPartition("sys_b", "group_b", 0); + ASSERT_NE(nullptr, partition); + ASSERT_TRUE(src_->ResizePartition(partition, 1_MiB)); + auto metadata = src_->Export(); + ASSERT_NE(nullptr, metadata); + ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0)); + + // Map source partitions. Additionally, map sys_b to simulate system_other after flashing. + std::string path; + for (const auto& name : {"sys_a", "vnd_a", "prd_a", "sys_b"}) { + ASSERT_TRUE(CreateLogicalPartition( + CreateLogicalPartitionParams{ + .block_device = fake_super, + .metadata_slot = 0, + .partition_name = name, + .timeout_ms = 1s, + .partition_opener = opener_.get(), + }, + &path)); + ASSERT_TRUE(WriteRandomData(path)); + auto hash = GetHash(path); + ASSERT_TRUE(hash.has_value()); + hashes_[name] = *hash; + } + + // OTA client blindly unmaps all partitions that are possibly mapped. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(sm->UnmapUpdateSnapshot(name)); + } + } + void TearDown() override { + RETURN_IF_NON_VIRTUAL_AB(); + + Cleanup(); + SnapshotTest::TearDown(); + } + void Cleanup() { + if (!image_manager_) { + InitializeState(); + } + MountMetadata(); + for (const auto& suffix : {"_a", "_b"}) { + test_device->set_slot_suffix(suffix); + + // Cheat our way out of merge failed states. + if (sm->ProcessUpdateState() == UpdateState::MergeFailed) { + ASSERT_TRUE(AcquireLock()); + ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::None)); + lock_ = {}; + } + + EXPECT_TRUE(sm->CancelUpdate()) << suffix; + } + EXPECT_TRUE(UnmapAll()); + } + + AssertionResult IsPartitionUnchanged(const std::string& name) { + std::string path; + if (!dm_.GetDmDevicePathByName(name, &path)) { + return AssertionFailure() << "Path of " << name << " cannot be determined"; + } + auto hash = GetHash(path); + if (!hash.has_value()) { + return AssertionFailure() << "Cannot read partition " << name << ": " << path; + } + auto it = hashes_.find(name); + if (it == hashes_.end()) { + return AssertionFailure() << "No existing hash for " << name << ". Bad test code?"; + } + if (it->second != *hash) { + return AssertionFailure() << "Content of " << name << " has changed"; + } + return AssertionSuccess(); + } + + std::optional GetSnapshotSize(const std::string& name) { + if (!AcquireLock()) { + return std::nullopt; + } + auto local_lock = std::move(lock_); + + SnapshotStatus status; + if (!sm->ReadSnapshotStatus(local_lock.get(), name, &status)) { + return std::nullopt; + } + return status.snapshot_size(); + } + + AssertionResult UnmapAll() { + for (const auto& name : {"sys", "vnd", "prd", "dlkm"}) { + if (!dm_.DeleteDeviceIfExists(name + "_a"s)) { + return AssertionFailure() << "Cannot unmap " << name << "_a"; + } + if (!DeleteSnapshotDevice(name + "_b"s)) { + return AssertionFailure() << "Cannot delete snapshot " << name << "_b"; + } + } + return AssertionSuccess(); + } + + AssertionResult MapOneUpdateSnapshot(const std::string& name) { + if (IsCompressionEnabled()) { + std::unique_ptr writer; + return MapUpdateSnapshot(name, &writer); + } else { + std::string path; + return MapUpdateSnapshot(name, &path); + } + } + + AssertionResult WriteSnapshotAndHash(const std::string& name) { + if (IsCompressionEnabled()) { + std::unique_ptr writer; + auto res = MapUpdateSnapshot(name, &writer); + if (!res) { + return res; + } + if (!WriteRandomData(writer.get(), &hashes_[name])) { + return AssertionFailure() << "Unable to write random data to snapshot " << name; + } + if (!writer->Finalize()) { + return AssertionFailure() << "Unable to finalize COW for " << name; + } + } else { + std::string path; + auto res = MapUpdateSnapshot(name, &path); + if (!res) { + return res; + } + if (!WriteRandomData(path, std::nullopt, &hashes_[name])) { + return AssertionFailure() << "Unable to write random data to snapshot " << name; + } + } + + // Make sure updates to one device are seen by all devices. + sync(); + + return AssertionSuccess() << "Written random data to snapshot " << name + << ", hash: " << hashes_[name]; + } + + // Generate a snapshot that moves all the upper blocks down to the start. + // It doesn't really matter the order, we just want copies that reference + // blocks that won't exist if the partition shrinks. + AssertionResult ShiftAllSnapshotBlocks(const std::string& name, uint64_t old_size) { + std::unique_ptr writer; + if (auto res = MapUpdateSnapshot(name, &writer); !res) { + return res; + } + if (!writer->options().max_blocks || !*writer->options().max_blocks) { + return AssertionFailure() << "No max blocks set for " << name << " writer"; + } + + uint64_t src_block = (old_size / writer->options().block_size) - 1; + uint64_t dst_block = 0; + uint64_t max_blocks = *writer->options().max_blocks; + while (dst_block < max_blocks && dst_block < src_block) { + if (!writer->AddCopy(dst_block, src_block)) { + return AssertionFailure() << "Unable to add copy for " << name << " for blocks " + << src_block << ", " << dst_block; + } + dst_block++; + src_block--; + } + if (!writer->Finalize()) { + return AssertionFailure() << "Unable to finalize writer for " << name; + } + + auto hash = HashSnapshot(writer.get()); + if (hash.empty()) { + return AssertionFailure() << "Unable to hash snapshot writer for " << name; + } + hashes_[name] = hash; + + return AssertionSuccess(); + } + + AssertionResult MapUpdateSnapshots(const std::vector& names = {"sys_b", "vnd_b", + "prd_b"}) { + for (const auto& name : names) { + auto res = MapOneUpdateSnapshot(name); + if (!res) { + return res; + } + } + return AssertionSuccess(); + } + + // Create fake install operations to grow the COW device size. + void AddOperation(PartitionUpdate* partition_update, uint64_t size_bytes = 0) { + auto e = partition_update->add_operations()->add_dst_extents(); + e->set_start_block(0); + if (size_bytes == 0) { + size_bytes = GetSize(partition_update); + } + e->set_num_blocks(size_bytes / manifest_.block_size()); + } + + void AddOperationForPartitions(std::vector partitions = {}) { + if (partitions.empty()) { + partitions = {sys_, vnd_, prd_}; + } + for (auto* partition : partitions) { + AddOperation(partition); + } + } + + std::unique_ptr opener_; + DeltaArchiveManifest manifest_; + std::unique_ptr src_; + std::map hashes_; + + PartitionUpdate* sys_ = nullptr; + PartitionUpdate* vnd_ = nullptr; + PartitionUpdate* prd_ = nullptr; + DynamicPartitionGroup* group_ = nullptr; +}; + +// Test full update flow executed by update_engine. Some partitions uses super empty space, +// some uses images, and some uses both. +// Also test UnmapUpdateSnapshot unmaps everything. +// Also test first stage mount and merge after this. +TEST_F(SnapshotUpdateTest, FullUpdateFlow) { + // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs + // fit in super, but not |prd|. + constexpr uint64_t partition_size = 3788_KiB; + SetSize(sys_, partition_size); + SetSize(vnd_, partition_size); + SetSize(prd_, 18_MiB); + + // Make sure |prd| does not fit in super at all. On VABC, this means we + // fake an extra large COW for |vnd| to fill up super. + vnd_->set_estimate_cow_size(30_MiB); + prd_->set_estimate_cow_size(30_MiB); + + AddOperationForPartitions(); + + // Execute the update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + + // Test that partitions prioritize using space in super. + auto tgt = MetadataBuilder::New(*opener_, "super", 1); + ASSERT_NE(tgt, nullptr); + ASSERT_NE(nullptr, tgt->FindPartition("sys_b-cow")); + ASSERT_NE(nullptr, tgt->FindPartition("vnd_b-cow")); + ASSERT_EQ(nullptr, tgt->FindPartition("prd_b-cow")); + + // Write some data to target partitions. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(WriteSnapshotAndHash(name)); + } + + // Assert that source partitions aren't affected. + for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)); + } + + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + // After reboot, init does first stage mount. + auto init = NewManagerForFirstStageMount("_b"); + ASSERT_NE(init, nullptr); + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + auto indicator = sm->GetRollbackIndicatorPath(); + ASSERT_NE(access(indicator.c_str(), R_OK), 0); + + // Check that the target partitions have the same content. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)); + } + + // Initiate the merge and wait for it to be completed. + ASSERT_TRUE(init->InitiateMerge()); + ASSERT_EQ(init->IsSnapuserdRequired(), IsCompressionEnabled()); + { + // We should have started in SECOND_PHASE since nothing shrinks. + ASSERT_TRUE(AcquireLock()); + auto local_lock = std::move(lock_); + auto status = init->ReadSnapshotUpdateStatus(local_lock.get()); + ASSERT_EQ(status.merge_phase(), MergePhase::SECOND_PHASE); + } + ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState()); + + // Make sure the second phase ran and deleted snapshots. + { + ASSERT_TRUE(AcquireLock()); + auto local_lock = std::move(lock_); + std::vector snapshots; + ASSERT_TRUE(init->ListSnapshots(local_lock.get(), &snapshots)); + ASSERT_TRUE(snapshots.empty()); + } + + // Check that the target partitions have the same content after the merge. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)) + << "Content of " << name << " changes after the merge"; + } +} + +TEST_F(SnapshotUpdateTest, DuplicateOps) { + if (!IsCompressionEnabled()) { + GTEST_SKIP() << "Compression-only test"; + } + + // Execute the update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + + // Write some data to target partitions. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(WriteSnapshotAndHash(name)); + } + + std::vector partitions = {sys_, vnd_, prd_}; + for (auto* partition : partitions) { + AddOperation(partition); + + std::unique_ptr writer; + auto res = MapUpdateSnapshot(partition->partition_name() + "_b", &writer); + ASSERT_TRUE(res); + ASSERT_TRUE(writer->AddZeroBlocks(0, 1)); + ASSERT_TRUE(writer->AddZeroBlocks(0, 1)); + ASSERT_TRUE(writer->Finalize()); + } + + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + // After reboot, init does first stage mount. + auto init = NewManagerForFirstStageMount("_b"); + ASSERT_NE(init, nullptr); + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + // Initiate the merge and wait for it to be completed. + ASSERT_TRUE(init->InitiateMerge()); + ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState()); +} + +// Test that shrinking and growing partitions at the same time is handled +// correctly in VABC. +TEST_F(SnapshotUpdateTest, SpaceSwapUpdate) { + if (!IsCompressionEnabled()) { + // b/179111359 + GTEST_SKIP() << "Skipping Virtual A/B Compression test"; + } + + auto old_sys_size = GetSize(sys_); + auto old_prd_size = GetSize(prd_); + + // Grow |sys| but shrink |prd|. + SetSize(sys_, old_sys_size * 2); + sys_->set_estimate_cow_size(8_MiB); + SetSize(prd_, old_prd_size / 2); + prd_->set_estimate_cow_size(1_MiB); + + AddOperationForPartitions(); + + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + + // Check that the old partition sizes were saved correctly. + { + ASSERT_TRUE(AcquireLock()); + auto local_lock = std::move(lock_); + + SnapshotStatus status; + ASSERT_TRUE(sm->ReadSnapshotStatus(local_lock.get(), "prd_b", &status)); + ASSERT_EQ(status.old_partition_size(), 3145728); + ASSERT_TRUE(sm->ReadSnapshotStatus(local_lock.get(), "sys_b", &status)); + ASSERT_EQ(status.old_partition_size(), 3145728); + } + + ASSERT_TRUE(WriteSnapshotAndHash("sys_b")); + ASSERT_TRUE(WriteSnapshotAndHash("vnd_b")); + ASSERT_TRUE(ShiftAllSnapshotBlocks("prd_b", old_prd_size)); + + sync(); + + // Assert that source partitions aren't affected. + for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)); + } + + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + // After reboot, init does first stage mount. + auto init = NewManagerForFirstStageMount("_b"); + ASSERT_NE(init, nullptr); + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + auto indicator = sm->GetRollbackIndicatorPath(); + ASSERT_NE(access(indicator.c_str(), R_OK), 0); + + // Check that the target partitions have the same content. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)); + } + + // Initiate the merge and wait for it to be completed. + ASSERT_TRUE(init->InitiateMerge()); + ASSERT_EQ(init->IsSnapuserdRequired(), IsCompressionEnabled()); + { + // Check that the merge phase is FIRST_PHASE until at least one call + // to ProcessUpdateState() occurs. + ASSERT_TRUE(AcquireLock()); + auto local_lock = std::move(lock_); + auto status = init->ReadSnapshotUpdateStatus(local_lock.get()); + ASSERT_EQ(status.merge_phase(), MergePhase::FIRST_PHASE); + } + + // Simulate shutting down the device and creating partitions again. + ASSERT_TRUE(UnmapAll()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + // Check that we used the correct types after rebooting mid-merge. + DeviceMapper::TargetInfo target; + ASSERT_TRUE(init->IsSnapshotDevice("prd_b", &target)); + ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user"); + ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target)); + ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user"); + ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target)); + ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user"); + + // Complete the merge. + ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState()); + + // Make sure the second phase ran and deleted snapshots. + { + ASSERT_TRUE(AcquireLock()); + auto local_lock = std::move(lock_); + std::vector snapshots; + ASSERT_TRUE(init->ListSnapshots(local_lock.get(), &snapshots)); + ASSERT_TRUE(snapshots.empty()); + } + + // Check that the target partitions have the same content after the merge. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)) + << "Content of " << name << " changes after the merge"; + } +} + +// Test that if new system partitions uses empty space in super, that region is not snapshotted. +TEST_F(SnapshotUpdateTest, DirectWriteEmptySpace) { + GTEST_SKIP() << "b/141889746"; + SetSize(sys_, 4_MiB); + // vnd_b and prd_b are unchanged. + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + ASSERT_EQ(3_MiB, GetSnapshotSize("sys_b").value_or(0)); +} + +// Test that if new system partitions uses space of old vendor partition, that region is +// snapshotted. +TEST_F(SnapshotUpdateTest, SnapshotOldPartitions) { + SetSize(sys_, 4_MiB); // grows + SetSize(vnd_, 2_MiB); // shrinks + // prd_b is unchanged + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + ASSERT_EQ(4_MiB, GetSnapshotSize("sys_b").value_or(0)); +} + +// Test that even if there seem to be empty space in target metadata, COW partition won't take +// it because they are used by old partitions. +TEST_F(SnapshotUpdateTest, CowPartitionDoNotTakeOldPartitions) { + SetSize(sys_, 2_MiB); // shrinks + // vnd_b and prd_b are unchanged. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + + auto tgt = MetadataBuilder::New(*opener_, "super", 1); + ASSERT_NE(nullptr, tgt); + auto metadata = tgt->Export(); + ASSERT_NE(nullptr, metadata); + std::vector written; + // Write random data to all COW partitions in super + for (auto p : metadata->partitions) { + if (GetPartitionGroupName(metadata->groups[p.group_index]) != kCowGroupName) { + continue; + } + std::string path; + ASSERT_TRUE(CreateLogicalPartition( + CreateLogicalPartitionParams{ + .block_device = fake_super, + .metadata = metadata.get(), + .partition = &p, + .timeout_ms = 1s, + .partition_opener = opener_.get(), + }, + &path)); + ASSERT_TRUE(WriteRandomData(path)); + written.push_back(GetPartitionName(p)); + } + ASSERT_FALSE(written.empty()) + << "No COW partitions are created even if there are empty space in super partition"; + + // Make sure source partitions aren't affected. + for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)); + } +} + +// Test that it crashes after creating snapshot status file but before creating COW image, then +// calling CreateUpdateSnapshots again works. +TEST_F(SnapshotUpdateTest, SnapshotStatusFileWithoutCow) { + // Write some trash snapshot files to simulate leftovers from previous runs. + { + ASSERT_TRUE(AcquireLock()); + auto local_lock = std::move(lock_); + SnapshotStatus status; + status.set_name("sys_b"); + ASSERT_TRUE(sm->WriteSnapshotStatus(local_lock.get(), status)); + ASSERT_TRUE(image_manager_->CreateBackingImage("sys_b-cow-img", 1_MiB, + IImageManager::CREATE_IMAGE_DEFAULT)); + } + + // Redo the update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b")); + + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + + // Check that target partitions can be mapped. + EXPECT_TRUE(MapUpdateSnapshots()); +} + +// Test that the old partitions are not modified. +TEST_F(SnapshotUpdateTest, TestRollback) { + // Execute the update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b")); + + AddOperationForPartitions(); + + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + + // Write some data to target partitions. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(WriteSnapshotAndHash(name)); + } + + // Assert that source partitions aren't affected. + for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)); + } + + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + // After reboot, init does first stage mount. + auto init = NewManagerForFirstStageMount("_b"); + ASSERT_NE(init, nullptr); + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + // Check that the target partitions have the same content. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)); + } + + // Simulate shutting down the device again. + ASSERT_TRUE(UnmapAll()); + init = NewManagerForFirstStageMount("_a"); + ASSERT_NE(init, nullptr); + ASSERT_FALSE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + // Assert that the source partitions aren't affected. + for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)); + } +} + +// Test that if an update is applied but not booted into, it can be canceled. +TEST_F(SnapshotUpdateTest, CancelAfterApply) { + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + ASSERT_TRUE(sm->CancelUpdate()); +} + +static std::vector ToIntervals(const std::vector>& extents) { + std::vector ret; + std::transform(extents.begin(), extents.end(), std::back_inserter(ret), + [](const auto& extent) { return extent->AsLinearExtent()->AsInterval(); }); + return ret; +} + +// Test that at the second update, old COW partition spaces are reclaimed. +TEST_F(SnapshotUpdateTest, ReclaimCow) { + // Make sure VABC cows are small enough that they fit in fake_super. + sys_->set_estimate_cow_size(64_KiB); + vnd_->set_estimate_cow_size(64_KiB); + prd_->set_estimate_cow_size(64_KiB); + + // Execute the first update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + ASSERT_TRUE(MapUpdateSnapshots()); + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + // After reboot, init does first stage mount. + auto init = NewManagerForFirstStageMount("_b"); + ASSERT_NE(init, nullptr); + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + init = nullptr; + + // Initiate the merge and wait for it to be completed. + auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b")); + ASSERT_TRUE(new_sm->InitiateMerge()); + ASSERT_EQ(UpdateState::MergeCompleted, new_sm->ProcessUpdateState()); + + // Execute the second update. + ASSERT_TRUE(new_sm->BeginUpdate()); + ASSERT_TRUE(new_sm->CreateUpdateSnapshots(manifest_)); + + // Check that the old COW space is reclaimed and does not occupy space of mapped partitions. + auto src = MetadataBuilder::New(*opener_, "super", 1); + ASSERT_NE(src, nullptr); + auto tgt = MetadataBuilder::New(*opener_, "super", 0); + ASSERT_NE(tgt, nullptr); + for (const auto& cow_part_name : {"sys_a-cow", "vnd_a-cow", "prd_a-cow"}) { + auto* cow_part = tgt->FindPartition(cow_part_name); + ASSERT_NE(nullptr, cow_part) << cow_part_name << " does not exist in target metadata"; + auto cow_intervals = ToIntervals(cow_part->extents()); + for (const auto& old_part_name : {"sys_b", "vnd_b", "prd_b"}) { + auto* old_part = src->FindPartition(old_part_name); + ASSERT_NE(nullptr, old_part) << old_part_name << " does not exist in source metadata"; + auto old_intervals = ToIntervals(old_part->extents()); + + auto intersect = Interval::Intersect(cow_intervals, old_intervals); + ASSERT_TRUE(intersect.empty()) << "COW uses space of source partitions"; + } + } +} + +TEST_F(SnapshotUpdateTest, RetrofitAfterRegularAb) { + constexpr auto kRetrofitGroupSize = kGroupSize / 2; + + // Initialize device-mapper / disk + ASSERT_TRUE(UnmapAll()); + FormatFakeSuper(); + + // Setup source partition metadata to have both _a and _b partitions. + src_ = MetadataBuilder::New(*opener_, "super", 0); + ASSERT_NE(nullptr, src_); + for (const auto& suffix : {"_a"s, "_b"s}) { + ASSERT_TRUE(src_->AddGroup(group_->name() + suffix, kRetrofitGroupSize)); + for (const auto& name : {"sys"s, "vnd"s, "prd"s}) { + auto partition = src_->AddPartition(name + suffix, group_->name() + suffix, 0); + ASSERT_NE(nullptr, partition); + ASSERT_TRUE(src_->ResizePartition(partition, 2_MiB)); + } + } + auto metadata = src_->Export(); + ASSERT_NE(nullptr, metadata); + ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0)); + + // Flash source partitions + std::string path; + for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { + ASSERT_TRUE(CreateLogicalPartition( + CreateLogicalPartitionParams{ + .block_device = fake_super, + .metadata_slot = 0, + .partition_name = name, + .timeout_ms = 1s, + .partition_opener = opener_.get(), + }, + &path)); + ASSERT_TRUE(WriteRandomData(path)); + auto hash = GetHash(path); + ASSERT_TRUE(hash.has_value()); + hashes_[name] = *hash; + } + + // Setup manifest. + group_->set_size(kRetrofitGroupSize); + for (auto* partition : {sys_, vnd_, prd_}) { + SetSize(partition, 2_MiB); + } + AddOperationForPartitions(); + + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + + // Test that COW image should not be created for retrofit devices; super + // should be big enough. + ASSERT_FALSE(image_manager_->BackingImageExists("sys_b-cow-img")); + ASSERT_FALSE(image_manager_->BackingImageExists("vnd_b-cow-img")); + ASSERT_FALSE(image_manager_->BackingImageExists("prd_b-cow-img")); + + // Write some data to target partitions. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(WriteSnapshotAndHash(name)); + } + + // Assert that source partitions aren't affected. + for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)); + } + + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); +} + +TEST_F(SnapshotUpdateTest, MergeCannotRemoveCow) { + // Make source partitions as big as possible to force COW image to be created. + SetSize(sys_, 10_MiB); + SetSize(vnd_, 10_MiB); + SetSize(prd_, 10_MiB); + sys_->set_estimate_cow_size(12_MiB); + vnd_->set_estimate_cow_size(12_MiB); + prd_->set_estimate_cow_size(12_MiB); + + src_ = MetadataBuilder::New(*opener_, "super", 0); + ASSERT_NE(src_, nullptr); + src_->RemoveGroupAndPartitions(group_->name() + "_a"); + src_->RemoveGroupAndPartitions(group_->name() + "_b"); + ASSERT_TRUE(FillFakeMetadata(src_.get(), manifest_, "_a")); + auto metadata = src_->Export(); + ASSERT_NE(nullptr, metadata); + ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0)); + + // Add operations for sys. The whole device is written. + AddOperation(sys_); + + // Execute the update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + ASSERT_TRUE(MapUpdateSnapshots()); + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + // After reboot, init does first stage mount. + // Normally we should use NewManagerForFirstStageMount, but if so, + // "gsid.mapped_image.sys_b-cow-img" won't be set. + auto init = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b")); + ASSERT_NE(init, nullptr); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + // Keep an open handle to the cow device. This should cause the merge to + // be incomplete. + auto cow_path = android::base::GetProperty("gsid.mapped_image.sys_b-cow-img", ""); + unique_fd fd(open(cow_path.c_str(), O_RDONLY | O_CLOEXEC)); + ASSERT_GE(fd, 0); + + // COW cannot be removed due to open fd, so expect a soft failure. + ASSERT_TRUE(init->InitiateMerge()); + ASSERT_EQ(UpdateState::MergeNeedsReboot, init->ProcessUpdateState()); + + // Simulate shutting down the device. + fd.reset(); + ASSERT_TRUE(UnmapAll()); + + // init does first stage mount again. + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + // sys_b should be mapped as a dm-linear device directly. + ASSERT_FALSE(sm->IsSnapshotDevice("sys_b", nullptr)); + + // Merge should be able to complete now. + ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState()); +} + +class MetadataMountedTest : public ::testing::Test { + public: + // This is so main() can instantiate this to invoke Cleanup. + virtual void TestBody() override {} + void SetUp() override { + SKIP_IF_NON_VIRTUAL_AB(); + metadata_dir_ = test_device->GetMetadataDir(); + ASSERT_TRUE(ReadDefaultFstab(&fstab_)); + } + void TearDown() override { + RETURN_IF_NON_VIRTUAL_AB(); + SetUp(); + // Remount /metadata + test_device->set_recovery(false); + EXPECT_TRUE(android::fs_mgr::EnsurePathMounted(&fstab_, metadata_dir_)); + } + AssertionResult IsMetadataMounted() { + Fstab mounted_fstab; + if (!ReadFstabFromFile("/proc/mounts", &mounted_fstab)) { + ADD_FAILURE() << "Failed to scan mounted volumes"; + return AssertionFailure() << "Failed to scan mounted volumes"; + } + + auto entry = GetEntryForPath(&fstab_, metadata_dir_); + if (entry == nullptr) { + return AssertionFailure() << "No mount point found in fstab for path " << metadata_dir_; + } + + auto mv = GetEntryForMountPoint(&mounted_fstab, entry->mount_point); + if (mv == nullptr) { + return AssertionFailure() << metadata_dir_ << " is not mounted"; + } + return AssertionSuccess() << metadata_dir_ << " is mounted"; + } + std::string metadata_dir_; + Fstab fstab_; +}; + +void MountMetadata() { + MetadataMountedTest().TearDown(); +} + +TEST_F(MetadataMountedTest, Android) { + auto device = sm->EnsureMetadataMounted(); + EXPECT_NE(nullptr, device); + device.reset(); + + EXPECT_TRUE(IsMetadataMounted()); + EXPECT_TRUE(sm->CancelUpdate()) << "Metadata dir should never be unmounted in Android mode"; +} + +TEST_F(MetadataMountedTest, Recovery) { + test_device->set_recovery(true); + metadata_dir_ = test_device->GetMetadataDir(); + + EXPECT_TRUE(android::fs_mgr::EnsurePathUnmounted(&fstab_, metadata_dir_)); + EXPECT_FALSE(IsMetadataMounted()); + + auto device = sm->EnsureMetadataMounted(); + EXPECT_NE(nullptr, device); + EXPECT_TRUE(IsMetadataMounted()); + + device.reset(); + EXPECT_FALSE(IsMetadataMounted()); +} + +// Test that during a merge, we can wipe data in recovery. +TEST_F(SnapshotUpdateTest, MergeInRecovery) { + // Execute the first update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + ASSERT_TRUE(MapUpdateSnapshots()); + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + // After reboot, init does first stage mount. + auto init = NewManagerForFirstStageMount("_b"); + ASSERT_NE(init, nullptr); + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + init = nullptr; + + // Initiate the merge and then immediately stop it to simulate a reboot. + auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b")); + ASSERT_TRUE(new_sm->InitiateMerge()); + ASSERT_TRUE(UnmapAll()); + + // Simulate a reboot into recovery. + auto test_device = std::make_unique(fake_super, "_b"); + test_device->set_recovery(true); + new_sm = NewManagerForFirstStageMount(test_device.release()); + + ASSERT_TRUE(new_sm->HandleImminentDataWipe()); + ASSERT_EQ(new_sm->GetUpdateState(), UpdateState::None); +} + +// Test that a merge does not clear the snapshot state in fastboot. +TEST_F(SnapshotUpdateTest, MergeInFastboot) { + // Execute the first update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + ASSERT_TRUE(MapUpdateSnapshots()); + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + // After reboot, init does first stage mount. + auto init = NewManagerForFirstStageMount("_b"); + ASSERT_NE(init, nullptr); + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + init = nullptr; + + // Initiate the merge and then immediately stop it to simulate a reboot. + auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b")); + ASSERT_TRUE(new_sm->InitiateMerge()); + ASSERT_TRUE(UnmapAll()); + + // Simulate a reboot into recovery. + auto test_device = std::make_unique(fake_super, "_b"); + test_device->set_recovery(true); + new_sm = NewManagerForFirstStageMount(test_device.release()); + + ASSERT_TRUE(new_sm->FinishMergeInRecovery()); + + ASSERT_TRUE(UnmapAll()); + + auto mount = new_sm->EnsureMetadataMounted(); + ASSERT_TRUE(mount && mount->HasDevice()); + ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted); + + // Finish the merge in a normal boot. + test_device = std::make_unique(fake_super, "_b"); + init = NewManagerForFirstStageMount(test_device.release()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + init = nullptr; + + test_device = std::make_unique(fake_super, "_b"); + new_sm = NewManagerForFirstStageMount(test_device.release()); + ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted); + ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::None); +} + +// Test that after an OTA, before a merge, we can wipe data in recovery. +TEST_F(SnapshotUpdateTest, DataWipeRollbackInRecovery) { + // Execute the first update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + ASSERT_TRUE(MapUpdateSnapshots()); + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + // Simulate a reboot into recovery. + auto test_device = new TestDeviceInfo(fake_super, "_b"); + test_device->set_recovery(true); + auto new_sm = NewManagerForFirstStageMount(test_device); + + ASSERT_TRUE(new_sm->HandleImminentDataWipe()); + // Manually mount metadata so that we can call GetUpdateState() below. + MountMetadata(); + EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None); + EXPECT_TRUE(test_device->IsSlotUnbootable(1)); + EXPECT_FALSE(test_device->IsSlotUnbootable(0)); +} + +// Test that after an OTA and a bootloader rollback with no merge, we can wipe +// data in recovery. +TEST_F(SnapshotUpdateTest, DataWipeAfterRollback) { + // Execute the first update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + ASSERT_TRUE(MapUpdateSnapshots()); + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + // Simulate a rollback, with reboot into recovery. + auto test_device = new TestDeviceInfo(fake_super, "_a"); + test_device->set_recovery(true); + auto new_sm = NewManagerForFirstStageMount(test_device); + + ASSERT_TRUE(new_sm->HandleImminentDataWipe()); + EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None); + EXPECT_FALSE(test_device->IsSlotUnbootable(0)); + EXPECT_FALSE(test_device->IsSlotUnbootable(1)); +} + +// Test update package that requests data wipe. +TEST_F(SnapshotUpdateTest, DataWipeRequiredInPackage) { + AddOperationForPartitions(); + // Execute the update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + + // Write some data to target partitions. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(WriteSnapshotAndHash(name)) << name; + } + + ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + // Simulate a reboot into recovery. + auto test_device = new TestDeviceInfo(fake_super, "_b"); + test_device->set_recovery(true); + auto new_sm = NewManagerForFirstStageMount(test_device); + + ASSERT_TRUE(new_sm->HandleImminentDataWipe()); + // Manually mount metadata so that we can call GetUpdateState() below. + MountMetadata(); + EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None); + ASSERT_FALSE(test_device->IsSlotUnbootable(1)); + ASSERT_FALSE(test_device->IsSlotUnbootable(0)); + + ASSERT_TRUE(UnmapAll()); + + // Now reboot into new slot. + test_device = new TestDeviceInfo(fake_super, "_b"); + auto init = NewManagerForFirstStageMount(test_device); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + // Verify that we are on the downgraded build. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)) << name; + } +} + +// Test update package that requests data wipe. +TEST_F(SnapshotUpdateTest, DataWipeWithStaleSnapshots) { + AddOperationForPartitions(); + + // Execute the update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + + // Write some data to target partitions. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(WriteSnapshotAndHash(name)) << name; + } + + // Create a stale snapshot that should not exist. + { + ASSERT_TRUE(AcquireLock()); + + PartitionCowCreator cow_creator = { + .compression_enabled = IsCompressionEnabled(), + .compression_algorithm = IsCompressionEnabled() ? "gz" : "none", + }; + SnapshotStatus status; + status.set_name("sys_a"); + status.set_device_size(1_MiB); + status.set_snapshot_size(2_MiB); + status.set_cow_partition_size(2_MiB); + + ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status)); + lock_ = nullptr; + + ASSERT_TRUE(sm->EnsureImageManager()); + ASSERT_TRUE(sm->image_manager()->CreateBackingImage("sys_a", 1_MiB, 0)); + } + + ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + // Simulate a reboot into recovery. + auto test_device = new TestDeviceInfo(fake_super, "_b"); + test_device->set_recovery(true); + auto new_sm = NewManagerForFirstStageMount(test_device); + + ASSERT_TRUE(new_sm->HandleImminentDataWipe()); + // Manually mount metadata so that we can call GetUpdateState() below. + MountMetadata(); + EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None); + ASSERT_FALSE(test_device->IsSlotUnbootable(1)); + ASSERT_FALSE(test_device->IsSlotUnbootable(0)); + + ASSERT_TRUE(UnmapAll()); + + // Now reboot into new slot. + test_device = new TestDeviceInfo(fake_super, "_b"); + auto init = NewManagerForFirstStageMount(test_device); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + // Verify that we are on the downgraded build. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)) << name; + } +} + +TEST_F(SnapshotUpdateTest, Hashtree) { + constexpr auto partition_size = 4_MiB; + constexpr auto data_size = 3_MiB; + constexpr auto hashtree_size = 512_KiB; + constexpr auto fec_size = partition_size - data_size - hashtree_size; + + const auto block_size = manifest_.block_size(); + SetSize(sys_, partition_size); + AddOperation(sys_, data_size); + + sys_->set_estimate_cow_size(partition_size + data_size); + + // Set hastree extents. + sys_->mutable_hash_tree_data_extent()->set_start_block(0); + sys_->mutable_hash_tree_data_extent()->set_num_blocks(data_size / block_size); + + sys_->mutable_hash_tree_extent()->set_start_block(data_size / block_size); + sys_->mutable_hash_tree_extent()->set_num_blocks(hashtree_size / block_size); + + // Set FEC extents. + sys_->mutable_fec_data_extent()->set_start_block(0); + sys_->mutable_fec_data_extent()->set_num_blocks((data_size + hashtree_size) / block_size); + + sys_->mutable_fec_extent()->set_start_block((data_size + hashtree_size) / block_size); + sys_->mutable_fec_extent()->set_num_blocks(fec_size / block_size); + + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + + // Map and write some data to target partition. + ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"})); + ASSERT_TRUE(WriteSnapshotAndHash("sys_b")); + + // Finish update. + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + // After reboot, init does first stage mount. + auto init = NewManagerForFirstStageMount("_b"); + ASSERT_NE(init, nullptr); + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + // Check that the target partition have the same content. Hashtree and FEC extents + // should be accounted for. + ASSERT_TRUE(IsPartitionUnchanged("sys_b")); +} + +// Test for overflow bit after update +TEST_F(SnapshotUpdateTest, Overflow) { + if (IsCompressionEnabled()) { + GTEST_SKIP() << "No overflow bit set for userspace COWs"; + } + + const auto actual_write_size = GetSize(sys_); + const auto declared_write_size = actual_write_size - 1_MiB; + + AddOperation(sys_, declared_write_size); + + // Execute the update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + + // Map and write some data to target partitions. + ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"})); + ASSERT_TRUE(WriteSnapshotAndHash("sys_b")); + + std::vector table; + ASSERT_TRUE(DeviceMapper::Instance().GetTableStatus("sys_b", &table)); + ASSERT_EQ(1u, table.size()); + EXPECT_TRUE(table[0].IsOverflowSnapshot()); + + ASSERT_FALSE(sm->FinishedSnapshotWrites(false)) + << "FinishedSnapshotWrites should detect overflow of CoW device."; +} + +TEST_F(SnapshotUpdateTest, LowSpace) { + static constexpr auto kMaxFree = 10_MiB; + auto userdata = std::make_unique(); + ASSERT_TRUE(userdata->Init(kMaxFree)); + + // Grow all partitions to 10_MiB, total 30_MiB. This requires 30 MiB of CoW space. After + // using the empty space in super (< 1 MiB), it uses 30 MiB of /userdata space. + constexpr uint64_t partition_size = 10_MiB; + SetSize(sys_, partition_size); + SetSize(vnd_, partition_size); + SetSize(prd_, partition_size); + sys_->set_estimate_cow_size(partition_size); + vnd_->set_estimate_cow_size(partition_size); + prd_->set_estimate_cow_size(partition_size); + + AddOperationForPartitions(); + + // Execute the update. + ASSERT_TRUE(sm->BeginUpdate()); + auto res = sm->CreateUpdateSnapshots(manifest_); + ASSERT_FALSE(res); + ASSERT_EQ(Return::ErrorCode::NO_SPACE, res.error_code()); + ASSERT_GE(res.required_size(), 14_MiB); + ASSERT_LT(res.required_size(), 40_MiB); +} + +TEST_F(SnapshotUpdateTest, AddPartition) { + group_->add_partition_names("dlkm"); + + auto dlkm = manifest_.add_partitions(); + dlkm->set_partition_name("dlkm"); + dlkm->set_estimate_cow_size(2_MiB); + SetSize(dlkm, 3_MiB); + + // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs + // fit in super, but not |prd|. + constexpr uint64_t partition_size = 3788_KiB; + SetSize(sys_, partition_size); + SetSize(vnd_, partition_size); + SetSize(prd_, partition_size); + SetSize(dlkm, partition_size); + + AddOperationForPartitions({sys_, vnd_, prd_, dlkm}); + + // Execute the update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + + // Write some data to target partitions. + for (const auto& name : {"sys_b", "vnd_b", "prd_b", "dlkm_b"}) { + ASSERT_TRUE(WriteSnapshotAndHash(name)); + } + + // Assert that source partitions aren't affected. + for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)); + } + + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + // After reboot, init does first stage mount. + auto init = NewManagerForFirstStageMount("_b"); + ASSERT_NE(init, nullptr); + + ASSERT_TRUE(init->EnsureSnapuserdConnected()); + init->set_use_first_stage_snapuserd(true); + + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + // Check that the target partitions have the same content. + std::vector partitions = {"sys_b", "vnd_b", "prd_b", "dlkm_b"}; + for (const auto& name : partitions) { + ASSERT_TRUE(IsPartitionUnchanged(name)); + } + + ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE)); + for (const auto& name : partitions) { + ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete(name + "-user-cow-init")); + } + + // Initiate the merge and wait for it to be completed. + ASSERT_TRUE(init->InitiateMerge()); + ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState()); + + // Check that the target partitions have the same content after the merge. + for (const auto& name : {"sys_b", "vnd_b", "prd_b", "dlkm_b"}) { + ASSERT_TRUE(IsPartitionUnchanged(name)) + << "Content of " << name << " changes after the merge"; + } +} + +class AutoKill final { + public: + explicit AutoKill(pid_t pid) : pid_(pid) {} + ~AutoKill() { + if (pid_ > 0) kill(pid_, SIGKILL); + } + + bool valid() const { return pid_ > 0; } + + private: + pid_t pid_; +}; + +TEST_F(SnapshotUpdateTest, DaemonTransition) { + if (!IsCompressionEnabled()) { + GTEST_SKIP() << "Skipping Virtual A/B Compression test"; + } + + // Ensure a connection to the second-stage daemon, but use the first-stage + // code paths thereafter. + ASSERT_TRUE(sm->EnsureSnapuserdConnected()); + sm->set_use_first_stage_snapuserd(true); + + AddOperationForPartitions(); + // Execute the update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + ASSERT_TRUE(MapUpdateSnapshots()); + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + ASSERT_TRUE(UnmapAll()); + + auto init = NewManagerForFirstStageMount("_b"); + ASSERT_NE(init, nullptr); + + ASSERT_TRUE(init->EnsureSnapuserdConnected()); + init->set_use_first_stage_snapuserd(true); + + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + ASSERT_EQ(access("/dev/dm-user/sys_b-init", F_OK), 0); + ASSERT_EQ(access("/dev/dm-user/sys_b", F_OK), -1); + + ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE)); + + // :TODO: this is a workaround to ensure the handler list stays empty. We + // should make this test more like actual init, and spawn two copies of + // snapuserd, given how many other tests we now have for normal snapuserd. + ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-init")); + ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-init")); + ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-init")); + + // The control device should have been renamed. + ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-init", 10s)); + ASSERT_EQ(access("/dev/dm-user/sys_b", F_OK), 0); +} + +TEST_F(SnapshotUpdateTest, MapAllSnapshots) { + AddOperationForPartitions(); + // Execute the update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(WriteSnapshotAndHash(name)); + } + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + ASSERT_TRUE(sm->MapAllSnapshots(10s)); + + // Read bytes back and verify they match the cache. + ASSERT_TRUE(IsPartitionUnchanged("sys_b")); + + ASSERT_TRUE(sm->UnmapAllSnapshots()); +} + +TEST_F(SnapshotUpdateTest, CancelOnTargetSlot) { + AddOperationForPartitions(); + + // Execute the update from B->A. + test_device->set_slot_suffix("_b"); + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + + ASSERT_TRUE(UnmapAll()); + std::string path; + ASSERT_TRUE(CreateLogicalPartition( + CreateLogicalPartitionParams{ + .block_device = fake_super, + .metadata_slot = 0, + .partition_name = "sys_a", + .timeout_ms = 1s, + .partition_opener = opener_.get(), + }, + &path)); + + // Switch back to "A", make sure we can cancel. Instead of unmapping sys_a + // we should simply delete the old snapshots. + test_device->set_slot_suffix("_a"); + ASSERT_TRUE(sm->BeginUpdate()); +} + +class FlashAfterUpdateTest : public SnapshotUpdateTest, + public WithParamInterface> { + public: + AssertionResult InitiateMerge(const std::string& slot_suffix) { + auto sm = SnapshotManager::New(new TestDeviceInfo(fake_super, slot_suffix)); + if (!sm->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)) { + return AssertionFailure() << "Cannot CreateLogicalAndSnapshotPartitions"; + } + if (!sm->InitiateMerge()) { + return AssertionFailure() << "Cannot initiate merge"; + } + return AssertionSuccess(); + } +}; + +TEST_P(FlashAfterUpdateTest, FlashSlotAfterUpdate) { + // Execute the update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + ASSERT_TRUE(MapUpdateSnapshots()); + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + + // Simulate shutting down the device. + ASSERT_TRUE(UnmapAll()); + + bool after_merge = std::get<1>(GetParam()); + if (after_merge) { + ASSERT_TRUE(InitiateMerge("_b")); + // Simulate shutting down the device after merge has initiated. + ASSERT_TRUE(UnmapAll()); + } + + auto flashed_slot = std::get<0>(GetParam()); + auto flashed_slot_suffix = SlotSuffixForSlotNumber(flashed_slot); + + // Simulate flashing |flashed_slot|. This clears the UPDATED flag. + auto flashed_builder = MetadataBuilder::New(*opener_, "super", flashed_slot); + ASSERT_NE(flashed_builder, nullptr); + flashed_builder->RemoveGroupAndPartitions(group_->name() + flashed_slot_suffix); + flashed_builder->RemoveGroupAndPartitions(kCowGroupName); + ASSERT_TRUE(FillFakeMetadata(flashed_builder.get(), manifest_, flashed_slot_suffix)); + + // Deliberately remove a partition from this build so that + // InitiateMerge do not switch state to "merging". This is possible in + // practice because the list of dynamic partitions may change. + ASSERT_NE(nullptr, flashed_builder->FindPartition("prd" + flashed_slot_suffix)); + flashed_builder->RemovePartition("prd" + flashed_slot_suffix); + + // Note that fastbootd always updates the partition table of both slots. + auto flashed_metadata = flashed_builder->Export(); + ASSERT_NE(nullptr, flashed_metadata); + ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *flashed_metadata, 0)); + ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *flashed_metadata, 1)); + + std::string path; + for (const auto& name : {"sys", "vnd"}) { + ASSERT_TRUE(CreateLogicalPartition( + CreateLogicalPartitionParams{ + .block_device = fake_super, + .metadata_slot = flashed_slot, + .partition_name = name + flashed_slot_suffix, + .timeout_ms = 1s, + .partition_opener = opener_.get(), + }, + &path)); + ASSERT_TRUE(WriteRandomData(path)); + auto hash = GetHash(path); + ASSERT_TRUE(hash.has_value()); + hashes_[name + flashed_slot_suffix] = *hash; + } + + // Simulate shutting down the device after flash. + ASSERT_TRUE(UnmapAll()); + + // Simulate reboot. After reboot, init does first stage mount. + auto init = NewManagerForFirstStageMount(flashed_slot_suffix); + ASSERT_NE(init, nullptr); + + if (flashed_slot && after_merge) { + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + } + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + // Check that the target partitions have the same content. + for (const auto& name : {"sys", "vnd"}) { + ASSERT_TRUE(IsPartitionUnchanged(name + flashed_slot_suffix)); + } + + // There should be no snapshot to merge. + auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, flashed_slot_suffix)); + if (flashed_slot == 0 && after_merge) { + ASSERT_EQ(UpdateState::MergeCompleted, new_sm->ProcessUpdateState()); + } else { + // update_engine calls ProcessUpdateState first -- should see Cancelled. + ASSERT_EQ(UpdateState::Cancelled, new_sm->ProcessUpdateState()); + } + + // Next OTA calls CancelUpdate no matter what. + ASSERT_TRUE(new_sm->CancelUpdate()); +} + +INSTANTIATE_TEST_SUITE_P(Snapshot, FlashAfterUpdateTest, Combine(Values(0, 1), Bool()), + [](const TestParamInfo& info) { + return "Flash"s + (std::get<0>(info.param) ? "New"s : "Old"s) + + "Slot"s + (std::get<1>(info.param) ? "After"s : "Before"s) + + "Merge"s; + }); + +// Test behavior of ImageManager::Create on low space scenario. These tests assumes image manager +// uses /data as backup device. +class ImageManagerTest : public SnapshotTest, public WithParamInterface { + protected: + void SetUp() override { + SKIP_IF_NON_VIRTUAL_AB(); + SnapshotTest::SetUp(); + userdata_ = std::make_unique(); + ASSERT_TRUE(userdata_->Init(GetParam())); + } + void TearDown() override { + RETURN_IF_NON_VIRTUAL_AB(); + return; // BUG(149738928) + + EXPECT_TRUE(!image_manager_->BackingImageExists(kImageName) || + image_manager_->DeleteBackingImage(kImageName)); + } + static constexpr const char* kImageName = "my_image"; + std::unique_ptr userdata_; +}; + +TEST_P(ImageManagerTest, CreateImageEnoughAvailSpace) { + if (userdata_->available_space() == 0) { + GTEST_SKIP() << "/data is full (" << userdata_->available_space() + << " bytes available), skipping"; + } + ASSERT_TRUE(image_manager_->CreateBackingImage(kImageName, userdata_->available_space(), + IImageManager::CREATE_IMAGE_DEFAULT)) + << "Should be able to create image with size = " << userdata_->available_space() + << " bytes"; + ASSERT_TRUE(image_manager_->DeleteBackingImage(kImageName)) + << "Should be able to delete created image"; +} + +TEST_P(ImageManagerTest, CreateImageNoSpace) { + uint64_t to_allocate = userdata_->free_space() + userdata_->bsize(); + auto res = image_manager_->CreateBackingImage(kImageName, to_allocate, + IImageManager::CREATE_IMAGE_DEFAULT); + ASSERT_FALSE(res) << "Should not be able to create image with size = " << to_allocate + << " bytes because only " << userdata_->free_space() << " bytes are free"; + ASSERT_EQ(FiemapStatus::ErrorCode::NO_SPACE, res.error_code()) << res.string(); +} + +std::vector ImageManagerTestParams() { + std::vector ret; + for (uint64_t size = 1_MiB; size <= 512_MiB; size *= 2) { + ret.push_back(size); + } + return ret; +} + +INSTANTIATE_TEST_SUITE_P(ImageManagerTest, ImageManagerTest, ValuesIn(ImageManagerTestParams())); + +bool Mkdir(const std::string& path) { + if (mkdir(path.c_str(), 0700) && errno != EEXIST) { + std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl; + return false; + } + return true; +} + +class SnapshotTestEnvironment : public ::testing::Environment { + public: + ~SnapshotTestEnvironment() override {} + void SetUp() override; + void TearDown() override; + + private: + bool CreateFakeSuper(); + + std::unique_ptr super_images_; +}; + +bool SnapshotTestEnvironment::CreateFakeSuper() { + // Create and map the fake super partition. + static constexpr int kImageFlags = + IImageManager::CREATE_IMAGE_DEFAULT | IImageManager::CREATE_IMAGE_ZERO_FILL; + if (!super_images_->CreateBackingImage("fake-super", kSuperSize, kImageFlags)) { + LOG(ERROR) << "Could not create fake super partition"; + return false; + } + if (!super_images_->MapImageDevice("fake-super", 10s, &fake_super)) { + LOG(ERROR) << "Could not map fake super partition"; + return false; + } + test_device->set_fake_super(fake_super); + return true; +} + +void SnapshotTestEnvironment::SetUp() { + // b/163082876: GTEST_SKIP in Environment will make atest report incorrect results. Until + // that is fixed, don't call GTEST_SKIP here, but instead call GTEST_SKIP in individual test + // suites. + RETURN_IF_NON_VIRTUAL_AB_MSG("Virtual A/B is not enabled, skipping global setup.\n"); + + std::vector paths = { + // clang-format off + "/data/gsi/ota/test", + "/data/gsi/ota/test/super", + "/metadata/gsi/ota/test", + "/metadata/gsi/ota/test/super", + "/metadata/ota/test", + "/metadata/ota/test/snapshots", + // clang-format on + }; + for (const auto& path : paths) { + ASSERT_TRUE(Mkdir(path)); + } + + // Create this once, otherwise, gsid will start/stop between each test. + test_device = new TestDeviceInfo(); + sm = SnapshotManager::New(test_device); + ASSERT_NE(nullptr, sm) << "Could not create snapshot manager"; + + // Use a separate image manager for our fake super partition. + super_images_ = IImageManager::Open("ota/test/super", 10s); + ASSERT_NE(nullptr, super_images_) << "Could not create image manager"; + + // Map the old image if one exists so we can safely unmap everything that + // depends on it. + bool recreate_fake_super; + if (super_images_->BackingImageExists("fake-super")) { + if (super_images_->IsImageMapped("fake-super")) { + ASSERT_TRUE(super_images_->GetMappedImageDevice("fake-super", &fake_super)); + } else { + ASSERT_TRUE(super_images_->MapImageDevice("fake-super", 10s, &fake_super)); + } + test_device->set_fake_super(fake_super); + recreate_fake_super = true; + } else { + ASSERT_TRUE(CreateFakeSuper()); + recreate_fake_super = false; + } + + // Clean up previous run. + MetadataMountedTest().TearDown(); + SnapshotUpdateTest().Cleanup(); + SnapshotTest().Cleanup(); + + if (recreate_fake_super) { + // Clean up any old copy. + DeleteBackingImage(super_images_.get(), "fake-super"); + ASSERT_TRUE(CreateFakeSuper()); + } +} + +void SnapshotTestEnvironment::TearDown() { + RETURN_IF_NON_VIRTUAL_AB(); + if (super_images_ != nullptr) { + DeleteBackingImage(super_images_.get(), "fake-super"); + } +} + +} // namespace snapshot +} // namespace android + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(new ::android::snapshot::SnapshotTestEnvironment()); + + android::base::SetProperty("ctl.stop", "snapuserd"); + android::base::SetProperty("snapuserd.test.dm.snapshots", "0"); + + return RUN_ALL_TESTS(); +} From a781512188dd1a975a40f29a5d1aa52ea42fb8f7 Mon Sep 17 00:00:00 2001 From: Akilesh Kailash Date: Wed, 18 Aug 2021 00:10:43 +0000 Subject: [PATCH 3/3] snapuserd: Add unit test for test merge code path Most of the test cases are similar to dm-snapshot-merge. Additional test cases have been added primarily to test I/O's in parallel with merge. Bug: 193863397 Test: snapuserd_test Signed-off-by: Akilesh Kailash Change-Id: I2764e6971989c121be873cc425cac464f31ce85f --- fs_mgr/libsnapshot/snapuserd/Android.bp | 36 + .../user-space-merge/snapuserd_test.cpp | 861 ++++++++++++++++++ 2 files changed, 897 insertions(+) create mode 100644 fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp index c9b05124b..93b0f7c28 100644 --- a/fs_mgr/libsnapshot/snapuserd/Android.bp +++ b/fs_mgr/libsnapshot/snapuserd/Android.bp @@ -142,3 +142,39 @@ cc_test { auto_gen_config: true, require_root: false, } + +cc_test { + name: "snapuserd_test", + defaults: [ + "fs_mgr_defaults", + ], + srcs: [ + "user-space-merge/snapuserd_test.cpp", + ], + cflags: [ + "-Wall", + "-Werror", + ], + shared_libs: [ + "libbase", + "liblog", + ], + static_libs: [ + "libbrotli", + "libgtest", + "libsnapshot_cow", + "libsnapshot_snapuserd", + "libcutils_sockets", + "libz", + "libfs_mgr", + "libdm", + "libext4_utils", + ], + header_libs: [ + "libstorage_literals_headers", + "libfiemap_headers", + ], + test_min_api_level: 30, + auto_gen_config: true, + require_root: false, +} diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp new file mode 100644 index 000000000..1c3e04b89 --- /dev/null +++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp @@ -0,0 +1,861 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "snapuserd_core.h" + +namespace android { +namespace snapshot { + +using namespace android::storage_literals; +using android::base::unique_fd; +using LoopDevice = android::dm::LoopDevice; +using namespace std::chrono_literals; +using namespace android::dm; +using namespace std; + +static constexpr char kSnapuserdSocketTest[] = "snapuserdTest"; + +class Tempdevice { + public: + Tempdevice(const std::string& name, const DmTable& table) + : dm_(DeviceMapper::Instance()), name_(name), valid_(false) { + valid_ = dm_.CreateDevice(name, table, &path_, std::chrono::seconds(5)); + } + Tempdevice(Tempdevice&& other) noexcept + : dm_(other.dm_), name_(other.name_), path_(other.path_), valid_(other.valid_) { + other.valid_ = false; + } + ~Tempdevice() { + if (valid_) { + dm_.DeleteDevice(name_); + } + } + bool Destroy() { + if (!valid_) { + return false; + } + valid_ = false; + return dm_.DeleteDevice(name_); + } + const std::string& path() const { return path_; } + const std::string& name() const { return name_; } + bool valid() const { return valid_; } + + Tempdevice(const Tempdevice&) = delete; + Tempdevice& operator=(const Tempdevice&) = delete; + + Tempdevice& operator=(Tempdevice&& other) noexcept { + name_ = other.name_; + valid_ = other.valid_; + other.valid_ = false; + return *this; + } + + private: + DeviceMapper& dm_; + std::string name_; + std::string path_; + bool valid_; +}; + +class SnapuserTest final { + public: + bool Setup(); + bool SetupOrderedOps(); + bool SetupOrderedOpsInverted(); + bool SetupCopyOverlap_1(); + bool SetupCopyOverlap_2(); + bool Merge(); + void ValidateMerge(); + void ReadSnapshotDeviceAndValidate(); + void Shutdown(); + void MergeInterrupt(); + void MergeInterruptFixed(int duration); + void MergeInterruptRandomly(int max_duration); + void StartMerge(); + void CheckMergeCompletion(); + + static const uint64_t kSectorSize = 512; + + private: + void SetupImpl(); + + void SimulateDaemonRestart(); + + void CreateCowDevice(); + void CreateCowDeviceOrderedOps(); + void CreateCowDeviceOrderedOpsInverted(); + void CreateCowDeviceWithCopyOverlap_1(); + void CreateCowDeviceWithCopyOverlap_2(); + bool SetupDaemon(); + void CreateBaseDevice(); + void InitCowDevice(); + void SetDeviceControlName(); + void InitDaemon(); + void CreateDmUserDevice(); + void StartSnapuserdDaemon(); + + unique_ptr base_loop_; + unique_ptr dmuser_dev_; + + std::string system_device_ctrl_name_; + std::string system_device_name_; + + unique_fd base_fd_; + std::unique_ptr cow_system_; + std::unique_ptr client_; + std::unique_ptr orig_buffer_; + std::unique_ptr merged_buffer_; + bool setup_ok_ = false; + bool merge_ok_ = false; + size_t size_ = 100_MiB; + int cow_num_sectors_; + int total_base_size_; +}; + +static unique_fd CreateTempFile(const std::string& name, size_t size) { + unique_fd fd(syscall(__NR_memfd_create, name.c_str(), MFD_ALLOW_SEALING)); + if (fd < 0) { + return {}; + } + if (size) { + if (ftruncate(fd, size) < 0) { + perror("ftruncate"); + return {}; + } + if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) { + perror("fcntl"); + return {}; + } + } + return fd; +} + +void SnapuserTest::Shutdown() { + ASSERT_TRUE(dmuser_dev_->Destroy()); + + auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_; + ASSERT_TRUE(client_->WaitForDeviceDelete(system_device_ctrl_name_)); + ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted(misc_device, 10s)); + ASSERT_TRUE(client_->DetachSnapuserd()); +} + +bool SnapuserTest::Setup() { + SetupImpl(); + return setup_ok_; +} + +bool SnapuserTest::SetupOrderedOps() { + CreateBaseDevice(); + CreateCowDeviceOrderedOps(); + return SetupDaemon(); +} + +bool SnapuserTest::SetupOrderedOpsInverted() { + CreateBaseDevice(); + CreateCowDeviceOrderedOpsInverted(); + return SetupDaemon(); +} + +bool SnapuserTest::SetupCopyOverlap_1() { + CreateBaseDevice(); + CreateCowDeviceWithCopyOverlap_1(); + return SetupDaemon(); +} + +bool SnapuserTest::SetupCopyOverlap_2() { + CreateBaseDevice(); + CreateCowDeviceWithCopyOverlap_2(); + return SetupDaemon(); +} + +bool SnapuserTest::SetupDaemon() { + SetDeviceControlName(); + + StartSnapuserdDaemon(); + + CreateDmUserDevice(); + InitCowDevice(); + InitDaemon(); + + setup_ok_ = true; + + return setup_ok_; +} + +void SnapuserTest::StartSnapuserdDaemon() { + pid_t pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + std::string arg0 = "/system/bin/snapuserd"; + std::string arg1 = "-socket="s + kSnapuserdSocketTest; + char* const argv[] = {arg0.data(), arg1.data(), nullptr}; + ASSERT_GE(execv(arg0.c_str(), argv), 0); + } else { + client_ = SnapuserdClient::Connect(kSnapuserdSocketTest, 10s); + ASSERT_NE(client_, nullptr); + } +} + +void SnapuserTest::CreateBaseDevice() { + unique_fd rnd_fd; + + total_base_size_ = (size_ * 5); + base_fd_ = CreateTempFile("base_device", total_base_size_); + ASSERT_GE(base_fd_, 0); + + rnd_fd.reset(open("/dev/random", O_RDONLY)); + ASSERT_TRUE(rnd_fd > 0); + + std::unique_ptr random_buffer = std::make_unique(1_MiB); + + for (size_t j = 0; j < ((total_base_size_) / 1_MiB); j++) { + ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true); + ASSERT_EQ(android::base::WriteFully(base_fd_, random_buffer.get(), 1_MiB), true); + } + + ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0); + + base_loop_ = std::make_unique(base_fd_, 10s); + ASSERT_TRUE(base_loop_->valid()); +} + +void SnapuserTest::ReadSnapshotDeviceAndValidate() { + unique_fd fd(open(dmuser_dev_->path().c_str(), O_RDONLY)); + ASSERT_GE(fd, 0); + std::unique_ptr snapuserd_buffer = std::make_unique(size_); + + // COPY + loff_t offset = 0; + ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true); + ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), size_), 0); + + // REPLACE + offset += size_; + ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true); + ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + size_, size_), 0); + + // ZERO + offset += size_; + ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true); + ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 2), size_), 0); + + // REPLACE + offset += size_; + ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true); + ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0); + + // XOR + offset += size_; + ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true); + ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 4), size_), 0); +} + +void SnapuserTest::CreateCowDeviceWithCopyOverlap_2() { + std::string path = android::base::GetExecutableDirectory(); + cow_system_ = std::make_unique(path); + + CowOptions options; + options.compression = "gz"; + CowWriter writer(options); + + ASSERT_TRUE(writer.Initialize(cow_system_->fd)); + + size_t num_blocks = size_ / options.block_size; + size_t x = num_blocks; + size_t blk_src_copy = 0; + + // Create overlapping copy operations + while (1) { + ASSERT_TRUE(writer.AddCopy(blk_src_copy, blk_src_copy + 1)); + x -= 1; + if (x == 1) { + break; + } + blk_src_copy += 1; + } + + // Flush operations + ASSERT_TRUE(writer.Finalize()); + + // Construct the buffer required for validation + orig_buffer_ = std::make_unique(total_base_size_); + + // Read the entire base device + ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0), + true); + + // Merged operations required for validation + int block_size = 4096; + x = num_blocks; + loff_t src_offset = block_size; + loff_t dest_offset = 0; + + while (1) { + memmove((char*)orig_buffer_.get() + dest_offset, (char*)orig_buffer_.get() + src_offset, + block_size); + x -= 1; + if (x == 1) { + break; + } + src_offset += block_size; + dest_offset += block_size; + } +} + +void SnapuserTest::CreateCowDeviceWithCopyOverlap_1() { + std::string path = android::base::GetExecutableDirectory(); + cow_system_ = std::make_unique(path); + + CowOptions options; + options.compression = "gz"; + CowWriter writer(options); + + ASSERT_TRUE(writer.Initialize(cow_system_->fd)); + + size_t num_blocks = size_ / options.block_size; + size_t x = num_blocks; + size_t blk_src_copy = num_blocks - 1; + + // Create overlapping copy operations + while (1) { + ASSERT_TRUE(writer.AddCopy(blk_src_copy + 1, blk_src_copy)); + x -= 1; + if (x == 0) { + ASSERT_EQ(blk_src_copy, 0); + break; + } + blk_src_copy -= 1; + } + + // Flush operations + ASSERT_TRUE(writer.Finalize()); + + // Construct the buffer required for validation + orig_buffer_ = std::make_unique(total_base_size_); + + // Read the entire base device + ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0), + true); + + // Merged operations + ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), options.block_size, 0), + true); + ASSERT_EQ(android::base::ReadFullyAtOffset( + base_fd_, (char*)orig_buffer_.get() + options.block_size, size_, 0), + true); +} + +void SnapuserTest::CreateCowDeviceOrderedOpsInverted() { + unique_fd rnd_fd; + loff_t offset = 0; + + std::string path = android::base::GetExecutableDirectory(); + cow_system_ = std::make_unique(path); + + rnd_fd.reset(open("/dev/random", O_RDONLY)); + ASSERT_TRUE(rnd_fd > 0); + + std::unique_ptr random_buffer_1_ = std::make_unique(size_); + + // Fill random data + for (size_t j = 0; j < (size_ / 1_MiB); j++) { + ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0), + true); + + offset += 1_MiB; + } + + CowOptions options; + options.compression = "gz"; + CowWriter writer(options); + + ASSERT_TRUE(writer.Initialize(cow_system_->fd)); + + size_t num_blocks = size_ / options.block_size; + size_t blk_end_copy = num_blocks * 3; + size_t source_blk = num_blocks - 1; + size_t blk_src_copy = blk_end_copy - 1; + uint16_t xor_offset = 5; + + size_t x = num_blocks; + while (1) { + ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy)); + x -= 1; + if (x == 0) { + break; + } + source_blk -= 1; + blk_src_copy -= 1; + } + + for (size_t i = num_blocks; i > 0; i--) { + ASSERT_TRUE(writer.AddXorBlocks(num_blocks + i - 1, + &random_buffer_1_.get()[options.block_size * (i - 1)], + options.block_size, 2 * num_blocks + i - 1, xor_offset)); + } + // Flush operations + ASSERT_TRUE(writer.Finalize()); + // Construct the buffer required for validation + orig_buffer_ = std::make_unique(total_base_size_); + // Read the entire base device + ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0), + true); + // Merged Buffer + memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + 2 * size_, size_); + memmove(orig_buffer_.get() + size_, (char*)orig_buffer_.get() + 2 * size_ + xor_offset, size_); + for (int i = 0; i < size_; i++) { + orig_buffer_.get()[size_ + i] ^= random_buffer_1_.get()[i]; + } +} + +void SnapuserTest::CreateCowDeviceOrderedOps() { + unique_fd rnd_fd; + loff_t offset = 0; + + std::string path = android::base::GetExecutableDirectory(); + cow_system_ = std::make_unique(path); + + rnd_fd.reset(open("/dev/random", O_RDONLY)); + ASSERT_TRUE(rnd_fd > 0); + + std::unique_ptr random_buffer_1_ = std::make_unique(size_); + + // Fill random data + for (size_t j = 0; j < (size_ / 1_MiB); j++) { + ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0), + true); + + offset += 1_MiB; + } + memset(random_buffer_1_.get(), 0, size_); + + CowOptions options; + options.compression = "gz"; + CowWriter writer(options); + + ASSERT_TRUE(writer.Initialize(cow_system_->fd)); + + size_t num_blocks = size_ / options.block_size; + size_t x = num_blocks; + size_t source_blk = 0; + size_t blk_src_copy = 2 * num_blocks; + uint16_t xor_offset = 5; + + while (1) { + ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy)); + + x -= 1; + if (x == 0) { + break; + } + source_blk += 1; + blk_src_copy += 1; + } + + ASSERT_TRUE(writer.AddXorBlocks(num_blocks, random_buffer_1_.get(), size_, 2 * num_blocks, + xor_offset)); + // Flush operations + ASSERT_TRUE(writer.Finalize()); + // Construct the buffer required for validation + orig_buffer_ = std::make_unique(total_base_size_); + // Read the entire base device + ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0), + true); + // Merged Buffer + memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + 2 * size_, size_); + memmove(orig_buffer_.get() + size_, (char*)orig_buffer_.get() + 2 * size_ + xor_offset, size_); + for (int i = 0; i < size_; i++) { + orig_buffer_.get()[size_ + i] ^= random_buffer_1_.get()[i]; + } +} + +void SnapuserTest::CreateCowDevice() { + unique_fd rnd_fd; + loff_t offset = 0; + + std::string path = android::base::GetExecutableDirectory(); + cow_system_ = std::make_unique(path); + + rnd_fd.reset(open("/dev/random", O_RDONLY)); + ASSERT_TRUE(rnd_fd > 0); + + std::unique_ptr random_buffer_1_ = std::make_unique(size_); + + // Fill random data + for (size_t j = 0; j < (size_ / 1_MiB); j++) { + ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0), + true); + + offset += 1_MiB; + } + + CowOptions options; + options.compression = "gz"; + CowWriter writer(options); + + ASSERT_TRUE(writer.Initialize(cow_system_->fd)); + + size_t num_blocks = size_ / options.block_size; + size_t blk_end_copy = num_blocks * 2; + size_t source_blk = num_blocks - 1; + size_t blk_src_copy = blk_end_copy - 1; + + uint32_t sequence[num_blocks * 2]; + // Sequence for Copy ops + for (int i = 0; i < num_blocks; i++) { + sequence[i] = num_blocks - 1 - i; + } + // Sequence for Xor ops + for (int i = 0; i < num_blocks; i++) { + sequence[num_blocks + i] = 5 * num_blocks - 1 - i; + } + ASSERT_TRUE(writer.AddSequenceData(2 * num_blocks, sequence)); + + size_t x = num_blocks; + while (1) { + ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy)); + x -= 1; + if (x == 0) { + break; + } + source_blk -= 1; + blk_src_copy -= 1; + } + + source_blk = num_blocks; + blk_src_copy = blk_end_copy; + + ASSERT_TRUE(writer.AddRawBlocks(source_blk, random_buffer_1_.get(), size_)); + + size_t blk_zero_copy_start = source_blk + num_blocks; + size_t blk_zero_copy_end = blk_zero_copy_start + num_blocks; + + ASSERT_TRUE(writer.AddZeroBlocks(blk_zero_copy_start, num_blocks)); + + size_t blk_random2_replace_start = blk_zero_copy_end; + + ASSERT_TRUE(writer.AddRawBlocks(blk_random2_replace_start, random_buffer_1_.get(), size_)); + + size_t blk_xor_start = blk_random2_replace_start + num_blocks; + size_t xor_offset = BLOCK_SZ / 2; + ASSERT_TRUE(writer.AddXorBlocks(blk_xor_start, random_buffer_1_.get(), size_, num_blocks, + xor_offset)); + + // Flush operations + ASSERT_TRUE(writer.Finalize()); + // Construct the buffer required for validation + orig_buffer_ = std::make_unique(total_base_size_); + std::string zero_buffer(size_, 0); + ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), size_, size_), true); + memcpy((char*)orig_buffer_.get() + size_, random_buffer_1_.get(), size_); + memcpy((char*)orig_buffer_.get() + (size_ * 2), (void*)zero_buffer.c_str(), size_); + memcpy((char*)orig_buffer_.get() + (size_ * 3), random_buffer_1_.get(), size_); + ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, &orig_buffer_.get()[size_ * 4], size_, + size_ + xor_offset), + true); + for (int i = 0; i < size_; i++) { + orig_buffer_.get()[(size_ * 4) + i] = + (uint8_t)(orig_buffer_.get()[(size_ * 4) + i] ^ random_buffer_1_.get()[i]); + } +} + +void SnapuserTest::InitCowDevice() { + uint64_t num_sectors = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path, + base_loop_->device(), base_loop_->device()); + ASSERT_NE(num_sectors, 0); +} + +void SnapuserTest::SetDeviceControlName() { + system_device_name_.clear(); + system_device_ctrl_name_.clear(); + + std::string str(cow_system_->path); + std::size_t found = str.find_last_of("/\\"); + ASSERT_NE(found, std::string::npos); + system_device_name_ = str.substr(found + 1); + + system_device_ctrl_name_ = system_device_name_ + "-ctrl"; +} + +void SnapuserTest::CreateDmUserDevice() { + unique_fd fd(TEMP_FAILURE_RETRY(open(base_loop_->device().c_str(), O_RDONLY | O_CLOEXEC))); + ASSERT_TRUE(fd > 0); + + uint64_t dev_sz = get_block_device_size(fd.get()); + ASSERT_TRUE(dev_sz > 0); + + cow_num_sectors_ = dev_sz >> 9; + + DmTable dmuser_table; + ASSERT_TRUE(dmuser_table.AddTarget( + std::make_unique(0, cow_num_sectors_, system_device_ctrl_name_))); + ASSERT_TRUE(dmuser_table.valid()); + + dmuser_dev_ = std::make_unique(system_device_name_, dmuser_table); + ASSERT_TRUE(dmuser_dev_->valid()); + ASSERT_FALSE(dmuser_dev_->path().empty()); + + auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_; + ASSERT_TRUE(android::fs_mgr::WaitForFile(misc_device, 10s)); +} + +void SnapuserTest::InitDaemon() { + bool ok = client_->AttachDmUser(system_device_ctrl_name_); + ASSERT_TRUE(ok); +} + +void SnapuserTest::CheckMergeCompletion() { + while (true) { + double percentage = client_->GetMergePercent(); + if ((int)percentage == 100) { + break; + } + + std::this_thread::sleep_for(1s); + } +} + +void SnapuserTest::SetupImpl() { + CreateBaseDevice(); + CreateCowDevice(); + + SetDeviceControlName(); + + StartSnapuserdDaemon(); + + CreateDmUserDevice(); + InitCowDevice(); + InitDaemon(); + + setup_ok_ = true; +} + +bool SnapuserTest::Merge() { + StartMerge(); + CheckMergeCompletion(); + merge_ok_ = true; + return merge_ok_; +} + +void SnapuserTest::StartMerge() { + bool ok = client_->InitiateMerge(system_device_ctrl_name_); + ASSERT_TRUE(ok); +} + +void SnapuserTest::ValidateMerge() { + merged_buffer_ = std::make_unique(total_base_size_); + ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, merged_buffer_.get(), total_base_size_, 0), + true); + ASSERT_EQ(memcmp(merged_buffer_.get(), orig_buffer_.get(), total_base_size_), 0); +} + +void SnapuserTest::SimulateDaemonRestart() { + Shutdown(); + std::this_thread::sleep_for(500ms); + SetDeviceControlName(); + StartSnapuserdDaemon(); + CreateDmUserDevice(); + InitCowDevice(); + InitDaemon(); +} + +void SnapuserTest::MergeInterruptRandomly(int max_duration) { + std::srand(std::time(nullptr)); + StartMerge(); + + for (int i = 0; i < 20; i++) { + int duration = std::rand() % max_duration; + std::this_thread::sleep_for(std::chrono::milliseconds(duration)); + SimulateDaemonRestart(); + StartMerge(); + } + + SimulateDaemonRestart(); + ASSERT_TRUE(Merge()); +} + +void SnapuserTest::MergeInterruptFixed(int duration) { + StartMerge(); + + for (int i = 0; i < 25; i++) { + std::this_thread::sleep_for(std::chrono::milliseconds(duration)); + SimulateDaemonRestart(); + StartMerge(); + } + + SimulateDaemonRestart(); + ASSERT_TRUE(Merge()); +} + +void SnapuserTest::MergeInterrupt() { + // Interrupt merge at various intervals + StartMerge(); + std::this_thread::sleep_for(250ms); + SimulateDaemonRestart(); + + StartMerge(); + std::this_thread::sleep_for(250ms); + SimulateDaemonRestart(); + + StartMerge(); + std::this_thread::sleep_for(150ms); + SimulateDaemonRestart(); + + StartMerge(); + std::this_thread::sleep_for(100ms); + SimulateDaemonRestart(); + + StartMerge(); + std::this_thread::sleep_for(800ms); + SimulateDaemonRestart(); + + StartMerge(); + std::this_thread::sleep_for(600ms); + SimulateDaemonRestart(); + + ASSERT_TRUE(Merge()); +} + +TEST(Snapuserd_Test, Snapshot_IO_TEST) { + SnapuserTest harness; + ASSERT_TRUE(harness.Setup()); + // I/O before merge + harness.ReadSnapshotDeviceAndValidate(); + ASSERT_TRUE(harness.Merge()); + harness.ValidateMerge(); + // I/O after merge - daemon should read directly + // from base device + harness.ReadSnapshotDeviceAndValidate(); + harness.Shutdown(); +} + +TEST(Snapuserd_Test, Snapshot_MERGE_IO_TEST) { + SnapuserTest harness; + ASSERT_TRUE(harness.Setup()); + // Issue I/O before merge begins + std::async(std::launch::async, &SnapuserTest::ReadSnapshotDeviceAndValidate, &harness); + // Start the merge + ASSERT_TRUE(harness.Merge()); + harness.ValidateMerge(); + harness.Shutdown(); +} + +TEST(Snapuserd_Test, Snapshot_MERGE_IO_TEST_1) { + SnapuserTest harness; + ASSERT_TRUE(harness.Setup()); + // Start the merge + harness.StartMerge(); + // Issue I/O in parallel when merge is in-progress + std::async(std::launch::async, &SnapuserTest::ReadSnapshotDeviceAndValidate, &harness); + harness.CheckMergeCompletion(); + harness.ValidateMerge(); + harness.Shutdown(); +} + +TEST(Snapuserd_Test, Snapshot_Merge_Resume) { + SnapuserTest harness; + ASSERT_TRUE(harness.Setup()); + harness.MergeInterrupt(); + harness.ValidateMerge(); + harness.Shutdown(); +} + +TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_1) { + SnapuserTest harness; + ASSERT_TRUE(harness.SetupCopyOverlap_1()); + ASSERT_TRUE(harness.Merge()); + harness.ValidateMerge(); + harness.Shutdown(); +} + +TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_2) { + SnapuserTest harness; + ASSERT_TRUE(harness.SetupCopyOverlap_2()); + ASSERT_TRUE(harness.Merge()); + harness.ValidateMerge(); + harness.Shutdown(); +} + +TEST(Snapuserd_Test, Snapshot_COPY_Overlap_Merge_Resume_TEST) { + SnapuserTest harness; + ASSERT_TRUE(harness.SetupCopyOverlap_1()); + harness.MergeInterrupt(); + harness.ValidateMerge(); + harness.Shutdown(); +} + +TEST(Snapuserd_Test, Snapshot_Merge_Crash_Fixed_Ordered) { + SnapuserTest harness; + ASSERT_TRUE(harness.SetupOrderedOps()); + harness.MergeInterruptFixed(300); + harness.ValidateMerge(); + harness.Shutdown(); +} + +TEST(Snapuserd_Test, Snapshot_Merge_Crash_Random_Ordered) { + SnapuserTest harness; + ASSERT_TRUE(harness.SetupOrderedOps()); + harness.MergeInterruptRandomly(500); + harness.ValidateMerge(); + harness.Shutdown(); +} + +TEST(Snapuserd_Test, Snapshot_Merge_Crash_Fixed_Inverted) { + SnapuserTest harness; + ASSERT_TRUE(harness.SetupOrderedOpsInverted()); + harness.MergeInterruptFixed(50); + harness.ValidateMerge(); + harness.Shutdown(); +} + +TEST(Snapuserd_Test, Snapshot_Merge_Crash_Random_Inverted) { + SnapuserTest harness; + ASSERT_TRUE(harness.SetupOrderedOpsInverted()); + harness.MergeInterruptRandomly(50); + harness.ValidateMerge(); + harness.Shutdown(); +} + +} // namespace snapshot +} // namespace android + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}