blob: bcc203a1215941bbccaabda315263f471b217190 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gpt/gpt.h"
#include <assert.h>
#include <errno.h>
#include <fuchsia/hardware/block/driver/c/banjo.h>
#include <inttypes.h>
#include <lib/cksum.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/syscalls.h> // for zx_cprng_draw
#include <algorithm>
#include <iterator>
#include <limits>
#include <memory>
#include <optional>
#include <fbl/algorithm.h>
#include <fbl/vector.h>
#include <gpt/guid.h>
#include <mbr/mbr.h>
#include <range/range.h>
#include <safemath/checked_math.h>
#include <src/lib/uuid/uuid.h>
#include "src/lib/utf_conversion/utf_conversion.h"
#include "src/storage/lib/block_client/cpp/block_device.h"
#include "src/storage/lib/block_client/cpp/reader.h"
#include "src/storage/lib/block_client/cpp/writer.h"
namespace gpt {
namespace {
using BlockRange = range::Range<uint64_t>;
#define G_PRINTF(f, ...) \
if (debug_out) \
printf((f), ##__VA_ARGS__);
bool debug_out = false;
void print_array(const gpt_partition_t* const partitions[kPartitionCount], int c) {
char GUID[GPT_GUID_STRLEN];
char name[GPT_NAME_LEN / 2 + 1];
for (int i = 0; i < c; ++i) {
uint8_to_guid_string(GUID, partitions[i]->type);
memset(name, 0, GPT_NAME_LEN / 2 + 1);
utf16_to_cstring(name, reinterpret_cast<const uint16_t*>(partitions[i]->name),
GPT_NAME_LEN / 2);
printf("Name: %s \n Start: %lu -- End: %lu \nType: %s\n", name, partitions[i]->first,
partitions[i]->last, GUID);
}
}
// Write a block to device "fd", writing "size" bytes followed by zero-byte padding to the next
// block size.
zx_status_t write_partial_block(block_client::BlockDevice& device, void* data, size_t size,
off_t offset, size_t blocksize) {
block_client::Writer writer(device);
// If input block is already rounded to blocksize, just directly write from our buffer.
if (size % blocksize == 0) {
if (writer.Write(offset, size, data) != ZX_OK) {
return ZX_ERR_IO;
}
return ZX_OK;
}
// Otherwise, pad out data to blocksize.
size_t new_size = fbl::round_up(size, blocksize);
std::unique_ptr<uint8_t[]> block(new uint8_t[new_size]);
memcpy(block.get(), data, size);
memset(block.get() + size, 0, new_size - size);
if (writer.Write(offset, new_size, block.get()) != ZX_OK) {
return ZX_ERR_IO;
}
return ZX_OK;
}
void partition_init(gpt_partition_t* part, const char* name, const uint8_t* type,
const uint8_t* guid, uint64_t first, uint64_t last, uint64_t flags) {
memcpy(part->type, type, sizeof(part->type));
memcpy(part->guid, guid, sizeof(part->guid));
part->first = first;
part->last = last;
part->flags = flags;
const size_t num_utf16_bits = sizeof(uint16_t);
cstring_to_utf16(reinterpret_cast<uint16_t*>(part->name), name,
sizeof(part->name) / num_utf16_bits);
}
zx_status_t gpt_sync_current(block_client::BlockDevice& device, uint64_t blocksize,
gpt_header_t* header, gpt_partition_t* ptable) {
// Check all offset calculations are valid
off_t table_offset;
if (!safemath::CheckMul(header->entries, blocksize).Cast<off_t>().AssignIfValid(&table_offset)) {
return ZX_ERR_OUT_OF_RANGE;
}
size_t ptable_size;
if (!safemath::CheckMul(header->entries_count, header->entries_size)
.Cast<size_t>()
.AssignIfValid(&ptable_size)) {
return ZX_ERR_OUT_OF_RANGE;
}
off_t header_offset;
if (!safemath::CheckMul(header->current, blocksize).Cast<off_t>().AssignIfValid(&header_offset)) {
return ZX_ERR_OUT_OF_RANGE;
}
// Write partition table first
zx_status_t status = write_partial_block(device, ptable, ptable_size, table_offset, blocksize);
if (status != ZX_OK) {
return status;
}
// Then write the header
return write_partial_block(device, header, sizeof(*header), header_offset, blocksize);
}
int compare(const void* ls, const void* rs) {
const auto* l = *static_cast<gpt_partition_t* const*>(ls);
const auto* r = *static_cast<gpt_partition_t* const*>(rs);
if (l == nullptr && r == nullptr) {
return 0;
}
if (l == nullptr) {
return 1;
}
if (r == nullptr) {
return -1;
}
if (l->first == r->first) {
return 0;
}
return (l->first < r->first) ? -1 : 1;
}
// Returns a copy of |str| with any hex values converted to uppercase.
std::string HexToUpper(std::string str) {
for (char& ch : str) {
if (ch >= 'a' && ch <= 'f') {
ch -= 'a';
ch += 'A';
}
}
return str;
}
// Converts a GPT inclusive range [start, end] to an end-exclusive range::Range [start, end).
// Returns std::nullopt if the range conflicts with GPT headers or exceeds the device's block size.
std::optional<BlockRange> ConvertBlockRange(uint64_t start_block, uint64_t end_block,
uint64_t block_count) {
if (block_count == 0)
return std::nullopt;
if (start_block > end_block)
return std::nullopt;
if (start_block < kPrimaryEntriesStartBlock)
return std::nullopt;
const uint64_t backup_header_block = block_count - 1;
// Backup GPT header should be in the last block in the device.
if (start_block >= backup_header_block || end_block >= backup_header_block)
return std::nullopt;
// Overflow shouldn't be possible due to validating against backup_header_block.
return BlockRange{start_block, safemath::CheckAdd(end_block, 1).ValueOrDie()};
}
} // namespace
__BEGIN_CDECLS
void gpt_set_debug_output_enabled(bool enabled) { debug_out = enabled; }
void gpt_sort_partitions(const gpt_partition_t** partitions, size_t count) {
qsort(partitions, count, sizeof(gpt_partition_t*), compare);
}
// TODO(69527): migrate usages to |utf8_to_utf16| in utf_conversion.h
void cstring_to_utf16(uint16_t* dst, const char* src, size_t maxlen) {
size_t len = strlen(src);
if (len > maxlen) {
len = maxlen - 1;
}
for (size_t i = 0; i < len; i++) {
dst[i] = static_cast<uint16_t>(src[i] & 0x7f);
}
dst[len] = 0;
}
// TODO(69527): migrate usages to |utf16_to_utf8| in utf_conversion.h
char* utf16_to_cstring(char* dst, const uint16_t* src, size_t len) {
size_t i = 0;
char* ptr = dst;
while (i < len) {
char c = static_cast<char>(src[i++] & 0x7f);
*ptr++ = c;
if (!c) {
break;
}
}
return dst;
}
bool gpt_is_sys_guid(const uint8_t* guid, ssize_t len) {
static const uint8_t sys_guid[GPT_GUID_LEN] = GUID_SYSTEM_VALUE;
return len == GPT_GUID_LEN && !memcmp(guid, sys_guid, GPT_GUID_LEN);
}
bool gpt_is_data_guid(const uint8_t* guid, ssize_t len) {
static const uint8_t data_guid[GPT_GUID_LEN] = GUID_DATA_VALUE;
return len == GPT_GUID_LEN && !memcmp(guid, data_guid, GPT_GUID_LEN);
}
bool gpt_is_efi_guid(const uint8_t* guid, ssize_t len) {
static const uint8_t efi_guid[GPT_GUID_LEN] = GUID_EFI_VALUE;
return len == GPT_GUID_LEN && !memcmp(guid, efi_guid, GPT_GUID_LEN);
}
bool gpt_is_factory_guid(const uint8_t* guid, ssize_t len) {
static const uint8_t factory_guid[GPT_GUID_LEN] = GPT_FACTORY_TYPE_GUID;
return len == GPT_GUID_LEN && !memcmp(guid, factory_guid, GPT_GUID_LEN);
}
void uint8_to_guid_string(char* dst, const uint8_t* src) {
strcpy(dst, HexToUpper(uuid::Uuid(src).ToString()).c_str());
}
__END_CDECLS
zx::result<> GetPartitionName(const gpt_entry_t& entry, char* name, size_t capacity) {
size_t len = capacity;
const uint16_t* utf16_name = reinterpret_cast<const uint16_t*>(entry.name);
const uint16_t* utf16_name_end = utf16_name + (sizeof(entry.name) / sizeof(uint16_t));
const size_t utf16_name_len = std::distance(utf16_name, std::find(utf16_name, utf16_name_end, 0));
if (zx_status_t status =
utf16_to_utf8(utf16_name, utf16_name_len, reinterpret_cast<uint8_t*>(name), &len,
UTF_CONVERT_FLAG_FORCE_LITTLE_ENDIAN);
status != ZX_OK) {
return zx::error(status);
}
if (len >= capacity) {
return zx::error(ZX_ERR_BUFFER_TOO_SMALL);
}
name[len] = 0;
return zx::ok();
}
fpromise::result<gpt_header_t, zx_status_t> InitializePrimaryHeader(uint64_t block_size,
uint64_t block_count) {
gpt_header_t header = {};
if (block_size < kHeaderSize) {
return fpromise::error(ZX_ERR_INVALID_ARGS);
}
if (block_count <= MinimumBlockDeviceSize(block_size).value()) {
return fpromise::error(ZX_ERR_BUFFER_TOO_SMALL);
}
header.magic = kMagicNumber;
header.revision = kRevision;
header.size = kHeaderSize;
header.current = kPrimaryHeaderStartBlock;
// backup gpt is in the last block
header.backup = block_count - 1;
// First usable block is the block after end of primary copy.
header.first = kPrimaryHeaderStartBlock + MinimumBlocksPerCopy(block_size).value();
// Last usable block is the block before beginning of backup entries array.
header.last = block_count - MinimumBlocksPerCopy(block_size).value() - 1;
// We have ensured above that there are more blocks than MinimumBlockDeviceSize().
ZX_DEBUG_ASSERT(header.first <= header.last);
// generate a guid
zx_cprng_draw(header.guid, GPT_GUID_LEN);
// fill in partition table fields in header
header.entries = kPrimaryEntriesStartBlock;
header.entries_count = kPartitionCount;
header.entries_size = kEntrySize;
// Finally, calculate header checksum
header.crc32 = crc32(0, reinterpret_cast<const uint8_t*>(&header), kHeaderSize);
return fpromise::ok(header);
}
// Returns user friendly error message given `status`.
const char* HeaderStatusToCString(zx_status_t status) {
switch (status) {
case ZX_OK:
return "valid partition";
case ZX_ERR_BAD_STATE:
return "bad header magic";
case ZX_ERR_INVALID_ARGS:
return "invalid header size";
case ZX_ERR_IO_DATA_INTEGRITY:
return "invalid header (CRC or invalid range)";
case ZX_ERR_IO_OVERRUN:
return "too many partitions";
case ZX_ERR_FILE_BIG:
return "invalid entry size";
case ZX_ERR_BUFFER_TOO_SMALL:
return "last block > block count";
case ZX_ERR_OUT_OF_RANGE:
return "invalid usable blocks";
default:
return "unknown error";
}
}
zx_status_t ValidateHeader(const gpt_header_t* header, uint64_t block_count) {
ZX_ASSERT(header != nullptr);
if (header->magic != kMagicNumber)
return ZX_ERR_BAD_STATE;
if (header->size != sizeof(gpt_header_t) || block_count < kPrimaryEntriesStartBlock)
return ZX_ERR_INVALID_ARGS;
gpt_header_t copy;
memcpy(&copy, header, sizeof(gpt_header_t));
copy.crc32 = 0;
uint32_t crc = crc32(0, reinterpret_cast<uint8_t*>(&copy), copy.size);
if (crc != header->crc32)
return ZX_ERR_IO_DATA_INTEGRITY;
if (header->entries_count > kPartitionCount)
return ZX_ERR_IO_OVERRUN;
if (header->entries_size != kEntrySize)
return ZX_ERR_FILE_BIG;
if (header->current >= block_count || header->backup >= block_count)
return ZX_ERR_BUFFER_TOO_SMALL;
if (!ConvertBlockRange(header->first, header->last, block_count))
return ZX_ERR_IO_DATA_INTEGRITY;
return ZX_OK;
}
fpromise::result<uint64_t, zx_status_t> EntryBlockCount(const gpt_entry_t* entry) {
if (entry == nullptr) {
return fpromise::error(ZX_ERR_INVALID_ARGS);
}
auto result = ValidateEntry(entry);
if (result.is_error()) {
return fpromise::error(ZX_ERR_BAD_STATE);
}
if (!result.value()) {
return fpromise::error(ZX_ERR_NOT_FOUND);
}
return fpromise::ok(entry->last - entry->first + 1);
}
fpromise::result<bool, zx_status_t> ValidateEntry(const gpt_entry_t* entry) {
uuid::Uuid zero_guid;
bool guid_valid = (uuid::Uuid(entry->guid) != zero_guid);
bool type_valid = (uuid::Uuid(entry->type) != zero_guid);
bool range_valid = (entry->first != 0) && (entry->first <= entry->last);
if (!guid_valid && !type_valid && !range_valid) {
// None of the fields are initialized. It is unused entry but this is not
// an error case.
return fpromise::ok(false);
}
// Guid is one/few of the fields that is uninitialized.
if (!guid_valid) {
return fpromise::error(ZX_ERR_BAD_STATE);
}
// Type is one/few of the fields that is uninitialized.
if (!type_valid) {
return fpromise::error(ZX_ERR_BAD_STATE);
}
// The range seems to be the problem.
if (!range_valid) {
return fpromise::error(ZX_ERR_OUT_OF_RANGE);
}
// All fields are initialized and contain valid data.
return fpromise::ok(true);
}
bool IsPartitionVisible(const gpt_partition_t* partition) {
return !((partition->flags & kFlagHidden) == kFlagHidden);
}
void SetPartitionVisibility(gpt_partition_t* partition, bool visible) {
if (visible) {
partition->flags &= ~kFlagHidden;
} else {
partition->flags |= kFlagHidden;
}
}
mbr::Mbr MakeProtectiveMbr(uint64_t blocks_in_disk) {
mbr::Mbr mbr = {};
mbr.partitions[0].chs_address_start[1] = 0x1;
mbr.partitions[0].type = mbr::kPartitionTypeGptProtective;
mbr.partitions[0].chs_address_end[0] = 0xff;
mbr.partitions[0].chs_address_end[1] = 0xff;
mbr.partitions[0].chs_address_end[2] = 0xff;
// Protective MBR should start at sector 1, and extend to the end of the disk.
// If the number of blocks exceeds 32-bits, we simply make it as large as possible.
mbr.partitions[0].start_sector_lba = 1;
mbr.partitions[0].num_sectors =
static_cast<uint32_t>(std::min(0xffff'ffffUL, blocks_in_disk - 1));
return mbr;
}
zx_status_t GptDevice::FinalizeAndSync(bool persist) {
auto result = InitializePrimaryHeader(blocksize_, blocks_);
if (result.is_error()) {
return result.error();
}
// fill in the new header fields
gpt_header_t header = result.value();
if (valid_) {
header.current = header_.current;
header.backup = header_.backup;
memcpy(header.guid, header_.guid, 16);
header.entries = header_.entries;
}
// always write 128 entries in partition table
size_t ptable_size = kPartitionCount * sizeof(gpt_partition_t);
std::unique_ptr<gpt_partition_t[]> buf(new gpt_partition_t[kPartitionCount]);
if (!buf) {
return ZX_ERR_NO_MEMORY;
}
memset(buf.get(), 0, ptable_size);
// generate partition table
uint8_t* ptr = reinterpret_cast<uint8_t*>(buf.get());
for (uint32_t i = 0; i < kPartitionCount && partitions_[i] != nullptr; i++) {
memcpy(ptr, partitions_[i], kEntrySize);
ptr += kEntrySize;
}
header.entries_crc = crc32(0, reinterpret_cast<uint8_t*>(buf.get()), ptable_size);
uint64_t ptable_blocks = ptable_size / blocksize_;
header.first = header.entries + ptable_blocks;
header.last = header.backup - ptable_blocks - 1;
// calculate header checksum
header.crc32 = 0;
header.crc32 = crc32(0, reinterpret_cast<const uint8_t*>(&header), kHeaderSize);
// the copy cached in priv is the primary copy
memcpy(&header_, &header, sizeof(header));
// the header copy on stack is now the backup copy...
header.current = header_.backup;
header.backup = header_.current;
header.entries = header_.last + 1;
header.crc32 = 0;
header.crc32 = crc32(0, reinterpret_cast<const uint8_t*>(&header), kHeaderSize);
if (persist) {
if (!device_) {
return ZX_ERR_BAD_STATE;
}
// Write protective MBR.
mbr::Mbr mbr = MakeProtectiveMbr(blocks_);
zx_status_t status = write_partial_block(*device_, &mbr, sizeof(mbr), /*offset=*/0, blocksize_);
if (status != ZX_OK) {
return status;
}
// write backup to disk
status = gpt_sync_current(*device_, blocksize_, &header, buf.get());
if (status != ZX_OK) {
return status;
}
// write primary copy to disk
status = gpt_sync_current(*device_, blocksize_, &header_, buf.get());
if (status != ZX_OK) {
return status;
}
}
// align backup with new on-disk state
memcpy(ptable_backup_, ptable_, sizeof(ptable_));
valid_ = true;
return ZX_OK;
}
void GptDevice::PrintTable() const {
int count = 0;
for (; partitions_[count] != nullptr; ++count)
;
print_array(partitions_, count);
}
bool RangesOverlapsWithOtherRanges(const fbl::Vector<BlockRange>& ranges, const BlockRange& range) {
return std::any_of(ranges.begin(), ranges.end(),
[&range](const BlockRange& r) { return Overlap(r, range); });
}
zx_status_t GptDevice::ValidateEntries(const uint8_t* buffer, uint64_t block_count) const {
ZX_DEBUG_ASSERT(buffer != nullptr);
ZX_DEBUG_ASSERT(!valid_);
const gpt_partition_t* partitions = reinterpret_cast<const gpt_partition_t*>(buffer);
fbl::Vector<BlockRange> ranges;
std::optional<BlockRange> usable_range =
ConvertBlockRange(header_.first, header_.last, block_count);
// We should be here after we have validated the header.
ZX_ASSERT(usable_range);
// Verify crc before we process entries.
const uint64_t partition_array_size = static_cast<uint64_t>(header_.entries_count) * kEntrySize;
const uint32_t partition_array_crc_without_padding = crc32(0, buffer, partition_array_size);
const uint32_t partition_array_crc_with_padding = crc32(0, buffer, kMaxPartitionTableSize);
if (header_.entries_crc == partition_array_crc_without_padding) {
// The partition entry array CRC covers only the number of entries specified
// in the header. This is the expected case.
} else if (header_.entries_crc == partition_array_crc_with_padding) {
// The partition entry array CRC includes padding bytes/blocks following the
// array. This is technically incorrect, but should be accepted anyway to
// maintain the previous behavior of the GPT driver.
G_PRINTF("GPT partition entry array CRC includes padding, continuing anyway");
} else {
return ZX_ERR_BAD_STATE;
}
// Entries are not guaranteed to be sorted. We have to validate the range
// of blocks they occupy by comparing each valid partition against all others.
for (uint32_t i = 0; i < header_.entries_count; i++) {
const gpt_entry_t* entry = &partitions[i];
auto result = ValidateEntry(entry);
// It is ok to have an empty entry but it is not ok entry.
if (result.is_error()) {
return result.error();
}
if (!result.value()) {
continue;
}
// Ensure partition range doesn't conflict with device size or GPT headers.
std::optional<BlockRange> partition_range =
ConvertBlockRange(entry->first, entry->last, block_count);
if (!partition_range)
return ZX_ERR_IO_DATA_INTEGRITY;
// Entry's first block should be greater than or equal to GPT's first usable block.
// Entry's last block should be less than or equal to GPT's last usable block.
if (!Contains(*usable_range, *partition_range)) {
return ZX_ERR_ALREADY_EXISTS;
}
if (RangesOverlapsWithOtherRanges(ranges, *partition_range)) {
return ZX_ERR_OUT_OF_RANGE;
}
ranges.push_back(*partition_range);
}
return ZX_OK;
}
zx_status_t GptDevice::LoadEntries(const uint8_t* buffer, uint64_t buffer_size,
uint64_t block_count) {
size_t entries_size = static_cast<size_t>(header_.entries_count) * kEntrySize;
// Ensure that we have large buffer that can contain all the
// entries in the GPT.
if (buffer_size < entries_size) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
if (zx_status_t status = ValidateEntries(buffer, block_count); status != ZX_OK) {
return status;
}
memcpy(ptable_, buffer, entries_size);
// save original state so we can know what we changed
memcpy(ptable_backup_, buffer, entries_size);
// fill the table of valid partitions
for (unsigned i = 0; i < header_.entries_count; i++) {
auto result = ValidateEntry(&ptable_[i]);
// It is ok to have an empty entry but not invalid entry.
if (result.is_error()) {
return result.error();
}
if (!result.value()) {
continue;
}
partitions_[i] = &ptable_[i];
}
return ZX_OK;
}
zx_status_t GptDevice::GetDiffs(uint32_t idx, uint32_t* diffs) const {
*diffs = 0;
if (idx >= kPartitionCount) {
return ZX_ERR_OUT_OF_RANGE;
}
if (partitions_[idx] == nullptr) {
return ZX_ERR_NOT_FOUND;
}
const gpt_partition_t* a = partitions_[idx];
const gpt_partition_t* b = &ptable_backup_[idx];
if (memcmp(a->type, b->type, sizeof(a->type)) != 0) {
*diffs |= kGptDiffType;
}
if (memcmp(a->guid, b->guid, sizeof(a->guid)) != 0) {
*diffs |= kGptDiffGuid;
}
if (a->first != b->first) {
*diffs |= kGptDiffFirst;
}
if (a->last != b->last) {
*diffs |= kGptDiffLast;
}
if (a->flags != b->flags) {
*diffs |= kGptDiffFlags;
}
if (memcmp(a->name, b->name, sizeof(a->name)) != 0) {
*diffs |= kGptDiffName;
}
return ZX_OK;
}
zx::result<std::unique_ptr<GptDevice>> GptDevice::Init(
std::unique_ptr<block_client::BlockDevice> device, uint32_t blocksize, uint64_t block_count) {
off_t offset;
uint8_t block[blocksize];
if (blocksize < kMinimumBlockSize) {
G_PRINTF("blocksize < %u not supported\n", kMinimumBlockSize);
return zx::error(ZX_ERR_INTERNAL);
}
if (blocksize > kMaximumBlockSize) {
G_PRINTF("blocksize > %u not supported\n", kMaximumBlockSize);
return zx::error(ZX_ERR_INTERNAL);
}
offset = 0;
block_client::Reader reader(*device);
if (zx_status_t status = reader.Read(offset, blocksize, block); status != ZX_OK) {
G_PRINTF("Failed to read %u @ %lld: %s\n", blocksize, offset, zx_status_get_string(status));
return zx::error(ZX_ERR_IO);
}
// read the gpt header (lba 1)
offset = static_cast<off_t>(kPrimaryHeaderStartBlock) * blocksize;
size_t size = MinimumBytesPerCopy(blocksize).value();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[size]);
if (zx_status_t status = reader.Read(offset, size, buffer.get()); status != ZX_OK) {
G_PRINTF("Failed to read %lu @ %lld: %s\n", size, offset, zx_status_get_string(status));
return zx::error(ZX_ERR_IO);
}
zx::result dev = Load(buffer.get(), size, blocksize, block_count);
if (dev.is_error()) {
// We did not find valid gpt on the file. Initialize new gpt.
G_PRINTF("%s\n", HeaderStatusToCString(dev.error_value()));
std::unique_ptr dev = std::unique_ptr<GptDevice>(new GptDevice());
dev->blocksize_ = blocksize;
dev->blocks_ = block_count;
dev->device_ = std::move(device);
return zx::ok(std::move(dev));
}
dev.value()->device_ = std::move(device);
return dev;
}
zx::result<std::unique_ptr<GptDevice>> GptDevice::Create(
std::unique_ptr<block_client::BlockDevice> device, uint32_t blocksize, uint64_t blocks) {
return Init(std::move(device), blocksize, blocks);
}
zx::result<std::unique_ptr<GptDevice>> GptDevice::Load(const uint8_t* buffer, uint64_t buffer_size,
uint32_t blocksize, uint64_t blocks) {
if (buffer == nullptr) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (blocksize < kHeaderSize) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (buffer_size < kHeaderSize) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (zx_status_t status = ValidateHeader(reinterpret_cast<const gpt_header_t*>(buffer), blocks);
status != ZX_OK) {
return zx::error(status);
}
std::unique_ptr<GptDevice> dev(new GptDevice());
memcpy(&dev->header_, buffer, sizeof(gpt_header_t));
if (zx_status_t status = dev->LoadEntries(&buffer[blocksize], buffer_size - blocksize, blocks);
status != ZX_OK) {
return zx::error(status);
}
dev->blocksize_ = blocksize;
dev->blocks_ = blocks;
dev->valid_ = true;
return zx::ok(std::move(dev));
}
zx_status_t GptDevice::Finalize() { return FinalizeAndSync(false); }
zx_status_t GptDevice::Sync() { return FinalizeAndSync(true); }
zx_status_t GptDevice::Range(uint64_t* block_start, uint64_t* block_end) const {
if (!valid_) {
G_PRINTF("partition header invalid\n");
return ZX_ERR_INTERNAL;
}
// check range
*block_start = header_.first;
*block_end = header_.last;
return ZX_OK;
}
zx_status_t GptDevice::FindFreeRange(uint64_t blocks, uint64_t* out_offset) const {
if (!valid_) {
G_PRINTF("partition header invalid\n");
return ZX_ERR_INTERNAL;
}
std::vector<BlockRange> allocated_ranges;
// Insert a synthetic start entry to mark the first usable block.
allocated_ranges.emplace_back(0, header_.first);
for (uint32_t idx = 0; idx < kPartitionCount; idx++) {
zx::result<const gpt_partition_t*> entry = GetPartition(idx);
// skip non-existent partitions
if (entry.is_error()) {
continue;
}
std::optional<BlockRange> partition_range =
ConvertBlockRange(entry->first, entry->last, header_.backup + 1);
if (!partition_range) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
allocated_ranges.push_back(*partition_range);
}
std::sort(allocated_ranges.begin(), allocated_ranges.end(),
[](const auto& a, const auto& b) { return a.Start() < b.Start(); });
// Insert a synthetic entry to mark the last usable block.
allocated_ranges.emplace_back(header_.last + 1, header_.last + 1);
zx_status_t status = ZX_ERR_NO_SPACE;
for (unsigned i = 0; i < allocated_ranges.size() - 1; ++i) {
ZX_DEBUG_ASSERT(allocated_ranges[i].End() <= allocated_ranges[i + 1].Start());
if (allocated_ranges[i + 1].Start() - allocated_ranges[i].End() >= blocks) {
*out_offset = allocated_ranges[i].End();
status = ZX_OK;
break;
}
}
return status;
}
zx::result<uint32_t> GptDevice::AddPartition(const char* name, const uint8_t* type,
const uint8_t* guid, uint64_t offset, uint64_t blocks,
uint64_t flags) {
if (!valid_) {
G_PRINTF("partition header invalid, sync to generate a default header\n");
return zx::error(ZX_ERR_INTERNAL);
}
if (blocks == 0) {
G_PRINTF("partition must be at least 1 block\n");
return zx::error(ZX_ERR_INVALID_ARGS);
}
uint64_t first = offset;
uint64_t last = first + blocks - 1;
// check range
if (last < first || first < header_.first || last > header_.last) {
G_PRINTF("partition must be in range of usable blocks[%" PRIu64 ", %" PRIu64 "]\n",
header_.first, header_.last);
return zx::error(ZX_ERR_INVALID_ARGS);
}
// Ensure guids are valid
auto valid_guid = [](const uint8_t* guid) {
for (int i = 0; i < GPT_GUID_LEN; ++i) {
if (guid[i] != 0) {
return true;
}
}
return false;
};
if (!valid_guid(type) || !valid_guid(guid)) {
G_PRINTF("GUIDs must be nonzero");
return zx::error(ZX_ERR_INVALID_ARGS);
}
// check for overlap
uint32_t i;
std::optional<uint32_t> tail;
for (i = 0; i < kPartitionCount; i++) {
if (!partitions_[i]) {
tail = i;
break;
}
if (first <= partitions_[i]->last && last >= partitions_[i]->first) {
G_PRINTF("partition range overlaps\n");
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
}
if (!tail) {
G_PRINTF("too many partitions\n");
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
// find a free slot
gpt_partition_t* part = nullptr;
for (i = 0; i < kPartitionCount; i++) {
if (ptable_[i].first == 0 && ptable_[i].last == 0) {
part = &ptable_[i];
break;
}
}
assert(part);
// insert the new element into the list
partition_init(part, name, type, guid, first, last, flags);
partitions_[tail.value()] = part;
return zx::ok(i);
}
zx_status_t GptDevice::ClearPartition(uint64_t offset, uint64_t blocks) {
if (!valid_) {
G_PRINTF("partition header invalid, sync to generate a default header\n");
return ZX_ERR_WRONG_TYPE;
}
if (blocks == 0) {
G_PRINTF("must clear at least 1 block\n");
return ZX_ERR_NO_RESOURCES;
}
uint64_t first = offset;
uint64_t last = offset + blocks - 1;
if (last < first || first < header_.first || last > header_.last) {
G_PRINTF("must clear in the range of usable blocks[%" PRIu64 ", %" PRIu64 "]\n", header_.first,
header_.last);
return ZX_ERR_OUT_OF_RANGE;
}
char zero[blocksize_];
memset(zero, 0, sizeof(zero));
for (size_t i = first; i <= last; i++) {
off_t offset;
if (!safemath::CheckMul(blocksize_, i).Cast<off_t>().AssignIfValid(&offset)) {
return ZX_ERR_OUT_OF_RANGE;
}
block_client::Writer writer(*device_);
zx_status_t status = writer.Write(offset, blocksize_, zero);
if (status != ZX_OK) {
G_PRINTF("Failed to write to block %zu; errno: %d\n", i, status);
return ZX_ERR_IO;
}
}
return ZX_OK;
}
zx_status_t GptDevice::RemovePartition(const uint8_t* guid) {
// look for the entry in the partition list
uint32_t i;
for (i = 0; i < kPartitionCount; i++) {
if (!memcmp(partitions_[i]->guid, guid, sizeof(partitions_[i]->guid))) {
break;
}
}
if (i == kPartitionCount) {
G_PRINTF("partition not found\n");
return ZX_ERR_NOT_FOUND;
}
// clear the entry
memset(partitions_[i], 0, kEntrySize);
// pack the partition list
for (i = i + 1; i < kPartitionCount; i++) {
if (partitions_[i] == nullptr) {
partitions_[i - 1] = nullptr;
} else {
partitions_[i - 1] = partitions_[i];
}
}
return ZX_OK;
}
zx_status_t GptDevice::RemoveAllPartitions() {
memset(partitions_, 0, sizeof(partitions_));
return ZX_OK;
}
zx::result<gpt_partition_t*> GptDevice::GetPartitionPtr(uint32_t partition_index) const {
if (partition_index >= kPartitionCount)
return zx::error(ZX_ERR_OUT_OF_RANGE);
if (partitions_[partition_index] == nullptr)
return zx::error(ZX_ERR_NOT_FOUND);
return zx::ok(partitions_[partition_index]);
}
zx::result<gpt_partition_t*> GptDevice::GetPartition(uint32_t partition_index) {
return GetPartitionPtr(partition_index);
}
zx::result<const gpt_partition_t*> GptDevice::GetPartition(uint32_t partition_index) const {
return GetPartitionPtr(partition_index);
}
zx_status_t GptDevice::SetPartitionType(uint32_t partition_index, const uint8_t* type) {
zx::result<gpt_partition_t*> p = GetPartition(partition_index);
if (p.is_ok()) {
memcpy((*p)->type, type, GPT_GUID_LEN);
}
return p.status_value();
}
zx_status_t GptDevice::SetPartitionGuid(uint32_t partition_index, const uint8_t* guid) {
zx::result<gpt_partition_t*> p = GetPartition(partition_index);
if (p.is_ok()) {
memcpy((*p)->guid, guid, GPT_GUID_LEN);
}
return p.status_value();
}
zx_status_t GptDevice::SetPartitionVisibility(uint32_t partition_index, bool visible) {
zx::result<gpt_partition_t*> p = GetPartition(partition_index);
if (p.is_ok()) {
gpt::SetPartitionVisibility(*p, visible);
}
return p.status_value();
}
zx_status_t GptDevice::SetPartitionRange(uint32_t partition_index, uint64_t start, uint64_t end) {
zx::result<gpt_partition_t*> p = GetPartition(partition_index);
if (p.is_error()) {
return p.error_value();
}
uint64_t block_start, block_end;
if (zx_status_t status = Range(&block_start, &block_end); status != ZX_OK) {
return status;
}
if ((start < block_start) || (end > block_end) || (start >= end)) {
return ZX_ERR_INVALID_ARGS;
}
for (uint32_t idx = 0; idx < kPartitionCount; idx++) {
zx::result<const gpt_partition_t*> curr_partition = GetPartition(idx);
// skip this partition and non-existent partitions
if ((idx == partition_index) || (curr_partition.is_error())) {
continue;
}
// skip partitions we don't intersect
if ((start > (*curr_partition)->last) || (end < (*curr_partition)->first)) {
continue;
}
return ZX_ERR_OUT_OF_RANGE;
}
(*p)->first = start;
(*p)->last = end;
return ZX_OK;
}
zx_status_t GptDevice::GetPartitionFlags(uint32_t partition_index, uint64_t* flags) const {
zx::result<const gpt_partition_t*> p = GetPartition(partition_index);
if (p.is_ok()) {
*flags = (*p)->flags;
}
return p.status_value();
}
// TODO(auradkar): flags are unckecked for invalid flags
zx_status_t GptDevice::SetPartitionFlags(uint32_t partition_index, uint64_t flags) {
zx::result<gpt_partition_t*> p = GetPartition(partition_index);
if (p.is_ok()) {
(*p)->flags = flags;
}
return p.status_value();
}
void GptDevice::GetHeaderGuid(uint8_t (*disk_guid_out)[GPT_GUID_LEN]) const {
memcpy(disk_guid_out, header_.guid, GPT_GUID_LEN);
}
block_client::BlockDevice& GptDevice::device() { return *device_; }
} // namespace gpt