diff --git a/Makefile.dep b/Makefile.dep index cc85b1fcef32..95a1c4542ae6 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -597,6 +597,20 @@ ifneq (,$(filter emcute,$(USEMODULE))) USEMODULE += xtimer endif +ifneq (,$(filter constfs,$(USEMODULE))) + USEMODULE += vfs +endif + +ifneq (,$(filter devfs,$(USEMODULE))) + USEMODULE += vfs +endif + +ifneq (,$(filter vfs,$(USEMODULE))) + ifeq (native, $(BOARD)) + USEMODULE += native_vfs + endif +endif + # include package dependencies -include $(USEPKG:%=$(RIOTPKG)/%/Makefile.dep) diff --git a/boards/mulle/Makefile.dep b/boards/mulle/Makefile.dep index 96549169fd03..8b84653c906a 100644 --- a/boards/mulle/Makefile.dep +++ b/boards/mulle/Makefile.dep @@ -6,12 +6,10 @@ ifneq (,$(filter saul_default,$(USEMODULE))) USEMODULE += lis3dh endif -# The RTT clock drives the core clock in the default configuration -FEATURES_REQUIRED += periph_rtt - # The Mulle uses NVRAM to store persistent variables, such as boot count. USEMODULE += nvram_spi -FEATURES_REQUIRED += periph_spi +USEMODULE += nvram +USEMODULE += devfs ifneq (,$(filter saul_default,$(USEMODULE))) USEMODULE += saul_gpio diff --git a/boards/mulle/board.c b/boards/mulle/board.c index 3bc2899f6328..82058218b031 100644 --- a/boards/mulle/board.c +++ b/boards/mulle/board.c @@ -24,11 +24,13 @@ #include "cpu.h" #include "mcg.h" #include "periph/gpio.h" -#include "periph/uart.h" #include "periph/rtt.h" #include "periph/spi.h" #include "nvram-spi.h" +#include "nvram.h" #include "xtimer.h" +#include "vfs.h" +#include "fs/devfs.h" static nvram_t mulle_nvram_dev; nvram_t *mulle_nvram = &mulle_nvram_dev; @@ -39,6 +41,12 @@ static nvram_spi_params_t nvram_spi_params = { .address_count = MULLE_NVRAM_SPI_ADDRESS_COUNT, }; +static devfs_t mulle_nvram_devfs = { + .path = "/mulle-fram", + .f_op = &nvram_vfs_ops, + .private_data = &mulle_nvram_dev, +}; + /** @brief Initialize the GPIO pins controlling the power switches. */ static inline void power_pins_init(void); @@ -210,6 +218,10 @@ static int mulle_nvram_init(void) return -5; } } + + /* Register DevFS node */ + devfs_register(&mulle_nvram_devfs); + return 0; } diff --git a/cpu/atmega_common/avr-libc-extra/time.h b/cpu/atmega_common/avr-libc-extra/time.h index e1bd2778b946..00a6c87bd172 100644 --- a/cpu/atmega_common/avr-libc-extra/time.h +++ b/cpu/atmega_common/avr-libc-extra/time.h @@ -101,8 +101,8 @@ #ifndef TIME_H #define TIME_H -#include -#include +#include +#include #ifdef __cplusplus extern "C" { @@ -111,12 +111,6 @@ extern "C" { /** \ingroup avr_time */ /* @{ */ - /** - time_t represents seconds elapsed from Midnight, Jan 1 2000 UTC (the Y2K 'epoch'). - Its range allows this implementation to represent time up to Tue Feb 7 06:28:15 2136 UTC. - */ - typedef uint32_t time_t; - /** The time function returns the systems current time stamp. If timer is not a null pointer, the return value is also assigned to the object it points to. diff --git a/cpu/atmega_common/avr-libc-extra/unistd.h b/cpu/atmega_common/avr-libc-extra/unistd.h index 7d9153f93e8d..39049c605f99 100644 --- a/cpu/atmega_common/avr-libc-extra/unistd.h +++ b/cpu/atmega_common/avr-libc-extra/unistd.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2016 Eistec AB * Copyright (C) 2015 Kaspar Schleiser * * This file is subject to the terms and conditions of the GNU Lesser @@ -9,21 +10,98 @@ #ifndef UNISTD_H #define UNISTD_H +#include #include +#include #ifdef __cplusplus extern "C" { #endif -#define STDIN_FILENO 0 ///< stdin file descriptor -#define STDOUT_FILENO 1 ///< stdout file descriptor -#define STDERR_FILENO 2 ///< stderr file descriptor +#define STDIN_FILENO 0 /* standard input file descriptor */ +#define STDOUT_FILENO 1 /* standard output file descriptor */ +#define STDERR_FILENO 2 /* standard error file descriptor */ -int close(int fildes); +#define F_OK 0 +#define R_OK 4 +#define W_OK 2 +#define X_OK 1 -typedef uint32_t useconds_t; -int usleep(useconds_t usec); -unsigned int sleep(unsigned int seconds); +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +int access(const char *, int); +unsigned alarm(unsigned); +int chdir(const char *); +int chown(const char *, uid_t, gid_t); +int close(int); +size_t confstr(int, char *, size_t); +int dup(int); +int dup2(int, int); +void _exit(int); +int execl(const char *, const char *, ...); +int execle(const char *, const char *, ...); +int execlp(const char *, const char *, ...); +int execv(const char *, char *const []); +int execve(const char *, char *const [], char *const []); +int execvp(const char *, char *const []); +int faccessat(int, const char *, int, int); +int fchdir(int); +int fchown(int, uid_t, gid_t); +int fchownat(int, const char *, uid_t, gid_t, int); +int fexecve(int, char *const [], char *const []); +pid_t fork(void); +long fpathconf(int, int); +int ftruncate(int, off_t); +char *getcwd(char *, size_t); +gid_t getegid(void); +uid_t geteuid(void); +gid_t getgid(void); +int getgroups(int, gid_t []); +int gethostname(char *, size_t); +char *getlogin(void); +int getlogin_r(char *, size_t); +int getopt(int, char * const [], const char *); +pid_t getpgid(pid_t); +pid_t getpgrp(void); +pid_t getpid(void); +pid_t getppid(void); +pid_t getsid(pid_t); +uid_t getuid(void); +int isatty(int); +int lchown(const char *, uid_t, gid_t); +int link(const char *, const char *); +int linkat(int, const char *, int, const char *, int); +off_t lseek(int, off_t, int); +long pathconf(const char *, int); +int pause(void); +int pipe(int [2]); +ssize_t pread(int, void *, size_t, off_t); +ssize_t pwrite(int, const void *, size_t, off_t); +ssize_t read(int, void *, size_t); +ssize_t readlink(const char *restrict, char *restrict, size_t); +ssize_t readlinkat(int, const char *restrict, char *restrict, size_t); +int rmdir(const char *); +int setegid(gid_t); +int seteuid(uid_t); +int setgid(gid_t); +int setpgid(pid_t, pid_t); +pid_t setsid(void); +int setuid(uid_t); +unsigned sleep(unsigned); +int symlink(const char *, const char *); +int symlinkat(const char *, int, const char *); +long sysconf(int); +pid_t tcgetpgrp(int); +int tcsetpgrp(int, pid_t); +int truncate(const char *, off_t); +char *ttyname(int); +int ttyname_r(int, char *, size_t); +int unlink(const char *); +int unlinkat(int, const char *, int); +int usleep(useconds_t); +ssize_t write(int, const void *, size_t); #ifdef __cplusplus } diff --git a/cpu/atmega_common/include/sys/stat.h b/cpu/atmega_common/include/sys/stat.h new file mode 100644 index 000000000000..49e8533c6574 --- /dev/null +++ b/cpu/atmega_common/include/sys/stat.h @@ -0,0 +1,120 @@ +/* + * 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 POSIX compatible sys/stat.h definitions + * @author Joakim Nohlgård + */ + +#ifndef SYS_STAT_H_ +#define SYS_STAT_H_ + +#include /* for struct timespec */ +#include /* for fsblkcnt_t, fsfilcnt_t */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief File information + */ +struct stat { + dev_t st_dev; /**< Device ID of device containing file. */ + ino_t st_ino; /**< File serial number. */ + mode_t st_mode; /**< Mode of file (see below). */ + nlink_t st_nlink; /**< Number of hard links to the file. */ + uid_t st_uid; /**< User ID of file. */ + gid_t st_gid; /**< Group ID of file. */ + dev_t st_rdev; /**< Device ID (if file is character or block special). */ + /** + * For regular files, the file size in bytes. + * For symbolic links, the length in bytes of the pathname contained in the + * symbolic link. + * For a shared memory object, the length in bytes. + * For a typed memory object, the length in bytes. + * For other file types, the use of this field is unspecified. + */ + off_t st_size; + struct timespec st_atim; /**< Last data access timestamp. */ + struct timespec st_mtim; /**< Last data modification timestamp. */ + struct timespec st_ctim; /**< Last file status change timestamp. */ + /** + * A file system-specific preferred I/O block size for this object. In some + * file system types, this may vary from file to file. + */ + blksize_t st_blksize; + blkcnt_t st_blocks; /**< Number of blocks allocated for this object. */ +}; + +/* These bitmasks and numbers are the same as in newlib */ +#define S_IFMT 0170000 /* type of file */ +#define S_IFDIR 0040000 /* directory */ +#define S_IFCHR 0020000 /* character special */ +#define S_IFBLK 0060000 /* block special */ +#define S_IFREG 0100000 /* regular */ +#define S_IFLNK 0120000 /* symbolic link */ +#define S_IFSOCK 0140000 /* socket */ +#define S_IFIFO 0010000 /* fifo */ + +/* These numbers are well-known and can be found in the manual page for sys_stat.h */ +#define S_IRWXU 0700 /**< Read, write, execute/search by owner. */ +#define S_IRUSR 0400 /**< Read permission, owner. */ +#define S_IWUSR 0200 /**< Write permission, owner. */ +#define S_IXUSR 0100 /**< Execute/search permission, owner. */ +#define S_IRWXG 070 /**< Read, write, execute/search by group. */ +#define S_IRGRP 040 /**< Read permission, group. */ +#define S_IWGRP 020 /**< Write permission, group. */ +#define S_IXGRP 010 /**< Execute/search permission, group. */ +#define S_IRWXO 07 /**< Read, write, execute/search by others. */ +#define S_IROTH 04 /**< Read permission, others. */ +#define S_IWOTH 02 /**< Write permission, others. */ +#define S_IXOTH 01 /**< Execute/search permission, others. */ +#define S_ISUID 04000 /**< Set-user-ID on execution. */ +#define S_ISGID 02000 /**< Set-group-ID on execution. */ +#define S_ISVTX 01000 /**< On directories, restricted deletion flag */ + +/* File type test macros, taken from newlib */ +#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) +#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) +#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) + +/* These function prototypes are required by the standard */ +int chmod(const char *, mode_t); +int fchmod(int, mode_t); +int fchmodat(int, const char *, mode_t, int); +int fstat(int, struct stat *); +int fstatat(int, const char *restrict, struct stat *restrict, int); +int futimens(int, const struct timespec [2]); +int lstat(const char *restrict, struct stat *restrict); +int mkdir(const char *, mode_t); +int mkdirat(int, const char *, mode_t); +int mkfifo(const char *, mode_t); +int mkfifoat(int, const char *, mode_t); +int mknod(const char *, mode_t, dev_t); +int mknodat(int, const char *, mode_t, dev_t); +int stat(const char *restrict, struct stat *restrict); +mode_t umask(mode_t); +int utimensat(int, const char *, const struct timespec [2], int); + +/* Special tv_nsec values for futimens(2) and utimensat(2). */ +#define UTIME_NOW (-2L) +#define UTIME_OMIT (-1L) + +#ifdef __cplusplus +} +#endif + +#endif /* SYS_STAT_H_ */ + +/** @} */ diff --git a/cpu/atmega_common/include/sys/types.h b/cpu/atmega_common/include/sys/types.h index 74bf6c6ccf17..7cce094a6c83 100644 --- a/cpu/atmega_common/include/sys/types.h +++ b/cpu/atmega_common/include/sys/types.h @@ -7,22 +7,41 @@ * directory for more details. */ - #ifndef AVR_TYPES_H - #define AVR_TYPES_H +#ifndef SYS_TYPES_H_ +#define SYS_TYPES_H_ -#include +#include #include #ifdef __cplusplus extern "C" { #endif -typedef int16_t suseconds_t; -typedef signed int ssize_t; -typedef unsigned int off_t; +typedef int32_t blkcnt_t; /**< Used for file block counts */ +typedef int32_t blksize_t; /**< Used for block sizes */ +typedef uint32_t clock_t; /**< Used for system times in clock ticks */ +typedef uint32_t clockid_t; /**< Used for clock ID type in the clock and timer functions */ +typedef int16_t dev_t; /**< Used for device IDs */ +typedef uint32_t fsblkcnt_t; /**< Used for file system block counts */ +typedef uint32_t fsfilcnt_t; /**< Used for file system file counts */ +typedef uint16_t gid_t; /**< Used for group IDs */ +typedef uint16_t id_t; /**< Used as a general identifier */ +typedef uint32_t ino_t; /**< Used for file serial numbers */ +typedef uint32_t key_t; /**< Used for XSI interprocess communication */ +typedef uint32_t mode_t; /**< Used for some file attributes */ +typedef uint16_t nlink_t; /**< Used for link counts */ +typedef int32_t off_t; /**< Used for file sizes and offsets */ +typedef int pid_t; /**< Used for process IDs and process group IDs */ +typedef unsigned int size_t; /**< Used for sizes of objects */ +typedef signed int ssize_t; /**< Used for a count of bytes or an error indication */ +typedef int32_t suseconds_t; /**< Used for time in microseconds */ +typedef int32_t time_t; /**< Used for time in seconds */ +typedef uint32_t timer_t; /**< Used for timer ID returned by timer_create() */ +typedef uint16_t uid_t; /**< Used for user IDs */ +typedef uint32_t useconds_t; /**< Used for time in microseconds */ #ifdef __cplusplus } #endif -#endif /* ifndef AVR_TYPES_H */ +#endif /* SYS_TYPES_H_ */ diff --git a/cpu/msp430_common/include/msp430_types.h b/cpu/msp430_common/include/msp430_types.h index 2af057e6ffcb..81114dd195a1 100644 --- a/cpu/msp430_common/include/msp430_types.h +++ b/cpu/msp430_common/include/msp430_types.h @@ -62,12 +62,27 @@ struct timeval { time_t tv_usec; /**< microseconds */ }; -/** - * @brief Definition of `clockid_t` for the MSP430 - * - * @todo Remove once msp430 libc supports clockid_t - */ -typedef int clockid_t; +typedef int32_t blkcnt_t; /**< Used for file block counts */ +typedef int32_t blksize_t; /**< Used for block sizes */ +typedef uint32_t clock_t; /**< Used for system times in clock ticks */ +typedef uint32_t clockid_t; /**< Used for clock ID type in the clock and timer functions */ +typedef int16_t dev_t; /**< Used for device IDs */ +typedef uint32_t fsblkcnt_t; /**< Used for file system block counts */ +typedef uint32_t fsfilcnt_t; /**< Used for file system file counts */ +typedef uint16_t gid_t; /**< Used for group IDs */ +typedef uint16_t id_t; /**< Used as a general identifier */ +typedef uint32_t ino_t; /**< Used for file serial numbers */ +typedef uint32_t key_t; /**< Used for XSI interprocess communication */ +typedef uint32_t mode_t; /**< Used for some file attributes */ +typedef uint16_t nlink_t; /**< Used for link counts */ +typedef int32_t off_t; /**< Used for file sizes and offsets */ +typedef int pid_t; /**< Used for process IDs and process group IDs */ +typedef unsigned int size_t; /**< Used for sizes of objects */ +typedef signed int ssize_t; /**< Used for a count of bytes or an error indication */ +typedef int32_t suseconds_t; /**< Used for time in microseconds */ +typedef uint32_t timer_t; /**< Used for timer ID returned by timer_create() */ +typedef uint16_t uid_t; /**< Used for user IDs */ +typedef uint32_t useconds_t; /**< Used for time in microseconds */ #ifdef __cplusplus } diff --git a/cpu/msp430_common/include/stdatomic.h b/cpu/msp430_common/include/stdatomic.h index 8efd6ea95fbe..acecd28db2c2 100644 --- a/cpu/msp430_common/include/stdatomic.h +++ b/cpu/msp430_common/include/stdatomic.h @@ -308,7 +308,7 @@ typedef _Atomic(uintmax_t) atomic_uintmax_t; #if __has_builtin(__sync_swap) /* Clang provides a full-barrier atomic exchange - use it if available. */ #define atomic_exchange_explicit(object, desired, order) \ - ((void)(order), __sync_swap(&(object)->__val, desired)) + __sync_swap(&(object)->__val, desired) #else /* * __sync_lock_test_and_set() is only an acquire barrier in theory (although in @@ -324,20 +324,21 @@ __extension__ ({ \ __sync_lock_test_and_set(&(__o)->__val, __d); \ }) #endif +/* Ignoring the order argument when using __sync builtins */ #define atomic_fetch_add_explicit(object, operand, order) \ - ((void)(order), __sync_fetch_and_add(&(object)->__val, \ - __atomic_apply_stride(object, operand))) + __sync_fetch_and_add(&(object)->__val, \ + __atomic_apply_stride(object, operand)) #define atomic_fetch_and_explicit(object, operand, order) \ - ((void)(order), __sync_fetch_and_and(&(object)->__val, operand)) + __sync_fetch_and_and(&(object)->__val, operand) #define atomic_fetch_or_explicit(object, operand, order) \ - ((void)(order), __sync_fetch_and_or(&(object)->__val, operand)) + __sync_fetch_and_or(&(object)->__val, operand) #define atomic_fetch_sub_explicit(object, operand, order) \ - ((void)(order), __sync_fetch_and_sub(&(object)->__val, \ - __atomic_apply_stride(object, operand))) + __sync_fetch_and_sub(&(object)->__val, \ + __atomic_apply_stride(object, operand)) #define atomic_fetch_xor_explicit(object, operand, order) \ - ((void)(order), __sync_fetch_and_xor(&(object)->__val, operand)) + __sync_fetch_and_xor(&(object)->__val, operand) #define atomic_load_explicit(object, order) \ - ((void)(order), __sync_fetch_and_add(&(object)->__val, 0)) + __sync_fetch_and_add(&(object)->__val, 0) #define atomic_store_explicit(object, desired, order) \ ((void)atomic_exchange_explicit(object, desired, order)) #endif diff --git a/cpu/msp430_common/include/stdio.h b/cpu/msp430_common/include/stdio.h index 3e13eb90a8e3..8cfb4cd38ef2 100644 --- a/cpu/msp430_common/include/stdio.h +++ b/cpu/msp430_common/include/stdio.h @@ -31,6 +31,10 @@ extern "C" { #endif +#define SEEK_SET 0 /* Seek from beginning of file. */ +#define SEEK_CUR 1 /* Seek from current position. */ +#define SEEK_END 2 /* Seek from end of file. */ + int getchar(void); #ifdef __cplusplus diff --git a/cpu/msp430_common/include/sys/stat.h b/cpu/msp430_common/include/sys/stat.h new file mode 100644 index 000000000000..e87823272e07 --- /dev/null +++ b/cpu/msp430_common/include/sys/stat.h @@ -0,0 +1,128 @@ +/* + * 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 POSIX compatible sys/stat.h definitions + * @author Joakim Nohlgård + */ + +/* If building on native we need to use the system libraries instead */ +#ifdef CPU_NATIVE +#pragma GCC system_header +/* without the GCC pragma above #include_next will trigger a pedantic error */ +#include_next +#else +#ifndef SYS_STAT_H_ +#define SYS_STAT_H_ + +#include /* for struct timespec */ +#include /* for fsblkcnt_t, fsfilcnt_t */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief File information + */ +struct stat { + dev_t st_dev; /**< Device ID of device containing file. */ + ino_t st_ino; /**< File serial number. */ + mode_t st_mode; /**< Mode of file (see below). */ + nlink_t st_nlink; /**< Number of hard links to the file. */ + uid_t st_uid; /**< User ID of file. */ + gid_t st_gid; /**< Group ID of file. */ + dev_t st_rdev; /**< Device ID (if file is character or block special). */ + /** + * For regular files, the file size in bytes. + * For symbolic links, the length in bytes of the pathname contained in the + * symbolic link. + * For a shared memory object, the length in bytes. + * For a typed memory object, the length in bytes. + * For other file types, the use of this field is unspecified. + */ + off_t st_size; + struct timespec st_atim; /**< Last data access timestamp. */ + struct timespec st_mtim; /**< Last data modification timestamp. */ + struct timespec st_ctim; /**< Last file status change timestamp. */ + /** + * A file system-specific preferred I/O block size for this object. In some + * file system types, this may vary from file to file. + */ + blksize_t st_blksize; + blkcnt_t st_blocks; /**< Number of blocks allocated for this object. */ +}; + +/* These bitmasks and numbers are the same as in newlib */ +#define S_IFMT 0170000 /* type of file */ +#define S_IFDIR 0040000 /* directory */ +#define S_IFCHR 0020000 /* character special */ +#define S_IFBLK 0060000 /* block special */ +#define S_IFREG 0100000 /* regular */ +#define S_IFLNK 0120000 /* symbolic link */ +#define S_IFSOCK 0140000 /* socket */ +#define S_IFIFO 0010000 /* fifo */ + +/* These numbers are well-known and can be found in the manual page for sys_stat.h */ +#define S_IRWXU 0700 /**< Read, write, execute/search by owner. */ +#define S_IRUSR 0400 /**< Read permission, owner. */ +#define S_IWUSR 0200 /**< Write permission, owner. */ +#define S_IXUSR 0100 /**< Execute/search permission, owner. */ +#define S_IRWXG 070 /**< Read, write, execute/search by group. */ +#define S_IRGRP 040 /**< Read permission, group. */ +#define S_IWGRP 020 /**< Write permission, group. */ +#define S_IXGRP 010 /**< Execute/search permission, group. */ +#define S_IRWXO 07 /**< Read, write, execute/search by others. */ +#define S_IROTH 04 /**< Read permission, others. */ +#define S_IWOTH 02 /**< Write permission, others. */ +#define S_IXOTH 01 /**< Execute/search permission, others. */ +#define S_ISUID 04000 /**< Set-user-ID on execution. */ +#define S_ISGID 02000 /**< Set-group-ID on execution. */ +#define S_ISVTX 01000 /**< On directories, restricted deletion flag */ + +/* File type test macros, taken from newlib */ +#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) +#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) +#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) + +/* These function prototypes are required by the standard */ +int chmod(const char *, mode_t); +int fchmod(int, mode_t); +int fchmodat(int, const char *, mode_t, int); +int fstat(int, struct stat *); +int fstatat(int, const char *restrict, struct stat *restrict, int); +int futimens(int, const struct timespec [2]); +int lstat(const char *restrict, struct stat *restrict); +int mkdir(const char *, mode_t); +int mkdirat(int, const char *, mode_t); +int mkfifo(const char *, mode_t); +int mkfifoat(int, const char *, mode_t); +int mknod(const char *, mode_t, dev_t); +int mknodat(int, const char *, mode_t, dev_t); +int stat(const char *restrict, struct stat *restrict); +mode_t umask(mode_t); +int utimensat(int, const char *, const struct timespec [2], int); + +/* Special tv_nsec values for futimens(2) and utimensat(2). */ +#define UTIME_NOW (-2L) +#define UTIME_OMIT (-1L) + +#ifdef __cplusplus +} +#endif + +#endif /* SYS_STAT_H_ */ + +#endif /* CPU_NATIVE */ + +/** @} */ diff --git a/cpu/msp430_common/include/unistd.h b/cpu/msp430_common/include/unistd.h index 234c89bb142f..39049c605f99 100644 --- a/cpu/msp430_common/include/unistd.h +++ b/cpu/msp430_common/include/unistd.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2016 Eistec AB * Copyright (C) 2015 Kaspar Schleiser * * This file is subject to the terms and conditions of the GNU Lesser @@ -9,23 +10,98 @@ #ifndef UNISTD_H #define UNISTD_H +#include #include +#include #ifdef __cplusplus extern "C" { #endif -#include "msp430_types.h" +#define STDIN_FILENO 0 /* standard input file descriptor */ +#define STDOUT_FILENO 1 /* standard output file descriptor */ +#define STDERR_FILENO 2 /* standard error file descriptor */ -#define STDIN_FILENO 0 ///< stdin file descriptor -#define STDOUT_FILENO 1 ///< stdout file descriptor -#define STDERR_FILENO 2 ///< stderr file descriptor +#define F_OK 0 +#define R_OK 4 +#define W_OK 2 +#define X_OK 1 -int close(int fildes); +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 -typedef uint32_t useconds_t; -int usleep(useconds_t usec); -unsigned int sleep(unsigned int seconds); +int access(const char *, int); +unsigned alarm(unsigned); +int chdir(const char *); +int chown(const char *, uid_t, gid_t); +int close(int); +size_t confstr(int, char *, size_t); +int dup(int); +int dup2(int, int); +void _exit(int); +int execl(const char *, const char *, ...); +int execle(const char *, const char *, ...); +int execlp(const char *, const char *, ...); +int execv(const char *, char *const []); +int execve(const char *, char *const [], char *const []); +int execvp(const char *, char *const []); +int faccessat(int, const char *, int, int); +int fchdir(int); +int fchown(int, uid_t, gid_t); +int fchownat(int, const char *, uid_t, gid_t, int); +int fexecve(int, char *const [], char *const []); +pid_t fork(void); +long fpathconf(int, int); +int ftruncate(int, off_t); +char *getcwd(char *, size_t); +gid_t getegid(void); +uid_t geteuid(void); +gid_t getgid(void); +int getgroups(int, gid_t []); +int gethostname(char *, size_t); +char *getlogin(void); +int getlogin_r(char *, size_t); +int getopt(int, char * const [], const char *); +pid_t getpgid(pid_t); +pid_t getpgrp(void); +pid_t getpid(void); +pid_t getppid(void); +pid_t getsid(pid_t); +uid_t getuid(void); +int isatty(int); +int lchown(const char *, uid_t, gid_t); +int link(const char *, const char *); +int linkat(int, const char *, int, const char *, int); +off_t lseek(int, off_t, int); +long pathconf(const char *, int); +int pause(void); +int pipe(int [2]); +ssize_t pread(int, void *, size_t, off_t); +ssize_t pwrite(int, const void *, size_t, off_t); +ssize_t read(int, void *, size_t); +ssize_t readlink(const char *restrict, char *restrict, size_t); +ssize_t readlinkat(int, const char *restrict, char *restrict, size_t); +int rmdir(const char *); +int setegid(gid_t); +int seteuid(uid_t); +int setgid(gid_t); +int setpgid(pid_t, pid_t); +pid_t setsid(void); +int setuid(uid_t); +unsigned sleep(unsigned); +int symlink(const char *, const char *); +int symlinkat(const char *, int, const char *); +long sysconf(int); +pid_t tcgetpgrp(int); +int tcsetpgrp(int, pid_t); +int truncate(const char *, off_t); +char *ttyname(int); +int ttyname_r(int, char *, size_t); +int unlink(const char *); +int unlinkat(int, const char *, int); +int usleep(useconds_t); +ssize_t write(int, const void *, size_t); #ifdef __cplusplus } diff --git a/cpu/msp430_common/msp430-main.c b/cpu/msp430_common/msp430-main.c index 9577e60727c5..a91a288a87f3 100644 --- a/cpu/msp430_common/msp430-main.c +++ b/cpu/msp430_common/msp430-main.c @@ -177,4 +177,15 @@ splx_(int sr) } /*---------------------------------------------------------------------------*/ +size_t strnlen(const char *s, size_t maxlen) +{ + size_t len; + + for (len = 0; len < maxlen; len++, s++) { + if (!*s) + break; + } + return (len); +} + extern void board_init(void); diff --git a/cpu/native/Makefile b/cpu/native/Makefile index c601d3323e72..d66f5a971ea0 100644 --- a/cpu/native/Makefile +++ b/cpu/native/Makefile @@ -1,6 +1,7 @@ MODULE = cpu DIRS += periph +DIRS += vfs ifneq (,$(filter netdev2_tap,$(USEMODULE))) DIRS += netdev2_tap diff --git a/cpu/native/vfs/Makefile b/cpu/native/vfs/Makefile new file mode 100644 index 000000000000..e3b46ebd7f4e --- /dev/null +++ b/cpu/native/vfs/Makefile @@ -0,0 +1,3 @@ +MODULE=native_vfs + +include $(RIOTBASE)/Makefile.base diff --git a/cpu/native/vfs/native_vfs.c b/cpu/native/vfs/native_vfs.c new file mode 100644 index 000000000000..d3e18cc394ca --- /dev/null +++ b/cpu/native/vfs/native_vfs.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2016 Kaspar Schleiser + * + * 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 native_cpu + * @{ + * + * @file + * @brief VFS wrappers for POSIX file I/O functions + * + * @author Kaspar Schleiser + */ + +#include +#include +#include +#include +#include + +#include "vfs.h" + +int open(const char *name, int flags, ...) +{ + unsigned mode = 0; + + if ((flags & O_CREAT)) { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, unsigned); + va_end(ap); + } + + int fd = vfs_open(name, flags, mode); + if (fd < 0) { + /* vfs returns negative error codes */ + errno = -fd; + return -1; + } + return fd; +} + +ssize_t read(int fd, void *dest, size_t count) +{ + int res = vfs_read(fd, dest, count); + + if (res < 0) { + /* vfs returns negative error codes */ + errno = -res; + return -1; + } + return res; +} + +ssize_t write(int fd, const void *src, size_t count) +{ + int res = vfs_write(fd, src, count); + + if (res < 0) { + /* vfs returns negative error codes */ + errno = -res; + return -1; + } + return res; +} + +int close(int fd) +{ + int res = vfs_close(fd); + + if (res < 0) { + /* vfs returns negative error codes */ + errno = -res; + return -1; + } + return res; +} + +int fcntl(int fd, int cmd, ...) +{ + unsigned long arg; + va_list ap; + va_start(ap, cmd); + arg = va_arg(ap, unsigned long); + va_end(ap); + + int res = vfs_fcntl(fd, cmd, arg); + + if (res < 0) { + /* vfs returns negative error codes */ + errno = -res; + return -1; + } + return res; +} + + +off_t lseek(int fd, off_t off, int whence) +{ + int res = vfs_lseek(fd, off, whence); + + if (res < 0) { + /* vfs returns negative error codes */ + errno = -res; + return -1; + } + return res; +} + +int fstat(int fd, struct stat *buf) +{ + int res = vfs_fstat(fd, buf); + + if (res < 0) { + /* vfs returns negative error codes */ + errno = -res; + return -1; + } + return 0; +} + +int stat(const char *name, struct stat *st) +{ + int res = vfs_stat(name, st); + + if (res < 0) { + /* vfs returns negative error codes */ + errno = -res; + return -1; + } + return 0; +} + +int unlink(const char *path) +{ + int res = vfs_unlink(path); + + if (res < 0) { + /* vfs returns negative error codes */ + errno = -res; + return -1; + } + return 0; +} + +/** @} */ diff --git a/dist/tools/externc/check.sh b/dist/tools/externc/check.sh index cdb1188f9336..67c807403d6e 100755 --- a/dist/tools/externc/check.sh +++ b/dist/tools/externc/check.sh @@ -36,7 +36,7 @@ for FILE in ${FILES}; do | sed -e 's/$/ /' \ | tr -d '\r\n' \ | sed -e 's/ */ /g' \ - | grep -v -q '#ifdef __cplusplus extern "C" { #endif'; \ + | grep -v -q '#ifdef __cplusplus extern "C" {'; \ then EXIT_CODE=1 echo "file does not have a C++ compatible header: '${FILE}'" diff --git a/dist/tools/mkconstfs/README.md b/dist/tools/mkconstfs/README.md new file mode 100644 index 000000000000..c71a69ba0402 --- /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 000000000000..bda89c76d888 --- /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/doc/doxygen/riot.doxyfile b/doc/doxygen/riot.doxyfile index 1e3a4a7e1197..5925eaa27b01 100644 --- a/doc/doxygen/riot.doxyfile +++ b/doc/doxygen/riot.doxyfile @@ -818,6 +818,7 @@ EXCLUDE_SYMLINKS = NO # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = */board/*/tools/* \ + */cpu/atmega_common/include/sys/*.h \ */cpu/msp430_common/include/stdatomic.h \ */cpu/msp430_common/include/sys/*.h \ */cpu/native/osx-libc-extra \ diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index 558ff0e35a6d..67b72ba398fd 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -150,6 +150,7 @@ ifneq (,$(filter nrfmin,$(USEMODULE))) endif ifneq (,$(filter nvram_spi,$(USEMODULE))) + USEMODULE += nvram USEMODULE += xtimer endif diff --git a/drivers/include/nvram.h b/drivers/include/nvram.h index 6bc79feff6d0..4eab01be30c4 100644 --- a/drivers/include/nvram.h +++ b/drivers/include/nvram.h @@ -30,6 +30,10 @@ #include #include +#if MODULE_VFS +#include "vfs.h" +#endif + #ifdef __cplusplus extern "C" { #endif @@ -79,6 +83,10 @@ typedef struct nvram { void *extra; } nvram_t; +#if MODULE_VFS +extern const vfs_file_ops_t nvram_vfs_ops; +#endif + #ifdef __cplusplus } #endif diff --git a/drivers/nvram/Makefile b/drivers/nvram/Makefile new file mode 100644 index 000000000000..48055ad6339b --- /dev/null +++ b/drivers/nvram/Makefile @@ -0,0 +1,3 @@ +MODULE = nvram + +include $(RIOTBASE)/Makefile.base diff --git a/drivers/nvram/nvram-vfs.c b/drivers/nvram/nvram-vfs.c new file mode 100644 index 000000000000..9118ae60d527 --- /dev/null +++ b/drivers/nvram/nvram-vfs.c @@ -0,0 +1,136 @@ +/* + * 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. + */ + +#if MODULE_VFS + +#include +#include +#include + +#include "nvram.h" +#include "vfs.h" + +/** + * @ingroup nvram + * @{ + * + * @file + * + * @brief NVRAM generic VFS operations + * + * This allows the nvram driver to register as a node on DevFS + * + * See boards/mulle or tests/unittests/tests-devfs for examples on how to use. + * + * Tested with nvram_spi on Mulle + * + * @author Joakim Nohlgård + */ + +static int nvram_vfs_fstat(vfs_file_t *filp, struct stat *buf); +static off_t nvram_vfs_lseek(vfs_file_t *filp, off_t off, int whence); +static ssize_t nvram_vfs_read(vfs_file_t *filp, void *dest, size_t nbytes); +static ssize_t nvram_vfs_write(vfs_file_t *filp, const void *src, size_t nbytes); + +const vfs_file_ops_t nvram_vfs_ops = { + .fstat = nvram_vfs_fstat, + .lseek = nvram_vfs_lseek, + .read = nvram_vfs_read, + .write = nvram_vfs_write, +}; + +static int nvram_vfs_fstat(vfs_file_t *filp, struct stat *buf) +{ + if (buf == NULL) { + return -EFAULT; + } + nvram_t *dev = filp->private_data.ptr; + if (dev == NULL) { + return -EFAULT; + } + buf->st_nlink = 1; + buf->st_size = dev->size; + return 0; +} + +static off_t nvram_vfs_lseek(vfs_file_t *filp, off_t off, int whence) +{ + nvram_t *dev = filp->private_data.ptr; + if (dev == NULL) { + return -EFAULT; + } + switch (whence) { + case SEEK_SET: + break; + case SEEK_CUR: + off += filp->pos; + break; + case SEEK_END: + off += dev->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 */ + filp->pos = off; + return off; +} + +static ssize_t nvram_vfs_read(vfs_file_t *filp, void *dest, size_t nbytes) +{ + nvram_t *dev = filp->private_data.ptr; + if (dev == NULL) { + return -EFAULT; + } + uint32_t src = filp->pos; + if (src >= dev->size) { + return 0; + } + if (src + nbytes > dev->size) { + nbytes = dev->size - src; + } + int res = dev->read(dev, dest, src, nbytes); + if (res < 0) { + return res; + } + /* Advance file position */ + filp->pos += res; + return res; +} + +static ssize_t nvram_vfs_write(vfs_file_t *filp, const void *src, size_t nbytes) +{ + nvram_t *dev = filp->private_data.ptr; + if (dev == NULL) { + return -EFAULT; + } + uint32_t dest = filp->pos; + if (dest >= dev->size) { + return 0; + } + if (dest + nbytes > dev->size) { + nbytes = dev->size - dest; + } + int res = dev->write(dev, src, dest, nbytes); + if (res < 0) { + return res; + } + /* Advance file position */ + filp->pos += res; + return res; +} + +/** @} */ + +#else +typedef int dont_be_pedantic; +#endif /* MODULE_VFS */ diff --git a/sys/Makefile b/sys/Makefile index 80ebe4ee284e..78a120f68561 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -112,6 +112,14 @@ ifneq (,$(filter emcute,$(USEMODULE))) DIRS += net/application_layer/emcute endif +ifneq (,$(filter constfs,$(USEMODULE))) + DIRS += fs/constfs +endif + +ifneq (,$(filter devfs,$(USEMODULE))) + DIRS += fs/devfs +endif + DIRS += $(dir $(wildcard $(addsuffix /Makefile, ${USEMODULE}))) include $(RIOTBASE)/Makefile.base diff --git a/sys/Makefile.include b/sys/Makefile.include index 7a460af0f8c2..680f655b17bb 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/auto_init/auto_init.c b/sys/auto_init/auto_init.c index c7813baf1ead..5169fa97d94f 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -152,6 +152,11 @@ void auto_init(void) DEBUG("Auto init gcoap module.\n"); gcoap_init(); #endif +#ifdef MODULE_DEVFS + DEBUG("Mounting /dev\n"); + extern void auto_init_devfs(void); + auto_init_devfs(); +#endif /* initialize network devices */ #ifdef MODULE_AUTO_INIT_GNRC_NETIF diff --git a/sys/fs/constfs/Makefile b/sys/fs/constfs/Makefile new file mode 100644 index 000000000000..9798aac2d4e6 --- /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 000000000000..9b29a08a19fb --- /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/devfs/Makefile b/sys/fs/devfs/Makefile new file mode 100644 index 000000000000..67d6ef1eb3cd --- /dev/null +++ b/sys/fs/devfs/Makefile @@ -0,0 +1,2 @@ +MODULE=devfs +include $(RIOTBASE)/Makefile.base diff --git a/sys/fs/devfs/auto_init_devfs.c b/sys/fs/devfs/auto_init_devfs.c new file mode 100644 index 000000000000..9de9b57057c4 --- /dev/null +++ b/sys/fs/devfs/auto_init_devfs.c @@ -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. + * + */ + +/** + * @ingroup auto_init_fs + * @{ + * + * @file + * @brief Automatic mount of DevFS on /dev + * + * @author Joakim Nohlgård + * + * @} + */ + +#include "vfs.h" +#include "fs/devfs.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +static vfs_mount_t _devfs_auto_init_mount = { + .fs = &devfs_file_system, + .mount_point = "/dev", +}; + +void auto_init_devfs(void) +{ + DEBUG("auto_init_devfs: mounting /dev\n"); + vfs_mount(&_devfs_auto_init_mount); +} diff --git a/sys/fs/devfs/devfs.c b/sys/fs/devfs/devfs.c new file mode 100644 index 000000000000..07dc70b82663 --- /dev/null +++ b/sys/fs/devfs/devfs.c @@ -0,0 +1,242 @@ +/* + * 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_devfs + * @{ + * + * @file + * @brief DevFS 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 "fs/devfs.h" +#include "vfs.h" +#include "mutex.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/** + * @internal + * @brief DevFS list head + * + * DevFS operates as a singleton, the same files show up in all mounted instances. + */ +static clist_node_t _devfs_list; +/** + * @internal + * @brief mutex to protect the DevFS list from corruption + */ +static mutex_t _devfs_mutex = MUTEX_INIT; + +/* No need for file system operations, no extra work to be done on + * mount/umount. unlink is not permitted, use devfs_unregister instead */ + +/* File operations */ +/* open is overloaded to allow searching for the correct device */ +static int devfs_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path); +/* A minimal fcntl is also provided to enable SETFL handling */ +static int devfs_fcntl(vfs_file_t *filp, int cmd, int arg); + +/* Directory operations */ +static int devfs_opendir(vfs_DIR *dirp, const char *dirname, const char *abs_path); +static int devfs_readdir(vfs_DIR *dirp, vfs_dirent_t *entry); +static int devfs_closedir(vfs_DIR *dirp); + +static const vfs_file_ops_t devfs_file_ops = { + .open = devfs_open, + .fcntl = devfs_fcntl, +}; + +static const vfs_dir_ops_t devfs_dir_ops = { + .opendir = devfs_opendir, + .readdir = devfs_readdir, + .closedir = devfs_closedir, +}; + +const vfs_file_system_t devfs_file_system = { + .f_op = &devfs_file_ops, + .d_op = &devfs_dir_ops, +}; + +static int devfs_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path) +{ + DEBUG("devfs_open: %p, \"%s\", 0x%x, 0%03lo, \"%s\"\n", (void *)filp, name, flags, (unsigned long)mode, abs_path); + /* linear search through the device list */ + mutex_lock(&_devfs_mutex); + clist_node_t *it = _devfs_list.next; + if (it == NULL) { + /* list empty */ + mutex_unlock(&_devfs_mutex); + return -ENOENT; + } + do { + it = it->next; + devfs_t *devp = container_of(it, devfs_t, list_entry); + if (strcmp(devp->path, name) == 0) { + mutex_unlock(&_devfs_mutex); + DEBUG("devfs_open: Found :)\n"); + /* Add private data from DevFS node */ + filp->private_data.ptr = devp->private_data; + /* Replace f_op with the operations provided by the device driver */ + filp->f_op = devp->f_op; + /* Chain the open() method for the specific device */ + if (filp->f_op->open != NULL) { + return filp->f_op->open(filp, name, flags, mode, abs_path); + } + return 0; + } + } while (it != _devfs_list.next); + mutex_unlock(&_devfs_mutex); + DEBUG("devfs_open: Not found :(\n"); + return -ENOENT; +} + +static int devfs_fcntl(vfs_file_t *filp, int cmd, int arg) +{ + DEBUG("devfs_fcntl: %p, 0x%x, 0x%x\n", (void *)filp, cmd, arg); + switch (cmd) { + /* F_GETFL is handled directly by vfs_fcntl */ + case F_SETFL: + DEBUG("devfs_fcntl: SETFL: %d\n", arg); + filp->flags = arg; + return filp->flags; + default: + return -EINVAL; + } +} + +static int devfs_opendir(vfs_DIR *dirp, const char *dirname, const char *abs_path) +{ + (void) abs_path; + DEBUG("devfs_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.ptr = NULL; + return 0; +} + +static int devfs_readdir(vfs_DIR *dirp, vfs_dirent_t *entry) +{ + DEBUG("devfs_readdir: %p, %p\n", (void *)dirp, (void *)entry); + mutex_lock(&_devfs_mutex); + clist_node_t *it = dirp->private_data.ptr; + if (it == _devfs_list.next) { + /* end of list was reached */ + mutex_unlock(&_devfs_mutex); + return 0; + } + if (it == NULL) { + /* first readdir after opendir */ + it = _devfs_list.next; + if (it == NULL) { + /* empty list */ + mutex_unlock(&_devfs_mutex); + return 0; + } + } + it = it->next; + dirp->private_data.ptr = it; + mutex_unlock(&_devfs_mutex); + devfs_t *devp = container_of(it, devfs_t, list_entry); + if (devp->path == NULL) { + /* skip past the broken entry and try again */ + return -EAGAIN; + } + size_t len = strnlen(devp->path, VFS_NAME_MAX + 1); + if (len > VFS_NAME_MAX) { + /* name does not fit in vfs_dirent_t buffer */ + /* skip past the broken entry and try again */ + return -EAGAIN; + } + /* clear the dirent */ + memset(entry, 0, sizeof(*entry)); + /* copy the string, including terminating null */ + memcpy(&entry->d_name[0], devp->path, len + 1); + return 1; +} + +static int devfs_closedir(vfs_DIR *dirp) +{ + /* Just an example, it's not necessary to define closedir if there is + * nothing to clean up */ + (void) dirp; + DEBUG("devfs_closedir: %p\n", (void *)dirp); + return 0; +} + +int devfs_register(devfs_t *devp) +{ + DEBUG("devfs_register: %p\n", (void *)devp); + if (devp == NULL) { + return -EINVAL; + } + DEBUG("devfs_register: \"%s\" -> (%p, %p)\n", devp->path, (void *)devp->f_op, devp->private_data); + if (devp->path == NULL) { + return -EINVAL; + } + if (devp->f_op == NULL) { + return -EINVAL; + } + mutex_lock(&_devfs_mutex); + clist_node_t *it = _devfs_list.next; + if (it != NULL) { + /* list not empty */ + do { + it = it->next; + if (it == &devp->list_entry) { + /* Already registered */ + mutex_unlock(&_devfs_mutex); + DEBUG("devfs_register: %p already registered\n", (void *)devp); + return -EEXIST; + } + devfs_t *devit = container_of(it, devfs_t, list_entry); + if (strcmp(devit->path, devp->path) == 0) { + /* Path already registered */ + mutex_unlock(&_devfs_mutex); + DEBUG("devfs_register: \"%s\" occupied\n", devp->path); + return -EEXIST; + } + } while(it != _devfs_list.next); + } + /* insert last in list */ + clist_rpush(&_devfs_list, &devp->list_entry); + mutex_unlock(&_devfs_mutex); + return 0; +} + +int devfs_unregister(devfs_t *devp) +{ + DEBUG("devfs_unregister: %p\n", (void *)devp); + if (devp == NULL) { + return -EINVAL; + } + mutex_lock(&_devfs_mutex); + /* find devp in the list and remove it */ + clist_node_t *node = clist_remove(&_devfs_list, &devp->list_entry); + mutex_unlock(&_devfs_mutex); + if (node == NULL) { + /* not found */ + DEBUG("devfs_unregister: ERR not registered!\n"); + return -ENOENT; + } + return 0; +} diff --git a/sys/fs/doc.txt b/sys/fs/doc.txt new file mode 100644 index 000000000000..f20b48aac5a8 --- /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 000000000000..a0286e6dea7f --- /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/fs/devfs.h b/sys/include/fs/devfs.h new file mode 100644 index 000000000000..f5c4260edd67 --- /dev/null +++ b/sys/include/fs/devfs.h @@ -0,0 +1,94 @@ +/* + * 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_devfs DevFS device file system + * @ingroup fs + * @brief Dynamic device file system + * + * This file system implementation allows devices to register file names for + * easier access to device drivers from shell commands etc. + * + * The idea is similar to the /dev directory on Unix. + * + * @{ + * @file + * @brief DevFS public API + * @author Joakim Nohlgård + */ + +#ifndef DEVFS_H_ +#define DEVFS_H_ + +#include "clist.h" +#include "vfs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief DevFS node typedef + */ +typedef struct devfs devfs_t; + +/** + * @brief A device "file" consists of a file name and an opaque pointer to device driver private data + * + * The file system is implemented as a linked list. + */ +struct devfs { + clist_node_t list_entry; /**< List item entry */ + const char *path; /**< File system relative path to this node */ + const vfs_file_ops_t *f_op; /**< Pointer to file operations table for this device */ + void *private_data; /**< Pointer to device driver specific data */ +}; + +/** + * @brief DevFS file system driver + * + * For use with vfs_mount + */ +extern const vfs_file_system_t devfs_file_system; + +/** + * @brief Register a node in DevFS + * + * The node will immediately become available to @c vfs_open, if DevFS is already + * mounted somewhere. + * + * If DevFS is not mounted, the node will be registered and will become + * available to @c vfs_open when DevFS is mounted. + * + * @param[in] node DevFS node to register + * + * @return 0 on success + * @return <0 on error + */ +int devfs_register(devfs_t *node); + +/** + * @brief Remove a registration from DevFS + * + * The node will no longer be available to @c vfs_open, but any already opened FDs + * will remain open. + * + * @param[in] node DevFS node to unregister + * + * @return 0 on success + * @return <0 on error + */ +int devfs_unregister(devfs_t *node); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ diff --git a/sys/include/vfs.h b/sys/include/vfs.h new file mode 100644 index 000000000000..60c36420cecc --- /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/newlib/syscalls.c b/sys/newlib/syscalls.c index d44d4c9e7b8e..c2e1a9e7b962 100644 --- a/sys/newlib/syscalls.c +++ b/sys/newlib/syscalls.c @@ -39,6 +39,9 @@ #include "irq.h" #include "log.h" #include "periph/pm.h" +#if MODULE_VFS +#include "vfs.h" +#endif #include "uart_stdio.h" @@ -152,137 +155,307 @@ int _kill_r(struct _reent *r, pid_t pid, int sig) return -1; } +#if MODULE_VFS /** * @brief Open a file * - * @param r TODO - * @param name TODO - * @param mode TODO + * This is a wrapper around @c vfs_open * - * @return TODO + * @param r pointer to reent structure + * @param name file name to open + * @param flags flags, see man 3p open + * @param mode mode, file creation mode if the file is created when opening + * + * @return fd number (>= 0) on success + * @return -1 on error, @c r->_errno set to a constant from errno.h to indicate the error */ int _open_r(struct _reent *r, const char *name, int flags, int mode) { - (void) name; - (void) flags; - (void) mode; - r->_errno = ENODEV; /* not implemented yet */ - return -1; + int fd = vfs_open(name, flags, mode); + if (fd < 0) { + /* vfs returns negative error codes */ + r->_errno = -fd; + return -1; + } + return fd; } /** - * @brief Read from a file + * @brief Read bytes from an open file * - * All input is read from UART_0. The function will block until a byte is actually read. + * This is a wrapper around @c vfs_read * - * Note: the read function does not buffer - data will be lost if the function is not - * called fast enough. + * @param[in] r pointer to reent structure + * @param[in] fd open file descriptor obtained from @c open() + * @param[out] dest destination buffer + * @param[in] count maximum number of bytes to read * - * TODO: implement more sophisticated read call. + * @return number of bytes read on success + * @return -1 on error, @c r->_errno set to a constant from errno.h to indicate the error + */ +_ssize_t _read_r(struct _reent *r, int fd, void *dest, size_t count) +{ + int res = vfs_read(fd, dest, count); + if (res < 0) { + /* vfs returns negative error codes */ + r->_errno = -res; + return -1; + } + return res; +} + +/** + * @brief Write bytes to an open file * - * @param r TODO - * @param fd TODO - * @param buffer TODO - * @param int TODO + * This is a wrapper around @c vfs_write * - * @return TODO + * @param[in] r pointer to reent structure + * @param[in] fd open file descriptor obtained from @c open() + * @param[in] src source data buffer + * @param[in] count maximum number of bytes to write + * + * @return number of bytes written on success + * @return -1 on error, @c r->_errno set to a constant from errno.h to indicate the error */ -_ssize_t _read_r(struct _reent *r, int fd, void *buffer, size_t count) +_ssize_t _write_r(struct _reent *r, int fd, const void *src, size_t count) { - (void)r; - (void)fd; - return uart_stdio_read(buffer, count); + int res = vfs_write(fd, src, count); + if (res < 0) { + /* vfs returns negative error codes */ + r->_errno = -res; + return -1; + } + return res; } /** - * @brief Write characters to a file + * @brief Close an open file * - * All output is currently directed to UART_0, independent of the given file descriptor. - * The write call will further block until the byte is actually written to the UART. + * This is a wrapper around @c vfs_close * - * TODO: implement more sophisticated write call. + * If this call returns an error, the fd should still be considered invalid and + * no further attempt to use it shall be made, not even to retry @c close() * - * @param r TODO - * @param fd TODO - * @param data TODO - * @param int TODO + * @param[in] r pointer to reent structure + * @param[in] fd open file descriptor obtained from @c open() * - * @return TODO + * @return 0 on success + * @return -1 on error, @c r->_errno set to a constant from errno.h to indicate the error */ -_ssize_t _write_r(struct _reent *r, int fd, const void *data, size_t count) +int _close_r(struct _reent *r, int fd) { - (void) r; - (void) fd; - return uart_stdio_write(data, count); + int res = vfs_close(fd); + if (res < 0) { + /* vfs returns negative error codes */ + r->_errno = -res; + return -1; + } + return res; } /** - * @brief Close a file + * @brief Query or set options on an open file * - * @param r TODO - * @param fd TODO + * This is a wrapper around @c vfs_fcntl * - * @return TODO + * @param[in] r pointer to reent structure + * @param[in] fd open file descriptor obtained from @c open() + * @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 -1 on error, @c r->_errno set to a constant from errno.h to indicate the error */ -int _close_r(struct _reent *r, int fd) +int _fcntl_r (struct _reent *r, int fd, int cmd, int arg) { - (void) fd; - r->_errno = ENODEV; /* not implemented yet */ - return -1; + int res = vfs_fcntl(fd, cmd, arg); + if (res < 0) { + /* vfs returns negative error codes */ + r->_errno = -res; + return -1; + } + return res; } /** - * @brief Set position in a file + * @brief Seek to position in file * - * @param r TODO - * @param fd TODO - * @param pos TODO - * @param dir TODO + * This is a wrapper around @c vfs_lseek * - * @return TODO + * @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] r pointer to reent structure + * @param[in] fd open file descriptor obtained from @c 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 -1 on error, @c r->_errno set to a constant from errno.h to indicate the error */ -_off_t _lseek_r(struct _reent *r, int fd, _off_t pos, int dir) +_off_t _lseek_r(struct _reent *r, int fd, _off_t off, int whence) { - (void) fd; - (void) pos; - (void) dir; - r->_errno = ENODEV; /* not implemented yet */ - return -1; + int res = vfs_lseek(fd, off, whence); + if (res < 0) { + /* vfs returns negative error codes */ + r->_errno = -res; + return -1; + } + return res; } /** - * @brief Status of an open file + * @brief Get status of an open file * - * @param r TODO - * @param fd TODO - * @param stat TODO + * This is a wrapper around @c vfs_fstat * - * @return TODO + * @param[in] r pointer to reent structure + * @param[in] fd open file descriptor obtained from @c open() + * @param[out] buf pointer to stat struct to fill + * + * @return 0 on success + * @return -1 on error, @c r->_errno set to a constant from errno.h to indicate the error */ -int _fstat_r(struct _reent *r, int fd, struct stat *st) +int _fstat_r(struct _reent *r, int fd, struct stat *buf) { - (void) fd; - (void) st; - r->_errno = ENODEV; /* not implemented yet */ - return -1; + int res = vfs_fstat(fd, buf); + if (res < 0) { + /* vfs returns negative error codes */ + r->_errno = -res; + return -1; + } + return 0; } /** * @brief Status of a file (by name) * - * @param r TODO - * @param name TODO - * @param stat TODO + * This is a wrapper around @c vfs_fstat * - * @return TODO + * @param[in] r pointer to reent structure + * @param[in] name path to file + * @param[out] buf pointer to stat struct to fill + * + * @return 0 on success + * @return -1 on error, @c r->_errno set to a constant from errno.h to indicate the error + */ +int _stat_r(struct _reent *r, const char *name, struct stat *st) +{ + int res = vfs_stat(name, st); + if (res < 0) { + /* vfs returns negative error codes */ + r->_errno = -res; + return -1; + } + return 0; +} + +/** + * @brief Unlink (delete) a file + * + * @param[in] r pointer to reent structure + * @param[in] path path to file to be deleted + * + * @return 0 on success + * @return -1 on error, @c r->_errno set to a constant from errno.h to indicate the error */ +int _unlink_r(struct _reent *r, const char *path) +{ + int res = vfs_unlink(path); + if (res < 0) { + /* vfs returns negative error codes */ + r->_errno = -res; + return -1; + } + return 0; +} + +#else /* MODULE_VFS */ + +/* Fallback stdio_uart wrappers for when VFS is not used, does not allow any + * other file access */ +/* + * Fallback read function + * + * All input is read from uart_stdio regardless of fd number. The function will + * block until a byte is actually read. + * + * Note: the read function does not buffer - data will be lost if the function is not + * called fast enough. + */ +_ssize_t _read_r(struct _reent *r, int fd, void *buffer, size_t count) +{ + (void)r; + (void)fd; + return uart_stdio_read(buffer, count); +} + +/* + * Fallback write function + * + * All output is directed to uart_stdio, independent of the given file descriptor. + * The write call will further block until the byte is actually written to the UART. + */ +_ssize_t _write_r(struct _reent *r, int fd, const void *data, size_t count) +{ + (void) r; + (void) fd; + return uart_stdio_write(data, count); +} + +/* Stubs to avoid linking errors, these functions do not have any effect */ +int _open_r(struct _reent *r, const char *name, int flags, int mode) +{ + (void) name; + (void) flags; + (void) mode; + r->_errno = ENODEV; + return -1; +} + +int _close_r(struct _reent *r, int fd) +{ + (void) fd; + r->_errno = ENODEV; + return -1; +} + +_off_t _lseek_r(struct _reent *r, int fd, _off_t pos, int dir) +{ + (void) fd; + (void) pos; + (void) dir; + r->_errno = ENODEV; + return -1; +} + +int _fstat_r(struct _reent *r, int fd, struct stat *st) +{ + (void) fd; + (void) st; + r->_errno = ENODEV; + return -1; +} + int _stat_r(struct _reent *r, const char *name, struct stat *st) { (void) name; (void) st; - r->_errno = ENODEV; /* not implemented yet */ + r->_errno = ENODEV; + return -1; +} + +int _unlink_r(struct _reent *r, const char *path) +{ + (void) path; + r->_errno = ENODEV; return -1; } +#endif /* MODULE_VFS */ /** * @brief Query whether output stream is a terminal @@ -303,21 +476,6 @@ int _isatty_r(struct _reent *r, int fd) return 0; } -/** - * @brief Remove a file's directory entry - * - * @param r TODO - * @param path TODO - * - * @return TODO - */ -int _unlink_r(struct _reent *r, const char *path) -{ - (void) path; - r->_errno = ENODEV; /* not implemented yet */ - return -1; -} - /** * @brief Send a signal to a thread * diff --git a/sys/posix/include/fcntl.h b/sys/posix/include/fcntl.h new file mode 100644 index 000000000000..797f116bc720 --- /dev/null +++ b/sys/posix/include/fcntl.h @@ -0,0 +1,77 @@ +/* + * 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 + * @ingroup posix + * @brief POSIX compatible fcntl.h definitions + * @author Joakim Nohlgård + * + * @see http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/fcntl.h.html + */ + +/** @todo Remove ifdef BOARD_MIPS_MALTA special case after + * [#6639](https://github.com/RIOT-OS/RIOT/pull/6639) is merged */ +#ifndef DOXYGEN +#if defined(CPU_NATIVE) || MODULE_NEWLIB || defined(BOARD_MIPS_MALTA) +/* If building on native or newlib we need to use the system header instead */ +#pragma GCC system_header +/* without the GCC pragma above #include_next will trigger a pedantic error */ +#include_next +#else +#ifndef FCNTL_H_ +#define FCNTL_H_ + +#include /* for mode_t, off_t */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Constants used for the flags in fcntl and open */ +/* The names of the constants are defined by POSIX but most of these numbers + * were chosen somewhat arbitrarily */ +#define O_ACCMODE (O_RDONLY|O_WRONLY|O_RDWR) +#define O_RDONLY 0 /* Read only */ +#define O_WRONLY 1 /* Write only */ +#define O_RDWR 2 /* Read+write */ +#define O_APPEND 0x0008 /* Set append mode */ +#define O_CREAT 0x0010 /* Create file if it does not exist */ +#define O_TRUNC 0x0020 /* Truncate flag */ +#define O_EXCL 0x0040 /* Exclusive use flag */ + +#define F_DUPFD 0 /* Duplicate file descriptor */ +#define F_GETFD 1 /* Get file descriptor flags */ +#define F_SETFD 2 /* Set file descriptor flags */ +#define F_GETFL 3 /* Get file status flags */ +#define F_SETFL 4 /* Set file status flags */ +#define F_GETOWN 5 /* Get owner */ +#define F_SETOWN 6 /* Set owner */ +#define F_GETLK 7 /* Get record-locking information */ +#define F_SETLK 8 /* Set or Clear a record-lock (Non-Blocking) */ +#define F_SETLKW 9 /* Set or Clear a record-lock (Blocking) */ + +#define FD_CLOEXEC 1 /* Close the file descriptor upon execution of an exec family function */ + +int creat(const char *, mode_t); +int fcntl(int, int, ...); +int open(const char *, int, ...); +int openat(int, const char *, int, ...); +int posix_fadvise(int, off_t, off_t, int); +int posix_fallocate(int, off_t, off_t); + +#ifdef __cplusplus +} +#endif + +#endif /* FCNTL_H_ */ + +#endif /* CPU_NATIVE */ + +#endif /* DOXYGEN */ +/** @} */ diff --git a/sys/posix/include/sys/statvfs.h b/sys/posix/include/sys/statvfs.h new file mode 100644 index 000000000000..42c142023154 --- /dev/null +++ b/sys/posix/include/sys/statvfs.h @@ -0,0 +1,78 @@ +/* + * 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 POSIX compatible sys/statvfs.h definitions + * @author Joakim Nohlgård + */ + +/* If building on native we need to use the system libraries instead */ +#ifdef CPU_NATIVE +#pragma GCC system_header +/* without the GCC pragma above #include_next will trigger a pedantic error */ +#include_next +#else +#ifndef SYS_STATVFS_H_ +#define SYS_STATVFS_H_ + +#include /* for fsblkcnt_t, fsfilcnt_t */ +/** @todo Remove ifdef BOARD_MIPS_MALTA special case after + * [#6639](https://github.com/RIOT-OS/RIOT/pull/6639) is merged */ +#if MODULE_NEWLIB || defined(BOARD_MIPS_MALTA) +/* newlib support for fsblkcnt_t was only recently added to the newlib git + * repository, commit f3e587d30a9f65d0c6551ad14095300f6e81672e, 15 apr 2016. + * Will be included in release 2.5.0, around new year 2016/2017. + * We provide the below workaround if the used tool chain is too old. */ +#ifndef _FSBLKCNT_T_DECLARED /* for statvfs() */ +#include +/* Default to 32 bit file sizes on newlib platforms */ +typedef uint32_t fsblkcnt_t; +typedef uint32_t fsfilcnt_t; +#define _FSBLKCNT_T_DECLARED +#endif +#endif +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief File system information + */ +struct statvfs { + unsigned long f_bsize; /**< File system block size. */ + unsigned long f_frsize; /**< Fundamental file system block size. */ + fsblkcnt_t f_blocks; /**< Total number of blocks on file system in + units of @c f_frsize. */ + fsblkcnt_t f_bfree; /**< Total number of free blocks. */ + fsblkcnt_t f_bavail; /**< Number of free blocks available to + non-privileged process. */ + fsfilcnt_t f_files; /**< Total number of file serial numbers. */ + fsfilcnt_t f_ffree; /**< Total number of free file serial numbers. */ + fsfilcnt_t f_favail; /**< Number of file serial numbers available to + non-privileged process. */ + + unsigned long f_fsid; /**< File system ID. */ + unsigned long f_flag; /**< Bit mask of f_flag values. */ + unsigned long f_namemax; /**< Maximum filename length. */ +}; + +enum { + ST_RDONLY = 1, /* Mount read-only. */ + ST_NOSUID = 2, /* Ignore suid and sgid bits. */ +}; + +#ifdef __cplusplus +} +#endif + +#endif /* SYS_STATVFS_H_ */ + +#endif /* CPU_NATIVE */ + +/** @} */ diff --git a/sys/shell/commands/Makefile b/sys/shell/commands/Makefile index d3c9ccfc70f1..135bcc4669cd 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 000000000000..5571542d5678 --- /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 5713d6b5507a..d72730051c41 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/uart_stdio/uart_stdio.c b/sys/uart_stdio/uart_stdio.c index 55f2a395be8f..b87ccef2bc0d 100644 --- a/sys/uart_stdio/uart_stdio.c +++ b/sys/uart_stdio/uart_stdio.c @@ -1,6 +1,7 @@ /* * Copyright (C) 2013 INRIA * 2015 Kaspar Schleiser + * 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 @@ -19,12 +20,17 @@ * @author Oliver Hahm * @author Ludwig Knüpfer * @author Kaspar Schleiser + * @author Joakim Nohlgård * * @} */ #include - +#if MODULE_VFS +#include +#include +#include +#endif #include "uart_stdio.h" #include "board.h" @@ -36,12 +42,47 @@ extern ethos_t ethos; #endif +#if MODULE_VFS +#include "vfs.h" +#endif + #define ENABLE_DEBUG 0 #include "debug.h" static char _rx_buf_mem[UART_STDIO_RX_BUFSIZE]; isrpipe_t uart_stdio_isrpipe = ISRPIPE_INIT(_rx_buf_mem); +#if MODULE_VFS +static ssize_t uart_stdio_vfs_read(vfs_file_t *filp, void *dest, size_t nbytes); +static ssize_t uart_stdio_vfs_write(vfs_file_t *filp, const void *src, size_t nbytes); + +/** + * @brief VFS file operation table for stdin/stdout/stderr + */ +static vfs_file_ops_t uart_stdio_vfs_ops = { + .read = uart_stdio_vfs_read, + .write = uart_stdio_vfs_write, +}; + +static ssize_t uart_stdio_vfs_read(vfs_file_t *filp, void *dest, size_t nbytes) +{ + int fd = filp->private_data.value; + if (fd != STDIN_FILENO) { + return -EBADF; + } + return uart_stdio_read(dest, nbytes); +} + +static ssize_t uart_stdio_vfs_write(vfs_file_t *filp, const void *src, size_t nbytes) +{ + int fd = filp->private_data.value; + if (fd == STDIN_FILENO) { + return -EBADF; + } + return uart_stdio_write(src, nbytes); +} +#endif + void uart_stdio_init(void) { #ifndef USE_ETHOS_FOR_STDIO @@ -49,6 +90,21 @@ void uart_stdio_init(void) #else uart_init(ETHOS_UART, ETHOS_BAUDRATE, (uart_rx_cb_t) isrpipe_write_one, &uart_stdio_isrpipe); #endif +#if MODULE_VFS + int fd; + fd = vfs_bind(STDIN_FILENO, O_RDONLY, &uart_stdio_vfs_ops, (void *)STDIN_FILENO); + if (fd < 0) { + /* How to handle errors on init? */ + } + fd = vfs_bind(STDOUT_FILENO, O_WRONLY, &uart_stdio_vfs_ops, (void *)STDOUT_FILENO); + if (fd < 0) { + /* How to handle errors on init? */ + } + fd = vfs_bind(STDERR_FILENO, O_WRONLY, &uart_stdio_vfs_ops, (void *)STDERR_FILENO); + if (fd < 0) { + /* How to handle errors on init? */ + } +#endif } int uart_stdio_read(char* buffer, int count) diff --git a/sys/vfs/Makefile b/sys/vfs/Makefile new file mode 100644 index 000000000000..48422e909a47 --- /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 000000000000..0217dd4fe37a --- /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/emb6/ping.c b/tests/emb6/ping.c index 1f06fb536e24..e83f9d64e628 100644 --- a/tests/emb6/ping.c +++ b/tests/emb6/ping.c @@ -90,7 +90,7 @@ static void handle_reply(uip_ipaddr_t *source, uint8_t ttl, uint8_t *data, ipv6_addr_to_str(addr_str, (ipv6_addr_t *)source, sizeof(addr_str)); - (void)atomic_fetch_add(&received, 1); /* Ignore return value, we only want to increment the counter */ + atomic_fetch_add(&received, 1); printf("%" PRIu16 " bytes from %s: icmp_seq=%" PRIu16 " ttl=%u quota=%i/%i\n", datalen, addr_str, byteorder_ntohs(ping->seq), (unsigned)ttl, atomic_load(&received), atomic_load(&num)); diff --git a/tests/unittests/tests-devfs/Makefile b/tests/unittests/tests-devfs/Makefile new file mode 100644 index 000000000000..48422e909a47 --- /dev/null +++ b/tests/unittests/tests-devfs/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/tests/unittests/tests-devfs/Makefile.include b/tests/unittests/tests-devfs/Makefile.include new file mode 100644 index 000000000000..ded525dbfa8b --- /dev/null +++ b/tests/unittests/tests-devfs/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE += vfs +USEMODULE += devfs diff --git a/tests/unittests/tests-devfs/tests-devfs.c b/tests/unittests/tests-devfs/tests-devfs.c new file mode 100644 index 000000000000..cb9dd321c714 --- /dev/null +++ b/tests/unittests/tests-devfs/tests-devfs.c @@ -0,0 +1,160 @@ +/* + * 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 DevFS + * + * @author Joakim Nohlgård + */ +#include +#include +#include +#include + +#include "fs/devfs.h" + +#include "embUnit/embUnit.h" + +#include "tests-devfs.h" + +static int _mock_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path); +static ssize_t _mock_read(vfs_file_t *filp, void *dest, size_t nbytes); +static ssize_t _mock_write(vfs_file_t *filp, const void *src, size_t nbytes); + +static volatile int _mock_open_calls = 0; +static volatile int _mock_read_calls = 0; +static volatile int _mock_write_calls = 0; + +static int _mock_private_data; + +static const vfs_file_ops_t _mock_devfs_ops = { + .open = _mock_open, + .read = _mock_read, + .write = _mock_write, +}; + +static int _mock_private_data_tag = 4321; + +static devfs_t _mock_devfs_node = { + .path = "/mock0", + .f_op = &_mock_devfs_ops, + .private_data = &_mock_private_data_tag, +}; + +static vfs_mount_t _test_devfs_mount = { + .fs = &devfs_file_system, + .mount_point = "/test", + .private_data = &_mock_private_data, +}; + +static int _mock_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path) +{ + (void) name; + (void) flags; + (void) mode; + (void) abs_path; + if (filp->private_data.ptr != &_mock_private_data_tag) { + return -4321; + } + int *np = filp->mp->private_data; + ++(*np); + ++_mock_open_calls; + return 0; +} + +static ssize_t _mock_read(vfs_file_t *filp, void *dest, size_t nbytes) +{ + (void) dest; + (void) nbytes; + if (filp->private_data.ptr != &_mock_private_data_tag) { + return -4321; + } + int *np = filp->mp->private_data; + ++(*np); + ++_mock_read_calls; + return 0; +} + +static ssize_t _mock_write(vfs_file_t *filp, const void *src, size_t nbytes) +{ + (void) src; + (void) nbytes; + if (filp->private_data.ptr != &_mock_private_data_tag) { + return -4321; + } + int *np = filp->mp->private_data; + ++(*np); + ++_mock_write_calls; + return 0; +} + +static void test_devfs_register(void) +{ + int res = devfs_register(NULL); + TEST_ASSERT(res < 0); + + res = devfs_register(&_mock_devfs_node); + TEST_ASSERT(res == 0); + + res = devfs_register(&_mock_devfs_node); + TEST_ASSERT(res < 0); + + res = devfs_unregister(&_mock_devfs_node); + TEST_ASSERT(res == 0); + + res = devfs_unregister(&_mock_devfs_node); + TEST_ASSERT(res < 0); +} + +static void test_devfs_mount_open(void) +{ + _mock_private_data = 12345; + int res; + res = vfs_mount(&_test_devfs_mount); + TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT_EQUAL_INT(_mock_private_data, 12345); + + res = devfs_register(&_mock_devfs_node); + TEST_ASSERT_EQUAL_INT(0, res); + + int count = _mock_open_calls; + int fd = vfs_open("/test/mock0", O_RDWR, 0); + TEST_ASSERT(fd >= 0); + TEST_ASSERT_EQUAL_INT(count + 1, _mock_open_calls); + TEST_ASSERT_EQUAL_INT(_mock_private_data, 12346); + + res = vfs_close(fd); + TEST_ASSERT_EQUAL_INT(0, res); + + res = devfs_unregister(&_mock_devfs_node); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_umount(&_test_devfs_mount); + TEST_ASSERT_EQUAL_INT(0, res); +} + +Test *tests_devfs_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_devfs_register), + new_TestFixture(test_devfs_mount_open), + }; + + EMB_UNIT_TESTCALLER(devfs_tests, NULL, NULL, fixtures); + + return (Test *)&devfs_tests; +} + +void tests_devfs(void) +{ + TESTS_RUN(tests_devfs_tests()); +} +/** @} */ diff --git a/tests/unittests/tests-devfs/tests-devfs.h b/tests/unittests/tests-devfs/tests-devfs.h new file mode 100644 index 000000000000..1d9a45e66ae6 --- /dev/null +++ b/tests/unittests/tests-devfs/tests-devfs.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 DevFS + * + * @author Joakim Nohlgård + */ +#ifndef TESTS_DEVFS_H +#define TESTS_DEVFS_H + +#include "embUnit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The entry point of this test suite. + */ +void tests_devfs(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TESTS_DEVFS_H */ +/** @} */ diff --git a/tests/unittests/tests-vfs/Makefile b/tests/unittests/tests-vfs/Makefile new file mode 100644 index 000000000000..48422e909a47 --- /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 000000000000..f35c90d855fb --- /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 000000000000..ddce730fe355 --- /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 000000000000..fa56a04ff7ec --- /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 000000000000..4b5ae05ea52d --- /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 000000000000..7443e8577c77 --- /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 000000000000..ab18da5a7456 --- /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 || defined(BOARD_NATIVE) +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 || defined(BOARD_NATIVE) + 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 000000000000..6e6d6fb8e9e7 --- /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 000000000000..4b14365fd521 --- /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 000000000000..c8c91489f58c --- /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 000000000000..edd6457537e9 --- /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 */ +/** @} */