// // Copyright (C) 2018 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/boot_control_android.h" #include #include #include #include #include #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::_; 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::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* kFakeMappedPath = "/fake/mapped/path/"; constexpr const uint32_t kFakeMetadataSize = 65536; // A map describing the size of each partition. using PartitionSizes = std::map; // 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 return x << 20; } unsigned long long operator"" _GiB(unsigned long long x) { // NOLINT return x << 30; } template std::ostream& operator<<(std::ostream& os, const std::map& param) { os << "{"; bool first = true; for (const auto& pair : param) { if (!first) os << ", "; os << pair.first << ":" << pair.second; first = false; } return os << "}"; } inline std::string GetDevice(const std::string& name) { return kFakeDevicePath + name; } inline std::string GetSuperDevice() { return GetDevice(fs_mgr_get_super_partition_name()); } 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 << "}"; } std::unique_ptr NewFakeMetadata(const PartitionSizes& sizes) { auto builder = MetadataBuilder::New(10_GiB, kFakeMetadataSize, kMaxNumSlots); 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)); } return builder; } class MetadataMatcher : public MatcherInterface { public: explicit MetadataMatcher(const PartitionSizes& partition_sizes) : partition_sizes_(partition_sizes) {} 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; } } return success; } void DescribeTo(std::ostream* os) const override { *os << "expect: " << partition_sizes_; } void DescribeNegationTo(std::ostream* os) const override { *os << "expect not: " << partition_sizes_; } private: PartitionSizes partition_sizes_; }; inline Matcher MetadataMatches( const PartitionSizes& partition_sizes) { return MakeMatcher(new MetadataMatcher(partition_sizes)); } class BootControlAndroidTest : public ::testing::Test { protected: void SetUp() override { // Fake init bootctl_ bootctl_.module_ = new NiceMock(); bootctl_.dynamic_control_ = std::make_unique>(); ON_CALL(module(), getNumberSlots()).WillByDefault(Invoke([] { return kMaxNumSlots; })); ON_CALL(module(), getSuffix(_, _)) .WillByDefault(Invoke([](auto slot, auto cb) { EXPECT_LE(slot, kMaxNumSlots); cb(slot < kMaxNumSlots ? kSlotSuffixes[slot] : ""); return Void(); })); ON_CALL(dynamicControl(), IsDynamicPartitionsEnabled()) .WillByDefault(Return(true)); ON_CALL(dynamicControl(), GetDeviceDir(_)) .WillByDefault(Invoke([](auto path) { *path = kFakeDevicePath; return true; })); } // Return the mocked HAL module. NiceMock& module() { return static_cast&>(*bootctl_.module_); } // Return the mocked DynamicPartitionControlInterface. NiceMock& dynamicControl() { return static_cast&>( *bootctl_.dynamic_control_); } // Set the fake metadata to return when LoadMetadataBuilder is called on // |slot|. void SetMetadata(uint32_t slot, const PartitionSizes& sizes) { EXPECT_CALL(dynamicControl(), LoadMetadataBuilder(GetSuperDevice(), slot)) .WillOnce( Invoke([sizes](auto, auto) { return NewFakeMetadata(sizes); })); } // Expect that MapPartitionOnDeviceMapper is called on target() metadata slot // with each partition in |partitions|. void ExpectMap(const std::set& partitions) { // Error when MapPartitionOnDeviceMapper is called on unknown arguments. ON_CALL(dynamicControl(), MapPartitionOnDeviceMapper(_, _, _, _)) .WillByDefault(Return(false)); for (const auto& partition : partitions) { EXPECT_CALL( dynamicControl(), MapPartitionOnDeviceMapper(GetSuperDevice(), partition, target(), _)) .WillOnce(Invoke([this](auto, auto partition, auto, auto path) { auto it = mapped_devices_.find(partition); if (it != mapped_devices_.end()) { *path = it->second; return true; } mapped_devices_[partition] = *path = kFakeMappedPath + partition; return true; })); } } // Expect that UnmapPartitionOnDeviceMapper is called on target() metadata // slot with each partition in |partitions|. void ExpectUnmap(const std::set& 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, auto) { mapped_devices_.erase(partition); return true; })); } } void ExpectRemap(const std::set& partitions) { ExpectUnmap(partitions); ExpectMap(partitions); } void ExpectDevicesAreMapped(const std::set& 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."; } } 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 + std::string(kSlotSuffixes[source()]); } // Return partition names with suffix of target(). std::string T(const std::string& name) { return name + std::string(kSlotSuffixes[target()]); } // Set source and target slots to use before testing. void SetSlots(const TestParam& slots) { slots_ = slots; ON_CALL(module(), getCurrentSlot()).WillByDefault(Invoke([this] { return source(); })); // Should not store metadata to source slot. EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, source())) .Times(0); } BootControlAndroid bootctl_; // BootControlAndroid under test. TestParam slots_; // mapped devices through MapPartitionOnDeviceMapper. std::map mapped_devices_; }; class BootControlAndroidTestP : public BootControlAndroidTest, public ::testing::WithParamInterface { public: void SetUp() override { BootControlAndroidTest::SetUp(); SetSlots(GetParam()); } }; // Test no resize if no dynamic partitions at all. TEST_P(BootControlAndroidTestP, NoResizeIfNoDynamicPartitions) { SetMetadata(source(), {}); SetMetadata(target(), {}); // Should not need to resize and store metadata EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target())) .Times(0); EXPECT_CALL(dynamicControl(), DeviceExists(Eq(GetDevice("static_a")))) .Times(AnyNumber()) .WillRepeatedly(Return(true)); EXPECT_CALL(dynamicControl(), DeviceExists(Eq(GetDevice("static_b")))) .Times(AnyNumber()) .WillRepeatedly(Return(true)); EXPECT_TRUE(bootctl_.InitPartitionMetadata(target(), {{"static", 1_GiB}})); ExpectDevicesAreMapped({}); } // Test no resize if update manifest does not contain any dynamic partitions TEST_P(BootControlAndroidTestP, NoResizeIfEmptyMetadata) { SetMetadata(source(), {{S("system"), 4_GiB}, {S("vendor"), 100_MiB}, {T("system"), 3_GiB}, {T("vendor"), 150_MiB}}); SetMetadata(target(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 3_GiB}, {T("vendor"), 150_MiB}}); // Should not need to resize and store metadata EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target())) .Times(0); EXPECT_CALL(dynamicControl(), DeviceExists(Eq(GetDevice("static_a")))) .Times(AnyNumber()) .WillRepeatedly(Return(true)); EXPECT_CALL(dynamicControl(), DeviceExists(Eq(GetDevice("static_b")))) .Times(AnyNumber()) .WillRepeatedly(Return(true)); EXPECT_TRUE(bootctl_.InitPartitionMetadata(target(), {{"static", 1_GiB}})); ExpectDevicesAreMapped({}); } // Do not resize if manifest size matches size in target metadata. When resuming // from an update, do not redo the resize if not needed. TEST_P(BootControlAndroidTestP, NoResizeIfSizeMatchWhenResizing) { SetMetadata(source(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}}); SetMetadata(target(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 3_GiB}, {T("vendor"), 1_GiB}}); // Should not need to resize and store metadata EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target())) .Times(0); ExpectRemap({T("system"), T("vendor")}); EXPECT_TRUE(bootctl_.InitPartitionMetadata( target(), {{"system", 3_GiB}, {"vendor", 1_GiB}})); ExpectDevicesAreMapped({T("system"), T("vendor")}); } // Do not resize if manifest size matches size in target metadata. When resuming // from an update, do not redo the resize if not needed. TEST_P(BootControlAndroidTestP, NoResizeIfSizeMatchWhenAdding) { SetMetadata(source(), {{S("system"), 2_GiB}, {T("system"), 2_GiB}}); SetMetadata( target(), {{S("system"), 2_GiB}, {T("system"), 2_GiB}, {T("vendor"), 1_GiB}}); // Should not need to resize and store metadata EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target())) .Times(0); ExpectRemap({T("system"), T("vendor")}); EXPECT_TRUE(bootctl_.InitPartitionMetadata( target(), {{"system", 2_GiB}, {"vendor", 1_GiB}})); ExpectDevicesAreMapped({T("system"), T("vendor")}); } // Do not resize if manifest size matches size in target metadata. When resuming // from an update, do not redo the resize if not needed. TEST_P(BootControlAndroidTestP, NoResizeIfSizeMatchWhenDeleting) { SetMetadata(source(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 2_GiB}, {T("vendor"), 1_GiB}}); SetMetadata(target(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 2_GiB}, {T("vendor"), 0}}); // Should not need to resize and store metadata EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target())) .Times(0); ExpectUnmap({T("system"), T("vendor")}); ExpectMap({T("system")}); EXPECT_TRUE(bootctl_.InitPartitionMetadata( target(), {{"system", 2_GiB}, {"vendor", 0}})); ExpectDevicesAreMapped({T("system")}); } // 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); EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), MetadataMatches({{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 3_GiB}, {T("vendor"), 1_GiB}}), target())) .WillOnce(Return(true)); ExpectRemap({T("system"), T("vendor")}); EXPECT_TRUE(bootctl_.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); EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), MetadataMatches({{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 2_GiB}, {T("vendor"), 150_MiB}}), target())) .WillOnce(Return(true)); ExpectRemap({T("system"), T("vendor")}); EXPECT_TRUE(bootctl_.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(), {}); EXPECT_CALL(dynamicControl(), StoreMetadata( GetSuperDevice(), MetadataMatches({{T("system"), 2_GiB}, {T("vendor"), 1_GiB}}), target())) .WillOnce(Return(true)); ExpectRemap({T("system"), T("vendor")}); EXPECT_TRUE(bootctl_.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}}); EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), MetadataMatches({{S("system"), 2_GiB}, {T("system"), 2_GiB}, {T("vendor"), 1_GiB}}), target())) .WillOnce(Return(true)); ExpectRemap({T("system"), T("vendor")}); EXPECT_TRUE(bootctl_.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); EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), MetadataMatches({{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 2_GiB}, {T("vendor"), 0}}), target())) .WillOnce(Return(true)); ExpectUnmap({T("system"), T("vendor")}); ExpectMap({T("system")}); EXPECT_TRUE(bootctl_.InitPartitionMetadata( target(), {{"system", 2_GiB}, {"vendor", 0}})); 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); EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), MetadataMatches({{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 0}, {T("vendor"), 0}}), target())) .WillOnce(Return(true)); ExpectUnmap({T("system"), T("vendor")}); ExpectMap({}); EXPECT_TRUE( bootctl_.InitPartitionMetadata(target(), {{"system", 0}, {"vendor", 0}})); ExpectDevicesAreMapped({}); } // Test corrupt source metadata case. This shouldn't happen in practice, // because the device is already booted normally. TEST_P(BootControlAndroidTestP, CorruptedSourceMetadata) { EXPECT_CALL(dynamicControl(), LoadMetadataBuilder(GetSuperDevice(), source())) .WillOnce(Invoke([](auto, auto) { return nullptr; })); EXPECT_FALSE(bootctl_.InitPartitionMetadata(target(), {})) << "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) { SetMetadata(source(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 0}, {T("vendor"), 0}}); EXPECT_CALL(dynamicControl(), LoadMetadataBuilder(GetSuperDevice(), target())) .WillOnce(Invoke([](auto, auto) { return nullptr; })); EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), MetadataMatches({{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 3_GiB}, {T("vendor"), 150_MiB}}), target())) .WillOnce(Return(true)); ExpectRemap({T("system"), T("vendor")}); EXPECT_TRUE(bootctl_.InitPartitionMetadata( target(), {{"system", 3_GiB}, {"vendor", 150_MiB}})); ExpectDevicesAreMapped({T("system"), T("vendor")}); } // 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"; } INSTANTIATE_TEST_CASE_P(ParamTest, 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 PartitionSizes update_sizes_1() { return { {"grown_a", 2_GiB}, {"shrunk_a", 1_GiB}, {"same_a", 100_MiB}, {"deleted_a", 150_MiB}, {"grown_b", 3_GiB}, {"shrunk_b", 150_MiB}, {"same_b", 100_MiB}, {"added_b", 150_MiB}, {"deleted_b", 0}, }; } 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}}; } // 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()); EXPECT_CALL( dynamicControl(), StoreMetadata( GetSuperDevice(), MetadataMatches(update_sizes_1()), target())) .WillOnce(Return(true)); 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}})); ExpectDevicesAreMapped({"grown_b", "shrunk_b", "same_b", "added_b"}); } // 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()); EXPECT_CALL( dynamicControl(), StoreMetadata( GetSuperDevice(), MetadataMatches(update_sizes_2()), target())) .WillOnce(Return(true)); 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}})); ExpectDevicesAreMapped({"grown_a", "shrunk_a", "same_a", "deleted_a"}); } TEST_F(BootControlAndroidTest, ApplyingToCurrentSlot) { SetSlots({1, 1}); EXPECT_FALSE(bootctl_.InitPartitionMetadata(target(), {})) << "Should not be able to apply to current slot."; } } // namespace chromeos_update_engine