Merge "liblp: Add support for updateable partition groups."
This commit is contained in:
commit
d7f2c56089
8 changed files with 214 additions and 16 deletions
|
|
@ -79,8 +79,9 @@ void ZeroExtent::AddTo(LpMetadata* out) const {
|
|||
out->extents.push_back(LpMetadataExtent{num_sectors_, LP_TARGET_TYPE_ZERO, 0});
|
||||
}
|
||||
|
||||
Partition::Partition(const std::string& name, const std::string& guid, uint32_t attributes)
|
||||
: name_(name), guid_(guid), attributes_(attributes), size_(0) {}
|
||||
Partition::Partition(const std::string& name, const std::string& group_name,
|
||||
const std::string& guid, uint32_t attributes)
|
||||
: name_(name), group_name_(group_name), guid_(guid), attributes_(attributes), size_(0) {}
|
||||
|
||||
void Partition::AddExtent(std::unique_ptr<Extent>&& extent) {
|
||||
size_ += extent->num_sectors() * LP_SECTOR_SIZE;
|
||||
|
|
@ -128,6 +129,17 @@ void Partition::ShrinkTo(uint64_t aligned_size) {
|
|||
DCHECK(size_ == aligned_size);
|
||||
}
|
||||
|
||||
uint64_t Partition::BytesOnDisk() const {
|
||||
uint64_t sectors = 0;
|
||||
for (const auto& extent : extents_) {
|
||||
if (!extent->AsLinearExtent()) {
|
||||
continue;
|
||||
}
|
||||
sectors += extent->num_sectors();
|
||||
}
|
||||
return sectors * LP_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
std::unique_ptr<MetadataBuilder> MetadataBuilder::New(const std::string& block_device,
|
||||
uint32_t slot_number) {
|
||||
std::unique_ptr<LpMetadata> metadata = ReadMetadata(block_device.c_str(), slot_number);
|
||||
|
|
@ -175,14 +187,23 @@ MetadataBuilder::MetadataBuilder() {
|
|||
header_.header_size = sizeof(header_);
|
||||
header_.partitions.entry_size = sizeof(LpMetadataPartition);
|
||||
header_.extents.entry_size = sizeof(LpMetadataExtent);
|
||||
header_.groups.entry_size = sizeof(LpMetadataPartitionGroup);
|
||||
}
|
||||
|
||||
bool MetadataBuilder::Init(const LpMetadata& metadata) {
|
||||
geometry_ = metadata.geometry;
|
||||
|
||||
for (const auto& group : metadata.groups) {
|
||||
std::string group_name = GetPartitionGroupName(group);
|
||||
if (!AddGroup(group_name, group.maximum_size)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& partition : metadata.partitions) {
|
||||
Partition* builder = AddPartition(GetPartitionName(partition), GetPartitionGuid(partition),
|
||||
partition.attributes);
|
||||
std::string group_name = GetPartitionGroupName(metadata.groups[partition.group_index]);
|
||||
Partition* builder = AddPartition(GetPartitionName(partition), group_name,
|
||||
GetPartitionGuid(partition), partition.attributes);
|
||||
if (!builder) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -292,11 +313,29 @@ bool MetadataBuilder::Init(const BlockDeviceInfo& device_info, uint32_t metadata
|
|||
geometry_.alignment_offset = device_info_.alignment_offset;
|
||||
geometry_.block_device_size = device_info_.size;
|
||||
geometry_.logical_block_size = device_info.logical_block_size;
|
||||
|
||||
if (!AddGroup("default", 0)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetadataBuilder::AddGroup(const std::string& group_name, uint64_t maximum_size) {
|
||||
if (FindGroup(group_name)) {
|
||||
LERROR << "Group already exists: " << group_name;
|
||||
return false;
|
||||
}
|
||||
groups_.push_back(std::make_unique<PartitionGroup>(group_name, maximum_size));
|
||||
return true;
|
||||
}
|
||||
|
||||
Partition* MetadataBuilder::AddPartition(const std::string& name, const std::string& guid,
|
||||
uint32_t attributes) {
|
||||
return AddPartition(name, "default", guid, attributes);
|
||||
}
|
||||
|
||||
Partition* MetadataBuilder::AddPartition(const std::string& name, const std::string& group_name,
|
||||
const std::string& guid, uint32_t attributes) {
|
||||
if (name.empty()) {
|
||||
LERROR << "Partition must have a non-empty name.";
|
||||
return nullptr;
|
||||
|
|
@ -305,7 +344,11 @@ Partition* MetadataBuilder::AddPartition(const std::string& name, const std::str
|
|||
LERROR << "Attempting to create duplication partition with name: " << name;
|
||||
return nullptr;
|
||||
}
|
||||
partitions_.push_back(std::make_unique<Partition>(name, guid, attributes));
|
||||
if (!FindGroup(group_name)) {
|
||||
LERROR << "Could not find partition group: " << group_name;
|
||||
return nullptr;
|
||||
}
|
||||
partitions_.push_back(std::make_unique<Partition>(name, group_name, guid, attributes));
|
||||
return partitions_.back().get();
|
||||
}
|
||||
|
||||
|
|
@ -318,6 +361,26 @@ Partition* MetadataBuilder::FindPartition(const std::string& name) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
PartitionGroup* MetadataBuilder::FindGroup(const std::string& group_name) const {
|
||||
for (const auto& group : groups_) {
|
||||
if (group->name() == group_name) {
|
||||
return group.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint64_t MetadataBuilder::TotalSizeOfGroup(PartitionGroup* group) const {
|
||||
uint64_t total = 0;
|
||||
for (const auto& partition : partitions_) {
|
||||
if (partition->group_name() != group->name()) {
|
||||
continue;
|
||||
}
|
||||
total += partition->BytesOnDisk();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
void MetadataBuilder::RemovePartition(const std::string& name) {
|
||||
for (auto iter = partitions_.begin(); iter != partitions_.end(); iter++) {
|
||||
if ((*iter)->name() == name) {
|
||||
|
|
@ -328,8 +391,23 @@ void MetadataBuilder::RemovePartition(const std::string& name) {
|
|||
}
|
||||
|
||||
bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t aligned_size) {
|
||||
// Figure out how much we need to allocate.
|
||||
PartitionGroup* group = FindGroup(partition->group_name());
|
||||
CHECK(group);
|
||||
|
||||
// Figure out how much we need to allocate, and whether our group has
|
||||
// enough space remaining.
|
||||
uint64_t space_needed = aligned_size - partition->size();
|
||||
if (group->maximum_size() > 0) {
|
||||
uint64_t group_size = TotalSizeOfGroup(group);
|
||||
if (group_size >= group->maximum_size() ||
|
||||
group->maximum_size() - group_size < space_needed) {
|
||||
LERROR << "Partition " << partition->name() << " is part of group " << group->name()
|
||||
<< " which does not have enough space free (" << space_needed << "requested, "
|
||||
<< group_size << " used out of " << group->maximum_size();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t sectors_needed = space_needed / LP_SECTOR_SIZE;
|
||||
DCHECK(sectors_needed * LP_SECTOR_SIZE == space_needed);
|
||||
|
||||
|
|
@ -441,6 +519,20 @@ std::unique_ptr<LpMetadata> MetadataBuilder::Export() {
|
|||
metadata->header = header_;
|
||||
metadata->geometry = geometry_;
|
||||
|
||||
std::map<std::string, size_t> group_indices;
|
||||
for (const auto& group : groups_) {
|
||||
LpMetadataPartitionGroup out = {};
|
||||
|
||||
if (group->name().size() > sizeof(out.name)) {
|
||||
LERROR << "Partition group name is too long: " << group->name();
|
||||
return nullptr;
|
||||
}
|
||||
strncpy(out.name, group->name().c_str(), sizeof(out.name));
|
||||
out.maximum_size = group->maximum_size();
|
||||
|
||||
metadata->groups.push_back(out);
|
||||
}
|
||||
|
||||
// Flatten the partition and extent structures into an LpMetadata, which
|
||||
// makes it very easy to validate, serialize, or pass on to device-mapper.
|
||||
for (const auto& partition : partitions_) {
|
||||
|
|
@ -475,6 +567,7 @@ std::unique_ptr<LpMetadata> MetadataBuilder::Export() {
|
|||
|
||||
metadata->header.partitions.num_entries = static_cast<uint32_t>(metadata->partitions.size());
|
||||
metadata->header.extents.num_entries = static_cast<uint32_t>(metadata->extents.size());
|
||||
metadata->header.groups.num_entries = static_cast<uint32_t>(metadata->groups.size());
|
||||
return metadata;
|
||||
}
|
||||
|
||||
|
|
@ -530,8 +623,10 @@ bool MetadataBuilder::ResizePartition(Partition* partition, uint64_t requested_s
|
|||
ShrinkPartition(partition, aligned_size);
|
||||
}
|
||||
|
||||
LINFO << "Partition " << partition->name() << " will resize from " << old_size << " bytes to "
|
||||
<< aligned_size << " bytes";
|
||||
if (partition->size() != old_size) {
|
||||
LINFO << "Partition " << partition->name() << " will resize from " << old_size
|
||||
<< " bytes to " << aligned_size << " bytes";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -495,3 +495,28 @@ TEST(liblp, AlignedFreeSpace) {
|
|||
unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 512, 1);
|
||||
ASSERT_EQ(builder, nullptr);
|
||||
}
|
||||
|
||||
TEST(liblp, HasDefaultGroup) {
|
||||
BlockDeviceInfo device_info(1024 * 1024, 0, 0, 4096);
|
||||
unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1);
|
||||
ASSERT_NE(builder, nullptr);
|
||||
|
||||
EXPECT_FALSE(builder->AddGroup("default", 0));
|
||||
}
|
||||
|
||||
TEST(liblp, GroupSizeLimits) {
|
||||
BlockDeviceInfo device_info(1024 * 1024, 0, 0, 4096);
|
||||
unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1);
|
||||
ASSERT_NE(builder, nullptr);
|
||||
|
||||
ASSERT_TRUE(builder->AddGroup("google", 16384));
|
||||
|
||||
Partition* partition = builder->AddPartition("system", "google", TEST_GUID, 0);
|
||||
ASSERT_NE(partition, nullptr);
|
||||
EXPECT_TRUE(builder->ResizePartition(partition, 8192));
|
||||
EXPECT_EQ(partition->size(), 8192);
|
||||
EXPECT_TRUE(builder->ResizePartition(partition, 16384));
|
||||
EXPECT_EQ(partition->size(), 16384);
|
||||
EXPECT_FALSE(builder->ResizePartition(partition, 32768));
|
||||
EXPECT_EQ(partition->size(), 16384);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,11 +95,25 @@ class ZeroExtent final : public Extent {
|
|||
void AddTo(LpMetadata* out) const override;
|
||||
};
|
||||
|
||||
class PartitionGroup final {
|
||||
public:
|
||||
explicit PartitionGroup(const std::string& name, uint64_t maximum_size)
|
||||
: name_(name), maximum_size_(maximum_size) {}
|
||||
|
||||
const std::string& name() const { return name_; }
|
||||
uint64_t maximum_size() const { return maximum_size_; }
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
uint64_t maximum_size_;
|
||||
};
|
||||
|
||||
class Partition final {
|
||||
friend class MetadataBuilder;
|
||||
|
||||
public:
|
||||
Partition(const std::string& name, const std::string& guid, uint32_t attributes);
|
||||
Partition(const std::string& name, const std::string& group_name, const std::string& guid,
|
||||
uint32_t attributes);
|
||||
|
||||
// Add a raw extent.
|
||||
void AddExtent(std::unique_ptr<Extent>&& extent);
|
||||
|
|
@ -107,7 +121,12 @@ class Partition final {
|
|||
// Remove all extents from this partition.
|
||||
void RemoveExtents();
|
||||
|
||||
// Compute the size used by linear extents. This is the same as size(),
|
||||
// but does not factor in extents which do not take up space.
|
||||
uint64_t BytesOnDisk() const;
|
||||
|
||||
const std::string& name() const { return name_; }
|
||||
const std::string& group_name() const { return group_name_; }
|
||||
uint32_t attributes() const { return attributes_; }
|
||||
const std::string& guid() const { return guid_; }
|
||||
const std::vector<std::unique_ptr<Extent>>& extents() const { return extents_; }
|
||||
|
|
@ -117,6 +136,7 @@ class Partition final {
|
|||
void ShrinkTo(uint64_t aligned_size);
|
||||
|
||||
std::string name_;
|
||||
std::string group_name_;
|
||||
std::string guid_;
|
||||
std::vector<std::unique_ptr<Extent>> extents_;
|
||||
uint32_t attributes_;
|
||||
|
|
@ -156,12 +176,24 @@ class MetadataBuilder {
|
|||
return New(device_info, metadata_max_size, metadata_slot_count);
|
||||
}
|
||||
|
||||
// Define a new partition group. By default there is one group called
|
||||
// "default", with an unrestricted size. A non-zero size will restrict the
|
||||
// total space used by all partitions in the group.
|
||||
//
|
||||
// This can fail and return false if the group already exists.
|
||||
bool AddGroup(const std::string& group_name, uint64_t maximum_size);
|
||||
|
||||
// Export metadata so it can be serialized to an image, to disk, or mounted
|
||||
// via device-mapper.
|
||||
std::unique_ptr<LpMetadata> Export();
|
||||
|
||||
// Add a partition, returning a handle so it can be sized as needed. If a
|
||||
// partition with the given name already exists, nullptr is returned.
|
||||
Partition* AddPartition(const std::string& name, const std::string& group_name,
|
||||
const std::string& guid, uint32_t attributes);
|
||||
|
||||
// Same as AddPartition above, but uses the default partition group which
|
||||
// has no size restrictions.
|
||||
Partition* AddPartition(const std::string& name, const std::string& guid, uint32_t attributes);
|
||||
|
||||
// Delete a partition by name if it exists.
|
||||
|
|
@ -202,10 +234,13 @@ class MetadataBuilder {
|
|||
bool GrowPartition(Partition* partition, uint64_t aligned_size);
|
||||
void ShrinkPartition(Partition* partition, uint64_t aligned_size);
|
||||
uint64_t AlignSector(uint64_t sector);
|
||||
PartitionGroup* FindGroup(const std::string& group_name) const;
|
||||
uint64_t TotalSizeOfGroup(PartitionGroup* group) const;
|
||||
|
||||
LpMetadataGeometry geometry_;
|
||||
LpMetadataHeader header_;
|
||||
std::vector<std::unique_ptr<Partition>> partitions_;
|
||||
std::vector<std::unique_ptr<PartitionGroup>> groups_;
|
||||
BlockDeviceInfo device_info_;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ struct LpMetadata {
|
|||
LpMetadataHeader header;
|
||||
std::vector<LpMetadataPartition> partitions;
|
||||
std::vector<LpMetadataExtent> extents;
|
||||
std::vector<LpMetadataPartitionGroup> groups;
|
||||
};
|
||||
|
||||
// Place an initial partition table on the device. This will overwrite the
|
||||
|
|
@ -68,6 +69,7 @@ std::unique_ptr<LpMetadata> ReadFromImageBlob(const void* data, size_t bytes);
|
|||
// Helper to extract safe C++ strings from partition info.
|
||||
std::string GetPartitionName(const LpMetadataPartition& partition);
|
||||
std::string GetPartitionGuid(const LpMetadataPartition& partition);
|
||||
std::string GetPartitionGroupName(const LpMetadataPartitionGroup& group);
|
||||
|
||||
// Helper to return a slot number for a slot suffix.
|
||||
uint32_t SlotNumberForSlotSuffix(const std::string& suffix);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ extern "C" {
|
|||
#define LP_METADATA_HEADER_MAGIC 0x414C5030
|
||||
|
||||
/* Current metadata version. */
|
||||
#define LP_METADATA_MAJOR_VERSION 1
|
||||
#define LP_METADATA_MAJOR_VERSION 2
|
||||
#define LP_METADATA_MINOR_VERSION 0
|
||||
|
||||
/* Attributes for the LpMetadataPartition::attributes field.
|
||||
|
|
@ -216,6 +216,8 @@ typedef struct LpMetadataHeader {
|
|||
LpMetadataTableDescriptor partitions;
|
||||
/* 92: Extent table descriptor. */
|
||||
LpMetadataTableDescriptor extents;
|
||||
/* 104: Updateable group descriptor. */
|
||||
LpMetadataTableDescriptor groups;
|
||||
} __attribute__((packed)) LpMetadataHeader;
|
||||
|
||||
/* This struct defines a logical partition entry, similar to what would be
|
||||
|
|
@ -245,6 +247,9 @@ typedef struct LpMetadataPartition {
|
|||
* least one extent.
|
||||
*/
|
||||
uint32_t num_extents;
|
||||
|
||||
/* 64: Group this partition belongs to. */
|
||||
uint32_t group_index;
|
||||
} __attribute__((packed)) LpMetadataPartition;
|
||||
|
||||
/* This extent is a dm-linear target, and the index is an index into the
|
||||
|
|
@ -271,6 +276,19 @@ typedef struct LpMetadataExtent {
|
|||
uint64_t target_data;
|
||||
} __attribute__((packed)) LpMetadataExtent;
|
||||
|
||||
/* This struct defines an entry in the groups table. Each group has a maximum
|
||||
* size, and partitions in a group must not exceed that size. There is always
|
||||
* a "default" group of unlimited size, which is used when not using update
|
||||
* groups or when using overlayfs or fastbootd.
|
||||
*/
|
||||
typedef struct LpMetadataPartitionGroup {
|
||||
/* 0: Name of this group. Any unused characters must be 0. */
|
||||
char name[36];
|
||||
|
||||
/* 36: Maximum size in bytes. If 0, the group has no maximum size. */
|
||||
uint64_t maximum_size;
|
||||
} LpMetadataPartitionGroup;
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -343,9 +343,6 @@ TEST(liblp, TooManyPartitions) {
|
|||
ASSERT_NE(partition, nullptr);
|
||||
}
|
||||
ASSERT_NE(partition, nullptr);
|
||||
// Add one extent to any partition to fill up more space - we're at 508
|
||||
// bytes after this, out of 512.
|
||||
ASSERT_TRUE(builder->ResizePartition(partition, 1024));
|
||||
|
||||
unique_ptr<LpMetadata> exported = builder->Export();
|
||||
ASSERT_NE(exported, nullptr);
|
||||
|
|
|
|||
|
|
@ -188,7 +188,8 @@ static bool ValidateMetadataHeader(const LpMetadataHeader& header) {
|
|||
return false;
|
||||
}
|
||||
if (!ValidateTableBounds(header, header.partitions) ||
|
||||
!ValidateTableBounds(header, header.extents)) {
|
||||
!ValidateTableBounds(header, header.extents) ||
|
||||
!ValidateTableBounds(header, header.groups)) {
|
||||
LERROR << "Logical partition metadata has invalid table bounds.";
|
||||
return false;
|
||||
}
|
||||
|
|
@ -202,6 +203,10 @@ static bool ValidateMetadataHeader(const LpMetadataHeader& header) {
|
|||
LERROR << "Logical partition metadata has invalid extent table entry size.";
|
||||
return false;
|
||||
}
|
||||
if (header.groups.entry_size != sizeof(LpMetadataPartitionGroup)) {
|
||||
LERROR << "Logical partition metadata has invalid group table entry size.";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -257,6 +262,10 @@ static std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geome
|
|||
LERROR << "Logical partition has invalid extent list.";
|
||||
return nullptr;
|
||||
}
|
||||
if (partition.group_index >= header.groups.num_entries) {
|
||||
LERROR << "Logical partition has invalid group index.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
metadata->partitions.push_back(partition);
|
||||
}
|
||||
|
|
@ -269,6 +278,16 @@ static std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geome
|
|||
|
||||
metadata->extents.push_back(extent);
|
||||
}
|
||||
|
||||
cursor = buffer.get() + header.groups.offset;
|
||||
for (size_t i = 0; i < header.groups.num_entries; i++) {
|
||||
LpMetadataPartitionGroup group = {};
|
||||
memcpy(&group, cursor, sizeof(group));
|
||||
cursor += header.groups.entry_size;
|
||||
|
||||
metadata->groups.push_back(group);
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
|
|
@ -345,5 +364,9 @@ std::string GetPartitionName(const LpMetadataPartition& partition) {
|
|||
return NameFromFixedArray(partition.name, sizeof(partition.name));
|
||||
}
|
||||
|
||||
std::string GetPartitionGroupName(const LpMetadataPartitionGroup& group) {
|
||||
return NameFromFixedArray(group.name, sizeof(group.name));
|
||||
}
|
||||
|
||||
} // namespace fs_mgr
|
||||
} // namespace android
|
||||
|
|
|
|||
|
|
@ -56,14 +56,17 @@ std::string SerializeMetadata(const LpMetadata& input) {
|
|||
metadata.partitions.size() * sizeof(LpMetadataPartition));
|
||||
std::string extents(reinterpret_cast<const char*>(metadata.extents.data()),
|
||||
metadata.extents.size() * sizeof(LpMetadataExtent));
|
||||
std::string groups(reinterpret_cast<const char*>(metadata.groups.data()),
|
||||
metadata.groups.size() * sizeof(LpMetadataPartitionGroup));
|
||||
|
||||
// Compute positions of tables.
|
||||
header.partitions.offset = 0;
|
||||
header.extents.offset = header.partitions.offset + partitions.size();
|
||||
header.tables_size = header.extents.offset + extents.size();
|
||||
header.groups.offset = header.extents.offset + extents.size();
|
||||
header.tables_size = header.groups.offset + groups.size();
|
||||
|
||||
// Compute payload checksum.
|
||||
std::string tables = partitions + extents;
|
||||
std::string tables = partitions + extents + groups;
|
||||
SHA256(tables.data(), tables.size(), header.tables_checksum);
|
||||
|
||||
// Compute header checksum.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue