summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp2
-rw-r--r--boot_control_android.cc146
-rw-r--r--boot_control_android_unittest.cc645
-rw-r--r--dynamic_partition_control_android.cc156
-rw-r--r--dynamic_partition_control_android.h54
-rw-r--r--dynamic_partition_control_android_unittest.cc484
-rw-r--r--dynamic_partition_control_interface.h50
-rw-r--r--dynamic_partition_test_utils.h254
-rw-r--r--dynamic_partition_utils.cc39
-rw-r--r--dynamic_partition_utils.h33
-rw-r--r--mock_dynamic_partition_control.h31
11 files changed, 1083 insertions, 811 deletions
diff --git a/Android.bp b/Android.bp
index 1d146a5c..a13a92b7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -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