blob: ac0854c8594eee6a2b35a62444601b7123db79ff [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/lib/storage/block_client/cpp/fake_block_device.h"
#include "src/storage/f2fs/f2fs.h"
#include "unit_lib.h"
namespace f2fs {
namespace {
class FileTest : public F2fsFakeDevTestFixture {
public:
FileTest()
: F2fsFakeDevTestFixture(TestOptions{
.block_count = uint64_t{8} * 1024 * 1024 * 1024 / kDefaultSectorSize,
}) {}
};
TEST_F(FileTest, BlkAddrLevel) {
srand(testing::UnitTest::GetInstance()->random_seed());
fbl::RefPtr<fs::Vnode> test_file;
ASSERT_EQ(root_dir_->Create("test", S_IFREG, &test_file), ZX_OK);
fbl::RefPtr<VnodeF2fs> test_file_vn = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(test_file));
File *test_file_ptr = static_cast<File *>(test_file_vn.get());
char buf[kPageSize];
unsigned int level = 0;
for (size_t i = 0; i < kPageSize; ++i) {
buf[i] = static_cast<char>(rand());
}
// fill kAddrsPerInode blocks
for (int i = 0; i < kAddrsPerInode; ++i) {
FileTester::AppendToFile(test_file_ptr, buf, kPageSize);
}
// check direct node #1 is not available yet
MapTester::CheckNodeLevel(fs_.get(), test_file_ptr, level);
// fill one more block
FileTester::AppendToFile(test_file_ptr, buf, kPageSize);
// check direct node #1 is available
MapTester::CheckNodeLevel(fs_.get(), test_file_ptr, ++level);
// fill direct node #1
for (int i = 1; i < kAddrsPerBlock; ++i) {
FileTester::AppendToFile(test_file_ptr, buf, kPageSize);
}
// check direct node #2 is not available yet
MapTester::CheckNodeLevel(fs_.get(), test_file_ptr, level);
// fill one more block
FileTester::AppendToFile(test_file_ptr, buf, kPageSize);
// check direct node #2 is available
MapTester::CheckNodeLevel(fs_.get(), test_file_ptr, ++level);
// fill direct node #2
for (int i = 1; i < kAddrsPerBlock; ++i) {
FileTester::AppendToFile(test_file_ptr, buf, kPageSize);
}
// check indirect node #1 is not available yet
MapTester::CheckNodeLevel(fs_.get(), test_file_ptr, level);
// fill one more block
FileTester::AppendToFile(test_file_ptr, buf, kPageSize);
// check indirect node #1 is available
MapTester::CheckNodeLevel(fs_.get(), test_file_ptr, ++level);
ASSERT_EQ(test_file_vn->Close(), ZX_OK);
test_file_vn = nullptr;
}
TEST_F(FileTest, NidAndBlkaddrAllocFree) {
srand(testing::UnitTest::GetInstance()->random_seed());
fbl::RefPtr<fs::Vnode> test_file;
ASSERT_EQ(root_dir_->Create("test", S_IFREG, &test_file), ZX_OK);
fbl::RefPtr<VnodeF2fs> test_file_vn = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(test_file));
File *test_file_ptr = static_cast<File *>(test_file_vn.get());
char buf[kPageSize];
for (size_t i = 0; i < kPageSize; ++i) {
buf[i] = static_cast<char>(rand() % 128);
}
// Fill until direct nodes are full
unsigned int level = 2;
for (int i = 0; i < kAddrsPerInode + kAddrsPerBlock * 2; ++i) {
FileTester::AppendToFile(test_file_ptr, buf, kPageSize);
}
test_file_ptr->SyncFile(0, test_file_ptr->GetSize(), false);
MapTester::CheckNodeLevel(fs_.get(), test_file_ptr, level);
// Build nid and blkaddr set
std::unordered_set<nid_t> nid_set;
std::unordered_set<block_t> blkaddr_set;
nid_set.insert(test_file_ptr->Ino());
{
LockedPage ipage;
ASSERT_EQ(fs_->GetNodeManager().GetNodePage(test_file_ptr->Ino(), &ipage), ZX_OK);
Inode *inode = &(ipage->GetAddress<Node>()->i);
for (int i = 0; i < kNidsPerInode; ++i) {
if (inode->i_nid[i] != 0U)
nid_set.insert(inode->i_nid[i]);
}
for (int i = 0; i < kAddrsPerInode; ++i) {
ASSERT_NE(inode->i_addr[i], kNullAddr);
blkaddr_set.insert(inode->i_addr[i]);
}
for (int i = 0; i < 2; ++i) {
LockedPage direct_node_page;
ASSERT_EQ(fs_->GetNodeManager().GetNodePage(inode->i_nid[i], &direct_node_page), ZX_OK);
DirectNode *direct_node = &(direct_node_page->GetAddress<Node>()->dn);
for (int j = 0; j < kAddrsPerBlock; j++) {
ASSERT_NE(direct_node->addr[j], kNullAddr);
blkaddr_set.insert(direct_node->addr[j]);
}
}
}
ASSERT_EQ(nid_set.size(), level + 1);
ASSERT_EQ(blkaddr_set.size(), static_cast<uint32_t>(kAddrsPerInode + kAddrsPerBlock * 2));
// After writing checkpoint, check if nids are removed from free nid list
// Also, for allocated blkaddr, check if corresponding bit is set in valid bitmap of segment
fs_->WriteCheckpoint(false, false);
MapTester::CheckNidsInuse(fs_.get(), nid_set);
MapTester::CheckBlkaddrsInuse(fs_.get(), blkaddr_set);
// Remove file, writing checkpoint, then check if nids are added to free nid list
// Also, for allocated blkaddr, check if corresponding bit is cleared in valid bitmap of segment
ASSERT_EQ(test_file_vn->Close(), ZX_OK);
test_file_vn = nullptr;
root_dir_->Unlink("test", false);
fs_->WriteCheckpoint(false, false);
MapTester::CheckNidsFree(fs_.get(), nid_set);
MapTester::CheckBlkaddrsFree(fs_.get(), blkaddr_set);
test_file_vn = nullptr;
}
TEST_F(FileTest, FileReadExceedFileSize) {
srand(testing::UnitTest::GetInstance()->random_seed());
fbl::RefPtr<fs::Vnode> test_file;
ASSERT_EQ(root_dir_->Create("test", S_IFREG, &test_file), ZX_OK);
fbl::RefPtr<VnodeF2fs> test_file_vn = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(test_file));
File *test_file_ptr = static_cast<File *>(test_file_vn.get());
uint32_t data_size = kPageSize * 7 / 4;
uint32_t read_location = kPageSize * 5 / 4;
char w_buf[data_size];
char r_buf[read_location + kPageSize];
for (size_t i = 0; i < data_size; ++i) {
w_buf[i] = static_cast<char>(rand() % 128);
}
// Write data
FileTester::AppendToFile(test_file_ptr, w_buf, data_size);
ASSERT_EQ(test_file_ptr->GetSize(), data_size);
size_t out;
// Read first part of file
ASSERT_EQ(test_file_ptr->Read(r_buf, read_location, 0, &out), ZX_OK);
ASSERT_EQ(out, read_location);
// Read excess file size, then check if actual read size does not exceed the end of file
ASSERT_EQ(test_file_ptr->Read(&(r_buf[read_location]), kPageSize, read_location, &out), ZX_OK);
ASSERT_EQ(out, data_size - read_location);
ASSERT_EQ(memcmp(r_buf, w_buf, data_size), 0);
ASSERT_EQ(test_file_vn->Close(), ZX_OK);
test_file_vn = nullptr;
}
TEST(FileTest2, FailedNidReuse) {
std::unique_ptr<Bcache> bc;
constexpr uint64_t kBlockCount = 409600;
FileTester::MkfsOnFakeDevWithOptions(&bc, MkfsOptions(), kBlockCount);
std::unique_ptr<F2fs> fs;
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), MountOptions{}, &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> tmp_child;
uint32_t iter = 0;
while (true) {
if (auto err = root_dir->Create(std::to_string(++iter), S_IFREG, &tmp_child); err != ZX_OK) {
ASSERT_EQ(err, ZX_ERR_NO_SPACE);
break;
}
ASSERT_EQ(tmp_child->Close(), ZX_OK);
tmp_child = nullptr;
}
const uint32_t kIteration = fs->GetNodeManager().GetFreeNidCount() + 1;
for (uint32_t i = 0; i < kIteration; ++i) {
ASSERT_EQ(root_dir->Create(std::to_string(++iter), S_IFREG, &tmp_child), ZX_ERR_NO_SPACE);
}
for (uint32_t i = 0; i < kIteration; ++i) {
ASSERT_EQ(root_dir->Create(std::to_string(++iter), S_IFDIR, &tmp_child), ZX_ERR_NO_SPACE);
}
root_dir->Close();
root_dir = nullptr;
FileTester::Unmount(std::move(fs), &bc);
}
TEST(DeadLockTest, Truncate) {
std::unique_ptr<Bcache> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Disable inline data option
ASSERT_EQ(options.SetValue(options.GetNameView(kOptInlineData), 0), 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));
fbl::RefPtr<fs::Vnode> test_file;
root_dir->Create("test2", S_IFREG, &test_file);
fbl::RefPtr<f2fs::File> vn = fbl::RefPtr<f2fs::File>::Downcast(std::move(test_file));
constexpr int kNTry = 1000;
uint8_t buf[kPageSize * 2] = {1};
FileTester::AppendToFile(vn.get(), buf, sizeof(buf));
std::thread thread1 = std::thread([&]() {
for (int i = 0; i < kNTry; ++i) {
size_t out_actual;
ASSERT_EQ(vn->Write(buf, sizeof(buf), 0, &out_actual), ZX_OK);
}
});
std::thread thread2 = std::thread([&]() {
for (int i = 0; i < kNTry; ++i) {
ASSERT_EQ(vn->Truncate(0), ZX_OK);
}
});
thread1.join();
thread2.join();
vn->Close();
vn = nullptr;
root_dir->Close();
root_dir = nullptr;
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
} // namespace
} // namespace f2fs