/* * Copyright 2024, Hiroyuki OYAMA * * SPDX-License-Identifier: BSD-3-Clause */ #include #include // #include // #include // #include // #include #include "vfs.h" typedef struct { const char *dir; void *filesystem; void *device; } mountpoint_t; typedef struct { fs_file_t *file; filesystem_t *filesystem; char path[PATH_MAX1 + 1]; } file_descriptor_t; typedef struct { fs_dir_t *dir; filesystem_t *filesystem; } dir_descriptor_t; #if !defined(PICO_VFS_MAX_MOUNTPOINT) #define PICO_VFS_MAX_MOUNTPOINT 10 #endif #define FS_MAX_MOUNTPOINT PICO_VFS_MAX_MOUNTPOINT #define STDIO_FILNO_MAX 2 #define FILENO_VALUE(fd) (fd + STDIO_FILNO_MAX + 1) // Conversion to file descriptors for publication #define FILENO_INDEX(fd) (fd - STDIO_FILNO_MAX - 1) // Conversion to file descriptors for internal use static mountpoint_t mountpoints[FS_MAX_MOUNTPOINT] = {0}; // Mount points and file system map static size_t max_file_descriptor = 0; // File descriptor current maximum value static file_descriptor_t *file_descriptor = NULL; // File descriptor and file system map static size_t max_dir_descriptor = 0; // Dir descriptor current maximum value static dir_descriptor_t *dir_descriptor = NULL; // Dir descriptor and file system map static int _error_remap(int err) { if (err >= 0) { errno = 0; return err; } errno = -err; return -1; } static const char *remove_prefix(const char *str, const char *prefix) { size_t len_prefix = strlen(prefix); size_t len_str = strlen(str); if (len_str < len_prefix || strncmp(str, prefix, len_prefix) != 0) { return str; } return str + len_prefix; } static mountpoint_t *find_mountpoint(const char *path) { mountpoint_t *longest_match = NULL; size_t longest_length = 0; for (size_t i = 0; i < FS_MAX_MOUNTPOINT; i++) { if (mountpoints[i].dir == NULL) { continue; } size_t prefix_length = strlen(mountpoints[i].dir); if (prefix_length > longest_length && strncmp(path, mountpoints[i].dir, prefix_length) == 0) { longest_match = &mountpoints[i]; longest_length = prefix_length; } } return longest_match; } static bool is_valid_file_descriptor(int fildes) { if (fildes <= STDIO_FILNO_MAX || (int)max_file_descriptor <= FILENO_INDEX(fildes)) return false; else return true; } int fs_format(filesystem_t *fs, blockdevice_t *device) { if (!device->is_initialized) { int err = device->init(device); if (err != BD_ERROR_OK) { return _error_remap(err); } } return fs->format(fs, device); } int fs_mount(const char *dir, filesystem_t *fs, blockdevice_t *device) { if (!device->is_initialized) { int err = device->init(device); if (err) return _error_remap(err); } int err = fs->mount(fs, device, false); if (err) { return _error_remap(err); } for (size_t i = 0; i < FS_MAX_MOUNTPOINT; i++) { if (mountpoints[i].filesystem == NULL) { mountpoints[i].filesystem = fs; mountpoints[i].device = device; mountpoints[i].dir = VFS_strdup(dir); return _error_remap(0); } } return _error_remap(-EFAULT); } int fs_unmount(const char *path) { mountpoint_t *mp = find_mountpoint(path); if (mp == NULL) { return _error_remap(-ENOENT); } filesystem_t *fs = mp->filesystem; int err = fs->unmount(fs); if (err) { return _error_remap(err); } mp->filesystem = NULL; mp->device = NULL; VFS_free(mp->dir); return _error_remap(0); } int fs_reformat(const char *path) { mountpoint_t *mp = find_mountpoint(path); if (mp == NULL) { return _error_remap(-ENOENT); } filesystem_t *fs = mp->filesystem; blockdevice_t *device = mp->device; int err = fs->unmount(fs); if (err) { return _error_remap(err); } err = fs->format(fs, device); if (err) { return _error_remap(err); } err = fs->mount(fs, device, false); return _error_remap(err); } int fs_info(const char *path, filesystem_t **fs, blockdevice_t **device) { (void)fs; (void)device; mountpoint_t *mp = find_mountpoint(path); if (mp == NULL) { return _error_remap(-ENOENT); } *fs = mp->filesystem; *device = mp->device; return _error_remap(0); } #if 0 int _fstat(int fildes, struct stat *st) { auto_init_recursive_mutex(_mutex); recursive_mutex_enter_blocking(&_mutex); if (fildes == STDIN_FILENO || fildes == STDOUT_FILENO || fildes == STDERR_FILENO) { recursive_mutex_exit(&_mutex); st->st_size = 0; st->st_mode = S_IFCHR; return _error_remap(0); } if (!is_valid_file_descriptor(fildes)) { recursive_mutex_exit(&_mutex); return _error_remap(-EBADF); } fs_file_t *file = file_descriptor[FILENO_INDEX(fildes)].file; filesystem_t *fs = file_descriptor[FILENO_INDEX(fildes)].filesystem; if (fs == NULL) { recursive_mutex_exit(&_mutex); return _error_remap(-EBADF); } off_t size = 0; if (fs->type != FILESYSTEM_TYPE_FAT) { off_t current = fs->file_tell(fs, file); size = fs->file_seek(fs, file, 0, SEEK_END); off_t err = fs->file_seek(fs, file, current, SEEK_SET); if (current != err) { recursive_mutex_exit(&_mutex); return _error_remap(err); } } else { /* NOTE: Support for different behaviour of FatFs from POSIX * * FatFs has a problem where f_size() reports a larger than actual file size when * moved to a position beyond the f_lseek() file size; POSIX reports the actual size * written to the file, not the seek position. */ const char *path = file_descriptor[FILENO_INDEX(fildes)].path; mountpoint_t *mp = find_mountpoint(path); if (mp == NULL) { recursive_mutex_exit(&_mutex); return _error_remap(-ENOENT); } const char *entity_path = remove_prefix(path, mp->dir); filesystem_t *fs = mp->filesystem; struct stat finfo = {0}; int err = fs->stat(fs, entity_path, &finfo); if (err != 0) { recursive_mutex_exit(&_mutex); return _error_remap(err); } size = finfo.st_size; } recursive_mutex_exit(&_mutex); st->st_size = size; st->st_mode = S_IFREG; return _error_remap(0); } #endif char *fs_strerror(int errnum) { if (errnum > 5000) { // SD blockdevice error const char *str = ""; switch (errnum) { case 5001: str = "operation would block"; break; case 5002: str = "unsupported operation"; break; case 5003: str = "invalid parameter"; break; case 5004: str = "uninitialized"; break; case 5005: str = "device is missing or not connected"; break; case 5006: str = "write protected"; break; case 5007: str = "unusable card"; break; case 5008: str = "No response from device"; break; case 5009: str = "CRC error"; break; case 5010: str = "Erase error: reset/sequence"; break; case 5011: str = "Write error: !SPI_DATA_ACCEPTED"; break; default: break; } return (char *)str; } else if (errnum > 4000) { // On-board flash blockdevice error const char *str = ""; switch (errnum) { case 4001: str = "operation timeout"; break; case 4002: str = "safe execution is not possible"; break; case 4003: str = "method fails due to dynamic resource exhaustion"; break; default: break; } return (char *)str; } else { return strerror(errnum); } } static int _assign_file_descriptor() { int fd = -1; if (max_file_descriptor == 0) { max_file_descriptor = 2; file_descriptor = VFS_calloc(max_file_descriptor, sizeof(file_descriptor_t)); if (file_descriptor == NULL) { printf("_open: Out of memory\n"); return -1; } } for (size_t i = 0; i < max_file_descriptor; i++) { if (file_descriptor[i].filesystem == NULL) { fd = i; break; } } if (fd == -1) { // Extend the management array size_t last_max = max_file_descriptor; max_file_descriptor *= 2; file_descriptor = realloc(file_descriptor, sizeof(file_descriptor_t) * max_file_descriptor); if (file_descriptor == NULL) { printf("_open: Out of memory\n"); return -1; } for (size_t i = last_max; i < max_file_descriptor; i++) { file_descriptor[i].filesystem = NULL; file_descriptor[i].file = NULL; } fd = last_max; } return FILENO_VALUE(fd); } int VFS_open(const char *path, int oflags, ...) { mountpoint_t *mp = find_mountpoint(path); if (mp == NULL) { return _error_remap(-ENOENT); } const char *entity_path = remove_prefix(path, mp->dir); // find file descriptor int fd = _assign_file_descriptor(); if (fd == -1) { return _error_remap(-ENFILE); } filesystem_t *fs = mp->filesystem; fs_file_t *file = file_descriptor[FILENO_INDEX(fd)].file = VFS_calloc(1, sizeof(fs_file_t)); if (file == NULL) { return _error_remap(-ENOMEM); } int err = fs->file_open(fs, file, entity_path, oflags); if (err < 0) { VFS_free(file); file_descriptor[FILENO_INDEX(fd)].file = NULL; return _error_remap(err); } file_descriptor[FILENO_INDEX(fd)].filesystem = fs; strncpy(file_descriptor[FILENO_INDEX(fd)].path, path, PATH_MAX1); return _error_remap(fd); } ssize_t VFS_read(int fildes, void *buf, size_t nbyte) { if (!is_valid_file_descriptor(fildes)) { return _error_remap(-EBADF); } fs_file_t *file = file_descriptor[FILENO_INDEX(fildes)].file; filesystem_t *fs = file_descriptor[FILENO_INDEX(fildes)].filesystem; if (fs == NULL) { return _error_remap(-EBADF); } ssize_t size = fs->file_read(fs, file, buf, nbyte); return _error_remap(size); } ssize_t VFS_write(int fildes, const void *buf, size_t nbyte) { if (!is_valid_file_descriptor(fildes)) { return _error_remap(-EBADF); } fs_file_t *file = file_descriptor[FILENO_INDEX(fildes)].file; filesystem_t *fs = file_descriptor[FILENO_INDEX(fildes)].filesystem; if (fs == NULL) { return _error_remap(-EBADF); } ssize_t size = fs->file_write(fs, file, buf, nbyte); return _error_remap(size); } int VFS_close(int fildes) { if (!is_valid_file_descriptor(fildes)) { printf("_close error fildes=%d\n", fildes); return _error_remap(-EBADF); } fs_file_t *file = file_descriptor[FILENO_INDEX(fildes)].file; filesystem_t *fs = file_descriptor[FILENO_INDEX(fildes)].filesystem; if (fs == NULL) { return _error_remap(-EBADF); } int err = fs->file_close(fs, file); VFS_free(file); file_descriptor[FILENO_INDEX(fildes)].filesystem = NULL; file_descriptor[FILENO_INDEX(fildes)].file = NULL; file_descriptor[FILENO_INDEX(fildes)].path[0] = '\0'; return _error_remap(err); } int32_t VFS_seek(int fildes, int32_t offset, int whence) { if (!is_valid_file_descriptor(fildes)) { return _error_remap(-EBADF); } fs_file_t *file = file_descriptor[FILENO_INDEX(fildes)].file; filesystem_t *fs = file_descriptor[FILENO_INDEX(fildes)].filesystem; if (fs == NULL) { return _error_remap(-EBADF); } int32_t pos = fs->file_seek(fs, file, offset, whence); return _error_remap(pos); } int32_t VFS_fsize(int fildes) { if (!is_valid_file_descriptor(fildes)) { return _error_remap(-EBADF); } fs_file_t *file = file_descriptor[FILENO_INDEX(fildes)].file; filesystem_t *fs = file_descriptor[FILENO_INDEX(fildes)].filesystem; if (fs == NULL) { return _error_remap(-EBADF); } int32_t filesize = fs->file_size(fs, file); return _error_remap(filesize); } int VFS_stat(const char *path, struct VFS_stat *st) { mountpoint_t *mp = find_mountpoint(path); if (mp == NULL) { return _error_remap(-ENOENT); } const char *entity_path = remove_prefix(path, mp->dir); filesystem_t *fs = mp->filesystem; int err = fs->stat(fs, entity_path, st); return _error_remap(err); } int VFS_ftruncate(int fildes, int32_t length) { if (!is_valid_file_descriptor(fildes)) { return _error_remap(-EBADF); } fs_file_t *file = file_descriptor[FILENO_INDEX(fildes)].file; filesystem_t *fs = file_descriptor[FILENO_INDEX(fildes)].filesystem; if (fs == NULL) { return _error_remap(-EBADF); } int err = fs->file_truncate(fs, file, length); return _error_remap(err); } int VFS_mkdir(const char *path, mode_t mode) { mountpoint_t *mp = find_mountpoint(path); if (mp == NULL) { return _error_remap(-ENOENT); } const char *entity_path = remove_prefix(path, mp->dir); filesystem_t *fs = mp->filesystem; int err = fs->mkdir(fs, entity_path, mode); return _error_remap(err); } static int _assign_dir_descriptor(void) { int fd = -1; if (max_dir_descriptor == 0) { max_dir_descriptor = 2; dir_descriptor = VFS_calloc(max_dir_descriptor, sizeof(dir_descriptor_t)); if (dir_descriptor == NULL) { printf("_opendir: Out of memory\n"); return -1; } } for (size_t i = 0; i < max_dir_descriptor; i++) { if (dir_descriptor[i].filesystem == NULL) { fd = i; break; } } if (fd == -1) { // Extend the management array size_t last_max = max_dir_descriptor; max_dir_descriptor *= 2; dir_descriptor = realloc(dir_descriptor, sizeof(dir_descriptor_t) * max_dir_descriptor); if (dir_descriptor == NULL) { printf("_opendir: Out of memory\n"); return -1; } for (size_t i = last_max; i < max_dir_descriptor; i++) { dir_descriptor[i].filesystem = NULL; dir_descriptor[i].dir = NULL; } fd = (int)last_max; } return fd; } fs_dir_t *VFS_opendir(const char *path) { mountpoint_t *mp = find_mountpoint(path); if (mp == NULL) { _error_remap(-ENOENT); return NULL; } const char *entity_path = remove_prefix(path, mp->dir); // find dir descriptor int fd = _assign_dir_descriptor(); if (fd == -1) { _error_remap(-ENFILE); return NULL; } fs_dir_t *dir = dir_descriptor[fd].dir = VFS_calloc(1, sizeof(fs_dir_t)); if (dir == NULL) { _error_remap(-ENOMEM); return NULL; } filesystem_t *fs = mp->filesystem; int err = fs->dir_open(fs, dir, entity_path); if (err != 0) { VFS_free(dir); dir_descriptor[fd].dir = NULL; _error_remap(err); return NULL; } dir_descriptor[fd].filesystem = fs; dir->fd = fd; return dir; } struct dirent *VFS_readdir(fs_dir_t *dir) { fs_dir_t *_dir = dir_descriptor[dir->fd].dir; filesystem_t *fs = dir_descriptor[dir->fd].filesystem; if (fs == NULL) { _error_remap(-EBADF); return NULL; } VFS_memset(&_dir->current, 0, sizeof(_dir->current)); int err = fs->dir_read(fs, _dir, &_dir->current); if (err == 0) { return &_dir->current; } else if (err == -ENOENT) { VFS_memset(&_dir->current, 0, sizeof(_dir->current)); _error_remap(0); return NULL; } else { VFS_memset(&_dir->current, 0, sizeof(_dir->current)); _error_remap(err); return NULL; } } int VFS_closedir(fs_dir_t *dir) { int fd = dir->fd; fs_dir_t *_dir = dir_descriptor[dir->fd].dir; filesystem_t *fs = dir_descriptor[dir->fd].filesystem; if (fs == NULL) { return _error_remap(-EBADF); } int err = fs->dir_close(fs, _dir); dir_descriptor[fd].filesystem = NULL; VFS_free(dir_descriptor[fd].dir); dir_descriptor[fd].dir = NULL; return _error_remap(err); } int VFS_rmdir(const char *path) { mountpoint_t *mp = find_mountpoint(path); if (mp == NULL) { return _error_remap(-ENOENT); } const char *entity_path = remove_prefix(path, mp->dir); printf("entity_path:%s\n", entity_path); filesystem_t *fs = mp->filesystem; int err = fs->rmdir(fs, entity_path); return _error_remap(err); } int VFS_rename(const char *old, const char *new) { mountpoint_t *mp = find_mountpoint(old); if (mp == NULL) { return _error_remap(-ENOENT); } const char *old_entity_path = remove_prefix(old, mp->dir); const char *new_entity_path = remove_prefix(new, mp->dir); filesystem_t *fs = mp->filesystem; int err = fs->rename(fs, old_entity_path, new_entity_path); return _error_remap(err); } int VFS_unlink(const char *path) { mountpoint_t *mp = find_mountpoint(path); if (mp == NULL) { return _error_remap(-ENOENT); } const char *entity_path = remove_prefix(path, mp->dir); filesystem_t *fs = mp->filesystem; int err = fs->remove(fs, entity_path); return _error_remap(err); }