| // 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 |