diff --git a/fs_mgr/Android.bp b/fs_mgr/Android.bp index 923442f90..b0b4839e7 100644 --- a/fs_mgr/Android.bp +++ b/fs_mgr/Android.bp @@ -57,12 +57,15 @@ cc_library_static { "libavb", "libfstab", "libdm", + "liblp", ], export_static_lib_headers: [ "libfstab", "libdm", + "liblp", ], whole_static_libs: [ + "liblp", "liblogwrap", "libdm", "libfstab", diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp new file mode 100644 index 000000000..d7bc6b573 --- /dev/null +++ b/fs_mgr/liblp/Android.bp @@ -0,0 +1,38 @@ +// +// 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. +// + +cc_library_static { + name: "liblp", + host_supported: true, + recovery_available: true, + defaults: ["fs_mgr_defaults"], + srcs: [ + "builder.cpp", + "reader.cpp", + "utility.cpp", + "writer.cpp", + ], + static_libs: [ + "libbase", + "liblog", + "libcrypto", + "libcrypto_utils", + ], + whole_static_libs: [ + "libext2_uuid", + ], + export_include_dirs: ["include"], +} diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp new file mode 100644 index 000000000..bfbf3e550 --- /dev/null +++ b/fs_mgr/liblp/builder.cpp @@ -0,0 +1,352 @@ +/* + * 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" + +#include + +#include + +#include +#include + +#include "liblp/metadata_format.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); +} + +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) { + size_ += extent->num_sectors() * LP_SECTOR_SIZE; + extents_.push_back(std::move(extent)); +} + +void Partition::RemoveExtents() { + size_ = 0; + extents_.clear(); +} + +void Partition::ShrinkTo(uint64_t requested_size) { + uint64_t aligned_size = AlignToSector(requested_size); + if (size_ <= aligned_size) { + return; + } + 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_ == requested_size); +} + +std::unique_ptr MetadataBuilder::New(uint64_t blockdevice_size, + 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)) { + return nullptr; + } + return builder; +} + +std::unique_ptr MetadataBuilder::New(const LpMetadata& metadata) { + std::unique_ptr 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(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(extent.num_sectors); + builder->AddExtent(std::move(copy)); + } + } + } + return true; +} + +bool MetadataBuilder::Init(uint64_t blockdevice_size, 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 = AlignToSector(metadata_max_size); + + // 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 (blockdevice_size < total_reserved || blockdevice_size - total_reserved < LP_SECTOR_SIZE) { + 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; + geometry_.metadata_max_size = metadata_max_size; + geometry_.metadata_slot_count = metadata_slot_count; + DCHECK(geometry_.last_logical_sector >= geometry_.first_logical_sector); + 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(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 requested_size) { + // Align the space needed up to the nearest sector. + uint64_t aligned_size = AlignToSector(requested_size); + if (partition->size() >= aligned_size) { + return true; + } + + // 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) {} + bool operator<(const Interval& other) const { return start < other.start; } + }; + std::vector intervals; + + // Collect all extents in the partition table. + for (const auto& partition : partitions_) { + for (const auto& extent : partition->extents()) { + LinearExtent* linear = extent->AsLinearExtent(); + if (!linear) { + continue; + } + intervals.emplace_back(linear->physical_sector(), + linear->physical_sector() + extent->num_sectors()); + } + } + + // Sort extents by starting sector. + std::sort(intervals.begin(), intervals.end()); + + // 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> 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); + 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); + sectors_needed -= extent->num_sectors(); + new_extents.push_back(std::move(extent)); + 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(current.start - previous.end, previous.end); + 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: the last usable sector is inclusive. + if (first_sector + sectors_needed > geometry_.last_logical_sector) { + LERROR << "Not enough free space to expand partition: " << partition->name(); + return false; + } + auto extent = std::make_unique(sectors_needed, first_sector); + new_extents.push_back(std::move(extent)); + } + + for (auto& extent : new_extents) { + partition->AddExtent(std::move(extent)); + } + return true; +} + +void MetadataBuilder::ShrinkPartition(Partition* partition, uint64_t requested_size) { + partition->ShrinkTo(requested_size); +} + +std::unique_ptr MetadataBuilder::Export() { + std::unique_ptr metadata = std::make_unique(); + 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(metadata->extents.size()); + part.num_extents = static_cast(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(metadata->partitions.size()); + metadata->header.extents.num_entries = static_cast(metadata->extents.size()); + return metadata; +} + +} // namespace fs_mgr +} // namespace android diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h new file mode 100644 index 000000000..fb982e27a --- /dev/null +++ b/fs_mgr/liblp/include/liblp/builder.h @@ -0,0 +1,169 @@ +// +// 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. +// + +#ifndef LIBLP_METADATA_BUILDER_H +#define LIBLP_METADATA_BUILDER_H + +#include +#include + +#include +#include + +#include "metadata_format.h" + +namespace android { +namespace fs_mgr { + +class LinearExtent; + +// Abstraction around dm-targets that can be encoded into logical partition tables. +class Extent { + public: + explicit Extent(uint64_t num_sectors) : num_sectors_(num_sectors) {} + virtual ~Extent() {} + + virtual void AddTo(LpMetadata* out) const = 0; + virtual LinearExtent* AsLinearExtent() { return nullptr; } + + uint64_t num_sectors() const { return num_sectors_; } + void set_num_sectors(uint64_t num_sectors) { num_sectors_ = num_sectors; } + + protected: + uint64_t num_sectors_; +}; + +// This corresponds to a dm-linear target. +class LinearExtent final : public Extent { + public: + LinearExtent(uint64_t num_sectors, uint64_t physical_sector) + : Extent(num_sectors), physical_sector_(physical_sector) {} + + void AddTo(LpMetadata* metadata) const override; + LinearExtent* AsLinearExtent() override { return this; } + + uint64_t physical_sector() const { return physical_sector_; } + + private: + uint64_t physical_sector_; +}; + +// This corresponds to a dm-zero target. +class ZeroExtent final : public Extent { + public: + explicit ZeroExtent(uint64_t num_sectors) : Extent(num_sectors) {} + + void AddTo(LpMetadata* out) const override; +}; + +class Partition final { + public: + Partition(const std::string& name, const std::string& guid, uint32_t attributes); + + // Add a raw extent. + void AddExtent(std::unique_ptr&& extent); + + // 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_; } + const std::vector>& extents() const { return extents_; } + uint64_t size() const { return size_; } + + private: + std::string name_; + std::string guid_; + std::vector> extents_; + uint32_t attributes_; + uint64_t size_; +}; + +class MetadataBuilder { + public: + // Construct an empty logical partition table builder. The block device size + // and maximum metadata size must be specified, as this will determine which + // areas of the physical partition can be flashed for metadata vs for logical + // partitions. + // + // 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, + uint32_t metadata_max_size, + uint32_t metadata_slot_count); + + // Import an existing table for modification. If the table is not valid, for + // example it contains duplicate partition names, then nullptr is returned. + static std::unique_ptr New(const LpMetadata& metadata); + + // Export metadata so it can be serialized to an image, to disk, or mounted + // via device-mapper. + std::unique_ptr Export(); + + // Add a partition, returning a handle so it can be sized as needed. If a + // partition with the given name already exists, nullptr is returned. + Partition* AddPartition(const std::string& name, const std::string& guid, uint32_t attributes); + + // Delete a partition by name if it exists. + void RemovePartition(const std::string& name); + + // Find a partition by name. If no partition is found, nullptr is returned. + Partition* FindPartition(const std::string& name); + + // Grow a partition to the requested size. If the partition's size is already + // greater or equal to the requested size, this will return true and the + // partition table will not be changed. Otherwise, a greedy algorithm is + // used to find free gaps in the partition table and allocate them for this + // partition. If not enough space can be allocated, false is returned, and + // the parition table will not be modified. + // + // The size will be rounded UP to the nearest sector. + // + // Note, this is an in-memory operation, and it does not alter the + // underlying filesystem or contents of the partition on disk. + bool GrowPartition(Partition* partition, uint64_t requested_size); + + // Shrink a partition to the requested size. If the partition is already + // smaller than the given size, this will return and the partition table + // will not be changed. Otherwise, extents will be removed and/or shrunk + // from the end of the partition until it is the requested size. + // + // The size will be rounded UP to the nearest sector. + // + // Note, this is an in-memory operation, and it does not alter the + // underlying filesystem or contents of the partition on disk. + void ShrinkPartition(Partition* partition, uint64_t requested_size); + + private: + MetadataBuilder(); + bool Init(uint64_t blockdevice_size, uint32_t metadata_max_size, uint32_t metadata_slot_count); + bool Init(const LpMetadata& metadata); + + LpMetadataGeometry geometry_; + LpMetadataHeader header_; + std::vector> partitions_; +}; + +} // namespace fs_mgr +} // namespace android + +#endif /* LIBLP_METADATA_BUILDER_H */ diff --git a/fs_mgr/liblp/include/liblp/metadata_format.h b/fs_mgr/liblp/include/liblp/metadata_format.h new file mode 100644 index 000000000..f6262ff63 --- /dev/null +++ b/fs_mgr/liblp/include/liblp/metadata_format.h @@ -0,0 +1,263 @@ +/* + * 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. + */ + +#ifndef LOGICAL_PARTITION_METADATA_FORMAT_H_ +#define LOGICAL_PARTITION_METADATA_FORMAT_H_ + +#ifdef __cplusplus +#include +#include +#endif + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Magic signature for LpMetadataGeometry. */ +#define LP_METADATA_GEOMETRY_MAGIC 0x616c4467 + +/* Space reserved for geometry information. */ +#define LP_METADATA_GEOMETRY_SIZE 4096 + +/* Magic signature for LpMetadataHeader. */ +#define LP_METADATA_HEADER_MAGIC 0x414C5030 + +/* Current metadata version. */ +#define LP_METADATA_MAJOR_VERSION 1 +#define LP_METADATA_MINOR_VERSION 0 + +/* Attributes for the LpMetadataPartition::attributes field. + * + * READONLY - The partition should not be considered writable. When used with + * device mapper, the block device will be created as read-only. + */ +#define LP_PARTITION_ATTR_READONLY 0x1 + +/* Mask that defines all valid attributes. */ +#define LP_PARTITION_ATTRIBUTE_MASK (LP_PARTITION_ATTR_READONLY) + +/* Default name of the physical partition that holds logical partition entries. + * The layout of this partition will look like: + * + * +--------------------+ + * | Disk Geometry | + * +--------------------+ + * | Metadata | + * +--------------------+ + * | Logical Partitions | + * +--------------------+ + * | Backup Metadata | + * +--------------------+ + * | Geometry Backup | + * +--------------------+ + */ +#define LP_METADATA_PARTITION_NAME "android" + +/* Size of a sector is always 512 bytes for compatibility with the Linux kernel. */ +#define LP_SECTOR_SIZE 512 + +/* This structure is stored at sector 0 in the first 4096 bytes of the + * partition, and again in the very last 4096 bytes. It is never modified and + * describes how logical partition information can be located. + */ +typedef struct LpMetadataGeometry { + /* 0: Magic signature (LP_METADATA_GEOMETRY_MAGIC). */ + uint32_t magic; + + /* 4: Size of the LpMetadataGeometry struct. */ + uint32_t struct_size; + + /* 8: SHA256 checksum of this struct, with this field set to 0. */ + uint8_t checksum[32]; + + /* 40: Maximum amount of space a single copy of the metadata can use. */ + uint32_t metadata_max_size; + + /* 44: Number of copies of the metadata to keep. For A/B devices, this + * will be 2. For an A/B/C device, it would be 3, et cetera. For Non-A/B + * it will be 1. A backup copy of each slot is kept, so if this is "2", + * there will be four copies total. + */ + uint32_t metadata_slot_count; + + /* 48: First usable sector for allocating logical partitions. this will be + * the first sector after the initial 4096 geometry block, followed by the + * space consumed by metadata_max_size*metadata_slot_count. + */ + uint64_t first_logical_sector; + + /* 56: Last usable sector, inclusive, for allocating logical partitions. + * At the end of this sector will follow backup metadata slots and the + * backup geometry block at the very end. + */ + uint64_t last_logical_sector; +} __attribute__((packed)) LpMetadataGeometry; + +/* The logical partition metadata has a number of tables; they are described + * in the header via the following structure. + * + * The size of the table can be computed by multiplying entry_size by + * num_entries, and the result must not overflow a 32-bit signed integer. + */ +typedef struct LpMetadataTableDescriptor { + /* 0: Location of the table, relative to the metadata header. */ + uint32_t offset; + /* 4: Number of entries in the table. */ + uint32_t num_entries; + /* 8: Size of each entry in the table, in bytes. */ + uint32_t entry_size; +} __attribute__((packed)) LpMetadataTableDescriptor; + +/* Binary format for the header of the logical partition metadata format. + * + * The format has three sections. The header must occur first, and the + * proceeding tables may be placed in any order after. + * + * +-----------------------------------------+ + * | Header data - fixed size | + * +-----------------------------------------+ + * | Partition table - variable size | + * +-----------------------------------------+ + * | Partition table extents - variable size | + * +-----------------------------------------+ + * + * The "Header" portion is described by LpMetadataHeader. It will always + * precede the other three blocks. + * + * All fields are stored in little-endian byte order when serialized. + * + * This struct is versioned; see the |major_version| and |minor_version| + * fields. + */ +typedef struct LpMetadataHeader { + /* 0: Four bytes equal to LP_METADATA_HEADER_MAGIC. */ + uint32_t magic; + + /* 4: Version number required to read this metadata. If the version is not + * equal to the library version, the metadata should be considered + * incompatible. + */ + uint16_t major_version; + + /* 6: Minor version. A library supporting newer features should be able to + * read metadata with an older minor version. However, an older library + * should not support reading metadata if its minor version is higher. + */ + uint16_t minor_version; + + /* 8: The size of this header struct. */ + uint32_t header_size; + + /* 12: SHA256 checksum of the header, up to |header_size| bytes, computed as + * if this field were set to 0. + */ + uint8_t header_checksum[32]; + + /* 44: The total size of all tables. This size is contiguous; tables may not + * have gaps in between, and they immediately follow the header. + */ + uint32_t tables_size; + + /* 48: SHA256 checksum of all table contents. */ + uint8_t tables_checksum[32]; + + /* 80: Partition table descriptor. */ + LpMetadataTableDescriptor partitions; + /* 92: Extent table descriptor. */ + LpMetadataTableDescriptor extents; +} __attribute__((packed)) LpMetadataHeader; + +/* This struct defines a logical partition entry, similar to what would be + * present in a GUID Partition Table. + */ +typedef struct LpMetadataPartition { + /* 0: Name of this partition in ASCII characters. Any unused characters in + * the buffer must be set to 0. Characters may only be alphanumeric or _. + * The name must include at least one ASCII character, and it must be unique + * across all partition names. The length (36) is the same as the maximum + * length of a GPT partition name. + */ + char name[36]; + + /* 36: Globally unique identifier (GUID) of this partition. */ + uint8_t guid[16]; + + /* 52: Attributes for the partition (see LP_PARTITION_ATTR_* flags above). */ + uint32_t attributes; + + /* 56: Index of the first extent owned by this partition. The extent will + * start at logical sector 0. Gaps between extents are not allowed. + */ + uint32_t first_extent_index; + + /* 60: Number of extents in the partition. Every partition must have at + * least one extent. + */ + uint32_t num_extents; +} __attribute__((packed)) LpMetadataPartition; + +/* This extent is a dm-linear target, and the index is an index into the + * LinearExtent table. + */ +#define LP_TARGET_TYPE_LINEAR 0 + +/* This extent is a dm-zero target. The index is ignored and must be 0. */ +#define LP_TARGET_TYPE_ZERO 1 + +/* This struct defines an extent entry in the extent table block. */ +typedef struct LpMetadataExtent { + /* 0: Length of this extent, in 512-byte sectors. */ + uint64_t num_sectors; + + /* 8: Target type for device-mapper (see LP_TARGET_TYPE_* values). */ + uint32_t target_type; + + /* 12: Contents depends on target_type. + * + * LINEAR: The sector on the physical partition that this extent maps onto. + * ZERO: This field must be 0. + */ + uint64_t target_data; +} __attribute__((packed)) LpMetadataExtent; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#ifdef __cplusplus +namespace android { +namespace fs_mgr { + +// Helper structure for easily interpreting deserialized metadata, or +// re-serializing metadata. +struct LpMetadata { + LpMetadataGeometry geometry; + LpMetadataHeader header; + std::vector partitions; + std::vector extents; +}; + +// Helper to extract safe C++ strings from partition info. +std::string GetPartitionName(const LpMetadataPartition& partition); +std::string GetPartitionGuid(const LpMetadataPartition& partition); + +} // namespace fs_mgr +} // namespace android +#endif + +#endif /* LOGICAL_PARTITION_METADATA_FORMAT_H_ */ diff --git a/fs_mgr/liblp/include/liblp/reader.h b/fs_mgr/liblp/include/liblp/reader.h new file mode 100644 index 000000000..e7fa46dcb --- /dev/null +++ b/fs_mgr/liblp/include/liblp/reader.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#ifndef LIBLP_READER_H_ +#define LIBLP_READER_H_ + +#include + +#include + +#include "metadata_format.h" + +namespace android { +namespace fs_mgr { + +// Read logical partition metadata from its predetermined location on a block +// device. If readback fails, we also attempt to load from a backup copy. +std::unique_ptr ReadMetadata(const char* block_device, uint32_t slot_number); + +// Read and validate the logical partition geometry from a block device. +bool ReadLogicalPartitionGeometry(const char* block_device, LpMetadataGeometry* geometry); + +// Read logical partition metadata from an image file that was created with +// WriteToImageFile(). +std::unique_ptr ReadFromImageFile(const char* file); + +} // namespace fs_mgr +} // namespace android + +#endif /* LIBLP_READER_H_ */ diff --git a/fs_mgr/liblp/include/liblp/writer.h b/fs_mgr/liblp/include/liblp/writer.h new file mode 100644 index 000000000..02fb21fc1 --- /dev/null +++ b/fs_mgr/liblp/include/liblp/writer.h @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#ifndef LIBLP_WRITER_H +#define LIBLP_WRITER_H + +#include "metadata_format.h" + +namespace android { +namespace fs_mgr { + +// When flashing the initial logical partition layout, we also write geometry +// information at the start and end of the big physical partition. This helps +// locate metadata and backup metadata in the case of corruption or a failed +// update. For normal changes to the metadata, we never modify the geometry. +enum class SyncMode { + // Write geometry information. + Flash, + // Normal update of a single slot. + Update +}; + +// Write the given partition table to the given block device, writing only +// copies according to the given sync mode. +// +// This will perform some verification, such that the device has enough space +// to store the metadata as well as all of its extents. +// +// The slot number indicates which metadata slot to use. +bool WritePartitionTable(const char* block_device, const LpMetadata& metadata, SyncMode sync_mode, + uint32_t slot_number); + +// Helper function to serialize geometry and metadata to a normal file, for +// flashing or debugging. +bool WriteToImageFile(const char* file, const LpMetadata& metadata); + +} // namespace fs_mgr +} // namespace android + +#endif /* LIBLP_WRITER_H */ diff --git a/fs_mgr/liblp/reader.cpp b/fs_mgr/liblp/reader.cpp new file mode 100644 index 000000000..1b70da9a8 --- /dev/null +++ b/fs_mgr/liblp/reader.cpp @@ -0,0 +1,307 @@ +/* + * 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/reader.h" + +#include +#include +#include + +#include + +#include +#include + +#include "utility.h" + +namespace android { +namespace fs_mgr { + +// Parse an LpMetadataGeometry from a buffer. The buffer must be at least +// LP_METADATA_GEOMETRY_SIZE bytes in size. +static bool ParseGeometry(const void* buffer, LpMetadataGeometry* geometry) { + static_assert(sizeof(*geometry) <= LP_METADATA_GEOMETRY_SIZE); + memcpy(geometry, buffer, sizeof(*geometry)); + + // Check the magic signature. + if (geometry->magic != LP_METADATA_GEOMETRY_MAGIC) { + LERROR << "Logical partition metadata has invalid geometry magic signature."; + return false; + } + // Recompute and check the CRC32. + { + LpMetadataGeometry temp = *geometry; + memset(&temp.checksum, 0, sizeof(temp.checksum)); + SHA256(&temp, sizeof(temp), temp.checksum); + if (memcmp(temp.checksum, geometry->checksum, sizeof(temp.checksum)) != 0) { + LERROR << "Logical partition metadata has invalid geometry checksum."; + return false; + } + } + // Check that the struct size is equal (this will have to change if we ever + // change the struct size in a release). + if (geometry->struct_size != sizeof(LpMetadataGeometry)) { + LERROR << "Logical partition metadata has invalid struct size."; + return false; + } + if (geometry->metadata_slot_count == 0) { + LERROR << "Logical partition metadata has invalid slot count."; + return false; + } + + // Check that the metadata area and logical partition areas don't overlap. + off64_t end_of_metadata = + GetPrimaryMetadataOffset(*geometry, geometry->metadata_slot_count - 1) + + geometry->metadata_max_size; + if (uint64_t(end_of_metadata) > geometry->first_logical_sector * LP_SECTOR_SIZE) { + LERROR << "Logical partition metadata overlaps with logical partition contents."; + return false; + } + return true; +} + +// Read and validate geometry information from a block device that holds +// logical partitions. If the information is corrupted, this will attempt +// to read it from a secondary backup location. +static bool ReadLogicalPartitionGeometry(int fd, LpMetadataGeometry* geometry) { + // Read the first 4096 bytes. + std::unique_ptr buffer = std::make_unique(LP_METADATA_GEOMETRY_SIZE); + if (lseek64(fd, 0, SEEK_SET) == (off64_t)-1) { + PERROR << __PRETTY_FUNCTION__ << "lseek64 failed"; + return false; + } + if (!android::base::ReadFully(fd, buffer.get(), LP_METADATA_GEOMETRY_SIZE)) { + PERROR << __PRETTY_FUNCTION__ << "read " << LP_METADATA_GEOMETRY_SIZE << " bytes failed"; + return false; + } + if (ParseGeometry(buffer.get(), geometry)) { + return true; + } + + // Try the backup copy in the last 4096 bytes. + if (lseek64(fd, -LP_METADATA_GEOMETRY_SIZE, SEEK_END) == (off64_t)-1) { + PERROR << __PRETTY_FUNCTION__ << "lseek64 failed, offset " << -LP_METADATA_GEOMETRY_SIZE; + return false; + } + if (!android::base::ReadFully(fd, buffer.get(), LP_METADATA_GEOMETRY_SIZE)) { + PERROR << __PRETTY_FUNCTION__ << "backup read " << LP_METADATA_GEOMETRY_SIZE + << " bytes failed"; + return false; + } + return ParseGeometry(buffer.get(), geometry); +} + +// Helper function to read geometry from a device without an open descriptor. +bool ReadLogicalPartitionGeometry(const char* block_device, LpMetadataGeometry* geometry) { + android::base::unique_fd fd(open(block_device, O_RDONLY)); + if (fd < 0) { + PERROR << __PRETTY_FUNCTION__ << "open failed: " << block_device; + return false; + } + return ReadLogicalPartitionGeometry(fd, geometry); +} + +static bool ValidateTableBounds(const LpMetadataHeader& header, + const LpMetadataTableDescriptor& table) { + if (table.offset > header.tables_size) { + return false; + } + uint64_t table_size = uint64_t(table.num_entries) * table.entry_size; + if (header.tables_size - table.offset < table_size) { + return false; + } + return true; +} + +static bool ValidateMetadataHeader(const LpMetadataHeader& header) { + // To compute the header's checksum, we have to temporarily set its checksum + // field to 0. + { + LpMetadataHeader temp = header; + memset(&temp.header_checksum, 0, sizeof(temp.header_checksum)); + SHA256(&temp, sizeof(temp), temp.header_checksum); + if (memcmp(temp.header_checksum, header.header_checksum, sizeof(temp.header_checksum)) != 0) { + LERROR << "Logical partition metadata has invalid checksum."; + return false; + } + } + + // Do basic validation of key metadata bits. + if (header.magic != LP_METADATA_HEADER_MAGIC) { + LERROR << "Logical partition metadata has invalid magic value."; + return false; + } + // Check that the version is compatible. + if (header.major_version != LP_METADATA_MAJOR_VERSION || + header.minor_version > LP_METADATA_MINOR_VERSION) { + LERROR << "Logical partition metadata has incompatible version."; + return false; + } + if (!ValidateTableBounds(header, header.partitions) || + !ValidateTableBounds(header, header.extents)) { + LERROR << "Logical partition metadata has invalid table bounds."; + return false; + } + // Check that table entry sizes can accomodate their respective structs. If + // table sizes change, these checks will have to be adjusted. + if (header.partitions.entry_size != sizeof(LpMetadataPartition)) { + LERROR << "Logical partition metadata has invalid partition table entry size."; + return false; + } + if (header.extents.entry_size != sizeof(LpMetadataExtent)) { + LERROR << "Logical partition metadata has invalid extent table entry size."; + return false; + } + return true; +} + +using ReadMetadataFn = std::function; + +// Parse and validate all metadata at the current position in the given file +// descriptor. +static std::unique_ptr ParseMetadata(int fd) { + // First read and validate the header. + std::unique_ptr metadata = std::make_unique(); + if (!android::base::ReadFully(fd, &metadata->header, sizeof(metadata->header))) { + PERROR << __PRETTY_FUNCTION__ << "read " << sizeof(metadata->header) << "bytes failed"; + return nullptr; + } + if (!ValidateMetadataHeader(metadata->header)) { + return nullptr; + } + + LpMetadataHeader& header = metadata->header; + + // Read the metadata payload. Allocation is fallible in case the metadata is + // corrupt and has some huge value. + std::unique_ptr buffer(new (std::nothrow) uint8_t[header.tables_size]); + if (!buffer) { + LERROR << "Out of memory reading logical partition tables."; + return nullptr; + } + if (!android::base::ReadFully(fd, buffer.get(), header.tables_size)) { + PERROR << __PRETTY_FUNCTION__ << "read " << header.tables_size << "bytes failed"; + return nullptr; + } + + uint8_t checksum[32]; + SHA256(buffer.get(), header.tables_size, checksum); + if (memcmp(checksum, header.tables_checksum, sizeof(checksum)) != 0) { + LERROR << "Logical partition metadata has invalid table checksum."; + return nullptr; + } + + // ValidateTableSize ensured that |cursor| is valid for the number of + // entries in the table. + uint8_t* cursor = buffer.get() + header.partitions.offset; + for (size_t i = 0; i < header.partitions.num_entries; i++) { + LpMetadataPartition partition; + memcpy(&partition, cursor, sizeof(partition)); + cursor += header.partitions.entry_size; + + if (partition.attributes & ~LP_PARTITION_ATTRIBUTE_MASK) { + LERROR << "Logical partition has invalid attribute set."; + return nullptr; + } + if (partition.first_extent_index + partition.num_extents > header.extents.num_entries) { + LERROR << "Logical partition has invalid extent list."; + return nullptr; + } + + metadata->partitions.push_back(partition); + } + + cursor = buffer.get() + header.extents.offset; + for (size_t i = 0; i < header.extents.num_entries; i++) { + LpMetadataExtent extent; + memcpy(&extent, cursor, sizeof(extent)); + cursor += header.extents.entry_size; + + metadata->extents.push_back(extent); + } + + return metadata; +} + +std::unique_ptr ReadMetadata(const char* block_device, uint32_t slot_number) { + android::base::unique_fd fd(open(block_device, O_RDONLY)); + if (fd < 0) { + PERROR << __PRETTY_FUNCTION__ << "open failed: " << block_device; + return nullptr; + } + LpMetadataGeometry geometry; + if (!ReadLogicalPartitionGeometry(fd, &geometry)) { + return nullptr; + } + + // First try the primary copy. + off64_t offset = GetPrimaryMetadataOffset(geometry, slot_number); + if (lseek64(fd, offset, SEEK_SET) == (off64_t)-1) { + PERROR << __PRETTY_FUNCTION__ << "lseek64 failed: offset " << offset; + return nullptr; + } + if (std::unique_ptr metadata = ParseMetadata(fd)) { + return metadata; + } + + // Next try the backup copy. + offset = GetBackupMetadataOffset(geometry, slot_number); + if (lseek64(fd, offset, SEEK_END) == (off64_t)-1) { + PERROR << __PRETTY_FUNCTION__ << "lseek64 failed: offset " << offset; + return nullptr; + } + return ParseMetadata(fd); +} + +std::unique_ptr ReadFromImageFile(const char* file) { + android::base::unique_fd fd(open(file, O_RDONLY)); + if (fd < 0) { + PERROR << __PRETTY_FUNCTION__ << "open failed: " << file; + return nullptr; + } + + LpMetadataGeometry geometry; + if (!ReadLogicalPartitionGeometry(fd, &geometry)) { + return nullptr; + } + if (lseek64(fd, LP_METADATA_GEOMETRY_SIZE, SEEK_SET) == (off64_t)-1) { + PERROR << __PRETTY_FUNCTION__ << "lseek64 failed: offset " << LP_METADATA_GEOMETRY_SIZE; + return nullptr; + } + std::unique_ptr metadata = ParseMetadata(fd); + if (!metadata) { + return nullptr; + } + metadata->geometry = geometry; + return metadata; +} + +static std::string NameFromFixedArray(const char* name, size_t buffer_size) { + // If the end of the buffer has a null character, it's safe to assume the + // buffer is null terminated. Otherwise, we cap the string to the input + // buffer size. + if (name[buffer_size - 1] == '\0') { + return std::string(name); + } + return std::string(name, buffer_size); +} + +std::string GetPartitionName(const LpMetadataPartition& partition) { + return NameFromFixedArray(partition.name, sizeof(partition.name)); +} + +} // namespace fs_mgr +} // namespace android diff --git a/fs_mgr/liblp/utility.cpp b/fs_mgr/liblp/utility.cpp new file mode 100644 index 000000000..c4b70288a --- /dev/null +++ b/fs_mgr/liblp/utility.cpp @@ -0,0 +1,89 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "utility.h" + +namespace android { +namespace fs_mgr { + +bool GetDescriptorSize(int fd, uint64_t* size) { + struct stat s; + if (fstat(fd, &s) < 0) { + PERROR << __PRETTY_FUNCTION__ << "fstat failed"; + return false; + } + + if (S_ISBLK(s.st_mode)) { + if (ioctl(fd, BLKGETSIZE64, size) != -1) { + return true; + } + } + + off64_t result = lseek64(fd, 0, SEEK_END); + if (result == (off64_t)-1) { + PERROR << __PRETTY_FUNCTION__ << "lseek64 failed"; + return false; + } + + *size = result; + return true; +} + +off64_t GetPrimaryMetadataOffset(const LpMetadataGeometry& geometry, uint32_t slot_number) { + CHECK(slot_number < geometry.metadata_slot_count); + + off64_t offset = LP_METADATA_GEOMETRY_SIZE + geometry.metadata_max_size * slot_number; + CHECK(offset + geometry.metadata_max_size <= + off64_t(geometry.first_logical_sector * LP_SECTOR_SIZE)); + return offset; +} + +off64_t GetBackupMetadataOffset(const LpMetadataGeometry& geometry, uint32_t slot_number) { + CHECK(slot_number < geometry.metadata_slot_count); + off64_t start = off64_t(-LP_METADATA_GEOMETRY_SIZE) - + off64_t(geometry.metadata_max_size) * geometry.metadata_slot_count; + return start + off64_t(geometry.metadata_max_size * slot_number); +} + +void SHA256(const void* data, size_t length, uint8_t out[32]) { + SHA256_CTX c; + SHA256_Init(&c); + SHA256_Update(&c, data, length); + SHA256_Final(out, &c); +} + +std::string GetPartitionGuid(const LpMetadataPartition& partition) { + // 32 hex characters, four hyphens. Unfortunately libext2_uuid provides no + // macro to assist with buffer sizing. + static const size_t kGuidLen = 36; + char buffer[kGuidLen + 1]; + uuid_unparse(partition.guid, buffer); + return buffer; +} + +} // namespace fs_mgr +} // namespace android diff --git a/fs_mgr/liblp/utility.h b/fs_mgr/liblp/utility.h new file mode 100644 index 000000000..dc559eda3 --- /dev/null +++ b/fs_mgr/liblp/utility.h @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#ifndef LIBLP_UTILITY_H +#define LIBLP_UTILITY_H + +#include +#include +#include + +#include + +#include "liblp/metadata_format.h" + +#define LP_TAG "[liblp]" +#define LERROR LOG(ERROR) << LP_TAG +#define PERROR PLOG(ERROR) << LP_TAG + +namespace android { +namespace fs_mgr { + +// Determine the size of a block device (or file). Logs and returns false on +// error. After calling this, the position of |fd| may have changed. +bool GetDescriptorSize(int fd, uint64_t* size); + +// Return the offset of a primary metadata slot, relative to the start of the +// device. +off64_t GetPrimaryMetadataOffset(const LpMetadataGeometry& geometry, uint32_t slot_number); + +// Return the offset of a backup metadata slot, relative to the end of the +// device. +off64_t GetBackupMetadataOffset(const LpMetadataGeometry& geometry, uint32_t slot_number); + +// Compute a SHA256 hash. +void SHA256(const void* data, size_t length, uint8_t out[32]); + +} // namespace fs_mgr +} // namespace android + +#endif // LIBLP_UTILITY_H diff --git a/fs_mgr/liblp/writer.cpp b/fs_mgr/liblp/writer.cpp new file mode 100644 index 000000000..aab244a32 --- /dev/null +++ b/fs_mgr/liblp/writer.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2007 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 +#include + +#include + +#include +#include + +#include "liblp/reader.h" +#include "liblp/writer.h" +#include "utility.h" + +namespace android { +namespace fs_mgr { + +static std::string SerializeGeometry(const LpMetadataGeometry& input) { + LpMetadataGeometry geometry = input; + memset(geometry.checksum, 0, sizeof(geometry.checksum)); + SHA256(&geometry, sizeof(geometry), geometry.checksum); + return std::string(reinterpret_cast(&geometry), sizeof(geometry)); +} + +static bool CompareGeometry(const LpMetadataGeometry& g1, const LpMetadataGeometry& g2) { + return g1.metadata_max_size == g2.metadata_max_size && + g1.metadata_slot_count == g2.metadata_slot_count && + g1.first_logical_sector == g2.first_logical_sector && + g1.last_logical_sector == g2.last_logical_sector; +} + +static std::string SerializeMetadata(const LpMetadata& input) { + LpMetadata metadata = input; + LpMetadataHeader& header = metadata.header; + + // Serialize individual tables. + std::string partitions(reinterpret_cast(metadata.partitions.data()), + metadata.partitions.size() * sizeof(LpMetadataPartition)); + std::string extents(reinterpret_cast(metadata.extents.data()), + metadata.extents.size() * sizeof(LpMetadataExtent)); + + // Compute positions of tables. + header.partitions.offset = 0; + header.extents.offset = header.partitions.offset + partitions.size(); + header.tables_size = header.extents.offset + extents.size(); + + // Compute payload checksum. + std::string tables = partitions + extents; + SHA256(tables.data(), tables.size(), header.tables_checksum); + + // Compute header checksum. + memset(header.header_checksum, 0, sizeof(header.header_checksum)); + SHA256(&header, sizeof(header), header.header_checksum); + + std::string header_blob = + std::string(reinterpret_cast(&metadata.header), sizeof(metadata.header)); + return header_blob + tables; +} + +// Perform sanity checks so we don't accidentally overwrite valid metadata +// with potentially invalid metadata, or random partition data with metadata. +static bool ValidateGeometryAndMetadata(const LpMetadata& metadata, uint64_t blockdevice_size, + uint64_t metadata_size) { + const LpMetadataHeader& header = metadata.header; + const LpMetadataGeometry& geometry = metadata.geometry; + // Validate the usable sector range. + if (geometry.first_logical_sector > geometry.last_logical_sector) { + LERROR << "Logical partition metadata has invalid sector range."; + return false; + } + // Make sure we're writing within the space reserved. + if (metadata_size > geometry.metadata_max_size) { + LERROR << "Logical partition metadata is too large."; + return false; + } + + // Make sure the device has enough space to store two backup copies of the + // metadata. + uint64_t reserved_size = LP_METADATA_GEOMETRY_SIZE + + uint64_t(geometry.metadata_max_size) * geometry.metadata_slot_count; + if (reserved_size > blockdevice_size || + reserved_size > geometry.first_logical_sector * LP_SECTOR_SIZE) { + LERROR << "Not enough space to store all logical partition metadata slots."; + return false; + } + if (blockdevice_size - reserved_size < (geometry.last_logical_sector + 1) * LP_SECTOR_SIZE) { + LERROR << "Not enough space to backup all logical partition metadata slots."; + return false; + } + + // Make sure all partition entries reference valid extents. + for (const auto& partition : metadata.partitions) { + if (partition.first_extent_index + partition.num_extents > metadata.extents.size()) { + LERROR << "Partition references invalid extent."; + return false; + } + } + + // Make sure all linear extents have a valid range. + for (const auto& extent : metadata.extents) { + if (extent.target_type == LP_TARGET_TYPE_LINEAR) { + uint64_t physical_sector = extent.target_data; + if (physical_sector < geometry.first_logical_sector || + physical_sector + extent.num_sectors > geometry.last_logical_sector) { + LERROR << "Extent table entry is out of bounds."; + return false; + } + } + } + return true; +} + +bool WritePartitionTable(const char* block_device, const LpMetadata& metadata, SyncMode sync_mode, + uint32_t slot_number) { + android::base::unique_fd fd(open(block_device, O_RDWR | O_SYNC)); + if (fd < 0) { + PERROR << __PRETTY_FUNCTION__ << "open failed: " << block_device; + return false; + } + + uint64_t size; + if (!GetDescriptorSize(fd, &size)) { + return false; + } + + const LpMetadataGeometry& geometry = metadata.geometry; + if (sync_mode != SyncMode::Flash) { + // Verify that the old geometry is identical. If it's not, then we've + // based this new metadata on invalid assumptions. + LpMetadataGeometry old_geometry; + if (!ReadLogicalPartitionGeometry(block_device, &old_geometry)) { + return false; + } + if (!CompareGeometry(geometry, old_geometry)) { + LERROR << "Incompatible geometry in new logical partition metadata"; + return false; + } + } + + // Make sure we're writing to a valid metadata slot. + if (slot_number >= geometry.metadata_slot_count) { + LERROR << "Invalid logical partition metadata slot number."; + return false; + } + + // Before writing geometry and/or logical partition tables, perform some + // basic checks that the geometry and tables are coherent, and will fit + // on the given block device. + std::string blob = SerializeMetadata(metadata); + if (!ValidateGeometryAndMetadata(metadata, size, blob.size())) { + return false; + } + + // First write geometry if this is a flash operation. It gets written to + // the first and last 4096-byte regions of the device. + if (sync_mode == SyncMode::Flash) { + std::string blob = SerializeGeometry(metadata.geometry); + if (lseek64(fd, 0, SEEK_SET) == (off64_t)-1) { + PERROR << __PRETTY_FUNCTION__ << "lseek64 failed: offset 0"; + return false; + } + if (!android::base::WriteFully(fd, blob.data(), blob.size())) { + PERROR << __PRETTY_FUNCTION__ << "write " << blob.size() + << " bytes failed: " << block_device; + return false; + } + if (lseek64(fd, -LP_METADATA_GEOMETRY_SIZE, SEEK_END) == (off64_t)-1) { + PERROR << __PRETTY_FUNCTION__ << "lseek64 failed: offset " << -LP_METADATA_GEOMETRY_SIZE; + return false; + } + if (!android::base::WriteFully(fd, blob.data(), blob.size())) { + PERROR << __PRETTY_FUNCTION__ << "backup write " << blob.size() + << " bytes failed: " << block_device; + return false; + } + } + + // Write the primary copy of the metadata. + off64_t primary_offset = GetPrimaryMetadataOffset(geometry, slot_number); + if (lseek64(fd, primary_offset, SEEK_SET) == (off64_t)-1) { + PERROR << __PRETTY_FUNCTION__ << "lseek64 failed: offset " << primary_offset; + return false; + } + if (!android::base::WriteFully(fd, blob.data(), blob.size())) { + PERROR << __PRETTY_FUNCTION__ << "write " << blob.size() + << " bytes failed: " << block_device; + return false; + } + + // Write the backup copy of the metadata. + off64_t backup_offset = GetBackupMetadataOffset(geometry, slot_number); + off64_t abs_offset = lseek64(fd, backup_offset, SEEK_END); + if (abs_offset == (off64_t)-1) { + PERROR << __PRETTY_FUNCTION__ << "lseek64 failed: offset " << backup_offset; + return false; + } + if (abs_offset < off64_t((geometry.last_logical_sector + 1) * LP_SECTOR_SIZE)) { + PERROR << __PRETTY_FUNCTION__ << "backup offset " << abs_offset + << " is within logical partition bounds, sector " << geometry.last_logical_sector; + return false; + } + if (!android::base::WriteFully(fd, blob.data(), blob.size())) { + PERROR << __PRETTY_FUNCTION__ << "backup write " << blob.size() + << " bytes failed: " << block_device; + return false; + } + return true; +} + +bool WriteToImageFile(const char* file, const LpMetadata& input) { + std::string geometry = SerializeGeometry(input.geometry); + std::string padding(LP_METADATA_GEOMETRY_SIZE - geometry.size(), '\0'); + std::string metadata = SerializeMetadata(input); + + std::string everything = geometry + padding + metadata; + + android::base::unique_fd fd(open(file, O_CREAT | O_RDWR | O_TRUNC, 0644)); + if (fd < 0) { + PERROR << __PRETTY_FUNCTION__ << "open failed: " << file; + return false; + } + if (!android::base::WriteFully(fd, everything.data(), everything.size())) { + PERROR << __PRETTY_FUNCTION__ << "write " << everything.size() << " bytes failed: " << file; + return false; + } + return true; +} + +} // namespace fs_mgr +} // namespace android