| // 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 <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <block-client/cpp/fake-device.h> |
| #include <gtest/gtest.h> |
| |
| #include "third_party/f2fs/f2fs.h" |
| |
| namespace f2fs { |
| namespace { |
| |
| using block_client::FakeBlockDevice; |
| constexpr uint32_t kCheckpointVersionTest = 0; |
| constexpr uint32_t kCheckpointNatBitmapTest = 1; |
| |
| constexpr uint32_t kCheckpointPack0 = 0; |
| constexpr uint32_t kCheckpointPack1 = 1; |
| |
| constexpr uint32_t kCheckpointLoopCnt = 20; |
| constexpr uint8_t kRootDirNatBit = 0x80; |
| |
| void ReadCheckpoint(F2fs *fs, block_t cp_addr, Page **cp_out) { |
| Page *cp_page[2]; // cp_page[0]: header, cp_page[1]: footer |
| SbInfo &sbi = fs->GetSbInfo(); |
| uint64_t blk_size = sbi.blocksize; |
| Checkpoint *cp_block; |
| uint64_t version[2]; // version[0]: header, version[1]: footer |
| uint32_t crc; |
| size_t crc_offset; |
| |
| for (int i = 0; i < 2; i++) { |
| // Read checkpoint pack header/footer |
| cp_page[i] = fs->GetMetaPage(cp_addr); |
| ASSERT_NE(cp_page[i], nullptr); |
| // Check header CRC |
| cp_block = static_cast<Checkpoint *>(PageAddress(cp_page[i])); |
| ASSERT_NE(cp_block, nullptr); |
| crc_offset = LeToCpu(cp_block->checksum_offset); |
| ASSERT_LT(crc_offset, blk_size); |
| |
| crc = *reinterpret_cast<uint32_t *>(reinterpret_cast<uint8_t *>(cp_block) + crc_offset); |
| ASSERT_TRUE(F2fsCrcValid(crc, cp_block, crc_offset)); |
| |
| // Get the version number |
| version[i] = LeToCpu(cp_block->checkpoint_ver); |
| |
| // Read checkpoint pack footer |
| cp_addr += LeToCpu(cp_block->cp_pack_total_block_count) - 1; |
| } |
| |
| ASSERT_EQ(version[0], version[1]); |
| |
| F2fsPutPage(cp_page[1], 1); |
| |
| *cp_out = cp_page[0]; |
| } |
| |
| void GetLastCheckpoint(F2fs *fs, uint32_t expect_cp_position, bool after_mkfs, Page **cp_out) { |
| SuperBlock &fsb = fs->RawSb(); |
| Checkpoint *cp_block1 = nullptr, *cp_block2 = nullptr, *cur_cp_block = nullptr; |
| Page *cp_page1 = nullptr, *cp_page2 = nullptr, *cur_cp_page = nullptr; |
| block_t cp_addr; |
| uint32_t cp_position = 0; |
| |
| cp_addr = LeToCpu(fsb.cp_blkaddr); |
| ReadCheckpoint(fs, cp_addr, &cp_page1); |
| cp_block1 = static_cast<Checkpoint *>(PageAddress(cp_page1)); |
| |
| if (!after_mkfs) { |
| cp_addr += 1 << LeToCpu(fsb.log_blocks_per_seg); |
| ReadCheckpoint(fs, cp_addr, &cp_page2); |
| cp_block2 = static_cast<Checkpoint *>(PageAddress(cp_page2)); |
| } |
| |
| if (after_mkfs) { |
| cur_cp_page = cp_page1; |
| cur_cp_block = cp_block1; |
| cp_position = kCheckpointPack0; |
| } else if (cp_block1 && cp_block2) { |
| if (VerAfter(cp_block2->checkpoint_ver, cp_block1->checkpoint_ver)) { |
| cur_cp_page = cp_page2; |
| cur_cp_block = cp_block2; |
| cp_position = kCheckpointPack1; |
| ASSERT_EQ(cp_block1->checkpoint_ver, cp_block2->checkpoint_ver - 1); |
| } else { |
| cur_cp_page = cp_page1; |
| cur_cp_block = cp_block1; |
| cp_position = kCheckpointPack0; |
| ASSERT_EQ(cp_block2->checkpoint_ver, cp_block1->checkpoint_ver - 1); |
| } |
| } else { |
| ASSERT_EQ(0, 1); |
| } |
| |
| FX_LOGS(INFO) << "CP[" << cp_position << "] Version = " << cur_cp_block->checkpoint_ver; |
| ASSERT_EQ(cp_position, expect_cp_position); |
| |
| *cp_out = cur_cp_page; |
| |
| if (!after_mkfs) { |
| if (cp_position == kCheckpointPack0) { |
| F2fsPutPage(cp_page2, 1); |
| } else { |
| F2fsPutPage(cp_page1, 1); |
| } |
| } |
| } |
| |
| inline void *GetBitmapPrt(Checkpoint *ckpt, MetaBitmap flag) { |
| uint32_t offset = (flag == MetaBitmap::kNatBitmap) ? ckpt->sit_ver_bitmap_bytesize : 0; |
| return &ckpt->sit_nat_version_bitmap + offset; |
| } |
| |
| void CreateDirs(F2fs *fs, int dir_cnt, uint64_t version) { |
| fbl::RefPtr<VnodeF2fs> data_root; |
| ASSERT_EQ(VnodeF2fs::Vget(fs, fs->RawSb().root_ino, &data_root), ZX_OK); |
| Dir *root_dir = static_cast<Dir *>(data_root.get()); |
| std::string filename; |
| |
| for (int i = 0; i < dir_cnt; i++) { |
| fbl::RefPtr<fs::Vnode> vnode; |
| filename = "dir_" + std::to_string(version) + "_" + std::to_string(i); |
| ASSERT_EQ(root_dir->Create(filename.c_str(), S_IFDIR, &vnode), ZX_OK); |
| vnode.reset(); |
| } |
| } |
| |
| void CreateFiles(F2fs *fs, int file_cnt, uint64_t version) { |
| fbl::RefPtr<VnodeF2fs> data_root; |
| ASSERT_EQ(VnodeF2fs::Vget(fs, fs->RawSb().root_ino, &data_root), ZX_OK); |
| Dir *root_dir = static_cast<Dir *>(data_root.get()); |
| std::string filename; |
| |
| for (int i = 0; i < file_cnt; i++) { |
| fbl::RefPtr<fs::Vnode> vnode; |
| filename = "file_" + std::to_string(version) + "_" + std::to_string(i); |
| ASSERT_EQ(root_dir->Create(filename.c_str(), S_IFREG, &vnode), ZX_OK); |
| vnode.reset(); |
| } |
| } |
| |
| void CheckpointTestVersion(F2fs *fs, uint32_t expect_cp_position, uint32_t expect_cp_ver, |
| bool after_mkfs) { |
| Checkpoint *cp = nullptr; |
| Page *cp_page = nullptr; |
| |
| GetLastCheckpoint(fs, expect_cp_position, after_mkfs, &cp_page); |
| cp = static_cast<Checkpoint *>(PageAddress(cp_page)); |
| |
| ASSERT_EQ(cp->checkpoint_ver, expect_cp_ver); |
| |
| F2fsPutPage(cp_page, 1); |
| } |
| |
| void CheckpointTestNatBitmap(F2fs *fs, uint32_t expect_cp_position, uint32_t expect_cp_ver, |
| bool after_mkfs, uint8_t *&pre_bitmap) { |
| Page *cp_page = nullptr; |
| uint8_t *version_bitmap; |
| uint32_t cur_nat_block = 0; |
| uint8_t cur_nat_bit = 0; |
| |
| // 1. Get last checkpoint |
| GetLastCheckpoint(fs, expect_cp_position, after_mkfs, &cp_page); |
| Checkpoint *cp = static_cast<Checkpoint *>(PageAddress(cp_page)); |
| ASSERT_EQ(cp->checkpoint_ver, expect_cp_ver); |
| |
| // 2. Get NAT version bitmap |
| version_bitmap = static_cast<uint8_t *>(GetBitmapPrt(cp, MetaBitmap::kNatBitmap)); |
| ASSERT_NE(version_bitmap, nullptr); |
| |
| if (pre_bitmap == nullptr) |
| pre_bitmap = new uint8_t[cp->nat_ver_bitmap_bytesize](); |
| |
| #ifdef F2FS_BU_DEBUG |
| std::cout << "CP ver= " << cp->checkpoint_ver << ", pre_bitmap = " << std::hex; |
| printf("%02x ", (static_cast<uint8_t *>(pre_bitmap))[0]); |
| |
| for (uint32_t i = 0; i < 8 /*cp->nat_ver_bitmap_bytesize*/; i++) { |
| std::cout << std::bitset<8>((static_cast<uint8_t *>(pre_bitmap))[i]) << " "; |
| } |
| std::cout << std::dec << std::endl; |
| |
| std::cout << "CP ver= " << cp->checkpoint_ver << ", version_bitmap = " << std::hex; |
| printf("%02x ", (static_cast<uint8_t *>(version_bitmap))[0]); |
| |
| for (uint32_t i = 0; i < 8 /*cp->nat_ver_bitmap_bytesize*/; i++) { |
| std::cout << std::bitset<8>((static_cast<uint8_t *>(version_bitmap))[i]) << " "; |
| } |
| std::cout << std::dec << std::endl; |
| #endif |
| |
| // 3. Validate version bitmap |
| // Check root dir version bitmap |
| ASSERT_EQ((static_cast<uint8_t *>(version_bitmap))[0] & kRootDirNatBit, |
| cp->checkpoint_ver % 2 ? 0x00 : kRootDirNatBit); |
| |
| // Check dir and file inode version bitmap |
| if (!after_mkfs) { |
| if (cp->checkpoint_ver % 2) { |
| (static_cast<uint8_t *>(pre_bitmap))[0] &= ~kRootDirNatBit; |
| } else { |
| (static_cast<uint8_t *>(pre_bitmap))[0] |= kRootDirNatBit; |
| } |
| |
| cur_nat_block = cp->checkpoint_ver - 2; |
| cur_nat_bit = 0x80 >> (cur_nat_block % 8); |
| (static_cast<uint8_t *>(pre_bitmap))[cur_nat_block / 8] |= cur_nat_bit; |
| |
| ASSERT_EQ((static_cast<uint8_t *>(version_bitmap))[cur_nat_block / 8], |
| (static_cast<uint8_t *>(pre_bitmap))[cur_nat_block / 8]); |
| |
| #ifdef F2FS_BU_DEBUG |
| std::cout << "CP ver= " << cp->checkpoint_ver << ", exp pre_bitmap = " << std::hex; |
| printf("%02x ", (static_cast<uint8_t *>(pre_bitmap))[0]); |
| |
| for (uint32_t i = 0; i < 8 /*cp->nat_ver_bitmap_bytesize*/; i++) { |
| std::cout << std::bitset<8>((static_cast<uint8_t *>(pre_bitmap))[i]) << " "; |
| } |
| std::cout << std::dec << std::endl; |
| #endif |
| |
| ASSERT_EQ(memcmp(pre_bitmap, version_bitmap, cp->nat_ver_bitmap_bytesize), 0); |
| } |
| |
| memcpy(pre_bitmap, version_bitmap, cp->nat_ver_bitmap_bytesize); |
| |
| // 4. Creates inodes and triggers checkpoint |
| // It creates 455 inodes in the root dir to make one dirty NAT block, and |
| // it triggers checkpoint. It results in one bit triggered in NAT bitmap. |
| // Since the current F2FS impl. supports only sync IO, every file creation results in |
| // updating the root inode, and thus the first bit (root inode) in NAT bitmap is also triggered. |
| for (int i = 0; i < 4; i++) { |
| CreateDirs(fs, 1, cp->checkpoint_ver * 10 + i); |
| CreateFiles(fs, 100, cp->checkpoint_ver * 10 + i); |
| } |
| |
| CreateDirs(fs, 1, cp->checkpoint_ver * 10 + 4); |
| if (after_mkfs) { |
| CreateFiles(fs, 46, cp->checkpoint_ver * 10 + 4); // Mkfs uses 4 nids |
| } else { |
| CreateFiles(fs, 50, cp->checkpoint_ver * 10 + 4); // 5 dirs + 450 files = 455 |
| } |
| |
| F2fsPutPage(cp_page, 1); |
| } |
| |
| void CheckpointTestMain(MountOptions &options, uint32_t test, uint8_t *&priv) { |
| std::unique_ptr<f2fs::Bcache> bc; |
| bool readonly_device = false; |
| bool after_mkfs = true; |
| int checkpoint_pack = kCheckpointPack0; |
| auto device = std::make_unique<FakeBlockDevice>( |
| FakeBlockDevice::Config{.block_count = kMinVolumeSize / kDefaultSectorSize, |
| .block_size = kDefaultSectorSize, |
| .supports_trim = false}); |
| |
| ASSERT_TRUE(device); |
| ASSERT_EQ(CreateBcache(std::move(device), &readonly_device, &bc), ZX_OK); |
| |
| MkfsWorker mkfs(bc.get()); |
| ASSERT_EQ(mkfs.DoMkfs(), ZX_OK); |
| |
| std::unique_ptr<F2fs> fs; |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| |
| // Enclose the channels in a scope to ensure they are closed before we shut down. |
| { |
| zx::channel mount_channel, remote_mount_channel; |
| ASSERT_EQ(zx::channel::create(0, &mount_channel, &remote_mount_channel), ZX_OK); |
| auto fs_or = f2fs::CreateFsAndRoot( |
| options, loop.dispatcher(), std::move(bc), std::move(mount_channel), [] {}, |
| ServeLayout::kExportDirectory); |
| |
| ASSERT_EQ(fs_or.is_ok(), true); |
| fs = std::move(fs_or).value(); |
| |
| // Validate checkpoint |
| for (uint32_t i = 1; i <= kCheckpointLoopCnt + 1; i++) { |
| if (!after_mkfs) |
| fs->WriteCheckpoint(false, false); |
| |
| switch (test) { |
| case kCheckpointVersionTest: |
| CheckpointTestVersion(fs.get(), checkpoint_pack, i, after_mkfs); |
| break; |
| case kCheckpointNatBitmapTest: |
| CheckpointTestNatBitmap(fs.get(), checkpoint_pack, i, after_mkfs, priv); |
| break; |
| default: |
| ASSERT_EQ(0, 1); |
| break; |
| }; |
| |
| if (after_mkfs) |
| after_mkfs = false; |
| |
| if (checkpoint_pack == kCheckpointPack0) { |
| checkpoint_pack = kCheckpointPack1; |
| } else { |
| checkpoint_pack = kCheckpointPack0; |
| } |
| } |
| } |
| fs->Shutdown([&loop](zx_status_t) { loop.Quit(); }); |
| __UNUSED F2fs *relinquish = fs.release(); |
| |
| ASSERT_EQ(loop.Run(), ZX_ERR_CANCELED); |
| } |
| |
| TEST(CheckpointTest, Version) { |
| MountOptions options; |
| uint8_t *priv = nullptr; |
| |
| CheckpointTestMain(options, kCheckpointVersionTest, priv); |
| } |
| |
| TEST(CheckpointTest, NatBitmap) { |
| MountOptions options; |
| uint8_t *pre_bitmap = nullptr; |
| |
| CheckpointTestMain(options, kCheckpointNatBitmapTest, pre_bitmap); |
| |
| delete[] pre_bitmap; |
| } |
| |
| } // namespace |
| } // namespace f2fs |