diff --git a/Makefile.dep b/Makefile.dep index 3b13db9cc6..383bb7f089 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -668,6 +668,20 @@ ifneq (,$(filter ccn-lite,$(USEPKG))) USEMODULE += ccn-lite-utils endif +ifneq (,$(filter fatfs_vfs,$(USEMODULE))) + USEPKG += fatfs + USEMODULE += vfs + + ifeq ($(BOARD),native) + USEMODULE += fatfs_diskio_native + FATFS_DISKIO_NATIVE_DEFAULT_FILE ?= \"riot_fatfs_disk.img\" + else + USEMODULE += fatfs_diskio_sdcard_spi + USEMODULE += auto_init_storage + endif + +endif + # always select gpio (until explicit dependencies are sorted out) FEATURES_OPTIONAL += periph_gpio diff --git a/core/include/kernel_defines.h b/core/include/kernel_defines.h index 6584935d88..d3fc3e8676 100644 --- a/core/include/kernel_defines.h +++ b/core/include/kernel_defines.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Freie Universität Berlin + * 2017 HAW-Hamburg * * 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 @@ -14,6 +15,7 @@ * @brief Common macros and compiler attributes/pragmas configuration * * @author René Kijewski + * @author Michel Rottleuthner */ #ifndef KERNEL_DEFINES_H @@ -114,6 +116,18 @@ */ #define ALIGN_OF(T) (offsetof(struct { char c; T t; }, t)) +/** + * @def BUILD_BUG_ON(condition) + * @brief Forces a compilation error if condition is true. + * This trick is only needed if the condition can't be evaluated + * before compile time (i.e. sizeof(sometype_t) < 42 ) + * For more details on this see for example: + * https://git.kernel.org/pub/scm/linux/kernel/git/stable/ + * linux-stable.git/tree/include/linux/bug.h + * @param[in] condition A condition that will be evaluated at compile time + */ +#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2 * !!(condition)])) + #ifdef __cplusplus } #endif diff --git a/cpu/native/mtd/mtd_native.c b/cpu/native/mtd/mtd_native.c index 5e6089df94..5c3abaa0d6 100644 --- a/cpu/native/mtd/mtd_native.c +++ b/cpu/native/mtd/mtd_native.c @@ -37,7 +37,7 @@ static int _init(mtd_dev_t *dev) FILE *f = real_fopen(_dev->fname, "r"); if (!f) { - DEBUG("mtd_native: init: creating file %s\n", name); + DEBUG("mtd_native: init: creating file %s\n", _dev->fname); f = real_fopen(_dev->fname, "w+"); if (!f) { return -EIO; diff --git a/pkg/fatfs/Makefile.include b/pkg/fatfs/Makefile.include index 6579d73d0e..2db484e799 100644 --- a/pkg/fatfs/Makefile.include +++ b/pkg/fatfs/Makefile.include @@ -12,6 +12,10 @@ ifneq (,$(filter fatfs_diskio_sdcard_spi,$(USEMODULE))) DIRS += $(RIOTBASE)/pkg/fatfs/fatfs_diskio/sdcard_spi endif +ifneq (,$(filter fatfs_vfs,$(USEMODULE))) + DIRS += $(RIOTBASE)/pkg/fatfs/fatfs_vfs +endif + ifeq ($(shell uname -s),Darwin) CFLAGS += -Wno-empty-body endif diff --git a/pkg/fatfs/fatfs_vfs/Makefile b/pkg/fatfs/fatfs_vfs/Makefile new file mode 100644 index 0000000000..a74c941acb --- /dev/null +++ b/pkg/fatfs/fatfs_vfs/Makefile @@ -0,0 +1,3 @@ +MODULE := fatfs_vfs + +include $(RIOTBASE)/Makefile.base diff --git a/pkg/fatfs/fatfs_vfs/fatfs_vfs.c b/pkg/fatfs/fatfs_vfs/fatfs_vfs.c new file mode 100644 index 0000000000..12fda2f7cb --- /dev/null +++ b/pkg/fatfs/fatfs_vfs/fatfs_vfs.c @@ -0,0 +1,467 @@ +/* + * Copyright (C) 2017 HAW-Hamburg + * + * 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 + * @{ + * + * @file + * @brief FatFs wrapper for vfs + * + * @author Michel Rottleuthner + * + * @} + */ + + +#include +#include +#include +#include /* for struct stat */ +#include + +#include "fs/fatfs.h" + +#include "kernel_defines.h" /* needed for BUILD_BUG_ON */ +#include "time.h" + +#define ENABLE_DEBUG (0) +#include + +static int fatfs_err_to_errno(int32_t err); +static void _fatfs_time_to_timespec(WORD fdate, WORD ftime, time_t *time); + +static int _mount(vfs_mount_t *mountp) +{ + /* if one of the lines below fail to compile you probably need to adjust + vfs buffer sizes ;) */ + BUILD_BUG_ON(VFS_DIR_BUFFER_SIZE < sizeof(DIR)); + BUILD_BUG_ON(VFS_FILE_BUFFER_SIZE < sizeof(fatfs_file_desc_t)); + + fatfs_desc_t *fs_desc = (fatfs_desc_t *)mountp->private_data; + + char volume_str[FATFS_MAX_VOL_STR_LEN]; + snprintf(volume_str, sizeof(volume_str), "%d:/", fs_desc->vol_idx); + + memset(&fs_desc->fat_fs, 0, sizeof(fs_desc->fat_fs)); + + DEBUG("mounting file system of volume '%s'\n", volume_str); + FRESULT res = f_mount(&fs_desc->fat_fs, volume_str, 1); + + if (res == FR_OK) { + DEBUG("[OK]"); + } + else { + DEBUG("[ERROR]"); + } + + return fatfs_err_to_errno(res); +} + +static int _umount(vfs_mount_t *mountp) +{ + fatfs_desc_t *fs_desc = mountp->private_data; + + DEBUG("fatfs_vfs.c: _umount: private_data = %p\n", mountp->private_data); + + char volume_str[FATFS_MAX_VOL_STR_LEN]; + snprintf(volume_str, sizeof(volume_str), "%d:/", fs_desc->vol_idx); + + DEBUG("unmounting file system of volume '%s'\n", volume_str); + FRESULT res = f_mount(NULL, volume_str, 1); + + if (res == FR_OK) { + DEBUG("[OK]"); + memset(&fs_desc->fat_fs, 0, sizeof(fs_desc->fat_fs)); + } + else { + DEBUG("[ERROR]"); + } + + return fatfs_err_to_errno(res); +} + +static int _unlink(vfs_mount_t *mountp, const char *name) +{ + char fatfs_abs_path[FATFS_MAX_VOL_STR_LEN + VFS_NAME_MAX + 1]; + fatfs_desc_t *fs_desc = (fatfs_desc_t *)mountp->private_data; + + snprintf(fatfs_abs_path, sizeof(fatfs_abs_path), "%d:/%s", + fs_desc->vol_idx, name); + + return fatfs_err_to_errno(f_unlink(fatfs_abs_path)); +} + +static int _rename(vfs_mount_t *mountp, const char *from_path, + const char *to_path) +{ + char fatfs_abs_path_f[FATFS_MAX_VOL_STR_LEN + VFS_NAME_MAX + 1]; + char fatfs_abs_path_t[FATFS_MAX_VOL_STR_LEN + VFS_NAME_MAX + 1]; + fatfs_desc_t *fs_desc = (fatfs_desc_t *)mountp->private_data; + + snprintf(fatfs_abs_path_f, sizeof(fatfs_abs_path_f), "%d:/%s", + fs_desc->vol_idx, from_path); + + snprintf(fatfs_abs_path_t, sizeof(fatfs_abs_path_t), "%d:/%s", + fs_desc->vol_idx, to_path); + + return fatfs_err_to_errno(f_rename(fatfs_abs_path_f, fatfs_abs_path_t)); +} + +static int _open(vfs_file_t *filp, const char *name, int flags, mode_t mode, + const char *abs_path) +{ + fatfs_file_desc_t *fd = (fatfs_file_desc_t *)&filp->private_data.buffer[0]; + char fatfs_abs_path[FATFS_MAX_VOL_STR_LEN + VFS_NAME_MAX + 1]; + fatfs_desc_t *fs_desc = (fatfs_desc_t *)filp->mp->private_data; + snprintf(fatfs_abs_path, sizeof(fatfs_abs_path), "%d:/%s", fs_desc->vol_idx, + name); + + (void) abs_path; + (void) mode; /* fatfs can't use mode param with f_open*/ + DEBUG("fatfs_vfs.c: _open: private_data = %p, name = %s; flags = 0x%x\n", + filp->mp->private_data, name, flags); + + strncpy(fd->fname, fatfs_abs_path, VFS_NAME_MAX); + + uint8_t fatfs_flags = 0; + + if ((flags & O_ACCMODE) == O_RDONLY) { + fatfs_flags |= FA_READ; + } + if ((flags & O_ACCMODE) == O_WRONLY) { + fatfs_flags |= FA_WRITE; + } + if ((flags & O_ACCMODE) == O_RDWR) { + fatfs_flags |= (FA_READ | FA_WRITE); + } + if ((flags & O_APPEND) == O_APPEND) { + fatfs_flags |= FA_OPEN_APPEND; + } + if ((flags & O_TRUNC) == O_TRUNC) { + fatfs_flags |= FA_CREATE_ALWAYS; + } + if ((flags & O_CREAT) == O_CREAT) { + fatfs_flags |= FA_CREATE_NEW; + } + else { + fatfs_flags |= FA_OPEN_EXISTING; + } + + FRESULT open_resu = f_open(&fd->file, fatfs_abs_path, fatfs_flags); + if (open_resu == FR_OK) { + DEBUG("[OK]"); + } + else { + DEBUG("[ERROR]"); + } + + DEBUG("fatfs_vfs.c _open: returning fatfserr=%d; errno=%d\n", open_resu, + fatfs_err_to_errno(open_resu)); + + return fatfs_err_to_errno(open_resu); +} + +static int _close(vfs_file_t *filp) +{ + fatfs_file_desc_t *fd = (fatfs_file_desc_t *)filp->private_data.buffer; + + DEBUG("fatfs_vfs.c: _close: private_data = %p\n", filp->mp->private_data); + + FRESULT res = f_close(&fd->file); + + if (res == FR_OK) { + DEBUG("[OK]"); + } + else { + DEBUG("[FAILED] (f_close error %d)\n", res); + } + + return fatfs_err_to_errno(res); +} + +static ssize_t _write(vfs_file_t *filp, const void *src, size_t nbytes) +{ + fatfs_file_desc_t *fd = (fatfs_file_desc_t *)filp->private_data.buffer; + + UINT bw; + + FRESULT res = f_write(&fd->file, src, nbytes, &bw); + + if (res != FR_OK) { + return fatfs_err_to_errno(res); + } + + return (ssize_t)bw; +} + +static ssize_t _read(vfs_file_t *filp, void *dest, size_t nbytes) +{ + fatfs_file_desc_t *fd = (fatfs_file_desc_t *)filp->private_data.buffer; + + UINT br; + + FRESULT res = f_read(&fd->file, dest, nbytes, &br); + + + if (res != FR_OK) { + return fatfs_err_to_errno(res); + } + + return (ssize_t)br; +} + +static off_t _lseek(vfs_file_t *filp, off_t off, int whence) +{ + fatfs_file_desc_t *fd = (fatfs_file_desc_t *)filp->private_data.buffer; + FRESULT res; + off_t new_pos = 0; + + if (whence == SEEK_SET) { + new_pos = off; + } + else if (whence == SEEK_CUR) { + new_pos = f_tell(&fd->file) + off; + } + else if (whence == SEEK_END) { + new_pos = f_size(&fd->file) + off; + } + else { + return fatfs_err_to_errno(FR_INVALID_PARAMETER); + } + + res = f_lseek(&fd->file, new_pos); + + if (res == FR_OK) { + return new_pos; + } + + return fatfs_err_to_errno(res); +} + +static int _fstat(vfs_file_t *filp, struct stat *buf) +{ + fatfs_file_desc_t *fd = (fatfs_file_desc_t *)filp->private_data.buffer; + char fatfs_abs_path[FATFS_MAX_VOL_STR_LEN + VFS_NAME_MAX + 1]; + fatfs_desc_t *fs_desc = (fatfs_desc_t *)filp->mp->private_data; + FILINFO fi; + FRESULT res; + + snprintf(fatfs_abs_path, sizeof(fatfs_abs_path), "%d:/%s", fs_desc->vol_idx, + fd->fname); + + memset(buf, 0, sizeof(*buf)); + + res = f_stat(fatfs_abs_path, &fi); + + if (res != FR_OK) { + return fatfs_err_to_errno(res); + } + + buf->st_size = fi.fsize; + + /* set last modification timestamp */ + #ifdef SYS_STAT_H_ + _fatfs_time_to_timespec(fi.fdate, fi.ftime, &(buf->st_mtim.tv_sec)); + #else + _fatfs_time_to_timespec(fi.fdate, fi.ftime, &(buf->st_mtime)); + #endif + + if (fi.fattrib & AM_DIR) { + buf->st_mode = S_IFDIR; /**< it's a directory */ + } + else { + buf->st_mode = S_IFREG; /**< it's a regular file */ + } + + /** always grant read access */ + buf->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH); + + if (fi.fattrib & AM_RDO) { + /** grant write access if file isn't RO*/ + buf->st_mode ^= (S_IWUSR | S_IWGRP | S_IWOTH); + } + + return fatfs_err_to_errno(res); +} + +static int _opendir(vfs_DIR *dirp, const char *dirname, const char *abs_path) +{ + DIR *dir = (DIR *)&dirp->private_data.buffer; + char fatfs_abs_path[FATFS_MAX_VOL_STR_LEN + VFS_NAME_MAX + 1]; + fatfs_desc_t *fs_desc = (fatfs_desc_t *)dirp->mp->private_data; + (void) abs_path; + + snprintf(fatfs_abs_path, sizeof(fatfs_abs_path), "%d:/%s", fs_desc->vol_idx, + dirname); + + return fatfs_err_to_errno(f_opendir(dir, fatfs_abs_path)); +} + +static int _readdir(vfs_DIR *dirp, vfs_dirent_t *entry) +{ + DIR *dir = (DIR *)&dirp->private_data.buffer[0]; + FILINFO fi; + + FRESULT res = f_readdir(dir, &fi); + + if (res == FR_OK) { + if (fi.fname[0] == 0) { + return 0; /**< end of dir reached */ + } + else { + entry->d_ino = 0; //TODO: set this properly + strncpy(entry->d_name, fi.fname, VFS_NAME_MAX); + return 1; + } + } + + return fatfs_err_to_errno(res); +} + +static int _closedir(vfs_DIR *dirp) +{ + DIR *dir = (DIR *)&dirp->private_data.buffer[0]; + + return fatfs_err_to_errno(f_closedir(dir)); +} + +static int _mkdir (vfs_mount_t *mountp, const char *name, mode_t mode) +{ + char fatfs_abs_path[FATFS_MAX_VOL_STR_LEN + VFS_NAME_MAX + 1]; + fatfs_desc_t *fs_desc = (fatfs_desc_t *)mountp->private_data; + (void) mode; + + snprintf(fatfs_abs_path, sizeof(fatfs_abs_path), "%d:/%s", fs_desc->vol_idx, + name); + return fatfs_err_to_errno(f_mkdir(fatfs_abs_path)); +} + +static int _rmdir (vfs_mount_t *mountp, const char *name) +{ + char fatfs_abs_path[FATFS_MAX_VOL_STR_LEN + VFS_NAME_MAX + 1]; + fatfs_desc_t *fs_desc = (fatfs_desc_t *)mountp->private_data; + + snprintf(fatfs_abs_path, sizeof(fatfs_abs_path), "%d:/%s", fs_desc->vol_idx, + name); + return fatfs_err_to_errno(f_unlink(fatfs_abs_path)); +} + +static void _fatfs_time_to_timespec(WORD fdate, WORD ftime, time_t *time) +{ + const uint16_t days_for_month[12] = { 0, 31, 59, 90, 120, 151, 181, 212, + 243, 273, 304, 334 }; + + int year = ((fdate >> 9) & 0x7F) + FATFS_YEAR_OFFSET - EPOCH_YEAR_OFFSET; + int month = ((fdate >> 5) & 0x0F) - 1; /**< 0..11 */ + int day = (fdate & 0x1F) - 1; /**< 0..31 */ + + int hour = (ftime >> 11) & 0x1F; /**< 0..23 */ + int min = (ftime >> 5) & 0x3F; /**< 0..59 */ + int sec = (ftime & 0x1F) * 2; /**< 0..58 */ + + /* leap years since 1970 */ + int leap_years = ((year - 1 - EPOCH_YEAR_OFFSET - 2) / 4) - + ((year - 1 - 1900) / 100) + + ((year - 1 - 1600) / 400); + + long days_since_epoch = (year - EPOCH_YEAR_OFFSET) * 365 + + days_for_month[month] + + leap_years + + day; + + if ((month > 2) && + ((year % 4 == 0) && + ((year % 100 != 0) || (year % 400 == 0)))) { + days_since_epoch++; /* add one day if this is a leap year */ + } + + *time = ((((days_since_epoch * 24) + hour) * 60) + min) * 60 + sec; +} + +static int fatfs_err_to_errno(int32_t err) +{ + switch (err) { + case FR_OK: + return 0; + case FR_DISK_ERR: + return -EIO; + case FR_INT_ERR: + return -EIO; + case FR_NOT_READY: + return -ENODEV; + case FR_NO_FILE: + return -ENOENT; + case FR_NO_PATH: + return -ENOENT; + case FR_INVALID_NAME: + return -ENOENT; + case FR_DENIED: + return -EACCES; + case FR_EXIST: + return -EEXIST; + case FR_INVALID_OBJECT: + #ifdef EBADFD + return -EBADFD; + #else + return -EINVAL; + #endif + case FR_WRITE_PROTECTED: + return -EACCES; + case FR_INVALID_DRIVE: + return -ENXIO; + case FR_NOT_ENABLED: + return -ENODEV; + case FR_NO_FILESYSTEM: + return -ENODEV; + case FR_MKFS_ABORTED: + return -EINVAL; + case FR_TIMEOUT: + return -EBUSY; + case FR_LOCKED: + return -EACCES; + case FR_NOT_ENOUGH_CORE: + return -ENOMEM; + case FR_TOO_MANY_OPEN_FILES: + return -ENFILE; + case FR_INVALID_PARAMETER: + return -EINVAL; + } + + return (int) err; +} + +static const vfs_file_system_ops_t fatfs_fs_ops = { + .mount = _mount, + .umount = _umount, + .rename = _rename, + .unlink = _unlink, + .mkdir = _mkdir, + .rmdir = _rmdir, +}; + +static const vfs_file_ops_t fatfs_file_ops = { + .open = _open, + .close = _close, + .read = _read, + .write = _write, + .lseek = _lseek, + .fstat = _fstat, +}; + +static const vfs_dir_ops_t fatfs_dir_ops = { + .opendir = _opendir, + .readdir = _readdir, + .closedir = _closedir, +}; + +const vfs_file_system_t fatfs_file_system = { + .fs_op = &fatfs_fs_ops, + .f_op = &fatfs_file_ops, + .d_op = &fatfs_dir_ops, +}; diff --git a/sys/include/fs/fatfs.h b/sys/include/fs/fatfs.h new file mode 100644 index 0000000000..683959c945 --- /dev/null +++ b/sys/include/fs/fatfs.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 HAW-Hamburg + * + * 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_fatfs FatFs integration + * @ingroup sys_fs + * @{ + * + * @file + * @brief FatFs integration for vfs + * + * @author Michel Rottleuthner + */ + +#ifndef FATFS_H +#define FATFS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef VFS_DIR_BUFFER_SIZE +#define VFS_DIR_BUFFER_SIZE (44) +#endif + +#ifndef VFS_FILE_BUFFER_SIZE +#define VFS_FILE_BUFFER_SIZE (72) +#endif + +#include "fatfs/ff.h" +#include "vfs.h" + +#ifndef FATFS_YEAR_OFFSET +#define FATFS_YEAR_OFFSET (1980) +#endif + +#define EPOCH_YEAR_OFFSET (1970) + +/** Size of the buffer needed for directory -> should be: sizeof(DIR)*/ +#define FATFS_DIR_SIZE (44) +/** the problem with the above is: it's not possible to use sizeof(DIR) as this is later usen in #if (see below) */ + +/** Size of the buffer needed for directory -> should be: sizeof(fatfs_file_desc_t)*/ +#define FATFS_FILE_SIZE (72) + +#define FATFS_MAX_VOL_STR_LEN (4) /**< size needed for volume strings like "n:/" where n is the volume id */ +#define FATFS_MOUNT_OPT (1) /**< 0:mount on first file access, 1 mount in f_mount() call */ + +#if (VFS_DIR_BUFFER_SIZE < FATFS_DIR_SIZE) +#error "VFS_DIR_BUFFER_SIZE too small" +#endif + +#if (VFS_FILE_BUFFER_SIZE < FATFS_FILE_SIZE) +#error "VFS_FILE_BUFFER_SIZE too small" +#endif + +/** + * needed info to run a FatFs instance + */ +typedef struct fatfs_desc { + FATFS fat_fs; /**< FatFs work area needed for each volume */ + uint8_t vol_idx; /**< low level device that is used by FatFs */ +} fatfs_desc_t; + +/** + * info of a single opened file + */ +typedef struct fatfs_file_desc { + FIL file; /**< FatFs work area for a single file */ + char fname[VFS_NAME_MAX + 1]; /**< name of the file (some FatFs functions e.g. f_stat use filename instead of FIL) */ +} fatfs_file_desc_t; + +/** The FatFs vfs driver, a pointer to a fatfs_desc_t must be provided as vfs_mountp::private_data */ +extern const vfs_file_system_t fatfs_file_system; + +#ifdef __cplusplus +} +#endif + +#endif /* FATFS_H */ + +/** @} */ diff --git a/sys/include/vfs.h b/sys/include/vfs.h index 9b2beac45f..163c5c027f 100644 --- a/sys/include/vfs.h +++ b/sys/include/vfs.h @@ -119,6 +119,29 @@ extern "C" { #define VFS_DIR_BUFFER_SIZE (12) #endif +#ifndef VFS_FILE_BUFFER_SIZE +/** + * @brief Size of buffer space in vfs_file_t + * + * Same as with VFS_DIR_BUFFER_SIZE some file systems (e.g. FatFs) require more space + * to store data about their files. + * + * + * Guidelines are same as with VFS_DIR_BUFFER_SIZE, so add the following snippet + * to your fs header: + * + * @attention @code + * #if VFS_FILE_BUFFER_SIZE < 123 + * #error VFS_FILE_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_FILE_BUFFER_SIZE (1) +#endif + #ifndef VFS_NAME_MAX /** * @brief Maximum length of the name in a @c vfs_dirent_t (not including terminating null) @@ -192,6 +215,7 @@ typedef struct { union { void *ptr; /**< pointer to private data */ int value; /**< alternatively, you can use private_data as an int */ + uint8_t buffer[VFS_FILE_BUFFER_SIZE]; /**< Buffer space, in case a single pointer is not enough */ } private_data; /**< File system driver private data, implementation defined */ } vfs_file_t; diff --git a/tests/pkg_fatfs/create_fat_image_file.sh b/tests/pkg_fatfs/create_fat_image_file.sh new file mode 100755 index 0000000000..fa72597b7b --- /dev/null +++ b/tests/pkg_fatfs/create_fat_image_file.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# +# Copyright (C) 2017 HAW-Hamburg +# +# 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. +# +dd if=/dev/zero of=riot_fatfs_disk.img bs=1M count=$1 +mkfs.fat riot_fatfs_disk.img +sudo mkdir -p /media/riot_fatfs_disk +sudo mount -o loop,umask=000 riot_fatfs_disk.img /media/riot_fatfs_disk +touch /media/riot_fatfs_disk/test.txt +echo "the test file content 123 abc" | tr '\n' '\0' >> /media/riot_fatfs_disk/test.txt +sudo umount /media/riot_fatfs_disk +tar -cjf riot_fatfs_disk.tar.gz riot_fatfs_disk.img +rm riot_fatfs_disk.img diff --git a/tests/pkg_fatfs/riot_fatfs_disk.tar.gz b/tests/pkg_fatfs/riot_fatfs_disk.tar.gz new file mode 100644 index 0000000000..d7835b9db7 Binary files /dev/null and b/tests/pkg_fatfs/riot_fatfs_disk.tar.gz differ diff --git a/tests/pkg_fatfs_vfs/Makefile b/tests/pkg_fatfs_vfs/Makefile new file mode 100644 index 0000000000..44b562dd44 --- /dev/null +++ b/tests/pkg_fatfs_vfs/Makefile @@ -0,0 +1,34 @@ +APPLICATION = pkg_fatfs_vfs +include ../Makefile.tests_common + +USEMODULE += fatfs_vfs + +FATFS_DISKIO_NATIVE_DEFAULT_FILE ?= \"./bin/riot_fatfs_disk.img\" + +# whitelist can be removed when the problem described in #6063 was resolved +# this list is composed of boards that support spi + native +BOARD_WHITELIST := native airfy-beacon arduino-due arduino-duemilanove arduino-mega2560 \ + arduino-uno arduino-zero avsextrem cc2538dk fox frdm-k64f iotlab-a8-m3 \ + iotlab-m3 limifrog-v1 maple-mini msb-430 msb-430h msba2 msbiot mulle \ + nrf52840dk nrf52dk nrf6310 nucleo-f072 nucleo-f091 nucleo-f103 \ + nucleo-f302 nucleo-f303 nucleo-f334 nucleo-f401 nucleo-f410 nucleo-f411 \ + nucleo-f446 nucleo-l053 nucleo-l073 nucleo-l1 nucleo-l476 nucleo144-f207 \ + nucleo144-f303 nucleo144-f413 nucleo144-f429 nucleo144-f446 nucleo32-f031 \ + nucleo32-f042 nucleo32-f303 nucleo32-l031 nucleo32-l432 openmote-cc2538 \ + pba-d-01-kw2x pca10005 remote-pa remote-reva remote-revb samd21-xpro \ + saml21-xpro samr21-xpro sodaq-autonomo spark-core stm32f0discovery \ + stm32f3discovery stm32f4discovery telosb udoo waspmote-pro weio \ + wsn430-v1_3b wsn430-v1_4 yunjia-nrf51822 z1 + +# export is needed to pass this value to the Makefile of the native_diskio module +export FATFS_DISKIO_NATIVE_DEFAULT_FILE + +include $(RIOTBASE)/Makefile.include + +#this generates a compressed fat image file that can be used by the fat driver on native +fatimage: + @./create_fat_image_file.sh + +test: + @tar -xjf riot_fatfs_disk.tar.gz -C ./bin/ + ./tests/01-run.py diff --git a/tests/pkg_fatfs_vfs/create_fat_image_file.sh b/tests/pkg_fatfs_vfs/create_fat_image_file.sh new file mode 100755 index 0000000000..517c6b3667 --- /dev/null +++ b/tests/pkg_fatfs_vfs/create_fat_image_file.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# +# Copyright (C) 2017 HAW-Hamburg +# +# 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. +# +dd if=/dev/zero of=riot_fatfs_disk.img bs=1M count=128 +mkfs.fat riot_fatfs_disk.img +sudo mkdir -p /media/riot_fatfs_disk +sudo mount -o loop,umask=000 riot_fatfs_disk.img /media/riot_fatfs_disk +touch /media/riot_fatfs_disk/test.txt +echo "the test file content 123 abc" | tr '\n' '\0' >> /media/riot_fatfs_disk/test.txt +sudo umount /media/riot_fatfs_disk +tar -cjf riot_fatfs_disk.tar.gz riot_fatfs_disk.img +rm riot_fatfs_disk.img diff --git a/tests/pkg_fatfs_vfs/main.c b/tests/pkg_fatfs_vfs/main.c new file mode 100644 index 0000000000..4ba369b4bd --- /dev/null +++ b/tests/pkg_fatfs_vfs/main.c @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2017 HAW-Hamburg + * + * 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 tests + * @{ + * + * @file + * @brief test application for using fatfs with vfs + * + * @author Michel Rottleuthner + * @} + */ + +#include +#include +#include +#include + +#include "fs/fatfs.h" +#include "vfs.h" + +#if FATFS_FFCONF_OPT_FS_NORTC == 0 +#include "periph/rtc.h" +#endif + +#define MNT_PATH "/test" +#define FNAME1 "TEST.TXT" +#define FNAME2 "NEWFILE.TXT" +#define FNAME_RNMD "RENAMED.TXT" +#define FNAME_NXIST "NOFILE.TXT" +#define FULL_FNAME1 (MNT_PATH "/" FNAME1) +#define FULL_FNAME2 (MNT_PATH "/" FNAME2) +#define FULL_FNAME_RNMD (MNT_PATH "/" FNAME_RNMD) +#define FULL_FNAME_NXIST (MNT_PATH "/" FNAME_NXIST) +#define DIR_NAME "SOMEDIR" + +static const char test_txt[] = "the test file content 123 abc"; +static const char test_txt2[] = "another text"; +static const char test_txt3[] = "hello world for vfs"; + +static fatfs_desc_t fatfs = { + .vol_idx = 0 +}; + +static vfs_mount_t _test_vfs_mount = { + .mount_point = MNT_PATH, + .fs = &fatfs_file_system, + .private_data = (void *)&fatfs, +}; + +static void print_test_result(const char *test_name, int ok) +{ + printf("%s:[%s]\n", test_name, ok ? "OK" : "FAILED"); +} + +static void test_mount(void) +{ + print_test_result("test_mount__mount", vfs_mount(&_test_vfs_mount) == 0); + print_test_result("test_mount__umount", vfs_umount(&_test_vfs_mount) == 0); +} + +static void test_open(void) +{ + int fd; + print_test_result("test_open__mount", vfs_mount(&_test_vfs_mount) == 0); + + /* try to open file that doesn't exist */ + fd = vfs_open(FULL_FNAME_NXIST, O_RDONLY, 0); + print_test_result("test_open__open", fd == -ENOENT); + + /* open file with RO, WO and RW access */ + fd = vfs_open(FULL_FNAME1, O_RDONLY, 0); + print_test_result("test_open__open_ro", fd >= 0); + print_test_result("test_open__close_ro", vfs_close(fd) == 0); + fd = vfs_open(FULL_FNAME1, O_WRONLY, 0); + print_test_result("test_open__open_wo", fd >= 0); + print_test_result("test_open__close_wo", vfs_close(fd) == 0); + fd = vfs_open(FULL_FNAME1, O_RDWR, 0); + print_test_result("test_open__open_rw", fd >= 0); + print_test_result("test_open__close_rw", vfs_close(fd) == 0); + + print_test_result("test_open__umount", vfs_umount(&_test_vfs_mount) == 0); +} + +static void test_rw(void) +{ + char buf[sizeof(test_txt) + sizeof(test_txt2)]; + int fd; + ssize_t nr, nw; + off_t new_pos; + + print_test_result("test_rw__mount", vfs_mount(&_test_vfs_mount) == 0); + + fd = vfs_open(FULL_FNAME1, O_RDONLY, 0); + print_test_result("test_rw__open_ro", fd >= 0); + + /* compare file content with expected value */ + memset(buf, 0, sizeof(buf)); + nr = vfs_read(fd, buf, sizeof(test_txt)); + print_test_result("test_rw__read_ro", (nr == sizeof(test_txt)) && + (strncmp(buf, test_txt, sizeof(test_txt)) == 0)); + + /* try to write to RO file (success if no bytes are actually written) */ + nw = vfs_write(fd, test_txt2, sizeof(test_txt2)); + print_test_result("test_rw__write_ro", nw <= 0); + print_test_result("test_rw__close_ro", vfs_close(fd) == 0); + + fd = vfs_open("/test/test.txt", O_WRONLY, 0); + print_test_result("test_rw__open_wo", fd >= 0); + + /* try to read from WO file (success if no bytes are actually read) */ + nr = vfs_read(fd, buf, sizeof(test_txt)); + print_test_result("test_rw__read_wo", nw <= 0); + + print_test_result("test_rw__close_wo", vfs_close(fd) == 0); + + memset(buf, 0, sizeof(buf)); + fd = vfs_open(FULL_FNAME1, O_RDWR, 0); + print_test_result("test_rw__open_rw", fd >= 0); + + /* read file content and compare it to the expected value */ + nr = vfs_read(fd, buf, sizeof(test_txt)); + print_test_result("test_rw__read_rw", (nr == sizeof(test_txt)) && + (strncmp(buf, test_txt, sizeof(test_txt)) == 0)); + + /* write to the file (text should be appended to the end of file) */ + nw = vfs_write(fd, test_txt2, sizeof(test_txt2)); + print_test_result("test_rw__write_rw", nw == sizeof(test_txt2)); + + /* seek to start of file */ + new_pos = vfs_lseek(fd, 0, SEEK_SET); + print_test_result("test_rw__lseek", new_pos == 0); + + /* read file content and compare to expected value */ + memset(buf, 0, sizeof(buf)); + nr = vfs_read(fd, buf, sizeof(buf)); + print_test_result("test_rw__read_rw", (nr == sizeof(buf)) && + (strncmp(buf, test_txt, sizeof(test_txt)) == 0) && + (strncmp(&buf[sizeof(test_txt)], + test_txt2, + sizeof(test_txt2)) == 0)); + + + print_test_result("test_rw__close_rw", vfs_close(fd) == 0); + + /* create new file */ + fd = vfs_open(FULL_FNAME2, O_RDWR | O_CREAT, 0); + print_test_result("test_rw__open_rwc", fd >= 0); + + /* write to the new file, read it's content and compare to expected value */ + nw = vfs_write(fd, test_txt3, sizeof(test_txt3)); + print_test_result("test_rw__write_rwc", nw == sizeof(test_txt3)); + + new_pos = vfs_lseek(fd, 0, SEEK_SET); + print_test_result("test_rw__lseek_rwc", new_pos == 0); + + memset(buf, 0, sizeof(buf)); + nr = vfs_read(fd, buf, sizeof(test_txt3)); + print_test_result("test_rw__read_rwc", (nr == sizeof(test_txt3)) && + (strncmp(buf, test_txt3, sizeof(test_txt3)) == 0)); + + print_test_result("test_rw__close_rwc", vfs_close(fd) == 0); + print_test_result("test_rw__umount", vfs_umount(&_test_vfs_mount) == 0); +} + +static void test_dir(void) +{ + vfs_DIR dir; + vfs_dirent_t entry; + vfs_dirent_t entry2; + + print_test_result("test_dir__mount", vfs_mount(&_test_vfs_mount) == 0); + print_test_result("test_dir__opendir", vfs_opendir(&dir, MNT_PATH) == 0); + print_test_result("test_dir__readdir1", vfs_readdir(&dir, &entry) == 1); + print_test_result("test_dir__readdir2", vfs_readdir(&dir, &entry2) == 1); + + print_test_result("test_dir__readdir_name", + ((strncmp(FNAME1, entry.d_name, sizeof(FNAME1)) == 0) && + (strncmp(FNAME2, entry2.d_name, sizeof(FNAME2)) == 0)) + || + ((strncmp(FNAME1, entry2.d_name, sizeof(FNAME1)) == 0) && + (strncmp(FNAME2, entry.d_name, sizeof(FNAME2)) == 0))); + + print_test_result("test_dir__readdir3", vfs_readdir(&dir, &entry2) == 0); + print_test_result("test_dir__closedir", vfs_closedir(&dir) == 0); + print_test_result("test_dir__umount", vfs_umount(&_test_vfs_mount) == 0); +} + + +static void test_rename(void) +{ + vfs_DIR dir; + vfs_dirent_t entry; + vfs_dirent_t entry2; + + print_test_result("test_rename__mount", vfs_mount(&_test_vfs_mount) == 0); + + print_test_result("test_rename__rename", + vfs_rename(FULL_FNAME1, FULL_FNAME_RNMD) == 0); + + print_test_result("test_rename__opendir", vfs_opendir(&dir, MNT_PATH) == 0); + print_test_result("test_rename__readdir1", vfs_readdir(&dir, &entry) == 1); + print_test_result("test_rename__readdir2", vfs_readdir(&dir, &entry2) == 1); + + print_test_result("test_rename__check_name", + ((strncmp(FNAME_RNMD, entry.d_name, sizeof(FNAME_RNMD)) == 0) && + (strncmp(FNAME2, entry2.d_name, sizeof(FNAME2)) == 0)) + || + ((strncmp(FNAME_RNMD, entry2.d_name, sizeof(FNAME_RNMD)) == 0) && + (strncmp(FNAME2, entry.d_name, sizeof(FNAME2)) == 0))); + + print_test_result("test_rename__readdir3", vfs_readdir(&dir, &entry2) == 0); + print_test_result("test_rename__closedir", vfs_closedir(&dir) == 0); + print_test_result("test_rename__umount", vfs_umount(&_test_vfs_mount) == 0); +} + +static void test_unlink(void) +{ + vfs_DIR dir; + vfs_dirent_t entry; + + print_test_result("test_unlink__mount", vfs_mount(&_test_vfs_mount) == 0); + print_test_result("test_unlink__unlink1", vfs_unlink(FULL_FNAME2) == 0); + print_test_result("test_unlink__unlink2", vfs_unlink(FULL_FNAME_RNMD) == 0); + print_test_result("test_unlink__opendir", vfs_opendir(&dir, MNT_PATH) == 0); + print_test_result("test_unlink__readdir", vfs_readdir(&dir, &entry) == 0); + print_test_result("test_unlink__closedir", vfs_closedir(&dir) == 0); + print_test_result("test_unlink__umount", vfs_umount(&_test_vfs_mount) == 0); +} + +static void test_mkrmdir(void) +{ + vfs_DIR dir; + + print_test_result("test_mkrmdir__mount", vfs_mount(&_test_vfs_mount) == 0); + + print_test_result("test_mkrmdir__mkdir", + vfs_mkdir(MNT_PATH"/"DIR_NAME, 0) == 0); + + print_test_result("test_mkrmdir__opendir1", + vfs_opendir(&dir, MNT_PATH"/"DIR_NAME) == 0); + + print_test_result("test_mkrmdir__closedir", vfs_closedir(&dir) == 0); + + print_test_result("test_mkrmdir__rmdir", + vfs_rmdir(MNT_PATH"/"DIR_NAME) == 0); + + print_test_result("test_mkrmdir__opendir2", + vfs_opendir(&dir, MNT_PATH"/"DIR_NAME) < 0); + + print_test_result("test_mkrmdir__umount", + vfs_umount(&_test_vfs_mount) == 0); +} + +static void test_create(void) +{ + int fd; + ssize_t nw; + print_test_result("test_create__mount", vfs_mount(&_test_vfs_mount) == 0); + + fd = vfs_open(FULL_FNAME1, O_WRONLY | O_CREAT, 0); + print_test_result("test_create__open_wo", fd >= 0); + + nw = vfs_write(fd, test_txt, sizeof(test_txt)); + print_test_result("test_create__write_wo", nw == sizeof(test_txt)); + print_test_result("test_create__close_wo", vfs_close(fd) == 0); + print_test_result("test_create__umount", vfs_umount(&_test_vfs_mount) == 0); +} + + +int main(void) +{ + #if FATFS_FFCONF_OPT_FS_NORTC == 0 + rtc_init(); + #endif + + printf("Tests for FatFs over VFS - test results will be printed " + "in the format test_name:result\n"); + + test_mount(); + test_open(); + test_rw(); + test_dir(); + test_rename(); + test_unlink(); + test_mkrmdir(); + test_create(); + + printf("Test end.\n"); + + return 0; +} diff --git a/tests/pkg_fatfs_vfs/riot_fatfs_disk.tar.gz b/tests/pkg_fatfs_vfs/riot_fatfs_disk.tar.gz new file mode 100644 index 0000000000..7bd72ebc2c Binary files /dev/null and b/tests/pkg_fatfs_vfs/riot_fatfs_disk.tar.gz differ diff --git a/tests/pkg_fatfs_vfs/tests/01-run.py b/tests/pkg_fatfs_vfs/tests/01-run.py new file mode 100755 index 0000000000..6150bbc8c5 --- /dev/null +++ b/tests/pkg_fatfs_vfs/tests/01-run.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +# Copyright (C) 2017 HAW-Hamburg.de +# +# 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. + +import os +import sys + +sys.path.append(os.path.join(os.environ['RIOTBASE'], 'dist/tools/testrunner')) +import testrunner + +from datetime import datetime + +class TestFailed(Exception): + pass + +def testfunc(child): + + child.expect(u"Tests for FatFs over VFS - test results will be printed in " + "the format test_name:result\r\n") + + while True: + res = child.expect([u"[^\n]*:\[OK\]\r\n", + u"Test end.\r\n", + u".[^\n]*:\[FAILED\]\r\n" , + u".*\r\n"]) + if res > 1: + raise TestFailed(child.after.split(':',1)[0] + " test failed!") + elif res == 1: + break; + +if __name__ == "__main__": + sys.exit(testrunner.run(testfunc))