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 <getopt.h>
  
  #include <cmath>
  #include <codecvt>
  #include <iostream>
  
  #include <safemath/checked_math.h>
  
  #include "src/lib/uuid/uuid.h"
  #include "src/storage/f2fs/f2fs.h"
  
  namespace f2fs {
  
  void MkfsWorker::PrintCurrentOption() {
    std::cerr << "f2fs mkfs label = " << mkfs_options_.label << std::endl;
    std::cerr << "f2fs mkfs heap-based allocation = " << mkfs_options_.heap_based_allocation
              << std::endl;
    std::cerr << "f2fs mkfs overprovision ratio = " << mkfs_options_.overprovision_ratio << std::endl;
    std::cerr << "f2fs mkfs segments per sector = " << mkfs_options_.segs_per_sec << std::endl;
    std::cerr << "f2fs mkfs sectors per zone = " << mkfs_options_.secs_per_zone << std::endl;
    std::cerr << "f2fs mkfs extension list = " << mkfs_options_.extension_list << std::endl;
  }
  
  zx::status<std::unique_ptr<Bcache>> MkfsWorker::DoMkfs() {
    InitGlobalParameters();
  
    if (zx_status_t ret = GetDeviceInfo(); ret != ZX_OK)
      return zx::error(ret);
  
    if (zx_status_t ret = FormatDevice(); ret != ZX_OK)
      return zx::error(ret);
    return zx::ok(std::move(bc_));
  }
  
  // String must be less than 16 characters.
  void AsciiToUnicode(const std::string &in_string, std::u16string *out_string) {
    std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> cvt16;
  
    out_string->assign(cvt16.from_bytes(in_string));
  }
  
  void MkfsWorker::InitGlobalParameters() {
    static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__);
  
    params_.sector_size = kDefaultSectorSize;
    params_.sectors_per_blk = kDefaultSectorsPerBlock;
    params_.blks_per_seg = kDefaultBlocksPerSegment;
    params_.reserved_segments = 0;
    params_.overprovision = mkfs_options_.overprovision_ratio;
    params_.segs_per_sec = mkfs_options_.segs_per_sec;
    params_.secs_per_zone = mkfs_options_.secs_per_zone;
    params_.heap = (mkfs_options_.heap_based_allocation ? 1 : 0);
    if (mkfs_options_.label.length() != 0) {
      ZX_ASSERT(mkfs_options_.label.length() + 1 <= kVolumeLabelLength);
      memcpy(params_.vol_label, mkfs_options_.label.c_str(), mkfs_options_.label.length() + 1);
    } else {
      memset(params_.vol_label, 0, sizeof(params_.vol_label));
  
      params_.vol_label[0] = 'F';
      params_.vol_label[1] = '2';
      params_.vol_label[2] = 'F';
      params_.vol_label[3] = 'S';
      params_.vol_label[4] = '\0';
    }
    params_.device_name = nullptr;
  
    params_.extension_list = mkfs_options_.extension_list;
  }
  
  zx_status_t MkfsWorker::GetDeviceInfo() {
  #ifdef __Fuchsia__
    fuchsia_hardware_block_BlockInfo info;
  
    bc_->device()->BlockGetInfo(&info);
  
    params_.sector_size = info.block_size;
    params_.sectors_per_blk = kBlockSize / info.block_size;
    params_.total_sectors = info.block_count;
    params_.start_sector = kSuperblockStart;
  
    if (info.block_size < kDefaultSectorSize || info.block_size > kBlockSize) {
      std::cerr << "Error: Block size " << info.block_size << " is not supported" << std::endl;
      return ZX_ERR_INVALID_ARGS;
    }
  #else   // __Fuchsia__
    params_.sector_size = kDefaultSectorSize;
    params_.sectors_per_blk = kBlockSize / kDefaultSectorSize;
    params_.total_sectors = bc_->Maxblk() * kDefaultSectorSize / kBlockSize;
    params_.start_sector = kSuperblockStart;
  #endif  // __Fuchsia__
  
    return ZX_OK;
  }
  
  void MkfsWorker::ConfigureExtensionList() {
    super_block_.extension_count = 0;
    memset(super_block_.extension_list, 0, sizeof(super_block_.extension_list));
  
    int name_len;
    int i = 0;
  
    for (const char *ext : kMediaExtList) {
      name_len = static_cast<int>(strlen(ext));
      memcpy(super_block_.extension_list[i++], ext, name_len);
    }
    super_block_.extension_count = i;
  
    if (!params_.extension_list.length())
      return;
  
    // add user ext list
    char *ue = strtok(const_cast<char *>(params_.extension_list.c_str()), ",");
    while (ue != nullptr) {
      name_len = static_cast<int>(strlen(ue));
      memcpy(super_block_.extension_list[i++], ue, name_len);
      ue = strtok(nullptr, ",");
      if (i >= kMaxExtension)
        break;
    }
  
    super_block_.extension_count = i;
  }
  
  zx_status_t MkfsWorker::WriteToDisk(FsBlock &buf, block_t bno) {
  #ifdef __Fuchsia__
    return bc_->Writeblk(bno, buf.GetData().data());
  #else   // __Fuchsia__
    return bc_->Writeblk(bno, buf.GetData());
  #endif  // __Fuchsia__
  }
  
  zx::status<uint32_t> MkfsWorker::GetCalculatedOp(uint32_t user_op) {
    uint32_t max_op = 0;
    uint32_t max_user_segments = 0;
  
    if (user_op < 100 && user_op > 0)
      return zx::ok(user_op);
  
    for (uint32_t calc_op = 1; calc_op < 100; ++calc_op) {
      uint32_t reserved_segments =
          (2 * (100 / calc_op + 1) + kNrCursegType) * super_block_.segs_per_sec;
  
      if ((safemath::CheckSub(LeToCpu(super_block_.segment_count_main), 2).ValueOrDie()) <
          reserved_segments) {
        continue;
      }
      uint32_t user_segments =
          (super_block_.segment_count_main -
           (safemath::CheckSub(super_block_.segment_count_main, reserved_segments) * calc_op / 100) -
           reserved_segments)
              .ValueOrDie();
  
      if (user_segments > max_user_segments &&
          safemath::CheckSub(super_block_.segment_count_main, 2).ValueOrDie() >= reserved_segments) {
        max_user_segments = user_segments;
        max_op = calc_op;
      }
    }
  
    if (max_op == 0)
      return zx::error(ZX_ERR_INVALID_ARGS);
    return zx::ok(max_op);
  }
  
  zx_status_t MkfsWorker::PrepareSuperblock() {
    super_block_.magic = CpuToLe(uint32_t{kF2fsSuperMagic});
    super_block_.major_ver = CpuToLe(kMajorVersion);
    super_block_.minor_ver = CpuToLe(kMinorVersion);
  
    uint32_t log_sectorsize = static_cast<uint32_t>(log2(static_cast<double>(params_.sector_size)));
    uint32_t log_sectors_per_block =
        static_cast<uint32_t>(log2(static_cast<double>(params_.sectors_per_blk)));
    uint32_t log_blocksize = log_sectorsize + log_sectors_per_block;
    uint32_t log_blks_per_seg =
        static_cast<uint32_t>(log2(static_cast<double>(params_.blks_per_seg)));
  
    super_block_.log_sectorsize = CpuToLe(log_sectorsize);
  
    if (log_sectorsize < kMinLogSectorSize || log_sectorsize > kMaxLogSectorSize) {
      FX_LOGS(ERROR) << "Error: Failed to get the sector size: " << params_.sector_size << "!";
      return ZX_ERR_INVALID_ARGS;
    }
  
    super_block_.log_sectors_per_block = CpuToLe(log_sectors_per_block);
  
    if (log_sectors_per_block < 0 ||
        log_sectors_per_block > (kMaxLogSectorSize - kMinLogSectorSize)) {
      FX_LOGS(ERROR) << "Error: Failed to get sectors per block: " << params_.sectors_per_blk << "!";
      return ZX_ERR_INVALID_ARGS;
    }
  
    super_block_.log_blocksize = CpuToLe(log_blocksize);
    super_block_.log_blocks_per_seg = CpuToLe(log_blks_per_seg);
  
    if (log_blks_per_seg !=
        static_cast<uint32_t>(log2(static_cast<double>(kDefaultBlocksPerSegment)))) {
      FX_LOGS(ERROR) << "Error: Failed to get block per segment: " << params_.blks_per_seg << "!";
      return ZX_ERR_INVALID_ARGS;
    }
  
    super_block_.segs_per_sec = CpuToLe(params_.segs_per_sec);
    super_block_.secs_per_zone = CpuToLe(params_.secs_per_zone);
    uint64_t blk_size_bytes = 1 << log_blocksize;
    uint32_t segment_size_bytes = static_cast<uint32_t>(blk_size_bytes * params_.blks_per_seg);
    uint32_t zone_size_bytes = static_cast<uint32_t>(blk_size_bytes * params_.secs_per_zone *
                                                     params_.segs_per_sec * params_.blks_per_seg);
  
    super_block_.checksum_offset = 0;
  
    super_block_.block_count =
        CpuToLe((params_.total_sectors * params_.sector_size) / blk_size_bytes);
  
    uint64_t zone_align_start_offset =
        (params_.start_sector * params_.sector_size + 2 * kBlockSize + zone_size_bytes - 1) /
            zone_size_bytes * zone_size_bytes -
        params_.start_sector * params_.sector_size;
  
    if (params_.start_sector % params_.sectors_per_blk) {
      FX_LOGS(WARNING) << "WARN: Align start sector number in a unit of pages";
      FX_LOGS(WARNING) << "\ti.e., start sector: " << params_.start_sector
                       << ", ofs: " << params_.start_sector % params_.sectors_per_blk
                       << " (sectors per page: " << params_.sectors_per_blk << ")";
    }
  
    super_block_.segment_count = static_cast<uint32_t>(CpuToLe(
        (safemath::CheckSub(params_.total_sectors * params_.sector_size, zone_align_start_offset) /
         segment_size_bytes)
            .ValueOrDie()));
  
    super_block_.segment0_blkaddr =
        static_cast<uint32_t>(CpuToLe(zone_align_start_offset / blk_size_bytes));
    super_block_.cp_blkaddr = super_block_.segment0_blkaddr;
  
    super_block_.segment_count_ckpt = CpuToLe(kNumberOfCheckpointPack);
  
    super_block_.sit_blkaddr =
        CpuToLe(LeToCpu(super_block_.segment0_blkaddr) +
                (LeToCpu(super_block_.segment_count_ckpt) * (1 << log_blks_per_seg)));
  
    uint32_t blocks_for_sit =
        (safemath::CheckSub(LeToCpu(super_block_.segment_count) + kSitEntryPerBlock, 1) /
         kSitEntryPerBlock)
            .ValueOrDie();
  
    uint32_t sit_segments =
        (safemath::CheckSub(blocks_for_sit + params_.blks_per_seg, 1) / params_.blks_per_seg)
            .ValueOrDie();
  
    super_block_.segment_count_sit = CpuToLe(sit_segments * 2);
  
    super_block_.nat_blkaddr =
        CpuToLe(LeToCpu(super_block_.sit_blkaddr) +
                (LeToCpu(super_block_.segment_count_sit) * params_.blks_per_seg));
  
    uint32_t total_valid_blks_available =
        (safemath::CheckSub(
             LeToCpu(super_block_.segment_count),
             LeToCpu(super_block_.segment_count_ckpt) + LeToCpu(super_block_.segment_count_sit)) *
         params_.blks_per_seg)
            .ValueOrDie();
  
    uint32_t blocks_for_nat =
        (safemath::CheckSub(total_valid_blks_available + kNatEntryPerBlock, 1) / kNatEntryPerBlock)
            .ValueOrDie();
  
    super_block_.segment_count_nat = CpuToLe(safemath::checked_cast<uint32_t>(
        (safemath::CheckSub(blocks_for_nat + params_.blks_per_seg, 1) / params_.blks_per_seg)
            .ValueOrDie()));
  
    // The number of node segments should not be exceeded a "Threshold".
    // This number resizes NAT bitmap area in a CP page.
    // So the threshold is determined not to overflow one CP page
    uint32_t sit_bitmap_size =
        ((LeToCpu(super_block_.segment_count_sit) / 2) << log_blks_per_seg) / 8;
    uint32_t max_sit_bitmap_size = std::min(sit_bitmap_size, kMaxSitBitmapSize);
  
    uint32_t max_nat_bitmap_size;
    if (max_sit_bitmap_size >
        kChecksumOffset - sizeof(Checkpoint) + 1 + (kDefaultBlocksPerSegment / kBitsPerByte)) {
      max_nat_bitmap_size = kChecksumOffset - sizeof(Checkpoint) + 1;
      super_block_.cp_payload = (max_sit_bitmap_size + kBlockSize - 1) / kBlockSize;
    } else {
      max_nat_bitmap_size = kChecksumOffset - sizeof(Checkpoint) + 1 - max_sit_bitmap_size;
      super_block_.cp_payload = 0;
    }
  
    uint32_t max_nat_segments = (max_nat_bitmap_size * 8) >> log_blks_per_seg;
  
    if (LeToCpu(super_block_.segment_count_nat) > max_nat_segments)
      super_block_.segment_count_nat = CpuToLe(max_nat_segments);
  
    super_block_.segment_count_nat = CpuToLe(LeToCpu(super_block_.segment_count_nat) * 2);
  
    super_block_.ssa_blkaddr =
        CpuToLe(LeToCpu(super_block_.nat_blkaddr) +
                LeToCpu(super_block_.segment_count_nat) * params_.blks_per_seg);
  
    total_valid_blks_available =
        (LeToCpu(super_block_.segment_count) -
         (LeToCpu(super_block_.segment_count_ckpt) + LeToCpu(super_block_.segment_count_sit) +
          LeToCpu(super_block_.segment_count_nat))) *
        params_.blks_per_seg;
  
    uint32_t blocks_for_ssa = total_valid_blks_available / params_.blks_per_seg + 1;
  
    super_block_.segment_count_ssa = CpuToLe(safemath::checked_cast<uint32_t>(
        ((blocks_for_ssa + safemath::CheckSub(params_.blks_per_seg, 1)) / params_.blks_per_seg)
            .ValueOrDie()));
  
    uint64_t total_meta_segments =
        LeToCpu(super_block_.segment_count_ckpt) + LeToCpu(super_block_.segment_count_sit) +
        LeToCpu(super_block_.segment_count_nat) + LeToCpu(super_block_.segment_count_ssa);
  
    if (uint64_t diff =
            total_meta_segments % static_cast<uint64_t>(params_.segs_per_sec * params_.secs_per_zone);
        diff != 0) {
      super_block_.segment_count_ssa = static_cast<uint32_t>(CpuToLe(
          LeToCpu(super_block_.segment_count_ssa) +
          (params_.segs_per_sec * params_.secs_per_zone - safemath::checked_cast<uint32_t>(diff))));
    }
  
    super_block_.main_blkaddr =
        CpuToLe(LeToCpu(super_block_.ssa_blkaddr) +
                (LeToCpu(super_block_.segment_count_ssa) * params_.blks_per_seg));
  
    super_block_.segment_count_main = CpuToLe(safemath::checked_cast<uint32_t>(
        safemath::CheckSub(
            LeToCpu(super_block_.segment_count),
            (LeToCpu(super_block_.segment_count_ckpt)) + LeToCpu(super_block_.segment_count_sit) +
                LeToCpu(super_block_.segment_count_nat) + LeToCpu(super_block_.segment_count_ssa))
            .ValueOrDie()));
  
    super_block_.section_count =
        CpuToLe(LeToCpu(super_block_.segment_count_main) / params_.segs_per_sec);
  
    super_block_.segment_count_main =
        CpuToLe(LeToCpu(super_block_.section_count) * params_.segs_per_sec);
  
    auto op = GetCalculatedOp(params_.overprovision);
    if (op.is_error()) {
      FX_LOGS(WARNING) << "Error: Device size is not sufficient for F2FS volume";
      return ZX_ERR_NO_SPACE;
    }
    params_.overprovision = op.value();
  
    // The number of reserved_segments depends on the OP value. Assuming OP is 20%, 20% of a dirty
    // segment is invalid. That is, running GC on 5 dirty segments can obtain one free segment.
    // Therefore, the required reserved_segments can be obtained with 100 / OP.
    // If the data page is moved to another segment due to GC, the dnode that refers to it should be
    // modified. This requires twice the reserved_segments.
    // Current active segments have the next segment in advance, of which require 6 additional
    // segments.
    params_.reserved_segments =
        (2 * (100 / params_.overprovision + 1) + kNrCursegType) * params_.segs_per_sec;
  
    if ((safemath::CheckSub(LeToCpu(super_block_.segment_count_main), 2).ValueOrDie()) <
        params_.reserved_segments) {
      FX_LOGS(ERROR) << "Error: Device size is not sufficient for F2FS volume, more segment needed ="
                     << params_.reserved_segments - (LeToCpu(super_block_.segment_count_main) - 2);
      return ZX_ERR_NO_SPACE;
    }
  
    memcpy(super_block_.uuid, uuid::Uuid::Generate().bytes(), 16);
  
    std::string vol_label(reinterpret_cast<char const *>(params_.vol_label));
    std::u16string volume_name;
  
    AsciiToUnicode(vol_label, &volume_name);
  
    volume_name.copy(reinterpret_cast<char16_t *>(super_block_.volume_name), vol_label.size());
    super_block_.volume_name[vol_label.size()] = '\0';
  
    super_block_.node_ino = CpuToLe(1U);
    super_block_.meta_ino = CpuToLe(2U);
    super_block_.root_ino = CpuToLe(3U);
  
    uint32_t total_zones =
        ((safemath::CheckSub(LeToCpu(super_block_.segment_count_main), 1) / params_.segs_per_sec) /
         params_.secs_per_zone)
            .ValueOrDie();
    if (total_zones <= kNrCursegType) {
      FX_LOGS(ERROR) << "Error: " << total_zones << " zones: Need more zones by shrinking zone size";
      return ZX_ERR_NO_SPACE;
    }
  
    if (params_.heap) {
      params_.cur_seg[static_cast<int>(CursegType::kCursegHotNode)] =
          (total_zones - 1) * params_.segs_per_sec * params_.secs_per_zone +
          ((params_.secs_per_zone - 1) * params_.segs_per_sec);
      params_.cur_seg[static_cast<int>(CursegType::kCursegWarmNode)] =
          params_.cur_seg[static_cast<int>(CursegType::kCursegHotNode)] -
          params_.segs_per_sec * params_.secs_per_zone;
      params_.cur_seg[static_cast<int>(CursegType::kCursegColdNode)] =
          params_.cur_seg[static_cast<int>(CursegType::kCursegWarmNode)] -
          params_.segs_per_sec * params_.secs_per_zone;
      params_.cur_seg[static_cast<int>(CursegType::kCursegHotData)] =
          params_.cur_seg[static_cast<int>(CursegType::kCursegColdNode)] -
          params_.segs_per_sec * params_.secs_per_zone;
      params_.cur_seg[static_cast<int>(CursegType::kCursegColdData)] = 0;
      params_.cur_seg[static_cast<int>(CursegType::kCursegWarmData)] =
          params_.cur_seg[static_cast<int>(CursegType::kCursegColdData)] +
          params_.segs_per_sec * params_.secs_per_zone;
    } else {
      params_.cur_seg[static_cast<int>(CursegType::kCursegHotNode)] = 0;
      params_.cur_seg[static_cast<int>(CursegType::kCursegWarmNode)] =
          params_.cur_seg[static_cast<int>(CursegType::kCursegHotNode)] +
          params_.segs_per_sec * params_.secs_per_zone;
      params_.cur_seg[static_cast<int>(CursegType::kCursegColdNode)] =
          params_.cur_seg[static_cast<int>(CursegType::kCursegWarmNode)] +
          params_.segs_per_sec * params_.secs_per_zone;
      params_.cur_seg[static_cast<int>(CursegType::kCursegHotData)] =
          params_.cur_seg[static_cast<int>(CursegType::kCursegColdNode)] +
          params_.segs_per_sec * params_.secs_per_zone;
      params_.cur_seg[static_cast<int>(CursegType::kCursegColdData)] =
          params_.cur_seg[static_cast<int>(CursegType::kCursegHotData)] +
          params_.segs_per_sec * params_.secs_per_zone;
      params_.cur_seg[static_cast<int>(CursegType::kCursegWarmData)] =
          params_.cur_seg[static_cast<int>(CursegType::kCursegColdData)] +
          params_.segs_per_sec * params_.secs_per_zone;
    }
  
    ConfigureExtensionList();
  
    return ZX_OK;
  }
  
  zx_status_t MkfsWorker::InitSitArea() {
    FsBlock sit_block;
    uint32_t segment_count_sit_blocks = (1 << LeToCpu(super_block_.log_blocks_per_seg)) *
                                        (LeToCpu(super_block_.segment_count_sit) / 2);
  
    block_t sit_segment_block_num = LeToCpu(super_block_.sit_blkaddr);
  
    for (block_t index = 0; index < segment_count_sit_blocks; ++index) {
      if (zx_status_t ret = WriteToDisk(sit_block, sit_segment_block_num + index); ret != ZX_OK) {
        FX_LOGS(ERROR) << "Error: While zeroing out the sit area on disk!!!";
        return ret;
      }
    }
  
    return ZX_OK;
  }
  
  zx_status_t MkfsWorker::InitNatArea() {
    FsBlock nat_block;
    uint32_t segment_count_nat_blocks = (1 << LeToCpu(super_block_.log_blocks_per_seg)) *
                                        (LeToCpu(super_block_.segment_count_nat) / 2);
  
    block_t nat_segment_block_num = LeToCpu(super_block_.nat_blkaddr);
  
    for (block_t index = 0; index < segment_count_nat_blocks; ++index) {
      if (zx_status_t ret = WriteToDisk(nat_block, nat_segment_block_num + index); ret != ZX_OK) {
        FX_LOGS(ERROR) << "Error: While zeroing out the nat area on disk!!!";
        return ret;
      }
    }
  
    return ZX_OK;
  }
  
  zx_status_t MkfsWorker::WriteCheckPointPack() {
    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__
  
    if (!checkpoint) {
      FX_LOGS(ERROR) << "Error: Alloc Failed for Checkpoint!!!";
      return ZX_ERR_NO_MEMORY;
    }
  
    FsBlock summary_block;
  #ifdef __Fuchsia__
    SummaryBlock *summary = reinterpret_cast<SummaryBlock *>(summary_block.GetData().data());
  #else   // __Fuchsia__
    SummaryBlock *summary = reinterpret_cast<SummaryBlock *>(summary_block.GetData());
  #endif  // __Fuchsia__
  
    if (!summary) {
      FX_LOGS(ERROR) << "Error: Alloc Failed for summay_node!!!";
      return ZX_ERR_NO_MEMORY;
    }
  
    // 1. cp page 1 of checkpoint pack 1
    checkpoint->checkpoint_ver = 1;
    checkpoint->cur_node_segno[0] =
        CpuToLe(params_.cur_seg[static_cast<int>(CursegType::kCursegHotNode)]);
    checkpoint->cur_node_segno[1] =
        CpuToLe(params_.cur_seg[static_cast<int>(CursegType::kCursegWarmNode)]);
    checkpoint->cur_node_segno[2] =
        CpuToLe(params_.cur_seg[static_cast<int>(CursegType::kCursegColdNode)]);
    checkpoint->cur_data_segno[0] =
        CpuToLe(params_.cur_seg[static_cast<int>(CursegType::kCursegHotData)]);
    checkpoint->cur_data_segno[1] =
        CpuToLe(params_.cur_seg[static_cast<int>(CursegType::kCursegWarmData)]);
    checkpoint->cur_data_segno[2] =
        CpuToLe(params_.cur_seg[static_cast<int>(CursegType::kCursegColdData)]);
    for (int i = 3; i < kMaxActiveNodeLogs; ++i) {
      checkpoint->cur_node_segno[i] = 0xffffffff;
      checkpoint->cur_data_segno[i] = 0xffffffff;
    }
  
    checkpoint->cur_node_blkoff[0] = CpuToLe(uint16_t{1});
    checkpoint->cur_data_blkoff[0] = CpuToLe(uint16_t{1});
    checkpoint->valid_block_count = CpuToLe(2UL);
    checkpoint->rsvd_segment_count = CpuToLe(params_.reserved_segments);
    checkpoint->overprov_segment_count = CpuToLe(safemath::checked_cast<uint32_t>(
        (safemath::CheckSub(LeToCpu(super_block_.segment_count_main),
                            LeToCpu(checkpoint->rsvd_segment_count)) *
         params_.overprovision / 100)
            .ValueOrDie()));
    checkpoint->overprov_segment_count = CpuToLe(LeToCpu(checkpoint->overprov_segment_count) +
                                                 LeToCpu(checkpoint->rsvd_segment_count));
  
    // main segments - reserved segments - (node + data segments)
    checkpoint->free_segment_count = CpuToLe(safemath::checked_cast<uint32_t>(
        safemath::CheckSub(LeToCpu(super_block_.segment_count_main), kNrCursegType).ValueOrDie()));
  
    checkpoint->user_block_count = CpuToLe(safemath::checked_cast<uint64_t>(
        (safemath::CheckSub(LeToCpu(checkpoint->free_segment_count) + kNrCursegType,
                            LeToCpu(checkpoint->overprov_segment_count)) *
         params_.blks_per_seg)
            .ValueOrDie()));
  
    checkpoint->cp_pack_total_block_count = CpuToLe(8U + LeToCpu(super_block_.cp_payload));
    checkpoint->ckpt_flags |= kCpUmountFlag;
    checkpoint->cp_pack_start_sum = CpuToLe(1U + LeToCpu(super_block_.cp_payload));
    checkpoint->valid_node_count = CpuToLe(1U);
    checkpoint->valid_inode_count = CpuToLe(1U);
    checkpoint->next_free_nid = CpuToLe(LeToCpu(super_block_.root_ino) + 1);
  
    checkpoint->sit_ver_bitmap_bytesize = CpuToLe(
        ((LeToCpu(super_block_.segment_count_sit) / 2) << LeToCpu(super_block_.log_blocks_per_seg)) /
        8);
  
    checkpoint->nat_ver_bitmap_bytesize = CpuToLe(
        ((LeToCpu(super_block_.segment_count_nat) / 2) << LeToCpu(super_block_.log_blocks_per_seg)) /
        8);
  
    checkpoint->checksum_offset = CpuToLe(kChecksumOffset);
  
    uint32_t crc = F2fsCalCrc32(kF2fsSuperMagic, checkpoint, LeToCpu(checkpoint->checksum_offset));
    *(reinterpret_cast<uint32_t *>(reinterpret_cast<uint8_t *>(checkpoint) +
                                   LeToCpu(checkpoint->checksum_offset))) = crc;
  
    block_t cp_segment_block_num = LeToCpu(super_block_.segment0_blkaddr);
  
    if (zx_status_t ret = WriteToDisk(checkpoint_block, cp_segment_block_num); ret != ZX_OK) {
      FX_LOGS(ERROR) << "Error: While writing the ckp to disk!!!";
      return ret;
    }
  
    for (uint32_t i = 0; i < super_block_.cp_payload; ++i) {
      ++cp_segment_block_num;
      FsBlock zero_buffer;
      if (zx_status_t ret = WriteToDisk(zero_buffer, cp_segment_block_num); ret != ZX_OK) {
        FX_LOGS(ERROR) << "Error: While zeroing out the sit bitmap area on disk!!!";
        return ret;
      }
    }
  
    // 2. Prepare and write Segment summary for data blocks
    memset(summary, 0, sizeof(SummaryBlock));
    SetSumType((&summary->footer), kSumTypeData);
  
    summary->entries[0].nid = super_block_.root_ino;
    summary->entries[0].ofs_in_node = 0;
  
    ++cp_segment_block_num;
    if (zx_status_t ret = WriteToDisk(summary_block, cp_segment_block_num); ret != ZX_OK) {
      FX_LOGS(ERROR) << "Error: While writing the summary_block to disk!!!";
      return ret;
    }
  
    // 3. Fill segment summary for data block to zero.
    memset(summary, 0, sizeof(SummaryBlock));
    SetSumType((&summary->footer), kSumTypeData);
  
    ++cp_segment_block_num;
    if (zx_status_t ret = WriteToDisk(summary_block, cp_segment_block_num); ret != ZX_OK) {
      FX_LOGS(ERROR) << "Error: While writing the summary_block to disk!!!";
      return ret;
    }
  
    // 4. Fill segment summary for data block to zero.
    memset(summary, 0, sizeof(SummaryBlock));
    SetSumType((&summary->footer), kSumTypeData);
  
    // inode sit for root
    summary->n_sits = CpuToLe(uint16_t{6});
    summary->sit_j.entries[0].segno = checkpoint->cur_node_segno[0];
    summary->sit_j.entries[0].se.vblocks =
        CpuToLe(uint16_t{(static_cast<int>(CursegType::kCursegHotNode) << 10) | 1});
    SetValidBitmap(0, summary->sit_j.entries[0].se.valid_map);
    summary->sit_j.entries[1].segno = checkpoint->cur_node_segno[1];
    summary->sit_j.entries[1].se.vblocks =
        CpuToLe(uint16_t{(static_cast<int>(CursegType::kCursegWarmNode) << 10)});
    summary->sit_j.entries[2].segno = checkpoint->cur_node_segno[2];
    summary->sit_j.entries[2].se.vblocks =
        CpuToLe(uint16_t{(static_cast<int>(CursegType::kCursegColdNode) << 10)});
  
    // data sit for root
    summary->sit_j.entries[3].segno = checkpoint->cur_data_segno[0];
    summary->sit_j.entries[3].se.vblocks =
        CpuToLe(uint16_t{(static_cast<uint16_t>(CursegType::kCursegHotData) << 10) | 1});
    SetValidBitmap(0, summary->sit_j.entries[3].se.valid_map);
    summary->sit_j.entries[4].segno = checkpoint->cur_data_segno[1];
    summary->sit_j.entries[4].se.vblocks =
        CpuToLe(uint16_t{(static_cast<int>(CursegType::kCursegWarmData) << 10)});
    summary->sit_j.entries[5].segno = checkpoint->cur_data_segno[2];
    summary->sit_j.entries[5].se.vblocks =
        CpuToLe(uint16_t{(static_cast<int>(CursegType::kCursegColdData) << 10)});
  
    ++cp_segment_block_num;
    if (zx_status_t ret = WriteToDisk(summary_block, cp_segment_block_num); ret != ZX_OK) {
      FX_LOGS(ERROR) << "Error: While writing the summary_block to disk!!!";
      return ret;
    }
  
    // 5. Prepare and write Segment summary for node blocks
    memset(summary, 0, sizeof(SummaryBlock));
    SetSumType((&summary->footer), kSumTypeNode);
  
    summary->entries[0].nid = super_block_.root_ino;
    summary->entries[0].ofs_in_node = 0;
  
    ++cp_segment_block_num;
    if (zx_status_t ret = WriteToDisk(summary_block, cp_segment_block_num); ret != ZX_OK) {
      FX_LOGS(ERROR) << "Error: While writing the summary_block to disk!!!";
      return ret;
    }
  
    // 6. Fill segment summary for data block to zero.
    memset(summary, 0, sizeof(SummaryBlock));
    SetSumType((&summary->footer), kSumTypeNode);
  
    ++cp_segment_block_num;
    if (zx_status_t ret = WriteToDisk(summary_block, cp_segment_block_num); ret != ZX_OK) {
      FX_LOGS(ERROR) << "Error: While writing the summary_block to disk!!!";
      return ret;
    }
  
    // 7. Fill segment summary for data block to zero.
    memset(summary, 0, sizeof(SummaryBlock));
    SetSumType((&summary->footer), kSumTypeNode);
  
    ++cp_segment_block_num;
    if (zx_status_t ret = WriteToDisk(summary_block, cp_segment_block_num); ret != ZX_OK) {
      FX_LOGS(ERROR) << "Error: While writing the summary_block to disk!!!";
      return ret;
    }
  
    // 8. cp page2
    ++cp_segment_block_num;
    if (zx_status_t ret = WriteToDisk(checkpoint_block, cp_segment_block_num); ret != ZX_OK) {
      FX_LOGS(ERROR) << "Error: While writing the checkpoint to disk!!!";
      return ret;
    }
  
    // 9. cp pages of check point pack 2
    // Initiatialize other checkpoint pack with version zero
    checkpoint->checkpoint_ver = 0;
  
    crc = F2fsCalCrc32(kF2fsSuperMagic, checkpoint, LeToCpu(checkpoint->checksum_offset));
    *(reinterpret_cast<uint32_t *>(reinterpret_cast<uint8_t *>(checkpoint) +
                                   LeToCpu(checkpoint->checksum_offset))) = crc;
  
    cp_segment_block_num = (LeToCpu(super_block_.segment0_blkaddr) + params_.blks_per_seg);
    if (zx_status_t ret = WriteToDisk(checkpoint_block, cp_segment_block_num); ret != ZX_OK) {
      FX_LOGS(ERROR) << "Error: While writing the checkpoint to disk!!!";
      return ret;
    }
  
    for (uint32_t i = 0; i < super_block_.cp_payload; ++i) {
      ++cp_segment_block_num;
      FsBlock zero_buffer;
      if (zx_status_t ret = WriteToDisk(zero_buffer, cp_segment_block_num); ret != ZX_OK) {
        FX_LOGS(ERROR) << "Error: While zeroing out the sit bitmap area on disk!!!";
        return ret;
      }
    }
  
    cp_segment_block_num +=
        checkpoint->cp_pack_total_block_count - 1 - LeToCpu(super_block_.cp_payload);
    if (zx_status_t ret = WriteToDisk(checkpoint_block, cp_segment_block_num); ret != ZX_OK) {
      FX_LOGS(ERROR) << "Error: While writing the checkpoint to disk!!!";
      return ret;
    }
  
    return ZX_OK;
  }
  
  zx_status_t MkfsWorker::WriteSuperblock() {
    FsBlock super_block;
  #ifdef __Fuchsia__
    uint8_t *super_block_buff = reinterpret_cast<uint8_t *>(super_block.GetData().data());
  #else   // __Fuchsia__
    uint8_t *super_block_buff = reinterpret_cast<uint8_t *>(super_block.GetData());
  #endif  // __Fuchsia__
  
    memcpy(super_block_buff + kSuperOffset, &super_block_, sizeof(super_block_));
  
    for (block_t index = 0; index < 2; ++index) {
      if (zx_status_t ret = WriteToDisk(super_block, index); ret != ZX_OK) {
        FX_LOGS(ERROR) << "Error: While while writing supe_blk on disk!!! index : " << index;
        return ret;
      }
    }
  
    return ZX_OK;
  }
  
  zx_status_t MkfsWorker::WriteRootInode() {
    FsBlock raw_block;
  #ifdef __Fuchsia__
    Node *raw_node = reinterpret_cast<Node *>(raw_block.GetData().data());
  #else   // __Fuchsia__
    Node *raw_node = reinterpret_cast<Node *>(raw_block.GetData());
  #endif  // __Fuchsia__
    if (raw_node == nullptr) {
      FX_LOGS(ERROR) << "Error: Alloc Failed for raw_node!!!";
      return ZX_ERR_NO_MEMORY;
    }
  
    raw_node->footer.nid = super_block_.root_ino;
    raw_node->footer.ino = super_block_.root_ino;
    raw_node->footer.cp_ver = CpuToLe(1UL);
    raw_node->footer.next_blkaddr = CpuToLe(
        LeToCpu(super_block_.main_blkaddr) +
        params_.cur_seg[static_cast<int>(CursegType::kCursegHotNode)] * params_.blks_per_seg + 1);
  
    raw_node->i.i_mode = CpuToLe(uint16_t{0x41ed});
    raw_node->i.i_links = CpuToLe(2U);
    raw_node->i.i_uid = CpuToLe(getuid());
    raw_node->i.i_gid = CpuToLe(getgid());
  
    uint64_t blk_size_bytes = 1 << LeToCpu(super_block_.log_blocksize);
    raw_node->i.i_size = CpuToLe(1 * blk_size_bytes);  // dentry
    raw_node->i.i_blocks = CpuToLe(2UL);
  
    timespec cur_time;
    clock_gettime(CLOCK_REALTIME, &cur_time);
    raw_node->i.i_atime = static_cast<uint64_t>(cur_time.tv_sec);
    raw_node->i.i_atime_nsec = static_cast<uint32_t>(cur_time.tv_nsec);
    raw_node->i.i_ctime = static_cast<uint64_t>(cur_time.tv_sec);
    raw_node->i.i_ctime_nsec = static_cast<uint32_t>(cur_time.tv_nsec);
    raw_node->i.i_mtime = static_cast<uint64_t>(cur_time.tv_sec);
    raw_node->i.i_mtime_nsec = static_cast<uint32_t>(cur_time.tv_nsec);
    raw_node->i.i_generation = 0;
    raw_node->i.i_xattr_nid = 0;
    raw_node->i.i_flags = 0;
    raw_node->i.i_current_depth = CpuToLe(1U);
  
    uint64_t data_blk_nor =
        LeToCpu(super_block_.main_blkaddr) +
        params_.cur_seg[static_cast<int>(CursegType::kCursegHotData)] * params_.blks_per_seg;
    raw_node->i.i_addr[0] = static_cast<uint32_t>(CpuToLe(data_blk_nor));
  
    raw_node->i.i_ext.fofs = 0;
    raw_node->i.i_ext.blk_addr = static_cast<uint32_t>(CpuToLe(data_blk_nor));
    raw_node->i.i_ext.len = CpuToLe(1U);
  
    block_t node_segment_block_num = LeToCpu(super_block_.main_blkaddr);
    node_segment_block_num += safemath::checked_cast<uint64_t>(
        params_.cur_seg[static_cast<int>(CursegType::kCursegHotNode)] * params_.blks_per_seg);
  
    if (zx_status_t ret = WriteToDisk(raw_block, node_segment_block_num); ret != ZX_OK) {
      FX_LOGS(ERROR) << "Error: While writing the raw_node to disk!!!, size = " << sizeof(Node);
      return ret;
    }
  
    memset(raw_node, 0xff, sizeof(Node));
  
    if (zx_status_t ret = WriteToDisk(raw_block, node_segment_block_num + 1); ret != ZX_OK) {
      FX_LOGS(ERROR) << "Error: While writing the raw_node to disk!!!";
      return ret;
    }
  
    return ZX_OK;
  }
  
  zx_status_t MkfsWorker::UpdateNatRoot() {
    FsBlock raw_nat_block;
  #ifdef __Fuchsia__
    NatBlock *nat_block = reinterpret_cast<NatBlock *>(raw_nat_block.GetData().data());
  #else   // __Fuchsia__
    NatBlock *nat_block = reinterpret_cast<NatBlock *>(raw_nat_block.GetData());
  #endif  // __Fuchsia__
    if (nat_block == nullptr) {
      FX_LOGS(ERROR) << "Error: Alloc Failed for nat_blk!!!";
      return ZX_ERR_NO_MEMORY;
    }
  
    // update root
    nat_block->entries[super_block_.root_ino].block_addr =
        CpuToLe(LeToCpu(super_block_.main_blkaddr) +
                params_.cur_seg[static_cast<int>(CursegType::kCursegHotNode)] * params_.blks_per_seg);
    nat_block->entries[super_block_.root_ino].ino = super_block_.root_ino;
  
    // update node nat
    nat_block->entries[super_block_.node_ino].block_addr = CpuToLe(1U);
    nat_block->entries[super_block_.node_ino].ino = super_block_.node_ino;
  
    // update meta nat
    nat_block->entries[super_block_.meta_ino].block_addr = CpuToLe(1U);
    nat_block->entries[super_block_.meta_ino].ino = super_block_.meta_ino;
  
    block_t nat_segment_block_num = LeToCpu(super_block_.nat_blkaddr);
  
    if (zx_status_t ret = WriteToDisk(raw_nat_block, nat_segment_block_num); ret != ZX_OK) {
      FX_LOGS(ERROR) << "Error: While writing the nat_blk set0 to disk!!!";
      return ret;
    }
  
    return ZX_OK;
  }
  
  zx_status_t MkfsWorker::AddDefaultDentryRoot() {
    FsBlock raw_dent_block;
  #ifdef __Fuchsia__
    DentryBlock *dent_block = reinterpret_cast<DentryBlock *>(raw_dent_block.GetData().data());
  #else   // __Fuchsia__
    DentryBlock *dent_block = reinterpret_cast<DentryBlock *>(raw_dent_block.GetData());
  #endif  // __Fuchsia__
    if (dent_block == nullptr) {
      FX_LOGS(ERROR) << "Error: Alloc Failed for dent_blk!!!";
      return ZX_ERR_NO_MEMORY;
    }
  
    dent_block->dentry[0].hash_code = 0;
    dent_block->dentry[0].ino = super_block_.root_ino;
    dent_block->dentry[0].name_len = CpuToLe(uint16_t{1});
    dent_block->dentry[0].file_type = static_cast<uint8_t>(FileType::kFtDir);
    memcpy(dent_block->filename[0], ".", 1);
  
    dent_block->dentry[1].hash_code = 0;
    dent_block->dentry[1].ino = super_block_.root_ino;
    dent_block->dentry[1].name_len = CpuToLe(uint16_t{2});
    dent_block->dentry[1].file_type = static_cast<uint8_t>(FileType::kFtDir);
    memcpy(dent_block->filename[1], "..", 2);
  
    // bitmap for . and ..
    dent_block->dentry_bitmap[0] = (1 << 1) | (1 << 0);
    block_t data_block_num =
        LeToCpu(super_block_.main_blkaddr) +
        params_.cur_seg[static_cast<int>(CursegType::kCursegHotData)] * params_.blks_per_seg;
  
    if (zx_status_t ret = WriteToDisk(raw_dent_block, data_block_num); ret != ZX_OK) {
      FX_LOGS(ERROR) << "Error: While writing the dentry_blk to disk!!!";
      return ret;
    }
  
    return ZX_OK;
  }
  
  zx_status_t MkfsWorker::CreateRootDir() {
    zx_status_t err = ZX_OK;
    const char err_msg[] = "Error creating root directry: ";
    if (err = WriteRootInode(); err != ZX_OK) {
      FX_LOGS(ERROR) << err_msg << "Failed to write root inode" << err;
      return err;
    }
    if (err = UpdateNatRoot(); err != ZX_OK) {
      FX_LOGS(ERROR) << err_msg << "Failed to update NAT for root" << err;
      return err;
    }
    if (err = AddDefaultDentryRoot(); err != ZX_OK) {
      FX_LOGS(ERROR) << err_msg << "Failed to add default dentries for root" << err;
      return err;
    }
    return err;
  }
  
  zx_status_t MkfsWorker::TrimDevice() { return bc_->Trim(0, static_cast<block_t>(bc_->Maxblk())); }
  
  zx_status_t MkfsWorker::FormatDevice() {
    zx_status_t err = ZX_OK;
    const char err_msg[] = "Error formatting the device: ";
    if (err = PrepareSuperblock(); err != ZX_OK) {
      FX_LOGS(ERROR) << err_msg << "Failed to prepare superblock information";
      return err;
    }
  
    if (err = TrimDevice(); err != ZX_OK) {
      if (err == ZX_ERR_NOT_SUPPORTED) {
        FX_LOGS(INFO) << "This device doesn't support TRIM";
      } else {
        FX_LOGS(ERROR) << err_msg << "Failed to trim whole device" << err;
        return err;
      }
    }
  
    if (err = InitSitArea(); err != ZX_OK) {
      FX_LOGS(ERROR) << err_msg << "Failed to Initialise the SIT AREA" << err;
      return err;
    }
  
    if (err = InitNatArea(); err != ZX_OK) {
      FX_LOGS(ERROR) << err_msg << "Failed to Initialise the NAT AREA" << err;
      return err;
    }
  
    if (err = CreateRootDir(); err != ZX_OK) {
      FX_LOGS(ERROR) << err_msg << "Failed to create the root directory" << err;
      return err;
    }
  
    if (err = WriteCheckPointPack(); err != ZX_OK) {
      FX_LOGS(ERROR) << err_msg << "Failed to write the check point pack" << err;
      return err;
    }
  
    if (err = WriteSuperblock(); err != ZX_OK) {
      FX_LOGS(ERROR) << err_msg << "Failed to write the Super Block" << err;
      return err;
    }
  
    // Ensure that all cached data is flushed in the underlying block device
    bc_->Flush();
    return err;
  }
  
  void PrintUsage() {
    std::cerr << "Usage: mkfs -p \"[OPTIONS]\" devicepath f2fs" << std::endl;
    std::cerr << "[OPTIONS]" << std::endl;
    std::cerr << "  -l label" << std::endl;
    std::cerr << "  -a heap-based allocation [default: 1]" << std::endl;
    std::cerr << "  -o overprovision ratio [default: 5]" << std::endl;
    std::cerr << "  -s # of segments per section [default: 1]" << std::endl;
    std::cerr << "  -z # of sections per zone [default: 1]" << std::endl;
    std::cerr << "  -e [extension list] e.g. \"mp3,gif,mov\"" << std::endl;
    std::cerr << "e.g. mkfs -p \"-l hello -a 1 -o 5 -s 1 -z 1 -e mp3,gif\" devicepath f2fs"
              << std::endl;
  }
  
  zx_status_t ParseOptions(int argc, char **argv, MkfsOptions &options) {
    struct option opts[] = {
        {"label", required_argument, nullptr, 'l'},
        {"heap", required_argument, nullptr, 'a'},
        {"op", required_argument, nullptr, 'o'},
        {"seg_per_sec", required_argument, nullptr, 's'},
        {"sec_per_zone", required_argument, nullptr, 'z'},
        {"ext_list", required_argument, nullptr, 'e'},
        {nullptr, 0, nullptr, 0},
    };
  
    int opt_index = -1;
    int c = -1;
  
  #ifdef __Fuchsia__
    optreset = 1;
  #endif  // __Fuchsia__
    optind = 1;
  
    while ((c = getopt_long(argc, argv, "l:a:o:s:z:e:", opts, &opt_index)) >= 0) {
      switch (c) {
        case 'l':
          options.label = optarg;
          if (options.label.length() >= kVolumeLabelLength) {
            std::cerr << "ERROR: label length should be less than 16." << std::endl;
            return ZX_ERR_INVALID_ARGS;
          }
          break;
        case 'a':
          options.heap_based_allocation = (static_cast<uint32_t>(strtoul(optarg, nullptr, 0)) != 0);
          break;
        case 'o':
          options.overprovision_ratio = static_cast<uint32_t>(strtoul(optarg, nullptr, 0));
          if (options.overprovision_ratio == 0) {
            std::cerr << "ERROR: overprovision ratio should be larger than 0." << std::endl;
            return ZX_ERR_INVALID_ARGS;
          }
          break;
        case 's':
          options.segs_per_sec = static_cast<uint32_t>(strtoul(optarg, nullptr, 0));
          if (options.segs_per_sec == 0) {
            std::cerr << "ERROR: # of segments per section should be larger than 0." << std::endl;
            return ZX_ERR_INVALID_ARGS;
          }
          break;
        case 'z':
          options.secs_per_zone = static_cast<uint32_t>(strtoul(optarg, nullptr, 0));
          if (options.secs_per_zone == 0) {
            std::cerr << "ERROR: # of sections per zone should be larger than 0." << std::endl;
            return ZX_ERR_INVALID_ARGS;
          }
          break;
        case 'e':
          options.extension_list = optarg;
          break;
        default:
          PrintUsage();
          return ZX_ERR_INVALID_ARGS;
      };
    };
  
    return ZX_OK;
  }
  
  zx::status<std::unique_ptr<Bcache>> Mkfs(const MkfsOptions &options, std::unique_ptr<Bcache> bc) {
    MkfsWorker mkfs(std::move(bc), options);
    return mkfs.DoMkfs();
  }
  
  }  // namespace f2fs
  