| // 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/fsck.h" |
| |
| #include <lib/cksum.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <iomanip> |
| #include <limits> |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <utility> |
| |
| #include <safemath/checked_math.h> |
| |
| #include "src/storage/lib/vfs/cpp/journal/format.h" |
| #include "src/storage/minfs/format.h" |
| #include "zircon/errors.h" |
| |
| #ifdef __Fuchsia__ |
| |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| |
| #include <storage/buffer/owned_vmoid.h> |
| |
| #include "src/storage/lib/block_client/cpp/reader.h" |
| |
| #else |
| |
| #include <storage/buffer/array_buffer.h> |
| |
| #endif |
| |
| #include "src/storage/minfs/runner.h" |
| |
| namespace minfs { |
| |
| namespace { |
| |
| #ifdef __Fuchsia__ |
| using RawBitmap = bitmap::RawBitmapGeneric<bitmap::VmoStorage>; |
| #else |
| using RawBitmap = bitmap::RawBitmapGeneric<bitmap::DefaultStorage>; |
| #endif |
| |
| // The structure is initialized to an invalid state such that the block offset is the last block |
| // that an inode can address and that block is a double indirect block - this potentially cannot |
| // happen. |
| struct BlockInfo { |
| ino_t owner = std::numeric_limits<ino_t>::max(); // Inode number that maps this block. |
| blk_t offset = std::numeric_limits<blk_t>::max(); // Offset, in blocks, where this block is. |
| BlockType type = BlockType::kDoubleIndirect; // What is this block used as. |
| }; |
| |
| const std::string kBlockInfoDirectStr("direct"); |
| const std::string kBlockInfoIndirectStr("indirect"); |
| const std::string kBlockInfokDoubleIndirectStr("double indirect"); |
| |
| // Given a type of block, returns human readable c-string for the block type. |
| std::string BlockTypeToString(BlockType type) { |
| switch (type) { |
| case BlockType::kDirect: |
| return kBlockInfoDirectStr; |
| case BlockType::kIndirect: |
| return kBlockInfoIndirectStr; |
| case BlockType::kDoubleIndirect: |
| return kBlockInfokDoubleIndirectStr; |
| default: |
| ZX_ASSERT(false); |
| } |
| } |
| |
| // Returns the logical block accessed from the "indirect" structure within an inode. |
| // |direct| refers to the index within the indirect block. |
| blk_t LogicalBlockIndirect(blk_t indirect, blk_t direct = 0) { |
| ZX_DEBUG_ASSERT(indirect < kMinfsIndirect); |
| ZX_DEBUG_ASSERT(direct < kMinfsDirectPerIndirect); |
| const blk_t start = kMinfsDirect; |
| return start + (indirect * kMinfsDirectPerIndirect) + direct; |
| } |
| // Returns the logical block accessed from the "doubly indirect" structure within an inode. |
| // |indirect| refers to an index within the doubly_indirect block. |
| // |direct| refers to an index within |indirect|. |
| blk_t LogicalBlockDoublyIndirect(blk_t doubly_indirect, blk_t indirect = 0, blk_t direct = 0) { |
| ZX_DEBUG_ASSERT(doubly_indirect < kMinfsDoublyIndirect); |
| ZX_DEBUG_ASSERT(indirect < kMinfsDirectPerIndirect); |
| ZX_DEBUG_ASSERT(direct < kMinfsDirectPerIndirect); |
| const blk_t start = kMinfsDirect + (kMinfsIndirect * kMinfsDirectPerIndirect); |
| return start + (kMinfsDirectPerDindirect * doubly_indirect) + |
| (indirect * kMinfsDirectPerIndirect) + direct; |
| } |
| |
| class MinfsChecker { |
| public: |
| static zx::result<std::unique_ptr<MinfsChecker>> Create(FuchsiaDispatcher dispatcher, |
| std::unique_ptr<Bcache> bc, |
| const FsckOptions& options); |
| |
| static std::unique_ptr<Bcache> Destroy(std::unique_ptr<MinfsChecker> checker) { |
| return Runner::Destroy(std::move(checker->runner_)); |
| } |
| |
| void CheckReserved(); |
| zx::result<> CheckInode(ino_t ino, ino_t parent, bool dot_or_dotdot); |
| zx::result<> CheckUnlinkedInodes(); |
| zx::result<> CheckForUnusedBlocks() const; |
| zx::result<> CheckForUnusedInodes() const; |
| zx::result<> CheckLinkCounts() const; |
| zx::result<> CheckAllocatedCounts() const; |
| zx::result<> CheckSuperblockIntegrity() const; |
| |
| void DumpStats(); |
| |
| bool conforming() const { return conforming_; } |
| |
| private: |
| struct InodeNthBnoResult { |
| blk_t bno = 0; |
| blk_t next_n = false; |
| }; |
| |
| explicit MinfsChecker(std::unique_ptr<Runner> runner, const FsckOptions& fsck_options) |
| : fsck_options_(fsck_options), runner_(std::move(runner)), fs_(runner_->minfs()) {} |
| |
| // Not copyable or movable |
| MinfsChecker(const MinfsChecker&) = delete; |
| MinfsChecker& operator=(const MinfsChecker&) = delete; |
| MinfsChecker(MinfsChecker&&) = delete; |
| MinfsChecker& operator=(MinfsChecker&&) = delete; |
| |
| // Reads the inode and optionally checks the magic value to ensure it is either a file or |
| // directory. |
| zx::result<Inode> GetInode(ino_t ino, bool check_magic = true) const; |
| |
| // Returns the nth block within an inode, relative to the start of the |
| // file. Returns the |
| // |
| // Returns a pair of `next_n` and `bno`. `next_n` might contain a bno. This `next_n` is for |
| // performance reasons -- it allows fsck to avoid repeatedly checking the same |
| // indirect / doubly indirect blocks with all internal bno unallocated. |
| zx::result<InodeNthBnoResult> GetInodeNthBno(Inode* inode, blk_t n); |
| zx::result<> CheckDirectory(Inode* inode, ino_t ino, ino_t parent, uint32_t flags); |
| std::optional<std::string> CheckDataBlock(blk_t bno, BlockInfo block_info); |
| zx::result<> CheckFile(Inode* inode, ino_t ino); |
| // Checks just one inode without recursing. inode_stack_ will contain any child inodes (if the |
| // inode happens to be a directory). |
| zx::result<> CheckOneInode(ino_t ino, ino_t parent, bool dot_or_dotdot); |
| |
| const FsckOptions fsck_options_; |
| |
| // "Set once"-style flag to identify if anything nonconforming |
| // was found in the underlying filesystem -- even if it was fixed. |
| bool conforming_ = true; |
| |
| std::unique_ptr<Runner> runner_; |
| Minfs& fs_; |
| RawBitmap checked_inodes_; |
| RawBitmap checked_blocks_; |
| ino_t max_inode_ = 0; |
| |
| // blk_info_ provides reverse lookup capability - a block number is mapped to |
| // a set of BlockInfo. The filesystem is inconsistent if a block has more than |
| // one <inode, offset, type>. |
| std::map<blk_t, std::vector<BlockInfo>> blk_info_; |
| |
| uint32_t alloc_inodes_ = 0; |
| uint32_t alloc_blocks_ = 0; |
| fbl::Array<int64_t> links_; |
| |
| blk_t cached_doubly_indirect_; |
| blk_t cached_indirect_; |
| uint8_t doubly_indirect_cache_[kMinfsBlockSize]; |
| uint8_t indirect_cache_[kMinfsBlockSize]; |
| uint32_t indirect_blocks_ = 0; |
| uint32_t directory_blocks_ = 0; |
| |
| // A stack of inodes to check. |
| std::vector<std::tuple</*ino=*/ino_t, /*parent=*/ino_t, /*dot_or_dotdot=*/bool>> inode_stack_; |
| }; |
| |
| zx::result<Inode> MinfsChecker::GetInode(ino_t ino, bool check_magic) const { |
| if (ino >= fs_.Info().inode_count) { |
| FX_LOGS(ERROR) << "check: ino " << ino << " out of range (>=" << fs_.Info().inode_count << ")"; |
| return zx::error(ZX_ERR_OUT_OF_RANGE); |
| } |
| |
| Inode inode; |
| fs_.GetInodeManager()->Load(ino, &inode); |
| if (check_magic && (inode.magic != kMinfsMagicFile) && (inode.magic != kMinfsMagicDir)) { |
| FX_LOGS(ERROR) << "check: ino " << ino << " has bad magic 0x" << std::hex << inode.magic; |
| return zx::error(ZX_ERR_IO_DATA_INTEGRITY); |
| } |
| return zx::ok(inode); |
| } |
| |
| #define CD_DUMP 1 |
| #define CD_RECURSE 2 |
| |
| zx::result<MinfsChecker::InodeNthBnoResult> MinfsChecker::GetInodeNthBno(Inode* inode, blk_t n) { |
| InodeNthBnoResult result{ |
| .bno{}, |
| // The default value for the "next n". It's easier to set it here anyway, |
| // since we proceed to modify n in the code below. |
| .next_n = n + 1, |
| }; |
| |
| if (n < kMinfsDirect) { |
| result.bno = inode->dnum[n]; |
| return zx::ok(result); |
| } |
| |
| n -= kMinfsDirect; |
| uint32_t i = n / kMinfsDirectPerIndirect; // indirect index |
| uint32_t j = n % kMinfsDirectPerIndirect; // direct index |
| |
| if (i < kMinfsIndirect) { |
| blk_t ibno; |
| if ((ibno = inode->inum[i]) == 0) { |
| result.bno = 0; |
| result.next_n = kMinfsDirect + (i + 1) * kMinfsDirectPerIndirect; |
| return zx::ok(result); |
| } |
| |
| if (cached_indirect_ != ibno) { |
| if (auto status = fs_.ReadDat(ibno, indirect_cache_); status.is_error()) { |
| return status.take_error(); |
| } |
| cached_indirect_ = ibno; |
| } |
| |
| uint32_t* ientry = reinterpret_cast<uint32_t*>(indirect_cache_); |
| result.bno = ientry[j]; |
| return zx::ok(result); |
| } |
| |
| n -= kMinfsIndirect * kMinfsDirectPerIndirect; |
| i = n / (kMinfsDirectPerDindirect); // doubly indirect index |
| n -= (i * kMinfsDirectPerDindirect); |
| j = n / kMinfsDirectPerIndirect; // indirect index |
| uint32_t k = n % kMinfsDirectPerIndirect; // direct index |
| |
| if (i < kMinfsDoublyIndirect) { |
| blk_t dibno; |
| if ((dibno = inode->dinum[i]) == 0) { |
| result.bno = 0; |
| result.next_n = kMinfsDirect + kMinfsIndirect * kMinfsDirectPerIndirect + |
| (i + 1) * kMinfsDirectPerDindirect; |
| return zx::ok(result); |
| } |
| |
| if (cached_doubly_indirect_ != dibno) { |
| if (auto status = fs_.ReadDat(dibno, doubly_indirect_cache_); status.is_error()) { |
| return status.take_error(); |
| } |
| cached_doubly_indirect_ = dibno; |
| } |
| |
| uint32_t* dientry = reinterpret_cast<uint32_t*>(doubly_indirect_cache_); |
| blk_t ibno; |
| if ((ibno = dientry[j]) == 0) { |
| result.bno = 0; |
| result.next_n = kMinfsDirect + kMinfsIndirect * kMinfsDirectPerIndirect + |
| (i * kMinfsDirectPerDindirect) + (j + 1) * kMinfsDirectPerIndirect; |
| return zx::ok(result); |
| } |
| |
| if (cached_indirect_ != ibno) { |
| if (auto status = fs_.ReadDat(ibno, indirect_cache_); status.is_error()) { |
| return status.take_error(); |
| } |
| cached_indirect_ = ibno; |
| } |
| |
| uint32_t* ientry = reinterpret_cast<uint32_t*>(indirect_cache_); |
| result.bno = ientry[k]; |
| return zx::ok(result); |
| } |
| |
| return zx::error(ZX_ERR_OUT_OF_RANGE); |
| } |
| |
| zx::result<> MinfsChecker::CheckDirectory(Inode* inode, ino_t ino, ino_t parent, uint32_t flags) { |
| unsigned eno = 0; |
| bool dot = false; |
| bool dotdot = false; |
| uint32_t dirent_count = 0; |
| |
| zx::result<> status; |
| fbl::RefPtr<VnodeMinfs> vn; |
| VnodeMinfs::Recreate(&fs_, ino, &vn); |
| |
| size_t off = 0; |
| bool is_last = false; |
| auto dirent_buffer = std::make_unique<DirentBuffer<kMinfsMaxDirentSize>>(); |
| Dirent* de = &dirent_buffer->dirent; |
| do { |
| size_t actual; |
| status = vn->ReadInternal(nullptr, de, kMinfsDirentSize, off, &actual); |
| if (status.is_ok() && actual == 0 && inode->link_count == 0 && parent == 0) { |
| // This is OK as it's an unlinked directory. |
| break; |
| } |
| if (status.is_error() || actual != kMinfsDirentSize) { |
| FX_LOGS(ERROR) << "check: ino#" << eno << ": Could not read de[" << ino << "] at " << off; |
| if (inode->dirent_count >= 2 && inode->dirent_count == eno - 1) { |
| // So we couldn't read the last direntry, for whatever reason, but our |
| // inode says that we shouldn't have been able to read it anyway. |
| FX_LOGS(ERROR) << "check: de count (" << eno << ") > inode_dirent_count (" |
| << inode->dirent_count << ")"; |
| } |
| return status.is_error() ? status.take_error() : zx::error(ZX_ERR_IO); |
| } |
| |
| const uint32_t rlen = static_cast<uint32_t>(DirentReservedSize(de, off)); |
| const uint32_t dlen = DirentSize(de->namelen); |
| is_last = de->reclen & kMinfsReclenLast; |
| if (!is_last && ((rlen < kMinfsDirentSize) || (dlen > rlen) || (dlen > kMinfsMaxDirentSize) || |
| (rlen & kMinfsDirentAlignmentMask))) { |
| FX_LOGS(ERROR) << "check: ino#" << ino << ": de[" << eno << "]: bad dirent reclen (" << rlen |
| << ") dlen(" << dlen << "), maxsize(" << kMinfsMaxDirentSize << "), size(" |
| << kMinfsDirentSize << ")"; |
| return zx::error(ZX_ERR_IO_DATA_INTEGRITY); |
| } |
| if (de->ino == 0) { |
| if (flags & CD_DUMP) { |
| FX_LOGS(DEBUG) << "ino#" << ino << ": de[" << eno << "]: <empty> reclen=" << rlen; |
| } |
| } else { |
| // Re-read the dirent to acquire the full name |
| status = vn->ReadInternal(nullptr, de, dlen, off, &actual); |
| if (status.is_error() || actual != dlen) { |
| FX_LOGS(ERROR) << "check: Error reading dirent of size: " << dlen; |
| return zx::error(ZX_ERR_IO); |
| } |
| bool dot_or_dotdot = false; |
| if ((de->namelen == 0) || (de->namelen > (rlen - kMinfsDirentSize))) { |
| FX_LOGS(ERROR) << "check: ino#" << ino << ": de[" << eno << "]: invalid namelen " |
| << de->namelen; |
| return zx::error(ZX_ERR_IO_DATA_INTEGRITY); |
| } |
| if ((de->namelen == 1) && (de->name[0] == '.')) { |
| if (dot) { |
| FX_LOGS(ERROR) << "check: ino#" << ino << ": multiple '.' entries"; |
| conforming_ = false; |
| } |
| dot_or_dotdot = true; |
| dot = true; |
| if (de->ino != ino) { |
| FX_LOGS(ERROR) << "check: ino#" << ino << ": de[" << eno << "]: '.' ino=" << de->ino |
| << " (not self!)"; |
| conforming_ = false; |
| } |
| } |
| if ((de->namelen == 2) && (de->name[0] == '.') && (de->name[1] == '.')) { |
| if (dotdot) { |
| FX_LOGS(ERROR) << "check: ino#" << ino << ": multiple '..' entries"; |
| conforming_ = false; |
| } |
| dot_or_dotdot = true; |
| dotdot = true; |
| if (de->ino != parent) { |
| FX_LOGS(ERROR) << "check: ino#" << ino << ": de[" << eno << "]: '..' ino=" << de->ino |
| << " (not parent (ino#" << parent << ")!)"; |
| conforming_ = false; |
| } |
| } |
| if (flags & CD_DUMP) { |
| FX_LOGS(DEBUG) << "ino#" << ino << ": de[" << eno << "]: ino=" << de->ino |
| << " type=" << de->type << " '" << std::string_view(de->name, de->namelen) |
| << "' " << (is_last ? "[last]" : ""); |
| } |
| if (flags & CD_RECURSE) { |
| inode_stack_.push_back(std::make_tuple(de->ino, ino, dot_or_dotdot)); |
| } |
| dirent_count++; |
| } |
| off += rlen; |
| eno++; |
| } while (!is_last); |
| if (inode->link_count == 0 && inode->dirent_count != 0) { |
| FX_LOGS(ERROR) << "check: dirent_count (" << inode->dirent_count |
| << ") for unlinked directory != 0"; |
| conforming_ = false; |
| } |
| if (dirent_count != inode->dirent_count) { |
| FX_LOGS(ERROR) << "check: ino#" << ino << ": dirent_count of " << inode->dirent_count |
| << " != " << dirent_count << " (actual)"; |
| conforming_ = false; |
| } |
| if (dot == false && inode->link_count > 0) { |
| FX_LOGS(ERROR) << "check: ino#" << ino << ": directory missing '.'"; |
| conforming_ = false; |
| } |
| if (dotdot == false && inode->link_count > 0) { |
| FX_LOGS(ERROR) << "check: ino#" << ino << ": directory missing '..'"; |
| conforming_ = false; |
| } |
| return zx::ok(); |
| } |
| |
| std::optional<std::string> MinfsChecker::CheckDataBlock(blk_t bno, BlockInfo block_info) { |
| if (bno == 0) { |
| return std::string("reserved bno"); |
| } |
| if (bno >= fs_.Info().block_count) { |
| return std::string("out of range"); |
| } |
| if (!fs_.GetBlockAllocator().CheckAllocated(bno)) { |
| return std::string("not allocated"); |
| } |
| if (checked_blocks_.Get(bno, bno + 1)) { |
| auto entries = blk_info_[bno].size(); |
| // The entries are printed as |
| // "double-allocated" |
| // " <ino: 4294967295, off: 4294967295 type: DI>\n" |
| std::string str("double-allocated\n"); |
| for (size_t i = 0; i < entries; i++) { |
| str.append(" <ino: " + std::to_string(blk_info_[bno][i].owner) + |
| ", off: " + std::to_string(blk_info_[bno][i].offset) + |
| " type: " + BlockTypeToString(blk_info_[bno][i].type) + ">\n"); |
| } |
| blk_info_[bno].push_back(block_info); |
| return str; |
| } |
| checked_blocks_.Set(bno, bno + 1); |
| std::vector<BlockInfo> vec; |
| vec.push_back(block_info); |
| blk_info_.insert(std::pair<blk_t, std::vector<BlockInfo>>(bno, vec)); |
| alloc_blocks_++; |
| if (block_info.type != BlockType::kDirect) { |
| ++indirect_blocks_; |
| } |
| return std::nullopt; |
| } |
| |
| zx::result<> MinfsChecker::CheckFile(Inode* inode, ino_t ino) { |
| FX_LOGS(DEBUG) << "Direct blocks: "; |
| for (const blk_t& dnum : inode->dnum) { |
| FX_LOGS(DEBUG) << " " << dnum << ","; |
| } |
| FX_LOGS(DEBUG) << " ..."; |
| |
| uint32_t block_count = 0; |
| |
| // count and sanity-check indirect blocks |
| for (unsigned n = 0; n < kMinfsIndirect; n++) { |
| if (inode->inum[n]) { |
| BlockInfo block_info = {ino, LogicalBlockIndirect(n), BlockType::kIndirect}; |
| auto msg = CheckDataBlock(inode->inum[n], block_info); |
| if (msg) { |
| FX_LOGS(WARNING) << "check: ino#" << ino << ": indirect block " << n << "(@" |
| << inode->inum[n] << "): " << msg.value(); |
| conforming_ = false; |
| } |
| block_count++; |
| } |
| } |
| |
| // count and sanity-check doubly indirect blocks |
| for (unsigned n = 0; n < kMinfsDoublyIndirect; n++) { |
| if (inode->dinum[n]) { |
| BlockInfo block_info = {ino, LogicalBlockDoublyIndirect(n), BlockType::kDoubleIndirect}; |
| auto msg = CheckDataBlock(inode->dinum[n], block_info); |
| if (msg) { |
| FX_LOGS(WARNING) << "check: ino#" << ino << ": doubly indirect block " << n << "(@" |
| << inode->dinum[n] << "): " << msg.value(); |
| conforming_ = false; |
| } |
| block_count++; |
| |
| char data[kMinfsBlockSize]; |
| if (auto status = fs_.ReadDat(inode->dinum[n], data); status.is_error()) { |
| return status.take_error(); |
| } |
| uint32_t* entry = reinterpret_cast<uint32_t*>(data); |
| |
| for (unsigned m = 0; m < kMinfsDirectPerIndirect; m++) { |
| if (entry[m]) { |
| BlockInfo block_info = {ino, LogicalBlockDoublyIndirect(n, m), BlockType::kIndirect}; |
| msg = CheckDataBlock(entry[m], block_info); |
| if (msg) { |
| FX_LOGS(WARNING) << "check: ino#" << ino << ": indirect block (in dind) " << m << "(@" |
| << entry[m] << "): " << msg.value(); |
| conforming_ = false; |
| } |
| block_count++; |
| } |
| } |
| } |
| } |
| |
| // count and sanity-check data blocks |
| |
| // The next block which would be allocated if we expand the file size |
| // by a single block. |
| unsigned next_blk = 0; |
| cached_doubly_indirect_ = 0; |
| cached_indirect_ = 0; |
| |
| blk_t n = 0; |
| while (true) { |
| auto nth_bno_or = GetInodeNthBno(inode, n); |
| if (nth_bno_or.is_error()) { |
| if (nth_bno_or.error_value() == ZX_ERR_OUT_OF_RANGE) { |
| break; |
| } else { |
| return nth_bno_or.take_error(); |
| } |
| } |
| |
| assert(nth_bno_or->next_n > n); |
| if (nth_bno_or->bno) { |
| next_blk = n + 1; |
| block_count++; |
| BlockInfo block_info = {ino, n, BlockType::kDirect}; |
| auto msg = CheckDataBlock(nth_bno_or->bno, block_info); |
| if (msg) { |
| FX_LOGS(WARNING) << "check: ino#" << ino << ": block " << n << "(@" << nth_bno_or->bno |
| << "): " << msg.value(); |
| conforming_ = false; |
| } |
| } |
| n = nth_bno_or->next_n; |
| } |
| if (next_blk) { |
| unsigned max_blocks = fbl::round_up(inode->size, kMinfsBlockSize) / kMinfsBlockSize; |
| if (next_blk > max_blocks) { |
| FX_LOGS(WARNING) << "check: ino#" << ino << ": filesize too small"; |
| conforming_ = false; |
| } |
| } |
| if (block_count != inode->block_count) { |
| FX_LOGS(WARNING) << "check: ino#" << ino << ": block count " << inode->block_count |
| << ", actual blocks " << block_count; |
| conforming_ = false; |
| } |
| return zx::ok(); |
| } |
| |
| void MinfsChecker::CheckReserved() { |
| // Check reserved inode '0'. |
| if (fs_.GetInodeManager()->GetInodeAllocator()->CheckAllocated(0)) { |
| ZX_ASSERT(!checked_inodes_.Get(0, 1)); |
| checked_inodes_.Set(0, 1); |
| alloc_inodes_++; |
| } else { |
| FX_LOGS(WARNING) << "check: reserved inode#0: not marked in-use"; |
| conforming_ = false; |
| } |
| |
| // Check reserved data block '0'. |
| if (fs_.GetBlockAllocator().CheckAllocated(0)) { |
| checked_blocks_.Set(0, 1); |
| alloc_blocks_++; |
| } else { |
| FX_LOGS(WARNING) << "check: reserved block#0: not marked in-use"; |
| conforming_ = false; |
| } |
| } |
| |
| zx::result<> MinfsChecker::CheckInode(ino_t ino, ino_t parent, bool dot_or_dotdot) { |
| for (;;) { |
| if (zx::result result = CheckOneInode(ino, parent, dot_or_dotdot); result.is_error()) |
| return result.take_error(); |
| |
| if (inode_stack_.empty()) |
| return zx::ok(); |
| |
| auto [next_ino, next_parent, next_dot_or_dotdot] = inode_stack_.back(); |
| inode_stack_.pop_back(); |
| ino = next_ino; |
| parent = next_parent; |
| dot_or_dotdot = next_dot_or_dotdot; |
| } |
| } |
| |
| zx::result<> MinfsChecker::CheckOneInode(ino_t ino, ino_t parent, bool dot_or_dotdot) { |
| auto inode_or = GetInode(ino); |
| if (inode_or.is_error()) { |
| FX_LOGS(ERROR) << "check: ino#" << ino << ": not readable: " << inode_or.error_value(); |
| return inode_or.take_error(); |
| } |
| |
| Inode inode = std::move(inode_or.value()); |
| |
| bool prev_checked = checked_inodes_.Get(ino, ino + 1); |
| |
| if (inode.magic == kMinfsMagicDir && prev_checked && !dot_or_dotdot) { |
| FX_LOGS(ERROR) << "check: ino#" << ino |
| << ": Multiple hard links to directory (excluding '.' and '..') found"; |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| |
| if (!safemath::CheckAdd(links_[ino - 1], 1).AssignIfValid(&links_[ino - 1])) { |
| FX_LOGS(ERROR) << "Ino " << ino << " overflowed int64_t."; |
| return zx::error(ZX_ERR_OUT_OF_RANGE); |
| } |
| |
| if (prev_checked) { |
| // we've been here before |
| return zx::ok(); |
| } |
| |
| if (!safemath::CheckSub(links_[ino - 1], inode.link_count).AssignIfValid(&links_[ino - 1])) { |
| FX_LOGS(ERROR) << "Ino " << ino << " underflowed int64_t."; |
| return zx::error(ZX_ERR_OUT_OF_RANGE); |
| } |
| checked_inodes_.Set(ino, ino + 1); |
| max_inode_ = std::max(ino, max_inode_); |
| alloc_inodes_++; |
| |
| if (!fs_.GetInodeManager()->GetInodeAllocator()->CheckAllocated(ino)) { |
| FX_LOGS(WARNING) << "check: ino#" << ino << ": not marked in-use"; |
| conforming_ = false; |
| } |
| |
| if (inode.magic == kMinfsMagicDir) { |
| FX_LOGS(DEBUG) << "ino#" << ino << ": DIR blks=" << inode.block_count |
| << " links=" << inode.link_count; |
| if (auto status = CheckFile(&inode, ino); status.is_error()) { |
| return status.take_error(); |
| } |
| if (auto status = CheckDirectory(&inode, ino, parent, CD_DUMP); status.is_error()) { |
| return status.take_error(); |
| } |
| if (auto status = CheckDirectory(&inode, ino, parent, CD_RECURSE); status.is_error()) { |
| return status.take_error(); |
| } |
| directory_blocks_ += inode.block_count; |
| } else { |
| if (ino == kMinfsRootIno) { |
| FX_LOGS(ERROR) << "Root inode must be a directory"; |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| FX_LOGS(DEBUG) << "ino#" << ino << ": FILE blks=" << inode.block_count |
| << " links=" << inode.link_count << " size=" << inode.size; |
| if (auto status = CheckFile(&inode, ino); status.is_error()) { |
| return status.take_error(); |
| } |
| } |
| return zx::ok(); |
| } |
| |
| zx::result<> MinfsChecker::CheckUnlinkedInodes() { |
| ino_t last_ino = 0; |
| ino_t next_ino = fs_.Info().unlinked_head; |
| ino_t unlinked_count = 0; |
| |
| while (next_ino != 0) { |
| unlinked_count++; |
| |
| auto inode_or = GetInode(next_ino); |
| if (inode_or.is_error()) { |
| FX_LOGS(ERROR) << "check: ino#" << next_ino << ": not readable: " << inode_or.error_value(); |
| return inode_or.take_error(); |
| } |
| |
| Inode inode = std::move(inode_or.value()); |
| |
| if (inode.link_count > 0) { |
| FX_LOGS(ERROR) << "check: ino#" << next_ino << ": should have 0 links"; |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| |
| if (inode.last_inode != last_ino) { |
| FX_LOGS(ERROR) << "check: ino#" << next_ino << ": incorrect last unlinked inode"; |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| |
| links_[next_ino - 1] = -1; |
| |
| if (auto status = CheckInode(next_ino, 0, 0); status.is_error()) { |
| FX_LOGS(ERROR) << "minfs_check: CheckInode failure: " << status.error_value(); |
| return status.take_error(); |
| } |
| |
| last_ino = next_ino; |
| next_ino = inode.next_inode; |
| } |
| |
| if (fs_.Info().unlinked_tail != last_ino) { |
| FX_LOGS(ERROR) << "minfs_check: Incorrect unlinked tail: " << fs_.Info().unlinked_tail; |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| |
| if (unlinked_count > 0 && !fsck_options_.quiet) { |
| FX_LOGS(WARNING) << "minfs_check: Warning: " << unlinked_count << " unlinked inodes found"; |
| } |
| |
| return zx::ok(); |
| } |
| |
| zx::result<> MinfsChecker::CheckForUnusedBlocks() const { |
| unsigned missing = 0; |
| |
| for (unsigned n = 0; n < fs_.Info().block_count; n++) { |
| if (fs_.GetBlockAllocator().CheckAllocated(n)) { |
| if (!checked_blocks_.Get(n, n + 1)) { |
| missing++; |
| } |
| } |
| } |
| if (missing > 0) { |
| FX_LOGS(ERROR) << "check: " << missing << " allocated block" << (missing > 1 ? "s" : "") |
| << " not in use"; |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| return zx::ok(); |
| } |
| |
| zx::result<> MinfsChecker::CheckForUnusedInodes() const { |
| unsigned missing = 0; |
| for (unsigned n = 0; n < fs_.Info().inode_count; n++) { |
| if (fs_.GetInodeManager()->GetInodeAllocator()->CheckAllocated(n)) { |
| if (!checked_inodes_.Get(n, n + 1)) { |
| missing++; |
| } |
| } |
| } |
| // Minfs behaviour was changed in revision 1 so that purged inodes have their magic field changed |
| // to kMinfsMagicPurged. Prior to this, the inodes were left intact. |
| if (missing > 0) { |
| FX_LOGS(ERROR) << "check: " << missing << " allocated inode" << (missing > 1 ? "s" : "") |
| << " not in use"; |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| return zx::ok(); |
| } |
| |
| zx::result<> MinfsChecker::CheckLinkCounts() const { |
| unsigned error = 0; |
| for (uint32_t n = 0; n < fs_.Info().inode_count; n++) { |
| if (links_[n] != 0) { |
| error += 1; |
| FX_LOGS(ERROR) << "check: inode#" << n + 1 << " has incorrect link count " << links_[n]; |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| } |
| if (error) { |
| FX_LOGS(ERROR) << "check: " << error << " inode" << (error > 1 ? "s" : "") |
| << " with incorrect link count"; |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| return zx::ok(); |
| } |
| |
| zx::result<> MinfsChecker::CheckAllocatedCounts() const { |
| zx::result<> status = zx::ok(); |
| if (alloc_blocks_ != fs_.Info().alloc_block_count) { |
| FX_LOGS(ERROR) << "check: incorrect allocated block count " << fs_.Info().alloc_block_count |
| << " (should be " << alloc_blocks_ << ")"; |
| status = zx::error(ZX_ERR_BAD_STATE); |
| } |
| |
| if (alloc_inodes_ != fs_.Info().alloc_inode_count) { |
| FX_LOGS(ERROR) << "check: incorrect allocated inode count " << fs_.Info().alloc_inode_count |
| << " (should be " << alloc_inodes_ << ")"; |
| status = zx::error(ZX_ERR_BAD_STATE); |
| } |
| |
| return status; |
| } |
| |
| zx::result<> MinfsChecker::CheckSuperblockIntegrity() const { |
| char data[kMinfsBlockSize]; |
| blk_t journal_block; |
| |
| #ifdef __Fuchsia__ |
| journal_block = static_cast<blk_t>(JournalStartBlock(fs_.Info())); |
| #else |
| journal_block = fs_.GetBlockOffsets().JournalStartBlock(); |
| #endif |
| |
| if (fs_.bc_->Readblk(journal_block, data).is_error()) { |
| FX_LOGS(ERROR) << "could not read journal block"; |
| return zx::error(ZX_ERR_IO); |
| } |
| |
| // Check that the journal superblock is valid. |
| fs::JournalInfo* journal_info = reinterpret_cast<fs::JournalInfo*>(data); |
| if (journal_info->magic != fs::kJournalMagic) { |
| FX_LOGS(ERROR) << "invalid journal magic"; |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| |
| uint32_t old_checksum = journal_info->checksum; |
| journal_info->checksum = 0; |
| journal_info->checksum = crc32(0, reinterpret_cast<uint8_t*>(data), sizeof(fs::JournalInfo)); |
| if (journal_info->checksum != old_checksum) { |
| FX_LOGS(ERROR) << "invalid journal checksum: actual = " << old_checksum |
| << ", expected = " << journal_info->checksum; |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| |
| // Check that the backup superblock is valid. |
| blk_t backup_location; |
| if ((fs_.Info().flags & kMinfsFlagFVM) == 0) { |
| backup_location = kNonFvmSuperblockBackup; |
| } else { |
| #ifdef __Fuchsia__ |
| backup_location = kFvmSuperblockBackup; |
| #else |
| backup_location = fs_.GetBlockOffsets().IntegrityStartBlock(); |
| #endif |
| } |
| |
| if (fs_.bc_->Readblk(backup_location, data).is_error()) { |
| FX_LOGS(ERROR) << "could not read backup superblock"; |
| return zx::error(ZX_ERR_IO); |
| } |
| |
| Superblock* backup_info = reinterpret_cast<Superblock*>(data); |
| #ifdef __Fuchsia__ |
| return CheckSuperblock(*backup_info, fs_.bc_->device(), fs_.bc_->Maxblk()); |
| #else |
| return CheckSuperblock(*backup_info, fs_.bc_->Maxblk()); |
| #endif |
| } |
| |
| zx::result<std::unique_ptr<MinfsChecker>> MinfsChecker::Create(FuchsiaDispatcher dispatcher, |
| std::unique_ptr<Bcache> bc, |
| const FsckOptions& fsck_options) { |
| auto fs = Runner::Create( |
| dispatcher, std::move(bc), |
| MountOptions{ |
| .writability = fsck_options.read_only ? minfs::Writability::ReadOnlyDisk |
| : minfs::Writability::Writable, |
| .repair_filesystem = fsck_options.repair, |
| .fsck_after_every_transaction = false, // Explicit in case the default is overridden. |
| .quiet = fsck_options.quiet, |
| }); |
| if (fs.is_error()) { |
| FX_LOGS(ERROR) << "MinfsChecker::Create Failed to Create Minfs: " << fs.error_value(); |
| return fs.take_error(); |
| } |
| |
| const Superblock& info = fs->minfs().Info(); |
| |
| auto checker = std::unique_ptr<MinfsChecker>(new MinfsChecker(*std::move(fs), fsck_options)); |
| checker->links_.reset(new int64_t[info.inode_count]{0}, info.inode_count); |
| checker->links_[0] = -1; |
| checker->cached_doubly_indirect_ = 0; |
| checker->cached_indirect_ = 0; |
| |
| if (zx_status_t status = checker->checked_inodes_.Reset(info.inode_count); status != ZX_OK) { |
| FX_LOGS(ERROR) << "MinfsChecker::Init Failed to reset checked inodes: " << status; |
| return zx::error(status); |
| } |
| if (zx_status_t status = checker->checked_blocks_.Reset(info.block_count); status != ZX_OK) { |
| FX_LOGS(ERROR) << "MinfsChecker::Init Failed to reset checked blocks: " << status; |
| return zx::error(status); |
| } |
| |
| return zx::ok(std::move(checker)); |
| } |
| |
| void MinfsChecker::DumpStats() { |
| if (!fsck_options_.quiet) { |
| FX_LOGS(INFO) << "Minfs fsck:\n inodes : " << alloc_inodes_ - 1 |
| << "\n blocks : " << alloc_blocks_ - 1 |
| << "\n indirect blocks : " << indirect_blocks_ |
| << "\n directory blocks : " << directory_blocks_; |
| } |
| } |
| |
| #ifdef __Fuchsia__ |
| |
| // Write Superblock and Backup Superblock to disk. |
| zx::result<> WriteSuperblockAndBackupSuperblock(fs::DeviceTransactionHandler* transaction_handler, |
| block_client::BlockDevice* device, |
| Superblock* info) { |
| storage::VmoBuffer buffer; |
| zx_status_t status = |
| buffer.Initialize(transaction_handler->GetDevice(), 1, kMinfsBlockSize, "fsck-super-block"); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| memcpy(buffer.Data(0), info, sizeof(*info)); |
| fs::BufferedOperationsBuilder builder; |
| builder |
| .Add(storage::Operation{.type = storage::OperationType::kWrite, |
| .vmo_offset = 0, |
| .dev_offset = kSuperblockStart, |
| .length = 1}, |
| &buffer) |
| .Add(storage::Operation{.type = storage::OperationType::kWrite, |
| .vmo_offset = 0, |
| .dev_offset = (info->flags & kMinfsFlagFVM ? kFvmSuperblockBackup |
| : kNonFvmSuperblockBackup), |
| .length = 1}, |
| &buffer); |
| return zx::make_result(transaction_handler->RunRequests(builder.TakeOperations())); |
| } |
| |
| // Reads backup superblock from correct location depending on whether filesystem has FVM support. |
| zx::result<Superblock> ReadBackupSuperblock(fs::TransactionHandler* transaction_handler, |
| block_client::BlockDevice* device, uint32_t max_blocks, |
| uint32_t backup_location) { |
| Superblock backup; |
| block_client::Reader reader(*device); |
| if (zx_status_t status = reader.Read(static_cast<uint64_t>(backup_location) * kMinfsBlockSize, |
| kMinfsBlockSize, &backup); |
| status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| if (auto status = CheckSuperblock(backup, device, max_blocks); status.is_error()) { |
| return status.take_error(); |
| } |
| // Found a valid backup superblock. Confirm if the FVM flags are set in the backup superblock. |
| if ((backup_location == kFvmSuperblockBackup) && ((backup.flags & kMinfsFlagFVM) == 0)) { |
| return zx::error(ZX_ERR_BAD_STATE); |
| } else if ((backup_location == kNonFvmSuperblockBackup) && |
| ((backup.flags & kMinfsFlagFVM) != 0)) { |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| |
| return zx::ok(std::move(backup)); |
| } |
| #endif |
| |
| } // namespace |
| |
| // Repairs superblock from backup. |
| #ifdef __Fuchsia__ |
| zx::result<Superblock> RepairSuperblock(fs::DeviceTransactionHandler* transaction_handler, |
| block_client::BlockDevice* device, uint32_t max_blocks) { |
| // Try the FVM backup location first. |
| auto backup_info_or = |
| ReadBackupSuperblock(transaction_handler, device, max_blocks, kFvmSuperblockBackup); |
| |
| if (backup_info_or.is_error()) { |
| // Try the non-fvm backup superblock location. |
| backup_info_or = |
| ReadBackupSuperblock(transaction_handler, device, max_blocks, kNonFvmSuperblockBackup); |
| } |
| |
| if (backup_info_or.is_error()) { |
| FX_LOGS(ERROR) << "Fsck::RepairSuperblock failed. Unrepairable superblock: " |
| << backup_info_or.error_value(); |
| return backup_info_or.take_error(); |
| } |
| FX_LOGS(INFO) << "Superblock corrupted. Repairing filesystem from backup superblock."; |
| |
| Superblock& backup_info = backup_info_or.value(); |
| |
| // Try to reconstruct alloc_*_counts of the backup superblock, since the |
| // alloc_*_counts might be out-of-sync with the actual values. |
| zx::result<> status = ReconstructAllocCounts(transaction_handler, device, &backup_info); |
| |
| if (status.is_error()) { |
| FX_LOGS(ERROR) << "Fsck::ReconstructAllocCounts failed. Unrepairable superblock: " |
| << status.status_value(); |
| return status.take_error(); |
| } |
| // Recalculate checksum. |
| UpdateChecksum(&backup_info); |
| |
| // Update superblock and backup superblock. |
| status = WriteSuperblockAndBackupSuperblock(transaction_handler, device, &backup_info); |
| |
| if (status.is_error()) { |
| FX_LOGS(ERROR) << "Fsck::RepairSuperblock failed to repair superblock from backup :" |
| << status.status_value(); |
| } |
| |
| return zx::ok(std::move(backup_info)); |
| } |
| #endif |
| |
| zx::result<Superblock> LoadSuperblock(Bcache* bc) { |
| Superblock info; |
| zx::result<> status = bc->Readblk(kSuperblockStart, &info); |
| if (status.is_error()) { |
| FX_LOGS(ERROR) << "could not read info block."; |
| return status.take_error(); |
| } |
| DumpInfo(info); |
| #ifdef __Fuchsia__ |
| status = CheckSuperblock(info, bc->device(), bc->Maxblk()); |
| #else |
| status = CheckSuperblock(info, bc->Maxblk()); |
| #endif |
| if (status.is_error()) { |
| FX_LOGS(ERROR) << "Fsck: check_info failure: " << status.error_value(); |
| return status.take_error(); |
| } |
| return zx::ok(info); |
| } |
| |
| zx::result<uint64_t> UsedDataSize(std::unique_ptr<Bcache>& bc) { |
| auto info_or = LoadSuperblock(bc.get()); |
| if (info_or.is_error()) { |
| return info_or.take_error(); |
| } |
| Superblock& info = info_or.value(); |
| |
| uint64_t size = |
| static_cast<uint64_t>(info.alloc_block_count) * static_cast<uint64_t>(info.block_size); |
| return zx::ok(size); |
| } |
| |
| zx::result<uint64_t> UsedInodes(std::unique_ptr<Bcache>& bc) { |
| auto info_or = LoadSuperblock(bc.get()); |
| if (info_or.is_error()) { |
| return info_or.take_error(); |
| } |
| |
| Superblock& info = info_or.value(); |
| return zx::ok(info.alloc_inode_count); |
| } |
| |
| zx::result<uint64_t> UsedSize(std::unique_ptr<Bcache>& bc) { |
| auto info_or = LoadSuperblock(bc.get()); |
| if (info_or.is_error()) { |
| return info_or.take_error(); |
| } |
| |
| Superblock& info = info_or.value(); |
| uint64_t size = (NonDataBlocks(info) + info.alloc_block_count) * info.block_size; |
| return zx::ok(size); |
| } |
| |
| #ifdef __Fuchsia__ |
| zx::result<uint32_t> CalculateBitsSetBitmap(fs::TransactionHandler* transaction_handler, |
| block_client::BlockDevice* device, blk_t start_block, |
| uint32_t num_blocks) { |
| #else |
| zx::result<uint32_t> CalculateBitsSetBitmap(fs::TransactionHandler* transaction_handler, |
| blk_t start_block, uint32_t num_blocks) { |
| #endif |
| minfs::RawBitmap bitmap; |
| zx_status_t status = bitmap.Reset(static_cast<size_t>(num_blocks) * kMinfsBlockBits); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| #ifdef __Fuchsia__ |
| storage::OwnedVmoid map_vmoid; |
| status = |
| device->BlockAttachVmo(bitmap.StorageUnsafe()->GetVmo(), &map_vmoid.GetReference(device)); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| fs::internal::BorrowedBuffer buffer(map_vmoid.get()); |
| #else |
| fs::internal::BorrowedBuffer buffer(bitmap.StorageUnsafe()->GetData()); |
| #endif |
| status = |
| transaction_handler->RunOperation(storage::Operation{.type = storage::OperationType::kRead, |
| .vmo_offset = 0, |
| .dev_offset = start_block, |
| .length = num_blocks}, |
| &buffer); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| // Efficiently iterate through the bitmap to count the number of bits set in the bitmap. |
| size_t off = 0; |
| size_t bitmap_size = bitmap.size(); |
| size_t count = 0; |
| |
| while (off < bitmap_size) { |
| size_t ind = 0; |
| if (bitmap.Find(true, off, bitmap_size, 1, &ind) == ZX_OK) { |
| size_t scan_ind = 0; |
| if (bitmap.Scan(ind, bitmap_size, true, &scan_ind)) { |
| count += (bitmap_size - ind); |
| break; |
| } |
| count += (scan_ind - ind); |
| off = scan_ind + 1; |
| |
| } else { |
| break; |
| } |
| } |
| |
| return zx::ok(static_cast<uint32_t>(count)); |
| } |
| |
| #ifdef __Fuchsia__ |
| zx::result<> ReconstructAllocCounts(fs::TransactionHandler* transaction_handler, |
| block_client::BlockDevice* device, Superblock* out_info) { |
| #else |
| zx::result<> ReconstructAllocCounts(fs::TransactionHandler* transaction_handler, |
| Superblock* out_info) { |
| #endif |
| uint32_t allocation_bitmap_num_blocks = |
| (out_info->block_count + kMinfsBlockBits - 1) / kMinfsBlockBits; |
| |
| #ifdef __Fuchsia__ |
| // Correct allocated block count. |
| auto alloc_block_count_or = CalculateBitsSetBitmap( |
| transaction_handler, device, out_info->abm_block, allocation_bitmap_num_blocks); |
| #else |
| auto alloc_block_count_or = CalculateBitsSetBitmap(transaction_handler, out_info->abm_block, |
| allocation_bitmap_num_blocks); |
| #endif |
| if (alloc_block_count_or.is_error()) { |
| return alloc_block_count_or.take_error(); |
| } |
| out_info->alloc_block_count = alloc_block_count_or.value(); |
| |
| uint32_t inode_bitmap_num_blocks = |
| (out_info->inode_count + kMinfsBlockBits - 1) / kMinfsBlockBits; |
| |
| #ifdef __Fuchsia__ |
| // Correct allocated inode count. |
| auto alloc_inode_count_or = CalculateBitsSetBitmap(transaction_handler, device, |
| out_info->ibm_block, inode_bitmap_num_blocks); |
| #else |
| auto alloc_inode_count_or = |
| CalculateBitsSetBitmap(transaction_handler, out_info->ibm_block, inode_bitmap_num_blocks); |
| #endif |
| |
| if (alloc_inode_count_or.is_error()) { |
| return alloc_inode_count_or.take_error(); |
| } |
| out_info->alloc_inode_count = alloc_inode_count_or.value(); |
| |
| return zx::ok(); |
| } |
| |
| zx::result<std::unique_ptr<Bcache>> Fsck(std::unique_ptr<Bcache> bc, const FsckOptions& options) { |
| FuchsiaDispatcher dispatcher = nullptr; // Use null for the dispatcher on host. |
| #ifdef __Fuchsia__ |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| dispatcher = loop.dispatcher(); |
| if (zx_status_t status = loop.StartThread(); status != ZX_OK) { |
| FX_LOGS(ERROR) << "Cannot initialize dispatch loop: " << zx_status_get_string(status); |
| return zx::error(status); |
| } |
| #endif |
| |
| auto chk_or = MinfsChecker::Create(dispatcher, std::move(bc), options); |
| if (chk_or.is_error()) { |
| FX_LOGS(ERROR) << "Fsck: Init failure: " << chk_or.error_value(); |
| return chk_or.take_error(); |
| } |
| |
| chk_or->CheckReserved(); |
| |
| if (auto status = chk_or->CheckInode(kMinfsRootIno, kMinfsRootIno, 0); status.is_error()) { |
| FX_LOGS(ERROR) << "Fsck: CheckInode failure: " << status.error_value(); |
| return status.take_error(); |
| } |
| |
| zx::result<> r; |
| zx_status_t status = ZX_OK; |
| |
| // Save an error if it occurs, but check for subsequent errors anyway. |
| r = chk_or->CheckUnlinkedInodes(); |
| status |= (status != ZX_OK) ? 0 : r.status_value(); |
| r = chk_or->CheckForUnusedBlocks(); |
| status |= (status != ZX_OK) ? 0 : r.status_value(); |
| r = chk_or->CheckForUnusedInodes(); |
| status |= (status != ZX_OK) ? 0 : r.status_value(); |
| r = chk_or->CheckLinkCounts(); |
| status |= (status != ZX_OK) ? 0 : r.status_value(); |
| r = chk_or->CheckAllocatedCounts(); |
| status |= (status != ZX_OK) ? 0 : r.status_value(); |
| |
| r = chk_or->CheckSuperblockIntegrity(); |
| status |= (status != ZX_OK) ? 0 : r.status_value(); |
| |
| status |= (status != ZX_OK) ? 0 : (chk_or->conforming() ? ZX_OK : ZX_ERR_BAD_STATE); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| chk_or->DumpStats(); |
| |
| bc = MinfsChecker::Destroy(std::move(chk_or.value())); |
| return zx::ok(std::move(bc)); |
| } |
| |
| } // namespace minfs |