blob: 3ddb0c71c144e25eb33ef919f4591d0d53754543 [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.
// This file describes the on-disk format of MinFS
#ifndef SRC_STORAGE_MINFS_FORMAT_H_
#define SRC_STORAGE_MINFS_FORMAT_H_
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <zircon/assert.h>
#include <zircon/types.h>
#include <limits>
#include <fbl/algorithm.h>
namespace minfs {
// Type of a reference to block number, either absolute (able to index into disk directly) or
// relative to some entity (such as a file).
using blk_t = uint32_t;
// The type of an inode number, which may be used as an index into the inode table.
using ino_t = uint32_t;
// clang-format off
constexpr uint64_t kMinfsMagic0 = (0x002153466e694d21ULL);
constexpr uint64_t kMinfsMagic1 = (0x385000d3d3d3d304ULL);
// 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 that has
// touched a filesystem instance.
//
// 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 kBlobfsCurrentMinorVersion is used to updated the
// oldest_minor_version field in the header when it is opened.
//
// See //src/storage/docs/versioning.md for more.
constexpr uint32_t kMinfsCurrentMajorVersion = 9u;
constexpr uint32_t kMinfsCurrentMinorVersion = 2u;
constexpr ino_t kMinfsRootIno = 1;
constexpr uint32_t kMinfsFlagClean = 0x00000001; // Currently unused,
constexpr uint32_t kMinfsFlagFVM = 0x00000002; // Mounted on FVM.
constexpr uint32_t kMinfsBlockSize = 8192;
constexpr uint32_t kMinfsBlockBits = (kMinfsBlockSize * 8);
constexpr uint32_t kMinfsInodeSize = 256;
constexpr uint32_t kMinfsInodesPerBlock = (kMinfsBlockSize / kMinfsInodeSize);
constexpr uint32_t kMinfsDirect = 16;
constexpr uint32_t kMinfsIndirect = 31;
constexpr uint32_t kMinfsDoublyIndirect = 1;
constexpr uint32_t kMinfsDirectPerIndirect = (kMinfsBlockSize / sizeof(blk_t));
constexpr uint32_t kMinfsDirectPerDindirect = kMinfsDirectPerIndirect * kMinfsDirectPerIndirect;
// It is not possible to have a block at or past this one due to the limitations of the inode and
// indirect blocks.
// TODO(https://fxbug.dev/42106384): Remove this artificial cap when MinFS can safely deal with files larger than 4GB.
constexpr uint64_t kMinfsMaxFileBlock =
(std::numeric_limits<uint32_t>::max() / kMinfsBlockSize) - 1;
constexpr uint64_t kMinfsMaxFileSize = kMinfsMaxFileBlock * kMinfsBlockSize;
constexpr uint32_t kMinfsTypeFile = 8;
constexpr uint32_t kMinfsTypeDir = 4;
// Number of blocks allocated to the superblock.
constexpr blk_t kSuperblockBlocks = 1;
// Number of blocks allocated to the backup superblock.
constexpr blk_t kBackupSuperblockBlocks = 1;
// Superblock location.
constexpr size_t kSuperblockStart = 0;
// NonFVM and FVM backup superblock locations.
constexpr size_t kNonFvmSuperblockBackup = 7;
constexpr size_t kFvmSuperblockBackup = 0x40000;
constexpr size_t kFVMBlockInodeBmStart = 0x10000;
constexpr size_t kFVMBlockDataBmStart = 0x20000;
constexpr size_t kFVMBlockInodeStart = 0x30000;
constexpr size_t kFVMBlockJournalStart = kFvmSuperblockBackup + kBackupSuperblockBlocks;
constexpr size_t kFVMBlockDataStart = 0x50000;
constexpr blk_t kJournalEntryHeaderMaxBlocks = 2040;
// clang-format on
constexpr uint32_t MinfsMagic(uint32_t T) { return 0xAA6f6e00 | T; }
constexpr uint32_t kMinfsMagicDir = MinfsMagic(kMinfsTypeDir);
constexpr uint32_t kMinfsMagicFile = MinfsMagic(kMinfsTypeFile);
constexpr uint32_t MinfsMagicType(uint32_t n) { return n & 0xFF; }
constexpr uint32_t kMinfsMagicPurged = 0xdeaddead;
struct Superblock {
uint64_t magic0;
uint64_t magic1;
// The format version is the version of the overall format. If this is larger than
// kCurrentMinfsMajorVersion the driver must not access the data.
//
// See also "oldest_minor_version" below and //src/storage/docs/versioning.md.
//
// The deprecated2 field used to store a minor version which was never used and should always
// be zero. Old versions of the MinFS driver will fail to mount if this field is nonzero when
// otherwise they may have been able to mount a filesystem of version 9.
uint32_t major_version;
uint32_t deprecated2;
uint32_t checksum; // Crc32 checksum of the contents of the info block.
uint32_t generation_count; // Generation count of backup superblock for debugging purpose.
uint32_t flags;
uint32_t block_size; // 8K typical.
uint32_t inode_size; // 256.
uint32_t block_count; // total number of data blocks.
uint32_t inode_count; // total number of inodes.
uint32_t alloc_block_count; // total number of allocated data blocks.
uint32_t alloc_inode_count; // total number of allocated inodes.
uint32_t ibm_block; // first blockno of inode allocation bitmap.
uint32_t abm_block; // first blockno of block allocation bitmap.
uint32_t ino_block; // first blockno of inode table.
uint32_t integrity_start_block; // first blockno available for journal + backup superblock.
uint32_t dat_block; // first blockno available for file data.
// The following fields are only valid with (flags & kMinfsFlagFVM):
uint32_t slice_size; // Underlying slice size.
uint32_t deprecated1; // Unused but not necessarily 0 (saved total vslices in old vers.).
uint32_t ibm_slices; // Slices allocated to inode bitmap.
uint32_t abm_slices; // Slices allocated to block bitmap.
uint32_t ino_slices; // Slices allocated to inode table.
uint32_t integrity_slices; // Slices allocated to integrity section (journal + backup
// superblock).
uint32_t dat_slices; // Slices allocated to file data section.
uint32_t unlinked_head; // Index to the first unlinked (but open) inode.
uint32_t unlinked_tail; // Index to the last unlinked (but open) inode.
// Records the oldest revision of Minfs code that has touched this volume. It can be used for
// example by fsck to determine what checks should be strict and what should be warnings. This
// should be incremented any time there's any change in how data is written to the device, even
// if it's backwards compatible. Compatibility is determined by major_version above.
//
// See //src/storage/docs/versioning.md
uint32_t oldest_minor_version;
uint32_t reserved[2018];
uint32_t BlockSize() const {
// Either intentionally or unintenttionally, we do not want to change block
// size to anything other than kMinfsBlockSize yet. This is because changing
// block size might lead to format change and also because anything other
// than 8k is not well tested. So assert when we find block size other
// than 8k.
ZX_ASSERT(block_size == kMinfsBlockSize);
return block_size;
}
// Returns true if kMinfsFlagFVM is set for superblock.
bool GetFlagFvm() const { return (flags & kMinfsFlagFVM) == kMinfsFlagFVM; }
// Returns first block number from where inode bitmap starts.
uint64_t InodeBitmapStartBlock() const {
if (!GetFlagFvm()) {
return ibm_block;
}
return kFVMBlockInodeBmStart;
}
// Returns first block number from where data bitmap starts.
uint64_t DataBitmapStartBlock() const {
if (!GetFlagFvm()) {
return abm_block;
}
return kFVMBlockDataBmStart;
}
// Returns first block number from where inode table starts.
uint64_t InodeTableStartBlock() const {
if (!GetFlagFvm()) {
return ino_block;
}
return kFVMBlockInodeStart;
}
// Returns first block number from where data blocks starts.
uint64_t DataStartBlock() const {
if (!GetFlagFvm()) {
return dat_block;
}
return kFVMBlockDataStart;
}
// Returns first block number from where backup superblock starts.
uint64_t BackupSuperblockStart() const {
if (!GetFlagFvm()) {
return kNonFvmSuperblockBackup;
}
return kFvmSuperblockBackup;
}
};
static_assert(sizeof(Superblock) == kMinfsBlockSize, "minfs info size is wrong");
// Notes:
// - The inode bitmap, block bitmap, inode table, journal, and data
// regions must be in that order and may not overlap.
// - The abm has an entry for every block on the volume, including
// the info block (0), the bitmaps, etc.
// - Data blocks referenced from direct and indirect block tables
// in inodes are also relative to (0), but it is not legal for
// a block number of less than dat_block (start of data blocks)
// to be used.
// - Inode numbers refer to the inode in block:
// ino_block + ino / kMinfsInodesPerBlock
// at offset: ino % kMinfsInodesPerBlock.
// - Inode 0 is never used, should be marked allocated but ignored.
// The minimal number of slices to allocate a MinFS partition:
// Superblock, Inode bitmap, Data bitmap, Inode Table, Journal (2), and actual data.
constexpr size_t kMinfsMinimumSlices = 7;
constexpr uint64_t kMinfsDefaultInodeCount = 4096;
struct Inode {
uint32_t magic;
uint32_t size;
uint32_t block_count;
uint32_t link_count;
uint64_t create_time;
uint64_t modify_time;
uint32_t seq_num; // bumped when modified
uint32_t gen_num; // bumped when deleted
uint32_t dirent_count; // for directories
ino_t last_inode; // index to the previous unlinked inode
ino_t next_inode; // index to the next unlinked inode
uint32_t rsvd[3];
blk_t dnum[kMinfsDirect]; // direct blocks
blk_t inum[kMinfsIndirect]; // indirect blocks
blk_t dinum[kMinfsDoublyIndirect]; // doubly indirect blocks
};
static_assert(sizeof(Inode) == kMinfsInodeSize, "minfs inode size is wrong");
struct Dirent {
ino_t ino; // Inode number.
uint32_t reclen; // Low 28 bits: Length of record. High 4 bits: Flags
uint8_t namelen; // Length of the filename.
uint8_t type; // One of kMinfsType*.
char name[]; // The name bytes follow immediately. There is no trailing null.
};
constexpr uint8_t kMinfsDirentAlignment = 4;
constexpr uint8_t kMinfsDirentAlignmentMask = kMinfsDirentAlignment - 1;
static_assert(kMinfsDirentAlignment == alignof(Dirent), "Dirent alignment changed");
constexpr uint32_t kMinfsDirentSize = sizeof(Dirent);
// Returns the length of the Dirent structure required to hold a name of the given length. See
// also DirentReservedSize().
constexpr uint32_t DirentSize(uint8_t namelen) {
return kMinfsDirentSize + fbl::round_up<uint32_t>(namelen, kMinfsDirentAlignment);
}
constexpr uint8_t kMinfsMaxNameSize = 255;
// The largest acceptable value of DirentSize(dirent->namelen). The 'dirent->reclen' field may be
// larger after coalescing entries.
constexpr uint32_t kMinfsMaxDirentSize = DirentSize(kMinfsMaxNameSize);
constexpr uint32_t kMinfsMaxDirectorySize = fbl::round_down((1u << 20) - 1, kMinfsDirentAlignment);
static_assert(kMinfsMaxDirectorySize % kMinfsDirentAlignment == 0, "Invalid max directory size");
static_assert(kMinfsMaxDirectorySize < (1u << 20), "Max directory size too large");
// Storage for a Dirent padded out to the size for the maximum length. This is used as a buffer to
// read into with the correct alignment.
template <size_t max_size = kMinfsMaxDirentSize>
union DirentBuffer {
uint8_t raw[max_size];
Dirent dirent;
};
static_assert(kMinfsMaxNameSize >= NAME_MAX,
"MinFS names must be large enough to hold NAME_MAX characters");
constexpr uint32_t kMinfsReclenMask = 0x0FFFFFFF;
constexpr uint32_t kMinfsReclenLast = 0x80000000;
// Returns the data size reserved to this Dirent. For the last one, this will be the size from the
// beginning of it to the max space available for all records in a directory. For others, only
// part of this data may be used, see DirentSize().
constexpr uint32_t DirentReservedSize(Dirent* de, size_t off) {
return (de->reclen & kMinfsReclenLast) ? kMinfsMaxDirectorySize - static_cast<uint32_t>(off)
: de->reclen & kMinfsReclenMask;
}
static_assert(kMinfsMaxDirectorySize <= kMinfsReclenMask,
"MinFS directory size must be smaller than reclen mask");
// Notes:
// - dirents with ino of 0 are free, and skipped over on lookup.
// - reclen must be a multiple of 4.
// - The last record in a directory has the "kMinfsReclenLast" flag set. The actual size of this
// record can be computed from the offset at which this record starts. If the MAX_DIR_SIZE is
// increased, this 'last' record will also increase in size.
// blocksize 8K 16K 32K
// 16 dir = 128K 256K 512K
// 32 ind = 512M 1024M 2048M
// 1GB -> 128K blocks -> 16K bitmap (2K qword)
// 4GB -> 512K blocks -> 64K bitmap (8K qword)
// 32GB -> 4096K blocks -> 512K bitmap (64K qwords)
// Block Cache (bcache.c).
constexpr uint32_t kMinfsHashBits = (8);
// Sets kMinfsFlagFVM for given superblock.
constexpr void SetMinfsFlagFvm(Superblock& info) { info.flags |= kMinfsFlagFVM; }
constexpr uint64_t InodeBitmapBlocks(const Superblock& info) {
if ((info.flags & kMinfsFlagFVM) == kMinfsFlagFVM) {
auto blocks_per_slice = static_cast<blk_t>(info.slice_size / kMinfsBlockSize);
return info.ibm_slices * blocks_per_slice;
}
return info.abm_block - info.ibm_block;
}
constexpr uint64_t BlockBitmapBlocks(const Superblock& info) {
if ((info.flags & kMinfsFlagFVM) == kMinfsFlagFVM) {
auto blocks_per_slice = static_cast<blk_t>(info.slice_size / kMinfsBlockSize);
return info.abm_slices * blocks_per_slice;
}
return info.ino_block - info.abm_block;
}
constexpr uint64_t InodeBlocks(const Superblock& info) {
if ((info.flags & kMinfsFlagFVM) == kMinfsFlagFVM) {
auto blocks_per_slice = static_cast<blk_t>(info.slice_size / kMinfsBlockSize);
return info.ino_slices * blocks_per_slice;
}
return info.integrity_start_block - info.ino_block;
}
constexpr uint64_t JournalStartBlock(const Superblock& info) {
if ((info.flags & kMinfsFlagFVM) == kMinfsFlagFVM) {
return kFVMBlockJournalStart;
}
return info.integrity_start_block + kBackupSuperblockBlocks;
}
constexpr uint64_t JournalBlocks(const Superblock& info) {
if ((info.flags & kMinfsFlagFVM) == kMinfsFlagFVM) {
auto blocks_per_slice = static_cast<blk_t>(info.slice_size / kMinfsBlockSize);
return info.integrity_slices * blocks_per_slice - kBackupSuperblockBlocks;
}
return info.dat_block - info.integrity_start_block - kBackupSuperblockBlocks;
}
constexpr uint64_t DataBlocks(const Superblock& info) {
if ((info.flags & kMinfsFlagFVM) == kMinfsFlagFVM) {
auto blocks_per_slice = static_cast<blk_t>(info.slice_size / kMinfsBlockSize);
return info.dat_slices * blocks_per_slice;
}
return info.block_count;
}
constexpr uint64_t NonDataBlocks(const Superblock& info) {
return InodeBitmapBlocks(info) + BlockBitmapBlocks(info) + InodeBlocks(info) +
JournalBlocks(info);
}
} // namespace minfs
#endif // SRC_STORAGE_MINFS_FORMAT_H_