text: // Copyright 2017 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 "blobfs-checker.h"
  
  #include <inttypes.h>
  
  #include <utility>
  
  #include <fs/trace.h>
  
  #ifdef __Fuchsia__
  
  #include <fuchsia/hardware/block/volume/c/fidl.h>
  #include <zircon/status.h>
  
  #include <fs/journal/replay.h>
  
  #else
  
  #include <blobfs/host.h>
  
  #endif
  
  #include "iterator/allocated-extent-iterator.h"
  #include "iterator/extent-iterator.h"
  
  namespace blobfs {
  
  void BlobfsChecker::TraverseInodeBitmap() {
    for (unsigned n = 0; n < blobfs_->info_.inode_count; n++) {
      Inode* inode = blobfs_->GetNode(n);
      if (inode->header.IsAllocated()) {
        alloc_inodes_++;
        if (inode->header.IsExtentContainer()) {
          // TODO(smklein): sanity check these containers.
          continue;
        }
  
        bool valid = true;
  
        AllocatedExtentIterator extents = AllocatedExtentIterator(blobfs_->GetNodeFinder(), n);
        while (!extents.Done()) {
          const Extent* extent;
          zx_status_t status = extents.Next(&extent);
          if (status != ZX_OK) {
            FS_TRACE_ERROR("check: Failed to acquire extent %u within inode %u.\n",
                           extents.ExtentIndex(), n);
            valid = false;
            break;
          }
  
          uint64_t start_block = extent->Start();
          uint64_t end_block = extent->Start() + extent->Length();
          uint64_t first_unset = 0;
          if (!blobfs_->CheckBlocksAllocated(start_block, end_block, &first_unset)) {
            FS_TRACE_ERROR("check: ino %u using blocks [%" PRIu64 ", %" PRIu64
                           "). "
                           "Not fully allocated in block bitmap; first unset @%" PRIu64 "\n",
                           n, start_block, end_block, first_unset);
            valid = false;
          }
          inode_blocks_ += extent->Length();
        }
  
        if (blobfs_->LoadAndVerifyBlob(n) != ZX_OK) {
          FS_TRACE_ERROR("check: detected inode %u with bad state\n", n);
          valid = false;
        }
        if (!valid) {
          error_blobs_++;
        }
      }
    }
  }
  
  void BlobfsChecker::TraverseBlockBitmap() {
    for (uint64_t n = 0; n < blobfs_->info_.data_block_count; n++) {
      if (blobfs_->CheckBlocksAllocated(n, n + 1)) {
        alloc_blocks_++;
      }
    }
  }
  
  zx_status_t BlobfsChecker::CheckAllocatedCounts() const {
    zx_status_t status = ZX_OK;
    if (alloc_blocks_ != blobfs_->info_.alloc_block_count) {
      FS_TRACE_ERROR("check: incorrect allocated block count %" PRIu64 " (should be %u)\n",
                     blobfs_->info_.alloc_block_count, alloc_blocks_);
      status = ZX_ERR_BAD_STATE;
    }
  
    if (alloc_blocks_ < kStartBlockMinimum) {
      FS_TRACE_ERROR("check: allocated blocks (%u) are less than minimum (%" PRIu64 ")\n",
                     alloc_blocks_, kStartBlockMinimum);
      status = ZX_ERR_BAD_STATE;
    }
  
    if (inode_blocks_ + kStartBlockMinimum != alloc_blocks_) {
      FS_TRACE_ERROR(
          "check: bitmap allocated blocks (%u) do not match inode allocated blocks "
          "(%" PRIu64 ")\n",
          alloc_blocks_, inode_blocks_ + kStartBlockMinimum);
      status = ZX_ERR_BAD_STATE;
    }
  
    if (alloc_inodes_ != blobfs_->info_.alloc_inode_count) {
      FS_TRACE_ERROR("check: incorrect allocated inode count %" PRIu64 " (should be %u)\n",
                     blobfs_->info_.alloc_inode_count, alloc_inodes_);
      status = ZX_ERR_BAD_STATE;
    }
  
    if (error_blobs_) {
      status = ZX_ERR_BAD_STATE;
    }
  
    return status;
  }
  
  zx_status_t BlobfsChecker::Check() {
    TraverseInodeBitmap();
    TraverseBlockBitmap();
    return CheckAllocatedCounts();
  }
  
  BlobfsChecker::BlobfsChecker(std::unique_ptr<Blobfs> blobfs)
      : blobfs_(std::move(blobfs)),
        alloc_inodes_(0),
        alloc_blocks_(0),
        error_blobs_(0),
        inode_blocks_(0) {}
  
  zx_status_t BlobfsChecker::Initialize(bool apply_journal) {
  #ifdef __Fuchsia__
    zx_status_t status;
    if (apply_journal) {
      status = fs::ReplayJournal(blobfs_.get(), blobfs_.get(), JournalStartBlock(blobfs_->info_),
                                 JournalBlocks(blobfs_->info_), nullptr);
      if (status != ZX_OK) {
        FS_TRACE_ERROR("blobfs: Unable to apply journal contents: %d\n", status);
        return status;
      }
    }
  
    status = CheckFvmConsistency(&blobfs_->Info(), blobfs_->Device());
    if (status != ZX_OK) {
      FS_TRACE_ERROR("blobfs: Inconsistent metadata does not match FVM: %d\n", status);
      return status;
    }
  #endif
    return ZX_OK;
  }
  
  #ifdef __Fuchsia__
  zx_status_t CheckFvmConsistency(const Superblock* info, BlockDevice* device) {
    if ((info->flags & kBlobFlagFVM) == 0) {
      return ZX_OK;
    }
  
    fuchsia_hardware_block_volume_VolumeInfo fvm_info;
    zx_status_t status = device->VolumeQuery(&fvm_info);
    if (status != ZX_OK) {
      FS_TRACE_ERROR("blobfs: Unable to query FVM, status: %s\n", zx_status_get_string(status));
      return status;
    }
  
    if (info->slice_size != fvm_info.slice_size) {
      FS_TRACE_ERROR("blobfs: Slice size did not match expected\n");
      return ZX_ERR_BAD_STATE;
    }
    const size_t kBlocksPerSlice = info->slice_size / kBlobfsBlockSize;
  
    size_t expected_count[4];
    expected_count[0] = info->abm_slices;
    expected_count[1] = info->ino_slices;
    expected_count[2] = info->journal_slices;
    expected_count[3] = info->dat_slices;
  
    uint64_t start_slices[4];
    start_slices[0] = kFVMBlockMapStart / kBlocksPerSlice;
    start_slices[1] = kFVMNodeMapStart / kBlocksPerSlice;
    start_slices[2] = kFVMJournalStart / kBlocksPerSlice;
    start_slices[3] = kFVMDataStart / kBlocksPerSlice;
  
    fuchsia_hardware_block_volume_VsliceRange
        ranges[fuchsia_hardware_block_volume_MAX_SLICE_REQUESTS];
    size_t actual_ranges_count;
    status = device->VolumeQuerySlices(start_slices, fbl::count_of(start_slices), ranges,
                                       &actual_ranges_count);
    if (status != ZX_OK) {
      FS_TRACE_ERROR("blobfs: Cannot query slices, status: %s\n", zx_status_get_string(status));
      return status;
    }
  
    if (actual_ranges_count != fbl::count_of(start_slices)) {
      FS_TRACE_ERROR("blobfs: Missing slice\n");
      return ZX_ERR_BAD_STATE;
    }
  
    for (size_t i = 0; i < fbl::count_of(start_slices); i++) {
      size_t blobfs_count = expected_count[i];
      size_t fvm_count = ranges[i].count;
  
      if (!ranges[i].allocated || fvm_count < blobfs_count) {
        // Currently, since Blobfs can only grow new slices, it should not be possible for
        // the FVM to report a slice size smaller than what is reported by Blobfs. In this
        // case, automatically fail without trying to resolve the situation, as it is
        // possible that Blobfs structures are allocated in the slices that have been lost.
        FS_TRACE_ERROR("blobfs: Mismatched slice count\n");
        return ZX_ERR_IO_DATA_INTEGRITY;
      }
  
      if (fvm_count > blobfs_count) {
        // If FVM reports more slices than we expect, try to free remainder.
        uint64_t offset = start_slices[i] + blobfs_count;
        uint64_t length = fvm_count - blobfs_count;
        zx_status_t status = device->VolumeShrink(offset, length);
        if (status != ZX_OK) {
          FS_TRACE_ERROR("blobfs: Unable to shrink to expected size: %s\n",
                         zx_status_get_string(status));
          return status;
        }
      }
    }
  
    return ZX_OK;
  }
  #endif  // ifdef __Fuchsia__
  
  }  // namespace blobfs
  