From dcc37329df40e9b591c7a16c862de500c979349a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Nohlg=C3=A5rd?= Date: Fri, 8 Jul 2016 15:56:09 +0200 Subject: [PATCH] sys/vfs: A virtual file system (VFS) layer for RIOT The VFS layer provides file system abstractions to allow using a unified interface to access files from mounted file systems. --- Makefile.dep | 4 + dist/tools/mkconstfs/README.md | 16 + dist/tools/mkconstfs/mkconstfs.py | 91 ++ sys/Makefile | 4 + sys/Makefile.include | 4 + sys/fs/constfs/Makefile | 2 + sys/fs/constfs/constfs.c | 319 ++++++ sys/fs/doc.txt | 12 + sys/include/fs/constfs.h | 66 ++ sys/include/vfs.h | 834 ++++++++++++++++ sys/shell/commands/Makefile | 3 + sys/shell/commands/sc_vfs.c | 424 ++++++++ sys/shell/commands/shell_commands.c | 9 + sys/vfs/Makefile | 1 + sys/vfs/vfs.c | 932 ++++++++++++++++++ tests/unittests/tests-vfs/Makefile | 1 + tests/unittests/tests-vfs/Makefile.include | 2 + tests/unittests/tests-vfs/tests-vfs-bind.c | 122 +++ tests/unittests/tests-vfs/tests-vfs-dir-ops.c | 124 +++ .../unittests/tests-vfs/tests-vfs-file-ops.c | 169 ++++ .../tests-vfs/tests-vfs-file-system-ops.c | 169 ++++ .../tests-vfs/tests-vfs-mount-constfs.c | 223 +++++ .../tests-vfs/tests-vfs-normalize_path.c | 153 +++ .../tests-vfs/tests-vfs-open-close.c | 52 + tests/unittests/tests-vfs/tests-vfs.c | 40 + tests/unittests/tests-vfs/tests-vfs.h | 37 + 26 files changed, 3813 insertions(+) create mode 100644 dist/tools/mkconstfs/README.md create mode 100755 dist/tools/mkconstfs/mkconstfs.py create mode 100644 sys/fs/constfs/Makefile create mode 100644 sys/fs/constfs/constfs.c create mode 100644 sys/fs/doc.txt create mode 100644 sys/include/fs/constfs.h create mode 100644 sys/include/vfs.h create mode 100644 sys/shell/commands/sc_vfs.c create mode 100644 sys/vfs/Makefile create mode 100644 sys/vfs/vfs.c create mode 100644 tests/unittests/tests-vfs/Makefile create mode 100644 tests/unittests/tests-vfs/Makefile.include create mode 100644 tests/unittests/tests-vfs/tests-vfs-bind.c create mode 100644 tests/unittests/tests-vfs/tests-vfs-dir-ops.c create mode 100644 tests/unittests/tests-vfs/tests-vfs-file-ops.c create mode 100644 tests/unittests/tests-vfs/tests-vfs-file-system-ops.c create mode 100644 tests/unittests/tests-vfs/tests-vfs-mount-constfs.c create mode 100644 tests/unittests/tests-vfs/tests-vfs-normalize_path.c create mode 100644 tests/unittests/tests-vfs/tests-vfs-open-close.c create mode 100644 tests/unittests/tests-vfs/tests-vfs.c create mode 100644 tests/unittests/tests-vfs/tests-vfs.h diff --git a/Makefile.dep b/Makefile.dep index cc85b1fcef..b909cf125f 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -597,6 +597,10 @@ ifneq (,$(filter emcute,$(USEMODULE))) USEMODULE += xtimer endif +ifneq (,$(filter constfs,$(USEMODULE))) + USEMODULE += vfs +endif + # include package dependencies -include $(USEPKG:%=$(RIOTPKG)/%/Makefile.dep) diff --git a/dist/tools/mkconstfs/README.md b/dist/tools/mkconstfs/README.md new file mode 100644 index 0000000000..c71a69ba04 --- /dev/null +++ b/dist/tools/mkconstfs/README.md @@ -0,0 +1,16 @@ +# Introduction + +This tool creates a .c file including all data from a local directory as data +structures that can be mounted using constfs. + +# Usage + + mkconstfs.py /path/to/files / + + #include "vfs.h" + #include "fs/constfs.h" + extern const vfs_mount_t _constfs; + + [...] + + vfs_mount((vfs_mount_t *)&_constfs); diff --git a/dist/tools/mkconstfs/mkconstfs.py b/dist/tools/mkconstfs/mkconstfs.py new file mode 100755 index 0000000000..bda89c76d8 --- /dev/null +++ b/dist/tools/mkconstfs/mkconstfs.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 + +import codecs +import os +import sys + +FILE_TYPE = "static const uint8_t" + +def mkconstfs(root_path, mount_point, constfs_name): + print("/* This file was automatically generated by mkconstfs */") + print("#include \"fs/constfs.h\"") + print("") + + for dirname, subdir_list, file_list in os.walk(root_path): + target_dirname = os.path.join("/", dirname[len(root_path):]) + for fname in file_list: + local_fname = os.path.join(dirname, fname) + target_fname = os.path.join(target_dirname, fname) + print_file_data(local_fname, target_fname) + + print("\nstatic const constfs_file_t _files[] = {") + + for mangled_name, target_name, _ in files: + print(" {") + print(" .path = \"%s\"," % target_name) + print(" .data = %s," % mangled_name) + print(" .size = sizeof(%s)" % mangled_name) + print(" },") + print("};") + + print(""" +static const constfs_t _fs_data = { + .files = _files, + .nfiles = sizeof(_files) / sizeof(_files[0]), +}; + +vfs_mount_t %s = { + .fs = &constfs_file_system, + .mount_point = \"%s\", + .private_data = (void *)&_fs_data, +}; + """ % (constfs_name, mount_point)) + +def mangle_name(fname): + fname = fname.replace("/", "__") + fname = fname.replace(".", "__") + + return fname + +def print_file_data(local_fname, target_fname): + mangled_name = mangle_name(target_fname) + print(FILE_TYPE, mangled_name, "[] = {", end="") + + line_length = 8 + nread = 0 + with open(local_fname, 'rb') as f: + byte = f.read(1) + while byte: + if nread == 0: + print("\n ", end="") + elif nread % line_length == 0: + print(",\n ", end="") + else: + print(", ", end="") + nread += 1 + print ("0x" + codecs.encode(byte, 'hex').decode('ascii'), end="") + # Do stuff with byte. + byte = f.read(1) + + print("\n};") + + files.append((mangled_name, target_fname, nread)) + +files = [] + +if __name__=="__main__": + mountpoint = "/" + constfs_name = "_constfs" + + if len(sys.argv) < 2: + print("usage: mkconstfs.py [mountpoint] [constfs_name]") + exit(1) + + path = sys.argv[1] + if len(sys.argv) > 2: + mountpoint = sys.argv[2] + + if len(sys.argv) > 3: + constfs_name = sys.argv[3] + + mkconstfs(path, mountpoint, constfs_name) diff --git a/sys/Makefile b/sys/Makefile index 80ebe4ee28..8e4f043577 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -112,6 +112,10 @@ ifneq (,$(filter emcute,$(USEMODULE))) DIRS += net/application_layer/emcute endif +ifneq (,$(filter constfs,$(USEMODULE))) + DIRS += fs/constfs +endif + DIRS += $(dir $(wildcard $(addsuffix /Makefile, ${USEMODULE}))) include $(RIOTBASE)/Makefile.base diff --git a/sys/Makefile.include b/sys/Makefile.include index 7a460af0f8..680f655b17 100644 --- a/sys/Makefile.include +++ b/sys/Makefile.include @@ -32,6 +32,10 @@ ifneq (,$(filter oneway_malloc,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/sys/oneway-malloc/include endif +ifneq (,$(filter vfs,$(USEMODULE))) + USEMODULE_INCLUDES += $(RIOTBASE)/sys/posix/include +endif + ifneq (,$(filter cpp11-compat,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/sys/cpp11-compat/include # make sure cppsupport.o is linked explicitly because __dso_handle is not diff --git a/sys/fs/constfs/Makefile b/sys/fs/constfs/Makefile new file mode 100644 index 0000000000..9798aac2d4 --- /dev/null +++ b/sys/fs/constfs/Makefile @@ -0,0 +1,2 @@ +MODULE=constfs +include $(RIOTBASE)/Makefile.base diff --git a/sys/fs/constfs/constfs.c b/sys/fs/constfs/constfs.c new file mode 100644 index 0000000000..9b29a08a19 --- /dev/null +++ b/sys/fs/constfs/constfs.c @@ -0,0 +1,319 @@ +/* + * 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 fs_constfs + * @{ + * + * @file + * @brief ConstFS implementation + * + * @author Joakim Nohlgård + * + * @} + */ + +/* Required for strnlen in string.h, when building with -std=c99 */ +#define _DEFAULT_SOURCE 1 +#include +#include +#include +#include +#include + +#include "fs/constfs.h" +#include "vfs.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* File system operations */ +static int constfs_mount(vfs_mount_t *mountp); +static int constfs_umount(vfs_mount_t *mountp); +static int constfs_unlink(vfs_mount_t *mountp, const char *name); +static int constfs_stat(vfs_mount_t *mountp, const char *restrict name, struct stat *restrict buf); +static int constfs_statvfs(vfs_mount_t *mountp, const char *restrict path, struct statvfs *restrict buf); + +/* File operations */ +static int constfs_close(vfs_file_t *filp); +static int constfs_fstat(vfs_file_t *filp, struct stat *buf); +static off_t constfs_lseek(vfs_file_t *filp, off_t off, int whence); +static int constfs_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path); +static ssize_t constfs_read(vfs_file_t *filp, void *dest, size_t nbytes); +static ssize_t constfs_write(vfs_file_t *filp, const void *src, size_t nbytes); + +/* Directory operations */ +static int constfs_opendir(vfs_DIR *dirp, const char *dirname, const char *abs_path); +static int constfs_readdir(vfs_DIR *dirp, vfs_dirent_t *entry); +static int constfs_closedir(vfs_DIR *dirp); + +static const vfs_file_system_ops_t constfs_fs_ops = { + .mount = constfs_mount, + .umount = constfs_umount, + .unlink = constfs_unlink, + .statvfs = constfs_statvfs, + .stat = constfs_stat, +}; + +static const vfs_file_ops_t constfs_file_ops = { + .close = constfs_close, + .fstat = constfs_fstat, + .lseek = constfs_lseek, + .open = constfs_open, + .read = constfs_read, + .write = constfs_write, +}; + +static const vfs_dir_ops_t constfs_dir_ops = { + .opendir = constfs_opendir, + .readdir = constfs_readdir, + .closedir = constfs_closedir, +}; + + +const vfs_file_system_t constfs_file_system = { + .f_op = &constfs_file_ops, + .fs_op = &constfs_fs_ops, + .d_op = &constfs_dir_ops, +}; + +/** + * @internal + * @brief Fill a file information struct with information about the file + * pointed to by @p fp + * + * @param[in] fp file to query + * @param[out] buf output buffer + */ +static void _constfs_write_stat(const constfs_file_t *fp, struct stat *restrict buf); + +static int constfs_mount(vfs_mount_t *mountp) +{ + /* perform any extra initialization here */ + (void) mountp; /* prevent warning: unused parameter */ + return 0; +} + +static int constfs_umount(vfs_mount_t *mountp) +{ + /* free resources and perform any clean up here */ + (void) mountp; /* prevent warning: unused parameter */ + return 0; +} + +static int constfs_unlink(vfs_mount_t *mountp, const char *name) +{ + /* Removing files is prohibited */ + (void) mountp; /* prevent warning: unused parameter */ + (void) name; /* prevent warning: unused parameter */ + return -EROFS; +} + +static int constfs_stat(vfs_mount_t *mountp, const char *restrict name, struct stat *restrict buf) +{ + (void) name; + /* Fill out some information about this file */ + if (buf == NULL) { + return -EFAULT; + } + constfs_t *fs = mountp->private_data; + /* linear search through the files array */ + for (size_t i = 0; i < fs->nfiles; ++i) { + DEBUG("constfs_stat ? \"%s\"\n", fs->files[i].path); + if (strcmp(fs->files[i].path, name) == 0) { + DEBUG("constfs_stat: Found :)\n"); + _constfs_write_stat(&fs->files[i], buf); + buf->st_ino = i; + return 0; + } + } + DEBUG("constfs_stat: Not found :(\n"); + return -ENOENT; +} + +static int constfs_statvfs(vfs_mount_t *mountp, const char *restrict path, struct statvfs *restrict buf) +{ + (void) path; + /* Fill out some information about this file system */ + if (buf == NULL) { + return -EFAULT; + } + constfs_t *fs = mountp->private_data; + /* clear out the stat buffer first */ + memset(buf, 0, sizeof(*buf)); + buf->f_bsize = sizeof(uint8_t); /* block size */ + buf->f_frsize = sizeof(uint8_t); /* fundamental block size */ + fsblkcnt_t f_blocks = 0; + for (size_t i = 0; i < fs->nfiles; ++i) { + f_blocks += fs->files[i].size; + } + buf->f_blocks = f_blocks; /* Blocks total */ + buf->f_bfree = 0; /* Blocks free */ + buf->f_bavail = 0; /* Blocks available to non-privileged processes */ + buf->f_files = fs->nfiles; /* Total number of file serial numbers */ + buf->f_ffree = 0; /* Total number of free file serial numbers */ + buf->f_favail = 0; /* Number of file serial numbers available to non-privileged process */ + buf->f_fsid = 0; /* File system id */ + buf->f_flag = (ST_RDONLY | ST_NOSUID); /* File system flags */ + buf->f_namemax = UINT8_MAX; /* Maximum file name length */ + return 0; +} + +static int constfs_close(vfs_file_t *filp) +{ + /* perform any necessary clean ups */ + (void) filp; /* prevent warning: unused parameter */ + return 0; +} + +static int constfs_fstat(vfs_file_t *filp, struct stat *buf) +{ + constfs_file_t *fp = filp->private_data.ptr; + if (buf == NULL) { + return -EFAULT; + } + _constfs_write_stat(fp, buf); + return 0; +} + +static off_t constfs_lseek(vfs_file_t *filp, off_t off, int whence) +{ + constfs_file_t *fp = filp->private_data.ptr; + switch (whence) { + case SEEK_SET: + break; + case SEEK_CUR: + off += filp->pos; + break; + case SEEK_END: + off += fp->size; + break; + default: + return -EINVAL; + } + if (off < 0) { + /* the resulting file offset would be negative */ + return -EINVAL; + } + /* POSIX allows seeking past the end of the file, even with O_RDONLY */ + filp->pos = off; + return off; +} + +static int constfs_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path) +{ + (void) mode; + (void) abs_path; + constfs_t *fs = filp->mp->private_data; + DEBUG("constfs_open: %p, \"%s\", 0x%x, 0%03lo, \"%s\"\n", (void *)filp, name, flags, (unsigned long)mode, abs_path); + /* We only support read access */ + if ((flags & O_ACCMODE) != O_RDONLY) { + return -EROFS; + } + /* linear search through the files array */ + for (size_t i = 0; i < fs->nfiles; ++i) { + DEBUG("constfs_open ? \"%s\"\n", fs->files[i].path); + if (strcmp(fs->files[i].path, name) == 0) { + DEBUG("constfs_open: Found :)\n"); + filp->private_data.ptr = (void *)&fs->files[i]; + return 0; + } + } + DEBUG("constfs_open: Not found :(\n"); + return -ENOENT; +} + +static ssize_t constfs_read(vfs_file_t *filp, void *dest, size_t nbytes) +{ + constfs_file_t *fp = filp->private_data.ptr; + DEBUG("constfs_read: %p, %p, %lu\n", (void *)filp, dest, (unsigned long)nbytes); + if ((size_t)filp->pos >= fp->size) { + /* Current offset is at or beyond end of file */ + return 0; + } + + if (nbytes > (fp->size - filp->pos)) { + nbytes = fp->size - filp->pos; + } + memcpy(dest, fp->data + filp->pos, nbytes); + DEBUG("constfs_read: read %d bytes\n", nbytes); + filp->pos += nbytes; + return nbytes; +} + +static ssize_t constfs_write(vfs_file_t *filp, const void *src, size_t nbytes) +{ + DEBUG("constfs_write: %p, %p, %lu\n", (void *)filp, src, (unsigned long)nbytes); + /* Read only file system */ + DEBUG("constfs_write: read only FS\n"); + /* prevent warning: unused parameter */ + (void) filp; + (void) src; + (void) nbytes; + return -EBADF; +} + +static int constfs_opendir(vfs_DIR *dirp, const char *dirname, const char *abs_path) +{ + (void) abs_path; + DEBUG("constfs_opendir: %p, \"%s\", \"%s\"\n", (void *)dirp, dirname, abs_path); + if (strncmp(dirname, "/", 2) != 0) { + /* We keep it simple and only support a flat file system, only a root directory */ + return -ENOENT; + } + dirp->private_data.value = 0; + return 0; +} + +static int constfs_readdir(vfs_DIR *dirp, vfs_dirent_t *entry) +{ + DEBUG("constfs_readdir: %p, %p\n", (void *)dirp, (void *)entry); + constfs_t *fs = dirp->mp->private_data; + int filenum = dirp->private_data.value; + if ((size_t)filenum >= fs->nfiles) { + /* End of stream */ + return 0; + } + const constfs_file_t *fp = &fs->files[filenum]; + if (fp->path == NULL) { + return -EIO; + } + size_t len = strnlen(fp->path, VFS_NAME_MAX + 1); + if (len > VFS_NAME_MAX) { + /* name does not fit in vfs_dirent_t buffer */ + /* skipping past the broken entry */ + ++filenum; + dirp->private_data.value = filenum; + return -EAGAIN; + } + /* copy the string, including terminating null */ + memcpy(&entry->d_name[0], fp->path, len + 1); + entry->d_ino = filenum; + ++filenum; + dirp->private_data.value = filenum; + return 1; +} + +static int constfs_closedir(vfs_DIR *dirp) +{ + /* Just an example, it's not necessary to define closedir if there is + * nothing to clean up */ + (void) dirp; + return 0; +} + +static void _constfs_write_stat(const constfs_file_t *fp, struct stat *restrict buf) +{ + /* clear out the stat buffer first */ + memset(buf, 0, sizeof(*buf)); + buf->st_nlink = 1; + buf->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; + buf->st_size = fp->size; + buf->st_blocks = fp->size; + buf->st_blksize = sizeof(uint8_t); +} diff --git a/sys/fs/doc.txt b/sys/fs/doc.txt new file mode 100644 index 0000000000..f20b48aac5 --- /dev/null +++ b/sys/fs/doc.txt @@ -0,0 +1,12 @@ +/* + * 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. + */ + +/** + * @defgroup fs File systems + * @brief File system libraries + */ diff --git a/sys/include/fs/constfs.h b/sys/include/fs/constfs.h new file mode 100644 index 0000000000..a0286e6dea --- /dev/null +++ b/sys/include/fs/constfs.h @@ -0,0 +1,66 @@ +/* + * 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. + */ + +/** + * @defgroup fs_constfs ConstFS static file system + * @ingroup fs + * @brief Constant file system resident in arrays + * + * This is an example of how to implement a simple file system driver for the + * RIOT VFS layer. The implementation uses an array of @c constfs_file_t objects + * as its storage back-end. + * + * @{ + * @file + * @brief ConstFS public API + * @author Joakim Nohlgård + */ + +#ifndef CONSTFS_H_ +#define CONSTFS_H_ + +#include +#include + +#include "vfs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief A file in ConstFS (file name + contents) + */ +typedef struct { + const char *path; /**< file system relative path to file */ + const size_t size; /**< length of @c data */ + const uint8_t *data; /**< pointer to file contents */ +} constfs_file_t; + +/** + * @brief ConstFS file system superblock + */ +typedef struct { + const size_t nfiles; /**< Number of files */ + const constfs_file_t *files; /**< Files array */ +} constfs_t; + +/** + * @brief ConstFS file system driver + * + * For use with vfs_mount + */ +extern const vfs_file_system_t constfs_file_system; + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ diff --git a/sys/include/vfs.h b/sys/include/vfs.h new file mode 100644 index 0000000000..60c36420ce --- /dev/null +++ b/sys/include/vfs.h @@ -0,0 +1,834 @@ +/* + * 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. + */ + +/** + * @defgroup sys_vfs Virtual File System (VFS) layer + * @ingroup sys + * @brief Provides an interface for accessing files and directories from + * different devices and file systems + * + * This layer is modeled as a mix between POSIX syscalls (e.g. open) and the + * Linux VFS layer implementation, with major reductions in the feature set, in + * order to fit the resource constrained platforms that RIOT targets. + * + * The overall design goals are: + * - Provide implementations for all newlib "file" syscalls + * - Keep it simple, do not add every possible file operation from Linux VFS. + * - Easy to map existing file system implementations for resource constrained systems onto the VFS layer API + * - Avoid keeping a central `enum` of all file system drivers that has to be kept up to date with external packages etc. + * - Use POSIX `` numbers as negative return codes for errors, avoid the global `errno` variable. + * - Only absolute paths to files (no per-process working directory) + * - No dynamic memory allocation + * + * + * The API should be easy to understand for users who are familiar with the + * POSIX file functions (open, close, read, write, fstat, lseek etc.) + * + * The VFS layer keeps track of mounted file systems and open files, the + * `vfs_open` function searches the array of mounted file systems and dispatches + * the call to the file system instance with the longest matching mount point prefix. + * Subsequent calls to `vfs_read`, `vfs_write`, etc will do a look up in the + * table of open files and dispatch the call to the correct file system driver + * for handling. + * + * `vfs_mount` takes a string containing the mount point, a file system driver + * specification (`struct file_system`), and an opaque pointer that only the FS + * driver knows how to use, which can be used to keep driver parameters in order + * to allow dynamic handling of multiple devices. + * + * @todo VFS layer reference counting and locking for open files and + * simultaneous access. + * + * @{ + * @file + * @brief VFS layer API declarations + * @author Joakim Nohlgård + */ + +#ifndef VFS_H_ +#define VFS_H_ + +#include +/* The stdatomic.h in GCC gives compilation errors with C++ + * see: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60932 + */ +#ifdef __cplusplus +#include +/* Make atomic_int available without namespace specifier */ +using std::atomic_int; +#else +#include /* for atomic_int */ +#endif +#include /* for struct stat */ +#include /* for off_t etc. */ +#include /* for struct statvfs */ + +#include "kernel_types.h" +#include "clist.h" + +#ifdef __cplusplus +extern "C" { +/* restrict is a C99 keyword, not valid in C++, but GCC and Clang have the + * __restrict__ extension keyword which can be used instead */ +#define restrict __restrict__ +/* If the above is not supported by the compiler, you can replace it with an + * empty definition instead: */ +/* #define restrict */ +#endif + +#ifndef VFS_MAX_OPEN_FILES +/** + * @brief Maximum number of simultaneous open files + */ +#define VFS_MAX_OPEN_FILES (16) +#endif + +#ifndef VFS_DIR_BUFFER_SIZE +/** + * @brief Size of buffer space in vfs_DIR + * + * This space is needed to avoid dynamic memory allocations for some file + * systems where a single pointer is not enough space for its directory stream + * state, e.g. SPIFFS. + * + * Guidelines: + * + * SPIFFS requires a sizeof(spiffs_DIR) (6-16 bytes, depending on target + * platform and configuration) buffer for its DIR struct. + * + * @attention File system developers: If your file system requires a buffer for + * DIR streams that is larger than a single pointer or @c int variable, ensure + * that you have a preprocessor check in your header file (so that it is + * impossible to attempt to mount the file system without running into a + * compiler error): + * + * @attention @code + * #if VFS_DIR_BUFFER_SIZE < 123 + * #error VFS_DIR_BUFFER_SIZE is too small, at least 123 bytes is required + * #endif + * @endcode + * + * @attention Put the check in the public header file (.h), do not put the check in the + * implementation (.c) file. + */ +#define VFS_DIR_BUFFER_SIZE (12) +#endif + +#ifndef VFS_NAME_MAX +/** + * @brief Maximum length of the name in a @c vfs_dirent_t (not including terminating null) + * + * Maximum number of bytes in a filename (not including terminating null). + * + * Similar to the POSIX macro NAME_MAX + */ +#define VFS_NAME_MAX (31) +#endif + +/** + * @brief Used with vfs_bind to bind to any available fd number + */ +#define VFS_ANY_FD (-1) + +/* Forward declarations */ +/** + * @brief struct @c vfs_file_ops typedef + */ +typedef struct vfs_file_ops vfs_file_ops_t; + +/** + * @brief struct @c vfs_dir_ops typedef + */ +typedef struct vfs_dir_ops vfs_dir_ops_t; + +/** + * @brief struct @c vfs_file_system_ops typedef + */ +typedef struct vfs_file_system_ops vfs_file_system_ops_t; + +/** + * @brief struct @c vfs_mount_struct typedef + */ +/* not struct vfs_mount because of name collision with the function */ +typedef struct vfs_mount_struct vfs_mount_t; + +/** + * @brief A file system driver + */ +typedef struct { + const vfs_file_ops_t *f_op; /**< File operations table */ + const vfs_dir_ops_t *d_op; /**< Directory operations table */ + const vfs_file_system_ops_t *fs_op; /**< File system operations table */ +} vfs_file_system_t; + +/** + * @brief A mounted file system + */ +struct vfs_mount_struct { + clist_node_t list_entry; /**< List entry for the _vfs_mount_list list */ + const vfs_file_system_t *fs; /**< The file system driver for the mount point */ + const char *mount_point; /**< Mount point, e.g. "/mnt/cdrom" */ + size_t mount_point_len; /**< Length of mount_point string (set by vfs_mount) */ + atomic_int open_files; /**< Number of currently open files */ + void *private_data; /**< File system driver private data, implementation defined */ +}; + +/** + * @brief Information about an open file + * + * Similar to, but not equal to, struct file in Linux + */ +typedef struct { + const vfs_file_ops_t *f_op; /**< File operations table */ + vfs_mount_t *mp; /**< Pointer to mount table entry */ + int flags; /**< File flags */ + off_t pos; /**< Current position in the file */ + kernel_pid_t pid; /**< PID of the process that opened the file */ + union { + void *ptr; /**< pointer to private data */ + int value; /**< alternatively, you can use private_data as an int */ + } private_data; /**< File system driver private data, implementation defined */ +} vfs_file_t; + +/** + * @brief Internal representation of a file system directory entry + * + * Used by opendir, readdir, closedir + * + * @attention This structure should be treated as an opaque blob and must not be + * modified by user code. The contents should only be used by file system drivers. + */ +typedef struct { + const vfs_dir_ops_t *d_op; /**< Directory operations table */ + vfs_mount_t *mp; /**< Pointer to mount table entry */ + union { + void *ptr; /**< pointer to private data */ + int value; /**< alternatively, you can use private_data as an int */ + uint8_t buffer[VFS_DIR_BUFFER_SIZE]; /**< Buffer space, in case a single pointer is not enough */ + } private_data; /**< File system driver private data, implementation defined */ +} vfs_DIR; + +/** + * @brief User facing directory entry + * + * Used to hold the output from readdir + * + * @note size, modification time, and other information is part of the file + * status, not the directory entry. + */ +typedef struct { + ino_t d_ino; /**< file serial number, unique for the file system ("inode" in Linux) */ + char d_name[VFS_NAME_MAX + 1]; /**< file name, relative to its containing directory */ +} vfs_dirent_t; + +/** + * @brief Operations on open files + * + * Similar, but not equal, to struct file_operations in Linux + */ +struct vfs_file_ops { + /** + * @brief Close an open file + * + * This function must perform any necessary clean ups and flush any internal + * buffers in the file system driver. + * + * If an error occurs, the file will still be considered closed by the VFS + * layer. Therefore, the proper clean up must still be performed by the file + * system driver before returning any error code. + * + * @note This implementation does not consider @c -EINTR a special return code, + * the file is still considered closed. + * + * @param[in] filp pointer to open file + * + * @return 0 on success + * @return <0 on error, the file is considered closed anyway + */ + int (*close) (vfs_file_t *filp); + + /** + * @brief Query/set options on an open file + * + * @param[in] filp pointer to open file + * @param[in] cmd fcntl command, see man 3p fcntl + * @param[in] arg argument to fcntl command, see man 3p fcntl + * + * @return 0 on success + * @return <0 on error + */ + int (*fcntl) (vfs_file_t *filp, int cmd, int arg); + + /** + * @brief Get status of an open file + * + * @param[in] filp pointer to open file + * @param[out] buf pointer to stat struct to fill + * + * @return 0 on success + * @return <0 on error + */ + int (*fstat) (vfs_file_t *filp, struct stat *buf); + + /** + * @brief Seek to position in file + * + * @p whence determines the function of the seek and should be set to one of + * the following values: + * + * - @c SEEK_SET: Seek to absolute offset @p off + * - @c SEEK_CUR: Seek to current location + @p off + * - @c SEEK_END: Seek to end of file + @p off + * + * @param[in] filp pointer to open file + * @param[in] off seek offset + * @param[in] whence determines the seek method, see detailed description + * + * @return the new seek location in the file on success + * @return <0 on error + */ + off_t (*lseek) (vfs_file_t *filp, off_t off, int whence); + + /** + * @brief Attempt to open a file in the file system at rel_path + * + * A file system driver should perform the necessary checks for file + * existence etc in this function. + * + * The VFS layer will initialize the contents of @p *filp so that + * @c filp->f_op points to the mounted file system's @c vfs_file_ops_t. + * @c filp->private_data.ptr will be initialized to NULL, @c filp->pos will + * be set to 0. + * + * @note @p name is an absolute path inside the file system, @p abs_path is + * the path to the file in the VFS, example: + * @p abs_path = "/mnt/hd/foo/bar", @p name = "/foo/bar" + * + * @note @p name and @p abs_path may point to different locations within the + * same const char array and the strings may overlap + * + * @param[in] filp pointer to open file + * @param[in] name null-terminated name of the file to open, relative to the file system root, including a leading slash + * @param[in] flags flags for opening, see man 2 open, man 3p open + * @param[in] mode mode for creating a new file, see man 2 open, man 3p open + * @param[in] abs_path null-terminated name of the file to open, relative to the VFS root ("/") + * + * @return 0 on success + * @return <0 on error + */ + int (*open) (vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path); + + /** + * @brief Read bytes from an open file + * + * @param[in] filp pointer to open file + * @param[in] dest pointer to destination buffer + * @param[in] nbytes maximum number of bytes to read + * + * @return number of bytes read on success + * @return <0 on error + */ + ssize_t (*read) (vfs_file_t *filp, void *dest, size_t nbytes); + + /** + * @brief Write bytes to an open file + * + * @param[in] filp pointer to open file + * @param[in] src pointer to source buffer + * @param[in] nbytes maximum number of bytes to write + * + * @return number of bytes written on success + * @return <0 on error + */ + ssize_t (*write) (vfs_file_t *filp, const void *src, size_t nbytes); +}; + +/** + * @brief Operations on open directories + */ +struct vfs_dir_ops { + /** + * @brief Open a directory for reading with readdir + * + * @param[in] dirp pointer to open directory + * @param[in] name null-terminated name of the dir to open, relative to the file system root, including a leading slash + * @param[in] abs_path null-terminated name of the dir to open, relative to the VFS root ("/") + * + * @return 0 on success + * @return <0 on error + */ + int (*opendir) (vfs_DIR *dirp, const char *dirname, const char *abs_path); + + /** + * @brief Read a single entry from the open directory dirp and advance the + * read position by one + * + * @p entry will be populated with information about the next entry in the + * directory stream @p dirp + * + * If @p entry was updated successfully, @c readdir shall return 1. + * + * If the end of stream was reached, @c readdir shall return 0 and @p entry + * shall remain untouched. + * + * @param[in] dirp pointer to open directory + * @param[out] entry directory entry information + * + * @return 1 if @p entry was updated + * @return 0 if @p dirp has reached the end of the directory index + * @return <0 on error + */ + int (*readdir) (vfs_DIR *dirp, vfs_dirent_t *entry); + + /** + * @brief Close an open directory + * + * @param[in] dirp pointer to open directory + * + * @return 0 on success + * @return <0 on error, the directory stream dirp should be considered invalid + */ + int (*closedir) (vfs_DIR *dirp); +}; + +/** + * @brief Operations on mounted file systems + * + * Similar, but not equal, to struct super_operations in Linux + */ +struct vfs_file_system_ops { + /** + * @brief Perform any extra processing needed after mounting a file system + * + * If this call returns an error, the whole vfs_mount call will signal a + * failure. + * + * All fields of @p mountp will be initialized by vfs_mount beforehand, + * @c private_data will be initialized to NULL. + * + * @param[in] mountp file system mount being mounted + * + * @return 0 on success + * @return <0 on error + */ + int (*mount) (vfs_mount_t *mountp); + + /** + * @brief Perform the necessary clean up for unmounting a file system + * + * @param[in] mountp file system mount being unmounted + * + * @return 0 on success + * @return <0 on error + */ + int (*umount) (vfs_mount_t *mountp); + + /** + * @brief Rename a file + * + * The file @p from_path will be renamed to @p to_path + * + * @note it is not possible to rename files across different file system + * + * @param[in] mountp file system mount to operate on + * @param[in] from_path absolute path to existing file + * @param[in] to_path absolute path to destination + * + * @return 0 on success + * @return <0 on error + */ + int (*rename) (vfs_mount_t *mountp, const char *from_path, const char *to_path); + + /** + * @brief Unlink (delete) a file from the file system + * + * @param[in] mountp file system mount to operate on + * @param[in] name name of the file to delete + * + * @return 0 on success + * @return <0 on error + */ + int (*unlink) (vfs_mount_t *mountp, const char *name); + + /** + * @brief Create a directory on the file system + * + * @param[in] mountp file system mount to operate on + * @param[in] name name of the directory to create + * @param[in] mode file creation mode bits + * + * @return 0 on success + * @return <0 on error + */ + int (*mkdir) (vfs_mount_t *mountp, const char *name, mode_t mode); + + /** + * @brief Remove a directory from the file system + * + * Only empty directories may be removed. + * + * @param[in] mountp file system mount to operate on + * @param[in] name name of the directory to remove + * + * @return 0 on success + * @return <0 on error + */ + int (*rmdir) (vfs_mount_t *mountp, const char *name); + + /** + * @brief Get file status + * + * @param[in] mountp file system mount to operate on + * @param[in] path path to file being queried + * @param[out] buf pointer to stat struct to fill + * + * @return 0 on success + * @return <0 on error + */ + int (*stat) (vfs_mount_t *mountp, const char *restrict path, struct stat *restrict buf); + + /** + * @brief Get file system status + * + * @p path is only passed for consistency against the POSIX statvfs function. + * @c vfs_statvfs calls this function only when it has determined that + * @p path belongs to this file system. @p path is a file system relative + * path and does not necessarily name an existing file. + * + * @param[in] mountp file system mount to operate on + * @param[in] path path to a file on the file system being queried + * @param[out] buf pointer to statvfs struct to fill + * + * @return 0 on success + * @return <0 on error + */ + int (*statvfs) (vfs_mount_t *mountp, const char *restrict path, struct statvfs *restrict buf); + + /** + * @brief Get file system status of an open file + * + * @p path is only passed for consistency against the POSIX statvfs function. + * @c vfs_statvfs calls this function only when it has determined that + * @p path belongs to this file system. @p path is a file system relative + * path and does not necessarily name an existing file. + * + * @param[in] mountp file system mount to operate on + * @param[in] filp pointer to an open file on the file system being queried + * @param[out] buf pointer to statvfs struct to fill + * + * @return 0 on success + * @return <0 on error + */ + int (*fstatvfs) (vfs_mount_t *mountp, vfs_file_t *filp, struct statvfs *buf); +}; + +/** + * @brief Close an open file + * + * @param[in] fd fd number to close + * + * @return 0 on success + * @return <0 on error + */ +int vfs_close(int fd); + +/** + * @brief Query/set options on an open file + * + * @param[in] fd fd number to operate on + * @param[in] cmd fcntl command, see man 3p fcntl + * @param[in] arg argument to fcntl command, see man 3p fcntl + * + * @return 0 on success + * @return <0 on error + */ +int vfs_fcntl(int fd, int cmd, int arg); + +/** + * @brief Get status of an open file + * + * @param[in] fd fd number obtained from vfs_open + * @param[out] buf pointer to stat struct to fill + * + * @return 0 on success + * @return <0 on error + */ +int vfs_fstat(int fd, struct stat *buf); + +/** + * @brief Get file system status of the file system containing an open file + * + * @param[in] fd fd number obtained from vfs_open + * @param[out] buf pointer to statvfs struct to fill + * + * @return 0 on success + * @return <0 on error + */ +int vfs_fstatvfs(int fd, struct statvfs *buf); + +/** + * @brief Seek to position in file + * + * @p whence determines the function of the seek and should be set to one of + * the following values: + * + * - @c SEEK_SET: Seek to absolute offset @p off + * - @c SEEK_CUR: Seek to current location + @p off + * - @c SEEK_END: Seek to end of file + @p off + * + * @param[in] fd fd number obtained from vfs_open + * @param[in] off seek offset + * @param[in] whence determines the seek method, see detailed description + * + * @return the new seek location in the file on success + * @return <0 on error + */ +off_t vfs_lseek(int fd, off_t off, int whence); + +/** + * @brief Open a file + * + * @param[in] name file name to open + * @param[in] flags flags for opening, see man 3p open + * @param[in] mode file mode + * + * @return fd number on success (>= 0) + * @return <0 on error + */ +int vfs_open(const char *name, int flags, mode_t mode); + +/** + * @brief Read bytes from an open file + * + * @param[in] fd fd number obtained from vfs_open + * @param[out] dest destination buffer to hold the file contents + * @param[in] count maximum number of bytes to read + * + * @return number of bytes read on success + * @return <0 on error + */ +ssize_t vfs_read(int fd, void *dest, size_t count); + +/** + * @brief Write bytes to an open file + * + * @param[in] fd fd number obtained from vfs_open + * @param[in] src pointer to source buffer + * @param[in] count maximum number of bytes to write + * + * @return number of bytes written on success + * @return <0 on error + */ +ssize_t vfs_write(int fd, const void *src, size_t count); + +/** + * @brief Open a directory for reading with readdir + * + * The data in @c *dirp will be initialized by @c vfs_opendir + * + * @param[out] dirp pointer to directory stream struct for storing the state + * @param[in] dirname null-terminated name of the dir to open, absolute file system path + * + * @return 0 on success + * @return <0 on error + */ +int vfs_opendir(vfs_DIR *dirp, const char *dirname); + +/** + * @brief Read a single entry from the open directory dirp and advance the + * read position by one + * + * @p entry will be populated with information about the next entry in the + * directory stream @p dirp + * + * @attention Calling vfs_readdir on an uninitialized @c vfs_DIR is forbidden + * and may lead to file system corruption and random system failures. + * + * @param[in] dirp pointer to open directory + * @param[out] entry directory entry information + * + * @return 0 on success + * @return <0 on error + */ +int vfs_readdir(vfs_DIR *dirp, vfs_dirent_t *entry); + +/** + * @brief Close an open directory + * + * @attention Calling vfs_closedir on an uninitialized @c vfs_DIR is forbidden + * and may lead to file system corruption and random system failures. + * + * @param[in] dirp pointer to open directory + * + * @return 0 on success + * @return <0 on error, the directory stream dirp should be considered invalid + */ +int vfs_closedir(vfs_DIR *dirp); + +/** + * @brief Mount a file system + * + * @p mountp should have been populated in advance with a file system driver, + * a mount point, and private_data (if the file system driver uses one). + * + * @param[in] mountp pointer to the mount structure of the file system to mount + * + * @return 0 on success + * @return <0 on error + */ +int vfs_mount(vfs_mount_t *mountp); + +/** + * @brief Rename a file + * + * The file @p from_path will be renamed to @p to_path + * + * @note it is not possible to rename files across different file system + * + * @param[in] from_path absolute path to existing file + * @param[in] to_path absolute path to destination + * + * @return 0 on success + * @return <0 on error + */ +int vfs_rename(const char *from_path, const char *to_path); + +/** + * @brief Unmount a mounted file system + * + * This will fail if there are any open files on the mounted file system + * + * @param[in] mountp pointer to the mount structure of the file system to unmount + * + * @return 0 on success + * @return <0 on error + */ +int vfs_umount(vfs_mount_t *mountp); + +/** + * @brief Unlink (delete) a file from a mounted file system + * + * @param[in] name name of file to delete + * + * @return 0 on success + * @return <0 on error + */ +int vfs_unlink(const char *name); + +/** + * @brief Create a directory on the file system + * + * @param[in] name name of the directory to create + * @param[in] mode file creation mode bits + * + * @return 0 on success + * @return <0 on error + */ +int vfs_mkdir(const char *name, mode_t mode); + +/** + * @brief Remove a directory from the file system + * + * Only empty directories may be removed. + * + * @param[in] name name of the directory to remove + * + * @return 0 on success + * @return <0 on error + */ +int vfs_rmdir(const char *name); + +/** + * @brief Get file status + * + * @param[in] path path to file being queried + * @param[out] buf pointer to stat struct to fill + * + * @return 0 on success + * @return <0 on error + */ +int vfs_stat(const char *restrict path, struct stat *restrict buf); + +/** + * @brief Get file system status + * + * @p path can be any path that resolves to the file system being queried, it + * does not have to be an existing file. + * + * @param[in] path path to a file on the file system being queried + * @param[out] buf pointer to statvfs struct to fill + * + * @return 0 on success + * @return <0 on error + */ +int vfs_statvfs(const char *restrict path, struct statvfs *restrict buf); + +/** + * @brief Allocate a new file descriptor and give it file operations + * + * The new fd will be initialized with pointers to the given @p f_op file + * operations table and @p private_data. + * + * This function can be used to give file-like functionality to devices, e.g. UART. + * + * @p private_data can be used for passing instance information to the file + * operation handlers in @p f_op. + * + * @param[in] fd Desired fd number, use VFS_ANY_FD for any available fd + * @param[in] flags not implemented yet + * @param[in] f_op pointer to file operations table + * @param[in] private_data opaque pointer to private data + * + * @return fd number on success (>= 0) + * @return <0 on error + */ +int vfs_bind(int fd, int flags, const vfs_file_ops_t *f_op, void *private_data); + +/** + * @brief Normalize a path + * + * Normalizing a path means to remove all relative components ("..", ".") and + * any double slashes. + * + * @note @p buf is allowed to overlap @p path if @p &buf[0] <= @p &path[0] + * + * @attention @p path must be an absolute path (starting with @c / ) + * + * @param[out] buf buffer to store normalized path + * @param[in] path path to normalize + * @param[in] buflen available space in @p buf + * + * @return number of path components in the normalized path on success + * @return <0 on error + */ +int vfs_normalize_path(char *buf, const char *path, size_t buflen); + +/** + * @brief Iterate through all mounted file systems + * + * @attention Not thread safe! Do not mix calls to this function with other + * calls which modify the mount table, such as vfs_mount() and vfs_umount() + * + * Set @p cur to @c NULL to start from the beginning + * + * @see @c sc_vfs.c (@c df command) for a usage example + * + * @param[in] cur current iterator value + * + * @return Pointer to next mounted file system in list after @p cur + * @return NULL if @p cur is the last element in the list + */ +const vfs_mount_t *vfs_iterate_mounts(const vfs_mount_t *cur); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ diff --git a/sys/shell/commands/Makefile b/sys/shell/commands/Makefile index d3c9ccfc70..135bcc4669 100644 --- a/sys/shell/commands/Makefile +++ b/sys/shell/commands/Makefile @@ -61,6 +61,9 @@ endif ifneq (,$(filter sntp,$(USEMODULE))) SRC += sc_sntp.c endif +ifneq (,$(filter vfs,$(USEMODULE))) + SRC += sc_vfs.c +endif # TODO # Conditional building not possible at the moment due to diff --git a/sys/shell/commands/sc_vfs.c b/sys/shell/commands/sc_vfs.c new file mode 100644 index 0000000000..5571542d56 --- /dev/null +++ b/sys/shell/commands/sc_vfs.c @@ -0,0 +1,424 @@ +/* + * 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_shell_commands + * @{ + * + * @file + * @brief Shell commands for the VFS module + * + * @author Joakim Nohlgård + * + * @} + */ + +#if MODULE_VFS +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vfs.h" + +#define SHELL_VFS_BUFSIZE 256 +static uint8_t _shell_vfs_data_buffer[SHELL_VFS_BUFSIZE]; + +static void _ls_usage(char **argv) +{ + printf("%s \n", argv[0]); + puts("list files in "); +} + +static void _vfs_usage(char **argv) +{ + printf("%s [bytes] [offset]\n", argv[0]); + printf("%s ls \n", argv[0]); + printf("%s cp \n", argv[0]); + printf("%s mv \n", argv[0]); + printf("%s rm \n", argv[0]); + printf("%s df [path]\n", argv[0]); + puts("r: Read [bytes] bytes at [offset] in file "); + puts("w: not implemented yet"); + puts("ls: list files in "); + puts("mv: Move file to "); + puts("cp: Copy file to "); + puts("rm: Unlink (delete) "); + puts("df: show file system space utilization stats"); +} + +/* Macro used by _errno_string to expand errno labels to string and print it */ +#define _case_snprintf_errno_name(x) \ + case x: \ + res = snprintf(buf, buflen, #x); \ + break + +static int _errno_string(int err, char *buf, size_t buflen) +{ + int len = 0; + int res; + if (err < 0) { + res = snprintf(buf, buflen, "-"); + if (res < 0) { + return res; + } + if ((size_t)res <= buflen) { + buf += res; + buflen -= res; + } + len += res; + err = -err; + } + switch (err) { + _case_snprintf_errno_name(EACCES); + _case_snprintf_errno_name(ENOENT); + _case_snprintf_errno_name(EINVAL); + _case_snprintf_errno_name(EFAULT); + _case_snprintf_errno_name(EROFS); + _case_snprintf_errno_name(EIO); + _case_snprintf_errno_name(ENAMETOOLONG); + _case_snprintf_errno_name(EPERM); + + default: + res = snprintf(buf, buflen, "%d", err); + break; + } + if (res < 0) { + return res; + } + len += res; + return len; +} +#undef _case_snprintf_errno_name + +static void _print_df(const char *path) +{ + struct statvfs buf; + int res = vfs_statvfs(path, &buf); + printf("%-16s ", path); + if (res < 0) { + char err[16]; + _errno_string(res, err, sizeof(err)); + printf("statvfs failed: %s\n", err); + return; + } + printf("%12lu %12lu %12lu %7lu%%\n", (unsigned long)buf.f_blocks, + (unsigned long)(buf.f_blocks - buf.f_bfree), (unsigned long)buf.f_bavail, + (unsigned long)(((buf.f_blocks - buf.f_bfree) * 100) / buf.f_blocks)); +} + +static int _df_handler(int argc, char **argv) +{ + puts("Mountpoint Total Used Available Capacity"); + if (argc > 1) { + const char *path = argv[1]; + _print_df(path); + } + else { + /* Iterate through all mount points */ + const vfs_mount_t *it = NULL; + while ((it = vfs_iterate_mounts(it)) != NULL) { + _print_df(it->mount_point); + } + } + return 0; +} + +static int _read_handler(int argc, char **argv) +{ + uint8_t buf[16]; + size_t nbytes = sizeof(buf); + off_t offset = 0; + char *path = argv[1]; + if (argc < 2) { + puts("vfs read: missing file name"); + return 1; + } + if (argc > 2) { + nbytes = atoi(argv[2]); + } + if (argc > 3) { + offset = atoi(argv[3]); + } + + int res; + res = vfs_normalize_path(path, path, strlen(path) + 1); + if (res < 0) { + _errno_string(res, (char *)buf, sizeof(buf)); + printf("Invalid path \"%s\": %s\n", path, buf); + return 5; + } + + int fd = vfs_open(path, O_RDONLY, 0); + if (fd < 0) { + _errno_string(fd, (char *)buf, sizeof(buf)); + printf("Error opening file \"%s\": %s\n", path, buf); + return 3; + } + + res = vfs_lseek(fd, offset, SEEK_SET); + if (res < 0) { + _errno_string(res, (char *)buf, sizeof(buf)); + printf("Seek error: %s\n", buf); + vfs_close(fd); + return 4; + } + + while (nbytes > 0) { + memset(buf, 0, sizeof(buf)); + size_t line_len = (nbytes < sizeof(buf) ? nbytes : sizeof(buf)); + res = vfs_read(fd, buf, line_len); + if (res < 0) { + _errno_string(res, (char *)buf, sizeof(buf)); + printf("Read error: %s\n", buf); + vfs_close(fd); + return 5; + } + else if ((size_t)res > line_len) { + printf("BUFFER OVERRUN! %d > %lu\n", res, (unsigned long)line_len); + vfs_close(fd); + return 6; + } + else if (res == 0) { + /* EOF */ + printf("-- EOF --\n"); + break; + } + printf("%08lx:", (unsigned long)offset); + for (int k = 0; k < res; ++k) { + if ((k % 2) == 0) { + putchar(' '); + } + printf("%02x", buf[k]); + } + for (int k = res; k < sizeof(buf); ++k) { + if ((k % 2) == 0) { + putchar(' '); + } + putchar(' '); + putchar(' '); + } + putchar(' '); + putchar(' '); + for (int k = 0; k < res; ++k) { + if (isprint(buf[k])) { + putchar(buf[k]); + } + else { + putchar('.'); + } + } + puts(""); + offset += res; + nbytes -= res; + } + + vfs_close(fd); + return 0; +} + +static int _cp_handler(int argc, char **argv) +{ + char errbuf[16]; + if (argc < 3) { + _vfs_usage(argv); + return 1; + } + char *src_name = argv[1]; + char *dest_name = argv[2]; + printf("%s: copy src: %s dest: %s\n", argv[0], src_name, dest_name); + + int fd_in = vfs_open(src_name, O_RDONLY, 0); + if (fd_in < 0) { + _errno_string(fd_in, (char *)errbuf, sizeof(errbuf)); + printf("Error opening file for reading \"%s\": %s\n", src_name, errbuf); + return 2; + } + int fd_out = vfs_open(dest_name, O_WRONLY | O_TRUNC | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO); + if (fd_out < 0) { + _errno_string(fd_out, (char *)errbuf, sizeof(errbuf)); + printf("Error opening file for writing \"%s\": %s\n", dest_name, errbuf); + return 2; + } + int eof = 0; + while (eof == 0) { + size_t bufspace = sizeof(_shell_vfs_data_buffer); + size_t pos = 0; + while (bufspace > 0) { + int res = vfs_read(fd_in, &_shell_vfs_data_buffer[pos], bufspace); + if (res < 0) { + _errno_string(res, (char *)errbuf, sizeof(errbuf)); + printf("Error reading %lu bytes @ 0x%lx in \"%s\" (%d): %s\n", + (unsigned long)bufspace, (unsigned long)pos, src_name, fd_in, errbuf); + vfs_close(fd_in); + vfs_close(fd_out); + return 2; + } + if (res == 0) { + /* EOF */ + eof = 1; + break; + } + if (res > bufspace) { + printf("READ BUFFER OVERRUN! %d > %lu\n", res, (unsigned long)bufspace); + vfs_close(fd_in); + vfs_close(fd_out); + return 3; + } + pos += res; + bufspace -= res; + } + bufspace = pos; + pos = 0; + while (bufspace > 0) { + int res = vfs_write(fd_out, &_shell_vfs_data_buffer[pos], bufspace); + if (res <= 0) { + _errno_string(res, (char *)errbuf, sizeof(errbuf)); + printf("Error writing %lu bytes @ 0x%lx in \"%s\" (%d): %s\n", + (unsigned long)bufspace, (unsigned long)pos, dest_name, fd_out, errbuf); + vfs_close(fd_in); + vfs_close(fd_out); + return 4; + } + if (res > bufspace) { + printf("WRITE BUFFER OVERRUN! %d > %lu\n", res, (unsigned long)bufspace); + vfs_close(fd_in); + vfs_close(fd_out); + return 5; + } + bufspace -= res; + } + } + printf("Copied: %s -> %s\n", src_name, dest_name); + vfs_close(fd_in); + vfs_close(fd_out); + return 0; +} + +static int _mv_handler(int argc, char **argv) +{ + if (argc < 3) { + _vfs_usage(argv); + return 1; + } + char *src_name = argv[1]; + char *dest_name = argv[2]; + printf("%s: move src: %s dest: %s\n", argv[0], src_name, dest_name); + + int res = vfs_rename(src_name, dest_name); + if (res < 0) { + char errbuf[16]; + _errno_string(res, (char *)errbuf, sizeof(errbuf)); + printf("mv ERR: %s\n", errbuf); + return 2; + } + return 0; +} + +static int _rm_handler(int argc, char **argv) +{ + if (argc < 2) { + _vfs_usage(argv); + return 1; + } + char *rm_name = argv[1]; + printf("%s: unlink: %s\n", argv[0], rm_name); + + int res = vfs_unlink(rm_name); + if (res < 0) { + char errbuf[16]; + _errno_string(res, (char *)errbuf, sizeof(errbuf)); + printf("rm ERR: %s\n", errbuf); + return 2; + } + return 0; +} + +int _ls_handler(int argc, char **argv) +{ + if (argc < 2) { + _ls_usage(argv); + return 1; + } + char *path = argv[1]; + uint8_t buf[16]; + int res; + res = vfs_normalize_path(path, path, strlen(path) + 1); + if (res < 0) { + _errno_string(res, (char *)buf, sizeof(buf)); + printf("Invalid path \"%s\": %s\n", path, buf); + return 5; + } + vfs_DIR dir; + res = vfs_opendir(&dir, path); + if (res < 0) { + _errno_string(res, (char *)buf, sizeof(buf)); + printf("vfs_opendir error: %s\n", buf); + return 1; + } + unsigned int nfiles = 0; + + while (1) { + vfs_dirent_t entry; + res = vfs_readdir(&dir, &entry); + if (res < 0) { + _errno_string(res, (char *)buf, sizeof(buf)); + printf("vfs_opendir error: %s\n", buf); + if (res == -EAGAIN) { + /* try again */ + continue; + } + return 2; + } + if (res == 0) { + /* end of stream */ + break; + } + printf("%s\n", entry.d_name); + ++nfiles; + } + printf("total %u files\n", nfiles); + return 0; +} + +int _vfs_handler(int argc, char **argv) +{ + if (argc < 2) { + _vfs_usage(argv); + return 1; + } + if (strcmp(argv[1], "r") == 0) { + /* pass on to read handler, shifting the arguments by one */ + return _read_handler(argc - 1, &argv[1]); + } + else if (strcmp(argv[1], "ls") == 0) { + return _ls_handler(argc - 1, &argv[1]); + } + else if (strcmp(argv[1], "cp") == 0) { + return _cp_handler(argc - 1, &argv[1]); + } + else if (strcmp(argv[1], "mv") == 0) { + return _mv_handler(argc - 1, &argv[1]); + } + else if (strcmp(argv[1], "rm") == 0) { + return _rm_handler(argc - 1, &argv[1]); + } + else if (strcmp(argv[1], "df") == 0) { + return _df_handler(argc - 1, &argv[1]); + } + else { + printf("vfs: unsupported sub-command \"%s\"\n", argv[1]); + return 1; + } +} +#endif diff --git a/sys/shell/commands/shell_commands.c b/sys/shell/commands/shell_commands.c index 5713d6b550..d72730051c 100644 --- a/sys/shell/commands/shell_commands.c +++ b/sys/shell/commands/shell_commands.c @@ -127,6 +127,11 @@ extern int _ccnl_fib(int argc, char **argv); extern int _ntpdate(int argc, char **argv); #endif +#ifdef MODULE_VFS +extern int _vfs_handler(int argc, char **argv); +extern int _ls_handler(int argc, char **argv); +#endif + const shell_command_t _shell_command_list[] = { {"reboot", "Reboot the node", _reboot_handler}, #ifdef MODULE_CONFIG @@ -211,6 +216,10 @@ const shell_command_t _shell_command_list[] = { #endif #ifdef MODULE_SNTP { "ntpdate", "synchronizes with a remote time server", _ntpdate }, +#endif +#ifdef MODULE_VFS + {"vfs", "virtual file system operations", _vfs_handler}, + {"ls", "list files", _ls_handler}, #endif {NULL, NULL, NULL} }; diff --git a/sys/vfs/Makefile b/sys/vfs/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/vfs/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/vfs/vfs.c b/sys/vfs/vfs.c new file mode 100644 index 0000000000..0217dd4fe3 --- /dev/null +++ b/sys/vfs/vfs.c @@ -0,0 +1,932 @@ +/* + * 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 + */ + +#include /* for error codes */ +#include /* for strncmp */ +#include /* for NULL */ +#include /* for off_t etc */ +#include /* for struct stat */ +#include /* for struct statvfs */ +#include /* for O_ACCMODE, ..., fcntl */ + +#include "vfs.h" +#include "mutex.h" +#include "thread.h" +#include "kernel_types.h" +#include "clist.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" +#if 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 /* for STDOUT_FILENO */ +#define DEBUG_NOT_STDOUT(fd, ...) if (fd != STDOUT_FILENO) { DEBUG(__VA_ARGS__); } +#else +#define DEBUG_NOT_STDOUT(...) +#endif + +/** + * @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 + */ +inline static 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 + */ +inline static 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 + */ +inline static 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 + */ +inline static 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 + */ +inline static 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; + } + 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]; + if (filp->mp->fs->fs_op->fstatvfs == NULL) { + /* file system driver does not implement fstatvfs() */ + if (filp->mp->fs->fs_op->statvfs != NULL) { + /* Fall back to statvfs */ + return filp->mp->fs->fs_op->statvfs(filp->mp, "/", buf); + } + return -EINVAL; + } + return filp->mp->fs->fs_op->fstatvfs(filp->mp, filp, 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 */ + atomic_fetch_sub(&mountp->open_files, 1); + 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, name); + 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; +} + +ssize_t vfs_read(int fd, void *dest, size_t count) +{ + DEBUG("vfs_read: %d, %p, %lu\n", fd, dest, (unsigned long)count); + if (dest == 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_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 filp->f_op->read(filp, dest, count); +} + + +ssize_t vfs_write(int fd, const void *src, size_t count) +{ + DEBUG_NOT_STDOUT(fd, "vfs_write: %d, %p, %lu\n", fd, src, (unsigned long)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); +} + +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, dirname); + if (res < 0) { + /* remember to decrement the open_files count */ + atomic_fetch_sub(&mountp->open_files, 1); + 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)); + atomic_fetch_sub(&mountp->open_files, 1); + return res; +} + +int vfs_mount(vfs_mount_t *mountp) +{ + DEBUG("vfs_mount: %p\n", (void *)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_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_mount: Already mounted\n"); + return -EBUSY; + } + 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) { + mutex_unlock(&_mount_mutex); + return res; + } + } + } + /* insert last in list */ + 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) +{ + DEBUG("vfs_umount: %p\n", (void *)mountp); + if ((mountp == NULL) || (mountp->mount_point == NULL)) { + return -EINVAL; + } + mutex_lock(&_mount_mutex); + DEBUG("vfs_umount: -> \"%s\" open=%d\n", mountp->mount_point, atomic_load(&mountp->open_files)); + if (atomic_load(&mountp->open_files) > 0) { + 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 */ + atomic_fetch_sub(&mountp->open_files, 1); + return -EPERM; + } + 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 */ + atomic_fetch_sub(&mountp->open_files, 1); + 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 */ + atomic_fetch_sub(&mountp->open_files, 1); + atomic_fetch_sub(&mountp_to->open_files, 1); + 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 */ + atomic_fetch_sub(&mountp->open_files, 1); + atomic_fetch_sub(&mountp_to->open_files, 1); + 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 */ + atomic_fetch_sub(&mountp->open_files, 1); + return -EPERM; + } + 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 */ + atomic_fetch_sub(&mountp->open_files, 1); + 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 */ + atomic_fetch_sub(&mountp->open_files, 1); + return -EPERM; + } + 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 */ + atomic_fetch_sub(&mountp->open_files, 1); + 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 */ + atomic_fetch_sub(&mountp->open_files, 1); + return -EPERM; + } + 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 */ + atomic_fetch_sub(&mountp->open_files, 1); + 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 */ + atomic_fetch_sub(&mountp->open_files, 1); + return -EPERM; + } + res = mountp->fs->fs_op->stat(mountp, rel_path, buf); + /* remember to decrement the open_files count */ + atomic_fetch_sub(&mountp->open_files, 1); + 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 */ + atomic_fetch_sub(&mountp->open_files, 1); + return -EPERM; + } + res = mountp->fs->fs_op->statvfs(mountp, rel_path, buf); + /* remember to decrement the open_files count */ + atomic_fetch_sub(&mountp->open_files, 1); + 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), %lu\n", buf, path, path, (unsigned long)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, len, buf, path, 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; + } + } + else { + node = cur->list_entry.next; + if (node == _vfs_mounts_list.next) { + return NULL; + } + } + return container_of(node, vfs_mount_t, list_entry); +} + +inline static int _allocate_fd(int fd) +{ + if (fd < 0) { + for (fd = 0; fd < VFS_MAX_OPEN_FILES; ++fd) { + 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; +} + +inline static 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) { + atomic_fetch_sub(&_vfs_open_files[fd].mp->open_files, 1); + } + _vfs_open_files[fd].pid = KERNEL_PID_UNDEF; +} + +inline static 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; +} + +inline static 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 */ + atomic_fetch_add(&mountp->open_files, 1); + mutex_unlock(&_mount_mutex); + *mountpp = mountp; + if (rel_path != NULL) { + *rel_path = name + longest_match; + } + return 0; +} + +inline static 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; +} + +/** @} */ diff --git a/tests/unittests/tests-vfs/Makefile b/tests/unittests/tests-vfs/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/tests/unittests/tests-vfs/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/tests/unittests/tests-vfs/Makefile.include b/tests/unittests/tests-vfs/Makefile.include new file mode 100644 index 0000000000..f35c90d855 --- /dev/null +++ b/tests/unittests/tests-vfs/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE += vfs +USEMODULE += constfs diff --git a/tests/unittests/tests-vfs/tests-vfs-bind.c b/tests/unittests/tests-vfs/tests-vfs-bind.c new file mode 100644 index 0000000000..ddce730fe3 --- /dev/null +++ b/tests/unittests/tests-vfs/tests-vfs-bind.c @@ -0,0 +1,122 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * @brief Unittests for vfs_bind + * + * @author Joakim Nohlgård + */ +#include +#include +#include +#include +#include + +#include "embUnit/embUnit.h" + +#include "vfs.h" + +#include "tests-vfs.h" + +static const uint8_t str_data[] = "This is a test file"; + /* 01234567890123456789 */ + /* 0 1 */ + +#define _VFS_TEST_BIND_BUFSIZE 8 + +static ssize_t _mock_write(vfs_file_t *filp, const void *src, size_t nbytes); +static ssize_t _mock_read(vfs_file_t *filp, void *dest, size_t nbytes); + +static volatile int _mock_write_calls = 0; +static volatile int _mock_read_calls = 0; + +static vfs_file_ops_t _test_bind_ops = { + .read = _mock_read, + .write = _mock_write, +}; + +static ssize_t _mock_write(vfs_file_t *filp, const void *src, size_t nbytes) +{ + void *dest = filp->private_data.ptr; + ++_mock_write_calls; + if (nbytes > _VFS_TEST_BIND_BUFSIZE) { + nbytes = _VFS_TEST_BIND_BUFSIZE; + } + memcpy(dest, src, nbytes); + return nbytes; +} + +static ssize_t _mock_read(vfs_file_t *filp, void *dest, size_t nbytes) +{ + void *src = filp->private_data.ptr; + ++_mock_read_calls; + if (nbytes > _VFS_TEST_BIND_BUFSIZE) { + nbytes = _VFS_TEST_BIND_BUFSIZE; + } + memcpy(dest, src, nbytes); + return nbytes; +} + +static void test_vfs_bind(void) +{ + int fd; + uint8_t buf[_VFS_TEST_BIND_BUFSIZE]; + fd = vfs_bind(VFS_ANY_FD, O_RDWR, &_test_bind_ops, &buf[0]); + TEST_ASSERT(fd >= 0); + if (fd < 0) { + return; + } + + ssize_t nbytes; + int ncalls = _mock_write_calls; + nbytes = vfs_write(fd, &str_data[0], sizeof(str_data)); + TEST_ASSERT_EQUAL_INT(_mock_write_calls, ncalls + 1); + TEST_ASSERT_EQUAL_INT(_VFS_TEST_BIND_BUFSIZE, nbytes); + TEST_ASSERT_EQUAL_INT(0, memcmp(&str_data[0], &buf[0], nbytes)); + + char strbuf[64]; + memset(strbuf, '\0', sizeof(strbuf)); + ncalls = _mock_read_calls; + nbytes = vfs_read(fd, strbuf, sizeof(strbuf)); + TEST_ASSERT_EQUAL_INT(_mock_read_calls, ncalls + 1); + TEST_ASSERT_EQUAL_INT(_VFS_TEST_BIND_BUFSIZE, nbytes); + TEST_ASSERT_EQUAL_INT(0, memcmp(&buf[0], &strbuf[0], nbytes)); + + int res = vfs_close(fd); + TEST_ASSERT_EQUAL_INT(0, res); +} + +static void test_vfs_bind__leak_fds(void) +{ + /* This test was added after a bug was discovered in the _allocate_fd code to + * make sure that fds are not leaked when doing multiple bind/close calls */ + /* Try >VFS_MAX_OPEN_FILES times to open and close fds to see that they are + * not leaked */ + for (unsigned int i = 0; i < VFS_MAX_OPEN_FILES; ++i) { + test_vfs_bind(); + } + /* The following will fail if the fds above are not properly freed */ + test_vfs_bind(); +} + +Test *tests_vfs_bind_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_vfs_bind), + new_TestFixture(test_vfs_bind__leak_fds), + }; + + EMB_UNIT_TESTCALLER(vfs_bind_tests, NULL, NULL, fixtures); + + return (Test *)&vfs_bind_tests; +} + +/** @} */ diff --git a/tests/unittests/tests-vfs/tests-vfs-dir-ops.c b/tests/unittests/tests-vfs/tests-vfs-dir-ops.c new file mode 100644 index 0000000000..fa56a04ff7 --- /dev/null +++ b/tests/unittests/tests-vfs/tests-vfs-dir-ops.c @@ -0,0 +1,124 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * @brief Transparent-box unit tests of vfs functions stubs used when the file + * system does not implement the requested function. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "embUnit/embUnit.h" + +#include "vfs.h" + +#include "tests-vfs.h" + +static const vfs_file_system_ops_t null_fs_ops = { + .mount = NULL, + .umount = NULL, + .unlink = NULL, + .statvfs = NULL, + .stat = NULL, +}; + +static const vfs_file_ops_t null_file_ops = { + .close = NULL, + .fstat = NULL, + .lseek = NULL, + .open = NULL, + .read = NULL, + .write = NULL, +}; + +static const vfs_dir_ops_t null_dir_ops = { + .opendir = NULL, + .readdir = NULL, + .closedir = NULL, +}; + +static const vfs_file_system_t null_file_system = { + .f_op = &null_file_ops, + .fs_op = &null_fs_ops, + .d_op = &null_dir_ops, +}; + +static vfs_mount_t _test_vfs_mount_null = { + .mount_point = "/test", + .fs = &null_file_system, + .private_data = NULL, +}; + +static int _test_vfs_dir_op_status = -1; +static vfs_DIR _test_dir; + +static void setup(void) +{ + int res = vfs_mount(&_test_vfs_mount_null); + if (res < 0) { + _test_vfs_dir_op_status = -1; + return; + } + _test_vfs_dir_op_status = vfs_opendir(&_test_dir, "/test/mydir"); +} + +static void teardown(void) +{ + if (_test_vfs_dir_op_status >= 0) { + vfs_closedir(&_test_dir); + _test_vfs_dir_op_status = -1; + } + vfs_umount(&_test_vfs_mount_null); +} + +static void test_vfs_null_dir_ops_opendir(void) +{ + TEST_ASSERT_EQUAL_INT(0, _test_vfs_dir_op_status); + int res = vfs_opendir(NULL, "/test/mydir2"); + TEST_ASSERT_EQUAL_INT(-EINVAL, res); +} + +static void test_vfs_null_dir_ops_closedir(void) +{ + TEST_ASSERT_EQUAL_INT(0, _test_vfs_dir_op_status); + int res = vfs_closedir(&_test_dir); + TEST_ASSERT_EQUAL_INT(0, res); + res = vfs_closedir(&_test_dir); + TEST_ASSERT_EQUAL_INT(-EBADF, res); +} + +static void test_vfs_null_dir_ops_readdir(void) +{ + TEST_ASSERT_EQUAL_INT(0, _test_vfs_dir_op_status); + vfs_dirent_t buf; + int res = vfs_readdir(&_test_dir, &buf); + TEST_ASSERT_EQUAL_INT(-EINVAL, res); +} + +Test *tests_vfs_null_dir_ops_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_vfs_null_dir_ops_opendir), + new_TestFixture(test_vfs_null_dir_ops_closedir), + new_TestFixture(test_vfs_null_dir_ops_readdir), + }; + + EMB_UNIT_TESTCALLER(vfs_dir_op_tests, setup, teardown, fixtures); + + return (Test *)&vfs_dir_op_tests; +} + +/** @} */ diff --git a/tests/unittests/tests-vfs/tests-vfs-file-ops.c b/tests/unittests/tests-vfs/tests-vfs-file-ops.c new file mode 100644 index 0000000000..4b5ae05ea5 --- /dev/null +++ b/tests/unittests/tests-vfs/tests-vfs-file-ops.c @@ -0,0 +1,169 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * @brief Transparent-box unit tests of vfs functions stubs used when the file + * system does not implement the requested function. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "embUnit/embUnit.h" + +#include "vfs.h" + +#include "tests-vfs.h" + +static const vfs_file_system_ops_t null_fs_ops = { + .mount = NULL, + .umount = NULL, + .unlink = NULL, + .statvfs = NULL, + .stat = NULL, +}; + +static const vfs_file_ops_t null_file_ops = { + .close = NULL, + .fstat = NULL, + .lseek = NULL, + .open = NULL, + .read = NULL, + .write = NULL, +}; + +static const vfs_dir_ops_t null_dir_ops = { + .opendir = NULL, + .readdir = NULL, + .closedir = NULL, +}; + +static const vfs_file_system_t null_file_system = { + .f_op = &null_file_ops, + .fs_op = &null_fs_ops, + .d_op = &null_dir_ops, +}; + +static vfs_mount_t _test_vfs_mount_null = { + .mount_point = "/test", + .fs = &null_file_system, + .private_data = NULL, +}; + +static int _test_vfs_file_op_my_fd = -1; + +static void setup(void) +{ + int res = vfs_mount(&_test_vfs_mount_null); + if (res < 0) { + _test_vfs_file_op_my_fd = -1; + return; + } + _test_vfs_file_op_my_fd = vfs_open("/test/somefile", O_RDONLY, 0); +} + +static void teardown(void) +{ + if (_test_vfs_file_op_my_fd >= 0) { + vfs_close(_test_vfs_file_op_my_fd); + _test_vfs_file_op_my_fd = -1; + } + vfs_umount(&_test_vfs_mount_null); +} + +static void test_vfs_null_file_ops_close(void) +{ + TEST_ASSERT(_test_vfs_file_op_my_fd >= 0); + int res = vfs_close(_test_vfs_file_op_my_fd); + TEST_ASSERT_EQUAL_INT(0, res); + _test_vfs_file_op_my_fd = -1; /* prevent double close */ +} + +static void test_vfs_null_file_ops_fcntl(void) +{ + TEST_ASSERT(_test_vfs_file_op_my_fd >= 0); + int res = vfs_fcntl(_test_vfs_file_op_my_fd, F_GETFL, 0); + TEST_ASSERT_EQUAL_INT(O_RDONLY, res); + res = vfs_fcntl(_test_vfs_file_op_my_fd, F_GETFD, 0); + TEST_ASSERT_EQUAL_INT(-EINVAL, res); +} + +static void test_vfs_null_file_ops_lseek(void) +{ + TEST_ASSERT(_test_vfs_file_op_my_fd >= 0); + off_t pos; + pos = vfs_lseek(_test_vfs_file_op_my_fd, 4, SEEK_SET); + TEST_ASSERT_EQUAL_INT(4, pos); + pos = vfs_lseek(_test_vfs_file_op_my_fd, 3, SEEK_CUR); + TEST_ASSERT_EQUAL_INT(7, pos); + pos = vfs_lseek(_test_vfs_file_op_my_fd, -7, SEEK_CUR); + TEST_ASSERT_EQUAL_INT(0, pos); + pos = vfs_lseek(_test_vfs_file_op_my_fd, -1, SEEK_CUR); + TEST_ASSERT_EQUAL_INT(-EINVAL, pos); + pos = vfs_lseek(_test_vfs_file_op_my_fd, 12345, SEEK_SET); + TEST_ASSERT_EQUAL_INT(12345, pos); + pos = vfs_lseek(_test_vfs_file_op_my_fd, -1, SEEK_SET); + TEST_ASSERT_EQUAL_INT(-EINVAL, pos); + pos = vfs_lseek(_test_vfs_file_op_my_fd, 0, SEEK_END); /* not implemented in "file system" */ + TEST_ASSERT_EQUAL_INT(-EINVAL, pos); + pos = vfs_lseek(_test_vfs_file_op_my_fd, 0, SEEK_CUR); + TEST_ASSERT_EQUAL_INT(12345, pos); +} + +static void test_vfs_null_file_ops_fstat(void) +{ + TEST_ASSERT(_test_vfs_file_op_my_fd >= 0); + struct stat buf; + int res = vfs_fstat(_test_vfs_file_op_my_fd, &buf); + TEST_ASSERT_EQUAL_INT(-EINVAL, res); +} + +static void test_vfs_null_file_ops_read(void) +{ + TEST_ASSERT(_test_vfs_file_op_my_fd >= 0); + uint8_t buf[8]; + int res = vfs_read(_test_vfs_file_op_my_fd, buf, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(-EINVAL, res); + res = vfs_read(_test_vfs_file_op_my_fd, NULL, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(-EFAULT, res); +} + +static void test_vfs_null_file_ops_write(void) +{ + TEST_ASSERT(_test_vfs_file_op_my_fd >= 0); + static const char buf[] = "Unit test"; + int res = vfs_write(_test_vfs_file_op_my_fd, buf, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(-EBADF, res); + res = vfs_write(_test_vfs_file_op_my_fd, NULL, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(-EFAULT, res); +} + +Test *tests_vfs_null_file_ops_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_vfs_null_file_ops_close), + new_TestFixture(test_vfs_null_file_ops_fcntl), + new_TestFixture(test_vfs_null_file_ops_lseek), + new_TestFixture(test_vfs_null_file_ops_fstat), + new_TestFixture(test_vfs_null_file_ops_read), + new_TestFixture(test_vfs_null_file_ops_write), + }; + + EMB_UNIT_TESTCALLER(vfs_file_op_tests, setup, teardown, fixtures); + + return (Test *)&vfs_file_op_tests; +} + +/** @} */ diff --git a/tests/unittests/tests-vfs/tests-vfs-file-system-ops.c b/tests/unittests/tests-vfs/tests-vfs-file-system-ops.c new file mode 100644 index 0000000000..7443e8577c --- /dev/null +++ b/tests/unittests/tests-vfs/tests-vfs-file-system-ops.c @@ -0,0 +1,169 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * @brief Transparent-box unit tests of vfs functions stubs used when the file + * system does not implement the requested function. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "embUnit/embUnit.h" + +#include "vfs.h" + +#include "tests-vfs.h" + +static const vfs_file_system_ops_t null_fs_ops = { + .mount = NULL, + .umount = NULL, + .unlink = NULL, + .statvfs = NULL, + .stat = NULL, +}; + +static const vfs_file_ops_t null_file_ops = { + .close = NULL, + .fstat = NULL, + .lseek = NULL, + .open = NULL, + .read = NULL, + .write = NULL, +}; + +static const vfs_dir_ops_t null_dir_ops = { + .opendir = NULL, + .readdir = NULL, + .closedir = NULL, +}; + +static const vfs_file_system_t null_file_system = { + .f_op = &null_file_ops, + .fs_op = &null_fs_ops, + .d_op = &null_dir_ops, +}; + +static vfs_mount_t _test_vfs_mount_null = { + .mount_point = "/test", + .fs = &null_file_system, + .private_data = NULL, +}; + +static int _test_vfs_fs_op_mount_res = -1; + +static void setup(void) +{ + _test_vfs_fs_op_mount_res = vfs_mount(&_test_vfs_mount_null); +} + +static void teardown(void) +{ + vfs_umount(&_test_vfs_mount_null); +} + +static void test_vfs_null_fs_ops_mount(void) +{ + TEST_ASSERT_EQUAL_INT(0, _test_vfs_fs_op_mount_res); + int res = vfs_mount(&_test_vfs_mount_null); + /* Already mounted */ + TEST_ASSERT_EQUAL_INT(-EBUSY, res); +} + +static void test_vfs_null_fs_ops_umount(void) +{ + TEST_ASSERT_EQUAL_INT(0, _test_vfs_fs_op_mount_res); + int res = vfs_umount(&_test_vfs_mount_null); + TEST_ASSERT_EQUAL_INT(0, res); + res = vfs_umount(&_test_vfs_mount_null); + /* Not mounted */ + TEST_ASSERT_EQUAL_INT(-EINVAL, res); +} + +static void test_vfs_null_fs_ops_rename(void) +{ + TEST_ASSERT_EQUAL_INT(0, _test_vfs_fs_op_mount_res); + int res = vfs_rename("/test/foo", "/test/bar"); + TEST_ASSERT_EQUAL_INT(-EPERM, res); +} + +static void test_vfs_null_fs_ops_unlink(void) +{ + TEST_ASSERT_EQUAL_INT(0, _test_vfs_fs_op_mount_res); + int res = vfs_unlink("/test/foo"); + TEST_ASSERT_EQUAL_INT(-EPERM, res); +} + +static void test_vfs_null_fs_ops_mkdir(void) +{ + TEST_ASSERT_EQUAL_INT(0, _test_vfs_fs_op_mount_res); + int res = vfs_mkdir("/test/foodir", 0); + TEST_ASSERT_EQUAL_INT(-EPERM, res); +} + +static void test_vfs_null_fs_ops_rmdir(void) +{ + TEST_ASSERT_EQUAL_INT(0, _test_vfs_fs_op_mount_res); + int res = vfs_rmdir("/test/foodir"); + TEST_ASSERT_EQUAL_INT(-EPERM, res); +} + +static void test_vfs_null_fs_ops_stat(void) +{ + TEST_ASSERT_EQUAL_INT(0, _test_vfs_fs_op_mount_res); + struct stat buf; + int res = vfs_stat("/test/foo", &buf); + TEST_ASSERT_EQUAL_INT(-EPERM, res); +} + +static void test_vfs_null_fs_ops_statvfs(void) +{ + TEST_ASSERT_EQUAL_INT(0, _test_vfs_fs_op_mount_res); + struct statvfs buf; + int res = vfs_statvfs("/test", &buf); + TEST_ASSERT_EQUAL_INT(-EPERM, res); +} + +static void test_vfs_null_fs_ops_fstatvfs(void) +{ + TEST_ASSERT_EQUAL_INT(0, _test_vfs_fs_op_mount_res); + int fd = vfs_open("/test/baz", O_RDONLY, 0); + TEST_ASSERT(fd >= 0); + struct statvfs buf; + int res = vfs_fstatvfs(fd, &buf); + TEST_ASSERT_EQUAL_INT(-EINVAL, res); +} + + +Test *tests_vfs_null_file_system_ops_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_vfs_null_fs_ops_mount), + new_TestFixture(test_vfs_null_fs_ops_umount), + new_TestFixture(test_vfs_null_fs_ops_rename), + new_TestFixture(test_vfs_null_fs_ops_unlink), + new_TestFixture(test_vfs_null_fs_ops_mkdir), + new_TestFixture(test_vfs_null_fs_ops_rmdir), + new_TestFixture(test_vfs_null_fs_ops_stat), + new_TestFixture(test_vfs_null_fs_ops_statvfs), + new_TestFixture(test_vfs_null_fs_ops_fstatvfs), + }; + + EMB_UNIT_TESTCALLER(vfs_fs_op_tests, setup, teardown, fixtures); + + return (Test *)&vfs_fs_op_tests; +} + +/** @} */ diff --git a/tests/unittests/tests-vfs/tests-vfs-mount-constfs.c b/tests/unittests/tests-vfs/tests-vfs-mount-constfs.c new file mode 100644 index 0000000000..88a96b510d --- /dev/null +++ b/tests/unittests/tests-vfs/tests-vfs-mount-constfs.c @@ -0,0 +1,223 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * @brief Unittests for vfs_mount, vfs_umount, ConstFS, VFS POSIX wrappers + * + * @author Joakim Nohlgård + */ +#include +#include +#include +#include +#include +#include + +#include "embUnit/embUnit.h" + +#include "vfs.h" +#include "fs/constfs.h" + +#include "tests-vfs.h" + +static const uint8_t bin_data[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, +}; +static const uint8_t str_data[] = "This is a test file"; + /* 01234567890123456789 */ + /* 0 1 */ +static const constfs_file_t _files[] = { + { + .path = "/test.txt", + .data = str_data, + .size = sizeof(str_data), + }, + { + .path = "/data.bin", + .data = bin_data, + .size = sizeof(bin_data), + }, +}; + +static const constfs_t fs_data = { + .files = _files, + .nfiles = sizeof(_files) / sizeof(_files[0]), +}; + +static vfs_mount_t _test_vfs_mount_invalid_mount = { + .mount_point = "test", + .fs = &constfs_file_system, + .private_data = (void *)&fs_data, +}; + +static vfs_mount_t _test_vfs_mount = { + .mount_point = "/test", + .fs = &constfs_file_system, + .private_data = (void *)&fs_data, +}; + +static void test_vfs_mount_umount(void) +{ + int res; + res = vfs_mount(&_test_vfs_mount); + TEST_ASSERT_EQUAL_INT(0, res); + res = vfs_umount(&_test_vfs_mount); + TEST_ASSERT_EQUAL_INT(0, res); +} + +static void test_vfs_mount__invalid(void) +{ + int res; + res = vfs_mount(NULL); + TEST_ASSERT(res < 0); + + res = vfs_mount(&_test_vfs_mount_invalid_mount); + TEST_ASSERT(res < 0); +} + +static void test_vfs_umount__invalid_mount(void) +{ + int res; + res = vfs_umount(NULL); + TEST_ASSERT(res < 0); + res = vfs_umount(&_test_vfs_mount); + TEST_ASSERT(res < 0); +} + +static void test_vfs_constfs_open(void) +{ + int res; + res = vfs_mount(&_test_vfs_mount); + TEST_ASSERT_EQUAL_INT(0, res); + + int fd; + fd = vfs_open("/test/notfound", O_RDONLY, 0); + TEST_ASSERT(fd == -ENOENT); + if (fd >= 0) { + vfs_close(fd); + } + fd = vfs_open("/test/test.txt", O_WRONLY, 0); + TEST_ASSERT(fd == -EROFS); + if (fd >= 0) { + vfs_close(fd); + } + fd = vfs_open("/test/test.txt", O_RDWR, 0); + TEST_ASSERT(fd == -EROFS); + if (fd >= 0) { + vfs_close(fd); + } + fd = vfs_open("/test/test.txt", O_RDONLY, 0); + TEST_ASSERT(fd >= 0); + if (fd >= 0) { + res = vfs_close(fd); + TEST_ASSERT_EQUAL_INT(0, res); + } + + res = vfs_umount(&_test_vfs_mount); + TEST_ASSERT_EQUAL_INT(0, res); +} + +static void test_vfs_constfs_read_lseek(void) +{ + int res; + res = vfs_mount(&_test_vfs_mount); + TEST_ASSERT_EQUAL_INT(0, res); + + int fd = vfs_open("/test/test.txt", O_RDONLY, 0); + TEST_ASSERT(fd >= 0); + + char strbuf[64]; + memset(strbuf, '\0', sizeof(strbuf)); + ssize_t nbytes; + nbytes = vfs_read(fd, strbuf, sizeof(strbuf)); + TEST_ASSERT_EQUAL_INT(sizeof(str_data), nbytes); + TEST_ASSERT_EQUAL_STRING((const char *)&str_data[0], (const char *)&strbuf[0]); + + off_t pos; + /* lseek to the middle */ + memset(strbuf, '\0', sizeof(strbuf)); + pos = vfs_lseek(fd, sizeof(str_data) / 2, SEEK_SET); + TEST_ASSERT_EQUAL_INT(sizeof(str_data) / 2, pos); + nbytes = vfs_read(fd, strbuf, sizeof(strbuf)); + TEST_ASSERT_EQUAL_INT((sizeof(str_data) + 1) / 2, nbytes); /* + 1 for rounding up */ + TEST_ASSERT_EQUAL_STRING((const char *)&str_data[sizeof(str_data) / 2], (const char *)&strbuf[0]); + + /* lseek to near the end */ + memset(strbuf, '\0', sizeof(strbuf)); + pos = vfs_lseek(fd, -1, SEEK_END); + TEST_ASSERT_EQUAL_INT(sizeof(str_data) - 1, pos); + nbytes = vfs_read(fd, strbuf, sizeof(strbuf)); + TEST_ASSERT_EQUAL_INT(1, nbytes); + TEST_ASSERT_EQUAL_STRING((const char *)&str_data[sizeof(str_data) - 1], (const char *)&strbuf[0]); + + res = vfs_fcntl(fd, F_GETFL, 0); + TEST_ASSERT_EQUAL_INT(O_RDONLY, res); + + res = vfs_close(fd); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_umount(&_test_vfs_mount); + TEST_ASSERT_EQUAL_INT(0, res); +} + +#if MODULE_NEWLIB +static void test_vfs_constfs__posix(void) +{ + int res; + res = vfs_mount(&_test_vfs_mount); + TEST_ASSERT_EQUAL_INT(0, res); + + int fd = open("/test/test.txt", O_RDONLY, 0); + TEST_ASSERT(fd >= 0); + + char strbuf[64]; + memset(strbuf, '\0', sizeof(strbuf)); + ssize_t nbytes; + nbytes = read(fd, strbuf, sizeof(strbuf)); + TEST_ASSERT_EQUAL_INT(sizeof(str_data), nbytes); + TEST_ASSERT_EQUAL_STRING((const char *)&str_data[0], (const char *)&strbuf[0]); + +#if HAVE_FCNTL + /* fcntl support is optional in newlib */ + res = fcntl(fd, F_GETFL, 0); + TEST_ASSERT_EQUAL_INT(O_RDONLY, res); +#endif + + res = close(fd); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_umount(&_test_vfs_mount); + TEST_ASSERT_EQUAL_INT(0, res); +} +#endif + +Test *tests_vfs_mount_constfs_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_vfs_mount_umount), + new_TestFixture(test_vfs_mount__invalid), + new_TestFixture(test_vfs_umount__invalid_mount), + new_TestFixture(test_vfs_constfs_open), + new_TestFixture(test_vfs_constfs_read_lseek), +#if MODULE_NEWLIB + new_TestFixture(test_vfs_constfs__posix), +#endif + }; + + EMB_UNIT_TESTCALLER(vfs_mount_tests, NULL, NULL, fixtures); + + return (Test *)&vfs_mount_tests; +} + +/** @} */ diff --git a/tests/unittests/tests-vfs/tests-vfs-normalize_path.c b/tests/unittests/tests-vfs/tests-vfs-normalize_path.c new file mode 100644 index 0000000000..6e6d6fb8e9 --- /dev/null +++ b/tests/unittests/tests-vfs/tests-vfs-normalize_path.c @@ -0,0 +1,153 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * @brief Unittests for vfs_normalize_path + * + * @author Joakim Nohlgård + */ +#include +#include +#include +#include +#include + +#include "embUnit/embUnit.h" + +#include "vfs.h" + +#include "tests-vfs.h" + +static void test_vfs_normalize_path__noop(void) +{ + static const char path[] = "/this/is/a/test"; + char buf[16]; + int res = vfs_normalize_path(buf, path, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(4, res); + TEST_ASSERT_EQUAL_STRING((const char *)&path[0], (const char *)&buf[0]); +} + +static void test_vfs_normalize_path__slashes(void) +{ + static const char path[] = "///////////////////////////////"; + static const char expected[] = "/"; + char buf[4]; + int res = vfs_normalize_path(buf, path, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(1, res); + TEST_ASSERT_EQUAL_STRING((const char *)&expected[0], (const char *)&buf[0]); +} + +static void test_vfs_normalize_path__dot(void) +{ + static const char path[] = "/abc/./def/././zxcv././."; + static const char expected[] = "/abc/def/zxcv."; + char buf[16]; + int res = vfs_normalize_path(buf, path, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(3, res); + TEST_ASSERT_EQUAL_STRING((const char *)&expected[0], (const char *)&buf[0]); +} + +static void test_vfs_normalize_path__reduce(void) +{ + static const char path[] = "/abc/../def"; + static const char expected[] = "/def"; + char buf[16]; + int res = vfs_normalize_path(buf, path, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(1, res); + TEST_ASSERT_EQUAL_STRING((const char *)&expected[0], (const char *)&buf[0]); +} + +static void test_vfs_normalize_path__trailing(void) +{ + static const char path[] = "/mydir/"; + static const char expected[] = "/mydir/"; + char buf[16]; + int res = vfs_normalize_path(buf, path, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(1, res); + TEST_ASSERT_EQUAL_STRING((const char *)&expected[0], (const char *)&buf[0]); +} + +static void test_vfs_normalize_path__outside(void) +{ + static const char path[] = "/somewhere/../.."; + static const char path2[] = "/../abdgh"; + char buf[16]; + int res = vfs_normalize_path(buf, path, sizeof(buf)); + TEST_ASSERT(res < 0); + res = vfs_normalize_path(buf, path2, sizeof(buf)); + TEST_ASSERT(res < 0); +} + +static void test_vfs_normalize_path__toolong(void) +{ + static const char path[] = "/abc"; + char buf[4]; + int res = vfs_normalize_path(buf, path, sizeof(buf)); + TEST_ASSERT(res < 0); +} + +static void test_vfs_normalize_path__shorten(void) +{ +#if 0 + /* Not supported by the current implementation */ + /* The current implementation needs enough buffer space to store the longest + * prefix path before each ../ reduction */ + static const char path[] = "/qwerty/asdfghjkl/.."; + static const char expected[] = "/qwerty"; + char buf[8]; +#endif + static const char path[] = "/12345/6789/.."; + static const char expected[] = "/12345"; + char buf[12]; + int res = vfs_normalize_path(buf, path, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(1, res); + TEST_ASSERT_EQUAL_STRING((const char *)&expected[0], (const char *)&buf[0]); +} + +static void test_vfs_normalize_path__shorten_inplace(void) +{ + char path[] = "/qwerty/asdfghjkl/.."; + static const char expected[] = "/qwerty"; + int res = vfs_normalize_path(path, path, sizeof(path)); + TEST_ASSERT_EQUAL_INT(1, res); + TEST_ASSERT_EQUAL_STRING((const char *)&expected[0], (const char *)&path[0]); +} + +static void test_vfs_normalize_path__empty(void) +{ + char path[] = ""; + static const char expected[] = ""; + char buf[4]; + int res = vfs_normalize_path(buf, path, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT_EQUAL_STRING((const char *)&expected[0], (const char *)&path[0]); +} + +Test *tests_vfs_normalize_path_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_vfs_normalize_path__noop), + new_TestFixture(test_vfs_normalize_path__slashes), + new_TestFixture(test_vfs_normalize_path__dot), + new_TestFixture(test_vfs_normalize_path__reduce), + new_TestFixture(test_vfs_normalize_path__trailing), + new_TestFixture(test_vfs_normalize_path__outside), + new_TestFixture(test_vfs_normalize_path__toolong), + new_TestFixture(test_vfs_normalize_path__shorten), + new_TestFixture(test_vfs_normalize_path__shorten_inplace), + new_TestFixture(test_vfs_normalize_path__empty), + }; + + EMB_UNIT_TESTCALLER(vfs_normalize_path_tests, NULL, NULL, fixtures); + + return (Test *)&vfs_normalize_path_tests; +} +/** @} */ diff --git a/tests/unittests/tests-vfs/tests-vfs-open-close.c b/tests/unittests/tests-vfs/tests-vfs-open-close.c new file mode 100644 index 0000000000..4b14365fd5 --- /dev/null +++ b/tests/unittests/tests-vfs/tests-vfs-open-close.c @@ -0,0 +1,52 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * @brief Unittests for vfs_open, vfs_close + * + * @author Joakim Nohlgård + */ +#include +#include +#include +#include +#include + +#include "embUnit/embUnit.h" + +#include "vfs.h" + +#include "tests-vfs.h" + +static void test_vfs_close__invalid_fd(void) +{ + int res = vfs_close(-1); + TEST_ASSERT(res < 0); +} + +static void test_vfs_open__notfound(void) +{ + int fd = vfs_open("/notfound/path", O_RDONLY, 0); + TEST_ASSERT(fd < 0); +} + +Test *tests_vfs_open_close_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_vfs_open__notfound), + new_TestFixture(test_vfs_close__invalid_fd), + }; + + EMB_UNIT_TESTCALLER(vfs_open_close_tests, NULL, NULL, fixtures); + + return (Test *)&vfs_open_close_tests; +} +/** @} */ diff --git a/tests/unittests/tests-vfs/tests-vfs.c b/tests/unittests/tests-vfs/tests-vfs.c new file mode 100644 index 0000000000..c8c91489f5 --- /dev/null +++ b/tests/unittests/tests-vfs/tests-vfs.c @@ -0,0 +1,40 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * @brief Unittest entry point for the VFS test group + * + * @author Joakim Nohlgård + */ + +#include "embUnit/embUnit.h" + +#include "tests-vfs.h" + +Test *tests_vfs_bind_tests(void); +Test *tests_vfs_mount_constfs_tests(void); +Test *tests_vfs_open_close_tests(void); +Test *tests_vfs_normalize_path_tests(void); +Test *tests_vfs_null_file_ops_tests(void); +Test *tests_vfs_null_file_system_ops_tests(void); +Test *tests_vfs_null_dir_ops_tests(void); + +void tests_vfs(void) +{ + TESTS_RUN(tests_vfs_open_close_tests()); + TESTS_RUN(tests_vfs_bind_tests()); + TESTS_RUN(tests_vfs_mount_constfs_tests()); + TESTS_RUN(tests_vfs_normalize_path_tests()); + TESTS_RUN(tests_vfs_null_file_ops_tests()); + TESTS_RUN(tests_vfs_null_file_system_ops_tests()); + TESTS_RUN(tests_vfs_null_dir_ops_tests()); +} +/** @} */ diff --git a/tests/unittests/tests-vfs/tests-vfs.h b/tests/unittests/tests-vfs/tests-vfs.h new file mode 100644 index 0000000000..edd6457537 --- /dev/null +++ b/tests/unittests/tests-vfs/tests-vfs.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +/** + * @addtogroup unittests + * @{ + * + * @file + * @brief Unittests for the ``vfs`` module + * + * @author Joakim Nohlgård + */ +#ifndef TESTS_VFS_H +#define TESTS_VFS_H + +#include "embUnit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The entry point of this test suite. + */ +void tests_vfs(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TESTS_VFS_H */ +/** @} */