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
This commit is contained in:
parent
c69029f2a8
commit
20a23bb41c
|
@ -20,6 +20,7 @@
|
|||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<Extent> 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::Return> 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::Return> 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;
|
||||
}
|
||||
|
|
|
@ -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<Interval> cow_partition_usable_regions;
|
||||
};
|
||||
|
||||
std::optional<Return> Run();
|
||||
|
|
|
@ -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<AutoUnmapImage>(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<AutoUnmapImage>(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<DmTargetLinear>(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<AutoUnmapDevice>(&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<LockedFile> {
|
||||
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<std::string, SnapshotStatus> 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<AutoDeleteSnapshot>(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,
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include <liblp/mock_property_fetcher.h>
|
||||
|
||||
#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<SnapshotManager::LockedFile> 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<std::string> 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;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "utility.h"
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/strings.h>
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -103,5 +103,8 @@ struct AutoDeleteSnapshot : AutoDevice {
|
|||
std::vector<android::fs_mgr::Partition*> 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
|
||||
|
|
Loading…
Reference in New Issue