Merge branch 'feature/redesign_vfs_t' into 'master'

feat(storage/vfs): redesign esp_vfs_t struct

Closes IDF-10326 and DOC-9138

See merge request espressif/esp-idf!31449
This commit is contained in:
Martin Vychodil
2024-10-22 20:24:38 +08:00
10 changed files with 1056 additions and 126 deletions

View File

@@ -29,6 +29,8 @@
#include <string.h>
#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 <min_fd; max_fd).
@@ -492,6 +486,23 @@ ssize_t esp_vfs_pwrite(int fd, const void *src, size_t size, off_t offset);
*/
void esp_vfs_dump_fds(FILE *fp);
/**
* @brief Dump all registered FSs to the provided FILE*
*
* Dump the FSs in the format:
@verbatim
<index>:<VFS Path Prefix> -> <VFS entry ptr>
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

View File

@@ -0,0 +1,308 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <stdarg.h>
#include <unistd.h>
#include <utime.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include <sys/types.h>
#include <sys/reent.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/termios.h>
#include <sys/poll.h>
#ifdef __clang__ // TODO LLVM-330
#include <sys/dirent.h>
#else
#include <dirent.h>
#endif
#include <string.h>
#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

View File

@@ -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

View File

@@ -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}

View File

@@ -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));
}

View File

@@ -0,0 +1,166 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#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");
}

View File

@@ -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, "<index>:<VFS Path Prefix> -> <VFS entry ptr>\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,16 +1478,22 @@ 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) {
continue;
}
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.start_select(nfds, &item->readfds, &item->writefds, &item->errorfds, sel_sem,
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) {
@@ -1108,7 +1512,6 @@ int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds
return -1;
}
}
}
if (socket_select) {
ESP_LOGD(TAG, "calling socket_select with the following FDs");
@@ -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

View File

@@ -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 \

View File

@@ -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

View File

@@ -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