| // 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 <block-client/cpp/fake-device.h> |
| #include <gtest/gtest.h> |
| |
| #include "third_party/f2fs/f2fs.h" |
| |
| namespace f2fs { |
| namespace { |
| |
| using block_client::FakeBlockDevice; |
| constexpr uint32_t kMountVerifyTest = 0; |
| constexpr uint32_t kMountDisableExtTest = 1; |
| constexpr uint32_t kMountActiveLogsTest = 2; |
| |
| void MountTestVerifyOptions(F2fs *fs, MountOptions &options) { |
| uint32_t value; |
| SbInfo &sbi = fs->GetSbInfo(); |
| for (uint32_t i = 0; i < kOptMaxNum; i++) { |
| ASSERT_EQ(options.GetValue(i, &value), ZX_OK); |
| switch (i) { |
| case kOptActiveLogs: |
| ASSERT_EQ(static_cast<uint32_t>(sbi.active_logs), value); |
| break; |
| case kOptDiscard: |
| ASSERT_EQ((value == 0), (TestOpt(&sbi, kMountDiscard) == 0)); |
| break; |
| case kOptBgGcOff: |
| ASSERT_EQ((value == 0), (TestOpt(&sbi, kMountBgGcOff) == 0)); |
| break; |
| case kOptNoHeap: |
| ASSERT_EQ((value == 0), (TestOpt(&sbi, kMountNoheap) == 0)); |
| break; |
| case kOptDisableExtIdentify: |
| ASSERT_EQ((value == 0), (TestOpt(&sbi, kMountDisableExtIdentify) == 0)); |
| break; |
| case kOptNoUserXAttr: |
| ASSERT_EQ((value == 0), (TestOpt(&sbi, kMountNoXAttr) == 0)); |
| break; |
| case kOptNoAcl: |
| ASSERT_EQ((value == 0), (TestOpt(&sbi, kMountNoAcl) == 0)); |
| break; |
| case kOptDisableRollForward: |
| ASSERT_EQ((value == 0), (TestOpt(&sbi, kMountDisableRollForward) == 0)); |
| break; |
| }; |
| } |
| ASSERT_EQ(options.GetValue(kOptMaxNum, &value), ZX_ERR_INVALID_ARGS); |
| } |
| |
| void MountTestDisableExt(F2fs *fs, uint32_t expectation) { |
| fbl::RefPtr<VnodeF2fs> data_root; |
| bool result = (expectation > 0 ? true : false); |
| ASSERT_EQ(VnodeF2fs::Vget(fs, fs->RawSb().root_ino, &data_root), ZX_OK); |
| Dir *root_dir = static_cast<Dir *>(data_root.get()); |
| |
| for (const char *ext_item : kMediaExtList) { |
| std::string name = "test."; |
| name += ext_item; |
| fbl::RefPtr<fs::Vnode> vnode; |
| // Use S_IFDIR to create regular file |
| ASSERT_EQ(root_dir->Create(name, S_IFREG, &vnode), ZX_OK); |
| File *file = static_cast<File *>(vnode.get()); |
| ASSERT_EQ(NodeMgr::IsColdFile(file), result); |
| vnode->Close(); |
| } |
| } |
| |
| void TestSegmentType(F2fs *fs, Dir *root_dir, const std::string_view name, bool is_dir, |
| std::vector<CursegType> &out) { |
| fbl::RefPtr<fs::Vnode> vnode; |
| uint64_t flag = (is_dir ? S_IFDIR : S_IFREG); |
| nid_t nid = 100; |
| uint32_t inode_ofs = 0; |
| uint32_t indirect_node_ofs = 3; |
| ASSERT_EQ(root_dir->Create(name, flag, &vnode), ZX_OK); |
| VnodeF2fs *vn = static_cast<VnodeF2fs *>(vnode.get()); |
| |
| // data block test |
| Page *page = GrabCachePage(vn, vn->Ino(), 0); |
| CursegType type = fs->Segmgr().GetSegmentType(page, PageType::kData); |
| out.push_back(type); |
| F2fsPutPage(page, 1); |
| |
| // Dnode block test |
| page = GrabCachePage(nullptr, NodeIno(&fs->GetSbInfo()), vn->Ino()); |
| fs->Nodemgr().FillNodeFooter(page, page->index, vn->Ino(), inode_ofs, true); |
| fs->Nodemgr().SetColdNode(vn, page); |
| type = fs->Segmgr().GetSegmentType(page, PageType::kNode); |
| out.push_back(type); |
| F2fsPutPage(page, 1); |
| |
| // indirect node block test |
| page = GrabCachePage(nullptr, NodeIno(&fs->GetSbInfo()), nid); |
| fs->Nodemgr().FillNodeFooter(page, page->index, vn->Ino(), indirect_node_ofs, true); |
| fs->Nodemgr().SetColdNode(vn, page); |
| type = fs->Segmgr().GetSegmentType(page, PageType::kNode); |
| out.push_back(type); |
| F2fsPutPage(page, 1); |
| vnode->Close(); |
| } |
| |
| void MountTestActiveLogs(F2fs *fs, MountOptions options) { |
| fbl::RefPtr<VnodeF2fs> data_root; |
| ASSERT_EQ(VnodeF2fs::Vget(fs, fs->RawSb().root_ino, &data_root), ZX_OK); |
| Dir *root_dir = static_cast<Dir *>(data_root.get()); |
| const char *filenames[] = {"dir", "warm.exe", "cold.mp4"}; |
| std::vector<CursegType> results(3, CursegType::kNoCheckType); |
| uint32_t num_logs = 0; |
| ASSERT_EQ(options.GetValue(kOptActiveLogs, &num_logs), ZX_OK); |
| |
| constexpr int dir_file = 0; |
| constexpr int warm_file = 1; |
| __UNUSED constexpr int cold_file = 2; |
| |
| __UNUSED constexpr int data_block = 0; |
| constexpr int dnode_block = 1; |
| constexpr int indirect_node_block = 2; |
| |
| for (int i = 0; i < 3; i++) { |
| results.clear(); |
| TestSegmentType(fs, root_dir, filenames[i], (i == dir_file), results); |
| for (int j = 0; j < 3; j++) { |
| CursegType type = results[j]; |
| if (j == indirect_node_block) { |
| if (num_logs > 2) |
| ASSERT_EQ(type, CursegType::kCursegColdNode); |
| else |
| ASSERT_EQ(type, CursegType::kCursegHotNode); |
| } else if (j == dnode_block) { |
| if (i == dir_file || num_logs == 2) { |
| ASSERT_EQ(type, CursegType::kCursegHotNode); |
| } else if (num_logs == 6) { |
| ASSERT_EQ(type, CursegType::kCursegWarmNode); |
| } else |
| ASSERT_EQ(type, CursegType::kCursegColdNode); |
| |
| } else { // data block case |
| if (i == dir_file || num_logs == 2) { |
| ASSERT_EQ(type, CursegType::kCursegHotData); |
| } else { |
| if (i == warm_file && num_logs == 6) |
| ASSERT_EQ(type, CursegType::kCursegWarmData); |
| else |
| ASSERT_EQ(type, CursegType::kCursegColdData); |
| } |
| } |
| } |
| } |
| } |
| |
| void MountTestMain(MountOptions &options, uint32_t test, uint32_t priv) { |
| std::unique_ptr<f2fs::Bcache> bc; |
| bool readonly_device = false; |
| auto device = std::make_unique<FakeBlockDevice>( |
| FakeBlockDevice::Config{.block_count = kMinVolumeSize / kDefaultSectorSize, |
| .block_size = kDefaultSectorSize, |
| .supports_trim = false}); |
| |
| ASSERT_TRUE(device); |
| ASSERT_EQ(CreateBcache(std::move(device), &readonly_device, &bc), ZX_OK); |
| |
| MkfsWorker mkfs(bc.get()); |
| ASSERT_EQ(mkfs.DoMkfs(), ZX_OK); |
| |
| std::unique_ptr<F2fs> fs; |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| |
| // Enclose the channels in a scope to ensure they are closed before we shut down. |
| { |
| zx::channel mount_channel, remote_mount_channel; |
| ASSERT_EQ(zx::channel::create(0, &mount_channel, &remote_mount_channel), ZX_OK); |
| auto fs_or = f2fs::CreateFsAndRoot( |
| options, loop.dispatcher(), std::move(bc), std::move(mount_channel), [] {}, |
| ServeLayout::kExportDirectory); |
| |
| ASSERT_EQ(fs_or.is_ok(), true); |
| fs = std::move(fs_or).value(); |
| switch (test) { |
| case kMountVerifyTest: |
| MountTestVerifyOptions(fs.get(), options); |
| break; |
| case kMountDisableExtTest: |
| MountTestDisableExt(fs.get(), priv); |
| break; |
| case kMountActiveLogsTest: |
| MountTestActiveLogs(fs.get(), options); |
| break; |
| default: |
| ASSERT_EQ(0, 1); |
| break; |
| }; |
| } |
| fs->Shutdown([&loop](zx_status_t) { loop.Quit(); }); |
| __UNUSED F2fs *relinquish = fs.release(); |
| |
| ASSERT_EQ(loop.Run(), ZX_ERR_CANCELED); |
| } |
| |
| TEST(MountTest, Verify) { |
| MountOptions options; |
| MountTestMain(options, kMountVerifyTest, 0); |
| } |
| |
| TEST(MountTest, DisableExtOptions) { |
| constexpr uint32_t ShouldNotBeCold = 0; |
| MountOptions options{}; |
| ASSERT_EQ(options.SetValue(options.GetNameView(kOptDisableExtIdentify), 1), ZX_OK); |
| MountTestMain(options, kMountDisableExtTest, ShouldNotBeCold); |
| } |
| |
| TEST(MountTest, EnableExtOptions) { |
| constexpr uint32_t ShouldBeCold = 1; |
| MountOptions options{}; |
| ASSERT_EQ(options.SetValue(options.GetNameView(kOptDisableExtIdentify), 0), ZX_OK); |
| MountTestMain(options, kMountDisableExtTest, ShouldBeCold); |
| } |
| |
| TEST(MountTest, ActiveLogsOptions) { |
| for (uint32_t i = 2; i <= 6; i += 2) { |
| MountOptions options{}; |
| ASSERT_EQ(options.SetValue(options.GetNameView(kOptActiveLogs), i), ZX_OK); |
| MountTestMain(options, kMountActiveLogsTest, 0); |
| } |
| } |
| |
| } // namespace |
| } // namespace f2fs |