blob: 952bae240619950bbc39c29cb1656898e2615e88 [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.
#ifndef SRC_STORAGE_GPT_INCLUDE_GPT_GPT_H_
#define SRC_STORAGE_GPT_INCLUDE_GPT_GPT_H_
#include <fidl/fuchsia.device/cpp/wire.h>
#include <fidl/fuchsia.hardware.block/cpp/wire.h>
#include <lib/fpromise/result.h>
#include <lib/zx/result.h>
#include <memory>
#include <gpt/c/gpt.h>
#ifdef __Fuchsia__
#include "src/storage/lib/block_client/cpp/block_device.h"
#endif
namespace gpt {
// GPT magic number.
constexpr uint64_t kMagicNumber = GPT_MAGIC;
// GPT version 1.0
constexpr uint32_t kRevision = 0x00010000;
// GPT expect fixed size header. Verify that size of gpt_header_t meets
// the standards.
constexpr uint32_t kHeaderSize = GPT_HEADER_SIZE;
static_assert(kHeaderSize == sizeof(gpt_header_t), "invalid gpt header size");
// A copy of GPT is always at block 1. Location of backup copy is pointed by
// field within gpt_header_t.
constexpr uint64_t kPrimaryHeaderStartBlock = 1;
// Block size is expected to be large enough to hold gpt_header_t. GPT
// entries array start in the next block i.e. 2.
constexpr uint64_t kPrimaryEntriesStartBlock = kPrimaryHeaderStartBlock + 1;
// Last block should contain the header for GPT backup copy.
constexpr uint64_t BackupHeaderStartBlock(uint64_t block_count) { return block_count - 1; }
// Maximum number of partitions supported.
constexpr uint32_t kPartitionCount = 128;
// Number of blocks required to hold gpt_header_t. This should be always 1.
constexpr uint32_t kHeaderBlocks = 1;
// Minimum supperted block size.
constexpr uint32_t kMinimumBlockSize = 512;
// Maximum supported blocks size. 1MiB.
constexpr uint32_t kMaximumBlockSize = 1 << 20;
// GPT expect fixed size entry structure. Verify that size of gpt_entry_t meets
// the standards.
constexpr uint32_t kEntrySize = GPT_ENTRY_SIZE;
static_assert(kEntrySize == sizeof(gpt_entry_t), "invalid gpt entry size");
// Maximum size of the partition entry table.
constexpr size_t kMaxPartitionTableSize = static_cast<size_t>(kPartitionCount) * kEntrySize;
// Size of array need to store "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
// There are other places where we use different macros to get this
// string length. Assert that we are in sync with the rest.
constexpr size_t kGuidStrLength = (2UL * GPT_GUID_LEN) + (4UL * sizeof('-')) + sizeof('\0');
static_assert(kGuidStrLength == GPT_GUID_STRLEN, "Guid print format changed");
// Size of null terminated char array to store non-utf16 GUID partition name.
constexpr uint64_t kGuidCNameLength = (GPT_NAME_LEN / 2) + 1;
// Maximum size, including null terminator, of a partition's name in UTF-8.
// It's at most 3 UTF-8 code units for every UTF-16 code unit. Code points > 0x10000 (which
// require 4 UTF-8 code units) get encoded as surrogate pairs in UTF-16.
constexpr size_t kMaxUtf8NameLen = ((GPT_NAME_LEN / sizeof(char16_t)) * 3) + 1;
constexpr uint32_t kGptDiffType = 0x01;
constexpr uint32_t kGptDiffGuid = 0x02;
constexpr uint32_t kGptDiffFirst = 0x04;
constexpr uint32_t kGptDiffLast = 0x08;
constexpr uint32_t kGptDiffFlags = 0x10;
constexpr uint32_t kGptDiffName = 0x20;
constexpr uint64_t kFlagHidden = 0x2;
// Returns the maximum size in bytes to hold header block and partition table.
constexpr fpromise::result<size_t, zx_status_t> MinimumBytesPerCopy(uint64_t block_size) {
if (block_size < kHeaderSize) {
return fpromise::error(ZX_ERR_INVALID_ARGS);
}
return fpromise::ok(block_size + kMaxPartitionTableSize);
}
// Returns the maximum blocks needed to hold header block and partition table.
constexpr fpromise::result<uint64_t, zx_status_t> MinimumBlocksPerCopy(uint64_t block_size) {
if (block_size < kHeaderSize) {
return fpromise::error(ZX_ERR_INVALID_ARGS);
}
return fpromise::ok((MinimumBytesPerCopy(block_size).value() + block_size - 1) / block_size);
}
// Returns the minimum blocks needed to hold two copies of GPT at appropriate
// offset (considering mbr block).
constexpr fpromise::result<uint64_t, zx_status_t> MinimumBlockDeviceSize(uint64_t block_size) {
if (block_size < kHeaderSize) {
return fpromise::error(ZX_ERR_INVALID_ARGS);
}
// There are two copies of GPT and a block for MBR(or such use).
return fpromise::ok(kPrimaryHeaderStartBlock + (2 * MinimumBlocksPerCopy(block_size).value()));
}
// Returns number of addressable blocks. On finding entry
// - nullptr, returns ZX_ERR_INVALID_ARGS
// - invalid, returns ZX_ERR_BAD_STATE
// - uninitialized, returns ZX_ERR_NOT_FOUND
fpromise::result<uint64_t, zx_status_t> EntryBlockCount(const gpt_entry_t* entry);
// Sets or clears partition visibility flag
void SetPartitionVisibility(gpt_partition_t* partition, bool visible);
// Returns true if partition's kHiddenFlag is not set i.e. partition
// is visible
bool IsPartitionVisible(const gpt_partition_t* partition);
// Returns a null terminated UTF-8 representation of the partition name.
zx::result<> GetPartitionName(const gpt_entry_t& entry, char* name, size_t capacity);
class GptDevice {
public:
#ifdef __Fuchsia__
static zx::result<std::unique_ptr<GptDevice>> Create(
std::unique_ptr<block_client::BlockDevice> device, uint32_t blocksize, uint64_t blocks);
#endif
// Loads gpt header and gpt entries array from |buffer| of length |size|
// belonging to "block device" with |blocks| number of blocks and each
// block of size |blocksize|. On finding valid header and entries, returns a GptDevice.
static zx::result<std::unique_ptr<GptDevice>> Load(const uint8_t* buffer, uint64_t size,
uint32_t blocksize, uint64_t blocks);
// returns true if the partition table on the device is valid
bool Valid() const { return valid_; }
// Returns the range of usable blocks within the GPT, from [block_start, block_end] (inclusive)
zx_status_t Range(uint64_t* block_start, uint64_t* block_end) const;
// Finds a contiguous series of |blocks| which are unallocated, returning the offset of the range
// in |out_offset|. Returns ZX_ERR_NO_SPACE if no free range that large exists.
zx_status_t FindFreeRange(uint64_t blocks, uint64_t* out_offset) const;
// Writes changes to partition table to the device. If the device does not contain valid
// GPT, a gpt header gets created. Sync doesn't nudge block device driver to rescan the
// partitions. So it is the caller's responsibility to rescan partitions for the changes
// if needed.
zx_status_t Sync();
// perform all checks and computations on the in-memory representation, but DOES
// NOT write it out to disk. To perform checks AND write to disk, use Sync
zx_status_t Finalize();
// Adds a partition to in-memory instance of GptDevice. The changes stay visible
// only to this instance. Needs a Sync to write the changes to the device.
// Returns the partition's index.
zx::result<uint32_t> AddPartition(const char* name, const uint8_t* type, const uint8_t* guid,
uint64_t offset, uint64_t blocks, uint64_t flags);
// Writes zeroed blocks at an arbitrary offset (in blocks) within the device.
//
// Can be used alongside gpt_partition_add to ensure a newly created partition
// will not read stale superblock data.
zx_status_t ClearPartition(uint64_t offset, uint64_t blocks);
// Removes a partition from in-memory instance of GptDevice. The changes stay visible
// only to this instace. Needs a Sync to write the changes to the device.
zx_status_t RemovePartition(const uint8_t* guid);
// Removes all partitions from in-memory instance of GptDevice. The changes stay visible
// only to this instace. Needs a Sync to write the changes to the device.
zx_status_t RemoveAllPartitions();
// given a gpt device, get the GUID for the disk
void GetHeaderGuid(uint8_t (*disk_guid_out)[GPT_GUID_LEN]) const;
// return true if partition# idx has been locally modified
zx_status_t GetDiffs(uint32_t idx, uint32_t* diffs) const;
// Returns mutable pointer to partition entry at given index, otherwise ZX_ERR_NOT_FOUND
// if the partition entry is nullptr, or ZX_ERR_OUT_OF_RANGE if the index is out of range.
zx::result<gpt_partition_t*> GetPartition(uint32_t partition_index);
// Returns const pointer to partition entry at given index, otherwise ZX_ERR_NOT_FOUND
// if the partition entry is nullptr, or ZX_ERR_OUT_OF_RANGE if the index is out of range.
zx::result<const gpt_partition_t*> GetPartition(uint32_t partition_index) const;
// Updates the type of partition at index partition_index
zx_status_t SetPartitionType(uint32_t partition_index, const uint8_t* type);
// Updates the guid(id) of partition at index partition_index
zx_status_t SetPartitionGuid(uint32_t partition_index, const uint8_t* guid);
// Makes partition visible if 'visible' is true
zx_status_t SetPartitionVisibility(uint32_t partition_index, bool visible);
// Changes partition's partitions start and end blocks. If there is conflict with
// either other partitions or the device, then returns non-ZX_OK value
zx_status_t SetPartitionRange(uint32_t partition_index, uint64_t start, uint64_t end);
// Returns current flags for partition at index partition_index
zx_status_t GetPartitionFlags(uint32_t partition_index, uint64_t* flags) const;
// Sets flags for partition at index partition_index
zx_status_t SetPartitionFlags(uint32_t partition_index, uint64_t flags);
// print out the GPT
void PrintTable() const;
// Return device's block size
uint64_t BlockSize() const { return blocksize_; }
uint32_t EntryCount() const {
if (!valid_) {
return kPartitionCount;
}
return header_.entries_count;
}
// Return number of bytes entries array occupies.
uint64_t EntryArraySize() const {
if (!valid_) {
return kMaxPartitionTableSize;
}
return (static_cast<uint64_t>(header_.entries_count) * kEntrySize);
}
// Return number of blocks that entries array occupies.
uint64_t EntryArrayBlockCount() const {
return ((EntryArraySize() + blocksize_ - 1) / blocksize_);
}
// Return total number of blocks in the device
uint64_t TotalBlockCount() const { return blocks_; }
#ifdef __Fuchsia__
block_client::BlockDevice& device();
#endif
private:
GptDevice() { valid_ = false; }
zx_status_t FinalizeAndSync(bool persist);
#ifdef __Fuchsia__
// Reads the partition table from the device, or reformats the device if no valid GPT is found.
static zx::result<std::unique_ptr<GptDevice>> Init(
std::unique_ptr<block_client::BlockDevice> device, uint32_t blocksize, uint64_t block_count);
#endif
zx_status_t LoadEntries(const uint8_t* buffer, uint64_t buffer_size, uint64_t block_count);
// Walks entries array and returns error if crc doesn't match or ValidateEntry returns error.
zx_status_t ValidateEntries(const uint8_t* buffer, uint64_t block_count) const;
zx::result<gpt_partition_t*> GetPartitionPtr(uint32_t partition_index) const;
// true if the partition table on the device is valid
bool valid_;
// pointer to a list of partitions
gpt_partition_t* partitions_[kPartitionCount] = {};
#ifdef __Fuchsia__
std::unique_ptr<block_client::BlockDevice> device_;
#endif
// block size in bytes
uint64_t blocksize_ = 0;
// number of blocks
uint64_t blocks_ = 0;
// header buffer, should be primary copy
gpt_header_t header_ = {};
// partition table buffer
gpt_partition_t ptable_[kPartitionCount] = {};
// copy of buffer from when last init'd or sync'd.
gpt_partition_t ptable_backup_[kPartitionCount] = {};
};
// On success returns initialized gpt header. On finding either |block_size| or
// |block_count| is not large enough, returns error.
fpromise::result<gpt_header_t, zx_status_t> InitializePrimaryHeader(uint64_t block_size,
uint64_t block_count);
// Validates gpt header. Each type of inconsistency leads to unique status code.
// The status can be used to print user friendly error messages.
zx_status_t ValidateHeader(const gpt_header_t* header, uint64_t block_count);
// A gpt entry can exists in three states
// - unused; where all fields are zeroed.
// - valid; field have sensible values;
// - error; one or more fields are in inconsistent state.
// Returns
// - true if the entry is valid
// - false if entry is unused
// - error if entry fields are inconsistent
fpromise::result<bool, zx_status_t> ValidateEntry(const gpt_entry_t* entry);
// Converts status returned by ValidateHeader to a human readable error message.
const char* HeaderStatusToCString(zx_status_t status);
} // namespace gpt
#endif // SRC_STORAGE_GPT_INCLUDE_GPT_GPT_H_