blob: d151d51baca9e739063ce96806d38fc81e1ae469 [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 <block-client/cpp/fake-device.h>
#include <gtest/gtest.h>
#include "third_party/f2fs/f2fs.h"
#include "unit_lib.h"
namespace f2fs {
namespace {
TEST(DirTest, DentryReuse) {
std::unique_ptr<Bcache> bc;
unittest_lib::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
ASSERT_EQ(options.SetValue(options.GetNameView(kOptInlineDentry), 0), ZX_OK);
unittest_lib::MountWithOptions(options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
unittest_lib::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
fbl::RefPtr<fs::Vnode> test_dir;
ASSERT_EQ(root_dir->Create("test", S_IFDIR, &test_dir), ZX_OK);
fbl::RefPtr<VnodeF2fs> test_dir_vn = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(test_dir));
Dir *test_dir_ptr = static_cast<Dir *>(test_dir_vn.get());
std::unordered_set<std::string> child_set = {"a", "b", "c", "d", "e"};
for (auto iter : child_set) {
unittest_lib::CreateChild(test_dir_ptr, S_IFDIR, iter);
}
ASSERT_EQ(test_dir_vn->GetSize(), kPageCacheSize);
// remove "b" and "d"
unittest_lib::DeleteChild(test_dir_ptr, "b");
child_set.erase("b");
unittest_lib::DeleteChild(test_dir_ptr, "d");
child_set.erase("d");
// Check remain children are in first dentry page
unittest_lib::CheckChildrenInBlock(test_dir_ptr, 0, child_set);
// create "f" and "g", and rename "a" to "h"
unittest_lib::CreateChild(test_dir_ptr, S_IFDIR, "f");
child_set.insert("f");
unittest_lib::CreateChild(test_dir_ptr, S_IFDIR, "g");
child_set.insert("g");
ASSERT_EQ(test_dir_ptr->Rename(test_dir_vn, "a", "h", true, true), ZX_OK);
child_set.erase("a");
child_set.insert("h");
// Check children are in first dentry page
unittest_lib::CheckChildrenInBlock(test_dir_ptr, 0, child_set);
// fill all dentry slots in first dentry page
unsigned int child_count = child_set.size();
for (; child_count < kNrDentryInBlock - 2; ++child_count) {
unittest_lib::CreateChild(test_dir_ptr, S_IFDIR, std::to_string(child_count));
child_set.insert(std::to_string(child_count));
}
// Dir size should not be increased yet
ASSERT_EQ(test_dir_vn->GetSize(), kPageCacheSize);
// Check children are in first dentry page
unittest_lib::CheckChildrenInBlock(test_dir_ptr, 0, child_set);
// if one more child created, new dentry page will be allocated
std::unordered_set<std::string> child_set_second_page;
unittest_lib::CreateChild(test_dir_ptr, S_IFDIR, std::to_string(child_count));
child_set_second_page.insert(std::to_string(child_count));
ASSERT_EQ(test_dir_vn->GetSize(), kPageCacheSize * 2);
unittest_lib::CheckChildrenInBlock(test_dir_ptr, 1, child_set_second_page);
// Delete the last child, then the second page should not be accessed
unittest_lib::DeleteChild(test_dir_ptr, std::to_string(child_count));
std::unordered_set<std::string> empty_set;
unittest_lib::CheckChildrenInBlock(test_dir_ptr, 1, empty_set);
// Delete all children, then check empty dir
for (auto iter : child_set) {
unittest_lib::DeleteChild(test_dir_ptr, iter);
}
unittest_lib::CheckChildrenInBlock(test_dir_ptr, 0, empty_set);
ASSERT_EQ(test_dir_vn->Close(), ZX_OK);
test_dir_vn = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
unittest_lib::Unmount(std::move(fs), &bc);
}
TEST(DirTest, DentryBucket) {
std::unique_ptr<Bcache> bc;
unittest_lib::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
ASSERT_EQ(options.SetValue(options.GetNameView(kOptInlineDentry), 0), ZX_OK);
unittest_lib::MountWithOptions(options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
unittest_lib::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
fbl::RefPtr<fs::Vnode> test_dir;
ASSERT_EQ(root_dir->Create("test", S_IFDIR, &test_dir), ZX_OK);
fbl::RefPtr<VnodeF2fs> test_dir_vn = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(test_dir));
Dir *test_dir_ptr = static_cast<Dir *>(test_dir_vn.get());
// fill level-0 dentry blocks, since it has only one bucket
std::unordered_set<std::string> child_set;
unsigned int child_count = 0;
for (; child_count < kNrDentryInBlock * 2 - 2; ++child_count) {
unittest_lib::CreateChild(test_dir_ptr, S_IFDIR, std::to_string(child_count));
child_set.insert(std::to_string(child_count));
}
// size should be as same as 2 pages
ASSERT_EQ(test_dir_vn->GetSize(), kPageCacheSize * 2);
// at level 1, child will be devided into two buckets, depending on their hash value
std::unordered_set<std::string> first_bucket_child;
std::unordered_set<std::string> second_bucket_child;
for (; child_count < kNrDentryInBlock * 3 - 2; ++child_count) {
std::string name(std::to_string(child_count));
unittest_lib::CreateChild(test_dir_ptr, S_IFDIR, name);
unsigned int bucket_id = DentryHash(name.data(), name.length()) % 2;
if (bucket_id == 0) {
first_bucket_child.insert(name);
} else {
second_bucket_child.insert(name);
}
}
// check level 1, bucket 0
unsigned int bidx = Dir::DirBlockIndex(1, 0);
unittest_lib::CheckChildrenInBlock(test_dir_ptr, bidx, first_bucket_child);
// delete all children in level 1, bucket 0
for (auto iter : first_bucket_child) {
unittest_lib::DeleteChild(test_dir_ptr, iter);
}
std::unordered_set<std::string> empty_set;
unittest_lib::CheckChildrenInBlock(test_dir_ptr, bidx, empty_set);
// check level 1, bucket 1
bidx = Dir::DirBlockIndex(1, 1);
unittest_lib::CheckChildrenInBlock(test_dir_ptr, bidx, second_bucket_child);
// delete all children in level 1, bucket 1
for (auto iter : second_bucket_child) {
unittest_lib::DeleteChild(test_dir_ptr, iter);
}
unittest_lib::CheckChildrenInBlock(test_dir_ptr, bidx, empty_set);
// Delete all children in level 0, then check empty dir
for (auto iter : child_set) {
unittest_lib::DeleteChild(test_dir_ptr, iter);
}
unittest_lib::CheckChildrenInBlock(test_dir_ptr, 0, empty_set);
ASSERT_EQ(test_dir_vn->Close(), ZX_OK);
test_dir_vn = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
unittest_lib::Unmount(std::move(fs), &bc);
}
TEST(DirTest, MultiSlotDentry) {
auto seed = time(nullptr);
srand(seed);
std::cout << "Random seed for DirTest.MultiSlotDentry: " << seed << std::endl;
std::unique_ptr<Bcache> bc;
unittest_lib::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
ASSERT_EQ(options.SetValue(options.GetNameView(kOptInlineDentry), 0), ZX_OK);
unittest_lib::MountWithOptions(options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
unittest_lib::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
fbl::RefPtr<fs::Vnode> test_dir;
ASSERT_EQ(root_dir->Create("test", S_IFDIR, &test_dir), ZX_OK);
fbl::RefPtr<VnodeF2fs> test_dir_vn = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(test_dir));
Dir *test_dir_ptr = static_cast<Dir *>(test_dir_vn.get());
// fill first dentry page
unsigned int slots_filled = 2;
unsigned int max_slots = (kMaxNameLen + kNameLen - 1) / kNameLen;
std::unordered_set<std::string> child_set;
while (slots_filled <= kNrDentryInBlock - max_slots) {
unsigned int namelen = rand() % kMaxNameLen + 1;
std::string name = unittest_lib::GetRandomName(namelen);
unsigned int slots = (namelen + kNameLen - 1) / kNameLen;
if (child_set.find(name) != child_set.end())
continue;
unittest_lib::CreateChild(test_dir_ptr, S_IFDIR, name);
child_set.insert(name);
slots_filled += slots;
}
// check only one dentry page
ASSERT_EQ(test_dir_vn->GetSize(), kPageCacheSize);
// Check children are in first dentry page
unittest_lib::CheckChildrenInBlock(test_dir_ptr, 0, child_set);
// New child with large name than slot, then new dentry page allocated
std::unordered_set<std::string> child_second_page;
unsigned int namelen = (kNrDentryInBlock - slots_filled) * kNameLen + 1;
std::string name;
do {
name = unittest_lib::GetRandomName(namelen);
} while (child_set.find(name) != child_set.end());
unittest_lib::CreateChild(test_dir_ptr, S_IFDIR, name);
child_second_page.insert(name);
ASSERT_EQ(test_dir_vn->GetSize(), kPageCacheSize * 2);
unittest_lib::CheckChildrenInBlock(test_dir_ptr, 1, child_second_page);
// Create new child that can be written in renaming slots in the first page,
// then dentry will be written in the first page.
namelen = (kNrDentryInBlock - slots_filled) * kNameLen;
do {
name = unittest_lib::GetRandomName(namelen);
} while (child_set.find(name) != child_set.end() &&
child_second_page.find(name) != child_second_page.end());
unittest_lib::CreateChild(test_dir_ptr, S_IFDIR, name);
child_set.insert(name);
unittest_lib::CheckChildrenInBlock(test_dir_ptr, 0, child_set);
// Delete all, and check empty dir
child_set.merge(child_second_page);
for (auto iter : child_set) {
unittest_lib::DeleteChild(test_dir_ptr, iter);
}
std::unordered_set<std::string> empty_set;
unittest_lib::CheckChildrenInBlock(test_dir_ptr, 0, empty_set);
ASSERT_EQ(test_dir_vn->Close(), ZX_OK);
test_dir_vn = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
unittest_lib::Unmount(std::move(fs), &bc);
}
} // namespace
} // namespace f2fs