From d22459dff30dd4a030dca04aec683b42fb927e50 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Fri, 13 Sep 2019 14:49:53 -0700 Subject: [PATCH] libsnapshot: Add snapshot metadata updater. This class knows how to update super metadata for virtual A/B devices. Test: libsnapshot_test Bug: 138816109 Change-Id: I9e375c76814e0dcbb47fc2ea9e4903ba69ccf7f8 --- fs_mgr/libsnapshot/Android.bp | 7 + .../libsnapshot/snapshot_metadata_updater.cpp | 273 +++++++++++++++ .../libsnapshot/snapshot_metadata_updater.h | 85 +++++ .../snapshot_metadata_updater_test.cpp | 328 ++++++++++++++++++ fs_mgr/libsnapshot/test_helpers.cpp | 37 ++ fs_mgr/libsnapshot/test_helpers.h | 14 + fs_mgr/libsnapshot/utility.h | 1 + 7 files changed, 745 insertions(+) create mode 100644 fs_mgr/libsnapshot/snapshot_metadata_updater.cpp create mode 100644 fs_mgr/libsnapshot/snapshot_metadata_updater.h create mode 100644 fs_mgr/libsnapshot/snapshot_metadata_updater_test.cpp diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp index f73b18975..bb941dd9b 100644 --- a/fs_mgr/libsnapshot/Android.bp +++ b/fs_mgr/libsnapshot/Android.bp @@ -32,6 +32,7 @@ cc_defaults { "libfs_mgr", "libfstab", "liblp", + "update_metadata-protos", ], whole_static_libs: [ "libext2_uuid", @@ -41,6 +42,9 @@ cc_defaults { header_libs: [ "libfiemap_headers", ], + export_static_lib_headers: [ + "update_metadata-protos", + ], export_header_lib_headers: [ "libfiemap_headers", ], @@ -51,6 +55,7 @@ filegroup { name: "libsnapshot_sources", srcs: [ "snapshot.cpp", + "snapshot_metadata_updater.cpp", "partition_cow_creator.cpp", "utility.cpp", ], @@ -87,10 +92,12 @@ cc_test { srcs: [ "snapshot_test.cpp", "partition_cow_creator_test.cpp", + "snapshot_metadata_updater_test.cpp", "test_helpers.cpp", ], shared_libs: [ "libbinder", + "libprotobuf-cpp-lite", "libutils", ], static_libs: [ diff --git a/fs_mgr/libsnapshot/snapshot_metadata_updater.cpp b/fs_mgr/libsnapshot/snapshot_metadata_updater.cpp new file mode 100644 index 000000000..60bf79647 --- /dev/null +++ b/fs_mgr/libsnapshot/snapshot_metadata_updater.cpp @@ -0,0 +1,273 @@ +// +// 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 "snapshot_metadata_updater.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using android::fs_mgr::MetadataBuilder; +using android::fs_mgr::Partition; +using android::fs_mgr::SlotSuffixForSlotNumber; +using chromeos_update_engine::DeltaArchiveManifest; + +namespace android { +namespace snapshot { +SnapshotMetadataUpdater::SnapshotMetadataUpdater(MetadataBuilder* builder, uint32_t target_slot, + const DeltaArchiveManifest& manifest) + : builder_(builder), target_suffix_(SlotSuffixForSlotNumber(target_slot)) { + if (!manifest.has_dynamic_partition_metadata()) { + return; + } + + // Key: partition name ("system"). Value: group name ("group"). + // No suffix. + std::map partition_group_map; + const auto& metadata_groups = manifest.dynamic_partition_metadata().groups(); + groups_.reserve(metadata_groups.size()); + for (const auto& group : metadata_groups) { + groups_.emplace_back(Group{group.name() + target_suffix_, &group}); + for (const auto& partition_name : group.partition_names()) { + partition_group_map[partition_name] = group.name(); + } + } + + for (const auto& p : manifest.partitions()) { + auto it = partition_group_map.find(p.partition_name()); + if (it != partition_group_map.end()) { + partitions_.emplace_back(Partition{p.partition_name() + target_suffix_, + std::string(it->second) + target_suffix_, &p}); + } + } +} + +bool SnapshotMetadataUpdater::ShrinkPartitions() const { + for (const auto& partition_update : partitions_) { + auto* existing_partition = builder_->FindPartition(partition_update.name); + if (existing_partition == nullptr) { + continue; + } + auto new_size = partition_update->new_partition_info().size(); + if (existing_partition->size() <= new_size) { + continue; + } + if (!builder_->ResizePartition(existing_partition, new_size)) { + return false; + } + } + return true; +} + +bool SnapshotMetadataUpdater::DeletePartitions() const { + std::vector partitions_to_delete; + // Don't delete partitions in groups where the group name doesn't have target_suffix, + // e.g. default. + for (auto* existing_partition : ListPartitionsWithSuffix(builder_, target_suffix_)) { + auto iter = std::find_if(partitions_.begin(), partitions_.end(), + [existing_partition](auto&& partition_update) { + return partition_update.name == existing_partition->name(); + }); + // Update package metadata doesn't have this partition. Prepare to delete it. + // Not deleting from builder_ yet because it may break ListPartitionsWithSuffix if it were + // to return an iterable view of builder_. + if (iter == partitions_.end()) { + partitions_to_delete.push_back(existing_partition->name()); + } + } + + for (const auto& partition_name : partitions_to_delete) { + builder_->RemovePartition(partition_name); + } + return true; +} + +bool SnapshotMetadataUpdater::MovePartitionsToDefault() const { + for (const auto& partition_update : partitions_) { + auto* existing_partition = builder_->FindPartition(partition_update.name); + if (existing_partition == nullptr) { + continue; + } + if (existing_partition->group_name() == partition_update.group_name) { + continue; + } + // Move to "default" group (which doesn't have maximum size constraint) + // temporarily. + if (!builder_->ChangePartitionGroup(existing_partition, android::fs_mgr::kDefaultGroup)) { + return false; + } + } + return true; +} + +bool SnapshotMetadataUpdater::ShrinkGroups() const { + for (const auto& group_update : groups_) { + auto* existing_group = builder_->FindGroup(group_update.name); + if (existing_group == nullptr) { + continue; + } + if (existing_group->maximum_size() <= group_update->size()) { + continue; + } + if (!builder_->ChangeGroupSize(existing_group->name(), group_update->size())) { + return false; + } + } + return true; +} + +bool SnapshotMetadataUpdater::DeleteGroups() const { + std::vector existing_groups = builder_->ListGroups(); + for (const auto& existing_group_name : existing_groups) { + // Don't delete groups without target suffix, e.g. default. + if (!android::base::EndsWith(existing_group_name, target_suffix_)) { + continue; + } + + auto iter = std::find_if(groups_.begin(), groups_.end(), + [&existing_group_name](auto&& group_update) { + return group_update.name == existing_group_name; + }); + // Update package metadata has this group as well, so not deleting it. + if (iter != groups_.end()) { + continue; + } + // Update package metadata doesn't have this group. Before deleting it, sanity check that it + // doesn't have any partitions left. Update metadata shouldn't assign any partitions to this + // group, so all partitions that originally belong to this group should be moved by + // MovePartitionsToDefault at this point. + auto existing_partitions_in_group = builder_->ListPartitionsInGroup(existing_group_name); + if (!existing_partitions_in_group.empty()) { + std::vector partition_names_in_group; + std::transform(existing_partitions_in_group.begin(), existing_partitions_in_group.end(), + std::back_inserter(partition_names_in_group), + [](auto* p) { return p->name(); }); + LOG(ERROR) + << "Group " << existing_group_name + << " cannot be deleted because the following partitions are left unassigned: [" + << android::base::Join(partition_names_in_group, ",") << "]"; + return false; + } + builder_->RemoveGroupAndPartitions(existing_group_name); + } + return true; +} + +bool SnapshotMetadataUpdater::AddGroups() const { + for (const auto& group_update : groups_) { + if (builder_->FindGroup(group_update.name) == nullptr) { + if (!builder_->AddGroup(group_update.name, group_update->size())) { + return false; + } + } + } + return true; +} + +bool SnapshotMetadataUpdater::GrowGroups() const { + for (const auto& group_update : groups_) { + auto* existing_group = builder_->FindGroup(group_update.name); + if (existing_group == nullptr) { + continue; + } + if (existing_group->maximum_size() >= group_update->size()) { + continue; + } + if (!builder_->ChangeGroupSize(existing_group->name(), group_update->size())) { + return false; + } + } + return true; +} + +bool SnapshotMetadataUpdater::AddPartitions() const { + for (const auto& partition_update : partitions_) { + if (builder_->FindPartition(partition_update.name) == nullptr) { + auto* p = + builder_->AddPartition(partition_update.name, partition_update.group_name, + LP_PARTITION_ATTR_READONLY | LP_PARTITION_ATTR_UPDATED); + if (p == nullptr) { + return false; + } + } + } + // Will be resized in GrowPartitions. + return true; +} + +bool SnapshotMetadataUpdater::GrowPartitions() const { + for (const auto& partition_update : partitions_) { + auto* existing_partition = builder_->FindPartition(partition_update.name); + if (existing_partition == nullptr) { + continue; + } + auto new_size = partition_update->new_partition_info().size(); + if (existing_partition->size() >= new_size) { + continue; + } + if (!builder_->ResizePartition(existing_partition, new_size)) { + return false; + } + } + return true; +} + +bool SnapshotMetadataUpdater::MovePartitionsToCorrectGroup() const { + for (const auto& partition_update : partitions_) { + auto* existing_partition = builder_->FindPartition(partition_update.name); + if (existing_partition == nullptr) { + continue; + } + if (existing_partition->group_name() == partition_update.group_name) { + continue; + } + if (!builder_->ChangePartitionGroup(existing_partition, partition_update.group_name)) { + return false; + } + } + return true; +} + +bool SnapshotMetadataUpdater::Update() const { + // Remove extents used by COW devices by removing the COW group completely. + builder_->RemoveGroupAndPartitions(android::snapshot::kCowGroupName); + + // The order of these operations are important so that we + // always have enough space to grow or add new partitions / groups. + // clang-format off + return ShrinkPartitions() && + DeletePartitions() && + MovePartitionsToDefault() && + ShrinkGroups() && + DeleteGroups() && + AddGroups() && + GrowGroups() && + AddPartitions() && + GrowPartitions() && + MovePartitionsToCorrectGroup(); + // clang-format on +} +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/snapshot_metadata_updater.h b/fs_mgr/libsnapshot/snapshot_metadata_updater.h new file mode 100644 index 000000000..83c94607d --- /dev/null +++ b/fs_mgr/libsnapshot/snapshot_metadata_updater.h @@ -0,0 +1,85 @@ +// +// 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 + +#include "utility.h" + +namespace android { +namespace snapshot { + +// Helper class that modifies a super partition metadata for an update for +// Virtual A/B devices. +class SnapshotMetadataUpdater { + using DeltaArchiveManifest = chromeos_update_engine::DeltaArchiveManifest; + using DynamicPartitionMetadata = chromeos_update_engine::DynamicPartitionMetadata; + using DynamicPartitionGroup = chromeos_update_engine::DynamicPartitionGroup; + using PartitionUpdate = chromeos_update_engine::PartitionUpdate; + + public: + // Caller is responsible for ensuring the lifetime of manifest to be longer + // than SnapshotMetadataUpdater. + SnapshotMetadataUpdater(android::fs_mgr::MetadataBuilder* builder, uint32_t target_slot, + const DeltaArchiveManifest& manifest); + bool Update() const; + + private: + bool RenameGroupSuffix() const; + bool ShrinkPartitions() const; + bool DeletePartitions() const; + bool MovePartitionsToDefault() const; + bool ShrinkGroups() const; + bool DeleteGroups() const; + bool AddGroups() const; + bool GrowGroups() const; + bool AddPartitions() const; + bool GrowPartitions() const; + bool MovePartitionsToCorrectGroup() const; + + // Wraps a DynamicPartitionGroup with a slot-suffixed name. Always use + // .name instead of ->name() because .name has the slot suffix (e.g. + // .name is "group_b" and ->name() is "group".) + struct Group { + std::string name; + const DynamicPartitionGroup* group; + const DynamicPartitionGroup* operator->() const { return group; } + }; + // Wraps a PartitionUpdate with a slot-suffixed name / group name. Always use + // .name instead of ->partition_name() because .name has the slot suffix (e.g. + // .name is "system_b" and ->partition_name() is "system".) + struct Partition { + std::string name; + std::string group_name; + const PartitionUpdate* partition; + const PartitionUpdate* operator->() const { return partition; } + }; + + android::fs_mgr::MetadataBuilder* const builder_; + const std::string target_suffix_; + std::vector groups_; + std::vector partitions_; +}; + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/snapshot_metadata_updater_test.cpp b/fs_mgr/libsnapshot/snapshot_metadata_updater_test.cpp new file mode 100644 index 000000000..535653a77 --- /dev/null +++ b/fs_mgr/libsnapshot/snapshot_metadata_updater_test.cpp @@ -0,0 +1,328 @@ +// +// 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 "snapshot_metadata_updater.h" + +#include +#include + +#include +#include +#include +#include + +#include "test_helpers.h" + +using namespace android::storage_literals; +using android::fs_mgr::LpMetadata; +using android::fs_mgr::MetadataBuilder; +using android::fs_mgr::SlotSuffixForSlotNumber; +using chromeos_update_engine::DeltaArchiveManifest; +using chromeos_update_engine::DynamicPartitionGroup; +using chromeos_update_engine::PartitionUpdate; +using testing::AssertionFailure; +using testing::AssertionResult; +using testing::AssertionSuccess; + +namespace android { +namespace snapshot { + +class SnapshotMetadataUpdaterTest : public ::testing::TestWithParam { + public: + void SetUp() override { + target_slot_ = GetParam(); + target_suffix_ = SlotSuffixForSlotNumber(target_slot_); + builder_ = MetadataBuilder::New(4_GiB + 1_MiB, 4_KiB, 2); + + group_ = manifest_.mutable_dynamic_partition_metadata()->add_groups(); + group_->set_name("group"); + group_->set_size(4_GiB); + group_->add_partition_names("system"); + group_->add_partition_names("vendor"); + system_ = manifest_.add_partitions(); + system_->set_partition_name("system"); + SetSize(system_, 2_GiB); + vendor_ = manifest_.add_partitions(); + vendor_->set_partition_name("vendor"); + SetSize(vendor_, 1_GiB); + + ASSERT_TRUE(FillFakeMetadata(builder_.get(), manifest_, target_suffix_)); + } + + // Append suffix to name. + std::string T(std::string_view name) { return std::string(name) + target_suffix_; } + + AssertionResult UpdateAndExport() { + SnapshotMetadataUpdater updater(builder_.get(), target_slot_, manifest_); + if (!updater.Update()) { + return AssertionFailure() << "Update failed."; + } + + exported_ = builder_->Export(); + if (exported_ == nullptr) { + return AssertionFailure() << "Export failed."; + } + return AssertionSuccess(); + } + + // Check that in |builder_|, partition |name| + |target_suffix_| has the given |size|. + AssertionResult CheckSize(std::string_view name, uint64_t size) { + auto p = builder_->FindPartition(T(name)); + if (p == nullptr) { + return AssertionFailure() << "Cannot find partition " << T(name); + } + if (p->size() != size) { + return AssertionFailure() << "Partition " << T(name) << " should be " << size + << " bytes, but is " << p->size() << " bytes."; + } + return AssertionSuccess() << "Partition" << T(name) << " is " << size << " bytes."; + } + + // Check that in |builder_|, group |name| + |target_suffix_| has the given |size|. + AssertionResult CheckGroupSize(std::string_view name, uint64_t size) { + auto g = builder_->FindGroup(T(name)); + if (g == nullptr) { + return AssertionFailure() << "Cannot find group " << T(name); + } + if (g->maximum_size() != size) { + return AssertionFailure() << "Group " << T(name) << " should be " << size + << " bytes, but is " << g->maximum_size() << " bytes."; + } + return AssertionSuccess() << "Group" << T(name) << " is " << size << " bytes."; + } + + // Check that in |builder_|, partition |partition_name| + |target_suffix_| is in group + // |group_name| + |target_suffix_|; + AssertionResult CheckGroupName(std::string_view partition_name, std::string_view group_name) { + auto p = builder_->FindPartition(T(partition_name)); + if (p == nullptr) { + return AssertionFailure() << "Cannot find partition " << T(partition_name); + } + if (p->group_name() != T(group_name)) { + return AssertionFailure() << "Partition " << T(partition_name) << " should be in " + << T(group_name) << ", but is in " << p->group_name() << "."; + } + return AssertionSuccess() << "Partition" << T(partition_name) << " is in " << T(group_name) + << "."; + } + + std::unique_ptr builder_; + uint32_t target_slot_; + std::string target_suffix_; + DeltaArchiveManifest manifest_; + std::unique_ptr exported_; + DynamicPartitionGroup* group_ = nullptr; + PartitionUpdate* system_ = nullptr; + PartitionUpdate* vendor_ = nullptr; +}; + +TEST_P(SnapshotMetadataUpdaterTest, NoChange) { + EXPECT_TRUE(UpdateAndExport()); + + EXPECT_TRUE(CheckGroupSize("group", 4_GiB)); + EXPECT_TRUE(CheckSize("system", 2_GiB)); + EXPECT_TRUE(CheckGroupName("system", "group")); + EXPECT_TRUE(CheckSize("vendor", 1_GiB)); + EXPECT_TRUE(CheckGroupName("vendor", "group")); +} + +TEST_P(SnapshotMetadataUpdaterTest, GrowWithinBounds) { + SetSize(system_, 2_GiB + 512_MiB); + SetSize(vendor_, 1_GiB + 512_MiB); + + ASSERT_TRUE(UpdateAndExport()); + + EXPECT_TRUE(CheckSize("system", 2_GiB + 512_MiB)); + EXPECT_TRUE(CheckSize("vendor", 1_GiB + 512_MiB)); +} + +TEST_P(SnapshotMetadataUpdaterTest, GrowOverSuper) { + SetSize(system_, 3_GiB); + SetSize(vendor_, 1_GiB + 512_MiB); + + EXPECT_FALSE(UpdateAndExport()); +} + +TEST_P(SnapshotMetadataUpdaterTest, GrowOverGroup) { + SetSize(system_, 3_GiB); + SetSize(vendor_, 1_GiB + 4_KiB); + + EXPECT_FALSE(UpdateAndExport()); +} + +TEST_P(SnapshotMetadataUpdaterTest, Add) { + group_->add_partition_names("product"); + auto product = manifest_.add_partitions(); + product->set_partition_name("product"); + SetSize(product, 1_GiB); + + EXPECT_TRUE(UpdateAndExport()); + + EXPECT_TRUE(CheckSize("system", 2_GiB)); + EXPECT_TRUE(CheckSize("vendor", 1_GiB)); + EXPECT_TRUE(CheckSize("product", 1_GiB)); +} + +TEST_P(SnapshotMetadataUpdaterTest, AddTooBig) { + group_->add_partition_names("product"); + auto product = manifest_.add_partitions(); + product->set_partition_name("product"); + SetSize(product, 1_GiB + 4_KiB); + + EXPECT_FALSE(UpdateAndExport()); +} + +TEST_P(SnapshotMetadataUpdaterTest, ShrinkAll) { + SetSize(system_, 1_GiB); + SetSize(vendor_, 512_MiB); + + ASSERT_TRUE(UpdateAndExport()); + + EXPECT_TRUE(CheckSize("system", 1_GiB)); + EXPECT_TRUE(CheckSize("vendor", 512_MiB)); +} + +TEST_P(SnapshotMetadataUpdaterTest, ShrinkAndGrow) { + SetSize(system_, 3_GiB + 512_MiB); + SetSize(vendor_, 512_MiB); + + ASSERT_TRUE(UpdateAndExport()); + + EXPECT_TRUE(CheckSize("system", 3_GiB + 512_MiB)); + EXPECT_TRUE(CheckSize("vendor", 512_MiB)); +} + +TEST_P(SnapshotMetadataUpdaterTest, ShrinkAndAdd) { + SetSize(system_, 2_GiB); + SetSize(vendor_, 512_MiB); + group_->add_partition_names("product"); + auto product = manifest_.add_partitions(); + product->set_partition_name("product"); + SetSize(product, 1_GiB + 512_MiB); + + ASSERT_TRUE(UpdateAndExport()); + + EXPECT_TRUE(CheckSize("system", 2_GiB)); + EXPECT_TRUE(CheckSize("vendor", 512_MiB)); + EXPECT_TRUE(CheckSize("product", 1_GiB + 512_MiB)); +} + +TEST_P(SnapshotMetadataUpdaterTest, Delete) { + group_->mutable_partition_names()->RemoveLast(); + // No need to delete it from manifest.partitions as SnapshotMetadataUpdater + // should ignore them (treat them as static partitions). + + EXPECT_TRUE(UpdateAndExport()); + + EXPECT_TRUE(CheckSize("system", 2_GiB)); + EXPECT_EQ(nullptr, builder_->FindPartition(T("vendor"))); +} + +TEST_P(SnapshotMetadataUpdaterTest, DeleteAndGrow) { + group_->mutable_partition_names()->RemoveLast(); + SetSize(system_, 4_GiB); + + EXPECT_TRUE(UpdateAndExport()); + + EXPECT_TRUE(CheckSize("system", 4_GiB)); +} + +TEST_P(SnapshotMetadataUpdaterTest, DeleteAndAdd) { + group_->mutable_partition_names()->RemoveLast(); + group_->add_partition_names("product"); + auto product = manifest_.add_partitions(); + product->set_partition_name("product"); + SetSize(product, 2_GiB); + + EXPECT_TRUE(UpdateAndExport()); + + EXPECT_TRUE(CheckSize("system", 2_GiB)); + EXPECT_EQ(nullptr, builder_->FindPartition(T("vendor"))); + EXPECT_TRUE(CheckSize("product", 2_GiB)); +} + +TEST_P(SnapshotMetadataUpdaterTest, GrowGroup) { + group_->set_size(4_GiB + 512_KiB); + SetSize(system_, 2_GiB + 256_KiB); + SetSize(vendor_, 2_GiB + 256_KiB); + + EXPECT_TRUE(UpdateAndExport()); + + EXPECT_TRUE(CheckSize("system", 2_GiB + 256_KiB)); + EXPECT_TRUE(CheckSize("vendor", 2_GiB + 256_KiB)); +} + +TEST_P(SnapshotMetadataUpdaterTest, ShrinkGroup) { + group_->set_size(1_GiB); + SetSize(system_, 512_MiB); + SetSize(vendor_, 512_MiB); + + EXPECT_TRUE(UpdateAndExport()); + + EXPECT_TRUE(CheckSize("system", 512_MiB)); + EXPECT_TRUE(CheckSize("vendor", 512_MiB)); +} + +TEST_P(SnapshotMetadataUpdaterTest, MoveToNewGroup) { + group_->mutable_partition_names()->RemoveLast(); + group_->set_size(2_GiB); + + auto another_group = manifest_.mutable_dynamic_partition_metadata()->add_groups(); + another_group->set_name("another_group"); + another_group->set_size(2_GiB); + another_group->add_partition_names("vendor"); + SetSize(vendor_, 2_GiB); + + EXPECT_TRUE(UpdateAndExport()); + + EXPECT_TRUE(CheckGroupSize("group", 2_GiB)); + EXPECT_TRUE(CheckGroupSize("another_group", 2_GiB)); + EXPECT_TRUE(CheckSize("system", 2_GiB)); + EXPECT_TRUE(CheckGroupName("system", "group")); + EXPECT_TRUE(CheckSize("vendor", 2_GiB)); + EXPECT_TRUE(CheckGroupName("vendor", "another_group")); +} + +TEST_P(SnapshotMetadataUpdaterTest, DeleteAndAddGroup) { + manifest_.mutable_dynamic_partition_metadata()->mutable_groups()->RemoveLast(); + group_ = nullptr; + + auto another_group = manifest_.mutable_dynamic_partition_metadata()->add_groups(); + another_group->set_name("another_group"); + another_group->set_size(4_GiB); + another_group->add_partition_names("system"); + another_group->add_partition_names("vendor"); + another_group->add_partition_names("product"); + auto product = manifest_.add_partitions(); + product->set_partition_name("product"); + SetSize(product, 1_GiB); + + EXPECT_TRUE(UpdateAndExport()); + + EXPECT_EQ(nullptr, builder_->FindGroup(T("group"))); + EXPECT_TRUE(CheckGroupSize("another_group", 4_GiB)); + EXPECT_TRUE(CheckSize("system", 2_GiB)); + EXPECT_TRUE(CheckGroupName("system", "another_group")); + EXPECT_TRUE(CheckSize("vendor", 1_GiB)); + EXPECT_TRUE(CheckGroupName("vendor", "another_group")); + EXPECT_TRUE(CheckSize("product", 1_GiB)); + EXPECT_TRUE(CheckGroupName("product", "another_group")); +} + +INSTANTIATE_TEST_SUITE_P(, SnapshotMetadataUpdaterTest, testing::Values(0, 1)); + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp index e12ec7744..1a6a5932f 100644 --- a/fs_mgr/libsnapshot/test_helpers.cpp +++ b/fs_mgr/libsnapshot/test_helpers.cpp @@ -27,6 +27,8 @@ using android::base::ReadFully; using android::base::unique_fd; using android::base::WriteFully; using android::fiemap::IImageManager; +using testing::AssertionFailure; +using testing::AssertionSuccess; void DeleteBackingImage(IImageManager* manager, const std::string& name) { if (manager->IsImageMapped(name)) { @@ -110,5 +112,40 @@ std::optional GetHash(const std::string& path) { return ToHexString(out, sizeof(out)); } +AssertionResult FillFakeMetadata(MetadataBuilder* builder, const DeltaArchiveManifest& manifest, + const std::string& suffix) { + for (const auto& group : manifest.dynamic_partition_metadata().groups()) { + if (!builder->AddGroup(group.name() + suffix, group.size())) { + return AssertionFailure() + << "Cannot add group " << group.name() << " with size " << group.size(); + } + for (const auto& partition_name : group.partition_names()) { + auto p = builder->AddPartition(partition_name + suffix, group.name() + suffix, + 0 /* attr */); + if (!p) { + return AssertionFailure() << "Cannot add partition " << partition_name + suffix + << " to group " << group.name() << suffix; + } + } + } + for (const auto& partition : manifest.partitions()) { + auto p = builder->FindPartition(partition.partition_name() + suffix); + if (!p) { + return AssertionFailure() << "Cannot resize partition " << partition.partition_name() + << suffix << "; it is not found."; + } + if (!builder->ResizePartition(p, partition.new_partition_info().size())) { + return AssertionFailure() + << "Cannot resize partition " << partition.partition_name() << suffix + << " to size " << partition.new_partition_info().size(); + } + } + return AssertionSuccess(); +} + +void SetSize(PartitionUpdate* partition_update, uint64_t size) { + partition_update->mutable_new_partition_info()->set_size(size); +} + } // namespace snapshot } // namespace android diff --git a/fs_mgr/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/test_helpers.h index d917e350b..9d8c7b083 100644 --- a/fs_mgr/libsnapshot/test_helpers.h +++ b/fs_mgr/libsnapshot/test_helpers.h @@ -17,13 +17,20 @@ #include #include +#include #include #include #include +#include namespace android { namespace snapshot { +using android::fs_mgr::MetadataBuilder; +using chromeos_update_engine::DeltaArchiveManifest; +using chromeos_update_engine::PartitionUpdate; +using testing::AssertionResult; + using namespace std::string_literals; // Redirect requests for "super" to our fake super partition. @@ -72,5 +79,12 @@ bool WriteRandomData(const std::string& device); std::optional GetHash(const std::string& path); +// Add partitions and groups described by |manifest|. +AssertionResult FillFakeMetadata(MetadataBuilder* builder, const DeltaArchiveManifest& manifest, + const std::string& suffix); + +// In the update package metadata, set a partition with the given size. +void SetSize(PartitionUpdate* partition_update, uint64_t size); + } // namespace snapshot } // namespace android diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h index a700c467f..75c694c10 100644 --- a/fs_mgr/libsnapshot/utility.h +++ b/fs_mgr/libsnapshot/utility.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace android { namespace snapshot {