From 20a23bb41c7ec59ff416eebf3a3012e79baef10c Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Tue, 27 Aug 2019 14:25:31 -0700 Subject: [PATCH] libsnapshot: Also use empty space in super for COW The super partition usually has some empty space even after the target partitions are created, especially for retrofit Virtual A/B devices. Use that empty space for COW before taking up userdata space. - PartitionCowCreator computes free regions in super partition metadata and use that space until it is used up. It returns a pair of numbers (cow_partition_size, cow_file_size) and let SnapshotManager to create the partition / images with proper sizes. - A region is considered free iff it is used by NEITHER target NOR source partitions - The list is in PartitionCowCreator's return value so that SnapshotManager can use it as a guide when creating partitions. - These partitions are created under the group named "cow". - Avoid mapping COW partitions directly during init first stage mount. Init only maps them when they are needed by the top-level device. - CreateCowImage no longer zero-fills the first 4 bytes of the image. (See below) - CreateUpdatePartitions: after creating the snapshot, also maps the COW devices (via MapCowDevices()) and zero-fills the first 4 bytes of the top-level COW device. - Add a new SnapshotManager::MapCowDevices() function, which maps both the COW partition (in super) and the COW image (through IImageManager) (if necessary). Then, create a dm-linear target that concatenates them (if necessary). - Add a new SnapshotManager::UnmapCowDevices() functions that does the reverse MapCowDevices(). - DeleteSnapshot also unmaps the top-level COW device and COW partition before unmapping the COW images (before deleting them). Test: libsnapshot_test Change-Id: I0098b7e842ab48b0b4dd2a59142098b098d23d34 --- .../include/libsnapshot/snapshot.h | 27 +- fs_mgr/libsnapshot/partition_cow_creator.cpp | 36 ++- fs_mgr/libsnapshot/partition_cow_creator.h | 2 + fs_mgr/libsnapshot/snapshot.cpp | 267 ++++++++++++++---- fs_mgr/libsnapshot/snapshot_test.cpp | 58 +++- fs_mgr/libsnapshot/utility.cpp | 34 +++ fs_mgr/libsnapshot/utility.h | 3 + 7 files changed, 356 insertions(+), 71 deletions(-) diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h index 660f60e94..bb6ff4ee8 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,9 @@ namespace snapshot { struct AutoDeleteCowImage; struct AutoDeleteSnapshot; struct PartitionCowCreator; +struct AutoDeviceList; + +static constexpr const std::string_view kCowGroupName = "cow"; enum class UpdateState : unsigned int { // No update or merge is in progress. @@ -266,9 +270,8 @@ class SnapshotManager final { // be mapped with two table entries: a dm-snapshot range covering // snapshot_size, and a dm-linear range covering the remainder. // - // All sizes are specified in bytes, and the device, snapshot and COW partition sizes - // must be a multiple of the sector size (512 bytes). COW file size will be rounded up - // to the nearest sector. + // All sizes are specified in bytes, and the device, snapshot, COW partition and COW file sizes + // must be a multiple of the sector size (512 bytes). bool CreateSnapshot(LockedFile* lock, const std::string& name, SnapshotStatus status); // |name| should be the base partition name (e.g. "system_a"). Create the @@ -287,8 +290,7 @@ class SnapshotManager final { std::string* dev_path); // Map a COW image that was previous created with CreateCowImage. - bool MapCowImage(const std::string& name, const std::chrono::milliseconds& timeout_ms, - std::string* cow_image_device); + bool MapCowImage(const std::string& name, const std::chrono::milliseconds& timeout_ms); // Remove the backing copy-on-write image and snapshot states for the named snapshot. The // caller is responsible for ensuring that the snapshot is unmapped. @@ -369,6 +371,21 @@ class SnapshotManager final { bool MapPartitionWithSnapshot(LockedFile* lock, CreateLogicalPartitionParams params, std::string* path); + // Map the COW devices, including the partition in super and the images. + // |params|: + // - |partition_name| should be the name of the top-level partition (e.g. system_b), + // not system_b-cow-img + // - |device_name| and |partition| is ignored + // - |timeout_ms| and the rest is respected + // Return the path in |cow_device_path| (e.g. /dev/block/dm-1) and major:minor in + // |cow_device_string| + bool MapCowDevices(LockedFile* lock, const CreateLogicalPartitionParams& params, + const SnapshotStatus& snapshot_status, AutoDeviceList* created_devices, + std::string* cow_name); + + // The reverse of MapCowDevices. + bool UnmapCowDevices(LockedFile* lock, const std::string& name); + // The reverse of MapPartitionWithSnapshot. bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name); diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp index dd4116bee..44d886093 100644 --- a/fs_mgr/libsnapshot/partition_cow_creator.cpp +++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp @@ -20,7 +20,9 @@ #include "utility.h" +using android::dm::kSectorSize; using android::fs_mgr::Extent; +using android::fs_mgr::Interval; using android::fs_mgr::kDefaultBlockSize; using android::fs_mgr::Partition; @@ -44,8 +46,8 @@ static std::unique_ptr Intersect(Extent* target_extent, Extent* existing auto target_linear_extent = target_extent->AsLinearExtent(); if (!target_linear_extent) return nullptr; - return android::fs_mgr::Interval::Intersect(target_linear_extent->AsInterval(), - existing_linear_extent->AsInterval()) + return Interval::Intersect(target_linear_extent->AsInterval(), + existing_linear_extent->AsInterval()) .AsExtent(); } @@ -121,6 +123,10 @@ std::optional PartitionCowCreator::Run() { CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME && target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME); + uint64_t logical_block_size = current_metadata->logical_block_size(); + CHECK(logical_block_size != 0 && !(logical_block_size & (logical_block_size - 1))) + << "logical_block_size is not power of 2"; + Return ret; ret.snapshot_status.device_size = target_partition->size(); @@ -137,9 +143,31 @@ std::optional PartitionCowCreator::Run() { RoundUp(ret.snapshot_status.snapshot_size * kCowEstimateFactor, kDefaultBlockSize); } - // TODO: create COW partition in target_metadata to save space. - ret.snapshot_status.cow_partition_size = 0; + // Compute regions that are free in both current and target metadata. These are the regions + // we can use for COW partition. + auto target_free_regions = target_metadata->GetFreeRegions(); + auto current_free_regions = current_metadata->GetFreeRegions(); + auto free_regions = Interval::Intersect(target_free_regions, current_free_regions); + uint64_t free_region_length = 0; + for (const auto& interval : free_regions) { + free_region_length += interval.length() * kSectorSize; + } + + LOG(INFO) << "Remaining free space for COW: " << free_region_length << " bytes"; + + // Compute the COW partition size. + ret.snapshot_status.cow_partition_size = std::min(*cow_size, free_region_length); + // Round it down to the nearest logical block. Logical partitions must be a multiple + // of logical blocks. + ret.snapshot_status.cow_partition_size &= ~(logical_block_size - 1); + // Assign cow_partition_usable_regions to indicate what regions should the COW partition uses. + ret.cow_partition_usable_regions = std::move(free_regions); + + // The rest of the COW space is allocated on ImageManager. ret.snapshot_status.cow_file_size = (*cow_size) - ret.snapshot_status.cow_partition_size; + // Round it up to the nearest sector. + ret.snapshot_status.cow_file_size += kSectorSize - 1; + ret.snapshot_status.cow_file_size &= ~(kSectorSize - 1); return ret; } diff --git a/fs_mgr/libsnapshot/partition_cow_creator.h b/fs_mgr/libsnapshot/partition_cow_creator.h index d9ee4905c..a2359143e 100644 --- a/fs_mgr/libsnapshot/partition_cow_creator.h +++ b/fs_mgr/libsnapshot/partition_cow_creator.h @@ -29,6 +29,7 @@ namespace snapshot { // Helper class that creates COW for a partition. struct PartitionCowCreator { using Extent = android::fs_mgr::Extent; + using Interval = android::fs_mgr::Interval; using MetadataBuilder = android::fs_mgr::MetadataBuilder; using Partition = android::fs_mgr::Partition; @@ -48,6 +49,7 @@ struct PartitionCowCreator { struct Return { SnapshotManager::SnapshotStatus snapshot_status; + std::vector cow_partition_usable_regions; }; std::optional Run(); diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp index 410074a52..111b1daa0 100644 --- a/fs_mgr/libsnapshot/snapshot.cpp +++ b/fs_mgr/libsnapshot/snapshot.cpp @@ -55,6 +55,7 @@ using android::fiemap::IImageManager; using android::fs_mgr::CreateDmTable; using android::fs_mgr::CreateLogicalPartition; using android::fs_mgr::CreateLogicalPartitionParams; +using android::fs_mgr::GetPartitionGroupName; using android::fs_mgr::GetPartitionName; using android::fs_mgr::LpMetadata; using android::fs_mgr::MetadataBuilder; @@ -105,7 +106,7 @@ SnapshotManager::SnapshotManager(IDeviceInfo* device) : device_(device) { metadata_dir_ = device_->GetMetadataDir(); } -[[maybe_unused]] static std::string GetCowName(const std::string& snapshot_name) { +static std::string GetCowName(const std::string& snapshot_name) { return snapshot_name + "-cow"; } @@ -208,7 +209,7 @@ bool SnapshotManager::CreateSnapshot(LockedFile* lock, const std::string& name, CHECK(lock->lock_mode() == LOCK_EX); // Sanity check these sizes. Like liblp, we guarantee the partition size // is respected, which means it has to be sector-aligned. (This guarantee - // is useful for locating avb footers correctly). The COW size, however, + // is useful for locating avb footers correctly). The COW file size, however, // can be arbitrarily larger than specified, so we can safely round it up. if (status.device_size % kSectorSize != 0) { LOG(ERROR) << "Snapshot " << name @@ -220,10 +221,17 @@ bool SnapshotManager::CreateSnapshot(LockedFile* lock, const std::string& name, << status.snapshot_size; return false; } - - // Round the COW size up to the nearest sector. - status.cow_file_size += kSectorSize - 1; - status.cow_file_size &= ~(kSectorSize - 1); + if (status.cow_partition_size % kSectorSize != 0) { + LOG(ERROR) << "Snapshot " << name + << " cow partition size is not a multiple of the sector size: " + << status.cow_partition_size; + return false; + } + if (status.cow_file_size % kSectorSize != 0) { + LOG(ERROR) << "Snapshot " << name << " cow file size is not a multiple of the sector size: " + << status.cow_partition_size; + return false; + } status.state = SnapshotState::Created; status.sectors_allocated = 0; @@ -256,26 +264,7 @@ bool SnapshotManager::CreateCowImage(LockedFile* lock, const std::string& name) std::string cow_image_name = GetCowImageDeviceName(name); int cow_flags = IImageManager::CREATE_IMAGE_DEFAULT; - if (!images_->CreateBackingImage(cow_image_name, status.cow_file_size, cow_flags)) { - return false; - } - - // when the kernel creates a persistent dm-snapshot, it requires a CoW file - // to store the modifications. The kernel interface does not specify how - // the CoW is used, and there is no standard associated. - // By looking at the current implementation, the CoW file is treated as: - // - a _NEW_ snapshot if its first 32 bits are zero, so the newly created - // dm-snapshot device will look like a perfect copy of the origin device; - // - an _EXISTING_ snapshot if the first 32 bits are equal to a - // kernel-specified magic number and the CoW file metadata is set as valid, - // so it can be used to resume the last state of a snapshot device; - // - an _INVALID_ snapshot otherwise. - // To avoid zero-filling the whole CoW file when a new dm-snapshot is - // created, here we zero-fill only the first 32 bits. This is a temporary - // workaround that will be discussed again when the kernel API gets - // consolidated. - ssize_t dm_snap_magic_size = 4; // 32 bit - return images_->ZeroFillNewImage(cow_image_name, dm_snap_magic_size); + return images_->CreateBackingImage(cow_image_name, status.cow_file_size, cow_flags); } bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name, @@ -393,21 +382,24 @@ bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name, } bool SnapshotManager::MapCowImage(const std::string& name, - const std::chrono::milliseconds& timeout_ms, - std::string* cow_dev) { + const std::chrono::milliseconds& timeout_ms) { if (!EnsureImageManager()) return false; auto cow_image_name = GetCowImageDeviceName(name); bool ok; + std::string cow_dev; if (has_local_image_manager_) { // If we forced a local image manager, it means we don't have binder, // which means first-stage init. We must use device-mapper. const auto& opener = device_->GetPartitionOpener(); - ok = images_->MapImageWithDeviceMapper(opener, cow_image_name, cow_dev); + ok = images_->MapImageWithDeviceMapper(opener, cow_image_name, &cow_dev); } else { - ok = images_->MapImageDevice(cow_image_name, timeout_ms, cow_dev); + ok = images_->MapImageDevice(cow_image_name, timeout_ms, &cow_dev); } - if (!ok) { + + if (ok) { + LOG(INFO) << "Mapped " << cow_image_name << " to " << cow_dev; + } else { LOG(ERROR) << "Could not map image device: " << cow_image_name; } return ok; @@ -441,11 +433,12 @@ bool SnapshotManager::DeleteSnapshot(LockedFile* lock, const std::string& name) CHECK(lock->lock_mode() == LOCK_EX); if (!EnsureImageManager()) return false; + if (!UnmapCowDevices(lock, name)) { + return false; + } + auto cow_image_name = GetCowImageDeviceName(name); if (images_->BackingImageExists(cow_image_name)) { - if (!images_->UnmapImageIfExists(cow_image_name)) { - return false; - } if (!images_->DeleteBackingImage(cow_image_name)) { return false; } @@ -1176,6 +1169,12 @@ bool SnapshotManager::CreateLogicalAndSnapshotPartitions(const std::string& supe } for (const auto& partition : metadata->partitions) { + if (GetPartitionGroupName(metadata->groups[partition.group_index]) == kCowGroupName) { + LOG(INFO) << "Skip mapping partition " << GetPartitionName(partition) << " in group " + << kCowGroupName; + continue; + } + CreateLogicalPartitionParams params = { .block_device = super_device, .metadata = metadata.get(), @@ -1298,22 +1297,20 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock, return false; } - // If there is a timeout specified, compute the remaining time to call Map* functions. - // init calls CreateLogicalAndSnapshotPartitions, which has no timeout specified. Still call - // Map* functions in this case. auto remaining_time = GetRemainingTime(params.timeout_ms, begin); if (remaining_time.count() < 0) return false; - std::string cow_image_device; - if (!MapCowImage(params.GetPartitionName(), remaining_time, &cow_image_device)) { - LOG(ERROR) << "Could not map cow image for partition: " << params.GetPartitionName(); + std::string cow_name; + CreateLogicalPartitionParams cow_params = params; + cow_params.timeout_ms = remaining_time; + if (!MapCowDevices(lock, cow_params, *live_snapshot_status, &created_devices, &cow_name)) { + return false; + } + std::string cow_device; + if (!dm.GetDeviceString(cow_name, &cow_device)) { + LOG(ERROR) << "Could not determine major/minor for: " << cow_name; return false; } - created_devices.EmplaceBack(images_.get(), - GetCowImageDeviceName(params.partition_name)); - - // TODO: map cow linear device here - std::string cow_device = cow_image_device; remaining_time = GetRemainingTime(params.timeout_ms, begin); if (remaining_time.count() < 0) return false; @@ -1340,12 +1337,11 @@ bool SnapshotManager::UnmapPartitionWithSnapshot(LockedFile* lock, return false; } - if (!UnmapCowImage(target_partition_name)) { + if (!UnmapCowDevices(lock, target_partition_name)) { return false; } auto& dm = DeviceMapper::Instance(); - std::string base_name = GetBaseDeviceName(target_partition_name); if (!dm.DeleteDeviceIfExists(base_name)) { LOG(ERROR) << "Cannot delete base device: " << base_name; @@ -1357,6 +1353,97 @@ bool SnapshotManager::UnmapPartitionWithSnapshot(LockedFile* lock, return true; } +bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartitionParams& params, + const SnapshotStatus& snapshot_status, + AutoDeviceList* created_devices, std::string* cow_name) { + CHECK(lock); + if (!EnsureImageManager()) return false; + CHECK(snapshot_status.cow_partition_size + snapshot_status.cow_file_size > 0); + auto begin = std::chrono::steady_clock::now(); + + std::string partition_name = params.GetPartitionName(); + std::string cow_image_name = GetCowImageDeviceName(partition_name); + *cow_name = GetCowName(partition_name); + + auto& dm = DeviceMapper::Instance(); + + // Map COW image if necessary. + if (snapshot_status.cow_file_size > 0) { + auto remaining_time = GetRemainingTime(params.timeout_ms, begin); + if (remaining_time.count() < 0) return false; + + if (!MapCowImage(partition_name, remaining_time)) { + LOG(ERROR) << "Could not map cow image for partition: " << partition_name; + return false; + } + created_devices->EmplaceBack(images_.get(), cow_image_name); + + // If no COW partition exists, just return the image alone. + if (snapshot_status.cow_partition_size == 0) { + *cow_name = std::move(cow_image_name); + LOG(INFO) << "Mapped COW image for " << partition_name << " at " << *cow_name; + return true; + } + } + + auto remaining_time = GetRemainingTime(params.timeout_ms, begin); + if (remaining_time.count() < 0) return false; + + CHECK(snapshot_status.cow_partition_size > 0); + + // Create the DmTable for the COW device. It is the DmTable of the COW partition plus + // COW image device as the last extent. + CreateLogicalPartitionParams cow_partition_params = params; + cow_partition_params.partition = nullptr; + cow_partition_params.partition_name = *cow_name; + cow_partition_params.device_name.clear(); + DmTable table; + if (!CreateDmTable(cow_partition_params, &table)) { + return false; + } + // If the COW image exists, append it as the last extent. + if (snapshot_status.cow_file_size > 0) { + std::string cow_image_device; + if (!dm.GetDeviceString(cow_image_name, &cow_image_device)) { + LOG(ERROR) << "Cannot determine major/minor for: " << cow_image_name; + return false; + } + auto cow_partition_sectors = snapshot_status.cow_partition_size / kSectorSize; + auto cow_image_sectors = snapshot_status.cow_file_size / kSectorSize; + table.Emplace(cow_partition_sectors, cow_image_sectors, cow_image_device, + 0); + } + + // We have created the DmTable now. Map it. + std::string cow_path; + if (!dm.CreateDevice(*cow_name, table, &cow_path, remaining_time)) { + LOG(ERROR) << "Could not create COW device: " << *cow_name; + return false; + } + created_devices->EmplaceBack(&dm, *cow_name); + LOG(INFO) << "Mapped COW device for " << params.GetPartitionName() << " at " << cow_path; + return true; +} + +bool SnapshotManager::UnmapCowDevices(LockedFile* lock, const std::string& name) { + CHECK(lock); + if (!EnsureImageManager()) return false; + + auto& dm = DeviceMapper::Instance(); + auto cow_name = GetCowName(name); + if (!dm.DeleteDeviceIfExists(cow_name)) { + LOG(ERROR) << "Cannot unmap " << cow_name; + return false; + } + + std::string cow_image_name = GetCowImageDeviceName(name); + if (!images_->UnmapImageIfExists(cow_image_name)) { + LOG(ERROR) << "Cannot unmap image " << cow_image_name; + return false; + } + return true; +} + auto SnapshotManager::OpenFile(const std::string& file, int open_flags, int lock_flags) -> std::unique_ptr { unique_fd fd(open(file.c_str(), open_flags | O_CLOEXEC | O_NOFOLLOW | O_SYNC, 0660)); @@ -1638,6 +1725,22 @@ bool SnapshotManager::CreateUpdateSnapshots(MetadataBuilder* target_metadata, suffixed_cow_sizes[name + target_suffix] = size; } + target_metadata->RemoveGroupAndPartitions(kCowGroupName); + if (!target_metadata->AddGroup(kCowGroupName, 0)) { + LOG(ERROR) << "Cannot add group " << kCowGroupName; + return false; + } + + // Check that all these metadata is not retrofit dynamic partitions. Snapshots on + // devices with retrofit dynamic partitions does not make sense. + // This ensures that current_metadata->GetFreeRegions() uses the same device + // indices as target_metadata (i.e. 0 -> "super"). + // This is also assumed in MapCowDevices() call below. + CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME && + target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME); + + std::map all_snapshot_status; + // In case of error, automatically delete devices that are created along the way. // Note that "lock" is destroyed after "created_devices", so it is safe to use |lock| for // these devices. @@ -1648,7 +1751,7 @@ bool SnapshotManager::CreateUpdateSnapshots(MetadataBuilder* target_metadata, auto it = suffixed_cow_sizes.find(target_partition->name()); if (it != suffixed_cow_sizes.end()) { cow_size = it->second; - LOG(INFO) << "Using provided COW size " << cow_size << " for partition " + LOG(INFO) << "Using provided COW size " << *cow_size << " for partition " << target_partition->name(); } @@ -1694,6 +1797,30 @@ bool SnapshotManager::CreateUpdateSnapshots(MetadataBuilder* target_metadata, } created_devices.EmplaceBack(this, lock.get(), target_partition->name()); + // Create the COW partition. That is, use any remaining free space in super partition before + // creating the COW images. + if (cow_creator_ret->snapshot_status.cow_partition_size > 0) { + CHECK(cow_creator_ret->snapshot_status.cow_partition_size % kSectorSize == 0) + << "cow_partition_size == " + << cow_creator_ret->snapshot_status.cow_partition_size + << " is not a multiple of sector size " << kSectorSize; + auto cow_partition = target_metadata->AddPartition(GetCowName(target_partition->name()), + kCowGroupName, 0 /* flags */); + if (cow_partition == nullptr) { + return false; + } + + if (!target_metadata->ResizePartition( + cow_partition, cow_creator_ret->snapshot_status.cow_partition_size, + cow_creator_ret->cow_partition_usable_regions)) { + LOG(ERROR) << "Cannot create COW partition on metadata with size " + << cow_creator_ret->snapshot_status.cow_partition_size; + return false; + } + // Only the in-memory target_metadata is modified; nothing to clean up if there is an + // error in the future. + } + // Create the backing COW image if necessary. if (cow_creator_ret->snapshot_status.cow_file_size > 0) { if (!CreateCowImage(lock.get(), target_partition->name())) { @@ -1701,13 +1828,55 @@ bool SnapshotManager::CreateUpdateSnapshots(MetadataBuilder* target_metadata, } } + all_snapshot_status[target_partition->name()] = std::move(cow_creator_ret->snapshot_status); + LOG(INFO) << "Successfully created snapshot for " << target_partition->name(); } + auto& dm = DeviceMapper::Instance(); + auto exported_target_metadata = target_metadata->Export(); + CreateLogicalPartitionParams cow_params{ + .block_device = LP_METADATA_DEFAULT_PARTITION_NAME, + .metadata = exported_target_metadata.get(), + .timeout_ms = std::chrono::milliseconds::max(), + .partition_opener = &device_->GetPartitionOpener(), + }; + for (auto* target_partition : ListPartitionsWithSuffix(target_metadata, target_suffix)) { + AutoDeviceList created_devices_for_cow; + + if (!UnmapPartitionWithSnapshot(lock.get(), target_partition->name())) { + LOG(ERROR) << "Cannot unmap existing COW devices before re-mapping them for zero-fill: " + << target_partition->name(); + return false; + } + + auto it = all_snapshot_status.find(target_partition->name()); + CHECK(it != all_snapshot_status.end()) << target_partition->name(); + cow_params.partition_name = target_partition->name(); + std::string cow_name; + if (!MapCowDevices(lock.get(), cow_params, it->second, &created_devices_for_cow, + &cow_name)) { + return false; + } + + std::string cow_path; + if (!dm.GetDmDevicePathByName(cow_name, &cow_path)) { + LOG(ERROR) << "Cannot determine path for " << cow_name; + return false; + } + + if (!InitializeCow(cow_path)) { + LOG(ERROR) << "Can't zero-fill COW device for " << target_partition->name() << ": " + << cow_path; + return false; + } + // Let destructor of created_devices_for_cow to unmap the COW devices. + }; + created_devices.Release(); LOG(INFO) << "Successfully created all snapshots for target slot " << target_suffix; - return res; + return true; } bool SnapshotManager::MapUpdateSnapshot(const CreateLogicalPartitionParams& params, diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp index 429fd8e93..086ee9507 100644 --- a/fs_mgr/libsnapshot/snapshot_test.cpp +++ b/fs_mgr/libsnapshot/snapshot_test.cpp @@ -34,6 +34,7 @@ #include #include "test_helpers.h" +#include "utility.h" namespace android { namespace snapshot { @@ -214,7 +215,9 @@ class SnapshotTest : public ::testing::Test { void DeleteSnapshotDevice(const std::string& snapshot) { DeleteDevice(snapshot); DeleteDevice(snapshot + "-inner"); + DeleteDevice(snapshot + "-cow"); ASSERT_TRUE(image_manager_->UnmapImageIfExists(snapshot + "-cow-img")); + DeleteDevice(snapshot + "-base"); } void DeleteDevice(const std::string& device) { if (dm_.GetState(device) != DmDeviceState::INVALID) { @@ -222,6 +225,35 @@ class SnapshotTest : public ::testing::Test { } } + 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 (!InitializeCow(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) { + if (!sm->MapCowImage(name, timeout_ms)) { + return AssertionFailure() << "Cannot map cow image " << name; + } + if (!dm_.GetDmDevicePathByName(name + "-cow-img"s, path)) { + return AssertionFailure() << "No path for " << name << "-cow-img"; + } + return AssertionSuccess(); + } + DeviceMapper& dm_; std::unique_ptr lock_; android::fiemap::IImageManager* image_manager_ = nullptr; @@ -236,7 +268,7 @@ TEST_F(SnapshotTest, CreateSnapshot) { {.device_size = kDeviceSize, .snapshot_size = kDeviceSize, .cow_file_size = kDeviceSize})); - ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot")); + ASSERT_TRUE(CreateCowImage("test-snapshot")); std::vector snapshots; ASSERT_TRUE(sm->ListSnapshots(lock_.get(), &snapshots)); @@ -265,13 +297,13 @@ TEST_F(SnapshotTest, MapSnapshot) { {.device_size = kDeviceSize, .snapshot_size = kDeviceSize, .cow_file_size = kDeviceSize})); - ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot")); + ASSERT_TRUE(CreateCowImage("test-snapshot")); std::string base_device; ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device)); std::string cow_device; - ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &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, @@ -288,13 +320,13 @@ TEST_F(SnapshotTest, MapPartialSnapshot) { {.device_size = kDeviceSize, .snapshot_size = kSnapshotSize, .cow_file_size = kSnapshotSize})); - ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot")); + ASSERT_TRUE(CreateCowImage("test-snapshot")); std::string base_device; ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device)); std::string cow_device; - ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &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, @@ -344,8 +376,8 @@ TEST_F(SnapshotTest, Merge) { {.device_size = kDeviceSize, .snapshot_size = kDeviceSize, .cow_file_size = kDeviceSize})); - ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b")); - ASSERT_TRUE(sm->MapCowImage("test_partition_b", 10s, &cow_device)); + ASSERT_TRUE(CreateCowImage("test_partition_b")); + ASSERT_TRUE(MapCowImage("test_partition_b", 10s, &cow_device)); ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test_partition_b", base_device, cow_device, 10s, &snap_device)); @@ -403,11 +435,11 @@ TEST_F(SnapshotTest, MergeCannotRemoveCow) { {.device_size = kDeviceSize, .snapshot_size = kDeviceSize, .cow_file_size = kDeviceSize})); - ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot")); + ASSERT_TRUE(CreateCowImage("test-snapshot")); std::string base_device, cow_device, snap_device; ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device)); - ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &cow_device)); + ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device)); ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s, &snap_device)); @@ -437,7 +469,7 @@ TEST_F(SnapshotTest, MergeCannotRemoveCow) { // Map snapshot should fail now, because we're in a merge-complete state. ASSERT_TRUE(AcquireLock()); - ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &cow_device)); + ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device)); ASSERT_FALSE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s, &snap_device)); @@ -462,7 +494,7 @@ TEST_F(SnapshotTest, FirstStageMountAndMerge) { {.device_size = kDeviceSize, .snapshot_size = kDeviceSize, .cow_file_size = kDeviceSize})); - ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b")); + ASSERT_TRUE(CreateCowImage("test_partition_b")); // Simulate a reboot into the new slot. lock_ = nullptr; @@ -504,7 +536,7 @@ TEST_F(SnapshotTest, FlashSuperDuringUpdate) { {.device_size = kDeviceSize, .snapshot_size = kDeviceSize, .cow_file_size = kDeviceSize})); - ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b")); + ASSERT_TRUE(CreateCowImage("test_partition_b")); // Simulate a reboot into the new slot. lock_ = nullptr; @@ -552,7 +584,7 @@ TEST_F(SnapshotTest, FlashSuperDuringMerge) { {.device_size = kDeviceSize, .snapshot_size = kDeviceSize, .cow_file_size = kDeviceSize})); - ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b")); + ASSERT_TRUE(CreateCowImage("test_partition_b")); // Simulate a reboot into the new slot. lock_ = nullptr; diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp index 19d58b787..66629e873 100644 --- a/fs_mgr/libsnapshot/utility.cpp +++ b/fs_mgr/libsnapshot/utility.cpp @@ -14,6 +14,7 @@ #include "utility.h" +#include #include #include @@ -75,5 +76,38 @@ AutoDeleteSnapshot::~AutoDeleteSnapshot() { } } +bool InitializeCow(const std::string& device) { + // When the kernel creates a persistent dm-snapshot, it requires a CoW file + // to store the modifications. The kernel interface does not specify how + // the CoW is used, and there is no standard associated. + // By looking at the current implementation, the CoW file is treated as: + // - a _NEW_ snapshot if its first 32 bits are zero, so the newly created + // dm-snapshot device will look like a perfect copy of the origin device; + // - an _EXISTING_ snapshot if the first 32 bits are equal to a + // kernel-specified magic number and the CoW file metadata is set as valid, + // so it can be used to resume the last state of a snapshot device; + // - an _INVALID_ snapshot otherwise. + // To avoid zero-filling the whole CoW file when a new dm-snapshot is + // created, here we zero-fill only the first 32 bits. This is a temporary + // workaround that will be discussed again when the kernel API gets + // consolidated. + // TODO(b/139202197): Remove this hack once the kernel API is consolidated. + constexpr ssize_t kDmSnapZeroFillSize = 4; // 32-bit + + char zeros[kDmSnapZeroFillSize] = {0}; + android::base::unique_fd fd(open(device.c_str(), O_WRONLY | O_BINARY)); + if (fd < 0) { + PLOG(ERROR) << "Can't open COW device: " << device; + return false; + } + + LOG(INFO) << "Zero-filling COW device: " << device; + if (!android::base::WriteFully(fd, zeros, kDmSnapZeroFillSize)) { + PLOG(ERROR) << "Can't zero-fill COW device for " << device; + return false; + } + return true; +} + } // namespace snapshot } // namespace android diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h index 7e7e24ad9..a700c467f 100644 --- a/fs_mgr/libsnapshot/utility.h +++ b/fs_mgr/libsnapshot/utility.h @@ -103,5 +103,8 @@ struct AutoDeleteSnapshot : AutoDevice { std::vector ListPartitionsWithSuffix( android::fs_mgr::MetadataBuilder* builder, const std::string& suffix); +// Initialize a device before using it as the COW device for a dm-snapshot device. +bool InitializeCow(const std::string& device); + } // namespace snapshot } // namespace android