blob: 3fdc64a1c4b50db1a38c09884a044e15c4fa230e [file] [log] [blame]
// Copyright 2022 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 <cstdint>
#include <string_view>
#include <unordered_set>
#include <safemath/checked_math.h>
#include "src/storage/f2fs/f2fs.h"
#include "src/storage/lib/block_client/cpp/fake_block_device.h"
#include "unit_lib.h"
namespace f2fs {
namespace {
zx_status_t CheckDataPage(F2fs *fs, pgoff_t data_blkaddr, uint32_t index) {
zx_status_t ret = ZX_ERR_INVALID_ARGS;
LockedPage page;
if (ret = fs->GetMetaPage(data_blkaddr, &page); ret != ZX_OK) {
return ret;
}
if (*static_cast<uint32_t *>(page->GetAddress()) == index) {
ret = ZX_OK;
}
return ret;
}
block_t StartBidxOfNodeWithoutVnode(NodePage &node_page) {
constexpr uint32_t kOfsInode = 0;
constexpr uint32_t kOfsDirectNode2 = 2;
constexpr uint32_t kOfsIndirectNode1 = 3;
constexpr uint32_t kOfsIndirectNode2 = 4 + kNidsPerBlock;
constexpr uint32_t kOfsDoubleIndirectNode = 5 + 2 * kNidsPerBlock;
uint32_t node_ofs = node_page.OfsOfNode(), NumOfIndirectNodes = 0;
if (node_ofs == kOfsInode) {
return 0;
} else if (node_ofs <= kOfsDirectNode2) {
NumOfIndirectNodes = 0;
} else if (node_ofs >= kOfsIndirectNode1 && node_ofs < kOfsIndirectNode2) {
NumOfIndirectNodes = 1;
} else if (node_ofs >= kOfsIndirectNode2 && node_ofs < kOfsDoubleIndirectNode) {
NumOfIndirectNodes = 2;
} else {
NumOfIndirectNodes = (node_ofs - kOfsDoubleIndirectNode - 2) / (kNidsPerBlock + 1);
}
uint32_t bidx = node_ofs - NumOfIndirectNodes - 1;
// Since the test does not use InlineXattr, Use |kAddrsPerInode| value instead of
// |VnodeF2fs::GetAddrsPerInode| function.
return (kAddrsPerInode + safemath::CheckMul(bidx, kAddrsPerBlock)).ValueOrDie();
}
zx::result<pgoff_t> CheckNodePage(F2fs *fs, NodePage &node_page) {
uint32_t block_count = 0, start_index = 0, checked = 0;
if (node_page.IsInode()) {
block_count = kAddrsPerInode;
} else {
block_count = kAddrsPerBlock;
}
start_index = StartBidxOfNodeWithoutVnode(node_page);
for (uint32_t index = 0; index < block_count; ++index) {
block_t data_blkaddr = node_page.GetBlockAddr(index);
if (data_blkaddr == kNullAddr) {
continue;
}
if (CheckDataPage(fs, data_blkaddr, safemath::checked_cast<uint32_t>(start_index + index)) !=
ZX_OK) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
++checked;
}
return zx::ok(checked);
}
zx::result<fbl::RefPtr<VnodeF2fs>> CreateFileAndWritePages(Dir *dir_vnode,
std::string_view file_name,
pgoff_t page_count, uint32_t signiture) {
zx::result file_fs_vnode = dir_vnode->Create(file_name, fs::CreationType::kFile);
if (file_fs_vnode.is_error()) {
return file_fs_vnode.take_error();
}
fbl::RefPtr<VnodeF2fs> fsync_vnode = fbl::RefPtr<VnodeF2fs>::Downcast(*std::move(file_fs_vnode));
File *fsync_file_ptr = static_cast<File *>(fsync_vnode.get());
// Write a page
for (uint32_t index = 0; index < page_count; ++index) {
uint32_t write_buf[PAGE_SIZE / (sizeof(uint32_t) / sizeof(uint8_t))];
for (uint32_t &integer : write_buf) {
integer = index + signiture;
}
FileTester::AppendToFile(fsync_file_ptr, write_buf, PAGE_SIZE);
}
return zx::ok(std::move(fsync_vnode));
}
void CheckFsyncedFile(F2fs *fs, ino_t ino, pgoff_t data_page_count, pgoff_t node_page_count) {
block_t data_blkaddr = fs->GetSegmentManager().NextFreeBlkAddr(CursegType::kCursegWarmNode);
uint64_t curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
pgoff_t checked_data_page_count = 0;
pgoff_t checked_node_page_count = 0;
while (true) {
LockedPage page;
ASSERT_EQ(fs->GetMetaPage(data_blkaddr, &page), ZX_OK);
NodePage *node_page = &page.GetPage<NodePage>();
if (curr_checkpoint_ver != node_page->CpverOfNode()) {
break;
}
if (node_page->InoOfNode() == ino) {
if (node_page_count == ++checked_node_page_count) {
ASSERT_TRUE(node_page->IsFsyncDnode());
} else {
ASSERT_FALSE(node_page->IsFsyncDnode());
}
auto result = CheckNodePage(fs, *node_page);
ASSERT_EQ(result.status_value(), ZX_OK);
checked_data_page_count += result.value();
}
data_blkaddr = node_page->NextBlkaddrOfNode();
}
ASSERT_EQ(checked_data_page_count, data_page_count);
ASSERT_EQ(checked_node_page_count, node_page_count);
}
TEST(FsyncRecoveryTest, FsyncInode) {
std::unique_ptr<BcacheMapper> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 0), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// 1. Create file and write data pages
const pgoff_t data_page_count = 1;
const pgoff_t node_page_count = 1;
auto ret = CreateFileAndWritePages(root_dir.get(), "fsync_inode_file", data_page_count, 0);
ASSERT_TRUE(ret.is_ok());
auto fsync_vnode = std::move(ret.value());
// 2. Fsync file
ino_t fsync_file_ino = fsync_vnode->Ino();
block_t pre_next_node_blkaddr =
fs->GetSegmentManager().NextFreeBlkAddr(CursegType::kCursegWarmNode);
block_t pre_next_data_blkaddr =
fs->GetSegmentManager().NextFreeBlkAddr(CursegType::kCursegWarmData);
uint64_t pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(fsync_vnode->SyncFile(false), ZX_OK);
uint64_t curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// Checkpoint should not be performed instead of fsync
ASSERT_EQ(pre_checkpoint_ver, curr_checkpoint_ver);
ASSERT_EQ(fsync_vnode->Close(), ZX_OK);
fsync_vnode = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 3. SPO
FileTester::SuddenPowerOff(std::move(fs), &bc);
// 4. Remount without roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 1), ZX_OK);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(pre_checkpoint_ver, curr_checkpoint_ver);
// 5. Check fsynced inode pages
block_t curr_next_node_blkaddr =
fs->GetSegmentManager().NextFreeBlkAddr(CursegType::kCursegWarmNode);
ASSERT_EQ(pre_next_node_blkaddr, curr_next_node_blkaddr);
block_t curr_next_data_blkaddr =
fs->GetSegmentManager().NextFreeBlkAddr(CursegType::kCursegWarmData);
ASSERT_EQ(pre_next_data_blkaddr, curr_next_data_blkaddr);
CheckFsyncedFile(fs.get(), fsync_file_ino, data_page_count, node_page_count);
// 6. Unmount and check filesystem
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(FsyncRecoveryTest, FsyncDnode) {
std::unique_ptr<BcacheMapper> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 0), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// 1. Create file and write data pages to use dnode.
const pgoff_t data_page_count = kAddrsPerInode + 1;
const pgoff_t node_page_count = 2;
auto ret = CreateFileAndWritePages(root_dir.get(), "fsync_dnode_file", data_page_count, 0);
ASSERT_TRUE(ret.is_ok());
auto fsync_vnode = std::move(ret.value());
// 2. Fsync file
ino_t fsync_file_ino = fsync_vnode->Ino();
block_t pre_next_node_blkaddr =
fs->GetSegmentManager().NextFreeBlkAddr(CursegType::kCursegWarmNode);
block_t pre_next_data_blkaddr =
fs->GetSegmentManager().NextFreeBlkAddr(CursegType::kCursegWarmData);
uint64_t pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(fsync_vnode->SyncFile(false), ZX_OK);
uint64_t curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// Checkpoint should not be performed instead of fsync
ASSERT_EQ(pre_checkpoint_ver, curr_checkpoint_ver);
ASSERT_EQ(fsync_vnode->Close(), ZX_OK);
fsync_vnode = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 3. SPO
FileTester::SuddenPowerOff(std::move(fs), &bc);
// 4. Remount without roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 1), ZX_OK);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(pre_checkpoint_ver, curr_checkpoint_ver);
// 5. Check fsynced inode pages
block_t curr_next_node_blkaddr =
fs->GetSegmentManager().NextFreeBlkAddr(CursegType::kCursegWarmNode);
ASSERT_EQ(pre_next_node_blkaddr, curr_next_node_blkaddr);
block_t curr_next_data_blkaddr =
fs->GetSegmentManager().NextFreeBlkAddr(CursegType::kCursegWarmData);
ASSERT_EQ(pre_next_data_blkaddr, curr_next_data_blkaddr);
CheckFsyncedFile(fs.get(), fsync_file_ino, data_page_count, node_page_count);
// 6. Unmount and check filesystem
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(FsyncRecoveryTest, FsyncIndirectDnode) {
std::unique_ptr<BcacheMapper> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 0), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// 1. Create file and write data pages to use indirect dnode.
const pgoff_t data_page_count = kAddrsPerInode + kAddrsPerBlock * 2 + 1;
const pgoff_t node_page_count = 4;
auto ret =
CreateFileAndWritePages(root_dir.get(), "fsync_indirect_dnode_file", data_page_count, 0);
ASSERT_TRUE(ret.is_ok());
auto fsync_vnode = std::move(ret.value());
// 2. Fsync file
ino_t fsync_file_ino = fsync_vnode->Ino();
block_t pre_next_node_blkaddr =
fs->GetSegmentManager().NextFreeBlkAddr(CursegType::kCursegWarmNode);
block_t pre_next_data_blkaddr =
fs->GetSegmentManager().NextFreeBlkAddr(CursegType::kCursegWarmData);
uint64_t pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(fsync_vnode->SyncFile(false), ZX_OK);
uint64_t curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// Checkpoint should not be performed instead of fsync
ASSERT_EQ(pre_checkpoint_ver, curr_checkpoint_ver);
ASSERT_EQ(fsync_vnode->Close(), ZX_OK);
fsync_vnode = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 3. SPO
FileTester::SuddenPowerOff(std::move(fs), &bc);
// 4. Remount without roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 1), ZX_OK);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(pre_checkpoint_ver, curr_checkpoint_ver);
// 5. Check fsynced inode pages
block_t curr_next_node_blkaddr =
fs->GetSegmentManager().NextFreeBlkAddr(CursegType::kCursegWarmNode);
ASSERT_EQ(pre_next_node_blkaddr, curr_next_node_blkaddr);
block_t curr_next_data_blkaddr =
fs->GetSegmentManager().NextFreeBlkAddr(CursegType::kCursegWarmData);
ASSERT_EQ(pre_next_data_blkaddr, curr_next_data_blkaddr);
CheckFsyncedFile(fs.get(), fsync_file_ino, data_page_count, node_page_count);
// 6. Unmount and check filesystem
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(FsyncRecoveryTest, FsyncCheckpoint) {
std::unique_ptr<BcacheMapper> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 0), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// 1. Fsync directory
zx::result file_fs_vnode = root_dir->Create("fsync_dir", fs::CreationType::kDirectory);
ASSERT_TRUE(file_fs_vnode.is_ok()) << file_fs_vnode.status_string();
fbl::RefPtr<VnodeF2fs> fsync_vnode = fbl::RefPtr<VnodeF2fs>::Downcast(*std::move(file_fs_vnode));
uint64_t pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(fsync_vnode->SyncFile(false), ZX_OK);
uint64_t curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// fsync should trigger checkpoint
ASSERT_EQ(pre_checkpoint_ver + 1, curr_checkpoint_ver);
ASSERT_EQ(fsync_vnode->Close(), ZX_OK);
fsync_vnode = nullptr;
// 2. Fsync Nlink > 1
file_fs_vnode = root_dir->Create("fsync_file_nlink", fs::CreationType::kFile);
ASSERT_TRUE(file_fs_vnode.is_ok()) << file_fs_vnode.status_string();
fsync_vnode = fbl::RefPtr<VnodeF2fs>::Downcast(*std::move(file_fs_vnode));
fsync_vnode->IncNlink();
pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(fsync_vnode->SyncFile(false), ZX_OK);
curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// fsync should trigger checkpoint
ASSERT_EQ(pre_checkpoint_ver + 1, curr_checkpoint_ver);
fsync_vnode->DropNlink();
fsync_vnode->SetDirty();
ASSERT_EQ(fsync_vnode->Close(), ZX_OK);
fsync_vnode = nullptr;
// 3. Fsync vnode with kNeedCp flag
file_fs_vnode = root_dir->Create("fsync_file_need_cp", fs::CreationType::kFile);
ASSERT_TRUE(file_fs_vnode.is_ok()) << file_fs_vnode.status_string();
fsync_vnode = fbl::RefPtr<VnodeF2fs>::Downcast(*std::move(file_fs_vnode));
fsync_vnode->SetFlag(InodeInfoFlag::kNeedCp);
pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(fsync_vnode->SyncFile(false), ZX_OK);
curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// fsync should trigger checkpoint
ASSERT_EQ(pre_checkpoint_ver + 1, curr_checkpoint_ver);
ASSERT_EQ(fsync_vnode->Close(), ZX_OK);
fsync_vnode = nullptr;
// 4. Not enough SpaceForRollForward
file_fs_vnode = root_dir->Create("fsync_file_space_for_roll_forward", fs::CreationType::kFile);
ASSERT_TRUE(file_fs_vnode.is_ok()) << file_fs_vnode.status_string();
fsync_vnode = fbl::RefPtr<VnodeF2fs>::Downcast(*std::move(file_fs_vnode));
block_t temp_user_block_count = fs->GetSuperblockInfo().GetTotalBlockCount();
fs->GetSuperblockInfo().SetTotalBlockCount(0);
pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(fsync_vnode->SyncFile(false), ZX_OK);
curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// fsync should trigger checkpoint
ASSERT_EQ(pre_checkpoint_ver + 1, curr_checkpoint_ver);
fs->GetSuperblockInfo().SetTotalBlockCount(temp_user_block_count);
ASSERT_EQ(fsync_vnode->Close(), ZX_OK);
fsync_vnode = nullptr;
// 5. NeedToSyncDir()
FileTester::CreateChild(root_dir.get(), S_IFDIR, "parent_dir");
fbl::RefPtr<fs::Vnode> child_dir_vn;
FileTester::Lookup(root_dir.get(), "parent_dir", &child_dir_vn);
file_fs_vnode = child_dir_vn->Create("fsync_file", fs::CreationType::kFile);
ASSERT_TRUE(file_fs_vnode.is_ok()) << file_fs_vnode.status_string();
fsync_vnode = fbl::RefPtr<VnodeF2fs>::Downcast(*std::move(file_fs_vnode));
pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(fsync_vnode->SyncFile(false), ZX_OK);
curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// fsync should trigger checkpoint
ASSERT_EQ(pre_checkpoint_ver + 1, curr_checkpoint_ver);
ASSERT_EQ(fsync_vnode->Close(), ZX_OK);
fsync_vnode = nullptr;
ASSERT_EQ(child_dir_vn->Close(), ZX_OK);
child_dir_vn = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 6. Enable kMountDisableRollForward option
// Remount without roll-forward recovery
FileTester::Unmount(std::move(fs), &bc);
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 1), ZX_OK);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
FileTester::CreateRoot(fs.get(), &root);
root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
file_fs_vnode = root_dir->Create("fsync_file_disable_roll_forward", fs::CreationType::kFile);
ASSERT_TRUE(file_fs_vnode.is_ok()) << file_fs_vnode.status_string();
fsync_vnode = fbl::RefPtr<VnodeF2fs>::Downcast(*std::move(file_fs_vnode));
pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(fsync_vnode->SyncFile(false), ZX_OK);
curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// fsync should trigger checkpoint
ASSERT_EQ(pre_checkpoint_ver + 1, curr_checkpoint_ver);
ASSERT_EQ(fsync_vnode->Close(), ZX_OK);
fsync_vnode = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// Unmount and check filesystem
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(FsyncRecoveryTest, FsyncRecoveryIndirectDnode) {
std::unique_ptr<BcacheMapper> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 0), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// 1. Create file and write data pages to use indirect dnode.
const pgoff_t data_page_count = kAddrsPerInode + kAddrsPerBlock * 2 + 1;
std::string file_name("recovery_indirect_dnode_file");
auto ret = CreateFileAndWritePages(root_dir.get(), file_name, data_page_count, 0);
ASSERT_TRUE(ret.is_ok());
auto fsync_vnode = std::move(ret.value());
// 2. Fsync file
uint64_t pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(fsync_vnode->SyncFile(false), ZX_OK);
uint64_t curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// Checkpoint should not be performed instead of fsync
ASSERT_EQ(pre_checkpoint_ver, curr_checkpoint_ver);
ASSERT_EQ(fsync_vnode->Close(), ZX_OK);
fsync_vnode = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 4. SPO
FileTester::SuddenPowerOff(std::move(fs), &bc);
// 5. Remount with roll-forward recovery
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(pre_checkpoint_ver + 1, curr_checkpoint_ver);
// 6. Check fsynced file
FileTester::CreateRoot(fs.get(), &root);
root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
fbl::RefPtr<fs::Vnode> file_fs_vnode;
FileTester::Lookup(root_dir.get(), file_name, &file_fs_vnode);
fsync_vnode = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(file_fs_vnode));
File *fsync_file_ptr = static_cast<File *>(fsync_vnode.get());
ASSERT_EQ(fsync_vnode->GetSize(), data_page_count * PAGE_SIZE);
for (uint32_t index = 0; index < data_page_count; ++index) {
uint32_t write_buf[PAGE_SIZE / (sizeof(uint32_t) / sizeof(uint8_t))];
FileTester::ReadFromFile(fsync_file_ptr, write_buf, PAGE_SIZE,
static_cast<size_t>(index) * PAGE_SIZE);
ASSERT_EQ(write_buf[0], index);
}
ASSERT_EQ(fsync_vnode->Close(), ZX_OK);
fsync_vnode = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 7. Unmount and check filesystem
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(FsyncRecoveryTest, FsyncRecoveryMultipleFiles) {
std::unique_ptr<BcacheMapper> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 0), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// 1. Create file 1
const pgoff_t data_page_count_1 = kAddrsPerInode + kAddrsPerBlock * 2 + 1;
uint32_t file_1_signature = 0x111111;
std::string file_name_1("recovery_file_1");
auto ret =
CreateFileAndWritePages(root_dir.get(), file_name_1, data_page_count_1, file_1_signature);
ASSERT_TRUE(ret.is_ok());
auto fsync_vnode_1 = std::move(ret.value());
// 2. Fsync file 1
uint64_t pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(fsync_vnode_1->SyncFile(false), ZX_OK);
uint64_t curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// Checkpoint should not be performed instead of fsync
ASSERT_EQ(pre_checkpoint_ver, curr_checkpoint_ver);
// 3. Create file 2
const pgoff_t data_page_count_2 = kAddrsPerInode + kAddrsPerBlock * 2 + 1;
uint32_t file_2_signature = 0x222222;
std::string file_name_2("recovery_file_2");
ret = CreateFileAndWritePages(root_dir.get(), file_name_2, data_page_count_2, file_2_signature);
ASSERT_TRUE(ret.is_ok());
auto fsync_vnode_2 = std::move(ret.value());
// 4. Fsync file 2
pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(fsync_vnode_2->SyncFile(false), ZX_OK);
curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// Checkpoint should not be performed instead of fsync
ASSERT_EQ(pre_checkpoint_ver, curr_checkpoint_ver);
ASSERT_EQ(fsync_vnode_1->Close(), ZX_OK);
fsync_vnode_1 = nullptr;
ASSERT_EQ(fsync_vnode_2->Close(), ZX_OK);
fsync_vnode_2 = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 5. SPO
FileTester::SuddenPowerOff(std::move(fs), &bc);
// 6. Remount with roll-forward recovery
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(pre_checkpoint_ver + 1, curr_checkpoint_ver);
FileTester::CreateRoot(fs.get(), &root);
root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// 7. Check fsynced file 1
fbl::RefPtr<fs::Vnode> file_fs_vnode_1;
FileTester::Lookup(root_dir.get(), file_name_1, &file_fs_vnode_1);
fsync_vnode_1 = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(file_fs_vnode_1));
File *fsync_file_ptr_1 = static_cast<File *>(fsync_vnode_1.get());
ASSERT_EQ(fsync_vnode_1->GetSize(), data_page_count_1 * PAGE_SIZE);
for (uint32_t index = 0; index < data_page_count_1; ++index) {
uint32_t write_buf[PAGE_SIZE / (sizeof(uint32_t) / sizeof(uint8_t))];
FileTester::ReadFromFile(fsync_file_ptr_1, write_buf, PAGE_SIZE,
static_cast<size_t>(index) * PAGE_SIZE);
ASSERT_EQ(write_buf[0], index + file_1_signature);
}
// 8. Check fsynced file 2
fbl::RefPtr<fs::Vnode> file_fs_vnode_2;
FileTester::Lookup(root_dir.get(), file_name_2, &file_fs_vnode_2);
fsync_vnode_2 = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(file_fs_vnode_2));
File *fsync_file_ptr_2 = static_cast<File *>(fsync_vnode_2.get());
ASSERT_EQ(fsync_vnode_2->GetSize(), data_page_count_2 * PAGE_SIZE);
for (uint32_t index = 0; index < data_page_count_2; ++index) {
uint32_t write_buf[PAGE_SIZE / (sizeof(uint32_t) / sizeof(uint8_t))];
FileTester::ReadFromFile(fsync_file_ptr_2, write_buf, PAGE_SIZE,
static_cast<size_t>(index) * PAGE_SIZE);
ASSERT_EQ(write_buf[0], index + file_2_signature);
}
ASSERT_EQ(fsync_vnode_1->Close(), ZX_OK);
fsync_vnode_1 = nullptr;
ASSERT_EQ(fsync_vnode_2->Close(), ZX_OK);
fsync_vnode_2 = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 9. Unmount and check filesystem
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(FsyncRecoveryTest, FsyncRecoveryInlineData) {
srand(testing::UnitTest::GetInstance()->random_seed());
std::unique_ptr<BcacheMapper> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 0), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// The inline_data recovery policy is as follows.
// [prev.] [next] of inline_data flag
// o o -> 1. recover inline_data
// o x -> 2. remove inline_data, and then recover data blocks
// 1. recover inline_data
// Inline file creation
std::string inline_file_name("inline");
zx::result inline_raw_vnode = root_dir->Create(inline_file_name, fs::CreationType::kFile);
ASSERT_TRUE(inline_raw_vnode.is_ok()) << inline_raw_vnode.status_string();
fbl::RefPtr<VnodeF2fs> inline_vnode =
fbl::RefPtr<VnodeF2fs>::Downcast(*std::move(inline_raw_vnode));
File *inline_file_ptr = static_cast<File *>(inline_vnode.get());
inline_vnode->SetFlag(InodeInfoFlag::kInlineData);
FileTester::CheckInlineFile(inline_vnode.get());
fs->SyncFs();
// Write until entire inline data space is written
size_t target_size = inline_file_ptr->MaxInlineData() - 1;
auto w_buf = std::make_unique<char[]>(inline_file_ptr->MaxInlineData());
auto r_buf = std::make_unique<char[]>(inline_file_ptr->MaxInlineData());
for (size_t i = 0; i < inline_file_ptr->MaxInlineData(); ++i) {
w_buf[i] = static_cast<char>(rand());
}
FileTester::AppendToInline(inline_file_ptr, w_buf.get(), target_size);
FileTester::CheckInlineFile(inline_vnode.get());
ASSERT_EQ(inline_file_ptr->GetSize(), target_size);
ASSERT_EQ(inline_vnode->SyncFile(false), ZX_OK);
ASSERT_EQ(inline_vnode->Close(), ZX_OK);
inline_vnode = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// SPO and remount with roll-forward recovery
FileTester::SuddenPowerOff(std::move(fs), &bc);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
FileTester::CreateRoot(fs.get(), &root);
root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
fbl::RefPtr<fs::Vnode> lookup_vn;
FileTester::Lookup(root_dir.get(), inline_file_name, &lookup_vn);
inline_vnode = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(lookup_vn));
inline_file_ptr = static_cast<File *>(inline_vnode.get());
FileTester::CheckInlineFile(inline_vnode.get());
// Check recovery inline data
FileTester::ReadFromFile(inline_file_ptr, r_buf.get(), target_size, 0);
ASSERT_EQ(memcmp(r_buf.get(), w_buf.get(), target_size), 0);
// As fuchsia f2fs doesn't use inlinedata, |inline_vnode| should move inline data to data block
// during read()
FileTester::CheckNonInlineFile(inline_vnode.get());
// 2. remove inline_data, and then recover data blocks
// Write one more byte, then it should be converted to noinline
inline_vnode->Truncate(0);
inline_vnode->SetFlag(InodeInfoFlag::kInlineData);
FileTester::CheckInlineFile(inline_vnode.get());
FileTester::AppendToInline(inline_file_ptr, w_buf.get(), target_size);
target_size = inline_file_ptr->MaxInlineData();
FileTester::AppendToFile(inline_file_ptr, w_buf.get() + target_size - 1, 1);
FileTester::CheckNonInlineFile(inline_vnode.get());
ASSERT_EQ(inline_file_ptr->GetSize(), target_size);
ASSERT_EQ(inline_vnode->SyncFile(false), ZX_OK);
inline_file_ptr = nullptr;
ASSERT_EQ(inline_vnode->Close(), ZX_OK);
inline_vnode = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// SPO and remount with roll-forward recovery
FileTester::SuddenPowerOff(std::move(fs), &bc);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
FileTester::CreateRoot(fs.get(), &root);
root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
FileTester::Lookup(root_dir.get(), inline_file_name, &lookup_vn);
inline_vnode = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(lookup_vn));
inline_file_ptr = static_cast<File *>(inline_vnode.get());
FileTester::CheckNonInlineFile(inline_vnode.get());
ASSERT_EQ(inline_file_ptr->GetSize(), target_size);
FileTester::ReadFromFile(inline_file_ptr, r_buf.get(), target_size, 0);
ASSERT_EQ(memcmp(r_buf.get(), w_buf.get(), target_size), 0);
inline_file_ptr = nullptr;
ASSERT_EQ(inline_vnode->Close(), ZX_OK);
inline_vnode = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// Unmount and check filesystem
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(FsyncRecoveryTest, RecoveryWithoutFsync) {
std::unique_ptr<BcacheMapper> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 0), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// 1. Create file and write data pages to use indirect dnode.
const pgoff_t data_page_count = 1;
std::string file_name("recovery_without_fsync_file");
auto ret = CreateFileAndWritePages(root_dir.get(), file_name, data_page_count, 0);
auto fsync_vnode = std::move(ret.value());
ASSERT_EQ(fsync_vnode->Close(), ZX_OK);
fsync_vnode = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 2. SPO without fsync
FileTester::SuddenPowerOff(std::move(fs), &bc);
// 3. Remount with roll-forward recovery
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
// 4. Check fsynced file
FileTester::CreateRoot(fs.get(), &root);
root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// File not found.
fbl::RefPtr<fs::Vnode> file_fs_vnode;
FileTester::Lookup(root_dir.get(), file_name, &file_fs_vnode);
ASSERT_EQ(file_fs_vnode, nullptr);
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 5. Unmount and check filesystem
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(FsyncRecoveryTest, RenameFileWithStrictFsync) {
std::unique_ptr<BcacheMapper> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 0), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
// This is same scenario of xfstest generic/342
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// 1. Create "a"
FileTester::CreateChild(root_dir.get(), S_IFDIR, "a");
fbl::RefPtr<fs::Vnode> child_dir_vn;
FileTester::Lookup(root_dir.get(), "a", &child_dir_vn);
fbl::RefPtr<Dir> child_dir = fbl::RefPtr<Dir>::Downcast(std::move(child_dir_vn));
ASSERT_EQ(child_dir->SyncFile(false), ZX_OK);
// 2. Create "a/foo"
uint32_t first_signature = 0xa1;
uint32_t data_page_count = 4;
auto ret = CreateFileAndWritePages(child_dir.get(), "foo", data_page_count, first_signature);
ASSERT_TRUE(ret.is_ok());
fbl::RefPtr<VnodeF2fs> first_foo_vnode = std::move(*ret);
ASSERT_EQ(first_foo_vnode->SyncFile(false), ZX_OK);
// 3. Rename "a/foo" to "a/bar"
FileTester::RenameChild(child_dir, child_dir, "foo", "bar");
// 4. Create "a/foo"
uint32_t second_signature = 0xb2;
ret = CreateFileAndWritePages(child_dir.get(), "foo", data_page_count, second_signature);
ASSERT_TRUE(ret.is_ok());
fbl::RefPtr<VnodeF2fs> second_foo_vnode = std::move(*ret);
// 5. Fsync "a/foo"
uint64_t pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(second_foo_vnode->SyncFile(false), ZX_OK);
uint64_t curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// Checkpoint should be performed instead of fsync in STRICT mode
ASSERT_EQ(pre_checkpoint_ver + 1, curr_checkpoint_ver);
ASSERT_EQ(first_foo_vnode->Close(), ZX_OK);
first_foo_vnode = nullptr;
ASSERT_EQ(second_foo_vnode->Close(), ZX_OK);
second_foo_vnode = nullptr;
ASSERT_EQ(child_dir->Close(), ZX_OK);
child_dir = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 6. SPO
FileTester::SuddenPowerOff(std::move(fs), &bc);
// 7. Remount
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
FileTester::CreateRoot(fs.get(), &root);
root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
FileTester::Lookup(root_dir.get(), "a", &child_dir_vn);
child_dir = fbl::RefPtr<Dir>::Downcast(std::move(child_dir_vn));
// 8. Find "a/bar"
fbl::RefPtr<fs::Vnode> first_foo_vn;
FileTester::Lookup(child_dir.get(), "bar", &first_foo_vn);
auto first_foo_file = fbl::RefPtr<File>::Downcast(std::move(first_foo_vn));
// 9. Find "a/foo"
fbl::RefPtr<fs::Vnode> second_foo_vn;
FileTester::Lookup(child_dir.get(), "foo", &second_foo_vn);
auto second_foo_file = fbl::RefPtr<File>::Downcast(std::move(second_foo_vn));
// 10. Check fsynced file
ASSERT_EQ(first_foo_file->GetSize(), data_page_count * PAGE_SIZE);
for (uint32_t index = 0; index < data_page_count; ++index) {
uint32_t write_buf[PAGE_SIZE / (sizeof(uint32_t) / sizeof(uint8_t))];
FileTester::ReadFromFile(first_foo_file.get(), write_buf, PAGE_SIZE,
static_cast<size_t>(index) * PAGE_SIZE);
ASSERT_EQ(write_buf[0], index + first_signature);
}
ASSERT_EQ(second_foo_file->GetSize(), data_page_count * PAGE_SIZE);
for (uint32_t index = 0; index < data_page_count; ++index) {
uint32_t write_buf[PAGE_SIZE / (sizeof(uint32_t) / sizeof(uint8_t))];
FileTester::ReadFromFile(second_foo_file.get(), write_buf, PAGE_SIZE,
static_cast<size_t>(index) * PAGE_SIZE);
ASSERT_EQ(write_buf[0], index + second_signature);
}
ASSERT_EQ(first_foo_file->Close(), ZX_OK);
first_foo_file = nullptr;
ASSERT_EQ(second_foo_file->Close(), ZX_OK);
second_foo_file = nullptr;
ASSERT_EQ(child_dir->Close(), ZX_OK);
child_dir = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 11. Unmount and check filesystem
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(FsyncRecoveryTest, RenameFileToOtherDirWithStrictFsync) {
std::unique_ptr<BcacheMapper> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 0), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// 1. Create "a"
FileTester::CreateChild(root_dir.get(), S_IFDIR, "a");
fbl::RefPtr<fs::Vnode> child_a_dir_vn;
FileTester::Lookup(root_dir.get(), "a", &child_a_dir_vn);
fbl::RefPtr<Dir> child_a_dir = fbl::RefPtr<Dir>::Downcast(std::move(child_a_dir_vn));
ASSERT_EQ(child_a_dir->SyncFile(false), ZX_OK);
// 1. Create "b"
FileTester::CreateChild(root_dir.get(), S_IFDIR, "b");
fbl::RefPtr<fs::Vnode> child_b_dir_vn;
FileTester::Lookup(root_dir.get(), "b", &child_b_dir_vn);
fbl::RefPtr<Dir> child_b_dir = fbl::RefPtr<Dir>::Downcast(std::move(child_b_dir_vn));
ASSERT_EQ(child_b_dir->SyncFile(false), ZX_OK);
// 2. Create "a/foo"
uint32_t first_signature = 0xa1;
uint32_t data_page_count = 4;
auto ret = CreateFileAndWritePages(child_a_dir.get(), "foo", data_page_count, first_signature);
ASSERT_TRUE(ret.is_ok());
fbl::RefPtr<VnodeF2fs> first_foo_vnode = std::move(*ret);
ASSERT_EQ(first_foo_vnode->SyncFile(false), ZX_OK);
// 3. Rename "a/foo" to "b/bar"
FileTester::RenameChild(child_a_dir, child_b_dir, "foo", "bar");
// 4. Create "a/foo"
uint32_t second_signature = 0xb2;
ret = CreateFileAndWritePages(child_a_dir.get(), "foo", data_page_count, second_signature);
ASSERT_TRUE(ret.is_ok());
fbl::RefPtr<VnodeF2fs> second_foo_vnode = std::move(*ret);
// 5. Fsync "a/foo"
uint64_t pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(second_foo_vnode->SyncFile(false), ZX_OK);
uint64_t curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// Checkpoint should be performed instead of fsync in STRICT mode
ASSERT_EQ(pre_checkpoint_ver + 1, curr_checkpoint_ver);
ASSERT_EQ(first_foo_vnode->Close(), ZX_OK);
first_foo_vnode = nullptr;
ASSERT_EQ(second_foo_vnode->Close(), ZX_OK);
second_foo_vnode = nullptr;
ASSERT_EQ(child_a_dir->Close(), ZX_OK);
child_a_dir = nullptr;
ASSERT_EQ(child_b_dir->Close(), ZX_OK);
child_b_dir = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 6. SPO
FileTester::SuddenPowerOff(std::move(fs), &bc);
// 7. Remount
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
FileTester::CreateRoot(fs.get(), &root);
root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
FileTester::Lookup(root_dir.get(), "a", &child_a_dir_vn);
child_a_dir = fbl::RefPtr<Dir>::Downcast(std::move(child_a_dir_vn));
FileTester::Lookup(root_dir.get(), "b", &child_b_dir_vn);
child_b_dir = fbl::RefPtr<Dir>::Downcast(std::move(child_b_dir_vn));
// 8. Find "b/bar"
fbl::RefPtr<fs::Vnode> first_foo_vn;
FileTester::Lookup(child_b_dir.get(), "bar", &first_foo_vn);
auto first_foo_file = fbl::RefPtr<File>::Downcast(std::move(first_foo_vn));
// 9. Find "a/foo"
fbl::RefPtr<fs::Vnode> second_foo_vn;
FileTester::Lookup(child_a_dir.get(), "foo", &second_foo_vn);
auto second_foo_file = fbl::RefPtr<File>::Downcast(std::move(second_foo_vn));
// 10. Check fsynced file
ASSERT_EQ(first_foo_file->GetSize(), data_page_count * PAGE_SIZE);
for (uint32_t index = 0; index < data_page_count; ++index) {
uint32_t write_buf[PAGE_SIZE / (sizeof(uint32_t) / sizeof(uint8_t))];
FileTester::ReadFromFile(first_foo_file.get(), write_buf, PAGE_SIZE,
static_cast<size_t>(index) * PAGE_SIZE);
ASSERT_EQ(write_buf[0], index + first_signature);
}
ASSERT_EQ(second_foo_file->GetSize(), data_page_count * PAGE_SIZE);
for (uint32_t index = 0; index < data_page_count; ++index) {
uint32_t write_buf[PAGE_SIZE / (sizeof(uint32_t) / sizeof(uint8_t))];
FileTester::ReadFromFile(second_foo_file.get(), write_buf, PAGE_SIZE,
static_cast<size_t>(index) * PAGE_SIZE);
ASSERT_EQ(write_buf[0], index + second_signature);
}
ASSERT_EQ(first_foo_file->Close(), ZX_OK);
first_foo_file = nullptr;
ASSERT_EQ(second_foo_file->Close(), ZX_OK);
second_foo_file = nullptr;
ASSERT_EQ(child_a_dir->Close(), ZX_OK);
child_a_dir = nullptr;
ASSERT_EQ(child_b_dir->Close(), ZX_OK);
child_b_dir = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 11. Unmount and check filesystem
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(FsyncRecoveryTest, RenameDirectoryWithStrictFsync) {
std::unique_ptr<BcacheMapper> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 0), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// 1. Create "a"
FileTester::CreateChild(root_dir.get(), S_IFDIR, "a");
fbl::RefPtr<fs::Vnode> child_dir_vn;
FileTester::Lookup(root_dir.get(), "a", &child_dir_vn);
fbl::RefPtr<Dir> child_dir = fbl::RefPtr<Dir>::Downcast(std::move(child_dir_vn));
ASSERT_EQ(child_dir->SyncFile(false), ZX_OK);
// 2. Create "a/foo"
FileTester::CreateChild(child_dir.get(), S_IFDIR, "foo");
fbl::RefPtr<fs::Vnode> first_foo_vnode;
FileTester::Lookup(child_dir.get(), "foo", &first_foo_vnode);
auto first_foo_dir = fbl::RefPtr<Dir>::Downcast(std::move(first_foo_vnode));
FileTester::CreateChild(first_foo_dir.get(), S_IFREG, "bar_verification_file");
ASSERT_EQ(first_foo_dir->SyncFile(false), ZX_OK);
// 3. Rename "a/foo" to "a/bar"
FileTester::RenameChild(child_dir, child_dir, "foo", "bar");
// 4. Create "a/foo"
FileTester::CreateChild(child_dir.get(), S_IFDIR, "foo");
fbl::RefPtr<fs::Vnode> second_foo_vnode;
FileTester::Lookup(child_dir.get(), "foo", &second_foo_vnode);
auto second_foo_dir = fbl::RefPtr<Dir>::Downcast(std::move(second_foo_vnode));
FileTester::CreateChild(second_foo_dir.get(), S_IFREG, "foo_verification_file");
// 5. Fsync "a/foo"
uint64_t pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(second_foo_dir->SyncFile(false), ZX_OK);
uint64_t curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// Checkpoint should be performed instead of fsync in STRICT mode
ASSERT_EQ(pre_checkpoint_ver + 1, curr_checkpoint_ver);
ASSERT_EQ(first_foo_dir->Close(), ZX_OK);
first_foo_dir = nullptr;
ASSERT_EQ(second_foo_dir->Close(), ZX_OK);
second_foo_dir = nullptr;
ASSERT_EQ(child_dir->Close(), ZX_OK);
child_dir = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 6. SPO
FileTester::SuddenPowerOff(std::move(fs), &bc);
// 7. Remount
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
FileTester::CreateRoot(fs.get(), &root);
root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
FileTester::Lookup(root_dir.get(), "a", &child_dir_vn);
child_dir = fbl::RefPtr<Dir>::Downcast(std::move(child_dir_vn));
// 8. Find "a/bar"
fbl::RefPtr<fs::Vnode> first_foo_vn;
FileTester::Lookup(child_dir.get(), "bar", &first_foo_vn);
first_foo_dir = fbl::RefPtr<Dir>::Downcast(std::move(first_foo_vn));
ASSERT_NE(first_foo_dir, nullptr);
fbl::RefPtr<fs::Vnode> bar_verfication_vn;
FileTester::Lookup(first_foo_dir.get(), "bar_verification_file", &bar_verfication_vn);
ASSERT_NE(bar_verfication_vn, nullptr);
// 9. Find "a/foo"
fbl::RefPtr<fs::Vnode> second_foo_vn;
FileTester::Lookup(child_dir.get(), "foo", &second_foo_vn);
second_foo_dir = fbl::RefPtr<Dir>::Downcast(std::move(second_foo_vn));
ASSERT_NE(second_foo_dir, nullptr);
fbl::RefPtr<fs::Vnode> foo_verfication_vn;
FileTester::Lookup(second_foo_dir.get(), "foo_verification_file", &foo_verfication_vn);
ASSERT_NE(foo_verfication_vn, nullptr);
ASSERT_EQ(bar_verfication_vn->Close(), ZX_OK);
bar_verfication_vn = nullptr;
ASSERT_EQ(foo_verfication_vn->Close(), ZX_OK);
foo_verfication_vn = nullptr;
ASSERT_EQ(first_foo_dir->Close(), ZX_OK);
first_foo_dir = nullptr;
ASSERT_EQ(second_foo_dir->Close(), ZX_OK);
second_foo_dir = nullptr;
ASSERT_EQ(child_dir->Close(), ZX_OK);
child_dir = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 11. Unmount and check filesystem
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(FsyncRecoveryTest, AtomicFsync) {
std::unique_ptr<BcacheMapper> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 0), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// 1. Create file and write data pages.
const pgoff_t data_page_count = kAddrsPerInode + kAddrsPerBlock * 2 + 1;
std::string valid_file_name("valid_fsync_file");
auto ret = CreateFileAndWritePages(root_dir.get(), valid_file_name, data_page_count, 0);
ASSERT_TRUE(ret.is_ok());
auto valid_fsync_vnode = std::move(ret.value());
std::string invalid_file_name("invalid_fsync_file");
ret = CreateFileAndWritePages(root_dir.get(), invalid_file_name, data_page_count, 0);
ASSERT_TRUE(ret.is_ok());
auto invalid_fsync_vnode = std::move(ret.value());
// 2. Fsync file
uint64_t pre_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(valid_fsync_vnode->SyncFile(false), ZX_OK);
ASSERT_EQ(invalid_fsync_vnode->SyncFile(false), ZX_OK);
uint64_t curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
// Checkpoint should not be performed instead of fsync
ASSERT_EQ(pre_checkpoint_ver, curr_checkpoint_ver);
// 3. currupt invalid_fsync_file's last dnode page
block_t last_dnode_blkaddr =
fs->GetSegmentManager().NextFreeBlkAddr(CursegType::kCursegWarmNode) - 1;
BlockBuffer<Node> node_block;
fs->GetBc().Readblk(last_dnode_blkaddr, &node_block);
ASSERT_EQ(curr_checkpoint_ver, LeToCpu(node_block->footer.cp_ver));
ASSERT_EQ(node_block->footer.ino, invalid_fsync_vnode->Ino());
uint32_t mask = 1 << static_cast<uint32_t>(BitShift::kFsyncBitShift);
ASSERT_NE(mask & node_block->footer.flag, 0U);
uint32_t dummy_buf[PAGE_SIZE / (sizeof(uint32_t) / sizeof(uint8_t))] = {0};
fs->GetBc().Writeblk(last_dnode_blkaddr, dummy_buf);
ASSERT_EQ(valid_fsync_vnode->Close(), ZX_OK);
valid_fsync_vnode = nullptr;
ASSERT_EQ(invalid_fsync_vnode->Close(), ZX_OK);
invalid_fsync_vnode = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 4. SPO
FileTester::SuddenPowerOff(std::move(fs), &bc);
// 5. Remount with roll-forward recovery
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
curr_checkpoint_ver = fs->GetSuperblockInfo().GetCheckpoint().checkpoint_ver;
ASSERT_EQ(pre_checkpoint_ver + 1, curr_checkpoint_ver);
// 6. Check fsynced file
FileTester::CreateRoot(fs.get(), &root);
root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// Valid File can be successfully recovered
fbl::RefPtr<fs::Vnode> file_fs_vnode;
File *fsync_file_ptr;
FileTester::Lookup(root_dir.get(), valid_file_name, &file_fs_vnode);
valid_fsync_vnode = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(file_fs_vnode));
fsync_file_ptr = static_cast<File *>(valid_fsync_vnode.get());
ASSERT_EQ(valid_fsync_vnode->GetSize(), data_page_count * PAGE_SIZE);
for (uint32_t index = 0; index < data_page_count; ++index) {
uint32_t write_buf[PAGE_SIZE / (sizeof(uint32_t) / sizeof(uint8_t))];
FileTester::ReadFromFile(fsync_file_ptr, write_buf, PAGE_SIZE,
static_cast<size_t>(index) * PAGE_SIZE);
ASSERT_EQ(write_buf[0], index);
}
// Currupted invalid file cannot be recovered
FileTester::Lookup(root_dir.get(), invalid_file_name, &file_fs_vnode);
ASSERT_EQ(file_fs_vnode, nullptr);
ASSERT_EQ(valid_fsync_vnode->Close(), ZX_OK);
valid_fsync_vnode = nullptr;
invalid_fsync_vnode = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 7. Unmount and check filesystem
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(FsyncRecoveryTest, Fdatasync) {
std::unique_ptr<BcacheMapper> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable roll-forward recovery
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 0), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// 1. Create file
const pgoff_t data_page_count = kAddrsPerInode + 1;
auto ret = CreateFileAndWritePages(root_dir.get(), "fsync_dnode_file", data_page_count, 0);
ASSERT_TRUE(ret.is_ok());
auto fsync_vnode = std::move(ret.value());
ino_t fsync_file_ino = fsync_vnode->Ino();
File *file = static_cast<File *>(fsync_vnode.get());
size_t out;
char r_buf[kPageSize];
ASSERT_EQ(FileTester::Read(file, r_buf, kPageSize, kAddrsPerInode * kPageSize, &out), ZX_OK);
char w_buf[kPageSize];
std::memset(w_buf, 0xFF, kPageSize);
ASSERT_EQ(FileTester::Write(file, w_buf, kPageSize, kAddrsPerInode * kPageSize, &out), ZX_OK);
// 2. Checkpoint
fs->SyncFs(true);
// 3. Write the last block that causes updates on dnode
ASSERT_EQ(FileTester::Write(file, r_buf, kPageSize, kAddrsPerInode * kPageSize, &out), ZX_OK);
// 4. Request fdatasync() to log the dnode
ASSERT_EQ(fsync_vnode->SyncFile(true), ZX_OK);
ASSERT_EQ(fsync_vnode->Close(), ZX_OK);
fsync_vnode = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
// 5. SPO and check blocks to be recovered
FileTester::SuddenPowerOff(std::move(fs), &bc);
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 1), ZX_OK);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
CheckFsyncedFile(fs.get(), fsync_file_ino, 1, 1);
// 6. SPO and check the recovery
FileTester::SuddenPowerOff(std::move(fs), &bc);
ASSERT_EQ(options.SetValue(MountOption::kDisableRollForward, 0), ZX_OK);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
} // namespace
} // namespace f2fs