mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-29 11:55:00 +00:00
580 lines
16 KiB
C++
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();
|
|
}
|