1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/sys/vfs/vfs.c
2024-05-15 11:20:38 +02:00

1324 lines
39 KiB
C

/*
* Copyright (C) 2016 Eistec AB
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup sys_vfs
* @{
* @file
* @brief VFS layer implementation
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
*/
#include <errno.h> /* for error codes */
#include <string.h> /* for strncmp */
#include <stddef.h> /* for NULL */
#include <sys/types.h> /* for off_t etc */
#include <sys/stat.h> /* for struct stat */
#include <sys/statvfs.h> /* for struct statvfs */
#include <fcntl.h> /* for O_ACCMODE, ..., fcntl */
#include <unistd.h> /* for STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO */
#include "atomic_utils.h"
#include "clist.h"
#include "compiler_hints.h"
#include "container.h"
#include "modules.h"
#include "mutex.h"
#include "sched.h"
#include "test_utils/expect.h"
#include "thread.h"
#include "vfs.h"
#define ENABLE_DEBUG 0
#include "debug.h"
#if IS_ACTIVE(ENABLE_DEBUG)
/* Since some of these functions are called by printf, we can't really call
* printf from our functions or we end up in an infinite recursion. */
#include <unistd.h> /* for STDOUT_FILENO */
#define DEBUG_NOT_STDOUT(fd, ...) if (fd != STDOUT_FILENO) { DEBUG(__VA_ARGS__); }
#else
#define DEBUG_NOT_STDOUT(...)
#endif
/**
* @brief Automatic mountpoints
*/
XFA_INIT(vfs_mount_t, vfs_mountpoints_xfa);
/**
* @brief Number of automatic mountpoints
*/
#define MOUNTPOINTS_NUMOF XFA_LEN(vfs_mount_t, vfs_mountpoints_xfa)
/**
* @internal
* @brief Array of all currently open files
*
* This table maps POSIX fd numbers to vfs_file_t instances
*
* @attention STDIN, STDOUT, STDERR will use the three first items in this array.
*/
static vfs_file_t _vfs_open_files[VFS_MAX_OPEN_FILES];
/**
* @internal
* @brief List handle for list of all currently mounted file systems
*
* This singly linked list is used to dispatch vfs calls to the appropriate file
* system driver.
*/
static clist_node_t _vfs_mounts_list;
/**
* @internal
* @brief Find an unused entry in the _vfs_open_files array and mark it as used
*
* If the @p fd argument is non-negative, the allocation fails if the
* corresponding slot in the open files table is already occupied, no iteration
* is done to find another free number in this case.
*
* If the @p fd argument is negative, the algorithm will iterate through the
* open files table until it find an unused slot and return the number of that
* slot.
*
* @param[in] fd Desired fd number, use VFS_ANY_FD for any free fd
*
* @return fd on success
* @return <0 on error
*/
static inline int _allocate_fd(int fd);
/**
* @internal
* @brief Mark an allocated entry as unused in the _vfs_open_files array
*
* @param[in] fd fd to free
*/
static inline void _free_fd(int fd);
/**
* @internal
* @brief Initialize an entry in the _vfs_open_files array and mark it as used.
*
* @param[in] fd desired fd number, passed to _allocate_fd
* @param[in] f_op pointer to file operations table
* @param[in] mountp pointer to mount table entry, may be NULL
* @param[in] flags file flags
* @param[in] private_data private_data initial value
*
* @return fd on success
* @return <0 on error
*/
static inline int _init_fd(int fd, const vfs_file_ops_t *f_op, vfs_mount_t *mountp, int flags, void *private_data);
/**
* @internal
* @brief Find the file system associated with the file name @p name, and
* increment the open_files counter
*
* A pointer to the vfs_mount_t associated with the found mount will be written to @p mountpp.
* A pointer to the mount point-relative file name will be written to @p rel_path.
*
* @param[out] mountpp write address of the found mount to this pointer
* @param[in] name absolute path to file
* @param[out] rel_path (optional) output pointer for relative path
*
* @return mount index on success
* @return <0 on error
*/
static inline int _find_mount(vfs_mount_t **mountpp, const char *name, const char **rel_path);
/**
* @internal
* @brief Check that a given fd number is valid
*
* @param[in] fd fd to check
*
* @return 0 if the fd is valid
* @return <0 if the fd is not valid
*/
static inline int _fd_is_valid(int fd);
static mutex_t _mount_mutex = MUTEX_INIT;
static mutex_t _open_mutex = MUTEX_INIT;
int vfs_close(int fd)
{
DEBUG("vfs_close: %d\n", fd);
int res = _fd_is_valid(fd);
if (res < 0) {
return res;
}
vfs_file_t *filp = &_vfs_open_files[fd];
if (filp->f_op->close != NULL) {
/* We will invalidate the fd regardless of the outcome of the file
* system driver close() call below */
res = filp->f_op->close(filp);
}
_free_fd(fd);
return res;
}
int vfs_fcntl(int fd, int cmd, int arg)
{
DEBUG("vfs_fcntl: %d, %d, %d\n", fd, cmd, arg);
int res = _fd_is_valid(fd);
if (res < 0) {
return res;
}
vfs_file_t *filp = &_vfs_open_files[fd];
/* The default fcntl implementation below only allows querying flags,
* any other command requires insight into the file system driver */
switch (cmd) {
case F_GETFL:
/* Get file flags */
DEBUG("vfs_fcntl: GETFL: %d\n", filp->flags);
return filp->flags;
default:
break;
}
/* pass on to file system driver */
if (filp->f_op->fcntl != NULL) {
return filp->f_op->fcntl(filp, cmd, arg);
}
return -EINVAL;
}
int vfs_fstat(int fd, struct stat *buf)
{
DEBUG_NOT_STDOUT(fd, "vfs_fstat: %d, %p\n", fd, (void *)buf);
if (buf == NULL) {
return -EFAULT;
}
int res = _fd_is_valid(fd);
if (res < 0) {
return res;
}
vfs_file_t *filp = &_vfs_open_files[fd];
if (filp->f_op->fstat == NULL) {
/* driver does not implement fstat() */
return -EINVAL;
}
memset(buf, 0, sizeof(*buf));
return filp->f_op->fstat(filp, buf);
}
int vfs_fstatvfs(int fd, struct statvfs *buf)
{
DEBUG("vfs_fstatvfs: %d, %p\n", fd, (void *)buf);
if (buf == NULL) {
return -EFAULT;
}
int res = _fd_is_valid(fd);
if (res < 0) {
return res;
}
vfs_file_t *filp = &_vfs_open_files[fd];
memset(buf, 0, sizeof(*buf));
if (filp->mp->fs->fs_op->statvfs == NULL) {
/* file system driver does not implement statvfs() */
return -EINVAL;
}
return filp->mp->fs->fs_op->statvfs(filp->mp, "/", buf);
}
int vfs_dstatvfs(vfs_DIR *dirp, struct statvfs *buf)
{
DEBUG("vfs_dstatvfs: %p, %p\n", (void*)dirp, (void *)buf);
if (buf == NULL) {
return -EFAULT;
}
memset(buf, 0, sizeof(*buf));
if (dirp->mp->fs->fs_op->statvfs == NULL) {
/* file system driver does not implement statvfs() */
return -EINVAL;
}
return dirp->mp->fs->fs_op->statvfs(dirp->mp, "/", buf);
}
off_t vfs_lseek(int fd, off_t off, int whence)
{
DEBUG("vfs_lseek: %d, %ld, %d\n", fd, (long)off, whence);
int res = _fd_is_valid(fd);
if (res < 0) {
return res;
}
vfs_file_t *filp = &_vfs_open_files[fd];
if (filp->f_op->lseek == NULL) {
/* driver does not implement lseek() */
/* default seek functionality is naive */
switch (whence) {
case SEEK_SET:
break;
case SEEK_CUR:
off += filp->pos;
break;
case SEEK_END:
/* we could use fstat here, but most file system drivers will
* likely already implement lseek in a more efficient fashion */
return -EINVAL;
default:
return -EINVAL;
}
if (off < 0) {
/* the resulting file offset would be negative */
return -EINVAL;
}
filp->pos = off;
return off;
}
return filp->f_op->lseek(filp, off, whence);
}
int vfs_open(const char *name, int flags, mode_t mode)
{
DEBUG("vfs_open: \"%s\", 0x%x, 0%03lo\n", name, flags, (long unsigned int)mode);
if (name == NULL) {
return -EINVAL;
}
const char *rel_path;
vfs_mount_t *mountp;
int res = _find_mount(&mountp, name, &rel_path);
/* _find_mount implicitly increments the open_files count on success */
if (res < 0) {
/* No mount point maps to the requested file name */
DEBUG("vfs_open: no matching mount\n");
return res;
}
mutex_lock(&_open_mutex);
int fd = _init_fd(VFS_ANY_FD, mountp->fs->f_op, mountp, flags, NULL);
mutex_unlock(&_open_mutex);
if (fd < 0) {
DEBUG("vfs_open: _init_fd: ERR %d!\n", fd);
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return fd;
}
vfs_file_t *filp = &_vfs_open_files[fd];
if (filp->f_op->open != NULL) {
res = filp->f_op->open(filp, rel_path, flags, mode);
if (res < 0) {
/* something went wrong during open */
DEBUG("vfs_open: open: ERR %d!\n", res);
/* clean up */
_free_fd(fd);
return res;
}
}
DEBUG("vfs_open: opened %d\n", fd);
return fd;
}
static inline int _prep_read(int fd, const void *dest, vfs_file_t **filp)
{
if (dest == NULL) {
return -EFAULT;
}
int res = _fd_is_valid(fd);
if (res < 0) {
return res;
}
*filp = &_vfs_open_files[fd];
if ((((*filp)->flags & O_ACCMODE) != O_RDONLY) &&
(((*filp)->flags & O_ACCMODE) != O_RDWR)) {
/* File not open for reading */
return -EBADF;
}
if ((*filp)->f_op->read == NULL) {
/* driver does not implement read() */
return -EINVAL;
}
return 0;
}
ssize_t vfs_read(int fd, void *dest, size_t count)
{
DEBUG("vfs_read: %d, %p, %" PRIuSIZE "\n", fd, dest, count);
vfs_file_t *filp = NULL;
int res = _prep_read(fd, dest, &filp);
if (res) {
DEBUG("vfs_read: can't open file - %d\n", res);
return res;
}
return filp->f_op->read(filp, dest, count);
}
ssize_t vfs_readline(int fd, char *dst, size_t len_max)
{
DEBUG("vfs_readline: %d, %p, %" PRIuSIZE "\n", fd, (void *)dst, len_max);
vfs_file_t *filp = NULL;
int res = _prep_read(fd, dst, &filp);
if (res) {
DEBUG("vfs_readline: can't open file - %d\n", res);
return res;
}
const char *start = dst;
while (len_max) {
int res = filp->f_op->read(filp, dst, 1);
if (res < 0) {
break;
}
if (*dst == '\r' || *dst == '\n' || res == 0) {
*dst = 0;
++dst;
break;
} else {
--len_max;
++dst;
}
}
if (len_max == 0) {
return -E2BIG;
}
return dst - start;
}
ssize_t vfs_write(int fd, const void *src, size_t count)
{
DEBUG_NOT_STDOUT(fd, "vfs_write: %d, %p, %" PRIuSIZE "\n", fd, src, count);
if (src == NULL) {
return -EFAULT;
}
int res = _fd_is_valid(fd);
if (res < 0) {
return res;
}
vfs_file_t *filp = &_vfs_open_files[fd];
if (((filp->flags & O_ACCMODE) != O_WRONLY) & ((filp->flags & O_ACCMODE) != O_RDWR)) {
/* File not open for writing */
return -EBADF;
}
if (filp->f_op->write == NULL) {
/* driver does not implement write() */
return -EINVAL;
}
return filp->f_op->write(filp, src, count);
}
ssize_t vfs_write_iol(int fd, const iolist_t *snips)
{
int res, sum = 0;
while (snips) {
res = vfs_write(fd, snips->iol_base, snips->iol_len);
if (res < 0) {
return res;
}
sum += res;
snips = snips->iol_next;
}
return sum;
}
int vfs_fsync(int fd)
{
DEBUG_NOT_STDOUT(fd, "vfs_fsync: %d\n", fd);
int res = _fd_is_valid(fd);
if (res < 0) {
return res;
}
vfs_file_t *filp = &_vfs_open_files[fd];
if (((filp->flags & O_ACCMODE) != O_WRONLY) & ((filp->flags & O_ACCMODE) != O_RDWR)) {
/* File not open for writing */
return -EBADF;
}
if (filp->f_op->fsync == NULL) {
/* driver does not implement fsync() */
return -EINVAL;
}
return filp->f_op->fsync(filp);
}
int vfs_opendir(vfs_DIR *dirp, const char *dirname)
{
DEBUG("vfs_opendir: %p, \"%s\"\n", (void *)dirp, dirname);
if ((dirp == NULL) || (dirname == NULL)) {
return -EINVAL;
}
const char *rel_path;
vfs_mount_t *mountp;
int res = _find_mount(&mountp, dirname, &rel_path);
/* _find_mount implicitly increments the open_files count on success */
if (res < 0) {
/* No mount point maps to the requested file name */
DEBUG("vfs_open: no matching mount\n");
return res;
}
if (rel_path[0] == '\0') {
/* if the trailing slash is missing we will get an empty string back, to
* be consistent against the file system drivers we give the relative
* path "/" instead */
rel_path = "/";
}
if (mountp->fs->d_op == NULL) {
/* file system driver does not support directories */
return -EINVAL;
}
/* initialize dirp */
memset(dirp, 0, sizeof(*dirp));
dirp->mp = mountp;
dirp->d_op = mountp->fs->d_op;
if (dirp->d_op->opendir != NULL) {
int res = dirp->d_op->opendir(dirp, rel_path);
if (res < 0) {
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return res;
}
}
return 0;
}
int vfs_readdir(vfs_DIR *dirp, vfs_dirent_t *entry)
{
DEBUG("vfs_readdir: %p, %p\n", (void *)dirp, (void *)entry);
if ((dirp == NULL) || (entry == NULL)) {
return -EINVAL;
}
if (dirp->d_op != NULL) {
if (dirp->d_op->readdir != NULL) {
return dirp->d_op->readdir(dirp, entry);
}
}
return -EINVAL;
}
int vfs_closedir(vfs_DIR *dirp)
{
DEBUG("vfs_closedir: %p\n", (void *)dirp);
if (dirp == NULL) {
return -EINVAL;
}
vfs_mount_t *mountp = dirp->mp;
if (mountp == NULL) {
return -EBADF;
}
int res = 0;
if (dirp->d_op != NULL) {
if (dirp->d_op->closedir != NULL) {
res = dirp->d_op->closedir(dirp);
}
}
memset(dirp, 0, sizeof(*dirp));
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return res;
}
/**
* @brief Check if the given mount point is mounted
*
* If the mount point is not mounted, _mount_mutex will be locked by this function
*
* @param mountp mount point to check
* @return 0 on success (mount point is valid and not mounted)
* @return -EINVAL if mountp is invalid
* @return -EBUSY if mountp is already mounted
*/
static int check_mount(vfs_mount_t *mountp)
{
if ((mountp == NULL) || (mountp->fs == NULL) || (mountp->mount_point == NULL)) {
return -EINVAL;
}
DEBUG("vfs_mount: -> \"%s\" (%p), %p\n",
mountp->mount_point, (void *)mountp->mount_point, mountp->private_data);
if (mountp->mount_point[0] != '/') {
DEBUG("vfs: check_mount: not absolute mount_point path\n");
return -EINVAL;
}
mountp->mount_point_len = strlen(mountp->mount_point);
mutex_lock(&_mount_mutex);
/* Check for the same mount in the list of mounts to avoid loops */
clist_node_t *found = clist_find(&_vfs_mounts_list, &mountp->list_entry);
if (found != NULL) {
/* Same mount is already mounted */
mutex_unlock(&_mount_mutex);
DEBUG("vfs: check_mount: Already mounted\n");
return -EBUSY;
}
return 0;
}
int vfs_format(vfs_mount_t *mountp)
{
DEBUG("vfs_format: %p\n", (void *)mountp);
int ret = check_mount(mountp);
if (ret < 0) {
return ret;
}
mutex_unlock(&_mount_mutex);
if (mountp->fs->fs_op != NULL) {
if (mountp->fs->fs_op->format != NULL) {
return mountp->fs->fs_op->format(mountp);
}
}
/* Format operation not supported */
return -ENOTSUP;
}
int vfs_mount(vfs_mount_t *mountp)
{
DEBUG("vfs_mount: %p\n", (void *)mountp);
int ret = check_mount(mountp);
if (ret < 0) {
return ret;
}
if (mountp->fs->fs_op != NULL) {
if (mountp->fs->fs_op->mount != NULL) {
/* yes, a file system driver does not need to implement mount/umount */
int res = mountp->fs->fs_op->mount(mountp);
if (res < 0) {
DEBUG("vfs_mount: error %d\n", res);
mutex_unlock(&_mount_mutex);
return res;
}
}
}
/* Insert last in list. This property is relied on by vfs_iterate_mount_dirs. */
clist_rpush(&_vfs_mounts_list, &mountp->list_entry);
mutex_unlock(&_mount_mutex);
DEBUG("vfs_mount: mount done\n");
return 0;
}
int vfs_umount(vfs_mount_t *mountp, bool force)
{
DEBUG("vfs_umount: %p\n", (void *)mountp);
int ret = check_mount(mountp);
switch (ret) {
case 0:
DEBUG("vfs_umount: not mounted\n");
mutex_unlock(&_mount_mutex);
return -EINVAL;
case -EBUSY:
/* -EBUSY returned when fs is mounted, just continue */
break;
default:
DEBUG("vfs_umount: invalid fs\n");
return -EINVAL;
}
DEBUG("vfs_umount: -> \"%s\" open=%u\n", mountp->mount_point,
(unsigned)atomic_load_u16(&mountp->open_files));
if (atomic_load_u16(&mountp->open_files) > 0 && !force) {
mutex_unlock(&_mount_mutex);
return -EBUSY;
}
if (mountp->fs->fs_op != NULL) {
if (mountp->fs->fs_op->umount != NULL) {
int res = mountp->fs->fs_op->umount(mountp);
if (res < 0) {
/* umount failed */
DEBUG("vfs_umount: ERR %d!\n", res);
mutex_unlock(&_mount_mutex);
return res;
}
}
}
/* find mountp in the list and remove it */
clist_node_t *node = clist_remove(&_vfs_mounts_list, &mountp->list_entry);
if (node == NULL) {
/* not found */
DEBUG("vfs_umount: ERR not mounted!\n");
mutex_unlock(&_mount_mutex);
return -EINVAL;
}
mutex_unlock(&_mount_mutex);
return 0;
}
int vfs_rename(const char *from_path, const char *to_path)
{
DEBUG("vfs_rename: \"%s\", \"%s\"\n", from_path, to_path);
if ((from_path == NULL) || (to_path == NULL)) {
return -EINVAL;
}
const char *rel_from;
vfs_mount_t *mountp;
int res = _find_mount(&mountp, from_path, &rel_from);
/* _find_mount implicitly increments the open_files count on success */
if (res < 0) {
/* No mount point maps to the requested file name */
DEBUG("vfs_rename: from: no matching mount\n");
return res;
}
if ((mountp->fs->fs_op == NULL) || (mountp->fs->fs_op->rename == NULL)) {
/* rename not supported */
DEBUG("vfs_rename: rename not supported by fs!\n");
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return -EROFS;
}
const char *rel_to;
vfs_mount_t *mountp_to;
res = _find_mount(&mountp_to, to_path, &rel_to);
/* _find_mount implicitly increments the open_files count on success */
if (res < 0) {
/* No mount point maps to the requested file name */
DEBUG("vfs_rename: to: no matching mount\n");
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return res;
}
if (mountp_to != mountp) {
/* The paths are on different file systems */
DEBUG("vfs_rename: from_path and to_path are on different mounts\n");
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
before = atomic_fetch_sub_u16(&mountp_to->open_files, 1);
assume(before > 0);
return -EXDEV;
}
res = mountp->fs->fs_op->rename(mountp, rel_from, rel_to);
DEBUG("vfs_rename: rename %p, \"%s\" -> \"%s\"", (void *)mountp, rel_from, rel_to);
if (res < 0) {
/* something went wrong during rename */
DEBUG(": ERR %d!\n", res);
}
else {
DEBUG("\n");
}
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
before = atomic_fetch_sub_u16(&mountp_to->open_files, 1);
assume(before > 0);
return res;
}
/* TODO: Share code between vfs_unlink, vfs_mkdir, vfs_rmdir since they are almost identical */
int vfs_unlink(const char *name)
{
DEBUG("vfs_unlink: \"%s\"\n", name);
if (name == NULL) {
return -EINVAL;
}
const char *rel_path;
vfs_mount_t *mountp;
int res;
res = _find_mount(&mountp, name, &rel_path);
/* _find_mount implicitly increments the open_files count on success */
if (res < 0) {
/* No mount point maps to the requested file name */
DEBUG("vfs_unlink: no matching mount\n");
return res;
}
if ((mountp->fs->fs_op == NULL) || (mountp->fs->fs_op->unlink == NULL)) {
/* unlink not supported */
DEBUG("vfs_unlink: unlink not supported by fs!\n");
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return -EROFS;
}
res = mountp->fs->fs_op->unlink(mountp, rel_path);
DEBUG("vfs_unlink: unlink %p, \"%s\"", (void *)mountp, rel_path);
if (res < 0) {
/* something went wrong during unlink */
DEBUG(": ERR %d!\n", res);
}
else {
DEBUG("\n");
}
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return res;
}
int vfs_mkdir(const char *name, mode_t mode)
{
DEBUG("vfs_mkdir: \"%s\", 0%03lo\n", name, (long unsigned int)mode);
if (name == NULL) {
return -EINVAL;
}
const char *rel_path;
vfs_mount_t *mountp;
int res;
res = _find_mount(&mountp, name, &rel_path);
/* _find_mount implicitly increments the open_files count on success */
if (res < 0) {
/* No mount point maps to the requested file name */
DEBUG("vfs_mkdir: no matching mount\n");
return res;
}
if ((mountp->fs->fs_op == NULL) || (mountp->fs->fs_op->mkdir == NULL)) {
/* mkdir not supported */
DEBUG("vfs_mkdir: mkdir not supported by fs!\n");
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return -EROFS;
}
res = mountp->fs->fs_op->mkdir(mountp, rel_path, mode);
DEBUG("vfs_mkdir: mkdir %p, \"%s\"", (void *)mountp, rel_path);
if (res < 0) {
/* something went wrong during mkdir */
DEBUG(": ERR %d!\n", res);
}
else {
DEBUG("\n");
}
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return res;
}
int vfs_rmdir(const char *name)
{
DEBUG("vfs_rmdir: \"%s\"\n", name);
if (name == NULL) {
return -EINVAL;
}
const char *rel_path;
vfs_mount_t *mountp;
int res;
res = _find_mount(&mountp, name, &rel_path);
/* _find_mount implicitly increments the open_files count on success */
if (res < 0) {
/* No mount point maps to the requested file name */
DEBUG("vfs_rmdir: no matching mount\n");
return res;
}
if ((mountp->fs->fs_op == NULL) || (mountp->fs->fs_op->rmdir == NULL)) {
/* rmdir not supported */
DEBUG("vfs_rmdir: rmdir not supported by fs!\n");
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return -EROFS;
}
res = mountp->fs->fs_op->rmdir(mountp, rel_path);
DEBUG("vfs_rmdir: rmdir %p, \"%s\"", (void *)mountp, rel_path);
if (res < 0) {
/* something went wrong during rmdir */
DEBUG(": ERR %d!\n", res);
}
else {
DEBUG("\n");
}
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return res;
}
int vfs_stat(const char *restrict path, struct stat *restrict buf)
{
DEBUG("vfs_stat: \"%s\", %p\n", path, (void *)buf);
if (path == NULL || buf == NULL) {
return -EINVAL;
}
const char *rel_path;
vfs_mount_t *mountp;
int res;
res = _find_mount(&mountp, path, &rel_path);
/* _find_mount implicitly increments the open_files count on success */
if (res < 0) {
/* No mount point maps to the requested file name */
DEBUG("vfs_stat: no matching mount\n");
return res;
}
if ((mountp->fs->fs_op == NULL) || (mountp->fs->fs_op->stat == NULL)) {
/* stat not supported */
DEBUG("vfs_stat: stat not supported by fs!\n");
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return -EPERM;
}
memset(buf, 0, sizeof(*buf));
res = mountp->fs->fs_op->stat(mountp, rel_path, buf);
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return res;
}
int vfs_statvfs(const char *restrict path, struct statvfs *restrict buf)
{
DEBUG("vfs_statvfs: \"%s\", %p\n", path, (void *)buf);
if (path == NULL || buf == NULL) {
return -EINVAL;
}
const char *rel_path;
vfs_mount_t *mountp;
int res;
res = _find_mount(&mountp, path, &rel_path);
/* _find_mount implicitly increments the open_files count on success */
if (res < 0) {
/* No mount point maps to the requested file name */
DEBUG("vfs_statvfs: no matching mount\n");
return res;
}
if ((mountp->fs->fs_op == NULL) || (mountp->fs->fs_op->statvfs == NULL)) {
/* statvfs not supported */
DEBUG("vfs_statvfs: statvfs not supported by fs!\n");
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return -EPERM;
}
memset(buf, 0, sizeof(*buf));
res = mountp->fs->fs_op->statvfs(mountp, rel_path, buf);
/* remember to decrement the open_files count */
uint16_t before = atomic_fetch_sub_u16(&mountp->open_files, 1);
assume(before > 0);
return res;
}
int vfs_bind(int fd, int flags, const vfs_file_ops_t *f_op, void *private_data)
{
DEBUG("vfs_bind: %d, %d, %p, %p\n", fd, flags, (void*)f_op, private_data);
if (f_op == NULL) {
return -EINVAL;
}
mutex_lock(&_open_mutex);
fd = _init_fd(fd, f_op, NULL, flags, private_data);
mutex_unlock(&_open_mutex);
if (fd < 0) {
DEBUG("vfs_bind: _init_fd: ERR %d!\n", fd);
return fd;
}
DEBUG("vfs_bind: bound %d\n", fd);
return fd;
}
int vfs_normalize_path(char *buf, const char *path, size_t buflen)
{
DEBUG("vfs_normalize_path: %p, \"%s\" (%p), %" PRIuSIZE "\n",
(void *)buf, path, (void *)path, buflen);
size_t len = 0;
int npathcomp = 0;
const char *path_end = path + strlen(path); /* Find the terminating null byte */
if (len >= buflen) {
return -ENAMETOOLONG;
}
while(path <= path_end) {
DEBUG("vfs_normalize_path: + %d \"%.*s\" <- \"%s\" (%p)\n",
npathcomp, (int)len, buf, path, (void *)path);
if (path[0] == '\0') {
break;
}
while (path[0] == '/') {
/* skip extra slashes */
++path;
}
if (path[0] == '.') {
++path;
if (path[0] == '/' || path[0] == '\0') {
/* skip /./ components */
DEBUG("vfs_normalize_path: skip .\n");
continue;
}
if (path[0] == '.' && (path[1] == '/' || path[1] == '\0')) {
DEBUG("vfs_normalize_path: reduce ../\n");
if (len == 0) {
/* outside root */
return -EINVAL;
}
++path;
/* delete the last component of the path */
while (len > 0 && buf[--len] != '/') {}
--npathcomp;
continue;
}
}
buf[len++] = '/';
if (len >= buflen) {
return -ENAMETOOLONG;
}
if (path[0] == '\0') {
/* trailing slash in original path, don't increment npathcomp */
break;
}
++npathcomp;
/* copy the path component */
while (len < buflen && path[0] != '/' && path[0] != '\0') {
buf[len++] = path[0];
++path;
}
if (len >= buflen) {
return -ENAMETOOLONG;
}
}
/* special case for "/": (otherwise it will be zero) */
if (len == 1) {
npathcomp = 1;
}
buf[len] = '\0';
DEBUG("vfs_normalize_path: = %d, \"%s\"\n", npathcomp, buf);
return npathcomp;
}
const vfs_mount_t *vfs_iterate_mounts(const vfs_mount_t *cur)
{
clist_node_t *node;
if (cur == NULL) {
node = _vfs_mounts_list.next;
if (node == NULL) {
/* empty list */
return NULL;
}
node = node->next;
}
else {
node = cur->list_entry.next;
if (node == _vfs_mounts_list.next->next) {
return NULL;
}
}
return container_of(node, vfs_mount_t, list_entry);
}
/* General implementation note: This heavily relies on the produced opened dir
* to lock keep the underlying mount point from closing. */
bool vfs_iterate_mount_dirs(vfs_DIR *dir)
{
/* This is NULL after the prescribed initialization, or a valid (and
* locked) mount point otherwise */
vfs_mount_t *last_mp = dir->mp;
/* This is technically violating vfs_iterate_mounts' API, as that says no
* mounts or unmounts on the chain while iterating. However, as we know
* that the current dir's mount point is still on, the equivalent procedure
* of starting a new round of `vfs_iterate_mounts` from NULL and calling it
* until it produces `last_mp` (all while holding _mount_mutex) would leave
* us with the very same situation as if we started iteration with last_mp.
*
* On the cast discarding const: vfs_iterate_mounts's type is more for
* public use */
vfs_mount_t *next = (vfs_mount_t *)vfs_iterate_mounts(last_mp);
if (next == NULL) {
/* Ignoring errors, can't help with them */
vfs_closedir(dir);
return false;
}
/* Even if we held the mutex up to here (see above comment on the fiction
* of acquiring it, iterating to where we are, and releasing it again),
* we'd need to let go of it now to actually open the directory. This
* temporary count ensures that the file system will stick around for the
* directory open step that follows immediately */
uint16_t before = atomic_fetch_add_u16(&next->open_files, 1);
/* We cannot use assume() here, an overflow could occur in absence of
* any bugs and should also be checked for in production code. We use
* expect() here, which was actually written for unit tests but works
* here as well */
expect(before < UINT16_MAX);
/* Ignoring errors, can't help with them */
vfs_closedir(dir);
int err = vfs_opendir(dir, next->mount_point);
/* No matter the success, the open_files lock has done its duty */
before = atomic_fetch_sub_u16(&next->open_files, 1);
assume(before > 0);
if (err != 0) {
DEBUG("vfs_iterate_mount opendir erred: vfs_opendir(\"%s\") = %d\n", next->mount_point, err);
return false;
}
return true;
}
const vfs_file_t *vfs_file_get(int fd)
{
if (_fd_is_valid(fd) == 0) {
return &_vfs_open_files[fd];
}
else {
return NULL;
}
}
static inline int _allocate_fd(int fd)
{
if (fd < 0) {
for (fd = 0; fd < VFS_MAX_OPEN_FILES; ++fd) {
if ((fd == STDIN_FILENO) || (fd == STDOUT_FILENO) || (fd == STDERR_FILENO)) {
/* Do not auto-allocate the stdio file descriptor numbers to
* avoid conflicts between normal file system users and stdio
* drivers such as stdio_uart, stdio_rtt which need to be able
* to bind to these specific file descriptor numbers. */
continue;
}
if (_vfs_open_files[fd].pid == KERNEL_PID_UNDEF) {
break;
}
}
}
if (fd >= VFS_MAX_OPEN_FILES) {
/* The _vfs_open_files array is full */
return -ENFILE;
}
else if (_vfs_open_files[fd].pid != KERNEL_PID_UNDEF) {
/* The desired fd is already in use */
return -EEXIST;
}
kernel_pid_t pid = thread_getpid();
if (pid == KERNEL_PID_UNDEF) {
/* This happens when calling vfs_bind during boot, before threads have
* been started. */
pid = -1;
}
_vfs_open_files[fd].pid = pid;
return fd;
}
static inline void _free_fd(int fd)
{
DEBUG("_free_fd: %d, pid=%d\n", fd, _vfs_open_files[fd].pid);
if (_vfs_open_files[fd].mp != NULL) {
uint16_t before = atomic_fetch_sub_u16(&_vfs_open_files[fd].mp->open_files, 1);
assume(before > 0);
}
_vfs_open_files[fd].pid = KERNEL_PID_UNDEF;
}
static inline int _init_fd(int fd, const vfs_file_ops_t *f_op, vfs_mount_t *mountp, int flags, void *private_data)
{
fd = _allocate_fd(fd);
if (fd < 0) {
return fd;
}
vfs_file_t *filp = &_vfs_open_files[fd];
filp->mp = mountp;
filp->f_op = f_op;
filp->flags = flags;
filp->pos = 0;
filp->private_data.ptr = private_data;
return fd;
}
static inline int _find_mount(vfs_mount_t **mountpp, const char *name, const char **rel_path)
{
size_t longest_match = 0;
size_t name_len = strlen(name);
mutex_lock(&_mount_mutex);
clist_node_t *node = _vfs_mounts_list.next;
if (node == NULL) {
/* list empty */
mutex_unlock(&_mount_mutex);
return -ENOENT;
}
vfs_mount_t *mountp = NULL;
do {
node = node->next;
vfs_mount_t *it = container_of(node, vfs_mount_t, list_entry);
size_t len = it->mount_point_len;
if (len < longest_match) {
/* Already found a longer prefix */
continue;
}
if (len > name_len) {
/* path name is shorter than the mount point name */
continue;
}
if ((len > 1) && (name[len] != '/') && (name[len] != '\0')) {
/* name does not have a directory separator where mount point name ends */
continue;
}
if (strncmp(name, it->mount_point, len) == 0) {
/* mount_point is a prefix of name */
/* special check for mount_point == "/" */
if (len > 1) {
longest_match = len;
}
mountp = it;
}
} while (node != _vfs_mounts_list.next);
if (mountp == NULL) {
/* not found */
mutex_unlock(&_mount_mutex);
return -ENOENT;
}
/* Increment open files counter for this mount */
uint16_t before = atomic_fetch_add_u16(&mountp->open_files, 1);
/* We cannot use assume() here, an overflow could occur in absence of
* any bugs and should also be checked for in production code. We use
* expect() here, which was actually written for unit tests but works
* here as well */
expect(before < UINT16_MAX);
mutex_unlock(&_mount_mutex);
*mountpp = mountp;
if (rel_path != NULL) {
if (mountp->fs->flags & VFS_FS_FLAG_WANT_ABS_PATH) {
*rel_path = name;
} else {
*rel_path = name + longest_match;
}
}
return 0;
}
static inline int _fd_is_valid(int fd)
{
if ((unsigned int)fd >= VFS_MAX_OPEN_FILES) {
return -EBADF;
}
vfs_file_t *filp = &_vfs_open_files[fd];
if (filp->pid == KERNEL_PID_UNDEF) {
return -EBADF;
}
if (filp->f_op == NULL) {
return -EBADF;
}
return 0;
}
static bool _is_dir(vfs_mount_t *mountp, vfs_DIR *dir, const char *restrict path)
{
const vfs_dir_ops_t *ops = mountp->fs->d_op;
if (!ops->opendir) {
return false;
}
dir->d_op = ops;
dir->mp = mountp;
int res = ops->opendir(dir, path);
if (res < 0) {
return false;
}
ops->closedir(dir);
return true;
}
int vfs_sysop_stat_from_fstat(vfs_mount_t *mountp, const char *restrict path, struct stat *restrict buf)
{
const vfs_file_ops_t * f_op = mountp->fs->f_op;
union {
vfs_file_t file;
vfs_DIR dir;
} filedir = {
.file = {
.mp = mountp,
/* As per definition of the `vfsfile_ops::open` field */
.f_op = f_op,
.private_data = { .ptr = NULL },
.pos = 0,
},
};
int err = f_op->open(&filedir.file, path, 0, 0);
if (err < 0) {
if (_is_dir(mountp, &filedir.dir, path)) {
buf->st_mode = S_IFDIR;
return 0;
}
return err;
}
err = f_op->fstat(&filedir.file, buf);
f_op->close(&filedir.file);
return err;
}
static int _auto_mount(vfs_mount_t *mountp, unsigned i)
{
(void) i;
DEBUG("vfs%u: mounting as '%s'\n", i, mountp->mount_point);
int res = vfs_mount(mountp);
if (res == 0) {
return 0;
}
if (IS_ACTIVE(MODULE_VFS_AUTO_FORMAT)) {
DEBUG("vfs%u: formatting…\n", i);
res = vfs_format(mountp);
if (res) {
DEBUG("vfs%u: format: error %d\n", i, res);
return res;
}
res = vfs_mount(mountp);
}
if (res) {
DEBUG("vfs%u mount: error %d\n", i, res);
}
return res;
}
void auto_init_vfs(void)
{
for (unsigned i = 0; i < MOUNTPOINTS_NUMOF; ++i) {
_auto_mount(&vfs_mountpoints_xfa[i], i);
}
}
void auto_unmount_vfs(void)
{
for (unsigned i = 0; i < MOUNTPOINTS_NUMOF; ++i) {
vfs_umount(&vfs_mountpoints_xfa[i], true);
}
}
int vfs_mount_by_path(const char *path)
{
for (unsigned i = 0; i < MOUNTPOINTS_NUMOF; ++i) {
if (strcmp(path, vfs_mountpoints_xfa[i].mount_point) == 0) {
return _auto_mount(&vfs_mountpoints_xfa[i], i);
}
}
return -ENOENT;
}
int vfs_unmount_by_path(const char *path, bool force)
{
for (unsigned i = 0; i < MOUNTPOINTS_NUMOF; ++i) {
if (strcmp(path, vfs_mountpoints_xfa[i].mount_point) == 0) {
return vfs_umount(&vfs_mountpoints_xfa[i], force);
}
}
return -ENOENT;
}
int vfs_format_by_path(const char *path)
{
for (unsigned i = 0; i < MOUNTPOINTS_NUMOF; ++i) {
if (strcmp(path, vfs_mountpoints_xfa[i].mount_point) == 0) {
return vfs_format(&vfs_mountpoints_xfa[i]);
}
}
return -ENOENT;
}
/** @} */