| // 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 <fs/journal/format.h> |
| |
| #include "src/storage/minfs/format.h" |
| #ifdef __Fuchsia__ |
| #include <storage/buffer/owned_vmoid.h> |
| #else |
| #include <storage/buffer/array_buffer.h> |
| #endif |
| |
| #include "lib/fit/string_view.h" |
| #include "src/storage/minfs/minfs_private.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_status_t Create(std::unique_ptr<Bcache> bc, const FsckOptions& options, |
| std::unique_ptr<MinfsChecker>* out); |
| |
| static std::unique_ptr<Bcache> Destroy(std::unique_ptr<MinfsChecker> checker) { |
| return Minfs::Destroy(std::move(checker->fs_)); |
| } |
| |
| void CheckReserved(); |
| zx_status_t CheckInode(ino_t ino, ino_t parent, bool dot_or_dotdot); |
| zx_status_t CheckUnlinkedInodes(); |
| zx_status_t CheckForUnusedBlocks() const; |
| zx_status_t CheckForUnusedInodes() const; |
| zx_status_t CheckLinkCounts() const; |
| zx_status_t CheckAllocatedCounts() const; |
| zx_status_t CheckSuperblockIntegrity() const; |
| |
| void DumpStats(); |
| |
| bool conforming() const { return conforming_; } |
| |
| private: |
| MinfsChecker(const FsckOptions& fsck_options) : fsck_options_(fsck_options) {} |
| |
| // 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_status_t GetInode(Inode* inode, ino_t ino, bool check_magic = true) const; |
| |
| // Returns the nth block within an inode, relative to the start of the |
| // file. Returns the "next_n" which 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_status_t GetInodeNthBno(Inode* inode, blk_t n, blk_t* next_n, blk_t* bno_out); |
| zx_status_t CheckDirectory(Inode* inode, ino_t ino, ino_t parent, uint32_t flags); |
| std::optional<std::string> CheckDataBlock(blk_t bno, BlockInfo block_info); |
| zx_status_t CheckFile(Inode* inode, ino_t ino); |
| |
| 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<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<int32_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; |
| }; |
| |
| zx_status_t MinfsChecker::GetInode(Inode* inode, 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_ERR_OUT_OF_RANGE; |
| } |
| |
| 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_ERR_IO_DATA_INTEGRITY; |
| } |
| return ZX_OK; |
| } |
| |
| #define CD_DUMP 1 |
| #define CD_RECURSE 2 |
| |
| zx_status_t MinfsChecker::GetInodeNthBno(Inode* inode, blk_t n, blk_t* next_n, blk_t* bno_out) { |
| // 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) { |
| *bno_out = inode->dnum[n]; |
| return ZX_OK; |
| } |
| |
| 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) { |
| *bno_out = 0; |
| *next_n = kMinfsDirect + (i + 1) * kMinfsDirectPerIndirect; |
| return ZX_OK; |
| } |
| |
| if (cached_indirect_ != ibno) { |
| zx_status_t status; |
| if ((status = fs_->ReadDat(ibno, indirect_cache_)) != ZX_OK) { |
| return status; |
| } |
| cached_indirect_ = ibno; |
| } |
| |
| uint32_t* ientry = reinterpret_cast<uint32_t*>(indirect_cache_); |
| *bno_out = ientry[j]; |
| return ZX_OK; |
| } |
| |
| 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) { |
| *bno_out = 0; |
| *next_n = kMinfsDirect + kMinfsIndirect * kMinfsDirectPerIndirect + |
| (i + 1) * kMinfsDirectPerDindirect; |
| return ZX_OK; |
| } |
| |
| if (cached_doubly_indirect_ != dibno) { |
| zx_status_t status; |
| if ((status = fs_->ReadDat(dibno, doubly_indirect_cache_)) != ZX_OK) { |
| return status; |
| } |
| cached_doubly_indirect_ = dibno; |
| } |
| |
| uint32_t* dientry = reinterpret_cast<uint32_t*>(doubly_indirect_cache_); |
| blk_t ibno; |
| if ((ibno = dientry[j]) == 0) { |
| *bno_out = 0; |
| *next_n = kMinfsDirect + kMinfsIndirect * kMinfsDirectPerIndirect + |
| (i * kMinfsDirectPerDindirect) + (j + 1) * kMinfsDirectPerIndirect; |
| return ZX_OK; |
| } |
| |
| if (cached_indirect_ != ibno) { |
| zx_status_t status; |
| if ((status = fs_->ReadDat(ibno, indirect_cache_)) != ZX_OK) { |
| return status; |
| } |
| cached_indirect_ = ibno; |
| } |
| |
| uint32_t* ientry = reinterpret_cast<uint32_t*>(indirect_cache_); |
| *bno_out = ientry[k]; |
| return ZX_OK; |
| } |
| |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| zx_status_t 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_status_t status; |
| fbl::RefPtr<VnodeMinfs> vn; |
| VnodeMinfs::Recreate(fs_.get(), ino, &vn); |
| |
| size_t off = 0; |
| while (true) { |
| DirentBuffer dirent_buffer; |
| size_t actual; |
| status = vn->ReadInternal(nullptr, &dirent_buffer.dirent, kMinfsDirentSize, off, &actual); |
| if (status == ZX_OK && actual == 0 && inode->link_count == 0 && parent == 0) { |
| // This is OK as it's an unlinked directory. |
| break; |
| } |
| if (status != ZX_OK || 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 != ZX_OK ? status : ZX_ERR_IO; |
| } |
| |
| Dirent* de = &dirent_buffer.dirent; |
| uint32_t rlen = static_cast<uint32_t>(MinfsReclen(de, off)); |
| uint32_t dlen = DirentSize(de->namelen); |
| bool 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_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 |
| uint32_t record_full[DirentSize(NAME_MAX)]; |
| status = vn->ReadInternal(nullptr, record_full, DirentSize(de->namelen), off, &actual); |
| if (status != ZX_OK || actual != DirentSize(de->namelen)) { |
| FX_LOGS(ERROR) << "check: Error reading dirent of size: " << DirentSize(de->namelen); |
| return ZX_ERR_IO; |
| } |
| de = reinterpret_cast<Dirent*>(record_full); |
| 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_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) { |
| if ((status = CheckInode(de->ino, ino, dot_or_dotdot)) < 0) { |
| return status; |
| } |
| } |
| dirent_count++; |
| } |
| if (is_last) { |
| break; |
| } else { |
| off += rlen; |
| } |
| eno++; |
| } |
| 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_status_t MinfsChecker::CheckFile(Inode* inode, ino_t ino) { |
| FX_LOGS(DEBUG) << "Direct blocks: "; |
| for (unsigned n = 0; n < kMinfsDirect; n++) { |
| FX_LOGS(DEBUG) << " " << inode->dnum[n] << ","; |
| } |
| 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]; |
| zx_status_t status; |
| if ((status = fs_->ReadDat(inode->dinum[n], data)) != ZX_OK) { |
| return status; |
| } |
| 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) { |
| zx_status_t status; |
| blk_t bno; |
| blk_t next_n; |
| if ((status = GetInodeNthBno(inode, n, &next_n, &bno)) < 0) { |
| if (status == ZX_ERR_OUT_OF_RANGE) { |
| break; |
| } else { |
| return status; |
| } |
| } |
| assert(next_n > n); |
| if (bno) { |
| next_blk = n + 1; |
| block_count++; |
| BlockInfo block_info = {ino, n, BlockType::kDirect}; |
| auto msg = CheckDataBlock(bno, block_info); |
| if (msg) { |
| FX_LOGS(WARNING) << "check: ino#" << ino << ": block " << n << "(@" << bno |
| << "): " << msg.value(); |
| conforming_ = false; |
| } |
| } |
| n = 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_status_t MinfsChecker::CheckInode(ino_t ino, ino_t parent, bool dot_or_dotdot) { |
| Inode inode; |
| zx_status_t status; |
| |
| if ((status = GetInode(&inode, ino)) < 0) { |
| FX_LOGS(ERROR) << "check: ino#" << ino << ": not readable: " << status; |
| return status; |
| } |
| |
| 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_ERR_BAD_STATE; |
| } |
| |
| links_[ino - 1] += 1; |
| |
| if (prev_checked) { |
| // we've been here before |
| return ZX_OK; |
| } |
| |
| links_[ino - 1] -= inode.link_count; |
| 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 ((status = CheckFile(&inode, ino)) < 0) { |
| return status; |
| } |
| if ((status = CheckDirectory(&inode, ino, parent, CD_DUMP)) < 0) { |
| return status; |
| } |
| if ((status = CheckDirectory(&inode, ino, parent, CD_RECURSE)) < 0) { |
| return status; |
| } |
| directory_blocks_ += inode.block_count; |
| } else { |
| if (ino == kMinfsRootIno) { |
| FX_LOGS(ERROR) << "Root inode must be a directory"; |
| return ZX_ERR_BAD_STATE; |
| } |
| FX_LOGS(DEBUG) << "ino#" << ino << ": FILE blks=" << inode.block_count |
| << " links=" << inode.link_count << " size=" << inode.size; |
| if ((status = CheckFile(&inode, ino)) < 0) { |
| return status; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t 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++; |
| |
| Inode inode; |
| zx_status_t status = GetInode(&inode, next_ino); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "check: ino#" << next_ino << ": not readable: " << status; |
| return status; |
| } |
| |
| if (inode.link_count > 0) { |
| FX_LOGS(ERROR) << "check: ino#" << next_ino << ": should have 0 links"; |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (inode.last_inode != last_ino) { |
| FX_LOGS(ERROR) << "check: ino#" << next_ino << ": incorrect last unlinked inode"; |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| links_[next_ino - 1] = -1; |
| |
| if ((status = CheckInode(next_ino, 0, 0)) != ZX_OK) { |
| FX_LOGS(ERROR) << "minfs_check: CheckInode failure: " << status; |
| return status; |
| } |
| |
| 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_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_status_t 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) { |
| FX_LOGS(ERROR) << "check: " << missing << " allocated block" << (missing > 1 ? "s" : "") |
| << " not in use"; |
| return ZX_ERR_BAD_STATE; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t 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_ERR_BAD_STATE; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t 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_ERR_BAD_STATE; |
| } |
| } |
| if (error) { |
| FX_LOGS(ERROR) << "check: " << error << " inode" << (error > 1 ? "s" : "") |
| << " with incorrect link count"; |
| return ZX_ERR_BAD_STATE; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t MinfsChecker::CheckAllocatedCounts() const { |
| zx_status_t 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_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_ERR_BAD_STATE; |
| } |
| |
| return status; |
| } |
| |
| zx_status_t 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) < 0) { |
| FX_LOGS(ERROR) << "could not read journal block"; |
| return 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_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"; |
| return 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) < 0) { |
| FX_LOGS(ERROR) << "could not read backup superblock"; |
| return 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_status_t MinfsChecker::Create(std::unique_ptr<Bcache> bc, const FsckOptions& fsck_options, |
| std::unique_ptr<MinfsChecker>* out) { |
| std::unique_ptr<Minfs> fs; |
| zx_status_t status = Minfs::Create( |
| std::move(bc), |
| MountOptions{ |
| .readonly = fsck_options.read_only, |
| .repair_filesystem = fsck_options.repair, |
| .fsck_after_every_transaction = false, // Explicit in case the default is overridden. |
| .quiet = fsck_options.quiet, |
| }, |
| &fs); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "MinfsChecker::Create Failed to Create Minfs: " << status; |
| return status; |
| } |
| |
| const Superblock& info = fs->Info(); |
| |
| auto checker = std::unique_ptr<MinfsChecker>(new MinfsChecker(fsck_options)); |
| checker->links_.reset(new int32_t[info.inode_count]{0}, info.inode_count); |
| checker->links_[0] = -1; |
| checker->cached_doubly_indirect_ = 0; |
| checker->cached_indirect_ = 0; |
| |
| if ((status = checker->checked_inodes_.Reset(info.inode_count)) != ZX_OK) { |
| FX_LOGS(ERROR) << "MinfsChecker::Init Failed to reset checked inodes: " << status; |
| return status; |
| } |
| if ((status = checker->checked_blocks_.Reset(info.block_count)) != ZX_OK) { |
| FX_LOGS(ERROR) << "MinfsChecker::Init Failed to reset checked blocks: " << status; |
| return status; |
| } |
| checker->fs_ = std::move(fs); |
| *out = std::move(checker); |
| return ZX_OK; |
| } |
| |
| 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_status_t 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 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 transaction_handler->RunRequests(builder.TakeOperations()); |
| } |
| |
| // Reads backup superblock from correct location depending on whether filesystem has FVM support. |
| zx_status_t ReadBackupSuperblock(fs::TransactionHandler* transaction_handler, |
| block_client::BlockDevice* device, uint32_t max_blocks, |
| uint32_t backup_location, Superblock* out_backup) { |
| zx_status_t status = device->ReadBlock(backup_location, kMinfsBlockSize, out_backup); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = CheckSuperblock(out_backup, device, max_blocks); |
| if (status != ZX_OK) { |
| return status; |
| } |
| // Found a valid backup superblock. Confirm if the FVM flags are set in the backup superblock. |
| if ((backup_location == kFvmSuperblockBackup) && ((out_backup->flags & kMinfsFlagFVM) == 0)) { |
| return ZX_ERR_BAD_STATE; |
| } else if ((backup_location == kNonFvmSuperblockBackup) && |
| ((out_backup->flags & kMinfsFlagFVM) != 0)) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| return ZX_OK; |
| } |
| #endif |
| |
| } // namespace |
| |
| // Repairs superblock from backup. |
| #ifdef __Fuchsia__ |
| zx_status_t RepairSuperblock(fs::DeviceTransactionHandler* transaction_handler, |
| block_client::BlockDevice* device, uint32_t max_blocks, |
| Superblock* info_out) { |
| Superblock backup_info; |
| // Try the FVM backup location first. |
| zx_status_t status = ReadBackupSuperblock(transaction_handler, device, max_blocks, |
| kFvmSuperblockBackup, &backup_info); |
| |
| if (status != ZX_OK) { |
| // Try the non-fvm backup superblock location. |
| status = ReadBackupSuperblock(transaction_handler, device, max_blocks, kNonFvmSuperblockBackup, |
| &backup_info); |
| } |
| |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Fsck::RepairSuperblock failed. Unrepairable superblock: " << status; |
| return status; |
| } |
| FX_LOGS(INFO) << "Superblock corrupted. Repairing filesystem from backup superblock."; |
| |
| // Try to reconstruct alloc_*_counts of the backup superblock, since the |
| // alloc_*_counts might be out-of-sync with the actual values. |
| status = ReconstructAllocCounts(transaction_handler, device, &backup_info); |
| |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Fsck::ReconstructAllocCounts failed. Unrepairable superblock: " << status; |
| return status; |
| } |
| // Recalculate checksum. |
| UpdateChecksum(&backup_info); |
| |
| // Update superblock and backup superblock. |
| status = WriteSuperblockAndBackupSuperblock(transaction_handler, device, &backup_info); |
| |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Fsck::RepairSuperblock failed to repair superblock from backup :" << status; |
| } |
| |
| // Updating in-memory info. |
| memcpy(info_out, &backup_info, sizeof(backup_info)); |
| return status; |
| } |
| #endif |
| |
| zx_status_t LoadSuperblock(Bcache* bc, Superblock* out_info) { |
| zx_status_t status = bc->Readblk(kSuperblockStart, out_info); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "could not read info block."; |
| return status; |
| } |
| DumpInfo(*out_info); |
| #ifdef __Fuchsia__ |
| status = CheckSuperblock(out_info, bc->device(), bc->Maxblk()); |
| #else |
| status = CheckSuperblock(out_info, bc->Maxblk()); |
| #endif |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Fsck: check_info failure: " << status; |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t UsedDataSize(std::unique_ptr<Bcache>& bc, uint64_t* out_size) { |
| zx_status_t status; |
| Superblock info = {}; |
| if ((status = LoadSuperblock(bc.get(), &info)) != ZX_OK) { |
| return status; |
| } |
| |
| *out_size = (info.alloc_block_count * info.block_size); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsedInodes(std::unique_ptr<Bcache>& bc, uint64_t* out_inodes) { |
| zx_status_t status; |
| Superblock info = {}; |
| if ((status = LoadSuperblock(bc.get(), &info)) != ZX_OK) { |
| return status; |
| } |
| |
| *out_inodes = info.alloc_inode_count; |
| return ZX_OK; |
| } |
| |
| zx_status_t UsedSize(std::unique_ptr<Bcache>& bc, uint64_t* out_size) { |
| zx_status_t status; |
| Superblock info = {}; |
| if ((status = LoadSuperblock(bc.get(), &info)) != ZX_OK) { |
| return status; |
| } |
| |
| *out_size = (NonDataBlocks(info) + info.alloc_block_count) * info.block_size; |
| return ZX_OK; |
| } |
| |
| #ifdef __Fuchsia__ |
| zx_status_t CalculateBitsSetBitmap(fs::TransactionHandler* transaction_handler, |
| block_client::BlockDevice* device, blk_t start_block, |
| uint32_t num_blocks, uint32_t* out_bits_set) { |
| #else |
| zx_status_t CalculateBitsSetBitmap(fs::TransactionHandler* transaction_handler, blk_t start_block, |
| uint32_t num_blocks, uint32_t* out_bits_set) { |
| #endif |
| minfs::RawBitmap bitmap; |
| zx_status_t status = bitmap.Reset(num_blocks * kMinfsBlockBits); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| #ifdef __Fuchsia__ |
| storage::OwnedVmoid map_vmoid; |
| status = |
| device->BlockAttachVmo(bitmap.StorageUnsafe()->GetVmo(), &map_vmoid.GetReference(device)); |
| if (status != ZX_OK) { |
| return 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 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; |
| } |
| } |
| |
| *out_bits_set = static_cast<uint32_t>(count); |
| return ZX_OK; |
| } |
| |
| #ifdef __Fuchsia__ |
| zx_status_t ReconstructAllocCounts(fs::TransactionHandler* transaction_handler, |
| block_client::BlockDevice* device, Superblock* out_info) { |
| #else |
| zx_status_t 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. |
| zx_status_t status = |
| CalculateBitsSetBitmap(transaction_handler, device, out_info->abm_block, |
| allocation_bitmap_num_blocks, &(out_info->alloc_block_count)); |
| #else |
| zx_status_t status = |
| CalculateBitsSetBitmap(transaction_handler, out_info->abm_block, allocation_bitmap_num_blocks, |
| &(out_info->alloc_block_count)); |
| #endif |
| if (status != ZX_OK) { |
| return status; |
| } |
| uint32_t inode_bitmap_num_blocks = |
| (out_info->inode_count + kMinfsBlockBits - 1) / kMinfsBlockBits; |
| |
| #ifdef __Fuchsia__ |
| // Correct allocated inode count. |
| status = CalculateBitsSetBitmap(transaction_handler, device, out_info->ibm_block, |
| inode_bitmap_num_blocks, &(out_info->alloc_inode_count)); |
| #else |
| status = CalculateBitsSetBitmap(transaction_handler, out_info->ibm_block, inode_bitmap_num_blocks, |
| &(out_info->alloc_inode_count)); |
| #endif |
| |
| if (status != ZX_OK) { |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Fsck(std::unique_ptr<Bcache> bc, const FsckOptions& options, |
| std::unique_ptr<Bcache>* out_bc) { |
| std::unique_ptr<MinfsChecker> chk; |
| zx_status_t status = MinfsChecker::Create(std::move(bc), options, &chk); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Fsck: Init failure: " << status; |
| return status; |
| } |
| |
| chk->CheckReserved(); |
| |
| status = chk->CheckInode(kMinfsRootIno, kMinfsRootIno, 0); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Fsck: CheckInode failure: " << status; |
| return status; |
| } |
| |
| zx_status_t r; |
| |
| // Save an error if it occurs, but check for subsequent errors anyway. |
| r = chk->CheckUnlinkedInodes(); |
| status |= (status != ZX_OK) ? 0 : r; |
| r = chk->CheckForUnusedBlocks(); |
| status |= (status != ZX_OK) ? 0 : r; |
| r = chk->CheckForUnusedInodes(); |
| status |= (status != ZX_OK) ? 0 : r; |
| r = chk->CheckLinkCounts(); |
| status |= (status != ZX_OK) ? 0 : r; |
| r = chk->CheckAllocatedCounts(); |
| status |= (status != ZX_OK) ? 0 : r; |
| |
| r = chk->CheckSuperblockIntegrity(); |
| status |= (status != ZX_OK) ? 0 : r; |
| |
| status |= (status != ZX_OK) ? 0 : (chk->conforming() ? ZX_OK : ZX_ERR_BAD_STATE); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| chk->DumpStats(); |
| |
| if (out_bc != nullptr) { |
| *out_bc = MinfsChecker::Destroy(std::move(chk)); |
| } |
| return ZX_OK; |
| } |
| |
| } // namespace minfs |