| // 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 { |
| |
| TEST(InlineDirTest, InlineDirCreation) { |
| std::unique_ptr<BcacheMapper> bc; |
| FileTester::MkfsOnFakeDev(&bc); |
| |
| std::unique_ptr<F2fs> fs; |
| MountOptions options{}; |
| // Enable inline dir option |
| ASSERT_EQ(options.SetValue(MountOption::kInlineDentry, 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"); |
| zx::result inline_child = root_dir->Create(inline_dir_name, fs::CreationType::kDirectory); |
| ASSERT_TRUE(inline_child.is_ok()) << inline_child.status_string(); |
| |
| 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(MountOption::kInlineDentry, 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 |
| fbl::RefPtr<fs::Vnode> child; |
| FileTester::Lookup(root_dir.get(), inline_dir_name, &child); |
| inline_child_dir = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(child)); |
| FileTester::CheckInlineDir(inline_child_dir.get()); |
| |
| // However, newly created dir should be non-inline |
| std::string non_inline_dir_name("noninline"); |
| zx::result non_inline_child = root_dir->Create(non_inline_dir_name, fs::CreationType::kDirectory); |
| ASSERT_TRUE(non_inline_child.is_ok()) << non_inline_child.status_string(); |
| |
| 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), FsckOptions{.repair = false}, &bc), ZX_OK); |
| } |
| |
| TEST(InlineDirTest, InlineDirConvert) { |
| std::unique_ptr<BcacheMapper> bc; |
| FileTester::MkfsOnFakeDev(&bc); |
| |
| std::unique_ptr<F2fs> fs; |
| MountOptions options{}; |
| // Enable inline dir option |
| ASSERT_EQ(options.SetValue(MountOption::kInlineDentry, 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"); |
| zx::result inline_child = root_dir->Create(inline_dir_name, fs::CreationType::kDirectory); |
| ASSERT_TRUE(inline_child.is_ok()) << inline_child.status_string(); |
| |
| 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) { |
| umode_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(MountOption::kInlineDentry, 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 |
| fbl::RefPtr<fs::Vnode> child; |
| FileTester::Lookup(root_dir.get(), inline_dir_name, &child); |
| inline_child_dir = fbl::RefPtr<Dir>::Downcast(std::move(child)); |
| FileTester::CheckInlineDir(inline_child_dir.get()); |
| |
| // If one more dentry is added, it should be converted to non-inline dir |
| umode_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), FsckOptions{.repair = false}, &bc), ZX_OK); |
| } |
| |
| TEST(InlineDirTest, InlineDentryOps) { |
| std::unique_ptr<BcacheMapper> bc; |
| FileTester::MkfsOnFakeDev(&bc); |
| |
| std::unique_ptr<F2fs> fs; |
| MountOptions options{}; |
| // Enable inline dir option |
| ASSERT_EQ(options.SetValue(MountOption::kInlineDentry, 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"); |
| zx::result inline_child = root_dir->Create(inline_dir_name, fs::CreationType::kDirectory); |
| ASSERT_TRUE(inline_child.is_ok()) << inline_child.status_string(); |
| |
| 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)); |
| |
| fbl::RefPtr<fs::Vnode> child; |
| FileTester::Lookup(root_dir.get(), inline_dir_name, &child); |
| inline_child_dir = fbl::RefPtr<Dir>::Downcast(std::move(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), FsckOptions{.repair = false}, &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<BcacheMapper> 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)); |
| |
| zx::result vnode = root_dir->Create("alpha", fs::CreationType::kDirectory); |
| ASSERT_TRUE(vnode.is_ok()) << vnode.status_string(); |
| fbl::RefPtr<Dir> parent_dir = fbl::RefPtr<Dir>::Downcast(*std::move(vnode)); |
| |
| vnode = parent_dir->Create("bravo", fs::CreationType::kDirectory); |
| ASSERT_TRUE(vnode.is_ok()) << vnode.status_string(); |
| fbl::RefPtr<Dir> child_dir = fbl::RefPtr<Dir>::Downcast(*std::move(vnode)); |
| |
| vnode = child_dir->Create("charlie", fs::CreationType::kFile); |
| ASSERT_TRUE(vnode.is_ok()) << vnode.status_string(); |
| |
| 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), FsckOptions{.repair = false}), ZX_OK); |
| } |
| |
| TEST(InlineDirTest, InlineDirPino) { |
| std::unique_ptr<BcacheMapper> bc; |
| FileTester::MkfsOnFakeDev(&bc); |
| |
| std::unique_ptr<F2fs> fs; |
| MountOptions options{}; |
| |
| // Enable inline dir option |
| ASSERT_EQ(options.SetValue(MountOption::kInlineDentry, 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 |
| zx::result vnode = root_dir->Create("a", fs::CreationType::kDirectory); |
| ASSERT_TRUE(vnode.is_ok()) << vnode.status_string(); |
| fbl::RefPtr<Dir> a_dir = fbl::RefPtr<Dir>::Downcast(*std::move(vnode)); |
| ASSERT_EQ(a_dir->GetParentNid(), root_dir->Ino()); |
| |
| vnode = root_dir->Create("b", fs::CreationType::kDirectory); |
| ASSERT_TRUE(vnode.is_ok()) << vnode.status_string(); |
| fbl::RefPtr<Dir> b_dir = fbl::RefPtr<Dir>::Downcast(*std::move(vnode)); |
| ASSERT_EQ(b_dir->GetParentNid(), root_dir->Ino()); |
| |
| vnode = a_dir->Create("c", fs::CreationType::kDirectory); |
| ASSERT_TRUE(vnode.is_ok()) << vnode.status_string(); |
| fbl::RefPtr<Dir> c_dir = fbl::RefPtr<Dir>::Downcast(*std::move(vnode)); |
| ASSERT_EQ(c_dir->GetParentNid(), a_dir->Ino()); |
| |
| vnode = a_dir->Create("d", fs::CreationType::kFile); |
| ASSERT_TRUE(vnode.is_ok()) << vnode.status_string(); |
| fbl::RefPtr<File> d1_file = fbl::RefPtr<File>::Downcast(*std::move(vnode)); |
| ASSERT_EQ(d1_file->GetParentNid(), a_dir->Ino()); |
| |
| vnode = b_dir->Create("d", fs::CreationType::kFile); |
| ASSERT_TRUE(vnode.is_ok()) << vnode.status_string(); |
| 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)); |
| |
| fbl::RefPtr<fs::Vnode> lookup_vn; |
| FileTester::Lookup(root_dir.get(), "b", &lookup_vn); |
| b_dir = fbl::RefPtr<Dir>::Downcast(std::move(lookup_vn)); |
| FileTester::Lookup(b_dir.get(), "c", &lookup_vn); |
| c_dir = fbl::RefPtr<Dir>::Downcast(std::move(lookup_vn)); |
| FileTester::Lookup(b_dir.get(), "d", &lookup_vn); |
| d1_file = fbl::RefPtr<File>::Downcast(std::move(lookup_vn)); |
| |
| // 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), FsckOptions{.repair = false}, &bc), ZX_OK); |
| } |
| |
| TEST(InlineDataTest, InlineRegFileTruncate) { |
| srand(testing::UnitTest::GetInstance()->random_seed()); |
| |
| std::unique_ptr<BcacheMapper> bc; |
| FileTester::MkfsOnFakeDev(&bc); |
| |
| 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)); |
| |
| // Inline file creation |
| std::string inline_file_name("inline"); |
| zx::result inline_child = root_dir->Create(inline_file_name, fs::CreationType::kFile); |
| ASSERT_TRUE(inline_child.is_ok()) << inline_child.status_string(); |
| |
| fbl::RefPtr<VnodeF2fs> inline_child_file = |
| fbl::RefPtr<VnodeF2fs>::Downcast(*std::move(inline_child)); |
| |
| inline_child_file->SetFlag(InodeInfoFlag::kInlineData); |
| FileTester::CheckInlineFile(inline_child_file.get()); |
| |
| // Write until entire inline data space is written |
| File *inline_child_file_ptr = static_cast<File *>(inline_child_file.get()); |
| size_t target_size = inline_child_file_ptr->MaxInlineData() - 1; |
| |
| char w_buf[kPageSize]; |
| char r_buf[kPageSize]; |
| |
| for (size_t i = 0; i < kPageSize; ++i) { |
| w_buf[i] = static_cast<char>(rand()); |
| } |
| |
| FileTester::AppendToInline(inline_child_file_ptr, w_buf, target_size); |
| FileTester::CheckInlineFile(inline_child_file.get()); |
| ASSERT_EQ(inline_child_file_ptr->GetSize(), target_size); |
| |
| // Truncate to reduced size, then verify |
| target_size = inline_child_file_ptr->MaxInlineData() / 2; |
| ASSERT_EQ(inline_child_file_ptr->Truncate(target_size), ZX_OK); |
| FileTester::CheckInlineFile(inline_child_file.get()); |
| ASSERT_EQ(inline_child_file_ptr->GetSize(), target_size); |
| |
| // Truncate to original size, then verify |
| target_size = inline_child_file_ptr->MaxInlineData() - 1; |
| |
| for (size_t i = inline_child_file_ptr->MaxInlineData() / 2; i < kPageSize; ++i) { |
| w_buf[i] = 0; |
| } |
| |
| ASSERT_EQ(inline_child_file_ptr->Truncate(target_size), ZX_OK); |
| FileTester::CheckInlineFile(inline_child_file.get()); |
| ASSERT_EQ(inline_child_file_ptr->GetSize(), target_size); |
| |
| // Truncate to more than inline data size, then verify |
| target_size = kPageSize; |
| |
| ASSERT_EQ(inline_child_file_ptr->Truncate(kPageSize), ZX_OK); |
| FileTester::CheckNonInlineFile(inline_child_file.get()); |
| ASSERT_EQ(inline_child_file_ptr->GetSize(), target_size); |
| |
| FileTester::ReadFromFile(inline_child_file_ptr, r_buf, target_size, 0); |
| ASSERT_EQ(memcmp(r_buf, w_buf, target_size), 0); |
| |
| inline_child_file_ptr = nullptr; |
| ASSERT_EQ(inline_child_file->Close(), ZX_OK); |
| inline_child_file = nullptr; |
| ASSERT_EQ(root_dir->Close(), ZX_OK); |
| root_dir = nullptr; |
| |
| FileTester::Unmount(std::move(fs), &bc); |
| |
| // Remount and verify |
| FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs); |
| |
| FileTester::CreateRoot(fs.get(), &root); |
| root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root)); |
| |
| fbl::RefPtr<fs::Vnode> child; |
| FileTester::Lookup(root_dir.get(), inline_file_name, &child); |
| inline_child_file = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(child)); |
| FileTester::CheckNonInlineFile(inline_child_file.get()); |
| |
| inline_child_file_ptr = static_cast<File *>(inline_child_file.get()); |
| ASSERT_EQ(inline_child_file_ptr->GetSize(), target_size); |
| |
| FileTester::ReadFromFile(inline_child_file_ptr, r_buf, target_size, 0); |
| ASSERT_EQ(memcmp(r_buf, w_buf, target_size), 0); |
| |
| inline_child_file_ptr = nullptr; |
| ASSERT_EQ(inline_child_file->Close(), ZX_OK); |
| inline_child_file = nullptr; |
| ASSERT_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(InlineDataTest, DataExistFlag) { |
| std::unique_ptr<BcacheMapper> bc; |
| FileTester::MkfsOnFakeDev(&bc); |
| |
| std::unique_ptr<F2fs> fs; |
| MountOptions options{}; |
| // Enable inline data option |
| 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 file creation, then check if kDataExist flag is unset |
| std::string inline_file_name("inline"); |
| zx::result inline_child = root_dir->Create(inline_file_name, fs::CreationType::kFile); |
| ASSERT_TRUE(inline_child.is_ok()) << inline_child.status_string(); |
| |
| fbl::RefPtr<VnodeF2fs> inline_child_file = |
| fbl::RefPtr<VnodeF2fs>::Downcast(*std::move(inline_child)); |
| |
| inline_child_file->SetFlag(InodeInfoFlag::kInlineData); |
| FileTester::CheckInlineFile(inline_child_file.get()); |
| FileTester::CheckDataExistFlagUnset(inline_child_file.get()); |
| |
| // Write some data, then check if kDataExist flag is set |
| File *inline_child_file_ptr = static_cast<File *>(inline_child_file.get()); |
| constexpr std::string_view data_string = "hello"; |
| |
| FileTester::AppendToInline(inline_child_file_ptr, data_string.data(), data_string.size()); |
| FileTester::CheckInlineFile(inline_child_file.get()); |
| ASSERT_EQ(inline_child_file_ptr->GetSize(), data_string.size()); |
| FileTester::CheckDataExistFlagSet(inline_child_file.get()); |
| |
| // Truncate to non-zero size, then check if kDataExist flag is set |
| ASSERT_EQ(inline_child_file_ptr->Truncate(data_string.size() / 2), ZX_OK); |
| FileTester::CheckInlineFile(inline_child_file.get()); |
| ASSERT_EQ(inline_child_file_ptr->GetSize(), data_string.size() / 2); |
| FileTester::CheckDataExistFlagSet(inline_child_file.get()); |
| |
| // Truncate to zero size, then check if kDataExist flag is unset |
| ASSERT_EQ(inline_child_file_ptr->Truncate(0), ZX_OK); |
| FileTester::CheckInlineFile(inline_child_file.get()); |
| ASSERT_EQ(inline_child_file_ptr->GetSize(), 0UL); |
| FileTester::CheckDataExistFlagUnset(inline_child_file.get()); |
| |
| // Write data again, then check if kDataExist flag is set |
| FileTester::AppendToInline(inline_child_file_ptr, data_string.data(), data_string.size()); |
| FileTester::CheckInlineFile(inline_child_file.get()); |
| ASSERT_EQ(inline_child_file_ptr->GetSize(), data_string.size()); |
| FileTester::CheckDataExistFlagSet(inline_child_file.get()); |
| |
| inline_child_file_ptr = nullptr; |
| ASSERT_EQ(inline_child_file->Close(), ZX_OK); |
| inline_child_file = nullptr; |
| ASSERT_EQ(root_dir->Close(), ZX_OK); |
| root_dir = nullptr; |
| |
| FileTester::Unmount(std::move(fs), &bc); |
| |
| // Remount and check if KDataExist flag is still set |
| FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs); |
| |
| FileTester::CreateRoot(fs.get(), &root); |
| root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root)); |
| |
| fbl::RefPtr<fs::Vnode> child; |
| FileTester::Lookup(root_dir.get(), inline_file_name, &child); |
| inline_child_file = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(child)); |
| FileTester::CheckInlineFile(inline_child_file.get()); |
| FileTester::CheckDataExistFlagSet(inline_child_file.get()); |
| |
| ASSERT_EQ(inline_child_file->Close(), ZX_OK); |
| inline_child_file = nullptr; |
| ASSERT_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); |
| } |
| |
| } // namespace |
| } // namespace f2fs |