blob: 8b45c4dd28593149abbb1837b2704226d6175fee [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 <numeric>
#include <vector>
#include <gtest/gtest.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 {
constexpr uint32_t kMaxNodeCnt = 10;
class NodeManagerTest : public F2fsFakeDevTestFixture {
public:
NodeManagerTest() : F2fsFakeDevTestFixture(TestOptions{.run_fsck = false}) {}
};
zx_status_t GetLockedDnodePage(VnodeF2fs &vnode, pgoff_t index, LockedPage *out) {
auto path_or = GetNodePath(vnode, index);
if (path_or.is_error()) {
return path_or.error_value();
}
auto page_or = vnode.fs()->GetNodeManager().GetLockedDnodePage(*path_or, vnode.IsDir());
if (page_or.is_ok()) {
*out = std::move(*page_or);
}
vnode.IncBlocks(path_or->num_new_nodes);
return page_or.status_value();
}
void FaultInjectToDnodeAndTruncate(NodeManager &node_manager, fbl::RefPtr<VnodeF2fs> &vnode,
pgoff_t page_index, block_t fault_address,
zx_status_t exception_type) {
ino_t node_id;
{
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, page_index, &dnode_page), ZX_OK);
node_id = dnode_page.GetPage<NodePage>().NidOfNode();
}
block_t temp_block_address;
// Write out dirty nodes to allocate lba
WritebackOperation op = {.bSync = true};
vnode->fs()->GetNodeVnode().Writeback(op);
MapTester::GetCachedNatEntryBlockAddress(node_manager, node_id, temp_block_address);
vnode->fs()->GetNodeVnode().InvalidatePages();
// Set fault_address to the NAT entry
MapTester::SetCachedNatEntryBlockAddress(node_manager, node_id, fault_address);
ASSERT_EQ(vnode->TruncateInodeBlocks(page_index), exception_type);
// Restore the NAT entry
MapTester::SetCachedNatEntryBlockAddress(node_manager, node_id, temp_block_address);
// Retry truncate
vnode->fs()->GetNodeVnode().InvalidatePages();
ASSERT_EQ(vnode->TruncateInodeBlocks(page_index), ZX_OK);
}
TEST_F(NodeManagerTest, NatCache) {
NodeManager &node_manager = fs_->GetNodeManager();
size_t num_tree = 0, num_clean = 0, num_dirty = 0;
// 1. Check NAT cache is empty
MapTester::GetNatCacheEntryCount(node_manager, num_tree, num_clean, num_dirty);
ASSERT_EQ(num_tree, 1UL);
ASSERT_EQ(num_clean, 1UL); // root inode
ASSERT_EQ(num_dirty, 0UL);
// 2. Check NAT entry is cached in dirty NAT entries list
std::vector<fbl::RefPtr<VnodeF2fs>> vnodes;
std::vector<uint32_t> inos;
// Fill NAT cache
FileTester::CreateChildren(fs_.get(), vnodes, inos, root_dir_, "NATCache_", kMaxNodeCnt);
ASSERT_EQ(vnodes.size(), kMaxNodeCnt);
ASSERT_EQ(inos.size(), kMaxNodeCnt);
MapTester::GetNatCacheEntryCount(node_manager, num_tree, num_clean, num_dirty);
ASSERT_EQ(num_tree, static_cast<size_t>(kMaxNodeCnt + 1));
ASSERT_EQ(num_clean, static_cast<size_t>(1));
ASSERT_EQ(num_dirty, static_cast<size_t>(kMaxNodeCnt));
ASSERT_EQ(node_manager.GetNatCount(), static_cast<nid_t>(kMaxNodeCnt + 1));
// Lookup NAT cache
for (auto ino : inos) {
NodeInfo ni;
ASSERT_TRUE(MapTester::IsCachedNat(node_manager, ino));
fs_->GetNodeManager().GetNodeInfo(ino, ni);
ASSERT_EQ(ni.nid, ino);
}
// Move dirty entries to clean entries
fs_->SyncFs();
// 3. Check NAT entry is cached in clean NAT entries list
MapTester::GetNatCacheEntryCount(node_manager, num_tree, num_clean, num_dirty);
ASSERT_EQ(num_tree, static_cast<size_t>(kMaxNodeCnt + 1));
ASSERT_EQ(num_clean, static_cast<size_t>(kMaxNodeCnt + 1));
ASSERT_EQ(num_dirty, static_cast<size_t>(0));
ASSERT_EQ(node_manager.GetNatCount(), static_cast<nid_t>(kMaxNodeCnt + 1));
// Lookup NAT cache
for (auto ino : inos) {
NodeInfo ni;
ASSERT_TRUE(MapTester::IsCachedNat(node_manager, ino));
fs_->GetNodeManager().GetNodeInfo(ino, ni);
ASSERT_EQ(ni.nid, ino);
}
// 4. Flush all NAT cache entries
MapTester::RemoveAllNatEntries(node_manager);
ASSERT_EQ(node_manager.GetNatCount(), static_cast<uint32_t>(0));
CursegInfo *curseg =
fs_->GetSegmentManager().CURSEG_I(CursegType::kCursegHotData); // NAT Journal
SummaryBlock *sum = &curseg->sum_blk;
ASSERT_EQ(GetSumType(&sum->footer), kSumTypeData);
MapTester::GetNatCacheEntryCount(node_manager, num_tree, num_clean, num_dirty);
ASSERT_EQ(num_tree, static_cast<size_t>(0));
ASSERT_EQ(num_clean, static_cast<size_t>(0));
ASSERT_EQ(num_dirty, static_cast<size_t>(0));
ASSERT_EQ(NatsInCursum(*sum), static_cast<int>(kMaxNodeCnt + 1));
// Lookup NAT journal
for (auto ino : inos) {
NodeInfo ni;
ASSERT_FALSE(MapTester::IsCachedNat(node_manager, ino));
fs_->GetNodeManager().GetNodeInfo(ino, ni);
ASSERT_EQ(ni.nid, ino);
}
// 5. Check NAT cache miss and journal miss
std::vector<uint32_t> journal_inos;
// Fill NAT cache with journal size -2
// Root inode NAT(nid=4) is duplicated in cache and journal, so we need to keep two empty NAT
// entries
FileTester::CreateChildren(fs_.get(), vnodes, journal_inos, root_dir_, "NATJournal_",
kNatJournalEntries - kMaxNodeCnt - 2);
ASSERT_EQ(vnodes.size(), kNatJournalEntries - 2);
ASSERT_EQ(inos.size() + journal_inos.size(), kNatJournalEntries - 2);
// Fill NAT journal
fs_->SyncFs();
ASSERT_EQ(NatsInCursum(*sum), static_cast<int>(kNatJournalEntries - 1));
// Fill NAT cache over journal size
FileTester::CreateChildren(fs_.get(), vnodes, journal_inos, root_dir_, "NATJournalFlush_", 2);
ASSERT_EQ(vnodes.size(), kNatJournalEntries);
ASSERT_EQ(inos.size() + journal_inos.size(), kNatJournalEntries);
// Flush NAT journal
fs_->SyncFs();
ASSERT_EQ(NatsInCursum(*sum), static_cast<int>(0));
// Flush NAT cache
MapTester::RemoveAllNatEntries(node_manager);
ASSERT_EQ(node_manager.GetNatCount(), static_cast<uint32_t>(0));
// Check NAT cache empty
MapTester::GetNatCacheEntryCount(node_manager, num_tree, num_clean, num_dirty);
ASSERT_EQ(num_tree, static_cast<size_t>(0));
ASSERT_EQ(num_clean, static_cast<size_t>(0));
ASSERT_EQ(num_dirty, static_cast<size_t>(0));
ASSERT_EQ(node_manager.GetNatCount(), static_cast<nid_t>(0));
// Read NAT block
for (auto ino : inos) {
NodeInfo ni;
ASSERT_FALSE(MapTester::IsCachedNat(node_manager, ino));
fs_->GetNodeManager().GetNodeInfo(ino, ni);
ASSERT_EQ(ni.nid, ino);
}
MapTester::GetNatCacheEntryCount(node_manager, num_tree, num_clean, num_dirty);
ASSERT_EQ(num_tree, static_cast<size_t>(10));
ASSERT_EQ(num_clean, static_cast<size_t>(10));
ASSERT_EQ(num_dirty, static_cast<size_t>(0));
ASSERT_EQ(node_manager.GetNatCount(), static_cast<nid_t>(10));
// Shrink nat cache to reduce memory usage (test TryToFreeNats())
MapTester::SetNatCount(node_manager, node_manager.GetNatCount() + kNmWoutThreshold * 3);
fs_->SyncFs();
MapTester::GetNatCacheEntryCount(node_manager, num_tree, num_clean, num_dirty);
ASSERT_EQ(num_tree, static_cast<size_t>(0));
ASSERT_EQ(num_clean, static_cast<size_t>(0));
ASSERT_EQ(node_manager.GetNatCount(), static_cast<uint32_t>(kNmWoutThreshold * 3));
MapTester::SetNatCount(node_manager, 0);
for (auto &vnode_refptr : vnodes) {
ASSERT_EQ(vnode_refptr->Close(), ZX_OK);
vnode_refptr.reset();
}
}
TEST_F(NodeManagerTest, FreeNid) {
NodeManager &node_manager = fs_->GetNodeManager();
auto init_fcnt = node_manager.GetFreeNidCount();
nid_t nid = MapTester::ScanFreeNidList(node_manager);
// |nid| is the last element in free nid tree.
ASSERT_EQ(nid + 1, node_manager.GetNextScanNid());
auto nid_or = fs_->GetNodeManager().AllocNid();
ASSERT_TRUE(nid_or.is_ok());
ASSERT_EQ(nid_or, static_cast<nid_t>(4));
ASSERT_EQ(node_manager.GetFreeNidCount(), init_fcnt - 1);
nid = MapTester::GetNextFreeNidInList(node_manager);
ASSERT_EQ(nid, static_cast<nid_t>(5));
nid_or = fs_->GetNodeManager().AllocNid();
ASSERT_TRUE(nid_or.is_ok());
ASSERT_EQ(*nid_or, static_cast<nid_t>(5));
ASSERT_EQ(node_manager.GetFreeNidCount(), init_fcnt - 2);
fs_->GetNodeManager().AddFreeNid(*nid_or);
nid = MapTester::GetNextFreeNidInList(node_manager);
ASSERT_EQ(nid, static_cast<nid_t>(5));
}
TEST_F(NodeManagerTest, NodePage) {
// Alloc Inode
fbl::RefPtr<VnodeF2fs> vnode;
FileTester::VnodeWithoutParent(fs_.get(), S_IFREG, vnode);
ASSERT_TRUE(vnode->NewInodePage().is_ok());
NodeManager &node_manager = fs_->GetNodeManager();
uint64_t free_node_cnt = node_manager.GetFreeNidCount();
// Inode block
// |- direct node
// |- direct node
// |- indirect node
// | `- direct node
// |- indirect node
// | `- direct node
// `- double indirect node
// `- indirect node
// `- direct node
// Check inode (level 0)
nid_t node_nid = vnode->Ino();
const pgoff_t direct_index = 1;
{
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, direct_index, &dnode_page), ZX_OK);
MapTester::CheckDnodePage(dnode_page.GetPage<NodePage>(), node_nid);
}
{
auto path_or = GetNodePath(*vnode, direct_index);
ASSERT_TRUE(path_or.is_ok());
auto dnode_page_or = fs_->GetNodeManager().FindLockedDnodePage(*path_or);
ASSERT_TRUE(dnode_page_or.is_ok());
MapTester::CheckDnodePage((*dnode_page_or).GetPage<NodePage>(), node_nid);
}
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt);
// Check direct node (level 1)
node_nid += 1;
const pgoff_t indirect_index_lv1 = direct_index + kAddrsPerInode;
{
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv1, &dnode_page), ZX_OK);
MapTester::CheckDnodePage(dnode_page.GetPage<NodePage>(), node_nid);
}
{
auto path_or = GetNodePath(*vnode, indirect_index_lv1);
ASSERT_TRUE(path_or.is_ok());
auto dnode_page_or = fs_->GetNodeManager().FindLockedDnodePage(*path_or);
ASSERT_TRUE(dnode_page_or.is_ok());
MapTester::CheckDnodePage((*dnode_page_or).GetPage<NodePage>(), node_nid);
}
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 1);
// Check indirect node (level 2)
node_nid += 2;
const pgoff_t direct_blks = kAddrsPerBlock;
const pgoff_t indirect_index_lv2 = indirect_index_lv1 + direct_blks * 2;
{
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv2, &dnode_page), ZX_OK);
MapTester::CheckDnodePage(dnode_page.GetPage<NodePage>(), node_nid);
}
{
auto path_or = GetNodePath(*vnode, indirect_index_lv2);
ASSERT_TRUE(path_or.is_ok());
auto dnode_page_or = fs_->GetNodeManager().FindLockedDnodePage(*path_or);
ASSERT_TRUE(dnode_page_or.is_ok());
MapTester::CheckDnodePage((*dnode_page_or).GetPage<NodePage>(), node_nid);
}
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 2);
// Check second indirect node (level 2)
node_nid += 2;
const pgoff_t indirect_blks = static_cast<const pgoff_t>(kAddrsPerBlock) * kNidsPerBlock;
{
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv2 + indirect_blks, &dnode_page), ZX_OK);
MapTester::CheckDnodePage(dnode_page.GetPage<NodePage>(), node_nid);
}
{
auto path_or = GetNodePath(*vnode, indirect_index_lv2 + indirect_blks);
ASSERT_TRUE(path_or.is_ok());
auto dnode_page_or = fs_->GetNodeManager().FindLockedDnodePage(*path_or);
ASSERT_TRUE(dnode_page_or.is_ok());
MapTester::CheckDnodePage((*dnode_page_or).GetPage<NodePage>(), node_nid);
}
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 2);
// Check double indirect node (level 3)
node_nid += 3;
pgoff_t indirect_index_lv3 = indirect_index_lv2 + indirect_blks * 2;
{
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv3, &dnode_page), ZX_OK);
MapTester::CheckDnodePage(dnode_page.GetPage<NodePage>(), node_nid);
}
{
auto path_or = GetNodePath(*vnode, indirect_index_lv3);
ASSERT_TRUE(path_or.is_ok());
auto dnode_page_or = fs_->GetNodeManager().FindLockedDnodePage(*path_or);
ASSERT_TRUE(dnode_page_or.is_ok());
MapTester::CheckDnodePage((*dnode_page_or).GetPage<NodePage>(), node_nid);
}
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 3);
vnode->SetBlocks(0);
ASSERT_EQ(vnode->Close(), ZX_OK);
vnode.reset();
}
TEST_F(NodeManagerTest, NodePageExceptionCase) {
// Alloc Inode
fbl::RefPtr<VnodeF2fs> vnode;
FileTester::VnodeWithoutParent(fs_.get(), S_IFREG, vnode);
ASSERT_TRUE(vnode->NewInodePage().is_ok());
NodeManager &node_manager = fs_->GetNodeManager();
SuperblockInfo &superblock_info = fs_->GetSuperblockInfo();
// Inode block
// |- direct node
// |- direct node
// |- indirect node
// | `- direct node
// |- indirect node
// | `- direct node
// `- double indirect node
// `- indirect node
// `- direct node
// Check inode (level 0)
const pgoff_t direct_index = 1;
const pgoff_t direct_blks = kAddrsPerBlock;
const pgoff_t indirect_blks = static_cast<const pgoff_t>(kAddrsPerBlock) * kNidsPerBlock;
const pgoff_t indirect_index_lv1 = direct_index + kAddrsPerInode;
const pgoff_t indirect_index_lv2 = indirect_index_lv1 + direct_blks * 2;
const pgoff_t indirect_index_lv3 = indirect_index_lv2 + indirect_blks * 2;
// Check invalid page offset exception case
pgoff_t indirect_index_invalid_lv4 = indirect_index_lv3 + indirect_blks * kNidsPerBlock;
{
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_invalid_lv4, &dnode_page),
ZX_ERR_NOT_FOUND);
}
// Check invalid address
nid_t nid;
{
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv3 + 1, &dnode_page), ZX_OK);
nid = dnode_page.GetPage<NodePage>().NidOfNode();
}
// fault injection for GetNodePage()
fs_->SyncFs();
MapTester::SetCachedNatEntryBlockAddress(node_manager, nid, kNullAddr);
{
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv3, &dnode_page), ZX_ERR_NOT_FOUND);
}
// Check IncValidNodeCount() exception case
block_t tmp_total_valid_block_count = superblock_info.GetValidBlockCount();
superblock_info.SetValidBlockCount(superblock_info.GetTotalBlockCount());
{
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv1 + direct_blks, &dnode_page),
ZX_ERR_NO_SPACE);
}
superblock_info.SetValidBlockCount(tmp_total_valid_block_count);
block_t tmp_total_valid_node_count = superblock_info.GetValidNodeCount();
superblock_info.SetValidNodeCount(superblock_info.GetMaxNodeCount());
{
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv1 + direct_blks, &dnode_page),
ZX_ERR_NO_SPACE);
}
superblock_info.SetValidNodeCount(tmp_total_valid_node_count);
// Check NewNodePage() exception case
fbl::RefPtr<VnodeF2fs> test_vnode;
FileTester::VnodeWithoutParent(fs_.get(), S_IFREG, test_vnode);
test_vnode->SetFlag(InodeInfoFlag::kNoAlloc);
ASSERT_EQ(test_vnode->NewInodePage().status_value(), ZX_ERR_ACCESS_DENIED);
test_vnode->ClearFlag(InodeInfoFlag::kNoAlloc);
tmp_total_valid_block_count = superblock_info.GetValidBlockCount();
superblock_info.SetValidBlockCount(superblock_info.GetTotalBlockCount());
ASSERT_EQ(test_vnode->NewInodePage().status_value(), ZX_ERR_NO_SPACE);
ASSERT_EQ(test_vnode->Close(), ZX_OK);
test_vnode.reset();
superblock_info.SetValidBlockCount(tmp_total_valid_block_count);
vnode->SetBlocks(0);
// Check MaxNid
const Superblock &sb_raw = superblock_info.GetSuperblock();
uint32_t nat_segs = LeToCpu(sb_raw.segment_count_nat) >> 1;
uint32_t nat_blocks = nat_segs << LeToCpu(sb_raw.log_blocks_per_seg);
ASSERT_EQ(fs_->GetNodeManager().GetMaxNid(), kNatEntryPerBlock * nat_blocks);
ASSERT_EQ(vnode->Close(), ZX_OK);
vnode.reset();
}
TEST_F(NodeManagerTest, TruncateDoubleIndirect) {
// Alloc Inode
fbl::RefPtr<VnodeF2fs> vnode;
FileTester::VnodeWithoutParent(fs_.get(), S_IFREG, vnode);
ASSERT_TRUE(vnode->NewInodePage().is_ok());
SuperblockInfo &superblock_info = fs_->GetSuperblockInfo();
// Inode block
// |- direct node
// |- direct node
// |- indirect node
// | `- direct node
// |- indirect node
// | `- direct node
// `- double indirect node
// `- indirect node
// `- direct node
// Alloc a double indirect node (level 3)
const pgoff_t direct_blks = kAddrsPerBlock;
const pgoff_t indirect_blks = static_cast<const pgoff_t>(kAddrsPerBlock) * kNidsPerBlock;
const pgoff_t direct_index = kAddrsPerInode + 1;
const pgoff_t indirect_index = direct_index + direct_blks * 2;
const pgoff_t double_indirect_index = indirect_index + indirect_blks * 2;
const uint32_t inode_cnt = 2;
ASSERT_EQ(superblock_info.GetValidInodeCount(), inode_cnt);
ASSERT_EQ(superblock_info.GetValidNodeCount(), inode_cnt);
std::vector<nid_t> nids;
NodeManager &node_manager = fs_->GetNodeManager();
uint64_t initial_free_nid_cnt = node_manager.GetFreeNidCount();
// Alloc a direct node at double_indirect_index
{
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, double_indirect_index, &dnode_page), ZX_OK);
nids.push_back(dnode_page.GetPage<NodePage>().NidOfNode());
}
// # of alloc nodes = 1 double indirect + 1 indirect + 1 direct
uint32_t alloc_node_cnt = 3;
uint32_t node_cnt = inode_cnt + alloc_node_cnt;
// alloc_dnode cnt should be one
ASSERT_EQ(nids.size(), 1UL);
ASSERT_EQ(superblock_info.GetValidInodeCount(), inode_cnt);
ASSERT_EQ(superblock_info.GetValidNodeCount(), node_cnt);
// Truncate double the indirect node
ASSERT_EQ(vnode->TruncateInodeBlocks(double_indirect_index), ZX_OK);
node_cnt = inode_cnt;
ASSERT_EQ(superblock_info.GetValidNodeCount(), node_cnt);
MapTester::RemoveTruncatedNode(node_manager, nids);
ASSERT_EQ(nids.size(), 0UL);
ASSERT_EQ(node_manager.GetFreeNidCount(), initial_free_nid_cnt - alloc_node_cnt);
fs_->SyncFs();
// After checkpoint, we can reuse the removed nodes
ASSERT_EQ(node_manager.GetFreeNidCount(), initial_free_nid_cnt);
ASSERT_EQ(vnode->Close(), ZX_OK);
vnode.reset();
}
TEST_F(NodeManagerTest, TruncateIndirect) {
// Alloc Inode
fbl::RefPtr<VnodeF2fs> vnode;
FileTester::VnodeWithoutParent(fs_.get(), S_IFREG, vnode);
ASSERT_TRUE(vnode->NewInodePage().is_ok());
SuperblockInfo &superblock_info = fs_->GetSuperblockInfo();
// Inode block
// |- direct node
// |- direct node
// |- indirect node
// | `- direct node
// Fill indirect node (level 2)
const pgoff_t direct_blks = kAddrsPerBlock;
const pgoff_t direct_index = kAddrsPerInode + 1;
const pgoff_t indirect_index = direct_index + direct_blks * 2;
const uint32_t inode_cnt = 2;
ASSERT_EQ(superblock_info.GetValidInodeCount(), inode_cnt);
ASSERT_EQ(superblock_info.GetValidNodeCount(), inode_cnt);
std::vector<nid_t> nids;
NodeManager &node_manager = fs_->GetNodeManager();
uint64_t initial_free_nid_cnt = node_manager.GetFreeNidCount();
// Start from kAddrsPerInode to alloc new dnodes
for (pgoff_t i = kAddrsPerInode; i <= indirect_index; i += kAddrsPerBlock) {
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, i, &dnode_page), ZX_OK);
nids.push_back(dnode_page.GetPage<NodePage>().NidOfNode());
}
uint32_t indirect_node_cnt = 1;
uint32_t direct_node_cnt = 3;
uint32_t node_cnt = inode_cnt + direct_node_cnt + indirect_node_cnt;
uint32_t alloc_node_cnt = indirect_node_cnt + direct_node_cnt;
ASSERT_EQ(nids.size(), direct_node_cnt);
ASSERT_EQ(superblock_info.GetValidInodeCount(), inode_cnt);
ASSERT_EQ(superblock_info.GetValidNodeCount(), node_cnt);
// Truncate indirect nodes
ASSERT_EQ(vnode->TruncateInodeBlocks(indirect_index), ZX_OK);
--indirect_node_cnt;
--direct_node_cnt;
node_cnt = inode_cnt + direct_node_cnt + indirect_node_cnt;
ASSERT_EQ(superblock_info.GetValidNodeCount(), node_cnt);
MapTester::RemoveTruncatedNode(node_manager, nids);
ASSERT_EQ(nids.size(), direct_node_cnt);
// Truncate direct nodes
ASSERT_EQ(vnode->TruncateInodeBlocks(direct_index), ZX_OK);
direct_node_cnt -= 2;
node_cnt = inode_cnt + direct_node_cnt + indirect_node_cnt;
ASSERT_EQ(superblock_info.GetValidNodeCount(), node_cnt);
MapTester::RemoveTruncatedNode(node_manager, nids);
ASSERT_EQ(nids.size(), direct_node_cnt);
ASSERT_EQ(superblock_info.GetValidInodeCount(), inode_cnt);
ASSERT_EQ(node_manager.GetFreeNidCount(), initial_free_nid_cnt - alloc_node_cnt);
fs_->SyncFs();
// After checkpoint, we can reuse the removed nodes
ASSERT_EQ(node_manager.GetFreeNidCount(), initial_free_nid_cnt);
ASSERT_EQ(vnode->Close(), ZX_OK);
vnode.reset();
}
TEST_F(NodeManagerTest, TruncateExceptionCase) {
// Alloc Inode
fbl::RefPtr<VnodeF2fs> vnode;
FileTester::VnodeWithoutParent(fs_.get(), S_IFREG, vnode);
ASSERT_TRUE(vnode->NewInodePage().is_ok());
SuperblockInfo &superblock_info = fs_->GetSuperblockInfo();
// Inode block
// |- direct node
// |- direct node
// Fill direct node (level 1)
const uint32_t inode_cnt = 2;
ASSERT_EQ(superblock_info.GetValidInodeCount(), inode_cnt);
ASSERT_EQ(superblock_info.GetValidNodeCount(), inode_cnt);
const pgoff_t direct_index = 1;
const pgoff_t direct_blks = kAddrsPerBlock;
const pgoff_t indirect_blks = static_cast<const pgoff_t>(kAddrsPerBlock) * kNidsPerBlock;
const pgoff_t indirect_index_lv1 = direct_index + kAddrsPerInode;
const pgoff_t indirect_index_lv1_2nd = indirect_index_lv1 + direct_blks;
const pgoff_t indirect_index_lv2 = indirect_index_lv1 + direct_blks * 2;
const pgoff_t indirect_index_lv3 = indirect_index_lv2 + indirect_blks * 2;
// Check invalid page offset exception case
pgoff_t indirect_index_invalid_lv4 = indirect_index_lv3 + indirect_blks * kNidsPerBlock;
std::vector<nid_t> nids;
NodeManager &node_manager = fs_->GetNodeManager();
uint64_t initial_free_nid_cnt = node_manager.GetFreeNidCount();
// Start from kAddrsPerInode to alloc new dnodes
for (pgoff_t i = kAddrsPerInode; i <= indirect_index_lv3 + kNidsPerBlock; i += kAddrsPerBlock) {
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, i, &dnode_page), ZX_OK);
nids.push_back(dnode_page.GetPage<NodePage>().NidOfNode());
}
uint32_t direct_node_cnt = 4 + kNidsPerBlock * 2;
uint32_t indirect_node_cnt = 4; // 1 double indirect + 3 indirect
uint32_t node_cnt = inode_cnt + direct_node_cnt + indirect_node_cnt;
ASSERT_EQ(nids.size(), direct_node_cnt);
ASSERT_EQ(superblock_info.GetValidInodeCount(), inode_cnt);
ASSERT_EQ(superblock_info.GetValidNodeCount(), node_cnt);
// 1. Truncate invalid node
ASSERT_EQ(vnode->TruncateInodeBlocks(indirect_index_invalid_lv4), ZX_ERR_NOT_FOUND);
block_t fault_addr = kNewAddr - 1;
// 2. Check exception case of TruncatePartialNodes()
FaultInjectToDnodeAndTruncate(node_manager, vnode, indirect_index_lv3 + kNidsPerBlock, fault_addr,
ZX_ERR_OUT_OF_RANGE);
FaultInjectToDnodeAndTruncate(node_manager, vnode, indirect_index_lv2 + kNidsPerBlock, fault_addr,
ZX_ERR_OUT_OF_RANGE);
--indirect_node_cnt;
// 3. Check exception case of TruncateNodes()
FaultInjectToDnodeAndTruncate(node_manager, vnode, indirect_index_lv3, fault_addr,
ZX_ERR_OUT_OF_RANGE);
FaultInjectToDnodeAndTruncate(node_manager, vnode, indirect_index_lv2, fault_addr,
ZX_ERR_OUT_OF_RANGE);
--indirect_node_cnt;
// 4. Check exception case of TruncateDnode()
FaultInjectToDnodeAndTruncate(node_manager, vnode, indirect_index_lv1_2nd, fault_addr,
ZX_ERR_OUT_OF_RANGE);
--indirect_node_cnt;
// 5. Check exception case truncation of invalid address
FaultInjectToDnodeAndTruncate(node_manager, vnode, indirect_index_lv1, kNullAddr, ZX_OK);
--indirect_node_cnt;
node_cnt = inode_cnt + indirect_node_cnt;
ASSERT_EQ(superblock_info.GetValidNodeCount(), node_cnt);
// 6. Wrap up
MapTester::RemoveTruncatedNode(node_manager, nids);
ASSERT_EQ(nids.size(), 0UL);
ASSERT_EQ(superblock_info.GetValidInodeCount(), inode_cnt);
fs_->SyncFs();
// After checkpoint, we can reuse the removed nodes
ASSERT_EQ(node_manager.GetFreeNidCount(), initial_free_nid_cnt);
ASSERT_EQ(vnode->Close(), ZX_OK);
vnode.reset();
}
TEST_F(NodeManagerTest, NodeFooter) {
// Alloc Inode
fbl::RefPtr<VnodeF2fs> vnode;
FileTester::VnodeWithoutParent(fs_.get(), S_IFREG, vnode);
ASSERT_TRUE(vnode->NewInodePage().is_ok());
nid_t inode_nid = vnode->Ino();
{
const pgoff_t direct_index = 1;
LockedPage locked_dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, direct_index, &locked_dnode_page), ZX_OK);
NodePage *dnode_page = &locked_dnode_page.GetPage<NodePage>();
MapTester::CheckDnodePage(*dnode_page, inode_nid);
LockedPage locked_page;
fs_->GetNodeVnode().GrabCachePage(direct_index, &locked_page);
NodePage *page = &locked_page.GetPage<NodePage>();
// Check CopyNodeFooterFrom()
page->CopyNodeFooterFrom(*dnode_page);
ASSERT_EQ(page->InoOfNode(), vnode->Ino());
ASSERT_EQ(page->InoOfNode(), dnode_page->InoOfNode());
ASSERT_EQ(page->NidOfNode(), dnode_page->NidOfNode());
ASSERT_EQ(page->OfsOfNode(), dnode_page->OfsOfNode());
ASSERT_EQ(page->CpverOfNode(), dnode_page->CpverOfNode());
ASSERT_EQ(page->NextBlkaddrOfNode(), dnode_page->NextBlkaddrOfNode());
// Check footer.flag
ASSERT_EQ(page->IsFsyncDnode(), dnode_page->IsFsyncDnode());
ASSERT_EQ(page->IsFsyncDnode(), false);
page->SetFsyncMark(true);
ASSERT_EQ(page->IsFsyncDnode(), true);
page->SetFsyncMark(false);
ASSERT_EQ(page->IsFsyncDnode(), false);
ASSERT_EQ(page->IsDentDnode(), dnode_page->IsDentDnode());
ASSERT_EQ(page->IsDentDnode(), false);
page->SetDentryMark(false);
ASSERT_EQ(page->IsDentDnode(), false);
page->SetDentryMark(true);
ASSERT_EQ(page->IsDentDnode(), true);
bool mark = !fs_->GetNodeManager().IsCheckpointedNode(page->InoOfNode());
page->SetDentryMark(mark);
ASSERT_EQ(page->IsDentDnode(), true);
MapTester::SetCachedNatEntryCheckpointed(fs_->GetNodeManager(), dnode_page->NidOfNode());
mark = !fs_->GetNodeManager().IsCheckpointedNode(page->InoOfNode());
page->SetDentryMark(mark);
ASSERT_EQ(page->IsDentDnode(), false);
}
ASSERT_EQ(vnode->Close(), ZX_OK);
vnode.reset();
}
TEST_F(NodeManagerTest, GetDataBlockAddressesSinglePage) {
// Alloc inode
fbl::RefPtr<VnodeF2fs> file_vnode;
FileTester::VnodeWithoutParent(fs_.get(), S_IFREG, file_vnode);
auto vnode = fbl::RefPtr<File>::Downcast(std::move(file_vnode));
ASSERT_TRUE(vnode->NewInodePage().is_ok());
NodeManager &node_manager = fs_->GetNodeManager();
uint64_t free_node_cnt = node_manager.GetFreeNidCount();
uint8_t buf[kPageSize] = {1};
size_t out_actual;
// Inode block
// |- direct node
// |- direct node
// |- indirect node
// | `- direct node
// |- indirect node
// | `- direct node
// `- double indirect node
// `- indirect node
// `- direct node
// Check inode (level 0)
const pgoff_t direct_index = 0;
pgoff_t file_offset = direct_index * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), direct_index, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or = vnode->GetDataBlockAddresses(direct_index, 1);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt);
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, direct_index, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses.front());
}
// Check direct node (level 1)
const pgoff_t indirect_index_lv1 = direct_index + kAddrsPerInode;
file_offset = indirect_index_lv1 * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or = vnode->GetDataBlockAddresses(indirect_index_lv1, 1);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 1);
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv1, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses.front());
}
// Check indirect node (level 2)
const pgoff_t direct_blks = kAddrsPerBlock;
const pgoff_t indirect_index_lv2 = indirect_index_lv1 + direct_blks * 2;
file_offset = indirect_index_lv2 * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or = vnode->GetDataBlockAddresses(indirect_index_lv2, 1);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 2);
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv2, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses.front());
}
// Check second indirect node (level 2)
const pgoff_t indirect_blks = static_cast<const pgoff_t>(kAddrsPerBlock) * kNidsPerBlock;
file_offset = (indirect_index_lv2 + indirect_blks) * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or = vnode->GetDataBlockAddresses(indirect_index_lv2 + indirect_blks, 1);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 2);
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv2 + indirect_blks, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses.front());
}
// Check double indirect node (level 3)
pgoff_t indirect_index_lv3 = indirect_index_lv2 + indirect_blks * 2;
file_offset = indirect_index_lv3 * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or = vnode->GetDataBlockAddresses(indirect_index_lv3, 1);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 3);
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv3, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses.front());
}
vnode->SetBlocks(0);
ASSERT_EQ(vnode->Close(), ZX_OK);
vnode.reset();
}
TEST_F(NodeManagerTest, GetDataBlockAddressesMultiPage) {
// Alloc inode
fbl::RefPtr<VnodeF2fs> file_vnode;
FileTester::VnodeWithoutParent(fs_.get(), S_IFREG, file_vnode);
auto vnode = fbl::RefPtr<File>::Downcast(std::move(file_vnode));
ASSERT_TRUE(vnode->NewInodePage().is_ok());
NodeManager &node_manager = fs_->GetNodeManager();
uint64_t free_node_cnt = node_manager.GetFreeNidCount();
uint8_t buf[kPageSize * 2] = {1};
size_t out_actual;
// Inode block
// |- direct node
// |- direct node
// |- indirect node
// | `- direct node
// |- indirect node
// | `- direct node
// `- double indirect node
// `- indirect node
// `- direct node
// Check inode (level 0)
const pgoff_t direct_index = 0;
pgoff_t file_offset = direct_index * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or = vnode->GetDataBlockAddresses(direct_index, 2);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(block_addresses.size(), 2u);
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt); // inode dnode
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, direct_index, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses[0]);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(1ULL), block_addresses[1]);
}
// Check direct node (level 1)
const pgoff_t indirect_index_lv1 = direct_index + kAddrsPerInode;
file_offset = indirect_index_lv1 * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or = vnode->GetDataBlockAddresses(indirect_index_lv1, 2);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(block_addresses.size(), 2u);
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 1); // direct node
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv1, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses[0]);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(1ULL), block_addresses[1]);
}
// Check indirect node (level 2)
const pgoff_t direct_blks = kAddrsPerBlock;
const pgoff_t indirect_index_lv2 = indirect_index_lv1 + direct_blks * 2;
file_offset = indirect_index_lv2 * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or = vnode->GetDataBlockAddresses(indirect_index_lv2, 2);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(block_addresses.size(), 2u);
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 2); // indirect + direct node
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv2, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses[0]);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(1ULL), block_addresses[1]);
}
// Check second indirect node (level 2)
const pgoff_t indirect_blks = static_cast<const pgoff_t>(kAddrsPerBlock) * kNidsPerBlock;
file_offset = (indirect_index_lv2 + indirect_blks) * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or = vnode->GetDataBlockAddresses(indirect_index_lv2 + indirect_blks, 2);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(block_addresses.size(), 2u);
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 2); // indirect + direct node
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv2 + indirect_blks, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses[0]);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(1ULL), block_addresses[1]);
}
// Check double indirect node (level 3)
pgoff_t indirect_index_lv3 = indirect_index_lv2 + indirect_blks * 2;
file_offset = indirect_index_lv3 * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or = vnode->GetDataBlockAddresses(indirect_index_lv3, 2);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(block_addresses.size(), 2u);
ASSERT_EQ(node_manager.GetFreeNidCount(),
free_node_cnt -= 3); // double indirect + indirect + direct dnode
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv3, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses[0]);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(1ULL), block_addresses[1]);
}
vnode->SetBlocks(0);
ASSERT_EQ(vnode->Close(), ZX_OK);
vnode.reset();
}
TEST_F(NodeManagerTest, GetDataBlockAddressesCrossMultiPage) {
// Alloc inode
fbl::RefPtr<VnodeF2fs> file_vnode;
FileTester::VnodeWithoutParent(fs_.get(), S_IFREG, file_vnode);
auto vnode = fbl::RefPtr<File>::Downcast(std::move(file_vnode));
ASSERT_TRUE(vnode->NewInodePage().is_ok());
NodeManager &node_manager = fs_->GetNodeManager();
uint64_t free_node_cnt = node_manager.GetFreeNidCount();
uint8_t buf[kPageSize * 2] = {1};
size_t out_actual;
// Inode block
// |- direct node
// |- direct node
// |- indirect node
// | `- direct node
// |- indirect node
// | `- direct node
// `- double indirect node
// `- indirect node
// `- direct node
// Check inode + direct node (level 0 ~ 1)
pgoff_t file_offset = static_cast<pgoff_t>((kAddrsPerInode - 1)) * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or = vnode->GetDataBlockAddresses(kAddrsPerInode - 1, 2);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(block_addresses.size(), 2u);
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 1); // direct node
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, kAddrsPerInode - 1, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(kAddrsPerInode - 1), block_addresses[0]);
dnode_page.reset();
ASSERT_EQ(GetLockedDnodePage(*vnode, kAddrsPerInode, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses[1]);
}
// Check direct node + direct node (level 1)
const pgoff_t direct_blks = kAddrsPerBlock;
const pgoff_t indirect_index_lv1 = kAddrsPerInode;
file_offset = (indirect_index_lv1 + direct_blks - 1) * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or = vnode->GetDataBlockAddresses(indirect_index_lv1 + direct_blks - 1, 2);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(block_addresses.size(), 2u);
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 1); // direct dnode
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv1 + direct_blks - 1, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(kAddrsPerBlock - 1), block_addresses[0]);
dnode_page.reset();
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv1 + direct_blks, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses[1]);
}
// Check direct node + indirect node (level 1 ~ 2)
file_offset = (indirect_index_lv1 + direct_blks * 2 - 1) * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or =
vnode->GetDataBlockAddresses(indirect_index_lv1 + direct_blks * 2 - 1, 2);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(block_addresses.size(), 2u);
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 2); // indirect + direct node
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv1 + direct_blks * 2 - 1, &dnode_page),
ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(kAddrsPerBlock - 1), block_addresses[0]);
dnode_page.reset();
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv1 + direct_blks * 2, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses[1]);
}
// Check indirect node (level 2)
const pgoff_t indirect_index_lv2 = indirect_index_lv1 + direct_blks * kAddrsPerBlock;
file_offset = (indirect_index_lv2 - 1) * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or = vnode->GetDataBlockAddresses(indirect_index_lv2 - 1, 2);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(block_addresses.size(), 2u);
ASSERT_EQ(node_manager.GetFreeNidCount(), free_node_cnt -= 2); // direct node
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv2 - 1, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(kAddrsPerBlock - 1), block_addresses[0]);
dnode_page.reset();
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv2, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses[1]);
}
// Check second indirect node (level 3)
const pgoff_t indirect_blks = static_cast<const pgoff_t>(kAddrsPerBlock) * kNidsPerBlock;
file_offset = (indirect_index_lv2 + indirect_blks + direct_blks - 1) * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or =
vnode->GetDataBlockAddresses(indirect_index_lv2 + indirect_blks + direct_blks - 1, 2);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(block_addresses.size(), 2u);
ASSERT_EQ(node_manager.GetFreeNidCount(),
free_node_cnt -= 3); // direct node + indirect + direct node
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv2 + indirect_blks + direct_blks - 1,
&dnode_page),
ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(kAddrsPerBlock - 1), block_addresses[0]);
dnode_page.reset();
ASSERT_EQ(
GetLockedDnodePage(*vnode, indirect_index_lv2 + indirect_blks + direct_blks, &dnode_page),
ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses[1]);
}
// Check double indirect node (level 2 ~ 3)
pgoff_t indirect_index_lv3 = indirect_index_lv2 + indirect_blks * 2;
file_offset = (indirect_index_lv3 + direct_blks - 1) * kBlockSize;
{
vnode->Truncate(file_offset);
ASSERT_EQ(FileTester::Write(vnode.get(), buf, sizeof(buf), file_offset, &out_actual), ZX_OK);
ASSERT_EQ(vnode->SyncFile(false), ZX_OK);
auto block_addresses_or = vnode->GetDataBlockAddresses(indirect_index_lv3 + direct_blks - 1, 2);
ASSERT_TRUE(block_addresses_or.is_ok());
auto block_addresses = block_addresses_or.value();
ASSERT_EQ(block_addresses.size(), 2u);
ASSERT_EQ(node_manager.GetFreeNidCount(),
free_node_cnt -= 4); // direct node + double indirect + indirect + direct node
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv3 + direct_blks - 1, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(kAddrsPerBlock - 1), block_addresses[0]);
dnode_page.reset();
ASSERT_EQ(GetLockedDnodePage(*vnode, indirect_index_lv3 + direct_blks, &dnode_page), ZX_OK);
ASSERT_EQ(dnode_page.GetPage<NodePage>().GetBlockAddr(0ULL), block_addresses[1]);
}
vnode->SetBlocks(0);
ASSERT_EQ(vnode->Close(), ZX_OK);
vnode.reset();
}
TEST_F(NodeManagerTest, DnodeBidxConsistency) {
// To test |StartBidxOfNode|, kTargetOffset must be bigger than kAddrsPerInode, which is 923.
constexpr pgoff_t kTargetOffset = 30000U;
zx::result test_file = root_dir_->Create("test", fs::CreationType::kFile);
ASSERT_TRUE(test_file.is_ok()) << test_file.status_string();
fbl::RefPtr<f2fs::File> vn = fbl::RefPtr<f2fs::File>::Downcast(*std::move(test_file));
auto path_or = GetNodePath(*vn, kTargetOffset);
ASSERT_TRUE(path_or.is_ok());
auto ofs_in_node_or = GetOfsInDnode(*path_or);
size_t start_bidx_of_node;
{
LockedPage dnode_page;
ASSERT_EQ(GetLockedDnodePage(*vn, kTargetOffset, &dnode_page), ZX_OK);
start_bidx_of_node = dnode_page.GetPage<NodePage>().StartBidxOfNode(vn->GetAddrsPerInode());
}
ASSERT_EQ(start_bidx_of_node + ofs_in_node_or, kTargetOffset);
vn->Close();
vn = nullptr;
}
} // namespace
} // namespace f2fs