|  | // Copyright 2017 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_FVM_FORMAT_H_ | 
|  | #define SRC_STORAGE_FVM_FORMAT_H_ | 
|  |  | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <array> | 
|  | #include <iosfwd> | 
|  | #include <limits> | 
|  | #include <string> | 
|  | #include <type_traits> | 
|  |  | 
|  | #include <fbl/algorithm.h> | 
|  | #include <gpt/gpt.h> | 
|  |  | 
|  | #include "src/lib/digest/digest.h" | 
|  |  | 
|  | // FVM is a virtual volume manager that allows partitions inside of it to be dynamically sized. It | 
|  | // allocates data to partitions in units of fixed-size "slices". | 
|  | // | 
|  | // It maintains two copies of its data so there is a previous version to roll-back to in case of | 
|  | // corruption. The latest one is determined by looking at the generation numbers stored in the | 
|  | // headers (see Header::generation). | 
|  | // | 
|  | // There are two main structures which follow the header: the partition table contains the | 
|  | // information for each virtual partition, and the allocation table maps which slice of the device | 
|  | // is allocated to which partition. | 
|  | // | 
|  | // It's possible for the FVM partition to be smaller than the underlying physical device. The | 
|  | // current size used by FVM is stored in |fvm_partition_size| (the header does not store the size of | 
|  | // the underlying device). As long as the allocation table has enough entries, the FVM partition may | 
|  | // be dynamically expanded to use more of the underlying device. | 
|  | // | 
|  | //             +---------------------------------- | 
|  | // Block 0  -> | Header (primary) | 
|  | //             | <padding> | 
|  | //             +---------------------------------- | 
|  | // Block 1  -> | Partition table (starts at block boundary) | 
|  | //       .     | | 
|  | //       .     |   ... Table of VPartitionEntry ... | 
|  | //       .     | | 
|  | //             +---------------------------------- | 
|  | // Block X  -> | Allocation table (starts at block boundary) | 
|  | //       .     | | 
|  | //       .     |   ... Table of SliceEntry[pslice_count] ... | 
|  | //       .     | | 
|  | //             | <padding to next block boundary> | 
|  | // Block Y  -> +----------------------------------     <- Metadata used bytes (block boundary). | 
|  | //       .     | | 
|  | //       .     |   <unused allocation table space> | 
|  | //       .     | | 
|  | //             +==================================     <- Metadata allocated bytes (block boundary). | 
|  | // Block Z  -> | Header (secondary, starts at block boundary) | 
|  | //             +---------------------------------- | 
|  | //             | Partition table (secondary) | 
|  | //             +---------------------------------- | 
|  | //             | Allocation table (secondary) | 
|  | //             +================================== | 
|  | //             | Physical slice "1" data... | 
|  |  | 
|  | namespace fvm { | 
|  |  | 
|  | // Unique identifier mapped to a GPT partition that contains a FVM. | 
|  | static constexpr uint64_t kMagic = 0x54524150204d5646; | 
|  |  | 
|  | // Current version of the format. The major version determines backwards-compatibility. The minor | 
|  | // version can be freely incremented at any time and does not impact backwards-compatibility; the | 
|  | // more often it is updated, the more granularly we can find out what the oldest driver was that | 
|  | // has touched a device. | 
|  | // | 
|  | // Minimally, the minor version should be incremented whenever a (backwards-compatible) format | 
|  | // change is made, but it can also be incremented when major logic changes are made in case there is | 
|  | // chance of bugs being introduced and we would like to be able to detect if the filesystem has been | 
|  | // touched by a potentially buggy driver. The kCurrentMinorVersion is used to updated the | 
|  | // oldest_minor_version field in the header when it is opened. | 
|  | // | 
|  | // See //src/storage/docs/versioning.md for more. | 
|  | // | 
|  | // ************************************************************************************************* | 
|  | // | 
|  | // IMPORTANT: When changing either kCurrentMajorVersion or kCurrentMinorVersion: | 
|  | // | 
|  | //   * Update //third_party/cobalt_config/fuchsia/local_storage/versions.txt | 
|  | //     (submission order does not matter). | 
|  | // | 
|  | //   * Update //src/storage/fvm/README.md with what changed. | 
|  | // | 
|  | // ************************************************************************************************* | 
|  | static constexpr uint64_t kCurrentMajorVersion = 1; | 
|  | static constexpr uint64_t kCurrentMinorVersion = 1; | 
|  |  | 
|  | // Defines the block size of that the FVM driver exposes. This need not be the block size of the | 
|  | // underlying device. | 
|  | static constexpr uint64_t kBlockSize = 8192; | 
|  |  | 
|  | // One past the maximum number of virtual partitions that can be created. | 
|  | // Valid partitions indices range from 1 to 1023. | 
|  | // TODO(fxb/59980) make this consistent so we can use the whole table. Either use 0-1023 as the | 
|  | // valid partition range, or 1-1024. | 
|  | static constexpr uint64_t kMaxVPartitions = 1024; | 
|  | static constexpr uint64_t kMaxUsablePartitions = kMaxVPartitions - 1; | 
|  |  | 
|  | // Maximum size for a partition GUID. | 
|  | static constexpr size_t kGuidSize = GPT_GUID_LEN; | 
|  |  | 
|  | // Maximum string length for virtual partition GUID. | 
|  | static constexpr size_t kGuidStrLen = GPT_GUID_STRLEN; | 
|  |  | 
|  | // Maximum length allowed for a virtual partition name. | 
|  | static constexpr size_t kMaxVPartitionNameLength = 24; | 
|  |  | 
|  | // Number of bits required for the VSlice address space. | 
|  | static constexpr uint64_t kSliceEntryVSliceBits = 32; | 
|  |  | 
|  | // Number of bits required for the VPartition address space. | 
|  | static constexpr uint64_t kSliceEntryVPartitionBits = 16; | 
|  |  | 
|  | // Maximum number of VSlices that can be addressed. | 
|  | static constexpr uint64_t kMaxVSlices = 1ull << (kSliceEntryVSliceBits - 1); | 
|  |  | 
|  | // The maximum slice size should be such that it never overflows the address space. | 
|  | static constexpr uint64_t kMaxSliceSize = std::numeric_limits<uint64_t>::max() / kMaxVSlices; | 
|  |  | 
|  | // Provides a placeholder instange GUID, that will be updated when a driver loads a partition with | 
|  | // such instance GUID. | 
|  | static constexpr std::array<uint8_t, kGuidSize> kPlaceHolderInstanceGuid = {0}; | 
|  |  | 
|  | // Type GUID for an internally defined partition used to reserve slices for future uses. | 
|  | static constexpr std::array<uint8_t, kGuidSize> kReservedPartitionTypeGuid = { | 
|  | 0xa0, 0x44, 0xe4, 0x75, 0xaa, 0x6b, 0xe5, 0xf3, 0x52, 0x4b, 0xf3, 0x67, 0x81, 0x61, 0xf9, 0xce, | 
|  | }; | 
|  |  | 
|  | namespace internal { | 
|  |  | 
|  | // FVM block alignment properties for a given type. | 
|  | template <typename T> | 
|  | struct block_alignment { | 
|  | static constexpr bool may_cross_boundary = (kBlockSize % sizeof(T)) != 0; | 
|  | static constexpr bool ends_at_boundary = (sizeof(T) % kBlockSize); | 
|  | }; | 
|  |  | 
|  | }  // namespace internal | 
|  |  | 
|  | // Defines the type of superblocks of an FVM. The key difference is how the offset from the | 
|  | // beginning is calculated. They both share the same format. | 
|  | enum class SuperblockType { | 
|  | kPrimary, | 
|  | kSecondary, | 
|  | }; | 
|  |  | 
|  | // FVM header which describes the contents and layout of the volume manager. | 
|  | struct Header { | 
|  | // Creates headers representing structures with the given information. FVM allows for growth, so | 
|  | // the "growable" variants takes the initial size as well as the max size that the metadata tables | 
|  | // can represent to allow for future growth up until that limit. FromDiskSize()/FromSliceCount() | 
|  | // will not necessarily allow for future growth (table padding up to a block boundary may allow | 
|  | // some growth). | 
|  | // | 
|  | // The partition and slice tables will end up both being one larger than the number of usable | 
|  | // entries passed in to this function. So don't pass even numbers to make the tables | 
|  | // nicely aligned, pass one less. | 
|  | // TODO(fxb/59980) make table_size == usable_count. | 
|  | // | 
|  | // Currently the partition table size is fixed, so usable_partitions must be kMaxUsablePartitions | 
|  | // or it will assert. | 
|  | // TODO(fxb/40192): Allow usable partitions to vary. | 
|  | static Header FromDiskSize(size_t usable_partitions, size_t disk_size, size_t slice_size); | 
|  | static Header FromGrowableDiskSize(size_t usable_partitions, size_t initial_disk_size, | 
|  | size_t max_disk_size, size_t slice_size); | 
|  | static Header FromSliceCount(size_t usable_partitions, size_t usable_slices, size_t slice_size); | 
|  | static Header FromGrowableSliceCount(size_t usable_partitions, size_t initial_usable_slices, | 
|  | size_t max_usable_slices, size_t slice_size); | 
|  |  | 
|  | // Sets the number of slices. | 
|  | //   0 <= usable_slices <= GetAllocationTableAllocatedEntryCount(); | 
|  | // This will update the pslice_count as well as the fvm_partition_size accordingly. | 
|  | void SetSliceCount(size_t usable_slices); | 
|  |  | 
|  | // Getters --------------------------------------------------------------------------------------- | 
|  |  | 
|  | // Validates the header. This does NOT check the hash because that covers the entire metadata | 
|  | // which is not available to just the header class. | 
|  | // | 
|  | // The disk size is passed to validate that the specified disk image does not overflow the device. | 
|  | // To disable checking, pass std::numeric_limits<uint64_t>::max() for the disk_size. | 
|  | // | 
|  | // The underlying device's disk_block_size is passed to ensure that it is a multiple of of the | 
|  | // slice size. To disable checking this, pass fvm::kBlockSize for the device block size. | 
|  | // | 
|  | // A description of the error (if present) will be placed into the given output parameter | 
|  | // (required). | 
|  | bool IsValid(uint64_t disk_size, uint64_t disk_block_size, std::string& out_err) const; | 
|  |  | 
|  | // Like IsValid but perfoms the minimal possible validation only on the partition and allocation | 
|  | // table sizes. This does not check any other data, even the signature. | 
|  | // | 
|  | // This function is useful when reading the primary superblock and we need to find the secondary | 
|  | // superblock. The secondary header is located after the allocation and partition tables, so the | 
|  | // primary header must have valid sizes for these tables before we can consider which variant is | 
|  | // valid. | 
|  | bool HasValidTableSizes(std::string& out_err) const; | 
|  |  | 
|  | // The partition table always starts at a block offset, and is always a multiple of blocks | 
|  | // long in bytes. | 
|  | // | 
|  | // Partition IDs count from 1 but the partition table is indexed from 0, leaving an unused | 
|  | // VPartitionEntry at the beginning of this table. The "entry count" is therefore one less than | 
|  | // the size of the table. | 
|  | size_t GetPartitionTableOffset() const; | 
|  | size_t GetPartitionTableEntryCount() const; | 
|  | size_t GetPartitionTableByteSize() const; | 
|  |  | 
|  | // Returns the offset of the given VPartitionEntry struct from the beginning of the Header. The | 
|  | // valid input range is: | 
|  | //   0 < index <= GetPartitionTableEntryCount() | 
|  | size_t GetPartitionEntryOffset(size_t index) const; | 
|  |  | 
|  | // The allocation table begins on a block boundary after the partition table. It has a "used" | 
|  | // portion which are available for use by partitions (though they may not be used yet). Then it | 
|  | // has a potentially-larger "allocated" portion which allows FVM to grow assuming the underlying | 
|  | // device has room. | 
|  | size_t GetAllocationTableOffset() const; | 
|  | size_t GetAllocationTableUsedEntryCount() const; | 
|  | size_t GetAllocationTableUsedByteSize() const; | 
|  | size_t GetAllocationTableAllocatedEntryCount() const; | 
|  | size_t GetAllocationTableAllocatedByteSize() const; | 
|  |  | 
|  | // Computes the number of allocation table entries that are needed for the given disk size, | 
|  | // capping it at the maximum number representable using the current allocation table size. | 
|  | // This is used when growing the FVM partition. | 
|  | size_t GetMaxAllocationTableEntriesForDiskSize(size_t disk_size) const; | 
|  |  | 
|  | // Byte offset from the beginning of the Header to the allocation table entry (SliceEntry struct) | 
|  | // with the given ID. Physical slice IDs are 1-based so valid input is: | 
|  | //   1 <= pslice <= pslice_count | 
|  | // To get the actual slice data, see GetSliceOffset(). | 
|  | size_t GetSliceEntryOffset(size_t pslice) const; | 
|  |  | 
|  | // The metadata counts the header, partition table, and allocation table. The allocation table | 
|  | // may contain unused entries (allowing FVM to grow as long as there is space on the underlying | 
|  | // device), in which case the used bytes will be less than the allocated bytes. | 
|  | // | 
|  | // When the Header is default-initialized and the disk or slice size is 0, the metadata size | 
|  | // is defined to be 0. | 
|  | size_t GetMetadataUsedBytes() const; | 
|  | size_t GetMetadataAllocatedBytes() const; | 
|  |  | 
|  | // Returns the offset in bytes of the given superblock from the beginning of the device. | 
|  | size_t GetSuperblockOffset(SuperblockType type) const; | 
|  |  | 
|  | // Returns the offset in the device (counting both copies of the metadata) where the first slice | 
|  | // data starts. | 
|  | size_t GetDataStartOffset() const; | 
|  |  | 
|  | // Byte offset from the beginning of the device to the slice data. Physical slice IDs are 1-based | 
|  | // so valid input is: | 
|  | //   1 <= pslice <= pslice_count | 
|  | // This gets the actual slice data. To get the slice's allocation table entry, see | 
|  | // GetSliceEntryOffset(). | 
|  | size_t GetSliceDataOffset(size_t pslice) const; | 
|  |  | 
|  | // Returns a multi-line stringified representation of the header, useful for debugging. See also | 
|  | // "operator<<" for a more compact representation. | 
|  | std::string ToString() const; | 
|  |  | 
|  | // Data ------------------------------------------------------------------------------------------ | 
|  |  | 
|  | // Unique identifier for this format type. Expected to be kMagic. | 
|  | uint64_t magic = kMagic; | 
|  |  | 
|  | // Major version, If this is larger than kCurrentMajorVersion the driver must not access the data. | 
|  | // See notes above the version constants above. | 
|  | uint64_t major_version = kCurrentMajorVersion; | 
|  |  | 
|  | // The number of physical slices which can be addressed and allocated by the virtual parititons. | 
|  | // This is the number of slices that will fit in the current fvm_partition_size, minus the size | 
|  | // of the two copies of the metadata at the beginning of the device. | 
|  | // | 
|  | // Physical slices are 1-indexed (0 means "none"). Therefore: | 
|  | //   1 <= |maximum valid pslice| <= pslice_count | 
|  | // | 
|  | // IMPORTANT NOTE: Due to fxbug.dev/59980, this value is one less than the number of entries worth | 
|  | // of space in the allocation table because there is an unused 0 entry. | 
|  | uint64_t pslice_count = 0; | 
|  |  | 
|  | // Size of the each slice in bytes. Must be a multiple of kBlockSize. | 
|  | uint64_t slice_size = 0; | 
|  |  | 
|  | // Current size of the volume the fvm described by this header. This might be smaller than the | 
|  | // size of the underlying device (see comments at the top of the file). Must be a multiple of | 
|  | // kBlockSize. | 
|  | uint64_t fvm_partition_size = 0; | 
|  |  | 
|  | // Size in bytes of the partition table of the superblock the header describes. Must be a | 
|  | // multiple of kBlockSize. | 
|  | // | 
|  | // Currently this is fixed to be the size required to hold exactly kMaxVPartitions and various | 
|  | // code assumes this constant. | 
|  | // TODO(fxbug.dev/40192): Use this value so the partition table can have different sizes. | 
|  | uint64_t vpartition_table_size = 0; | 
|  |  | 
|  | // Size in bytes reserved for the allocation table. Must be a multiple of kBlockSize. This | 
|  | // includes extra space allowing the fvm to grow as the underlying volume grows. The | 
|  | // currently-used allocation table size, which defines the number of slices that can be addressed, | 
|  | // is derived from |pslice_count|. | 
|  | uint64_t allocation_table_size = 0; | 
|  |  | 
|  | // Use to determine over two copies(primary, secondary) of superblock, which one is the latest | 
|  | // one. This is incremented for each metadata change so the valid metadata with the largest | 
|  | // generation is the one to use. | 
|  | uint64_t generation = 0; | 
|  |  | 
|  | // Integrity check of the entire metadata (one copy). When computing the hash (use | 
|  | // fvm::UpdateHash() to compute), this field is is considered to be 0-filled. | 
|  | uint8_t hash[digest::kSha256Length] = {0}; | 
|  |  | 
|  | // The oldest minor version corresponding to the kCurrentMinorVersion of the software that has | 
|  | // written to this FVM image. When opening for writes, the driver should check this and lower it | 
|  | // if the current revision is lower than the one stored in this header. This does not say anything | 
|  | // about backwards-compatibility, that is determined by major_version above. | 
|  | // | 
|  | // See //src/storage/docs/versioning.md for more. | 
|  | uint64_t oldest_minor_version = kCurrentMinorVersion; | 
|  |  | 
|  | // Fill remainder of the block. | 
|  | uint8_t reserved[0]; | 
|  | }; | 
|  |  | 
|  | // Outputs a compact, single-line description of the header (useful for syslog). See | 
|  | // Header.ToString() for a more verbose multiline version. | 
|  | std::ostream& operator<<(std::ostream& out, const Header& header); | 
|  |  | 
|  | static_assert(std::is_standard_layout_v<Header> && sizeof(Header) <= kBlockSize, | 
|  | "fvm::Header must fit within one block and match standard layout."); | 
|  |  | 
|  | // Represent an entry in the FVM Partition table, which is fixed size contiguous flat buffer. | 
|  | struct VPartitionEntry { | 
|  | VPartitionEntry() = default; | 
|  |  | 
|  | // The name must not contain embedded nulls (this code will assert if it does). The name will | 
|  | // be truncated to kMaxVPartitionNameLength characters. | 
|  | // | 
|  | // See StringFromArray() to create the name parmeter if you're creating from another fixed-length | 
|  | // buffer. | 
|  | // | 
|  | // The format of the flags is not user-controllable since the flag values are not exposed in the | 
|  | // header. Pass either 0 for default, or copy from another VPartitionEntry. | 
|  | VPartitionEntry(const uint8_t type[kGuidSize], const uint8_t guid[kGuidSize], uint32_t slices, | 
|  | std::string name, uint32_t flags = 0); | 
|  |  | 
|  | // Creates an entry which is used to retain slices for future use. | 
|  | static VPartitionEntry CreateReservedPartition(); | 
|  |  | 
|  | // Creates a string from the given possibly-null-terminated fixed-length buffer. If there is a | 
|  | // null terminator it will indicate the end of the string. if there is none, the string will use | 
|  | // up the entire input buffer. | 
|  | template <size_t N> | 
|  | static std::string StringFromArray(const uint8_t (&array)[N]) { | 
|  | return std::string( | 
|  | reinterpret_cast<const char*>(array), | 
|  | std::distance(std::begin(array), std::find(std::begin(array), std::end(array), 0))); | 
|  | } | 
|  |  | 
|  | // Returns the allowed set of flags in |raw_flags|. | 
|  | static uint32_t MaskInvalidFlags(uint32_t raw_flags); | 
|  |  | 
|  | // Returns true if the entry is allocated. | 
|  | bool IsAllocated() const; | 
|  |  | 
|  | // Returns true if the entry is free. | 
|  | bool IsFree() const; | 
|  |  | 
|  | // Releases the partition, marking it as free. | 
|  | void Release(); | 
|  |  | 
|  | // Returns true if the partition is should be treated as active.. | 
|  | bool IsActive() const; | 
|  |  | 
|  | // Returns true if the partition is flagged as inactive. | 
|  | bool IsInactive() const; | 
|  |  | 
|  | // Returns true if the partition is an internal one used for reserving slices for future uses. | 
|  | bool IsInternalReservationPartition() const; | 
|  |  | 
|  | // Marks this entry active status as |is_active|. | 
|  | void SetActive(bool is_active); | 
|  |  | 
|  | std::string name() const { return StringFromArray(unsafe_name); } | 
|  | void set_name(std::string_view name) { | 
|  | std::fill(std::copy_n(name.begin(), std::min(name.size(), kMaxVPartitionNameLength), | 
|  | std::begin(unsafe_name)), | 
|  | std::end(unsafe_name), 0); | 
|  | } | 
|  |  | 
|  | // Mirrors GPT value. | 
|  | uint8_t type[kGuidSize] = {0}; | 
|  |  | 
|  | // Mirrors GPT value. | 
|  | uint8_t guid[kGuidSize] = {0}; | 
|  |  | 
|  | // Number of allocated slices. | 
|  | uint32_t slices = 0u; | 
|  |  | 
|  | // Used internally by the mutator/accessor functions above. | 
|  | uint32_t flags = 0u; | 
|  |  | 
|  | // Partition name. The name is counted until the first null byte or the end of the static buffer | 
|  | // (it is not null terminated in this case). Prefer to use the name() accessor above which | 
|  | // abstracts this handling. | 
|  | uint8_t unsafe_name[kMaxVPartitionNameLength] = {0}; | 
|  | }; | 
|  |  | 
|  | // Outputs a single-line description of the partition entry. | 
|  | std::ostream& operator<<(std::ostream& out, const VPartitionEntry& entry); | 
|  |  | 
|  | static_assert(sizeof(VPartitionEntry) == 64, "Unchecked VPartitionEntry size change."); | 
|  | static_assert(std::is_standard_layout_v<VPartitionEntry>, | 
|  | "VPartitionEntry must be standard layout compilant and trivial."); | 
|  | static_assert(!internal::block_alignment<VPartitionEntry>::may_cross_boundary, | 
|  | "VPartitionEntry must not cross block boundary."); | 
|  | static_assert(!internal::block_alignment<VPartitionEntry[kMaxVPartitions]>::ends_at_boundary, | 
|  | "VPartitionEntry table max size must end at block boundary."); | 
|  |  | 
|  | // A Slice Entry represents the allocation of a slice. | 
|  | // | 
|  | // Slice Entries are laid out in an array on disk. The index into this array determines the | 
|  | // "physical slice" being accessed, where physical slices consist of all disk space immediately | 
|  | // following the FVM metadata on an FVM partition. | 
|  | struct SliceEntry { | 
|  | SliceEntry() = default; | 
|  | SliceEntry(uint64_t vpartition, uint64_t vslice); | 
|  |  | 
|  | // Returns true if this slice is assigned to a partition. | 
|  | bool IsAllocated() const; | 
|  |  | 
|  | // Returns true if this slice is unassigned. | 
|  | bool IsFree() const; | 
|  |  | 
|  | // Resets the slice entry, marking it as Free. | 
|  | void Release(); | 
|  |  | 
|  | // Returns the |vpartition| that owns this slice. | 
|  | uint64_t VPartition() const; | 
|  |  | 
|  | // Returns the |vslice| of this slice. This represents the relative order of the slices | 
|  | // assigned to |vpartition|. This is, the block device exposed to |partition| sees an array of | 
|  | // all slices assigned to it, sorted by |vslice|. | 
|  | uint64_t VSlice() const; | 
|  |  | 
|  | // Sets the contents of the slice entry to |partition| and |slice|. | 
|  | void Set(uint64_t vpartition, uint64_t vslice); | 
|  |  | 
|  | // Packed entry, the format must remain obscure to the user. | 
|  | uint64_t data = 0u; | 
|  | }; | 
|  |  | 
|  | // Outputs a single-line description of the partition entry. | 
|  | std::ostream& operator<<(std::ostream& out, const SliceEntry& entry); | 
|  |  | 
|  | static_assert(sizeof(SliceEntry) == 8, "Unchecked SliceEntry size change."); | 
|  | static_assert(std::is_standard_layout_v<SliceEntry>, "VSliceEntry must be standard layout."); | 
|  | static_assert(!internal::block_alignment<SliceEntry>::may_cross_boundary, | 
|  | "VSliceEntry must not cross block boundary."); | 
|  |  | 
|  | // Due to the way partitions are counted, the usable partitions (the input to this function) is one | 
|  | // smaller than the number table entries because the 0th entry is not used. This function may | 
|  | // generate a larger-than-needed partition table to ensure it's block-aligned. | 
|  | // | 
|  | // TODO(fxb/59980) Remove the unused 0th entry so we can actually use the full number passed in. | 
|  | constexpr size_t PartitionTableByteSizeForUsablePartitionCount(size_t usable_partitions) { | 
|  | // This multiply shouldn't overflow because usable_partitions should always be <= | 
|  | // kMaxUsablePartitions. | 
|  | return fbl::round_up(sizeof(VPartitionEntry) * (usable_partitions + 1), kBlockSize); | 
|  | } | 
|  |  | 
|  | constexpr size_t AllocTableByteSizeForUsableSliceCount(size_t slice_count) { | 
|  | // Reserve the 0th table entry so need +1 to get the usable slices. | 
|  | // | 
|  | // This multiply shouldn't overflow since slice_count should be <= kMaxVSlices which leaves many | 
|  | // spare bits. | 
|  | return fbl::round_up(sizeof(SliceEntry) * (slice_count + 1), kBlockSize); | 
|  | } | 
|  |  | 
|  | constexpr size_t BlocksToSlices(size_t slice_size, size_t block_size, size_t block_count) { | 
|  | if (slice_size == 0 || slice_size < block_size) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | const size_t kBlocksPerSlice = slice_size / block_size; | 
|  | return (block_count + kBlocksPerSlice - 1) / kBlocksPerSlice; | 
|  | } | 
|  |  | 
|  | constexpr size_t SlicesToBlocks(size_t slice_size, size_t block_size, size_t slice_count) { | 
|  | // This multiply shouldn't overflow because the slice size and slice count show always be capped | 
|  | // to the max values (which were picked to avoid overflow). | 
|  | return slice_count * slice_size / block_size; | 
|  | } | 
|  |  | 
|  | // Limits on the maximum metadata sizes. These value are to prevent overallocating buffers if | 
|  | // the metadata is corrupt. The metadata size counts one copy of the metadata only (there will | 
|  | // actually be two copies at the beginning of the device. | 
|  | static constexpr uint64_t kMaxPartitionTableByteSize = | 
|  | PartitionTableByteSizeForUsablePartitionCount(kMaxUsablePartitions); | 
|  | static constexpr uint64_t kMaxAllocationTableByteSize = | 
|  | fbl::round_up(sizeof(SliceEntry) * kMaxVSlices, kBlockSize); | 
|  | static constexpr uint64_t kMaxMetadataByteSize = | 
|  | kBlockSize + kMaxPartitionTableByteSize + kMaxAllocationTableByteSize; | 
|  |  | 
|  | inline void Header::SetSliceCount(size_t usable_slices) { | 
|  | pslice_count = usable_slices; | 
|  | fvm_partition_size = GetDataStartOffset() + usable_slices * slice_size; | 
|  | } | 
|  |  | 
|  | inline size_t Header::GetPartitionTableOffset() const { | 
|  | // The partition table starts at the first block after the header. | 
|  | return kBlockSize; | 
|  | } | 
|  |  | 
|  | inline size_t Header::GetPartitionTableEntryCount() const { | 
|  | // The partition table has an unused 0th entry, so the number of usable entries in the table | 
|  | // is one less than that. | 
|  | // TODO(fxb/59980) the partition table is 0-indexed (with the 0th entry not used) while the | 
|  | // valid indices start from 1 (0 means invalid). This should be more consistent to remove the | 
|  | // unused entry. | 
|  | // Currently we expect the partition table count and size to be constant. | 
|  | // TODO(bug 40192): Derive this from the header so we can have different sizes. | 
|  | return kMaxVPartitions - 1; | 
|  | } | 
|  |  | 
|  | inline size_t Header::GetPartitionTableByteSize() const { | 
|  | // Currently we expect the partition table count and size to be constant. | 
|  | // TODO(bug 40192): Derive this from the header so we can have different sizes. | 
|  | return sizeof(VPartitionEntry) * kMaxVPartitions; | 
|  | } | 
|  |  | 
|  | inline size_t Header::GetPartitionEntryOffset(size_t index) const { | 
|  | return GetPartitionTableOffset() + index * sizeof(VPartitionEntry); | 
|  | } | 
|  |  | 
|  | inline size_t Header::GetAllocationTableOffset() const { | 
|  | // The allocation table follows the partition table immediately. | 
|  | return GetPartitionTableOffset() + GetPartitionTableByteSize(); | 
|  | } | 
|  |  | 
|  | inline size_t Header::GetAllocationTableUsedEntryCount() const { return pslice_count; } | 
|  |  | 
|  | inline size_t Header::GetAllocationTableUsedByteSize() const { | 
|  | // Ensure the used allocation table byte size is always on a multiple of the block suize. | 
|  | return fbl::round_up(sizeof(SliceEntry) * GetAllocationTableUsedEntryCount(), kBlockSize); | 
|  | } | 
|  |  | 
|  | inline size_t Header::GetAllocationTableAllocatedEntryCount() const { | 
|  | // The "-1" here allows for the unused 0 indexed slice. | 
|  | // TODO(fxbug.dev/59980) the allocation table is 0-indexed (with the 0th entry not used) while the | 
|  | // allocation data itself is 1-indexed. This inconsistency should be fixed. | 
|  | if (size_t byte_size = GetAllocationTableAllocatedByteSize(); byte_size > 0) | 
|  | return byte_size / sizeof(SliceEntry) - 1; | 
|  | return 0;  // Don't underflow if the table is empty. | 
|  | } | 
|  |  | 
|  | inline size_t Header::GetAllocationTableAllocatedByteSize() const { return allocation_table_size; } | 
|  |  | 
|  | inline size_t Header::GetMaxAllocationTableEntriesForDiskSize(size_t disk_size) const { | 
|  | size_t start_offset = GetDataStartOffset(); | 
|  | if (slice_size == 0 || disk_size <= start_offset) | 
|  | return 0;  // Prevent underflow. | 
|  |  | 
|  | // See how many slices fit in in the non-metadata portion of the device. | 
|  | size_t requested_slices = (disk_size - start_offset) / slice_size; | 
|  |  | 
|  | // That value may be limited by the maximum number of entries in the allocation table. | 
|  | return std::min(requested_slices, GetAllocationTableAllocatedEntryCount()); | 
|  | } | 
|  |  | 
|  | inline size_t Header::GetSliceEntryOffset(size_t pslice) const { | 
|  | // TODO(fxbug.dev/59980) the allocation table is 0-indexed (with the 0th entry not used) while the | 
|  | // allocation data itself is 1-indexed. This inconsistency should be fixed, | 
|  | return GetAllocationTableOffset() + pslice * sizeof(SliceEntry); | 
|  | } | 
|  |  | 
|  | inline size_t Header::GetMetadataUsedBytes() const { | 
|  | if (fvm_partition_size == 0 || slice_size == 0) | 
|  | return 0;  // Uninitialized header. | 
|  |  | 
|  | // The used metadata ends after the used portion of the allocation table. | 
|  | // | 
|  | // This addition won't overflow when the allocation and partition tables are <= their max sizes. | 
|  | return GetAllocationTableOffset() + GetAllocationTableUsedByteSize(); | 
|  | } | 
|  |  | 
|  | inline size_t Header::GetMetadataAllocatedBytes() const { | 
|  | if (fvm_partition_size == 0 || slice_size == 0) | 
|  | return 0;  // Uninitialized header. | 
|  |  | 
|  | // The metadata ends after the allocation table. | 
|  | // | 
|  | // This addition won't overflow when the allocation and partition tables are <= their max sizes. | 
|  | return GetAllocationTableOffset() + GetAllocationTableAllocatedByteSize(); | 
|  | } | 
|  |  | 
|  | inline size_t Header::GetSuperblockOffset(SuperblockType type) const { | 
|  | return (type == SuperblockType::kPrimary) ? 0 : GetMetadataAllocatedBytes(); | 
|  | } | 
|  |  | 
|  | inline size_t Header::GetDataStartOffset() const { | 
|  | // The slice data starts after the two copies of metadata at beginning of device. | 
|  | return 2 * GetMetadataAllocatedBytes(); | 
|  | } | 
|  |  | 
|  | inline size_t Header::GetSliceDataOffset(size_t pslice) const { | 
|  | // The pslice is 1-based and the array starts at the "data start". | 
|  | return GetDataStartOffset() + (pslice - 1) * slice_size; | 
|  | } | 
|  |  | 
|  | }  // namespace fvm | 
|  |  | 
|  | // Following describes the android sparse format | 
|  |  | 
|  | constexpr uint32_t kAndroidSparseHeaderMagic = 0xed26ff3a; | 
|  |  | 
|  | struct AndroidSparseHeader { | 
|  | const uint32_t kMagic = kAndroidSparseHeaderMagic; | 
|  | const uint16_t kMajorVersion = 0x1; | 
|  | const uint16_t kMinorVersion = 0x0; | 
|  | uint16_t file_header_size = 0; | 
|  | uint16_t chunk_header_size = 0; | 
|  | uint32_t block_size = 0; | 
|  | uint32_t total_blocks = 0; | 
|  | uint32_t total_chunks = 0; | 
|  | // CRC32 checksum of the original data, including dont-care chunk | 
|  | uint32_t image_checksum = 0; | 
|  | }; | 
|  |  | 
|  | enum AndroidSparseChunkType : uint16_t { | 
|  | kChunkTypeRaw = 0xCAC1, | 
|  | kChunkTypeFill = 0xCAC2, | 
|  | kChunkTypeDontCare = 0xCAC3, | 
|  | }; | 
|  |  | 
|  | struct AndroidSparseChunkHeader { | 
|  | AndroidSparseChunkType chunk_type; | 
|  | uint16_t reserved1 = 0; | 
|  | // In the unit of blocks | 
|  | uint32_t chunk_blocks = 0; | 
|  | // In the unit of bytes | 
|  | uint32_t total_size = 0; | 
|  | }; | 
|  |  | 
|  | #endif  // SRC_STORAGE_FVM_FORMAT_H_ |