blob: c9561ccd7c43d0f4a35fec9c369a8041b0e1926c [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 "src/storage/f2fs/f2fs.h"
#include "unit_lib.h"
namespace f2fs {
namespace {
TEST(InlineDirTest, InlineDirCreation) {
std::unique_ptr<Bcache> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable inline dir option
ASSERT_EQ(options.SetValue(options.GetNameView(kOptInlineDentry), 1), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// Inline dir creation
std::string inline_dir_name("inline");
fbl::RefPtr<fs::Vnode> inline_child;
ASSERT_EQ(root_dir->Create(inline_dir_name, S_IFDIR, &inline_child), ZX_OK);
fbl::RefPtr<VnodeF2fs> inline_child_dir =
fbl::RefPtr<VnodeF2fs>::Downcast(std::move(inline_child));
FileTester::CheckInlineDir(inline_child_dir.get());
ASSERT_EQ(inline_child_dir->Close(), ZX_OK);
inline_child_dir = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
FileTester::Unmount(std::move(fs), &bc);
// Disable inline dir option
ASSERT_EQ(options.SetValue(options.GetNameView(kOptInlineDentry), 0), ZX_OK);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
FileTester::CreateRoot(fs.get(), &root);
root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// Check if existing inline dir is still inline regardless of mount option
FileTester::Lookup(root_dir.get(), inline_dir_name, &inline_child);
inline_child_dir = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(inline_child));
FileTester::CheckInlineDir(inline_child_dir.get());
// However, newly created dir should be non-inline
std::string non_inline_dir_name("noninline");
fbl::RefPtr<fs::Vnode> non_inline_child;
ASSERT_EQ(root_dir->Create(non_inline_dir_name, S_IFDIR, &non_inline_child), ZX_OK);
fbl::RefPtr<VnodeF2fs> non_inline_child_dir =
fbl::RefPtr<VnodeF2fs>::Downcast(std::move(non_inline_child));
FileTester::CheckNonInlineDir(non_inline_child_dir.get());
ASSERT_EQ(inline_child_dir->Close(), ZX_OK);
inline_child_dir = nullptr;
ASSERT_EQ(non_inline_child_dir->Close(), ZX_OK);
non_inline_child_dir = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), &bc), ZX_OK);
}
TEST(InlineDirTest, InlineDirConvert) {
std::unique_ptr<Bcache> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable inline dir option
ASSERT_EQ(options.SetValue(options.GetNameView(kOptInlineDentry), 1), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// Inline dir creation
std::string inline_dir_name("inline");
fbl::RefPtr<fs::Vnode> inline_child;
ASSERT_EQ(root_dir->Create(inline_dir_name, S_IFDIR, &inline_child), ZX_OK);
fbl::RefPtr<Dir> inline_child_dir = fbl::RefPtr<Dir>::Downcast(std::move(inline_child));
unsigned int child_count = 0;
// Fill all slots of inline dentry
// Since two dentry slots are already allocated for "." and "..", decrease 2 from kNrInlineDentry
for (; child_count < inline_child_dir->MaxInlineDentry() - 2; ++child_count) {
uint32_t mode = child_count % 2 == 0 ? S_IFDIR : S_IFREG;
FileTester::CreateChild(inline_child_dir.get(), mode, std::to_string(child_count));
}
// It should be inline
FileTester::CheckInlineDir(inline_child_dir.get());
ASSERT_EQ(inline_child_dir->Close(), ZX_OK);
inline_child_dir = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
FileTester::Unmount(std::move(fs), &bc);
// Disable inline dir option
ASSERT_EQ(options.SetValue(options.GetNameView(kOptInlineDentry), 0), ZX_OK);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
FileTester::CreateRoot(fs.get(), &root);
root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// Check if existing inline dir is still inline regardless of mount option
FileTester::Lookup(root_dir.get(), inline_dir_name, &inline_child);
inline_child_dir = fbl::RefPtr<Dir>::Downcast(std::move(inline_child));
FileTester::CheckInlineDir(inline_child_dir.get());
// If one more dentry is added, it should be converted to non-inline dir
uint32_t mode = child_count % 2 == 0 ? S_IFDIR : S_IFREG;
FileTester::CreateChild(inline_child_dir.get(), mode, std::to_string(child_count));
FileTester::CheckNonInlineDir(inline_child_dir.get());
ASSERT_EQ(inline_child_dir->Close(), ZX_OK);
inline_child_dir = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), &bc), ZX_OK);
}
TEST(InlineDirTest, InlineDentryOps) {
std::unique_ptr<Bcache> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable inline dir option
ASSERT_EQ(options.SetValue(options.GetNameView(kOptInlineDentry), 1), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// Inline dir creation
std::string inline_dir_name("inline");
fbl::RefPtr<fs::Vnode> inline_child;
ASSERT_EQ(root_dir->Create(inline_dir_name, S_IFDIR, &inline_child), ZX_OK);
fbl::RefPtr<Dir> inline_child_dir = fbl::RefPtr<Dir>::Downcast(std::move(inline_child));
std::unordered_set<std::string> child_set = {"a", "b", "c", "d", "e"};
Dir *dir_ptr = inline_child_dir.get();
for (auto iter : child_set) {
FileTester::CreateChild(dir_ptr, S_IFDIR, iter);
}
FileTester::CheckChildrenFromReaddir(dir_ptr, child_set);
// remove "b" and "d"
ASSERT_EQ(dir_ptr->Unlink("b", true), ZX_OK);
child_set.erase("b");
ASSERT_EQ(dir_ptr->Unlink("d", true), ZX_OK);
child_set.erase("d");
FileTester::CheckChildrenFromReaddir(dir_ptr, child_set);
// create "f" and "g"
FileTester::CreateChild(dir_ptr, S_IFDIR, "f");
child_set.insert("f");
FileTester::CreateChild(dir_ptr, S_IFDIR, "g");
child_set.insert("g");
FileTester::CheckChildrenFromReaddir(dir_ptr, child_set);
// rename "g" to "h"
ASSERT_EQ(dir_ptr->Rename(inline_child_dir, "g", "h", true, true), ZX_OK);
child_set.erase("g");
child_set.insert("h");
FileTester::CheckChildrenFromReaddir(dir_ptr, child_set);
// fill all inline dentry slots
auto child_count = child_set.size();
for (; child_count < inline_child_dir->MaxInlineDentry() - 2; ++child_count) {
FileTester::CreateChild(dir_ptr, S_IFDIR, std::to_string(child_count));
child_set.insert(std::to_string(child_count));
}
FileTester::CheckChildrenFromReaddir(dir_ptr, child_set);
// It should be inline
FileTester::CheckInlineDir(dir_ptr);
// one more entry
FileTester::CreateChild(dir_ptr, S_IFDIR, std::to_string(child_count));
child_set.insert(std::to_string(child_count));
FileTester::CheckChildrenFromReaddir(dir_ptr, child_set);
// It should be non inline
FileTester::CheckNonInlineDir(dir_ptr);
ASSERT_EQ(inline_child_dir->Close(), ZX_OK);
inline_child_dir = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
FileTester::Unmount(std::move(fs), &bc);
// Check dentry after remount
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
FileTester::CreateRoot(fs.get(), &root);
root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
FileTester::Lookup(root_dir.get(), inline_dir_name, &inline_child);
inline_child_dir = fbl::RefPtr<Dir>::Downcast(std::move(inline_child));
dir_ptr = inline_child_dir.get();
FileTester::CheckNonInlineDir(dir_ptr);
FileTester::CheckChildrenFromReaddir(dir_ptr, child_set);
ASSERT_EQ(inline_child_dir->Close(), ZX_OK);
inline_child_dir = nullptr;
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = nullptr;
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), &bc), ZX_OK);
}
TEST(InlineDirTest, NestedInlineDirectories) {
// There was a reported malfunction of inline-directories when the volume size is small.
// This test evaluates such case.
std::unique_ptr<Bcache> bc;
FileTester::MkfsOnFakeDev(&bc, 102400, 512);
std::unique_ptr<F2fs> fs;
MountOptions options{};
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
fbl::RefPtr<fs::Vnode> vnode;
ASSERT_EQ(root_dir->Create(std::string("alpha"), S_IFDIR, &vnode), ZX_OK);
fbl::RefPtr<Dir> parent_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
ASSERT_EQ(parent_dir->Create(std::string("bravo"), S_IFDIR, &vnode), ZX_OK);
fbl::RefPtr<Dir> child_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
ASSERT_EQ(child_dir->Create(std::string("charlie"), S_IFREG, &vnode), ZX_OK);
fbl::RefPtr<File> child_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
char data[] = "Hello, world!";
FileTester::AppendToFile(child_file.get(), data, sizeof(data));
ASSERT_EQ(child_file->Close(), ZX_OK);
ASSERT_EQ(child_dir->Close(), ZX_OK);
ASSERT_EQ(parent_dir->Close(), ZX_OK);
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = parent_dir = child_dir = nullptr;
child_file = nullptr;
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc)), ZX_OK);
}
TEST(InlineDirTest, InlineDirPino) {
std::unique_ptr<Bcache> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Enable inline dir option
ASSERT_EQ(options.SetValue(options.GetNameView(kOptInlineDentry), 1), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
// Inline dir creation
fbl::RefPtr<fs::Vnode> vnode;
ASSERT_EQ(root_dir->Create("a", S_IFDIR, &vnode), ZX_OK);
fbl::RefPtr<Dir> a_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
ASSERT_EQ(a_dir->GetParentNid(), root_dir->Ino());
ASSERT_EQ(root_dir->Create("b", S_IFDIR, &vnode), ZX_OK);
fbl::RefPtr<Dir> b_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
ASSERT_EQ(b_dir->GetParentNid(), root_dir->Ino());
ASSERT_EQ(a_dir->Create("c", S_IFDIR, &vnode), ZX_OK);
fbl::RefPtr<Dir> c_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
ASSERT_EQ(c_dir->GetParentNid(), a_dir->Ino());
ASSERT_EQ(a_dir->Create("d", S_IFREG, &vnode), ZX_OK);
fbl::RefPtr<File> d1_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
ASSERT_EQ(d1_file->GetParentNid(), a_dir->Ino());
ASSERT_EQ(b_dir->Create("d", S_IFREG, &vnode), ZX_OK);
fbl::RefPtr<File> d2_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
ASSERT_EQ(d2_file->GetParentNid(), b_dir->Ino());
// rename "/a/c" to "/b/c" and "/a/d" to "/b/d"
ASSERT_EQ(a_dir->Rename(b_dir, "c", "c", true, true), ZX_OK);
ASSERT_EQ(a_dir->Rename(b_dir, "d", "d", false, false), ZX_OK);
// Check i_pino of renamed directory
ASSERT_EQ(c_dir->GetParentNid(), b_dir->Ino());
ASSERT_EQ(d1_file->GetParentNid(), b_dir->Ino());
ASSERT_EQ(d1_file->Close(), ZX_OK);
ASSERT_EQ(d2_file->Close(), ZX_OK);
ASSERT_EQ(c_dir->Close(), ZX_OK);
ASSERT_EQ(b_dir->Close(), ZX_OK);
ASSERT_EQ(a_dir->Close(), ZX_OK);
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = a_dir = b_dir = c_dir = nullptr;
d1_file = d2_file = nullptr;
// Remount
FileTester::Unmount(std::move(fs), &bc);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
FileTester::CreateRoot(fs.get(), &root);
root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
FileTester::Lookup(root_dir.get(), "b", &vnode);
b_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
FileTester::Lookup(b_dir.get(), "c", &vnode);
c_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
FileTester::Lookup(b_dir.get(), "d", &vnode);
d1_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
// Check i_pino of renamed directory
ASSERT_EQ(c_dir->GetParentNid(), b_dir->Ino());
ASSERT_EQ(d1_file->GetParentNid(), b_dir->Ino());
ASSERT_EQ(d1_file->Close(), ZX_OK);
ASSERT_EQ(c_dir->Close(), ZX_OK);
ASSERT_EQ(b_dir->Close(), ZX_OK);
ASSERT_EQ(root_dir->Close(), ZX_OK);
root_dir = b_dir = c_dir = nullptr;
d1_file = nullptr;
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), &bc), ZX_OK);
}
} // namespace
} // namespace f2fs