Merge branch 'feature/std_filesystem_supported_v5.4' into 'release/v5.4'

storage: make std::filesystem supported (v5.4)

See merge request espressif/esp-idf!35430
This commit is contained in:
Ivan Grokhotkov 2024-12-13 17:56:42 +08:00
commit bd0fc67f13
20 changed files with 718 additions and 12 deletions

View File

@ -39,7 +39,7 @@ typedef struct {
FATFS fs; /* fatfs library FS structure */
char tmp_path_buf[FILENAME_MAX+3]; /* temporary buffer used to prepend drive name to the path */
char tmp_path_buf2[FILENAME_MAX+3]; /* as above; used in functions which take two path arguments */
bool *o_append; /* O_APPEND is stored here for each max_files entries (because O_APPEND is not compatible with FA_OPEN_APPEND) */
uint32_t *flags; /* file descriptor flags, array of max_files size */
#ifdef CONFIG_VFS_SUPPORT_DIR
char dir_path[FILENAME_MAX]; /* variable to store path of opened directory*/
struct cached_data cached_fileinfo;
@ -85,6 +85,7 @@ static int vfs_fat_open(void* ctx, const char * path, int flags, int mode);
static int vfs_fat_close(void* ctx, int fd);
static int vfs_fat_fstat(void* ctx, int fd, struct stat * st);
static int vfs_fat_fsync(void* ctx, int fd);
static int vfs_fat_fcntl(void* ctx, int fd, int cmd, int arg);
#ifdef CONFIG_VFS_SUPPORT_DIR
static int vfs_fat_stat(void* ctx, const char * path, struct stat * st);
static int vfs_fat_link(void* ctx, const char* n1, const char* n2);
@ -170,6 +171,7 @@ static const esp_vfs_fs_ops_t s_vfs_fat = {
.open_p = &vfs_fat_open,
.close_p = &vfs_fat_close,
.fstat_p = &vfs_fat_fstat,
.fcntl_p = &vfs_fat_fcntl,
.fsync_p = &vfs_fat_fsync,
#ifdef CONFIG_VFS_SUPPORT_DIR
.dir = &s_vfs_fat_dir,
@ -199,19 +201,19 @@ esp_err_t esp_vfs_fat_register_cfg(const esp_vfs_fat_conf_t* conf, FATFS** out_f
return ESP_ERR_NO_MEM;
}
memset(fat_ctx, 0, ctx_size);
fat_ctx->o_append = ff_memalloc(max_files * sizeof(bool));
if (fat_ctx->o_append == NULL) {
fat_ctx->flags = ff_memalloc(max_files * sizeof(*fat_ctx->flags));
if (fat_ctx->flags == NULL) {
free(fat_ctx);
return ESP_ERR_NO_MEM;
}
memset(fat_ctx->o_append, 0, max_files * sizeof(bool));
memset(fat_ctx->flags, 0, max_files * sizeof(*fat_ctx->flags));
fat_ctx->max_files = max_files;
strlcpy(fat_ctx->fat_drive, conf->fat_drive, sizeof(fat_ctx->fat_drive) - 1);
strlcpy(fat_ctx->base_path, conf->base_path, sizeof(fat_ctx->base_path) - 1);
esp_err_t err = esp_vfs_register_fs(conf->base_path, &s_vfs_fat, ESP_VFS_FLAG_CONTEXT_PTR | ESP_VFS_FLAG_STATIC, fat_ctx);
if (err != ESP_OK) {
free(fat_ctx->o_append);
free(fat_ctx->flags);
free(fat_ctx);
return err;
}
@ -239,7 +241,7 @@ esp_err_t esp_vfs_fat_unregister_path(const char* base_path)
return err;
}
_lock_close(&fat_ctx->lock);
free(fat_ctx->o_append);
free(fat_ctx->flags);
free(fat_ctx);
s_fat_ctxs[ctx] = NULL;
return ESP_OK;
@ -427,7 +429,7 @@ static int vfs_fat_open(void* ctx, const char * path, int flags, int mode)
// Other VFS drivers handles O_APPEND well (to the best of my knowledge),
// therefore this flag is stored here (at this VFS level) in order to save
// memory.
fat_ctx->o_append[fd] = (flags & O_APPEND) == O_APPEND;
fat_ctx->flags[fd] = (flags & (O_APPEND | O_ACCMODE));
_lock_release(&fat_ctx->lock);
return fd;
}
@ -438,7 +440,7 @@ static ssize_t vfs_fat_write(void* ctx, int fd, const void * data, size_t size)
FIL* file = &fat_ctx->files[fd];
FRESULT res;
_lock_acquire(&fat_ctx->lock);
if (fat_ctx->o_append[fd]) {
if (fat_ctx->flags[fd] & O_APPEND) {
if ((res = f_lseek(file, f_size(file))) != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
@ -672,6 +674,26 @@ static int vfs_fat_fstat(void* ctx, int fd, struct stat * st)
return 0;
}
static int vfs_fat_fcntl(void* ctx, int fd, int cmd, int arg)
{
vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
switch (cmd) {
case F_GETFL:
return fat_ctx->flags[fd];
case F_SETFL:
fat_ctx->flags[fd] = arg;
return 0;
// no-ops:
case F_SETLK:
case F_SETLKW:
case F_GETLK:
return 0;
default:
errno = EINVAL;
return -1;
}
}
#ifdef CONFIG_VFS_SUPPORT_DIR
static inline mode_t get_stat_mode(bool is_dir)

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -9,6 +9,7 @@
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <dirent.h>
#include <sys/param.h>
/* realpath logic:
@ -122,3 +123,20 @@ int chdir(const char *path)
errno = ENOSYS;
return -1;
}
/* std::filesystem functions call chmod and exit with an exception if it fails,
* so not failing with ENOSYS seems a better solution.
*/
int chmod(const char *path, mode_t mode)
{
return 0;
}
/* As a workaround for libstdc++ being built with _GLIBCXX_HAVE_DIRFD,
* we have to provide at least a stub for dirfd function.
*/
int dirfd(DIR *dirp)
{
errno = ENOSYS;
return -1;
}

View File

@ -1,13 +1,18 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include "esp_err.h"
#include "esp_log.h"
#include "sdkconfig.h"
static const char *TAG = "sysconf";
#ifdef CONFIG_FREERTOS_UNICORE
#define CPU_NUM 1
#else
@ -25,3 +30,24 @@ long sysconf(int arg)
return -1;
}
}
// pathconf
long fpathconf(int fildes, int name)
{
if (name == _PC_PATH_MAX) {
return PATH_MAX;
}
ESP_LOGW(TAG, "fpathconf: unsupported name %d", name);
errno = EINVAL;
return -1;
}
long pathconf(const char *path, int name)
{
if (name == _PC_PATH_MAX) {
return PATH_MAX;
}
ESP_LOGW(TAG, "pathconf: unsupported name %d", name);
errno = EINVAL;
return -1;
}

View File

@ -13,6 +13,7 @@ The following C++ features are supported:
- :ref:`cplusplus_multithreading`
- :ref:`cplusplus_rtti`
- :doc:`thread-local-storage` (``thread_local`` keyword)
- :ref:`cplusplus_filesystem`
- All C++ features implemented by GCC, except for some :ref:`cplusplus_limitations`. See `GCC documentation <https://gcc.gnu.org/projects/cxx-status.html>`_ for details on features implemented by GCC.
@ -94,6 +95,21 @@ Enabling this option compiles all C++ files with RTTI support enabled, which all
See :example:`cxx/rtti` for an example of using RTTI in ESP-IDF. Specifically, this example demonstrates how to use the RTTI feature in ESP-IDF, enabling compile time support for RTTI, and showing how to print demangled type names of objects and functions, and how dynamic_cast behaves with objects of two classes derived from a common base class.
.. _cplusplus_filesystem:
Filesystem Library
------------------
C++ Filesystem library (``#include <filesystem>``) features are supported in ESP-IDF, with the following exceptions:
- Since symbolic and hard links are not supported in ESP-IDF, related functions are not implemented.
- ``std::filesystem::space`` is not implemented.
- ``std::filesystem::resize_file`` is not implemented.
- ``std::filesystem::current_path`` always returns ``/``. Setting the current path is not supported.
- ``std::filesystem::permissions`` doesn't support setting file permissions.
Note that the choice of the filesystem also affects the behavior of the filesystem library. For example, SPIFFS filesystem has limited support for directories, therefore the related std::filesystem functions may not work as they do on a filesystem which does support directories.
Developing in C++
-----------------
@ -186,7 +202,6 @@ Limitations
- Linker script generator does not support function level placements for functions with C++ linkage.
- Various section attributes (such as ``IRAM_ATTR``) are ignored when used with template functions.
- Vtables are placed into Flash and are not accessible when the flash cache is disabled. Therefore, virtual function calls should be avoided in :ref:`iram-safe-interrupt-handlers`. Placement of Vtables cannot be adjusted using the linker script generator, yet.
- C++ filesystem (``std::filesystem``) features are not supported.
What to Avoid

View File

@ -13,6 +13,7 @@ ESP-IDF 支持以下 C++ 功能:
- :ref:`cplusplus_multithreading`
- :ref:`cplusplus_rtti`
- :doc:`thread-local-storage` ``thread_local`` 关键字)
- :ref:`cplusplus_filesystem`
- 除部分 :ref:`cplusplus_limitations`,所有由 GCC 实现的 C++ 功能均受支持。有关由 GCC 所实现功能的详细信息,请参阅 `GCC 文档 <https://gcc.gnu.org/projects/cxx-status.html>`_
@ -94,6 +95,21 @@ ESP-IDF 默认禁用对 RTTI 的支持,可以用 :ref:`CONFIG_COMPILER_CXX_RTT
有关在 ESP-IDF 中使用 RTTI 的示例,请参阅 :example:`cxx/rtti`。该示例演示了如何在 ESP-IDF 中使用 RTTI 功能,启用编译时对 RTTI 的支持,并展示了如何打印对象和函数的去混淆类型名称,以及 dynamic_cast 在两个继承自同一基类的对象上如何表现。
.. _cplusplus_filesystem:
文件系统库
----------
ESP-IDF 支持 C++ 文件系统库 (``#include <filesystem>``),但有部分功能尚未实现:
- 由于 ESP-IDF 不支持符号链接和硬链接,因此相关函数未实现。
- 未实现 ``std::filesystem::space``
- 未实现 ``std::filesystem::resize_file``
- ``std::filesystem::current_path`` 只返回 ``/``。不支持设置当前路径。
- ``std::filesystem::permissions`` 不支持设置文件权限。
请注意文件系统的选择也会影响文件系统库的行为。例如SPIFFS 文件系统对目录的支持有限,因此相关的 std::filesystem 函数可能无法像在支持目录的文件系统上那样正常工作。
在 C++ 中进行开发
-----------------
@ -186,7 +202,6 @@ ESP-IDF 支持 ``iostream`` 功能,但应注意:
- 链接脚本生成器不支持将具有 C++ 链接的函数单独放置在内存的特定位置。
- 当与模板函数一起使用时,会忽略各种节属性(例如 ``IRAM_ATTR``)。
- vtable 位于 flash 中,在禁用 flash 缓存时无法访问。因此,在 :ref:`iram-safe-interrupt-handlers` 中应避免调用虚拟函数。目前尚无法使用链接器脚本生成器调整 vtable 的放置位置。
- 不支持 C++ 文件系统 (``std::filesystem``) 功能。
注意事项

View File

@ -38,3 +38,15 @@ tools/test_apps/storage/sdmmc_console:
- sdmmc
- esp_driver_sdmmc
- esp_driver_sdspi
tools/test_apps/storage/std_filesystem:
enable:
- if: IDF_TARGET in ["esp32", "esp32c3"]
reason: one Xtensa and one RISC-V chip should be enough
disable:
- if: IDF_TOOLCHAIN == "clang"
reason: Issue with C++ exceptions on Xtensa, issue with getrandom linking on RISC-V
depends_components:
- vfs
- newlib
- fatfs

View File

@ -0,0 +1,8 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
list(PREPEND SDKCONFIG_DEFAULTS "$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers" "sdkconfig.defaults")
project(std_filesystem_test)

View File

@ -0,0 +1,70 @@
| Supported Targets | ESP32 | ESP32-C3 |
| ----------------- | ----- | -------- |
This is a test app which verifies that std::filesystem features work in ESP-IDF. The tests are written using [Catch2](https://github.com/catchorg/Catch2) managed [component](https://components.espressif.com/components/espressif/catch2/).
To run the tests:
```shell
idf.py flash monitor
```
Or, in QEMU:
```shell
idf.py qemu monitor
```
Or, using pytest:
```shell
idf.py build
pytest --embedded-services idf,qemu --target esp32 --ignore managed_components
```
## Feature Support
Please update `_cplusplus_filesystem` section in cplusplus.rst when modifying this table.
| Feature | Supported | Tested | Comment |
|------------------------------|-----------|--------|---------------------------------------------------------------------------------------------------------------|
| absolute | y | y | |
| canonical | y | y | |
| weakly_canonical | y | y | |
| relative | y | y | |
| proximate | y | y | |
| copy | y | y | this function has complex behavior, not sure about test coverage |
| copy_file | y | y | |
| copy_symlink | n | n | symlinks are not supported |
| create_directory | y | y | |
| create_directories | y | y | |
| create_hard_link | n | n | hard links are not supported |
| create_symlink | n | n | symlinks are not supported |
| create_directory_symlink | n | n | symlinks are not supported |
| current_path | partial | y | setting path is not supported in IDF |
| exists | y | y | |
| equivalent | y | y | |
| file_size | y | y | |
| hard_link_count | n | n | hard links are not supported |
| last_write_time | y | y | |
| permissions | partial | y | setting permissions is not supported |
| read_symlink | n | n | symlinks are not supported |
| remove | y | y | |
| remove_all | y | y | |
| rename | y | y | |
| resize_file | n | y | doesn't work, toolchain has to be built with _GLIBCXX_HAVE_TRUNCATE |
| space | n | y | doesn't work, toolchain has to be built with _GLIBCXX_HAVE_SYS_STATVFS_H and statvfs function must be defined |
| status | y | y | |
| symlink_status | n | n | symlinks are not supported |
| temp_directory_path | y | y | works if /tmp directory has been mounted |
| directory_iterator | y | y | |
| recursive_directory_iterator | y | y | |
| is_block_file | y | y | |
| is_character_file | y | y | |
| is_directory | y | y | |
| is_empty | y | y | |
| is_fifo | y | y | |
| is_other | n | n | |
| is_regular_file | y | y | |
| is_socket | y | y | |
| is_symlink | y | y | |

View File

@ -0,0 +1,10 @@
idf_component_register(SRCS
"test_std_filesystem_main.cpp"
"test_ops.cpp"
"test_paths.cpp"
"test_status.cpp"
INCLUDE_DIRS "."
PRIV_REQUIRES vfs fatfs
WHOLE_ARCHIVE)
fatfs_create_spiflash_image(storage ${CMAKE_CURRENT_LIST_DIR}/test_fs_image FLASH_IN_PROJECT)

View File

@ -0,0 +1,2 @@
dependencies:
espressif/catch2: "^3.7.0"

View File

@ -0,0 +1 @@
1234567890

View File

@ -0,0 +1,196 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <cstddef>
#include <filesystem>
#include <catch2/catch_test_macros.hpp>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/utime.h>
#include "esp_vfs_fat.h"
#include "wear_levelling.h"
class OpsTest {
private:
wl_handle_t m_wl_handle;
public:
OpsTest()
{
esp_vfs_fat_mount_config_t mount_config = VFS_FAT_MOUNT_DEFAULT_CONFIG();
esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl("/test", "storage", &mount_config, &m_wl_handle);
if (err != ESP_OK) {
throw std::runtime_error("Failed to mount FAT filesystem");
}
}
~OpsTest()
{
esp_vfs_fat_spiflash_unmount_rw_wl("/test", m_wl_handle);
}
void test_create_remove()
{
std::filesystem::create_directory("/test/dir");
CHECK(std::filesystem::exists("/test/dir"));
CHECK(std::filesystem::remove("/test/dir"));
CHECK(!std::filesystem::exists("/test/dir"));
}
/*
The following two tests rely on the following directory structure
in the generated FAT filesystem:
/test
test_dir_iter
dir1
   f1
dir2
dir3
f3
*/
void test_directory_iterator()
{
std::filesystem::directory_iterator it("/test/test_dir_iter");
CHECK(it != std::filesystem::directory_iterator());
CHECK(it->path() == "/test/test_dir_iter/dir1");
CHECK(it->is_directory());
++it;
CHECK(it != std::filesystem::directory_iterator());
CHECK(it->path() == "/test/test_dir_iter/dir2");
CHECK(it->is_directory());
++it;
CHECK(it == std::filesystem::directory_iterator());
}
void test_recursive_directory_iterator()
{
std::filesystem::recursive_directory_iterator it("/test/test_dir_iter");
CHECK(it != std::filesystem::recursive_directory_iterator());
CHECK(it->path() == "/test/test_dir_iter/dir1");
CHECK(it->is_directory());
++it;
CHECK(it != std::filesystem::recursive_directory_iterator());
CHECK(it->path() == "/test/test_dir_iter/dir1/f1");
CHECK(it->is_regular_file());
++it;
CHECK(it != std::filesystem::recursive_directory_iterator());
CHECK(it->path() == "/test/test_dir_iter/dir2");
CHECK(it->is_directory());
++it;
CHECK(it != std::filesystem::recursive_directory_iterator());
CHECK(it->path() == "/test/test_dir_iter/dir2/dir3");
CHECK(it->is_directory());
++it;
CHECK(it != std::filesystem::recursive_directory_iterator());
CHECK(it->path() == "/test/test_dir_iter/dir2/dir3/f3");
CHECK(it->is_regular_file());
++it;
CHECK(it == std::filesystem::recursive_directory_iterator());
}
void test_copy_remove_recursive_copy()
{
if (std::filesystem::exists("/test/copy_dir")) {
CHECK(std::filesystem::remove_all("/test/copy_dir"));
}
CHECK(std::filesystem::create_directory("/test/copy_dir"));
REQUIRE_NOTHROW(std::filesystem::copy("/test/test_dir_iter/dir1/f1", "/test/copy_dir/f1"));
CHECK(std::filesystem::exists("/test/copy_dir/f1"));
CHECK(std::filesystem::remove("/test/copy_dir/f1"));
CHECK(std::filesystem::remove("/test/copy_dir"));
REQUIRE_NOTHROW(std::filesystem::copy("/test/test_dir_iter", "/test/copy_dir", std::filesystem::copy_options::recursive));
CHECK(std::filesystem::exists("/test/copy_dir/dir1/f1"));
CHECK(std::filesystem::exists("/test/copy_dir/dir2/dir3/f3"));
CHECK(std::filesystem::remove_all("/test/copy_dir"));
}
void test_create_directories()
{
if (std::filesystem::exists("/test/create_dir")) {
CHECK(std::filesystem::remove_all("/test/create_dir"));
}
CHECK(std::filesystem::create_directories("/test/create_dir/dir1/dir2"));
CHECK(std::filesystem::exists("/test/create_dir/dir1/dir2"));
CHECK(std::filesystem::remove_all("/test/create_dir"));
}
void test_rename_file()
{
if (std::filesystem::exists("/test/rename_file")) {
CHECK(std::filesystem::remove("/test/rename_file"));
}
std::filesystem::create_directory("/test/rename_file");
std::filesystem::copy_file("/test/file", "/test/rename_file/file");
CHECK(std::filesystem::exists("/test/rename_file/file"));
std::filesystem::rename("/test/rename_file/file", "/test/rename_file/file2");
CHECK(std::filesystem::exists("/test/rename_file/file2"));
CHECK(std::filesystem::remove_all("/test/rename_file"));
}
void test_file_size_resize()
{
if (std::filesystem::exists("/test/file_size")) {
CHECK(std::filesystem::remove("/test/file_size"));
}
std::filesystem::copy_file("/test/file", "/test/file_size");
CHECK(std::filesystem::file_size("/test/file_size") == 11);
// Not supported: libstdc++ has to be built with _GLIBCXX_HAVE_TRUNCATE
CHECK_THROWS(std::filesystem::resize_file("/test/file_size", 20));
CHECK(std::filesystem::remove("/test/file_size"));
}
void test_file_last_write_time()
{
if (std::filesystem::exists("/test/file_time")) {
CHECK(std::filesystem::remove("/test/file_time"));
}
std::filesystem::copy_file("/test/file", "/test/file_time");
auto time = std::filesystem::last_write_time("/test/file_time");
struct stat st = {};
stat("/test/file_time", &st);
struct utimbuf times = {st.st_atime, st.st_mtime + 1000000000};
utime("/test/file_time", &times);
auto time2 = std::filesystem::last_write_time("/test/file_time");
CHECK(time2 > time);
}
void test_space()
{
// Not supported: libstdc++ has to be built with _GLIBCXX_HAVE_SYS_STATVFS_H and statvfs function
// has to be defined
CHECK_THROWS(std::filesystem::space("/test"));
}
void test_permissions()
{
auto perm = std::filesystem::status("/test/file").permissions();
CHECK(perm == std::filesystem::perms::all);
std::filesystem::permissions("/test/file", std::filesystem::perms::owner_read, std::filesystem::perm_options::replace);
// setting permissions is not supported and has no effect
perm = std::filesystem::status("/test/file").permissions();
CHECK(perm == std::filesystem::perms::all);
}
// when adding a test method, don't forget to add it to the list below
};
METHOD_AS_TEST_CASE(OpsTest::test_create_remove, "Test create and remove directories");
METHOD_AS_TEST_CASE(OpsTest::test_directory_iterator, "Test directory iterator");
METHOD_AS_TEST_CASE(OpsTest::test_recursive_directory_iterator, "Test recursive directory iterator");
METHOD_AS_TEST_CASE(OpsTest::test_copy_remove_recursive_copy, "Test copy, remove and recursive copy");
METHOD_AS_TEST_CASE(OpsTest::test_create_directories, "Test create directories");
METHOD_AS_TEST_CASE(OpsTest::test_rename_file, "Test rename file");
METHOD_AS_TEST_CASE(OpsTest::test_file_size_resize, "Test file size and resize");
METHOD_AS_TEST_CASE(OpsTest::test_file_last_write_time, "Test file last write time");
METHOD_AS_TEST_CASE(OpsTest::test_space, "Test space");
METHOD_AS_TEST_CASE(OpsTest::test_permissions, "Test permissions");

View File

@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <filesystem>
#include <catch2/catch_test_macros.hpp>
TEST_CASE("std::filesystem path, relative, proximate, absolute")
{
// In IDF, CWD is always in the the root directory
CHECK(std::filesystem::current_path() == "/");
// Create absolute path from relative path
std::filesystem::path rel_path("test/file.txt");
std::filesystem::path abs_path = std::filesystem::absolute(rel_path);
CHECK(abs_path == "/test/file.txt");
// Create relative path from absolute path
std::filesystem::path rel_path2 = std::filesystem::relative(abs_path);
CHECK(rel_path2 == "test/file.txt");
// Create relative path from absolute path with different base
std::filesystem::path rel_path3 = std::filesystem::relative(abs_path, "/test");
CHECK(rel_path3 == "file.txt");
std::filesystem::path prox_path = std::filesystem::proximate("/root1/file", "/root2");
CHECK(prox_path == "../root1/file");
}
TEST_CASE("std::filesystem weakly_canonical")
{
CHECK(std::filesystem::weakly_canonical("/a/b/c/./d/../e/f/../g") == "/a/b/c/e/g");
}
TEST_CASE("std::filesystem current_path")
{
// In IDF, CWD is always in the the root directory
CHECK(std::filesystem::current_path() == "/");
// Changing the current path in IDF is not supported
CHECK_THROWS(std::filesystem::current_path("/test"));
}

View File

@ -0,0 +1,213 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <filesystem>
#include <cstring>
#include <sys/errno.h>
#include <sys/stat.h>
#include <sys/dirent.h>
#include "esp_vfs.h"
#include "esp_err.h"
#include <catch2/catch_test_macros.hpp>
/* Helper VFS driver to test std::filesystem */
typedef struct {
const char* cmp_path;
int ret_errno;
struct stat ret_stat;
DIR out_dir;
int n_dir_entries;
struct dirent* ret_dirent_array;
} test_vfs_ctx_t;
static int test_vfs_open(void* ctx, const char* path, int flags, int mode)
{
test_vfs_ctx_t* vfs_ctx = (test_vfs_ctx_t*)ctx;
if (strcmp(path, vfs_ctx->cmp_path) != 0) {
errno = vfs_ctx->ret_errno;
return -1;
}
return 0;
}
static int test_vfs_stat(void* ctx, const char* path, struct stat* st)
{
test_vfs_ctx_t* vfs_ctx = (test_vfs_ctx_t*)ctx;
if (strcmp(path, vfs_ctx->cmp_path) != 0) {
errno = vfs_ctx->ret_errno;
return -1;
}
*st = vfs_ctx->ret_stat;
return 0;
}
static DIR* test_vfs_opendir(void* ctx, const char* name)
{
test_vfs_ctx_t* vfs_ctx = (test_vfs_ctx_t*)ctx;
if (strcmp(name, vfs_ctx->cmp_path) != 0) {
errno = vfs_ctx->ret_errno;
return nullptr;
}
return &vfs_ctx->out_dir;
}
static struct dirent* test_vfs_readdir(void* ctx, DIR* pdir)
{
test_vfs_ctx_t* vfs_ctx = (test_vfs_ctx_t*)ctx;
if (vfs_ctx->ret_errno) {
errno = vfs_ctx->ret_errno;
return nullptr;
}
if (vfs_ctx->n_dir_entries == 0) {
return nullptr;
}
vfs_ctx->n_dir_entries--;
struct dirent* ret = &vfs_ctx->ret_dirent_array[0];
vfs_ctx->ret_dirent_array++;
return ret;
}
static int test_vfs_closedir(void* ctx, DIR* pdir)
{
return 0;
}
/* Actual test case starts here */
TEST_CASE("std::filesystem status functions")
{
test_vfs_ctx_t test_ctx = {};
esp_vfs_t desc = {};
desc.flags = ESP_VFS_FLAG_CONTEXT_PTR;
desc.open_p = test_vfs_open;
desc.stat_p = test_vfs_stat;
desc.opendir_p = test_vfs_opendir;
desc.readdir_p = test_vfs_readdir;
desc.closedir_p = test_vfs_closedir;
REQUIRE(esp_vfs_register("/test", &desc, &test_ctx) == ESP_OK);
SECTION("Test file exists") {
test_ctx.cmp_path = "/file.txt";
test_ctx.ret_stat = {};
test_ctx.ret_stat.st_mode = S_IFREG;
CHECK(std::filesystem::exists("/test/file.txt"));
CHECK(std::filesystem::is_regular_file("/test/file.txt"));
}
SECTION("Test directory exists") {
test_ctx.cmp_path = "/dir";
test_ctx.ret_stat = {};
test_ctx.ret_stat.st_mode = S_IFDIR;
CHECK(std::filesystem::exists("/test/dir"));
CHECK(std::filesystem::is_directory("/test/dir"));
}
SECTION("Test non-existent file") {
test_ctx.cmp_path = "";
test_ctx.ret_errno = ENOENT;
CHECK(!std::filesystem::exists("/test/nonexistent"));
}
SECTION("Test is_character_file") {
test_ctx.cmp_path = "/chardev";
test_ctx.ret_stat = {};
test_ctx.ret_stat.st_mode = S_IFCHR;
CHECK(std::filesystem::exists("/test/chardev"));
CHECK(std::filesystem::is_character_file("/test/chardev"));
}
SECTION("Test is_block_file") {
test_ctx.cmp_path = "/blockdev";
test_ctx.ret_stat = {};
test_ctx.ret_stat.st_mode = S_IFBLK;
CHECK(std::filesystem::exists("/test/blockdev"));
CHECK(std::filesystem::is_block_file("/test/blockdev"));
}
SECTION("Test is_fifo") {
test_ctx.cmp_path = "/fifo";
test_ctx.ret_stat = {};
test_ctx.ret_stat.st_mode = S_IFIFO;
CHECK(std::filesystem::exists("/test/fifo"));
CHECK(std::filesystem::is_fifo("/test/fifo"));
}
SECTION("Test is_socket") {
test_ctx.cmp_path = "/socket";
test_ctx.ret_stat = {};
test_ctx.ret_stat.st_mode = S_IFSOCK;
CHECK(std::filesystem::exists("/test/socket"));
CHECK(std::filesystem::is_socket("/test/socket"));
}
SECTION("Test is_symlink") {
test_ctx.cmp_path = "/symlink";
test_ctx.ret_stat = {};
test_ctx.ret_stat.st_mode = S_IFLNK;
CHECK(std::filesystem::exists("/test/symlink"));
CHECK(std::filesystem::is_symlink("/test/symlink"));
}
SECTION("Test is_empty with file") {
test_ctx.cmp_path = "/file.txt";
test_ctx.ret_stat = {};
test_ctx.ret_stat.st_mode = S_IFREG;
test_ctx.ret_stat.st_size = 10;
CHECK(!std::filesystem::is_empty("/test/file.txt"));
test_ctx.ret_stat.st_size = 0;
CHECK(std::filesystem::is_empty("/test/file.txt"));
}
SECTION("Test is_empty with directory") {
test_ctx.cmp_path = "/dir";
test_ctx.ret_stat = {};
test_ctx.ret_stat.st_mode = S_IFDIR;
CHECK(std::filesystem::is_empty("/test/dir"));
}
SECTION("Test is_empty with non-empty directory") {
test_ctx.cmp_path = "/dir";
test_ctx.ret_stat = {};
test_ctx.ret_stat.st_mode = S_IFDIR;
test_ctx.n_dir_entries = 2;
struct dirent entries[2] = {
{ .d_ino = 0, .d_type = DT_REG, .d_name = "foo" },
{ .d_ino = 0, .d_type = DT_REG, .d_name = "bar" },
};
test_ctx.ret_dirent_array = entries;
CHECK(!std::filesystem::is_empty("/test/dir"));
}
SECTION("directory_iterator, empty directory") {
test_ctx.cmp_path = "/dir";
test_ctx.ret_stat = {};
test_ctx.ret_stat.st_mode = S_IFDIR;
test_ctx.n_dir_entries = 0;
struct dirent entries[2] = {
{ .d_ino = 0, .d_type = DT_REG, .d_name = "." },
{ .d_ino = 0, .d_type = DT_REG, .d_name = ".." },
};
test_ctx.ret_dirent_array = entries;
CHECK(std::filesystem::directory_iterator("/test/dir") == std::filesystem::directory_iterator{});
}
CHECK(esp_vfs_unregister("/test") == ESP_OK);
}

View File

@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <catch2/catch_session.hpp>
extern "C" void app_main(void)
{
const char *argv[] = {
"target_test_main",
"--durations",
"yes",
NULL
};
int argc = sizeof(argv)/sizeof(argv[0]) - 1;
auto result = Catch::Session().run(argc, argv);
if (result != 0) {
printf("Test failed with result %d\n", result);
} else {
printf("Test passed.\n");
}
}

View File

@ -0,0 +1,3 @@
# Name, Type, SubType, Offset, Size, Flags
factory, app, factory, 0x10000, 1400k,
storage, data, fat, , 528k,
1 # Name Type SubType Offset Size Flags
2 factory app factory 0x10000 1400k
3 storage data fat 528k

View File

@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.qemu
@pytest.mark.host_test
@pytest.mark.esp32
@pytest.mark.esp32c3
def test_std_filesystem(dut: Dut) -> None:
dut.expect_exact('All tests passed', timeout=200)

View File

@ -0,0 +1,14 @@
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_FATFS_LFN_HEAP=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_WARN_WRITE_STRINGS=y
CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=n
CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES=y
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
CONFIG_LOG_DEFAULT_LEVEL_WARN=y
CONFIG_LOG_MAXIMUM_LEVEL_INFO=y