blob: a91bb578096a45f0b464513f4c0843857c86e32b [file] [log] [blame]
// Copyright 2022 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 "src/storage/f2fs/test/compatibility/compatibility.h"
namespace f2fs {
std::string ConvertModeString(mode_t mode) {
std::stringstream ss;
ss << std::oct << mode;
return ss.str();
}
std::string EscapedFilename(std::string_view filename) {
std::set<char> special_character = {'`', '\"', '\\', '$'};
std::string ret = "\"";
for (size_t i = 0; i < filename.length(); ++i) {
if (special_character.find(filename[i]) != special_character.end()) {
ret.push_back('\\');
}
ret.push_back(filename[i]);
}
ret.push_back('\"');
return ret;
}
bool LinuxTestFile::IsValid() {
std::string result;
linux_operator_->ExecuteWithAssert(
{"bash -c 'set +H;[ -e", EscapedFilename(filename_), "]; echo $?'"}, &result);
return result == "0" || result == "0\n";
}
ssize_t LinuxTestFile::Write(const void* buf, size_t count) {
const uint8_t* buf_c = static_cast<const uint8_t*>(buf);
std::string hex_string;
for (uint64_t i = 0; i < count; ++i) {
char substring[10];
sprintf(substring, "\\x%02x", buf_c[i]);
hex_string.append(substring);
if (i > 0 && i % 500 == 0) {
linux_operator_->ExecuteWithAssert(
{"echo", "-en", std::string("\"").append(hex_string).append("\""), ">>", filename_});
hex_string.clear();
}
}
linux_operator_->ExecuteWithAssert(
{"echo", "-en", std::string("\"").append(hex_string).append("\""), ">>", filename_});
linux_operator_->ExecuteWithAssert({"ls -al", filename_});
return count;
}
int LinuxTestFile::Fchmod(mode_t mode) {
std::string result;
linux_operator_->ExecuteWithAssert({"chmod", ConvertModeString(mode), filename_}, &result);
if (result.length() > 0) {
return -1;
}
return 0;
}
int LinuxTestFile::Fstat(struct stat& file_stat) {
std::string result;
linux_operator_->ExecuteWithAssert({"stat", "-c", "\"%i %f %h %s %Z %Y %b\"", filename_},
&result);
std::vector<std::string> tokens;
std::stringstream ss(result);
std::string token;
while (getline(ss, token, ' ')) {
tokens.push_back(token);
}
file_stat.st_ino = std::stoul(tokens[0]);
{
std::stringstream ss;
ss << std::hex << tokens[1];
ss >> file_stat.st_mode;
}
file_stat.st_nlink = std::stoul(tokens[2]);
file_stat.st_size = std::stol(tokens[3]);
file_stat.st_ctime = std::stoul(tokens[4]);
file_stat.st_mtime = std::stoul(tokens[5]);
file_stat.st_blocks = std::stol(tokens[6]);
return 0;
}
int LinuxTestFile::Ftruncate(off_t len) {
std::string result;
linux_operator_->ExecuteWithAssert({"truncate", "-s", std::to_string(len), filename_}, &result);
return result.length() == 0 ? 0 : -1;
}
int LinuxTestFile::Fallocate(int mode, off_t offset, off_t len) {
auto converted_filename = filename_;
std::string command = "fallocate ";
if (mode & FALLOC_FL_PUNCH_HOLE) {
command.append("-p ");
}
if (mode & FALLOC_FL_KEEP_SIZE) {
command.append("-n ");
}
command.append("-o ")
.append(std::to_string(offset))
.append(" -l ")
.append(std::to_string(len))
.append(" ")
.append(converted_filename);
linux_operator_->ExecuteWithAssert({command});
return 0;
}
void LinuxTestFile::WritePattern(size_t block_count, size_t interval) {
for (uint32_t i = 0; i < block_count; i += interval) {
std::string pattern = std::to_string(i);
ASSERT_EQ(Write(pattern.c_str(), pattern.length()), static_cast<ssize_t>(pattern.length()));
off_t next_size = std::min(i + interval, block_count) * kBlockSize;
ASSERT_EQ(Ftruncate(next_size), 0);
}
}
void LinuxTestFile::VerifyPattern(size_t block_count, size_t interval) {
for (uint32_t i = 0; i < block_count; i += interval) {
std::string result;
linux_operator_->ExecuteWithAssert(
{"od -An -j", std::to_string(i * kBlockSize), "-N",
std::to_string(std::to_string(i).length()), "-c", filename_, "| tr -d ' \\n'"},
&result);
ASSERT_EQ(result, std::to_string(i));
}
}
ssize_t FuchsiaTestFile::Read(void* buf, size_t count) {
if (!vnode_->IsReg()) {
return 0;
}
File* file = static_cast<File*>(vnode_.get());
size_t ret = 0;
zx::stream stream;
if (auto ret = file->CreateStream(ZX_STREAM_MODE_READ, &stream); ret != ZX_OK) {
return 0;
}
zx_iovec_t iov = {
.buffer = buf,
.capacity = count,
};
if (stream.readv_at(0, offset_, &iov, 1, &ret) != ZX_OK) {
return 0;
}
offset_ += ret;
return ret;
}
ssize_t FuchsiaTestFile::Write(const void* buf, size_t count) {
if (!vnode_->IsReg()) {
return 0;
}
File* file = static_cast<File*>(vnode_.get());
size_t ret = 0;
zx::stream stream;
if (auto ret = file->CreateStream(ZX_STREAM_MODE_WRITE, &stream); ret != ZX_OK) {
return 0;
}
// Since zx_iovec_t::buffer is not a const type, we make a copied buffer and use it.
auto copied = std::make_unique<uint8_t[]>(count);
std::memcpy(copied.get(), buf, count);
zx_iovec_t iov = {
.buffer = copied.get(),
.capacity = count,
};
if (stream.writev_at(0, offset_, &iov, 1, &ret) != ZX_OK) {
return 0;
}
offset_ += ret;
return ret;
}
int FuchsiaTestFile::Fstat(struct stat& file_stat) {
fs::VnodeAttributes attr;
if (zx_status_t status = vnode_->GetAttributes(&attr); status != ZX_OK) {
return -EIO;
}
file_stat.st_ino = attr.inode;
file_stat.st_mode = static_cast<mode_t>(attr.mode);
file_stat.st_nlink = attr.link_count;
file_stat.st_size = attr.content_size;
file_stat.st_ctim.tv_sec = attr.creation_time / ZX_SEC(1);
file_stat.st_ctim.tv_nsec = attr.creation_time % ZX_SEC(1);
file_stat.st_mtim.tv_sec = attr.modification_time / ZX_SEC(1);
file_stat.st_mtim.tv_nsec = attr.modification_time % ZX_SEC(1);
file_stat.st_blocks = (vnode_->GetBlocks())
<< vnode_->fs()->GetSuperblockInfo().GetLogSectorsPerBlock();
return 0;
}
int FuchsiaTestFile::Ftruncate(off_t len) {
if (!vnode_->IsReg()) {
return -ENOTSUP;
}
File* file = static_cast<File*>(vnode_.get());
if (zx_status_t status = file->Truncate(len); status != ZX_OK) {
return -EIO;
}
return 0;
}
void FuchsiaTestFile::WritePattern(size_t block_count, size_t interval) {
char buffer[kBlockSize];
for (uint32_t i = 0; i < block_count; ++i) {
std::memset(buffer, 0, kBlockSize);
if (i % interval == 0) {
strcpy(buffer, std::to_string(i).c_str());
}
ASSERT_EQ(Write(buffer, sizeof(buffer)), static_cast<ssize_t>(sizeof(buffer)));
}
}
void FuchsiaTestFile::VerifyPattern(size_t block_count, size_t interval) {
char buffer[kBlockSize];
char zero_buffer[kBlockSize];
std::memset(zero_buffer, 0, kBlockSize);
for (uint32_t i = 0; i < block_count; ++i) {
ASSERT_EQ(Read(buffer, sizeof(buffer)), static_cast<ssize_t>(sizeof(buffer)));
if (i % interval == 0) {
ASSERT_EQ(std::string(buffer), std::to_string(i));
} else {
ASSERT_EQ(memcmp(buffer, zero_buffer, kBlockSize), 0);
}
}
}
zx_status_t LinuxOperator::Execute(const std::vector<std::string>& argv, std::string* result) {
return debian_guest_->Execute(argv, {}, zx::time::infinite(), result, nullptr);
}
void LinuxOperator::ExecuteWithAssert(const std::vector<std::string>& argv, std::string* result) {
ASSERT_EQ(Execute(argv, result), ZX_OK);
}
std::string LinuxOperator::ConvertPath(std::string_view path) {
// Convert only if |path| starts with |kLinuxPathPrefix|
if (path.substr(0, kLinuxPathPrefix.length()) == kLinuxPathPrefix) {
return std::string(mount_path_).append("/").append(path.substr(kLinuxPathPrefix.length()));
}
return std::string(path);
}
void LinuxOperator::CheckLinuxVersion(const int major, const int minor) {
std::string result;
ExecuteWithAssert({"uname -r | cut -d'.' -f1"}, &result);
int actual_major = std::stoi(result);
result.clear();
ExecuteWithAssert({"uname -r | cut -d'.' -f2"}, &result);
int actual_minor = std::stoi(result);
if (actual_major < major || (actual_major == major && actual_minor < minor)) {
GTEST_SKIP() << "need Linux version > " << major << "." << minor;
}
}
void LinuxOperator::CheckF2fsToolsVersion(const int major, const int minor) {
std::string result;
ExecuteWithAssert({"mkfs.f2fs -V | cut -d' ' -f2 | cut -d'.' -f1"}, &result);
int actual_major = std::stoi(result);
result.clear();
ExecuteWithAssert({"mkfs.f2fs -V | cut -d' ' -f2 | cut -d'.' -f2"}, &result);
int actual_minor = std::stoi(result);
if (actual_major < major || (actual_major == major && actual_minor < minor)) {
GTEST_SKIP() << "need f2fs-tools version > " << major << "." << minor;
}
}
void LinuxOperator::Mkfs(std::string_view opt) {
ExecuteWithAssert({"mkfs.f2fs", test_device_, "-f", opt.data()});
}
void LinuxOperator::Fsck() { ExecuteWithAssert({"fsck.f2fs", test_device_, "--dry-run"}); }
void LinuxOperator::Mount(std::string_view opt) {
ExecuteWithAssert({"mkdir", "-p", mount_path_});
ExecuteWithAssert({"mount", test_device_, mount_path_, opt.data()});
}
void LinuxOperator::Umount() { ExecuteWithAssert({"umount", mount_path_}); }
void LinuxOperator::Mkdir(std::string_view path, mode_t mode) {
ExecuteWithAssert({"mkdir", "-m", ConvertModeString(mode), ConvertPath(path)});
}
int LinuxOperator::Rmdir(std::string_view path) {
std::string result;
ExecuteWithAssert({"rmdir", ConvertPath(path)}, &result);
return (result.length() == 0) ? 0 : -1;
}
std::unique_ptr<TestFile> LinuxOperator::Open(std::string_view path, int flags, mode_t mode) {
if (flags & O_CREAT) {
if (flags & O_DIRECTORY) {
Mkdir(path, mode);
} else {
ExecuteWithAssert({"bash -c 'set +H;touch", EscapedFilename(ConvertPath(path)), ";chmod ",
ConvertModeString(mode), EscapedFilename(ConvertPath(path)), "'"});
}
}
return std::unique_ptr<TestFile>(new LinuxTestFile(ConvertPath(path), this));
}
void LinuxOperator::Rename(std::string_view oldpath, std::string_view newpath) {
ExecuteWithAssert({"mv", ConvertPath(oldpath), ConvertPath(newpath)});
}
void FuchsiaOperator::Mkfs(MkfsOptions opt) {
MkfsWorker mkfs(std::move(bc_), opt);
auto ret = mkfs.DoMkfs();
ASSERT_TRUE(ret.is_ok());
bc_ = std::move(*ret);
}
void FuchsiaOperator::Fsck() {
FsckWorker fsck(std::move(bc_), FsckOptions{.repair = false});
ASSERT_EQ(fsck.Run(), ZX_OK);
bc_ = fsck.Destroy();
}
void FuchsiaOperator::Mount(MountOptions opt) {
auto vfs_or = Runner::CreateRunner(loop_.dispatcher());
ASSERT_TRUE(vfs_or.is_ok());
auto fs_or = F2fs::Create(nullptr, std::move(bc_), opt, (*vfs_or).get());
ASSERT_TRUE(fs_or.is_ok());
(*fs_or)->SetVfsForTests(std::move(*vfs_or));
fs_ = std::move(*fs_or);
ASSERT_EQ(VnodeF2fs::Vget(fs_.get(), fs_->GetSuperblockInfo().GetRootIno(), &root_), ZX_OK);
ASSERT_EQ(root_->Open(root_->ValidateOptions(fs::VnodeConnectionOptions()).value(), nullptr),
ZX_OK);
}
void FuchsiaOperator::Umount() {
ASSERT_EQ(root_->Close(), ZX_OK);
root_.reset();
fs_->Sync();
fs_->PutSuper();
auto vfs_or = fs_->TakeVfsForTests();
ASSERT_TRUE(vfs_or.is_ok());
auto bc_or = fs_->TakeBc();
ASSERT_TRUE(bc_or.is_ok());
bc_ = std::move(*bc_or);
(*vfs_or).reset();
}
void FuchsiaOperator::Mkdir(std::string_view path, mode_t mode) {
auto new_dir = Open(path, O_CREAT | O_EXCL, S_IFDIR | mode);
ASSERT_TRUE(new_dir->IsValid());
}
int FuchsiaOperator::Rmdir(std::string_view path) {
auto ret = GetLastDirVnodeAndFileName(path);
if (ret.is_error()) {
return -1;
}
auto [parent_vnode, child_name] = ret.value();
if (zx_status_t status = fs_->vfs()->Unlink(parent_vnode, child_name, true); status != ZX_OK) {
return -1;
}
return 0;
}
std::unique_ptr<TestFile> FuchsiaOperator::Open(std::string_view path, int flags, mode_t mode) {
auto result = fs_->vfs()->Open(root_, path, ConvertFlag(flags), fs::Rights::ReadWrite(), mode);
if (result.is_error()) {
return std::unique_ptr<TestFile>(new FuchsiaTestFile(nullptr));
}
fbl::RefPtr<VnodeF2fs> vnode = fbl::RefPtr<VnodeF2fs>::Downcast(result.ok().vnode);
std::unique_ptr<TestFile> ret = std::make_unique<FuchsiaTestFile>(std::move(vnode));
return ret;
}
void FuchsiaOperator::Rename(std::string_view oldpath, std::string_view newpath) {
auto result = GetLastDirVnodeAndFileName(oldpath);
ASSERT_TRUE(result.is_ok());
auto [oldparent_vn, oldchild_name] = result.value();
result = GetLastDirVnodeAndFileName(newpath);
ASSERT_TRUE(result.is_ok());
auto [newparent_vn, newchild_name] = result.value();
ASSERT_EQ(oldparent_vn->Rename(newparent_vn, oldchild_name, newchild_name, false, false), ZX_OK);
}
uint32_t FuchsiaOperator::MaxInlineDentrySlots() {
Mkfs();
MountOptions options;
options.SetValue(MountOption::kInlineDentry, 1);
Mount(options);
auto umount = fit::defer([&] { Umount(); });
// Create inline directory
constexpr std::string_view inline_dir_path = "/inline";
Mkdir(inline_dir_path, 0755);
auto inline_dir = Open(inline_dir_path, O_RDWR, 0644);
Dir* raw_ptr =
static_cast<Dir*>(static_cast<FuchsiaTestFile*>(inline_dir.get())->GetRawVnodePtr());
return raw_ptr->MaxInlineDentry();
}
uint32_t FuchsiaOperator::MaxInlineDataLength() {
Mkfs();
MountOptions options;
Mount(options);
auto umount = fit::defer([&] { Umount(); });
auto inline_file = Open("/inline", O_RDWR | O_CREAT, 0644);
File* raw_ptr =
static_cast<File*>(static_cast<FuchsiaTestFile*>(inline_file.get())->GetRawVnodePtr());
return raw_ptr->MaxInlineData();
}
zx::result<std::pair<fbl::RefPtr<fs::Vnode>, std::string>>
FuchsiaOperator::GetLastDirVnodeAndFileName(std::string_view absolute_path) {
std::filesystem::path path(absolute_path);
if (!path.has_root_directory() || !path.has_filename()) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
fbl::RefPtr<fs::Vnode> vnode = root_;
for (auto token : path.parent_path().relative_path()) {
if (auto ret = vnode->Lookup(token.string(), &vnode); ret != ZX_OK) {
return zx::error(ret);
}
}
return zx::ok(make_pair(std::move(vnode), path.filename().string()));
}
fs::VnodeConnectionOptions ConvertFlag(int flags) {
fs::VnodeConnectionOptions options;
// TODO: O_PATH, O_DIRECT, O_TRUNC, O_APPEND
switch (flags & O_ACCMODE) {
case O_RDONLY:
options.rights.read = true;
break;
case O_WRONLY:
options.rights.write = true;
break;
case O_RDWR:
options.rights.read = true;
options.rights.write = true;
break;
default:
break;
}
if (flags & O_CREAT) {
options.flags.create = true;
}
if (flags & O_EXCL) {
options.flags.fail_if_exists = true;
}
return options;
}
void CompareStat(const struct stat& a, const struct stat& b) {
EXPECT_EQ(a.st_ino, b.st_ino);
EXPECT_EQ(a.st_mode, b.st_mode);
EXPECT_EQ(a.st_nlink, b.st_nlink);
EXPECT_EQ(a.st_size, b.st_size);
EXPECT_EQ(a.st_ctime, b.st_ctime);
EXPECT_EQ(a.st_mtime, b.st_mtime);
ASSERT_EQ(a.st_blocks, b.st_blocks);
}
} // namespace f2fs