Loading fs_mgr/liblp/builder.cpp +96 −56 Original line number Diff line number Diff line Loading @@ -48,10 +48,20 @@ bool GetBlockDeviceInfo(const std::string& block_device, BlockDeviceInfo* device PERROR << __PRETTY_FUNCTION__ << "BLKIOMIN failed"; return false; } if (ioctl(fd, BLKALIGNOFF, &device_info->alignment_offset) < 0) { int alignment_offset; if (ioctl(fd, BLKALIGNOFF, &alignment_offset) < 0) { PERROR << __PRETTY_FUNCTION__ << "BLKIOMIN failed"; return false; } int logical_block_size; if (ioctl(fd, BLKSSZGET, &logical_block_size) < 0) { PERROR << __PRETTY_FUNCTION__ << "BLKSSZGET failed"; return false; } device_info->alignment_offset = static_cast<uint32_t>(alignment_offset); device_info->logical_block_size = static_cast<uint32_t>(logical_block_size); return true; #else (void)block_device; Loading Loading @@ -178,6 +188,7 @@ bool MetadataBuilder::Init(const LpMetadata& metadata) { device_info_.alignment = geometry_.alignment; device_info_.alignment_offset = geometry_.alignment_offset; device_info_.logical_block_size = geometry_.logical_block_size; return true; } Loading @@ -201,6 +212,10 @@ bool MetadataBuilder::Init(const BlockDeviceInfo& device_info, uint32_t metadata LERROR << "Block device size must be a multiple of 512."; return false; } if (device_info_.logical_block_size % LP_SECTOR_SIZE != 0) { LERROR << "Logical block size must be a multiple of 512."; return false; } if (device_info_.alignment_offset % LP_SECTOR_SIZE != 0) { LERROR << "Alignment offset is not sector-aligned."; return false; Loading Loading @@ -244,6 +259,18 @@ bool MetadataBuilder::Init(const BlockDeviceInfo& device_info, uint32_t metadata return false; } // Finally, the size of the allocatable space must be a multiple of the // logical block size. If we have no more free space after this // computation, then we abort. Note that the last sector is inclusive, // so we have to account for that. uint64_t num_free_sectors = last_sector - first_sector + 1; uint64_t sectors_per_block = device_info_.logical_block_size / LP_SECTOR_SIZE; if (num_free_sectors < sectors_per_block) { LERROR << "Not enough space to allocate any partition tables."; return false; } last_sector = first_sector + (num_free_sectors / sectors_per_block) * sectors_per_block - 1; geometry_.first_logical_sector = first_sector; geometry_.last_logical_sector = last_sector; geometry_.metadata_max_size = metadata_max_size; Loading @@ -251,6 +278,7 @@ bool MetadataBuilder::Init(const BlockDeviceInfo& device_info, uint32_t metadata geometry_.alignment = device_info_.alignment; geometry_.alignment_offset = device_info_.alignment_offset; geometry_.block_device_size = device_info_.size; geometry_.logical_block_size = device_info.logical_block_size; return true; } Loading Loading @@ -297,87 +325,93 @@ bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t aligned_size) uint64_t end; Interval(uint64_t start, uint64_t end) : start(start), end(end) {} uint64_t length() const { return end - start; } bool operator<(const Interval& other) const { return start < other.start; } }; std::vector<Interval> intervals; // Collect all extents in the partition table. // Collect all extents in the partition table, then sort them by starting // sector. std::vector<Interval> extents; for (const auto& partition : partitions_) { for (const auto& extent : partition->extents()) { LinearExtent* linear = extent->AsLinearExtent(); if (!linear) { continue; } intervals.emplace_back(linear->physical_sector(), extents.emplace_back(linear->physical_sector(), linear->physical_sector() + extent->num_sectors()); } } std::sort(extents.begin(), extents.end()); // Convert the extent list into a list of gaps between the extents; i.e., // the list of ranges that are free on the disk. std::vector<Interval> free_regions; for (size_t i = 1; i < extents.size(); i++) { const Interval& previous = extents[i - 1]; const Interval& current = extents[i]; uint64_t aligned = AlignSector(previous.end); if (aligned >= current.start) { // There is no gap between these two extents, try the next one. // Note that we check with >= instead of >, since alignment may // bump the ending sector past the beginning of the next extent. continue; } // The new interval represents the free space starting at the end of // the previous interval, and ending at the start of the next interval. free_regions.emplace_back(aligned, current.start); } // Add a final interval representing the remainder of the free space. uint64_t last_free_extent_start = extents.empty() ? geometry_.first_logical_sector : extents.back().end; last_free_extent_start = AlignSector(last_free_extent_start); if (last_free_extent_start <= geometry_.last_logical_sector) { free_regions.emplace_back(last_free_extent_start, geometry_.last_logical_sector + 1); } // Sort extents by starting sector. std::sort(intervals.begin(), intervals.end()); const uint64_t sectors_per_block = device_info_.logical_block_size / LP_SECTOR_SIZE; CHECK(sectors_needed % sectors_per_block == 0); // Find gaps that we can use for new extents. Note we store new extents in a // temporary vector, and only commit them if we are guaranteed enough free // space. std::vector<std::unique_ptr<LinearExtent>> new_extents; for (size_t i = 1; i < intervals.size(); i++) { const Interval& previous = intervals[i - 1]; const Interval& current = intervals[i]; if (previous.end >= current.start) { // There is no gap between these two extents, try the next one. Note that // extents may never overlap, but just for safety, we ignore them if they // do. DCHECK(previous.end == current.start); for (auto& region : free_regions) { if (region.length() % sectors_per_block != 0) { // This should never happen, because it would imply that we // once allocated an extent that was not a multiple of the // block size. That extent would be rejected by DM_TABLE_LOAD. LERROR << "Region " << region.start << ".." << region.end << " is not a multiple of the block size, " << sectors_per_block; // If for some reason the final region is mis-sized we still want // to be able to grow partitions. So just to be safe, round the // region down to the nearest block. region.end = region.start + (region.length() / sectors_per_block) * sectors_per_block; if (!region.length()) { continue; } uint64_t aligned = AlignSector(previous.end); if (aligned >= current.start) { // After alignment, this extent is not usable. continue; } // This gap is enough to hold the remainder of the space requested, so we // can allocate what we need and return. if (current.start - aligned >= sectors_needed) { auto extent = std::make_unique<LinearExtent>(sectors_needed, aligned); sectors_needed -= extent->num_sectors(); uint64_t sectors = std::min(sectors_needed, region.length()); CHECK(sectors % sectors_per_block == 0); auto extent = std::make_unique<LinearExtent>(sectors, region.start); new_extents.push_back(std::move(extent)); sectors_needed -= sectors; if (!sectors_needed) { break; } // This gap is not big enough to fit the remainder of the space requested, // so consume the whole thing and keep looking for more. auto extent = std::make_unique<LinearExtent>(current.start - aligned, aligned); sectors_needed -= extent->num_sectors(); new_extents.push_back(std::move(extent)); } // If we still have more to allocate, take it from the remaining free space // in the allocatable region. if (sectors_needed) { uint64_t first_sector; if (intervals.empty()) { first_sector = geometry_.first_logical_sector; } else { first_sector = intervals.back().end; } DCHECK(first_sector <= geometry_.last_logical_sector); // Note: After alignment, |first_sector| may be > the last usable sector. first_sector = AlignSector(first_sector); // Note: the last usable sector is inclusive. if (first_sector > geometry_.last_logical_sector || geometry_.last_logical_sector + 1 - first_sector < sectors_needed) { LERROR << "Not enough free space to expand partition: " << partition->name(); return false; } auto extent = std::make_unique<LinearExtent>(sectors_needed, first_sector); new_extents.push_back(std::move(extent)); } // Everything succeeded, so commit the new extents. for (auto& extent : new_extents) { partition->AddExtent(std::move(extent)); } Loading Loading @@ -445,6 +479,12 @@ uint64_t MetadataBuilder::AlignSector(uint64_t sector) { void MetadataBuilder::set_block_device_info(const BlockDeviceInfo& device_info) { device_info_.size = device_info.size; // Note that if the logical block size changes, we're probably in trouble: // we could have already built extents that will only work on the previous // size. DCHECK(partitions_.empty() || device_info_.logical_block_size == device_info.logical_block_size); // The kernel does not guarantee these values are present, so we only // replace existing values if the new values are non-zero. if (device_info.alignment) { Loading @@ -457,7 +497,7 @@ void MetadataBuilder::set_block_device_info(const BlockDeviceInfo& device_info) bool MetadataBuilder::ResizePartition(Partition* partition, uint64_t requested_size) { // Align the space needed up to the nearest sector. uint64_t aligned_size = AlignTo(requested_size, LP_SECTOR_SIZE); uint64_t aligned_size = AlignTo(requested_size, device_info_.logical_block_size); if (aligned_size > partition->size()) { return GrowPartition(partition, aligned_size); Loading fs_mgr/liblp/builder_test.cpp +38 −12 Original line number Diff line number Diff line Loading @@ -92,11 +92,11 @@ TEST(liblp, PartitionAlignment) { Partition* system = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_READONLY); ASSERT_NE(system, nullptr); EXPECT_EQ(builder->ResizePartition(system, 10000), true); EXPECT_EQ(system->size(), 10240); EXPECT_EQ(system->size(), 12288); EXPECT_EQ(system->extents().size(), 1); builder->ResizePartition(system, 9000); EXPECT_EQ(system->size(), 9216); builder->ResizePartition(system, 7000); EXPECT_EQ(system->size(), 8192); EXPECT_EQ(system->extents().size(), 1); } Loading @@ -120,13 +120,13 @@ TEST(liblp, MetadataAlignment) { TEST(liblp, InternalAlignment) { // Test the metadata fitting within alignment. BlockDeviceInfo device_info(1024 * 1024, 768 * 1024, 0); BlockDeviceInfo device_info(1024 * 1024, 768 * 1024, 0, 4096); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 2); ASSERT_NE(builder, nullptr); unique_ptr<LpMetadata> exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.first_logical_sector, 1536); EXPECT_EQ(exported->geometry.last_logical_sector, 2035); EXPECT_EQ(exported->geometry.last_logical_sector, 2031); // Test a large alignment offset thrown in. device_info.alignment_offset = 753664; Loading @@ -135,7 +135,7 @@ TEST(liblp, InternalAlignment) { exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.first_logical_sector, 1472); EXPECT_EQ(exported->geometry.last_logical_sector, 2035); EXPECT_EQ(exported->geometry.last_logical_sector, 2031); // Alignment offset without alignment doesn't mean anything. device_info.alignment = 0; Loading @@ -150,7 +150,7 @@ TEST(liblp, InternalAlignment) { exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.first_logical_sector, 78); EXPECT_EQ(exported->geometry.last_logical_sector, 1975); EXPECT_EQ(exported->geometry.last_logical_sector, 1973); // Test a small alignment with no alignment offset. device_info.alignment = 11 * 1024; Loading @@ -163,7 +163,7 @@ TEST(liblp, InternalAlignment) { } TEST(liblp, InternalPartitionAlignment) { BlockDeviceInfo device_info(512 * 1024 * 1024, 768 * 1024, 753664); BlockDeviceInfo device_info(512 * 1024 * 1024, 768 * 1024, 753664, 4096); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 32 * 1024, 2); Partition* a = builder->AddPartition("a", TEST_GUID, 0); Loading Loading @@ -381,7 +381,7 @@ TEST(liblp, MetadataTooLarge) { static const size_t kMetadataSize = 64 * 1024; // No space to store metadata + geometry. BlockDeviceInfo device_info(kDiskSize, 0, 0); BlockDeviceInfo device_info(kDiskSize, 0, 0, 4096); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); Loading @@ -390,8 +390,8 @@ TEST(liblp, MetadataTooLarge) { builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); // Space for metadata + geometry + one free sector. device_info.size += LP_SECTOR_SIZE; // Space for metadata + geometry + one free block. device_info.size += device_info.logical_block_size; builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_NE(builder, nullptr); Loading Loading @@ -424,19 +424,21 @@ TEST(liblp, block_device_info) { ASSERT_EQ(device_info.alignment % LP_SECTOR_SIZE, 0); ASSERT_EQ(device_info.alignment_offset % LP_SECTOR_SIZE, 0); ASSERT_LE(device_info.alignment_offset, INT_MAX); ASSERT_EQ(device_info.logical_block_size % LP_SECTOR_SIZE, 0); // Having an alignment offset > alignment doesn't really make sense. ASSERT_LT(device_info.alignment_offset, device_info.alignment); } TEST(liblp, UpdateBlockDeviceInfo) { BlockDeviceInfo device_info(1024 * 1024, 4096, 1024); BlockDeviceInfo device_info(1024 * 1024, 4096, 1024, 4096); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1); ASSERT_NE(builder, nullptr); EXPECT_EQ(builder->block_device_info().size, device_info.size); EXPECT_EQ(builder->block_device_info().alignment, device_info.alignment); EXPECT_EQ(builder->block_device_info().alignment_offset, device_info.alignment_offset); EXPECT_EQ(builder->block_device_info().logical_block_size, device_info.logical_block_size); device_info.alignment = 0; device_info.alignment_offset = 2048; Loading @@ -450,3 +452,27 @@ TEST(liblp, UpdateBlockDeviceInfo) { EXPECT_EQ(builder->block_device_info().alignment, 8192); EXPECT_EQ(builder->block_device_info().alignment_offset, 2048); } TEST(liblp, InvalidBlockSize) { BlockDeviceInfo device_info(1024 * 1024, 0, 0, 513); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1); EXPECT_EQ(builder, nullptr); } TEST(liblp, AlignedExtentSize) { BlockDeviceInfo device_info(1024 * 1024, 0, 0, 4096); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1); ASSERT_NE(builder, nullptr); Partition* partition = builder->AddPartition("system", TEST_GUID, 0); ASSERT_NE(partition, nullptr); ASSERT_TRUE(builder->ResizePartition(partition, 512)); EXPECT_EQ(partition->size(), 4096); } TEST(liblp, AlignedFreeSpace) { // Only one sector free - at least one block is required. BlockDeviceInfo device_info(10240, 0, 0, 4096); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 512, 1); ASSERT_EQ(builder, nullptr); } fs_mgr/liblp/include/liblp/builder.h +15 −8 Original line number Diff line number Diff line Loading @@ -32,11 +32,16 @@ class LinearExtent; // By default, partitions are aligned on a 1MiB boundary. static const uint32_t kDefaultPartitionAlignment = 1024 * 1024; static const uint32_t kDefaultBlockSize = 4096; struct BlockDeviceInfo { BlockDeviceInfo() : size(0), alignment(0), alignment_offset(0) {} BlockDeviceInfo(uint64_t size, uint32_t alignment, uint32_t alignment_offset) : size(size), alignment(alignment), alignment_offset(alignment_offset) {} BlockDeviceInfo() : size(0), alignment(0), alignment_offset(0), logical_block_size(0) {} BlockDeviceInfo(uint64_t size, uint32_t alignment, uint32_t alignment_offset, uint32_t logical_block_size) : size(size), alignment(alignment), alignment_offset(alignment_offset), logical_block_size(logical_block_size) {} // Size of the block device, in bytes. uint64_t size; // Optimal target alignment, in bytes. Partition extents will be aligned to Loading @@ -46,6 +51,8 @@ struct BlockDeviceInfo { // |alignment_offset| on the target device is correctly aligned on its // parent device. This value must be 0 or a multiple of 512. uint32_t alignment_offset; // Block size, for aligning extent sizes and partition sizes. uint32_t logical_block_size; }; // Abstraction around dm-targets that can be encoded into logical partition tables. Loading Loading @@ -88,6 +95,8 @@ class ZeroExtent final : public Extent { }; class Partition final { friend class MetadataBuilder; public: Partition(const std::string& name, const std::string& guid, uint32_t attributes); Loading @@ -97,10 +106,6 @@ class Partition final { // Remove all extents from this partition. void RemoveExtents(); // Remove and/or shrink extents until the partition is the requested size. // See MetadataBuilder::ShrinkPartition for more information. void ShrinkTo(uint64_t requested_size); const std::string& name() const { return name_; } uint32_t attributes() const { return attributes_; } const std::string& guid() const { return guid_; } Loading @@ -108,6 +113,8 @@ class Partition final { uint64_t size() const { return size_; } private: void ShrinkTo(uint64_t aligned_size); std::string name_; std::string guid_; std::vector<std::unique_ptr<Extent>> extents_; Loading Loading @@ -144,7 +151,7 @@ class MetadataBuilder { // size. This is a convenience method for tests. static std::unique_ptr<MetadataBuilder> New(uint64_t blockdev_size, uint32_t metadata_max_size, uint32_t metadata_slot_count) { BlockDeviceInfo device_info(blockdev_size, 0, 0); BlockDeviceInfo device_info(blockdev_size, 0, 0, kDefaultBlockSize); return New(device_info, metadata_max_size, metadata_slot_count); } Loading fs_mgr/liblp/include/liblp/metadata_format.h +6 −0 Original line number Diff line number Diff line Loading @@ -136,6 +136,12 @@ typedef struct LpMetadataGeometry { * can be used to verify the geometry against a target device. */ uint64_t block_device_size; /* 76: Logical block size of the super partition block device. This is the * minimal alignment for partition and extent sizes, and it must be a * multiple of LP_SECTOR_SIZE. */ uint32_t logical_block_size; } __attribute__((packed)) LpMetadataGeometry; /* The logical partition metadata has a number of tables; they are described Loading fs_mgr/liblp/utility_test.cpp +2 −1 Original line number Diff line number Diff line Loading @@ -40,7 +40,8 @@ TEST(liblp, GetMetadataOffset) { 80000, 0, 0, 1024 * 1024}; 1024 * 1024, 4096}; EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 0), 4096); EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 1), 4096 + 16384); EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 2), 4096 + 16384 * 2); Loading Loading
fs_mgr/liblp/builder.cpp +96 −56 Original line number Diff line number Diff line Loading @@ -48,10 +48,20 @@ bool GetBlockDeviceInfo(const std::string& block_device, BlockDeviceInfo* device PERROR << __PRETTY_FUNCTION__ << "BLKIOMIN failed"; return false; } if (ioctl(fd, BLKALIGNOFF, &device_info->alignment_offset) < 0) { int alignment_offset; if (ioctl(fd, BLKALIGNOFF, &alignment_offset) < 0) { PERROR << __PRETTY_FUNCTION__ << "BLKIOMIN failed"; return false; } int logical_block_size; if (ioctl(fd, BLKSSZGET, &logical_block_size) < 0) { PERROR << __PRETTY_FUNCTION__ << "BLKSSZGET failed"; return false; } device_info->alignment_offset = static_cast<uint32_t>(alignment_offset); device_info->logical_block_size = static_cast<uint32_t>(logical_block_size); return true; #else (void)block_device; Loading Loading @@ -178,6 +188,7 @@ bool MetadataBuilder::Init(const LpMetadata& metadata) { device_info_.alignment = geometry_.alignment; device_info_.alignment_offset = geometry_.alignment_offset; device_info_.logical_block_size = geometry_.logical_block_size; return true; } Loading @@ -201,6 +212,10 @@ bool MetadataBuilder::Init(const BlockDeviceInfo& device_info, uint32_t metadata LERROR << "Block device size must be a multiple of 512."; return false; } if (device_info_.logical_block_size % LP_SECTOR_SIZE != 0) { LERROR << "Logical block size must be a multiple of 512."; return false; } if (device_info_.alignment_offset % LP_SECTOR_SIZE != 0) { LERROR << "Alignment offset is not sector-aligned."; return false; Loading Loading @@ -244,6 +259,18 @@ bool MetadataBuilder::Init(const BlockDeviceInfo& device_info, uint32_t metadata return false; } // Finally, the size of the allocatable space must be a multiple of the // logical block size. If we have no more free space after this // computation, then we abort. Note that the last sector is inclusive, // so we have to account for that. uint64_t num_free_sectors = last_sector - first_sector + 1; uint64_t sectors_per_block = device_info_.logical_block_size / LP_SECTOR_SIZE; if (num_free_sectors < sectors_per_block) { LERROR << "Not enough space to allocate any partition tables."; return false; } last_sector = first_sector + (num_free_sectors / sectors_per_block) * sectors_per_block - 1; geometry_.first_logical_sector = first_sector; geometry_.last_logical_sector = last_sector; geometry_.metadata_max_size = metadata_max_size; Loading @@ -251,6 +278,7 @@ bool MetadataBuilder::Init(const BlockDeviceInfo& device_info, uint32_t metadata geometry_.alignment = device_info_.alignment; geometry_.alignment_offset = device_info_.alignment_offset; geometry_.block_device_size = device_info_.size; geometry_.logical_block_size = device_info.logical_block_size; return true; } Loading Loading @@ -297,87 +325,93 @@ bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t aligned_size) uint64_t end; Interval(uint64_t start, uint64_t end) : start(start), end(end) {} uint64_t length() const { return end - start; } bool operator<(const Interval& other) const { return start < other.start; } }; std::vector<Interval> intervals; // Collect all extents in the partition table. // Collect all extents in the partition table, then sort them by starting // sector. std::vector<Interval> extents; for (const auto& partition : partitions_) { for (const auto& extent : partition->extents()) { LinearExtent* linear = extent->AsLinearExtent(); if (!linear) { continue; } intervals.emplace_back(linear->physical_sector(), extents.emplace_back(linear->physical_sector(), linear->physical_sector() + extent->num_sectors()); } } std::sort(extents.begin(), extents.end()); // Convert the extent list into a list of gaps between the extents; i.e., // the list of ranges that are free on the disk. std::vector<Interval> free_regions; for (size_t i = 1; i < extents.size(); i++) { const Interval& previous = extents[i - 1]; const Interval& current = extents[i]; uint64_t aligned = AlignSector(previous.end); if (aligned >= current.start) { // There is no gap between these two extents, try the next one. // Note that we check with >= instead of >, since alignment may // bump the ending sector past the beginning of the next extent. continue; } // The new interval represents the free space starting at the end of // the previous interval, and ending at the start of the next interval. free_regions.emplace_back(aligned, current.start); } // Add a final interval representing the remainder of the free space. uint64_t last_free_extent_start = extents.empty() ? geometry_.first_logical_sector : extents.back().end; last_free_extent_start = AlignSector(last_free_extent_start); if (last_free_extent_start <= geometry_.last_logical_sector) { free_regions.emplace_back(last_free_extent_start, geometry_.last_logical_sector + 1); } // Sort extents by starting sector. std::sort(intervals.begin(), intervals.end()); const uint64_t sectors_per_block = device_info_.logical_block_size / LP_SECTOR_SIZE; CHECK(sectors_needed % sectors_per_block == 0); // Find gaps that we can use for new extents. Note we store new extents in a // temporary vector, and only commit them if we are guaranteed enough free // space. std::vector<std::unique_ptr<LinearExtent>> new_extents; for (size_t i = 1; i < intervals.size(); i++) { const Interval& previous = intervals[i - 1]; const Interval& current = intervals[i]; if (previous.end >= current.start) { // There is no gap between these two extents, try the next one. Note that // extents may never overlap, but just for safety, we ignore them if they // do. DCHECK(previous.end == current.start); for (auto& region : free_regions) { if (region.length() % sectors_per_block != 0) { // This should never happen, because it would imply that we // once allocated an extent that was not a multiple of the // block size. That extent would be rejected by DM_TABLE_LOAD. LERROR << "Region " << region.start << ".." << region.end << " is not a multiple of the block size, " << sectors_per_block; // If for some reason the final region is mis-sized we still want // to be able to grow partitions. So just to be safe, round the // region down to the nearest block. region.end = region.start + (region.length() / sectors_per_block) * sectors_per_block; if (!region.length()) { continue; } uint64_t aligned = AlignSector(previous.end); if (aligned >= current.start) { // After alignment, this extent is not usable. continue; } // This gap is enough to hold the remainder of the space requested, so we // can allocate what we need and return. if (current.start - aligned >= sectors_needed) { auto extent = std::make_unique<LinearExtent>(sectors_needed, aligned); sectors_needed -= extent->num_sectors(); uint64_t sectors = std::min(sectors_needed, region.length()); CHECK(sectors % sectors_per_block == 0); auto extent = std::make_unique<LinearExtent>(sectors, region.start); new_extents.push_back(std::move(extent)); sectors_needed -= sectors; if (!sectors_needed) { break; } // This gap is not big enough to fit the remainder of the space requested, // so consume the whole thing and keep looking for more. auto extent = std::make_unique<LinearExtent>(current.start - aligned, aligned); sectors_needed -= extent->num_sectors(); new_extents.push_back(std::move(extent)); } // If we still have more to allocate, take it from the remaining free space // in the allocatable region. if (sectors_needed) { uint64_t first_sector; if (intervals.empty()) { first_sector = geometry_.first_logical_sector; } else { first_sector = intervals.back().end; } DCHECK(first_sector <= geometry_.last_logical_sector); // Note: After alignment, |first_sector| may be > the last usable sector. first_sector = AlignSector(first_sector); // Note: the last usable sector is inclusive. if (first_sector > geometry_.last_logical_sector || geometry_.last_logical_sector + 1 - first_sector < sectors_needed) { LERROR << "Not enough free space to expand partition: " << partition->name(); return false; } auto extent = std::make_unique<LinearExtent>(sectors_needed, first_sector); new_extents.push_back(std::move(extent)); } // Everything succeeded, so commit the new extents. for (auto& extent : new_extents) { partition->AddExtent(std::move(extent)); } Loading Loading @@ -445,6 +479,12 @@ uint64_t MetadataBuilder::AlignSector(uint64_t sector) { void MetadataBuilder::set_block_device_info(const BlockDeviceInfo& device_info) { device_info_.size = device_info.size; // Note that if the logical block size changes, we're probably in trouble: // we could have already built extents that will only work on the previous // size. DCHECK(partitions_.empty() || device_info_.logical_block_size == device_info.logical_block_size); // The kernel does not guarantee these values are present, so we only // replace existing values if the new values are non-zero. if (device_info.alignment) { Loading @@ -457,7 +497,7 @@ void MetadataBuilder::set_block_device_info(const BlockDeviceInfo& device_info) bool MetadataBuilder::ResizePartition(Partition* partition, uint64_t requested_size) { // Align the space needed up to the nearest sector. uint64_t aligned_size = AlignTo(requested_size, LP_SECTOR_SIZE); uint64_t aligned_size = AlignTo(requested_size, device_info_.logical_block_size); if (aligned_size > partition->size()) { return GrowPartition(partition, aligned_size); Loading
fs_mgr/liblp/builder_test.cpp +38 −12 Original line number Diff line number Diff line Loading @@ -92,11 +92,11 @@ TEST(liblp, PartitionAlignment) { Partition* system = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_READONLY); ASSERT_NE(system, nullptr); EXPECT_EQ(builder->ResizePartition(system, 10000), true); EXPECT_EQ(system->size(), 10240); EXPECT_EQ(system->size(), 12288); EXPECT_EQ(system->extents().size(), 1); builder->ResizePartition(system, 9000); EXPECT_EQ(system->size(), 9216); builder->ResizePartition(system, 7000); EXPECT_EQ(system->size(), 8192); EXPECT_EQ(system->extents().size(), 1); } Loading @@ -120,13 +120,13 @@ TEST(liblp, MetadataAlignment) { TEST(liblp, InternalAlignment) { // Test the metadata fitting within alignment. BlockDeviceInfo device_info(1024 * 1024, 768 * 1024, 0); BlockDeviceInfo device_info(1024 * 1024, 768 * 1024, 0, 4096); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 2); ASSERT_NE(builder, nullptr); unique_ptr<LpMetadata> exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.first_logical_sector, 1536); EXPECT_EQ(exported->geometry.last_logical_sector, 2035); EXPECT_EQ(exported->geometry.last_logical_sector, 2031); // Test a large alignment offset thrown in. device_info.alignment_offset = 753664; Loading @@ -135,7 +135,7 @@ TEST(liblp, InternalAlignment) { exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.first_logical_sector, 1472); EXPECT_EQ(exported->geometry.last_logical_sector, 2035); EXPECT_EQ(exported->geometry.last_logical_sector, 2031); // Alignment offset without alignment doesn't mean anything. device_info.alignment = 0; Loading @@ -150,7 +150,7 @@ TEST(liblp, InternalAlignment) { exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.first_logical_sector, 78); EXPECT_EQ(exported->geometry.last_logical_sector, 1975); EXPECT_EQ(exported->geometry.last_logical_sector, 1973); // Test a small alignment with no alignment offset. device_info.alignment = 11 * 1024; Loading @@ -163,7 +163,7 @@ TEST(liblp, InternalAlignment) { } TEST(liblp, InternalPartitionAlignment) { BlockDeviceInfo device_info(512 * 1024 * 1024, 768 * 1024, 753664); BlockDeviceInfo device_info(512 * 1024 * 1024, 768 * 1024, 753664, 4096); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 32 * 1024, 2); Partition* a = builder->AddPartition("a", TEST_GUID, 0); Loading Loading @@ -381,7 +381,7 @@ TEST(liblp, MetadataTooLarge) { static const size_t kMetadataSize = 64 * 1024; // No space to store metadata + geometry. BlockDeviceInfo device_info(kDiskSize, 0, 0); BlockDeviceInfo device_info(kDiskSize, 0, 0, 4096); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); Loading @@ -390,8 +390,8 @@ TEST(liblp, MetadataTooLarge) { builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); // Space for metadata + geometry + one free sector. device_info.size += LP_SECTOR_SIZE; // Space for metadata + geometry + one free block. device_info.size += device_info.logical_block_size; builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_NE(builder, nullptr); Loading Loading @@ -424,19 +424,21 @@ TEST(liblp, block_device_info) { ASSERT_EQ(device_info.alignment % LP_SECTOR_SIZE, 0); ASSERT_EQ(device_info.alignment_offset % LP_SECTOR_SIZE, 0); ASSERT_LE(device_info.alignment_offset, INT_MAX); ASSERT_EQ(device_info.logical_block_size % LP_SECTOR_SIZE, 0); // Having an alignment offset > alignment doesn't really make sense. ASSERT_LT(device_info.alignment_offset, device_info.alignment); } TEST(liblp, UpdateBlockDeviceInfo) { BlockDeviceInfo device_info(1024 * 1024, 4096, 1024); BlockDeviceInfo device_info(1024 * 1024, 4096, 1024, 4096); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1); ASSERT_NE(builder, nullptr); EXPECT_EQ(builder->block_device_info().size, device_info.size); EXPECT_EQ(builder->block_device_info().alignment, device_info.alignment); EXPECT_EQ(builder->block_device_info().alignment_offset, device_info.alignment_offset); EXPECT_EQ(builder->block_device_info().logical_block_size, device_info.logical_block_size); device_info.alignment = 0; device_info.alignment_offset = 2048; Loading @@ -450,3 +452,27 @@ TEST(liblp, UpdateBlockDeviceInfo) { EXPECT_EQ(builder->block_device_info().alignment, 8192); EXPECT_EQ(builder->block_device_info().alignment_offset, 2048); } TEST(liblp, InvalidBlockSize) { BlockDeviceInfo device_info(1024 * 1024, 0, 0, 513); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1); EXPECT_EQ(builder, nullptr); } TEST(liblp, AlignedExtentSize) { BlockDeviceInfo device_info(1024 * 1024, 0, 0, 4096); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1); ASSERT_NE(builder, nullptr); Partition* partition = builder->AddPartition("system", TEST_GUID, 0); ASSERT_NE(partition, nullptr); ASSERT_TRUE(builder->ResizePartition(partition, 512)); EXPECT_EQ(partition->size(), 4096); } TEST(liblp, AlignedFreeSpace) { // Only one sector free - at least one block is required. BlockDeviceInfo device_info(10240, 0, 0, 4096); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 512, 1); ASSERT_EQ(builder, nullptr); }
fs_mgr/liblp/include/liblp/builder.h +15 −8 Original line number Diff line number Diff line Loading @@ -32,11 +32,16 @@ class LinearExtent; // By default, partitions are aligned on a 1MiB boundary. static const uint32_t kDefaultPartitionAlignment = 1024 * 1024; static const uint32_t kDefaultBlockSize = 4096; struct BlockDeviceInfo { BlockDeviceInfo() : size(0), alignment(0), alignment_offset(0) {} BlockDeviceInfo(uint64_t size, uint32_t alignment, uint32_t alignment_offset) : size(size), alignment(alignment), alignment_offset(alignment_offset) {} BlockDeviceInfo() : size(0), alignment(0), alignment_offset(0), logical_block_size(0) {} BlockDeviceInfo(uint64_t size, uint32_t alignment, uint32_t alignment_offset, uint32_t logical_block_size) : size(size), alignment(alignment), alignment_offset(alignment_offset), logical_block_size(logical_block_size) {} // Size of the block device, in bytes. uint64_t size; // Optimal target alignment, in bytes. Partition extents will be aligned to Loading @@ -46,6 +51,8 @@ struct BlockDeviceInfo { // |alignment_offset| on the target device is correctly aligned on its // parent device. This value must be 0 or a multiple of 512. uint32_t alignment_offset; // Block size, for aligning extent sizes and partition sizes. uint32_t logical_block_size; }; // Abstraction around dm-targets that can be encoded into logical partition tables. Loading Loading @@ -88,6 +95,8 @@ class ZeroExtent final : public Extent { }; class Partition final { friend class MetadataBuilder; public: Partition(const std::string& name, const std::string& guid, uint32_t attributes); Loading @@ -97,10 +106,6 @@ class Partition final { // Remove all extents from this partition. void RemoveExtents(); // Remove and/or shrink extents until the partition is the requested size. // See MetadataBuilder::ShrinkPartition for more information. void ShrinkTo(uint64_t requested_size); const std::string& name() const { return name_; } uint32_t attributes() const { return attributes_; } const std::string& guid() const { return guid_; } Loading @@ -108,6 +113,8 @@ class Partition final { uint64_t size() const { return size_; } private: void ShrinkTo(uint64_t aligned_size); std::string name_; std::string guid_; std::vector<std::unique_ptr<Extent>> extents_; Loading Loading @@ -144,7 +151,7 @@ class MetadataBuilder { // size. This is a convenience method for tests. static std::unique_ptr<MetadataBuilder> New(uint64_t blockdev_size, uint32_t metadata_max_size, uint32_t metadata_slot_count) { BlockDeviceInfo device_info(blockdev_size, 0, 0); BlockDeviceInfo device_info(blockdev_size, 0, 0, kDefaultBlockSize); return New(device_info, metadata_max_size, metadata_slot_count); } Loading
fs_mgr/liblp/include/liblp/metadata_format.h +6 −0 Original line number Diff line number Diff line Loading @@ -136,6 +136,12 @@ typedef struct LpMetadataGeometry { * can be used to verify the geometry against a target device. */ uint64_t block_device_size; /* 76: Logical block size of the super partition block device. This is the * minimal alignment for partition and extent sizes, and it must be a * multiple of LP_SECTOR_SIZE. */ uint32_t logical_block_size; } __attribute__((packed)) LpMetadataGeometry; /* The logical partition metadata has a number of tables; they are described Loading
fs_mgr/liblp/utility_test.cpp +2 −1 Original line number Diff line number Diff line Loading @@ -40,7 +40,8 @@ TEST(liblp, GetMetadataOffset) { 80000, 0, 0, 1024 * 1024}; 1024 * 1024, 4096}; EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 0), 4096); EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 1), 4096 + 16384); EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 2), 4096 + 16384 * 2); Loading