Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d0e5bcc1 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge changes from topic "liblp-blocksize"

* changes:
  liblp: Require block-aligned partition sizes.
  liblp: Simplify GrowPartition().
parents ddcee93c 4d9c7459
Loading
Loading
Loading
Loading
+96 −56
Original line number Diff line number Diff line
@@ -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;
@@ -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;
}

@@ -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;
@@ -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;
@@ -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;
}

@@ -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));
    }
@@ -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) {
@@ -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);
+38 −12
Original line number Diff line number Diff line
@@ -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);
}

@@ -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;
@@ -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;
@@ -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;
@@ -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);
@@ -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);

@@ -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);

@@ -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;
@@ -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);
}
+15 −8
Original line number Diff line number Diff line
@@ -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
@@ -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.
@@ -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);

@@ -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_; }
@@ -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_;
@@ -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);
    }

+6 −0
Original line number Diff line number Diff line
@@ -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
+2 −1
Original line number Diff line number Diff line
@@ -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);