blob: 432e7f37a5a3cdc3aaa8589a9e79679719cf3af0 [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 <string.h>
#include <sstream>
#include <block-client/cpp/fake-device.h>
#include <gtest/gtest.h>
#include "third_party/f2fs/f2fs.h"
namespace f2fs {
namespace {
using block_client::FakeBlockDevice;
constexpr uint64_t kBlockCount = 819200;
constexpr uint32_t kBlockSize = 512;
const MkfsOptions default_option;
enum class ArgType {
Label,
SegsPerSec,
SecsPerZone,
Extension,
Heap,
OP,
};
void AddArg(std::vector<const char *> &argv, ArgType t, const char *val) {
switch (t) {
case ArgType::Label:
argv.push_back("-l");
break;
case ArgType::SegsPerSec:
argv.push_back("-s");
break;
case ArgType::SecsPerZone:
argv.push_back("-z");
break;
case ArgType::Extension:
argv.push_back("-e");
break;
case ArgType::Heap:
argv.push_back("-a");
break;
case ArgType::OP:
argv.push_back("-o");
break;
}
argv.push_back(val);
}
void PrintArg(std::vector<const char *> &argv) {
std::cout << "mkfs arg: ";
for (uint32_t i = 0; i < argv.size(); i++)
std::cout << (argv.data())[i] << " ";
std::cout << std::endl;
}
void DoMkfs(Bcache *bc, std::vector<const char *> &argv, bool expect_success) {
if (expect_success)
ASSERT_EQ(Mkfs(bc, static_cast<int>(argv.size()), const_cast<char **>(argv.data())), ZX_OK);
else
ASSERT_NE(Mkfs(bc, static_cast<int>(argv.size()), const_cast<char **>(argv.data())), ZX_OK);
}
void ReadSuperblock(Bcache *bc, SuperBlock *sb) { ASSERT_EQ(LoadSuperblock(bc, sb), ZX_OK); }
void ReadCheckpoint(Bcache *bc, SuperBlock &sb, Checkpoint *ckp) {
char buf[4096];
ASSERT_EQ(bc->Readblk(sb.segment0_blkaddr, buf), ZX_OK);
memcpy(ckp, buf, sizeof(Checkpoint));
}
void VerifyLabel(SuperBlock &sb, const char *label) {
std::string vol_label(label);
std::u16string volume_name;
AsciiToUnicode(vol_label, &volume_name);
uint16_t volume_name_arr[512];
volume_name.copy(reinterpret_cast<char16_t *>(volume_name_arr), vol_label.size());
volume_name_arr[vol_label.size()] = '\0';
ASSERT_EQ(memcmp(sb.volume_name, volume_name_arr, vol_label.size() + 1), 0);
}
void VerifySegsPerSec(SuperBlock &sb, uint32_t segs_per_sec) {
ASSERT_EQ(sb.segs_per_sec, segs_per_sec);
}
void VerifySecsPerZone(SuperBlock &sb, uint32_t secs_per_zone) {
ASSERT_EQ(sb.secs_per_zone, secs_per_zone);
}
void VerifyExtensionList(SuperBlock &sb, const char *extensions) {
ASSERT_LE(sb.extension_count, static_cast<uint32_t>(kMaxExtension));
uint32_t extension_iter = 0;
for (const char *ext_item : kMediaExtList) {
ASSERT_LT(extension_iter, sb.extension_count);
ASSERT_EQ(strcmp(reinterpret_cast<char *>(sb.extension_list[extension_iter++]), ext_item), 0);
}
ASSERT_EQ(extension_iter, sizeof(kMediaExtList) / sizeof(const char *));
std::istringstream iss(extensions);
std::string token;
while (std::getline(iss, token, ',')) {
ASSERT_LE(extension_iter, sb.extension_count);
ASSERT_EQ(strcmp(reinterpret_cast<char *>(sb.extension_list[extension_iter++]), token.c_str()),
0);
if (extension_iter >= kMaxExtension)
break;
}
ASSERT_EQ(extension_iter, sb.extension_count);
}
void VerifyHeapBasedAllocation(SuperBlock &sb, Checkpoint &ckp, bool is_heap_based) {
uint32_t total_zones =
((LeToCpu(sb.segment_count_main) - 1) / sb.segs_per_sec) / sb.secs_per_zone;
ASSERT_GT(total_zones, static_cast<uint32_t>(6));
uint32_t cur_seg[6];
if (is_heap_based) {
cur_seg[static_cast<int>(CursegType::kCursegHotNode)] =
(total_zones - 1) * sb.segs_per_sec * sb.secs_per_zone +
((sb.secs_per_zone - 1) * sb.segs_per_sec);
cur_seg[static_cast<int>(CursegType::kCursegWarmNode)] =
cur_seg[static_cast<int>(CursegType::kCursegHotNode)] - sb.segs_per_sec * sb.secs_per_zone;
cur_seg[static_cast<int>(CursegType::kCursegColdNode)] =
cur_seg[static_cast<int>(CursegType::kCursegWarmNode)] - sb.segs_per_sec * sb.secs_per_zone;
cur_seg[static_cast<int>(CursegType::kCursegHotData)] =
cur_seg[static_cast<int>(CursegType::kCursegColdNode)] - sb.segs_per_sec * sb.secs_per_zone;
cur_seg[static_cast<int>(CursegType::kCursegColdData)] = 0;
cur_seg[static_cast<int>(CursegType::kCursegWarmData)] =
cur_seg[static_cast<int>(CursegType::kCursegColdData)] + sb.segs_per_sec * sb.secs_per_zone;
} else {
cur_seg[static_cast<int>(CursegType::kCursegHotNode)] = 0;
cur_seg[static_cast<int>(CursegType::kCursegWarmNode)] =
cur_seg[static_cast<int>(CursegType::kCursegHotNode)] + sb.segs_per_sec * sb.secs_per_zone;
cur_seg[static_cast<int>(CursegType::kCursegColdNode)] =
cur_seg[static_cast<int>(CursegType::kCursegWarmNode)] + sb.segs_per_sec * sb.secs_per_zone;
cur_seg[static_cast<int>(CursegType::kCursegHotData)] =
cur_seg[static_cast<int>(CursegType::kCursegColdNode)] + sb.segs_per_sec * sb.secs_per_zone;
cur_seg[static_cast<int>(CursegType::kCursegColdData)] =
cur_seg[static_cast<int>(CursegType::kCursegHotData)] + sb.segs_per_sec * sb.secs_per_zone;
cur_seg[static_cast<int>(CursegType::kCursegWarmData)] =
cur_seg[static_cast<int>(CursegType::kCursegColdData)] + sb.segs_per_sec * sb.secs_per_zone;
}
ASSERT_EQ(ckp.cur_node_segno[0], cur_seg[static_cast<int>(CursegType::kCursegHotNode)]);
ASSERT_EQ(ckp.cur_node_segno[1], cur_seg[static_cast<int>(CursegType::kCursegWarmNode)]);
ASSERT_EQ(ckp.cur_node_segno[2], cur_seg[static_cast<int>(CursegType::kCursegColdNode)]);
ASSERT_EQ(ckp.cur_data_segno[0], cur_seg[static_cast<int>(CursegType::kCursegHotData)]);
ASSERT_EQ(ckp.cur_data_segno[1], cur_seg[static_cast<int>(CursegType::kCursegWarmData)]);
ASSERT_EQ(ckp.cur_data_segno[2], cur_seg[static_cast<int>(CursegType::kCursegColdData)]);
}
void VerifyOP(SuperBlock &sb, Checkpoint &ckp, uint32_t op_ratio) {
uint32_t overprov_segment_count =
CpuToLe((LeToCpu(sb.segment_count_main) - LeToCpu(ckp.rsvd_segment_count)) * op_ratio / 100 +
LeToCpu(ckp.rsvd_segment_count));
ASSERT_EQ(ckp.overprov_segment_count, overprov_segment_count);
}
TEST(FormatFilesystemTest, MkfsOptionsLabel) {
auto device = std::make_unique<FakeBlockDevice>(kBlockCount, kBlockSize);
std::unique_ptr<Bcache> bc;
bool readonly_device = false;
ASSERT_EQ(CreateBcache(std::move(device), &readonly_device, &bc), ZX_OK);
char label[20];
const char *default_label = "F2FS";
// Check default label is written when there is no arg for label
std::vector<const char *> argv = {"mkfs"};
DoMkfs(bc.get(), argv, true);
SuperBlock sb = {};
ReadSuperblock(bc.get(), &sb);
VerifyLabel(sb, default_label);
// Try with max size label (16)
argv.clear();
argv.push_back("mkfs");
memcpy(label, "0123456789abcde", 16);
AddArg(argv, ArgType::Label, label);
DoMkfs(bc.get(), argv, true);
ReadSuperblock(bc.get(), &sb);
VerifyLabel(sb, label);
// Check failure with label size larger than max size
argv.clear();
argv.push_back("mkfs");
memcpy(label, "0123456789abcdef", 17);
AddArg(argv, ArgType::Label, label);
DoMkfs(bc.get(), argv, false);
}
TEST(FormatFilesystemTest, MkfsOptionsSegsPerSec) {
auto device = std::make_unique<FakeBlockDevice>(kBlockCount, kBlockSize);
std::unique_ptr<Bcache> bc;
bool readonly_device = false;
ASSERT_EQ(CreateBcache(std::move(device), &readonly_device, &bc), ZX_OK);
// Check default value
std::vector<const char *> argv = {"mkfs"};
DoMkfs(bc.get(), argv, true);
SuperBlock sb = {};
ReadSuperblock(bc.get(), &sb);
VerifySegsPerSec(sb, default_option.segs_per_sec);
// Try with various values
const uint32_t segs_per_sec_list[] = {1, 2, 4, 8};
for (uint32_t segs_per_sec : segs_per_sec_list) {
std::cout << "segs_per_sec = " << segs_per_sec << std::endl;
argv.clear();
argv.push_back("mkfs");
AddArg(argv, ArgType::SegsPerSec, std::to_string(segs_per_sec).c_str());
DoMkfs(bc.get(), argv, true);
ReadSuperblock(bc.get(), &sb);
VerifySegsPerSec(sb, segs_per_sec);
}
// Check failure with zero
argv.clear();
argv.push_back("mkfs");
AddArg(argv, ArgType::SegsPerSec, "0");
DoMkfs(bc.get(), argv, false);
}
TEST(FormatFilesystemTest, MkfsOptionsSecsPerZone) {
auto device = std::make_unique<FakeBlockDevice>(kBlockCount, kBlockSize);
std::unique_ptr<Bcache> bc;
bool readonly_device = false;
ASSERT_EQ(CreateBcache(std::move(device), &readonly_device, &bc), ZX_OK);
// Check default value
std::vector<const char *> argv = {"mkfs"};
DoMkfs(bc.get(), argv, true);
SuperBlock sb = {};
ReadSuperblock(bc.get(), &sb);
VerifySecsPerZone(sb, default_option.secs_per_zone);
// Try with various values
const uint32_t secs_per_zone_list[] = {1, 2, 4, 8};
for (uint32_t secs_per_zone : secs_per_zone_list) {
std::cout << "secs_per_zone = " << secs_per_zone << std::endl;
argv.clear();
argv.push_back("mkfs");
AddArg(argv, ArgType::SecsPerZone, std::to_string(secs_per_zone).c_str());
DoMkfs(bc.get(), argv, true);
ReadSuperblock(bc.get(), &sb);
VerifySecsPerZone(sb, secs_per_zone);
}
// Check failure with zero
argv.clear();
argv.push_back("mkfs");
AddArg(argv, ArgType::SecsPerZone, "0");
DoMkfs(bc.get(), argv, false);
}
TEST(FormatFilesystemTest, MkfsOptionsExtensions) {
auto device = std::make_unique<FakeBlockDevice>(kBlockCount, kBlockSize);
std::unique_ptr<Bcache> bc;
bool readonly_device = false;
ASSERT_EQ(CreateBcache(std::move(device), &readonly_device, &bc), ZX_OK);
// Check default
std::vector<const char *> argv = {"mkfs"};
DoMkfs(bc.get(), argv, true);
SuperBlock sb = {};
ReadSuperblock(bc.get(), &sb);
VerifyExtensionList(sb, "");
// Try with max extension counts
std::string extensions("");
for (uint32_t i = sizeof(kMediaExtList) / sizeof(const char *); i < kMaxExtension; i++) {
if (i > sizeof(kMediaExtList) / sizeof(const char *))
extensions.append(",");
extensions.append(std::to_string(i));
}
char ext_arg_buf[512];
strcpy(ext_arg_buf, extensions.c_str());
argv.clear();
argv.push_back("mkfs");
AddArg(argv, ArgType::Extension, ext_arg_buf);
DoMkfs(bc.get(), argv, true);
ReadSuperblock(bc.get(), &sb);
VerifyExtensionList(sb, extensions.c_str());
// If exeeding max extension counts, only extensions within max count are valid
extensions.append(",foo");
strcpy(ext_arg_buf, extensions.c_str());
argv.clear();
argv.push_back("mkfs");
AddArg(argv, ArgType::Extension, ext_arg_buf);
DoMkfs(bc.get(), argv, true);
ReadSuperblock(bc.get(), &sb);
VerifyExtensionList(sb, extensions.c_str());
}
TEST(FormatFilesystemTest, MkfsOptionsHeapBasedAlloc) {
auto device = std::make_unique<FakeBlockDevice>(kBlockCount, kBlockSize);
std::unique_ptr<Bcache> bc;
bool readonly_device = false;
ASSERT_EQ(CreateBcache(std::move(device), &readonly_device, &bc), ZX_OK);
// Check default
std::vector<const char *> argv = {"mkfs"};
DoMkfs(bc.get(), argv, true);
SuperBlock sb = {};
ReadSuperblock(bc.get(), &sb);
Checkpoint ckp = {};
ReadCheckpoint(bc.get(), sb, &ckp);
VerifyHeapBasedAllocation(sb, ckp, default_option.heap_based_allocation);
// If arg set to 0, not using heap-based allocation
argv.clear();
argv.push_back("mkfs");
AddArg(argv, ArgType::Heap, "0");
DoMkfs(bc.get(), argv, true);
ReadSuperblock(bc.get(), &sb);
ReadCheckpoint(bc.get(), sb, &ckp);
VerifyHeapBasedAllocation(sb, ckp, false);
// If arg set to 1, using heap-based allocation
argv.clear();
argv.push_back("mkfs");
AddArg(argv, ArgType::Heap, "1");
DoMkfs(bc.get(), argv, true);
ReadSuperblock(bc.get(), &sb);
ReadCheckpoint(bc.get(), sb, &ckp);
VerifyHeapBasedAllocation(sb, ckp, true);
}
TEST(FormatFilesystemTest, MkfsOptionsOverprovision) {
auto device = std::make_unique<FakeBlockDevice>(kBlockCount, kBlockSize);
std::unique_ptr<Bcache> bc;
bool readonly_device = false;
ASSERT_EQ(CreateBcache(std::move(device), &readonly_device, &bc), ZX_OK);
// Check default
std::vector<const char *> argv = {"mkfs"};
DoMkfs(bc.get(), argv, true);
SuperBlock sb = {};
ReadSuperblock(bc.get(), &sb);
Checkpoint ckp = {};
ReadCheckpoint(bc.get(), sb, &ckp);
VerifyOP(sb, ckp, default_option.overprovision_ratio);
// Try with various values
const uint32_t overprovision_ratio_list[] = {1, 3, 5};
for (uint32_t overprovision_ratio : overprovision_ratio_list) {
std::cout << "overprovision_ratio = " << overprovision_ratio << std::endl;
argv.clear();
argv.push_back("mkfs");
AddArg(argv, ArgType::OP, std::to_string(overprovision_ratio).c_str());
DoMkfs(bc.get(), argv, true);
ReadCheckpoint(bc.get(), sb, &ckp);
VerifyOP(sb, ckp, overprovision_ratio);
}
// Check failure with zero
argv.clear();
argv.push_back("mkfs");
AddArg(argv, ArgType::OP, "0");
DoMkfs(bc.get(), argv, false);
}
TEST(FormatFilesystemTest, MkfsOptionsMixed) {
auto device = std::make_unique<FakeBlockDevice>(kBlockCount, kBlockSize);
std::unique_ptr<Bcache> bc;
bool readonly_device = false;
ASSERT_EQ(CreateBcache(std::move(device), &readonly_device, &bc), ZX_OK);
const char *label_list[] = {"aa", "bbbbb"};
const uint32_t segs_per_sec_list[] = {2, 4};
const uint32_t secs_per_zone_list[] = {2, 4};
const char *ext_list[] = {"foo", "foo,bar"};
const uint32_t heap_based_list[] = {0};
const uint32_t overprovision_list[] = {1, 3};
for (const char *label : label_list) {
for (const uint32_t segs_per_sec : segs_per_sec_list) {
for (const uint32_t secs_per_zone : secs_per_zone_list) {
for (const char *extensions : ext_list) {
for (const uint32_t heap_based : heap_based_list) {
for (const uint32_t overprovision : overprovision_list) {
std::vector<const char *> argv = {"mkfs"};
char ext_arg_buf[512];
AddArg(argv, ArgType::Label, label);
AddArg(argv, ArgType::SegsPerSec, std::to_string(segs_per_sec).c_str());
AddArg(argv, ArgType::SecsPerZone, std::to_string(secs_per_zone).c_str());
// Mkfs will tokenize extension list using strtok().
// Since strtok() needs to modify original string, use non-const buffer to deliver
// argument
strcpy(ext_arg_buf, extensions);
AddArg(argv, ArgType::Extension, ext_arg_buf);
AddArg(argv, ArgType::Heap, std::to_string(heap_based).c_str());
AddArg(argv, ArgType::OP, std::to_string(overprovision).c_str());
PrintArg(argv);
DoMkfs(bc.get(), argv, true);
SuperBlock sb = {};
ReadSuperblock(bc.get(), &sb);
Checkpoint ckp = {};
ReadCheckpoint(bc.get(), sb, &ckp);
VerifyLabel(sb, label);
VerifySegsPerSec(sb, segs_per_sec);
VerifySecsPerZone(sb, secs_per_zone);
VerifyExtensionList(sb, extensions);
VerifyHeapBasedAllocation(sb, ckp, (heap_based != 0));
VerifyOP(sb, ckp, overprovision);
}
}
}
}
}
}
}
} // namespace
} // namespace f2fs