forked from espressif/esp-idf
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:
@@ -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
|
||||
|
308
components/vfs/include/esp_vfs_ops.h
Normal file
308
components/vfs/include/esp_vfs_ops.h
Normal 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
|
@@ -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
|
||||
|
@@ -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}
|
||||
|
@@ -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));
|
||||
}
|
||||
|
166
components/vfs/test_apps/main/test_vfs_minified.c
Normal file
166
components/vfs/test_apps/main/test_vfs_minified.c
Normal 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");
|
||||
}
|
@@ -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
|
||||
|
@@ -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 \
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user