blob: d4d0a05df3e63099957bbfd85df888813e300f97 [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/syslog/cpp/macros.h>
#include <iterator>
#include <utility>
#include <fbl/ref_ptr.h>
#include <fs/journal/initializer.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"
namespace blobfs {
namespace {
using ::block_client::BlockDevice;
// Attempts to format the device as an FVM-based filesystem.
//
// If |device| does not speak FVM protocols, returns ZX_OK.
// If the volume cannot be modified (either removing old slices, or extending
// the volume to contain new slices), an error from the FVM is returned.
zx_status_t TryFormattingFVM(BlockDevice* device, Superblock* superblock) {
fuchsia_hardware_block_volume_VolumeInfo fvm_info = {};
zx_status_t status = device->VolumeQuery(&fvm_info);
if (status != ZX_OK) {
// If the device does not speak the FVM protocol, that's acceptable.
return ZX_OK;
}
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_ERR_IO_INVALID;
}
status = fvm::ResetAllSlices(device);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "mkfs: Failed to reset slices";
return 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);
uint64_t offset = kFVMBlockMapStart / blocks_per_slice;
uint64_t length = BlocksToSlices(BlocksRequiredForBits(data_blocks));
superblock->abm_slices = static_cast<uint32_t>(length);
status = device->VolumeExtend(offset, superblock->abm_slices);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "mkfs: Failed to allocate block map";
return status;
}
offset = kFVMNodeMapStart / blocks_per_slice;
superblock->ino_slices = 1;
status = device->VolumeExtend(offset, superblock->ino_slices);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "mkfs: Failed to allocate node map";
return status;
}
// Allocate the minimum number of journal blocks in FVM.
offset = kFVMJournalStart / blocks_per_slice;
length = fbl::round_up(kDefaultJournalBlocks, blocks_per_slice) / blocks_per_slice;
superblock->journal_slices = static_cast<uint32_t>(length);
status = device->VolumeExtend(offset, superblock->journal_slices);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "mkfs: Failed to allocate journal blocks";
return status;
}
// Allocate the minimum number of data blocks in the FVM.
offset = kFVMDataStart / blocks_per_slice;
length = fbl::round_up(kMinimumDataBlocks, blocks_per_slice) / blocks_per_slice;
superblock->dat_slices = static_cast<uint32_t>(length);
status = device->VolumeExtend(offset, superblock->dat_slices);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "mkfs: Failed to allocate data blocks";
return status;
}
superblock->inode_count =
static_cast<uint32_t>(superblock->ino_slices * superblock->slice_size / kBlobfsInodeSize);
superblock->data_block_count =
static_cast<uint32_t>(superblock->dat_slices * superblock->slice_size / kBlobfsBlockSize);
superblock->journal_block_count =
static_cast<uint32_t>(superblock->journal_slices * superblock->slice_size / kBlobfsBlockSize);
return ZX_OK;
}
// 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,
const fuchsia_hardware_block_BlockInfo& block_info) {
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](
fbl::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_info.block_size](uint64_t block) {
return block * (kBlobfsBlockSize / disk_block);
};
block_fifo_request_t requests[5] = {};
requests[0].opcode = BLOCKIO_WRITE;
requests[0].vmoid = vmoid.get();
requests[0].length = static_cast<uint32_t>(FsToDeviceBlocks(superblock_blocks));
requests[0].vmo_offset = FsToDeviceBlocks(0);
requests[0].dev_offset = FsToDeviceBlocks(0);
requests[1].opcode = BLOCKIO_WRITE;
requests[1].vmoid = vmoid.get();
requests[1].length = static_cast<uint32_t>(FsToDeviceBlocks(blockmap_blocks));
requests[1].vmo_offset = FsToDeviceBlocks(superblock_blocks);
requests[1].dev_offset = FsToDeviceBlocks(BlockMapStartBlock(superblock));
requests[2].opcode = BLOCKIO_WRITE;
requests[2].vmoid = vmoid.get();
requests[2].length = static_cast<uint32_t>(FsToDeviceBlocks(nodemap_blocks));
requests[2].vmo_offset = FsToDeviceBlocks(superblock_blocks + blockmap_blocks);
requests[2].dev_offset = FsToDeviceBlocks(NodeMapStartBlock(superblock));
requests[3].opcode = BLOCKIO_WRITE;
requests[3].vmoid = vmoid.get();
requests[3].length = static_cast<uint32_t>(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].opcode = BLOCKIO_WRITE;
requests[4].vmoid = vmoid.get();
requests[4].length = static_cast<uint32_t>(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 = {.opcode = BLOCKIO_FLUSH};
return device->FifoTransaction(&flush_request, 1);
}
} // namespace
zx_status_t FormatFilesystem(BlockDevice* device, const FilesystemOptions& options) {
zx_status_t status;
fuchsia_hardware_block_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 & BLOCK_FLAG_READONLY) {
FX_LOGS(ERROR) << "cannot format read-only device";
return ZX_ERR_ACCESS_DENIED;
}
if (block_info.block_size == 0 || block_info.block_count == 0) {
return ZX_ERR_NO_SPACE;
}
if (kBlobfsBlockSize % block_info.block_size != 0) {
return ZX_ERR_IO_INVALID;
}
uint64_t blocks = (block_info.block_size * block_info.block_count) / kBlobfsBlockSize;
Superblock superblock;
InitializeSuperblock(blocks, options, &superblock);
status = TryFormattingFVM(device, &superblock);
if (status != ZX_OK) {
return status;
}
status = CheckSuperblock(&superblock, blocks);
if (status != ZX_OK) {
return status;
}
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);
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