blob: 9010e806f10066946adf25b070612fb6a8468b1f [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 <unordered_set>
#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 {
class VnodeCacheTest : public SingleFileTest {
public:
VnodeCacheTest()
: SingleFileTest(S_IFDIR, TestOptions{.mount_options = {{MountOption::kInlineDentry, 0}}}) {}
uint32_t GetCachedVnodeCount() {
uint32_t cached_vnode_count = 0;
fs_->GetVCache().ForAllVnodes([&cached_vnode_count](fbl::RefPtr<VnodeF2fs> &vnode) {
++cached_vnode_count;
return ZX_OK;
});
return cached_vnode_count;
}
uint32_t GetDirtyVnodeCount() {
uint32_t dirty_vnode_count = 0;
fs_->GetVCache().ForDirtyVnodesIf(
[&dirty_vnode_count](fbl::RefPtr<VnodeF2fs> &vnode) {
++dirty_vnode_count;
return ZX_OK;
},
nullptr);
return dirty_vnode_count;
}
};
TEST_F(VnodeCacheTest, Basic) {
Dir *test_dir_ptr = &vnode<Dir>();
std::unordered_set<std::string> child_set = {"a", "b", "c", "d", "e"};
std::vector<ino_t> child_ino_set(0);
std::vector<std::string> deleted_child_set(0);
// create a, b, c, d, e in test
for (auto iter : child_set) {
FileTester::CreateChild(test_dir_ptr, S_IFDIR, iter);
}
// check if {a, b, c, d, e} vnodes are in both containers.
for (auto iter : child_set) {
fbl::RefPtr<fs::Vnode> vn;
FileTester::Lookup(test_dir_ptr, iter, &vn);
ASSERT_TRUE(vn);
VnodeF2fs *raw_vnode = reinterpret_cast<VnodeF2fs *>(vn.get());
ASSERT_TRUE(raw_vnode->IsDirty());
ASSERT_EQ((*raw_vnode).fbl::DoublyLinkedListable<fbl::RefPtr<VnodeF2fs>>::InContainer(), true);
ASSERT_EQ((*raw_vnode).fbl::WAVLTreeContainable<VnodeF2fs *>::InContainer(), true);
child_ino_set.push_back(raw_vnode->GetKey());
vn->Close();
vn.reset();
}
ASSERT_EQ(test_dir_ptr->GetSize(), kPageSize);
// flush dirty vnodes.
fs_->SyncFs();
// check if dirty vnodes are removed from dirty_list_
ASSERT_TRUE(fs_->GetVCache().IsDirtyListEmpty());
for (auto iter : child_set) {
fbl::RefPtr<fs::Vnode> vn;
FileTester::Lookup(test_dir_ptr, iter, &vn);
ASSERT_TRUE(vn);
VnodeF2fs *raw_vnode = reinterpret_cast<VnodeF2fs *>(vn.get());
ASSERT_FALSE(raw_vnode->IsDirty());
ASSERT_FALSE((*raw_vnode).fbl::DoublyLinkedListable<fbl::RefPtr<VnodeF2fs>>::InContainer());
ASSERT_EQ((*raw_vnode).fbl::WAVLTreeContainable<VnodeF2fs *>::InContainer(), true);
vn->Close();
}
// remove "b" and "d".
FileTester::DeleteChild(test_dir_ptr, "b");
deleted_child_set.push_back("b");
FileTester::DeleteChild(test_dir_ptr, "d");
deleted_child_set.push_back("d");
// free nids for b and d.
fs_->SyncFs();
// check if nodemgr and vnode cache remove b and d.
int i = 0;
for (auto iter : child_set) {
fbl::RefPtr<fs::Vnode> vn;
auto child = find(deleted_child_set.begin(), deleted_child_set.end(), iter);
FileTester::Lookup(test_dir_ptr, iter, &vn);
if (child != deleted_child_set.end()) {
ASSERT_FALSE(vn);
ino_t ino = child_ino_set.at(i);
fbl::RefPtr<VnodeF2fs> vn2;
ASSERT_EQ(fs_->GetVCache().Lookup(ino, &vn2), ZX_ERR_NOT_FOUND);
NodeInfo ni;
fs_->GetNodeManager().GetNodeInfo(ino, ni);
ASSERT_FALSE(ni.blk_addr);
} else {
ASSERT_TRUE(vn);
VnodeF2fs *raw_vnode = reinterpret_cast<VnodeF2fs *>(vn.get());
ASSERT_FALSE(raw_vnode->IsDirty());
ASSERT_FALSE((*raw_vnode).fbl::DoublyLinkedListable<fbl::RefPtr<VnodeF2fs>>::InContainer());
ASSERT_EQ((*raw_vnode).fbl::WAVLTreeContainable<VnodeF2fs *>::InContainer(), true);
vn->Close();
ino_t ino = child_ino_set.at(i);
fbl::RefPtr<VnodeF2fs> vn2;
ASSERT_EQ(fs_->GetVCache().Lookup(ino, &vn2), ZX_OK);
NodeInfo ni;
fs_->GetNodeManager().GetNodeInfo(ino, ni);
ASSERT_TRUE(ni.blk_addr);
}
++i;
}
}
TEST_F(VnodeCacheTest, VnodeCacheExceptionCase) {
fbl::RefPtr<VnodeF2fs> new_vnode;
// Check Create() exception
ASSERT_EQ(GetDirtyVnodeCount(), 2U);
ASSERT_EQ(GetCachedVnodeCount(), 2U);
ASSERT_EQ(VnodeF2fs::Vget(fs_.get(), fs_->GetSuperblockInfo().GetNodeIno(), &new_vnode),
ZX_ERR_NOT_FOUND);
// Check Add() exception
auto &test_vnode = vnode<Dir>();
ASSERT_EQ(GetDirtyVnodeCount(), 2U);
ASSERT_EQ(GetCachedVnodeCount(), 2U);
ASSERT_EQ(fs_->GetVCache().Add(&test_vnode), ZX_ERR_ALREADY_EXISTS);
ASSERT_EQ(GetDirtyVnodeCount(), 2U);
ASSERT_EQ(GetCachedVnodeCount(), 2U);
// Check AddDirty() exception
ASSERT_EQ(fs_->GetVCache().AddDirty(test_vnode), ZX_ERR_ALREADY_EXISTS);
ASSERT_EQ(GetDirtyVnodeCount(), 2U);
ASSERT_EQ(GetCachedVnodeCount(), 2U);
// Check ForAllVnodes() callback function
ASSERT_EQ(
fs_->GetVCache().ForAllVnodes([](fbl::RefPtr<VnodeF2fs> &vnode) { return ZX_ERR_STOP; }),
ZX_OK);
ASSERT_EQ(fs_->GetVCache().ForAllVnodes(
[](fbl::RefPtr<VnodeF2fs> &vnode) { return ZX_ERR_INVALID_ARGS; }),
ZX_ERR_INVALID_ARGS);
ASSERT_EQ(
fs_->GetVCache().ForDirtyVnodesIf([](fbl::RefPtr<VnodeF2fs> &vnode) { return ZX_ERR_STOP; },
[](fbl::RefPtr<VnodeF2fs> &vnode) { return ZX_OK; }),
ZX_OK);
ASSERT_EQ(fs_->GetVCache().ForDirtyVnodesIf(
[](fbl::RefPtr<VnodeF2fs> &vnode) { return ZX_ERR_INVALID_ARGS; },
[](fbl::RefPtr<VnodeF2fs> &vnode) { return ZX_OK; }),
ZX_ERR_INVALID_ARGS);
// Check Reset()
fs_->Sync();
ASSERT_EQ(GetDirtyVnodeCount(), 0U);
ASSERT_EQ(GetCachedVnodeCount(), 2U);
fs_->GetVCache().Reset();
ASSERT_EQ(GetDirtyVnodeCount(), 0U);
ASSERT_EQ(GetCachedVnodeCount(), 0U);
}
TEST_F(VnodeCacheTest, VnodeActivation) {
Dir *test_dir_ptr = &vnode<Dir>();
std::string child_name = "file";
FileTester::CreateChild(test_dir_ptr, S_IFDIR, child_name);
fbl::RefPtr<fs::Vnode> test_vnode;
FileTester::Lookup(test_dir_ptr, child_name, &test_vnode);
fbl::RefPtr<VnodeF2fs> test_f2fs_vnode = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(test_vnode));
ASSERT_TRUE(test_f2fs_vnode->IsActive());
ASSERT_EQ(test_f2fs_vnode->GetNameView().compare(child_name), 0);
ASSERT_EQ(test_f2fs_vnode->Close(), ZX_OK);
auto raw_pointer = test_f2fs_vnode.get();
test_f2fs_vnode.reset();
// "file" is active as VnodeCache::dirty_list_ keeps its ref.
ASSERT_TRUE(raw_pointer->IsActive());
fs_->SyncFs();
// "file" is inactive after checkpoint writes its vnode to disk.
ASSERT_FALSE(raw_pointer->IsActive());
// Get refptr for "file" from the vnode cache while it is being recycled.
std::thread thread1 = std::thread([&]() {
int iter = 10000;
while (--iter) {
fbl::RefPtr<VnodeF2fs> test_vnode;
ASSERT_EQ(VnodeF2fs::Vget(fs_.get(), raw_pointer->Ino(), &test_vnode), ZX_OK);
}
});
std::thread thread2 = std::thread([&]() {
int iter = 10000;
while (--iter) {
fbl::RefPtr<VnodeF2fs> test_vnode;
ASSERT_EQ(VnodeF2fs::Vget(fs_.get(), raw_pointer->Ino(), &test_vnode), ZX_OK);
}
});
thread1.join();
thread2.join();
ASSERT_FALSE(raw_pointer->IsActive());
}
} // namespace
} // namespace f2fs