text: // Copyright 2021 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 <bitset>
  #include <iostream>
  
  #include <safemath/checked_math.h>
  
  #include "src/storage/f2fs/f2fs.h"
  
  namespace f2fs {
  namespace {
  uint32_t MaxInlineData(const Inode &inode) {
    uint16_t extra_isize = (inode.i_inline & kExtraAttr) ? inode.i_extra_isize : 0;
    return sizeof(uint32_t) *
           (kAddrsPerInode - extra_isize / sizeof(uint32_t) - kInlineXattrAddrs - 1);
  }
  
  uint32_t MaxInlineDentry(const Inode &inode) {
    return MaxInlineData(inode) * kBitsPerByte /
           ((kSizeOfDirEntry + kDentrySlotLen) * kBitsPerByte + 1);
  }
  
  const uint8_t *InlineDentryBitmap(const Inode &inode) {
    uint16_t extra_isize = (inode.i_inline & kExtraAttr) ? inode.i_extra_isize : 0;
    return reinterpret_cast<const uint8_t *>(
        &inode.i_addr[extra_isize / sizeof(uint32_t) + kInlineStartOffset]);
  }
  
  const DirEntry *InlineDentryArray(const Inode &inode) {
    uint32_t reserved =
        MaxInlineData(inode) - MaxInlineDentry(inode) * (kSizeOfDirEntry + kDentrySlotLen);
    return reinterpret_cast<const DirEntry *>(InlineDentryBitmap(inode) + reserved);
  }
  
  const uint8_t (*InlineDentryNameArray(const Inode &inode))[kDentrySlotLen] {
    uint32_t reserved = MaxInlineData(inode) - MaxInlineDentry(inode) * kDentrySlotLen;
    return reinterpret_cast<const uint8_t(*)[kDentrySlotLen]>(InlineDentryBitmap(inode) + reserved);
  }
  }  // namespace
  
  template <typename T>
  static inline void DisplayMember(uint32_t typesize, T value, std::string_view name) {
    if (typesize == sizeof(char)) {
      std::cout << name << " [" << value << "]" << std::endl;
    } else {
      ZX_ASSERT(sizeof(T) <= typesize);
      std::cout << name << " [0x" << std::hex << value << " : " << std::dec << value << "]"
                << std::endl;
    }
  }
  
  static int32_t operator-(CursegType &a, CursegType &&b) {
    return (static_cast<int32_t>(a) - static_cast<int32_t>(b));
  }
  
  static bool operator<=(int32_t &a, CursegType &&b) { return (a <= static_cast<int32_t>(b)); }
  
  CursegType operator+(CursegType a, uint32_t &&b) {
    return static_cast<CursegType>(static_cast<uint32_t>(a) + b);
  }
  
  static inline bool IsSumNodeSeg(SummaryFooter &footer) { return footer.entry_type == kSumTypeNode; }
  
  static inline uint64_t BlkoffFromMain(SegmentManager &manager, uint64_t block_address) {
    ZX_ASSERT(block_address >= manager.GetMainAreaStartBlock());
    return block_address - manager.GetMainAreaStartBlock();
  }
  
  static inline uint32_t OffsetInSegment(SuperblockInfo &sbi, SegmentManager &manager,
                                         uint64_t block_address) {
    return (uint32_t)(BlkoffFromMain(manager, block_address) % (1 << sbi.GetLogBlocksPerSeg()));
  }
  
  static inline uint16_t AddrsPerInode(const Inode *i) {
  #if 0  // porting needed
  	      if (i->i_inline & kInlineXattr)
  					            return kAddrPerInode - kInlineXattrAddrs;
  #endif
    return kAddrsPerInode;
  }
  
  zx_status_t FsckWorker::ReadBlock(FsBlock &fs_block, block_t bno) {
  #ifdef __Fuchsia__
    return bc_->Readblk(bno, fs_block.GetData().data());
  #else   // __Fuchsia__
    return bc_->Readblk(bno, fs_block.GetData());
  #endif  // __Fuchsia__
  }
  
  zx_status_t FsckWorker::WriteBlock(FsBlock &fs_block, block_t bno) {
  #ifdef __Fuchsia__
    return bc_->Writeblk(bno, fs_block.GetData().data());
  #else   // __Fuchsia__
    return bc_->Writeblk(bno, fs_block.GetData());
  #endif  // __Fuchsia__
  }
  
  void FsckWorker::AddIntoInodeLinkMap(nid_t nid, uint32_t link_count) {
    auto ret = fsck_.inode_link_map.insert({nid, {.links = link_count, .actual_links = 1}});
    ZX_ASSERT(ret.second);
    if (link_count > 1) {
      FX_LOGS(INFO) << "ino[0x" << std::hex << nid << "] has hard links [0x" << link_count << "]";
    }
  }
  
  zx_status_t FsckWorker::FindAndIncreaseInodeLinkMap(nid_t nid) {
    if (auto ret = fsck_.inode_link_map.find(nid); ret != fsck_.inode_link_map.end()) {
      ++ret->second.actual_links;
      return ZX_OK;
    }
    return ZX_ERR_NOT_FOUND;
  }
  
  bool FsckWorker::IsValidSsaNodeBlock(nid_t nid, uint32_t block_address) {
    auto [ret, summary_entry] = GetSummaryEntry(block_address);
    ZX_ASSERT(static_cast<int>(ret) >= 0);
  
    if (ret == SegType::kSegTypeData || ret == SegType::kSegTypeCurData) {
      FX_LOGS(ERROR) << "Summary footer is not a node segment summary";
      ZX_ASSERT(0);
    } else if (ret == SegType::kSegTypeNode) {
      if (LeToCpu(summary_entry.nid) != nid) {
        FX_LOGS(ERROR) << "nid                       [0x" << std::hex << nid << "]";
        FX_LOGS(ERROR) << "target block_address           [0x" << std::hex << block_address << "]";
        FX_LOGS(ERROR) << "summary block_address          [0x" << std::hex
                       << segment_manager_->GetSumBlock(
                              segment_manager_->GetSegmentNumber(block_address))
                       << "]";
        FX_LOGS(ERROR) << "seg no / offset           [0x" << std::hex
                       << segment_manager_->GetSegmentNumber(block_address) << "/0x" << std::hex
                       << OffsetInSegment(superblock_info_, *segment_manager_, block_address) << "]";
        FX_LOGS(ERROR) << "summary_entry.nid         [0x" << std::hex << LeToCpu(summary_entry.nid)
                       << "]";
        FX_LOGS(ERROR) << "--> node block's nid      [0x" << std::hex << nid << "]";
        FX_LOGS(ERROR) << "Invalid node seg summary\n";
        ZX_ASSERT(0);
      }
    } else if (ret == SegType::kSegTypeCurNode) {
      // current node segment has no ssa
    } else {
      FX_LOGS(ERROR) << "Invalid return value of 'GetSummaryEntry'";
      ZX_ASSERT(0);
    }
    return true;
  }
  
  bool FsckWorker::IsValidSsaDataBlock(uint32_t block_address, uint32_t parent_nid,
                                       uint16_t index_in_node, uint8_t version) {
    auto [ret, summary_entry] = GetSummaryEntry(block_address);
    ZX_ASSERT(ret == SegType::kSegTypeData || ret == SegType::kSegTypeCurData);
  
    if (LeToCpu(summary_entry.nid) != parent_nid || summary_entry.version != version ||
        LeToCpu(summary_entry.ofs_in_node) != index_in_node) {
      FX_LOGS(ERROR) << "summary_entry.nid         [0x" << std::hex << LeToCpu(summary_entry.nid)
                     << "]";
      FX_LOGS(ERROR) << "summary_entry.version     [0x" << std::hex << summary_entry.version << "]";
      FX_LOGS(ERROR) << "summary_entry.ofs_in_node [0x" << std::hex
                     << LeToCpu(summary_entry.ofs_in_node) << "]";
  
      FX_LOGS(ERROR) << "parent nid                [0x" << std::hex << parent_nid << "]";
      FX_LOGS(ERROR) << "version from nat          [0x" << std::hex << version << "]";
      FX_LOGS(ERROR) << "index in parent node        [0x" << std::hex << index_in_node << "]";
  
      FX_LOGS(ERROR) << "Target data block address    [0x" << std::hex << block_address << "]";
      FX_LOGS(ERROR) << "Invalid data seg summary\n";
      ZX_ASSERT(0);
    }
    return true;
  }
  
  bool FsckWorker::IsValidNid(nid_t nid) {
    return nid <= (kNatEntryPerBlock * superblock_info_.GetRawSuperblock().segment_count_nat
                   << (superblock_info_.GetLogBlocksPerSeg() - 1));
  }
  
  bool FsckWorker::IsValidBlockAddress(uint32_t addr) {
    if (addr >= superblock_info_.GetRawSuperblock().block_count) {
      std::cout << std::hex << "block[0x" << addr << "] should be less than [0x"
                << superblock_info_.GetRawSuperblock().block_count << "]\n";
      return false;
    }
    if (addr < segment_manager_->GetMainAreaStartBlock()) {
      std::cout << std::hex << "block[0x" << addr << "] should be greater than [0x"
                << segment_manager_->GetMainAreaStartBlock() << "]\n";
      return false;
    }
    return true;
  }
  
  zx_status_t FsckWorker::ValidateNodeBlock(const Node &node_block, NodeInfo node_info,
                                            FileType ftype, NodeType ntype) {
    if (node_info.nid != LeToCpu(node_block.footer.nid) ||
        node_info.ino != LeToCpu(node_block.footer.ino)) {
      FX_LOGS(ERROR) << std::hex << "ino[0x" << node_info.ino << "] nid[0x" << node_info.nid
                     << "] blk_addr[0x" << node_info.blk_addr << "] footer.nid[0x"
                     << LeToCpu(node_block.footer.nid) << "] footer.ino[0x"
                     << LeToCpu(node_block.footer.ino) << "]";
      return ZX_ERR_INTERNAL;
    }
  
    if (ntype == NodeType::kTypeInode) {
      uint32_t i_links = LeToCpu(node_block.i.i_links);
  
      // Orphan node. i_links should be 0.
      if (ftype == FileType::kFtOrphan) {
        ZX_ASSERT(i_links == 0);
      } else {
        ZX_ASSERT(i_links > 0);
      }
    }
  
    return ZX_OK;
  }
  
  zx::status<bool> FsckWorker::UpdateContext(const Node &node_block, NodeInfo node_info,
                                             FileType ftype, NodeType ntype) {
    nid_t nid = node_info.nid;
    if (ftype != FileType::kFtOrphan || TestValidBitmap(nid, fsck_.nat_area_bitmap.get()) != 0x0) {
      ClearValidBitmap(nid, fsck_.nat_area_bitmap.get());
    } else {
      FX_LOGS(WARNING) << "nid duplicated [0x" << std::hex << nid << "]";
    }
  
    if (TestValidBitmap(BlkoffFromMain(*segment_manager_, node_info.blk_addr),
                        fsck_.main_area_bitmap.get()) == 0x0) {
      // Unvisited node, mark visited.
      SetValidBitmap(BlkoffFromMain(*segment_manager_, node_info.blk_addr),
                     fsck_.main_area_bitmap.get());
  
      if (ntype == NodeType::kTypeInode) {
        uint32_t i_links = LeToCpu(node_block.i.i_links);
        if (ftype != FileType::kFtDir) {
          // First time visiting this inode.
          AddIntoInodeLinkMap(nid, i_links);
          if (i_links > 1) {
            ++fsck_.result.multi_hard_link_files;
          }
        }
        ++fsck_.result.valid_inode_count;
      }
  
      ++fsck_.result.valid_block_count;
      ++fsck_.result.valid_node_count;
    } else {
      // Once visited here, it should be an Inode.
      if (ntype != NodeType::kTypeInode) {
        FX_LOGS(ERROR) << std::hex << "Duplicated node block. nid[0x" << nid << "] blk_addr[0x"
                       << node_info.blk_addr << "]";
        return zx::error(ZX_ERR_INTERNAL);
      }
  
      uint32_t i_links = LeToCpu(node_block.i.i_links);
  
      if (ftype == FileType::kFtDir) {
        FX_LOGS(INFO) << "Duplicated inode blk. ino[0x" << std::hex << nid << "][0x" << std::hex
                      << node_info.blk_addr;
        return zx::error(ZX_ERR_INTERNAL);
      } else {
        FX_LOGS(INFO) << "ino[0x" << std::hex << nid << "] has hard links [0x" << std::hex << i_links
                      << "]";
        // We don't go deeper.
        if (auto status = FindAndIncreaseInodeLinkMap(nid); status != ZX_OK) {
          return zx::error(status);
        }
        return zx::ok(false);
      }
    }
    return zx::ok(true);
  }
  
  zx::status<std::pair<std::unique_ptr<FsBlock>, NodeInfo>> FsckWorker::ReadNodeBlock(nid_t nid) {
    if (!IsValidNid(nid)) {
      return zx::error(ZX_ERR_INVALID_ARGS);
    }
  
    auto result = GetNodeInfo(nid);
    if (result.is_error()) {
      return zx::error(ZX_ERR_NOT_FOUND);
    }
    NodeInfo node_info = *result;
  
    if (node_info.blk_addr == kNewAddr) {
      return zx::ok(std::pair<std::unique_ptr<FsBlock>, NodeInfo>{nullptr, node_info});
    }
  
    if (!IsValidBlockAddress(node_info.blk_addr) || !IsValidSsaNodeBlock(nid, node_info.blk_addr)) {
      return zx::error(ZX_ERR_INTERNAL);
    }
  
    if (TestValidBitmap(BlkoffFromMain(*segment_manager_, node_info.blk_addr),
                        sit_area_bitmap_.get()) == 0x0) {
      FX_LOGS(INFO) << "SIT bitmap is 0x0. block_address[0x" << std::hex << node_info.blk_addr << "]";
      return zx::error(ZX_ERR_INTERNAL);
    }
  
    auto fs_block = std::make_unique<FsBlock>();
    ZX_ASSERT(ReadBlock(*fs_block, node_info.blk_addr) == ZX_OK);
  
    return zx::ok(std::pair<std::unique_ptr<FsBlock>, NodeInfo>{std::move(fs_block), node_info});
  }
  
  zx::status<TraverseResult> FsckWorker::CheckNodeBlock(const Inode *inode, nid_t nid, FileType ftype,
                                                        NodeType ntype) {
    uint64_t block_count = 0;
    uint32_t link_count = 0;
  
    // Read the node block.
    auto result = ReadNodeBlock(nid);
    if (result.is_error()) {
      return result.take_error();
    }
  
    auto [fs_block, node_info] = std::move(*result);
    if (fs_block == nullptr) {
      // This means that the block was already allocated, but not stored in disk.
      ZX_ASSERT(node_info.blk_addr == kNewAddr);
  
      ++fsck_.result.valid_block_count;
      ++fsck_.result.valid_node_count;
      if (ntype == NodeType::kTypeInode) {
        ++fsck_.result.valid_inode_count;
      }
      return zx::ok(TraverseResult{block_count, link_count});
    }
  
  #ifdef __Fuchsia__
    auto node_block = reinterpret_cast<Node *>(fs_block->GetData().data());
  #else   // __Fuchsia__
    auto node_block = reinterpret_cast<Node *>(fs_block->GetData());
  #endif  // __Fuchsia__
  
    // Validate the node block.
    if (auto status = ValidateNodeBlock(*node_block, node_info, ftype, ntype); status != ZX_OK) {
      return zx::error(status);
    }
  
    // Update fsck context.
    auto do_traverse = UpdateContext(*node_block, node_info, ftype, ntype);
    if (do_traverse.is_error()) {
      return do_traverse.take_error();
    }
  
    if (*do_traverse == true) {
      // Traverse to underlying structures.
      zx::status<TraverseResult> ret;
      switch (ntype) {
        case NodeType::kTypeInode:
          ret = TraverseInodeBlock(*node_block, node_info, ftype);
          break;
        case NodeType::kTypeDirectNode:
          ret = TraverseDnodeBlock(inode, *node_block, node_info, ftype);
          break;
        case NodeType::kTypeIndirectNode:
          ret = TraverseIndirectNodeBlock(inode, *node_block, ftype);
          break;
        case NodeType::kTypeDoubleIndirectNode:
          ret = TraverseDoubleIndirectNodeBlock(inode, *node_block, ftype);
          break;
        default:
          ret = zx::error(ZX_ERR_INTERNAL);
          break;
      }
  
      if (ret.is_error()) {
        return ret.take_error();
      }
  
      block_count += ret->block_count;
      link_count += ret->link_count;
  
      if (ntype == NodeType::kTypeInode) {
        uint32_t i_links = LeToCpu(node_block->i.i_links);
        uint64_t i_blocks = LeToCpu(node_block->i.i_blocks);
        if (i_blocks != block_count) {
          PrintNodeInfo(*node_block);
          FX_LOGS(ERROR) << "i_blocks != block_count";
          return zx::error(ZX_ERR_INTERNAL);
        }
        if (ftype == FileType::kFtDir && i_links != link_count) {
          PrintNodeInfo(*node_block);
          FX_LOGS(ERROR) << "i_links != link_count";
          return zx::error(ZX_ERR_INTERNAL);
        }
      }
    }
    return zx::ok(TraverseResult{block_count, link_count});
  }
  
  zx::status<TraverseResult> FsckWorker::TraverseInodeBlock(const Node &node_block,
                                                            NodeInfo node_info, FileType ftype) {
    uint32_t child_count = 0, child_files = 0;
    uint64_t block_count = 1;
    nid_t nid = node_info.nid;
    NodeType ntype;
    uint64_t i_blocks = LeToCpu(node_block.i.i_blocks);
  
    // ValidateNodeBlock ensures below.
    ZX_ASSERT(node_info.nid == node_info.ino);
    ZX_ASSERT(LeToCpu(node_block.footer.nid) == node_info.nid);
    ZX_ASSERT(LeToCpu(node_block.footer.ino) == node_info.ino);
  
  #if 0  // porting needed
    fsck_chk_xattr_blk(sbi, nid, LeToCpu(node_block->i.i_xattr_nid), block_count);
  #endif
  
    do {
      if (ftype == FileType::kFtChrdev || ftype == FileType::kFtBlkdev ||
          ftype == FileType::kFtFifo || ftype == FileType::kFtSock) {
        break;
      }
  #if 0  // porting needed
    if ((node_block->i.i_inline & F2FS_INLINE_DATA)) {
      FX_LOGS(INFO) << "ino[0x" << std::hex << nid << "] has inline data";
      break;
    }
  #endif
  
      if (node_block.i.i_inline & kInlineDentry) {
        if (auto status =
                CheckDentries(child_count, child_files, 1, InlineDentryBitmap(node_block.i),
                              InlineDentryArray(node_block.i), InlineDentryNameArray(node_block.i),
                              MaxInlineDentry(node_block.i));
            status != ZX_OK) {
          return zx::error(status);
        }
      } else {
        uint16_t base =
            (node_block.i.i_inline & kExtraAttr) ? node_block.i.i_extra_isize / sizeof(uint32_t) : 0;
  
        // check data blocks in inode
        for (uint16_t index = base; index < AddrsPerInode(&node_block.i); ++index) {
          if (LeToCpu(node_block.i.i_addr[index]) != 0) {
            ++block_count;
            if (auto status = CheckDataBlock(LeToCpu(node_block.i.i_addr[index]), child_count,
                                             child_files, (i_blocks == block_count), ftype, nid,
                                             index - base, node_info.version);
                status != ZX_OK) {
              return zx::error(status);
            }
          }
        }
      }
  
      // check node blocks in inode: direct(2) + indirect(2) + double indirect(1)
      for (int index = 0; index < 5; ++index) {
        if (index == 0 || index == 1) {
          ntype = NodeType::kTypeDirectNode;
        } else if (index == 2 || index == 3) {
          ntype = NodeType::kTypeIndirectNode;
        } else if (index == 4) {
          ntype = NodeType::kTypeDoubleIndirectNode;
        } else {
          ZX_ASSERT(0);
        }
  
        if (LeToCpu(node_block.i.i_nid[index]) != 0) {
          auto ret = CheckNodeBlock(&node_block.i, LeToCpu(node_block.i.i_nid[index]), ftype, ntype);
          if (ret.is_error()) {
            return ret.take_error();
          }
          block_count += ret->block_count;
          child_count += ret->link_count;
        }
      }
    } while (0);
  
    return zx::ok(TraverseResult{block_count, child_count});
  }
  
  zx::status<TraverseResult> FsckWorker::TraverseDnodeBlock(const Inode *inode,
                                                            const Node &node_block,
                                                            NodeInfo node_info, FileType ftype) {
    nid_t nid = node_info.nid;
    uint64_t block_count = 1;
    uint32_t child_count = 0, child_files = 0;
    for (uint16_t index = 0; index < kAddrsPerBlock; ++index) {
      if (LeToCpu(node_block.dn.addr[index]) == 0x0) {
        continue;
      }
      ++block_count;
      if (auto status = CheckDataBlock(LeToCpu(node_block.dn.addr[index]), child_count, child_files,
                                       LeToCpu(inode->i_blocks) == block_count, ftype, nid, index,
                                       node_info.version);
          status != ZX_OK) {
        return zx::error(status);
      }
    }
    return zx::ok(TraverseResult{block_count, child_count});
  }
  
  zx::status<TraverseResult> FsckWorker::TraverseIndirectNodeBlock(const Inode *inode,
                                                                   const Node &node_block,
                                                                   FileType ftype) {
    uint64_t block_count = 1;
    uint32_t child_count = 0;
    for (uint32_t i = 0; i < kNidsPerBlock; ++i) {
      if (LeToCpu(node_block.in.nid[i]) == 0x0) {
        continue;
      }
      auto ret =
          CheckNodeBlock(inode, LeToCpu(node_block.in.nid[i]), ftype, NodeType::kTypeDirectNode);
      if (ret.is_error()) {
        return ret;
      }
      block_count += ret->block_count;
      child_count += ret->link_count;
    }
    return zx::ok(TraverseResult{block_count, child_count});
  }
  
  zx::status<TraverseResult> FsckWorker::TraverseDoubleIndirectNodeBlock(const Inode *inode,
                                                                         const Node &node_block,
                                                                         FileType ftype) {
    uint64_t block_count = 1;
    uint32_t child_count = 0;
    for (int i = 0; i < kNidsPerBlock; ++i) {
      if (LeToCpu(node_block.in.nid[i]) == 0x0) {
        continue;
      }
      auto ret =
          CheckNodeBlock(inode, LeToCpu(node_block.in.nid[i]), ftype, NodeType::kTypeIndirectNode);
      if (ret.is_error()) {
        return ret;
      }
      block_count += ret->block_count;
      child_count += ret->link_count;
    }
    return zx::ok(TraverseResult{block_count, child_count});
  }
  
  void FsckWorker::PrintDentry(const uint32_t depth, std::string_view name,
                               const uint8_t *dentry_bitmap, const DirEntry &dentry, const int index,
                               const int last_block, const int max_entries) {
    int last_de = 0;
    int next_idx = 0;
    int name_len;
    int bit_offset;
  
  #if 0  // porting needed
    if (config.dbg_lv != -1)
      return;
  #endif
  
    name_len = LeToCpu(dentry.name_len);
    next_idx = index + (name_len + kDentrySlotLen - 1) / kDentrySlotLen;
  
    bit_offset = FindNextBit(dentry_bitmap, max_entries, next_idx);
    if (bit_offset >= max_entries && last_block) {
      last_de = 1;
    }
  
    if (tree_mark_.size() <= depth) {
      tree_mark_.resize(tree_mark_.size() * 2, 0);
    }
    if (last_de) {
      tree_mark_[depth] = '`';
    } else {
      tree_mark_[depth] = '|';
    }
  
    if (tree_mark_[depth - 1] == '`') {
      tree_mark_[depth - 1] = ' ';
    }
  
    for (uint32_t i = 1; i < depth; ++i) {
      std::cout << tree_mark_[i] << "   ";
    }
    std::cout << (last_de ? "`" : "|") << "-- " << name << std::endl;
  }
  
  zx_status_t FsckWorker::CheckDentries(uint32_t &child_count, uint32_t &child_files,
                                        const int last_block, const uint8_t *dentry_bitmap,
                                        const DirEntry *dentries, const uint8_t (*filename)[kNameLen],
                                        const int max_entries) {
    int num_entries = 0;
    uint32_t hash_code;
    FileType ftype;
  
    ++fsck_.dentry_depth;
  
    for (int i = 0; i < max_entries;) {
      if (TestBit(i, dentry_bitmap) == 0x0) {
        ++i;
        continue;
      }
  
      std::string_view name(reinterpret_cast<const char *>(filename[i]),
                            LeToCpu(dentries[i].name_len));
      hash_code = DentryHash(name);
  
      ftype = static_cast<FileType>(dentries[i].file_type);
  
      // Becareful. 'dentry.file_type' is not imode
      if (ftype == FileType::kFtDir) {
        ++child_count;
        if (IsDotOrDotDot(name)) {
          ++i;
          continue;
        }
      }
  
      // TODO: Should we check '.' and '..' entries?
      ZX_ASSERT(LeToCpu(dentries[i].hash_code) == hash_code);
  #if 0  // TODO: implement debug level
      // TODO: DBG (2)
      printf("[%3u] - no[0x%x] name[%s] len[0x%x] ino[0x%x] type[0x%x]\n", fsck->dentry_depth, i,
             name.data(), LeToCpu(dentries[i].name_len), LeToCpu(dentries[i].ino),
             dentries[i].file_type);
  #endif
  
      PrintDentry(fsck_.dentry_depth, name, dentry_bitmap, dentries[i], i, last_block, max_entries);
  
      auto ret = CheckNodeBlock(nullptr, LeToCpu(dentries[i].ino), ftype, NodeType::kTypeInode);
      if (ret.is_error()) {
        return ret.error_value();
      }
  
      i += (name.length() + kDentrySlotLen - 1) / kDentrySlotLen;
      ++num_entries;
      ++child_files;
    }
  #if 0  // TODO: implement debug level
    // TODO: DBG (1)
    printf("[%3d] Dentry Block [0x%x] Done : dentries:%d in %d slots (len:%d)\n\n",
           fsck->dentry_depth, blk_addr, num_entries, kNrDentryInBlock, kMaxNameLen);
  #endif
  
    --fsck_.dentry_depth;
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::CheckDentryBlock(uint32_t block_address, uint32_t &child_count,
                                           uint32_t &child_files, int last_block) {
    DentryBlock *de_blk;
  
    auto fs_block = std::make_unique<FsBlock>();
    ZX_ASSERT(ReadBlock(*fs_block, block_address) == ZX_OK);
  #ifdef __Fuchsia__
    de_blk = reinterpret_cast<DentryBlock *>(fs_block->GetData().data());
  #else   // __Fuchsia__
    de_blk = reinterpret_cast<DentryBlock *>(fs_block->GetData());
  #endif  // __Fuchsia__
  
    return CheckDentries(child_count, child_files, last_block, de_blk->dentry_bitmap, de_blk->dentry,
                         de_blk->filename, kNrDentryInBlock);
  }
  
  zx_status_t FsckWorker::CheckDataBlock(uint32_t block_address, uint32_t &child_count,
                                         uint32_t &child_files, int last_block, FileType ftype,
                                         uint32_t parent_nid, uint16_t index_in_node, uint8_t ver) {
    // Is it reserved block?
    if (block_address == kNewAddr) {
      ++fsck_.result.valid_block_count;
      return ZX_OK;
    }
  
    if (!IsValidBlockAddress(block_address)) {
      return ZX_ERR_INTERNAL;
    }
  
    IsValidSsaDataBlock(block_address, parent_nid, index_in_node, ver);
  
    if (TestValidBitmap(BlkoffFromMain(*segment_manager_, block_address), sit_area_bitmap_.get()) ==
        0x0) {
      ZX_ASSERT_MSG(0, "SIT bitmap is 0x0. block_address[0x%x]\n", block_address);
    }
  
    if (TestValidBitmap(BlkoffFromMain(*segment_manager_, block_address),
                        fsck_.main_area_bitmap.get()) != 0) {
      ZX_ASSERT_MSG(0, "Duplicated data block. pnid[0x%x] index[0x%x] block_address[0x%x]\n",
                    parent_nid, index_in_node, block_address);
    }
    SetValidBitmap(BlkoffFromMain(*segment_manager_, block_address), fsck_.main_area_bitmap.get());
  
    ++fsck_.result.valid_block_count;
  
    if (ftype == FileType::kFtDir) {
      return CheckDentryBlock(block_address, child_count, child_files, last_block);
    }
  
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::CheckOrphanNodes() {
    block_t start_blk, orphan_blkaddr;
    auto fs_block = std::make_unique<FsBlock>();
  
    if (!IsSetCkptFlags(&superblock_info_.GetCheckpoint(), kCpOrphanPresentFlag)) {
      return ZX_OK;
    }
  
    start_blk =
        superblock_info_.StartCpAddr() + 1 + LeToCpu(superblock_info_.GetRawSuperblock().cp_payload);
    orphan_blkaddr = superblock_info_.StartSumAddr() - 1;
  
    for (block_t i = 0; i < orphan_blkaddr; ++i) {
      ZX_ASSERT(ReadBlock(*fs_block, start_blk + i) == ZX_OK);
  #ifdef __Fuchsia__
      OrphanBlock *orphan_block = reinterpret_cast<OrphanBlock *>(fs_block->GetData().data());
  #else   // __Fuchsia__
      OrphanBlock *orphan_block = reinterpret_cast<OrphanBlock *>(fs_block->GetData());
  #endif  // __Fuchsia__
  
      for (block_t j = 0; j < LeToCpu(orphan_block->entry_count); ++j) {
        nid_t ino = LeToCpu(orphan_block->ino[j]);
  #if 0  // TODO: implement debug level
        // TODO: DBG (1)
        printf("[%3d] ino [0x%x]\n", i, ino);
  #endif
  
        auto status = CheckNodeBlock(nullptr, ino, FileType::kFtOrphan, NodeType::kTypeInode);
        if (status.is_error()) {
          return status.error_value();
        }
      }
    }
    return ZX_OK;
  }
  
  #if 0  // porting needed
  int FsckWorker::FsckChkXattrBlk(uint32_t ino, uint32_t x_nid, uint32_t *block_count) {
    FsckInfo *fsck = &fsck_;
    NodeInfo ni;
  
    if (x_nid == 0x0)
      return 0;
  
    if (TestValidBitmap(x_nid, fsck->nat_area_bitmap) != 0x0) {
      ClearValidBitmap(x_nid, fsck->nat_area_bitmap);
    } else {
      ZX_ASSERT_MSG(0, "xattr_nid duplicated [0x%x]\n", x_nid);
    }
  
    *block_count = *block_count + 1;
    ++fsck->chk.valid_block_count;
    ++fsck->chk.valid_node_count;
  
    ZX_ASSERT(GetNodeInfo(x_nid, &ni) >= 0);
  
    if (TestValidBitmap(BlkoffFromMain(superblock_info, ni.blk_addr), fsck->main_area_bitmap) != 0) {
      ZX_ASSERT_MSG(0,
                    "Duplicated node block for x_attr. "
                    "x_nid[0x%x] block addr[0x%x]\n",
                    x_nid, ni.blk_addr);
    }
    SetValidBitmap(BlkoffFromMain(superblock_info, ni.blk_addr), fsck->main_area_bitmap);
  #if 0  // TODO: implement debug level
    // TODO: DBG (2)
    printf("ino[0x%x] x_nid[0x%x]\n", ino, x_nid);
  #endif
    return 0;
  }
  #endif
  
  zx_status_t FsckWorker::Init() {
    fsck_ = FsckInfo{};
    fsck_.nr_main_blocks = segment_manager_->GetMainSegmentsCount()
                           << superblock_info_.GetLogBlocksPerSeg();
    fsck_.main_area_bitmap_size = (fsck_.nr_main_blocks + kBitsPerByte - 1) / kBitsPerByte;
    ZX_ASSERT(fsck_.main_area_bitmap_size == sit_area_bitmap_size_);
    fsck_.main_area_bitmap = std::make_unique<uint8_t[]>(fsck_.main_area_bitmap_size);
    if (fsck_.main_area_bitmap == nullptr) {
      return ZX_ERR_NO_MEMORY;
    }
  
    BuildNatAreaBitmap();
  
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::VerifyCursegOffset(CursegType segtype) {
    CursegInfo *curseg = segment_manager_->CURSEG_I(segtype);
    if (curseg->next_blkoff >= kSitVBlockMapSize * kBitsPerByte) {
      return ZX_ERR_INTERNAL;
    }
  
    block_t logical_curseg_offset = segment_manager_->GetMainAreaStartBlock() +
                                    curseg->segno * superblock_info_.GetBlocksPerSeg() +
                                    curseg->next_blkoff;
  
    if (!IsValidBlockAddress(logical_curseg_offset)) {
      return ZX_ERR_INTERNAL;
    }
  
    if (TestValidBitmap(BlkoffFromMain(*segment_manager_, logical_curseg_offset),
                        sit_area_bitmap_.get()) != 0x0) {
      return ZX_ERR_INTERNAL;
    }
  
    if (curseg->alloc_type == static_cast<uint8_t>(AllocMode::kLFS)) {
      for (block_t offset = curseg->next_blkoff + 1; offset < kSitVBlockMapSize; ++offset) {
        block_t logical_offset = segment_manager_->GetMainAreaStartBlock() +
                                 curseg->segno * superblock_info_.GetBlocksPerSeg() + offset;
  
        if (TestValidBitmap(BlkoffFromMain(*segment_manager_, logical_offset),
                            sit_area_bitmap_.get()) != 0x0) {
          return ZX_ERR_INTERNAL;
        }
      }
    }
  
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::Verify() {
    zx_status_t status = ZX_OK;
    uint32_t nr_unref_nid = 0;
  
    printf("\n");
  
    for (uint32_t i = 0; i < fsck_.nr_nat_entries; ++i) {
      if (TestValidBitmap(i, fsck_.nat_area_bitmap.get()) != 0) {
        printf("NID[0x%x] is unreachable\n", i);
        ++nr_unref_nid;
      }
    }
  
    auto iter = fsck_.inode_link_map.begin();
    while (iter != fsck_.inode_link_map.end()) {
      if (iter->second.links == iter->second.actual_links) {
        iter = fsck_.inode_link_map.erase(iter);
      } else {
        ++iter;
      }
    }
  
    for (auto const [nid, links] : fsck_.inode_link_map) {
      std::cout << std::hex << "NID[0x" << nid << "] has inconsistent link count [0x" << links.links
                << "] (actual: 0x" << links.actual_links << ")\n";
    }
  
    printf("[FSCK] Unreachable nat entries                       ");
    if (nr_unref_nid == 0x0) {
      printf(" [Ok..] [0x%x]\n", nr_unref_nid);
    } else {
      printf(" [Fail] [0x%x]\n", nr_unref_nid);
      status = ZX_ERR_INTERNAL;
    }
  
    printf("[FSCK] SIT valid block bitmap checking                ");
    if (memcmp(sit_area_bitmap_.get(), fsck_.main_area_bitmap.get(), sit_area_bitmap_size_) == 0x0) {
      printf("[Ok..]\n");
    } else {
      printf("[Fail]\n");
      status = ZX_ERR_INTERNAL;
    }
  
    printf("[FSCK] Hard link checking for regular file           ");
    if (fsck_.inode_link_map.empty()) {
      printf(" [Ok..] [0x%x]\n", fsck_.result.multi_hard_link_files);
    } else {
      printf(" [Fail] [0x%x]\n", fsck_.result.multi_hard_link_files);
      status = ZX_ERR_INTERNAL;
    }
  
    printf("[FSCK] valid_block_count matching with CP            ");
    if (superblock_info_.GetTotalValidBlockCount() == fsck_.result.valid_block_count) {
      printf(" [Ok..] [0x%x]\n", (uint32_t)fsck_.result.valid_block_count);
    } else {
      printf(" [Fail] [0x%x]\n", (uint32_t)fsck_.result.valid_block_count);
      status = ZX_ERR_INTERNAL;
    }
  
    printf("[FSCK] valid_node_count matcing with CP (de lookup)  ");
    if (superblock_info_.GetTotalValidNodeCount() == fsck_.result.valid_node_count) {
      printf(" [Ok..] [0x%x]\n", fsck_.result.valid_node_count);
    } else {
      printf(" [Fail] [0x%x]\n", fsck_.result.valid_node_count);
      status = ZX_ERR_INTERNAL;
    }
  
    printf("[FSCK] valid_node_count matcing with CP (nat lookup) ");
    if (superblock_info_.GetTotalValidNodeCount() == fsck_.result.valid_nat_entry_count) {
      printf(" [Ok..] [0x%x]\n", fsck_.result.valid_nat_entry_count);
    } else {
      printf(" [Fail] [0x%x]\n", fsck_.result.valid_nat_entry_count);
      status = ZX_ERR_INTERNAL;
    }
  
    printf("[FSCK] valid_inode_count matched with CP             ");
    if (superblock_info_.GetTotalValidInodeCount() == fsck_.result.valid_inode_count) {
      printf(" [Ok..] [0x%x]\n", fsck_.result.valid_inode_count);
    } else {
      printf(" [Fail] [0x%x]\n", fsck_.result.valid_inode_count);
      status = ZX_ERR_INTERNAL;
    }
  
    for (uint32_t segtype = 0; segtype < kNrCursegType; ++segtype) {
      std::cout << "[FSCK] next_blkoff in curseg (" << segtype << ") is free             ";
      if (VerifyCursegOffset(static_cast<CursegType>(segtype)) == ZX_OK) {
        std::cout << " [Ok..]\n";
      } else {
        std::cout << " [Fail]\n";
        status = ZX_ERR_INTERNAL;
      }
    }
  
    return status;
  }
  
  zx_status_t FsckWorker::RepairNat() {
    CursegInfo *curseg = segment_manager_->CURSEG_I(CursegType::kCursegHotData);
    SummaryBlock *summary_block = curseg->sum_blk;
  
    bool need_journal_update = false;
  
    for (nid_t nid = 0; nid < fsck_.nr_nat_entries; ++nid) {
      if (TestValidBitmap(nid, fsck_.nat_area_bitmap.get()) != 0) {
        std::cout << "Removing unreachable node [0x" << std::hex << nid << "]\n";
  
        // Lookup the journal first.
        bool found = false;
        for (int i = 0; i < NatsInCursum(summary_block); ++i) {
          if (LeToCpu(NidInJournal(summary_block, i)) == nid) {
            // If found, bring in the last entry.
            summary_block->nat_j.entries[i].nid =
                summary_block->nat_j.entries[LeToCpu(summary_block->n_nats) - 1].nid;
            summary_block->n_nats =
                CpuToLe(static_cast<uint16_t>(LeToCpu(summary_block->n_nats) - 1));
  
            need_journal_update = true;
            found = true;
            break;
          }
        }
        if (found) {
          continue;
        }
  
        // If not found, go for the NAT.
        block_t block_off = nid / kNatEntryPerBlock;
        int entry_off = nid % kNatEntryPerBlock;
        block_t seg_off = block_off >> superblock_info_.GetLogBlocksPerSeg();
        block_t block_addr = (node_manager_->GetNatAddress() +
                              (seg_off << superblock_info_.GetLogBlocksPerSeg() << 1) +
                              (block_off & ((1 << superblock_info_.GetLogBlocksPerSeg()) - 1)));
  
        if (TestValidBitmap(block_off, node_manager_->GetNatBitmap())) {
          block_addr += superblock_info_.GetBlocksPerSeg();
        }
  
        auto fs_block = std::make_unique<FsBlock>();
        ZX_ASSERT(ReadBlock(*fs_block, block_addr) == ZX_OK);
  #ifdef __Fuchsia__
        NatBlock *nat_block = reinterpret_cast<NatBlock *>(fs_block->GetData().data());
  #else   // __Fuchsia__
        NatBlock *nat_block = reinterpret_cast<NatBlock *>(fs_block->GetData());
  #endif  // __Fuchsia__
  
        nat_block->entries[entry_off] = RawNatEntry{};
        if (auto status = WriteBlock(*fs_block, block_addr); status != ZX_OK) {
          return status;
        }
      }
    }
  
    if (need_journal_update) {
      if (IsSetCkptFlags(&superblock_info_.GetCheckpoint(), kCpCompactSumFlag)) {
        block_t summary_addr = StartSummaryBlock();
        auto fs_block = std::make_unique<FsBlock>();
        ReadBlock(*fs_block, summary_addr);
  #ifdef __Fuchsia__
        memcpy(fs_block->GetData().data(), &summary_block->n_nats, kSumJournalSize);
  #else   // __Fuchsia__
        memcpy(fs_block->GetData(), &summary_block->n_nats, kSumJournalSize);
  #endif  // __Fuchsia__
        return WriteBlock(*fs_block, summary_addr);
      } else {
        if (IsSetCkptFlags(&superblock_info_.GetCheckpoint(), kCpUmountFlag)) {
          return WriteBlock(
              *reinterpret_cast<FsBlock *>(summary_block),
              SummaryBlockAddress(kNrCursegType, static_cast<int>(CursegType::kCursegHotData)));
        } else {
          return WriteBlock(
              *reinterpret_cast<FsBlock *>(summary_block),
              SummaryBlockAddress(kNrCursegDataType, static_cast<int>(CursegType::kCursegHotData)));
        }
      }
    }
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::RepairSit() {
    SitInfo &sit_i = segment_manager_->GetSitInfo();
    CursegInfo *curseg = segment_manager_->CURSEG_I(CursegType::kCursegColdData);
    SummaryBlock *summary_block = curseg->sum_blk;
  
    bool need_journal_update = false;
    for (uint32_t segno = 0; segno < sit_area_bitmap_size_ / kSitVBlockMapSize; ++segno) {
      uint32_t sit_byte_offset = segno * kSitVBlockMapSize;
      if (memcmp(sit_area_bitmap_.get() + sit_byte_offset,
                 fsck_.main_area_bitmap.get() + sit_byte_offset,
                 std::min(kSitVBlockMapSize, sit_area_bitmap_size_ - sit_byte_offset)) == 0x0) {
        continue;
      }
  
      // Lookup the journal first.
      bool found = false;
      for (int i = 0; i < SitsInCursum(summary_block); ++i) {
        if (LeToCpu(SegnoInJournal(summary_block, i)) == segno) {
          SitEntry &sit = summary_block->sit_j.entries[i].se;
          memcpy(sit.valid_map, fsck_.main_area_bitmap.get() + sit_byte_offset, kSitVBlockMapSize);
          sit.vblocks = 0;
          for (uint64_t j = 0; j < kSitVBlockMapSize; ++j) {
            sit.vblocks += std::bitset<8>(sit.valid_map[j]).count();
          }
  
          need_journal_update = true;
          found = true;
          break;
        }
      }
      if (found) {
        continue;
      }
  
      // If not found in journal, go for the Sit.
      std::unique_ptr<FsBlock> sit_block = GetCurrentSitPage(segno);
      uint32_t offset = segment_manager_->SitBlockOffset(segno);
      block_t sit_block_addr = sit_i.sit_base_addr + offset;
  
      if (TestValidBitmap(offset, sit_i.sit_bitmap.get())) {
        sit_block_addr += sit_i.sit_blocks;
      }
  
      SitEntry &sit_entry = reinterpret_cast<SitBlock *>(sit_block.get())
                                ->entries[segment_manager_->SitEntryOffset(segno)];
      memcpy(sit_entry.valid_map, fsck_.main_area_bitmap.get() + sit_byte_offset, kSitVBlockMapSize);
      sit_entry.vblocks = 0;
      for (uint64_t j = 0; j < kSitVBlockMapSize; ++j) {
        sit_entry.vblocks += std::bitset<8>(sit_entry.valid_map[j]).count();
      }
  
      if (auto status = WriteBlock(*sit_block.get(), sit_block_addr); status != ZX_OK) {
        return status;
      }
    }
  
    if (need_journal_update) {
      // Write the summary.
      if (IsSetCkptFlags(&superblock_info_.GetCheckpoint(), kCpCompactSumFlag)) {
        block_t summary_addr = StartSummaryBlock();
        auto fs_block = std::make_unique<FsBlock>();
        ReadBlock(*fs_block, summary_addr);
  #ifdef __Fuchsia__
        memcpy(fs_block->GetData().data() + kSumJournalSize, &summary_block->n_sits, kSumJournalSize);
  #else   // __Fuchsia__
        memcpy(fs_block->GetData() + kSumJournalSize, &summary_block->n_sits, kSumJournalSize);
  #endif  // __Fuchsia__
        return WriteBlock(*fs_block, summary_addr);
      } else {
        if (IsSetCkptFlags(&superblock_info_.GetCheckpoint(), kCpUmountFlag)) {
          return WriteBlock(
              *reinterpret_cast<FsBlock *>(summary_block),
              SummaryBlockAddress(kNrCursegType, static_cast<int>(CursegType::kCursegColdData)));
        } else {
          return WriteBlock(
              *reinterpret_cast<FsBlock *>(summary_block),
              SummaryBlockAddress(kNrCursegDataType, static_cast<int>(CursegType::kCursegColdData)));
        }
      }
    }
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::RepairCheckpoint() {
    bool need_update_checkpoint = false;
    if (superblock_info_.GetTotalValidBlockCount() != fsck_.result.valid_block_count) {
      superblock_info_.GetCheckpoint().valid_block_count = fsck_.result.valid_block_count;
      need_update_checkpoint = true;
    }
  
    if (superblock_info_.GetTotalValidNodeCount() != fsck_.result.valid_node_count) {
      superblock_info_.GetCheckpoint().valid_node_count = fsck_.result.valid_node_count;
      need_update_checkpoint = true;
    }
  
    if (superblock_info_.GetTotalValidInodeCount() != fsck_.result.valid_inode_count) {
      superblock_info_.GetCheckpoint().valid_inode_count = fsck_.result.valid_inode_count;
      need_update_checkpoint = true;
    }
  
    for (uint32_t segtype = 0; segtype < kNrCursegType; ++segtype) {
      CursegInfo *curseg = segment_manager_->CURSEG_I(static_cast<CursegType>(segtype));
      if (VerifyCursegOffset(static_cast<CursegType>(segtype)) != ZX_OK) {
        uint16_t offset;
        for (offset = 0; offset < kSitVBlockMapSize * kBitsPerByte; ++offset) {
          block_t logical_offset = segment_manager_->GetMainAreaStartBlock() +
                                   curseg->segno * superblock_info_.GetBlocksPerSeg() + offset;
          if (TestValidBitmap(BlkoffFromMain(*segment_manager_, logical_offset),
                              sit_area_bitmap_.get()) == 0x0) {
            break;
          }
        }
  
        if (segtype < static_cast<uint32_t>(CursegType::kCursegHotNode)) {
          superblock_info_.GetCheckpoint().cur_data_blkoff[segtype] = offset;
        } else {
          superblock_info_.GetCheckpoint()
              .cur_node_blkoff[segtype - static_cast<uint32_t>(CursegType::kCursegHotNode)] = offset;
        }
        superblock_info_.GetCheckpoint().alloc_type[segtype] = static_cast<uint8_t>(AllocMode::kSSR);
        need_update_checkpoint = true;
      }
    }
  
    if (need_update_checkpoint) {
      FsBlock checkpoint_block;
  #ifdef __Fuchsia__
      Checkpoint *checkpoint = reinterpret_cast<Checkpoint *>(checkpoint_block.GetData().data());
  #else   // __Fuchsia__
      Checkpoint *checkpoint = reinterpret_cast<Checkpoint *>(checkpoint_block.GetData());
  #endif  // __Fuchsia__
  
      memcpy(&checkpoint_block, &superblock_info_.GetCheckpoint(), superblock_info_.GetBlocksize());
  
      uint32_t crc = F2fsCalCrc32(kF2fsSuperMagic, checkpoint, LeToCpu(checkpoint->checksum_offset));
      *(reinterpret_cast<uint32_t *>(reinterpret_cast<uint8_t *>(checkpoint) +
                                     LeToCpu(checkpoint->checksum_offset))) = crc;
  
      if (auto status = WriteBlock(checkpoint_block, superblock_info_.StartCpAddr());
          status != ZX_OK) {
        return status;
      }
      if (auto status = WriteBlock(checkpoint_block, superblock_info_.StartCpAddr() +
                                                         checkpoint->cp_pack_total_block_count - 1);
          status != ZX_OK) {
        return status;
      }
    }
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::RepairInodeLinks() {
    for (auto const [nid, links] : fsck_.inode_link_map) {
      auto status = ReadNodeBlock(nid);
      if (status.is_error()) {
        return ZX_ERR_INTERNAL;
      }
  
      auto [fs_block, node_info] = std::move(*status);
  #ifdef __Fuchsia__
      auto node_block = reinterpret_cast<Node *>(fs_block->GetData().data());
  #else   // __Fuchsia__
      auto node_block = reinterpret_cast<Node *>(fs_block->GetData());
  #endif  // __Fuchsia__
  
      node_block->i.i_links = CpuToLe(links.actual_links);
      if (WriteBlock(*fs_block.get(), node_info.blk_addr) != ZX_OK) {
        return ZX_ERR_INTERNAL;
      }
    }
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::Repair() {
    if (auto ret = RepairNat(); ret != ZX_OK) {
      return ret;
    }
    if (auto ret = RepairSit(); ret != ZX_OK) {
      return ret;
    }
    if (auto ret = RepairCheckpoint(); ret != ZX_OK) {
      return ret;
    }
    if (auto ret = RepairInodeLinks(); ret != ZX_OK) {
      return ret;
    }
    return ZX_OK;
  }
  
  void FsckWorker::PrintInodeInfo(Inode &inode) {
    int namelen = LeToCpu(inode.i_namelen);
  
    DisplayMember(sizeof(uint32_t), inode.i_mode, "i_mode");
    DisplayMember(sizeof(uint32_t), inode.i_uid, "i_uid");
    DisplayMember(sizeof(uint32_t), inode.i_gid, "i_gid");
    DisplayMember(sizeof(uint32_t), inode.i_links, "i_links");
    DisplayMember(sizeof(uint64_t), inode.i_size, "i_size");
    DisplayMember(sizeof(uint64_t), inode.i_blocks, "i_blocks");
  
    DisplayMember(sizeof(uint64_t), inode.i_atime, "i_atime");
    DisplayMember(sizeof(uint32_t), inode.i_atime_nsec, "i_atime_nsec");
    DisplayMember(sizeof(uint64_t), inode.i_ctime, "i_ctime");
    DisplayMember(sizeof(uint32_t), inode.i_ctime_nsec, "i_ctime_nsec");
    DisplayMember(sizeof(uint64_t), inode.i_mtime, "i_mtime");
    DisplayMember(sizeof(uint32_t), inode.i_mtime_nsec, "i_mtime_nsec");
  
    DisplayMember(sizeof(uint32_t), inode.i_generation, "i_generation");
    DisplayMember(sizeof(uint32_t), inode.i_current_depth, "i_current_depth");
    DisplayMember(sizeof(uint32_t), inode.i_xattr_nid, "i_xattr_nid");
    DisplayMember(sizeof(uint32_t), inode.i_flags, "i_flags");
    DisplayMember(sizeof(uint32_t), inode.i_pino, "i_pino");
  
    if (namelen) {
      DisplayMember(sizeof(uint32_t), inode.i_namelen, "i_namelen");
      inode.i_name[namelen] = '\0';
      DisplayMember(sizeof(char), inode.i_name, "i_name");
    }
  
    printf("i_ext: fofs:%x blkaddr:%x len:%x\n", inode.i_ext.fofs, inode.i_ext.blk_addr,
           inode.i_ext.len);
  
    DisplayMember(sizeof(uint32_t), inode.i_addr[0], "i_addr[0]");  // Pointers to data blocks
    DisplayMember(sizeof(uint32_t), inode.i_addr[1], "i_addr[1]");  // Pointers to data blocks
    DisplayMember(sizeof(uint32_t), inode.i_addr[2], "i_addr[2]");  // Pointers to data blocks
    DisplayMember(sizeof(uint32_t), inode.i_addr[3], "i_addr[3]");  // Pointers to data blocks
  
    for (uint32_t i = 4; i < AddrsPerInode(&inode); ++i) {
      if (inode.i_addr[i] != 0x0) {
        printf("i_addr[0x%x] points data block\r\t\t\t\t[0x%4x]\n", i, inode.i_addr[i]);
        break;
      }
    }
  
    DisplayMember(sizeof(uint32_t), inode.i_nid[0], "i_nid[0]");  // direct
    DisplayMember(sizeof(uint32_t), inode.i_nid[1], "i_nid[1]");  // direct
    DisplayMember(sizeof(uint32_t), inode.i_nid[2], "i_nid[2]");  // indirect
    DisplayMember(sizeof(uint32_t), inode.i_nid[3], "i_nid[3]");  // indirect
    DisplayMember(sizeof(uint32_t), inode.i_nid[4], "i_nid[4]");  // double indirect
  
    printf("\n");
  }
  
  void FsckWorker::PrintNodeInfo(Node &node_block) {
    nid_t ino = LeToCpu(node_block.footer.ino);
    nid_t nid = LeToCpu(node_block.footer.nid);
    if (ino == nid) {
      FX_LOGS(INFO) << "Node ID [0x" << std::hex << nid << ":" << nid << "] is inode";
      PrintInodeInfo(node_block.i);
    } else {
      uint32_t *dump_blk = (uint32_t *)&node_block;
      FX_LOGS(INFO) << "Node ID [0x" << std::hex << nid << ":" << nid
                    << "] is direct node or indirect node";
      for (int i = 0; i <= 10; ++i) {  // MSG (0)
        printf("[%d]\t\t\t[0x%8x : %d]\n", i, dump_blk[i], dump_blk[i]);
      }
    }
  }
  
  void FsckWorker::PrintRawSuperblockInfo() {
    const Superblock &sb = superblock_info_.GetRawSuperblock();
  #if 0  // porting needed
    if (!config.dbg_lv)
      return;
  #endif
  
    printf("\n");
    printf("+--------------------------------------------------------+\n");
    printf("| Super block                                            |\n");
    printf("+--------------------------------------------------------+\n");
  
    DisplayMember(sizeof(uint32_t), sb.magic, "magic");
    DisplayMember(sizeof(uint32_t), sb.major_ver, "major_ver");
    DisplayMember(sizeof(uint32_t), sb.minor_ver, "minor_ver");
    DisplayMember(sizeof(uint32_t), sb.log_sectorsize, "log_sectorsize");
    DisplayMember(sizeof(uint32_t), sb.log_sectors_per_block, "log_sectors_per_block");
  
    DisplayMember(sizeof(uint32_t), sb.log_blocksize, "log_blocksize");
    DisplayMember(sizeof(uint32_t), sb.log_blocks_per_seg, "log_blocks_per_seg");
    DisplayMember(sizeof(uint32_t), sb.segs_per_sec, "segs_per_sec");
    DisplayMember(sizeof(uint32_t), sb.secs_per_zone, "secs_per_zone");
    DisplayMember(sizeof(uint32_t), sb.checksum_offset, "checksum_offset");
    DisplayMember(sizeof(uint64_t), sb.block_count, "block_count");
  
    DisplayMember(sizeof(uint32_t), sb.section_count, "section_count");
    DisplayMember(sizeof(uint32_t), sb.segment_count, "segment_count");
    DisplayMember(sizeof(uint32_t), sb.segment_count_ckpt, "segment_count_ckpt");
    DisplayMember(sizeof(uint32_t), sb.segment_count_sit, "segment_count_sit");
    DisplayMember(sizeof(uint32_t), sb.segment_count_nat, "segment_count_nat");
  
    DisplayMember(sizeof(uint32_t), sb.segment_count_ssa, "segment_count_ssa");
    DisplayMember(sizeof(uint32_t), sb.segment_count_main, "segment_count_main");
    DisplayMember(sizeof(uint32_t), sb.segment0_blkaddr, "segment0_blkaddr");
  
    DisplayMember(sizeof(uint32_t), sb.cp_blkaddr, "cp_blkaddr");
    DisplayMember(sizeof(uint32_t), sb.sit_blkaddr, "sit_blkaddr");
    DisplayMember(sizeof(uint32_t), sb.nat_blkaddr, "nat_blkaddr");
    DisplayMember(sizeof(uint32_t), sb.ssa_blkaddr, "ssa_blkaddr");
    DisplayMember(sizeof(uint32_t), sb.main_blkaddr, "main_blkaddr");
  
    DisplayMember(sizeof(uint32_t), sb.root_ino, "root_ino");
    DisplayMember(sizeof(uint32_t), sb.node_ino, "node_ino");
    DisplayMember(sizeof(uint32_t), sb.meta_ino, "meta_ino");
    DisplayMember(sizeof(uint32_t), sb.cp_payload, "cp_payload");
    printf("\n");
  }
  
  void FsckWorker::PrintCheckpointInfo() {
    Checkpoint &cp = superblock_info_.GetCheckpoint();
    uint32_t alloc_type;
  #if 0  // porting needed
    if (!config.dbg_lv)
      return;
  #endif
  
    printf("\n");
    printf("+--------------------------------------------------------+\n");
    printf("| Checkpoint                                             |\n");
    printf("+--------------------------------------------------------+\n");
  
    DisplayMember(sizeof(uint64_t), cp.checkpoint_ver, "checkpoint_ver");
    DisplayMember(sizeof(uint64_t), cp.user_block_count, "user_block_count");
    DisplayMember(sizeof(uint64_t), cp.valid_block_count, "valid_block_count");
    DisplayMember(sizeof(uint32_t), cp.rsvd_segment_count, "rsvd_segment_count");
    DisplayMember(sizeof(uint32_t), cp.overprov_segment_count, "overprov_segment_count");
    DisplayMember(sizeof(uint32_t), cp.free_segment_count, "free_segment_count");
  
    alloc_type = cp.alloc_type[static_cast<int>(CursegType::kCursegHotNode)];
    DisplayMember(sizeof(uint32_t), alloc_type, "alloc_type[CursegType::kCursegHotNode]");
    alloc_type = cp.alloc_type[static_cast<int>(CursegType::kCursegWarmNode)];
    DisplayMember(sizeof(uint32_t), alloc_type, "alloc_type[CursegType::kCursegWarmNode]");
    alloc_type = cp.alloc_type[static_cast<int>(CursegType::kCursegColdNode)];
    DisplayMember(sizeof(uint32_t), alloc_type, "alloc_type[CursegType::kCursegColdNode]");
    alloc_type = cp.alloc_type[static_cast<int>(CursegType::kCursegHotNode)];
    DisplayMember(sizeof(uint32_t), cp.cur_node_segno[0], "cur_node_segno[0]");
    DisplayMember(sizeof(uint32_t), cp.cur_node_segno[1], "cur_node_segno[1]");
    DisplayMember(sizeof(uint32_t), cp.cur_node_segno[2], "cur_node_segno[2]");
  
    DisplayMember(sizeof(uint32_t), cp.cur_node_blkoff[0], "cur_node_blkoff[0]");
    DisplayMember(sizeof(uint32_t), cp.cur_node_blkoff[1], "cur_node_blkoff[1]");
    DisplayMember(sizeof(uint32_t), cp.cur_node_blkoff[2], "cur_node_blkoff[2]");
  
    alloc_type = cp.alloc_type[static_cast<int>(CursegType::kCursegHotData)];
    DisplayMember(sizeof(uint32_t), alloc_type, "alloc_type[CursegType::kCursegHotData]");
    alloc_type = cp.alloc_type[static_cast<int>(CursegType::kCursegWarmData)];
    DisplayMember(sizeof(uint32_t), alloc_type, "alloc_type[CursegType::kCursegWarmData]");
    alloc_type = cp.alloc_type[static_cast<int>(CursegType::kCursegColdData)];
    DisplayMember(sizeof(uint32_t), alloc_type, "alloc_type[CursegType::kCursegColdData]");
    DisplayMember(sizeof(uint32_t), cp.cur_data_segno[0], "cur_data_segno[0]");
    DisplayMember(sizeof(uint32_t), cp.cur_data_segno[1], "cur_data_segno[1]");
    DisplayMember(sizeof(uint32_t), cp.cur_data_segno[2], "cur_data_segno[2]");
  
    DisplayMember(sizeof(uint32_t), cp.cur_data_blkoff[0], "cur_data_blkoff[0]");
    DisplayMember(sizeof(uint32_t), cp.cur_data_blkoff[1], "cur_data_blkoff[1]");
    DisplayMember(sizeof(uint32_t), cp.cur_data_blkoff[2], "cur_data_blkoff[2]");
  
    DisplayMember(sizeof(uint32_t), cp.ckpt_flags, "ckpt_flags");
    DisplayMember(sizeof(uint32_t), cp.cp_pack_total_block_count, "cp_pack_total_block_count");
    DisplayMember(sizeof(uint32_t), cp.cp_pack_start_sum, "cp_pack_start_sum");
    DisplayMember(sizeof(uint32_t), cp.valid_node_count, "valid_node_count");
    DisplayMember(sizeof(uint32_t), cp.valid_inode_count, "valid_inode_count");
    DisplayMember(sizeof(uint32_t), cp.next_free_nid, "next_free_nid");
    DisplayMember(sizeof(uint32_t), cp.sit_ver_bitmap_bytesize, "sit_ver_bitmap_bytesize");
    DisplayMember(sizeof(uint32_t), cp.nat_ver_bitmap_bytesize, "nat_ver_bitmap_bytesize");
    DisplayMember(sizeof(uint32_t), cp.checksum_offset, "checksum_offset");
    DisplayMember(sizeof(uint64_t), cp.elapsed_time, "elapsed_time");
  
    printf("\n\n");
  }
  
  zx_status_t FsckWorker::SanityCheckRawSuper(const Superblock *raw_super) {
    if (kF2fsSuperMagic != LeToCpu(raw_super->magic)) {
      return ZX_ERR_INTERNAL;
    }
    if (kBlockSize != kPageCacheSize) {
      return ZX_ERR_INTERNAL;
    }
    block_t blocksize = 1 << LeToCpu(raw_super->log_blocksize);
    if (kBlockSize != blocksize) {
      return ZX_ERR_INTERNAL;
    }
    if (LeToCpu(raw_super->log_sectorsize) > kMaxLogSectorSize ||
        LeToCpu(raw_super->log_sectorsize) < kMinLogSectorSize) {
      return ZX_ERR_INTERNAL;
    }
    if (LeToCpu(raw_super->log_sectors_per_block) + LeToCpu(raw_super->log_sectorsize) !=
        kMaxLogSectorSize) {
      return ZX_ERR_INTERNAL;
    }
    return ZX_OK;
  }
  
  zx::status<std::unique_ptr<FsBlock>> FsckWorker::GetSuperblock(block_t index) {
    if (index >= kSuperblockCopies) {
      return zx::error(ZX_ERR_OUT_OF_RANGE);
    }
    auto fs_blk = std::make_unique<FsBlock>();
    if (auto status = ReadBlock(*fs_blk.get(), kSuperblockStart + index) != ZX_OK; status != ZX_OK) {
      return zx::error(status);
    }
    return zx::ok(std::move(fs_blk));
  }
  
  zx_status_t FsckWorker::GetValidSuperblock() {
    for (block_t i = 0; i < kSuperblockCopies; ++i) {
      if (auto status = GetSuperblock(i); status.is_ok()) {
  #ifdef __Fuchsia__
        auto sb_ptr = reinterpret_cast<Superblock *>(status->GetData().data() + kSuperOffset);
  #else   // __Fuchsia__
        auto sb_ptr = reinterpret_cast<Superblock *>(status->GetData() + kSuperOffset);
  #endif  // __Fuchsia__
        if (auto sanity = SanityCheckRawSuper(sb_ptr); sanity == ZX_OK) {
          auto sb = std::make_shared<Superblock>(*sb_ptr);
          superblock_info_.SetRawSuperblock(sb);
  
          InitSuperblockInfo();
          return ZX_OK;
        }
      }
      FX_LOGS(WARNING) << "Can't find a valid F2FS superblock in block [" << i << "]";
    }
    return ZX_ERR_NOT_FOUND;
  }
  
  void FsckWorker::InitSuperblockInfo() {
    const Superblock &raw_super = superblock_info_.GetRawSuperblock();
  
    superblock_info_.SetLogSectorsPerBlock(LeToCpu(raw_super.log_sectors_per_block));
    superblock_info_.SetLogBlocksize(LeToCpu(raw_super.log_blocksize));
    superblock_info_.SetBlocksize(1 << superblock_info_.GetLogBlocksize());
    superblock_info_.SetLogBlocksPerSeg(LeToCpu(raw_super.log_blocks_per_seg));
    superblock_info_.SetBlocksPerSeg(1 << superblock_info_.GetLogBlocksPerSeg());
    superblock_info_.SetSegsPerSec(LeToCpu(raw_super.segs_per_sec));
    superblock_info_.SetSecsPerZone(LeToCpu(raw_super.secs_per_zone));
    superblock_info_.SetTotalSections(LeToCpu(raw_super.section_count));
    superblock_info_.SetTotalNodeCount((LeToCpu(raw_super.segment_count_nat) / 2) *
                                       superblock_info_.GetBlocksPerSeg() * kNatEntryPerBlock);
    superblock_info_.SetRootIno(LeToCpu(raw_super.root_ino));
    superblock_info_.SetNodeIno(LeToCpu(raw_super.node_ino));
    superblock_info_.SetMetaIno(LeToCpu(raw_super.meta_ino));
  #if 0  // porting needed
    superblock_info_.cur_victim_sec = kNullSegNo;
  #endif
  }
  
  zx::status<std::pair<std::unique_ptr<FsBlock>, uint64_t>> FsckWorker::ValidateCheckpoint(
      block_t cp_addr) {
    auto cp_page_1 = std::make_unique<FsBlock>();
    auto cp_page_2 = std::make_unique<FsBlock>();
    Checkpoint *cp_block;
    block_t blk_size = superblock_info_.GetBlocksize();
    uint64_t cur_version = 0, pre_version = 0;
    uint32_t crc = 0;
    uint32_t crc_offset;
  
    // Read the 1st cp block in this CP pack
    if (ReadBlock(*cp_page_1.get(), cp_addr) != ZX_OK) {
      return zx::error(ZX_ERR_INTERNAL);
    }
  
    cp_block = (Checkpoint *)cp_page_1.get();
    crc_offset = LeToCpu(cp_block->checksum_offset);
    if (crc_offset >= blk_size) {
      return zx::error(ZX_ERR_INTERNAL);
    }
  
    crc = *(uint32_t *)((uint8_t *)cp_block + crc_offset);
    if (!F2fsCrcValid(crc, cp_block, crc_offset)) {
      return zx::error(ZX_ERR_INTERNAL);
    }
  
    pre_version = LeToCpu(cp_block->checkpoint_ver);
  
    // Read the 2nd cp block in this CP pack
    cp_addr += LeToCpu(cp_block->cp_pack_total_block_count) - 1;
    if (ReadBlock(*cp_page_2.get(), cp_addr) != ZX_OK) {
      return zx::error(ZX_ERR_INTERNAL);
    }
  
    cp_block = (Checkpoint *)cp_page_2.get();
    crc_offset = LeToCpu(cp_block->checksum_offset);
    if (crc_offset >= blk_size) {
      return zx::error(ZX_ERR_INTERNAL);
    }
  
    crc = *(uint32_t *)((uint8_t *)cp_block + crc_offset);
    if (!F2fsCrcValid(crc, cp_block, crc_offset)) {
      return zx::error(ZX_ERR_INTERNAL);
    }
  
    cur_version = LeToCpu(cp_block->checkpoint_ver);
  
    if (cur_version == pre_version) {
      return zx::ok(std::pair<std::unique_ptr<FsBlock>, uint64_t>{std::move(cp_page_1), cur_version});
    }
    return zx::error(ZX_ERR_INTERNAL);
  }
  
  zx_status_t FsckWorker::GetValidCheckpoint() {
    const Superblock &raw_sb = superblock_info_.GetRawSuperblock();
    zx::status<std::pair<std::unique_ptr<FsBlock>, uint64_t>> current = zx::error(ZX_ERR_NOT_FOUND);
    block_t cp_start_blk_no = 0;
  
    for (auto checkpoint_start :
         {LeToCpu(raw_sb.cp_blkaddr),
          LeToCpu(raw_sb.cp_blkaddr) + (1 << LeToCpu(raw_sb.log_blocks_per_seg))}) {
      auto status = ValidateCheckpoint(checkpoint_start);
      if (status.is_error()) {
        continue;
      }
  
      if (current.is_error() || VerAfter(status->second, current->second)) {
        current = std::move(status);
        cp_start_blk_no = checkpoint_start;
      }
    }
  
    if (current.is_error()) {
      return current.error_value();
    }
  
    block_t blk_size = superblock_info_.GetBlocksize();
    memcpy(&superblock_info_.GetCheckpoint(), current->first.get(), blk_size);
  
    std::vector<FsBlock> checkpoint_trailer(raw_sb.cp_payload);
    for (uint32_t i = 0; i < raw_sb.cp_payload; ++i) {
      ReadBlock(checkpoint_trailer[i], cp_start_blk_no + 1 + i);
    }
    superblock_info_.SetCheckpointTrailer(std::move(checkpoint_trailer));
  
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::SanityCheckCkpt() {
    uint32_t total, fsmeta;
    const Superblock &raw_super = superblock_info_.GetRawSuperblock();
    Checkpoint &ckpt = superblock_info_.GetCheckpoint();
  
    total = LeToCpu(raw_super.segment_count);
    fsmeta = LeToCpu(raw_super.segment_count_ckpt);
    fsmeta += LeToCpu(raw_super.segment_count_sit);
    fsmeta += LeToCpu(raw_super.segment_count_nat);
    fsmeta += LeToCpu(ckpt.rsvd_segment_count);
    fsmeta += LeToCpu(raw_super.segment_count_ssa);
  
    if (fsmeta >= total) {
      return ZX_ERR_INVALID_ARGS;
    }
  
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::InitNodeManager() {
    const Superblock &sb_raw = superblock_info_.GetRawSuperblock();
    uint32_t nat_segs, nat_blocks;
  
    node_manager_->SetNatAddress(LeToCpu(sb_raw.nat_blkaddr));
  
    // segment_count_nat includes pair segment so divide to 2.
    nat_segs = LeToCpu(sb_raw.segment_count_nat) >> 1;
    nat_blocks = nat_segs << LeToCpu(sb_raw.log_blocks_per_seg);
    node_manager_->SetMaxNid(kNatEntryPerBlock * nat_blocks);
    node_manager_->SetFirstScanNid(LeToCpu(superblock_info_.GetCheckpoint().next_free_nid));
    node_manager_->SetNextScanNid(LeToCpu(superblock_info_.GetCheckpoint().next_free_nid));
    if (zx_status_t status =
            node_manager_->AllocNatBitmap(superblock_info_.BitmapSize(MetaBitmap::kNatBitmap));
        status != ZX_OK) {
      return ZX_ERR_NO_MEMORY;
    }
  
    // copy version bitmap
    node_manager_->SetNatBitmap(
        static_cast<uint8_t *>(superblock_info_.BitmapPtr(MetaBitmap::kNatBitmap)));
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::BuildNodeManager() {
    if (node_manager_ = std::make_unique<NodeManager>(&superblock_info_); node_manager_ == nullptr) {
      return ZX_ERR_NO_MEMORY;
    }
  
    if (zx_status_t err = InitNodeManager(); err != ZX_OK) {
      return err;
    }
  
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::BuildSitInfo() {
    const Superblock &raw_sb = superblock_info_.GetRawSuperblock();
    Checkpoint &ckpt = superblock_info_.GetCheckpoint();
    std::unique_ptr<SitInfo> sit_i;
    uint32_t sit_segs;
    uint8_t *src_bitmap;
    uint32_t bitmap_size;
  
    if (sit_i = std::make_unique<SitInfo>(); sit_i == nullptr) {
      return ZX_ERR_NO_MEMORY;
    }
  
    sit_i->sentries = new SegmentEntry[segment_manager_->TotalSegs()]();
  
    for (uint32_t start = 0; start < segment_manager_->TotalSegs(); ++start) {
      sit_i->sentries[start].cur_valid_map = std::make_unique<uint8_t[]>(kSitVBlockMapSize);
      sit_i->sentries[start].ckpt_valid_map = std::make_unique<uint8_t[]>(kSitVBlockMapSize);
      if (sit_i->sentries[start].cur_valid_map == nullptr ||
          sit_i->sentries[start].ckpt_valid_map == nullptr) {
        return ZX_ERR_NO_MEMORY;
      }
    }
  
    sit_segs = LeToCpu(raw_sb.segment_count_sit) >> 1;
    bitmap_size = superblock_info_.BitmapSize(MetaBitmap::kSitBitmap);
    if (src_bitmap = static_cast<uint8_t *>(superblock_info_.BitmapPtr(MetaBitmap::kSitBitmap));
        src_bitmap == nullptr) {
      return ZX_ERR_NO_MEMORY;
    }
  
    if (sit_i->sit_bitmap = std::make_unique<uint8_t[]>(bitmap_size); sit_i->sit_bitmap == nullptr) {
      return ZX_ERR_NO_MEMORY;
    }
  
    memcpy(sit_i->sit_bitmap.get(), src_bitmap, bitmap_size);
  
    sit_i->sit_base_addr = LeToCpu(raw_sb.sit_blkaddr);
    sit_i->sit_blocks = sit_segs << superblock_info_.GetLogBlocksPerSeg();
    sit_i->written_valid_blocks = LeToCpu(safemath::checked_cast<uint32_t>(ckpt.valid_block_count));
    sit_i->bitmap_size = bitmap_size;
    sit_i->dirty_sentries = 0;
    sit_i->sents_per_block = kSitEntryPerBlock;
    sit_i->elapsed_time = LeToCpu(ckpt.elapsed_time);
  
    segment_manager_->SetSitInfo(std::move(sit_i));
    return ZX_OK;
  }
  
  void FsckWorker::ResetCurseg(CursegType type, int modified) {
    CursegInfo *curseg = segment_manager_->CURSEG_I(type);
  
    curseg->segno = curseg->next_segno;
    curseg->zone = segment_manager_->GetZoneNoFromSegNo(curseg->segno);
    curseg->next_blkoff = 0;
    curseg->next_segno = kNullSegNo;
  }
  
  zx_status_t FsckWorker::ReadCompactedSummaries() {
    Checkpoint &ckpt = superblock_info_.GetCheckpoint();
    block_t start;
    auto fs_block = std::make_unique<FsBlock>();
    uint32_t offset;
    CursegInfo *curseg;
  
    start = StartSummaryBlock();
  
    ReadBlock(*fs_block, start++);
  
    curseg = segment_manager_->CURSEG_I(CursegType::kCursegHotData);
  #ifdef __Fuchsia__
    memcpy(&curseg->sum_blk->n_nats, fs_block->GetData().data(), kSumJournalSize);
  #else   // __Fuchsia__
    memcpy(&curseg->sum_blk->n_nats, fs_block->GetData(), kSumJournalSize);
  #endif  // __Fuchsia__
  
    curseg = segment_manager_->CURSEG_I(CursegType::kCursegColdData);
  #ifdef __Fuchsia__
    memcpy(&curseg->sum_blk->n_sits, fs_block->GetData().data() + kSumJournalSize, kSumJournalSize);
  #else   // __Fuchsia__
    memcpy(&curseg->sum_blk->n_sits, fs_block->GetData() + kSumJournalSize, kSumJournalSize);
  #endif  // __Fuchsia__
  
    offset = 2 * kSumJournalSize;
    for (int32_t i = static_cast<int32_t>(CursegType::kCursegHotData);
         i <= CursegType::kCursegColdData; ++i) {
      unsigned short blk_off;
      uint32_t segno;
  
      curseg = segment_manager_->CURSEG_I(static_cast<CursegType>(i));
      segno = LeToCpu(ckpt.cur_data_segno[i]);
      blk_off = LeToCpu(ckpt.cur_data_blkoff[i]);
      curseg->next_segno = segno;
      ResetCurseg(static_cast<CursegType>(i), 0);
      curseg->alloc_type = ckpt.alloc_type[i];
      curseg->next_blkoff = blk_off;
  
      if (curseg->alloc_type == static_cast<uint8_t>(AllocMode::kSSR)) {
        blk_off = safemath::checked_cast<unsigned short>(superblock_info_.GetBlocksPerSeg());
      }
  
      for (uint32_t j = 0; j < blk_off; ++j) {
        Summary *s;
  #ifdef __Fuchsia__
        s = (Summary *)(fs_block->GetData().data() + offset);
  #else   // __Fuchsia__
        s = (Summary *)(fs_block->GetData() + offset);
  #endif  // __Fuchsia__
        curseg->sum_blk->entries[j] = *s;
        offset += kSummarySize;
        if (offset + kSummarySize <= kPageCacheSize - kSumFooterSize) {
          continue;
        }
  #ifdef __Fuchsia__
        memset(fs_block->GetData().data(), 0, kPageSize);
  #else   // __Fuchsia__
        memset(fs_block->GetData(), 0, kPageSize);
  #endif  // __Fuchsia__
        ReadBlock(*fs_block, start++);
        offset = 0;
      }
    }
  
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::RestoreNodeSummary(uint32_t segno, SummaryBlock &summary_block) {
    Node *node_block;
    block_t addr;
    auto fs_block = std::make_unique<FsBlock>();
  
    // scan the node segment
    addr = segment_manager_->StartBlock(segno);
    for (uint32_t i = 0; i < superblock_info_.GetBlocksPerSeg(); ++i, ++addr) {
      if (ReadBlock(*fs_block, addr)) {
        break;
      }
  #ifdef __Fuchsia__
      node_block = reinterpret_cast<Node *>(fs_block->GetData().data());
  #else   // __Fuchsia__
      node_block = reinterpret_cast<Node *>(fs_block->GetData());
  #endif  // __Fuchsia__
      summary_block.entries[i].nid = node_block->footer.nid;
    }
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::ReadNormalSummaries(CursegType type) {
    Checkpoint &ckpt = superblock_info_.GetCheckpoint();
    auto fs_block = std::make_unique<FsBlock>();
    SummaryBlock *summary_block;
    CursegInfo *curseg;
    unsigned short blk_off;
    uint32_t segno = 0;
    block_t block_address = 0;
  
    if (segment_manager_->IsDataSeg(type)) {
      segno = LeToCpu(ckpt.cur_data_segno[static_cast<int>(type)]);
      blk_off = LeToCpu(ckpt.cur_data_blkoff[type - CursegType::kCursegHotData]);
  
      if (IsSetCkptFlags(&ckpt, kCpUmountFlag)) {
        block_address = SummaryBlockAddress(kNrCursegType, static_cast<int>(type));
      } else {
        block_address = SummaryBlockAddress(kNrCursegDataType, static_cast<int>(type));
      }
    } else {
      segno = LeToCpu(ckpt.cur_node_segno[type - CursegType::kCursegHotNode]);
      blk_off = LeToCpu(ckpt.cur_node_blkoff[type - CursegType::kCursegHotNode]);
  
      if (IsSetCkptFlags(&ckpt, kCpUmountFlag)) {
        block_address = SummaryBlockAddress(kNrCursegNodeType, type - CursegType::kCursegHotNode);
      } else {
        block_address = segment_manager_->GetSumBlock(segno);
      }
    }
  
    ReadBlock(*fs_block, block_address);
  #ifdef __Fuchsia__
    summary_block = reinterpret_cast<SummaryBlock *>(fs_block->GetData().data());
  #else   // __Fuchsia__
    summary_block = reinterpret_cast<SummaryBlock *>(fs_block->GetData());
  #endif  // __Fuchsia__
  
    if (segment_manager_->IsNodeSeg(type)) {
      if (IsSetCkptFlags(&ckpt, kCpUmountFlag)) {
  #if 0  // do not change original value
        Summary *sum_entry = &sum_blk->entries[0];
        for (uint64_t i = 0; i < superblock_info->GetBlocksPerSeg(); ++i, ++sum_entry) {
  				sum_entry->version = 0;
  				sum_entry->ofs_in_node = 0;
        }
  #endif
      } else {
        if (zx_status_t ret = RestoreNodeSummary(segno, *summary_block); ret != ZX_OK) {
          return ret;
        }
      }
    }
  
    curseg = segment_manager_->CURSEG_I(type);
    memcpy(curseg->sum_blk, summary_block, kPageCacheSize);
    curseg->next_segno = segno;
    ResetCurseg(type, 0);
    curseg->alloc_type = ckpt.alloc_type[static_cast<int>(type)];
    curseg->next_blkoff = blk_off;
  
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::RestoreCursegSummaries() {
    int32_t type = static_cast<int32_t>(CursegType::kCursegHotData);
  
    if (IsSetCkptFlags(&superblock_info_.GetCheckpoint(), kCpCompactSumFlag)) {
      if (zx_status_t ret = ReadCompactedSummaries(); ret != ZX_OK) {
        return ret;
      }
      type = static_cast<int32_t>(CursegType::kCursegHotNode);
    }
  
    for (; type <= CursegType::kCursegColdNode; ++type) {
      if (zx_status_t ret = ReadNormalSummaries(static_cast<CursegType>(type)); ret != ZX_OK) {
        return ret;
      }
    }
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::BuildCurseg() {
    for (int i = 0; i < kNrCursegType; ++i) {
      CursegInfo *curseg = segment_manager_->CURSEG_I(static_cast<CursegType>(i));
      curseg->raw_blk = new FsBlock();
      curseg->segno = kNullSegNo;
      curseg->next_blkoff = 0;
    }
    return RestoreCursegSummaries();
  }
  
  inline void FsckWorker::CheckSegmentRange(uint32_t segno) {
    uint32_t end_segno = segment_manager_->GetSegmentsCount() - 1;
    ZX_ASSERT(segno <= end_segno);
  }
  
  std::unique_ptr<FsBlock> FsckWorker::GetCurrentSitPage(uint32_t segno) {
    SitInfo &sit_i = segment_manager_->GetSitInfo();
    uint32_t offset = segment_manager_->SitBlockOffset(segno);
    block_t block_address = sit_i.sit_base_addr + offset;
    auto sit_block = std::make_unique<FsBlock>();
  
    CheckSegmentRange(segno);
  
    // calculate sit block address
    if (TestValidBitmap(offset, sit_i.sit_bitmap.get())) {
      block_address += sit_i.sit_blocks;
    }
  
    ReadBlock(*sit_block.get(), block_address);
  
    return sit_block;
  }
  
  void FsckWorker::CheckBlockCount(uint32_t segno, const SitEntry &raw_sit) {
    uint32_t end_segno = segment_manager_->GetSegmentsCount() - 1;
    int valid_blocks = 0;
  
    // check segment usage
    ZX_ASSERT(GetSitVblocks(raw_sit) <= superblock_info_.GetBlocksPerSeg());
  
    // check boundary of a given segment number
    ZX_ASSERT(segno <= end_segno);
  
    // check bitmap with valid block count
    for (uint64_t i = 0; i < superblock_info_.GetBlocksPerSeg(); ++i) {
      if (TestValidBitmap(i, raw_sit.valid_map)) {
        ++valid_blocks;
      }
    }
    ZX_ASSERT(GetSitVblocks(raw_sit) == valid_blocks);
  }
  
  void FsckWorker::SegmentInfoFromRawSit(SegmentEntry &segment_entry, const SitEntry &raw_sit) {
    segment_entry.valid_blocks = GetSitVblocks(raw_sit);
    segment_entry.ckpt_valid_blocks = GetSitVblocks(raw_sit);
    memcpy(segment_entry.cur_valid_map.get(), raw_sit.valid_map, kSitVBlockMapSize);
    memcpy(segment_entry.ckpt_valid_map.get(), raw_sit.valid_map, kSitVBlockMapSize);
    segment_entry.type = GetSitType(raw_sit);
    segment_entry.mtime = LeToCpu(raw_sit.mtime);
  }
  
  SegmentEntry &FsckWorker::GetSegmentEntry(uint32_t segno) {
    SitInfo &sit_i = segment_manager_->GetSitInfo();
    return sit_i.sentries[segno];
  }
  
  std::pair<std::unique_ptr<FsBlock>, SegType> FsckWorker::GetSumBlockInfo(uint32_t segno) {
    auto summary_block = std::make_unique<FsBlock>();
    Checkpoint &ckpt = superblock_info_.GetCheckpoint();
    CursegInfo *curseg;
    block_t ssa_blk;
  
    ssa_blk = segment_manager_->GetSumBlock(segno);
    for (int type = 0; type < kNrCursegNodeType; ++type) {
      if (segno == ckpt.cur_node_segno[type]) {
        curseg = segment_manager_->CURSEG_I(CursegType::kCursegHotNode + type);
        memcpy(summary_block.get(), curseg->sum_blk, kBlockSize);
        return {std::move(summary_block),
                SegType::kSegTypeCurNode};  // current node seg was not stored
      }
    }
  
    for (int type = 0; type < kNrCursegDataType; ++type) {
      if (segno == ckpt.cur_data_segno[type]) {
        curseg = segment_manager_->CURSEG_I(CursegType::kCursegHotData + type);
  #ifdef __Fuchsia__
        memcpy(summary_block->GetData().data(), curseg->sum_blk, kBlockSize);
  #else   // __Fuchsia__
        memcpy(summary_block->GetData(), curseg->sum_blk, kBlockSize);
  #endif  // __Fuchsia__
        ZX_ASSERT(!IsSumNodeSeg((reinterpret_cast<SummaryBlock *>(summary_block.get()))->footer));
  #if 0  // TODO: implement debug level
        // TODO: DBG (2)
        printf("segno [0x%x] is current data seg[0x%x]\n", segno, type);
  #endif
  
        return {std::move(summary_block),
                SegType::kSegTypeCurData};  // current data seg was not stored
      }
    }
  
    ZX_ASSERT(ReadBlock(*summary_block.get(), ssa_blk) == ZX_OK);
  
    if (IsSumNodeSeg((reinterpret_cast<SummaryBlock *>(summary_block.get()))->footer)) {
      return {std::move(summary_block), SegType::kSegTypeNode};
    }
    return {std::move(summary_block), SegType::kSegTypeData};
  }
  
  uint32_t FsckWorker::GetSegmentNumber(uint32_t block_address) {
    return (uint32_t)(BlkoffFromMain(*segment_manager_, block_address) >>
                      superblock_info_.GetLogBlocksPerSeg());
  }
  
  std::pair<SegType, Summary> FsckWorker::GetSummaryEntry(uint32_t block_address) {
    uint32_t segno, offset;
    Summary summary_entry;
  
    segno = GetSegmentNumber(block_address);
    offset = OffsetInSegment(superblock_info_, *segment_manager_, block_address);
  
    auto [summary_block, type] = GetSumBlockInfo(segno);
  #ifdef __Fuchsia__
    memcpy(&summary_entry,
           &((reinterpret_cast<SummaryBlock *>(summary_block->GetData().data()))->entries[offset]),
           sizeof(Summary));
  #else   // __Fuchsia__
    memcpy(&summary_entry,
           &((reinterpret_cast<SummaryBlock *>(summary_block->GetData()))->entries[offset]),
           sizeof(Summary));
  #endif  // __Fuchsia__
    return {type, summary_entry};
  }
  
  zx::status<RawNatEntry> FsckWorker::GetNatEntry(nid_t nid) {
    block_t block_off;
    block_t block_addr;
    block_t seg_off;
    int entry_off;
  
    if ((nid / kNatEntryPerBlock) > fsck_.nr_nat_entries) {
      FX_LOGS(WARNING) << "nid is over max nid";
      return zx::error(ZX_ERR_INVALID_ARGS);
    }
  
    if (auto result = LookupNatInJournal(nid); result.is_ok()) {
      return result;
    }
  
    block_off = nid / kNatEntryPerBlock;
    entry_off = nid % kNatEntryPerBlock;
  
    seg_off = block_off >> superblock_info_.GetLogBlocksPerSeg();
    block_addr =
        (node_manager_->GetNatAddress() + (seg_off << superblock_info_.GetLogBlocksPerSeg() << 1) +
         (block_off & ((1 << superblock_info_.GetLogBlocksPerSeg()) - 1)));
  
    if (TestValidBitmap(block_off, node_manager_->GetNatBitmap())) {
      block_addr += superblock_info_.GetBlocksPerSeg();
    }
  
    auto fs_block = std::make_unique<FsBlock>();
    ZX_ASSERT(ReadBlock(*fs_block, block_addr) == ZX_OK);
  #ifdef __Fuchsia__
    NatBlock *nat_block = reinterpret_cast<NatBlock *>(fs_block->GetData().data());
  #else   // __Fuchsia__
    NatBlock *nat_block = reinterpret_cast<NatBlock *>(fs_block->GetData());
  #endif  // __Fuchsia__
  
    return zx::ok(nat_block->entries[entry_off]);
  }
  
  zx::status<NodeInfo> FsckWorker::GetNodeInfo(nid_t nid) {
    NodeInfo node_info;
    auto result = GetNatEntry(nid);
    if (result.is_error()) {
      return result.take_error();
    }
    RawNatEntry raw_nat = *result;
  
    node_info.nid = nid;
    NodeInfoFromRawNat(node_info, raw_nat);
    return zx::ok(node_info);
  }
  
  void FsckWorker::BuildSitEntries() {
    SitInfo &sit_i = segment_manager_->GetSitInfo();
    CursegInfo *curseg = segment_manager_->CURSEG_I(CursegType::kCursegColdData);
    SummaryBlock *sum = curseg->sum_blk;
  
    for (uint32_t segno = 0; segno < segment_manager_->TotalSegs(); ++segno) {
      SegmentEntry &segment_entry = sit_i.sentries[segno];
      SitEntry sit;
      bool found = false;
  
      for (int i = 0; i < SitsInCursum(sum); ++i) {
        if (LeToCpu(SegnoInJournal(sum, i)) == segno) {
          sit = sum->sit_j.entries[i].se;
          found = true;
          break;
        }
      }
      if (found == false) {
        std::unique_ptr<FsBlock> sit_block = GetCurrentSitPage(segno);
        sit = reinterpret_cast<SitBlock *>(sit_block.get())
                  ->entries[segment_manager_->SitEntryOffset(segno)];
      }
      CheckBlockCount(segno, sit);
      SegmentInfoFromRawSit(segment_entry, sit);
    }
  }
  
  zx_status_t FsckWorker::BuildSegmentManager() {
    Superblock &raw_super = superblock_info_.GetRawSuperblock();
    Checkpoint &ckpt = superblock_info_.GetCheckpoint();
  
    if (segment_manager_ = std::make_unique<SegmentManager>(&superblock_info_);
        segment_manager_ == nullptr) {
      return ZX_ERR_NO_MEMORY;
    }
  
    // init sm info
    segment_manager_->SetSegment0StartBlock(LeToCpu(raw_super.segment0_blkaddr));
    segment_manager_->SetMainAreaStartBlock(LeToCpu(raw_super.main_blkaddr));
    segment_manager_->SetSegmentsCount(LeToCpu(raw_super.segment_count));
    segment_manager_->SetReservedSegmentsCount(LeToCpu(ckpt.rsvd_segment_count));
    segment_manager_->SetOPSegmentsCount(LeToCpu(ckpt.overprov_segment_count));
    segment_manager_->SetMainSegmentsCount(LeToCpu(raw_super.segment_count_main));
    segment_manager_->SetSSAreaStartBlock(LeToCpu(raw_super.ssa_blkaddr));
  
    if (auto status = BuildSitInfo(); status != ZX_OK) {
      return status;
    }
    if (auto status = BuildCurseg(); status != ZX_OK) {
      return status;
    }
    BuildSitEntries();
    return ZX_OK;
  }
  
  void FsckWorker::BuildSitAreaBitmap() {
    uint32_t free_segs = 0;
    uint32_t vblocks = 0;
  
    sit_area_bitmap_size_ = segment_manager_->GetMainSegmentsCount() * kSitVBlockMapSize;
    sit_area_bitmap_ = std::make_unique<uint8_t[]>(sit_area_bitmap_size_);
    uint8_t *ptr = sit_area_bitmap_.get();
  
    for (uint32_t segno = 0; segno < segment_manager_->GetMainSegmentsCount(); ++segno) {
      SegmentEntry &segment_entry = GetSegmentEntry(segno);
  
      memcpy(ptr, segment_entry.cur_valid_map.get(), kSitVBlockMapSize);
      ptr += kSitVBlockMapSize;
      vblocks = 0;
      for (uint64_t j = 0; j < kSitVBlockMapSize; ++j) {
        vblocks += std::bitset<8>(segment_entry.cur_valid_map[j]).count();
      }
      ZX_ASSERT(vblocks == segment_entry.valid_blocks);
  
      if (segment_entry.valid_blocks == 0x0) {
        if (superblock_info_.GetCheckpoint().cur_node_segno[0] == segno ||
            superblock_info_.GetCheckpoint().cur_data_segno[0] == segno ||
            superblock_info_.GetCheckpoint().cur_node_segno[1] == segno ||
            superblock_info_.GetCheckpoint().cur_data_segno[1] == segno ||
            superblock_info_.GetCheckpoint().cur_node_segno[2] == segno ||
            superblock_info_.GetCheckpoint().cur_data_segno[2] == segno) {
          continue;
        } else {
          ++free_segs;
        }
      } else {
        ZX_ASSERT(segment_entry.valid_blocks <= 512);
      }
    }
  
  #if 0  // TODO: implement debug level
    // TODO: DBG (1)
    printf("Blocks [0x%x : %d] Free Segs [0x%x : %d]\n\n", sum_vblocks, sum_vblocks, free_segs,
           free_segs);
  #endif
  }
  
  zx::status<RawNatEntry> FsckWorker::LookupNatInJournal(nid_t nid) {
    RawNatEntry raw_nat;
    CursegInfo *curseg = segment_manager_->CURSEG_I(CursegType::kCursegHotData);
    SummaryBlock *sum = curseg->sum_blk;
  
    for (int i = 0; i < NatsInCursum(sum); ++i) {
      if (LeToCpu(NidInJournal(sum, i)) == nid) {
        RawNatEntry ret = NatInJournal(sum, i);
  #if 0  // TODO: implement debug level
        // TODO: DBG (3)
        printf("==> Found nid [0x%x] in nat cache\n", nid);
  #endif
        memcpy(&raw_nat, &ret, sizeof(RawNatEntry));
        return zx::ok(raw_nat);
      }
    }
    return zx::error(ZX_ERR_NOT_FOUND);
  }
  
  void FsckWorker::BuildNatAreaBitmap() {
    const Superblock &raw_sb = superblock_info_.GetRawSuperblock();
    nid_t nid, nr_nat_blks;
  
    block_t block_off;
    block_t block_addr;
    block_t seg_off;
  
    // Alloc & build nat entry bitmap
    nr_nat_blks = (LeToCpu(raw_sb.segment_count_nat) / 2) << superblock_info_.GetLogBlocksPerSeg();
  
    fsck_.nr_nat_entries = nr_nat_blks * kNatEntryPerBlock;
    fsck_.nat_area_bitmap_size = (fsck_.nr_nat_entries + 7) / 8;
    fsck_.nat_area_bitmap = std::make_unique<uint8_t[]>(fsck_.nat_area_bitmap_size);
    ZX_ASSERT(fsck_.nat_area_bitmap.get() != nullptr);
  
    for (block_off = 0; block_off < nr_nat_blks; ++block_off) {
      seg_off = block_off >> superblock_info_.GetLogBlocksPerSeg();
      block_addr = node_manager_->GetNatAddress() +
                   (seg_off << superblock_info_.GetLogBlocksPerSeg() << 1) +
                   (block_off & ((1 << superblock_info_.GetLogBlocksPerSeg()) - 1));
  
      if (TestValidBitmap(block_off, node_manager_->GetNatBitmap())) {
        block_addr += superblock_info_.GetBlocksPerSeg();
      }
  
      auto fs_block = std::make_unique<FsBlock>();
      ZX_ASSERT(ReadBlock(*fs_block, block_addr) == ZX_OK);
  #ifdef __Fuchsia__
      NatBlock *nat_block = reinterpret_cast<NatBlock *>(fs_block->GetData().data());
  #else   // __Fuchsia__
      NatBlock *nat_block = reinterpret_cast<NatBlock *>(fs_block->GetData());
  #endif  // __Fuchsia__
  
      nid = block_off * kNatEntryPerBlock;
      for (uint32_t i = 0; i < kNatEntryPerBlock; ++i) {
        NodeInfo node_info;
        node_info.nid = nid + i;
  
        if ((nid + i) == superblock_info_.GetNodeIno() ||
            (nid + i) == superblock_info_.GetMetaIno()) {
          ZX_ASSERT(nat_block->entries[i].block_addr != 0x0);
          continue;
        }
  
        if (auto result = LookupNatInJournal(nid + i); result.is_ok()) {
          RawNatEntry raw_nat = *result;
          NodeInfoFromRawNat(node_info, raw_nat);
          if (node_info.blk_addr != kNullAddr) {
            SetValidBitmap(nid + i, fsck_.nat_area_bitmap.get());
            ++fsck_.result.valid_nat_entry_count;
  #if 0  // TODO: implement debug level
         // TODO: DBG (3)
            printf("nid[0x%x] in nat cache\n", nid + i);
  #endif
          }
        } else {
          NodeInfoFromRawNat(node_info, nat_block->entries[i]);
          if (node_info.blk_addr != kNullAddr) {
            ZX_ASSERT(nid + i != 0x0);
  #if 0  // TODO: implement debug level
         // TODO: DBG (3)
            printf("nid[0x%8x] in nat entry [0x%16x] [0x%8x]\n", nid + i, ni.blk_addr, ni.ino);
  #endif
            SetValidBitmap(nid + i, fsck_.nat_area_bitmap.get());
            ++fsck_.result.valid_nat_entry_count;
          }
        }
      }
    }
  #if 0  // TODO: implement debug level
    // TODO: DBG (1)
    printf("valid nat entries (block_addr != 0x0) [0x%8x : %u]\n", fsck->chk.valid_nat_entry_cnt,
           fsck->chk.valid_nat_entry_cnt);
  #endif
  }
  
  zx_status_t FsckWorker::DoMount() {
    if (mounted_) {
      DoUmount();
    }
  
    superblock_info_.SetActiveLogs(kNrCursegType);
  
    if (auto status = GetValidSuperblock(); status != ZX_OK) {
      return status;
    }
  
    PrintRawSuperblockInfo();
  
    if (auto status = GetValidCheckpoint(); status != ZX_OK) {
      FX_LOGS(ERROR) << "Can't find valid checkpoint" << status;
      return status;
    }
    if (auto status = SanityCheckCkpt(); status != ZX_OK) {
      FX_LOGS(ERROR) << "Checkpoint is polluted" << status;
      return status;
    }
  
    PrintCheckpointInfo();
    superblock_info_.SetTotalValidNodeCount(
        LeToCpu(superblock_info_.GetCheckpoint().valid_node_count));
    superblock_info_.SetTotalValidInodeCount(
        LeToCpu(superblock_info_.GetCheckpoint().valid_inode_count));
    superblock_info_.SetUserBlockCount(
        LeToCpu(static_cast<block_t>(superblock_info_.GetCheckpoint().user_block_count)));
    superblock_info_.SetTotalValidBlockCount(
        LeToCpu(static_cast<block_t>(superblock_info_.GetCheckpoint().valid_block_count)));
    superblock_info_.SetLastValidBlockCount(superblock_info_.GetTotalValidBlockCount());
    superblock_info_.SetAllocValidBlockCount(0);
  
    if (auto status = BuildSegmentManager(); status != ZX_OK) {
      FX_LOGS(ERROR) << "build_segment_manager failed: " << status;
      return status;
    }
    if (auto status = BuildNodeManager(); status != ZX_OK) {
      FX_LOGS(ERROR) << "build_segment_manager failed: " << status;
      return status;
    }
  
    BuildSitAreaBitmap();
  
    mounted_ = true;
    return ZX_OK;
  }
  
  void FsckWorker::DoUmount() {
    if (!mounted_) {
      return;
    }
  
    SitInfo &sit_i = segment_manager_->GetSitInfo();
  
    node_manager_.reset();
    for (uint32_t i = 0; i < segment_manager_->TotalSegs(); ++i) {
      sit_i.sentries[i].cur_valid_map.reset();
      sit_i.sentries[i].ckpt_valid_map.reset();
    }
    delete[] sit_i.sentries;
    sit_i.sit_bitmap.reset();
  
    for (uint32_t i = 0; i < kNrCursegType; ++i) {
      CursegInfo *curseg = segment_manager_->CURSEG_I(static_cast<CursegType>(i));
      delete curseg->raw_blk;
    }
  
    segment_manager_.reset();
  
    mounted_ = false;
  }
  
  zx_status_t FsckWorker::DoFsck() {
    if (auto status = Init(); status != ZX_OK) {
      return status;
    }
  
    if (auto status = CheckOrphanNodes(); status != ZX_OK) {
      return status;
    }
    FX_LOGS(INFO) << "checking orphan node.. done";
  
    // Traverse all block recursively from root inode
    if (auto status = CheckNodeBlock(nullptr, superblock_info_.GetRootIno(), FileType::kFtDir,
                                     NodeType::kTypeInode);
        status.is_error()) {
      return status.error_value();
    }
    FX_LOGS(INFO) << "checking node blocks.. done";
  
    if (auto status = Verify(); status != ZX_OK) {
      if (fsck_options_.repair) {
        status = Repair();
      }
      return status;
    }
    FX_LOGS(INFO) << "verifying.. done";
    return ZX_OK;
  }
  
  zx_status_t FsckWorker::Run() {
    zx_status_t status = ZX_OK;
    if (status = DoMount(); status != ZX_OK) {
      return status;
    }
  
    status = DoFsck();
  #if 0  // porting needed
    // ret = DoDump(superblock_info);
  #endif
    FX_LOGS(INFO) << "Fsck.. done: " << status;
    return status;
  }
  
  zx_status_t Fsck(std::unique_ptr<Bcache> bc, const FsckOptions &options,
                   std::unique_ptr<Bcache> *out) {
    zx_status_t status;
    FsckWorker fsck(std::move(bc), options);
    status = fsck.Run();
    if (out != nullptr) {
      *out = fsck.Destroy();
    }
    return status;
  }
  
  }  // namespace f2fs
  