| // |
| // 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 <algorithm> |
| #include <map> |
| #include <optional> |
| #include <set> |
| #include <string> |
| #include <string_view> |
| #include <vector> |
| |
| #include <android-base/logging.h> |
| #include <android-base/strings.h> |
| #include <fs_mgr.h> |
| #include <libsnapshot/snapshot.h> |
| |
| 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)) { |
| partial_update_ = manifest.partial_update(); |
| |
| if (!manifest.has_dynamic_partition_metadata()) { |
| return; |
| } |
| |
| // Key: partition name ("system"). Value: group name ("group"). |
| // No suffix. |
| std::map<std::string_view, std::string_view> 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 { |
| // For partial update, not all dynamic partitions are included in the payload. |
| // TODO(xunchang) delete the untouched partitions whose group is in the payload. |
| // e.g. Delete vendor in the following scenario |
| // On device: |
| // Group A: system, vendor |
| // In payload: |
| // Group A: system |
| if (partial_update_) { |
| LOG(INFO) << "Skip deleting partitions for partial update"; |
| return true; |
| } |
| |
| std::vector<std::string> 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 { |
| if (partial_update_) { |
| LOG(INFO) << "Skip deleting groups for partial update"; |
| return true; |
| } |
| |
| std::vector<std::string> 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, 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<std::string> 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 |