blob: 6a9a00d1fdfa2310b53ae606adcb6c7d25de9d57 [file] [log] [blame]
// 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++) {
InodePtr 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_), kBlobfsBlockSize, 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