Loading fs_mgr/liblp/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -52,6 +52,7 @@ cc_test { "libcrypto", "libcrypto_utils", "liblp", "libfs_mgr", ], srcs: [ "builder_test.cpp", Loading fs_mgr/liblp/builder.cpp +129 −22 Original line number Diff line number Diff line Loading @@ -16,22 +16,47 @@ #include "liblp/builder.h" #if defined(__linux__) #include <linux/fs.h> #endif #include <string.h> #include <sys/ioctl.h> #include <algorithm> #include <android-base/unique_fd.h> #include <uuid/uuid.h> #include "liblp/metadata_format.h" #include "liblp/reader.h" #include "utility.h" namespace android { namespace fs_mgr { // Align a byte count up to the nearest 512-byte sector. template <typename T> static inline T AlignToSector(T value) { return (value + (LP_SECTOR_SIZE - 1)) & ~T(LP_SECTOR_SIZE - 1); bool GetBlockDeviceInfo(const std::string& block_device, BlockDeviceInfo* device_info) { #if defined(__linux__) android::base::unique_fd fd(open(block_device.c_str(), O_RDONLY)); if (fd < 0) { PERROR << __PRETTY_FUNCTION__ << "open '" << block_device << "' failed"; return false; } if (!GetDescriptorSize(fd, &device_info->size)) { return false; } if (ioctl(fd, BLKIOMIN, &device_info->alignment) < 0) { PERROR << __PRETTY_FUNCTION__ << "BLKIOMIN failed"; return false; } if (ioctl(fd, BLKALIGNOFF, &device_info->alignment_offset) < 0) { PERROR << __PRETTY_FUNCTION__ << "BLKIOMIN failed"; return false; } return true; #else LERROR << __PRETTY_FUNCTION__ << ": Not supported on this operating system."; return false; #endif } void LinearExtent::AddTo(LpMetadata* out) const { Loading @@ -56,7 +81,7 @@ void Partition::RemoveExtents() { } void Partition::ShrinkTo(uint64_t requested_size) { uint64_t aligned_size = AlignToSector(requested_size); uint64_t aligned_size = AlignTo(requested_size, LP_SECTOR_SIZE); if (size_ <= aligned_size) { return; } Loading @@ -82,11 +107,28 @@ void Partition::ShrinkTo(uint64_t requested_size) { DCHECK(size_ == requested_size); } std::unique_ptr<MetadataBuilder> MetadataBuilder::New(uint64_t blockdevice_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); if (!metadata) { return nullptr; } std::unique_ptr<MetadataBuilder> builder = New(*metadata.get()); if (!builder) { return nullptr; } BlockDeviceInfo device_info; if (fs_mgr::GetBlockDeviceInfo(block_device, &device_info)) { builder->set_block_device_info(device_info); } return builder; } std::unique_ptr<MetadataBuilder> MetadataBuilder::New(const BlockDeviceInfo& device_info, uint32_t metadata_max_size, uint32_t metadata_slot_count) { std::unique_ptr<MetadataBuilder> builder(new MetadataBuilder()); if (!builder->Init(blockdevice_size, metadata_max_size, metadata_slot_count)) { if (!builder->Init(device_info, metadata_max_size, metadata_slot_count)) { return nullptr; } return builder; Loading Loading @@ -135,10 +177,13 @@ bool MetadataBuilder::Init(const LpMetadata& metadata) { } } } device_info_.alignment = geometry_.alignment; device_info_.alignment_offset = geometry_.alignment_offset; return true; } bool MetadataBuilder::Init(uint64_t blockdevice_size, uint32_t metadata_max_size, bool MetadataBuilder::Init(const BlockDeviceInfo& device_info, uint32_t metadata_max_size, uint32_t metadata_slot_count) { if (metadata_max_size < sizeof(LpMetadataHeader)) { LERROR << "Invalid metadata maximum size."; Loading @@ -150,7 +195,22 @@ bool MetadataBuilder::Init(uint64_t blockdevice_size, uint32_t metadata_max_size } // Align the metadata size up to the nearest sector. metadata_max_size = AlignToSector(metadata_max_size); metadata_max_size = AlignTo(metadata_max_size, LP_SECTOR_SIZE); // Check that device properties are sane. if (device_info_.alignment_offset % LP_SECTOR_SIZE != 0) { LERROR << "Alignment offset is not sector-aligned."; return false; } if (device_info_.alignment % LP_SECTOR_SIZE != 0) { LERROR << "Partition alignment is not sector-aligned."; return false; } if (device_info_.alignment_offset > device_info_.alignment) { LERROR << "Partition alignment offset is greater than its alignment."; return false; } device_info_ = device_info; // We reserve a geometry block (4KB) plus space for each copy of the // maximum size of a metadata blob. Then, we double that space since Loading @@ -158,20 +218,36 @@ bool MetadataBuilder::Init(uint64_t blockdevice_size, uint32_t metadata_max_size uint64_t reserved = LP_METADATA_GEOMETRY_SIZE + (uint64_t(metadata_max_size) * metadata_slot_count); uint64_t total_reserved = reserved * 2; if (blockdevice_size < total_reserved || blockdevice_size - total_reserved < LP_SECTOR_SIZE) { if (device_info_.size < total_reserved) { LERROR << "Attempting to create metadata on a block device that is too small."; return false; } // The last sector is inclusive. We subtract one to make sure that logical // partitions won't overlap with the same sector as the backup metadata, // which could happen if the block device was not aligned to LP_SECTOR_SIZE. geometry_.first_logical_sector = reserved / LP_SECTOR_SIZE; geometry_.last_logical_sector = ((blockdevice_size - reserved) / LP_SECTOR_SIZE) - 1; // Compute the first free sector, factoring in alignment. uint64_t free_area = AlignTo(reserved, device_info_.alignment, device_info_.alignment_offset); uint64_t first_sector = free_area / LP_SECTOR_SIZE; // Compute the last free sector, which is inclusive. We subtract 1 to make // sure that logical partitions won't overlap with the same sector as the // backup metadata, which could happen if the block device was not aligned // to LP_SECTOR_SIZE. uint64_t last_sector = ((device_info_.size - reserved) / LP_SECTOR_SIZE) - 1; // If this check fails, it means either (1) we did not have free space to // allocate a single sector, or (2) we did, but the alignment was high // enough to bump the first sector out of range. Either way, we cannot // continue. if (first_sector > last_sector) { LERROR << "Not enough space to allocate any partition tables."; return false; } geometry_.first_logical_sector = first_sector; geometry_.last_logical_sector = last_sector; geometry_.metadata_max_size = metadata_max_size; geometry_.metadata_slot_count = metadata_slot_count; DCHECK(geometry_.last_logical_sector >= geometry_.first_logical_sector); geometry_.alignment = device_info_.alignment; geometry_.alignment_offset = device_info_.alignment_offset; return true; } Loading Loading @@ -209,7 +285,7 @@ void MetadataBuilder::RemovePartition(const std::string& name) { bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t requested_size) { // Align the space needed up to the nearest sector. uint64_t aligned_size = AlignToSector(requested_size); uint64_t aligned_size = AlignTo(requested_size, LP_SECTOR_SIZE); if (partition->size() >= aligned_size) { return true; } Loading Loading @@ -259,10 +335,16 @@ bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t requested_siz 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 - previous.end >= sectors_needed) { auto extent = std::make_unique<LinearExtent>(sectors_needed, previous.end); if (current.start - aligned >= sectors_needed) { auto extent = std::make_unique<LinearExtent>(sectors_needed, aligned); sectors_needed -= extent->num_sectors(); new_extents.push_back(std::move(extent)); break; Loading @@ -270,7 +352,7 @@ bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t requested_siz // 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 - previous.end, previous.end); auto extent = std::make_unique<LinearExtent>(current.start - aligned, aligned); sectors_needed -= extent->num_sectors(); new_extents.push_back(std::move(extent)); } Loading @@ -286,8 +368,12 @@ bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t requested_siz } 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 (geometry_.last_logical_sector + 1 - first_sector < sectors_needed) { 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; } Loading Loading @@ -351,5 +437,26 @@ uint64_t MetadataBuilder::AllocatableSpace() const { return (geometry_.last_logical_sector - geometry_.first_logical_sector + 1) * LP_SECTOR_SIZE; } uint64_t MetadataBuilder::AlignSector(uint64_t sector) { // Note: when reading alignment info from the Kernel, we don't assume it // is aligned to the sector size, so we round up to the nearest sector. uint64_t lba = sector * LP_SECTOR_SIZE; uint64_t aligned = AlignTo(lba, device_info_.alignment, device_info_.alignment_offset); return AlignTo(aligned, LP_SECTOR_SIZE) / LP_SECTOR_SIZE; } void MetadataBuilder::set_block_device_info(const BlockDeviceInfo& device_info) { device_info_.size = device_info.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) { device_info_.alignment = device_info.alignment; } if (device_info.alignment_offset) { device_info_.alignment_offset = device_info.alignment_offset; } } } // namespace fs_mgr } // namespace android fs_mgr/liblp/builder_test.cpp +146 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ #include <gtest/gtest.h> #include <liblp/builder.h> #include "fs_mgr.h" #include "utility.h" using namespace std; using namespace android::fs_mgr; Loading Loading @@ -127,6 +129,89 @@ TEST(liblp, MetadataAlignment) { EXPECT_EQ(exported->geometry.metadata_max_size, 1024); } TEST(liblp, InternalAlignment) { // Test the metadata fitting within alignment. BlockDeviceInfo device_info(1024 * 1024, 768 * 1024, 0); 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); // Test a large alignment offset thrown in. device_info.alignment_offset = 753664; builder = MetadataBuilder::New(device_info, 1024, 2); ASSERT_NE(builder, nullptr); exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.first_logical_sector, 1472); EXPECT_EQ(exported->geometry.last_logical_sector, 2035); // Test only an alignment offset (which should simply bump up the first // logical sector). device_info.alignment = 0; builder = MetadataBuilder::New(device_info, 1024, 2); ASSERT_NE(builder, nullptr); exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.first_logical_sector, 1484); EXPECT_EQ(exported->geometry.last_logical_sector, 2035); // Test a small alignment with an alignment offset. device_info.alignment = 12 * 1024; device_info.alignment_offset = 3 * 1024; builder = MetadataBuilder::New(device_info, 16 * 1024, 2); ASSERT_NE(builder, nullptr); exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.first_logical_sector, 78); EXPECT_EQ(exported->geometry.last_logical_sector, 1975); // Test a small alignment with no alignment offset. device_info.alignment = 11 * 1024; builder = MetadataBuilder::New(device_info, 16 * 1024, 2); ASSERT_NE(builder, nullptr); exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.first_logical_sector, 72); EXPECT_EQ(exported->geometry.last_logical_sector, 1975); } TEST(liblp, InternalPartitionAlignment) { BlockDeviceInfo device_info(512 * 1024 * 1024, 768 * 1024, 753664); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 32 * 1024, 2); Partition* a = builder->AddPartition("a", TEST_GUID, 0); ASSERT_NE(a, nullptr); Partition* b = builder->AddPartition("b", TEST_GUID2, 0); ASSERT_NE(b, nullptr); // Add a bunch of small extents to each, interleaving. for (size_t i = 0; i < 10; i++) { ASSERT_TRUE(builder->GrowPartition(a, a->size() + 4096)); ASSERT_TRUE(builder->GrowPartition(b, b->size() + 4096)); } EXPECT_EQ(a->size(), 40960); EXPECT_EQ(b->size(), 40960); unique_ptr<LpMetadata> exported = builder->Export(); ASSERT_NE(exported, nullptr); // Check that each starting sector is aligned. for (const auto& extent : exported->extents) { ASSERT_EQ(extent.target_type, LP_TARGET_TYPE_LINEAR); EXPECT_EQ(extent.num_sectors, 8); uint64_t lba = extent.target_data * LP_SECTOR_SIZE; uint64_t aligned_lba = AlignTo(lba, device_info.alignment, device_info.alignment_offset); EXPECT_EQ(lba, aligned_lba); } // Sanity check one extent. EXPECT_EQ(exported->extents.back().target_data, 30656); } TEST(liblp, UseAllDiskSpace) { unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2); EXPECT_EQ(builder->AllocatableSpace(), 1036288); Loading Loading @@ -312,15 +397,72 @@ TEST(liblp, MetadataTooLarge) { static const size_t kMetadataSize = 64 * 1024; // No space to store metadata + geometry. unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize, kMetadataSize, 1); BlockDeviceInfo device_info(kDiskSize, 0, 0); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); // No space to store metadata + geometry + one free sector. builder = MetadataBuilder::New(kDiskSize + LP_METADATA_GEOMETRY_SIZE * 2, kMetadataSize, 1); device_info.size += LP_METADATA_GEOMETRY_SIZE * 2; builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); // Space for metadata + geometry + one free sector. builder = MetadataBuilder::New(kDiskSize + LP_METADATA_GEOMETRY_SIZE * 2 + LP_SECTOR_SIZE, kMetadataSize, 1); device_info.size += LP_SECTOR_SIZE; builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_NE(builder, nullptr); // Test with alignment. device_info.alignment = 131072; builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); device_info.alignment = 0; device_info.alignment_offset = 32768 - LP_SECTOR_SIZE; builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); } TEST(liblp, block_device_info) { std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(), fs_mgr_free_fstab); ASSERT_NE(fstab, nullptr); // This should read from the "super" partition once we have a well-defined // way to access it. struct fstab_rec* rec = fs_mgr_get_entry_for_mount_point(fstab.get(), "/data"); ASSERT_NE(rec, nullptr); BlockDeviceInfo device_info; ASSERT_TRUE(GetBlockDeviceInfo(rec->blk_device, &device_info)); // Sanity check that the device doesn't give us some weird inefficient // alignment. 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); // 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); 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); device_info.alignment = 0; device_info.alignment_offset = 2048; builder->set_block_device_info(device_info); EXPECT_EQ(builder->block_device_info().alignment, 4096); EXPECT_EQ(builder->block_device_info().alignment_offset, device_info.alignment_offset); device_info.alignment = 8192; device_info.alignment_offset = 0; builder->set_block_device_info(device_info); EXPECT_EQ(builder->block_device_info().alignment, 8192); EXPECT_EQ(builder->block_device_info().alignment_offset, 2048); } fs_mgr/liblp/include/liblp/builder.h +47 −2 Original line number Diff line number Diff line Loading @@ -30,6 +30,24 @@ namespace fs_mgr { class LinearExtent; // By default, partitions are aligned on a 1MiB boundary. static const uint32_t kDefaultPartitionAlignment = 1024 * 1024; 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) {} // Size of the block device, in bytes. uint64_t size; // Optimal target alignment, in bytes. Partition extents will be aligned to // this value by default. This value must be 0 or a multiple of 512. uint32_t alignment; // Alignment offset to parent device (if any), in bytes. The sector at // |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; }; // Abstraction around dm-targets that can be encoded into logical partition tables. class Extent { public: Loading Loading @@ -107,14 +125,29 @@ class MetadataBuilder { // If the parameters would yield invalid metadata, nullptr is returned. This // could happen if the block device size is too small to store the metadata // and backup copies. static std::unique_ptr<MetadataBuilder> New(uint64_t blockdevice_size, static std::unique_ptr<MetadataBuilder> New(const BlockDeviceInfo& device_info, uint32_t metadata_max_size, uint32_t metadata_slot_count); // Import an existing table for modification. This reads metadata off the // given block device and imports it. It also adjusts alignment information // based on run-time values in the operating system. static std::unique_ptr<MetadataBuilder> New(const std::string& block_device, uint32_t slot_number); // Import an existing table for modification. If the table is not valid, for // example it contains duplicate partition names, then nullptr is returned. // This method is for testing or changing off-line tables. static std::unique_ptr<MetadataBuilder> New(const LpMetadata& metadata); // Wrapper around New() with a BlockDeviceInfo that only specifies a device // 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); return New(device_info, metadata_max_size, metadata_slot_count); } // Export metadata so it can be serialized to an image, to disk, or mounted // via device-mapper. std::unique_ptr<LpMetadata> Export(); Loading Loading @@ -156,16 +189,28 @@ class MetadataBuilder { // Amount of space that can be allocated to logical partitions. uint64_t AllocatableSpace() const; // Merge new block device information into previous values. Alignment values // are only overwritten if the new values are non-zero. void set_block_device_info(const BlockDeviceInfo& device_info); const BlockDeviceInfo& block_device_info() const { return device_info_; } private: MetadataBuilder(); bool Init(uint64_t blockdevice_size, uint32_t metadata_max_size, uint32_t metadata_slot_count); bool Init(const BlockDeviceInfo& info, uint32_t metadata_max_size, uint32_t metadata_slot_count); bool Init(const LpMetadata& metadata); uint64_t AlignSector(uint64_t sector); LpMetadataGeometry geometry_; LpMetadataHeader header_; std::vector<std::unique_ptr<Partition>> partitions_; BlockDeviceInfo device_info_; }; // Read BlockDeviceInfo for a given block device. This always returns false // for non-Linux operating systems. bool GetBlockDeviceInfo(const std::string& block_device, BlockDeviceInfo* device_info); } // namespace fs_mgr } // namespace android Loading fs_mgr/liblp/include/liblp/metadata_format.h +22 −0 Original line number Diff line number Diff line Loading @@ -107,6 +107,28 @@ typedef struct LpMetadataGeometry { * backup geometry block at the very end. */ uint64_t last_logical_sector; /* 64: Alignment for defining partitions or partition extents. For example, * an alignment of 1MiB will require that all partitions have a size evenly * divisible by 1MiB, and that the smallest unit the partition can grow by * is 1MiB. * * Alignment is normally determined at runtime when growing or adding * partitions. If for some reason the alignment cannot be determined, then * this predefined alignment in the geometry is used instead. By default * it is set to 1MiB. */ uint32_t alignment; /* 68: Alignment offset for "stacked" devices. For example, if the "super" * partition itself is not aligned within the parent block device's * partition table, then we adjust for this in deciding where to place * |first_logical_sector|. * * Similar to |alignment|, this will be derived from the operating system. * If it cannot be determined, it is assumed to be 0. */ uint32_t alignment_offset; } __attribute__((packed)) LpMetadataGeometry; /* The logical partition metadata has a number of tables; they are described Loading Loading
fs_mgr/liblp/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -52,6 +52,7 @@ cc_test { "libcrypto", "libcrypto_utils", "liblp", "libfs_mgr", ], srcs: [ "builder_test.cpp", Loading
fs_mgr/liblp/builder.cpp +129 −22 Original line number Diff line number Diff line Loading @@ -16,22 +16,47 @@ #include "liblp/builder.h" #if defined(__linux__) #include <linux/fs.h> #endif #include <string.h> #include <sys/ioctl.h> #include <algorithm> #include <android-base/unique_fd.h> #include <uuid/uuid.h> #include "liblp/metadata_format.h" #include "liblp/reader.h" #include "utility.h" namespace android { namespace fs_mgr { // Align a byte count up to the nearest 512-byte sector. template <typename T> static inline T AlignToSector(T value) { return (value + (LP_SECTOR_SIZE - 1)) & ~T(LP_SECTOR_SIZE - 1); bool GetBlockDeviceInfo(const std::string& block_device, BlockDeviceInfo* device_info) { #if defined(__linux__) android::base::unique_fd fd(open(block_device.c_str(), O_RDONLY)); if (fd < 0) { PERROR << __PRETTY_FUNCTION__ << "open '" << block_device << "' failed"; return false; } if (!GetDescriptorSize(fd, &device_info->size)) { return false; } if (ioctl(fd, BLKIOMIN, &device_info->alignment) < 0) { PERROR << __PRETTY_FUNCTION__ << "BLKIOMIN failed"; return false; } if (ioctl(fd, BLKALIGNOFF, &device_info->alignment_offset) < 0) { PERROR << __PRETTY_FUNCTION__ << "BLKIOMIN failed"; return false; } return true; #else LERROR << __PRETTY_FUNCTION__ << ": Not supported on this operating system."; return false; #endif } void LinearExtent::AddTo(LpMetadata* out) const { Loading @@ -56,7 +81,7 @@ void Partition::RemoveExtents() { } void Partition::ShrinkTo(uint64_t requested_size) { uint64_t aligned_size = AlignToSector(requested_size); uint64_t aligned_size = AlignTo(requested_size, LP_SECTOR_SIZE); if (size_ <= aligned_size) { return; } Loading @@ -82,11 +107,28 @@ void Partition::ShrinkTo(uint64_t requested_size) { DCHECK(size_ == requested_size); } std::unique_ptr<MetadataBuilder> MetadataBuilder::New(uint64_t blockdevice_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); if (!metadata) { return nullptr; } std::unique_ptr<MetadataBuilder> builder = New(*metadata.get()); if (!builder) { return nullptr; } BlockDeviceInfo device_info; if (fs_mgr::GetBlockDeviceInfo(block_device, &device_info)) { builder->set_block_device_info(device_info); } return builder; } std::unique_ptr<MetadataBuilder> MetadataBuilder::New(const BlockDeviceInfo& device_info, uint32_t metadata_max_size, uint32_t metadata_slot_count) { std::unique_ptr<MetadataBuilder> builder(new MetadataBuilder()); if (!builder->Init(blockdevice_size, metadata_max_size, metadata_slot_count)) { if (!builder->Init(device_info, metadata_max_size, metadata_slot_count)) { return nullptr; } return builder; Loading Loading @@ -135,10 +177,13 @@ bool MetadataBuilder::Init(const LpMetadata& metadata) { } } } device_info_.alignment = geometry_.alignment; device_info_.alignment_offset = geometry_.alignment_offset; return true; } bool MetadataBuilder::Init(uint64_t blockdevice_size, uint32_t metadata_max_size, bool MetadataBuilder::Init(const BlockDeviceInfo& device_info, uint32_t metadata_max_size, uint32_t metadata_slot_count) { if (metadata_max_size < sizeof(LpMetadataHeader)) { LERROR << "Invalid metadata maximum size."; Loading @@ -150,7 +195,22 @@ bool MetadataBuilder::Init(uint64_t blockdevice_size, uint32_t metadata_max_size } // Align the metadata size up to the nearest sector. metadata_max_size = AlignToSector(metadata_max_size); metadata_max_size = AlignTo(metadata_max_size, LP_SECTOR_SIZE); // Check that device properties are sane. if (device_info_.alignment_offset % LP_SECTOR_SIZE != 0) { LERROR << "Alignment offset is not sector-aligned."; return false; } if (device_info_.alignment % LP_SECTOR_SIZE != 0) { LERROR << "Partition alignment is not sector-aligned."; return false; } if (device_info_.alignment_offset > device_info_.alignment) { LERROR << "Partition alignment offset is greater than its alignment."; return false; } device_info_ = device_info; // We reserve a geometry block (4KB) plus space for each copy of the // maximum size of a metadata blob. Then, we double that space since Loading @@ -158,20 +218,36 @@ bool MetadataBuilder::Init(uint64_t blockdevice_size, uint32_t metadata_max_size uint64_t reserved = LP_METADATA_GEOMETRY_SIZE + (uint64_t(metadata_max_size) * metadata_slot_count); uint64_t total_reserved = reserved * 2; if (blockdevice_size < total_reserved || blockdevice_size - total_reserved < LP_SECTOR_SIZE) { if (device_info_.size < total_reserved) { LERROR << "Attempting to create metadata on a block device that is too small."; return false; } // The last sector is inclusive. We subtract one to make sure that logical // partitions won't overlap with the same sector as the backup metadata, // which could happen if the block device was not aligned to LP_SECTOR_SIZE. geometry_.first_logical_sector = reserved / LP_SECTOR_SIZE; geometry_.last_logical_sector = ((blockdevice_size - reserved) / LP_SECTOR_SIZE) - 1; // Compute the first free sector, factoring in alignment. uint64_t free_area = AlignTo(reserved, device_info_.alignment, device_info_.alignment_offset); uint64_t first_sector = free_area / LP_SECTOR_SIZE; // Compute the last free sector, which is inclusive. We subtract 1 to make // sure that logical partitions won't overlap with the same sector as the // backup metadata, which could happen if the block device was not aligned // to LP_SECTOR_SIZE. uint64_t last_sector = ((device_info_.size - reserved) / LP_SECTOR_SIZE) - 1; // If this check fails, it means either (1) we did not have free space to // allocate a single sector, or (2) we did, but the alignment was high // enough to bump the first sector out of range. Either way, we cannot // continue. if (first_sector > last_sector) { LERROR << "Not enough space to allocate any partition tables."; return false; } geometry_.first_logical_sector = first_sector; geometry_.last_logical_sector = last_sector; geometry_.metadata_max_size = metadata_max_size; geometry_.metadata_slot_count = metadata_slot_count; DCHECK(geometry_.last_logical_sector >= geometry_.first_logical_sector); geometry_.alignment = device_info_.alignment; geometry_.alignment_offset = device_info_.alignment_offset; return true; } Loading Loading @@ -209,7 +285,7 @@ void MetadataBuilder::RemovePartition(const std::string& name) { bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t requested_size) { // Align the space needed up to the nearest sector. uint64_t aligned_size = AlignToSector(requested_size); uint64_t aligned_size = AlignTo(requested_size, LP_SECTOR_SIZE); if (partition->size() >= aligned_size) { return true; } Loading Loading @@ -259,10 +335,16 @@ bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t requested_siz 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 - previous.end >= sectors_needed) { auto extent = std::make_unique<LinearExtent>(sectors_needed, previous.end); if (current.start - aligned >= sectors_needed) { auto extent = std::make_unique<LinearExtent>(sectors_needed, aligned); sectors_needed -= extent->num_sectors(); new_extents.push_back(std::move(extent)); break; Loading @@ -270,7 +352,7 @@ bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t requested_siz // 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 - previous.end, previous.end); auto extent = std::make_unique<LinearExtent>(current.start - aligned, aligned); sectors_needed -= extent->num_sectors(); new_extents.push_back(std::move(extent)); } Loading @@ -286,8 +368,12 @@ bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t requested_siz } 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 (geometry_.last_logical_sector + 1 - first_sector < sectors_needed) { 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; } Loading Loading @@ -351,5 +437,26 @@ uint64_t MetadataBuilder::AllocatableSpace() const { return (geometry_.last_logical_sector - geometry_.first_logical_sector + 1) * LP_SECTOR_SIZE; } uint64_t MetadataBuilder::AlignSector(uint64_t sector) { // Note: when reading alignment info from the Kernel, we don't assume it // is aligned to the sector size, so we round up to the nearest sector. uint64_t lba = sector * LP_SECTOR_SIZE; uint64_t aligned = AlignTo(lba, device_info_.alignment, device_info_.alignment_offset); return AlignTo(aligned, LP_SECTOR_SIZE) / LP_SECTOR_SIZE; } void MetadataBuilder::set_block_device_info(const BlockDeviceInfo& device_info) { device_info_.size = device_info.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) { device_info_.alignment = device_info.alignment; } if (device_info.alignment_offset) { device_info_.alignment_offset = device_info.alignment_offset; } } } // namespace fs_mgr } // namespace android
fs_mgr/liblp/builder_test.cpp +146 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ #include <gtest/gtest.h> #include <liblp/builder.h> #include "fs_mgr.h" #include "utility.h" using namespace std; using namespace android::fs_mgr; Loading Loading @@ -127,6 +129,89 @@ TEST(liblp, MetadataAlignment) { EXPECT_EQ(exported->geometry.metadata_max_size, 1024); } TEST(liblp, InternalAlignment) { // Test the metadata fitting within alignment. BlockDeviceInfo device_info(1024 * 1024, 768 * 1024, 0); 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); // Test a large alignment offset thrown in. device_info.alignment_offset = 753664; builder = MetadataBuilder::New(device_info, 1024, 2); ASSERT_NE(builder, nullptr); exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.first_logical_sector, 1472); EXPECT_EQ(exported->geometry.last_logical_sector, 2035); // Test only an alignment offset (which should simply bump up the first // logical sector). device_info.alignment = 0; builder = MetadataBuilder::New(device_info, 1024, 2); ASSERT_NE(builder, nullptr); exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.first_logical_sector, 1484); EXPECT_EQ(exported->geometry.last_logical_sector, 2035); // Test a small alignment with an alignment offset. device_info.alignment = 12 * 1024; device_info.alignment_offset = 3 * 1024; builder = MetadataBuilder::New(device_info, 16 * 1024, 2); ASSERT_NE(builder, nullptr); exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.first_logical_sector, 78); EXPECT_EQ(exported->geometry.last_logical_sector, 1975); // Test a small alignment with no alignment offset. device_info.alignment = 11 * 1024; builder = MetadataBuilder::New(device_info, 16 * 1024, 2); ASSERT_NE(builder, nullptr); exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.first_logical_sector, 72); EXPECT_EQ(exported->geometry.last_logical_sector, 1975); } TEST(liblp, InternalPartitionAlignment) { BlockDeviceInfo device_info(512 * 1024 * 1024, 768 * 1024, 753664); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 32 * 1024, 2); Partition* a = builder->AddPartition("a", TEST_GUID, 0); ASSERT_NE(a, nullptr); Partition* b = builder->AddPartition("b", TEST_GUID2, 0); ASSERT_NE(b, nullptr); // Add a bunch of small extents to each, interleaving. for (size_t i = 0; i < 10; i++) { ASSERT_TRUE(builder->GrowPartition(a, a->size() + 4096)); ASSERT_TRUE(builder->GrowPartition(b, b->size() + 4096)); } EXPECT_EQ(a->size(), 40960); EXPECT_EQ(b->size(), 40960); unique_ptr<LpMetadata> exported = builder->Export(); ASSERT_NE(exported, nullptr); // Check that each starting sector is aligned. for (const auto& extent : exported->extents) { ASSERT_EQ(extent.target_type, LP_TARGET_TYPE_LINEAR); EXPECT_EQ(extent.num_sectors, 8); uint64_t lba = extent.target_data * LP_SECTOR_SIZE; uint64_t aligned_lba = AlignTo(lba, device_info.alignment, device_info.alignment_offset); EXPECT_EQ(lba, aligned_lba); } // Sanity check one extent. EXPECT_EQ(exported->extents.back().target_data, 30656); } TEST(liblp, UseAllDiskSpace) { unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2); EXPECT_EQ(builder->AllocatableSpace(), 1036288); Loading Loading @@ -312,15 +397,72 @@ TEST(liblp, MetadataTooLarge) { static const size_t kMetadataSize = 64 * 1024; // No space to store metadata + geometry. unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize, kMetadataSize, 1); BlockDeviceInfo device_info(kDiskSize, 0, 0); unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); // No space to store metadata + geometry + one free sector. builder = MetadataBuilder::New(kDiskSize + LP_METADATA_GEOMETRY_SIZE * 2, kMetadataSize, 1); device_info.size += LP_METADATA_GEOMETRY_SIZE * 2; builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); // Space for metadata + geometry + one free sector. builder = MetadataBuilder::New(kDiskSize + LP_METADATA_GEOMETRY_SIZE * 2 + LP_SECTOR_SIZE, kMetadataSize, 1); device_info.size += LP_SECTOR_SIZE; builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_NE(builder, nullptr); // Test with alignment. device_info.alignment = 131072; builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); device_info.alignment = 0; device_info.alignment_offset = 32768 - LP_SECTOR_SIZE; builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); } TEST(liblp, block_device_info) { std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(), fs_mgr_free_fstab); ASSERT_NE(fstab, nullptr); // This should read from the "super" partition once we have a well-defined // way to access it. struct fstab_rec* rec = fs_mgr_get_entry_for_mount_point(fstab.get(), "/data"); ASSERT_NE(rec, nullptr); BlockDeviceInfo device_info; ASSERT_TRUE(GetBlockDeviceInfo(rec->blk_device, &device_info)); // Sanity check that the device doesn't give us some weird inefficient // alignment. 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); // 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); 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); device_info.alignment = 0; device_info.alignment_offset = 2048; builder->set_block_device_info(device_info); EXPECT_EQ(builder->block_device_info().alignment, 4096); EXPECT_EQ(builder->block_device_info().alignment_offset, device_info.alignment_offset); device_info.alignment = 8192; device_info.alignment_offset = 0; builder->set_block_device_info(device_info); EXPECT_EQ(builder->block_device_info().alignment, 8192); EXPECT_EQ(builder->block_device_info().alignment_offset, 2048); }
fs_mgr/liblp/include/liblp/builder.h +47 −2 Original line number Diff line number Diff line Loading @@ -30,6 +30,24 @@ namespace fs_mgr { class LinearExtent; // By default, partitions are aligned on a 1MiB boundary. static const uint32_t kDefaultPartitionAlignment = 1024 * 1024; 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) {} // Size of the block device, in bytes. uint64_t size; // Optimal target alignment, in bytes. Partition extents will be aligned to // this value by default. This value must be 0 or a multiple of 512. uint32_t alignment; // Alignment offset to parent device (if any), in bytes. The sector at // |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; }; // Abstraction around dm-targets that can be encoded into logical partition tables. class Extent { public: Loading Loading @@ -107,14 +125,29 @@ class MetadataBuilder { // If the parameters would yield invalid metadata, nullptr is returned. This // could happen if the block device size is too small to store the metadata // and backup copies. static std::unique_ptr<MetadataBuilder> New(uint64_t blockdevice_size, static std::unique_ptr<MetadataBuilder> New(const BlockDeviceInfo& device_info, uint32_t metadata_max_size, uint32_t metadata_slot_count); // Import an existing table for modification. This reads metadata off the // given block device and imports it. It also adjusts alignment information // based on run-time values in the operating system. static std::unique_ptr<MetadataBuilder> New(const std::string& block_device, uint32_t slot_number); // Import an existing table for modification. If the table is not valid, for // example it contains duplicate partition names, then nullptr is returned. // This method is for testing or changing off-line tables. static std::unique_ptr<MetadataBuilder> New(const LpMetadata& metadata); // Wrapper around New() with a BlockDeviceInfo that only specifies a device // 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); return New(device_info, metadata_max_size, metadata_slot_count); } // Export metadata so it can be serialized to an image, to disk, or mounted // via device-mapper. std::unique_ptr<LpMetadata> Export(); Loading Loading @@ -156,16 +189,28 @@ class MetadataBuilder { // Amount of space that can be allocated to logical partitions. uint64_t AllocatableSpace() const; // Merge new block device information into previous values. Alignment values // are only overwritten if the new values are non-zero. void set_block_device_info(const BlockDeviceInfo& device_info); const BlockDeviceInfo& block_device_info() const { return device_info_; } private: MetadataBuilder(); bool Init(uint64_t blockdevice_size, uint32_t metadata_max_size, uint32_t metadata_slot_count); bool Init(const BlockDeviceInfo& info, uint32_t metadata_max_size, uint32_t metadata_slot_count); bool Init(const LpMetadata& metadata); uint64_t AlignSector(uint64_t sector); LpMetadataGeometry geometry_; LpMetadataHeader header_; std::vector<std::unique_ptr<Partition>> partitions_; BlockDeviceInfo device_info_; }; // Read BlockDeviceInfo for a given block device. This always returns false // for non-Linux operating systems. bool GetBlockDeviceInfo(const std::string& block_device, BlockDeviceInfo* device_info); } // namespace fs_mgr } // namespace android Loading
fs_mgr/liblp/include/liblp/metadata_format.h +22 −0 Original line number Diff line number Diff line Loading @@ -107,6 +107,28 @@ typedef struct LpMetadataGeometry { * backup geometry block at the very end. */ uint64_t last_logical_sector; /* 64: Alignment for defining partitions or partition extents. For example, * an alignment of 1MiB will require that all partitions have a size evenly * divisible by 1MiB, and that the smallest unit the partition can grow by * is 1MiB. * * Alignment is normally determined at runtime when growing or adding * partitions. If for some reason the alignment cannot be determined, then * this predefined alignment in the geometry is used instead. By default * it is set to 1MiB. */ uint32_t alignment; /* 68: Alignment offset for "stacked" devices. For example, if the "super" * partition itself is not aligned within the parent block device's * partition table, then we adjust for this in deciding where to place * |first_logical_sector|. * * Similar to |alignment|, this will be derived from the operating system. * If it cannot be determined, it is assumed to be 0. */ uint32_t alignment_offset; } __attribute__((packed)) LpMetadataGeometry; /* The logical partition metadata has a number of tables; they are described Loading