blob: 352647b8e4249618adae3565fe69cbddbe9b0856 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#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/liblp.h"
#include "reader.h"
#include "utility.h"
namespace android {
namespace fs_mgr {
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;
}
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;
(void)device_info;
LERROR << __PRETTY_FUNCTION__ << ": Not supported on this operating system.";
return false;
#endif
}
void LinearExtent::AddTo(LpMetadata* out) const {
out->extents.push_back(LpMetadataExtent{num_sectors_, LP_TARGET_TYPE_LINEAR, physical_sector_});
}
void ZeroExtent::AddTo(LpMetadata* out) const {
out->extents.push_back(LpMetadataExtent{num_sectors_, LP_TARGET_TYPE_ZERO, 0});
}
Partition::Partition(const std::string& name, const std::string& guid, uint32_t attributes)
: name_(name), guid_(guid), attributes_(attributes), size_(0) {}
void Partition::AddExtent(std::unique_ptr<Extent>&& extent) {
size_ += extent->num_sectors() * LP_SECTOR_SIZE;
if (LinearExtent* new_extent = extent->AsLinearExtent()) {
if (!extents_.empty() && extents_.back()->AsLinearExtent() &&
extents_.back()->AsLinearExtent()->end_sector() == new_extent->physical_sector()) {
// If the previous extent can be merged into this new one, do so
// to avoid creating unnecessary extents.
LinearExtent* prev_extent = extents_.back()->AsLinearExtent();
extent = std::make_unique<LinearExtent>(
prev_extent->num_sectors() + new_extent->num_sectors(),
prev_extent->physical_sector());
extents_.pop_back();
}
}
extents_.push_back(std::move(extent));
}
void Partition::RemoveExtents() {
size_ = 0;
extents_.clear();
}
void Partition::ShrinkTo(uint64_t aligned_size) {
if (aligned_size == 0) {
RemoveExtents();
return;
}
// Remove or shrink extents of any kind until the total partition size is
// equal to the requested size.
uint64_t sectors_to_remove = (size_ - aligned_size) / LP_SECTOR_SIZE;
while (sectors_to_remove) {
Extent* extent = extents_.back().get();
if (extent->num_sectors() > sectors_to_remove) {
size_ -= sectors_to_remove * LP_SECTOR_SIZE;
extent->set_num_sectors(extent->num_sectors() - sectors_to_remove);
break;
}
size_ -= (extent->num_sectors() * LP_SECTOR_SIZE);
sectors_to_remove -= extent->num_sectors();
extents_.pop_back();
}
DCHECK(size_ == aligned_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(device_info, metadata_max_size, metadata_slot_count)) {
return nullptr;
}
return builder;
}
std::unique_ptr<MetadataBuilder> MetadataBuilder::New(const LpMetadata& metadata) {
std::unique_ptr<MetadataBuilder> builder(new MetadataBuilder());
if (!builder->Init(metadata)) {
return nullptr;
}
return builder;
}
MetadataBuilder::MetadataBuilder() {
memset(&geometry_, 0, sizeof(geometry_));
geometry_.magic = LP_METADATA_GEOMETRY_MAGIC;
geometry_.struct_size = sizeof(geometry_);
memset(&header_, 0, sizeof(header_));
header_.magic = LP_METADATA_HEADER_MAGIC;
header_.major_version = LP_METADATA_MAJOR_VERSION;
header_.minor_version = LP_METADATA_MINOR_VERSION;
header_.header_size = sizeof(header_);
header_.partitions.entry_size = sizeof(LpMetadataPartition);
header_.extents.entry_size = sizeof(LpMetadataExtent);
}
bool MetadataBuilder::Init(const LpMetadata& metadata) {
geometry_ = metadata.geometry;
for (const auto& partition : metadata.partitions) {
Partition* builder = AddPartition(GetPartitionName(partition), GetPartitionGuid(partition),
partition.attributes);
if (!builder) {
return false;
}
for (size_t i = 0; i < partition.num_extents; i++) {
const LpMetadataExtent& extent = metadata.extents[partition.first_extent_index + i];
if (extent.target_type == LP_TARGET_TYPE_LINEAR) {
auto copy = std::make_unique<LinearExtent>(extent.num_sectors, extent.target_data);
builder->AddExtent(std::move(copy));
} else if (extent.target_type == LP_TARGET_TYPE_ZERO) {
auto copy = std::make_unique<ZeroExtent>(extent.num_sectors);
builder->AddExtent(std::move(copy));
}
}
}
device_info_.alignment = geometry_.alignment;
device_info_.alignment_offset = geometry_.alignment_offset;
device_info_.logical_block_size = geometry_.logical_block_size;
return true;
}
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.";
return false;
}
if (metadata_slot_count == 0) {
LERROR << "Invalid metadata slot count.";
return false;
}
// Align the metadata size up to the nearest sector.
metadata_max_size = AlignTo(metadata_max_size, LP_SECTOR_SIZE);
// Check that device properties are sane.
device_info_ = device_info;
if (device_info_.size % LP_SECTOR_SIZE != 0) {
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;
}
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;
}
// 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
// we store a backup copy of everything.
uint64_t reserved =
LP_METADATA_GEOMETRY_SIZE + (uint64_t(metadata_max_size) * metadata_slot_count);
uint64_t total_reserved = reserved * 2;
if (device_info_.size < total_reserved) {
LERROR << "Attempting to create metadata on a block device that is too small.";
return false;
}
// 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;
}
// 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;
geometry_.metadata_slot_count = metadata_slot_count;
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;
}
Partition* MetadataBuilder::AddPartition(const std::string& name, const std::string& guid,
uint32_t attributes) {
if (name.empty()) {
LERROR << "Partition must have a non-empty name.";
return nullptr;
}
if (FindPartition(name)) {
LERROR << "Attempting to create duplication partition with name: " << name;
return nullptr;
}
partitions_.push_back(std::make_unique<Partition>(name, guid, attributes));
return partitions_.back().get();
}
Partition* MetadataBuilder::FindPartition(const std::string& name) {
for (const auto& partition : partitions_) {
if (partition->name() == name) {
return partition.get();
}
}
return nullptr;
}
void MetadataBuilder::RemovePartition(const std::string& name) {
for (auto iter = partitions_.begin(); iter != partitions_.end(); iter++) {
if ((*iter)->name() == name) {
partitions_.erase(iter);
return;
}
}
}
bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t aligned_size) {
// Figure out how much we need to allocate.
uint64_t space_needed = aligned_size - partition->size();
uint64_t sectors_needed = space_needed / LP_SECTOR_SIZE;
DCHECK(sectors_needed * LP_SECTOR_SIZE == space_needed);
struct Interval {
uint64_t start;
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; }
};
// 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;
}
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);
}
const uint64_t sectors_per_block = device_info_.logical_block_size / LP_SECTOR_SIZE;
CHECK_NE(sectors_per_block, 0);
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 (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 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;
}
}
if (sectors_needed) {
LERROR << "Not enough free space to expand partition: " << partition->name();
return false;
}
// Everything succeeded, so commit the new extents.
for (auto& extent : new_extents) {
partition->AddExtent(std::move(extent));
}
return true;
}
void MetadataBuilder::ShrinkPartition(Partition* partition, uint64_t aligned_size) {
partition->ShrinkTo(aligned_size);
}
std::unique_ptr<LpMetadata> MetadataBuilder::Export() {
std::unique_ptr<LpMetadata> metadata = std::make_unique<LpMetadata>();
metadata->header = header_;
metadata->geometry = geometry_;
// Flatten the partition and extent structures into an LpMetadata, which
// makes it very easy to validate, serialize, or pass on to device-mapper.
for (const auto& partition : partitions_) {
LpMetadataPartition part;
memset(&part, 0, sizeof(part));
if (partition->name().size() > sizeof(part.name)) {
LERROR << "Partition name is too long: " << partition->name();
return nullptr;
}
if (partition->attributes() & ~(LP_PARTITION_ATTRIBUTE_MASK)) {
LERROR << "Partition " << partition->name() << " has unsupported attribute.";
return nullptr;
}
strncpy(part.name, partition->name().c_str(), sizeof(part.name));
if (uuid_parse(partition->guid().c_str(), part.guid) != 0) {
LERROR << "Could not parse guid " << partition->guid() << " for partition "
<< partition->name();
return nullptr;
}
part.first_extent_index = static_cast<uint32_t>(metadata->extents.size());
part.num_extents = static_cast<uint32_t>(partition->extents().size());
part.attributes = partition->attributes();
for (const auto& extent : partition->extents()) {
extent->AddTo(metadata.get());
}
metadata->partitions.push_back(part);
}
metadata->header.partitions.num_entries = static_cast<uint32_t>(metadata->partitions.size());
metadata->header.extents.num_entries = static_cast<uint32_t>(metadata->extents.size());
return metadata;
}
uint64_t MetadataBuilder::AllocatableSpace() const {
return (geometry_.last_logical_sector - geometry_.first_logical_sector + 1) * LP_SECTOR_SIZE;
}
uint64_t MetadataBuilder::UsedSpace() const {
uint64_t size = 0;
for (const auto& partition : partitions_) {
size += partition->size();
}
return 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;
// 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) {
device_info_.alignment = device_info.alignment;
}
if (device_info.alignment_offset) {
device_info_.alignment_offset = device_info.alignment_offset;
}
}
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, device_info_.logical_block_size);
uint64_t old_size = partition->size();
if (aligned_size > old_size) {
if (!GrowPartition(partition, aligned_size)) {
return false;
}
} else if (aligned_size < partition->size()) {
ShrinkPartition(partition, aligned_size);
}
LINFO << "Partition " << partition->name() << " will resize from " << old_size << " bytes to "
<< aligned_size << " bytes";
return true;
}
} // namespace fs_mgr
} // namespace android