Files
esp-idf/components/fatfs/host_test/main/test_fatfs_vfs.cpp
T

580 lines
16 KiB
C++

/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "ff.h"
#include "esp_partition.h"
#include "wear_levelling.h"
#include "diskio_impl.h"
#include "diskio_wl.h"
#include "esp_vfs_fat.h"
#include "esp_vfs.h"
#include <catch2/catch_test_macros.hpp>
static const char* TAG = "linux_vfs";
static BYTE pdrv;
static wl_handle_t wl_handle;
static char drv[3];
static void test_setup(void)
{
FATFS *fs;
esp_err_t ret = ESP_OK;
FRESULT fr_result;
esp_err_t esp_result;
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, "storage");
REQUIRE(partition != NULL);
// Mount wear-levelled partition
esp_result = wl_mount(partition, &wl_handle);
REQUIRE(esp_result == ESP_OK);
// Get a physical drive
esp_result = ff_diskio_get_drive(&pdrv);
REQUIRE(esp_result == ESP_OK);
// Register physical drive as wear-levelled partition
esp_result = ff_diskio_register_wl_partition(pdrv, wl_handle);
char temp_drv[3] = {(char)('0' + pdrv), ':', 0};
strncpy(drv, temp_drv, 3);
// Create FAT volume on the entire disk
LBA_t part_list[] = {100, 0, 0, 0};
BYTE workbuf[FF_MAX_SS];
const size_t workbuf_size = sizeof(workbuf);
fr_result = f_fdisk(pdrv, part_list, workbuf);
REQUIRE(fr_result == FR_OK);
const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, 0};
fr_result = f_mkfs("", &opt, workbuf, workbuf_size); // Use default volume
REQUIRE(fr_result == FR_OK);
esp_vfs_fat_conf_t conf = {
.base_path = "/linux",
.fat_drive = drv,
.max_files = 5,
};
ret = esp_vfs_fat_register_cfg(&conf, &fs);
if (ret == ESP_ERR_INVALID_STATE) {
// it's okay, already registered with VFS
} else if (ret != ESP_OK) {
ESP_LOGD(TAG, "esp_vfs_fat_register_cfg failed 0x(%x)", ret);
}
REQUIRE(ret == ESP_OK);
//Mount the volume
fr_result = f_mount(fs, "", 0);
REQUIRE(fr_result == FR_OK);
}
static void test_teardown(void)
{
esp_err_t esp_result;
FRESULT fr_result;
// Unmount default volume
fr_result = f_mount(0, drv, 0);
REQUIRE(fr_result == FR_OK);
ff_diskio_unregister(pdrv);
ff_diskio_clear_pdrv_wl(wl_handle);
esp_result = wl_unmount(wl_handle);
esp_vfs_fat_unregister_path("/linux");
REQUIRE(esp_result == ESP_OK);
}
static void test_fatfs_create_file_with_text(const char* name, const char* text)
{
int fd = -1;
fd = open(name, O_CREAT|O_RDWR, 0777);
REQUIRE(fd != -1);
ssize_t sz = write(fd, text, strlen(text));
REQUIRE(sz == strlen(text));
REQUIRE(0 == close(fd));
}
static void test_open_read_write_file()
{
constexpr const char *test_str = "0123456789\n";
const char *filename = "/linux/test.txt";
test_fatfs_create_file_with_text(filename, test_str);
int fd = open(filename, O_RDWR);
char data[strlen(test_str)];
size_t sz = read(fd, data, strlen(test_str));
REQUIRE(sz == strlen(test_str));
REQUIRE(0 == strncmp(data, test_str, strlen(test_str)));
close(fd);
}
TEST_CASE("open file, write and read back data via VFS", "[fatfs]")
{
test_setup();
test_open_read_write_file();
test_teardown();
}
static void test_lseek()
{
const char *test_str = "0123456789\n";
const char *filename = "/linux/lseek.txt";
test_fatfs_create_file_with_text(filename, test_str);
int fd = open(filename, O_RDWR);
off_t off = lseek(fd, 6, SEEK_CUR);
REQUIRE(off == 6);
off = lseek(fd, 3, SEEK_SET);
REQUIRE(off == 3);
off = lseek(fd, -9, SEEK_END);
REQUIRE(off == 2);
close(fd);
}
TEST_CASE("lseek via VFS", "[fatfs]")
{
test_setup();
test_lseek();
test_teardown();
}
static void test_pread()
{
char buf[32] = { 0 };
const char *test_str = "0123456789\n";
const char *filename = "/linux/pread.txt";
test_fatfs_create_file_with_text(filename, test_str);
int fd = open(filename, O_RDWR);
int r = pread(fd, buf, sizeof(buf), 0); // it is a regular read() with offset==0
REQUIRE(0 == strcmp(test_str, buf));
REQUIRE(strlen(test_str) == r);
memset(buf, 0, sizeof(buf));
r = pread(fd, buf, sizeof(buf), 1); // offset==1
REQUIRE(0 == strcmp(test_str + 1, buf));
REQUIRE(strlen(test_str) - 1 == r);
memset(buf, 0, sizeof(buf));
r = pread(fd, buf, sizeof(buf), 5); // offset==5
REQUIRE(0 == strcmp(test_str + 5, buf));
REQUIRE(strlen(test_str) - 5 == r);
close(fd);
}
TEST_CASE("can read file with pread", "[fatfs]")
{
test_setup();
test_pread();
test_teardown();
}
static void test_pwrite()
{
const char *test_str = "0123456789\n";
const char *msg = "ESP";
const char *filename = "/linux/pwrite.txt";
test_fatfs_create_file_with_text(filename, test_str);
int fd = open(filename, O_RDWR);
const off_t current_pos = lseek(fd, 0, SEEK_END); // O_APPEND is not the same - jumps to the end only before write()
const int r = pwrite(fd, msg, strlen(msg), 0);
REQUIRE(strlen(msg) == r);
REQUIRE(current_pos == lseek(fd, 0, SEEK_CUR)); // pwrite should not move the pointer
close(fd);
}
TEST_CASE("pwrite() works well", "[fatfs]")
{
test_setup();
test_pwrite();
test_teardown();
}
static void test_link_unlink_rename()
{
const char *filename_prefix = "/linux/link";
char name_copy[64];
char name_dst[64];
char name_src[64];
snprintf(name_copy, sizeof(name_copy), "%s_cpy.txt", filename_prefix);
snprintf(name_dst, sizeof(name_dst), "%s_dst.txt", filename_prefix);
snprintf(name_src, sizeof(name_src), "%s_src.txt", filename_prefix);
unlink(name_copy);
unlink(name_dst);
unlink(name_src);
const char *test_str = "0123456789\n";
test_fatfs_create_file_with_text(name_src, test_str);
REQUIRE(0 == link(name_src, name_copy));
REQUIRE(-1 != open(name_copy, O_RDONLY));
test_fatfs_create_file_with_text(name_copy, test_str);
rename(name_copy, name_dst);
REQUIRE(-1 == open(name_copy, O_RDONLY));
REQUIRE(-1 != open(name_dst, O_RDONLY));
unlink(name_dst);
unlink(name_src);
REQUIRE(-1 == open(name_src, O_RDONLY));
REQUIRE(-1 == open(name_dst, O_RDONLY));
}
TEST_CASE("link copies a file, unlink removes file, rename moves a file", "[fatfs]")
{
test_setup();
test_link_unlink_rename();
test_teardown();
}
static void test_opendir_closedir_readdir()
{
const char *dirname = "/linux/dir";
REQUIRE(0 == mkdir(dirname, 0755));
char name_dir_file[64];
const char * file_name = "test_opd.txt";
snprintf(name_dir_file, sizeof(name_dir_file), "%s/%s", dirname, file_name);
unlink(name_dir_file);
test_fatfs_create_file_with_text(name_dir_file, "test_opendir\n");
DIR* dir = opendir(dirname);
REQUIRE(dir != NULL);
bool found = false;
while (true) {
struct dirent* de = readdir(dir);
if (!de) {
break;
}
if (strcasecmp(de->d_name, file_name) == 0) {
found = true;
break;
}
}
REQUIRE(found == true);
REQUIRE(0 == closedir(dir));
unlink(name_dir_file);
}
void test_fatfs_opendir_readdir_rewinddir(const char* dir_prefix)
{
char name_dir_inner_file[64];
char name_dir_inner[64];
char name_dir_file3[64];
char name_dir_file2[64];
char name_dir_file1[64];
snprintf(name_dir_inner_file, sizeof(name_dir_inner_file), "%s/inner/3.txt", dir_prefix);
snprintf(name_dir_inner, sizeof(name_dir_inner), "%s/inner", dir_prefix);
snprintf(name_dir_file3, sizeof(name_dir_file2), "%s/boo.bin", dir_prefix);
snprintf(name_dir_file2, sizeof(name_dir_file2), "%s/2.txt", dir_prefix);
snprintf(name_dir_file1, sizeof(name_dir_file1), "%s/1.txt", dir_prefix);
unlink(name_dir_inner_file);
rmdir(name_dir_inner);
unlink(name_dir_file1);
unlink(name_dir_file2);
unlink(name_dir_file3);
rmdir(dir_prefix);
REQUIRE(0 == mkdir(dir_prefix, 0755));
test_fatfs_create_file_with_text(name_dir_file1, "1\n");
test_fatfs_create_file_with_text(name_dir_file2, "2\n");
test_fatfs_create_file_with_text(name_dir_file3, "\01\02\03");
REQUIRE(0 == mkdir(name_dir_inner, 0755));
test_fatfs_create_file_with_text(name_dir_inner_file, "3\n");
DIR* dir = opendir(dir_prefix);
REQUIRE(dir != NULL);
int count = 0;
const char* names[4];
while(count < 4) {
struct dirent* de = readdir(dir);
if (!de) {
break;
}
printf("found '%s'\n", de->d_name);
if (strcasecmp(de->d_name, "1.txt") == 0) {
REQUIRE(de->d_type == DT_REG);
names[count] = "1.txt";
++count;
} else if (strcasecmp(de->d_name, "2.txt") == 0) {
REQUIRE(de->d_type == DT_REG);
names[count] = "2.txt";
++count;
} else if (strcasecmp(de->d_name, "inner") == 0) {
REQUIRE(de->d_type == DT_DIR);
names[count] = "inner";
++count;
} else if (strcasecmp(de->d_name, "boo.bin") == 0) {
REQUIRE(de->d_type == DT_REG);
names[count] = "boo.bin";
++count;
} else {
printf("unexpected directory entry");
}
}
REQUIRE(count == 4);
rewinddir(dir);
struct dirent* de = readdir(dir);
REQUIRE(de != NULL);
REQUIRE(0 == strcasecmp(de->d_name, names[0]));
seekdir(dir, 3);
de = readdir(dir);
REQUIRE(de != NULL);
REQUIRE(0 == strcasecmp(de->d_name, names[3]));
seekdir(dir, 1);
de = readdir(dir);
REQUIRE(de != NULL);
REQUIRE(0 == strcasecmp(de->d_name, names[1]));
seekdir(dir, 2);
de = readdir(dir);
REQUIRE(de != NULL);
REQUIRE(0 == strcasecmp(de->d_name, names[2]));
REQUIRE(0 == closedir(dir));
}
TEST_CASE("opendir, readdir, rewinddir, seekdir work as expected", "[fatfs]")
{
test_setup();
test_fatfs_opendir_readdir_rewinddir("/linux/dir");
test_teardown();
}
TEST_CASE("can opendir, closedir and readdir of FS", "[fatfs]")
{
test_setup();
test_opendir_closedir_readdir();
test_teardown();
}
static void test_truncate()
{
const char *test_str = "0123456789\n";
const char *filename = "/linux/truncate.txt";
test_fatfs_create_file_with_text(filename, test_str);
struct stat st;
size_t size;
REQUIRE(0 == stat(filename, &st));
size = st.st_size;
REQUIRE(strlen(test_str) == size);
size_t trunc_add = 2;
off_t new_size = strlen(test_str) + trunc_add;
REQUIRE(0 == truncate(filename, new_size));
stat(filename, &st);
size = st.st_size;
REQUIRE(new_size == size);
const char truncated_1[] = "01234";
off_t truncated_len = strlen(truncated_1);
REQUIRE(0 == truncate(filename, truncated_len));
stat(filename, &st);
size = st.st_size;
REQUIRE(strlen(truncated_1) == size);
}
TEST_CASE("can truncate", "[fatfs]")
{
test_setup();
test_truncate();
test_teardown();
}
static void test_ftruncate()
{
const char *test_str = "0123456789\n";
const char *filename = "/linux/ftrunc.txt";
test_fatfs_create_file_with_text(filename, test_str);
int fd = open(filename, O_RDWR);
struct stat st;
size_t size;
fstat(fd, &st);
size = st.st_size;
REQUIRE(strlen(test_str) == size);
close(fd);
size_t trunc_add = 2;
off_t new_size = strlen(test_str) + trunc_add;
fd = open(filename, O_RDWR);
REQUIRE(0 == ftruncate(fd, new_size));
fstat(fd, &st);
size = st.st_size;
REQUIRE(new_size == size);
close(fd);
const char truncated_1[] = "01234";
off_t truncated_len = strlen(truncated_1);
fd = open(filename, O_RDWR);
REQUIRE(0 == ftruncate(fd, truncated_len));
fstat(fd, &st);
size = st.st_size;
REQUIRE(strlen(truncated_1) == size);
close(fd);
}
TEST_CASE("can ftruncate", "[fatfs]")
{
test_setup();
test_ftruncate();
test_teardown();
}
void test_fatfs_utime(const char* filename, const char* root_dir)
{
struct stat achieved_stat;
struct tm desired_tm;
struct utimbuf desired_time = {
.actime = 0, // access time is not supported
.modtime = 0,
};
time_t false_now = 0;
memset(&desired_tm, 0, sizeof(struct tm));
// Setting up a false actual time - used when the file is created and for modification with the current time
desired_tm.tm_mon = 10 - 1;
desired_tm.tm_mday = 31;
desired_tm.tm_year = 2018 - 1900;
desired_tm.tm_hour = 10;
desired_tm.tm_min = 35;
desired_tm.tm_sec = 23;
false_now = mktime(&desired_tm);
struct timeval now = { .tv_sec = false_now, .tv_usec = 0};
settimeofday(&now, NULL);
test_fatfs_create_file_with_text(filename, "");
// 00:00:00. January 1st, 1980 - FATFS cannot handle earlier dates
desired_tm.tm_mon = 1 - 1;
desired_tm.tm_mday = 1;
desired_tm.tm_year = 1980 - 1900;
desired_tm.tm_hour = 0;
desired_tm.tm_min = 0;
desired_tm.tm_sec = 0;
printf("Testing mod. time: %s", asctime(&desired_tm));
desired_time.modtime = mktime(&desired_tm);
REQUIRE(0 == utime(filename, &desired_time));
REQUIRE(0 == stat(filename, &achieved_stat));
REQUIRE(desired_time.modtime == achieved_stat.st_mtime);
// current time
REQUIRE(0 == utime(filename, NULL));
REQUIRE(0 == stat(filename, &achieved_stat));
printf("Mod. time changed to (false actual time): %s", ctime(&achieved_stat.st_mtime));
REQUIRE(desired_time.modtime != achieved_stat.st_mtime);
REQUIRE(false_now - achieved_stat.st_mtime <= 2); // two seconds of tolerance are given
// 23:59:08. December 31st, 2037
desired_tm.tm_mon = 12 - 1;
desired_tm.tm_mday = 31;
desired_tm.tm_year = 2037 - 1900;
desired_tm.tm_hour = 23;
desired_tm.tm_min = 59;
desired_tm.tm_sec = 8;
printf("Testing mod. time: %s", asctime(&desired_tm));
desired_time.modtime = mktime(&desired_tm);
REQUIRE(0 == utime(filename, &desired_time));
REQUIRE(0 == stat(filename, &achieved_stat));
REQUIRE(desired_time.modtime == achieved_stat.st_mtime);
//WARNING: it has the Unix Millennium bug (Y2K38)
// 00:00:00. January 1st, 1970 - FATFS cannot handle years before 1980
desired_tm.tm_mon = 1 - 1;
desired_tm.tm_mday = 1;
desired_tm.tm_year = 1970 - 1900;
desired_tm.tm_hour = 0;
desired_tm.tm_min = 0;
desired_tm.tm_sec = 0;
printf("Testing mod. time: %s", asctime(&desired_tm));
desired_time.modtime = mktime(&desired_tm);
REQUIRE(-1 == utime(filename, &desired_time));
REQUIRE(EINVAL == errno);
}
TEST_CASE("utime sets modification time", "[fatfs]")
{
test_setup();
test_fatfs_utime("/linux/utime.txt", "/linux");
test_teardown();
}
static void test_fstat()
{
const char *test_str = "0123456789\n";
const char *filename = "/linux/pwrite.txt";
test_fatfs_create_file_with_text(filename, test_str);
int fd = open(filename, O_RDWR);
struct stat st;
REQUIRE(0 == fstat(fd, &st));
REQUIRE((st.st_mode & S_IFREG) == S_IFREG);
REQUIRE(st.st_size == strlen(test_str));
close(fd);
}
TEST_CASE("fstat returns correct values", "[fatfs]")
{
test_setup();
test_fstat();
test_teardown();
}
static void test_mkdir_rmdir()
{
const char *dir_prefix = "/linux/dir";
REQUIRE(0 == mkdir(dir_prefix, 0777));
struct stat st;
REQUIRE(0 == stat(dir_prefix, &st));
REQUIRE((st.st_mode & S_IFDIR) == S_IFDIR);
REQUIRE((st.st_mode & S_IFREG) != S_IFREG);
REQUIRE(0 == rmdir(dir_prefix));
REQUIRE(-1 == stat(dir_prefix, &st));
}
TEST_CASE("can create and remove directories", "[fatfs]")
{
test_setup();
test_mkdir_rmdir();
test_teardown();
}