| // 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 <unordered_set> |
| |
| #include <gtest/gtest.h> |
| #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 { |
| |
| using Runner = ComponentRunner; |
| |
| class SegmentManagerTest : public F2fsFakeDevTestFixture { |
| public: |
| SegmentManagerTest() {} |
| |
| protected: |
| void MakeDirtySegments(size_t invalidate_ratio, int num_files) { |
| fs_->GetGcManager().DisableFgGc(); |
| for (int file_no = 0; file_no < num_files; ++file_no) { |
| zx::result test_file = root_dir_->Create(std::to_string(file_num++), fs::CreationType::kFile); |
| ASSERT_TRUE(test_file.is_ok()) << test_file.status_string(); |
| auto vnode = fbl::RefPtr<File>::Downcast(*std::move(test_file)); |
| std::array<char, kPageSize> buf; |
| std::memset(buf.data(), file_no, buf.size()); |
| for (size_t i = 0; i < fs_->GetSuperblockInfo().GetBlocksPerSeg(); ++i) { |
| FileTester::AppendToFile(vnode.get(), buf.data(), buf.size()); |
| } |
| vnode->SyncFile(false); |
| size_t truncate_size = vnode->GetSize() * (100 - invalidate_ratio) / 100; |
| EXPECT_EQ(vnode->Truncate(truncate_size), ZX_OK); |
| vnode->Close(); |
| } |
| fs_->SyncFs(); |
| fs_->GetGcManager().EnableFgGc(); |
| } |
| |
| private: |
| int file_num = 0; |
| }; |
| |
| TEST_F(SegmentManagerTest, BlkChaining) { |
| SuperblockInfo &superblock_info = fs_->GetSuperblockInfo(); |
| std::vector<block_t> blk_chain(0); |
| int nwritten = kDefaultBlocksPerSegment * 2; |
| // write the root inode, and read the block where the previous version of the root inode is stored |
| // to check if the block has a proper lba address to the next node block |
| for (int i = 0; i < nwritten; ++i) { |
| NodeInfo ni; |
| { |
| LockedPage read_page; |
| fs_->GetNodeManager().GetNodePage(superblock_info.GetRootIno(), &read_page); |
| blk_chain.push_back(read_page.GetPage<NodePage>().NextBlkaddrOfNode()); |
| read_page.SetDirty(); |
| } |
| WritebackOperation op = {.bSync = true}; |
| fs_->GetNodeVnode().Writeback(op); |
| |
| fs_->GetNodeManager().GetNodeInfo(superblock_info.GetRootIno(), ni); |
| ASSERT_NE(ni.blk_addr, kNullAddr); |
| ASSERT_NE(ni.blk_addr, kNewAddr); |
| ASSERT_EQ(ni.blk_addr, blk_chain[i]); |
| } |
| } |
| |
| TEST_F(SegmentManagerTest, DirtyToFree) TA_NO_THREAD_SAFETY_ANALYSIS { |
| SuperblockInfo &superblock_info = fs_->GetSuperblockInfo(); |
| |
| // check the precond. before making dirty segments |
| std::vector<uint32_t> prefree_array(0); |
| int nwritten = kDefaultBlocksPerSegment * 2; |
| uint32_t nprefree = 0; |
| ASSERT_FALSE(fs_->GetSegmentManager().PrefreeSegments()); |
| uint32_t nfree_segs = fs_->GetSegmentManager().FreeSegments(); |
| FreeSegmapInfo *free_i = &fs_->GetSegmentManager().GetFreeSegmentInfo(); |
| DirtySeglistInfo *dirty_i = &fs_->GetSegmentManager().GetDirtySegmentInfo(); |
| |
| // write the root inode repeatedly as much as 2 segments |
| for (int i = 0; i < nwritten; ++i) { |
| NodeInfo ni; |
| fs_->GetNodeManager().GetNodeInfo(superblock_info.GetRootIno(), ni); |
| ASSERT_NE(ni.blk_addr, kNullAddr); |
| ASSERT_NE(ni.blk_addr, kNewAddr); |
| block_t old_addr = ni.blk_addr; |
| |
| { |
| LockedPage read_page; |
| fs_->GetNodeManager().GetNodePage(superblock_info.GetRootIno(), &read_page); |
| read_page.SetDirty(); |
| } |
| |
| WritebackOperation op = {.bSync = true}; |
| fs_->GetNodeVnode().Writeback(op); |
| |
| if (fs_->GetSegmentManager().GetValidBlocks(fs_->GetSegmentManager().GetSegmentNumber(old_addr), |
| 0) == 0) { |
| prefree_array.push_back(fs_->GetSegmentManager().GetSegmentNumber(old_addr)); |
| ASSERT_EQ(fs_->GetSegmentManager().PrefreeSegments(), ++nprefree); |
| } |
| } |
| |
| // check the bitmaps and the number of free/prefree segments |
| ASSERT_EQ(fs_->GetSegmentManager().FreeSegments(), nfree_segs - nprefree); |
| for (auto &pre : prefree_array) { |
| ASSERT_TRUE(dirty_i->dirty_segmap[static_cast<int>(DirtyType::kPre)].GetOne(pre)); |
| ASSERT_TRUE(free_i->free_segmap.GetOne(pre)); |
| } |
| // triggers checkpoint to make prefree segments transit to free ones |
| fs_->SyncFs(); |
| |
| // check the bitmaps and the number of free/prefree segments |
| for (auto &pre : prefree_array) { |
| ASSERT_FALSE(free_i->free_segmap.GetOne(pre)); |
| ASSERT_FALSE(dirty_i->dirty_segmap[static_cast<int>(DirtyType::kPre)].GetOne(pre)); |
| } |
| ASSERT_EQ(fs_->GetSegmentManager().FreeSegments(), nfree_segs); |
| ASSERT_FALSE(fs_->GetSegmentManager().PrefreeSegments()); |
| } |
| |
| TEST_F(SegmentManagerTest, BalanceFs) TA_NO_THREAD_SAFETY_ANALYSIS { |
| uint32_t nfree_segs = fs_->GetSegmentManager().FreeSegments(); |
| |
| fs_->ClearOnRecovery(); |
| fs_->GetSegmentManager().BalanceFs(); |
| |
| ASSERT_EQ(fs_->GetSegmentManager().FreeSegments(), nfree_segs); |
| ASSERT_FALSE(fs_->GetSegmentManager().PrefreeSegments()); |
| |
| fs_->SetOnRecovery(); |
| fs_->GetSegmentManager().BalanceFs(); |
| |
| ASSERT_EQ(fs_->GetSegmentManager().FreeSegments(), nfree_segs); |
| ASSERT_FALSE(fs_->GetSegmentManager().PrefreeSegments()); |
| } |
| |
| TEST_F(SegmentManagerTest, InvalidateBlocksExceptionCase) { |
| // read the root inode block |
| LockedPage root_node_page; |
| SuperblockInfo &superblock_info = fs_->GetSuperblockInfo(); |
| fs_->GetNodeManager().GetNodePage(superblock_info.GetRootIno(), &root_node_page); |
| ASSERT_NE(root_node_page, nullptr); |
| |
| // Check InvalidateBlocks() exception case |
| block_t temp_written_valid_blocks = fs_->GetSegmentManager().GetSitInfo().written_valid_blocks; |
| fs_->GetSegmentManager().InvalidateBlocks(kNewAddr); |
| ASSERT_EQ(temp_written_valid_blocks, fs_->GetSegmentManager().GetSitInfo().written_valid_blocks); |
| } |
| |
| TEST_F(SegmentManagerTest, GetNewSegmentHeap) { |
| SuperblockInfo &superblock_info = fs_->GetSuperblockInfo(); |
| |
| // Check GetNewSegment() on AllocDirection::kAllocLeft |
| superblock_info.ClearOpt(MountOption::kNoHeap); |
| uint32_t nwritten = kDefaultBlocksPerSegment * 3; |
| |
| for (uint32_t i = 0; i < nwritten; ++i) { |
| NodeInfo ni, new_ni; |
| fs_->GetNodeManager().GetNodeInfo(superblock_info.GetRootIno(), ni); |
| ASSERT_NE(ni.blk_addr, kNullAddr); |
| ASSERT_NE(ni.blk_addr, kNewAddr); |
| |
| { |
| LockedPage read_page; |
| fs_->GetNodeManager().GetNodePage(superblock_info.GetRootIno(), &read_page); |
| read_page.SetDirty(); |
| } |
| WritebackOperation op = {.bSync = true}; |
| fs_->GetNodeVnode().Writeback(op); |
| |
| fs_->GetNodeManager().GetNodeInfo(superblock_info.GetRootIno(), new_ni); |
| ASSERT_NE(new_ni.blk_addr, kNullAddr); |
| ASSERT_NE(new_ni.blk_addr, kNewAddr); |
| |
| // first segment already has next segment with noheap option |
| if ((i > kDefaultBlocksPerSegment - 1) && ((ni.blk_addr + 1) % kDefaultBlocksPerSegment == 0)) { |
| ASSERT_LT(new_ni.blk_addr, ni.blk_addr); |
| } else { |
| ASSERT_GT(new_ni.blk_addr, ni.blk_addr); |
| } |
| } |
| } |
| |
| TEST_F(SegmentManagerTest, GetVictimSelPolicy) TA_NO_THREAD_SAFETY_ANALYSIS { |
| VictimSelPolicy policy = fs_->GetSegmentManager().GetVictimSelPolicy( |
| GcType::kFgGc, CursegType::kCursegHotNode, AllocMode::kSSR); |
| ASSERT_EQ(policy.gc_mode, GcMode::kGcGreedy); |
| ASSERT_EQ(policy.ofs_unit, 1U); |
| |
| policy = fs_->GetSegmentManager().GetVictimSelPolicy(GcType::kFgGc, CursegType::kNoCheckType, |
| AllocMode::kLFS); |
| ASSERT_EQ(policy.gc_mode, GcMode::kGcGreedy); |
| ASSERT_EQ(policy.ofs_unit, fs_->GetSuperblockInfo().GetSegsPerSec()); |
| ASSERT_EQ(policy.offset, |
| fs_->GetSegmentManager().GetLastVictim(static_cast<int>(GcMode::kGcGreedy))); |
| |
| policy = fs_->GetSegmentManager().GetVictimSelPolicy(GcType::kBgGc, CursegType::kNoCheckType, |
| AllocMode::kLFS); |
| ASSERT_EQ(policy.gc_mode, GcMode::kGcCb); |
| ASSERT_EQ(policy.ofs_unit, fs_->GetSuperblockInfo().GetSegsPerSec()); |
| ASSERT_EQ(policy.offset, fs_->GetSegmentManager().GetLastVictim(static_cast<int>(GcMode::kGcCb))); |
| |
| DirtySeglistInfo *dirty_info = &fs_->GetSegmentManager().GetDirtySegmentInfo(); |
| dirty_info->nr_dirty[static_cast<int>(DirtyType::kDirty)] = kMaxSearchLimit + 2; |
| policy = fs_->GetSegmentManager().GetVictimSelPolicy(GcType::kBgGc, CursegType::kNoCheckType, |
| AllocMode::kLFS); |
| ASSERT_EQ(policy.max_search, kMaxSearchLimit); |
| } |
| |
| TEST_F(SegmentManagerTest, GetMaxCost) TA_NO_THREAD_SAFETY_ANALYSIS { |
| VictimSelPolicy policy = fs_->GetSegmentManager().GetVictimSelPolicy( |
| GcType::kFgGc, CursegType::kCursegHotNode, AllocMode::kSSR); |
| policy.min_cost = fs_->GetSegmentManager().GetMaxCost(policy); |
| ASSERT_EQ(policy.min_cost, |
| static_cast<uint32_t>(1 << fs_->GetSuperblockInfo().GetLogBlocksPerSeg())); |
| |
| policy = fs_->GetSegmentManager().GetVictimSelPolicy(GcType::kFgGc, CursegType::kNoCheckType, |
| AllocMode::kLFS); |
| policy.min_cost = fs_->GetSegmentManager().GetMaxCost(policy); |
| ASSERT_EQ(policy.min_cost, |
| static_cast<uint32_t>(2 * (1 << fs_->GetSuperblockInfo().GetLogBlocksPerSeg()) * |
| policy.ofs_unit)); |
| |
| policy = fs_->GetSegmentManager().GetVictimSelPolicy(GcType::kBgGc, CursegType::kNoCheckType, |
| AllocMode::kLFS); |
| policy.min_cost = fs_->GetSegmentManager().GetMaxCost(policy); |
| ASSERT_EQ(policy.min_cost, std::numeric_limits<uint32_t>::max()); |
| } |
| |
| TEST_F(SegmentManagerTest, GetVictimByDefault) TA_NO_THREAD_SAFETY_ANALYSIS { |
| DirtySeglistInfo *dirty_info = &fs_->GetSegmentManager().GetDirtySegmentInfo(); |
| |
| uint32_t target_segno; |
| for (target_segno = 0; target_segno < fs_->GetSegmentManager().TotalSegs(); ++target_segno) { |
| if (!fs_->GetSegmentManager().SecUsageCheck(fs_->GetSegmentManager().GetSecNo(target_segno)) && |
| fs_->GetSegmentManager().GetValidBlocks(target_segno, 0) == 0U) { |
| break; |
| } |
| } |
| ASSERT_NE(target_segno, fs_->GetSegmentManager().TotalSegs()); |
| fs_->GetSegmentManager().SetSegmentEntryType(target_segno, CursegType::kCursegHotNode); |
| |
| // 1. Test SSR victim |
| fs_->GetSegmentManager().SetLastVictim(static_cast<int>(GcType::kBgGc), target_segno); |
| if (!dirty_info->dirty_segmap[static_cast<int>(DirtyType::kDirtyHotNode)].GetOne(target_segno)) { |
| dirty_info->dirty_segmap[static_cast<int>(DirtyType::kDirtyHotNode)].SetOne(target_segno); |
| ++dirty_info->nr_dirty[static_cast<int>(DirtyType::kDirtyHotNode)]; |
| } |
| |
| auto victim_or = fs_->GetSegmentManager().GetVictimByDefault( |
| GcType::kBgGc, CursegType::kCursegHotNode, AllocMode::kSSR); |
| ASSERT_FALSE(victim_or.is_error()); |
| uint32_t get_victim = victim_or.value(); |
| ASSERT_EQ(get_victim, target_segno); |
| |
| // 2. Test FgGc victim |
| fs_->GetSegmentManager().SetLastVictim(static_cast<int>(GcType::kFgGc), target_segno); |
| if (!dirty_info->dirty_segmap[static_cast<int>(DirtyType::kDirty)].GetOne(target_segno)) { |
| dirty_info->dirty_segmap[static_cast<int>(DirtyType::kDirty)].SetOne(target_segno); |
| ++dirty_info->nr_dirty[static_cast<int>(DirtyType::kDirty)]; |
| } |
| |
| victim_or = fs_->GetSegmentManager().GetVictimByDefault(GcType::kFgGc, CursegType::kNoCheckType, |
| AllocMode::kLFS); |
| ASSERT_FALSE(victim_or.is_error()); |
| get_victim = victim_or.value(); |
| ASSERT_EQ(get_victim, target_segno); |
| |
| // 3. Skip if cur_victim_sec is set (SSR) |
| ASSERT_EQ(fs_->GetSegmentManager().GetCurVictimSec(), |
| fs_->GetSegmentManager().GetSecNo(target_segno)); |
| ASSERT_TRUE( |
| dirty_info->dirty_segmap[static_cast<int>(DirtyType::kDirtyHotNode)].GetOne(target_segno)); |
| ASSERT_EQ(dirty_info->nr_dirty[static_cast<int>(DirtyType::kDirtyHotNode)], 1); |
| victim_or = fs_->GetSegmentManager().GetVictimByDefault(GcType::kBgGc, CursegType::kCursegHotNode, |
| AllocMode::kSSR); |
| ASSERT_TRUE(victim_or.is_error()); |
| |
| // 4. Skip if victim_secmap is set (kBgGc) |
| fs_->GetSegmentManager().SetCurVictimSec(kNullSecNo); |
| ASSERT_TRUE( |
| dirty_info->dirty_segmap[static_cast<int>(DirtyType::kDirtyHotNode)].GetOne(target_segno)); |
| ASSERT_EQ(dirty_info->nr_dirty[static_cast<int>(DirtyType::kDirty)], 1); |
| dirty_info->victim_secmap.SetOne(fs_->GetSegmentManager().GetSecNo(target_segno)); |
| victim_or = fs_->GetSegmentManager().GetVictimByDefault(GcType::kBgGc, CursegType::kCursegHotNode, |
| AllocMode::kLFS); |
| ASSERT_TRUE(victim_or.is_error()); |
| } |
| |
| TEST_F(SegmentManagerTest, SelectBGVictims) TA_NO_THREAD_SAFETY_ANALYSIS { |
| DirtySeglistInfo *dirty_info = &fs_->GetSegmentManager().GetDirtySegmentInfo(); |
| auto &bitmap = dirty_info->dirty_segmap[static_cast<int>(DirtyType::kDirty)]; |
| |
| const size_t elapsed_time = 100; |
| size_t invalid_ratio = 2; |
| SitInfo &segments = fs_->GetSegmentManager().GetSitInfo(); |
| // Adjust mount time |
| segments.mounted_time = time(nullptr) - elapsed_time; |
| |
| // Make a dirty segment with 98% of valid blocks |
| CursegInfo *curseg = fs_->GetSegmentManager().CURSEG_I(CursegType::kCursegWarmData); |
| uint32_t expected_by_bg = curseg->segno; |
| MakeDirtySegments(invalid_ratio, 1); |
| ASSERT_EQ(CountBits(bitmap, 0, bitmap.size()), 1U); |
| SegmentEntry &bg_victim = segments.sentries[expected_by_bg]; |
| |
| // Make a dirty segment with 97% of valid blocks |
| curseg = fs_->GetSegmentManager().CURSEG_I(CursegType::kCursegWarmData); |
| uint32_t expected_by_fg = curseg->segno; |
| MakeDirtySegments(invalid_ratio + 1, 1); |
| ASSERT_EQ(CountBits(bitmap, 0, bitmap.size()), 2U); |
| SegmentEntry &fg_victim = segments.sentries[expected_by_fg]; |
| |
| // Make a dirty segment with 99% of valid blocks |
| curseg = fs_->GetSegmentManager().CURSEG_I(CursegType::kCursegWarmData); |
| MakeDirtySegments(invalid_ratio - 1, 1); |
| ASSERT_EQ(CountBits(bitmap, 0, bitmap.size()), 3U); |
| SegmentEntry &dirty = segments.sentries[curseg->segno]; |
| |
| // Adjust the modification time for dirty segments |
| bg_victim.mtime = 1; |
| fg_victim.mtime = elapsed_time / 2; |
| dirty.mtime = elapsed_time; |
| |
| size_t valid_blocks_98 = CheckedDivRoundUp<size_t>( |
| fs_->GetSuperblockInfo().GetBlocksPerSeg() * (100 - invalid_ratio), 100); |
| size_t valid_blocks_97 = CheckedDivRoundUp<size_t>( |
| fs_->GetSuperblockInfo().GetBlocksPerSeg() * (100 - invalid_ratio - 1), 100); |
| |
| // GcType::kFgGc should select a victim according to valid blocks |
| auto victim_seg_or = fs_->GetSegmentManager().GetVictimByDefault( |
| GcType::kFgGc, CursegType::kNoCheckType, AllocMode::kLFS); |
| ASSERT_TRUE(victim_seg_or.is_ok()); |
| ASSERT_EQ(fs_->GetSegmentManager().GetValidBlocks(*victim_seg_or, true), valid_blocks_97); |
| ASSERT_EQ(expected_by_fg, *victim_seg_or); |
| fs_->GetSegmentManager().SetCurVictimSec(kNullSecNo); |
| |
| // GcType::kBgGc should select a victim according to the segment age and valid blocks |
| victim_seg_or = fs_->GetSegmentManager().GetVictimByDefault( |
| GcType::kBgGc, CursegType::kNoCheckType, AllocMode::kLFS); |
| ASSERT_TRUE(victim_seg_or.is_ok()); |
| ASSERT_EQ(fs_->GetSegmentManager().GetValidBlocks(*victim_seg_or, true), valid_blocks_98); |
| ASSERT_EQ(expected_by_bg, *victim_seg_or); |
| |
| // When a victim that GcType::kBgGc chooses is not handled yet, GcType::kFgGc should select the |
| // victim |
| victim_seg_or = fs_->GetSegmentManager().GetVictimByDefault( |
| GcType::kFgGc, CursegType::kNoCheckType, AllocMode::kLFS); |
| ASSERT_TRUE(victim_seg_or.is_ok()); |
| ASSERT_EQ(fs_->GetSegmentManager().GetValidBlocks(*victim_seg_or, true), valid_blocks_98); |
| fs_->GetSegmentManager().SetCurVictimSec(kNullSecNo); |
| |
| // Even after system time is modified, it can select a victim correctly. |
| fs_->GetSegmentManager().GetSitInfo().min_mtime = LLONG_MAX; |
| victim_seg_or = fs_->GetSegmentManager().GetVictimByDefault( |
| GcType::kBgGc, CursegType::kNoCheckType, AllocMode::kLFS); |
| ASSERT_TRUE(victim_seg_or.is_ok()); |
| ASSERT_EQ(fs_->GetSegmentManager().GetValidBlocks(*victim_seg_or, true), valid_blocks_98); |
| |
| fs_->GetSegmentManager().GetSitInfo().max_mtime = 0; |
| dirty_info->victim_secmap.ClearOne(*victim_seg_or / fs_->GetSuperblockInfo().GetSegsPerSec()); |
| victim_seg_or = fs_->GetSegmentManager().GetVictimByDefault( |
| GcType::kBgGc, CursegType::kNoCheckType, AllocMode::kLFS); |
| ASSERT_TRUE(victim_seg_or.is_ok()); |
| ASSERT_EQ(fs_->GetSegmentManager().GetValidBlocks(*victim_seg_or, true), valid_blocks_98); |
| } |
| |
| TEST_F(SegmentManagerTest, AllocateNewSegments) TA_NO_THREAD_SAFETY_ANALYSIS { |
| SuperblockInfo &superblock_info = fs_->GetSuperblockInfo(); |
| |
| uint32_t temp_free_segment = fs_->GetSegmentManager().FreeSegments(); |
| fs_->GetSegmentManager().AllocateNewSegments(); |
| ASSERT_EQ(temp_free_segment - 3, fs_->GetSegmentManager().FreeSegments()); |
| |
| superblock_info.ClearOpt(MountOption::kDisableRollForward); |
| temp_free_segment = fs_->GetSegmentManager().FreeSegments(); |
| for (int i = static_cast<int>(CursegType::kCursegHotNode); |
| i <= static_cast<int>(CursegType::kCursegColdNode); ++i) { |
| fs_->GetSegmentManager().AllocateSegmentByDefault(static_cast<CursegType>(i), true); |
| } |
| uint8_t type = |
| superblock_info.GetCheckpoint().alloc_type[static_cast<int>(CursegType::kCursegHotNode)]; |
| ASSERT_EQ(superblock_info.GetSegmentCount(type), 6UL); |
| ASSERT_EQ(temp_free_segment - 3, fs_->GetSegmentManager().FreeSegments()); |
| } |
| |
| TEST_F(SegmentManagerTest, DirtySegments) TA_NO_THREAD_SAFETY_ANALYSIS { |
| // read the root inode block |
| LockedPage root_node_page; |
| SuperblockInfo &superblock_info = fs_->GetSuperblockInfo(); |
| fs_->GetNodeManager().GetNodePage(superblock_info.GetRootIno(), &root_node_page); |
| ASSERT_NE(root_node_page, nullptr); |
| |
| DirtySeglistInfo &dirty_info = fs_->GetSegmentManager().GetDirtySegmentInfo(); |
| uint32_t dirtyDataSegments = dirty_info.nr_dirty[static_cast<int>(DirtyType::kDirtyHotData)] + |
| dirty_info.nr_dirty[static_cast<int>(DirtyType::kDirtyWarmData)] + |
| dirty_info.nr_dirty[static_cast<int>(DirtyType::kDirtyColdData)]; |
| |
| uint32_t dirtyNodeSegments = dirty_info.nr_dirty[static_cast<int>(DirtyType::kDirtyHotNode)] + |
| dirty_info.nr_dirty[static_cast<int>(DirtyType::kDirtyWarmNode)] + |
| dirty_info.nr_dirty[static_cast<int>(DirtyType::kDirtyColdNode)]; |
| |
| ASSERT_EQ(fs_->GetSegmentManager().DirtySegments(), dirtyDataSegments + dirtyNodeSegments); |
| } |
| |
| TEST(SegmentManagerOptionTest, Section) TA_NO_THREAD_SAFETY_ANALYSIS { |
| std::unique_ptr<BcacheMapper> bc; |
| MkfsOptions mkfs_options{}; |
| mkfs_options.segs_per_sec = 4; |
| FileTester::MkfsOnFakeDevWithOptions(&bc, mkfs_options); |
| |
| std::unique_ptr<F2fs> fs; |
| MountOptions mount_options{}; |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| FileTester::MountWithOptions(loop.dispatcher(), mount_options, &bc, &fs); |
| |
| uint32_t blocks_per_section = kDefaultBlocksPerSegment * mkfs_options.segs_per_sec; |
| SuperblockInfo &superblock_info = fs->GetSuperblockInfo(); |
| |
| for (uint32_t i = 0; i < blocks_per_section; ++i) { |
| NodeInfo ni; |
| CursegInfo *cur_segment = fs->GetSegmentManager().CURSEG_I(CursegType::kCursegHotNode); |
| |
| { |
| LockedPage root_node_page; |
| fs->GetNodeManager().GetNodePage(superblock_info.GetRootIno(), &root_node_page); |
| ASSERT_NE(root_node_page, nullptr); |
| |
| // Consume a block in the current section |
| root_node_page.SetDirty(); |
| } |
| WritebackOperation op = {.bSync = true}; |
| fs->GetNodeVnode().Writeback(op); |
| |
| fs->GetNodeManager().GetNodeInfo(superblock_info.GetRootIno(), ni); |
| ASSERT_NE(ni.blk_addr, kNullAddr); |
| ASSERT_NE(ni.blk_addr, kNewAddr); |
| |
| unsigned int expected = 1; |
| // When a new secton is allocated, the valid block count of the previous one should be zero |
| if ((ni.blk_addr + 1) % blocks_per_section == 0) { |
| expected = 0; |
| } |
| ASSERT_EQ(expected, |
| fs->GetSegmentManager().GetValidBlocks( |
| cur_segment->segno, safemath::checked_cast<int>(mkfs_options.segs_per_sec))); |
| ASSERT_FALSE(fs->GetSegmentManager().HasNotEnoughFreeSecs()); |
| } |
| |
| FileTester::Unmount(std::move(fs), &bc); |
| } |
| |
| TEST(SegmentManagerOptionTest, GetNewSegmentHeap) TA_NO_THREAD_SAFETY_ANALYSIS { |
| std::unique_ptr<BcacheMapper> bc; |
| MkfsOptions mkfs_options{}; |
| mkfs_options.heap_based_allocation = true; |
| mkfs_options.segs_per_sec = 4; |
| mkfs_options.secs_per_zone = 4; |
| FileTester::MkfsOnFakeDevWithOptions(&bc, mkfs_options); |
| |
| std::unique_ptr<F2fs> fs; |
| MountOptions mount_options{}; |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| FileTester::MountWithOptions(loop.dispatcher(), mount_options, &bc, &fs); |
| |
| // Clear kMountNoheap opt, Allocate a new segment for hot nodes |
| SuperblockInfo &superblock_info = fs->GetSuperblockInfo(); |
| superblock_info.ClearOpt(MountOption::kNoHeap); |
| fs->GetSegmentManager().NewCurseg(CursegType::kCursegHotNode, false); |
| |
| const uint32_t alloc_size = kDefaultBlocksPerSegment * mkfs_options.segs_per_sec; |
| uint32_t nwritten = alloc_size * mkfs_options.secs_per_zone * 3; |
| |
| for (uint32_t i = 0; i < nwritten; ++i) { |
| NodeInfo ni, new_ni; |
| fs->GetNodeManager().GetNodeInfo(superblock_info.GetRootIno(), ni); |
| ASSERT_NE(ni.blk_addr, kNullAddr); |
| ASSERT_NE(ni.blk_addr, kNewAddr); |
| |
| { |
| LockedPage root_node_page; |
| fs->GetNodeManager().GetNodePage(superblock_info.GetRootIno(), &root_node_page); |
| ASSERT_NE(root_node_page, nullptr); |
| root_node_page.SetDirty(); |
| } |
| |
| WritebackOperation op = {.bSync = true}; |
| fs->GetNodeVnode().Writeback(op); |
| |
| fs->GetNodeManager().GetNodeInfo(superblock_info.GetRootIno(), new_ni); |
| ASSERT_NE(new_ni.blk_addr, kNullAddr); |
| ASSERT_NE(new_ni.blk_addr, kNewAddr); |
| |
| // The heap style allocation tries to find a free node section from the end of main area |
| if ((i > alloc_size * 2 - 1) && (new_ni.blk_addr % alloc_size == 0)) { |
| ASSERT_LT(new_ni.blk_addr, ni.blk_addr); |
| } else { |
| ASSERT_GT(new_ni.blk_addr, ni.blk_addr); |
| } |
| } |
| |
| FileTester::Unmount(std::move(fs), &bc); |
| } |
| |
| TEST(SegmentManagerOptionTest, GetNewSegmentNoHeap) TA_NO_THREAD_SAFETY_ANALYSIS { |
| std::unique_ptr<BcacheMapper> bc; |
| MkfsOptions mkfs_options{}; |
| mkfs_options.heap_based_allocation = false; |
| mkfs_options.segs_per_sec = 4; |
| mkfs_options.secs_per_zone = 4; |
| FileTester::MkfsOnFakeDevWithOptions(&bc, mkfs_options); |
| |
| std::unique_ptr<F2fs> fs; |
| MountOptions mount_options; |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| FileTester::MountWithOptions(loop.dispatcher(), mount_options, &bc, &fs); |
| |
| // Set kMountNoheap opt, Allocate a new segment for hot nodes |
| SuperblockInfo &superblock_info = fs->GetSuperblockInfo(); |
| superblock_info.SetOpt(MountOption::kNoHeap); |
| fs->GetSegmentManager().NewCurseg(CursegType::kCursegHotNode, false); |
| |
| uint32_t nwritten = |
| kDefaultBlocksPerSegment * mkfs_options.segs_per_sec * mkfs_options.secs_per_zone * 3; |
| |
| for (uint32_t i = 0; i < nwritten; ++i) { |
| NodeInfo ni, new_ni; |
| fs->GetNodeManager().GetNodeInfo(superblock_info.GetRootIno(), ni); |
| ASSERT_NE(ni.blk_addr, kNullAddr); |
| ASSERT_NE(ni.blk_addr, kNewAddr); |
| |
| { |
| LockedPage root_node_page; |
| fs->GetNodeManager().GetNodePage(superblock_info.GetRootIno(), &root_node_page); |
| ASSERT_NE(root_node_page, nullptr); |
| root_node_page.SetDirty(); |
| } |
| WritebackOperation op = {.bSync = true}; |
| fs->GetNodeVnode().Writeback(op); |
| |
| fs->GetNodeManager().GetNodeInfo(superblock_info.GetRootIno(), new_ni); |
| ASSERT_NE(new_ni.blk_addr, kNullAddr); |
| ASSERT_NE(new_ni.blk_addr, kNewAddr); |
| // It tries to find a free nodesction from the start of main area |
| ASSERT_GT(new_ni.blk_addr, ni.blk_addr); |
| } |
| |
| FileTester::Unmount(std::move(fs), &bc); |
| } |
| |
| TEST(SegmentManagerOptionTest, DestroySegmentManagerExceptionCase) { |
| std::unique_ptr<BcacheMapper> bc; |
| MkfsOptions mkfs_options{}; |
| FileTester::MkfsOnFakeDevWithOptions(&bc, mkfs_options); |
| |
| MountOptions mount_options; |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| |
| auto superblock = LoadSuperblock(*bc); |
| ASSERT_TRUE(superblock.is_ok()); |
| // Create a vfs object for unit tests. |
| auto vfs_or = Runner::CreateRunner(loop.dispatcher()); |
| ZX_ASSERT(vfs_or.is_ok()); |
| std::unique_ptr<F2fs> fs = |
| std::make_unique<F2fs>(loop.dispatcher(), std::move(bc), mount_options, (*vfs_or).get()); |
| |
| ASSERT_EQ(fs->LoadSuper(std::move(*superblock)), ZX_OK); |
| |
| fs->Sync(); |
| |
| // fault injection |
| fs->GetSegmentManager().SetDirtySegmentInfo(nullptr); |
| fs->GetSegmentManager().SetFreeSegmentInfo(nullptr); |
| fs->GetSegmentManager().SetSitInfo(nullptr); |
| |
| fs->GetVCache().Reset(); |
| // test exception case |
| fs->Reset(); |
| } |
| |
| TEST(SegmentManagerOptionTest, ModeLfs) { |
| std::unique_ptr<BcacheMapper> bc; |
| MkfsOptions mkfs_options{}; |
| mkfs_options.segs_per_sec = 4; |
| FileTester::MkfsOnFakeDevWithOptions(&bc, mkfs_options); |
| |
| std::unique_ptr<F2fs> fs; |
| MountOptions mount_options; |
| mount_options.SetValue(MountOption::kForceLfs, static_cast<size_t>(ModeType::kModeLfs)); |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| FileTester::MountWithOptions(loop.dispatcher(), mount_options, &bc, &fs); |
| fbl::RefPtr<VnodeF2fs> root; |
| FileTester::CreateRoot(fs.get(), &root); |
| auto root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root)); |
| |
| ASSERT_EQ(fs->GetSuperblockInfo().TestOpt(MountOption::kForceLfs), true); |
| ASSERT_EQ(fs->GetSegmentManager().NeedSSR(), false); |
| |
| // Make SSR, IPU condition |
| FileTester::CreateChild(root_dir.get(), S_IFREG, "alpha"); |
| fbl::RefPtr<fs::Vnode> vn; |
| FileTester::Lookup(root_dir.get(), "alpha", &vn); |
| auto file = fbl::RefPtr<File>::Downcast(std::move(vn)); |
| char buf[4 * kPageSize] = { |
| 1, |
| }; |
| while (!fs->GetSegmentManager().NeedInplaceUpdate(file->IsDir())) { |
| size_t out_end, out_actual; |
| if (auto ret = FileTester::Append(file.get(), buf, sizeof(buf), &out_end, &out_actual); |
| ret == ZX_ERR_NO_SPACE) { |
| break; |
| } else { |
| ASSERT_EQ(ret, ZX_OK); |
| } |
| WritebackOperation op = {.bSync = true}; |
| file->Writeback(op); |
| } |
| |
| // Since kMountForceLfs is on, f2fs doesn't allocate segments in ssr manner. |
| ASSERT_EQ(fs->GetSegmentManager().NeedSSR(), false); |
| ASSERT_EQ(fs->GetSegmentManager().NeedInplaceUpdate(file->IsDir()), false); |
| |
| // Make SSR, IPU enable |
| fs->GetSuperblockInfo().ClearOpt(MountOption::kForceLfs); |
| ASSERT_EQ(fs->GetSegmentManager().NeedSSR(), true); |
| |
| EXPECT_EQ(file->Close(), ZX_OK); |
| file = nullptr; |
| |
| // Test ClearPrefreeSegments() |
| fs->GetSuperblockInfo().SetOpt(MountOption::kForceLfs); |
| FileTester::DeleteChild(root_dir.get(), "alpha", false); |
| fs->SyncFs(); |
| |
| EXPECT_EQ(root_dir->Close(), ZX_OK); |
| root_dir = nullptr; |
| FileTester::Unmount(std::move(fs), &bc); |
| EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK); |
| } |
| |
| TEST(SegmentManagerExceptionTest, BuildSitEntriesDiskFail) TA_NO_THREAD_SAFETY_ANALYSIS { |
| std::unique_ptr<BcacheMapper> bc; |
| FileTester::MkfsOnFakeDevWithOptions(&bc, MkfsOptions{}); |
| |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| |
| auto superblock = LoadSuperblock(*bc); |
| ASSERT_TRUE(superblock.is_ok()); |
| |
| // Create a vfs object for unit tests. |
| auto vfs_or = Runner::CreateRunner(loop.dispatcher()); |
| ASSERT_TRUE(vfs_or.is_ok()); |
| std::unique_ptr<F2fs> fs = |
| std::make_unique<F2fs>(loop.dispatcher(), std::move(bc), MountOptions{}, (*vfs_or).get()); |
| |
| ASSERT_EQ(fs->LoadSuper(std::move(*superblock)), ZX_OK); |
| |
| pgoff_t target_addr = fs->GetSegmentManager().CurrentSitAddr(0) * kDefaultSectorsPerBlock; |
| |
| auto hook = [target_addr](const block_fifo_request_t &_req, const zx::vmo *_vmo) { |
| if (_req.dev_offset == target_addr) { |
| return ZX_ERR_PEER_CLOSED; |
| } |
| return ZX_OK; |
| }; |
| |
| fs->GetSegmentManager().DestroySegmentManager(); |
| |
| // Invalidate sit page to read from disk |
| LockedPage sit_page; |
| ASSERT_EQ(fs->GetMetaPage(target_addr / kDefaultSectorsPerBlock, &sit_page), ZX_OK); |
| sit_page->Invalidate(); |
| sit_page.reset(); |
| |
| // Expect fail in BuildSitEntries() |
| DeviceTester::SetHook(fs.get(), hook); |
| ASSERT_EQ(fs->GetSegmentManager().BuildSegmentManager(), ZX_ERR_PEER_CLOSED); |
| |
| DeviceTester::SetHook(fs.get(), nullptr); |
| |
| fs->GetVCache().Reset(); |
| fs->Reset(); |
| } |
| |
| } // namespace |
| } // namespace f2fs |