blob: 694b00c29cd35631fe7d74347abea038fdc53579 [file] [log] [blame]
// Copyright 2019 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 "src/storage/blobfs/format.h"
#include <lib/cksum.h>
#include <lib/stdcompat/span.h>
#include <lib/syslog/cpp/macros.h>
#include <iterator>
#include <optional>
#include <utility>
#include <fbl/ref_ptr.h>
#include <safemath/checked_math.h>
#include <storage/buffer/owned_vmoid.h>
#include "src/storage/blobfs/common.h"
#include "src/storage/blobfs/mkfs.h"
#include "src/storage/fvm/client.h"
#include "src/storage/lib/vfs/cpp/journal/initializer.h"
namespace blobfs {
namespace {
using ::block_client::BlockDevice;
std::optional<fuchsia_hardware_block_volume::wire::VolumeManagerInfo> TryGetVolumeManagerInfo(
const BlockDevice& device) {
fuchsia_hardware_block_volume::wire::VolumeManagerInfo fvm_manager_info = {};
fuchsia_hardware_block_volume::wire::VolumeInfo volume_info = {};
zx_status_t status = device.VolumeGetInfo(&fvm_manager_info, &volume_info);
if (status != ZX_OK) {
return std::nullopt;
}
return fvm_manager_info;
}
// Generates a superblock that will cover the entire device described by |block_info|.
zx::result<Superblock> FormatSuperblock(const fuchsia_hardware_block::wire::BlockInfo& block_info,
const FilesystemOptions& options) {
uint64_t blocks = (block_info.block_size * block_info.block_count) / kBlobfsBlockSize;
Superblock superblock;
if (zx_status_t status = InitializeSuperblock(blocks, options, &superblock); status != ZX_OK) {
return zx::error(status);
}
zx_status_t status = CheckSuperblock(&superblock, blocks);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Check superblock failed: " << status;
return zx::error(status);
}
return zx::ok(superblock);
}
// Generates a FVM-aware superblock with the minimum number of slices reserved for each metadata
// region.
zx::result<Superblock> FormatSuperblockFVM(
BlockDevice* device, const fuchsia_hardware_block_volume::wire::VolumeManagerInfo& fvm_info,
const FilesystemOptions& options) {
Superblock superblock;
InitializeSuperblockOptions(options, &superblock);
superblock.slice_size = fvm_info.slice_size;
superblock.flags |= kBlobFlagFVM;
if (superblock.slice_size % kBlobfsBlockSize) {
FX_LOGS(ERROR) << "mkfs: Slice size not multiple of blobfs block";
return zx::error(ZX_ERR_IO_INVALID);
}
zx_status_t status = fvm::ResetAllSlices(device);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "mkfs: Failed to reset slices";
return zx::error(status);
}
const size_t blocks_per_slice = superblock.slice_size / kBlobfsBlockSize;
// Converts blocks to slices, rounding up to the nearest slice size.
auto BlocksToSlices = [blocks_per_slice](uint64_t blocks) {
return fbl::round_up(blocks, blocks_per_slice) / blocks_per_slice;
};
uint64_t data_blocks = fbl::round_up(kMinimumDataBlocks, blocks_per_slice);
// Allocate the minimum number of blocks for a minimal bitmap.
uint64_t offset = kFVMBlockMapStart / blocks_per_slice;
uint64_t length = BlocksToSlices(BlocksRequiredForBits(data_blocks));
superblock.abm_slices = safemath::checked_cast<decltype(superblock.abm_slices)>(length);
status = device->VolumeExtend(offset, superblock.abm_slices);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "mkfs: Failed to allocate block map";
return zx::error(status);
}
// Allocate the requested number of node blocks in FVM.
offset = kFVMNodeMapStart / blocks_per_slice;
length = BlocksToSlices(BlocksRequiredForInode(options.num_inodes));
superblock.ino_slices = safemath::checked_cast<decltype(superblock.ino_slices)>(length);
status = device->VolumeExtend(offset, superblock.ino_slices);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "mkfs: Failed to allocate node map";
return zx::error(status);
}
// Allocate the minimum number of journal blocks in FVM.
offset = kFVMJournalStart / blocks_per_slice;
length = BlocksToSlices(kMinimumJournalBlocks);
superblock.journal_slices = safemath::checked_cast<decltype(superblock.journal_slices)>(length);
status = device->VolumeExtend(offset, superblock.journal_slices);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "mkfs: Failed to allocate journal blocks";
return zx::error(status);
}
// Allocate the minimum number of data blocks in the FVM.
offset = kFVMDataStart / blocks_per_slice;
length = BlocksToSlices(kMinimumDataBlocks);
superblock.dat_slices = safemath::checked_cast<decltype(superblock.dat_slices)>(length);
status = device->VolumeExtend(offset, superblock.dat_slices);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "mkfs: Failed to allocate data blocks";
return zx::error(status);
}
superblock.inode_count = safemath::checked_cast<decltype(superblock.inode_count)>(
superblock.ino_slices * superblock.slice_size / kBlobfsInodeSize);
superblock.data_block_count = safemath::checked_cast<decltype(superblock.data_block_count)>(
superblock.dat_slices * superblock.slice_size / kBlobfsBlockSize);
superblock.journal_block_count = safemath::checked_cast<decltype(superblock.journal_block_count)>(
superblock.journal_slices * superblock.slice_size / kBlobfsBlockSize);
// Now that we've allocated some slices, re-query FVM for the number of blocks assigned to the
// partition. We'll use this as a sanity check in CheckSuperblock.
fuchsia_hardware_block::wire::BlockInfo block_info = {};
status = device->BlockGetInfo(&block_info);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Cannot acquire block info: " << status;
return zx::error(status);
}
uint64_t blocks = (block_info.block_count * block_info.block_size) / kBlobfsBlockSize;
status = CheckSuperblock(&superblock, blocks);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Check superblock failed: " << status;
return zx::error(status);
}
return zx::ok(superblock);
}
// Take the contents of the filesystem, generated in-memory, and transfer them to the underlying
// device.
zx_status_t WriteFilesystemToDisk(BlockDevice* device, const Superblock& superblock,
const RawBitmap& block_bitmap, uint64_t block_size) {
uint64_t blockmap_blocks = BlockMapBlocks(superblock);
uint64_t nodemap_blocks = NodeMapBlocks(superblock);
// All in-memory structures have been created successfully. Dump everything to disk.
uint64_t superblock_blocks = SuperblockBlocks(superblock);
uint64_t journal_blocks = JournalBlocks(superblock);
uint64_t total_blocks = superblock_blocks + blockmap_blocks + nodemap_blocks + journal_blocks;
zx::vmo vmo;
zx_status_t status = zx::vmo::create(kBlobfsBlockSize * total_blocks, 0, &vmo);
if (status != ZX_OK) {
return status;
}
storage::OwnedVmoid vmoid;
status = device->BlockAttachVmo(vmo, &vmoid.GetReference(device));
if (status != ZX_OK) {
return status;
}
// Write the root block.
status = vmo.write(&superblock, 0, kBlobfsBlockSize);
if (status != ZX_OK) {
return status;
}
// Write allocation bitmap.
for (uint64_t n = 0; n < blockmap_blocks; n++) {
uint64_t offset = kBlobfsBlockSize * (superblock_blocks + n);
uint64_t length = kBlobfsBlockSize;
status = vmo.write(GetRawBitmapData(block_bitmap, n), offset, length);
if (status != ZX_OK) {
return status;
}
}
// Write node map.
uint8_t block[kBlobfsBlockSize];
memset(block, 0, sizeof(block));
for (uint64_t n = 0; n < nodemap_blocks; n++) {
uint64_t offset = kBlobfsBlockSize * (superblock_blocks + blockmap_blocks + n);
uint64_t length = kBlobfsBlockSize;
status = vmo.write(block, offset, length);
if (status != ZX_OK) {
return status;
}
}
// Write the journal.
auto base_offset = superblock_blocks + blockmap_blocks + nodemap_blocks;
fs::WriteBlocksFn write_blocks_fn = [&vmo, &superblock, base_offset](
cpp20::span<const uint8_t> buffer, uint64_t block_offset,
uint64_t block_count) {
uint64_t offset =
safemath::CheckMul<uint64_t>(safemath::CheckAdd(base_offset, block_offset).ValueOrDie(),
kBlobfsBlockSize)
.ValueOrDie();
uint64_t size = safemath::CheckMul<uint64_t>(block_count, kBlobfsBlockSize).ValueOrDie();
ZX_ASSERT((block_offset + block_count) <= JournalBlocks(superblock));
ZX_ASSERT(buffer.size() >= size);
return vmo.write(buffer.data(), offset, size);
};
status = fs::MakeJournal(journal_blocks, write_blocks_fn);
if (status != ZX_OK) {
return status;
}
auto FsToDeviceBlocks = [disk_block = block_size](uint64_t block) -> uint64_t {
return block * (kBlobfsBlockSize / disk_block);
};
block_fifo_request_t requests[5] = {};
using RequestLengthType = decltype(block_fifo_request_t::length);
static_assert(
std::is_same_v<RequestLengthType, uint32_t>,
"Type of length field for block FIFO request has changed, validate conversions below.");
requests[0].command = {.opcode = BLOCK_OPCODE_WRITE, .flags = 0};
requests[0].vmoid = vmoid.get();
requests[0].length =
safemath::checked_cast<RequestLengthType>(FsToDeviceBlocks(superblock_blocks));
requests[0].vmo_offset = FsToDeviceBlocks(0);
requests[0].dev_offset = FsToDeviceBlocks(0);
requests[1].command = {.opcode = BLOCK_OPCODE_WRITE, .flags = 0};
requests[1].vmoid = vmoid.get();
requests[1].length = safemath::checked_cast<RequestLengthType>(FsToDeviceBlocks(blockmap_blocks));
requests[1].vmo_offset = FsToDeviceBlocks(superblock_blocks);
requests[1].dev_offset = FsToDeviceBlocks(BlockMapStartBlock(superblock));
requests[2].command = {.opcode = BLOCK_OPCODE_WRITE, .flags = 0};
requests[2].vmoid = vmoid.get();
requests[2].length = safemath::checked_cast<RequestLengthType>(FsToDeviceBlocks(nodemap_blocks));
requests[2].vmo_offset = FsToDeviceBlocks(superblock_blocks + blockmap_blocks);
requests[2].dev_offset = FsToDeviceBlocks(NodeMapStartBlock(superblock));
requests[3].command = {.opcode = BLOCK_OPCODE_WRITE, .flags = 0};
requests[3].vmoid = vmoid.get();
requests[3].length = safemath::checked_cast<RequestLengthType>(FsToDeviceBlocks(journal_blocks));
requests[3].vmo_offset = FsToDeviceBlocks(superblock_blocks + blockmap_blocks + nodemap_blocks);
requests[3].dev_offset = FsToDeviceBlocks(JournalStartBlock(superblock));
int count = 4;
if (superblock.flags & kBlobFlagFVM) {
requests[4].command = {.opcode = BLOCK_OPCODE_WRITE, .flags = 0};
requests[4].vmoid = vmoid.get();
requests[4].length =
safemath::checked_cast<RequestLengthType>(FsToDeviceBlocks(superblock_blocks));
requests[4].vmo_offset = FsToDeviceBlocks(0);
requests[4].dev_offset = FsToDeviceBlocks(kFVMBackupSuperblockOffset);
++count;
}
status = device->FifoTransaction(requests, count);
if (status != ZX_OK)
return status;
block_fifo_request_t flush_request = {
.command = {.opcode = BLOCK_OPCODE_FLUSH, .flags = 0},
};
return device->FifoTransaction(&flush_request, 1);
}
} // namespace
zx_status_t FormatFilesystem(BlockDevice* device, const FilesystemOptions& options) {
zx_status_t status;
fuchsia_hardware_block::wire::BlockInfo block_info = {};
status = device->BlockGetInfo(&block_info);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Cannot acquire block info: " << status;
return status;
}
if (block_info.flags & fuchsia_hardware_block::wire::Flag::kReadonly) {
FX_LOGS(ERROR) << "Cannot format read-only device";
return ZX_ERR_ACCESS_DENIED;
}
if (block_info.block_size == 0) {
FX_LOGS(ERROR) << "Device has zero-sized blocks";
return ZX_ERR_NO_SPACE;
}
if (kBlobfsBlockSize % block_info.block_size != 0) {
FX_LOGS(ERROR) << "Device block size " << block_info.block_size << " invalid";
return ZX_ERR_IO_INVALID;
}
zx::result<Superblock> superblock_or;
if (auto maybe_volume_info = TryGetVolumeManagerInfo(*device); maybe_volume_info.has_value()) {
superblock_or = FormatSuperblockFVM(device, maybe_volume_info.value(), options);
} else {
superblock_or = FormatSuperblock(block_info, options);
}
if (superblock_or.is_error()) {
return superblock_or.status_value();
}
const Superblock& superblock = superblock_or.value();
uint64_t blockmap_blocks = BlockMapBlocks(superblock);
RawBitmap block_bitmap;
if (block_bitmap.Reset(blockmap_blocks * kBlobfsBlockBits)) {
FX_LOGS(ERROR) << "Couldn't allocate block map";
return -1;
}
if (block_bitmap.Shrink(superblock.data_block_count)) {
FX_LOGS(ERROR) << "Couldn't shrink block map";
return -1;
}
// Reserve first |kStartBlockMinimum| data blocks
block_bitmap.Set(0, kStartBlockMinimum);
status = WriteFilesystemToDisk(device, superblock, block_bitmap, block_info.block_size);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to write to disk: " << status;
return status;
}
FX_LOGS(DEBUG) << "mkfs success";
return ZX_OK;
}
} // namespace blobfs