diff options
author | Yifan Hong <elsk@google.com> | 2018-10-18 17:46:27 -0700 |
---|---|---|
committer | Yifan Hong <elsk@google.com> | 2018-10-26 18:23:52 +0000 |
commit | d4db07e9bbf7c606d20e88c1d8d4f36697718ce7 (patch) | |
tree | 3400460f40d42709694c9dfdeea9ae789f4520c3 | |
parent | 9acd9cb3eda3e274ba137b3fb9e4b598965d5647 (diff) |
Support updateable groups.
Adds updateable group support to OTA.
* DeltaPerformer combines partition sizes with
dynamic_partition_metadata to
BootControlInterface::PartitionMetadata.
* BootControlAndroid::InitPartitionMetadata:
* Copy all groups / partitions from source metadata slot
* Remove all groups / partitions mentioned in the manifest (of the
target slot)
* Re-add all groups / partitions mentioned in the manifest.
* BootControlAndroid::InitPartitionMetadata can check
the incoming PartitionMetadata to see if a partition is dynamic
or not. The guessing logic is completely removed.
* Because a partition is removed then re-added, there is no need
for preserving the entry with size 0 to indicate that a partition
is removed. When update_engine sees a partition in a group "foo" on
the device, but manifest contains group "foo" without the partition,
it removes the partition.
* Hence, Removing a partition does NOT require keeping the entry (i.e.
RemovePartition is used instead of ShrinkPartition(0) ). This makes
retrofitting dynamic partitions on older devices easier.
The following is now allowed:
- Adding / removing / resizing partitions
- Adding / resizing groups
It is not allowed to remove a group, but a group can always be resized
to zero to deprecate it.
Test: update_engine_unittests
Bug: 117182932
Change-Id: I39d77f1d1d1fc52fc245f3de699635e6a429015e
-rw-r--r-- | boot_control_android.cc | 234 | ||||
-rw-r--r-- | boot_control_android_unittest.cc | 571 | ||||
-rw-r--r-- | common/boot_control_interface.h | 24 | ||||
-rw-r--r-- | payload_consumer/delta_performer.cc | 36 |
4 files changed, 556 insertions, 309 deletions
diff --git a/boot_control_android.cc b/boot_control_android.cc index 61711d2b..8eafdcea 100644 --- a/boot_control_android.cc +++ b/boot_control_android.cc @@ -18,9 +18,11 @@ #include <memory> #include <utility> +#include <vector> #include <base/bind.h> #include <base/logging.h> +#include <base/strings/string_util.h> #include <bootloader_message/bootloader_message.h> #include <brillo/message_loops/message_loop.h> #include <fs_mgr.h> @@ -31,9 +33,7 @@ using std::string; using android::dm::DmDeviceState; -using android::fs_mgr::MetadataBuilder; using android::fs_mgr::Partition; -using android::fs_mgr::UpdatePartitionTable; using android::hardware::hidl_string; using android::hardware::Return; using android::hardware::boot::V1_0::BoolResult; @@ -225,117 +225,103 @@ bool BootControlAndroid::MarkBootSuccessfulAsync( namespace { -// Resize |partition_name|_|slot| to the given |size|. -bool ResizePartition(MetadataBuilder* builder, - const string& target_partition_name, - uint64_t size) { - Partition* partition = builder->FindPartition(target_partition_name); - if (partition == nullptr) { - LOG(ERROR) << "Cannot find " << target_partition_name << " in metadata."; +bool InitPartitionMetadataInternal( + DynamicPartitionControlInterface* dynamic_control, + const string& super_device, + Slot source_slot, + Slot target_slot, + const string& target_suffix, + const PartitionMetadata& partition_metadata) { + auto builder = + dynamic_control->LoadMetadataBuilder(super_device, source_slot); + if (builder == nullptr) { + // TODO(elsk): allow reconstructing metadata from partition_metadata + // in recovery sideload. + LOG(ERROR) << "No metadata at " + << BootControlInterface::SlotName(source_slot); return false; } - uint64_t old_size = partition->size(); - const string action = "resize " + target_partition_name + " in super (" + - std::to_string(old_size) + " -> " + - std::to_string(size) + " bytes)"; - if (!builder->ResizePartition(partition, size)) { - LOG(ERROR) << "Cannot " << action << "; see previous log messages."; - return false; + std::vector<string> groups = builder->ListGroups(); + for (const auto& group_name : groups) { + if (base::EndsWith( + group_name, target_suffix, base::CompareCase::SENSITIVE)) { + LOG(INFO) << "Removing group " << group_name; + builder->RemoveGroupAndPartitions(group_name); + } } - if (partition->size() != size) { - LOG(ERROR) << "Cannot " << action - << "; value is misaligned and partition should have been " - << partition->size(); - return false; + uint64_t total_size = 0; + for (const auto& group : partition_metadata.groups) { + total_size += group.size; } - LOG(INFO) << "Successfully " << action; - - return true; -} - -bool ResizePartitions(DynamicPartitionControlInterface* dynamic_control, - const string& super_device, - Slot target_slot, - const string& target_suffix, - const PartitionMetadata& logical_sizes, - MetadataBuilder* builder) { - // Delete all extents to ensure that each partition has enough space to - // grow. - for (const auto& pair : logical_sizes) { - const string target_partition_name = pair.first + target_suffix; - if (builder->FindPartition(target_partition_name) == nullptr) { - // Use constant GUID because it is unused. - LOG(INFO) << "Adding partition " << target_partition_name << " to slot " - << BootControlInterface::SlotName(target_slot) << " in " - << super_device; - if (builder->AddPartition(target_partition_name, - LP_PARTITION_ATTR_READONLY) == nullptr) { - LOG(ERROR) << "Cannot add partition " << target_partition_name; - return false; - } - } - if (!ResizePartition(builder, pair.first + target_suffix, 0 /* size */)) { - return false; - } + if (total_size > (builder->AllocatableSpace() / 2)) { + LOG(ERROR) + << "The maximum size of all groups with suffix " << target_suffix + << " (" << total_size + << ") has exceeded half of allocatable space for dynamic partitions " + << (builder->AllocatableSpace() / 2) << "."; + return false; } - for (const auto& pair : logical_sizes) { - if (!ResizePartition(builder, pair.first + target_suffix, pair.second)) { - LOG(ERROR) << "Not enough space?"; + for (const auto& group : partition_metadata.groups) { + auto group_name_suffix = group.name + target_suffix; + if (!builder->AddGroup(group_name_suffix, group.size)) { + LOG(ERROR) << "Cannot add group " << group_name_suffix << " with size " + << group.size; return false; } + LOG(INFO) << "Added group " << group_name_suffix << " with size " + << group.size; + + for (const auto& partition : group.partitions) { + auto parition_name_suffix = partition.name + target_suffix; + Partition* p = builder->AddPartition( + parition_name_suffix, group_name_suffix, LP_PARTITION_ATTR_READONLY); + if (!p) { + LOG(ERROR) << "Cannot add partition " << parition_name_suffix + << " to group " << group_name_suffix; + return false; + } + if (!builder->ResizePartition(p, partition.size)) { + LOG(ERROR) << "Cannot resize partition " << parition_name_suffix + << " to size " << partition.size << ". Not enough space?"; + return false; + } + LOG(INFO) << "Added partition " << parition_name_suffix << " to group " + << group_name_suffix << " with size " << partition.size; + } } - if (!dynamic_control->StoreMetadata(super_device, builder, target_slot)) { - return false; - } - return true; + return dynamic_control->StoreMetadata( + super_device, builder.get(), target_slot); } -// Assume upgrading from slot A to B. A partition foo is considered dynamic -// iff one of the following: -// 1. foo_a exists as a dynamic partition (so it should continue to be a -// dynamic partition) -// 2. foo_b does not exist as a static partition (in which case we may be -// adding a new partition). -bool IsDynamicPartition(DynamicPartitionControlInterface* dynamic_control, - const base::FilePath& device_dir, - MetadataBuilder* source_metadata, - const string& partition_name, - const string& source_suffix, - const string& target_suffix) { - bool dynamic_source_exist = - source_metadata->FindPartition(partition_name + source_suffix) != nullptr; - bool static_target_exist = dynamic_control->DeviceExists( - device_dir.Append(partition_name + target_suffix).value()); - - return dynamic_source_exist || !static_target_exist; -} - -bool FilterPartitionSizes(DynamicPartitionControlInterface* dynamic_control, - const base::FilePath& device_dir, - const PartitionMetadata& partition_metadata, - MetadataBuilder* source_metadata, - const string& source_suffix, - const string& target_suffix, - PartitionMetadata* logical_sizes) { - for (const auto& pair : partition_metadata) { - if (!IsDynamicPartition(dynamic_control, - device_dir, - source_metadata, - pair.first, - source_suffix, - target_suffix)) { - // In the future we can check static partition sizes, but skip for now. - LOG(INFO) << pair.first << " is static; assume its size is " - << pair.second << " bytes."; - continue; +// Unmap all partitions, and remap partitions. +bool Remap(DynamicPartitionControlInterface* dynamic_control, + const string& super_device, + Slot target_slot, + const string& target_suffix, + const PartitionMetadata& partition_metadata) { + for (const auto& group : partition_metadata.groups) { + for (const auto& partition : group.partitions) { + if (!dynamic_control->UnmapPartitionOnDeviceMapper( + partition.name + target_suffix, true /* wait */)) { + return false; + } + if (partition.size == 0) { + continue; + } + string map_path; + if (!dynamic_control->MapPartitionOnDeviceMapper( + super_device, + partition.name + target_suffix, + target_slot, + &map_path)) { + return false; + } } - - logical_sizes->insert(pair); } return true; } @@ -362,60 +348,28 @@ bool BootControlAndroid::InitPartitionMetadata( return false; } - string current_suffix; - if (!GetSuffix(current_slot, ¤t_suffix)) { - return false; - } - string target_suffix; if (!GetSuffix(target_slot, &target_suffix)) { return false; } - auto builder = - dynamic_control_->LoadMetadataBuilder(super_device, current_slot); - if (builder == nullptr) { - return false; - } - - // Read metadata from current slot to determine which partitions are logical - // and may be resized. Do not read from target slot because metadata at - // target slot may be corrupted. - PartitionMetadata logical_sizes; - if (!FilterPartitionSizes(dynamic_control_.get(), - device_dir, - partition_metadata, - builder.get() /* source metadata */, - current_suffix, - target_suffix, - &logical_sizes)) { + if (!InitPartitionMetadataInternal(dynamic_control_.get(), + super_device, + current_slot, + target_slot, + target_suffix, + partition_metadata)) { return false; } - if (!ResizePartitions(dynamic_control_.get(), - super_device, - target_slot, - target_suffix, - logical_sizes, - builder.get())) { + if (!Remap(dynamic_control_.get(), + super_device, + target_slot, + target_suffix, + partition_metadata)) { return false; } - // Unmap all partitions, and remap partitions if size is non-zero. - for (const auto& pair : logical_sizes) { - if (!dynamic_control_->UnmapPartitionOnDeviceMapper( - pair.first + target_suffix, true /* wait */)) { - return false; - } - if (pair.second == 0) { - continue; - } - string map_path; - if (!dynamic_control_->MapPartitionOnDeviceMapper( - super_device, pair.first + target_suffix, target_slot, &map_path)) { - return false; - } - } return true; } diff --git a/boot_control_android_unittest.cc b/boot_control_android_unittest.cc index 34eabb04..8185508a 100644 --- a/boot_control_android_unittest.cc +++ b/boot_control_android_unittest.cc @@ -17,8 +17,10 @@ #include "update_engine/boot_control_android.h" #include <set> +#include <vector> -#include <android-base/strings.h> +#include <base/logging.h> +#include <base/strings/string_util.h> #include <fs_mgr.h> #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -26,7 +28,6 @@ #include "update_engine/mock_boot_control_hal.h" #include "update_engine/mock_dynamic_partition_control.h" -using android::base::Join; using android::fs_mgr::MetadataBuilder; using android::hardware::Void; using testing::_; @@ -40,6 +41,7 @@ using testing::Matcher; using testing::MatcherInterface; using testing::MatchResultListener; using testing::NiceMock; +using testing::Not; using testing::Return; namespace chromeos_update_engine { @@ -49,19 +51,37 @@ constexpr const char* kSlotSuffixes[kMaxNumSlots] = {"_a", "_b"}; constexpr const char* kFakeDevicePath = "/fake/dev/path/"; constexpr const char* kFakeMappedPath = "/fake/mapped/path/"; constexpr const uint32_t kFakeMetadataSize = 65536; +constexpr const char* kDefaultGroup = "foo"; + +// "vendor" +struct PartitionName : std::string { + using std::string::string; +}; + +// "vendor_a" +struct PartitionNameSuffix : std::string { + using std::string::string; +}; // A map describing the size of each partition. -using PartitionSizes = std::map<std::string, uint64_t>; +using PartitionSizes = std::map<PartitionName, uint64_t>; +using PartitionSuffixSizes = std::map<PartitionNameSuffix, uint64_t>; + +using PartitionMetadata = BootControlInterface::PartitionMetadata; // C++ standards do not allow uint64_t (aka unsigned long) to be the parameter // of user-defined literal operators. -unsigned long long operator"" _MiB(unsigned long long x) { // NOLINT +constexpr unsigned long long operator"" _MiB(unsigned long long x) { // NOLINT return x << 20; } -unsigned long long operator"" _GiB(unsigned long long x) { // NOLINT +constexpr unsigned long long operator"" _GiB(unsigned long long x) { // NOLINT return x << 30; } +constexpr uint64_t kDefaultGroupSize = 5_GiB; +// Super device size. 1 MiB for metadata. +constexpr uint64_t kDefaultSuperSize = kDefaultGroupSize * 2 + 1_MiB; + template <typename U, typename V> std::ostream& operator<<(std::ostream& os, const std::map<U, V>& param) { os << "{"; @@ -75,6 +95,32 @@ std::ostream& operator<<(std::ostream& os, const std::map<U, V>& param) { return os << "}"; } +template <typename T> +std::ostream& operator<<(std::ostream& os, const std::vector<T>& param) { + os << "["; + bool first = true; + for (const auto& e : param) { + if (!first) + os << ", "; + os << e; + first = false; + } + return os << "]"; +} + +std::ostream& operator<<(std::ostream& os, + const PartitionMetadata::Partition& p) { + return os << "{" << p.name << ", " << p.size << "}"; +} + +std::ostream& operator<<(std::ostream& os, const PartitionMetadata::Group& g) { + return os << "{" << g.name << ", " << g.size << ", " << g.partitions << "}"; +} + +std::ostream& operator<<(std::ostream& os, const PartitionMetadata& m) { + return os << m.groups; +} + inline std::string GetDevice(const std::string& name) { return kFakeDevicePath + name; } @@ -91,62 +137,122 @@ std::ostream& operator<<(std::ostream& os, const TestParam& param) { << "}"; } -std::unique_ptr<MetadataBuilder> NewFakeMetadata(const PartitionSizes& sizes) { - auto builder = MetadataBuilder::New(10_GiB, kFakeMetadataSize, kMaxNumSlots); +// To support legacy tests, auto-convert {name_a: size} map to +// PartitionMetadata. +PartitionMetadata toMetadata(const PartitionSuffixSizes& partition_sizes) { + PartitionMetadata metadata; + for (const char* suffix : kSlotSuffixes) { + metadata.groups.push_back( + {std::string(kDefaultGroup) + suffix, kDefaultGroupSize, {}}); + } + for (const auto& pair : partition_sizes) { + for (size_t suffix_idx = 0; suffix_idx < kMaxNumSlots; ++suffix_idx) { + if (base::EndsWith(pair.first, + kSlotSuffixes[suffix_idx], + base::CompareCase::SENSITIVE)) { + metadata.groups[suffix_idx].partitions.push_back( + {pair.first, pair.second}); + } + } + } + return metadata; +} + +// To support legacy tests, auto-convert {name: size} map to PartitionMetadata. +PartitionMetadata toMetadata(const PartitionSizes& partition_sizes) { + PartitionMetadata metadata; + metadata.groups.push_back( + {std::string{kDefaultGroup}, kDefaultGroupSize, {}}); + for (const auto& pair : partition_sizes) { + metadata.groups[0].partitions.push_back({pair.first, pair.second}); + } + return metadata; +} + +std::unique_ptr<MetadataBuilder> NewFakeMetadata( + const PartitionMetadata& metadata) { + auto builder = + MetadataBuilder::New(kDefaultSuperSize, kFakeMetadataSize, kMaxNumSlots); + EXPECT_GE(builder->AllocatableSpace(), kDefaultGroupSize * 2); EXPECT_NE(nullptr, builder); if (builder == nullptr) return nullptr; - for (const auto& pair : sizes) { - auto p = builder->AddPartition(pair.first, 0 /* attr */); - EXPECT_TRUE(p && builder->ResizePartition(p, pair.second)); + for (const auto& group : metadata.groups) { + EXPECT_TRUE(builder->AddGroup(group.name, group.size)); + for (const auto& partition : group.partitions) { + auto p = builder->AddPartition(partition.name, group.name, 0 /* attr */); + EXPECT_TRUE(p && builder->ResizePartition(p, partition.size)); + } } return builder; } class MetadataMatcher : public MatcherInterface<MetadataBuilder*> { public: - explicit MetadataMatcher(const PartitionSizes& partition_sizes) - : partition_sizes_(partition_sizes) {} + explicit MetadataMatcher(const PartitionSuffixSizes& partition_sizes) + : partition_metadata_(toMetadata(partition_sizes)) {} + explicit MetadataMatcher(const PartitionMetadata& partition_metadata) + : partition_metadata_(partition_metadata) {} + bool MatchAndExplain(MetadataBuilder* metadata, MatchResultListener* listener) const override { bool success = true; - for (const auto& pair : partition_sizes_) { - auto p = metadata->FindPartition(pair.first); - if (p == nullptr) { - if (success) - *listener << "; "; - *listener << "No partition " << pair.first; - success = false; - continue; - } - if (p->size() != pair.second) { - if (success) - *listener << "; "; - *listener << "Partition " << pair.first << " has size " << p->size() - << ", expected " << pair.second; - success = false; + for (const auto& group : partition_metadata_.groups) { + for (const auto& partition : group.partitions) { + auto p = metadata->FindPartition(partition.name); + if (p == nullptr) { + if (!success) + *listener << "; "; + *listener << "No partition " << partition.name; + success = false; + continue; + } + if (p->size() != partition.size) { + if (!success) + *listener << "; "; + *listener << "Partition " << partition.name << " has size " + << p->size() << ", expected " << partition.size; + success = false; + } + if (p->group_name() != group.name) { + if (!success) + *listener << "; "; + *listener << "Partition " << partition.name << " has group " + << p->group_name() << ", expected " << group.name; + success = false; + } } } return success; } void DescribeTo(std::ostream* os) const override { - *os << "expect: " << partition_sizes_; + *os << "expect: " << partition_metadata_; } void DescribeNegationTo(std::ostream* os) const override { - *os << "expect not: " << partition_sizes_; + *os << "expect not: " << partition_metadata_; } private: - PartitionSizes partition_sizes_; + PartitionMetadata partition_metadata_; }; inline Matcher<MetadataBuilder*> MetadataMatches( - const PartitionSizes& partition_sizes) { + const PartitionSuffixSizes& partition_sizes) { return MakeMatcher(new MetadataMatcher(partition_sizes)); } +inline Matcher<MetadataBuilder*> MetadataMatches( + const PartitionMetadata& partition_metadata) { + return MakeMatcher(new MetadataMatcher(partition_metadata)); +} + +MATCHER_P(HasGroup, group, " has group " + group) { + auto groups = arg->ListGroups(); + return std::find(groups.begin(), groups.end(), group) != groups.end(); +} + class BootControlAndroidTest : public ::testing::Test { protected: void SetUp() override { @@ -187,10 +293,15 @@ class BootControlAndroidTest : public ::testing::Test { // Set the fake metadata to return when LoadMetadataBuilder is called on // |slot|. - void SetMetadata(uint32_t slot, const PartitionSizes& sizes) { + void SetMetadata(uint32_t slot, const PartitionSuffixSizes& sizes) { + SetMetadata(slot, toMetadata(sizes)); + } + + void SetMetadata(uint32_t slot, const PartitionMetadata& metadata) { EXPECT_CALL(dynamicControl(), LoadMetadataBuilder(GetSuperDevice(), slot)) - .WillOnce( - Invoke([sizes](auto, auto) { return NewFakeMetadata(sizes); })); + .Times(AnyNumber()) + .WillRepeatedly(Invoke( + [metadata](auto, auto) { return NewFakeMetadata(metadata); })); } // Expect that MapPartitionOnDeviceMapper is called on target() metadata slot @@ -245,7 +356,7 @@ class BootControlAndroidTest : public ::testing::Test { } } - void ExpectStoreMetadata(const PartitionSizes& partition_sizes) { + void ExpectStoreMetadata(const PartitionSuffixSizes& partition_sizes) { ExpectStoreMetadataMatch(MetadataMatches(partition_sizes)); } @@ -261,13 +372,13 @@ class BootControlAndroidTest : public ::testing::Test { uint32_t target() { return slots_.target; } // Return partition names with suffix of source(). - std::string S(const std::string& name) { - return name + std::string(kSlotSuffixes[source()]); + PartitionNameSuffix S(const std::string& name) { + return PartitionNameSuffix(name + std::string(kSlotSuffixes[source()])); } // Return partition names with suffix of target(). - std::string T(const std::string& name) { - return name + std::string(kSlotSuffixes[target()]); + PartitionNameSuffix T(const std::string& name) { + return PartitionNameSuffix(name + std::string(kSlotSuffixes[target()])); } // Set source and target slots to use before testing. @@ -280,6 +391,16 @@ class BootControlAndroidTest : public ::testing::Test { // Should not store metadata to source slot. EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, source())) .Times(0); + // Should not load metadata from target slot. + EXPECT_CALL(dynamicControl(), + LoadMetadataBuilder(GetSuperDevice(), target())) + .Times(0); + } + + bool InitPartitionMetadata(uint32_t slot, PartitionSizes partition_sizes) { + auto m = toMetadata(partition_sizes); + LOG(INFO) << m; + return bootctl_.InitPartitionMetadata(slot, m); } BootControlAndroid bootctl_; // BootControlAndroid under test. @@ -301,191 +422,174 @@ class BootControlAndroidTestP // Test resize case. Grow if target metadata contains a partition with a size // less than expected. TEST_P(BootControlAndroidTestP, NeedGrowIfSizeNotMatchWhenResizing) { - PartitionSizes initial{{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 2_GiB}, - {T("vendor"), 1_GiB}}; - SetMetadata(source(), initial); - SetMetadata(target(), initial); + SetMetadata(source(), + {{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}); ExpectStoreMetadata({{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 3_GiB}, {T("vendor"), 1_GiB}}); ExpectRemap({T("system"), T("vendor")}); - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), {{"system", 3_GiB}, {"vendor", 1_GiB}})); + EXPECT_TRUE( + InitPartitionMetadata(target(), {{"system", 3_GiB}, {"vendor", 1_GiB}})); ExpectDevicesAreMapped({T("system"), T("vendor")}); } // Test resize case. Shrink if target metadata contains a partition with a size // greater than expected. TEST_P(BootControlAndroidTestP, NeedShrinkIfSizeNotMatchWhenResizing) { - PartitionSizes initial{{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 2_GiB}, - {T("vendor"), 1_GiB}}; - SetMetadata(source(), initial); - SetMetadata(target(), initial); + SetMetadata(source(), + {{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}); ExpectStoreMetadata({{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 2_GiB}, {T("vendor"), 150_MiB}}); ExpectRemap({T("system"), T("vendor")}); - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), {{"system", 2_GiB}, {"vendor", 150_MiB}})); + EXPECT_TRUE(InitPartitionMetadata(target(), + {{"system", 2_GiB}, {"vendor", 150_MiB}})); ExpectDevicesAreMapped({T("system"), T("vendor")}); } // Test adding partitions on the first run. TEST_P(BootControlAndroidTestP, AddPartitionToEmptyMetadata) { - SetMetadata(source(), {}); - SetMetadata(target(), {}); + SetMetadata(source(), PartitionSuffixSizes{}); ExpectStoreMetadata({{T("system"), 2_GiB}, {T("vendor"), 1_GiB}}); ExpectRemap({T("system"), T("vendor")}); - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), {{"system", 2_GiB}, {"vendor", 1_GiB}})); + EXPECT_TRUE( + InitPartitionMetadata(target(), {{"system", 2_GiB}, {"vendor", 1_GiB}})); ExpectDevicesAreMapped({T("system"), T("vendor")}); } // Test subsequent add case. TEST_P(BootControlAndroidTestP, AddAdditionalPartition) { SetMetadata(source(), {{S("system"), 2_GiB}, {T("system"), 2_GiB}}); - SetMetadata(target(), {{S("system"), 2_GiB}, {T("system"), 2_GiB}}); ExpectStoreMetadata( {{S("system"), 2_GiB}, {T("system"), 2_GiB}, {T("vendor"), 1_GiB}}); ExpectRemap({T("system"), T("vendor")}); - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), {{"system", 2_GiB}, {"vendor", 1_GiB}})); + EXPECT_TRUE( + InitPartitionMetadata(target(), {{"system", 2_GiB}, {"vendor", 1_GiB}})); ExpectDevicesAreMapped({T("system"), T("vendor")}); } // Test delete one partition. TEST_P(BootControlAndroidTestP, DeletePartition) { - PartitionSizes initial{{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 2_GiB}, - {T("vendor"), 1_GiB}}; - SetMetadata(source(), initial); - SetMetadata(target(), initial); - ExpectStoreMetadata({{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 2_GiB}, - {T("vendor"), 0}}); - ExpectUnmap({T("system"), T("vendor")}); - ExpectMap({T("system")}); + SetMetadata(source(), + {{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}); + // No T("vendor") + ExpectStoreMetadata( + {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 2_GiB}}); + ExpectRemap({T("system")}); - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), {{"system", 2_GiB}, {"vendor", 0}})); + EXPECT_TRUE(InitPartitionMetadata(target(), {{"system", 2_GiB}})); ExpectDevicesAreMapped({T("system")}); } // Test delete all partitions. TEST_P(BootControlAndroidTestP, DeleteAll) { - PartitionSizes initial{{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 2_GiB}, - {T("vendor"), 1_GiB}}; - SetMetadata(source(), initial); - SetMetadata(target(), initial); - ExpectStoreMetadata({{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 0}, - {T("vendor"), 0}}); - ExpectUnmap({T("system"), T("vendor")}); - ExpectMap({}); + SetMetadata(source(), + {{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}); + ExpectStoreMetadata({{S("system"), 2_GiB}, {S("vendor"), 1_GiB}}); - EXPECT_TRUE( - bootctl_.InitPartitionMetadata(target(), {{"system", 0}, {"vendor", 0}})); + EXPECT_TRUE(InitPartitionMetadata(target(), {})); ExpectDevicesAreMapped({}); } -// Test corrupt source metadata case. This shouldn't happen in practice, -// because the device is already booted normally. +// Test corrupt source metadata case. TEST_P(BootControlAndroidTestP, CorruptedSourceMetadata) { EXPECT_CALL(dynamicControl(), LoadMetadataBuilder(GetSuperDevice(), source())) .WillOnce(Invoke([](auto, auto) { return nullptr; })); - EXPECT_FALSE(bootctl_.InitPartitionMetadata(target(), {})) + EXPECT_FALSE(InitPartitionMetadata(target(), {{"system", 1_GiB}})) << "Should not be able to continue with corrupt source metadata"; } -// Test corrupt target metadata case. This may happen in practice. -// BootControlAndroid should copy from source metadata and make necessary -// modifications on it. -TEST_P(BootControlAndroidTestP, CorruptedTargetMetadata) { +// Test that InitPartitionMetadata fail if there is not enough space on the +// device. +TEST_P(BootControlAndroidTestP, NotEnoughSpace) { SetMetadata(source(), - {{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, + {{S("system"), 3_GiB}, + {S("vendor"), 2_GiB}, {T("system"), 0}, {T("vendor"), 0}}); - EXPECT_CALL(dynamicControl(), LoadMetadataBuilder(GetSuperDevice(), target())) - .WillOnce(Invoke([](auto, auto) { return nullptr; })); - ExpectStoreMetadata({{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 3_GiB}, - {T("vendor"), 150_MiB}}); - ExpectRemap({T("system"), T("vendor")}); - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), {{"system", 3_GiB}, {"vendor", 150_MiB}})); - ExpectDevicesAreMapped({T("system"), T("vendor")}); + EXPECT_FALSE( + InitPartitionMetadata(target(), {{"system", 3_GiB}, {"vendor", 3_GiB}})) + << "Should not be able to fit 11GiB data into 10GiB space"; } -// Test that InitPartitionMetadata fail if there is not enough space on the -// device. -TEST_P(BootControlAndroidTestP, NotEnoughSpace) { - PartitionSizes initial{{S("system"), 3_GiB}, - {S("vendor"), 2_GiB}, - {T("system"), 0}, - {T("vendor"), 0}}; - SetMetadata(source(), initial); - SetMetadata(target(), initial); - EXPECT_FALSE(bootctl_.InitPartitionMetadata( - target(), {{"system", 3_GiB}, {"vendor", 3_GiB}})) - << "Should not be able to fit 11GiB data into 10GiB space"; +TEST_P(BootControlAndroidTestP, NotEnoughSpaceForSlot) { + SetMetadata(source(), + {{S("system"), 1_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 0}, + {T("vendor"), 0}}); + EXPECT_FALSE( + InitPartitionMetadata(target(), {{"system", 3_GiB}, {"vendor", 3_GiB}})) + << "Should not be able to grow over size of super / 2"; } -INSTANTIATE_TEST_CASE_P(ParamTest, +INSTANTIATE_TEST_CASE_P(BootControlAndroidTest, BootControlAndroidTestP, testing::Values(TestParam{0, 1}, TestParam{1, 0})); -const PartitionSizes update_sizes_0() { - return {{"grown_a", 2_GiB}, - {"shrunk_a", 1_GiB}, - {"same_a", 100_MiB}, - {"deleted_a", 150_MiB}, - {"grown_b", 200_MiB}, - {"shrunk_b", 0}, - {"same_b", 0}}; +const PartitionSuffixSizes update_sizes_0() { + // Initial state is 0 for "other" slot. + return { + {"grown_a", 2_GiB}, + {"shrunk_a", 1_GiB}, + {"same_a", 100_MiB}, + {"deleted_a", 150_MiB}, + // no added_a + {"grown_b", 200_MiB}, + // simulate system_other + {"shrunk_b", 0}, + {"same_b", 0}, + {"deleted_b", 0}, + // no added_b + }; } -const PartitionSizes update_sizes_1() { +const PartitionSuffixSizes update_sizes_1() { return { {"grown_a", 2_GiB}, {"shrunk_a", 1_GiB}, {"same_a", 100_MiB}, {"deleted_a", 150_MiB}, + // no added_a {"grown_b", 3_GiB}, {"shrunk_b", 150_MiB}, {"same_b", 100_MiB}, {"added_b", 150_MiB}, - {"deleted_b", 0}, + // no deleted_b }; } -const PartitionSizes update_sizes_2() { - return {{"grown_a", 4_GiB}, - {"shrunk_a", 100_MiB}, - {"same_a", 100_MiB}, - {"added_a", 0_MiB}, - {"deleted_a", 64_MiB}, - {"grown_b", 3_GiB}, - {"shrunk_b", 150_MiB}, - {"same_b", 100_MiB}, - {"added_b", 150_MiB}, - {"deleted_b", 0}}; +const PartitionSuffixSizes update_sizes_2() { + return { + {"grown_a", 4_GiB}, + {"shrunk_a", 100_MiB}, + {"same_a", 100_MiB}, + {"deleted_a", 64_MiB}, + // no added_a + {"grown_b", 3_GiB}, + {"shrunk_b", 150_MiB}, + {"same_b", 100_MiB}, + {"added_b", 150_MiB}, + // no deleted_b + }; } // Test case for first update after the device is manufactured, in which @@ -497,15 +601,13 @@ TEST_F(BootControlAndroidTest, SimulatedFirstUpdate) { SetMetadata(source(), update_sizes_0()); SetMetadata(target(), update_sizes_0()); ExpectStoreMetadata(update_sizes_1()); - ExpectUnmap({"grown_b", "shrunk_b", "same_b", "added_b", "deleted_b"}); - ExpectMap({"grown_b", "shrunk_b", "same_b", "added_b"}); - - EXPECT_TRUE(bootctl_.InitPartitionMetadata(target(), - {{"grown", 3_GiB}, - {"shrunk", 150_MiB}, - {"same", 100_MiB}, - {"added", 150_MiB}, - {"deleted", 0_MiB}})); + ExpectRemap({"grown_b", "shrunk_b", "same_b", "added_b"}); + + EXPECT_TRUE(InitPartitionMetadata(target(), + {{"grown", 3_GiB}, + {"shrunk", 150_MiB}, + {"same", 100_MiB}, + {"added", 150_MiB}})); ExpectDevicesAreMapped({"grown_b", "shrunk_b", "same_b", "added_b"}); } @@ -518,22 +620,167 @@ TEST_F(BootControlAndroidTest, SimulatedSecondUpdate) { SetMetadata(target(), update_sizes_0()); ExpectStoreMetadata(update_sizes_2()); - ExpectUnmap({"grown_a", "shrunk_a", "same_a", "added_a", "deleted_a"}); - ExpectMap({"grown_a", "shrunk_a", "same_a", "deleted_a"}); - - EXPECT_TRUE(bootctl_.InitPartitionMetadata(target(), - {{"grown", 4_GiB}, - {"shrunk", 100_MiB}, - {"same", 100_MiB}, - {"added", 0_MiB}, - {"deleted", 64_MiB}})); + ExpectRemap({"grown_a", "shrunk_a", "same_a", "deleted_a"}); + + EXPECT_TRUE(InitPartitionMetadata(target(), + {{"grown", 4_GiB}, + {"shrunk", 100_MiB}, + {"same", 100_MiB}, + {"deleted", 64_MiB}})); ExpectDevicesAreMapped({"grown_a", "shrunk_a", "same_a", "deleted_a"}); } TEST_F(BootControlAndroidTest, ApplyingToCurrentSlot) { SetSlots({1, 1}); - EXPECT_FALSE(bootctl_.InitPartitionMetadata(target(), {})) + EXPECT_FALSE(InitPartitionMetadata(target(), {})) << "Should not be able to apply to current slot."; } +class BootControlAndroidGroupTestP : public BootControlAndroidTestP { + public: + void SetUp() override { + BootControlAndroidTestP::SetUp(); + SetMetadata( + source(), + {.groups = {SimpleGroup(S("android"), 3_GiB, S("system"), 2_GiB), + SimpleGroup(S("oem"), 2_GiB, S("vendor"), 1_GiB), + SimpleGroup(T("android"), 3_GiB, T("system"), 0), + SimpleGroup(T("oem"), 2_GiB, T("vendor"), 0)}}); + } + + // Return a simple group with only one partition. + PartitionMetadata::Group SimpleGroup(const std::string& group, + uint64_t group_size, + const std::string& partition, + uint64_t partition_size) { + return {.name = group, + .size = group_size, + .partitions = {{.name = partition, .size = partition_size}}}; + } + + void ExpectStoreMetadata(const PartitionMetadata& partition_metadata) { + ExpectStoreMetadataMatch(MetadataMatches(partition_metadata)); + } + + // Expect that target slot is stored with target groups. + void ExpectStoreMetadataMatch( + const Matcher<MetadataBuilder*>& matcher) override { + BootControlAndroidTestP::ExpectStoreMetadataMatch(AllOf( + MetadataMatches(PartitionMetadata{ + .groups = {SimpleGroup(S("android"), 3_GiB, S("system"), 2_GiB), + SimpleGroup(S("oem"), 2_GiB, S("vendor"), 1_GiB)}}), + matcher)); + } +}; + +// Allow to resize within group. +TEST_P(BootControlAndroidGroupTestP, ResizeWithinGroup) { + ExpectStoreMetadata(PartitionMetadata{ + .groups = {SimpleGroup(T("android"), 3_GiB, T("system"), 3_GiB), + SimpleGroup(T("oem"), 2_GiB, T("vendor"), 2_GiB)}}); + ExpectRemap({T("system"), T("vendor")}); + + EXPECT_TRUE(bootctl_.InitPartitionMetadata( + target(), + PartitionMetadata{ + .groups = {SimpleGroup("android", 3_GiB, "system", 3_GiB), + SimpleGroup("oem", 2_GiB, "vendor", 2_GiB)}})); + ExpectDevicesAreMapped({T("system"), T("vendor")}); +} + +TEST_P(BootControlAndroidGroupTestP, NotEnoughSpaceForGroup) { + EXPECT_FALSE(bootctl_.InitPartitionMetadata( + target(), + PartitionMetadata{ + .groups = {SimpleGroup("android", 3_GiB, "system", 1_GiB), + SimpleGroup("oem", 2_GiB, "vendor", 3_GiB)}})) + << "Should not be able to grow over maximum size of group"; +} + +TEST_P(BootControlAndroidGroupTestP, GroupTooBig) { + EXPECT_FALSE(bootctl_.InitPartitionMetadata( + target(), + PartitionMetadata{.groups = {{.name = "android", .size = 3_GiB}, + {.name = "oem", .size = 3_GiB}}})) + << "Should not be able to grow over size of super / 2"; +} + +TEST_P(BootControlAndroidGroupTestP, AddPartitionToGroup) { + ExpectStoreMetadata(PartitionMetadata{ + .groups = { + {.name = T("android"), + .size = 3_GiB, + .partitions = {{.name = T("system"), .size = 2_GiB}, + {.name = T("product_services"), .size = 1_GiB}}}}}); + ExpectRemap({T("system"), T("vendor"), T("product_services")}); + + EXPECT_TRUE(bootctl_.InitPartitionMetadata( + target(), + PartitionMetadata{ + .groups = { + {.name = "android", + .size = 3_GiB, + .partitions = {{.name = "system", .size = 2_GiB}, + {.name = "product_services", .size = 1_GiB}}}, + SimpleGroup("oem", 2_GiB, "vendor", 2_GiB)}})); + ExpectDevicesAreMapped({T("system"), T("vendor"), T("product_services")}); +} + +TEST_P(BootControlAndroidGroupTestP, RemovePartitionFromGroup) { + ExpectStoreMetadata(PartitionMetadata{ + .groups = {{.name = T("android"), .size = 3_GiB, .partitions = {}}}}); + ExpectRemap({T("vendor")}); + + EXPECT_TRUE(bootctl_.InitPartitionMetadata( + target(), + PartitionMetadata{ + .groups = {{.name = "android", .size = 3_GiB, .partitions = {}}, + SimpleGroup("oem", 2_GiB, "vendor", 2_GiB)}})); + ExpectDevicesAreMapped({T("vendor")}); +} + +TEST_P(BootControlAndroidGroupTestP, AddGroup) { + ExpectStoreMetadata(PartitionMetadata{ + .groups = { + SimpleGroup(T("new_group"), 2_GiB, T("new_partition"), 2_GiB)}}); + ExpectRemap({T("system"), T("vendor"), T("new_partition")}); + + EXPECT_TRUE(bootctl_.InitPartitionMetadata( + target(), + PartitionMetadata{ + .groups = { + SimpleGroup("android", 2_GiB, "system", 2_GiB), + SimpleGroup("oem", 1_GiB, "vendor", 1_GiB), + SimpleGroup("new_group", 2_GiB, "new_partition", 2_GiB)}})); + ExpectDevicesAreMapped({T("system"), T("vendor"), T("new_partition")}); +} + +TEST_P(BootControlAndroidGroupTestP, RemoveGroup) { + ExpectStoreMetadataMatch(Not(HasGroup(T("oem")))); + ExpectRemap({T("system")}); + EXPECT_TRUE(bootctl_.InitPartitionMetadata( + target(), + PartitionMetadata{ + .groups = {SimpleGroup("android", 2_GiB, "system", 2_GiB)}})); + ExpectDevicesAreMapped({T("system")}); +} + +TEST_P(BootControlAndroidGroupTestP, ResizeGroup) { + ExpectStoreMetadata(PartitionMetadata{ + .groups = {SimpleGroup(T("android"), 2_GiB, T("system"), 2_GiB), + SimpleGroup(T("oem"), 3_GiB, T("vendor"), 3_GiB)}}); + ExpectRemap({T("system"), T("vendor")}); + + EXPECT_TRUE(bootctl_.InitPartitionMetadata( + target(), + PartitionMetadata{ + .groups = {SimpleGroup("android", 2_GiB, "system", 2_GiB), + SimpleGroup("oem", 3_GiB, "vendor", 3_GiB)}})); + ExpectDevicesAreMapped({T("system"), T("vendor")}); +} + +INSTANTIATE_TEST_CASE_P(BootControlAndroidTest, + BootControlAndroidGroupTestP, + testing::Values(TestParam{0, 1}, TestParam{1, 0})); + } // namespace chromeos_update_engine diff --git a/common/boot_control_interface.h b/common/boot_control_interface.h index 1b76939a..43517ce2 100644 --- a/common/boot_control_interface.h +++ b/common/boot_control_interface.h @@ -20,6 +20,7 @@ #include <climits> #include <map> #include <string> +#include <vector> #include <base/callback.h> #include <base/macros.h> @@ -33,7 +34,19 @@ namespace chromeos_update_engine { class BootControlInterface { public: using Slot = unsigned int; - using PartitionMetadata = std::map<std::string, uint64_t>; + + struct PartitionMetadata { + struct Partition { + std::string name; + uint64_t size; + }; + struct Group { + std::string name; + uint64_t size; + std::vector<Partition> partitions; + }; + std::vector<Group> groups; + }; static const Slot kInvalidSlot = UINT_MAX; @@ -80,10 +93,11 @@ class BootControlInterface { virtual bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) = 0; // Initialize metadata of underlying partitions for a given |slot|. - // Ensure that partitions at the specified |slot| has a given size, as - // specified by |partition_metadata|. |partition_metadata| has the format: - // {"vendor": 524288000, "system": 2097152000, ...}; values must be - // aligned to the logical block size of the super partition. + // Ensure that all updateable groups with the suffix GetSuffix(|slot|) exactly + // matches the layout specified in |partition_metadata|. Ensure that + // partitions at the specified |slot| has a given size and updateable group, + // as specified by |partition_metadata|. Sizes must be aligned to the logical + // block size of the super partition. virtual bool InitPartitionMetadata( Slot slot, const PartitionMetadata& partition_metadata) = 0; diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc index 7dec48f1..7a19374f 100644 --- a/payload_consumer/delta_performer.cc +++ b/payload_consumer/delta_performer.cc @@ -944,8 +944,30 @@ bool DeltaPerformer::InitPartitionMetadata() { } BootControlInterface::PartitionMetadata partition_metadata; - for (const InstallPlan::Partition& partition : install_plan_->partitions) { - partition_metadata.emplace(partition.name, partition.target_size); + if (manifest_.has_dynamic_partition_metadata()) { + std::map<string, uint64_t> partition_sizes; + for (const InstallPlan::Partition& partition : install_plan_->partitions) { + partition_sizes.emplace(partition.name, partition.target_size); + } + for (const auto& group : manifest_.dynamic_partition_metadata().groups()) { + BootControlInterface::PartitionMetadata::Group e; + e.name = group.name(); + e.size = group.size(); + for (const auto& partition_name : group.partition_names()) { + auto it = partition_sizes.find(partition_name); + if (it == partition_sizes.end()) { + // TODO(tbao): Support auto-filling partition info for framework-only + // OTA. + LOG(ERROR) << "dynamic_partition_metadata contains partition " + << partition_name + << " but it is not part of the manifest. " + << "This is not supported."; + return false; + } + e.partitions.push_back({partition_name, it->second}); + } + partition_metadata.groups.push_back(std::move(e)); + } } if (!boot_control_->InitPartitionMetadata(install_plan_->target_slot, @@ -1676,6 +1698,16 @@ ErrorCode DeltaPerformer::ValidateManifest() { return ErrorCode::kPayloadTimestampError; } + if (major_payload_version_ == kChromeOSMajorPayloadVersion) { + if (manifest_.has_dynamic_partition_metadata()) { + LOG(ERROR) + << "Should not contain dynamic_partition_metadata for major version " + << kChromeOSMajorPayloadVersion + << ". Please use major version 2 or above."; + return ErrorCode::kPayloadMismatchedType; + } + } + // TODO(garnold) we should be adding more and more manifest checks, such as // partition boundaries etc (see chromium-os:37661). |