From c69029f2a8f64a28a47d60204c93c1f77356c493 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Thu, 25 Jul 2019 17:52:08 -0700 Subject: [PATCH] libsnapshot: APIs for all partitions Add CreateCowForUpdate / MapSnapshotDevicesForUpdate that update_engine and init can call them directly. Bug: 134536978 Test: libsnapshot_test Change-Id: If53c48855931db27454fd2893745915c77fd37f8 --- fs_mgr/liblp/builder.cpp | 26 +++ fs_mgr/liblp/include/liblp/builder.h | 17 +- fs_mgr/libsnapshot/Android.bp | 6 + .../include/libsnapshot/snapshot.h | 30 +++- fs_mgr/libsnapshot/partition_cow_creator.cpp | 148 +++++++++++++++++ fs_mgr/libsnapshot/partition_cow_creator.h | 61 +++++++ .../partition_cow_creator_test.cpp | 90 +++++++++++ fs_mgr/libsnapshot/snapshot.cpp | 153 ++++++++++++++++-- fs_mgr/libsnapshot/utility.cpp | 23 +++ fs_mgr/libsnapshot/utility.h | 22 +++ 10 files changed, 562 insertions(+), 14 deletions(-) create mode 100644 fs_mgr/libsnapshot/partition_cow_creator.cpp create mode 100644 fs_mgr/libsnapshot/partition_cow_creator.h create mode 100644 fs_mgr/libsnapshot/partition_cow_creator_test.cpp diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp index c5d6a3bad..ea3b58e1a 100644 --- a/fs_mgr/liblp/builder.cpp +++ b/fs_mgr/liblp/builder.cpp @@ -40,6 +40,10 @@ bool LinearExtent::AddTo(LpMetadata* out) const { return true; } +Interval LinearExtent::AsInterval() const { + return Interval(device_index(), physical_sector(), end_sector()); +} + bool ZeroExtent::AddTo(LpMetadata* out) const { out->extents.emplace_back(LpMetadataExtent{num_sectors_, LP_TARGET_TYPE_ZERO, 0, 0}); return true; @@ -96,6 +100,20 @@ void Partition::ShrinkTo(uint64_t aligned_size) { DCHECK(size_ == aligned_size); } +Partition Partition::GetBeginningExtents(uint64_t aligned_size) const { + Partition p(name_, group_name_, attributes_); + for (const auto& extent : extents_) { + auto le = extent->AsLinearExtent(); + if (le) { + p.AddExtent(std::make_unique(*le)); + } else { + p.AddExtent(std::make_unique(extent->num_sectors())); + } + } + p.ShrinkTo(aligned_size); + return p; +} + uint64_t Partition::BytesOnDisk() const { uint64_t sectors = 0; for (const auto& extent : extents_) { @@ -602,6 +620,10 @@ std::vector Interval::Intersect(const std::vector& a, return ret; } +std::unique_ptr Interval::AsExtent() const { + return std::make_unique(length(), device_index, start); +} + bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t aligned_size, const std::vector& free_region_hint) { uint64_t space_needed = aligned_size - partition->size(); @@ -1168,5 +1190,9 @@ std::string MetadataBuilder::GetBlockDevicePartitionName(uint64_t index) const { : ""; } +uint64_t MetadataBuilder::logical_block_size() const { + return geometry_.logical_block_size; +} + } // namespace fs_mgr } // namespace android diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h index 6f2ab75f3..69885fefa 100644 --- a/fs_mgr/liblp/include/liblp/builder.h +++ b/fs_mgr/liblp/include/liblp/builder.h @@ -33,10 +33,11 @@ namespace android { namespace fs_mgr { class LinearExtent; +struct Interval; // By default, partitions are aligned on a 1MiB boundary. -static const uint32_t kDefaultPartitionAlignment = 1024 * 1024; -static const uint32_t kDefaultBlockSize = 4096; +static constexpr uint32_t kDefaultPartitionAlignment = 1024 * 1024; +static constexpr uint32_t kDefaultBlockSize = 4096; // Name of the default group in a metadata. static constexpr std::string_view kDefaultGroup = "default"; @@ -74,6 +75,8 @@ class LinearExtent final : public Extent { return sector >= physical_sector_ && sector < end_sector(); } + Interval AsInterval() const; + private: uint32_t device_index_; uint64_t physical_sector_; @@ -127,6 +130,12 @@ class Partition final { const std::vector>& extents() const { return extents_; } uint64_t size() const { return size_; } + // Return a copy of *this, but with extents that includes only the first + // |aligned_size| bytes. |aligned_size| should be aligned to + // logical_block_size() of the MetadataBuilder that this partition belongs + // to. + Partition GetBeginningExtents(uint64_t aligned_size) const; + private: void ShrinkTo(uint64_t aligned_size); void set_group_name(std::string_view group_name) { group_name_ = group_name; } @@ -156,6 +165,8 @@ struct Interval { return (start == other.start) ? end < other.end : start < other.start; } + std::unique_ptr AsExtent() const; + // Intersect |a| with |b|. // If no intersection, result has 0 length(). static Interval Intersect(const Interval& a, const Interval& b); @@ -325,6 +336,8 @@ class MetadataBuilder { // Return the list of free regions not occupied by extents in the metadata. std::vector GetFreeRegions() const; + uint64_t logical_block_size() const; + private: MetadataBuilder(); MetadataBuilder(const MetadataBuilder&) = delete; diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp index 8df9c52d2..abf7afce4 100644 --- a/fs_mgr/libsnapshot/Android.bp +++ b/fs_mgr/libsnapshot/Android.bp @@ -25,10 +25,12 @@ cc_defaults { shared_libs: [ "libbase", "liblog", + "liblp", ], static_libs: [ "libdm", "libfs_mgr", + "libfstab", "liblp", ], whole_static_libs: [ @@ -49,6 +51,7 @@ filegroup { name: "libsnapshot_sources", srcs: [ "snapshot.cpp", + "partition_cow_creator.cpp", "utility.cpp", ], } @@ -77,6 +80,7 @@ cc_test { defaults: ["libsnapshot_defaults"], srcs: [ "snapshot_test.cpp", + "partition_cow_creator_test.cpp", "test_helpers.cpp", ], shared_libs: [ @@ -90,5 +94,7 @@ cc_test { "libgmock", "liblp", "libsnapshot", + "libsparse", + "libz", ], } diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h index c41a95135..660f60e94 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h @@ -17,13 +17,16 @@ #include #include +#include #include #include #include #include +#include #include #include +#include #include #ifndef FRIEND_TEST @@ -45,6 +48,10 @@ class IPartitionOpener; namespace snapshot { +struct AutoDeleteCowImage; +struct AutoDeleteSnapshot; +struct PartitionCowCreator; + enum class UpdateState : unsigned int { // No update or merge is in progress. None, @@ -75,8 +82,9 @@ enum class UpdateState : unsigned int { class SnapshotManager final { using CreateLogicalPartitionParams = android::fs_mgr::CreateLogicalPartitionParams; - using LpMetadata = android::fs_mgr::LpMetadata; using IPartitionOpener = android::fs_mgr::IPartitionOpener; + using LpMetadata = android::fs_mgr::LpMetadata; + using MetadataBuilder = android::fs_mgr::MetadataBuilder; public: // Dependency injection for testing. @@ -153,6 +161,20 @@ class SnapshotManager final { // Other: 0 UpdateState GetUpdateState(double* progress = nullptr); + // Create necessary COW device / files for OTA clients. New logical partitions will be added to + // group "cow" in target_metadata. Regions of partitions of current_metadata will be + // "write-protected" and snapshotted. + bool CreateUpdateSnapshots(MetadataBuilder* target_metadata, const std::string& target_suffix, + MetadataBuilder* current_metadata, const std::string& current_suffix, + const std::map& cow_sizes); + + // Map a snapshotted partition for OTA clients to write to. Write-protected regions are + // determined previously in CreateSnapshots. + bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params, std::string* snapshot_path); + + // Unmap a snapshot device that's previously mapped with MapUpdateSnapshot. + bool UnmapUpdateSnapshot(const std::string& target_partition_name); + // If this returns true, first-stage mount must call // CreateLogicalAndSnapshotPartitions rather than CreateLogicalPartitions. bool NeedSnapshotsInFirstStageMount(); @@ -174,6 +196,9 @@ class SnapshotManager final { FRIEND_TEST(SnapshotTest, MergeCannotRemoveCow); FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot); friend class SnapshotTest; + friend struct AutoDeleteCowImage; + friend struct AutoDeleteSnapshot; + friend struct PartitionCowCreator; using DmTargetSnapshot = android::dm::DmTargetSnapshot; using IImageManager = android::fiemap::IImageManager; @@ -344,6 +369,9 @@ class SnapshotManager final { bool MapPartitionWithSnapshot(LockedFile* lock, CreateLogicalPartitionParams params, std::string* path); + // The reverse of MapPartitionWithSnapshot. + bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name); + std::string gsid_dir_; std::string metadata_dir_; std::unique_ptr device_; diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp new file mode 100644 index 000000000..dd4116bee --- /dev/null +++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp @@ -0,0 +1,148 @@ +// 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. + +#include "partition_cow_creator.h" + +#include + +#include + +#include "utility.h" + +using android::fs_mgr::Extent; +using android::fs_mgr::kDefaultBlockSize; +using android::fs_mgr::Partition; + +namespace android { +namespace snapshot { + +// Round |d| up to a multiple of |block_size|. +static uint64_t RoundUp(double d, uint64_t block_size) { + uint64_t ret = ((uint64_t)ceil(d) + block_size - 1) / block_size * block_size; + CHECK(ret >= d) << "Can't round " << d << " up to a multiple of " << block_size; + return ret; +} + +// Intersect two linear extents. If no intersection, return an extent with length 0. +static std::unique_ptr Intersect(Extent* target_extent, Extent* existing_extent) { + // Convert target_extent and existing_extent to linear extents. Zero extents + // doesn't matter and doesn't result in any intersection. + auto existing_linear_extent = existing_extent->AsLinearExtent(); + if (!existing_extent) return nullptr; + + 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()) + .AsExtent(); +} + +// Check that partition |p| contains |e| fully. Both of them should +// be from |target_metadata|. +// Returns true as long as |e| is a subrange of any extent of |p|. +bool PartitionCowCreator::HasExtent(Partition* p, Extent* e) { + for (auto& partition_extent : p->extents()) { + auto intersection = Intersect(partition_extent.get(), e); + if (intersection != nullptr && intersection->num_sectors() == e->num_sectors()) { + return true; + } + } + return false; +} + +// Return the number of sectors, N, where |target_partition|[0..N] (from +// |target_metadata|) are the sectors that should be snapshotted. N is computed +// so that this range of sectors are used by partitions in |current_metadata|. +// +// The client code (update_engine) should have computed target_metadata by +// resizing partitions of current_metadata, so only the first N sectors should +// be snapshotted, not a range with start index != 0. +// +// Note that if partition A has shrunk and partition B has grown, the new +// extents of partition B may use the empty space that was used by partition A. +// In this case, that new extent cannot be written directly, as it may be used +// by the running system. Hence, all extents of the new partition B must be +// intersected with all old partitions (including old partition A and B) to get +// the region that needs to be snapshotted. +std::optional PartitionCowCreator::GetSnapshotSize() { + // Compute the number of sectors that needs to be snapshotted. + uint64_t snapshot_sectors = 0; + std::vector> intersections; + for (const auto& extent : target_partition->extents()) { + for (auto* existing_partition : + ListPartitionsWithSuffix(current_metadata, current_suffix)) { + for (const auto& existing_extent : existing_partition->extents()) { + auto intersection = Intersect(extent.get(), existing_extent.get()); + if (intersection != nullptr && intersection->num_sectors() > 0) { + snapshot_sectors += intersection->num_sectors(); + intersections.emplace_back(std::move(intersection)); + } + } + } + } + uint64_t snapshot_size = snapshot_sectors * kSectorSize; + + // Sanity check that all recorded intersections are indeed within + // target_partition[0..snapshot_sectors]. + Partition target_partition_snapshot = target_partition->GetBeginningExtents(snapshot_size); + for (const auto& intersection : intersections) { + if (!HasExtent(&target_partition_snapshot, intersection.get())) { + auto linear_intersection = intersection->AsLinearExtent(); + LOG(ERROR) << "Extent " + << (linear_intersection + ? (std::to_string(linear_intersection->physical_sector()) + "," + + std::to_string(linear_intersection->end_sector())) + : "") + << " is not part of Partition " << target_partition->name() << "[0.." + << snapshot_size + << "]. The metadata wasn't constructed correctly. This should not happen."; + return std::nullopt; + } + } + + return snapshot_size; +} + +std::optional PartitionCowCreator::Run() { + static constexpr double kCowEstimateFactor = 1.05; + + CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME && + target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME); + + Return ret; + ret.snapshot_status.device_size = target_partition->size(); + + auto snapshot_size = GetSnapshotSize(); + if (!snapshot_size.has_value()) return std::nullopt; + + ret.snapshot_status.snapshot_size = *snapshot_size; + + // TODO: always read from cow_size when the COW size is written in + // update package. kCowEstimateFactor is good for prototyping but + // we can't use that in production. + if (!cow_size.has_value()) { + cow_size = + 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; + ret.snapshot_status.cow_file_size = (*cow_size) - ret.snapshot_status.cow_partition_size; + + return ret; +} + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/partition_cow_creator.h b/fs_mgr/libsnapshot/partition_cow_creator.h new file mode 100644 index 000000000..d9ee4905c --- /dev/null +++ b/fs_mgr/libsnapshot/partition_cow_creator.h @@ -0,0 +1,61 @@ +// 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. + +#pragma once + +#include + +#include +#include + +#include + +#include + +namespace android { +namespace snapshot { + +// Helper class that creates COW for a partition. +struct PartitionCowCreator { + using Extent = android::fs_mgr::Extent; + using MetadataBuilder = android::fs_mgr::MetadataBuilder; + using Partition = android::fs_mgr::Partition; + + // The metadata that will be written to target metadata slot. + MetadataBuilder* target_metadata; + // The suffix of the target slot. + std::string target_suffix; + // The partition in target_metadata that needs to be snapshotted. + Partition* target_partition; + // The metadata at the current slot (that would be used if the device boots + // normally). This is used to determine which extents are being used. + MetadataBuilder* current_metadata; + // The suffix of the current slot. + std::string current_suffix; + // The COW size given by client code. + std::optional cow_size; + + struct Return { + SnapshotManager::SnapshotStatus snapshot_status; + }; + + std::optional Run(); + + private: + bool HasExtent(Partition* p, Extent* e); + std::optional GetSnapshotSize(); +}; + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp new file mode 100644 index 000000000..e30894358 --- /dev/null +++ b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp @@ -0,0 +1,90 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "partition_cow_creator.h" + +using ::android::fs_mgr::MetadataBuilder; +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Return; + +namespace android { +namespace snapshot { + +class MockPropertyFetcher : public fs_mgr::IPropertyFetcher { + public: + MOCK_METHOD2(GetProperty, std::string(const std::string&, const std::string&)); + MOCK_METHOD2(GetBoolProperty, bool(const std::string&, bool)); +}; + +class PartitionCowCreatorTest : ::testing::Test { + public: + void SetUp() override { + fs_mgr::IPropertyFetcher::OverrideForTesting(std::make_unique()); + + EXPECT_CALL(fetcher(), GetProperty("ro.boot.slot_suffix", _)) + .Times(AnyNumber()) + .WillRepeatedly(Return("_a")); + EXPECT_CALL(fetcher(), GetBoolProperty("ro.boot.dynamic_partitions", _)) + .Times(AnyNumber()) + .WillRepeatedly(Return(true)); + EXPECT_CALL(fetcher(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _)) + .Times(AnyNumber()) + .WillRepeatedly(Return(false)); + EXPECT_CALL(fetcher(), GetBoolProperty("ro.virtual_ab.enabled", _)) + .Times(AnyNumber()) + .WillRepeatedly(Return(true)); + } + void TearDown() override { + fs_mgr::IPropertyFetcher::OverrideForTesting(std::make_unique()); + } + MockPropertyFetcher& fetcher() { + return *static_cast(fs_mgr::IPropertyFetcher::GetInstance()); + } +}; + +TEST(PartitionCowCreator, IntersectSelf) { + auto builder_a = MetadataBuilder::New(1024 * 1024, 1024, 2); + ASSERT_NE(builder_a, nullptr); + auto system_a = builder_a->AddPartition("system_a", LP_PARTITION_ATTR_READONLY); + ASSERT_NE(system_a, nullptr); + ASSERT_TRUE(builder_a->ResizePartition(system_a, 40 * 1024)); + + auto builder_b = MetadataBuilder::New(1024 * 1024, 1024, 2); + ASSERT_NE(builder_b, nullptr); + auto system_b = builder_b->AddPartition("system_b", LP_PARTITION_ATTR_READONLY); + ASSERT_NE(system_b, nullptr); + ASSERT_TRUE(builder_b->ResizePartition(system_b, 40 * 1024)); + + PartitionCowCreator creator{.target_metadata = builder_b.get(), + .target_suffix = "_b", + .target_partition = system_b, + .current_metadata = builder_a.get(), + .current_suffix = "_a", + .cow_size = 20 * 1024}; + 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(20 * 1024, + ret->snapshot_status.cow_file_size + ret->snapshot_status.cow_partition_size); +} + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp index 7510e3af0..410074a52 100644 --- a/fs_mgr/libsnapshot/snapshot.cpp +++ b/fs_mgr/libsnapshot/snapshot.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -36,6 +37,7 @@ #include #include +#include "partition_cow_creator.h" #include "utility.h" namespace android { @@ -55,6 +57,7 @@ using android::fs_mgr::CreateLogicalPartition; using android::fs_mgr::CreateLogicalPartitionParams; using android::fs_mgr::GetPartitionName; using android::fs_mgr::LpMetadata; +using android::fs_mgr::MetadataBuilder; using android::fs_mgr::SlotNumberForSlotSuffix; using std::chrono::duration_cast; using namespace std::chrono_literals; @@ -413,22 +416,15 @@ bool SnapshotManager::MapCowImage(const std::string& name, bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) { CHECK(lock); - SnapshotStatus status; - if (!ReadSnapshotStatus(lock, name, &status)) { - return false; - } - auto& dm = DeviceMapper::Instance(); if (!dm.DeleteDeviceIfExists(name)) { LOG(ERROR) << "Could not delete snapshot device: " << name; return false; } - // There may be an extra device, since the kernel doesn't let us have a - // snapshot and linear target in the same table. - auto dm_name = GetSnapshotDeviceName(name, status); - if (name != dm_name && !dm.DeleteDeviceIfExists(dm_name)) { - LOG(ERROR) << "Could not delete inner snapshot device: " << dm_name; + auto snapshot_extra_device = GetSnapshotExtraDeviceName(name); + if (!dm.DeleteDeviceIfExists(snapshot_extra_device)) { + LOG(ERROR) << "Could not delete snapshot inner device: " << snapshot_extra_device; return false; } @@ -1078,7 +1074,7 @@ bool SnapshotManager::RemoveAllSnapshots(LockedFile* lock) { bool ok = true; for (const auto& name : snapshots) { - ok &= DeleteSnapshot(lock, name); + ok &= (UnmapPartitionWithSnapshot(lock, name) && DeleteSnapshot(lock, name)); } return ok; } @@ -1221,6 +1217,12 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock, CHECK(lock); path->clear(); + if (params.GetPartitionName() != params.GetDeviceName()) { + LOG(ERROR) << "Mapping snapshot with a different name is unsupported: partition_name = " + << params.GetPartitionName() << ", device_name = " << params.GetDeviceName(); + return false; + } + // Fill out fields in CreateLogicalPartitionParams so that we have more information (e.g. by // reading super partition metadata). CreateLogicalPartitionParams::OwnedData params_owned_data; @@ -1330,6 +1332,31 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock, return true; } +bool SnapshotManager::UnmapPartitionWithSnapshot(LockedFile* lock, + const std::string& target_partition_name) { + CHECK(lock); + + if (!UnmapSnapshot(lock, target_partition_name)) { + return false; + } + + if (!UnmapCowImage(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; + return false; + } + + LOG(INFO) << "Successfully unmapped snapshot " << target_partition_name; + + 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)); @@ -1597,5 +1624,109 @@ bool SnapshotManager::ForceLocalImageManager() { return true; } +bool SnapshotManager::CreateUpdateSnapshots(MetadataBuilder* target_metadata, + const std::string& target_suffix, + MetadataBuilder* current_metadata, + const std::string& current_suffix, + const std::map& cow_sizes) { + auto lock = LockExclusive(); + if (!lock) return false; + + // Add _{target_suffix} to COW size map. + std::map suffixed_cow_sizes; + for (const auto& [name, size] : cow_sizes) { + suffixed_cow_sizes[name + target_suffix] = size; + } + + // 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. + AutoDeviceList created_devices; + + for (auto* target_partition : ListPartitionsWithSuffix(target_metadata, target_suffix)) { + std::optional cow_size = std::nullopt; + 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 " + << target_partition->name(); + } + + // Compute the device sizes for the partition. + PartitionCowCreator cow_creator{target_metadata, target_suffix, target_partition, + current_metadata, current_suffix, cow_size}; + auto cow_creator_ret = cow_creator.Run(); + if (!cow_creator_ret.has_value()) { + return false; + } + + 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 + << ", cow partition 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.get(), target_partition->name())) { + LOG(ERROR) << "Cannot delete existing snapshot before creating a new one for partition " + << target_partition->name(); + return false; + } + + // 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; + CHECK(needs_snapshot == needs_cow); + + if (!needs_snapshot) { + LOG(INFO) << "Skip creating snapshot for partition " << target_partition->name() + << "because nothing needs to be snapshotted."; + continue; + } + + // Store these device sizes to snapshot status file. + if (!CreateSnapshot(lock.get(), target_partition->name(), + cow_creator_ret->snapshot_status)) { + return false; + } + created_devices.EmplaceBack(this, lock.get(), target_partition->name()); + + // Create the backing COW image if necessary. + if (cow_creator_ret->snapshot_status.cow_file_size > 0) { + if (!CreateCowImage(lock.get(), target_partition->name())) { + return false; + } + } + + LOG(INFO) << "Successfully created snapshot for " << target_partition->name(); + } + + created_devices.Release(); + LOG(INFO) << "Successfully created all snapshots for target slot " << target_suffix; + + return res; +} + +bool SnapshotManager::MapUpdateSnapshot(const CreateLogicalPartitionParams& params, + std::string* snapshot_path) { + auto lock = LockShared(); + if (!lock) return false; + if (!UnmapPartitionWithSnapshot(lock.get(), params.GetPartitionName())) { + LOG(ERROR) << "Cannot unmap existing snapshot before re-mapping it: " + << params.GetPartitionName(); + return false; + } + return MapPartitionWithSnapshot(lock.get(), params, snapshot_path); +} + +bool SnapshotManager::UnmapUpdateSnapshot(const std::string& target_partition_name) { + auto lock = LockShared(); + if (!lock) return false; + return UnmapPartitionWithSnapshot(lock.get(), target_partition_name); +} + } // namespace snapshot } // namespace android diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp index 164b4722b..19d58b787 100644 --- a/fs_mgr/libsnapshot/utility.cpp +++ b/fs_mgr/libsnapshot/utility.cpp @@ -17,6 +17,9 @@ #include #include +using android::fs_mgr::MetadataBuilder; +using android::fs_mgr::Partition; + namespace android { namespace snapshot { @@ -52,5 +55,25 @@ AutoUnmapImage::~AutoUnmapImage() { } } +std::vector ListPartitionsWithSuffix(MetadataBuilder* builder, + const std::string& suffix) { + std::vector ret; + for (const auto& group : builder->ListGroups()) { + for (auto* partition : builder->ListPartitionsInGroup(group)) { + if (!base::EndsWith(partition->name(), suffix)) { + continue; + } + ret.push_back(partition); + } + } + return ret; +} + +AutoDeleteSnapshot::~AutoDeleteSnapshot() { + if (!name_.empty() && !manager_->DeleteSnapshot(lock_, name_)) { + LOG(ERROR) << "Failed to auto delete snapshot " << name_; + } +} + } // namespace snapshot } // namespace android diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h index cbab4721d..7e7e24ad9 100644 --- a/fs_mgr/libsnapshot/utility.h +++ b/fs_mgr/libsnapshot/utility.h @@ -14,11 +14,14 @@ #pragma once +#include #include #include #include #include +#include +#include namespace android { namespace snapshot { @@ -81,5 +84,24 @@ struct AutoUnmapImage : AutoDevice { android::fiemap::IImageManager* images_ = nullptr; }; +// Automatically deletes a snapshot. |name| should be the name of the partition, e.g. "system_a". +// Client is responsible for maintaining the lifetime of |manager| and |lock|. +struct AutoDeleteSnapshot : AutoDevice { + AutoDeleteSnapshot(SnapshotManager* manager, SnapshotManager::LockedFile* lock, + const std::string& name) + : AutoDevice(name), manager_(manager), lock_(lock) {} + AutoDeleteSnapshot(AutoDeleteSnapshot&& other); + ~AutoDeleteSnapshot(); + + private: + DISALLOW_COPY_AND_ASSIGN(AutoDeleteSnapshot); + SnapshotManager* manager_ = nullptr; + SnapshotManager::LockedFile* lock_ = nullptr; +}; + +// Return a list of partitions in |builder| with the name ending in |suffix|. +std::vector ListPartitionsWithSuffix( + android::fs_mgr::MetadataBuilder* builder, const std::string& suffix); + } // namespace snapshot } // namespace android