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