diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp index 834bf3b1d..8cf0f3b4e 100644 --- a/fs_mgr/libsnapshot/Android.bp +++ b/fs_mgr/libsnapshot/Android.bp @@ -49,11 +49,17 @@ cc_defaults { "libfiemap_headers", ], export_include_dirs: ["include"], + proto: { + type: "lite", + export_proto_headers: true, + canonical_path_from_root: false, + }, } filegroup { name: "libsnapshot_sources", srcs: [ + "android/snapshot/snapshot.proto", "snapshot.cpp", "snapshot_metadata_updater.cpp", "partition_cow_creator.cpp", @@ -132,9 +138,10 @@ cc_binary { "libbinder", "libext4_utils", "libfs_mgr", - "libutils", "liblog", "liblp", + "libprotobuf-cpp-lite", + "libutils", ], init_rc: [ "snapshotctl.rc", diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto new file mode 100644 index 000000000..629c3a4bd --- /dev/null +++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto @@ -0,0 +1,87 @@ +// Copyright (C) 2019 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. + +syntax = "proto3"; +package android.snapshot; + +option optimize_for = LITE_RUNTIME; + +// Next: 4 +enum SnapshotState { + // No snapshot is found. + NONE = 0; + + // The snapshot has been created and possibly written to. Rollbacks are + // possible by destroying the snapshot. + CREATED = 1; + + // Changes are being merged. No rollbacks are possible beyond this point. + MERGING = 2; + + // Changes have been merged, Future reboots may map the base device + // directly. + MERGE_COMPLETED = 3; +} + +// Next: 9 +message SnapshotStatus { + // Name of the snapshot. This is usually the name of the snapshotted + // logical partition; for example, "system_b". + string name = 1; + + SnapshotState state = 2; + + // Size of the full (base) device. + uint64 device_size = 3; + + // Size of the snapshot. This is the sum of lengths of ranges in the base + // device that needs to be snapshotted during the update. + // This must be less than or equal to |device_size|. + // This value is 0 if no snapshot is needed for this device because + // no changes + uint64 snapshot_size = 4; + + // Size of the "COW partition". A COW partition is a special logical + // partition represented in the super partition metadata. This partition and + // the "COW image" form the "COW device" that supports the snapshot device. + // + // When SnapshotManager creates a COW device, it first searches for unused + // blocks in the super partition, and use those before creating the COW + // image if the COW partition is not big enough. + // + // This value is 0 if no space in super is left for the COW partition. + // |cow_partition_size + cow_file_size| must not be zero if |snapshot_size| + // is non-zero. + uint64 cow_partition_size = 5; + + // Size of the "COW file", or "COW image". A COW file / image is created + // when the "COW partition" is not big enough to store changes to the + // snapshot device. + // + // This value is 0 if |cow_partition_size| is big enough to hold all changes + // to the snapshot device. + uint64 cow_file_size = 6; + + // Sectors allocated for the COW device. Recording this value right after + // the update and before the merge allows us to infer the progress of the + // merge process. + // This is non-zero when |state| == MERGING or MERGE_COMPLETED. + uint64 sectors_allocated = 7; + + // Metadata sectors allocated for the COW device. Recording this value right + // before the update and before the merge allows us to infer the progress of + // the merge process. + // This is non-zero when |state| == MERGING or MERGE_COMPLETED. + uint64 metadata_sectors = 8; +} diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h index 0d6aa2c1d..69f28952b 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h @@ -53,8 +53,9 @@ namespace snapshot { struct AutoDeleteCowImage; struct AutoDeleteSnapshot; -struct PartitionCowCreator; struct AutoDeviceList; +struct PartitionCowCreator; +class SnapshotStatus; static constexpr const std::string_view kCowGroupName = "cow"; @@ -250,22 +251,6 @@ class SnapshotManager final { std::unique_ptr OpenFile(const std::string& file, int open_flags, int lock_flags); bool Truncate(LockedFile* file); - enum class SnapshotState : int { None, Created, Merging, MergeCompleted }; - static std::string to_string(SnapshotState state); - - // This state is persisted per-snapshot in /metadata/ota/snapshots/. - struct SnapshotStatus { - SnapshotState state = SnapshotState::None; - uint64_t device_size = 0; - uint64_t snapshot_size = 0; - uint64_t cow_partition_size = 0; - uint64_t cow_file_size = 0; - - // These are non-zero when merging. - uint64_t sectors_allocated = 0; - uint64_t metadata_sectors = 0; - }; - // 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(). @@ -282,7 +267,7 @@ class SnapshotManager final { // // 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); + bool CreateSnapshot(LockedFile* lock, SnapshotStatus* status); // |name| should be the base partition name (e.g. "system_a"). Create the // backing COW image using the size previously passed to CreateSnapshot(). @@ -363,8 +348,7 @@ class SnapshotManager final { UpdateState CheckTargetMergeState(LockedFile* lock, const std::string& name); // Interact with status files under /metadata/ota/snapshots. - bool WriteSnapshotStatus(LockedFile* lock, const std::string& name, - const SnapshotStatus& status); + bool WriteSnapshotStatus(LockedFile* lock, const SnapshotStatus& status); bool ReadSnapshotStatus(LockedFile* lock, const std::string& name, SnapshotStatus* status); std::string GetSnapshotStatusFilePath(const std::string& name); diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp index 404ef2727..eedc1cdd9 100644 --- a/fs_mgr/libsnapshot/partition_cow_creator.cpp +++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp @@ -18,6 +18,7 @@ #include +#include #include "utility.h" using android::dm::kSectorSize; @@ -84,13 +85,14 @@ std::optional PartitionCowCreator::Run() { << "logical_block_size is not power of 2"; Return ret; - ret.snapshot_status.device_size = target_partition->size(); + ret.snapshot_status.set_name(target_partition->name()); + ret.snapshot_status.set_device_size(target_partition->size()); // TODO(b/141889746): Optimize by using a smaller snapshot. Some ranges in target_partition // may be written directly. - ret.snapshot_status.snapshot_size = target_partition->size(); + ret.snapshot_status.set_snapshot_size(target_partition->size()); - auto cow_size = GetCowSize(ret.snapshot_status.snapshot_size); + auto cow_size = GetCowSize(ret.snapshot_status.snapshot_size()); if (!cow_size.has_value()) return std::nullopt; // Compute regions that are free in both current and target metadata. These are the regions @@ -106,18 +108,20 @@ std::optional PartitionCowCreator::Run() { 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); + uint64_t 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); + cow_partition_size &= ~(logical_block_size - 1); + ret.snapshot_status.set_cow_partition_size(cow_partition_size); // 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; + uint64_t 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); + cow_file_size += kSectorSize - 1; + cow_file_size &= ~(kSectorSize - 1); + ret.snapshot_status.set_cow_file_size(cow_file_size); return ret; } diff --git a/fs_mgr/libsnapshot/partition_cow_creator.h b/fs_mgr/libsnapshot/partition_cow_creator.h index 0e645c6d1..8888f78e7 100644 --- a/fs_mgr/libsnapshot/partition_cow_creator.h +++ b/fs_mgr/libsnapshot/partition_cow_creator.h @@ -20,8 +20,9 @@ #include #include +#include -#include +#include namespace android { namespace snapshot { @@ -51,7 +52,7 @@ struct PartitionCowCreator { const RepeatedPtrField* operations = nullptr; struct Return { - SnapshotManager::SnapshotStatus snapshot_status; + SnapshotStatus snapshot_status; std::vector cow_partition_usable_regions; }; diff --git a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp index ccd087e71..feb3c2d8e 100644 --- a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp +++ b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp @@ -51,8 +51,8 @@ TEST_F(PartitionCowCreatorTest, IntersectSelf) { .current_suffix = "_a"}; auto ret = creator.Run(); ASSERT_TRUE(ret.has_value()); - ASSERT_EQ(40 * 1024, ret->snapshot_status.device_size); - ASSERT_EQ(40 * 1024, ret->snapshot_status.snapshot_size); + ASSERT_EQ(40 * 1024, ret->snapshot_status.device_size()); + ASSERT_EQ(40 * 1024, ret->snapshot_status.snapshot_size()); } TEST_F(PartitionCowCreatorTest, Holes) { diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp index 02c7de6bf..5b758c958 100644 --- a/fs_mgr/libsnapshot/snapshot.cpp +++ b/fs_mgr/libsnapshot/snapshot.cpp @@ -38,6 +38,7 @@ #include #include +#include #include "partition_cow_creator.h" #include "snapshot_metadata_updater.h" #include "utility.h" @@ -234,42 +235,50 @@ bool SnapshotManager::FinishedSnapshotWrites() { return WriteUpdateState(lock.get(), UpdateState::Unverified); } -bool SnapshotManager::CreateSnapshot(LockedFile* lock, const std::string& name, - SnapshotManager::SnapshotStatus status) { +bool SnapshotManager::CreateSnapshot(LockedFile* lock, SnapshotStatus* status) { CHECK(lock); CHECK(lock->lock_mode() == LOCK_EX); + CHECK(status); + + if (status->name().empty()) { + LOG(ERROR) << "SnapshotStatus has no name."; + return false; + } // 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 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 - << " device size is not a multiple of the sector size: " << status.device_size; + if (status->device_size() % kSectorSize != 0) { + LOG(ERROR) << "Snapshot " << status->name() + << " device size is not a multiple of the sector size: " + << status->device_size(); return false; } - if (status.snapshot_size % kSectorSize != 0) { - LOG(ERROR) << "Snapshot " << name << " snapshot size is not a multiple of the sector size: " - << status.snapshot_size; + if (status->snapshot_size() % kSectorSize != 0) { + LOG(ERROR) << "Snapshot " << status->name() + << " snapshot size is not a multiple of the sector size: " + << status->snapshot_size(); return false; } - if (status.cow_partition_size % kSectorSize != 0) { - LOG(ERROR) << "Snapshot " << name + if (status->cow_partition_size() % kSectorSize != 0) { + LOG(ERROR) << "Snapshot " << status->name() << " cow partition size is not a multiple of the sector size: " - << status.cow_partition_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; + if (status->cow_file_size() % kSectorSize != 0) { + LOG(ERROR) << "Snapshot " << status->name() + << " cow file size is not a multiple of the sector size: " + << status->cow_file_size(); return false; } - status.state = SnapshotState::Created; - status.sectors_allocated = 0; - status.metadata_sectors = 0; + status->set_state(SnapshotState::CREATED); + status->set_sectors_allocated(0); + status->set_metadata_sectors(0); - if (!WriteSnapshotStatus(lock, name, status)) { - PLOG(ERROR) << "Could not write snapshot status: " << name; + if (!WriteSnapshotStatus(lock, *status)) { + PLOG(ERROR) << "Could not write snapshot status: " << status->name(); return false; } return true; @@ -287,15 +296,15 @@ bool SnapshotManager::CreateCowImage(LockedFile* lock, const std::string& name) // The COW file size should have been rounded up to the nearest sector in CreateSnapshot. // Sanity check this. - if (status.cow_file_size % kSectorSize != 0) { + if (status.cow_file_size() % kSectorSize != 0) { LOG(ERROR) << "Snapshot " << name << " COW file size is not a multiple of the sector size: " - << status.cow_file_size; + << status.cow_file_size(); return false; } std::string cow_image_name = GetCowImageDeviceName(name); int cow_flags = IImageManager::CREATE_IMAGE_DEFAULT; - return images_->CreateBackingImage(cow_image_name, status.cow_file_size, cow_flags); + return images_->CreateBackingImage(cow_image_name, status.cow_file_size(), cow_flags); } bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name, @@ -309,7 +318,7 @@ bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name, if (!ReadSnapshotStatus(lock, name, &status)) { return false; } - if (status.state == SnapshotState::MergeCompleted) { + if (status.state() == SnapshotState::MERGE_COMPLETED) { LOG(ERROR) << "Should not create a snapshot device for " << name << " after merging has completed."; return false; @@ -328,24 +337,23 @@ bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name, PLOG(ERROR) << "Could not determine block device size: " << base_device; return false; } - if (status.device_size != dev_size) { + if (status.device_size() != dev_size) { LOG(ERROR) << "Block device size for " << base_device << " does not match" - << "(expected " << status.device_size << ", got " << dev_size << ")"; + << "(expected " << status.device_size() << ", got " << dev_size << ")"; return false; } } - if (status.device_size % kSectorSize != 0) { - LOG(ERROR) << "invalid blockdev size for " << base_device << ": " << status.device_size; + if (status.device_size() % kSectorSize != 0) { + LOG(ERROR) << "invalid blockdev size for " << base_device << ": " << status.device_size(); return false; } - if (status.snapshot_size % kSectorSize != 0 || status.snapshot_size > status.device_size) { - LOG(ERROR) << "Invalid snapshot size for " << base_device << ": " << status.snapshot_size; + if (status.snapshot_size() % kSectorSize != 0 || + status.snapshot_size() > status.device_size()) { + LOG(ERROR) << "Invalid snapshot size for " << base_device << ": " << status.snapshot_size(); return false; } - uint64_t snapshot_sectors = status.snapshot_size / kSectorSize; - uint64_t linear_sectors = (status.device_size - status.snapshot_size) / kSectorSize; - - + uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize; + uint64_t linear_sectors = (status.device_size() - status.snapshot_size()) / kSectorSize; auto& dm = DeviceMapper::Instance(); @@ -557,8 +565,9 @@ bool SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string& if (!ReadSnapshotStatus(lock, name, &status)) { return false; } - if (status.state != SnapshotState::Created) { - LOG(WARNING) << "Snapshot " << name << " has unexpected state: " << to_string(status.state); + if (status.state() != SnapshotState::CREATED) { + LOG(WARNING) << "Snapshot " << name + << " has unexpected state: " << SnapshotState_Name(status.state()); } // After this, we return true because we technically did switch to a merge @@ -568,15 +577,15 @@ bool SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string& return false; } - status.state = SnapshotState::Merging; + status.set_state(SnapshotState::MERGING); DmTargetSnapshot::Status dm_status; if (!QuerySnapshotStatus(dm_name, nullptr, &dm_status)) { LOG(ERROR) << "Could not query merge status for snapshot: " << dm_name; } - status.sectors_allocated = dm_status.sectors_allocated; - status.metadata_sectors = dm_status.metadata_sectors; - if (!WriteSnapshotStatus(lock, name, status)) { + 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; } return true; @@ -821,7 +830,7 @@ UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std:: // rebooted after this check, the device will still be a snapshot-merge // target. If the have rebooted, the device will now be a linear target, // and we can try cleanup again. - if (snapshot_status.state == SnapshotState::MergeCompleted) { + if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) { // NB: It's okay if this fails now, we gave cleanup our best effort. OnSnapshotMergeComplete(lock, name, snapshot_status); return UpdateState::MergeCompleted; @@ -849,7 +858,7 @@ UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std:: // These two values are equal when merging is complete. if (status.sectors_allocated != status.metadata_sectors) { - if (snapshot_status.state == SnapshotState::MergeCompleted) { + if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) { LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete."; return UpdateState::MergeFailed; } @@ -864,8 +873,8 @@ UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std:: // This makes it simpler to reason about the next reboot: no matter what // part of cleanup failed, first-stage init won't try to create another // snapshot device for this partition. - snapshot_status.state = SnapshotState::MergeCompleted; - if (!WriteSnapshotStatus(lock, name, snapshot_status)) { + snapshot_status.set_state(SnapshotState::MERGE_COMPLETED); + if (!WriteSnapshotStatus(lock, snapshot_status)) { return UpdateState::MergeFailed; } if (!OnSnapshotMergeComplete(lock, name, snapshot_status)) { @@ -969,10 +978,10 @@ bool SnapshotManager::CollapseSnapshotDevice(const std::string& name, return false; } - uint64_t snapshot_sectors = status.snapshot_size / kSectorSize; - if (snapshot_sectors * kSectorSize != status.snapshot_size) { + uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize; + if (snapshot_sectors * kSectorSize != status.snapshot_size()) { LOG(ERROR) << "Snapshot " << name - << " size is not sector aligned: " << status.snapshot_size; + << " size is not sector aligned: " << status.snapshot_size(); return false; } @@ -1003,7 +1012,7 @@ bool SnapshotManager::CollapseSnapshotDevice(const std::string& name, << " sectors, got: " << outer_table[0].spec.length; return false; } - uint64_t expected_device_sectors = status.device_size / kSectorSize; + uint64_t expected_device_sectors = status.device_size() / kSectorSize; uint64_t actual_device_sectors = outer_table[0].spec.length + outer_table[1].spec.length; if (expected_device_sectors != actual_device_sectors) { LOG(ERROR) << "Outer device " << name << " should have " << expected_device_sectors @@ -1289,7 +1298,7 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock, return false; } // No live snapshot if merge is completed. - if (live_snapshot_status->state == SnapshotState::MergeCompleted) { + if (live_snapshot_status->state() == SnapshotState::MERGE_COMPLETED) { live_snapshot_status.reset(); } } while (0); @@ -1390,7 +1399,7 @@ bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartiti 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); + 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(); @@ -1400,7 +1409,7 @@ bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartiti auto& dm = DeviceMapper::Instance(); // Map COW image if necessary. - if (snapshot_status.cow_file_size > 0) { + if (snapshot_status.cow_file_size() > 0) { auto remaining_time = GetRemainingTime(params.timeout_ms, begin); if (remaining_time.count() < 0) return false; @@ -1411,7 +1420,7 @@ bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartiti 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) { + 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; @@ -1421,7 +1430,7 @@ bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartiti auto remaining_time = GetRemainingTime(params.timeout_ms, begin); if (remaining_time.count() < 0) return false; - CHECK(snapshot_status.cow_partition_size > 0); + 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. @@ -1434,14 +1443,14 @@ bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartiti return false; } // If the COW image exists, append it as the last extent. - if (snapshot_status.cow_file_size > 0) { + 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; + 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); } @@ -1602,101 +1611,38 @@ bool SnapshotManager::ReadSnapshotStatus(LockedFile* lock, const std::string& na return false; } - std::string contents; - if (!android::base::ReadFdToString(fd, &contents)) { - PLOG(ERROR) << "read failed: " << path; - return false; - } - auto pieces = android::base::Split(contents, " "); - if (pieces.size() != 7) { - LOG(ERROR) << "Invalid status line for snapshot: " << path; + if (!status->ParseFromFileDescriptor(fd.get())) { + PLOG(ERROR) << "Unable to parse " << path << " as SnapshotStatus"; return false; } - if (pieces[0] == "none") { - status->state = SnapshotState::None; - } else if (pieces[0] == "created") { - status->state = SnapshotState::Created; - } else if (pieces[0] == "merging") { - status->state = SnapshotState::Merging; - } else if (pieces[0] == "merge-completed") { - status->state = SnapshotState::MergeCompleted; - } else { - LOG(ERROR) << "Unrecognized state " << pieces[0] << " for snapshot: " << name; - return false; + if (status->name() != name) { + LOG(WARNING) << "Found snapshot status named " << status->name() << " in " << path; + status->set_name(name); } - if (!android::base::ParseUint(pieces[1], &status->device_size)) { - LOG(ERROR) << "Invalid device size in status line for: " << path; - return false; - } - if (!android::base::ParseUint(pieces[2], &status->snapshot_size)) { - LOG(ERROR) << "Invalid snapshot size in status line for: " << path; - return false; - } - if (!android::base::ParseUint(pieces[3], &status->cow_partition_size)) { - LOG(ERROR) << "Invalid cow linear size in status line for: " << path; - return false; - } - if (!android::base::ParseUint(pieces[4], &status->cow_file_size)) { - LOG(ERROR) << "Invalid cow file size in status line for: " << path; - return false; - } - if (!android::base::ParseUint(pieces[5], &status->sectors_allocated)) { - LOG(ERROR) << "Invalid snapshot size in status line for: " << path; - return false; - } - if (!android::base::ParseUint(pieces[6], &status->metadata_sectors)) { - LOG(ERROR) << "Invalid snapshot size in status line for: " << path; - return false; - } return true; } -std::string SnapshotManager::to_string(SnapshotState state) { - switch (state) { - case SnapshotState::None: - return "none"; - case SnapshotState::Created: - return "created"; - case SnapshotState::Merging: - return "merging"; - case SnapshotState::MergeCompleted: - return "merge-completed"; - default: - LOG(ERROR) << "Unknown snapshot state: " << (int)state; - return "unknown"; - } -} - -bool SnapshotManager::WriteSnapshotStatus(LockedFile* lock, const std::string& name, - const SnapshotStatus& status) { +bool SnapshotManager::WriteSnapshotStatus(LockedFile* lock, const SnapshotStatus& status) { // The caller must take an exclusive lock to modify snapshots. CHECK(lock); CHECK(lock->lock_mode() == LOCK_EX); + CHECK(!status.name().empty()); - auto path = GetSnapshotStatusFilePath(name); - unique_fd fd(open(path.c_str(), O_RDWR | O_CLOEXEC | O_NOFOLLOW | O_CREAT | O_SYNC, 0660)); + auto path = GetSnapshotStatusFilePath(status.name()); + unique_fd fd( + open(path.c_str(), O_RDWR | O_CLOEXEC | O_NOFOLLOW | O_CREAT | O_SYNC | O_TRUNC, 0660)); if (fd < 0) { PLOG(ERROR) << "Open failed: " << path; return false; } - std::vector pieces = { - to_string(status.state), - std::to_string(status.device_size), - std::to_string(status.snapshot_size), - std::to_string(status.cow_partition_size), - std::to_string(status.cow_file_size), - std::to_string(status.sectors_allocated), - std::to_string(status.metadata_sectors), - }; - auto contents = android::base::Join(pieces, " "); - - if (!android::base::WriteStringToFd(contents, fd)) { - PLOG(ERROR) << "write failed: " << path; + if (!status.SerializeToFileDescriptor(fd.get())) { + PLOG(ERROR) << "Unable to write SnapshotStatus to " << path; return false; } + return true; } @@ -1714,7 +1660,7 @@ bool SnapshotManager::Truncate(LockedFile* file) { std::string SnapshotManager::GetSnapshotDeviceName(const std::string& snapshot_name, const SnapshotStatus& status) { - if (status.device_size != status.snapshot_size) { + if (status.device_size() != status.snapshot_size()) { return GetSnapshotExtraDeviceName(snapshot_name); } return snapshot_name; @@ -1884,11 +1830,11 @@ bool SnapshotManager::CreateUpdateSnapshotsInternal( } LOG(INFO) << "For partition " << target_partition->name() - << ", device size = " << cow_creator_ret->snapshot_status.device_size - << ", snapshot size = " << cow_creator_ret->snapshot_status.snapshot_size + << ", device size = " << cow_creator_ret->snapshot_status.device_size() + << ", snapshot size = " << cow_creator_ret->snapshot_status.snapshot_size() << ", cow partition size = " - << cow_creator_ret->snapshot_status.cow_partition_size - << ", cow file size = " << cow_creator_ret->snapshot_status.cow_file_size; + << cow_creator_ret->snapshot_status.cow_partition_size() + << ", cow file size = " << cow_creator_ret->snapshot_status.cow_file_size(); // Delete any existing snapshot before re-creating one. if (!DeleteSnapshot(lock, target_partition->name())) { @@ -1899,9 +1845,9 @@ bool SnapshotManager::CreateUpdateSnapshotsInternal( // It is possible that the whole partition uses free space in super, and snapshot / COW // would not be needed. In this case, skip the partition. - bool needs_snapshot = cow_creator_ret->snapshot_status.snapshot_size > 0; - bool needs_cow = (cow_creator_ret->snapshot_status.cow_partition_size + - cow_creator_ret->snapshot_status.cow_file_size) > 0; + bool needs_snapshot = cow_creator_ret->snapshot_status.snapshot_size() > 0; + bool needs_cow = (cow_creator_ret->snapshot_status.cow_partition_size() + + cow_creator_ret->snapshot_status.cow_file_size()) > 0; CHECK(needs_snapshot == needs_cow); if (!needs_snapshot) { @@ -1911,17 +1857,17 @@ bool SnapshotManager::CreateUpdateSnapshotsInternal( } // Store these device sizes to snapshot status file. - if (!CreateSnapshot(lock, target_partition->name(), cow_creator_ret->snapshot_status)) { + if (!CreateSnapshot(lock, &cow_creator_ret->snapshot_status)) { return false; } created_devices->EmplaceBack(this, lock, 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) + 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 + << 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 */); @@ -1930,10 +1876,10 @@ bool SnapshotManager::CreateUpdateSnapshotsInternal( } if (!target_metadata->ResizePartition( - cow_partition, cow_creator_ret->snapshot_status.cow_partition_size, + 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; + << 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 @@ -1941,7 +1887,7 @@ bool SnapshotManager::CreateUpdateSnapshotsInternal( } // Create the backing COW image if necessary. - if (cow_creator_ret->snapshot_status.cow_file_size > 0) { + if (cow_creator_ret->snapshot_status.cow_file_size() > 0) { if (!CreateCowImage(lock, target_partition->name())) { return false; } @@ -2049,13 +1995,13 @@ bool SnapshotManager::Dump(std::ostream& os) { ok = false; continue; } - ss << " state: " << to_string(status.state) << std::endl; - ss << " device size (bytes): " << status.device_size << std::endl; - ss << " snapshot size (bytes): " << status.snapshot_size << std::endl; - ss << " cow partition size (bytes): " << status.cow_partition_size << std::endl; - ss << " cow file size (bytes): " << status.cow_file_size << std::endl; - ss << " allocated sectors: " << status.sectors_allocated << std::endl; - ss << " metadata sectors: " << status.metadata_sectors << std::endl; + ss << " state: " << SnapshotState_Name(status.state()) << std::endl; + ss << " device size (bytes): " << status.device_size() << std::endl; + ss << " snapshot size (bytes): " << status.snapshot_size() << std::endl; + ss << " cow partition size (bytes): " << status.cow_partition_size() << std::endl; + ss << " cow file size (bytes): " << status.cow_file_size() << std::endl; + ss << " allocated sectors: " << status.sectors_allocated() << std::endl; + ss << " metadata sectors: " << status.metadata_sectors() << std::endl; } os << ss.rdbuf(); return ok; diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp index f3994c1a0..fd7754ed5 100644 --- a/fs_mgr/libsnapshot/snapshot_test.cpp +++ b/fs_mgr/libsnapshot/snapshot_test.cpp @@ -33,6 +33,7 @@ #include #include +#include #include "test_helpers.h" #include "utility.h" @@ -272,10 +273,12 @@ TEST_F(SnapshotTest, CreateSnapshot) { ASSERT_TRUE(AcquireLock()); static const uint64_t kDeviceSize = 1024 * 1024; - ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", - {.device_size = kDeviceSize, - .snapshot_size = kDeviceSize, - .cow_file_size = kDeviceSize})); + 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(), &status)); ASSERT_TRUE(CreateCowImage("test-snapshot")); std::vector snapshots; @@ -285,11 +288,11 @@ TEST_F(SnapshotTest, CreateSnapshot) { // Scope so delete can re-acquire the snapshot file lock. { - SnapshotManager::SnapshotStatus status; + SnapshotStatus status; ASSERT_TRUE(sm->ReadSnapshotStatus(lock_.get(), "test-snapshot", &status)); - ASSERT_EQ(status.state, SnapshotManager::SnapshotState::Created); - ASSERT_EQ(status.device_size, kDeviceSize); - ASSERT_EQ(status.snapshot_size, kDeviceSize); + ASSERT_EQ(status.state(), SnapshotState::CREATED); + ASSERT_EQ(status.device_size(), kDeviceSize); + ASSERT_EQ(status.snapshot_size(), kDeviceSize); } ASSERT_TRUE(sm->UnmapSnapshot(lock_.get(), "test-snapshot")); @@ -301,10 +304,12 @@ TEST_F(SnapshotTest, MapSnapshot) { ASSERT_TRUE(AcquireLock()); static const uint64_t kDeviceSize = 1024 * 1024; - ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", - {.device_size = kDeviceSize, - .snapshot_size = kDeviceSize, - .cow_file_size = kDeviceSize})); + 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(), &status)); ASSERT_TRUE(CreateCowImage("test-snapshot")); std::string base_device; @@ -324,10 +329,12 @@ TEST_F(SnapshotTest, MapPartialSnapshot) { static const uint64_t kSnapshotSize = 1024 * 1024; static const uint64_t kDeviceSize = 1024 * 1024 * 2; - ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", - {.device_size = kDeviceSize, - .snapshot_size = kSnapshotSize, - .cow_file_size = kSnapshotSize})); + SnapshotStatus status; + status.set_name("test-snapshot"); + status.set_device_size(kDeviceSize); + status.set_snapshot_size(kSnapshotSize); + status.set_cow_file_size(kSnapshotSize); + ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status)); ASSERT_TRUE(CreateCowImage("test-snapshot")); std::string base_device; @@ -377,10 +384,12 @@ TEST_F(SnapshotTest, Merge) { ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize)); ASSERT_TRUE(MapUpdatePartitions()); ASSERT_TRUE(dm_.GetDmDevicePathByName("test_partition_b-base", &base_device)); - ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b", - {.device_size = kDeviceSize, - .snapshot_size = kDeviceSize, - .cow_file_size = kDeviceSize})); + SnapshotStatus status; + status.set_name("test_partition_b"); + status.set_device_size(kDeviceSize); + status.set_snapshot_size(kDeviceSize); + status.set_cow_file_size(kDeviceSize); + ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status)); 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, @@ -436,10 +445,12 @@ TEST_F(SnapshotTest, MergeCannotRemoveCow) { ASSERT_TRUE(AcquireLock()); static const uint64_t kDeviceSize = 1024 * 1024; - ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", - {.device_size = kDeviceSize, - .snapshot_size = kDeviceSize, - .cow_file_size = kDeviceSize})); + 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(), &status)); ASSERT_TRUE(CreateCowImage("test-snapshot")); std::string base_device, cow_device, snap_device; @@ -492,10 +503,12 @@ TEST_F(SnapshotTest, FirstStageMountAndMerge) { ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize)); ASSERT_TRUE(MapUpdatePartitions()); - ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b", - {.device_size = kDeviceSize, - .snapshot_size = kDeviceSize, - .cow_file_size = kDeviceSize})); + SnapshotStatus status; + status.set_name("test_partition_b"); + status.set_device_size(kDeviceSize); + status.set_snapshot_size(kDeviceSize); + status.set_cow_file_size(kDeviceSize); + ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status)); ASSERT_TRUE(CreateCowImage("test_partition_b")); // Simulate a reboot into the new slot. @@ -511,9 +524,8 @@ TEST_F(SnapshotTest, FirstStageMountAndMerge) { ASSERT_TRUE(AcquireLock()); // Validate that we have a snapshot device. - SnapshotManager::SnapshotStatus status; ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status)); - ASSERT_EQ(status.state, SnapshotManager::SnapshotState::Created); + ASSERT_EQ(status.state(), SnapshotState::CREATED); DeviceMapper::TargetInfo target; auto dm_name = init->GetSnapshotDeviceName("test_partition_b", status); @@ -528,10 +540,12 @@ TEST_F(SnapshotTest, FlashSuperDuringUpdate) { ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize)); ASSERT_TRUE(MapUpdatePartitions()); - ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b", - {.device_size = kDeviceSize, - .snapshot_size = kDeviceSize, - .cow_file_size = kDeviceSize})); + SnapshotStatus status; + status.set_name("test_partition_b"); + status.set_device_size(kDeviceSize); + status.set_snapshot_size(kDeviceSize); + status.set_cow_file_size(kDeviceSize); + ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status)); ASSERT_TRUE(CreateCowImage("test_partition_b")); // Simulate a reboot into the new slot. @@ -550,7 +564,6 @@ TEST_F(SnapshotTest, FlashSuperDuringUpdate) { ASSERT_TRUE(AcquireLock()); - SnapshotManager::SnapshotStatus status; ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status)); // We should not get a snapshot device now. @@ -570,10 +583,12 @@ TEST_F(SnapshotTest, FlashSuperDuringMerge) { ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize)); ASSERT_TRUE(MapUpdatePartitions()); - ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b", - {.device_size = kDeviceSize, - .snapshot_size = kDeviceSize, - .cow_file_size = kDeviceSize})); + SnapshotStatus status; + status.set_name("test_partition_b"); + status.set_device_size(kDeviceSize); + status.set_snapshot_size(kDeviceSize); + status.set_cow_file_size(kDeviceSize); + ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status)); ASSERT_TRUE(CreateCowImage("test_partition_b")); // Simulate a reboot into the new slot. @@ -699,11 +714,11 @@ class SnapshotUpdateTest : public SnapshotTest { } auto local_lock = std::move(lock_); - SnapshotManager::SnapshotStatus status; + SnapshotStatus status; if (!sm->ReadSnapshotStatus(local_lock.get(), name, &status)) { return std::nullopt; } - return status.snapshot_size; + return status.snapshot_size(); } AssertionResult UnmapAll() { @@ -869,8 +884,9 @@ TEST_F(SnapshotUpdateTest, SnapshotStatusFileWithoutCow) { { ASSERT_TRUE(AcquireLock()); auto local_lock = std::move(lock_); - ASSERT_TRUE(sm->WriteSnapshotStatus(local_lock.get(), "sys_b", - SnapshotManager::SnapshotStatus{})); + 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)); } diff --git a/init/Android.mk b/init/Android.mk index 62e452f4b..8fc44dad7 100644 --- a/init/Android.mk +++ b/init/Android.mk @@ -113,6 +113,7 @@ LOCAL_STATIC_LIBRARIES := \ libbacktrace \ libmodprobe \ libext2_uuid \ + libprotobuf-cpp-lite \ libsnapshot_nobinder \ LOCAL_SANITIZE := signed-integer-overflow