libsnapshot: CreateUpdateSnapshots reuse COW space
At the second update for Virtual A/B device,
PartitionCowCreator thinks the COW partitions in
the existing super metadata as occupied. In fact, these
partitions aren't used after the merge.
Now, unmap these partitions from the device mapper. If
something bad happens and the previous update has not been
merged yet, the unmap will fail. Then, delete these old
COW partitions from the device.
Test: Virtual A/B update twice
Bug: 135752105
Change-Id: Iab867ded19755089e6e0800e553a10fbcddbb931
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 9d0272b..2e689bd 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -1741,6 +1741,22 @@
return true;
}
+static void UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata) {
+ auto& dm = DeviceMapper::Instance();
+ std::vector<std::string> to_delete;
+ for (auto* existing_cow_partition : current_metadata->ListPartitionsInGroup(kCowGroupName)) {
+ if (!dm.DeleteDeviceIfExists(existing_cow_partition->name())) {
+ LOG(WARNING) << existing_cow_partition->name()
+ << " cannot be unmapped and its space cannot be reclaimed";
+ continue;
+ }
+ to_delete.push_back(existing_cow_partition->name());
+ }
+ for (const auto& name : to_delete) {
+ current_metadata->RemovePartition(name);
+ }
+}
+
bool SnapshotManager::CreateUpdateSnapshots(const DeltaArchiveManifest& manifest) {
auto lock = LockExclusive();
if (!lock) return false;
@@ -1788,6 +1804,10 @@
return false;
}
+ // Delete previous COW partitions in current_metadata so that PartitionCowCreator marks those as
+ // free regions.
+ UnmapAndDeleteCowPartition(current_metadata.get());
+
// 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
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index c7b2b4c..f67e76e 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -47,8 +47,10 @@
using android::fs_mgr::BlockDeviceInfo;
using android::fs_mgr::CreateLogicalPartitionParams;
using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::Extent;
using android::fs_mgr::GetPartitionGroupName;
using android::fs_mgr::GetPartitionName;
+using android::fs_mgr::Interval;
using android::fs_mgr::MetadataBuilder;
using chromeos_update_engine::DeltaArchiveManifest;
using chromeos_update_engine::PartitionUpdate;
@@ -983,6 +985,57 @@
ASSERT_TRUE(sm->CancelUpdate());
}
+static std::vector<Interval> ToIntervals(const std::vector<std::unique_ptr<Extent>>& extents) {
+ std::vector<Interval> ret;
+ std::transform(extents.begin(), extents.end(), std::back_inserter(ret),
+ [](const auto& extent) { return extent->AsLinearExtent()->AsInterval(); });
+ return ret;
+}
+
+// Test that at the second update, old COW partition spaces are reclaimed.
+TEST_F(SnapshotUpdateTest, ReclaimCow) {
+ // Execute the first update.
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+ ASSERT_TRUE(sm->FinishedSnapshotWrites());
+
+ // Simulate shutting down the device.
+ ASSERT_TRUE(UnmapAll());
+
+ // After reboot, init does first stage mount.
+ auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+ ASSERT_NE(init, nullptr);
+ ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+ ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+ init = nullptr;
+
+ // Initiate the merge and wait for it to be completed.
+ auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
+ ASSERT_TRUE(new_sm->InitiateMerge());
+ ASSERT_EQ(UpdateState::MergeCompleted, new_sm->ProcessUpdateState());
+
+ // Execute the second update.
+ ASSERT_TRUE(new_sm->BeginUpdate());
+ ASSERT_TRUE(new_sm->CreateUpdateSnapshots(manifest_));
+
+ // Check that the old COW space is reclaimed and does not occupy space of mapped partitions.
+ auto src = MetadataBuilder::New(*opener_, "super", 1);
+ auto tgt = MetadataBuilder::New(*opener_, "super", 0);
+ for (const auto& cow_part_name : {"sys_a-cow", "vnd_a-cow", "prd_a-cow"}) {
+ auto* cow_part = tgt->FindPartition(cow_part_name);
+ ASSERT_NE(nullptr, cow_part) << cow_part_name << " does not exist in target metadata";
+ auto cow_intervals = ToIntervals(cow_part->extents());
+ for (const auto& old_part_name : {"sys_b", "vnd_b", "prd_b"}) {
+ auto* old_part = src->FindPartition(old_part_name);
+ ASSERT_NE(nullptr, old_part) << old_part_name << " does not exist in source metadata";
+ auto old_intervals = ToIntervals(old_part->extents());
+
+ auto intersect = Interval::Intersect(cow_intervals, old_intervals);
+ ASSERT_TRUE(intersect.empty()) << "COW uses space of source partitions";
+ }
+ }
+}
+
} // namespace snapshot
} // namespace android