blob: 9a5f4a7d85d9a9081afac4103490c0376a90bd43 [file] [edit]
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <android-base/logging.h>
#include <gtest/gtest.h>
#include <libdm/loop_control.h>
#include <logwrap/logwrap.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <linux/f2fs.h>
#include <linux/fs.h>
#include <chrono>
#include <fstream>
using LoopDevice = android::dm::LoopDevice;
using namespace std::chrono_literals;
static const char* kMkfsPath = "/system/bin/make_f2fs";
static const char* kMountPath = "/system/bin/mount";
static const char* kUmountPath = "/system/bin/umount";
static const char* kTestFilePath = "/data/local/tmp/mnt/test";
namespace android {
class F2fsTest : public testing::Test {
void SetUp() override {
int fd = open("/data/local/tmp/img", O_RDWR | O_TRUNC | O_CREAT,
(S_IRWXU | S_IRGRP | S_IROTH));
int flags = FS_COMPR_FL;
int res;
page_size = getpagesize();
std::string block_size = std::to_string(page_size);
ASSERT_NE(fd, -1);
res = ftruncate(fd, 100 << 20); // 100 MB
ASSERT_EQ(res, 0);
close(fd);
std::vector<const char*> make_fs_argv = {
kMkfsPath, "-f", "-O", "extra_attr", "-O", "project_quota",
"-O", "compression", "-O", "casefold", "-C", "utf8",
"-g", "android",
};
make_fs_argv.push_back("-w");
make_fs_argv.push_back(block_size.c_str());
make_fs_argv.push_back("-b");
make_fs_argv.push_back(block_size.c_str());
make_fs_argv.push_back("/data/local/tmp/img");
res = logwrap_fork_execvp(make_fs_argv.size(), make_fs_argv.data(), nullptr,
false, LOG_KLOG, true, nullptr);
ASSERT_EQ(res, 0);
mkdir("/data/local/tmp/mnt", (S_IRWXU | S_IRGRP | S_IROTH));
LoopDevice loop_dev("/data/local/tmp/img", 10s);
ASSERT_TRUE(loop_dev.valid());
ASSERT_EQ(mount(loop_dev.device().c_str(), "/data/local/tmp/mnt", "f2fs", 0,
"compress_mode=user"),
0);
test_data1 = malloc(page_size);
ASSERT_NE(test_data1, nullptr);
memset(test_data1, 0x41, page_size);
test_data2 = malloc(page_size);
ASSERT_NE(test_data2, nullptr);
memset(test_data2, 0x61, page_size);
}
void TearDown() override {
ASSERT_EQ(umount2("/data/local/tmp/mnt", MNT_DETACH), 0);
ASSERT_EQ(unlink("/data/local/tmp/img"), 0);
ASSERT_EQ(rmdir("/data/local/tmp/mnt"), 0);
free(test_data1);
free(test_data2);
}
protected:
void* test_data1;
void* test_data2;
int page_size;
};
TEST_F(F2fsTest, test_normal_lseek) {
char buf[page_size];
int fd = open(kTestFilePath, O_RDWR | O_TRUNC | O_CREAT,
(S_IRWXU | S_IRGRP | S_IROTH));
ASSERT_NE(fd, -1);
ASSERT_EQ(lseek(fd, 1024 * page_size, SEEK_SET), 1024 * page_size);
for (int i = 0; i < 1024; i++) {
ASSERT_EQ(write(fd, test_data1, page_size), page_size);
}
fsync(fd);
ASSERT_EQ(lseek(fd, 0, SEEK_HOLE), 0);
ASSERT_EQ(lseek(fd, 0, SEEK_DATA), 1024 * page_size);
lseek(fd, 0, SEEK_SET);
write(fd, test_data2, page_size);
fsync(fd);
ASSERT_EQ(lseek(fd, 0, SEEK_DATA), 0);
ASSERT_EQ(lseek(fd, 0, SEEK_HOLE), page_size);
ASSERT_EQ(lseek(fd, page_size + 904, SEEK_DATA), 1024 * page_size);
}
TEST_F(F2fsTest, test_compressed_lseek) {
char buf[page_size];
int fd = open(kTestFilePath, O_RDWR | O_TRUNC | O_CREAT,
(S_IRWXU | S_IRGRP | S_IROTH));
ASSERT_NE(fd, -1);
int flags = FS_COMPR_FL;
ASSERT_NE(ioctl(fd, FS_IOC_SETFLAGS, &flags), -1);
ASSERT_EQ(lseek(fd, 1024 * page_size, SEEK_SET), 1024 * page_size);
for (int i = 0; i < 1024; i++) {
ASSERT_EQ(write(fd, test_data1, page_size), page_size);
}
fsync(fd);
ASSERT_EQ(lseek(fd, 0, SEEK_HOLE), 0);
ASSERT_EQ(lseek(fd, 0, SEEK_DATA), 1024 * page_size);
ASSERT_NE(ioctl(fd, F2FS_IOC_COMPRESS_FILE), -1);
lseek(fd, 0, SEEK_SET);
write(fd, test_data2, page_size);
fsync(fd);
ASSERT_EQ(lseek(fd, 0, SEEK_DATA), 0);
ASSERT_EQ(lseek(fd, 0, SEEK_HOLE), page_size);
ASSERT_EQ(lseek(fd, page_size + 904, SEEK_DATA), 1024 * page_size);
}
TEST_F(F2fsTest, test_sparse_decompress) {
char buf[page_size];
int res;
int fd = open(kTestFilePath, O_RDWR | O_TRUNC | O_CREAT,
(S_IRWXU | S_IRGRP | S_IROTH));
ASSERT_NE(fd, -1);
int flags = FS_COMPR_FL;
ASSERT_NE(fd, -1);
ASSERT_NE(ioctl(fd, FS_IOC_SETFLAGS, &flags), -1);
res = lseek(fd, 1024 * page_size, SEEK_SET);
ASSERT_EQ(res, 1024 * page_size);
for (int i = 0; i < 1024; i++) {
res = write(fd, test_data1, page_size);
ASSERT_EQ(res, page_size);
}
fsync(fd);
ASSERT_NE(ioctl(fd, F2FS_IOC_COMPRESS_FILE), -1);
lseek(fd, 0, SEEK_SET);
write(fd, test_data2, page_size);
fsync(fd);
int pid = fork();
if (pid == 0) {
// If this fails, we must reset the device or it will be left in a bad state
exit(ioctl(fd, F2FS_IOC_DECOMPRESS_FILE));
}
int status;
int time = 0;
while (time < 50) {
res = waitpid(pid, &status, WNOHANG);
if (res) {
ASSERT_EQ(pid, res);
ASSERT_EQ(WIFEXITED(status), true);
ASSERT_EQ(WEXITSTATUS(status), 0);
break;
}
sleep(5);
time += 5;
}
if (!res) {
std::ofstream reboot_trigger("/proc/sysrq-trigger");
reboot_trigger << "c";
reboot_trigger.close();
return;
}
close(fd);
// Check for corruption
fd = open(kTestFilePath, O_RDONLY);
ASSERT_NE(fd, -1);
res = read(fd, buf, page_size);
ASSERT_EQ(res, page_size);
ASSERT_EQ(memcmp(buf, test_data2, page_size), 0);
char empty_buf[page_size];
memset(empty_buf, 0, page_size);
for (int i = 1; i < 1024; i++) {
res = read(fd, buf, page_size);
ASSERT_EQ(res, page_size);
ASSERT_EQ(memcmp(buf, empty_buf, page_size), 0);
}
for (int i = 0; i < 1024; i++) {
res = read(fd, buf, page_size);
ASSERT_EQ(res, page_size);
ASSERT_EQ(memcmp(buf, test_data1, page_size), 0);
}
close(fd);
}
TEST_F(F2fsTest, test_casefolding_ignorable_codepoint) {
const char* kTestFolder = "/data/local/tmp/mnt/cf/";
const char* kTestFileCase1Path = "/data/local/tmp/mnt/cf/❤"; // u2764
const char* kTestFileCase2Path = "/data/local/tmp/mnt/cf/❤️"; // u2764 + ufe0f
struct stat stat1;
struct stat stat2;
ASSERT_EQ(mkdir(kTestFolder, (S_IRWXU | S_IRGRP | S_IROTH)), 0);
int fd = open(kTestFolder, O_RDONLY | O_DIRECTORY);
ASSERT_NE(fd, -1);
int flag = FS_CASEFOLD_FL;
ASSERT_EQ(ioctl(fd, FS_IOC_SETFLAGS, &flag), 0);
close(fd);
fd = open(kTestFileCase1Path, O_RDWR | O_TRUNC | O_CREAT,
(S_IRWXU | S_IRGRP | S_IROTH));
ASSERT_NE(fd, -1);
ASSERT_NE(fstat(fd, &stat1), -1);
close(fd);
fd = open(kTestFileCase2Path, O_RDWR | O_TRUNC | O_CREAT,
(S_IRWXU | S_IRGRP | S_IROTH));
ASSERT_NE(fd, -1);
ASSERT_NE(fstat(fd, &stat2), -1);
close(fd);
ASSERT_EQ(stat1.st_ino, stat2.st_ino);
}
} // namespace android