From 2332afb31fabd7b5fba7bfbc3f7073e02bbfb009 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 9 Jul 2018 12:12:52 -0700 Subject: [PATCH] liblp: Implement support for request queue alignment. Block devices in the Linux kernel have a "minimum I/O request" size. The minimum size is usually acquired by the block driver and can change from device to device. When stacking devices (such as with device-mapper), the kernel goes through great lengths to make sure this alignment is respected for optimal I/O. In device-mapper's case, misalignment can lead to kernel warnings and performance issues. While this is unlikely to matter with a few targets, it could become problematic on a large number of targets, and so we would prefer to align all partition extents to the minimum I/O size. We now support two new properties in the partition table geometry: an "alignment", which is the minimum I/O size, and an "alignment offset", which is an offset that when applied to sector 0, causes the sector to be properly aligned within its parent device (for example, if a physical partition is misaligned). All partition extents now begin on a sector that respects this alignment. One major caveat is that it is difficult for the initial partition table to have the correct alignment without build system and/or flash tool support. To accomodate this, all alignment is optional, and the lpmake tool will support a default alignment of 1MiB as a failsafe. Bug: 79173901 Test: liblp_test gtest Change-Id: I5bc41b90aa085f4f30393951af0d2b37c4ac2a72 --- fs_mgr/liblp/Android.bp | 1 + fs_mgr/liblp/builder.cpp | 151 ++++++++++++++++--- fs_mgr/liblp/builder_test.cpp | 150 +++++++++++++++++- fs_mgr/liblp/include/liblp/builder.h | 49 +++++- fs_mgr/liblp/include/liblp/metadata_format.h | 22 +++ fs_mgr/liblp/reader.cpp | 9 +- fs_mgr/liblp/utility.h | 24 +++ fs_mgr/liblp/utility_test.cpp | 13 +- 8 files changed, 389 insertions(+), 30 deletions(-) diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp index f7086a8c3..f59fa8468 100644 --- a/fs_mgr/liblp/Android.bp +++ b/fs_mgr/liblp/Android.bp @@ -52,6 +52,7 @@ cc_test { "libcrypto", "libcrypto_utils", "liblp", + "libfs_mgr", ], srcs: [ "builder_test.cpp", diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp index 0e4838c32..421f4e6ba 100644 --- a/fs_mgr/liblp/builder.cpp +++ b/fs_mgr/liblp/builder.cpp @@ -16,22 +16,47 @@ #include "liblp/builder.h" +#if defined(__linux__) +#include +#endif #include +#include #include +#include #include #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 -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 { @@ -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; } @@ -82,11 +107,28 @@ void Partition::ShrinkTo(uint64_t requested_size) { DCHECK(size_ == requested_size); } -std::unique_ptr MetadataBuilder::New(uint64_t blockdevice_size, +std::unique_ptr MetadataBuilder::New(const std::string& block_device, + uint32_t slot_number) { + std::unique_ptr metadata = ReadMetadata(block_device.c_str(), slot_number); + if (!metadata) { + return nullptr; + } + std::unique_ptr 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::New(const BlockDeviceInfo& device_info, uint32_t metadata_max_size, uint32_t metadata_slot_count) { std::unique_ptr 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; @@ -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."; @@ -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 @@ -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; } @@ -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; } @@ -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(sectors_needed, previous.end); + if (current.start - aligned >= sectors_needed) { + auto extent = std::make_unique(sectors_needed, aligned); sectors_needed -= extent->num_sectors(); new_extents.push_back(std::move(extent)); break; @@ -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(current.start - previous.end, previous.end); + auto extent = std::make_unique(current.start - aligned, aligned); sectors_needed -= extent->num_sectors(); new_extents.push_back(std::move(extent)); } @@ -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; } @@ -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 diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp index 2983f0f26..08440a3d0 100644 --- a/fs_mgr/liblp/builder_test.cpp +++ b/fs_mgr/liblp/builder_test.cpp @@ -16,6 +16,8 @@ #include #include +#include "fs_mgr.h" +#include "utility.h" using namespace std; using namespace android::fs_mgr; @@ -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 builder = MetadataBuilder::New(device_info, 1024, 2); + ASSERT_NE(builder, nullptr); + unique_ptr 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 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 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 builder = MetadataBuilder::New(1024 * 1024, 1024, 2); EXPECT_EQ(builder->AllocatableSpace(), 1036288); @@ -312,15 +397,72 @@ TEST(liblp, MetadataTooLarge) { static const size_t kMetadataSize = 64 * 1024; // No space to store metadata + geometry. - unique_ptr builder = MetadataBuilder::New(kDiskSize, kMetadataSize, 1); + BlockDeviceInfo device_info(kDiskSize, 0, 0); + unique_ptr 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(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 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); } diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h index 671a3bd2b..3cd95aed7 100644 --- a/fs_mgr/liblp/include/liblp/builder.h +++ b/fs_mgr/liblp/include/liblp/builder.h @@ -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: @@ -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 New(uint64_t blockdevice_size, + static std::unique_ptr 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 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 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 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 Export(); @@ -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> 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 diff --git a/fs_mgr/liblp/include/liblp/metadata_format.h b/fs_mgr/liblp/include/liblp/metadata_format.h index 85224352e..27602acbe 100644 --- a/fs_mgr/liblp/include/liblp/metadata_format.h +++ b/fs_mgr/liblp/include/liblp/metadata_format.h @@ -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 diff --git a/fs_mgr/liblp/reader.cpp b/fs_mgr/liblp/reader.cpp index 793818644..a0eeec929 100644 --- a/fs_mgr/liblp/reader.cpp +++ b/fs_mgr/liblp/reader.cpp @@ -41,11 +41,18 @@ static bool ParseGeometry(const void* buffer, LpMetadataGeometry* geometry) { LERROR << "Logical partition metadata has invalid geometry magic signature."; return false; } + // Reject if the struct size is larger than what we compiled. This is so we + // can compute a checksum with the |struct_size| field rather than using + // sizeof. + if (geometry->struct_size > sizeof(LpMetadataGeometry)) { + LERROR << "Logical partition metadata has unrecognized fields."; + return false; + } // Recompute and check the CRC32. { LpMetadataGeometry temp = *geometry; memset(&temp.checksum, 0, sizeof(temp.checksum)); - SHA256(&temp, sizeof(temp), temp.checksum); + SHA256(&temp, temp.struct_size, temp.checksum); if (memcmp(temp.checksum, geometry->checksum, sizeof(temp.checksum)) != 0) { LERROR << "Logical partition metadata has invalid geometry checksum."; return false; diff --git a/fs_mgr/liblp/utility.h b/fs_mgr/liblp/utility.h index 09ed31443..452227550 100644 --- a/fs_mgr/liblp/utility.h +++ b/fs_mgr/liblp/utility.h @@ -50,6 +50,30 @@ int64_t SeekFile64(int fd, int64_t offset, int whence); // Compute a SHA256 hash. void SHA256(const void* data, size_t length, uint8_t out[32]); +// Align |base| such that it is evenly divisible by |alignment|, which does not +// have to be a power of two. +constexpr uint64_t AlignTo(uint64_t base, uint32_t alignment) { + if (!alignment) { + return base; + } + uint64_t remainder = base % alignment; + if (remainder == 0) { + return base; + } + return base + (alignment - remainder); +} + +// Same as the above |AlignTo|, except that |base| is only aligned when added to +// |alignment_offset|. +constexpr uint64_t AlignTo(uint64_t base, uint32_t alignment, uint32_t alignment_offset) { + uint64_t aligned = AlignTo(base, alignment) + alignment_offset; + if (aligned - alignment >= base) { + // We overaligned (base < alignment_offset). + return aligned - alignment; + } + return aligned; +} + } // namespace fs_mgr } // namespace android diff --git a/fs_mgr/liblp/utility_test.cpp b/fs_mgr/liblp/utility_test.cpp index 25e8a254e..dcc569e0e 100644 --- a/fs_mgr/liblp/utility_test.cpp +++ b/fs_mgr/liblp/utility_test.cpp @@ -30,7 +30,7 @@ TEST(liblp, SlotNumberForSlotSuffix) { TEST(liblp, GetMetadataOffset) { LpMetadataGeometry geometry = { - LP_METADATA_GEOMETRY_MAGIC, sizeof(geometry), {0}, 16384, 4, 10000, 80000}; + LP_METADATA_GEOMETRY_MAGIC, sizeof(geometry), {0}, 16384, 4, 10000, 80000, 0, 0}; EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 0), 4096); EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 1), 4096 + 16384); EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 2), 4096 + 16384 * 2); @@ -41,3 +41,14 @@ TEST(liblp, GetMetadataOffset) { EXPECT_EQ(GetBackupMetadataOffset(geometry, 1), -4096 - 16384 * 3); EXPECT_EQ(GetBackupMetadataOffset(geometry, 0), -4096 - 16384 * 4); } + +TEST(liblp, AlignTo) { + EXPECT_EQ(AlignTo(37, 0), 37); + EXPECT_EQ(AlignTo(1024, 1024), 1024); + EXPECT_EQ(AlignTo(555, 1024), 1024); + EXPECT_EQ(AlignTo(555, 1000), 1000); + EXPECT_EQ(AlignTo(0, 1024), 0); + EXPECT_EQ(AlignTo(54, 32, 30), 62); + EXPECT_EQ(AlignTo(32, 32, 30), 62); + EXPECT_EQ(AlignTo(17, 32, 30), 30); +}