diff options
| author | Yifan Hong <elsk@google.com> | 2020-08-13 13:59:54 -0700 |
|---|---|---|
| committer | Semavi Ulusoy <doc.divxm@gmail.com> | 2021-07-06 00:34:42 +0300 |
| commit | f8285b8a772d21a22b4048d724698b6f41e23d06 (patch) | |
| tree | 5067a297a75060069ddb1b9ab8f3e2e5b0458e8e | |
| parent | e43822610e3940e03fb715c8c66a34c34428b483 (diff) | |
Check allocatable space correctly when sideloading on VAB
On a device with Virtual A/B, when sideloading and there's
not enough space in super partition to hold CoW, update_engine
falls back to overwriting all source partitions. In that case,
the allocatable space should be the whole super partition, not
a half of it.
Also update doc comments.
Test: unit test. RecoveryErrorShouldDeleteSource fails without the patch
but succeeds with the patch.
Bug: 163613538
Change-Id: I6bd6895a7eabeb4e8436e57b0ac6830c11d1e98f
| -rw-r--r-- | dynamic_partition_control_android.cc | 5 | ||||
| -rw-r--r-- | dynamic_partition_control_android.h | 7 | ||||
| -rw-r--r-- | dynamic_partition_control_android_unittest.cc | 119 | ||||
| -rw-r--r-- | dynamic_partition_test_utils.h | 6 |
4 files changed, 130 insertions, 7 deletions
diff --git a/dynamic_partition_control_android.cc b/dynamic_partition_control_android.cc index 708380a8..3103a381 100644 --- a/dynamic_partition_control_android.cc +++ b/dynamic_partition_control_android.cc @@ -780,6 +780,11 @@ bool DynamicPartitionControlAndroid::UpdatePartitionMetadata( MetadataBuilder* builder, uint32_t target_slot, const DeltaArchiveManifest& manifest) { + // Check preconditions. + CHECK(!GetVirtualAbFeatureFlag().IsEnabled() || IsRecovery()) + << "UpdatePartitionMetadata is called on a Virtual A/B device " + "but source partitions is not deleted. This is not allowed."; + // If applying downgrade from Virtual A/B to non-Virtual A/B, the left-over // COW group needs to be deleted to ensure there are enough space to create // target partitions. diff --git a/dynamic_partition_control_android.h b/dynamic_partition_control_android.h index 8ad75933..c2bec1dc 100644 --- a/dynamic_partition_control_android.h +++ b/dynamic_partition_control_android.h @@ -183,8 +183,11 @@ 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. + // Update |builder| according to |partition_metadata|. + // - In Android mode, this is only called when the device + // does not have Virtual A/B. + // - When sideloading, this maybe called as a fallback path if CoW cannot + // be created. bool UpdatePartitionMetadata(android::fs_mgr::MetadataBuilder* builder, uint32_t target_slot, const DeltaArchiveManifest& manifest); diff --git a/dynamic_partition_control_android_unittest.cc b/dynamic_partition_control_android_unittest.cc index 20819182..ccc4a36b 100644 --- a/dynamic_partition_control_android_unittest.cc +++ b/dynamic_partition_control_android_unittest.cc @@ -100,13 +100,15 @@ class DynamicPartitionControlAndroidTest : public ::testing::Test { // |slot|. void SetMetadata(uint32_t slot, const PartitionSuffixSizes& sizes, - uint32_t partition_attr = 0) { + uint32_t partition_attr = 0, + uint64_t super_size = kDefaultSuperSize) { EXPECT_CALL(dynamicControl(), LoadMetadataBuilder(GetSuperDevice(slot), slot, _)) .Times(AnyNumber()) - .WillRepeatedly(Invoke([sizes, partition_attr](auto, auto, auto) { + .WillRepeatedly(Invoke([=](auto, auto, auto) { return NewFakeMetadata(PartitionSuffixSizesToManifest(sizes), - partition_attr); + partition_attr, + super_size); })); } @@ -892,4 +894,115 @@ TEST_P(DynamicPartitionControlAndroidTestP, EraseSystemOtherAvbFooter) { ASSERT_EQ(new_expected, device_content); } +class FakeAutoDevice : public android::snapshot::AutoDevice { + public: + FakeAutoDevice() : AutoDevice("") {} +}; + +class SnapshotPartitionTestP : public DynamicPartitionControlAndroidTestP { + public: + void SetUp() override { + DynamicPartitionControlAndroidTestP::SetUp(); + ON_CALL(dynamicControl(), GetVirtualAbFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); + + snapshot_ = new NiceMock<MockSnapshotManager>(); + dynamicControl().snapshot_.reset(snapshot_); // takes ownership + EXPECT_CALL(*snapshot_, BeginUpdate()).WillOnce(Return(true)); + EXPECT_CALL(*snapshot_, EnsureMetadataMounted()) + .WillRepeatedly( + Invoke([]() { return std::make_unique<FakeAutoDevice>(); })); + + manifest_ = + PartitionSizesToManifest({{"system", 3_GiB}, {"vendor", 1_GiB}}); + } + void ExpectCreateUpdateSnapshots(android::snapshot::Return val) { + manifest_.mutable_dynamic_partition_metadata()->set_snapshot_enabled(true); + EXPECT_CALL(*snapshot_, CreateUpdateSnapshots(_)) + .WillRepeatedly(Invoke([&, val](const auto& manifest) { + // Deep comparison requires full protobuf library. Comparing the + // pointers are sufficient. + EXPECT_EQ(&manifest_, &manifest); + LOG(WARNING) << "CreateUpdateSnapshots returning " << val.string(); + return val; + })); + } + bool PreparePartitionsForUpdate(uint64_t* required_size) { + return dynamicControl().PreparePartitionsForUpdate( + source(), target(), manifest_, true /* update */, required_size); + } + MockSnapshotManager* snapshot_ = nullptr; + DeltaArchiveManifest manifest_; +}; + +// Test happy path of PreparePartitionsForUpdate on a Virtual A/B device. +TEST_P(SnapshotPartitionTestP, PreparePartitions) { + ExpectCreateUpdateSnapshots(android::snapshot::Return::Ok()); + uint64_t required_size = 0; + EXPECT_TRUE(PreparePartitionsForUpdate(&required_size)); + EXPECT_EQ(0u, required_size); +} + +// Test that if not enough space, required size returned by SnapshotManager is +// passed up. +TEST_P(SnapshotPartitionTestP, PreparePartitionsNoSpace) { + ExpectCreateUpdateSnapshots(android::snapshot::Return::NoSpace(1_GiB)); + uint64_t required_size = 0; + EXPECT_FALSE(PreparePartitionsForUpdate(&required_size)); + EXPECT_EQ(1_GiB, required_size); +} + +// Test that in recovery, use empty space in super partition for a snapshot +// update first. +TEST_P(SnapshotPartitionTestP, RecoveryUseSuperEmpty) { + ExpectCreateUpdateSnapshots(android::snapshot::Return::Ok()); + EXPECT_CALL(dynamicControl(), IsRecovery()).WillRepeatedly(Return(true)); + // Must not call PrepareDynamicPartitionsForUpdate if + // PrepareSnapshotPartitionsForUpdate succeeds. + EXPECT_CALL(dynamicControl(), PrepareDynamicPartitionsForUpdate(_, _, _, _)) + .Times(0); + uint64_t required_size = 0; + EXPECT_TRUE(PreparePartitionsForUpdate(&required_size)); + EXPECT_EQ(0u, required_size); +} + +// Test that in recovery, if CreateUpdateSnapshots throws an error, try +// the flashing path for full updates. +TEST_P(SnapshotPartitionTestP, RecoveryErrorShouldDeleteSource) { + // Expectation on PreparePartitionsForUpdate + ExpectCreateUpdateSnapshots(android::snapshot::Return::NoSpace(1_GiB)); + EXPECT_CALL(dynamicControl(), IsRecovery()).WillRepeatedly(Return(true)); + EXPECT_CALL(*snapshot_, CancelUpdate()).WillOnce(Return(true)); + EXPECT_CALL(dynamicControl(), PrepareDynamicPartitionsForUpdate(_, _, _, _)) + .WillRepeatedly(Invoke([&](auto source_slot, + auto target_slot, + const auto& manifest, + auto delete_source) { + EXPECT_EQ(source(), source_slot); + EXPECT_EQ(target(), target_slot); + // Deep comparison requires full protobuf library. Comparing the + // pointers are sufficient. + EXPECT_EQ(&manifest_, &manifest); + EXPECT_TRUE(delete_source); + return dynamicControl().RealPrepareDynamicPartitionsForUpdate( + source_slot, target_slot, manifest, delete_source); + })); + // Only one slot of space in super + uint64_t super_size = kDefaultGroupSize + 1_MiB; + // Expectation on PrepareDynamicPartitionsForUpdate + SetMetadata( + source(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}}, 0, super_size); + ExpectUnmap({T("system"), T("vendor")}); + // Expect that the source partitions aren't present in target super metadata. + ExpectStoreMetadata({{T("system"), 3_GiB}, {T("vendor"), 1_GiB}}); + + uint64_t required_size = 0; + EXPECT_TRUE(PreparePartitionsForUpdate(&required_size)); + EXPECT_EQ(0u, required_size); +} + +INSTANTIATE_TEST_CASE_P(DynamicPartitionControlAndroidTest, + SnapshotPartitionTestP, + testing::Values(TestParam{0, 1}, TestParam{1, 0})); + } // namespace chromeos_update_engine diff --git a/dynamic_partition_test_utils.h b/dynamic_partition_test_utils.h index 70a176b5..d701dce8 100644 --- a/dynamic_partition_test_utils.h +++ b/dynamic_partition_test_utils.h @@ -175,9 +175,11 @@ inline DeltaArchiveManifest PartitionSizesToManifest( } inline std::unique_ptr<MetadataBuilder> NewFakeMetadata( - const DeltaArchiveManifest& manifest, uint32_t partition_attr = 0) { + const DeltaArchiveManifest& manifest, + uint32_t partition_attr = 0, + uint64_t super_size = kDefaultSuperSize) { auto builder = - MetadataBuilder::New(kDefaultSuperSize, kFakeMetadataSize, kMaxNumSlots); + MetadataBuilder::New(super_size, kFakeMetadataSize, kMaxNumSlots); for (const auto& group : manifest.dynamic_partition_metadata().groups()) { EXPECT_TRUE(builder->AddGroup(group.name(), group.size())); for (const auto& partition_name : group.partition_names()) { |
