Files
TAIXIN/sdk/lib/VFS/vfs.c

730 lines
18 KiB
C
Raw Permalink Normal View History

/*
* Copyright 2024, Hiroyuki OYAMA
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <string.h>
#include <stdio.h>
// #include <sys/errno.h>
// #include <sys/dirent.h>
// #include <sys/unistd.h>
// #include <pico/mutex.h>
#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);
}