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