From 401f6e47134417708a4654cac150f15e0c70819a Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 25 Oct 2016 21:02:39 +0800 Subject: [PATCH] vfs: initial version of virtual filesystem API --- components/vfs/README.rst | 2 + components/vfs/component.mk | 1 + components/vfs/include/esp_vfs.h | 157 +++++++++++++++++ components/vfs/vfs.c | 285 +++++++++++++++++++++++++++++++ 4 files changed, 445 insertions(+) create mode 100644 components/vfs/README.rst create mode 100755 components/vfs/component.mk create mode 100644 components/vfs/include/esp_vfs.h create mode 100644 components/vfs/vfs.c diff --git a/components/vfs/README.rst b/components/vfs/README.rst new file mode 100644 index 0000000000..b32e22ccf8 --- /dev/null +++ b/components/vfs/README.rst @@ -0,0 +1,2 @@ +Virtual filesystem (VFS) is a driver which can perform operations on file-like objects. This can be a real filesystem (FAT, SPIFFS, etc.), or a device driver which exposes file-like interface. + \ No newline at end of file diff --git a/components/vfs/component.mk b/components/vfs/component.mk new file mode 100755 index 0000000000..fccf88db8a --- /dev/null +++ b/components/vfs/component.mk @@ -0,0 +1 @@ +include $(IDF_PATH)/make/component_common.mk diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h new file mode 100644 index 0000000000..0d1c3d1479 --- /dev/null +++ b/components/vfs/include/esp_vfs.h @@ -0,0 +1,157 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __ESP_VFS_H__ +#define __ESP_VFS_H__ + +#include +#include +#include "esp_err.h" +#include +#include +#include +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Maximum length of path prefix (not including zero terminator) + */ +#define ESP_VFS_PATH_MAX 15 + +/** + * Default value of flags member in esp_vfs_t structure. + */ +#define ESP_VFS_FLAG_DEFAULT 0 + +/** + * Flag which indicates that FS needs extra context pointer in syscalls. + */ +#define ESP_VFS_FLAG_CONTEXT_PTR 1 + +/** + * @brief VFS definition structure + * + * This structure should be filled with pointers to corresponding + * FS driver functions. + * + * If the FS implementation has an option to use certain offset for + * all file descriptors, this value should be passed into fd_offset + * field. Otherwise VFS component will translate all FDs to start + * at zero offset. + * + * Some FS implementations expect some state (e.g. pointer to some structure) + * to be passed in as a first argument. For these implementations, + * populate the members of this structure which have _p suffix, set + * flags member to ESP_VFS_FLAG_CONTEXT_PTR and provide the context pointer + * to esp_vfs_register function. + * If the implementation doesn't use this extra argument, populate the + * members without _p suffix and set flags memeber to ESP_VFS_FLAG_DEFAULT. + * + * If the FS driver doesn't provide some of the functions, set corresponding + * members to NULL. + */ +typedef struct +{ + int fd_offset; + int flags; + union { + size_t (*write_p)(void* p, int fd, const void * data, size_t size); + size_t (*write)(int fd, const void * data, size_t size); + }; + union { + _off_t (*lseek_p)(void* p, int fd, _off_t size, int mode); + _off_t (*lseek)(int fd, _off_t size, int mode); + }; + union { + ssize_t (*read_p)(void* ctx, int fd, void * dst, size_t size); + ssize_t (*read)(int fd, void * dst, size_t size); + }; + union { + int (*open_p)(void* ctx, const char * path, int flags, int mode); + int (*open)(const char * path, int flags, int mode); + }; + union { + int (*close_p)(void* ctx, int fd); + int (*close)(int fd); + }; + union { + int (*fstat_p)(void* ctx, int fd, struct stat * st); + int (*fstat)(int fd, struct stat * st); + }; + union { + int (*stat_p)(void* ctx, const char * path, struct stat * st); + int (*stat)(const char * path, struct stat * st); + }; + union { + int (*link_p)(void* ctx, const char* n1, const char* n2); + int (*link)(const char* n1, const char* n2); + }; + union { + int (*unlink_p)(void* ctx, const char *path); + int (*unlink)(const char *path); + }; + union { + int (*rename_p)(void* ctx, const char *src, const char *dst); + int (*rename)(const char *src, const char *dst); + }; +} esp_vfs_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, 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". + * @param vfs Pointer to esp_vfs_t, a structure which maps syscalls to + * the filesystem driver functions. VFS component doesn't + * assume ownership of this pointer. + * @param ctx If vfs->flags has ESP_VFS_FLAG_CONTEXT_PTR set, a pointer + * which should be passed to VFS functions. Otherwise, NULL. + * + * @return ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are + * registered. + */ +esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx); + + +/** + * These functions are to be used in newlib syscall table. They will be called by + * newlib when it needs to use any of the syscalls. + */ + +ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size); +_off_t esp_vfs_lseek(struct _reent *r, int fd, _off_t size, int mode); +ssize_t esp_vfs_read(struct _reent *r, int fd, void * dst, size_t size); +int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode); +int esp_vfs_close(struct _reent *r, int fd); +int esp_vfs_fstat(struct _reent *r, int fd, struct stat * st); +int esp_vfs_stat(struct _reent *r, const char * path, struct stat * st); +int esp_vfs_link(struct _reent *r, const char* n1, const char* n2); +int esp_vfs_unlink(struct _reent *r, const char *path); +int esp_vfs_rename(struct _reent *r, const char *src, const char *dst); + + + +#ifdef __cplusplus +} // extern "C" +#endif + + +#endif //__ESP_VFS_H__ diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c new file mode 100644 index 0000000000..afc7149bad --- /dev/null +++ b/components/vfs/vfs.c @@ -0,0 +1,285 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include "esp_vfs.h" +#include "esp_log.h" + +/* + * File descriptors visible by the applications are composed of two parts. + * Lower CONFIG_MAX_FD_BITS bits are used for the actual file descriptor. + * Next (16 - CONFIG_MAX_FD_BITS - 1) bits are used to identify the VFS this + * descriptor corresponds to. + * Highest bit is zero. + * We can only use 16 bits because newlib stores file descriptor as short int. + */ + +#ifndef CONFIG_MAX_FD_BITS +#define CONFIG_MAX_FD_BITS 12 +#endif + +#define MAX_VFS_ID_BITS (16 - CONFIG_MAX_FD_BITS - 1) +// mask of actual file descriptor (e.g. 0x00000fff) +#define VFS_FD_MASK ((1 << CONFIG_MAX_FD_BITS) - 1) +// max number of VFS entries +#define VFS_MAX_COUNT ((1 << MAX_VFS_ID_BITS) - 1) +// mask of VFS id (e.g. 0x00007000) +#define VFS_INDEX_MASK (VFS_MAX_COUNT << CONFIG_MAX_FD_BITS) +#define VFS_INDEX_S CONFIG_MAX_FD_BITS + +typedef struct vfs_entry_ { + esp_vfs_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 + int offset; // index of this structure in s_vfs array +} vfs_entry_t; + +static vfs_entry_t* s_vfs[VFS_MAX_COUNT] = { 0 }; +static size_t s_vfs_count = 0; + +esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx) +{ + if (s_vfs_count >= VFS_MAX_COUNT) { + return ESP_ERR_NO_MEM; + } + size_t len = strlen(base_path); + if (len < 2 || len > ESP_VFS_PATH_MAX) { + return ESP_ERR_INVALID_ARG; + } + if (base_path[0] != '/' || base_path[len - 1] == '/') { + return ESP_ERR_INVALID_ARG; + } + vfs_entry_t *entry = (vfs_entry_t*) malloc(sizeof(vfs_entry_t)); + if (entry == NULL) { + return ESP_ERR_NO_MEM; + } + strcpy(entry->path_prefix, base_path); // we have already verified argument length + memcpy(&entry->vfs, vfs, sizeof(esp_vfs_t)); + entry->path_prefix_len = len; + entry->ctx = ctx; + entry->offset = s_vfs_count; + s_vfs[s_vfs_count] = entry; + ++s_vfs_count; + return ESP_OK; +} + +static const vfs_entry_t* get_vfs_for_fd(int fd) +{ + int index = ((fd & VFS_INDEX_MASK) >> VFS_INDEX_S); + if (index >= s_vfs_count) { + return NULL; + } + return s_vfs[index]; +} + +static int translate_fd(const vfs_entry_t* vfs, int fd) +{ + return (fd & VFS_FD_MASK) + vfs->vfs.fd_offset; +} + +static const char* translate_path(const vfs_entry_t* vfs, const char* src_path) +{ + assert(strncmp(src_path, vfs->path_prefix, vfs->path_prefix_len) == 0); + return src_path + vfs->path_prefix_len; +} + +static 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 (len < vfs->path_prefix_len + 1) { // +1 is for the trailing slash after base path + continue; + } + if (memcmp(path, vfs->path_prefix, vfs->path_prefix_len) != 0) { // match prefix + continue; + } + if (path[vfs->path_prefix_len] != '/') { // don't match "/data" prefix for "/data1/foo.txt" + continue; + } + return vfs; + } + return NULL; +} + +/* + * Using huge multi-line macros is never nice, but in this case + * the only alternative is to repeat this chunk of code (with different function names) + * for each syscall being implemented. Given that this define is contained within a single + * file, this looks like a good tradeoff. + * + * First we check if syscall is implemented by VFS (corresponding member is not NULL), + * then call the right flavor of the method (e.g. open or open_p) depending on + * ESP_VFS_FLAG_CONTEXT_PTR flag. If ESP_VFS_FLAG_CONTEXT_PTR is set, context is passed + * in as first argument and _p variant is used for the call. + * 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) { \ + __errno_r(r) = ENOSYS; \ + return -1; \ + } \ + if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ + ret = (*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \ + } else { \ + ret = (*pvfs->vfs.func)(__VA_ARGS__);\ + } + + +int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode) +{ + const vfs_entry_t* vfs = get_vfs_for_path(path); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, path); + int ret; + CHECK_AND_CALL(ret, r, vfs, open, path_within_vfs, flags, mode); + return ret - vfs->vfs.fd_offset + (vfs->offset << VFS_INDEX_S); +} + +ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + if (vfs == NULL) { + __errno_r(r) = EBADF; + return -1; + } + int local_fd = translate_fd(vfs, fd); + int ret; + CHECK_AND_CALL(ret, r, vfs, write, local_fd, data, size); + return ret; +} + +_off_t esp_vfs_lseek(struct _reent *r, int fd, _off_t size, int mode) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + if (vfs == NULL) { + __errno_r(r) = EBADF; + return -1; + } + int local_fd = translate_fd(vfs, fd); + int ret; + CHECK_AND_CALL(ret, r, vfs, lseek, local_fd, size, mode); + return ret; +} + +ssize_t esp_vfs_read(struct _reent *r, int fd, void * dst, size_t size) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + if (vfs == NULL) { + __errno_r(r) = EBADF; + return -1; + } + int local_fd = translate_fd(vfs, fd); + int ret; + CHECK_AND_CALL(ret, r, vfs, read, local_fd, dst, size); + return ret; +} + + +int esp_vfs_close(struct _reent *r, int fd) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + if (vfs == NULL) { + __errno_r(r) = EBADF; + return -1; + } + int local_fd = translate_fd(vfs, fd); + int ret; + CHECK_AND_CALL(ret, r, vfs, close, local_fd); + return ret; +} + +int esp_vfs_fstat(struct _reent *r, int fd, struct stat * st) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(fd); + if (vfs == NULL) { + __errno_r(r) = EBADF; + return -1; + } + int local_fd = translate_fd(vfs, fd); + int ret; + CHECK_AND_CALL(ret, r, vfs, fstat, local_fd, st); + return ret; +} + +int esp_vfs_stat(struct _reent *r, const char * path, struct stat * st) +{ + const vfs_entry_t* vfs = get_vfs_for_path(path); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, path); + int ret; + CHECK_AND_CALL(ret, r, vfs, stat, path_within_vfs, st); + return ret; +} + +int esp_vfs_link(struct _reent *r, const char* n1, const char* n2) +{ + const vfs_entry_t* vfs = get_vfs_for_path(n1); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const vfs_entry_t* vfs2 = get_vfs_for_path(n2); + if (vfs != vfs2) { + __errno_r(r) = EXDEV; + return -1; + } + 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); + return ret; +} + +int esp_vfs_unlink(struct _reent *r, const char *path) +{ + const vfs_entry_t* vfs = get_vfs_for_path(path); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, path); + int ret; + CHECK_AND_CALL(ret, r, vfs, unlink, path_within_vfs); + return ret; +} + +int esp_vfs_rename(struct _reent *r, const char *src, const char *dst) +{ + const vfs_entry_t* vfs = get_vfs_for_path(src); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const vfs_entry_t* vfs_dst = get_vfs_for_path(dst); + if (vfs != vfs_dst) { + __errno_r(r) = EXDEV; + return -1; + } + 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); + return ret; +} -- 2.40.0