diff options
-rw-r--r-- | Android.bp | 2 | ||||
-rw-r--r-- | boot_control_android.cc | 146 | ||||
-rw-r--r-- | boot_control_android_unittest.cc | 645 | ||||
-rw-r--r-- | dynamic_partition_control_android.cc | 156 | ||||
-rw-r--r-- | dynamic_partition_control_android.h | 54 | ||||
-rw-r--r-- | dynamic_partition_control_android_unittest.cc | 484 | ||||
-rw-r--r-- | dynamic_partition_control_interface.h | 50 | ||||
-rw-r--r-- | dynamic_partition_test_utils.h | 254 | ||||
-rw-r--r-- | dynamic_partition_utils.cc | 39 | ||||
-rw-r--r-- | dynamic_partition_utils.h | 33 | ||||
-rw-r--r-- | mock_dynamic_partition_control.h | 31 |
11 files changed, 1083 insertions, 811 deletions
@@ -214,6 +214,7 @@ cc_library_static { srcs: [ "boot_control_android.cc", "dynamic_partition_control_android.cc", + "dynamic_partition_utils.cc", ], } @@ -677,6 +678,7 @@ cc_test { "common/terminator_unittest.cc", "common/test_utils.cc", "common/utils_unittest.cc", + "dynamic_partition_control_android_unittest.cc", "payload_consumer/bzip_extent_writer_unittest.cc", "payload_consumer/cached_file_descriptor_unittest.cc", "payload_consumer/delta_performer_integration_test.cc", diff --git a/boot_control_android.cc b/boot_control_android.cc index 8ab73be1..ce86666c 100644 --- a/boot_control_android.cc +++ b/boot_control_android.cc @@ -22,11 +22,10 @@ #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> #include <fs_mgr_overlayfs.h> +#include <libdm/dm.h> #include "update_engine/common/utils.h" #include "update_engine/dynamic_partition_control_android.h" @@ -34,7 +33,6 @@ using std::string; using android::dm::DmDeviceState; -using android::fs_mgr::Partition; using android::hardware::hidl_string; using android::hardware::Return; using android::hardware::boot::V1_0::BoolResult; @@ -111,9 +109,9 @@ bool BootControlAndroid::IsSuperBlockDevice( Slot slot, const string& partition_name_suffix) const { string source_device = - device_dir.Append(fs_mgr_get_super_partition_name(slot)).value(); - auto source_metadata = dynamic_control_->LoadMetadataBuilder( - source_device, slot, BootControlInterface::kInvalidSlot); + device_dir.Append(dynamic_control_->GetSuperPartitionName(slot)).value(); + auto source_metadata = + dynamic_control_->LoadMetadataBuilder(source_device, slot); return source_metadata->HasBlockDevice(partition_name_suffix); } @@ -124,10 +122,9 @@ BootControlAndroid::GetDynamicPartitionDevice( Slot slot, string* device) const { string super_device = - device_dir.Append(fs_mgr_get_super_partition_name(slot)).value(); + device_dir.Append(dynamic_control_->GetSuperPartitionName(slot)).value(); - auto builder = dynamic_control_->LoadMetadataBuilder( - super_device, slot, BootControlInterface::kInvalidSlot); + auto builder = dynamic_control_->LoadMetadataBuilder(super_device, slot); if (builder == nullptr) { LOG(ERROR) << "No metadata in slot " @@ -143,8 +140,8 @@ BootControlAndroid::GetDynamicPartitionDevice( if (IsSuperBlockDevice(device_dir, current_slot, partition_name_suffix)) { LOG(ERROR) << "The static partition " << partition_name_suffix << " is a block device for current metadata (" - << fs_mgr_get_super_partition_name(current_slot) << ", slot " - << BootControlInterface::SlotName(current_slot) + << dynamic_control_->GetSuperPartitionName(current_slot) + << ", slot " << BootControlInterface::SlotName(current_slot) << "). It cannot be used as a logical partition."; return DynamicPartitionDeviceStatus::ERROR; } @@ -196,7 +193,7 @@ bool BootControlAndroid::GetPartitionDevice(const string& partition_name, // current payload doesn't encode them as dynamic partitions. This may happen // when applying a retrofit update on top of a dynamic-partitions-enabled // build. - if (dynamic_control_->IsDynamicPartitionsEnabled() && + if (dynamic_control_->GetDynamicPartitionsFeatureFlag().IsEnabled() && (slot == GetCurrentSlot() || is_target_dynamic_)) { switch (GetDynamicPartitionDevice( device_dir, partition_name_suffix, slot, device)) { @@ -280,110 +277,6 @@ bool BootControlAndroid::MarkBootSuccessfulAsync( brillo::MessageLoop::kTaskIdNull; } -namespace { - -bool UpdatePartitionMetadata(DynamicPartitionControlInterface* dynamic_control, - Slot source_slot, - Slot target_slot, - const string& target_suffix, - const PartitionMetadata& partition_metadata) { - string device_dir_str; - if (!dynamic_control->GetDeviceDir(&device_dir_str)) { - return false; - } - base::FilePath device_dir(device_dir_str); - auto source_device = - device_dir.Append(fs_mgr_get_super_partition_name(source_slot)).value(); - - auto builder = dynamic_control->LoadMetadataBuilder( - source_device, source_slot, target_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; - } - - 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); - } - } - - uint64_t total_size = 0; - for (const auto& group : partition_metadata.groups) { - total_size += group.size; - } - - string expr; - uint64_t allocatable_space = builder->AllocatableSpace(); - if (!dynamic_control->IsDynamicPartitionsRetrofit()) { - allocatable_space /= 2; - expr = "half of "; - } - if (total_size > allocatable_space) { - LOG(ERROR) << "The maximum size of all groups with suffix " << target_suffix - << " (" << total_size << ") has exceeded " << expr - << " allocatable space for dynamic partitions " - << allocatable_space << "."; - return false; - } - - 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 partition_name_suffix = partition.name + target_suffix; - Partition* p = builder->AddPartition( - partition_name_suffix, group_name_suffix, LP_PARTITION_ATTR_READONLY); - if (!p) { - LOG(ERROR) << "Cannot add partition " << partition_name_suffix - << " to group " << group_name_suffix; - return false; - } - if (!builder->ResizePartition(p, partition.size)) { - LOG(ERROR) << "Cannot resize partition " << partition_name_suffix - << " to size " << partition.size << ". Not enough space?"; - return false; - } - LOG(INFO) << "Added partition " << partition_name_suffix << " to group " - << group_name_suffix << " with size " << partition.size; - } - } - - auto target_device = - device_dir.Append(fs_mgr_get_super_partition_name(target_slot)).value(); - return dynamic_control->StoreMetadata( - target_device, builder.get(), target_slot); -} - -bool UnmapTargetPartitions(DynamicPartitionControlInterface* dynamic_control, - 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)) { - return false; - } - } - } - return true; -} - -} // namespace - bool BootControlAndroid::InitPartitionMetadata( Slot target_slot, const PartitionMetadata& partition_metadata, @@ -395,7 +288,7 @@ bool BootControlAndroid::InitPartitionMetadata( "resources.\n" << "run adb enable-verity to deactivate if required and try again."; } - if (!dynamic_control_->IsDynamicPartitionsEnabled()) { + if (!dynamic_control_->GetDynamicPartitionsFeatureFlag().IsEnabled()) { return true; } @@ -417,23 +310,8 @@ bool BootControlAndroid::InitPartitionMetadata( return true; } - string target_suffix; - if (!GetSuffix(target_slot, &target_suffix)) { - return false; - } - - // Unmap all the target dynamic partitions because they would become - // inconsistent with the new metadata. - if (!UnmapTargetPartitions( - dynamic_control_.get(), target_suffix, partition_metadata)) { - return false; - } - - return UpdatePartitionMetadata(dynamic_control_.get(), - source_slot, - target_slot, - target_suffix, - partition_metadata); + return dynamic_control_->PreparePartitionsForUpdate( + source_slot, target_slot, partition_metadata); } } // namespace chromeos_update_engine diff --git a/boot_control_android_unittest.cc b/boot_control_android_unittest.cc index 94e195f8..6f02a070 100644 --- a/boot_control_android_unittest.cc +++ b/boot_control_android_unittest.cc @@ -26,236 +26,22 @@ #include <gtest/gtest.h> #include <libdm/dm.h> +#include "update_engine/dynamic_partition_test_utils.h" #include "update_engine/mock_boot_control_hal.h" #include "update_engine/mock_dynamic_partition_control.h" using android::dm::DmDeviceState; -using android::fs_mgr::MetadataBuilder; using android::hardware::Void; using std::string; using testing::_; using testing::AnyNumber; -using testing::Contains; -using testing::Eq; using testing::Invoke; -using testing::Key; -using testing::MakeMatcher; -using testing::Matcher; -using testing::MatcherInterface; -using testing::MatchResultListener; using testing::NiceMock; using testing::Not; using testing::Return; namespace chromeos_update_engine { -constexpr const uint32_t kMaxNumSlots = 2; -constexpr const char* kSlotSuffixes[kMaxNumSlots] = {"_a", "_b"}; -constexpr const char* kFakeDevicePath = "/fake/dev/path/"; -constexpr const char* kFakeDmDevicePath = "/fake/dm/dev/path/"; -constexpr const uint32_t kFakeMetadataSize = 65536; -constexpr const char* kDefaultGroup = "foo"; - -// A map describing the size of each partition. -// "{name, size}" -using PartitionSizes = std::map<string, uint64_t>; - -// "{name_a, size}" -using PartitionSuffixSizes = std::map<string, 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. -constexpr unsigned long long operator"" _MiB(unsigned long long x) { // NOLINT - return x << 20; -} -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 << "{"; - bool first = true; - for (const auto& pair : param) { - if (!first) - os << ", "; - os << pair.first << ":" << pair.second; - first = false; - } - 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 string GetDevice(const string& name) { - return kFakeDevicePath + name; -} - -inline string GetDmDevice(const string& name) { - return kFakeDmDevicePath + name; -} - -// TODO(elsk): fs_mgr_get_super_partition_name should be mocked. -inline string GetSuperDevice(uint32_t slot) { - return GetDevice(fs_mgr_get_super_partition_name(slot)); -} - -struct TestParam { - uint32_t source; - uint32_t target; -}; -std::ostream& operator<<(std::ostream& os, const TestParam& param) { - return os << "{source: " << param.source << ", target:" << param.target - << "}"; -} - -// To support legacy tests, auto-convert {name_a: size} map to -// PartitionMetadata. -PartitionMetadata partitionSuffixSizesToMetadata( - const PartitionSuffixSizes& partition_sizes) { - PartitionMetadata metadata; - for (const char* suffix : kSlotSuffixes) { - metadata.groups.push_back( - {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 partitionSizesToMetadata( - const PartitionSizes& partition_sizes) { - PartitionMetadata metadata; - metadata.groups.push_back({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& 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 PartitionSuffixSizes& partition_sizes) - : partition_metadata_(partitionSuffixSizesToMetadata(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& 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_metadata_; - } - - void DescribeNegationTo(std::ostream* os) const override { - *os << "expect not: " << partition_metadata_; - } - - private: - PartitionMetadata partition_metadata_; -}; - -inline Matcher<MetadataBuilder*> MetadataMatches( - 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 { @@ -274,10 +60,8 @@ class BootControlAndroidTest : public ::testing::Test { return Void(); })); - ON_CALL(dynamicControl(), IsDynamicPartitionsEnabled()) - .WillByDefault(Return(true)); - ON_CALL(dynamicControl(), IsDynamicPartitionsRetrofit()) - .WillByDefault(Return(false)); + ON_CALL(dynamicControl(), GetDynamicPartitionsFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); ON_CALL(dynamicControl(), DeviceExists(_)).WillByDefault(Return(true)); ON_CALL(dynamicControl(), GetDeviceDir(_)) .WillByDefault(Invoke([](auto path) { @@ -289,6 +73,13 @@ class BootControlAndroidTest : public ::testing::Test { *device = GetDmDevice(partition_name_suffix); return true; })); + + ON_CALL(dynamicControl(), GetSuperPartitionName(_)) + .WillByDefault(Return(kFakeSuper)); + } + + std::string GetSuperDevice(uint32_t slot) { + return GetDevice(dynamicControl().GetSuperPartitionName(slot)); } // Return the mocked HAL module. @@ -305,53 +96,14 @@ class BootControlAndroidTest : public ::testing::Test { // Set the fake metadata to return when LoadMetadataBuilder is called on // |slot|. void SetMetadata(uint32_t slot, const PartitionSuffixSizes& sizes) { - SetMetadata(slot, partitionSuffixSizesToMetadata(sizes)); - } - - void SetMetadata(uint32_t slot, const PartitionMetadata& metadata) { EXPECT_CALL(dynamicControl(), - LoadMetadataBuilder(GetSuperDevice(slot), slot, _)) + LoadMetadataBuilder(GetSuperDevice(slot), slot)) .Times(AnyNumber()) - .WillRepeatedly(Invoke([metadata](auto, auto, auto) { - return NewFakeMetadata(metadata); + .WillRepeatedly(Invoke([sizes](auto, auto) { + return NewFakeMetadata(PartitionSuffixSizesToMetadata(sizes)); })); } - // Expect that UnmapPartitionOnDeviceMapper is called on target() metadata - // slot with each partition in |partitions|. - void ExpectUnmap(const std::set<string>& partitions) { - // Error when UnmapPartitionOnDeviceMapper is called on unknown arguments. - ON_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(_)) - .WillByDefault(Return(false)); - - for (const auto& partition : partitions) { - EXPECT_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(partition)) - .WillOnce(Invoke([this](auto partition) { - mapped_devices_.erase(partition); - return true; - })); - } - } - - void ExpectDevicesAreMapped(const std::set<string>& partitions) { - ASSERT_EQ(partitions.size(), mapped_devices_.size()); - for (const auto& partition : partitions) { - EXPECT_THAT(mapped_devices_, Contains(Key(Eq(partition)))) - << "Expect that " << partition << " is mapped, but it is not."; - } - } - - void ExpectStoreMetadata(const PartitionSuffixSizes& partition_sizes) { - ExpectStoreMetadataMatch(MetadataMatches(partition_sizes)); - } - - virtual void ExpectStoreMetadataMatch( - const Matcher<MetadataBuilder*>& matcher) { - EXPECT_CALL(dynamicControl(), - StoreMetadata(GetSuperDevice(target()), matcher, target())) - .WillOnce(Return(true)); - } - uint32_t source() { return slots_.source; } uint32_t target() { return slots_.target; } @@ -369,28 +121,17 @@ class BootControlAndroidTest : public ::testing::Test { ON_CALL(module(), getCurrentSlot()).WillByDefault(Invoke([this] { return source(); })); - // Should not store metadata to source slot. - EXPECT_CALL(dynamicControl(), - StoreMetadata(GetSuperDevice(source()), _, source())) - .Times(0); - // Should not load metadata from target slot. - EXPECT_CALL(dynamicControl(), - LoadMetadataBuilder(GetSuperDevice(target()), target(), _)) - .Times(0); } bool InitPartitionMetadata(uint32_t slot, PartitionSizes partition_sizes, bool update_metadata = true) { - auto m = partitionSizesToMetadata(partition_sizes); - LOG(INFO) << m; + auto m = PartitionSizesToMetadata(partition_sizes); return bootctl_.InitPartitionMetadata(slot, m, update_metadata); } BootControlAndroid bootctl_; // BootControlAndroid under test. TestParam slots_; - // mapped devices through MapPartitionOnDeviceMapper. - std::map<string, string> mapped_devices_; }; class BootControlAndroidTestP @@ -403,125 +144,6 @@ class BootControlAndroidTestP } }; -// Test resize case. Grow if target metadata contains a partition with a size -// less than expected. -TEST_P(BootControlAndroidTestP, NeedGrowIfSizeNotMatchWhenResizing) { - 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}}); - ExpectUnmap({T("system"), T("vendor")}); - - EXPECT_TRUE( - InitPartitionMetadata(target(), {{"system", 3_GiB}, {"vendor", 1_GiB}})); -} - -// Test resize case. Shrink if target metadata contains a partition with a size -// greater than expected. -TEST_P(BootControlAndroidTestP, NeedShrinkIfSizeNotMatchWhenResizing) { - 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}}); - ExpectUnmap({T("system"), T("vendor")}); - - EXPECT_TRUE(InitPartitionMetadata(target(), - {{"system", 2_GiB}, {"vendor", 150_MiB}})); -} - -// Test adding partitions on the first run. -TEST_P(BootControlAndroidTestP, AddPartitionToEmptyMetadata) { - SetMetadata(source(), PartitionSuffixSizes{}); - ExpectStoreMetadata({{T("system"), 2_GiB}, {T("vendor"), 1_GiB}}); - ExpectUnmap({T("system"), T("vendor")}); - - EXPECT_TRUE( - InitPartitionMetadata(target(), {{"system", 2_GiB}, {"vendor", 1_GiB}})); -} - -// Test subsequent add case. -TEST_P(BootControlAndroidTestP, AddAdditionalPartition) { - SetMetadata(source(), {{S("system"), 2_GiB}, {T("system"), 2_GiB}}); - ExpectStoreMetadata( - {{S("system"), 2_GiB}, {T("system"), 2_GiB}, {T("vendor"), 1_GiB}}); - ExpectUnmap({T("system"), T("vendor")}); - - EXPECT_TRUE( - InitPartitionMetadata(target(), {{"system", 2_GiB}, {"vendor", 1_GiB}})); -} - -// Test delete one partition. -TEST_P(BootControlAndroidTestP, DeletePartition) { - 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}}); - ExpectUnmap({T("system")}); - - EXPECT_TRUE(InitPartitionMetadata(target(), {{"system", 2_GiB}})); -} - -// Test delete all partitions. -TEST_P(BootControlAndroidTestP, DeleteAll) { - 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(InitPartitionMetadata(target(), {})); -} - -// Test corrupt source metadata case. -TEST_P(BootControlAndroidTestP, CorruptedSourceMetadata) { - EXPECT_CALL(dynamicControl(), - LoadMetadataBuilder(GetSuperDevice(source()), source(), _)) - .WillOnce(Invoke([](auto, auto, auto) { return nullptr; })); - ExpectUnmap({T("system")}); - - EXPECT_FALSE(InitPartitionMetadata(target(), {{"system", 1_GiB}})) - << "Should not be able to continue with corrupt source metadata"; -} - -// Test that InitPartitionMetadata fail if there is not enough space on the -// device. -TEST_P(BootControlAndroidTestP, NotEnoughSpace) { - SetMetadata(source(), - {{S("system"), 3_GiB}, - {S("vendor"), 2_GiB}, - {T("system"), 0}, - {T("vendor"), 0}}); - EXPECT_FALSE( - 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"; -} - // Test applying retrofit update on a build with dynamic partitions enabled. TEST_P(BootControlAndroidTestP, ApplyRetrofitUpdateOnDynamicPartitionsEnabledBuild) { @@ -530,12 +152,6 @@ TEST_P(BootControlAndroidTestP, {S("vendor"), 1_GiB}, {T("system"), 2_GiB}, {T("vendor"), 1_GiB}}); - // Should not try to unmap any target partition. - EXPECT_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(_)).Times(0); - // Should not store metadata to target slot. - EXPECT_CALL(dynamicControl(), - StoreMetadata(GetSuperDevice(target()), _, target())) - .Times(0); // Not calling through BootControlAndroidTest::InitPartitionMetadata(), since // we don't want any default group in the PartitionMetadata. @@ -578,9 +194,7 @@ TEST_P(BootControlAndroidTestP, GetPartitionDeviceWhenResumingUpdate) { {S("vendor"), 1_GiB}, {T("system"), 2_GiB}, {T("vendor"), 1_GiB}}); - EXPECT_CALL(dynamicControl(), - StoreMetadata(GetSuperDevice(target()), _, target())) - .Times(0); + EXPECT_TRUE(InitPartitionMetadata( target(), {{"system", 2_GiB}, {"vendor", 1_GiB}}, false)); @@ -622,239 +236,10 @@ INSTANTIATE_TEST_CASE_P(BootControlAndroidTest, BootControlAndroidTestP, testing::Values(TestParam{0, 1}, TestParam{1, 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 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}, - // no deleted_b - }; -} - -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 -// case the "other" slot is likely of size "0" (except system, which is -// non-zero because of system_other partition) -TEST_F(BootControlAndroidTest, SimulatedFirstUpdate) { - SetSlots({0, 1}); - - SetMetadata(source(), update_sizes_0()); - SetMetadata(target(), update_sizes_0()); - ExpectStoreMetadata(update_sizes_1()); - ExpectUnmap({"grown_b", "shrunk_b", "same_b", "added_b"}); - - EXPECT_TRUE(InitPartitionMetadata(target(), - {{"grown", 3_GiB}, - {"shrunk", 150_MiB}, - {"same", 100_MiB}, - {"added", 150_MiB}})); -} - -// After first update, test for the second update. In the second update, the -// "added" partition is deleted and "deleted" partition is re-added. -TEST_F(BootControlAndroidTest, SimulatedSecondUpdate) { - SetSlots({1, 0}); - - SetMetadata(source(), update_sizes_1()); - SetMetadata(target(), update_sizes_0()); - - ExpectStoreMetadata(update_sizes_2()); - ExpectUnmap({"grown_a", "shrunk_a", "same_a", "deleted_a"}); - - EXPECT_TRUE(InitPartitionMetadata(target(), - {{"grown", 4_GiB}, - {"shrunk", 100_MiB}, - {"same", 100_MiB}, - {"deleted", 64_MiB}})); -} - TEST_F(BootControlAndroidTest, ApplyingToCurrentSlot) { SetSlots({1, 1}); 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 string& group, - uint64_t group_size, - const 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)}}); - ExpectUnmap({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)}}, - true)); -} - -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)}}, - true)) - << "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}}}, - true)) - << "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("system_ext"), .size = 1_GiB}}}}}); - ExpectUnmap({T("system"), T("vendor"), T("system_ext")}); - - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), - PartitionMetadata{ - .groups = {{.name = "android", - .size = 3_GiB, - .partitions = {{.name = "system", .size = 2_GiB}, - {.name = "system_ext", .size = 1_GiB}}}, - SimpleGroup("oem", 2_GiB, "vendor", 2_GiB)}}, - true)); -} - -TEST_P(BootControlAndroidGroupTestP, RemovePartitionFromGroup) { - ExpectStoreMetadata(PartitionMetadata{ - .groups = {{.name = T("android"), .size = 3_GiB, .partitions = {}}}}); - ExpectUnmap({T("vendor")}); - - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), - PartitionMetadata{ - .groups = {{.name = "android", .size = 3_GiB, .partitions = {}}, - SimpleGroup("oem", 2_GiB, "vendor", 2_GiB)}}, - true)); -} - -TEST_P(BootControlAndroidGroupTestP, AddGroup) { - ExpectStoreMetadata(PartitionMetadata{ - .groups = { - SimpleGroup(T("new_group"), 2_GiB, T("new_partition"), 2_GiB)}}); - ExpectUnmap({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)}}, - true)); -} - -TEST_P(BootControlAndroidGroupTestP, RemoveGroup) { - ExpectStoreMetadataMatch(Not(HasGroup(T("oem")))); - ExpectUnmap({T("system")}); - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), - PartitionMetadata{ - .groups = {SimpleGroup("android", 2_GiB, "system", 2_GiB)}}, - true)); -} - -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)}}); - ExpectUnmap({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)}}, - true)); -} - -INSTANTIATE_TEST_CASE_P(BootControlAndroidTest, - BootControlAndroidGroupTestP, - testing::Values(TestParam{0, 1}, TestParam{1, 0})); - } // namespace chromeos_update_engine diff --git a/dynamic_partition_control_android.cc b/dynamic_partition_control_android.cc index bfdd3752..1a1e021f 100644 --- a/dynamic_partition_control_android.cc +++ b/dynamic_partition_control_android.cc @@ -19,16 +19,20 @@ #include <memory> #include <set> #include <string> +#include <vector> #include <android-base/properties.h> #include <android-base/strings.h> #include <base/files/file_util.h> #include <base/logging.h> +#include <base/strings/string_util.h> #include <bootloader_message/bootloader_message.h> +#include <fs_mgr.h> #include <fs_mgr_dm_linear.h> #include "update_engine/common/boot_control_interface.h" #include "update_engine/common/utils.h" +#include "update_engine/dynamic_partition_utils.h" using android::base::GetBoolProperty; using android::base::Join; @@ -37,10 +41,14 @@ using android::dm::DmDeviceState; using android::fs_mgr::CreateLogicalPartition; using android::fs_mgr::DestroyLogicalPartition; using android::fs_mgr::MetadataBuilder; +using android::fs_mgr::Partition; using android::fs_mgr::PartitionOpener; +using android::fs_mgr::SlotSuffixForSlotNumber; namespace chromeos_update_engine { +using PartitionMetadata = BootControlInterface::PartitionMetadata; + constexpr char kUseDynamicPartitions[] = "ro.boot.dynamic_partitions"; constexpr char kRetrfoitDynamicPartitions[] = "ro.boot.dynamic_partitions_retrofit"; @@ -50,12 +58,26 @@ DynamicPartitionControlAndroid::~DynamicPartitionControlAndroid() { CleanupInternal(false /* wait */); } -bool DynamicPartitionControlAndroid::IsDynamicPartitionsEnabled() { - return GetBoolProperty(kUseDynamicPartitions, false); +static FeatureFlag GetFeatureFlag(const char* enable_prop, + const char* retrofit_prop) { + bool retrofit = GetBoolProperty(retrofit_prop, false); + bool enabled = GetBoolProperty(enable_prop, false); + if (retrofit && !enabled) { + LOG(ERROR) << retrofit_prop << " is true but " << enable_prop + << " is not. These sysprops are inconsistent. Assume that " + << enable_prop << " is true from now on."; + } + if (retrofit) { + return FeatureFlag(FeatureFlag::Value::RETROFIT); + } + if (enabled) { + return FeatureFlag(FeatureFlag::Value::LAUNCH); + } + return FeatureFlag(FeatureFlag::Value::NONE); } -bool DynamicPartitionControlAndroid::IsDynamicPartitionsRetrofit() { - return GetBoolProperty(kRetrfoitDynamicPartitions, false); +FeatureFlag DynamicPartitionControlAndroid::GetDynamicPartitionsFeatureFlag() { + return GetFeatureFlag(kUseDynamicPartitions, kRetrfoitDynamicPartitions); } bool DynamicPartitionControlAndroid::MapPartitionInternal( @@ -172,19 +194,18 @@ bool DynamicPartitionControlAndroid::GetDmDevicePathByName( std::unique_ptr<MetadataBuilder> DynamicPartitionControlAndroid::LoadMetadataBuilder( + const std::string& super_device, uint32_t source_slot) { + return LoadMetadataBuilder( + super_device, source_slot, BootControlInterface::kInvalidSlot); +} + +std::unique_ptr<MetadataBuilder> +DynamicPartitionControlAndroid::LoadMetadataBuilder( const std::string& super_device, uint32_t source_slot, uint32_t target_slot) { - std::unique_ptr<MetadataBuilder> builder; - - if (target_slot != BootControlInterface::kInvalidSlot && - IsDynamicPartitionsRetrofit()) { - builder = MetadataBuilder::NewForUpdate( - PartitionOpener(), super_device, source_slot, target_slot); - } else { - builder = - MetadataBuilder::New(PartitionOpener(), super_device, source_slot); - } + auto builder = MetadataBuilder::NewForUpdate( + PartitionOpener(), super_device, source_slot, target_slot); if (builder == nullptr) { LOG(WARNING) << "No metadata slot " @@ -210,7 +231,7 @@ bool DynamicPartitionControlAndroid::StoreMetadata( return false; } - if (IsDynamicPartitionsRetrofit()) { + if (GetDynamicPartitionsFeatureFlag().IsRetrofit()) { if (!FlashPartitionTable(super_device, *metadata)) { LOG(ERROR) << "Cannot write metadata to " << super_device; return false; @@ -265,4 +286,109 @@ bool DynamicPartitionControlAndroid::GetDeviceDir(std::string* out) { *out = base::FilePath(misc_device).DirName().value(); return true; } + +bool DynamicPartitionControlAndroid::PreparePartitionsForUpdate( + uint32_t source_slot, + uint32_t target_slot, + const PartitionMetadata& partition_metadata) { + const std::string target_suffix = SlotSuffixForSlotNumber(target_slot); + + // Unmap all the target dynamic partitions because they would become + // inconsistent with the new metadata. + for (const auto& group : partition_metadata.groups) { + for (const auto& partition : group.partitions) { + if (!UnmapPartitionOnDeviceMapper(partition.name + target_suffix)) { + return false; + } + } + } + + std::string device_dir_str; + if (!GetDeviceDir(&device_dir_str)) { + return false; + } + base::FilePath device_dir(device_dir_str); + auto source_device = + device_dir.Append(GetSuperPartitionName(source_slot)).value(); + + auto builder = LoadMetadataBuilder(source_device, source_slot, target_slot); + if (builder == nullptr) { + LOG(ERROR) << "No metadata at " + << BootControlInterface::SlotName(source_slot); + return false; + } + + if (!UpdatePartitionMetadata( + builder.get(), target_slot, partition_metadata)) { + return false; + } + + auto target_device = + device_dir.Append(GetSuperPartitionName(target_slot)).value(); + return StoreMetadata(target_device, builder.get(), target_slot); +} + +std::string DynamicPartitionControlAndroid::GetSuperPartitionName( + uint32_t slot) { + return fs_mgr_get_super_partition_name(slot); +} + +bool DynamicPartitionControlAndroid::UpdatePartitionMetadata( + MetadataBuilder* builder, + uint32_t target_slot, + const PartitionMetadata& partition_metadata) { + const std::string target_suffix = SlotSuffixForSlotNumber(target_slot); + DeleteGroupsWithSuffix(builder, target_suffix); + + uint64_t total_size = 0; + for (const auto& group : partition_metadata.groups) { + total_size += group.size; + } + + std::string expr; + uint64_t allocatable_space = builder->AllocatableSpace(); + if (!GetDynamicPartitionsFeatureFlag().IsRetrofit()) { + allocatable_space /= 2; + expr = "half of "; + } + if (total_size > allocatable_space) { + LOG(ERROR) << "The maximum size of all groups with suffix " << target_suffix + << " (" << total_size << ") has exceeded " << expr + << "allocatable space for dynamic partitions " + << allocatable_space << "."; + return false; + } + + 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 partition_name_suffix = partition.name + target_suffix; + Partition* p = builder->AddPartition( + partition_name_suffix, group_name_suffix, LP_PARTITION_ATTR_READONLY); + if (!p) { + LOG(ERROR) << "Cannot add partition " << partition_name_suffix + << " to group " << group_name_suffix; + return false; + } + if (!builder->ResizePartition(p, partition.size)) { + LOG(ERROR) << "Cannot resize partition " << partition_name_suffix + << " to size " << partition.size << ". Not enough space?"; + return false; + } + LOG(INFO) << "Added partition " << partition_name_suffix << " to group " + << group_name_suffix << " with size " << partition.size; + } + } + + return true; +} + } // namespace chromeos_update_engine diff --git a/dynamic_partition_control_android.h b/dynamic_partition_control_android.h index 334f9bd7..062a2d1a 100644 --- a/dynamic_partition_control_android.h +++ b/dynamic_partition_control_android.h @@ -29,30 +29,59 @@ class DynamicPartitionControlAndroid : public DynamicPartitionControlInterface { public: DynamicPartitionControlAndroid() = default; ~DynamicPartitionControlAndroid(); - bool IsDynamicPartitionsEnabled() override; - bool IsDynamicPartitionsRetrofit() override; + FeatureFlag GetDynamicPartitionsFeatureFlag() override; bool MapPartitionOnDeviceMapper(const std::string& super_device, const std::string& target_partition_name, uint32_t slot, bool force_writable, std::string* path) override; - bool UnmapPartitionOnDeviceMapper( - const std::string& target_partition_name) override; void Cleanup() override; bool DeviceExists(const std::string& path) override; android::dm::DmDeviceState GetState(const std::string& name) override; bool GetDmDevicePathByName(const std::string& name, std::string* path) override; std::unique_ptr<android::fs_mgr::MetadataBuilder> LoadMetadataBuilder( + const std::string& super_device, uint32_t source_slot) override; + + bool PreparePartitionsForUpdate(uint32_t source_slot, + uint32_t target_slot, + const BootControlInterface::PartitionMetadata& + partition_metadata) override; + bool GetDeviceDir(std::string* path) override; + std::string GetSuperPartitionName(uint32_t slot) override; + + protected: + // These functions are exposed for testing. + + // Unmap logical partition on device mapper. This is the reverse operation + // of MapPartitionOnDeviceMapper. + // Returns true if unmapped successfully. + virtual bool UnmapPartitionOnDeviceMapper( + const std::string& target_partition_name); + + // Retrieve metadata from |super_device| at slot |source_slot|. + // + // If |target_slot| != kInvalidSlot, before returning the metadata, this + // function modifies the metadata so that during updates, the metadata can be + // written to |target_slot|. In particular, on retrofit devices, the returned + // metadata automatically includes block devices at |target_slot|. + // + // If |target_slot| == kInvalidSlot, this function returns metadata at + // |source_slot| without modifying it. This is the same as + // LoadMetadataBuilder(const std::string&, uint32_t). + virtual std::unique_ptr<android::fs_mgr::MetadataBuilder> LoadMetadataBuilder( const std::string& super_device, uint32_t source_slot, - uint32_t target_slot) override; - bool StoreMetadata(const std::string& super_device, - android::fs_mgr::MetadataBuilder* builder, - uint32_t target_slot) override; - bool GetDeviceDir(std::string* path) override; + uint32_t target_slot); + + // Write metadata |builder| to |super_device| at slot |target_slot|. + virtual bool StoreMetadata(const std::string& super_device, + android::fs_mgr::MetadataBuilder* builder, + uint32_t target_slot); private: + friend class DynamicPartitionControlAndroidTest; + std::set<std::string> mapped_devices_; void CleanupInternal(bool wait); @@ -62,6 +91,13 @@ class DynamicPartitionControlAndroid : public DynamicPartitionControlInterface { bool force_writable, std::string* path); + // Update |builder| according to |partition_metadata|, assuming the device + // does not have Virtual A/B. + bool UpdatePartitionMetadata( + android::fs_mgr::MetadataBuilder* builder, + uint32_t target_slot, + const BootControlInterface::PartitionMetadata& partition_metadata); + DISALLOW_COPY_AND_ASSIGN(DynamicPartitionControlAndroid); }; diff --git a/dynamic_partition_control_android_unittest.cc b/dynamic_partition_control_android_unittest.cc new file mode 100644 index 00000000..5b3dfe31 --- /dev/null +++ b/dynamic_partition_control_android_unittest.cc @@ -0,0 +1,484 @@ +// +// 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 "update_engine/dynamic_partition_control_android.h" + +#include <set> +#include <vector> + +#include <base/logging.h> +#include <base/strings/string_util.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "update_engine/dynamic_partition_test_utils.h" +#include "update_engine/mock_dynamic_partition_control.h" + +using std::string; +using testing::_; +using testing::AnyNumber; +using testing::Invoke; +using testing::NiceMock; +using testing::Not; +using testing::Return; + +namespace chromeos_update_engine { + +class DynamicPartitionControlAndroidTest : public ::testing::Test { + public: + void SetUp() override { + module_ = std::make_unique<NiceMock<MockDynamicPartitionControlAndroid>>(); + + ON_CALL(dynamicControl(), GetDynamicPartitionsFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); + + ON_CALL(dynamicControl(), GetDeviceDir(_)) + .WillByDefault(Invoke([](auto path) { + *path = kFakeDevicePath; + return true; + })); + + ON_CALL(dynamicControl(), GetSuperPartitionName(_)) + .WillByDefault(Return(kFakeSuper)); + } + + // Return the mocked DynamicPartitionControlInterface. + NiceMock<MockDynamicPartitionControlAndroid>& dynamicControl() { + return static_cast<NiceMock<MockDynamicPartitionControlAndroid>&>(*module_); + } + + std::string GetSuperDevice(uint32_t slot) { + return GetDevice(dynamicControl().GetSuperPartitionName(slot)); + } + + uint32_t source() { return slots_.source; } + uint32_t target() { return slots_.target; } + + // Return partition names with suffix of source(). + std::string S(const std::string& name) { + return name + kSlotSuffixes[source()]; + } + + // Return partition names with suffix of target(). + std::string T(const std::string& name) { + return name + kSlotSuffixes[target()]; + } + + // Set the fake metadata to return when LoadMetadataBuilder is called on + // |slot|. + void SetMetadata(uint32_t slot, const PartitionSuffixSizes& sizes) { + EXPECT_CALL(dynamicControl(), + LoadMetadataBuilder(GetSuperDevice(slot), slot, _)) + .Times(AnyNumber()) + .WillRepeatedly(Invoke([sizes](auto, auto, auto) { + return NewFakeMetadata(PartitionSuffixSizesToMetadata(sizes)); + })); + } + + void ExpectStoreMetadata(const PartitionSuffixSizes& partition_sizes) { + EXPECT_CALL(dynamicControl(), + StoreMetadata(GetSuperDevice(target()), + MetadataMatches(partition_sizes), + target())) + .WillOnce(Return(true)); + } + + // Expect that UnmapPartitionOnDeviceMapper is called on target() metadata + // slot with each partition in |partitions|. + void ExpectUnmap(const std::set<std::string>& partitions) { + // Error when UnmapPartitionOnDeviceMapper is called on unknown arguments. + ON_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(_)) + .WillByDefault(Return(false)); + + for (const auto& partition : partitions) { + EXPECT_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(partition)) + .WillOnce(Return(true)); + } + } + bool PreparePartitionsForUpdate(const PartitionSizes& partition_sizes) { + return dynamicControl().PreparePartitionsForUpdate( + source(), target(), PartitionSizesToMetadata(partition_sizes)); + } + void SetSlots(const TestParam& slots) { slots_ = slots; } + + struct Listener : public ::testing::MatchResultListener { + explicit Listener(std::ostream* os) : MatchResultListener(os) {} + }; + + testing::AssertionResult UpdatePartitionMetadata( + const PartitionSuffixSizes& source_metadata, + const PartitionSizes& update_metadata, + const PartitionSuffixSizes& expected) { + return UpdatePartitionMetadata( + PartitionSuffixSizesToMetadata(source_metadata), + PartitionSizesToMetadata(update_metadata), + PartitionSuffixSizesToMetadata(expected)); + } + testing::AssertionResult UpdatePartitionMetadata( + const PartitionMetadata& source_metadata, + const PartitionMetadata& update_metadata, + const PartitionMetadata& expected) { + return UpdatePartitionMetadata( + source_metadata, update_metadata, MetadataMatches(expected)); + } + testing::AssertionResult UpdatePartitionMetadata( + const PartitionMetadata& source_metadata, + const PartitionMetadata& update_metadata, + const Matcher<MetadataBuilder*>& matcher) { + auto super_metadata = NewFakeMetadata(source_metadata); + if (!module_->UpdatePartitionMetadata( + super_metadata.get(), target(), update_metadata)) { + return testing::AssertionFailure() + << "UpdatePartitionMetadataInternal failed"; + } + std::stringstream ss; + Listener listener(&ss); + if (matcher.MatchAndExplain(super_metadata.get(), &listener)) { + return testing::AssertionSuccess() << ss.str(); + } else { + return testing::AssertionFailure() << ss.str(); + } + } + + std::unique_ptr<DynamicPartitionControlAndroid> module_; + TestParam slots_; +}; + +class DynamicPartitionControlAndroidTestP + : public DynamicPartitionControlAndroidTest, + public ::testing::WithParamInterface<TestParam> { + public: + void SetUp() override { + DynamicPartitionControlAndroidTest::SetUp(); + SetSlots(GetParam()); + } +}; + +// Test resize case. Grow if target metadata contains a partition with a size +// less than expected. +TEST_P(DynamicPartitionControlAndroidTestP, + NeedGrowIfSizeNotMatchWhenResizing) { + PartitionSuffixSizes source_metadata{{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}; + PartitionSuffixSizes expected{{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 3_GiB}, + {T("vendor"), 1_GiB}}; + PartitionSizes update_metadata{{"system", 3_GiB}, {"vendor", 1_GiB}}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +// Test resize case. Shrink if target metadata contains a partition with a size +// greater than expected. +TEST_P(DynamicPartitionControlAndroidTestP, + NeedShrinkIfSizeNotMatchWhenResizing) { + PartitionSuffixSizes source_metadata{{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}; + PartitionSuffixSizes expected{{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 150_MiB}}; + PartitionSizes update_metadata{{"system", 2_GiB}, {"vendor", 150_MiB}}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +// Test adding partitions on the first run. +TEST_P(DynamicPartitionControlAndroidTestP, AddPartitionToEmptyMetadata) { + PartitionSuffixSizes source_metadata{}; + PartitionSuffixSizes expected{{T("system"), 2_GiB}, {T("vendor"), 1_GiB}}; + PartitionSizes update_metadata{{"system", 2_GiB}, {"vendor", 1_GiB}}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +// Test subsequent add case. +TEST_P(DynamicPartitionControlAndroidTestP, AddAdditionalPartition) { + PartitionSuffixSizes source_metadata{{S("system"), 2_GiB}, + {T("system"), 2_GiB}}; + PartitionSuffixSizes expected{ + {S("system"), 2_GiB}, {T("system"), 2_GiB}, {T("vendor"), 1_GiB}}; + PartitionSizes update_metadata{{"system", 2_GiB}, {"vendor", 1_GiB}}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +// Test delete one partition. +TEST_P(DynamicPartitionControlAndroidTestP, DeletePartition) { + PartitionSuffixSizes source_metadata{{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}; + // No T("vendor") + PartitionSuffixSizes expected{ + {S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 2_GiB}}; + PartitionSizes update_metadata{{"system", 2_GiB}}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +// Test delete all partitions. +TEST_P(DynamicPartitionControlAndroidTestP, DeleteAll) { + PartitionSuffixSizes source_metadata{{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}; + PartitionSuffixSizes expected{{S("system"), 2_GiB}, {S("vendor"), 1_GiB}}; + PartitionSizes update_metadata{}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +// Test corrupt source metadata case. +TEST_P(DynamicPartitionControlAndroidTestP, CorruptedSourceMetadata) { + EXPECT_CALL(dynamicControl(), + LoadMetadataBuilder(GetSuperDevice(source()), source(), _)) + .WillOnce(Invoke([](auto, auto, auto) { return nullptr; })); + ExpectUnmap({T("system")}); + + EXPECT_FALSE(PreparePartitionsForUpdate({{"system", 1_GiB}})) + << "Should not be able to continue with corrupt source metadata"; +} + +// Test that UpdatePartitionMetadata fails if there is not enough space on the +// device. +TEST_P(DynamicPartitionControlAndroidTestP, NotEnoughSpace) { + PartitionSuffixSizes source_metadata{{S("system"), 3_GiB}, + {S("vendor"), 2_GiB}, + {T("system"), 0}, + {T("vendor"), 0}}; + PartitionSizes update_metadata{{"system", 3_GiB}, {"vendor", 3_GiB}}; + + EXPECT_FALSE(UpdatePartitionMetadata(source_metadata, update_metadata, {})) + << "Should not be able to fit 11GiB data into 10GiB space"; +} + +TEST_P(DynamicPartitionControlAndroidTestP, NotEnoughSpaceForSlot) { + PartitionSuffixSizes source_metadata{{S("system"), 1_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 0}, + {T("vendor"), 0}}; + PartitionSizes update_metadata{{"system", 3_GiB}, {"vendor", 3_GiB}}; + EXPECT_FALSE(UpdatePartitionMetadata(source_metadata, update_metadata, {})) + << "Should not be able to grow over size of super / 2"; +} + +INSTANTIATE_TEST_CASE_P(DynamicPartitionControlAndroidTest, + DynamicPartitionControlAndroidTestP, + testing::Values(TestParam{0, 1}, TestParam{1, 0})); + +class DynamicPartitionControlAndroidGroupTestP + : public DynamicPartitionControlAndroidTestP { + public: + PartitionMetadata source_metadata; + void SetUp() override { + DynamicPartitionControlAndroidTestP::SetUp(); + source_metadata = { + .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 string& group, + uint64_t group_size, + const string& partition, + uint64_t partition_size) { + return {.name = group, + .size = group_size, + .partitions = {{.name = partition, .size = partition_size}}}; + } +}; + +// Allow to resize within group. +TEST_P(DynamicPartitionControlAndroidGroupTestP, ResizeWithinGroup) { + PartitionMetadata expected{ + .groups = {SimpleGroup(T("android"), 3_GiB, T("system"), 3_GiB), + SimpleGroup(T("oem"), 2_GiB, T("vendor"), 2_GiB)}}; + + PartitionMetadata update_metadata{ + .groups = {SimpleGroup("android", 3_GiB, "system", 3_GiB), + SimpleGroup("oem", 2_GiB, "vendor", 2_GiB)}}; + + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +TEST_P(DynamicPartitionControlAndroidGroupTestP, NotEnoughSpaceForGroup) { + PartitionMetadata update_metadata{ + .groups = {SimpleGroup("android", 3_GiB, "system", 1_GiB), + SimpleGroup("oem", 2_GiB, "vendor", 3_GiB)}}; + EXPECT_FALSE(UpdatePartitionMetadata(source_metadata, update_metadata, {})) + << "Should not be able to grow over maximum size of group"; +} + +TEST_P(DynamicPartitionControlAndroidGroupTestP, GroupTooBig) { + PartitionMetadata update_metadata{ + .groups = {{.name = "android", .size = 3_GiB}, + {.name = "oem", .size = 3_GiB}}}; + EXPECT_FALSE(UpdatePartitionMetadata(source_metadata, update_metadata, {})) + << "Should not be able to grow over size of super / 2"; +} + +TEST_P(DynamicPartitionControlAndroidGroupTestP, AddPartitionToGroup) { + PartitionMetadata expected{ + .groups = {{.name = T("android"), + .size = 3_GiB, + .partitions = {{.name = T("system"), .size = 2_GiB}, + {.name = T("system_ext"), .size = 1_GiB}}}}}; + PartitionMetadata update_metadata{ + .groups = {{.name = "android", + .size = 3_GiB, + .partitions = {{.name = "system", .size = 2_GiB}, + {.name = "system_ext", .size = 1_GiB}}}, + SimpleGroup("oem", 2_GiB, "vendor", 2_GiB)}}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +TEST_P(DynamicPartitionControlAndroidGroupTestP, RemovePartitionFromGroup) { + PartitionMetadata expected{ + .groups = {{.name = T("android"), .size = 3_GiB, .partitions = {}}}}; + PartitionMetadata update_metadata{ + .groups = {{.name = "android", .size = 3_GiB, .partitions = {}}, + SimpleGroup("oem", 2_GiB, "vendor", 2_GiB)}}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +TEST_P(DynamicPartitionControlAndroidGroupTestP, AddGroup) { + PartitionMetadata expected{ + .groups = { + SimpleGroup(T("new_group"), 2_GiB, T("new_partition"), 2_GiB)}}; + PartitionMetadata update_metadata{ + .groups = {SimpleGroup("android", 2_GiB, "system", 2_GiB), + SimpleGroup("oem", 1_GiB, "vendor", 1_GiB), + SimpleGroup("new_group", 2_GiB, "new_partition", 2_GiB)}}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +TEST_P(DynamicPartitionControlAndroidGroupTestP, RemoveGroup) { + PartitionMetadata update_metadata{ + .groups = {SimpleGroup("android", 2_GiB, "system", 2_GiB)}}; + + EXPECT_TRUE(UpdatePartitionMetadata( + source_metadata, update_metadata, Not(HasGroup(T("oem"))))); +} + +TEST_P(DynamicPartitionControlAndroidGroupTestP, ResizeGroup) { + PartitionMetadata expected{ + .groups = {SimpleGroup(T("android"), 2_GiB, T("system"), 2_GiB), + SimpleGroup(T("oem"), 3_GiB, T("vendor"), 3_GiB)}}; + PartitionMetadata update_metadata{ + .groups = {SimpleGroup("android", 2_GiB, "system", 2_GiB), + SimpleGroup("oem", 3_GiB, "vendor", 3_GiB)}}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +INSTANTIATE_TEST_CASE_P(DynamicPartitionControlAndroidTest, + DynamicPartitionControlAndroidGroupTestP, + testing::Values(TestParam{0, 1}, TestParam{1, 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 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}, + // no deleted_b + }; +} + +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 +// case the "other" slot is likely of size "0" (except system, which is +// non-zero because of system_other partition) +TEST_F(DynamicPartitionControlAndroidTest, SimulatedFirstUpdate) { + SetSlots({0, 1}); + + SetMetadata(source(), update_sizes_0()); + SetMetadata(target(), update_sizes_0()); + ExpectStoreMetadata(update_sizes_1()); + ExpectUnmap({"grown_b", "shrunk_b", "same_b", "added_b"}); + + EXPECT_TRUE(PreparePartitionsForUpdate({{"grown", 3_GiB}, + {"shrunk", 150_MiB}, + {"same", 100_MiB}, + {"added", 150_MiB}})); +} + +// After first update, test for the second update. In the second update, the +// "added" partition is deleted and "deleted" partition is re-added. +TEST_F(DynamicPartitionControlAndroidTest, SimulatedSecondUpdate) { + SetSlots({1, 0}); + + SetMetadata(source(), update_sizes_1()); + SetMetadata(target(), update_sizes_0()); + + ExpectStoreMetadata(update_sizes_2()); + ExpectUnmap({"grown_a", "shrunk_a", "same_a", "deleted_a"}); + + EXPECT_TRUE(PreparePartitionsForUpdate({{"grown", 4_GiB}, + {"shrunk", 100_MiB}, + {"same", 100_MiB}, + {"deleted", 64_MiB}})); +} + +} // namespace chromeos_update_engine diff --git a/dynamic_partition_control_interface.h b/dynamic_partition_control_interface.h index d4590f7d..b3ce4ea6 100644 --- a/dynamic_partition_control_interface.h +++ b/dynamic_partition_control_interface.h @@ -26,17 +26,29 @@ #include <libdm/dm.h> #include <liblp/builder.h> +#include "update_engine/common/boot_control_interface.h" + namespace chromeos_update_engine { +struct FeatureFlag { + enum class Value { NONE = 0, RETROFIT, LAUNCH }; + constexpr explicit FeatureFlag(Value value) : value_(value) {} + constexpr bool IsEnabled() const { return value_ != Value::NONE; } + constexpr bool IsRetrofit() const { return value_ == Value::RETROFIT; } + + private: + Value value_; +}; + class DynamicPartitionControlInterface { public: virtual ~DynamicPartitionControlInterface() = default; - // Return true iff dynamic partitions is enabled on this device. - virtual bool IsDynamicPartitionsEnabled() = 0; - - // Return true iff dynamic partitions is retrofitted on this device. - virtual bool IsDynamicPartitionsRetrofit() = 0; + // Return the feature flags of dynamic partitions on this device. + // Return RETROFIT iff dynamic partitions is retrofitted on this device, + // LAUNCH iff this device is launched with dynamic partitions, + // NONE iff dynamic partitions is disabled on this device. + virtual FeatureFlag GetDynamicPartitionsFeatureFlag() = 0; // Map logical partition on device-mapper. // |super_device| is the device path of the physical partition ("super"). @@ -52,13 +64,6 @@ class DynamicPartitionControlInterface { bool force_writable, std::string* path) = 0; - // Unmap logical partition on device mapper. This is the reverse operation - // of MapPartitionOnDeviceMapper. - // If |wait| is set, wait until the device is unmapped. - // Returns true if unmapped successfully. - virtual bool UnmapPartitionOnDeviceMapper( - const std::string& target_partition_name) = 0; - // Do necessary cleanups before destroying the object. virtual void Cleanup() = 0; @@ -77,20 +82,23 @@ class DynamicPartitionControlInterface { std::string* path) = 0; // Retrieve metadata from |super_device| at slot |source_slot|. - // On retrofit devices, if |target_slot| != kInvalidSlot, the returned - // metadata automatically includes block devices at |target_slot|. virtual std::unique_ptr<android::fs_mgr::MetadataBuilder> LoadMetadataBuilder( - const std::string& super_device, - uint32_t source_slot, - uint32_t target_slot) = 0; + const std::string& super_device, uint32_t source_slot) = 0; - // Write metadata |builder| to |super_device| at slot |target_slot|. - virtual bool StoreMetadata(const std::string& super_device, - android::fs_mgr::MetadataBuilder* builder, - uint32_t target_slot) = 0; + // Prepare all partitions for an update specified in |partition_metadata|. + // This is needed before calling MapPartitionOnDeviceMapper(), otherwise the + // device would be mapped in an inconsistent way. + virtual bool PreparePartitionsForUpdate( + uint32_t source_slot, + uint32_t target_slot, + const BootControlInterface::PartitionMetadata& partition_metadata) = 0; // Return a possible location for devices listed by name. virtual bool GetDeviceDir(std::string* path) = 0; + + // Return the name of the super partition (which stores super partition + // metadata) for a given slot. + virtual std::string GetSuperPartitionName(uint32_t slot) = 0; }; } // namespace chromeos_update_engine diff --git a/dynamic_partition_test_utils.h b/dynamic_partition_test_utils.h new file mode 100644 index 00000000..574d30e8 --- /dev/null +++ b/dynamic_partition_test_utils.h @@ -0,0 +1,254 @@ +// +// 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. +// + +#ifndef UPDATE_ENGINE_DYNAMIC_PARTITION_TEST_UTILS_H_ +#define UPDATE_ENGINE_DYNAMIC_PARTITION_TEST_UTILS_H_ + +#include <stdint.h> + +#include <iostream> +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include <base/strings/string_util.h> +#include <fs_mgr.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <liblp/builder.h> + +#include "update_engine/common/boot_control_interface.h" + +namespace chromeos_update_engine { + +using android::fs_mgr::MetadataBuilder; +using testing::_; +using testing::MakeMatcher; +using testing::Matcher; +using testing::MatcherInterface; +using testing::MatchResultListener; + +constexpr const uint32_t kMaxNumSlots = 2; +constexpr const char* kSlotSuffixes[kMaxNumSlots] = {"_a", "_b"}; +constexpr const char* kFakeDevicePath = "/fake/dev/path/"; +constexpr const char* kFakeDmDevicePath = "/fake/dm/dev/path/"; +constexpr const uint32_t kFakeMetadataSize = 65536; +constexpr const char* kDefaultGroup = "foo"; +constexpr const char* kFakeSuper = "fake_super"; + +// A map describing the size of each partition. +// "{name, size}" +using PartitionSizes = std::map<std::string, uint64_t>; + +// "{name_a, size}" +using PartitionSuffixSizes = std::map<std::string, 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. +// clang-format off +inline constexpr unsigned long long operator"" _MiB(unsigned long long x) { // NOLINT + return x << 20; +} +inline constexpr unsigned long long operator"" _GiB(unsigned long long x) { // NOLINT + return x << 30; +} +// clang-format on + +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> +inline std::ostream& operator<<(std::ostream& os, const std::map<U, V>& param) { + os << "{"; + bool first = true; + for (const auto& pair : param) { + if (!first) + os << ", "; + os << pair.first << ":" << pair.second; + first = false; + } + return os << "}"; +} + +template <typename T> +inline 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 << "]"; +} + +inline std::ostream& operator<<(std::ostream& os, + const PartitionMetadata::Partition& p) { + return os << "{" << p.name << ", " << p.size << "}"; +} + +inline std::ostream& operator<<(std::ostream& os, + const PartitionMetadata::Group& g) { + return os << "{" << g.name << ", " << g.size << ", " << g.partitions << "}"; +} + +inline std::ostream& operator<<(std::ostream& os, const PartitionMetadata& m) { + return os << m.groups; +} + +inline std::string GetDevice(const std::string& name) { + return kFakeDevicePath + name; +} + +inline std::string GetDmDevice(const std::string& name) { + return kFakeDmDevicePath + name; +} + +// To support legacy tests, auto-convert {name_a: size} map to +// PartitionMetadata. +inline PartitionMetadata PartitionSuffixSizesToMetadata( + 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. +inline PartitionMetadata PartitionSizesToMetadata( + 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; +} + +inline 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& 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 PartitionSuffixSizes& partition_sizes) + : partition_metadata_(PartitionSuffixSizesToMetadata(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& 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_metadata_; + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "expect not: " << partition_metadata_; + } + + private: + PartitionMetadata partition_metadata_; +}; + +inline Matcher<MetadataBuilder*> MetadataMatches( + 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(); +} + +struct TestParam { + uint32_t source; + uint32_t target; +}; +inline std::ostream& operator<<(std::ostream& os, const TestParam& param) { + return os << "{source: " << param.source << ", target:" << param.target + << "}"; +} + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_DYNAMIC_PARTITION_TEST_UTILS_H_ diff --git a/dynamic_partition_utils.cc b/dynamic_partition_utils.cc new file mode 100644 index 00000000..f9bd886b --- /dev/null +++ b/dynamic_partition_utils.cc @@ -0,0 +1,39 @@ +// +// 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 "update_engine/dynamic_partition_utils.h" + +#include <vector> + +#include <base/logging.h> +#include <base/strings/string_util.h> + +using android::fs_mgr::MetadataBuilder; + +namespace chromeos_update_engine { + +void DeleteGroupsWithSuffix(MetadataBuilder* builder, + const std::string& suffix) { + std::vector<std::string> groups = builder->ListGroups(); + for (const auto& group_name : groups) { + if (base::EndsWith(group_name, suffix, base::CompareCase::SENSITIVE)) { + LOG(INFO) << "Removing group " << group_name; + builder->RemoveGroupAndPartitions(group_name); + } + } +} + +} // namespace chromeos_update_engine diff --git a/dynamic_partition_utils.h b/dynamic_partition_utils.h new file mode 100644 index 00000000..09fce00c --- /dev/null +++ b/dynamic_partition_utils.h @@ -0,0 +1,33 @@ +// +// 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. +// + +#ifndef UPDATE_ENGINE_DYNAMIC_PARTITION_UTILS_H_ +#define UPDATE_ENGINE_DYNAMIC_PARTITION_UTILS_H_ + +#include <string> + +#include <liblp/builder.h> + +namespace chromeos_update_engine { + +// Delete all groups (and their partitions) in |builder| that have names +// ending with |suffix|. +void DeleteGroupsWithSuffix(android::fs_mgr::MetadataBuilder* builder, + const std::string& suffix); + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_DYNAMIC_PARTITION_UTILS_H_ diff --git a/mock_dynamic_partition_control.h b/mock_dynamic_partition_control.h index cdfeeccc..26fc2469 100644 --- a/mock_dynamic_partition_control.h +++ b/mock_dynamic_partition_control.h @@ -21,6 +21,8 @@ #include <gmock/gmock.h> +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/dynamic_partition_control_android.h" #include "update_engine/dynamic_partition_control_interface.h" namespace chromeos_update_engine { @@ -33,6 +35,31 @@ class MockDynamicPartitionControl : public DynamicPartitionControlInterface { uint32_t, bool, std::string*)); + MOCK_METHOD0(Cleanup, void()); + MOCK_METHOD1(DeviceExists, bool(const std::string&)); + MOCK_METHOD1(GetState, ::android::dm::DmDeviceState(const std::string&)); + MOCK_METHOD2(GetDmDevicePathByName, bool(const std::string&, std::string*)); + MOCK_METHOD2(LoadMetadataBuilder, + std::unique_ptr<::android::fs_mgr::MetadataBuilder>( + const std::string&, uint32_t)); + MOCK_METHOD1(GetDeviceDir, bool(std::string*)); + MOCK_METHOD0(GetDynamicPartitionsFeatureFlag, FeatureFlag()); + MOCK_METHOD3(PreparePartitionsForUpdate, + bool(uint32_t, + uint32_t, + const BootControlInterface::PartitionMetadata&)); + MOCK_METHOD1(GetSuperPartitionName, std::string(uint32_t)); +}; + +class MockDynamicPartitionControlAndroid + : public DynamicPartitionControlAndroid { + public: + MOCK_METHOD5(MapPartitionOnDeviceMapper, + bool(const std::string&, + const std::string&, + uint32_t, + bool, + std::string*)); MOCK_METHOD1(UnmapPartitionOnDeviceMapper, bool(const std::string&)); MOCK_METHOD0(Cleanup, void()); MOCK_METHOD1(DeviceExists, bool(const std::string&)); @@ -46,8 +73,8 @@ class MockDynamicPartitionControl : public DynamicPartitionControlInterface { android::fs_mgr::MetadataBuilder*, uint32_t)); MOCK_METHOD1(GetDeviceDir, bool(std::string*)); - MOCK_METHOD0(IsDynamicPartitionsEnabled, bool()); - MOCK_METHOD0(IsDynamicPartitionsRetrofit, bool()); + MOCK_METHOD0(GetDynamicPartitionsFeatureFlag, FeatureFlag()); + MOCK_METHOD1(GetSuperPartitionName, std::string(uint32_t)); }; } // namespace chromeos_update_engine |