| // 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 <fcntl.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/lib/storage/vfs/cpp/journal/format.h" |
| #include "src/lib/storage/vfs/cpp/journal/initializer.h" |
| #include "src/lib/storage/vfs/cpp/trace.h" |
| #include "src/storage/minfs/allocator/allocator_reservation.h" |
| #include "src/storage/minfs/file.h" |
| #include "src/storage/minfs/fsck.h" |
| #include "src/storage/minfs/minfs_private.h" |
| #include "src/storage/minfs/writeback.h" |
| |
| #ifdef __Fuchsia__ |
| #include <fidl/fuchsia.minfs/cpp/wire.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/fit/defer.h> |
| #include <lib/inspect/service/cpp/service.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/lib/storage/vfs/cpp/journal/header_view.h" |
| #include "src/lib/storage/vfs/cpp/journal/journal.h" |
| #include "src/lib/storage/vfs/cpp/journal/replay.h" |
| #include "src/storage/fvm/client.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; |
| } |
| extend_request_t request; |
| const size_t kBlocksPerSlice = info->slice_size / info->BlockSize(); |
| if (info->ibm_slices) { |
| request.length = info->ibm_slices; |
| request.offset = kFVMBlockInodeBmStart / kBlocksPerSlice; |
| device->VolumeShrink(request.offset, request.length); |
| } |
| if (info->abm_slices) { |
| request.length = info->abm_slices; |
| request.offset = kFVMBlockDataBmStart / kBlocksPerSlice; |
| device->VolumeShrink(request.offset, request.length); |
| } |
| if (info->ino_slices) { |
| request.length = info->ino_slices; |
| request.offset = kFVMBlockInodeStart / kBlocksPerSlice; |
| device->VolumeShrink(request.offset, request.length); |
| } |
| if (info->dat_slices) { |
| request.length = info->dat_slices; |
| request.offset = kFVMBlockDataStart / kBlocksPerSlice; |
| device->VolumeShrink(request.offset, request.length); |
| } |
| } |
| |
| // Checks all slices against the block device. May shrink the partition. |
| zx::status<> CheckSlices(const Superblock& info, size_t blocks_per_slice, |
| block_client::BlockDevice* device, bool repair_slices) { |
| fuchsia_hardware_block_volume_VolumeManagerInfo manager_info; |
| fuchsia_hardware_block_volume_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; |
| |
| query_request_t request; |
| request.count = 4; |
| request.vslice_start[0] = kFVMBlockInodeBmStart / blocks_per_slice; |
| request.vslice_start[1] = kFVMBlockDataBmStart / blocks_per_slice; |
| request.vslice_start[2] = kFVMBlockInodeStart / blocks_per_slice; |
| request.vslice_start[3] = kFVMBlockDataStart / blocks_per_slice; |
| |
| fuchsia_hardware_block_volume_VsliceRange |
| ranges[fuchsia_hardware_block_volume_MAX_SLICE_REQUESTS]; |
| size_t ranges_count; |
| |
| status = device->VolumeQuerySlices(request.vslice_start, request.count, ranges, &ranges_count); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "unable to query FVM: " << status; |
| return zx::error(ZX_ERR_UNAVAILABLE); |
| } |
| |
| if (ranges_count != request.count) { |
| FX_LOGS(ERROR) << "requested FVM range :" << request.count |
| << " does not match received: " << ranges_count; |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| |
| for (uint32_t i = 0; i < request.count; 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. |
| extend_request_t shrink; |
| shrink.length = fvm_count - minfs_count; |
| shrink.offset = request.vslice_start[i] + minfs_count; |
| if ((status = device->VolumeShrink(shrink.offset, shrink.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::status<> CreateFvmData(const MountOptions& options, Superblock* info, |
| block_client::BlockDevice* device) { |
| fuchsia_hardware_block_volume_VolumeManagerInfo manager_info; |
| fuchsia_hardware_block_volume_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); |
| } |
| |
| extend_request_t 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 * 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::status<> 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::status<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::status<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)); |
| } |
| |
| uint32_t CalculateVsliceCount(const Superblock& superblock) { |
| // Account for an additional slice for the superblock itself. |
| return safemath::checked_cast<uint32_t>(1ull + static_cast<uint64_t>(superblock.ibm_slices) + |
| static_cast<uint64_t>(superblock.abm_slices) + |
| static_cast<uint64_t>(superblock.ino_slices) + |
| static_cast<uint64_t>(superblock.integrity_slices) + |
| static_cast<uint64_t>(superblock.dat_slices)); |
| } |
| |
| #ifdef __Fuchsia__ |
| zx::status<> CheckSuperblock(const Superblock& info, block_client::BlockDevice* device, |
| uint32_t max_blocks) { |
| #else |
| zx::status<> 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::status<> 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_.size() > 0) { |
| 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) { |
| #ifdef __Fuchsia__ |
| minfs->StopWriteback(); |
| #endif |
| return std::move(minfs->bc_); |
| } |
| |
| zx::status<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::status<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 (auto 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_(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() { vnode_hash_.clear(); } |
| |
| zx::status<> 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::status<> 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::status<> 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::status<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::status<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::status<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::status<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__ |
| if ((info.flags & kMinfsFlagClean) == 0 && !options.quiet) { |
| FX_LOGS(WARNING) << "filesystem not unmounted cleanly."; |
| } |
| |
| // Replay the journal before loading any other structures. |
| zx::status<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_.size() != 0 && 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::status<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::status<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::status<> 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::status<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::status<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::status<fs::FilesystemInfo> Minfs::GetFilesystemInfo() { |
| fs::FilesystemInfo info; |
| |
| info.SetFsId(fs_id_); |
| info.name = "minfs"; |
| info.fs_type = VFS_TYPE_MINFS; |
| |
| 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::status<fs_inspect::VolumeData::SizeInfo> size_info = |
| fs_inspect::VolumeData::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 |
| |
| uint32_t BlocksRequiredForInode(uint64_t inode_count) { |
| return safemath::checked_cast<uint32_t>((inode_count + kMinfsInodesPerBlock - 1) / |
| kMinfsInodesPerBlock); |
| } |
| |
| uint32_t BlocksRequiredForBits(uint64_t bit_count) { |
| return safemath::checked_cast<uint32_t>((bit_count + kMinfsBlockBits - 1) / kMinfsBlockBits); |
| } |
| |
| zx::status<> 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::status<> 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::status<> 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::status<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::status<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::status<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::status<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 |