blob: 52f10adb4a3bcc040b0c257db43579415afe4c53 [file] [log] [blame]
// Copyright 2016 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/minfs/minfs.h"
#include <inttypes.h>
#include <lib/cksum.h>
#include <lib/stdcompat/span.h>
#include <lib/syslog/cpp/macros.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <iomanip>
#include <limits>
#include <memory>
#include <utility>
#include <bitmap/raw-bitmap.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <safemath/safe_math.h>
#include "src/storage/lib/vfs/cpp/journal/format.h"
#include "src/storage/lib/vfs/cpp/journal/initializer.h"
#include "src/storage/minfs/allocator/allocator_reservation.h"
#include "src/storage/minfs/fsck.h"
#include "src/storage/minfs/minfs_private.h"
#include "src/storage/minfs/writeback.h"
#ifdef __Fuchsia__
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/fit/defer.h>
#include <lib/zx/clock.h>
#include <lib/zx/event.h>
#include <fbl/auto_lock.h>
#include <storage/buffer/owned_vmoid.h>
#include "sdk/lib/sys/cpp/service_directory.h"
#include "src/storage/fvm/client.h"
#include "src/storage/lib/vfs/cpp/journal/header_view.h"
#include "src/storage/lib/vfs/cpp/journal/journal.h"
#include "src/storage/lib/vfs/cpp/journal/replay.h"
#endif
namespace minfs {
namespace {
#ifdef __Fuchsia__
// Deletes all known slices from a MinFS Partition.
void FreeSlices(const Superblock* info, block_client::BlockDevice* device) {
if ((info->flags & kMinfsFlagFVM) == 0) {
return;
}
const size_t kBlocksPerSlice = info->slice_size / info->BlockSize();
if (info->ibm_slices) {
uint64_t length = info->ibm_slices;
uint64_t offset = kFVMBlockInodeBmStart / kBlocksPerSlice;
device->VolumeShrink(offset, length);
}
if (info->abm_slices) {
uint64_t length = info->abm_slices;
uint64_t offset = kFVMBlockDataBmStart / kBlocksPerSlice;
device->VolumeShrink(offset, length);
}
if (info->ino_slices) {
uint64_t length = info->ino_slices;
uint64_t offset = kFVMBlockInodeStart / kBlocksPerSlice;
device->VolumeShrink(offset, length);
}
if (info->dat_slices) {
uint64_t length = info->dat_slices;
uint64_t offset = kFVMBlockDataStart / kBlocksPerSlice;
device->VolumeShrink(offset, length);
}
}
// Checks all slices against the block device. May shrink the partition.
zx::result<> CheckSlices(const Superblock& info, size_t blocks_per_slice,
block_client::BlockDevice* device, bool repair_slices) {
fuchsia_hardware_block_volume::wire::VolumeManagerInfo manager_info;
fuchsia_hardware_block_volume::wire::VolumeInfo volume_info;
zx_status_t status = device->VolumeGetInfo(&manager_info, &volume_info);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "unable to query FVM :" << status;
return zx::error(ZX_ERR_UNAVAILABLE);
}
if (info.slice_size != manager_info.slice_size) {
FX_LOGS(ERROR) << "slice size " << info.slice_size << " did not match expected size "
<< manager_info.slice_size;
return zx::error(ZX_ERR_BAD_STATE);
}
size_t expected_count[4];
expected_count[0] = info.ibm_slices;
expected_count[1] = info.abm_slices;
expected_count[2] = info.ino_slices;
expected_count[3] = info.dat_slices;
size_t vslices[] = {
kFVMBlockInodeBmStart / blocks_per_slice,
kFVMBlockDataBmStart / blocks_per_slice,
kFVMBlockInodeStart / blocks_per_slice,
kFVMBlockDataStart / blocks_per_slice,
};
fuchsia_hardware_block_volume::wire::VsliceRange
ranges[fuchsia_hardware_block_volume::wire::kMaxSliceRequests];
size_t ranges_count;
status = device->VolumeQuerySlices(vslices, std::size(vslices), ranges, &ranges_count);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "unable to query FVM: " << status;
return zx::error(ZX_ERR_UNAVAILABLE);
}
if (ranges_count != std::size(vslices)) {
FX_LOGS(ERROR) << "requested FVM range :" << std::size(vslices)
<< " does not match received: " << ranges_count;
return zx::error(ZX_ERR_BAD_STATE);
}
for (uint32_t i = 0; i < std::size(vslices); i++) {
size_t minfs_count = expected_count[i];
size_t fvm_count = ranges[i].count;
if (!ranges[i].allocated || fvm_count < minfs_count) {
// Currently, since Minfs can only grow new slices (except for the one instance below), it
// should not be possible for the FVM to report a slice size smaller than what is reported by
// Minfs. In this case, automatically fail without trying to resolve the situation, as it is
// possible that Minfs structures are allocated in the slices that have been lost.
FX_LOGS(ERROR) << "mismatched slice count";
return zx::error(ZX_ERR_IO_DATA_INTEGRITY);
}
if (repair_slices && fvm_count > minfs_count) {
// If FVM reports more slices than we expect, try to free remainder.
uint64_t length = fvm_count - minfs_count;
uint64_t offset = vslices[i] + minfs_count;
if ((status = device->VolumeShrink(offset, length)) != ZX_OK) {
FX_LOGS(ERROR) << "Unable to shrink to expected size, status: " << status;
return zx::error(ZX_ERR_IO_DATA_INTEGRITY);
}
}
}
return zx::ok();
}
// Setups the superblock based on the mount options and the underlying device.
// It can be called when not loaded on top of FVM, in which case this function
// will do nothing.
zx::result<> CreateFvmData(const MountOptions& options, Superblock* info,
block_client::BlockDevice* device) {
fuchsia_hardware_block_volume::wire::VolumeManagerInfo manager_info;
fuchsia_hardware_block_volume::wire::VolumeInfo volume_info;
if (device->VolumeGetInfo(&manager_info, &volume_info) != ZX_OK) {
return zx::ok();
}
info->slice_size = static_cast<uint32_t>(manager_info.slice_size);
SetMinfsFlagFvm(*info);
if (info->slice_size % info->BlockSize()) {
FX_LOGS(ERROR) << "minfs mkfs: Slice size not multiple of minfs block: " << info->slice_size;
return zx::error(ZX_ERR_IO_INVALID);
}
const size_t kBlocksPerSlice = info->slice_size / info->BlockSize();
zx_status_t status = fvm::ResetAllSlices(device);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "minfs mkfs: Failed to reset FVM slices: " << status;
return zx::error(status);
}
struct {
size_t offset;
size_t length;
} request;
// Inode allocation bitmap.
info->ibm_slices = 1;
request.offset = kFVMBlockInodeBmStart / kBlocksPerSlice;
request.length = info->ibm_slices;
if (status = device->VolumeExtend(request.offset, request.length); status != ZX_OK) {
FX_LOGS(ERROR) << "minfs mkfs: Failed to allocate inode bitmap: " << status;
return zx::error(status);
}
// Data block allocation bitmap. Currently once slice should be enough for many more inodes than
// we currently reserve (this is validated with an assertion below).
info->abm_slices = 1;
request.offset = kFVMBlockDataBmStart / kBlocksPerSlice;
request.length = info->abm_slices;
if (status = device->VolumeExtend(request.offset, request.length); status != ZX_OK) {
FX_LOGS(ERROR) << "minfs mkfs: Failed to allocate data bitmap: " << status;
return zx::error(status);
}
// Inode slice: Compute the number required to contain at least the default number of inodes.
auto inode_blocks = (kMinfsDefaultInodeCount + kMinfsInodesPerBlock - 1) / kMinfsInodesPerBlock;
info->ino_slices = static_cast<uint32_t>((inode_blocks + kBlocksPerSlice - 1) / kBlocksPerSlice);
request.offset = kFVMBlockInodeStart / kBlocksPerSlice;
request.length = info->ino_slices;
if (status = device->VolumeExtend(request.offset, request.length); status != ZX_OK) {
FX_LOGS(ERROR) << "minfs mkfs: Failed to allocate inode table: " << status;
return zx::error(status);
}
// The inode bitmap should be big enough to hold all the inodes we reserved. If this triggers we
// need to write logic to compute the proper ibm_slices size.
ZX_DEBUG_ASSERT(info->ibm_slices * info->slice_size * static_cast<size_t>(8) >=
info->ino_slices * kBlocksPerSlice * kMinfsInodesPerBlock);
// Journal.
TransactionLimits limits(*info);
blk_t journal_blocks = limits.GetRecommendedIntegrityBlocks();
request.length = fbl::round_up(journal_blocks, kBlocksPerSlice) / kBlocksPerSlice;
request.offset = kFVMBlockJournalStart / kBlocksPerSlice;
if (status = device->VolumeExtend(request.offset, request.length); status != ZX_OK) {
FX_LOGS(ERROR) << "minfs mkfs: Failed to allocate journal blocks: " << status;
return zx::error(status);
}
info->integrity_slices = static_cast<blk_t>(request.length);
// Data.
ZX_ASSERT(options.fvm_data_slices > 0);
request.length = options.fvm_data_slices;
request.offset = kFVMBlockDataStart / kBlocksPerSlice;
if (status = device->VolumeExtend(request.offset, request.length); status != ZX_OK) {
FX_LOGS(ERROR) << "minfs mkfs: Failed to allocate data blocks: " << status;
return zx::error(status);
}
info->dat_slices = options.fvm_data_slices;
return zx::ok();
}
#endif
// Verifies that the allocated slices are sufficient to hold the allocated data
// structures of the filesystem.
zx::result<> VerifySlicesSize(const Superblock& info, const TransactionLimits& limits,
size_t blocks_per_slice) {
size_t ibm_blocks_needed = (info.inode_count + kMinfsBlockBits - 1) / kMinfsBlockBits;
size_t ibm_blocks_allocated = info.ibm_slices * blocks_per_slice;
if (ibm_blocks_needed > ibm_blocks_allocated) {
FX_LOGS(ERROR) << "Not enough slices for inode bitmap";
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (ibm_blocks_allocated + info.ibm_block >= info.abm_block) {
FX_LOGS(ERROR) << "Inode bitmap collides into block bitmap";
return zx::error(ZX_ERR_INVALID_ARGS);
}
size_t abm_blocks_needed = (info.block_count + kMinfsBlockBits - 1) / kMinfsBlockBits;
size_t abm_blocks_allocated = info.abm_slices * blocks_per_slice;
if (abm_blocks_needed > abm_blocks_allocated) {
FX_LOGS(ERROR) << "Not enough slices for block bitmap";
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (abm_blocks_allocated + info.abm_block >= info.ino_block) {
FX_LOGS(ERROR) << "Block bitmap collides with inode table";
return zx::error(ZX_ERR_INVALID_ARGS);
}
size_t ino_blocks_needed = (info.inode_count + kMinfsInodesPerBlock - 1) / kMinfsInodesPerBlock;
size_t ino_blocks_allocated = info.ino_slices * blocks_per_slice;
if (ino_blocks_needed > ino_blocks_allocated) {
FX_LOGS(ERROR) << "Not enough slices for inode table";
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (ino_blocks_allocated + info.ino_block >= info.integrity_start_block) {
FX_LOGS(ERROR) << "Inode table collides with data blocks";
return zx::error(ZX_ERR_INVALID_ARGS);
}
size_t journal_blocks_needed = limits.GetMinimumIntegrityBlocks();
size_t journal_blocks_allocated = info.integrity_slices * blocks_per_slice;
if (journal_blocks_needed > journal_blocks_allocated) {
FX_LOGS(ERROR) << "Not enough slices for journal";
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (journal_blocks_allocated + info.integrity_start_block > info.dat_block) {
FX_LOGS(ERROR) << "Journal collides with data blocks";
return zx::error(ZX_ERR_INVALID_ARGS);
}
size_t dat_blocks_needed = info.block_count;
size_t dat_blocks_allocated = info.dat_slices * blocks_per_slice;
if (dat_blocks_needed > dat_blocks_allocated) {
FX_LOGS(ERROR) << "Not enough slices for data blocks";
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (dat_blocks_allocated + info.dat_block > std::numeric_limits<blk_t>::max()) {
FX_LOGS(ERROR) << "Data blocks overflow blk_t";
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (dat_blocks_needed <= 1) {
FX_LOGS(ERROR) << "Not enough data blocks";
return zx::error(ZX_ERR_INVALID_ARGS);
}
return zx::ok();
}
// Fuses "reading the superblock from storage" with "correcting if it is wrong".
zx::result<Superblock> LoadSuperblockWithRepair(Bcache* bc, bool repair) {
auto info_or = LoadSuperblock(bc);
if (info_or.is_error()) {
if (!repair) {
FX_LOGS(ERROR) << "Cannot load superblock; not attempting to repair";
return info_or.take_error();
}
FX_LOGS(WARNING) << "Attempting to repair superblock";
#ifdef __Fuchsia__
info_or = RepairSuperblock(bc, bc->device(), bc->Maxblk());
if (info_or.is_error()) {
FX_LOGS(ERROR) << "Unable to repair corrupt filesystem.";
return info_or.take_error();
}
#else
return zx::error(ZX_ERR_NOT_SUPPORTED);
#endif
}
return info_or;
}
#ifdef __Fuchsia__
// Replays the journal and reloads the superblock (it may have been present in the journal).
//
// |info| is both an input and output parameter; it may be overwritten.
zx::result<fs::JournalSuperblock> ReplayJournalReloadSuperblock(Bcache* bc, Superblock* info) {
auto journal_block_or = ReplayJournal(bc, *info);
if (journal_block_or.is_error()) {
FX_LOGS(ERROR) << "Cannot replay journal";
return journal_block_or.take_value();
}
// Re-load the superblock after replaying the journal.
auto new_info_or = LoadSuperblock(bc);
if (new_info_or.is_error()) {
return new_info_or.take_error();
}
*info = std::move(new_info_or.value());
return journal_block_or;
}
#endif
} // namespace
zx_time_t GetTimeUTC() {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
zx_time_t time = zx_time_add_duration(ZX_SEC(ts.tv_sec), ts.tv_nsec);
return time;
}
void DumpInfo(const Superblock& info) {
FX_LOGS(DEBUG) << "magic0: " << std::setw(10) << info.magic0;
FX_LOGS(DEBUG) << "magic1: " << std::setw(10) << info.magic1;
FX_LOGS(DEBUG) << "major version: " << std::setw(10) << info.major_version;
FX_LOGS(DEBUG) << "data blocks: " << std::setw(10) << info.block_count << " (size "
<< info.block_size << ")";
FX_LOGS(DEBUG) << "inodes: " << std::setw(10) << info.inode_count << " (size " << info.inode_size
<< ")";
FX_LOGS(DEBUG) << "allocated blocks @ " << std::setw(10) << info.alloc_block_count;
FX_LOGS(DEBUG) << "allocated inodes @ " << std::setw(10) << info.alloc_inode_count;
FX_LOGS(DEBUG) << "inode bitmap @ " << std::setw(10) << info.ibm_block;
FX_LOGS(DEBUG) << "alloc bitmap @ " << std::setw(10) << info.abm_block;
FX_LOGS(DEBUG) << "inode table @ " << std::setw(10) << info.ino_block;
FX_LOGS(DEBUG) << "integrity start block @ " << std::setw(10) << info.integrity_start_block;
FX_LOGS(DEBUG) << "data blocks @ " << std::setw(10) << info.dat_block;
FX_LOGS(DEBUG) << "FVM-aware: " << ((info.flags & kMinfsFlagFVM) ? "YES" : "NO");
FX_LOGS(DEBUG) << "checksum: " << std::setw(10) << info.checksum;
FX_LOGS(DEBUG) << "generation count: " << std::setw(10) << info.generation_count;
FX_LOGS(DEBUG) << "oldest_minor_version: " << std::setw(10) << info.oldest_minor_version;
FX_LOGS(DEBUG) << "slice_size: " << info.slice_size;
FX_LOGS(DEBUG) << "ibm_slices: " << info.ibm_slices;
FX_LOGS(DEBUG) << "abm_slices: " << info.abm_slices;
FX_LOGS(DEBUG) << "ino_slices: " << info.ino_slices;
FX_LOGS(DEBUG) << "integrity_slices: " << info.integrity_slices;
FX_LOGS(DEBUG) << "dat_slices: " << info.integrity_slices;
}
void DumpInode(const Inode* inode, ino_t ino) {
FX_LOGS(DEBUG) << "inode[" << ino << "]: magic: " << std::setw(10) << inode->magic;
FX_LOGS(DEBUG) << "inode[" << ino << "]: size: " << std::setw(10) << inode->size;
FX_LOGS(DEBUG) << "inode[" << ino << "]: blocks: " << std::setw(10) << inode->block_count;
FX_LOGS(DEBUG) << "inode[" << ino << "]: links: " << std::setw(10) << inode->link_count;
}
void UpdateChecksum(Superblock* info) {
// Recalculate checksum.
info->generation_count += 1;
info->checksum = 0;
info->checksum = crc32(0, reinterpret_cast<uint8_t*>(info), sizeof(*info));
}
#ifdef __Fuchsia__
zx::result<> CheckSuperblock(const Superblock& info, block_client::BlockDevice* device,
uint32_t max_blocks) {
#else
zx::result<> CheckSuperblock(const Superblock& info, uint32_t max_blocks) {
#endif
DumpInfo(info);
// We validate the checksum first, but still validate the version before aborting since the
// checksum was added in Minfs version 8/0 (maj/min).
bool bad_checksum;
{
Superblock chksum_info;
memcpy(&chksum_info, &info, sizeof(chksum_info));
chksum_info.checksum = 0;
uint32_t checksum =
crc32(0, reinterpret_cast<const uint8_t*>(&chksum_info), sizeof(chksum_info));
bad_checksum = (info.checksum != checksum);
if (bad_checksum) {
FX_LOGS(ERROR) << "bad checksum: " << info.checksum << ". Expected: " << checksum;
}
}
if ((info.magic0 != kMinfsMagic0) || (info.magic1 != kMinfsMagic1)) {
FX_LOGS(ERROR) << "bad magic: " << std::setfill('0') << std::setw(8) << info.magic0
<< ". Minfs magic: " << std::setfill(' ') << std::setw(8) << kMinfsMagic0;
return zx::error(ZX_ERR_WRONG_TYPE);
}
if (info.major_version != kMinfsCurrentMajorVersion) {
FX_LOGS(ERROR) << "FS major version: " << std::setfill('0') << std::setw(8) << std::hex
<< info.major_version << ". Driver major version: " << std::setw(8)
<< kMinfsCurrentMajorVersion;
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
if (bad_checksum) {
// Abort processing other fields if the checksum failed (we already logged an error above).
return zx::error(ZX_ERR_IO_DATA_INTEGRITY);
}
if ((info.block_size != kMinfsBlockSize) || (info.inode_size != kMinfsInodeSize)) {
FX_LOGS(ERROR) << "bsz/isz " << info.block_size << "/" << info.inode_size << " unsupported";
return zx::error(ZX_ERR_IO_DATA_INTEGRITY);
}
if (info.alloc_block_count > info.block_count) {
FX_LOGS(ERROR) << "Number of allocated blocks (" << info.alloc_block_count
<< ") exceeds total number of blocks (" << info.block_count << ")!";
return zx::error(ZX_ERR_IO_DATA_INTEGRITY);
}
if (info.alloc_inode_count > info.inode_count) {
FX_LOGS(ERROR) << "Number of allocated inodes (" << info.alloc_inode_count
<< ") exceeds total number of inodes (" << info.inode_count << ")!";
return zx::error(ZX_ERR_IO_DATA_INTEGRITY);
}
TransactionLimits limits(info);
if ((info.flags & kMinfsFlagFVM) == 0) {
if (info.dat_block + info.block_count != max_blocks) {
FX_LOGS(ERROR) << "too large for device";
return zx::error(ZX_ERR_IO_DATA_INTEGRITY);
}
if (info.dat_block - info.integrity_start_block < limits.GetMinimumIntegrityBlocks()) {
FX_LOGS(ERROR) << "journal too small";
return zx::error(ZX_ERR_BAD_STATE);
}
} else {
const size_t kBlocksPerSlice = info.slice_size / info.BlockSize();
zx::result<> status;
#ifdef __Fuchsia__
status = CheckSlices(info, kBlocksPerSlice, device, /*repair_slices=*/false);
if (status.is_error()) {
return status.take_error();
}
#endif
status = VerifySlicesSize(info, limits, kBlocksPerSlice);
if (status.is_error()) {
return status.take_error();
}
}
return zx::ok();
}
#ifndef __Fuchsia__
BlockOffsets::BlockOffsets(const Bcache& bc, const SuperblockManager& sb) {
if (!bc.extent_lengths_.empty()) {
ZX_ASSERT(bc.extent_lengths_.size() == kExtentCount);
ibm_block_count_ = static_cast<blk_t>(bc.extent_lengths_[1] / sb.Info().BlockSize());
abm_block_count_ = static_cast<blk_t>(bc.extent_lengths_[2] / sb.Info().BlockSize());
ino_block_count_ = static_cast<blk_t>(bc.extent_lengths_[3] / sb.Info().BlockSize());
integrity_block_count_ = static_cast<blk_t>(bc.extent_lengths_[4] / sb.Info().BlockSize());
dat_block_count_ = static_cast<blk_t>(bc.extent_lengths_[5] / sb.Info().BlockSize());
ibm_start_block_ = static_cast<blk_t>(bc.extent_lengths_[0] / sb.Info().BlockSize());
abm_start_block_ = ibm_start_block_ + ibm_block_count_;
ino_start_block_ = abm_start_block_ + abm_block_count_;
integrity_start_block_ = ino_start_block_ + ino_block_count_;
dat_start_block_ = integrity_start_block_ + integrity_block_count_;
} else {
ibm_start_block_ = sb.Info().ibm_block;
abm_start_block_ = sb.Info().abm_block;
ino_start_block_ = sb.Info().ino_block;
integrity_start_block_ = sb.Info().integrity_start_block;
dat_start_block_ = sb.Info().dat_block;
ibm_block_count_ = abm_start_block_ - ibm_start_block_;
abm_block_count_ = ino_start_block_ - abm_start_block_;
ino_block_count_ = dat_start_block_ - ino_start_block_;
integrity_block_count_ = dat_start_block_ - integrity_start_block_;
dat_block_count_ = sb.Info().block_count;
}
}
#endif
std::unique_ptr<Bcache> Minfs::Destroy(std::unique_ptr<Minfs> minfs) {
minfs->Terminate();
return std::move(minfs->bc_);
}
void Minfs::Terminate() {
#ifdef __Fuchsia__
// Try to cancel any scheduled syncs, if it can't then if the dispatcher is running on another
// thread, ensure that there isn't a sync running by pushing another task into it.
if (dispatcher_ && journal_sync_task_.Cancel() != ZX_OK &&
dispatcher_ != async_get_default_dispatcher()) {
sync_completion_t completion;
async::TaskClosure finish_periodic_sync(
[&completion]() { sync_completion_signal(&completion); });
finish_periodic_sync.Post(dispatcher_);
sync_completion_wait(&completion, ZX_TIME_INFINITE);
}
StopWriteback();
dispatcher_ = nullptr;
fbl::AutoLock lock(&hash_lock_);
vnode_hash_.clear();
#endif
}
zx::result<std::unique_ptr<Transaction>> Minfs::BeginTransaction(size_t reserve_inodes,
size_t reserve_blocks) {
ZX_DEBUG_ASSERT(reserve_inodes <= TransactionLimits::kMaxInodeBitmapBlocks);
#ifdef __Fuchsia__
if (journal_ == nullptr) {
return zx::error(ZX_ERR_BAD_STATE);
}
if (!journal_->IsWritebackEnabled()) {
return zx::error(ZX_ERR_IO_REFUSED);
}
// TODO(planders): Once we are splitting up write transactions, assert this on host as well.
ZX_DEBUG_ASSERT(reserve_blocks <= limits_.GetMaximumDataBlocks());
#endif
// Reserve blocks from allocators before returning WritebackWork to client.
auto transaction_or = Transaction::Create(this, reserve_inodes, reserve_blocks, inodes_.get());
#ifdef __Fuchsia__
if (transaction_or.status_value() == ZX_ERR_NO_SPACE &&
(reserve_blocks > 0 || reserve_inodes > 0)) {
// When there's no more space, flush the journal in case a recent transaction has freed blocks
// but has yet to be flushed from the journal and committed. Then, try again.
FX_LOGS_FIRST_N(INFO, 10)
<< "Unable to reserve blocks. Flushing journal in attempt to reclaim unlinked blocks.";
auto sync_status = BlockingJournalSync();
if (sync_status.is_error()) {
FX_LOGS(ERROR) << "Failed to flush journal (status: " << sync_status.status_string() << ")";
inspect_tree_.OnOutOfSpace();
// Return the original status.
return transaction_or.take_error();
}
transaction_or = Transaction::Create(this, reserve_inodes, reserve_blocks, inodes_.get());
if (transaction_or.is_ok()) {
inspect_tree_.OnRecoveredSpace();
}
}
if (transaction_or.is_error()) {
FX_LOGS_FIRST_N(ERROR, 10) << "Failed to reserve blocks for transaction (status: "
<< transaction_or.status_string() << ")";
if (transaction_or.error_value() == ZX_ERR_NO_SPACE) {
inspect_tree_.OnOutOfSpace();
}
}
#endif
return transaction_or;
}
#ifdef __Fuchsia__
void Minfs::EnqueueCallback(SyncCallback callback) {
if (callback) {
journal_->schedule_task(journal_->Sync().then(
[closure = std::move(callback)](fpromise::result<void, zx_status_t>& result) mutable
-> fpromise::result<void, zx_status_t> {
if (result.is_ok()) {
closure(ZX_OK);
} else {
closure(result.error());
}
return fpromise::ok();
}));
} else {
journal_->schedule_task(journal_->Sync());
}
}
#endif
// To be used with promises to hold on to an object and release it when executed. It is used below
// to pin vnodes that might be referenced in a transaction and to keep deallocated blocks reserved
// until the transaction hits the device. See below for more.
template <typename T>
class ReleaseObject {
public:
explicit ReleaseObject(T object) : object_(std::move(object)) {}
void operator()([[maybe_unused]] const zx::result<void>& dont_care) { object_.reset(); }
private:
std::optional<T> object_;
};
void Minfs::CommitTransaction(std::unique_ptr<Transaction> transaction) {
transaction->inode_reservation().Commit(transaction.get());
transaction->block_reservation().Commit(transaction.get());
if (sb_->is_dirty()) {
sb_->Write(transaction.get(), UpdateBackupSuperblock::kNoUpdate);
}
#ifdef __Fuchsia__
ZX_DEBUG_ASSERT(journal_ != nullptr);
auto data_operations = transaction->RemoveDataOperations();
auto metadata_operations = transaction->RemoveMetadataOperations();
ZX_DEBUG_ASSERT(BlockCount(metadata_operations) <= limits_.GetMaximumEntryDataBlocks());
TRACE_DURATION("minfs", "CommitTransaction", "data_ops", data_operations.size(), "metadata_ops",
metadata_operations.size());
// We take the pending block deallocations here and hold on to them until the transaction has
// committed. Otherwise, it would be possible for data writes in a later transaction to make it
// out to those blocks, but if the transaction that freed those blocks doesn't make it, we will
// have erroneously overwritten those blocks. We don't need to do the same for inode allocations
// because writes to those blocks are always done via the journal which are always sequenced.
//
// There are some potential optimisations that probably aren't worth doing:
//
// * We only need to keep the blocks reserved for data writes. We could allow the blocks to be
// used for metadata (e.g. indirect blocks).
//
// * The allocator will currently reserve inodes that are freed in the same transaction i.e. it
// won't be possible to use free inodes until the next transaction. This probably can't
// happen anyway.
zx_status_t status = journal_->CommitTransaction(
{.metadata_operations = metadata_operations,
.data_promise = data_operations.empty() ? fs::Journal::Promise()
: journal_->WriteData(std::move(data_operations)),
// Keep blocks reserved until committed.
.commit_callback = [pending_deallocations =
transaction->block_reservation().TakePendingDeallocations()] {},
// Keep vnodes alive until complete because we cache data and it's not safe to read new
// data until the transaction is complete (and we could end up doing that if the vnode
// gets destroyed and then quickly recreated).
.complete_callback = [pinned_vnodes = transaction->RemovePinnedVnodes()] {}});
if (status != ZX_OK) {
FX_LOGS(ERROR) << "CommitTransaction failed: " << zx_status_get_string(status);
}
// Update filesystem usage information now that the transaction has been committed.
inspect_tree_.UpdateSpaceUsage(Info(), BlocksReserved());
if (!journal_sync_task_.is_pending()) {
// During mount, there isn't a dispatcher, so we won't queue a flush, but that won't matter
// since the only changes will be things like whether the volume is clean and it doesn't
// matter if they're not persisted.
if (dispatcher_) {
journal_sync_task_.PostDelayed(dispatcher_, kJournalBackgroundSyncTime);
}
}
#else
bc_->RunRequests(transaction->TakeOperations());
#endif
}
void Minfs::FsckAtEndOfTransaction() {
#ifdef __Fuchsia__
bc_->Pause();
{
auto bcache_or = Bcache::Create(bc_->device(), bc_->Maxblk());
ZX_ASSERT(bcache_or.is_ok());
bcache_or = Fsck(std::move(bcache_or.value()), FsckOptions{.read_only = true, .quiet = true});
ZX_ASSERT(bcache_or.is_ok());
}
bc_->Resume();
#endif
}
#ifdef __Fuchsia__
void Minfs::Sync(SyncCallback closure) {
if (journal_ == nullptr) {
if (closure)
closure(ZX_OK);
return;
}
auto dirty_vnodes = GetDirtyVnodes();
for (const fbl::RefPtr<VnodeMinfs>& vnode : dirty_vnodes) {
auto status = vnode->FlushCachedWrites();
ZX_ASSERT(status.is_ok());
}
EnqueueCallback(std::move(closure));
}
#endif
#ifdef __Fuchsia__
Minfs::Minfs(async_dispatcher_t* dispatcher, std::unique_ptr<Bcache> bc,
std::unique_ptr<SuperblockManager> sb, std::unique_ptr<Allocator> block_allocator,
std::unique_ptr<InodeManager> inodes, const MountOptions& mount_options,
fs::ManagedVfs* vfs)
: bc_(std::move(bc)),
sb_(std::move(sb)),
block_allocator_(std::move(block_allocator)),
inodes_(std::move(inodes)),
journal_sync_task_([this]() { Sync(); }),
inspect_tree_(dispatcher, bc_->device()),
limits_(sb_->Info()),
mount_options_(mount_options),
dispatcher_(dispatcher),
vfs_(vfs) {
zx::event::create(0, &fs_id_);
}
#else
Minfs::Minfs(std::unique_ptr<Bcache> bc, std::unique_ptr<SuperblockManager> sb,
std::unique_ptr<Allocator> block_allocator, std::unique_ptr<InodeManager> inodes,
BlockOffsets offsets, const MountOptions& mount_options, fs::Vfs* vfs)
: bc_(std::move(bc)),
sb_(std::move(sb)),
block_allocator_(std::move(block_allocator)),
inodes_(std::move(inodes)),
offsets_(offsets),
limits_(sb_->Info()),
mount_options_(mount_options),
vfs_(vfs) {}
#endif
Minfs::~Minfs() { Terminate(); }
zx::result<> Minfs::InoFree(Transaction* transaction, VnodeMinfs* vn) {
TRACE_DURATION("minfs", "Minfs::InoFree", "ino", vn->GetIno());
#ifdef __Fuchsia__
vn->CancelPendingWriteback();
#endif
inodes_->Free(transaction, vn->GetIno());
auto status = vn->BlocksShrink(transaction, 0);
if (status.is_error())
return status;
vn->MarkPurged();
InodeUpdate(transaction, vn->GetIno(), vn->GetInode());
ZX_DEBUG_ASSERT(vn->GetInode()->block_count == 0);
ZX_DEBUG_ASSERT(vn->IsUnlinked());
return zx::ok();
}
void Minfs::AddUnlinked(PendingWork* transaction, VnodeMinfs* vn) {
ZX_DEBUG_ASSERT(vn->GetInode()->link_count == 0);
Superblock* info = sb_->MutableInfo();
if (info->unlinked_tail == 0) {
// If no other vnodes are unlinked, |vn| is now both the head and the tail.
ZX_DEBUG_ASSERT(info->unlinked_head == 0);
info->unlinked_head = vn->GetIno();
info->unlinked_tail = vn->GetIno();
} else {
// Since all vnodes in the unlinked list are necessarily open, the last vnode
// must currently exist in the vnode lookup.
fbl::RefPtr<VnodeMinfs> last_vn = VnodeLookupInternal(info->unlinked_tail);
ZX_DEBUG_ASSERT(last_vn != nullptr);
// Add |vn| to the end of the unlinked list.
last_vn->SetNextInode(vn->GetIno());
vn->SetLastInode(last_vn->GetIno());
info->unlinked_tail = vn->GetIno();
last_vn->InodeSync(transaction, kMxFsSyncDefault);
vn->InodeSync(transaction, kMxFsSyncDefault);
}
}
void Minfs::RemoveUnlinked(PendingWork* transaction, VnodeMinfs* vn) {
if (vn->GetInode()->last_inode == 0) {
// If |vn| is the first unlinked inode, we just need to update the list head
// to the next inode (which may not exist).
ZX_DEBUG_ASSERT_MSG(Info().unlinked_head == vn->GetIno(),
"Vnode %u has no previous link, but is not listed as unlinked list head",
vn->GetIno());
sb_->MutableInfo()->unlinked_head = vn->GetInode()->next_inode;
} else {
// Set the previous vnode's next to |vn|'s next.
fbl::RefPtr<VnodeMinfs> last_vn = VnodeLookupInternal(vn->GetInode()->last_inode);
ZX_DEBUG_ASSERT(last_vn != nullptr);
last_vn->SetNextInode(vn->GetInode()->next_inode);
last_vn->InodeSync(transaction, kMxFsSyncDefault);
}
if (vn->GetInode()->next_inode == 0) {
// If |vn| is the last unlinked inode, we just need to update the list tail
// to the previous inode (which may not exist).
ZX_DEBUG_ASSERT_MSG(Info().unlinked_tail == vn->GetIno(),
"Vnode %u has no next link, but is not listed as unlinked list tail",
vn->GetIno());
sb_->MutableInfo()->unlinked_tail = vn->GetInode()->last_inode;
} else {
// Set the next vnode's previous to |vn|'s previous.
fbl::RefPtr<VnodeMinfs> next_vn = VnodeLookupInternal(vn->GetInode()->next_inode);
ZX_DEBUG_ASSERT(next_vn != nullptr);
next_vn->SetLastInode(vn->GetInode()->last_inode);
next_vn->InodeSync(transaction, kMxFsSyncDefault);
}
}
zx::result<> Minfs::PurgeUnlinked() {
ino_t last_ino = 0;
ino_t next_ino = Info().unlinked_head;
ino_t unlinked_count = 0;
if (next_ino == 0) {
ZX_DEBUG_ASSERT(Info().unlinked_tail == 0);
return zx::ok();
}
// Loop through the unlinked list and free all allocated resources.
fbl::RefPtr<VnodeMinfs> vn;
VnodeMinfs::Recreate(this, next_ino, &vn);
ZX_DEBUG_ASSERT(vn->GetInode()->last_inode == 0);
do {
auto transaction_or = BeginTransaction(0, 0);
if (transaction_or.is_error()) {
return transaction_or.take_error();
}
ZX_DEBUG_ASSERT(vn->GetInode()->link_count == 0);
if (auto status = InoFree(transaction_or.value().get(), vn.get()); status.is_error()) {
return status;
}
last_ino = next_ino;
next_ino = vn->GetInode()->next_inode;
sb_->MutableInfo()->unlinked_head = next_ino;
if (next_ino == 0) {
ZX_DEBUG_ASSERT(Info().unlinked_tail == last_ino);
sb_->MutableInfo()->unlinked_tail = 0;
} else {
// Fix the last_inode pointer in the next inode.
VnodeMinfs::Recreate(this, next_ino, &vn);
ZX_DEBUG_ASSERT(vn->GetInode()->last_inode == last_ino);
vn->GetMutableInode()->last_inode = 0;
InodeUpdate(transaction_or.value().get(), next_ino, vn->GetInode());
}
CommitTransaction(std::move(transaction_or.value()));
unlinked_count++;
} while (next_ino != 0);
ZX_DEBUG_ASSERT(Info().unlinked_head == 0);
ZX_DEBUG_ASSERT(Info().unlinked_tail == 0);
if (!mount_options_.quiet) {
FX_LOGS(WARNING) << "Found and purged " << unlinked_count << " unlinked vnode(s) on mount";
}
return zx::ok();
}
#ifdef __Fuchsia__
zx::result<> Minfs::UpdateCleanBitAndOldestRevision(bool is_clean) {
auto transaction_or = BeginTransaction(0, 0);
if (transaction_or.is_error()) {
FX_LOGS(ERROR) << "failed to " << (is_clean ? "set" : "unset")
<< " clean flag: " << transaction_or.error_value();
return transaction_or.take_error();
}
if (kMinfsCurrentMinorVersion < Info().oldest_minor_version) {
sb_->MutableInfo()->oldest_minor_version = kMinfsCurrentMinorVersion;
}
UpdateFlags(transaction_or.value().get(), kMinfsFlagClean, is_clean);
CommitTransaction(std::move(transaction_or.value()));
// Mount/unmount marks filesystem as dirty/clean. When we called UpdateFlags
// above, the underlying subsystems may complete the IO asynchronously. But
// these operations(and any other operations issued before) should be
// persisted to final location before we allow any other operation to the
// filesystem or before we return completion status to the caller.
return BlockingJournalSync();
}
void Minfs::StopWriteback() {
// Minfs already terminated.
if (!bc_) {
return;
}
if (mount_options_.writability == Writability::Writable) {
// Ignore errors here since there is nothing we can do.
[[maybe_unused]] auto _ = UpdateCleanBitAndOldestRevision(/*is_clean=*/true);
}
journal_ = nullptr;
[[maybe_unused]] auto _ = bc_->Sync();
}
#endif
fbl::RefPtr<VnodeMinfs> Minfs::VnodeLookupInternal(uint32_t ino) {
#ifdef __Fuchsia__
fbl::RefPtr<VnodeMinfs> vn;
{
// Avoid releasing a reference to |vn| while holding |hash_lock_|.
fbl::AutoLock lock(&hash_lock_);
auto rawVn = vnode_hash_.find(ino);
if (!rawVn.IsValid()) {
// Nothing exists in the lookup table
return nullptr;
}
vn = fbl::MakeRefPtrUpgradeFromRaw(rawVn.CopyPointer(), hash_lock_);
if (vn == nullptr) {
// The vn 'exists' in the map, but it is being deleted.
// Remove it (by key) so the next person doesn't trip on it,
// and so we can insert another node with the same key into the hash
// map.
// Notably, VnodeRelease erases the vnode by object, not key,
// so it will not attempt to replace any distinct Vnodes that happen
// to be re-using the same inode.
vnode_hash_.erase(ino);
}
}
return vn;
#else
return fbl::RefPtr(vnode_hash_.find(ino).CopyPointer());
#endif
}
void Minfs::InoNew(Transaction* transaction, const Inode* inode, ino_t* out_ino) {
size_t allocated_ino = transaction->AllocateInode();
*out_ino = static_cast<ino_t>(allocated_ino);
// Write the inode back to storage.
InodeUpdate(transaction, *out_ino, inode);
}
zx::result<fbl::RefPtr<VnodeMinfs>> Minfs::VnodeNew(Transaction* transaction, uint32_t type) {
TRACE_DURATION("minfs", "Minfs::VnodeNew");
if ((type != kMinfsTypeFile) && (type != kMinfsTypeDir)) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
fbl::RefPtr<VnodeMinfs> vn;
// Allocate the in-memory vnode
VnodeMinfs::Allocate(this, type, &vn);
// Allocate the on-disk inode
ino_t ino;
InoNew(transaction, vn->GetInode(), &ino);
vn->SetIno(ino);
VnodeInsert(vn.get());
return zx::ok(std::move(vn));
}
void Minfs::VnodeInsert(VnodeMinfs* vn) {
#ifdef __Fuchsia__
fbl::AutoLock lock(&hash_lock_);
#endif
ZX_DEBUG_ASSERT_MSG(!vnode_hash_.find(vn->GetKey()).IsValid(), "ino %u already in map\n",
vn->GetKey());
vnode_hash_.insert(vn);
}
fbl::RefPtr<VnodeMinfs> Minfs::VnodeLookup(uint32_t ino) {
fbl::RefPtr<VnodeMinfs> vn = VnodeLookupInternal(ino);
#ifdef __Fuchsia__
if (vn != nullptr && vn->IsUnlinked()) {
vn = nullptr;
}
#endif
return vn;
}
void Minfs::VnodeRelease(VnodeMinfs* vn) {
#ifdef __Fuchsia__
fbl::AutoLock lock(&hash_lock_);
#endif
vnode_hash_.erase(*vn);
}
zx::result<fbl::RefPtr<VnodeMinfs>> Minfs::VnodeGet(ino_t ino) {
TRACE_DURATION("minfs", "Minfs::VnodeGet", "ino", ino);
if ((ino < 1) || (ino >= Info().inode_count)) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
fbl::RefPtr<VnodeMinfs> vn = VnodeLookup(ino);
if (vn != nullptr) {
return zx::ok(std::move(vn));
}
VnodeMinfs::Recreate(this, ino, &vn);
if (vn->IsUnlinked()) {
// If a vnode we have recreated from disk is unlinked, something has gone wrong during the
// unlink process and our filesystem is now in an inconsistent state. In order to avoid
// further inconsistencies, prohibit access to this vnode.
FX_LOGS(WARNING) << "Attempted to load unlinked vnode " << ino;
return zx::error(ZX_ERR_BAD_STATE);
}
VnodeInsert(vn.get());
return zx::ok(std::move(vn));
}
// Allocate a new data block from the block bitmap.
void Minfs::BlockNew(PendingWork* transaction, blk_t* out_bno) const {
size_t allocated_bno = transaction->AllocateBlock();
*out_bno = static_cast<blk_t>(allocated_bno);
ValidateBno(*out_bno);
}
void Minfs::UpdateFlags(PendingWork* transaction, uint32_t flags, bool set) {
if (set) {
sb_->MutableInfo()->flags |= flags;
} else {
sb_->MutableInfo()->flags &= (~flags);
}
sb_->Write(transaction, UpdateBackupSuperblock::kUpdate);
}
#ifdef __Fuchsia__
void Minfs::BlockSwap(Transaction* transaction, blk_t in_bno, blk_t* out_bno) {
if (in_bno > 0) {
ValidateBno(in_bno);
}
size_t allocated_bno = transaction->SwapBlock(in_bno);
*out_bno = static_cast<blk_t>(allocated_bno);
ValidateBno(*out_bno);
}
#endif
void InitializeDirectory(void* bdata, ino_t ino_self, ino_t ino_parent) {
// The self directory is named "." (name length = 1).
constexpr auto kSelfSize = DirentSize(1);
DirentBuffer self;
self.dirent.ino = ino_self;
self.dirent.reclen = kSelfSize;
self.dirent.namelen = 1;
self.dirent.type = kMinfsTypeDir;
self.dirent.name[0] = '.';
// The parent directory is named ".." (name length = 2).
constexpr auto kParentSize = DirentSize(2);
DirentBuffer parent;
parent.dirent.ino = ino_parent;
parent.dirent.reclen = kParentSize | kMinfsReclenLast;
parent.dirent.namelen = 2;
parent.dirent.type = kMinfsTypeDir;
parent.dirent.name[0] = '.';
parent.dirent.name[1] = '.';
// Construct the output buffer by appending the two entries.
memcpy(bdata, self.raw, kSelfSize);
memcpy(&static_cast<uint8_t*>(bdata)[kSelfSize], parent.raw, kParentSize);
}
zx::result<std::pair<std::unique_ptr<Allocator>, std::unique_ptr<InodeManager>>>
Minfs::ReadInitialBlocks(const Superblock& info, Bcache& bc, SuperblockManager& superblock,
const MountOptions& mount_options) {
#ifdef __Fuchsia__
const blk_t abm_start_block = superblock.Info().abm_block;
const blk_t ibm_start_block = superblock.Info().ibm_block;
const blk_t ino_start_block = superblock.Info().ino_block;
#else
BlockOffsets offsets(bc, superblock);
const blk_t abm_start_block = offsets.AbmStartBlock();
const blk_t ibm_start_block = offsets.IbmStartBlock();
const blk_t ino_start_block = offsets.InoStartBlock();
#endif
fs::BufferedOperationsBuilder builder;
// Block Bitmap allocator initialization.
AllocatorFvmMetadata block_allocator_fvm =
AllocatorFvmMetadata(&superblock, SuperblockAllocatorAccess::Blocks());
AllocatorMetadata block_allocator_meta = AllocatorMetadata(
info.dat_block, abm_start_block, (info.flags & kMinfsFlagFVM) != 0,
std::move(block_allocator_fvm), &superblock, SuperblockAllocatorAccess::Blocks());
std::unique_ptr<PersistentStorage> storage(
#ifdef __Fuchsia__
new PersistentStorage(bc.device(), &superblock, superblock.Info().BlockSize(), nullptr,
std::move(block_allocator_meta), superblock.BlockSize()));
#else
new PersistentStorage(&superblock, superblock.Info().BlockSize(), nullptr,
std::move(block_allocator_meta), superblock.BlockSize()));
#endif
auto block_allocator_or = Allocator::Create(&builder, std::move(storage));
if (block_allocator_or.is_error()) {
FX_LOGS(ERROR) << "Create failed to initialize block allocator: "
<< block_allocator_or.error_value();
return block_allocator_or.take_error();
}
// Inode Bitmap allocator initialization.
AllocatorFvmMetadata inode_allocator_fvm =
AllocatorFvmMetadata(&superblock, SuperblockAllocatorAccess::Inodes());
AllocatorMetadata inode_allocator_meta = AllocatorMetadata(
ino_start_block, ibm_start_block, (info.flags & kMinfsFlagFVM) != 0,
std::move(inode_allocator_fvm), &superblock, SuperblockAllocatorAccess::Inodes());
#ifdef __Fuchsia__
auto inodes_or =
InodeManager::Create(bc.device(), &superblock, &builder, std::move(inode_allocator_meta),
ino_start_block, info.inode_count);
#else
auto inodes_or = InodeManager::Create(&bc, &superblock, &builder, std::move(inode_allocator_meta),
ino_start_block, info.inode_count);
#endif
if (inodes_or.is_error()) {
FX_LOGS(ERROR) << "Create failed to initialize inodes: " << inodes_or.error_value();
return inodes_or.take_error();
}
zx_status_t status = bc.RunRequests(builder.TakeOperations());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Create failed to read initial blocks: " << status;
return zx::error(status);
}
return zx::ok(
std::make_pair(std::move(block_allocator_or.value()), std::move(inodes_or.value())));
}
zx::result<std::unique_ptr<Minfs>> Minfs::Create(FuchsiaDispatcher dispatcher,
std::unique_ptr<Bcache> bc,
const MountOptions& options, PlatformVfs* vfs) {
// Read the superblock before replaying the journal.
auto info_or = LoadSuperblockWithRepair(bc.get(), options.repair_filesystem);
if (info_or.is_error()) {
return info_or.take_error();
}
Superblock& info = info_or.value();
#ifdef __Fuchsia__
// In Terminate we rely on the default dispatcher being set for the dispatcher thread, so
// assert that now.
async::PostTask(dispatcher,
[dispatcher] { ZX_ASSERT(async_get_default_dispatcher() == dispatcher); });
if ((info.flags & kMinfsFlagClean) == 0 && !options.quiet) {
FX_LOGS(WARNING) << "filesystem not unmounted cleanly.";
}
// Replay the journal before loading any other structures.
zx::result<fs::JournalSuperblock> journal_superblock_or;
if (options.writability != Writability::ReadOnlyDisk) {
journal_superblock_or = ReplayJournalReloadSuperblock(bc.get(), &info);
if (journal_superblock_or.is_error()) {
return journal_superblock_or.take_error();
}
} else if (!options.quiet) {
FX_LOGS(WARNING) << "Not replaying journal";
}
#endif
#ifndef __Fuchsia__
if (!bc->extent_lengths_.empty() && bc->extent_lengths_.size() != kExtentCount) {
FX_LOGS(ERROR) << "invalid number of extents";
return zx::error(ZX_ERR_INVALID_ARGS);
}
#endif
IntegrityCheck checks = options.repair_filesystem ? IntegrityCheck::kAll : IntegrityCheck::kNone;
zx::result<std::unique_ptr<SuperblockManager>> sb_or;
#ifdef __Fuchsia__
block_client::BlockDevice* device = bc->device();
sb_or = SuperblockManager::Create(device, info, bc->Maxblk(), checks);
#else
sb_or = SuperblockManager::Create(info, bc->Maxblk(), checks);
#endif
if (sb_or.is_error()) {
FX_LOGS(ERROR) << "Create failed to initialize superblock: " << sb_or.error_value();
return sb_or.take_error();
}
std::unique_ptr<SuperblockManager> sb = std::move(sb_or.value());
auto result = Minfs::ReadInitialBlocks(info, *bc, *sb, options);
if (result.is_error())
return result.take_error();
auto [block_allocator, inodes] = std::move(result).value();
std::unique_ptr<Minfs> out_fs;
#ifdef __Fuchsia__
out_fs = std::unique_ptr<Minfs>(new Minfs(dispatcher, std::move(bc), std::move(sb),
std::move(block_allocator), std::move(inodes), options,
vfs));
if (options.writability != Writability::ReadOnlyDisk) {
auto status = out_fs->InitializeJournal(std::move(journal_superblock_or.value()));
if (status.is_error()) {
FX_LOGS(ERROR) << "Cannot initialize journal";
return status.take_error();
}
if (options.fsck_after_every_transaction) {
FX_LOGS(ERROR) << "Will fsck after every transaction";
out_fs->journal_->set_write_metadata_callback(
fit::bind_member<&Minfs::FsckAtEndOfTransaction>(out_fs.get()));
}
}
if (options.repair_filesystem && (info.flags & kMinfsFlagFVM)) {
// After replaying the journal, it's now safe to repair the FVM slices.
const size_t kBlocksPerSlice = info.slice_size / info.BlockSize();
auto status = CheckSlices(info, kBlocksPerSlice, device, /*repair_slices=*/true);
if (status.is_error()) {
return status.take_error();
}
}
if (options.writability != Writability::ReadOnlyDisk) {
// On a read-write filesystem we unset the kMinfsFlagClean flag to indicate that the
// filesystem may begin receiving modifications.
//
// The kMinfsFlagClean flag is reset on orderly shutdown.
auto status = out_fs->UpdateCleanBitAndOldestRevision(/*is_clean=*/false);
if (status.is_error()) {
return status.take_error();
}
// After loading the rest of the filesystem, purge any remaining nodes in the unlinked list.
status = out_fs->PurgeUnlinked();
if (status.is_error()) {
FX_LOGS(ERROR) << "Cannot purge unlinked list";
return status.take_error();
}
if (options.writability == Writability::ReadOnlyFilesystem) {
// The filesystem should still be "writable"; we set the dirty bit while
// purging the unlinked list. Invoking StopWriteback here unsets the dirty bit.
out_fs->StopWriteback();
}
}
out_fs->InitializeInspectTree();
#else
BlockOffsets offsets(*bc, *sb);
out_fs =
std::unique_ptr<Minfs>(new Minfs(std::move(bc), std::move(sb), std::move(block_allocator),
std::move(inodes), offsets, options, vfs));
#endif // !defined(__Fuchsia__)
return zx::ok(std::move(out_fs));
} // namespace minfs
#ifdef __Fuchsia__
zx::result<fs::JournalSuperblock> ReplayJournal(Bcache* bc, const Superblock& info) {
FX_LOGS(INFO) << "Replaying journal";
auto superblock_or =
fs::ReplayJournal(bc, bc, JournalStartBlock(info), JournalBlocks(info), info.BlockSize());
if (superblock_or.is_error()) {
FX_LOGS(ERROR) << "Failed to replay journal";
} else {
FX_LOGS(DEBUG) << "Journal replayed";
}
return superblock_or;
}
zx::result<> Minfs::InitializeJournal(fs::JournalSuperblock journal_superblock) {
if (journal_ != nullptr) {
FX_LOGS(ERROR) << "Journal was already initialized.";
return zx::error(ZX_ERR_ALREADY_EXISTS);
}
const uint64_t journal_entry_blocks = JournalBlocks(sb_->Info()) - fs::kJournalMetadataBlocks;
std::unique_ptr<storage::BlockingRingBuffer> journal_buffer;
zx_status_t status = storage::BlockingRingBuffer::Create(GetMutableBcache(), journal_entry_blocks,
sb_->Info().BlockSize(),
"minfs-journal-buffer", &journal_buffer);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Cannot create journal buffer";
return zx::error(status);
}
std::unique_ptr<storage::BlockingRingBuffer> writeback_buffer;
status = storage::BlockingRingBuffer::Create(GetMutableBcache(), WritebackCapacity(),
sb_->Info().BlockSize(), "minfs-writeback-buffer",
&writeback_buffer);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Cannot create writeback buffer";
return zx::error(status);
}
journal_ = std::make_unique<fs::Journal>(GetMutableBcache(), std::move(journal_superblock),
std::move(journal_buffer), std::move(writeback_buffer),
JournalStartBlock(sb_->Info()));
return zx::ok();
}
void Minfs::InitializeInspectTree() {
zx::result<fs::FilesystemInfo> fs_info{GetFilesystemInfo()};
if (fs_info.is_error()) {
FX_LOGS(ERROR) << "Failed to initialize Minfs inspect tree: GetFilesystemInfo returned "
<< fs_info.status_string();
return;
}
inspect_tree_.Initialize(fs_info.value(), Info(), BlocksReserved());
}
#endif
zx::result<fbl::RefPtr<VnodeMinfs>> Minfs::OpenRootNode() {
auto vn_or = VnodeGet(kMinfsRootIno);
if (vn_or.is_error()) {
FX_LOGS(ERROR) << "cannot find root inode: " << vn_or.is_error();
return vn_or.take_error();
}
ZX_DEBUG_ASSERT(vn_or->IsDirectory());
return zx::ok(std::move(vn_or.value()));
}
#ifdef __Fuchsia__
zx::result<fs::FilesystemInfo> Minfs::GetFilesystemInfo() {
fs::FilesystemInfo info;
info.SetFsId(fs_id_);
info.name = "minfs";
info.fs_type = fuchsia_fs::VfsType::kMinfs;
info.block_size = static_cast<uint32_t>(BlockSize());
info.max_filename_size = kMinfsMaxNameSize;
fs_inspect::UsageData usage = CalculateSpaceUsage(Info(), BlocksReserved());
info.total_bytes = usage.total_bytes;
info.used_bytes = usage.used_bytes;
info.total_nodes = usage.total_nodes;
info.used_nodes = usage.used_nodes;
const block_client::BlockDevice* device = bc_->device();
if (device) {
zx::result<fs_inspect::FvmData::SizeInfo> size_info =
fs_inspect::FvmData::GetSizeInfoFromDevice(*device);
if (size_info.is_ok()) {
info.free_shared_pool_bytes = size_info->available_space_bytes;
} else {
FX_LOGS(DEBUG) << "Unable to obtain available space: " << size_info.status_string();
}
}
return zx::ok(info);
}
#endif
zx::result<> Mkfs(const MountOptions& options, Bcache* bc) {
Superblock info;
memset(&info, 0x00, sizeof(info));
info.magic0 = kMinfsMagic0;
info.magic1 = kMinfsMagic1;
info.major_version = kMinfsCurrentMajorVersion;
info.flags = kMinfsFlagClean;
info.block_size = kMinfsBlockSize;
info.inode_size = kMinfsInodeSize;
uint32_t blocks = 0;
uint32_t inodes = 0;
#ifdef __Fuchsia__
auto fvm_cleanup = fit::defer([device = bc->device(), &info]() { FreeSlices(&info, device); });
if (auto status = CreateFvmData(options, &info, bc->device()); status.is_error()) {
return status.take_error();
}
inodes = static_cast<uint32_t>(info.ino_slices * info.slice_size / kMinfsInodeSize);
blocks = static_cast<uint32_t>(info.dat_slices * info.slice_size / info.BlockSize());
#endif
if ((info.flags & kMinfsFlagFVM) == 0) {
inodes = kMinfsDefaultInodeCount;
blocks = bc->Maxblk();
}
// Determine how many blocks of inodes, allocation bitmaps,
// and inode bitmaps there are
uint32_t inoblks = (inodes + kMinfsInodesPerBlock - 1) / kMinfsInodesPerBlock;
uint32_t ibmblks = (inodes + kMinfsBlockBits - 1) / kMinfsBlockBits;
uint32_t abmblks = 0;
info.inode_count = inodes;
info.alloc_block_count = 0;
info.alloc_inode_count = 0;
if ((info.flags & kMinfsFlagFVM) == 0) {
blk_t non_dat_blocks;
blk_t journal_blocks = 0;
info.ibm_block = 8;
info.abm_block = info.ibm_block + fbl::round_up(ibmblks, 8u);
for (uint32_t alloc_bitmap_rounded = 8; alloc_bitmap_rounded < blocks;
alloc_bitmap_rounded += 8) {
// Increment bitmap blocks by 8, since we will always round this value up to 8.
ZX_ASSERT(alloc_bitmap_rounded % 8 == 0);
info.ino_block = info.abm_block + alloc_bitmap_rounded;
// Calculate the journal size based on other metadata structures.
TransactionLimits limits(info);
journal_blocks = limits.GetRecommendedIntegrityBlocks();
non_dat_blocks = 8 + fbl::round_up(ibmblks, 8u) + alloc_bitmap_rounded + inoblks;
// If the recommended journal count is too high, try using the minimum instead.
if (non_dat_blocks + journal_blocks >= blocks) {
journal_blocks = limits.GetMinimumIntegrityBlocks();
}
non_dat_blocks += journal_blocks;
if (non_dat_blocks >= blocks) {
FX_LOGS(ERROR) << "mkfs: Partition size ("
<< static_cast<uint64_t>(blocks) * info.BlockSize()
<< " bytes) is too small";
return zx::error(ZX_ERR_INVALID_ARGS);
}
info.block_count = blocks - non_dat_blocks;
// Calculate the exact number of bitmap blocks needed to track this many data blocks.
abmblks = (info.block_count + kMinfsBlockBits - 1) / kMinfsBlockBits;
if (alloc_bitmap_rounded >= abmblks) {
// It is possible that the abmblks value will actually bring us back to the next
// lowest tier of 8-rounded values. This means we may have 8 blocks allocated for
// the block bitmap which will never actually be used. This is not ideal, but is
// expected, and should only happen for very particular block counts.
break;
}
}
info.integrity_start_block = info.ino_block + inoblks;
info.dat_block = info.integrity_start_block + journal_blocks;
} else {
info.block_count = blocks;
abmblks = (info.block_count + kMinfsBlockBits - 1) / kMinfsBlockBits;
info.ibm_block = kFVMBlockInodeBmStart;
info.abm_block = kFVMBlockDataBmStart;
info.ino_block = kFVMBlockInodeStart;
info.integrity_start_block = kFvmSuperblockBackup;
info.dat_block = kFVMBlockDataStart;
}
info.oldest_minor_version = kMinfsCurrentMinorVersion;
DumpInfo(info);
RawBitmap abm;
RawBitmap ibm;
// By allocating the bitmap and then shrinking it, we keep the underlying
// storage a block multiple but ensure we can't allocate beyond the last
// real block or inode.
if (zx_status_t status = abm.Reset(fbl::round_up(info.block_count, kMinfsBlockBits));
status != ZX_OK) {
FX_LOGS(ERROR) << "mkfs: Failed to allocate block bitmap: " << status;
return zx::error(status);
}
if (zx_status_t status = ibm.Reset(fbl::round_up(info.inode_count, kMinfsBlockBits));
status != ZX_OK) {
FX_LOGS(ERROR) << "mkfs: Failed to allocate inode bitmap: " << status;
return zx::error(status);
}
if (zx_status_t status = abm.Shrink(info.block_count); status != ZX_OK) {
FX_LOGS(ERROR) << "mkfs: Failed to shrink block bitmap: " << status;
return zx::error(status);
}
if (zx_status_t status = ibm.Shrink(info.inode_count); status != ZX_OK) {
FX_LOGS(ERROR) << "mkfs: Failed to shrink inode bitmap: " << status;
return zx::error(status);
}
// Write rootdir
uint8_t blk[info.BlockSize()];
memset(blk, 0, sizeof(blk));
InitializeDirectory(blk, kMinfsRootIno, kMinfsRootIno);
if (auto status = bc->Writeblk(info.dat_block + 1, blk); status.is_error()) {
FX_LOGS(ERROR) << "mkfs: Failed to write root directory: " << status.error_value();
return status.take_error();
}
// Update inode bitmap
ibm.Set(0, 1);
ibm.Set(kMinfsRootIno, kMinfsRootIno + 1);
info.alloc_inode_count += 2;
// update block bitmap:
// Reserve the 0th data block (as a 'null' value)
// Reserve the 1st data block (for root directory)
abm.Set(0, 2);
info.alloc_block_count += 2;
// Write allocation bitmap
for (uint32_t n = 0; n < abmblks; n++) {
void* bmdata = fs::GetBlock(info.BlockSize(), abm.StorageUnsafe()->GetData(), n);
memcpy(blk, bmdata, info.BlockSize());
if (auto status = bc->Writeblk(info.abm_block + n, blk); status.is_error()) {
return status.take_error();
}
}
// Write inode bitmap
for (uint32_t n = 0; n < ibmblks; n++) {
void* bmdata = fs::GetBlock(info.BlockSize(), ibm.StorageUnsafe()->GetData(), n);
memcpy(blk, bmdata, info.BlockSize());
if (auto status = bc->Writeblk(info.ibm_block + n, blk); status.is_error()) {
return status.take_error();
}
}
// Write inodes
memset(blk, 0, sizeof(blk));
for (uint32_t n = 0; n < inoblks; n++) {
if (auto status = bc->Writeblk(info.ino_block + n, blk); status.is_error()) {
return status.take_error();
}
}
// Setup root inode
Inode* ino = reinterpret_cast<Inode*>(blk);
ino[kMinfsRootIno].magic = kMinfsMagicDir;
ino[kMinfsRootIno].size = info.BlockSize();
ino[kMinfsRootIno].block_count = 1;
ino[kMinfsRootIno].link_count = 2;
ino[kMinfsRootIno].dirent_count = 2;
ino[kMinfsRootIno].dnum[0] = 1;
ino[kMinfsRootIno].create_time = GetTimeUTC();
(void)bc->Writeblk(info.ino_block, blk);
info.generation_count = 0;
UpdateChecksum(&info);
// Write superblock info to disk.
(void)bc->Writeblk(kSuperblockStart, &info);
// Write backup superblock info to disk.
if ((info.flags & kMinfsFlagFVM) == 0) {
(void)bc->Writeblk(kNonFvmSuperblockBackup, &info);
} else {
(void)bc->Writeblk(kFvmSuperblockBackup, &info);
}
fs::WriteBlocksFn write_blocks_fn = [bc, info](cpp20::span<const uint8_t> buffer,
uint64_t block_offset, uint64_t block_count) {
ZX_ASSERT((block_count + block_offset) <= JournalBlocks(info));
ZX_ASSERT(buffer.size() >= (block_count * info.BlockSize()));
auto data = buffer.data();
while (block_count > 0) {
auto status = bc->Writeblk(static_cast<blk_t>(JournalStartBlock(info) + block_offset), data);
if (status.is_error()) {
return status.status_value();
}
block_offset = safemath::CheckAdd(block_offset, 1).ValueOrDie();
block_count = safemath::CheckSub(block_count, 1).ValueOrDie();
data += info.BlockSize();
}
return ZX_OK;
};
ZX_ASSERT(fs::MakeJournal(JournalBlocks(info), write_blocks_fn) == ZX_OK);
#ifdef __Fuchsia__
fvm_cleanup.cancel();
#endif
return bc->Sync();
}
zx::result<> Minfs::ReadDat(blk_t bno, void* data) {
#ifdef __Fuchsia__
return bc_->Readblk(Info().dat_block + bno, data);
#else
return ReadBlk(bno, offsets_.DatStartBlock(), offsets_.DatBlockCount(), Info().block_count, data);
#endif
}
zx_status_t Minfs::ReadBlock(blk_t start_block_num, void* out_data) const {
return bc_->Readblk(start_block_num, out_data).status_value();
}
#ifndef __Fuchsia__
zx::result<> Minfs::ReadBlk(blk_t bno, blk_t start, blk_t soft_max, blk_t hard_max,
void* data) const {
if (bno >= hard_max) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
if (bno >= soft_max) {
memset(data, 0, BlockSize());
return zx::ok();
}
return bc_->Readblk(start + bno, data);
}
zx::result<std::unique_ptr<minfs::Bcache>> CreateBcacheFromFd(
fbl::unique_fd fd, off_t start, off_t end, const fbl::Vector<size_t>& extent_lengths) {
if (start >= end) {
FX_LOGS(ERROR) << "Insufficient space allocated";
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (extent_lengths.size() != kExtentCount) {
FX_LOGS(ERROR) << "invalid number of extents : " << extent_lengths.size();
return zx::error(ZX_ERR_INVALID_ARGS);
}
struct stat s;
if (fstat(fd.get(), &s) < 0) {
FX_LOGS(ERROR) << "minfs could not find end of file/device";
return zx::error(ZX_ERR_IO);
}
if (s.st_size < end) {
FX_LOGS(ERROR) << "invalid file size";
return zx::error(ZX_ERR_INVALID_ARGS);
}
size_t size = (end - start) / minfs::kMinfsBlockSize;
auto bc_or = minfs::Bcache::Create(std::move(fd), static_cast<uint32_t>(size));
if (bc_or.is_error()) {
FX_LOGS(ERROR) << "cannot create block cache: " << bc_or.error_value();
return bc_or.take_error();
}
if (auto status = bc_or->SetSparse(start, extent_lengths); status.is_error()) {
FX_LOGS(ERROR) << "Bcache is already sparse: " << status.error_value();
return status.take_error();
}
return zx::ok(std::move(bc_or.value()));
}
zx::result<uint64_t> SparseUsedDataSize(fbl::unique_fd fd, off_t start, off_t end,
const fbl::Vector<size_t>& extent_lengths) {
auto bc_or = CreateBcacheFromFd(std::move(fd), start, end, extent_lengths);
if (bc_or.is_error()) {
return bc_or.take_error();
}
return UsedDataSize(bc_or.value());
}
zx::result<uint64_t> SparseUsedInodes(fbl::unique_fd fd, off_t start, off_t end,
const fbl::Vector<size_t>& extent_lengths) {
auto bc_or = CreateBcacheFromFd(std::move(fd), start, end, extent_lengths);
if (bc_or.is_error()) {
return bc_or.take_error();
}
return UsedInodes(bc_or.value());
}
zx::result<uint64_t> SparseUsedSize(fbl::unique_fd fd, off_t start, off_t end,
const fbl::Vector<size_t>& extent_lengths) {
auto bc_or = CreateBcacheFromFd(std::move(fd), start, end, extent_lengths);
if (bc_or.is_error()) {
return bc_or.take_error();
}
return UsedSize(bc_or.value());
}
#endif
#ifdef __Fuchsia__
fbl::Vector<BlockRegion> Minfs::GetAllocatedRegions() const {
return block_allocator_->GetAllocatedRegions();
}
#endif
} // namespace minfs