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