diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index 655bbf5790..8e71115a43 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -29,6 +29,8 @@ #include #include "sdkconfig.h" +#include "esp_vfs_ops.h" + #ifdef __cplusplus extern "C" { #endif @@ -62,20 +64,11 @@ extern "C" { */ #define ESP_VFS_FLAG_READONLY_FS (1 << 2) -/* - * @brief VFS identificator used for esp_vfs_register_with_id() - */ -typedef int esp_vfs_id_t; - /** - * @brief VFS semaphore type for select() - * + * Flag which indicates that VFS structure should be freed upon unregistering. + * @note Free if false, do not free if true */ -typedef struct -{ - bool is_sem_local; /*!< type of "sem" is SemaphoreHandle_t when true, defined by socket driver otherwise */ - void *sem; /*!< semaphore instance */ -} esp_vfs_select_sem_t; +#define ESP_VFS_FLAG_STATIC (1 << 3) /** * @brief VFS definition structure @@ -259,6 +252,8 @@ typedef struct #endif // CONFIG_VFS_SUPPORT_SELECT || defined __DOXYGEN__ } esp_vfs_t; + + /** * Register a virtual filesystem for given path prefix. * @@ -284,7 +279,6 @@ typedef struct */ esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx); - /** * Special case function for registering a VFS that uses a method other than * open() to open new file descriptors from the interval : -> + + where: + index : internal index in the table of registered FSs (the same as returned when registering fd with id) + VFS Path Prefix : file prefix used in the esp_vfs_register call or "NULL" + VFS entry ptr : pointer to the esp_vfs_fs_ops_t struct used internally when resolving the calls + @endverbatim + * + * @param fp File descriptor where data will be dumped + */ +void esp_vfs_dump_registered_paths(FILE *fp); + #ifdef __cplusplus } // extern "C" #endif diff --git a/components/vfs/include/esp_vfs_ops.h b/components/vfs/include/esp_vfs_ops.h new file mode 100644 index 0000000000..bd5d635235 --- /dev/null +++ b/components/vfs/include/esp_vfs_ops.h @@ -0,0 +1,308 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_err.h" +#include +#include +#include +#include +#include +#include +#ifdef __clang__ // TODO LLVM-330 +#include +#else +#include +#endif +#include +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _SYS_TYPES_FD_SET +#error "VFS should be used with FD_SETSIZE and FD_SET from sys/types.h" +#endif + +/* + * @brief VFS identificator used for esp_vfs_register_with_id() + */ +typedef int esp_vfs_id_t; + +/** + * @brief VFS semaphore type for select() + * + */ +typedef struct +{ + bool is_sem_local; /*!< type of "sem" is SemaphoreHandle_t when true, defined by socket driver otherwise */ + void *sem; /*!< semaphore instance */ +} esp_vfs_select_sem_t; + +#ifdef CONFIG_VFS_SUPPORT_DIR + +/** + * @brief Struct containing function pointers to directory related functionality. + * + */ +typedef struct { + union { + int (*stat_p)(void* ctx, const char * path, struct stat * st); /*!< stat with context pointer */ + int (*stat)(const char * path, struct stat * st); /*!< stat without context pointer */ + }; + union { + int (*link_p)(void* ctx, const char* n1, const char* n2); /*!< link with context pointer */ + int (*link)(const char* n1, const char* n2); /*!< link without context pointer */ + }; + union { + int (*unlink_p)(void* ctx, const char *path); /*!< unlink with context pointer */ + int (*unlink)(const char *path); /*!< unlink without context pointer */ + }; + union { + int (*rename_p)(void* ctx, const char *src, const char *dst); /*!< rename with context pointer */ + int (*rename)(const char *src, const char *dst); /*!< rename without context pointer */ + }; + union { + DIR* (*opendir_p)(void* ctx, const char* name); /*!< opendir with context pointer */ + DIR* (*opendir)(const char* name); /*!< opendir without context pointer */ + }; + union { + struct dirent* (*readdir_p)(void* ctx, DIR* pdir); /*!< readdir with context pointer */ + struct dirent* (*readdir)(DIR* pdir); /*!< readdir without context pointer */ + }; + union { + int (*readdir_r_p)(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent); /*!< readdir_r with context pointer */ + int (*readdir_r)(DIR* pdir, struct dirent* entry, struct dirent** out_dirent); /*!< readdir_r without context pointer */ + }; + union { + long (*telldir_p)(void* ctx, DIR* pdir); /*!< telldir with context pointer */ + long (*telldir)(DIR* pdir); /*!< telldir without context pointer */ + }; + union { + void (*seekdir_p)(void* ctx, DIR* pdir, long offset); /*!< seekdir with context pointer */ + void (*seekdir)(DIR* pdir, long offset); /*!< seekdir without context pointer */ + }; + union { + int (*closedir_p)(void* ctx, DIR* pdir); /*!< closedir with context pointer */ + int (*closedir)(DIR* pdir); /*!< closedir without context pointer */ + }; + union { + int (*mkdir_p)(void* ctx, const char* name, mode_t mode); /*!< mkdir with context pointer */ + int (*mkdir)(const char* name, mode_t mode); /*!< mkdir without context pointer */ + }; + union { + int (*rmdir_p)(void* ctx, const char* name); /*!< rmdir with context pointer */ + int (*rmdir)(const char* name); /*!< rmdir without context pointer */ + }; + union { + int (*access_p)(void* ctx, const char *path, int amode); /*!< access with context pointer */ + int (*access)(const char *path, int amode); /*!< access without context pointer */ + }; + union { + int (*truncate_p)(void* ctx, const char *path, off_t length); /*!< truncate with context pointer */ + int (*truncate)(const char *path, off_t length); /*!< truncate without context pointer */ + }; + union { + int (*ftruncate_p)(void* ctx, int fd, off_t length); /*!< ftruncate with context pointer */ + int (*ftruncate)(int fd, off_t length); /*!< ftruncate without context pointer */ + }; + union { + int (*utime_p)(void* ctx, const char *path, const struct utimbuf *times); /*!< utime with context pointer */ + int (*utime)(const char *path, const struct utimbuf *times); /*!< utime without context pointer */ + }; +} esp_vfs_dir_ops_t; + +#endif // CONFIG_VFS_SUPPORT_DIR + +#ifdef CONFIG_VFS_SUPPORT_TERMIOS + +/** + * @brief Struct containing function pointers to termios related functionality. + * + */ +typedef struct { + union { + int (*tcsetattr_p)(void *ctx, int fd, int optional_actions, const struct termios *p); /*!< tcsetattr with context pointer */ + int (*tcsetattr)(int fd, int optional_actions, const struct termios *p); /*!< tcsetattr without context pointer */ + }; + union { + int (*tcgetattr_p)(void *ctx, int fd, struct termios *p); /*!< tcgetattr with context pointer */ + int (*tcgetattr)(int fd, struct termios *p); /*!< tcgetattr without context pointer */ + }; + union { + int (*tcdrain_p)(void *ctx, int fd); /*!< tcdrain with context pointer */ + int (*tcdrain)(int fd); /*!< tcdrain without context pointer */ + }; + union { + int (*tcflush_p)(void *ctx, int fd, int select); /*!< tcflush with context pointer */ + int (*tcflush)(int fd, int select); /*!< tcflush without context pointer */ + }; + union { + int (*tcflow_p)(void *ctx, int fd, int action); /*!< tcflow with context pointer */ + int (*tcflow)(int fd, int action); /*!< tcflow without context pointer */ + }; + union { + pid_t (*tcgetsid_p)(void *ctx, int fd); /*!< tcgetsid with context pointer */ + pid_t (*tcgetsid)(int fd); /*!< tcgetsid without context pointer */ + }; + union { + int (*tcsendbreak_p)(void *ctx, int fd, int duration); /*!< tcsendbreak with context pointer */ + int (*tcsendbreak)(int fd, int duration); /*!< tcsendbreak without context pointer */ + }; +} esp_vfs_termios_ops_t; + +#endif // CONFIG_VFS_SUPPORT_TERMIOS + +#ifdef CONFIG_VFS_SUPPORT_SELECT + +/** + * @brief Struct containing function pointers to select related functionality. + * + */ +typedef struct { + /** start_select is called for setting up synchronous I/O multiplexing of the desired file descriptors in the given VFS */ + esp_err_t (*start_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, esp_vfs_select_sem_t sem, void **end_select_args); + + /** socket select function for socket FDs with the functionality of POSIX select(); this should be set only for the socket VFS */ + int (*socket_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout); + + /** called by VFS to interrupt the socket_select call when select is activated from a non-socket VFS driver; set only for the socket driver */ + void (*stop_socket_select)(void *sem); + + /** stop_socket_select which can be called from ISR; set only for the socket driver */ + void (*stop_socket_select_isr)(void *sem, BaseType_t *woken); + + /** end_select is called to stop the I/O multiplexing and deinitialize the environment created by start_select for the given VFS */ + void* (*get_socket_select_semaphore)(void); + + /** get_socket_select_semaphore returns semaphore allocated in the socket driver; set only for the socket driver */ + esp_err_t (*end_select)(void *end_select_args); +} esp_vfs_select_ops_t; + +#endif // CONFIG_VFS_SUPPORT_SELECT + +/** + * @brief Main struct of the minified vfs API, containing basic function pointers as well as pointers to the other subcomponents. + * + */ +typedef struct { + union { + ssize_t (*write_p)(void* p, int fd, const void * data, size_t size); /*!< Write with context pointer */ + ssize_t (*write)(int fd, const void * data, size_t size); /*!< Write without context pointer */ + }; + union { + off_t (*lseek_p)(void* p, int fd, off_t size, int mode); /*!< Seek with context pointer */ + off_t (*lseek)(int fd, off_t size, int mode); /*!< Seek without context pointer */ + }; + union { + ssize_t (*read_p)(void* ctx, int fd, void * dst, size_t size); /*!< Read with context pointer */ + ssize_t (*read)(int fd, void * dst, size_t size); /*!< Read without context pointer */ + }; + union { + ssize_t (*pread_p)(void *ctx, int fd, void * dst, size_t size, off_t offset); /*!< pread with context pointer */ + ssize_t (*pread)(int fd, void * dst, size_t size, off_t offset); /*!< pread without context pointer */ + }; + union { + ssize_t (*pwrite_p)(void *ctx, int fd, const void *src, size_t size, off_t offset); /*!< pwrite with context pointer */ + ssize_t (*pwrite)(int fd, const void *src, size_t size, off_t offset); /*!< pwrite without context pointer */ + }; + union { + int (*open_p)(void* ctx, const char * path, int flags, int mode); /*!< open with context pointer */ + int (*open)(const char * path, int flags, int mode); /*!< open without context pointer */ + }; + union { + int (*close_p)(void* ctx, int fd); /*!< close with context pointer */ + int (*close)(int fd); /*!< close without context pointer */ + }; + union { + int (*fstat_p)(void* ctx, int fd, struct stat * st); /*!< fstat with context pointer */ + int (*fstat)(int fd, struct stat * st); /*!< fstat without context pointer */ + }; + union { + int (*fcntl_p)(void* ctx, int fd, int cmd, int arg); /*!< fcntl with context pointer */ + int (*fcntl)(int fd, int cmd, int arg); /*!< fcntl without context pointer */ + }; + union { + int (*ioctl_p)(void* ctx, int fd, int cmd, va_list args); /*!< ioctl with context pointer */ + int (*ioctl)(int fd, int cmd, va_list args); /*!< ioctl without context pointer */ + }; + union { + int (*fsync_p)(void* ctx, int fd); /*!< fsync with context pointer */ + int (*fsync)(int fd); /*!< fsync without context pointer */ + }; + +#ifdef CONFIG_VFS_SUPPORT_DIR + esp_vfs_dir_ops_t *dir; /*!< pointer to the dir subcomponent */ +#endif + +#ifdef CONFIG_VFS_SUPPORT_TERMIOS + esp_vfs_termios_ops_t *termios; /*!< pointer to the termios subcomponent */ +#endif + +#if CONFIG_VFS_SUPPORT_SELECT || defined __DOXYGEN__ + esp_vfs_select_ops_t *select; /*!< pointer to the select subcomponent */ +#endif + +} esp_vfs_fs_ops_t; + +/** + * Register a virtual filesystem for given path prefix. + * + * @param base_path file path prefix associated with the filesystem. + * Must be a zero-terminated C string, may be empty. + * If not empty, must be up to ESP_VFS_PATH_MAX + * characters long, and at least 2 characters long. + * Name must start with a "/" and must not end with "/". + * For example, "/data" or "/dev/spi" are valid. + * These VFSes would then be called to handle file paths such as + * "/data/myfile.txt" or "/dev/spi/0". + * In the special case of an empty base_path, a "fallback" + * VFS is registered. Such VFS will handle paths which are not + * matched by any other registered VFS. + * @param vfs Pointer to esp_vfs_fs_ops_t, a structure which maps syscalls to + * the filesystem driver functions. VFS component does not assume ownership of this struct, but see flags for more info + * + * @param flags Set of binary flags controlling how the registered FS should be treated + * - ESP_VFS_FLAG_STATIC - if this flag is specified VFS assumes the provided esp_vfs_fs_ops_t and all its subcomponents are statically allocated, + * if it is not enabled a deep copy of the provided struct will be created, which will be managed by the VFS component + * - ESP_VFS_FLAG_CONTEXT_PTR - If set, the VFS will use the context-aware versions of the filesystem operation functions (suffixed with `_p`) in `esp_vfs_fs_ops_t` and its subcomponents. + * The `ctx` parameter will be passed as the context argument when these functions are invoked. + * + * @param ctx Context pointer for fs operation functions, see the ESP_VFS_FLAG_CONTEXT_PTR. + * Should be `NULL` if not used. + * + * @return ESP_OK if successful, ESP_ERR_NO_MEM if too many FSes are + * registered. + */ +esp_err_t esp_vfs_register_fs(const char* base_path, const esp_vfs_fs_ops_t* vfs, int flags, void* ctx); + +/** + * Analog of esp_vfs_register_with_id which accepts esp_vfs_fs_ops_t instead. + * + */ +esp_err_t esp_vfs_register_fs_with_id(const esp_vfs_fs_ops_t* vfs, int flags, void* ctx, esp_vfs_id_t* id); + +/** + * Alias for esp_vfs_unregister for naming consistency + */ +esp_err_t esp_vfs_unregister_fs(const char* base_path); + +/** + * Alias for esp_vfs_unregister_with_id for naming consistency + */ +esp_err_t esp_vfs_unregister_fs_with_id(esp_vfs_id_t id); + +#ifdef __cplusplus +} +#endif diff --git a/components/vfs/private_include/esp_vfs_private.h b/components/vfs/private_include/esp_vfs_private.h index fda3e9712b..5dcca69b55 100644 --- a/components/vfs/private_include/esp_vfs_private.h +++ b/components/vfs/private_include/esp_vfs_private.h @@ -19,7 +19,8 @@ extern "C" { #endif typedef struct vfs_entry_ { - esp_vfs_t vfs; // contains pointers to VFS functions + int flags; /*!< ESP_VFS_FLAG_CONTEXT_PTR and/or ESP_VFS_FLAG_READONLY_FS or ESP_VFS_FLAG_DEFAULT */ + const esp_vfs_fs_ops_t *vfs; // contains pointers to VFS functions char path_prefix[ESP_VFS_PATH_MAX]; // path prefix mapped to this VFS size_t path_prefix_len; // micro-optimization to avoid doing extra strlen void* ctx; // optional pointer which can be passed to VFS diff --git a/components/vfs/test_apps/main/CMakeLists.txt b/components/vfs/test_apps/main/CMakeLists.txt index 7ab4968a9e..e7cfc100f5 100644 --- a/components/vfs/test_apps/main/CMakeLists.txt +++ b/components/vfs/test_apps/main/CMakeLists.txt @@ -3,6 +3,7 @@ set(src "test_app_main.c" "test_vfs_access.c" "test_vfs_fd.c" "test_vfs_lwip.c" "test_vfs_open.c" "test_vfs_paths.c" "test_vfs_select.c" "test_vfs_nullfs.c" + "test_vfs_minified.c" ) idf_component_register(SRCS ${src} diff --git a/components/vfs/test_apps/main/test_vfs_lwip.c b/components/vfs/test_apps/main/test_vfs_lwip.c index 3bed7d5ec8..e9cf3870ab 100644 --- a/components/vfs/test_apps/main/test_vfs_lwip.c +++ b/components/vfs/test_apps/main/test_vfs_lwip.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -15,8 +15,9 @@ TEST_CASE("fstat() sets st_mode to socket type", "[vfs][lwip]") { test_case_uses_tcpip(); int socket_fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + TEST_ASSERT(socket_fd >= 0); struct stat stat = { 0 }; - fstat(socket_fd, &stat); + TEST_ASSERT_EQUAL(0, fstat(socket_fd, &stat)); TEST_ASSERT_TRUE(S_ISSOCK(stat.st_mode)); TEST_ASSERT_FALSE(S_ISBLK(stat.st_mode)); @@ -25,5 +26,5 @@ TEST_CASE("fstat() sets st_mode to socket type", "[vfs][lwip]") TEST_ASSERT_FALSE(S_ISREG(stat.st_mode)); TEST_ASSERT_FALSE(S_ISLNK(stat.st_mode)); - close(socket_fd); + TEST_ASSERT_EQUAL(0, close(socket_fd)); } diff --git a/components/vfs/test_apps/main/test_vfs_minified.c b/components/vfs/test_apps/main/test_vfs_minified.c new file mode 100644 index 0000000000..bef2e507fb --- /dev/null +++ b/components/vfs/test_apps/main/test_vfs_minified.c @@ -0,0 +1,166 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include +#include +#include + +#include "esp_err.h" +#include "esp_vfs_ops.h" +#include "unity.h" +#include "esp_vfs.h" + +#include "unity_test_runner.h" + +#define BUF_CAP 1024 + +typedef struct { + uint8_t data[BUF_CAP]; + size_t head; + size_t tail; +} cb_t; + +void cb_reset(cb_t* buf) { + buf->head = 0; + buf->tail = 0; +} + +size_t cb_count(const cb_t *buf) { + return buf->head - buf->tail; +} + +int cb_write(cb_t *buf, const uint8_t *data, size_t size) { + size_t count = cb_count(buf); + size_t space = BUF_CAP - count; + + size_t to_write = (size > space) ? space : size; + + size_t idx = buf->head % BUF_CAP; + size_t first_chunk = BUF_CAP - idx; + if (first_chunk > to_write) + first_chunk = to_write; + + memcpy(&buf->data[idx], data, first_chunk); + buf->head += first_chunk; + + if (first_chunk < to_write) { + size_t second_chunk = to_write - first_chunk; + memcpy(&buf->data[0], data + first_chunk, second_chunk); + buf->head += second_chunk; + } + + return (int)to_write; +} + +int cb_read(cb_t *buf, uint8_t *dest, size_t count) { + size_t available = cb_count(buf); + size_t to_read = (count > available) ? available : count; + + size_t idx = buf->tail % BUF_CAP; + size_t first_chunk = BUF_CAP - idx; + if (first_chunk > to_read) + first_chunk = to_read; + + memcpy(dest, &buf->data[idx], first_chunk); + buf->tail += first_chunk; + + if (first_chunk < to_read) { + size_t second_chunk = to_read - first_chunk; + memcpy(dest + first_chunk, &buf->data[0], second_chunk); + buf->tail += second_chunk; + } + + return (int)to_read; +} + + +int buffer_open(void *ctx, const char *path, int flags, int mode) { + return 0; +} + +int buffer_write(void *ctx, int fd, const void *data, size_t size) { + cb_t* buf = (cb_t*) ctx; + return cb_write(buf, data, size); +} + +int buffer_read(void *ctx, int fd, void *data, size_t size) { + cb_t* buf = (cb_t*) ctx; + return cb_read(buf, data, size); +} + +int buffer_close(void *ctx, int fd) { + cb_reset((cb_t*) ctx); + return 0; +} + +static esp_vfs_fs_ops_t s_buffer_fs = { + .write_p = buffer_write, + .read_p = buffer_read, + .open_p = buffer_open, + .close_p = buffer_close, +}; + +TEST_CASE("VFS won't create a copy when ESP_FLAG_VFS_STATIC is specified", "[esp_vfs_fs_ops_t]") +{ + TEST_MESSAGE("test"); + static esp_vfs_dir_ops_t dir = {}; + static esp_vfs_fs_ops_t vfs = { + .dir = &dir, + }; + + cb_t *buffer = calloc(1, sizeof(cb_t)); + + esp_err_t err = ESP_OK; + err = esp_vfs_register_fs("/buffer", &s_buffer_fs, ESP_VFS_FLAG_CONTEXT_PTR | ESP_VFS_FLAG_STATIC, buffer); + TEST_ASSERT_EQUAL(ESP_OK, err); + + err = esp_vfs_register_fs("/static", &vfs, ESP_VFS_FLAG_STATIC, NULL); + TEST_ASSERT_EQUAL(ESP_OK, err); + + err = esp_vfs_register_fs("/dynamic", &vfs, ESP_VFS_FLAG_DEFAULT, NULL); + TEST_ASSERT_EQUAL(ESP_OK, err); + + FILE *buf_f = fopen("/buffer/a", "r+"); + + esp_vfs_dump_registered_paths(buf_f); + + char read_buffer[512]; + size_t bytes_read = fread(read_buffer, 1, sizeof(read_buffer) - 1, buf_f); + read_buffer[bytes_read] = '\0'; // Null-terminate the string + + // Parse the buffer to extract VFS pointers + char *line = strtok(read_buffer, "\n"); + void *static_vfs_ptr = NULL; + void *dynamic_vfs_ptr = NULL; + + while (line != NULL) { + int index; + char path_prefix[64]; + char ptr_str[32]; + TEST_MESSAGE(line); + if (sscanf(line, "%d:%63s -> %31s", &index, path_prefix, ptr_str) == 3) { + void *vfs_ptr = (void *)strtoul(ptr_str, NULL, 0); + if (strcmp(path_prefix, "/static") == 0) { + static_vfs_ptr = vfs_ptr; + } else if (strcmp(path_prefix, "/dynamic") == 0) { + dynamic_vfs_ptr = vfs_ptr; + } + } + line = strtok(NULL, "\n"); + } + + // Check that the pointer for "/static" is the same as 'vfs' and "/dynamic" is different + TEST_ASSERT_EQUAL_PTR(&vfs, static_vfs_ptr); + TEST_ASSERT_NOT_EQUAL(&vfs, dynamic_vfs_ptr); + + free(buffer); + fclose(buf_f); + + esp_vfs_unregister("/buffer"); + esp_vfs_unregister("/static"); + esp_vfs_unregister("/dynamic"); +} diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index 906e8684d9..1f02e0f550 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -19,6 +19,7 @@ #include "freertos/semphr.h" #include "esp_vfs.h" #include "esp_vfs_private.h" +#include "include/esp_vfs.h" #include "sdkconfig.h" // Warn about using deprecated option @@ -75,8 +76,283 @@ static size_t s_vfs_count = 0; static fd_table_t s_fd_table[MAX_FDS] = { [0 ... MAX_FDS-1] = FD_TABLE_ENTRY_UNUSED }; static _lock_t s_fd_table_lock; -esp_err_t esp_vfs_register_common(const char* base_path, size_t len, const esp_vfs_t* vfs, void* ctx, int *vfs_index) +static ssize_t esp_get_free_index(void) { + for (ssize_t i = 0; i < VFS_MAX_COUNT; i++) { + if (s_vfs[i] == NULL) { + return i; + } + } + return -1; +} + +static void esp_vfs_free_fs_ops(esp_vfs_fs_ops_t *vfs) { +#ifdef CONFIG_VFS_SUPPORT_TERMIOS + free(vfs->termios); +#endif + +#ifdef CONFIG_VFS_SUPPORT_DIR + free(vfs->dir); +#endif + +#ifdef CONFIG_VFS_SUPPORT_SELECT + free(vfs->select); +#endif + + free(vfs); +} + +static void esp_vfs_free_entry(vfs_entry_t *entry) { + if (entry == NULL) { // Necessary because of the following flags check + return; + } + + if (!(entry->flags & ESP_VFS_FLAG_STATIC)) { + esp_vfs_free_fs_ops((esp_vfs_fs_ops_t*)entry->vfs); // const cast, but we know it's not static from the flag + } + + free(entry); +} + +static void esp_minify_vfs(const esp_vfs_t * const vfs, esp_vfs_fs_ops_t *min) { + assert(vfs != NULL); + assert(min != NULL); + *min = (esp_vfs_fs_ops_t) { + .write = vfs->write, + .lseek = vfs->lseek, + .read = vfs->read, + .pread = vfs->pread, + .pwrite = vfs->pwrite, + .open = vfs->open, + .close = vfs->close, + .fstat = vfs->fstat, + .fcntl = vfs->fcntl, + .ioctl = vfs->ioctl, + .fsync = vfs->fsync, +#ifdef CONFIG_VFS_SUPPORT_DIR + .dir = min->dir, +#endif +#ifdef CONFIG_VFS_SUPPORT_TERMIOS + .termios = min->termios, +#endif +#ifdef CONFIG_VFS_SUPPORT_SELECT + .select = min->select, +#endif + }; + +#ifdef CONFIG_VFS_SUPPORT_DIR + // If the dir functions are not implemented, we don't need to convert them + if (min->dir != NULL) { + *(min->dir) = (esp_vfs_dir_ops_t) { + .stat = vfs->stat, + .link = vfs->link, + .unlink = vfs->unlink, + .rename = vfs->rename, + .opendir = vfs->opendir, + .readdir = vfs->readdir, + .readdir_r = vfs->readdir_r, + .telldir = vfs->telldir, + .seekdir = vfs->seekdir, + .closedir = vfs->closedir, + .mkdir = vfs->mkdir, + .rmdir = vfs->rmdir, + .access = vfs->access, + .truncate = vfs->truncate, + .ftruncate = vfs->ftruncate, + .utime = vfs->utime, + }; + } +#endif // CONFIG_VFS_SUPPORT_DIR + +#ifdef CONFIG_VFS_SUPPORT_TERMIOS + // If the termios functions are not implemented, we don't need to convert them + if (min->termios != NULL) { + *(min->termios) = (esp_vfs_termios_ops_t) { + .tcsetattr = vfs->tcsetattr, + .tcgetattr = vfs->tcgetattr, + .tcdrain = vfs->tcdrain, + .tcflush = vfs->tcflush, + .tcflow = vfs->tcflow, + .tcgetsid = vfs->tcgetsid, + .tcsendbreak = vfs->tcsendbreak, + }; + } +#endif // CONFIG_VFS_SUPPORT_TERMIOS + +#ifdef CONFIG_VFS_SUPPORT_SELECT + // If the select functions are not implemented, we don't need to convert them + if (min->select != NULL) { + *(min->select) = (esp_vfs_select_ops_t) { + .start_select = vfs->start_select, + .socket_select = vfs->socket_select, + .stop_socket_select = vfs->stop_socket_select, + .stop_socket_select_isr = vfs->stop_socket_select_isr, + .get_socket_select_semaphore = vfs->get_socket_select_semaphore, + .end_select = vfs->end_select, + }; + } +#endif // CONFIG_VFS_SUPPORT_SELECT + +} + +static esp_vfs_fs_ops_t* esp_vfs_duplicate_fs_ops(const esp_vfs_fs_ops_t *vfs) { + esp_vfs_fs_ops_t *min = (esp_vfs_fs_ops_t*) heap_caps_malloc(sizeof(esp_vfs_fs_ops_t), VFS_MALLOC_FLAGS); + if (min == NULL) { + return NULL; + } + + memcpy(min, vfs, sizeof(esp_vfs_fs_ops_t)); + + // remove references to the original components +#ifdef CONFIG_VFS_SUPPORT_DIR + min->dir = NULL; +#endif +#ifdef CONFIG_VFS_SUPPORT_TERMIOS + min->termios = NULL; +#endif +#ifdef CONFIG_VFS_SUPPORT_SELECT + min->select = NULL; +#endif + +#ifdef CONFIG_VFS_SUPPORT_DIR + if (vfs->dir != NULL) { + min->dir = (esp_vfs_dir_ops_t*) heap_caps_malloc(sizeof(esp_vfs_dir_ops_t), VFS_MALLOC_FLAGS); + if (min->dir == NULL) { + goto fail; + } + memcpy(min->dir, vfs->dir, sizeof(esp_vfs_dir_ops_t)); + } +#endif + +#ifdef CONFIG_VFS_SUPPORT_TERMIOS + if (vfs->termios != NULL) { + min->termios = (esp_vfs_termios_ops_t*) heap_caps_malloc(sizeof(esp_vfs_termios_ops_t), VFS_MALLOC_FLAGS); + if (min->termios == NULL) { + goto fail; + } + memcpy(min->termios, vfs->termios, sizeof(esp_vfs_termios_ops_t)); + } +#endif + +#ifdef CONFIG_VFS_SUPPORT_SELECT + if (vfs->select != NULL) { + min->select = (esp_vfs_select_ops_t*) heap_caps_malloc(sizeof(esp_vfs_select_ops_t), VFS_MALLOC_FLAGS); + if (min->select == NULL) { + goto fail; + } + memcpy(min->select, vfs->select, sizeof(esp_vfs_select_ops_t)); + } +#endif + + return min; + +#if defined(CONFIG_VFS_SUPPORT_SELECT) || defined(CONFIG_VFS_SUPPORT_TERMIOS) || defined(CONFIG_VFS_SUPPORT_DIR) +fail: +#endif + esp_vfs_free_fs_ops(min); + return NULL; +} + +static esp_err_t esp_vfs_make_fs_ops(const esp_vfs_t *vfs, esp_vfs_fs_ops_t **min) { + if (vfs == NULL) { + ESP_LOGE(TAG, "Cannot minify NULL VFS"); + return ESP_ERR_INVALID_ARG; + } + + if (min == NULL) { + ESP_LOGE(TAG, "Cannot minify VFS to NULL"); + return ESP_ERR_INVALID_ARG; + } + + esp_vfs_fs_ops_t *main = (esp_vfs_fs_ops_t*) heap_caps_malloc(sizeof(esp_vfs_fs_ops_t), VFS_MALLOC_FLAGS); + if (main == NULL) { + return ESP_ERR_NO_MEM; + } + + // Initialize all fields to NULL + memset(main, 0, sizeof(esp_vfs_fs_ops_t)); + +#ifdef CONFIG_VFS_SUPPORT_DIR + bool skip_dir = + vfs->stat == NULL && + vfs->link == NULL && + vfs->unlink == NULL && + vfs->rename == NULL && + vfs->opendir == NULL && + vfs->readdir == NULL && + vfs->readdir_r == NULL && + vfs->telldir == NULL && + vfs->seekdir == NULL && + vfs->closedir == NULL && + vfs->mkdir == NULL && + vfs->rmdir == NULL && + vfs->access == NULL && + vfs->truncate == NULL && + vfs->ftruncate == NULL && + vfs->utime == NULL; + + if (!skip_dir) { + main->dir = (esp_vfs_dir_ops_t*) heap_caps_malloc(sizeof(esp_vfs_dir_ops_t), VFS_MALLOC_FLAGS); + if (main->dir == NULL) { + goto fail; + } + } +#endif + +#ifdef CONFIG_VFS_SUPPORT_TERMIOS + bool skip_termios = + vfs->tcsetattr == NULL && + vfs->tcgetattr == NULL && + vfs->tcdrain == NULL && + vfs->tcflush == NULL && + vfs->tcflow == NULL && + vfs->tcgetsid == NULL && + vfs->tcsendbreak == NULL; + + if (!skip_termios) { + main->termios = (esp_vfs_termios_ops_t*) heap_caps_malloc(sizeof(esp_vfs_termios_ops_t), VFS_MALLOC_FLAGS); + if (main->termios == NULL) { + goto fail; + } + } +#endif + +#ifdef CONFIG_VFS_SUPPORT_SELECT + bool skip_select = + vfs->start_select == NULL && + vfs->socket_select == NULL && + vfs->stop_socket_select == NULL && + vfs->stop_socket_select_isr == NULL && + vfs->get_socket_select_semaphore == NULL && + vfs->end_select == NULL; + + if (!skip_select) { + main->select = (esp_vfs_select_ops_t*) heap_caps_malloc(sizeof(esp_vfs_select_ops_t), VFS_MALLOC_FLAGS); + if (main->select == NULL) { + goto fail; + } + } +#endif + + esp_minify_vfs(vfs, main); + + *min = main; + return ESP_OK; + +#if defined(CONFIG_VFS_SUPPORT_SELECT) || defined(CONFIG_VFS_SUPPORT_TERMIOS) || defined(CONFIG_VFS_SUPPORT_DIR) +fail: + + esp_vfs_free_fs_ops(main); + return ESP_ERR_NO_MEM; +#endif +} + +static esp_err_t esp_vfs_register_fs_common(const char* base_path, size_t len, const esp_vfs_fs_ops_t* vfs, int flags, void* ctx, int *vfs_index) { + if (vfs == NULL) { + ESP_LOGE(TAG, "VFS is NULL"); + return ESP_ERR_INVALID_ARG; + } + if (len != LEN_PATH_PREFIX_IGNORED) { /* empty prefix is allowed, "/" is not allowed */ if ((len == 1) || (len > ESP_VFS_PATH_MAX)) { @@ -87,33 +363,36 @@ esp_err_t esp_vfs_register_common(const char* base_path, size_t len, const esp_v return ESP_ERR_INVALID_ARG; } } + + ssize_t index = esp_get_free_index(); + if (index < 0) { + return ESP_ERR_NO_MEM; + } + + if (s_vfs[index] != NULL) { + return ESP_ERR_INVALID_STATE; + } + + if (index == s_vfs_count) { + s_vfs_count++; + } + vfs_entry_t *entry = (vfs_entry_t*) heap_caps_malloc(sizeof(vfs_entry_t), VFS_MALLOC_FLAGS); if (entry == NULL) { return ESP_ERR_NO_MEM; } - size_t index; - for (index = 0; index < s_vfs_count; ++index) { - if (s_vfs[index] == NULL) { - break; - } - } - if (index == s_vfs_count) { - if (s_vfs_count >= VFS_MAX_COUNT) { - free(entry); - return ESP_ERR_NO_MEM; - } - ++s_vfs_count; - } + s_vfs[index] = entry; if (len != LEN_PATH_PREFIX_IGNORED) { strcpy(entry->path_prefix, base_path); // we have already verified argument length } else { bzero(entry->path_prefix, sizeof(entry->path_prefix)); } - memcpy(&entry->vfs, vfs, sizeof(esp_vfs_t)); entry->path_prefix_len = len; + entry->vfs = vfs; entry->ctx = ctx; entry->offset = index; + entry->flags = flags; if (vfs_index) { *vfs_index = index; @@ -122,6 +401,58 @@ esp_err_t esp_vfs_register_common(const char* base_path, size_t len, const esp_v return ESP_OK; } +esp_err_t esp_vfs_register_fs(const char* base_path, const esp_vfs_fs_ops_t* vfs, int flags, void* ctx) +{ + if (vfs == NULL) { + ESP_LOGE(TAG, "VFS is NULL"); + return ESP_ERR_INVALID_ARG; + } + + if ((flags & ESP_VFS_FLAG_STATIC)) { + return esp_vfs_register_fs_common(base_path, strlen(base_path), vfs, flags, ctx, NULL); + } + + esp_vfs_fs_ops_t *_vfs = esp_vfs_duplicate_fs_ops(vfs); + if (_vfs == NULL) { + return ESP_ERR_NO_MEM; + } + + esp_err_t ret = esp_vfs_register_fs_common(base_path, strlen(base_path), _vfs, flags, ctx, NULL); + if (ret != ESP_OK) { + esp_vfs_free_fs_ops(_vfs); + return ret; + } + + return ESP_OK; +} + +esp_err_t esp_vfs_register_common(const char* base_path, size_t len, const esp_vfs_t* vfs, void* ctx, int *vfs_index) +{ + if (vfs == NULL) { + ESP_LOGE(TAG, "VFS is NULL"); + return ESP_ERR_INVALID_ARG; + } + + if (vfs->flags & ESP_VFS_FLAG_STATIC) { + ESP_LOGE(TAG, "ESP_VFS_FLAG_STATIC is not supported for esp_vfs_t, use esp_vfs_register_fs instead"); + return ESP_ERR_INVALID_ARG; + } + + esp_vfs_fs_ops_t *_vfs = NULL; + esp_err_t ret = esp_vfs_make_fs_ops(vfs, &_vfs); + if (ret != ESP_OK) { + return ret; + } + + ret = esp_vfs_register_fs_common(base_path, len, _vfs, vfs->flags, ctx, vfs_index); + if (ret != ESP_OK) { + esp_vfs_free_fs_ops(_vfs); + return ret; + } + + return ESP_OK; +} + esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx) { return esp_vfs_register_common(base_path, strlen(base_path), vfs, ctx, NULL); @@ -134,7 +465,7 @@ esp_err_t esp_vfs_register_fd_range(const esp_vfs_t *vfs, void *ctx, int min_fd, return ESP_ERR_INVALID_ARG; } - int index = -1; + int index = 0; esp_err_t ret = esp_vfs_register_common("", LEN_PATH_PREFIX_IGNORED, vfs, ctx, &index); if (ret == ESP_OK) { @@ -164,6 +495,16 @@ esp_err_t esp_vfs_register_fd_range(const esp_vfs_t *vfs, void *ctx, int min_fd, return ret; } +esp_err_t esp_vfs_register_fs_with_id(const esp_vfs_fs_ops_t *vfs, int flags, void *ctx, esp_vfs_id_t *vfs_id) +{ + if (vfs_id == NULL) { + return ESP_ERR_INVALID_ARG; + } + + *vfs_id = -1; + return esp_vfs_register_fs_common("", LEN_PATH_PREFIX_IGNORED, vfs, flags, ctx, vfs_id); +} + esp_err_t esp_vfs_register_with_id(const esp_vfs_t *vfs, void *ctx, esp_vfs_id_t *vfs_id) { if (vfs_id == NULL) { @@ -180,7 +521,7 @@ esp_err_t esp_vfs_unregister_with_id(esp_vfs_id_t vfs_id) return ESP_ERR_INVALID_ARG; } vfs_entry_t* vfs = s_vfs[vfs_id]; - free(vfs); + esp_vfs_free_entry(vfs); s_vfs[vfs_id] = NULL; _lock_acquire(&s_fd_table_lock); @@ -193,8 +534,11 @@ esp_err_t esp_vfs_unregister_with_id(esp_vfs_id_t vfs_id) _lock_release(&s_fd_table_lock); return ESP_OK; + } +esp_err_t esp_vfs_unregister_fs_with_id(esp_vfs_id_t vfs_id) __attribute__((alias("esp_vfs_unregister_with_id"))); + esp_err_t esp_vfs_unregister(const char* base_path) { const size_t base_path_len = strlen(base_path); @@ -211,6 +555,8 @@ esp_err_t esp_vfs_unregister(const char* base_path) return ESP_ERR_INVALID_STATE; } +esp_err_t esp_vfs_unregister_fs(const char* base_path) __attribute__((alias("esp_vfs_unregister"))); + esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd) { return esp_vfs_register_fd_with_local_fd(vfs_id, -1, true, fd); @@ -290,6 +636,22 @@ void esp_vfs_dump_fds(FILE *fp) _lock_release(&s_fd_table_lock); } +void esp_vfs_dump_registered_paths(FILE *fp) +{ + fprintf(fp, "------------------------------------------------------\n"); + fprintf(fp, ": -> \n"); + fprintf(fp, "------------------------------------------------------\n"); + for (size_t i = 0; i < VFS_MAX_COUNT; ++i) { + fprintf( + fp, + "%d:%s -> %p\n", + i, + s_vfs[i] ? s_vfs[i]->path_prefix : "NULL", + s_vfs[i] ? s_vfs[i]->vfs : NULL + ); + } +} + /* * Set ESP_VFS_FLAG_READONLY_FS read-only flag for a registered virtual filesystem * for given path prefix. Should be only called from the esp_vfs_*filesystem* register @@ -306,7 +668,7 @@ esp_err_t esp_vfs_set_readonly_flag(const char* base_path) } if (base_path_len == vfs->path_prefix_len && memcmp(base_path, vfs->path_prefix, vfs->path_prefix_len) == 0) { - vfs->vfs.flags |= ESP_VFS_FLAG_READONLY_FS; + vfs->flags |= ESP_VFS_FLAG_READONLY_FS; return ESP_OK; } } @@ -365,7 +727,7 @@ const vfs_entry_t* get_vfs_for_path(const char* path) size_t len = strlen(path); for (size_t i = 0; i < s_vfs_count; ++i) { const vfs_entry_t* vfs = s_vfs[i]; - if (!vfs || vfs->path_prefix_len == LEN_PATH_PREFIX_IGNORED) { + if (vfs == NULL || vfs->path_prefix_len == LEN_PATH_PREFIX_IGNORED) { continue; } // match path prefix @@ -411,37 +773,69 @@ const vfs_entry_t* get_vfs_for_path(const char* path) * It is enough to check just one of them for NULL, as both variants are part of a union. */ #define CHECK_AND_CALL(ret, r, pvfs, func, ...) \ - if (pvfs->vfs.func == NULL) { \ + if (pvfs->vfs->func == NULL) { \ __errno_r(r) = ENOSYS; \ return -1; \ } \ - if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ - ret = (*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \ + if (pvfs->flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ + ret = (*pvfs->vfs->func ## _p)(pvfs->ctx, __VA_ARGS__); \ } else { \ - ret = (*pvfs->vfs.func)(__VA_ARGS__);\ + ret = (*pvfs->vfs->func)(__VA_ARGS__);\ } +#define CHECK_AND_CALL_SUBCOMPONENT(ret, r, pvfs, component, func, ...) \ + if (pvfs->vfs->component == NULL || pvfs->vfs->component->func == NULL) { \ + __errno_r(r) = ENOSYS; \ + return -1; \ + } \ + if (pvfs->flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ + ret = (*pvfs->vfs->component->func ## _p)(pvfs->ctx, __VA_ARGS__); \ + } else { \ + ret = (*pvfs->vfs->component->func)(__VA_ARGS__);\ + } #define CHECK_AND_CALLV(r, pvfs, func, ...) \ - if (pvfs->vfs.func == NULL) { \ + if (pvfs->vfs->func == NULL) { \ __errno_r(r) = ENOSYS; \ return; \ } \ - if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ - (*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \ + if (pvfs->flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ + (*pvfs->vfs->func ## _p)(pvfs->ctx, __VA_ARGS__); \ } else { \ - (*pvfs->vfs.func)(__VA_ARGS__);\ + (*pvfs->vfs->func)(__VA_ARGS__);\ + } + +#define CHECK_AND_CALL_SUBCOMPONENTV(r, pvfs, component, func, ...) \ + if (pvfs->vfs->component == NULL || pvfs->vfs->component->func == NULL) { \ + __errno_r(r) = ENOSYS; \ + return; \ + } \ + if (pvfs->flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ + (*pvfs->vfs->component->func ## _p)(pvfs->ctx, __VA_ARGS__); \ + } else { \ + (*pvfs->vfs->component->func)(__VA_ARGS__);\ } #define CHECK_AND_CALLP(ret, r, pvfs, func, ...) \ - if (pvfs->vfs.func == NULL) { \ + if (pvfs->vfs->func == NULL) { \ __errno_r(r) = ENOSYS; \ return NULL; \ } \ - if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ - ret = (*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \ + if (pvfs->flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ + ret = (*pvfs->vfs->func ## _p)(pvfs->ctx, __VA_ARGS__); \ } else { \ - ret = (*pvfs->vfs.func)(__VA_ARGS__);\ + ret = (*pvfs->vfs->func)(__VA_ARGS__);\ + } + +#define CHECK_AND_CALL_SUBCOMPONENTP(ret, r, pvfs, component, func, ...) \ + if (pvfs->vfs->component == NULL || pvfs->vfs->component->func == NULL) { \ + __errno_r(r) = ENOSYS; \ + return NULL; \ + } \ + if (pvfs->flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ + ret = (*pvfs->vfs->component->func ## _p)(pvfs->ctx, __VA_ARGS__); \ + } else { \ + ret = (*pvfs->vfs->component->func)(__VA_ARGS__);\ } #define CHECK_VFS_READONLY_FLAG(flags) \ @@ -459,7 +853,7 @@ int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode) } int acc_mode = flags & O_ACCMODE; - int ro_filesystem = vfs->vfs.flags & ESP_VFS_FLAG_READONLY_FS; + int ro_filesystem = vfs->flags & ESP_VFS_FLAG_READONLY_FS; if (acc_mode != O_RDONLY && ro_filesystem) { __errno_r(r) = EROFS; return -1; @@ -648,7 +1042,7 @@ int esp_vfs_stat(struct _reent *r, const char * path, struct stat * st) } const char* path_within_vfs = translate_path(vfs, path); int ret; - CHECK_AND_CALL(ret, r, vfs, stat, path_within_vfs, st); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, dir, stat, path_within_vfs, st); return ret; } @@ -662,7 +1056,7 @@ int esp_vfs_utime(const char *path, const struct utimbuf *times) return -1; } const char* path_within_vfs = translate_path(vfs, path); - CHECK_AND_CALL(ret, r, vfs, utime, path_within_vfs, times); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, dir, utime, path_within_vfs, times); return ret; } @@ -679,12 +1073,12 @@ int esp_vfs_link(struct _reent *r, const char* n1, const char* n2) return -1; } - CHECK_VFS_READONLY_FLAG(vfs2->vfs.flags); + CHECK_VFS_READONLY_FLAG(vfs2->flags); const char* path1_within_vfs = translate_path(vfs, n1); const char* path2_within_vfs = translate_path(vfs, n2); int ret; - CHECK_AND_CALL(ret, r, vfs, link, path1_within_vfs, path2_within_vfs); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, dir, link, path1_within_vfs, path2_within_vfs); return ret; } @@ -696,11 +1090,11 @@ int esp_vfs_unlink(struct _reent *r, const char *path) return -1; } - CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + CHECK_VFS_READONLY_FLAG(vfs->flags); const char* path_within_vfs = translate_path(vfs, path); int ret; - CHECK_AND_CALL(ret, r, vfs, unlink, path_within_vfs); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, dir, unlink, path_within_vfs); return ret; } @@ -712,7 +1106,7 @@ int esp_vfs_rename(struct _reent *r, const char *src, const char *dst) return -1; } - CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + CHECK_VFS_READONLY_FLAG(vfs->flags); const vfs_entry_t* vfs_dst = get_vfs_for_path(dst); if (vfs != vfs_dst) { @@ -720,12 +1114,12 @@ int esp_vfs_rename(struct _reent *r, const char *src, const char *dst) return -1; } - CHECK_VFS_READONLY_FLAG(vfs_dst->vfs.flags); + CHECK_VFS_READONLY_FLAG(vfs_dst->flags); const char* src_within_vfs = translate_path(vfs, src); const char* dst_within_vfs = translate_path(vfs, dst); int ret; - CHECK_AND_CALL(ret, r, vfs, rename, src_within_vfs, dst_within_vfs); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, dir, rename, src_within_vfs, dst_within_vfs); return ret; } @@ -739,7 +1133,7 @@ DIR* esp_vfs_opendir(const char* name) } const char* path_within_vfs = translate_path(vfs, name); DIR* ret; - CHECK_AND_CALLP(ret, r, vfs, opendir, path_within_vfs); + CHECK_AND_CALL_SUBCOMPONENTP(ret, r, vfs, dir, opendir, path_within_vfs); if (ret != NULL) { ret->dd_vfs_idx = vfs->offset; } @@ -755,7 +1149,7 @@ struct dirent* esp_vfs_readdir(DIR* pdir) return NULL; } struct dirent* ret; - CHECK_AND_CALLP(ret, r, vfs, readdir, pdir); + CHECK_AND_CALL_SUBCOMPONENTP(ret, r, vfs, dir, readdir, pdir); return ret; } @@ -768,7 +1162,7 @@ int esp_vfs_readdir_r(DIR* pdir, struct dirent* entry, struct dirent** out_diren return -1; } int ret; - CHECK_AND_CALL(ret, r, vfs, readdir_r, pdir, entry, out_dirent); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, dir, readdir_r, pdir, entry, out_dirent); return ret; } @@ -781,7 +1175,7 @@ long esp_vfs_telldir(DIR* pdir) return -1; } long ret; - CHECK_AND_CALL(ret, r, vfs, telldir, pdir); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, dir, telldir, pdir); return ret; } @@ -793,7 +1187,7 @@ void esp_vfs_seekdir(DIR* pdir, long loc) errno = EBADF; return; } - CHECK_AND_CALLV(r, vfs, seekdir, pdir, loc); + CHECK_AND_CALL_SUBCOMPONENTV(r, vfs, dir, seekdir, pdir, loc); } void esp_vfs_rewinddir(DIR* pdir) @@ -810,7 +1204,7 @@ int esp_vfs_closedir(DIR* pdir) return -1; } int ret; - CHECK_AND_CALL(ret, r, vfs, closedir, pdir); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, dir, closedir, pdir); return ret; } @@ -823,11 +1217,11 @@ int esp_vfs_mkdir(const char* name, mode_t mode) return -1; } - CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + CHECK_VFS_READONLY_FLAG(vfs->flags); const char* path_within_vfs = translate_path(vfs, name); int ret; - CHECK_AND_CALL(ret, r, vfs, mkdir, path_within_vfs, mode); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, dir, mkdir, path_within_vfs, mode); return ret; } @@ -840,11 +1234,11 @@ int esp_vfs_rmdir(const char* name) return -1; } - CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + CHECK_VFS_READONLY_FLAG(vfs->flags); const char* path_within_vfs = translate_path(vfs, name); int ret; - CHECK_AND_CALL(ret, r, vfs, rmdir, path_within_vfs); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, dir, rmdir, path_within_vfs); return ret; } @@ -858,7 +1252,7 @@ int esp_vfs_access(const char *path, int amode) return -1; } const char* path_within_vfs = translate_path(vfs, path); - CHECK_AND_CALL(ret, r, vfs, access, path_within_vfs, amode); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, dir, access, path_within_vfs, amode); return ret; } @@ -872,10 +1266,10 @@ int esp_vfs_truncate(const char *path, off_t length) return -1; } - CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + CHECK_VFS_READONLY_FLAG(vfs->flags); const char* path_within_vfs = translate_path(vfs, path); - CHECK_AND_CALL(ret, r, vfs, truncate, path_within_vfs, length); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, dir, truncate, path_within_vfs, length); return ret; } @@ -889,10 +1283,10 @@ int esp_vfs_ftruncate(int fd, off_t length) return -1; } - CHECK_VFS_READONLY_FLAG(vfs->vfs.flags); + CHECK_VFS_READONLY_FLAG(vfs->flags); int ret; - CHECK_AND_CALL(ret, r, vfs, ftruncate, local_fd, length); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, dir, ftruncate, local_fd, length); return ret; } @@ -905,8 +1299,12 @@ static void call_end_selects(int end_index, const fds_triple_t *vfs_fds_triple, for (int i = 0; i < end_index; ++i) { const vfs_entry_t *vfs = get_vfs_for_index(i); const fds_triple_t *item = &vfs_fds_triple[i]; - if (vfs && vfs->vfs.end_select && item->isset) { - esp_err_t err = vfs->vfs.end_select(driver_args[i]); + if (vfs != NULL + && vfs->vfs->select != NULL + && vfs->vfs->select->end_select != NULL + && item->isset + ) { + esp_err_t err = vfs->vfs->select->end_select(driver_args[i]); if (err != ESP_OK) { ESP_LOGD(TAG, "end_select failed: %s", esp_err_to_name(err)); } @@ -1023,8 +1421,8 @@ int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds esp_vfs_safe_fd_isset(fd, writefds) || esp_vfs_safe_fd_isset(fd, errorfds)) { const vfs_entry_t *vfs = s_vfs[vfs_index]; - socket_select = vfs->vfs.socket_select; - sel_sem.sem = vfs->vfs.get_socket_select_semaphore(); + socket_select = vfs->vfs->select->socket_select; + sel_sem.sem = vfs->vfs->select->get_socket_select_semaphore(); } } continue; @@ -1080,33 +1478,38 @@ int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds const vfs_entry_t *vfs = get_vfs_for_index(i); fds_triple_t *item = &vfs_fds_triple[i]; - if (vfs && !vfs->vfs.start_select) { + if (vfs == NULL || vfs->vfs->select == NULL || vfs->vfs->select->start_select == NULL) { ESP_LOGD(TAG, "start_select function callback for this vfs (s_vfs[%d]) is not defined", vfs->offset); - } else if (vfs && vfs->vfs.start_select && item->isset) { - // call start_select for all non-socket VFSs with has at least one FD set in readfds, writefds, or errorfds - // note: it can point to socket VFS but item->isset will be false for that - ESP_LOGD(TAG, "calling start_select for VFS ID %d with the following local FDs", i); - esp_vfs_log_fd_set("readfds", &item->readfds); - esp_vfs_log_fd_set("writefds", &item->writefds); - esp_vfs_log_fd_set("errorfds", &item->errorfds); - esp_err_t err = vfs->vfs.start_select(nfds, &item->readfds, &item->writefds, &item->errorfds, sel_sem, - driver_args + i); + continue; + } - if (err != ESP_OK) { - if (err != ESP_ERR_NOT_SUPPORTED) { - call_end_selects(i, vfs_fds_triple, driver_args); - } - (void) set_global_fd_sets(vfs_fds_triple, vfs_count, readfds, writefds, errorfds); - if (sel_sem.is_sem_local && sel_sem.sem) { - vSemaphoreDelete(sel_sem.sem); - sel_sem.sem = NULL; - } - free(vfs_fds_triple); - free(driver_args); - __errno_r(r) = EINTR; - ESP_LOGD(TAG, "start_select failed: %s", esp_err_to_name(err)); - return -1; + if (!item->isset) { + continue; + } + + // call start_select for all non-socket VFSs with has at least one FD set in readfds, writefds, or errorfds + // note: it can point to socket VFS but item->isset will be false for that + ESP_LOGD(TAG, "calling start_select for VFS ID %d with the following local FDs", i); + esp_vfs_log_fd_set("readfds", &item->readfds); + esp_vfs_log_fd_set("writefds", &item->writefds); + esp_vfs_log_fd_set("errorfds", &item->errorfds); + esp_err_t err = vfs->vfs->select->start_select(nfds, &item->readfds, &item->writefds, &item->errorfds, sel_sem, + driver_args + i); + + if (err != ESP_OK) { + if (err != ESP_ERR_NOT_SUPPORTED) { + call_end_selects(i, vfs_fds_triple, driver_args); } + (void) set_global_fd_sets(vfs_fds_triple, vfs_count, readfds, writefds, errorfds); + if (sel_sem.is_sem_local && sel_sem.sem) { + vSemaphoreDelete(sel_sem.sem); + sel_sem.sem = NULL; + } + free(vfs_fds_triple); + free(driver_args); + __errno_r(r) = EINTR; + ESP_LOGD(TAG, "start_select failed: %s", esp_err_to_name(err)); + return -1; } } @@ -1193,8 +1596,11 @@ void esp_vfs_select_triggered(esp_vfs_select_sem_t sem) // Note: s_vfs_count could have changed since the start of vfs_select() call. However, that change doesn't // matter here stop_socket_select() will be called for only valid VFS drivers. const vfs_entry_t *vfs = s_vfs[i]; - if (vfs != NULL && vfs->vfs.stop_socket_select != NULL) { - vfs->vfs.stop_socket_select(sem.sem); + if (vfs != NULL + && vfs->vfs->select != NULL + && vfs->vfs->select->stop_socket_select != NULL + ) { + vfs->vfs->select->stop_socket_select(sem.sem); break; } } @@ -1213,9 +1619,12 @@ void esp_vfs_select_triggered_isr(esp_vfs_select_sem_t sem, BaseType_t *woken) // Note: s_vfs_count could have changed since the start of vfs_select() call. However, that change doesn't // matter here stop_socket_select() will be called for only valid VFS drivers. const vfs_entry_t *vfs = s_vfs[i]; - if (vfs != NULL && vfs->vfs.stop_socket_select_isr != NULL) { + if (vfs != NULL + && vfs->vfs->select != NULL + && vfs->vfs->select->stop_socket_select_isr != NULL + ) { // Note: If the UART ISR resides in IRAM, the function referenced by stop_socket_select_isr should also be placed in IRAM. - vfs->vfs.stop_socket_select_isr(sem.sem, woken); + vfs->vfs->select->stop_socket_select_isr(sem.sem, woken); break; } } @@ -1236,7 +1645,7 @@ int tcgetattr(int fd, struct termios *p) return -1; } int ret; - CHECK_AND_CALL(ret, r, vfs, tcgetattr, local_fd, p); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, termios, tcgetattr, local_fd, p); return ret; } @@ -1250,7 +1659,7 @@ int tcsetattr(int fd, int optional_actions, const struct termios *p) return -1; } int ret; - CHECK_AND_CALL(ret, r, vfs, tcsetattr, local_fd, optional_actions, p); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, termios, tcsetattr, local_fd, optional_actions, p); return ret; } @@ -1264,7 +1673,7 @@ int tcdrain(int fd) return -1; } int ret; - CHECK_AND_CALL(ret, r, vfs, tcdrain, local_fd); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, termios, tcdrain, local_fd); return ret; } @@ -1278,7 +1687,7 @@ int tcflush(int fd, int select) return -1; } int ret; - CHECK_AND_CALL(ret, r, vfs, tcflush, local_fd, select); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, termios, tcflush, local_fd, select); return ret; } @@ -1292,7 +1701,7 @@ int tcflow(int fd, int action) return -1; } int ret; - CHECK_AND_CALL(ret, r, vfs, tcflow, local_fd, action); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, termios, tcflow, local_fd, action); return ret; } @@ -1306,7 +1715,7 @@ pid_t tcgetsid(int fd) return -1; } int ret; - CHECK_AND_CALL(ret, r, vfs, tcgetsid, local_fd); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, termios, tcgetsid, local_fd); return ret; } @@ -1320,7 +1729,7 @@ int tcsendbreak(int fd, int duration) return -1; } int ret; - CHECK_AND_CALL(ret, r, vfs, tcsendbreak, local_fd, duration); + CHECK_AND_CALL_SUBCOMPONENT(ret, r, vfs, termios, tcsendbreak, local_fd, duration); return ret; } #endif // CONFIG_VFS_SUPPORT_TERMIOS diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 8a131eddec..b48101881a 100644 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -314,6 +314,7 @@ INPUT = \ $(PROJECT_PATH)/components/vfs/include/esp_vfs_eventfd.h \ $(PROJECT_PATH)/components/vfs/include/esp_vfs_semihost.h \ $(PROJECT_PATH)/components/vfs/include/esp_vfs_null.h \ + $(PROJECT_PATH)/components/vfs/include/esp_vfs_ops.h \ $(PROJECT_PATH)/components/vfs/include/esp_vfs.h \ $(PROJECT_PATH)/components/wear_levelling/include/wear_levelling.h \ $(PROJECT_PATH)/components/wifi_provisioning/include/wifi_provisioning/manager.h \ diff --git a/docs/en/api-reference/storage/vfs.rst b/docs/en/api-reference/storage/vfs.rst index 44b1307546..b92bebba76 100644 --- a/docs/en/api-reference/storage/vfs.rst +++ b/docs/en/api-reference/storage/vfs.rst @@ -44,7 +44,7 @@ Case 1: API functions are declared without an extra context pointer (the FS driv .write = &myfs_write, // ... other members initialized - // When registering FS, context pointer (third argument) is NULL: + // When registering FS, context pointer (the third argument) is NULL: ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL)); Case 2: API functions are declared with an extra context pointer (the FS driver supports multiple instances):: @@ -141,7 +141,9 @@ A socket VFS driver needs to be registered with the following functions defined: Please see :component_file:`lwip/port/esp32xx/vfs_lwip.c` for a reference socket driver implementation using LWIP. .. note:: + If you use :cpp:func:`select` for socket file descriptors only then you can disable the :ref:`CONFIG_VFS_SUPPORT_SELECT` option to reduce the code size and improve performance. + You should not change the socket driver during an active :cpp:func:`select` call or you might experience some undefined behavior. Paths @@ -192,6 +194,17 @@ Standard I/O streams (``stdin``, ``stdout``, ``stderr``) are mapped to file desc Note that creating an eventfd with ``EFD_SUPPORT_ISR`` will cause interrupts to be temporarily disabled when reading, writing the file and during the beginning and the ending of the ``select()`` when this file is set. + +Minified VFS +------------ + +To minimize RAM usage, an alternative version of :cpp:func:`esp_vfs_register` function, :cpp:func:`esp_vfs_register_fs` is provided. This version accepts :cpp:class:`esp_vfs_fs_ops_t` instead of :cpp:class:`esp_vfs_t` alongside separate argument for OR-ed flags. Unlike :cpp:func:`esp_vfs_register`, it can handle statically allocated struct, as long as the ``ESP_VFS_FLAG_STATIC`` is provided. + +The :cpp:class:`esp_vfs_fs_ops_t` is split into separate structs based on features (directory operations, select support, termios support, ...). The main struct contains the basic functions (``read``, ``write``, ...), alongside pointers to the feature-specific structs. These pointers can be ``NULL`` indicating lack of support for all the functions provided by that struct, which decreases the required memory. + +Internally the VFS component uses this version of API, with additional steps to convert the :cpp:class:`esp_vfs_t` to :cpp:class:`esp_vfs_fs_ops_t` upon registration. + + Well Known VFS Devices ---------------------- @@ -208,11 +221,14 @@ Application Examples - :example:`system/select` demonstrates how to use synchronous I/O multiplexing with the ``select()`` function, using UART and socket file descriptors, and configuring both to act as loopbacks to receive messages sent from other tasks. + API Reference ------------- .. include-build-file:: inc/esp_vfs.inc +.. include-build-file:: inc/esp_vfs_ops.inc + .. include-build-file:: inc/esp_vfs_dev.inc .. include-build-file:: inc/uart_vfs.inc diff --git a/docs/zh_CN/api-reference/storage/vfs.rst b/docs/zh_CN/api-reference/storage/vfs.rst index ff633c189c..8f96113183 100644 --- a/docs/zh_CN/api-reference/storage/vfs.rst +++ b/docs/zh_CN/api-reference/storage/vfs.rst @@ -39,34 +39,34 @@ VFS 组件支持 C 库函数(如 fopen 和 fprintf 等)与文件系统 (FS) ssize_t myfs_write(int fd, const void * data, size_t size); - // In definition of esp_vfs_t: + // 在 esp_vfs_t 的定义中: .flags = ESP_VFS_FLAG_DEFAULT, .write = &myfs_write, - // ... other members initialized + // ... 其他成员已初始化 - // When registering FS, context pointer (third argument) is NULL: + // 注册文件系统时,上下文指针(第三个参数)为 NULL: ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL)); 示例 2:声明 API 函数时需要一个额外的上下文指针作为参数,即可支持多个 FS 驱动程序实例,此时使用 ``write_p`` :: ssize_t myfs_write(myfs_t* fs, int fd, const void * data, size_t size); - // In definition of esp_vfs_t: + // 在 esp_vfs_t 的定义中: .flags = ESP_VFS_FLAG_CONTEXT_PTR, .write_p = &myfs_write, - // ... other members initialized + // ... 其他成员已初始化 - // When registering FS, pass the FS context pointer into the third argument - // (hypothetical myfs_mount function is used for illustrative purposes) + // 注册文件系统时,将文件系统上下文指针传递给第三个参数 + // (使用假设的 myfs_mount 函数进行示例说明) myfs_t* myfs_inst1 = myfs_mount(partition1->offset, partition1->size); ESP_ERROR_CHECK(esp_vfs_register("/data1", &myfs, myfs_inst1)); - // Can register another instance: + // 可以注册另一个实例: myfs_t* myfs_inst2 = myfs_mount(partition2->offset, partition2->size); ESP_ERROR_CHECK(esp_vfs_register("/data2", &myfs, myfs_inst2)); 同步输入/输出多路复用 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^ VFS 组件支持通过 :cpp:func:`select` 进行同步输入/输出多路复用,其实现方式如下: @@ -91,16 +91,17 @@ VFS 组件支持通过 :cpp:func:`select` 进行同步输入/输出多路复用 :: - // In definition of esp_vfs_t: + // 在 esp_vfs_t 的定义中: .start_select = &uart_start_select, .end_select = &uart_end_select, - // ... other members initialized + // ... 其他成员已初始化 调用 :cpp:func:`start_select` 函数可以设置环境,检测指定 VFS 驱动的文件描述符读取/写入/错误条件。 调用 :cpp:func:`end_select` 函数可以终止/取消初始化/释放由 :cpp:func:`start_select` 设置的环境。 .. note:: + 在少数情况下,在调用 :cpp:func:`end_select` 之前可能并没有调用过 :cpp:func:`start_select`。因此 :cpp:func:`end_select` 的实现必须在该情况下返回错误而不能崩溃。 如需获取更多信息,请参考 :component_file:`esp_driver_uart/src/uart_vfs.c` 中 UART 外设的 VFS 驱动,尤其是函数 :cpp:func:`uart_vfs_dev_register`、:cpp:func:`uart_start_select` 和 :cpp:func:`uart_end_select`。 @@ -122,12 +123,12 @@ VFS 组件支持通过 :cpp:func:`select` 进行同步输入/输出多路复用 :: - // In definition of esp_vfs_t: + // 在 esp_vfs_t 的定义中: .socket_select = &lwip_select, .get_socket_select_semaphore = &lwip_get_socket_select_semaphore, .stop_socket_select = &lwip_stop_socket_select, .stop_socket_select_isr = &lwip_stop_socket_select_isr, - // ... other members initialized + // ... 其他成员已初始化 函数 :cpp:func:`socket_select` 是套接字驱动对 :cpp:func:`select` 的内部实现。该函数只对套接字 VFS 驱动的文件描述符起作用。 @@ -142,6 +143,7 @@ VFS 组件支持通过 :cpp:func:`select` 进行同步输入/输出多路复用 .. note:: 如果 :cpp:func:`select` 用于套接字文件描述符,可以禁用 :ref:`CONFIG_VFS_SUPPORT_SELECT` 选项来减少代码量,提高性能。 + 不要在 :cpp:func:`select` 调用过程中更改套接字驱动,否则会出现一些未定义行为。 路径 @@ -192,6 +194,17 @@ VFS 对文件路径长度没有限制,但文件系统路径前缀受 ``ESP_VFS 注意,用 ``EFD_SUPPORT_ISR`` 创建 eventfd 将导致在读取、写入文件时,以及在设置这个文件的 ``select()`` 开始和结束时,暂时禁用中断。 + +精简版 VFS +------------ + +为尽量减少 RAM 使用,提供了另一版本的 :cpp:func:`esp_vfs_register` 函数,即 :cpp:func:`esp_vfs_register_fs`。这个版本的函数接受 :cpp:class:`esp_vfs_fs_ops_t` 而不是 :cpp:class:`esp_vfs_t`,并且还接受按位或 (OR-ed) 的标志参数。与 :cpp:func:`esp_vfs_register` 函数不同,只要在调用时提供 ``ESP_VFS_FLAG_STATIC`` 标志,该函数就可以处理静态分配的结构体。 + +:cpp:class:`esp_vfs_fs_ops_t` 根据功能(如,目录操作、选择支持、termios 支持等)被拆分为不同的结构体。主结构体包含基本功能,如 ``read``、``write`` 等,并包含指向特定功能结构体的指针。这些指针可以设置为 ``NULL``,表示不支持该结构体中提供的所有功能,从而减少所需内存。 + +在内部,VFS 组件使用的是该版本的 API,并在注册时通过额外步骤将 :cpp:class:`esp_vfs_t` 转换为 :cpp:class:`esp_vfs_fs_ops_t`。 + + 常用 VFS 设备 ------------- @@ -208,11 +221,14 @@ IDF 定义了多个可供应用程序使用的 VFS 设备。这些设备包括 - :example:`system/select` 演示了如何使用 ``select()`` 函数进行同步 I/O 多路复用,使用 UART 和套接字文件描述符,并将二者配置为回环模式,以接收来自其他任务发送的消息。 + API 参考 ------------- .. include-build-file:: inc/esp_vfs.inc +.. include-build-file:: inc/esp_vfs_ops.inc + .. include-build-file:: inc/esp_vfs_dev.inc .. include-build-file:: inc/uart_vfs.inc