blob: f56372c63e833f888209dca625f3300ff4710777 [file] [log] [blame]
// 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