From 8fae39166bbb8f39fdecc76211f53e9bb4c799b3 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 12 Jul 2017 04:40:59 -0500 Subject: [PATCH 01/46] Initial commit --- LittleFileSystem.cpp | 342 +++++++++++++++++++++++++++++++++++++++++++ LittleFileSystem.h | 267 +++++++++++++++++++++++++++++++++ mbed_lib.json | 25 ++++ 3 files changed, 634 insertions(+) create mode 100644 LittleFileSystem.cpp create mode 100644 LittleFileSystem.h create mode 100644 mbed_lib.json diff --git a/LittleFileSystem.cpp b/LittleFileSystem.cpp new file mode 100644 index 0000000000..a827497d14 --- /dev/null +++ b/LittleFileSystem.cpp @@ -0,0 +1,342 @@ +/* mbed Microcontroller Library + * Copyright (c) 2006-2012 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "mbed.h" +#include "LittleFileSystem.h" +#include "errno.h" + + +////// Conversion functions ////// +static int lfs_toerror(int err) { + switch (err) { + case LFS_ERR_OK: return 0; + case LFS_ERR_IO: return -EIO; + case LFS_ERR_NOENT: return -ENOENT; + case LFS_ERR_EXISTS: return -EEXIST; + case LFS_ERR_NOTDIR: return -ENOTDIR; + case LFS_ERR_ISDIR: return -EISDIR; + case LFS_ERR_INVAL: return -EINVAL; + case LFS_ERR_NOSPC: return -ENOSPC; + case LFS_ERR_NOMEM: return -ENOMEM; + default: return err; + } +} + +static int lfs_fromflags(int flags) { + return ( + (((flags & 3) == O_RDONLY) ? LFS_O_RDONLY : 0) | + (((flags & 3) == O_WRONLY) ? LFS_O_WRONLY : 0) | + (((flags & 3) == O_RDWR) ? LFS_O_RDWR : 0) | + ((flags & O_CREAT) ? LFS_O_CREAT : 0) | + ((flags & O_EXCL) ? LFS_O_EXCL : 0) | + ((flags & O_TRUNC) ? LFS_O_TRUNC : 0) | + ((flags & O_APPEND) ? LFS_O_APPEND : 0)); +} + +static int lfs_fromwhence(int whence) { + switch (whence) { + case SEEK_SET: return LFS_SEEK_SET; + case SEEK_CUR: return LFS_SEEK_CUR; + case SEEK_END: return LFS_SEEK_END; + default: return whence; + } +} + +static int lfs_tomode(int type) { + int mode = S_IRWXU | S_IRWXG | S_IRWXO; + switch (type) { + case LFS_TYPE_DIR: return mode | S_IFDIR; + case LFS_TYPE_REG: return mode | S_IFREG; + default: return 0; + } +} + +static int lfs_totype(int type) { + switch (type) { + case LFS_TYPE_DIR: return DT_DIR; + case LFS_TYPE_REG: return DT_REG; + default: return DT_UNKNOWN; + } +} + + +////// Block device operations ////// +static int lfs_bd_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + BlockDevice *bd = (BlockDevice *)c->context; + return bd->read(buffer, block*c->block_size + off, size); +} + +static int lfs_bd_prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + BlockDevice *bd = (BlockDevice *)c->context; + return bd->program(buffer, block*c->block_size + off, size); +} + +static int lfs_bd_erase(const struct lfs_config *c, lfs_block_t block) { + BlockDevice *bd = (BlockDevice *)c->context; + return bd->erase(block*c->block_size, c->block_size); +} + +static int lfs_bd_sync(const struct lfs_config *c) { + return 0; +} + + +////// Generic filesystem operations ////// + +// Filesystem implementation (See LittleFileSystem.h) +LittleFileSystem::LittleFileSystem(const char *name, BlockDevice *bd, + lfs_size_t read_size, lfs_size_t prog_size, + lfs_size_t block_size, lfs_size_t lookahead) + : FileSystem(name) + , _read_size(read_size) + , _prog_size(prog_size) + , _block_size(block_size) + , _lookahead(lookahead) { + if (bd) { + mount(bd); + } +} + +LittleFileSystem::~LittleFileSystem() { + // nop if unmounted + unmount(); +} + +int LittleFileSystem::mount(BlockDevice *bd) { + _bd = bd; + int err = _bd->init(); + if (err) { + return err; + } + + memset(&_config, 0, sizeof(_config)); + _config.context = bd; + _config.read = lfs_bd_read; + _config.prog = lfs_bd_prog; + _config.erase = lfs_bd_erase; + _config.sync = lfs_bd_sync; + _config.read_size = bd->get_read_size(); + if (_config.read_size < _read_size) { + _config.read_size = _read_size; + } + _config.prog_size = bd->get_program_size(); + if (_config.prog_size < _prog_size) { + _config.prog_size = _prog_size; + } + _config.block_size = bd->get_erase_size(); + if (_config.block_size < _block_size) { + _config.block_size = _block_size; + } + _config.block_count = bd->size() / _config.block_size; + _config.lookahead = _config.block_count - _config.block_count % 32; + if (_config.lookahead > _lookahead) { + _config.lookahead = _lookahead; + } + + err = lfs_mount(&_lfs, &_config); + return lfs_toerror(err); +} + +int LittleFileSystem::unmount() { + if (_bd) { + int err = lfs_unmount(&_lfs); + if (err) { + return lfs_toerror(err); + } + + err = _bd->deinit(); + if (err) { + return err; + } + + _bd = NULL; + } + + return 0; +} + +int LittleFileSystem::format(BlockDevice *bd, + lfs_size_t read_size, lfs_size_t prog_size, + lfs_size_t block_size, lfs_size_t lookahead) { + int err = bd->init(); + if (err) { + return err; + } + + lfs_t _lfs; + struct lfs_config _config; + + memset(&_config, 0, sizeof(_config)); + _config.context = bd; + _config.read = lfs_bd_read; + _config.prog = lfs_bd_prog; + _config.erase = lfs_bd_erase; + _config.sync = lfs_bd_sync; + _config.read_size = bd->get_read_size(); + if (_config.read_size < read_size) { + _config.read_size = read_size; + } + _config.prog_size = bd->get_program_size(); + if (_config.prog_size < prog_size) { + _config.prog_size = prog_size; + } + _config.block_size = bd->get_erase_size(); + if (_config.block_size < block_size) { + _config.block_size = block_size; + } + _config.block_count = bd->size() / _config.block_size; + _config.lookahead = _config.block_count - _config.block_count % 32; + if (_config.lookahead > lookahead) { + _config.lookahead = lookahead; + } + + err = lfs_format(&_lfs, &_config); + if (err) { + return lfs_toerror(err); + } + + err = bd->deinit(); + if (err) { + return err; + } + + return 0; +} + +int LittleFileSystem::remove(const char *filename) { + int err = lfs_remove(&_lfs, filename); + return lfs_toerror(err); +} + +int LittleFileSystem::rename(const char *oldname, const char *newname) { + int err = lfs_rename(&_lfs, oldname, newname); + return lfs_toerror(err); +} + +int LittleFileSystem::mkdir(const char *name, mode_t mode) { + int err = lfs_mkdir(&_lfs, name); + return lfs_toerror(err); +} + +int LittleFileSystem::stat(const char *name, struct stat *st) { + struct lfs_info info; + int err = lfs_stat(&_lfs, name, &info); + st->st_size = info.size; + st->st_mode = lfs_tomode(info.type); + return lfs_toerror(err); +} + + +////// File operations ////// +int LittleFileSystem::file_open(fs_file_t *file, const char *path, int flags) { + lfs_file_t *f = new lfs_file_t; + *file = f; + int err = lfs_file_open(&_lfs, f, path, lfs_fromflags(flags)); + return lfs_toerror(err); +} + +int LittleFileSystem::file_close(fs_file_t file) { + lfs_file_t *f = (lfs_file_t *)file; + int err = lfs_file_close(&_lfs, f); + delete f; + return lfs_toerror(err); +} + +ssize_t LittleFileSystem::file_read(fs_file_t file, void *buffer, size_t len) { + lfs_file_t *f = (lfs_file_t *)file; + int res = lfs_file_read(&_lfs, f, buffer, len); + return lfs_toerror(res); +} + +ssize_t LittleFileSystem::file_write(fs_file_t file, const void *buffer, size_t len) { + lfs_file_t *f = (lfs_file_t *)file; + int res = lfs_file_write(&_lfs, f, buffer, len); + return lfs_toerror(res); +} + +int LittleFileSystem::file_sync(fs_file_t file) { + lfs_file_t *f = (lfs_file_t *)file; + int err = lfs_file_sync(&_lfs, f); + return lfs_toerror(err); +} + +off_t LittleFileSystem::file_seek(fs_file_t file, off_t offset, int whence) { + lfs_file_t *f = (lfs_file_t *)file; + off_t res = lfs_file_seek(&_lfs, f, offset, lfs_fromwhence(whence)); + return lfs_toerror(res); +} + +off_t LittleFileSystem::file_tell(fs_file_t file) { + lfs_file_t *f = (lfs_file_t *)file; + off_t res = lfs_file_tell(&_lfs, f); + return lfs_toerror(res); +} + +off_t LittleFileSystem::file_size(fs_file_t file) { + lfs_file_t *f = (lfs_file_t *)file; + off_t res = lfs_file_size(&_lfs, f); + return lfs_toerror(res); +} + + +////// Dir operations ////// +int LittleFileSystem::dir_open(fs_dir_t *dir, const char *path) { + lfs_dir_t *d = new lfs_dir_t; + *dir = d; + int err = lfs_dir_open(&_lfs, d, path); + return lfs_toerror(err); +} + +int LittleFileSystem::dir_close(fs_dir_t dir) { + lfs_dir_t *d = (lfs_dir_t *)dir; + int err = lfs_dir_close(&_lfs, d); + delete d; + return lfs_toerror(err); +} + +ssize_t LittleFileSystem::dir_read(fs_dir_t dir, struct dirent *ent) { + lfs_dir_t *d = (lfs_dir_t *)dir; + struct lfs_info info; + int res = lfs_dir_read(&_lfs, d, &info); + if (res == 1) { + ent->d_type = lfs_totype(info.type); + strcpy(ent->d_name, info.name); + } + return lfs_toerror(res); +} + +void LittleFileSystem::dir_seek(fs_dir_t dir, off_t offset) { + lfs_dir_t *d = (lfs_dir_t *)dir; + lfs_dir_seek(&_lfs, d, offset); +} + +off_t LittleFileSystem::dir_tell(fs_dir_t dir) { + lfs_dir_t *d = (lfs_dir_t *)dir; + return lfs_dir_tell(&_lfs, d); +} + +void LittleFileSystem::dir_rewind(fs_dir_t dir) { + lfs_dir_t *d = (lfs_dir_t *)dir; + lfs_dir_rewind(&_lfs, d); +} + diff --git a/LittleFileSystem.h b/LittleFileSystem.h new file mode 100644 index 0000000000..6f1c173060 --- /dev/null +++ b/LittleFileSystem.h @@ -0,0 +1,267 @@ +/* mbed Microcontroller Library + * Copyright (c) 2006-2012 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef MBED_LFSFILESYSTEM_H +#define MBED_LFSFILESYSTEM_H + +#include "FileSystem.h" +#include "BlockDevice.h" +extern "C" { +#include "lfs.h" +} + +using namespace mbed; + + +/** + * LittleFileSystem, a little filesystem + */ +class LittleFileSystem : public FileSystem { +public: + /** Lifetime of the LittleFileSystem + * + * @param name Name to add filesystem to tree as + * @param bd BlockDevice to mount, may be passed instead to mount call + * @param read_size + * Minimum size of a block read. This determines the size of read buffers. + * This may be larger than the physical read size to improve performance + * by caching more of the block device. + * @param prog_size + * Minimum size of a block program. This determines the size of program + * buffers. This may be larger than the physical program size to improve + * performance by caching more of the block device. + * @param block_size + * Size of an erasable block. This does not impact ram consumption and + * may be larger than the physical erase size. However, this should be + * kept small as each file currently takes up an entire block. + * @param lookahead + * Number of blocks to lookahead during block allocation. A larger + * lookahead reduces the number of passes required to allocate a block. + * The lookahead buffer requires only 1 bit per block so it can be quite + * large with little ram impact. Should be a multiple of 32. + */ + LittleFileSystem(const char *name=NULL, BlockDevice *bd=NULL, + lfs_size_t read_size=MBED_LFS_READ_SIZE, + lfs_size_t prog_size=MBED_LFS_PROG_SIZE, + lfs_size_t block_size=MBED_LFS_BLOCK_SIZE, + lfs_size_t lookahead=MBED_LFS_LOOKAHEAD); + virtual ~LittleFileSystem(); + + /** Formats a logical drive, FDISK partitioning rule. + * + * The block device to format should be mounted when this function is called. + * + * @param bd This is the block device that will be formated. + * @param read_size + * Minimum size of a block read. This determines the size of read buffers. + * This may be larger than the physical read size to improve performance + * by caching more of the block device. + * @param prog_size + * Minimum size of a block program. This determines the size of program + * buffers. This may be larger than the physical program size to improve + * performance by caching more of the block device. + * @param block_size + * Size of an erasable block. This does not impact ram consumption and + * may be larger than the physical erase size. However, this should be + * kept small as each file currently takes up an entire block. + * @param lookahead + * Number of blocks to lookahead during block allocation. A larger + * lookahead reduces the number of passes required to allocate a block. + * The lookahead buffer requires only 1 bit per block so it can be quite + * large with little ram impact. Should be a multiple of 32. + */ + static int format(BlockDevice *bd, + lfs_size_t read_size=MBED_LFS_READ_SIZE, + lfs_size_t prog_size=MBED_LFS_PROG_SIZE, + lfs_size_t block_size=MBED_LFS_BLOCK_SIZE, + lfs_size_t lookahead=MBED_LFS_LOOKAHEAD); + + /** Mounts a filesystem to a block device + * + * @param bd BlockDevice to mount to + * @return 0 on success, negative error code on failure + */ + virtual int mount(BlockDevice *bd); + + /** Unmounts a filesystem from the underlying block device + * + * @return 0 on success, negative error code on failure + */ + virtual int unmount(); + + /** Remove a file from the filesystem. + * + * @param path The name of the file to remove. + * @return 0 on success, negative error code on failure + */ + virtual int remove(const char *path); + + /** Rename a file in the filesystem. + * + * @param path The name of the file to rename. + * @param newpath The name to rename it to + * @return 0 on success, negative error code on failure + */ + virtual int rename(const char *path, const char *newpath); + + /** Store information about the file in a stat structure + * + * @param path The name of the file to find information about + * @param st The stat buffer to write to + * @return 0 on success, negative error code on failure + */ + virtual int stat(const char *path, struct stat *st); + + /** Create a directory in the filesystem. + * + * @param path The name of the directory to create. + * @param mode The permissions with which to create the directory + * @return 0 on success, negative error code on failure + */ + virtual int mkdir(const char *path, mode_t mode); + +protected: + /** Open a file on the filesystem + * + * @param file Destination for the handle to a newly created file + * @param path The name of the file to open + * @param flags The flags to open the file in, one of O_RDONLY, O_WRONLY, O_RDWR, + * bitwise or'd with one of O_CREAT, O_TRUNC, O_APPEND + * @return 0 on success, negative error code on failure + */ + virtual int file_open(fs_file_t *file, const char *path, int flags); + + /** Close a file + * + * @param file File handle + * return 0 on success, negative error code on failure + */ + virtual int file_close(fs_file_t file); + + /** Read the contents of a file into a buffer + * + * @param file File handle + * @param buffer The buffer to read in to + * @param size The number of bytes to read + * @return The number of bytes read, 0 at end of file, negative error on failure + */ + virtual ssize_t file_read(fs_file_t file, void *buffer, size_t len); + + /** Write the contents of a buffer to a file + * + * @param file File handle + * @param buffer The buffer to write from + * @param size The number of bytes to write + * @return The number of bytes written, negative error on failure + */ + virtual ssize_t file_write(fs_file_t file, const void *buffer, size_t len); + + /** Flush any buffers associated with the file + * + * @param file File handle + * @return 0 on success, negative error code on failure + */ + virtual int file_sync(fs_file_t file); + + /** Move the file position to a given offset from from a given location + * + * @param file File handle + * @param offset The offset from whence to move to + * @param whence The start of where to seek + * SEEK_SET to start from beginning of file, + * SEEK_CUR to start from current position in file, + * SEEK_END to start from end of file + * @return The new offset of the file + */ + virtual off_t file_seek(fs_file_t file, off_t offset, int whence); + + /** Get the file position of the file + * + * @param file File handle + * @return The current offset in the file + */ + virtual off_t file_tell(fs_file_t file); + + /** Get the size of the file + * + * @param file File handle + * @return Size of the file in bytes + */ + virtual off_t file_size(fs_file_t file); + + /** Open a directory on the filesystem + * + * @param dir Destination for the handle to the directory + * @param path Name of the directory to open + * @return 0 on success, negative error code on failure + */ + virtual int dir_open(fs_dir_t *dir, const char *path); + + /** Close a directory + * + * @param dir Dir handle + * return 0 on success, negative error code on failure + */ + virtual int dir_close(fs_dir_t dir); + + /** Read the next directory entry + * + * @param dir Dir handle + * @param ent The directory entry to fill out + * @return 1 on reading a filename, 0 at end of directory, negative error on failure + */ + virtual ssize_t dir_read(fs_dir_t dir, struct dirent *ent); + + /** Set the current position of the directory + * + * @param dir Dir handle + * @param offset Offset of the location to seek to, + * must be a value returned from dir_tell + */ + virtual void dir_seek(fs_dir_t dir, off_t offset); + + /** Get the current position of the directory + * + * @param dir Dir handle + * @return Position of the directory that can be passed to dir_rewind + */ + virtual off_t dir_tell(fs_dir_t dir); + + /** Rewind the current position to the beginning of the directory + * + * @param dir Dir handle + */ + virtual void dir_rewind(fs_dir_t dir); + +private: + lfs_t _lfs; // _the actual filesystem + struct lfs_config _config; + BlockDevice *_bd; // the block device + + // default parameters + const lfs_size_t _read_size; + const lfs_size_t _prog_size; + const lfs_size_t _block_size; + const lfs_size_t _lookahead; +}; + + +#endif diff --git a/mbed_lib.json b/mbed_lib.json new file mode 100644 index 0000000000..7d2efc807a --- /dev/null +++ b/mbed_lib.json @@ -0,0 +1,25 @@ +{ + "name": "littlefs", + "config": { + "read_size": { + "macro_name": "MBED_LFS_READ_SIZE", + "value": 64, + "help": "Minimum size of a block read. This determines the size of read buffers. This may be larger than the physical read size to improve performance by caching more of the block device." + }, + "prog_size": { + "macro_name": "MBED_LFS_PROG_SIZE", + "value": 64, + "help": "Minimum size of a block program. This determines the size of program buffers. This may be larger than the physical program size to improve performance by caching more of the block device." + }, + "block_size": { + "macro_name": "MBED_LFS_BLOCK_SIZE", + "value": 512, + "help": "Size of an erasable block. This does not impact ram consumption and may be larger than the physical erase size. However, this should be kept small as each file currently takes up an entire block." + }, + "lookahead": { + "macro_name": "MBED_LFS_LOOKAHEAD", + "value": 128, + "help": "Number of blocks to lookahead during block allocation. A larger lookahead reduces the number of passes required to allocate a block. The lookahead buffer requires only 1 bit per block so it can be quite large with little ram impact. Should be a multiple of 32." + } + } +} From 98af79f087e8d95b28614e982a144a122f2dcbda Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 12 Jul 2017 04:44:33 -0500 Subject: [PATCH 02/46] Squashed 'littlefs/' content from commit 663e953 git-subtree-dir: littlefs git-subtree-split: 663e953a5073ac8e459f60d18375f887f14e21a2 --- .travis.yml | 4 + DESIGN.md | 973 ++++++++++++++++++ LICENSE.md | 165 +++ Makefile | 57 ++ README.md | 142 +++ emubd/lfs_emubd.c | 242 +++++ emubd/lfs_emubd.h | 78 ++ lfs.c | 2146 ++++++++++++++++++++++++++++++++++++++++ lfs.h | 435 ++++++++ lfs_util.c | 25 + lfs_util.h | 45 + tests/stats.py | 30 + tests/template.fmt | 105 ++ tests/test.py | 49 + tests/test_alloc.sh | 261 +++++ tests/test_corrupt.sh | 106 ++ tests/test_dirs.sh | 287 ++++++ tests/test_files.sh | 114 +++ tests/test_format.sh | 49 + tests/test_orphan.sh | 41 + tests/test_parallel.sh | 186 ++++ tests/test_paths.sh | 86 ++ tests/test_seek.sh | 281 ++++++ 23 files changed, 5907 insertions(+) create mode 100644 .travis.yml create mode 100644 DESIGN.md create mode 100644 LICENSE.md create mode 100644 Makefile create mode 100644 README.md create mode 100644 emubd/lfs_emubd.c create mode 100644 emubd/lfs_emubd.h create mode 100644 lfs.c create mode 100644 lfs.h create mode 100644 lfs_util.c create mode 100644 lfs_util.h create mode 100755 tests/stats.py create mode 100644 tests/template.fmt create mode 100755 tests/test.py create mode 100755 tests/test_alloc.sh create mode 100755 tests/test_corrupt.sh create mode 100755 tests/test_dirs.sh create mode 100755 tests/test_files.sh create mode 100755 tests/test_format.sh create mode 100755 tests/test_orphan.sh create mode 100755 tests/test_parallel.sh create mode 100755 tests/test_paths.sh create mode 100755 tests/test_seek.sh diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..0936b29f44 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +script: + - CFLAGS="-DLFS_READ_SIZE=16 -DLFS_PROG_SIZE=16" make test + - CFLAGS="-DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make test + - CFLAGS="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make test diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000000..3381c7919c --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,973 @@ +## The design of the little filesystem + +The littlefs is a little fail-safe filesystem designed for embedded systems. + +``` + | | | .---._____ + .-----. | | +--|o |---| littlefs | +--| |---| | + '-----' '----------' + | | | +``` + +For a bit of backstory, the littlefs was developed with the goal of learning +more about filesystem design by tackling the relative unsolved problem of +managing a robust filesystem resilient to power loss on devices +with limited RAM and ROM. + +The embedded systems the littlefs is targeting are usually 32bit +microcontrollers with around 32Kbytes of RAM and 512Kbytes of ROM. These are +often paired with SPI NOR flash chips with about 4Mbytes of flash storage. + +Flash itself is a very interesting piece of technology with quite a bit of +nuance. Unlike most other forms of storage, writing to flash requires two +operations: erasing and programming. The programming operation is relatively +cheap, and can be very granular. For NOR flash specifically, byte-level +programs are quite common. Erasing, however, requires an expensive operation +that forces the state of large blocks of memory to reset in a destructive +reaction that gives flash its name. The [Wikipedia entry](https://en.wikipedia.org/wiki/Flash_memory) +has more information if you are interesting in how this works. + +This leaves us with an interesting set of limitations that can be simplified +to three strong requirements: + +1. **Fail-safe** - This is actually the main goal of the littlefs and the focus + of this project. Embedded systems are usually designed without a shutdown + routine and a notable lack of user interface for recovery, so filesystems + targeting embedded systems should be prepared to lose power an any given + time. + + Despite this state of things, there are very few embedded filesystems that + handle power loss in a reasonable manner, and can become corrupted if the + user is unlucky enough. + +2. **Wear awareness** - Due to the destructive nature of flash, most flash + chips have a limited number of erase cycles, usually in the order of around + 100,000 erases per block for NOR flash. Filesystems that don't take wear + into account can easily burn through blocks used to store frequently updated + metadata. + + Consider the [FAT filesystem](https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system), + which stores a file allocation table (FAT) at a specific offset from the + beginning of disk. Every block allocation will update this table, and after + 100,000 updates, the block will likely go bad, rendering the filesystem + unusable even if there are many more erase cycles available on the storage. + +3. **Bounded RAM/ROM** - Even with the design difficulties presented by the + previous two limitations, we have already seen several flash filesystems + developed on PCs that handle power loss just fine, such as the + logging filesystems. However, these filesystems take advantage of the + relatively cheap access to RAM, and use some rather... opportunistic... + techniques, such as reconstructing the entire directory structure in RAM. + These operations make perfect sense when the filesystem's only concern is + erase cycles, but the idea is a bit silly on embedded systems. + + To cater to embedded systems, the littlefs has the simple limitation of + using only a bounded amount of RAM and ROM. That is, no matter what is + written to the filesystem, and no matter how large the underlying storage + is, the littlefs will always use the same amount of RAM and ROM. This + presents a very unique challenge, and makes presumably simple operations, + such as iterating through the directory tree, surprisingly difficult. + +## Existing designs? + +There are of course, many different existing filesystem. Heres a very rough +summary of the general ideas behind some of them. + +Most of the existing filesystems fall into the one big category of filesystem +designed in the early days of spinny magnet disks. While there is a vast amount +of interesting technology and ideas in this area, the nature of spinny magnet +disks encourage properties such as grouping writes near each other, that don't +make as much sense on recent storage types. For instance, on flash, write +locality is not as important and can actually increase wear destructively. + +One of the most popular designs for flash filesystems is called the +[logging filesystem](https://en.wikipedia.org/wiki/Log-structured_file_system). +The flash filesystems [jffs](https://en.wikipedia.org/wiki/JFFS) +and [yaffs](https://en.wikipedia.org/wiki/YAFFS) are good examples. In +logging filesystem, data is not store in a data structure on disk, but instead +the changes to the files are stored on disk. This has several neat advantages, +such as the fact that the data is written in a cyclic log format naturally +wear levels as a side effect. And, with a bit of error detection, the entire +filesystem can easily be designed to be resilient to power loss. The +journalling component of most modern day filesystems is actually a reduced +form of a logging filesystem. However, logging filesystems have a difficulty +scaling as the size of storage increases. And most filesystems compensate by +caching large parts of the filesystem in RAM, a strategy that is unavailable +for embedded systems. + +Another interesting filesystem design technique that the littlefs borrows the +most from, is the [copy-on-write (COW)](https://en.wikipedia.org/wiki/Copy-on-write). +A good example of this is the [btrfs](https://en.wikipedia.org/wiki/Btrfs) +filesystem. COW filesystems can easily recover from corrupted blocks and have +natural protection against power loss. However, if they are not designed with +wear in mind, a COW filesystem could unintentionally wear down the root block +where the COW data structures are synchronized. + +## Metadata pairs + +The core piece of technology that provides the backbone for the littlefs is +the concept of metadata pairs. The key idea here, is that any metadata that +needs to be updated atomically is stored on a pair of blocks tagged with +a revision count and checksum. Every update alternates between these two +pairs, so that at any time there is always a backup containing the previous +state of the metadata. + +Consider a small example where each metadata pair has a revision count, +a number as data, and the xor of the block as a quick checksum. If +we update the data to a value of 9, and then to a value of 5, here is +what the pair of blocks may look like after each update: +``` + block 1 block 2 block 1 block 2 block 1 block 2 +.---------.---------. .---------.---------. .---------.---------. +| rev: 1 | rev: 0 | | rev: 1 | rev: 2 | | rev: 3 | rev: 2 | +| data: 3 | data: 0 | -> | data: 3 | data: 9 | -> | data: 5 | data: 9 | +| xor: 2 | xor: 0 | | xor: 2 | xor: 11 | | xor: 6 | xor: 11 | +'---------'---------' '---------'---------' '---------'---------' + let data = 9 let data = 5 +``` + +After each update, we can find the most up to date value of data by looking +at the revision count. + +Now consider what the blocks may look like if we suddenly loss power while +changing the value of data to 5: +``` + block 1 block 2 block 1 block 2 block 1 block 2 +.---------.---------. .---------.---------. .---------.---------. +| rev: 1 | rev: 0 | | rev: 1 | rev: 2 | | rev: 3 | rev: 2 | +| data: 3 | data: 0 | -> | data: 3 | data: 9 | -x | data: 3 | data: 9 | +| xor: 2 | xor: 0 | | xor: 2 | xor: 11 | | xor: 2 | xor: 11 | +'---------'---------' '---------'---------' '---------'---------' + let data = 9 let data = 5 + powerloss!!! +``` + +In this case, block 1 was partially written with a new revision count, but +the littlefs hadn't made it to updating the value of data. However, if we +check our checksum we notice that block 1 was corrupted. So we fall back to +block 2 and use the value 9. + +Using this concept, the littlefs is able to update metadata blocks atomically. +There are a few other tweaks, such as using a 32bit crc and using sequence +arithmetic to handle revision count overflow, but the basic concept +is the same. These metadata pairs define the backbone of the littlefs, and the +rest of the filesystem is built on top of these atomic updates. + +## Files + +Now, the metadata pairs do come with some drawbacks. Most notably, each pair +requires two blocks for each block of data. I'm sure users would be very +unhappy if their storage was suddenly cut in half! Instead of storing +everything in these metadata blocks, the littlefs uses a COW data structure +for files which is in turn pointed to by a metadata block. When +we update a file, we create a copies of any blocks that are modified until +the metadata blocks are updated with the new copy. Once the metadata block +points to the new copy, we deallocate the old blocks that are no longer in use. + +Here is what updating a one-block file may look like: +``` + block 1 block 2 block 1 block 2 block 1 block 2 +.---------.---------. .---------.---------. .---------.---------. +| rev: 1 | rev: 0 | | rev: 1 | rev: 0 | | rev: 1 | rev: 2 | +| file: 4 | file: 0 | -> | file: 4 | file: 0 | -> | file: 4 | file: 5 | +| xor: 5 | xor: 0 | | xor: 5 | xor: 0 | | xor: 5 | xor: 7 | +'---------'---------' '---------'---------' '---------'---------' + | | | + v v v + block 4 block 4 block 5 block 4 block 5 +.--------. .--------. .--------. .--------. .--------. +| old | | old | | new | | old | | new | +| data | | data | | data | | data | | data | +| | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' + update data in file update metadata pair +``` + +It doesn't matter if we lose power while writing block 5 with the new data, +since the old data remains unmodified in block 4. This example also +highlights how the atomic updates of the metadata blocks provide a +synchronization barrier for the rest of the littlefs. + +At this point, it may look like we are wasting an awfully large amount +of space on the metadata. Just looking at that example, we are using +three blocks to represent a file that fits comfortably in one! So instead +of giving each file a metadata pair, we actually store the metadata for +all files contained in a single directory in a single metadata block. + +Now we could just leave files here, copying the entire file on write +provides the synchronization without the duplicated memory requirements +of the metadata blocks. However, we can do a bit better. + +## CTZ linked-lists + +There are many different data structures for representing the actual +files in filesystems. Of these, the littlefs uses a rather unique [COW](https://upload.wikimedia.org/wikipedia/commons/0/0c/Cow_female_black_white.jpg) +data structure that allows the filesystem to reuse unmodified parts of the +file without additional metadata pairs. + +First lets consider storing files in a simple linked-list. What happens when +append a block? We have to change the last block in the linked-list to point +to this new block, which means we have to copy out the last block, and change +the second-to-last block, and then the third-to-last, and so on until we've +copied out the entire file. + +``` +Exhibit A: A linked-list +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 |->| data 1 |->| data 2 |->| data 4 |->| data 5 |->| data 6 | +| | | | | | | | | | | | +| | | | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +To get around this, the littlefs, at its heart, stores files backwards. Each +block points to its predecessor, with the first block containing no pointers. +If you think about this, it makes a bit of sense. Appending blocks just point +to their predecessor and no other blocks need to be updated. If we update +a block in the middle, we will need to copy out the blocks that follow, +but can reuse the blocks before the modified block. Since most file operations +either reset the file each write or append to files, this design avoids +copying the file in the most common cases. + +``` +Exhibit B: A backwards linked-list +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 |<-| data 1 |<-| data 2 |<-| data 4 |<-| data 5 |<-| data 6 | +| | | | | | | | | | | | +| | | | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +However, a backwards linked-list does come with a rather glaring problem. +Iterating over a file _in order_ has a runtime of O(n^2). Gah! A quadratic +runtime to just _read_ a file? That's awful. Keep in mind reading files are +usually the most common filesystem operation. + +To avoid this problem, the littlefs uses a multilayered linked-list. For +every block that is divisible by a power of two, the block contains an +additional pointer that points back by that power of two. Another way of +thinking about this design is that there are actually many linked-lists +threaded together, with each linked-lists skipping an increasing number +of blocks. If you're familiar with data-structures, you may have also +recognized that this is a deterministic skip-list. + +To find the power of two factors efficiently, we can use the instruction +[count trailing zeros (CTZ)](https://en.wikipedia.org/wiki/Count_trailing_zeros), +which is where this linked-list's name comes from. + +``` +Exhibit C: A backwards CTZ linked-list +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 |<-| data 1 |<-| data 2 |<-| data 3 |<-| data 4 |<-| data 5 | +| |<-| |--| |<-| |--| | | | +| |<-| |--| |--| |--| | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +Taking exhibit C for example, here is the path from data block 5 to data +block 1. You can see how data block 3 was completely skipped: +``` +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 | | data 1 |<-| data 2 | | data 3 | | data 4 |<-| data 5 | +| | | | | |<-| |--| | | | +| | | | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +The path to data block 0 is even more quick, requiring only two jumps: +``` +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 | | data 1 | | data 2 | | data 3 | | data 4 |<-| data 5 | +| | | | | | | | | | | | +| |<-| |--| |--| |--| | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +The CTZ linked-list has quite a few interesting properties. All of the pointers +in the block can be found by just knowing the index in the list of the current +block, and, with a bit of math, the amortized overhead for the linked-list is +only two pointers per block. Most importantly, the CTZ linked-list has a +worst case lookup runtime of O(logn), which brings the runtime of reading a +file down to O(n logn). Given that the constant runtime is divided by the +amount of data we can store in a block, this is pretty reasonable. + +Here is what it might look like to update a file stored with a CTZ linked-list: +``` + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 0 | + | file: 6 | file: 0 | + | size: 4 | size: 0 | + | xor: 3 | xor: 0 | + '---------'---------' + | + v + block 3 block 4 block 5 block 6 +.--------. .--------. .--------. .--------. +| data 0 |<-| data 1 |<-| data 2 |<-| data 3 | +| |<-| |--| | | | +| | | | | | | | +'--------' '--------' '--------' '--------' + +| update data in file +v + + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 0 | + | file: 6 | file: 0 | + | size: 4 | size: 0 | + | xor: 3 | xor: 0 | + '---------'---------' + | + v + block 3 block 4 block 5 block 6 +.--------. .--------. .--------. .--------. +| data 0 |<-| data 1 |<-| old |<-| old | +| |<-| |--| data 2 | | data 3 | +| | | | | | | | +'--------' '--------' '--------' '--------' + ^ ^ ^ + | | | block 7 block 8 block 9 block 10 + | | | .--------. .--------. .--------. .--------. + | | '----| new |<-| new |<-| new |<-| new | + | '----------------| data 2 |<-| data 3 |--| data 4 | | data 5 | + '------------------| |--| |--| | | | + '--------' '--------' '--------' '--------' + +| update metadata pair +v + + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 2 | + | file: 6 | file: 10| + | size: 4 | size: 6 | + | xor: 3 | xor: 14 | + '---------'---------' + | + | + block 3 block 4 block 5 block 6 | +.--------. .--------. .--------. .--------. | +| data 0 |<-| data 1 |<-| old |<-| old | | +| |<-| |--| data 2 | | data 3 | | +| | | | | | | | | +'--------' '--------' '--------' '--------' | + ^ ^ ^ v + | | | block 7 block 8 block 9 block 10 + | | | .--------. .--------. .--------. .--------. + | | '----| new |<-| new |<-| new |<-| new | + | '----------------| data 2 |<-| data 3 |--| data 4 | | data 5 | + '------------------| |--| |--| | | | + '--------' '--------' '--------' '--------' +``` + +## Block allocation + +So those two ideas provide the grounds for the filesystem. The metadata pairs +give us directories, and the CTZ linked-lists give us files. But this leaves +one big [elephant](https://upload.wikimedia.org/wikipedia/commons/3/37/African_Bush_Elephant.jpg) +of a question. How do we get those blocks in the first place? + +One common strategy is to store unallocated blocks in a big free list, and +initially the littlefs was designed with this in mind. By storing a reference +to the free list in every single metadata pair, additions to the free list +could be updated atomically at the same time the replacement blocks were +stored in the metadata pair. During boot, every metadata pair had to be +scanned to find the most recent free list, but once the list was found the +state of all free blocks becomes known. + +However, this approach had several issues: +- There was a lot of nuanced logic for adding blocks to the free list without + modifying the blocks, since the blocks remain active until the metadata is + updated. +- The free list had to support both additions and removals in fifo order while + minimizing block erases. +- The free list had to handle the case where the file system completely ran + out of blocks and may no longer be able to add blocks to the free list. +- If we used a revision count to track the most recently updated free list, + metadata blocks that were left unmodified were ticking time bombs that would + cause the system to go haywire if the revision count overflowed +- Every single metadata block wasted space to store these free list references. + +Actually, to simplify, this approach had one massive glaring issue: complexity. + +> Complexity leads to fallibility. +> Fallibility leads to unmaintainability. +> Unmaintainability leads to suffering. + +Or at least, complexity leads to increased code size, which is a problem +for embedded systems. + +In the end, the littlefs adopted more of a "drop it on the floor" strategy. +That is, the littlefs doesn't actually store information about which blocks +are free on the storage. The littlefs already stores which files _are_ in +use, so to find a free block, the littlefs just takes all of the blocks that +exist and subtract the blocks that are in use. + +Of course, it's not quite that simple. Most filesystems that adopt this "drop +it on the floor" strategy either rely on some properties inherent to the +filesystem, such as the cyclic-buffer structure of logging filesystems, +or use a bitmap or table stored in RAM to track free blocks, which scales +with the size of storage and is problematic when you have limited RAM. You +could iterate through every single block in storage and check it against +every single block in the filesystem on every single allocation, but that +would have an abhorrent runtime. + +So the littlefs compromises. It doesn't store a bitmap the size of the storage, +but it does store a little bit-vector that contains a fixed set lookahead +for block allocations. During a block allocation, the lookahead vector is +checked for any free blocks, if there are none, the lookahead region jumps +forward and the entire filesystem is scanned for free blocks. + +Here's what it might look like to allocate 4 blocks on a decently busy +filesystem with a 32bit lookahead and a total of +128 blocks (512Kbytes of storage if blocks are 4Kbyte): +``` +boot... lookahead: + fs blocks: fffff9fffffffffeffffffffffff0000 +scanning... lookahead: fffff9ff + fs blocks: fffff9fffffffffeffffffffffff0000 +alloc = 21 lookahead: fffffdff + fs blocks: fffffdfffffffffeffffffffffff0000 +alloc = 22 lookahead: ffffffff + fs blocks: fffffffffffffffeffffffffffff0000 +scanning... lookahead: fffffffe + fs blocks: fffffffffffffffeffffffffffff0000 +alloc = 63 lookahead: ffffffff + fs blocks: ffffffffffffffffffffffffffff0000 +scanning... lookahead: ffffffff + fs blocks: ffffffffffffffffffffffffffff0000 +scanning... lookahead: ffffffff + fs blocks: ffffffffffffffffffffffffffff0000 +scanning... lookahead: ffff0000 + fs blocks: ffffffffffffffffffffffffffff0000 +alloc = 112 lookahead: ffff8000 + fs blocks: ffffffffffffffffffffffffffff8000 +``` + +While this lookahead approach still has an asymptotic runtime of O(n^2) to +scan all of storage, the lookahead reduces the practical runtime to a +reasonable amount. Bit-vectors are surprisingly compact, given only 16 bytes, +the lookahead could track 128 blocks. For a 4Mbyte flash chip with 4Kbyte +blocks, the littlefs would only need 8 passes to scan the entire storage. + +The real benefit of this approach is just how much it simplified the design +of the littlefs. Deallocating blocks is as simple as simply forgetting they +exist, and there is absolutely no concern of bugs in the deallocation code +causing difficult to detect memory leaks. + +## Directories + +Now we just need directories to store our files. Since we already have +metadata blocks that store information about files, lets just use these +metadata blocks as the directories. Maybe turn the directories into linked +lists of metadata blocks so it isn't limited by the number of files that fit +in a single block. Add entries that represent other nested directories. +Drop "." and ".." entries, cause who needs them. Dust off our hands and +we now have a directory tree. + +``` + .--------. + |root dir| + | pair 0 | + | | + '--------' + .-' '-------------------------. + v v + .--------. .--------. .--------. + | dir A |------->| dir A | | dir B | + | pair 0 | | pair 1 | | pair 0 | + | | | | | | + '--------' '--------' '--------' + .-' '-. | .-' '-. + v v v v v +.--------. .--------. .--------. .--------. .--------. +| file C | | file D | | file E | | file F | | file G | +| | | | | | | | | | +| | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' +``` + +Unfortunately it turns out it's not that simple. See, iterating over a +directory tree isn't actually all that easy, especially when you're trying +to fit in a bounded amount of RAM, which rules out any recursive solution. +And since our block allocator involves iterating over the entire filesystem +tree, possibly multiple times in a single allocation, iteration needs to be +efficient. + +So, as a solution, the littlefs adopted a sort of threaded tree. Each +directory not only contains pointers to all of its children, but also a +pointer to the next directory. These pointers create a linked-list that +is threaded through all of the directories in the filesystem. Since we +only use this linked list to check for existance, the order doesn't actually +matter. As an added plus, we can repurpose the pointer for the individual +directory linked-lists and avoid using any additional space. + +``` + .--------. + |root dir|-. + | pair 0 | | + .--------| |-' + | '--------' + | .-' '-------------------------. + | v v + | .--------. .--------. .--------. + '->| dir A |------->| dir A |------->| dir B | + | pair 0 | | pair 1 | | pair 0 | + | | | | | | + '--------' '--------' '--------' + .-' '-. | .-' '-. + v v v v v +.--------. .--------. .--------. .--------. .--------. +| file C | | file D | | file E | | file F | | file G | +| | | | | | | | | | +| | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' +``` + +This threaded tree approach does come with a few tradeoffs. Now, anytime we +want to manipulate the directory tree, we find ourselves having to update two +pointers instead of one. For anyone familiar with creating atomic data +structures this should set off a whole bunch of red flags. + +But unlike the data structure guys, we can update a whole block atomically! So +as long as we're really careful (and cheat a little bit), we can still +manipulate the directory tree in a way that is resilient to power loss. + +Consider how we might add a new directory. Since both pointers that reference +it can come from the same directory, we only need a single atomic update to +finagle the directory into the filesystem: +``` + .--------. + |root dir|-. + | pair 0 | | +.--| |-' +| '--------' +| | +| v +| .--------. +'->| dir A | + | pair 0 | + | | + '--------' + +| create the new directory block +v + + .--------. + |root dir|-. + | pair 0 | | + .--| |-' + | '--------' + | | + | v + | .--------. +.--------. '->| dir A | +| dir B |---->| pair 0 | +| pair 0 | | | +| | '--------' +'--------' + +| update root to point to directory B +v + + .--------. + |root dir|-. + | pair 0 | | +.--------| |-' +| '--------' +| .-' '-. +| v v +| .--------. .--------. +'->| dir B |->| dir A | + | pair 0 | | pair 0 | + | | | | + '--------' '--------' +``` + +Note that even though directory B was added after directory A, we insert +directory B before directory A in the linked-list because it is convenient. + +Now how about removal: +``` + .--------. .--------. + |root dir|------->|root dir|-. + | pair 0 | | pair 1 | | +.--------| |--------| |-' +| '--------' '--------' +| .-' '-. | +| v v v +| .--------. .--------. .--------. +'->| dir A |->| dir B |->| dir C | + | pair 0 | | pair 0 | | pair 0 | + | | | | | | + '--------' '--------' '--------' + +| update root to no longer contain directory B +v + + .--------. .--------. + |root dir|------------->|root dir|-. + | pair 0 | | pair 1 | | +.--| |--------------| |-' +| '--------' '--------' +| | | +| v v +| .--------. .--------. .--------. +'->| dir A |->| dir B |->| dir C | + | pair 0 | | pair 0 | | pair 0 | + | | | | | | + '--------' '--------' '--------' + +| remove directory B from the linked-list +v + + .--------. .--------. + |root dir|->|root dir|-. + | pair 0 | | pair 1 | | +.--| |--| |-' +| '--------' '--------' +| | | +| v v +| .--------. .--------. +'->| dir A |->| dir C | + | pair 0 | | pair 0 | + | | | | + '--------' '--------' +``` + +Wait, wait, wait, wait, wait, that's not atomic at all! If power is lost after +removing directory B from the root, directory B is still in the linked-list. +We've just created a memory leak! + +And to be honest, I don't have a clever solution for this case. As a +side-effect of using multiple pointers in the threaded tree, the littlefs +can end up with orphan blocks that have no parents and should have been +removed. + +To keep these orphan blocks from becoming a problem, the littlefs has a +deorphan step that simply iterates through every directory in the linked-list +and checks it against every directory entry in the filesystem to see if it +has a parent. The deorphan step occurs on the first block allocation after +boot, so orphans should never cause the littlefs to run out of storage +prematurely. + +And for my final trick, moving a directory: +``` + .--------. + |root dir|-. + | pair 0 | | +.--------| |-' +| '--------' +| .-' '-. +| v v +| .--------. .--------. +'->| dir A |->| dir B | + | pair 0 | | pair 0 | + | | | | + '--------' '--------' + +| update directory B to point to directory A +v + + .--------. + |root dir|-. + | pair 0 | | +.--------| |-' +| '--------' +| .-----' '-. +| | v +| | .--------. +| | .->| dir B | +| | | | pair 0 | +| | | | | +| | | '--------' +| | .-------' +| v v | +| .--------. | +'->| dir A |-' + | pair 0 | + | | + '--------' + +| update root to no longer contain directory A +v + .--------. + |root dir|-. + | pair 0 | | +.----| |-' +| '--------' +| | +| v +| .--------. +| .->| dir B | +| | | pair 0 | +| '--| |-. +| '--------' | +| | | +| v | +| .--------. | +'--->| dir A |-' + | pair 0 | + | | + '--------' +``` + +Note that once again we don't care about the ordering of directories in the +linked-list, so we can simply leave directories in their old positions. This +does make the diagrams a bit hard to draw, but the littlefs doesn't really +care. + +It's also worth noting that once again we have an operation that isn't actually +atomic. After we add directory A to directory B, we could lose power, leaving +directory A as a part of both the root directory and directory B. However, +there isn't anything inherent to the littlefs that prevents a directory from +having multiple parents, so in this case, we just allow that to happen. Extra +care is taken to only remove a directory from the linked-list if there are +no parents left in the filesystem. + +## Wear awareness + +So now that we have all of the pieces of a filesystem, we can look at a more +subtle attribute of embedded storage: The wear down of flash blocks. + +The first concern for the littlefs, is that prefectly valid blocks can suddenly +become unusable. As a nice side-effect of using a COW data-structure for files, +we can simply move on to a different block when a file write fails. All +modifications to files are performed in copies, so we will only replace the +old file when we are sure none of the new file has errors. Directories, on +the other hand, need a different strategy. + +The solution to directory corruption in the littlefs relies on the redundant +nature of the metadata pairs. If an error is detected during a write to one +of the metadata pairs, we seek out a new block to take its place. Once we find +a block without errors, we iterate through the directory tree, updating any +references to the corrupted metadata pair to point to the new metadata block. +Just like when we remove directories, we can lose power during this operation +and end up with a desynchronized metadata pair in our filesystem. And just like +when we remove directories, we leave the possibility of a desynchronized +metadata pair up to the deorphan step to clean up. + +Here's what encountering a directory error may look like with all of +the directories and directory pointers fully expanded: +``` + root dir + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 0 |--. + | | |-.| +.------| | |-|' +|.-----| | |-' +|| '---------'---------' +|| |||||'--------------------------------------------------. +|| ||||'-----------------------------------------. | +|| |||'-----------------------------. | | +|| ||'--------------------. | | | +|| |'-------. | | | | +|| v v v v v v +|| dir A dir B dir C +|| block 3 block 4 block 5 block 6 block 7 block 8 +|| .---------.---------. .---------.---------. .---------.---------. +|'->| rev: 1 | rev: 0 |->| rev: 1 | rev: 0 |->| rev: 1 | rev: 0 | +'-->| | |->| | |->| | | + | | | | | | | + | | | | | | | | | + '---------'---------' '---------'---------' '---------'---------' + +| update directory B +v + + root dir + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 0 |--. + | | |-.| +.------| | |-|' +|.-----| | |-' +|| '---------'---------' +|| |||||'--------------------------------------------------. +|| ||||'-----------------------------------------. | +|| |||'-----------------------------. | | +|| ||'--------------------. | | | +|| |'-------. | | | | +|| v v v v v v +|| dir A dir B dir C +|| block 3 block 4 block 5 block 6 block 7 block 8 +|| .---------.---------. .---------.---------. .---------.---------. +|'->| rev: 1 | rev: 0 |->| rev: 1 | rev: 2 |->| rev: 1 | rev: 0 | +'-->| | |->| | corrupt!|->| | | + | | | | | corrupt!| | | | + | | | | | corrupt!| | | | + '---------'---------' '---------'---------' '---------'---------' + +| oh no! corruption detected +v allocate a replacement block + + root dir + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 0 |--. + | | |-.| +.------| | |-|' +|.-----| | |-' +|| '---------'---------' +|| |||||'----------------------------------------------------. +|| ||||'-------------------------------------------. | +|| |||'-----------------------------. | | +|| ||'--------------------. | | | +|| |'-------. | | | | +|| v v v v v v +|| dir A dir B dir C +|| block 3 block 4 block 5 block 6 block 7 block 8 +|| .---------.---------. .---------.---------. .---------.---------. +|'->| rev: 1 | rev: 0 |->| rev: 1 | rev: 2 |--->| rev: 1 | rev: 0 | +'-->| | |->| | corrupt!|--->| | | + | | | | | corrupt!| .->| | | + | | | | | corrupt!| | | | | + '---------'---------' '---------'---------' | '---------'---------' + block 9 | + .---------. | + | rev: 2 |-' + | | + | | + | | + '---------' + +| update root directory to contain block 9 +v + + root dir + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 2 |--. + | | |-.| +.-----| | |-|' +|.----| | |-' +|| '---------'---------' +|| .--------'||||'----------------------------------------------. +|| | |||'-------------------------------------. | +|| | ||'-----------------------. | | +|| | |'------------. | | | +|| | | | | | | +|| v v v v v v +|| dir A dir B dir C +|| block 3 block 4 block 5 block 9 block 7 block 8 +|| .---------.---------. .---------. .---------. .---------.---------. +|'->| rev: 1 | rev: 0 |-->| rev: 1 |-| rev: 2 |--->| rev: 1 | rev: 0 | +'-->| | |-. | | | |--->| | | + | | | | | | | | .->| | | + | | | | | | | | | | | | + '---------'---------' | '---------' '---------' | '---------'---------' + | block 6 | + | .---------. | + '------------>| rev: 2 |-' + | corrupt!| + | corrupt!| + | corrupt!| + '---------' + +| remove corrupted block from linked-list +v + + root dir + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 2 |--. + | | |-.| +.-----| | |-|' +|.----| | |-' +|| '---------'---------' +|| .--------'||||'-----------------------------------------. +|| | |||'--------------------------------. | +|| | ||'--------------------. | | +|| | |'-----------. | | | +|| | | | | | | +|| v v v v v v +|| dir A dir B dir C +|| block 3 block 4 block 5 block 9 block 7 block 8 +|| .---------.---------. .---------.---------. .---------.---------. +|'->| rev: 1 | rev: 2 |->| rev: 1 | rev: 2 |->| rev: 1 | rev: 0 | +'-->| | |->| | |->| | | + | | | | | | | | | + | | | | | | | | | + '---------'---------' '---------'---------' '---------'---------' +``` + +Also one question I've been getting is, what about the root directory? +It can't move so wouldn't the filesystem die as soon as the root blocks +develop errors? And you would be correct. So instead of storing the root +in the first few blocks of the storage, the root is actually pointed to +by the superblock. The superblock contains a few bits of static data, but +outside of when the filesystem is formatted, it is only updated when the root +develops errors and needs to be moved. + +## Wear leveling + +The second concern for the littlefs, is that blocks in the filesystem may wear +unevenly. In this situation, a filesystem may meet an early demise where +there are no more non-corrupted blocks that aren't in use. It may be entirely +possible that files were written once and left unmodified, wasting the +potential erase cycles of the blocks it sits on. + +Wear leveling is a term that describes distributing block writes evenly to +avoid the early termination of a flash part. There are typically two levels +of wear leveling: +1. Dynamic wear leveling - Blocks are distributed evenly during blocks writes. + Note that the issue with write-once files still exists in this case. +2. Static wear leveling - Unmodified blocks are evicted for new block writes. + This provides the longest lifetime for a flash device. + +Now, it's possible to use the revision count on metadata pairs to approximate +the wear of a metadata block. And combined with the COW nature of files, the +littlefs could provide a form of dynamic wear leveling. + +However, the littlefs does not. This is for a few reasons. Most notably, even +if the littlefs did implement dynamic wear leveling, this would still not +handle the case of write-once files, and near the end of the lifetime of a +flash device, you would likely end up with uneven wear on the blocks anyways. + +As a flash device reaches the end of its life, the metadata blocks will +naturally be the first to go since they are updated most often. In this +situation, the littlefs is designed to simply move on to another set of +metadata blocks. This travelling means that at the end of a flash device's +life, the filesystem will have worn the device down as evenly as a dynamic +wear leveling filesystem could anyways. Simply put, if the lifetime of flash +is a serious concern, static wear leveling is the only valid solution. + +This is a very important takeaway to note. If your storage stack uses highly +sensitive storage such as NAND flash. In most cases you are going to be better +off just using a [flash translation layer (FTL)](https://en.wikipedia.org/wiki/Flash_translation_layer). +NAND flash already has many limitations that make it poorly suited for an +embedded system: low erase cycles, very large blocks, errors that can develop +even during reads, errors that can develop during writes of neighboring blocks. +Managing sensitive storage such as NAND flash is out of scope for the littlefs. +The littlefs does have some properties that may be beneficial on top of a FTL, +such as limiting the number of writes where possible. But if you have the +storage requirements that necessitate the need of NAND flash, you should have +the RAM to match and just use an FTL or flash filesystem. + +## Summary + +So, to summarize: + +1. The littlefs is composed of directory blocks +2. Each directory is a linked-list of metadata pairs +3. These metadata pairs can be updated atomically by alternative which + metadata block is active +4. Directory blocks contain either references to other directories or files +5. Files are represented by copy-on-write CTZ linked-lists +6. The CTZ linked-lists support appending in O(1) and reading in O(n logn) +7. Blocks are allocated by scanning the filesystem for used blocks in a + fixed-size lookahead region is that stored in a bit-vector +8. To facilitate scanning the filesystem, all directories are part of a + linked-list that is threaded through the entire filesystem +9. If a block develops an error, the littlefs allocates a new block, and + moves the data and references of the old block to the new. +10. Any case where an atomic operation is not possible, it is taken care of + by a deorphan step that occurs on the first allocation after boot + +Welp, that's the little filesystem. Thanks for reading! + diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000..59cd3f8a32 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,165 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..2c9e8bfafa --- /dev/null +++ b/Makefile @@ -0,0 +1,57 @@ +TARGET = lfs + +CC = gcc +AR = ar +SIZE = size + +SRC += $(wildcard *.c emubd/*.c) +OBJ := $(SRC:.c=.o) +DEP := $(SRC:.c=.d) +ASM := $(SRC:.c=.s) + +TEST := $(patsubst tests/%.sh,%,$(wildcard tests/test_*)) + +ifdef DEBUG +CFLAGS += -O0 -g3 +else +CFLAGS += -Os +endif +ifdef WORD +CFLAGS += -m$(WORD) +endif +CFLAGS += -I. +CFLAGS += -std=c99 -Wall -pedantic + + +all: $(TARGET) + +asm: $(ASM) + +size: $(OBJ) + $(SIZE) -t $^ + +.SUFFIXES: +test: test_format test_dirs test_files test_seek test_parallel \ + test_alloc test_paths test_orphan test_corrupt +test_%: tests/test_%.sh + ./$< + +-include $(DEP) + +$(TARGET): $(OBJ) + $(CC) $(CFLAGS) $^ $(LFLAGS) -o $@ + +%.a: $(OBJ) + $(AR) rcs $@ $^ + +%.o: %.c + $(CC) -c -MMD $(CFLAGS) $< -o $@ + +%.s: %.c + $(CC) -S $(CFLAGS) $< -o $@ + +clean: + rm -f $(TARGET) + rm -f $(OBJ) + rm -f $(DEP) + rm -f $(ASM) diff --git a/README.md b/README.md new file mode 100644 index 0000000000..0ae1cc61ea --- /dev/null +++ b/README.md @@ -0,0 +1,142 @@ +## The little filesystem + +A little fail-safe filesystem designed for embedded systems. + +``` + | | | .---._____ + .-----. | | +--|o |---| littlefs | +--| |---| | + '-----' '----------' + | | | +``` + +**Fail-safe** - The littlefs is designed to work consistently with random +power failures. During filesystem operations the storage on disk is always +kept in a valid state. The filesystem also has strong copy-on-write garuntees. +When updating a file, the original file will remain unmodified until the +file is closed, or sync is called. + +**Wear awareness** - While the littlefs does not implement static wear +leveling, the littlefs takes into account write errors reported by the +underlying block device and uses a limited form of dynamic wear leveling +to manage blocks that go bad during the lifetime of the filesystem. + +**Bounded ram/rom** - The littlefs is designed to work in a +limited amount of memory, recursion is avoided, and dynamic memory is kept +to a minimum. The littlefs allocates two fixed-size buffers for general +operations, and one fixed-size buffer per file. If there is only ever one file +in use, all memory can be provided statically and the littlefs can be used +in a system without dynamic memory. + +## Example + +Here's a simple example that updates a file named `boot_count` every time +main runs. The program can be interrupted at any time without losing track +of how many times it has been booted and without corrupting the filesystem: + +``` c +#include "lfs.h" + +// variables used by the filesystem +lfs_t lfs; +lfs_file_t file; + +// configuration of the filesystem is provided by this struct +const struct lfs_config cfg = { + // block device operations + .read = user_provided_block_device_read, + .prog = user_provided_block_device_prog, + .erase = user_provided_block_device_erase, + .sync = user_provided_block_device_sync, + + // block device configuration + .read_size = 16, + .prog_size = 16, + .block_size = 4096, + .block_count = 128, + .lookahead = 128, +}; + +// entry point +int main(void) { + // mount the filesystem + int err = lfs_mount(&lfs, &cfg); + + // reformat if we can't mount the filesystem + // this should only happen on the first boot + if (err) { + lfs_format(&lfs, &cfg); + lfs_mount(&lfs, &cfg); + } + + // read current count + uint32_t boot_count = 0; + lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT); + lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count)); + + // update boot count + boot_count += 1; + printf("boot_count: %ld\n", boot_count); + lfs_file_rewind(&lfs, &file); + lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count)); + + // remember the storage is not updated until the file is closed successfully + lfs_file_close(&lfs, &file); + + // release any resources we were using + lfs_unmount(&lfs); +} +``` + +## Usage + +Detailed documentation (or at least as much detail as is currently available) +can be cound in the comments in [lfs.h](lfs.h). + +As you may have noticed, the littlefs takes in a configuration structure that +defines how the filesystem operates. The configuration struct provides the +filesystem with the block device operations and dimensions, tweakable +parameters that tradeoff memory usage for performance, and optional +static buffers if the user wants to avoid dynamic memory. + +The state of the littlefs is stored in the `lfs_t` type which is left up +to the user to allocate, allowing multiple filesystems to be in use +simultaneously. With the `lfs_t` and configuration struct, a user can either +format a block device or mount the filesystem. + +Once mounted, the littlefs provides a full set of posix-like file and +directory functions, with the deviation that the allocation of filesystem +structures must be provided by the user. An important addition is that +no file updates will actually be written to disk until a sync or close +is called. + +## Other notes + +All littlefs have the potential to return a negative error code. The errors +can be either one of those found in the `enum lfs_error` in [lfs.h](lfs.h), +or an error returned by the user's block device operations. + +It should also be noted that the littlefs does not do anything to insure +that the data written to disk is machine portable. It should be fine as +long as the machines involved share endianness and don't have really +strange padding requirements. If the question does come up, the littlefs +metadata should be stored on disk in little-endian format. + +## Design + +the littlefs was developed with the goal of learning more about filesystem +design by tackling the relative unsolved problem of managing a robust +filesystem resilient to power loss on devices with limited RAM and ROM. +More detail on the solutions and tradeoffs incorporated into this filesystem +can be found in [DESIGN.md](DESIGN.md). + +## Testing + +The littlefs comes with a test suite designed to run on a pc using the +[emulated block device](emubd/lfs_emubd.h) found in the emubd directory. +The tests assume a linux environment and can be started with make: + +``` bash +make test +``` diff --git a/emubd/lfs_emubd.c b/emubd/lfs_emubd.c new file mode 100644 index 0000000000..b38d9d3361 --- /dev/null +++ b/emubd/lfs_emubd.c @@ -0,0 +1,242 @@ +/* + * Block device emulated on standard files + * + * Copyright (c) 2017 Christopher Haster + * Distributed under the Apache 2.0 license + */ +#include "emubd/lfs_emubd.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Block device emulated on existing filesystem +int lfs_emubd_create(const struct lfs_config *cfg, const char *path) { + lfs_emubd_t *emu = cfg->context; + emu->cfg.read_size = cfg->read_size; + emu->cfg.prog_size = cfg->prog_size; + emu->cfg.block_size = cfg->block_size; + emu->cfg.block_count = cfg->block_count; + + // Allocate buffer for creating children files + size_t pathlen = strlen(path); + emu->path = malloc(pathlen + 1 + LFS_NAME_MAX + 1); + if (!emu->path) { + return -ENOMEM; + } + + strcpy(emu->path, path); + emu->path[pathlen] = '/'; + emu->child = &emu->path[pathlen+1]; + memset(emu->child, '\0', LFS_NAME_MAX+1); + + // Create directory if it doesn't exist + int err = mkdir(path, 0777); + if (err && errno != EEXIST) { + return -errno; + } + + // Load stats to continue incrementing + snprintf(emu->child, LFS_NAME_MAX, "stats"); + FILE *f = fopen(emu->path, "r"); + if (!f) { + return -errno; + } + + size_t res = fread(&emu->stats, sizeof(emu->stats), 1, f); + if (res < 1) { + return -errno; + } + + err = fclose(f); + if (err) { + return -errno; + } + + return 0; +} + +void lfs_emubd_destroy(const struct lfs_config *cfg) { + lfs_emubd_sync(cfg); + + lfs_emubd_t *emu = cfg->context; + free(emu->path); +} + +int lfs_emubd_read(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + lfs_emubd_t *emu = cfg->context; + uint8_t *data = buffer; + + // Check if read is valid + assert(off % cfg->read_size == 0); + assert(size % cfg->read_size == 0); + assert(block < cfg->block_count); + + // Zero out buffer for debugging + memset(data, 0, size); + + // Read data + snprintf(emu->child, LFS_NAME_MAX, "%x", block); + + FILE *f = fopen(emu->path, "rb"); + if (!f && errno != ENOENT) { + return -errno; + } + + if (f) { + int err = fseek(f, off, SEEK_SET); + if (err) { + return -errno; + } + + size_t res = fread(data, 1, size, f); + if (res < size && !feof(f)) { + return -errno; + } + + err = fclose(f); + if (err) { + return -errno; + } + } + + emu->stats.read_count += 1; + return 0; +} + +int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + lfs_emubd_t *emu = cfg->context; + const uint8_t *data = buffer; + + // Check if write is valid + assert(off % cfg->prog_size == 0); + assert(size % cfg->prog_size == 0); + assert(block < cfg->block_count); + + // Program data + snprintf(emu->child, LFS_NAME_MAX, "%x", block); + + FILE *f = fopen(emu->path, "r+b"); + if (!f && errno != ENOENT) { + return -errno; + } + + // Check that file was erased + assert(f); + + int err = fseek(f, off, SEEK_SET); + if (err) { + return -errno; + } + + size_t res = fwrite(data, 1, size, f); + if (res < size) { + return -errno; + } + + err = fseek(f, off, SEEK_SET); + if (err) { + return -errno; + } + + uint8_t dat; + res = fread(&dat, 1, 1, f); + if (res < 1) { + return -errno; + } + + err = fclose(f); + if (err) { + return -errno; + } + + emu->stats.prog_count += 1; + return 0; +} + +int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { + lfs_emubd_t *emu = cfg->context; + + // Check if erase is valid + assert(block < cfg->block_count); + + // Erase the block + snprintf(emu->child, LFS_NAME_MAX, "%x", block); + struct stat st; + int err = stat(emu->path, &st); + if (err && errno != ENOENT) { + return -errno; + } + + if (!err && S_ISREG(st.st_mode)) { + int err = unlink(emu->path); + if (err) { + return -errno; + } + } + + if (err || S_ISREG(st.st_mode)) { + FILE *f = fopen(emu->path, "w"); + if (!f) { + return -errno; + } + + err = fclose(f); + if (err) { + return -errno; + } + } + + emu->stats.erase_count += 1; + return 0; +} + +int lfs_emubd_sync(const struct lfs_config *cfg) { + lfs_emubd_t *emu = cfg->context; + + // Just write out info/stats for later lookup + snprintf(emu->child, LFS_NAME_MAX, "config"); + FILE *f = fopen(emu->path, "w"); + if (!f) { + return -errno; + } + + size_t res = fwrite(&emu->cfg, sizeof(emu->cfg), 1, f); + if (res < 1) { + return -errno; + } + + int err = fclose(f); + if (err) { + return -errno; + } + + snprintf(emu->child, LFS_NAME_MAX, "stats"); + f = fopen(emu->path, "w"); + if (!f) { + return -errno; + } + + res = fwrite(&emu->stats, sizeof(emu->stats), 1, f); + if (res < 1) { + return -errno; + } + + err = fclose(f); + if (err) { + return -errno; + } + + return 0; +} + diff --git a/emubd/lfs_emubd.h b/emubd/lfs_emubd.h new file mode 100644 index 0000000000..083b2ce362 --- /dev/null +++ b/emubd/lfs_emubd.h @@ -0,0 +1,78 @@ +/* + * Block device emulated on standard files + * + * Copyright (c) 2017 Christopher Haster + * Distributed under the Apache 2.0 license + */ +#ifndef LFS_EMUBD_H +#define LFS_EMUBD_H + +#include "lfs.h" +#include "lfs_util.h" + + +// Config options +#ifndef LFS_EMUBD_READ_SIZE +#define LFS_EMUBD_READ_SIZE 1 +#endif + +#ifndef LFS_EMUBD_PROG_SIZE +#define LFS_EMUBD_PROG_SIZE 1 +#endif + +#ifndef LFS_EMUBD_ERASE_SIZE +#define LFS_EMUBD_ERASE_SIZE 512 +#endif + +#ifndef LFS_EMUBD_TOTAL_SIZE +#define LFS_EMUBD_TOTAL_SIZE 524288 +#endif + + +// The emu bd state +typedef struct lfs_emubd { + char *path; + char *child; + + struct { + uint64_t read_count; + uint64_t prog_count; + uint64_t erase_count; + } stats; + + struct { + uint32_t read_size; + uint32_t prog_size; + uint32_t block_size; + uint32_t block_count; + } cfg; +} lfs_emubd_t; + + +// Create a block device using path for the directory to store blocks +int lfs_emubd_create(const struct lfs_config *cfg, const char *path); + +// Clean up memory associated with emu block device +void lfs_emubd_destroy(const struct lfs_config *cfg); + +// Read a block +int lfs_emubd_read(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + +// Program a block +// +// The block must have previously been erased. +int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + +// Erase a block +// +// A block must be erased before being programmed. The +// state of an erased block is undefined. +int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block); + +// Sync the block device +int lfs_emubd_sync(const struct lfs_config *cfg); + + +#endif diff --git a/lfs.c b/lfs.c new file mode 100644 index 0000000000..a2d94eec61 --- /dev/null +++ b/lfs.c @@ -0,0 +1,2146 @@ +/* + * The little filesystem + * + * Copyright (c) 2017 Christopher Haster + * Distributed under the Apache 2.0 license + */ +#include "lfs.h" +#include "lfs_util.h" + +#include +#include +#include + + +/// Caching block device operations /// +static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache, + const lfs_cache_t *pcache, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + assert(block < lfs->cfg->block_count); + + while (size > 0) { + if (pcache && block == pcache->block && off >= pcache->off && + off < pcache->off + lfs->cfg->prog_size) { + // is already in pcache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->prog_size - (off-pcache->off)); + memcpy(data, &pcache->buffer[off-pcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + if (block == rcache->block && off >= rcache->off && + off < rcache->off + lfs->cfg->read_size) { + // is already in rcache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->read_size - (off-rcache->off)); + memcpy(data, &rcache->buffer[off-rcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + if (off % lfs->cfg->read_size == 0 && size >= lfs->cfg->read_size) { + // bypass cache? + lfs_size_t diff = size - (size % lfs->cfg->read_size); + int err = lfs->cfg->read(lfs->cfg, block, off, data, diff); + if (err) { + return err; + } + + data += diff; + off += diff; + size -= diff; + continue; + } + + // load to cache, first condition can no longer fail + rcache->block = block; + rcache->off = off - (off % lfs->cfg->read_size); + int err = lfs->cfg->read(lfs->cfg, rcache->block, + rcache->off, rcache->buffer, lfs->cfg->read_size); + if (err) { + return err; + } + } + + return 0; +} + +static int lfs_cache_cmp(lfs_t *lfs, lfs_cache_t *rcache, + const lfs_cache_t *pcache, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + + for (lfs_off_t i = 0; i < size; i++) { + uint8_t c; + int err = lfs_cache_read(lfs, rcache, pcache, + block, off+i, &c, 1); + if (err) { + return err; + } + + if (c != data[i]) { + return false; + } + } + + return true; +} + +static int lfs_cache_crc(lfs_t *lfs, lfs_cache_t *rcache, + const lfs_cache_t *pcache, lfs_block_t block, + lfs_off_t off, lfs_size_t size, uint32_t *crc) { + for (lfs_off_t i = 0; i < size; i++) { + uint8_t c; + int err = lfs_cache_read(lfs, rcache, pcache, + block, off+i, &c, 1); + if (err) { + return err; + } + + lfs_crc(crc, &c, 1); + } + + return 0; +} + +static int lfs_cache_flush(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache) { + if (pcache->block != 0xffffffff) { + int err = lfs->cfg->prog(lfs->cfg, pcache->block, + pcache->off, pcache->buffer, lfs->cfg->prog_size); + if (err) { + return err; + } + + if (rcache) { + int res = lfs_cache_cmp(lfs, rcache, NULL, pcache->block, + pcache->off, pcache->buffer, lfs->cfg->prog_size); + if (res < 0) { + return res; + } + + if (!res) { + return LFS_ERR_CORRUPT; + } + } + + pcache->block = 0xffffffff; + } + + return 0; +} + +static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache, + lfs_cache_t *rcache, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + assert(block < lfs->cfg->block_count); + + while (size > 0) { + if (block == pcache->block && off >= pcache->off && + off < pcache->off + lfs->cfg->prog_size) { + // is already in pcache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->prog_size - (off-pcache->off)); + memcpy(&pcache->buffer[off-pcache->off], data, diff); + + data += diff; + off += diff; + size -= diff; + + if (off % lfs->cfg->prog_size == 0) { + // eagerly flush out pcache if we fill up + int err = lfs_cache_flush(lfs, pcache, rcache); + if (err) { + return err; + } + } + + continue; + } + + // pcache must have been flushed, either by programming and + // entire block or manually flushing the pcache + assert(pcache->block == 0xffffffff); + + if (off % lfs->cfg->prog_size == 0 && + size >= lfs->cfg->prog_size) { + // bypass pcache? + lfs_size_t diff = size - (size % lfs->cfg->prog_size); + int err = lfs->cfg->prog(lfs->cfg, block, off, data, diff); + if (err) { + return err; + } + + if (rcache) { + int res = lfs_cache_cmp(lfs, rcache, NULL, + block, off, data, diff); + if (res < 0) { + return res; + } + + if (!res) { + return LFS_ERR_CORRUPT; + } + } + + data += diff; + off += diff; + size -= diff; + continue; + } + + // prepare pcache, first condition can no longer fail + pcache->block = block; + pcache->off = off - (off % lfs->cfg->prog_size); + } + + return 0; +} + + +/// General lfs block device operations /// +static int lfs_bd_read(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + // if we ever do more than writes to alternating pairs, + // this may need to consider pcache + return lfs_cache_read(lfs, &lfs->rcache, NULL, + block, off, buffer, size); +} + +static int lfs_bd_prog(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + return lfs_cache_prog(lfs, &lfs->pcache, NULL, + block, off, buffer, size); +} + +static int lfs_bd_cmp(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + return lfs_cache_cmp(lfs, &lfs->rcache, NULL, block, off, buffer, size); +} + +static int lfs_bd_crc(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, lfs_size_t size, uint32_t *crc) { + return lfs_cache_crc(lfs, &lfs->rcache, NULL, block, off, size, crc); +} + +static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { + return lfs->cfg->erase(lfs->cfg, block); +} + +static int lfs_bd_sync(lfs_t *lfs) { + lfs->rcache.block = 0xffffffff; + + int err = lfs_cache_flush(lfs, &lfs->pcache, NULL); + if (err) { + return err; + } + + return lfs->cfg->sync(lfs->cfg); +} + + +/// Internal operations predeclared here /// +int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); +static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir); +static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2], + lfs_dir_t *parent, lfs_entry_t *entry); +static int lfs_relocate(lfs_t *lfs, + const lfs_block_t oldpair[2], const lfs_block_t newpair[2]); +int lfs_deorphan(lfs_t *lfs); + + +/// Block allocator /// +static int lfs_alloc_lookahead(void *p, lfs_block_t block) { + lfs_t *lfs = p; + + lfs_block_t off = (block - lfs->free.start) % lfs->cfg->block_count; + if (off < lfs->cfg->lookahead) { + lfs->free.lookahead[off / 32] |= 1U << (off % 32); + } + + return 0; +} + +static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { + // deorphan if we haven't yet, only needed once after poweron + if (!lfs->deorphaned) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + + while (true) { + while (true) { + // check if we have looked at all blocks since last ack + if (lfs->free.start + lfs->free.off == lfs->free.end) { + LFS_WARN("No more free space %d", lfs->free.end); + return LFS_ERR_NOSPC; + } + + if (lfs->free.off >= lfs->cfg->lookahead) { + break; + } + + lfs_block_t off = lfs->free.off; + lfs->free.off += 1; + + if (!(lfs->free.lookahead[off / 32] & (1U << (off % 32)))) { + // found a free block + *block = (lfs->free.start + off) % lfs->cfg->block_count; + return 0; + } + } + + lfs->free.start += lfs->cfg->lookahead; + lfs->free.off = 0; + + // find mask of free blocks from tree + memset(lfs->free.lookahead, 0, lfs->cfg->lookahead/8); + int err = lfs_traverse(lfs, lfs_alloc_lookahead, lfs); + if (err) { + return err; + } + } +} + +static void lfs_alloc_ack(lfs_t *lfs) { + lfs->free.end = lfs->free.start + lfs->free.off + lfs->cfg->block_count; +} + + +/// Metadata pair and directory operations /// +static inline void lfs_pairswap(lfs_block_t pair[2]) { + lfs_block_t t = pair[0]; + pair[0] = pair[1]; + pair[1] = t; +} + +static inline bool lfs_pairisnull(const lfs_block_t pair[2]) { + return pair[0] == 0xffffffff || pair[1] == 0xffffffff; +} + +static inline int lfs_paircmp( + const lfs_block_t paira[2], + const lfs_block_t pairb[2]) { + return !(paira[0] == pairb[0] || paira[1] == pairb[1] || + paira[0] == pairb[1] || paira[1] == pairb[0]); +} + +static inline bool lfs_pairsync( + const lfs_block_t paira[2], + const lfs_block_t pairb[2]) { + return (paira[0] == pairb[0] && paira[1] == pairb[1]) || + (paira[0] == pairb[1] && paira[1] == pairb[0]); +} + +static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) { + // allocate pair of dir blocks + for (int i = 0; i < 2; i++) { + int err = lfs_alloc(lfs, &dir->pair[i]); + if (err) { + return err; + } + } + + // rather than clobbering one of the blocks we just pretend + // the revision may be valid + int err = lfs_bd_read(lfs, dir->pair[0], 0, &dir->d.rev, 4); + if (err) { + return err; + } + + // set defaults + dir->d.rev += 1; + dir->d.size = sizeof(dir->d)+4; + dir->d.tail[0] = -1; + dir->d.tail[1] = -1; + dir->off = sizeof(dir->d); + + // don't write out yet, let caller take care of that + return 0; +} + +static int lfs_dir_fetch(lfs_t *lfs, + lfs_dir_t *dir, const lfs_block_t pair[2]) { + // copy out pair, otherwise may be aliasing dir + const lfs_block_t tpair[2] = {pair[0], pair[1]}; + bool valid = false; + + // check both blocks for the most recent revision + for (int i = 0; i < 2; i++) { + struct lfs_disk_dir test; + int err = lfs_bd_read(lfs, tpair[i], 0, &test, sizeof(test)); + if (err) { + return err; + } + + if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) { + continue; + } + + if ((0x7fffffff & test.size) < sizeof(test)+4 || + (0x7fffffff & test.size) > lfs->cfg->block_size) { + continue; + } + + uint32_t crc = 0xffffffff; + lfs_crc(&crc, &test, sizeof(test)); + err = lfs_bd_crc(lfs, tpair[i], sizeof(test), + (0x7fffffff & test.size) - sizeof(test), &crc); + if (err) { + return err; + } + + if (crc != 0) { + continue; + } + + valid = true; + + // setup dir in case it's valid + dir->pair[0] = tpair[(i+0) % 2]; + dir->pair[1] = tpair[(i+1) % 2]; + dir->off = sizeof(dir->d); + dir->d = test; + } + + if (!valid) { + LFS_ERROR("Corrupted dir pair at %d %d", tpair[0], tpair[1]); + return LFS_ERR_CORRUPT; + } + + return 0; +} + +struct lfs_region { + lfs_off_t oldoff; + lfs_size_t oldlen; + const void *newdata; + lfs_size_t newlen; +}; + +static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir, + const struct lfs_region *regions, int count) { + dir->d.rev += 1; + lfs_pairswap(dir->pair); + for (int i = 0; i < count; i++) { + dir->d.size += regions[i].newlen - regions[i].oldlen; + } + + const lfs_block_t oldpair[2] = {dir->pair[0], dir->pair[1]}; + bool relocated = false; + + while (true) { + int err = lfs_bd_erase(lfs, dir->pair[0]); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + uint32_t crc = 0xffffffff; + lfs_crc(&crc, &dir->d, sizeof(dir->d)); + err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d)); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + int i = 0; + lfs_off_t oldoff = sizeof(dir->d); + lfs_off_t newoff = sizeof(dir->d); + while (newoff < (0x7fffffff & dir->d.size)-4) { + if (i < count && regions[i].oldoff == oldoff) { + lfs_crc(&crc, regions[i].newdata, regions[i].newlen); + int err = lfs_bd_prog(lfs, dir->pair[0], + newoff, regions[i].newdata, regions[i].newlen); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + oldoff += regions[i].oldlen; + newoff += regions[i].newlen; + i += 1; + } else { + uint8_t data; + int err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1); + if (err) { + return err; + } + + lfs_crc(&crc, &data, 1); + err = lfs_bd_prog(lfs, dir->pair[0], newoff, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + oldoff += 1; + newoff += 1; + } + } + + err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + err = lfs_bd_sync(lfs); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // successful commit, check checksum to make sure + crc = 0xffffffff; + err = lfs_bd_crc(lfs, dir->pair[0], 0, 0x7fffffff & dir->d.size, &crc); + if (err) { + return err; + } + + if (crc == 0) { + break; + } + +relocate: + //commit was corrupted + LFS_DEBUG("Bad block at %d", dir->pair[0]); + + // drop caches and prepare to relocate block + relocated = true; + lfs->pcache.block = 0xffffffff; + + // can't relocate superblock, filesystem is now frozen + if (lfs_paircmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) { + LFS_WARN("Superblock %d has become unwritable", oldpair[0]); + return LFS_ERR_CORRUPT; + } + + // relocate half of pair + err = lfs_alloc(lfs, &dir->pair[0]); + if (err) { + return err; + } + } + + if (relocated) { + // update references if we relocated + LFS_DEBUG("Relocating %d %d to %d %d", + oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]); + return lfs_relocate(lfs, oldpair, dir->pair); + } + + return 0; +} + +static int lfs_dir_update(lfs_t *lfs, lfs_dir_t *dir, + const lfs_entry_t *entry, const void *data) { + return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, sizeof(entry->d), &entry->d, sizeof(entry->d)}, + {entry->off+sizeof(entry->d), entry->d.len-sizeof(entry->d), + data, entry->d.len-sizeof(entry->d)} + }, data ? 2 : 1); +} + +static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir, + lfs_entry_t *entry, const void *data) { + // check if we fit, if top bit is set we do not and move on + while (true) { + if (dir->d.size + entry->d.len <= lfs->cfg->block_size) { + entry->off = dir->d.size - 4; + return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, 0, &entry->d, sizeof(entry->d)}, + {entry->off, 0, data, entry->d.len - sizeof(entry->d)} + }, 2); + } + + // we need to allocate a new dir block + if (!(0x80000000 & dir->d.size)) { + lfs_dir_t newdir; + int err = lfs_dir_alloc(lfs, &newdir); + if (err) { + return err; + } + + newdir.d.tail[0] = dir->d.tail[0]; + newdir.d.tail[1] = dir->d.tail[1]; + entry->off = newdir.d.size - 4; + err = lfs_dir_commit(lfs, &newdir, (struct lfs_region[]){ + {entry->off, 0, &entry->d, sizeof(entry->d)}, + {entry->off, 0, data, entry->d.len - sizeof(entry->d)} + }, 2); + if (err) { + return err; + } + + dir->d.size |= 0x80000000; + dir->d.tail[0] = newdir.pair[0]; + dir->d.tail[1] = newdir.pair[1]; + return lfs_dir_commit(lfs, dir, NULL, 0); + } + + int err = lfs_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + } +} + +static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { + // either shift out the one entry or remove the whole dir block + if (dir->d.size == sizeof(dir->d)+4) { + lfs_dir_t pdir; + int res = lfs_pred(lfs, dir->pair, &pdir); + if (res < 0) { + return res; + } + + if (!(pdir.d.size & 0x80000000)) { + return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, entry->d.len, NULL, 0}, + }, 1); + } else { + pdir.d.tail[0] = dir->d.tail[0]; + pdir.d.tail[1] = dir->d.tail[1]; + return lfs_dir_commit(lfs, dir, NULL, 0); + } + } else { + return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, entry->d.len, NULL, 0}, + }, 1); + } +} + +static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { + while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)-4) { + if (!(0x80000000 & dir->d.size)) { + entry->off = dir->off; + return LFS_ERR_NOENT; + } + + int err = lfs_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + + dir->off = sizeof(dir->d); + dir->pos += sizeof(dir->d) + 4; + } + + int err = lfs_bd_read(lfs, dir->pair[0], dir->off, + &entry->d, sizeof(entry->d)); + if (err) { + return err; + } + + dir->off += entry->d.len; + dir->pos += entry->d.len; + entry->off = dir->off - entry->d.len; + return 0; +} + +static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, + lfs_entry_t *entry, const char **path) { + const char *pathname = *path; + size_t pathlen; + + while (true) { + nextname: + // skip slashes + pathname += strspn(pathname, "/"); + pathlen = strcspn(pathname, "/"); + + // skip '.' and root '..' + if ((pathlen == 1 && memcmp(pathname, ".", 1) == 0) || + (pathlen == 2 && memcmp(pathname, "..", 2) == 0)) { + pathname += pathlen; + goto nextname; + } + + // skip if matched by '..' in name + const char *suffix = pathname + pathlen; + size_t sufflen; + int depth = 1; + while (true) { + suffix += strspn(suffix, "/"); + sufflen = strcspn(suffix, "/"); + if (sufflen == 0) { + break; + } + + if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) { + depth -= 1; + if (depth == 0) { + pathname = suffix + sufflen; + goto nextname; + } + } else { + depth += 1; + } + + suffix += sufflen; + } + + // find path + while (true) { + int err = lfs_dir_next(lfs, dir, entry); + if (err) { + return err; + } + + if ((entry->d.type != LFS_TYPE_REG && + entry->d.type != LFS_TYPE_DIR) || + entry->d.name != pathlen) { + continue; + } + + int res = lfs_bd_cmp(lfs, dir->pair[0], + entry->off + sizeof(entry->d), pathname, pathlen); + if (res < 0) { + return res; + } + + // found match + if (res) { + break; + } + } + + pathname += pathlen; + pathname += strspn(pathname, "/"); + if (pathname[0] == '\0') { + return 0; + } + + // continue on if we hit a directory + if (entry->d.type != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + int err = lfs_dir_fetch(lfs, dir, entry->d.u.dir); + if (err) { + return err; + } + + *path = pathname; + } + + return 0; +} + + +/// Top level directory operations /// +int lfs_mkdir(lfs_t *lfs, const char *path) { + // fetch parent directory + lfs_dir_t cwd; + int err = lfs_dir_fetch(lfs, &cwd, lfs->root); + if (err) { + return err; + } + + lfs_entry_t entry; + err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err != LFS_ERR_NOENT) { + return err ? err : LFS_ERR_EXISTS; + } + + // build up new directory + lfs_alloc_ack(lfs); + + lfs_dir_t dir; + err = lfs_dir_alloc(lfs, &dir); + if (err) { + return err; + } + dir.d.tail[0] = cwd.d.tail[0]; + dir.d.tail[1] = cwd.d.tail[1]; + + err = lfs_dir_commit(lfs, &dir, NULL, 0); + if (err) { + return err; + } + + entry.d.type = LFS_TYPE_DIR; + entry.d.name = strlen(path); + entry.d.len = sizeof(entry.d) + entry.d.name; + entry.d.u.dir[0] = dir.pair[0]; + entry.d.u.dir[1] = dir.pair[1]; + + cwd.d.tail[0] = dir.pair[0]; + cwd.d.tail[1] = dir.pair[1]; + + err = lfs_dir_append(lfs, &cwd, &entry, path); + if (err) { + return err; + } + + lfs_alloc_ack(lfs); + return 0; +} + +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { + dir->pair[0] = lfs->root[0]; + dir->pair[1] = lfs->root[1]; + + int err = lfs_dir_fetch(lfs, dir, dir->pair); + if (err) { + return err; + } + + if (strspn(path, "/.") == strlen(path)) { + // can only be something like '/././../.' + dir->head[0] = dir->pair[0]; + dir->head[1] = dir->pair[1]; + dir->pos = sizeof(dir->d) - 2; + dir->off = sizeof(dir->d); + return 0; + } + + lfs_entry_t entry; + err = lfs_dir_find(lfs, dir, &entry, &path); + if (err) { + return err; + } else if (entry.d.type != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + err = lfs_dir_fetch(lfs, dir, entry.d.u.dir); + if (err) { + return err; + } + + // setup head dir + // special offset for '.' and '..' + dir->head[0] = dir->pair[0]; + dir->head[1] = dir->pair[1]; + dir->pos = sizeof(dir->d) - 2; + dir->off = sizeof(dir->d); + return 0; +} + +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) { + // do nothing, dir is always synchronized + return 0; +} + +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { + memset(info, 0, sizeof(*info)); + + // special offset for '.' and '..' + if (dir->pos == sizeof(dir->d) - 2) { + info->type = LFS_TYPE_DIR; + strcpy(info->name, "."); + dir->pos += 1; + return 1; + } else if (dir->pos == sizeof(dir->d) - 1) { + info->type = LFS_TYPE_DIR; + strcpy(info->name, ".."); + dir->pos += 1; + return 1; + } + + lfs_entry_t entry; + while (true) { + int err = lfs_dir_next(lfs, dir, &entry); + if (err) { + return (err == LFS_ERR_NOENT) ? 0 : err; + } + + if (entry.d.type == LFS_TYPE_REG || + entry.d.type == LFS_TYPE_DIR) { + break; + } + } + + info->type = entry.d.type; + if (info->type == LFS_TYPE_REG) { + info->size = entry.d.u.file.size; + } + + int err = lfs_bd_read(lfs, dir->pair[0], entry.off + sizeof(entry.d), + info->name, entry.d.name); + if (err) { + return err; + } + + return 1; +} + +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { + // simply walk from head dir + int err = lfs_dir_rewind(lfs, dir); + if (err) { + return err; + } + dir->pos = off; + + while (off > (0x7fffffff & dir->d.size)) { + off -= 0x7fffffff & dir->d.size; + if (!(0x80000000 & dir->d.size)) { + return LFS_ERR_INVAL; + } + + int err = lfs_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + } + + dir->off = off; + return 0; +} + +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) { + return dir->pos; +} + +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { + // reload the head dir + int err = lfs_dir_fetch(lfs, dir, dir->head); + if (err) { + return err; + } + + dir->pair[0] = dir->head[0]; + dir->pair[1] = dir->head[1]; + dir->pos = sizeof(dir->d) - 2; + dir->off = sizeof(dir->d); + return 0; +} + + +/// File index list operations /// +static int lfs_index(lfs_t *lfs, lfs_off_t *off) { + lfs_off_t i = 0; + lfs_size_t words = lfs->cfg->block_size / 4; + + while (*off >= lfs->cfg->block_size) { + i += 1; + *off -= lfs->cfg->block_size; + *off += 4*lfs_min(lfs_ctz(i)+1, words-1); + } + + return i; +} + +static int lfs_index_find(lfs_t *lfs, + lfs_cache_t *rcache, const lfs_cache_t *pcache, + lfs_block_t head, lfs_size_t size, + lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) { + if (size == 0) { + *block = -1; + *off = 0; + return 0; + } + + lfs_off_t current = lfs_index(lfs, &(lfs_off_t){size-1}); + lfs_off_t target = lfs_index(lfs, &pos); + lfs_size_t words = lfs->cfg->block_size / 4; + + while (current > target) { + lfs_size_t skip = lfs_min( + lfs_npw2(current-target+1) - 1, + lfs_min(lfs_ctz(current)+1, words-1) - 1); + + int err = lfs_cache_read(lfs, rcache, pcache, head, 4*skip, &head, 4); + if (err) { + return err; + } + + current -= 1 << skip; + } + + *block = head; + *off = pos; + return 0; +} + +static int lfs_index_extend(lfs_t *lfs, + lfs_cache_t *rcache, lfs_cache_t *pcache, + lfs_block_t head, lfs_size_t size, + lfs_off_t *block, lfs_block_t *off) { + while (true) { + // go ahead and grab a block + int err = lfs_alloc(lfs, block); + if (err) { + return err; + } + + err = lfs_bd_erase(lfs, *block); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (size == 0) { + *off = 0; + return 0; + } + + size -= 1; + lfs_off_t index = lfs_index(lfs, &size); + size += 1; + + // just copy out the last block if it is incomplete + if (size != lfs->cfg->block_size) { + for (lfs_off_t i = 0; i < size; i++) { + uint8_t data; + int err = lfs_cache_read(lfs, rcache, NULL, head, i, &data, 1); + if (err) { + return err; + } + + err = lfs_cache_prog(lfs, pcache, rcache, *block, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + *off = size; + return 0; + } + + // append block + index += 1; + lfs_size_t words = lfs->cfg->block_size / 4; + lfs_size_t skips = lfs_min(lfs_ctz(index)+1, words-1); + + for (lfs_off_t i = 0; i < skips; i++) { + int err = lfs_cache_prog(lfs, pcache, rcache, + *block, 4*i, &head, 4); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (i != skips-1) { + err = lfs_cache_read(lfs, rcache, NULL, head, 4*i, &head, 4); + if (err) { + return err; + } + } + } + + *off = 4*skips; + return 0; + +relocate: + LFS_DEBUG("Bad block at %d", *block); + + // just clear cache and try a new block + pcache->block = 0xffffffff; + } +} + +static int lfs_index_traverse(lfs_t *lfs, + lfs_cache_t *rcache, const lfs_cache_t *pcache, + lfs_block_t head, lfs_size_t size, + int (*cb)(void*, lfs_block_t), void *data) { + if (size == 0) { + return 0; + } + + lfs_off_t index = lfs_index(lfs, &(lfs_off_t){size-1}); + + while (true) { + int err = cb(data, head); + if (err) { + return err; + } + + if (index == 0) { + return 0; + } + + err = lfs_cache_read(lfs, rcache, pcache, head, 0, &head, 4); + if (err) { + return err; + } + + index -= 1; + } + + return 0; +} + + +/// Top level file operations /// +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags) { + // allocate entry for file if it doesn't exist + lfs_dir_t cwd; + int err = lfs_dir_fetch(lfs, &cwd, lfs->root); + if (err) { + return err; + } + + lfs_entry_t entry; + err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + if (!(flags & LFS_O_CREAT)) { + return LFS_ERR_NOENT; + } + + // create entry to remember name + entry.d.type = LFS_TYPE_REG; + entry.d.name = strlen(path); + entry.d.len = sizeof(entry.d) + entry.d.name; + entry.d.u.file.head = -1; + entry.d.u.file.size = 0; + err = lfs_dir_append(lfs, &cwd, &entry, path); + if (err) { + return err; + } + } else if (entry.d.type == LFS_TYPE_DIR) { + return LFS_ERR_ISDIR; + } else if (flags & LFS_O_EXCL) { + return LFS_ERR_EXISTS; + } + + // setup file struct + file->pair[0] = cwd.pair[0]; + file->pair[1] = cwd.pair[1]; + file->poff = entry.off; + file->head = entry.d.u.file.head; + file->size = entry.d.u.file.size; + file->flags = flags; + file->pos = 0; + + if (flags & LFS_O_TRUNC) { + file->head = -1; + file->size = 0; + } + + // allocate buffer if needed + file->cache.block = 0xffffffff; + if (lfs->cfg->file_buffer) { + file->cache.buffer = lfs->cfg->file_buffer; + } else if ((file->flags & 3) == LFS_O_RDONLY) { + file->cache.buffer = malloc(lfs->cfg->read_size); + if (!file->cache.buffer) { + return LFS_ERR_NOMEM; + } + } else { + file->cache.buffer = malloc(lfs->cfg->prog_size); + if (!file->cache.buffer) { + return LFS_ERR_NOMEM; + } + } + + // add to list of files + file->next = lfs->files; + lfs->files = file; + + return 0; +} + +int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { + int err = lfs_file_sync(lfs, file); + + // remove from list of files + for (lfs_file_t **p = &lfs->files; *p; p = &(*p)->next) { + if (*p == file) { + *p = file->next; + break; + } + } + + // clean up memory + if (!lfs->cfg->file_buffer) { + free(file->cache.buffer); + } + + return err; +} + +static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { +relocate: + LFS_DEBUG("Bad block at %d", file->block); + + // just relocate what exists into new block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + + err = lfs_bd_erase(lfs, nblock); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // either read from dirty cache or disk + for (lfs_off_t i = 0; i < file->off; i++) { + uint8_t data; + err = lfs_cache_read(lfs, &lfs->rcache, &file->cache, + file->block, i, &data, 1); + if (err) { + return err; + } + + err = lfs_cache_prog(lfs, &lfs->pcache, &lfs->rcache, + nblock, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // copy over new state of file + memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->prog_size); + file->cache.block = lfs->pcache.block; + file->cache.off = lfs->pcache.off; + lfs->pcache.block = 0xffffffff; + + file->block = nblock; + return 0; +} + +static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { + if (file->flags & LFS_F_READING) { + // just drop read cache + file->cache.block = 0xffffffff; + file->flags &= ~LFS_F_READING; + } + + if (file->flags & LFS_F_WRITING) { + lfs_off_t pos = file->pos; + + // copy over anything after current branch + lfs_file_t orig = { + .head = file->head, + .size = file->size, + .flags = LFS_O_RDONLY, + .pos = file->pos, + .cache = lfs->rcache, + }; + lfs->rcache.block = 0xffffffff; + + while (file->pos < file->size) { + // copy over a byte at a time, leave it up to caching + // to make this efficient + uint8_t data; + lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1); + if (res < 0) { + return res; + } + + res = lfs_file_write(lfs, file, &data, 1); + if (res < 0) { + return res; + } + + // keep our reference to the rcache in sync + if (lfs->rcache.block != 0xffffffff) { + orig.cache.block = 0xffffffff; + lfs->rcache.block = 0xffffffff; + } + } + + // write out what we have + while (true) { + int err = lfs_cache_flush(lfs, &file->cache, &lfs->rcache); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + break; +relocate: + err = lfs_file_relocate(lfs, file); + if (err) { + return err; + } + } + + // actual file updates + file->head = file->block; + file->size = file->pos; + file->flags &= ~LFS_F_WRITING; + file->flags |= LFS_F_DIRTY; + + file->pos = pos; + } + + return 0; +} + +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + if ((file->flags & LFS_F_DIRTY) && !lfs_pairisnull(file->pair)) { + // update dir entry + lfs_dir_t cwd; + int err = lfs_dir_fetch(lfs, &cwd, file->pair); + if (err) { + return err; + } + + lfs_entry_t entry = {.off = file->poff}; + err = lfs_bd_read(lfs, cwd.pair[0], entry.off, + &entry.d, sizeof(entry.d)); + if (err) { + return err; + } + + if (entry.d.type != LFS_TYPE_REG) { + // sanity check valid entry + return LFS_ERR_INVAL; + } + + entry.d.u.file.head = file->head; + entry.d.u.file.size = file->size; + + err = lfs_dir_update(lfs, &cwd, &entry, NULL); + if (err) { + return err; + } + + file->flags &= ~LFS_F_DIRTY; + } + + return 0; +} + +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + lfs_size_t nsize = size; + + if ((file->flags & 3) == LFS_O_WRONLY) { + return LFS_ERR_INVAL; + } + + if (file->flags & LFS_F_WRITING) { + // flush out any writes + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + } + + size = lfs_min(size, file->size - file->pos); + nsize = size; + + while (nsize > 0) { + // check if we need a new block + if (!(file->flags & LFS_F_READING) || + file->off == lfs->cfg->block_size) { + int err = lfs_index_find(lfs, &file->cache, NULL, + file->head, file->size, + file->pos, &file->block, &file->off); + if (err) { + return err; + } + + file->flags |= LFS_F_READING; + } + + // read as much as we can in current block + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + int err = lfs_cache_read(lfs, &file->cache, NULL, + file->block, file->off, data, diff); + if (err) { + return err; + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + } + + return size; +} + +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + lfs_size_t nsize = size; + + if ((file->flags & 3) == LFS_O_RDONLY) { + return LFS_ERR_INVAL; + } + + if (file->flags & LFS_F_READING) { + // drop any reads + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + } + + if ((file->flags & LFS_O_APPEND) && file->pos < file->size) { + file->pos = file->size; + } + + while (nsize > 0) { + // check if we need a new block + if (!(file->flags & LFS_F_WRITING) || + file->off == lfs->cfg->block_size) { + if (!(file->flags & LFS_F_WRITING)) { + // find out which block we're extending from + int err = lfs_index_find(lfs, &file->cache, NULL, + file->head, file->size, + file->pos, &file->block, &file->off); + if (err) { + return err; + } + + // mark cache as dirty since we may have read data into it + file->cache.block = 0xffffffff; + file->flags |= LFS_F_WRITING; + } + + // extend file with new blocks + lfs_alloc_ack(lfs); + int err = lfs_index_extend(lfs, &lfs->rcache, &file->cache, + file->block, file->pos, + &file->block, &file->off); + if (err) { + return err; + } + } + + // program as much as we can in current block + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + while (true) { + int err = lfs_cache_prog(lfs, &file->cache, &lfs->rcache, + file->block, file->off, data, diff); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + break; +relocate: + err = lfs_file_relocate(lfs, file); + if (err) { + return err; + } + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + + lfs_alloc_ack(lfs); + } + + return size; +} + +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence) { + // write out everything beforehand, may be noop if rdonly + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + // update pos + lfs_off_t pos = file->pos; + + if (whence == LFS_SEEK_SET) { + file->pos = off; + } else if (whence == LFS_SEEK_CUR) { + file->pos = file->pos + off; + } else if (whence == LFS_SEEK_END) { + file->pos = file->size + off; + } + + return pos; +} + +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { + return file->pos; +} + +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) { + lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_SET); + if (res < 0) { + return res; + } + + return 0; +} + +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { + return lfs_max(file->pos, file->size); +} + + +/// General fs oprations /// +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { + lfs_dir_t cwd; + int err = lfs_dir_fetch(lfs, &cwd, lfs->root); + if (err) { + return err; + } + + lfs_entry_t entry; + err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err) { + return err; + } + + memset(info, 0, sizeof(*info)); + info->type = entry.d.type; + if (info->type == LFS_TYPE_REG) { + info->size = entry.d.u.file.size; + } + + err = lfs_bd_read(lfs, cwd.pair[0], entry.off + sizeof(entry.d), + info->name, entry.d.name); + if (err) { + return err; + } + + return 0; +} + +int lfs_remove(lfs_t *lfs, const char *path) { + lfs_dir_t cwd; + int err = lfs_dir_fetch(lfs, &cwd, lfs->root); + if (err) { + return err; + } + + lfs_entry_t entry; + err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err) { + return err; + } + + lfs_dir_t dir; + if (entry.d.type == LFS_TYPE_DIR) { + // must be empty before removal, checking size + // without masking top bit checks for any case where + // dir is not empty + int err = lfs_dir_fetch(lfs, &dir, entry.d.u.dir); + if (err) { + return err; + } else if (dir.d.size != sizeof(dir.d)+4) { + return LFS_ERR_INVAL; + } + } + + // remove the entry + err = lfs_dir_remove(lfs, &cwd, &entry); + if (err) { + return err; + } + + // shift over any files that are affected + for (lfs_file_t *f = lfs->files; f; f = f->next) { + if (lfs_paircmp(f->pair, cwd.pair) == 0) { + if (f->poff == entry.off) { + f->pair[0] = 0xffffffff; + f->pair[1] = 0xffffffff; + } else if (f->poff > entry.off) { + f->poff -= entry.d.len; + } + } + } + + // if we were a directory, just run a deorphan step, this should + // collect us, although is expensive + if (entry.d.type == LFS_TYPE_DIR) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + + return 0; +} + +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { + // find old entry + lfs_dir_t oldcwd; + int err = lfs_dir_fetch(lfs, &oldcwd, lfs->root); + if (err) { + return err; + } + + lfs_entry_t oldentry; + err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath); + if (err) { + return err; + } + + // allocate new entry + lfs_dir_t newcwd; + err = lfs_dir_fetch(lfs, &newcwd, lfs->root); + if (err) { + return err; + } + + lfs_entry_t preventry; + err = lfs_dir_find(lfs, &newcwd, &preventry, &newpath); + if (err && err != LFS_ERR_NOENT) { + return err; + } + bool prevexists = (err != LFS_ERR_NOENT); + + // must have same type + if (prevexists && preventry.d.type != oldentry.d.type) { + return LFS_ERR_INVAL; + } + + lfs_dir_t dir; + if (prevexists && preventry.d.type == LFS_TYPE_DIR) { + // must be empty before removal, checking size + // without masking top bit checks for any case where + // dir is not empty + int err = lfs_dir_fetch(lfs, &dir, preventry.d.u.dir); + if (err) { + return err; + } else if (dir.d.size != sizeof(dir.d)+4) { + return LFS_ERR_INVAL; + } + } + + // move to new location + lfs_entry_t newentry = preventry; + newentry.d = oldentry.d; + newentry.d.name = strlen(newpath); + newentry.d.len = sizeof(newentry.d) + newentry.d.name; + + if (prevexists) { + int err = lfs_dir_update(lfs, &newcwd, &newentry, newpath); + if (err) { + return err; + } + } else { + int err = lfs_dir_append(lfs, &newcwd, &newentry, newpath); + if (err) { + return err; + } + } + + // fetch again in case newcwd == oldcwd + err = lfs_dir_fetch(lfs, &oldcwd, oldcwd.pair); + if (err) { + return err; + } + + err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath); + if (err) { + return err; + } + + // remove from old location + err = lfs_dir_remove(lfs, &oldcwd, &oldentry); + if (err) { + return err; + } + + // shift over any files that are affected + for (lfs_file_t *f = lfs->files; f; f = f->next) { + if (lfs_paircmp(f->pair, oldcwd.pair) == 0) { + if (f->poff == oldentry.off) { + f->pair[0] = 0xffffffff; + f->pair[1] = 0xffffffff; + } else if (f->poff > oldentry.off) { + f->poff -= oldentry.d.len; + } + } + } + + // if we were a directory, just run a deorphan step, this should + // collect us, although is expensive + if (prevexists && preventry.d.type == LFS_TYPE_DIR) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + + return 0; +} + + +/// Filesystem operations /// +static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { + lfs->cfg = cfg; + + // setup read cache + lfs->rcache.block = 0xffffffff; + if (lfs->cfg->read_buffer) { + lfs->rcache.buffer = lfs->cfg->read_buffer; + } else { + lfs->rcache.buffer = malloc(lfs->cfg->read_size); + if (!lfs->rcache.buffer) { + return LFS_ERR_NOMEM; + } + } + + // setup program cache + lfs->pcache.block = 0xffffffff; + if (lfs->cfg->prog_buffer) { + lfs->pcache.buffer = lfs->cfg->prog_buffer; + } else { + lfs->pcache.buffer = malloc(lfs->cfg->prog_size); + if (!lfs->pcache.buffer) { + return LFS_ERR_NOMEM; + } + } + + // setup lookahead + if (lfs->cfg->lookahead_buffer) { + lfs->free.lookahead = lfs->cfg->lookahead_buffer; + } else { + lfs->free.lookahead = malloc(lfs->cfg->lookahead/8); + if (!lfs->free.lookahead) { + return LFS_ERR_NOMEM; + } + } + + // setup default state + lfs->root[0] = 0xffffffff; + lfs->root[1] = 0xffffffff; + lfs->files = NULL; + lfs->deorphaned = false; + + return 0; +} + +static int lfs_deinit(lfs_t *lfs) { + // free allocated memory + if (!lfs->cfg->read_buffer) { + free(lfs->rcache.buffer); + } + + if (!lfs->cfg->prog_buffer) { + free(lfs->pcache.buffer); + } + + if (!lfs->cfg->lookahead_buffer) { + free(lfs->free.lookahead); + } + + return 0; +} + +int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { + int err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + // create free lookahead + memset(lfs->free.lookahead, 0, lfs->cfg->lookahead/8); + lfs->free.start = 0; + lfs->free.off = 0; + lfs->free.end = lfs->free.start + lfs->cfg->block_count; + + // create superblock dir + lfs_alloc_ack(lfs); + lfs_dir_t superdir; + err = lfs_dir_alloc(lfs, &superdir); + if (err) { + return err; + } + + // write root directory + lfs_dir_t root; + err = lfs_dir_alloc(lfs, &root); + if (err) { + return err; + } + + err = lfs_dir_commit(lfs, &root, NULL, 0); + if (err) { + return err; + } + + lfs->root[0] = root.pair[0]; + lfs->root[1] = root.pair[1]; + + // write superblocks + lfs_superblock_t superblock = { + .off = sizeof(superdir.d), + .d.type = LFS_TYPE_SUPERBLOCK, + .d.name = sizeof(superblock.d.magic), + .d.len = sizeof(superblock.d), + .d.version = 0x00010001, + .d.magic = {"littlefs"}, + .d.block_size = lfs->cfg->block_size, + .d.block_count = lfs->cfg->block_count, + .d.root = {lfs->root[0], lfs->root[1]}, + }; + superdir.d.tail[0] = root.pair[0]; + superdir.d.tail[1] = root.pair[1]; + superdir.d.size = sizeof(superdir.d) + sizeof(superblock.d) + 4; + + // write both pairs to be safe + bool valid = false; + for (int i = 0; i < 2; i++) { + int err = lfs_dir_commit(lfs, &superdir, (struct lfs_region[]){ + {sizeof(superdir.d), sizeof(superblock.d), + &superblock.d, sizeof(superblock.d)} + }, 1); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + valid = valid || !err; + } + + if (!valid) { + return LFS_ERR_CORRUPT; + } + + // sanity check that fetch works + err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + lfs_alloc_ack(lfs); + return lfs_deinit(lfs); +} + +int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { + int err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + // setup free lookahead + lfs->free.start = -lfs->cfg->lookahead; + lfs->free.off = lfs->cfg->lookahead; + lfs->free.end = lfs->free.start + lfs->cfg->block_count; + + // load superblock + lfs_dir_t dir; + lfs_superblock_t superblock; + err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); + if (!err) { + err = lfs_bd_read(lfs, dir.pair[0], + sizeof(dir.d), &superblock.d, sizeof(superblock.d)); + + lfs->root[0] = superblock.d.root[0]; + lfs->root[1] = superblock.d.root[1]; + } + + if (err == LFS_ERR_CORRUPT || + memcmp(superblock.d.magic, "littlefs", 8) != 0) { + LFS_ERROR("Invalid superblock at %d %d", dir.pair[0], dir.pair[1]); + return LFS_ERR_CORRUPT; + } + + if (superblock.d.version > (0x00010001 | 0x0000ffff)) { + LFS_ERROR("Invalid version %d.%d\n", + 0xffff & (superblock.d.version >> 16), + 0xffff & (superblock.d.version >> 0)); + return LFS_ERR_INVAL; + } + + return err; +} + +int lfs_unmount(lfs_t *lfs) { + return lfs_deinit(lfs); +} + + +/// Littlefs specific operations /// +int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + // iterate over metadata pairs + lfs_dir_t dir; + lfs_entry_t entry; + lfs_block_t cwd[2] = {0, 1}; + + while (true) { + for (int i = 0; i < 2; i++) { + int err = cb(data, cwd[i]); + if (err) { + return err; + } + } + + int err = lfs_dir_fetch(lfs, &dir, cwd); + if (err) { + return err; + } + + // iterate over contents + while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) { + int err = lfs_bd_read(lfs, dir.pair[0], dir.off, + &entry.d, sizeof(entry.d)); + if (err) { + return err; + } + + dir.off += entry.d.len; + if ((0xf & entry.d.type) == (0xf & LFS_TYPE_REG)) { + int err = lfs_index_traverse(lfs, &lfs->rcache, NULL, + entry.d.u.file.head, entry.d.u.file.size, cb, data); + if (err) { + return err; + } + } + } + + cwd[0] = dir.d.tail[0]; + cwd[1] = dir.d.tail[1]; + + if (lfs_pairisnull(cwd)) { + break; + } + } + + // iterate over any open files + for (lfs_file_t *f = lfs->files; f; f = f->next) { + if (f->flags & LFS_F_DIRTY) { + int err = lfs_index_traverse(lfs, &lfs->rcache, &f->cache, + f->head, f->size, cb, data); + if (err) { + return err; + } + } + + if (f->flags & LFS_F_WRITING) { + int err = lfs_index_traverse(lfs, &lfs->rcache, &f->cache, + f->block, f->pos, cb, data); + if (err) { + return err; + } + } + } + + return 0; +} + +static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir) { + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + // iterate over all directory directory entries + int err = lfs_dir_fetch(lfs, pdir, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + while (!lfs_pairisnull(pdir->d.tail)) { + if (lfs_paircmp(pdir->d.tail, dir) == 0) { + return true; + } + + int err = lfs_dir_fetch(lfs, pdir, pdir->d.tail); + if (err) { + return err; + } + } + + return false; +} + +static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2], + lfs_dir_t *parent, lfs_entry_t *entry) { + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + parent->d.tail[0] = 0; + parent->d.tail[1] = 1; + + // iterate over all directory directory entries + while (!lfs_pairisnull(parent->d.tail)) { + int err = lfs_dir_fetch(lfs, parent, parent->d.tail); + if (err) { + return err; + } + + while (true) { + int err = lfs_dir_next(lfs, parent, entry); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + if (((0xf & entry->d.type) == (0xf & LFS_TYPE_DIR)) && + lfs_paircmp(entry->d.u.dir, dir) == 0) { + return true; + } + } + } + + return false; +} + +static int lfs_relocate(lfs_t *lfs, + const lfs_block_t oldpair[2], const lfs_block_t newpair[2]) { + // find parent + lfs_dir_t parent; + lfs_entry_t entry; + int res = lfs_parent(lfs, oldpair, &parent, &entry); + if (res < 0) { + return res; + } + + if (res) { + // update disk, this creates a desync + entry.d.u.dir[0] = newpair[0]; + entry.d.u.dir[1] = newpair[1]; + + int err = lfs_dir_update(lfs, &parent, &entry, NULL); + if (err) { + return err; + } + + // update internal root + if (lfs_paircmp(oldpair, lfs->root) == 0) { + LFS_DEBUG("Relocating root %d %d", newpair[0], newpair[1]); + lfs->root[0] = newpair[0]; + lfs->root[1] = newpair[1]; + } + + // clean up bad block, which should now be a desync + return lfs_deorphan(lfs); + } + + // find pred + res = lfs_pred(lfs, oldpair, &parent); + if (res < 0) { + return res; + } + + if (res) { + // just replace bad pair, no desync can occur + parent.d.tail[0] = newpair[0]; + parent.d.tail[0] = newpair[0]; + + return lfs_dir_commit(lfs, &parent, NULL, 0); + } + + // couldn't find dir, must be new + return 0; +} + +int lfs_deorphan(lfs_t *lfs) { + lfs->deorphaned = true; + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + lfs_dir_t pdir; + lfs_dir_t cdir; + + // skip superblock + int err = lfs_dir_fetch(lfs, &pdir, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + // iterate over all directories + while (!lfs_pairisnull(pdir.d.tail)) { + int err = lfs_dir_fetch(lfs, &cdir, pdir.d.tail); + if (err) { + return err; + } + + // only check head blocks + if (!(0x80000000 & pdir.d.size)) { + // check if we have a parent + lfs_dir_t parent; + lfs_entry_t entry; + int res = lfs_parent(lfs, pdir.d.tail, &parent, &entry); + if (res < 0) { + return res; + } + + if (!res) { + // we are an orphan + LFS_DEBUG("Orphan %d %d", pdir.d.tail[0], pdir.d.tail[1]); + + pdir.d.tail[0] = cdir.d.tail[0]; + pdir.d.tail[1] = cdir.d.tail[1]; + + err = lfs_dir_commit(lfs, &pdir, NULL, 0); + if (err) { + return err; + } + + break; + } + + if (!lfs_pairsync(entry.d.u.dir, pdir.d.tail)) { + // we have desynced + LFS_DEBUG("Desync %d %d", entry.d.u.dir[0], entry.d.u.dir[1]); + + pdir.d.tail[0] = entry.d.u.dir[0]; + pdir.d.tail[1] = entry.d.u.dir[1]; + + err = lfs_dir_commit(lfs, &pdir, NULL, 0); + if (err) { + return err; + } + + break; + } + } + + memcpy(&pdir, &cdir, sizeof(pdir)); + } + + return 0; +} + diff --git a/lfs.h b/lfs.h new file mode 100644 index 0000000000..0133b94aec --- /dev/null +++ b/lfs.h @@ -0,0 +1,435 @@ +/* + * The little filesystem + * + * Copyright (c) 2017 Christopher Haster + * Distributed under the Apache 2.0 license + */ +#ifndef LFS_H +#define LFS_H + +#include +#include + + +/// Definitions /// + +// Type definitions +typedef uint32_t lfs_size_t; +typedef uint32_t lfs_off_t; + +typedef int32_t lfs_ssize_t; +typedef int32_t lfs_soff_t; + +typedef uint32_t lfs_block_t; + +// Max name size in bytes +#ifndef LFS_NAME_MAX +#define LFS_NAME_MAX 255 +#endif + +// Possible error codes, these are negative to allow +// valid positive return values +enum lfs_error { + LFS_ERR_OK = 0, // No error + LFS_ERR_IO = -5, // Error during device operation + LFS_ERR_CORRUPT = -52, // Corrupted + LFS_ERR_NOENT = -2, // No directory entry + LFS_ERR_EXISTS = -17, // Entry already exists + LFS_ERR_NOTDIR = -20, // Entry is not a dir + LFS_ERR_ISDIR = -21, // Entry is a dir + LFS_ERR_INVAL = -22, // Invalid parameter + LFS_ERR_NOSPC = -28, // No space left on device + LFS_ERR_NOMEM = -12, // No more memory available +}; + +// File types +enum lfs_type { + LFS_TYPE_REG = 0x11, + LFS_TYPE_DIR = 0x22, + LFS_TYPE_SUPERBLOCK = 0xe2, +}; + +// File open flags +enum lfs_open_flags { + // open flags + LFS_O_RDONLY = 1, // Open a file as read only + LFS_O_WRONLY = 2, // Open a file as write only + LFS_O_RDWR = 3, // Open a file as read and write + LFS_O_CREAT = 0x0100, // Create a file if it does not exist + LFS_O_EXCL = 0x0200, // Fail if a file already exists + LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size + LFS_O_APPEND = 0x0800, // Move to end of file on every write + + // internally used flags + LFS_F_DIRTY = 0x10000, // File does not match storage + LFS_F_WRITING = 0x20000, // File has been written since last flush + LFS_F_READING = 0x40000, // File has been read since last flush +}; + +// File seek flags +enum lfs_whence_flags { + LFS_SEEK_SET = 0, // Seek relative to an absolute position + LFS_SEEK_CUR = 1, // Seek relative to the current file position + LFS_SEEK_END = 2, // Seek relative to the end of the file +}; + + +// Configuration provided during initialization of the littlefs +struct lfs_config { + // Opaque user provided context that can be used to pass + // information to the block device operations + void *context; + + // Read a region in a block. Negative error codes are propogated + // to the user. + int (*read)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + + // Program a region in a block. The block must have previously + // been erased. Negative error codes are propogated to the user. + // The prog function must return LFS_ERR_CORRUPT if the block should + // be considered bad. + int (*prog)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + + // Erase a block. A block must be erased before being programmed. + // The state of an erased block is undefined. Negative error codes + // are propogated to the user. + int (*erase)(const struct lfs_config *c, lfs_block_t block); + + // Sync the state of the underlying block device. Negative error codes + // are propogated to the user. + int (*sync)(const struct lfs_config *c); + + // Minimum size of a block read. This determines the size of read buffers. + // This may be larger than the physical read size to improve performance + // by caching more of the block device. + lfs_size_t read_size; + + // Minimum size of a block program. This determines the size of program + // buffers. This may be larger than the physical program size to improve + // performance by caching more of the block device. + lfs_size_t prog_size; + + // Size of an erasable block. This does not impact ram consumption and + // may be larger than the physical erase size. However, this should be + // kept small as each file currently takes up an entire block . + lfs_size_t block_size; + + // Number of erasable blocks on the device. + lfs_size_t block_count; + + // Number of blocks to lookahead during block allocation. A larger + // lookahead reduces the number of passes required to allocate a block. + // The lookahead buffer requires only 1 bit per block so it can be quite + // large with little ram impact. Should be a multiple of 32. + lfs_size_t lookahead; + + // Optional, statically allocated read buffer. Must be read sized. + void *read_buffer; + + // Optional, statically allocated program buffer. Must be program sized. + void *prog_buffer; + + // Optional, statically allocated lookahead buffer. Must be 1 bit per + // lookahead block. + void *lookahead_buffer; + + // Optional, statically allocated buffer for files. Must be program sized. + // If enabled, only one file may be opened at a time. + void *file_buffer; +}; + + +// File info structure +struct lfs_info { + // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR + uint8_t type; + + // Size of the file, only valid for REG files + lfs_size_t size; + + // Name of the file stored as a null-terminated string + char name[LFS_NAME_MAX+1]; +}; + + +/// littlefs data structures /// +typedef struct lfs_entry { + lfs_off_t off; + + struct lfs_disk_entry { + uint8_t type; + uint8_t name; + uint16_t len; + union { + struct { + lfs_block_t head; + lfs_size_t size; + } file; + lfs_block_t dir[2]; + } u; + } d; +} lfs_entry_t; + +typedef struct lfs_cache { + lfs_block_t block; + lfs_off_t off; + uint8_t *buffer; +} lfs_cache_t; + +typedef struct lfs_file { + struct lfs_file *next; + lfs_block_t pair[2]; + lfs_off_t poff; + + lfs_block_t head; + lfs_size_t size; + + uint32_t flags; + lfs_off_t pos; + lfs_block_t block; + lfs_off_t off; + lfs_cache_t cache; +} lfs_file_t; + +typedef struct lfs_dir { + lfs_block_t pair[2]; + lfs_off_t off; + + lfs_block_t head[2]; + lfs_off_t pos; + + struct lfs_disk_dir { + uint32_t rev; + lfs_size_t size; + lfs_block_t tail[2]; + } d; +} lfs_dir_t; + +typedef struct lfs_superblock { + lfs_off_t off; + + struct lfs_disk_superblock { + uint8_t type; + uint8_t name; + uint16_t len; + lfs_block_t root[2]; + uint32_t block_size; + uint32_t block_count; + uint32_t version; + char magic[8]; + } d; +} lfs_superblock_t; + +typedef struct lfs_free { + lfs_block_t end; + lfs_block_t start; + lfs_block_t off; + uint32_t *lookahead; +} lfs_free_t; + +// The littlefs type +typedef struct lfs { + const struct lfs_config *cfg; + + lfs_block_t root[2]; + lfs_file_t *files; + bool deorphaned; + + lfs_cache_t rcache; + lfs_cache_t pcache; + + lfs_free_t free; +} lfs_t; + + +/// Filesystem functions /// + +// Format a block device with the littlefs +// +// Requires a littlefs object and config struct. This clobbers the littlefs +// object, and does not leave the filesystem mounted. +// +// Returns a negative error code on failure. +int lfs_format(lfs_t *lfs, const struct lfs_config *config); + +// Mounts a littlefs +// +// Requires a littlefs object and config struct. Multiple filesystems +// may be mounted simultaneously with multiple littlefs objects. Both +// lfs and config must be allocated while mounted. +// +// Returns a negative error code on failure. +int lfs_mount(lfs_t *lfs, const struct lfs_config *config); + +// Unmounts a littlefs +// +// Does nothing besides releasing any allocated resources. +// Returns a negative error code on failure. +int lfs_unmount(lfs_t *lfs); + +/// General operations /// + +// Removes a file or directory +// +// If removing a directory, the directory must be empty. +// Returns a negative error code on failure. +int lfs_remove(lfs_t *lfs, const char *path); + +// Rename or move a file or directory +// +// If the destination exists, it must match the source in type. +// If the destination is a directory, the directory must be empty. +// +// Note: If power loss occurs, it is possible that the file or directory +// will exist in both the oldpath and newpath simultaneously after the +// next mount. +// +// Returns a negative error code on failure. +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); + +// Find info about a file or directory +// +// Fills out the info structure, based on the specified file or directory. +// Returns a negative error code on failure. +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); + + +/// File operations /// + +// Open a file +// +// The mode that the file is opened in is determined +// by the flags, which are values from the enum lfs_open_flags +// that are bitwise-ored together. +// +// Returns a negative error code on failure. +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags); + +// Close a file +// +// Any pending writes are written out to storage as though +// sync had been called and releases any allocated resources. +// +// Returns a negative error code on failure. +int lfs_file_close(lfs_t *lfs, lfs_file_t *file); + +// Synchronize a file on storage +// +// Any pending writes are written out to storage. +// Returns a negative error code on failure. +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); + +// Read data from file +// +// Takes a buffer and size indicating where to store the read data. +// Returns the number of bytes read, or a negative error code on failure. +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size); + +// Write data to file +// +// Takes a buffer and size indicating the data to write. The file will not +// actually be updated on the storage until either sync or close is called. +// +// Returns the number of bytes written, or a negative error code on failure. +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size); + +// Change the position of the file +// +// The change in position is determined by the offset and whence flag. +// Returns the old position of the file, or a negative error code on failure. +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence); + +// Return the position of the file +// +// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) +// Returns the position of the file, or a negative error code on failure. +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); + +// Change the position of the file to the beginning of the file +// +// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) +// Returns a negative error code on failure. +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); + +// Return the size of the file +// +// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END) +// Returns the size of the file, or a negative error code on failure. +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); + + +/// Directory operations /// + +// Create a directory +// +// Returns a negative error code on failure. +int lfs_mkdir(lfs_t *lfs, const char *path); + +// Open a directory +// +// Once open a directory can be used with read to iterate over files. +// Returns a negative error code on failure. +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path); + +// Close a directory +// +// Releases any allocated resources. +// Returns a negative error code on failure. +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir); + +// Read an entry in the directory +// +// Fills out the info structure, based on the specified file or directory. +// Returns a negative error code on failure. +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info); + +// Change the position of the directory +// +// The new off must be a value previous returned from tell and specifies +// an absolute offset in the directory seek. +// +// Returns a negative error code on failure. +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off); + +// Return the position of the directory +// +// The returned offset is only meant to be consumed by seek and may not make +// sense, but does indicate the current position in the directory iteration. +// +// Returns the position of the directory, or a negative error code on failure. +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir); + +// Change the position of the directory to the beginning of the directory +// +// Returns a negative error code on failure. +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); + + +/// Miscellaneous littlefs specific operations /// + +// Traverse through all blocks in use by the filesystem +// +// The provided callback will be called with each block address that is +// currently in use by the filesystem. This can be used to determine which +// blocks are in use or how much of the storage is available. +// +// Returns a negative error code on failure. +int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); + +// Prunes any recoverable errors that may have occured in the filesystem +// +// Not needed to be called by user unless an operation is interrupted +// but the filesystem is still mounted. This is already called on first +// allocation. +// +// Returns a negative error code on failure. +int lfs_deorphan(lfs_t *lfs); + + +#endif diff --git a/lfs_util.c b/lfs_util.c new file mode 100644 index 0000000000..0a7234c3a5 --- /dev/null +++ b/lfs_util.c @@ -0,0 +1,25 @@ +/* + * lfs util functions + * + * Copyright (c) 2017 Christopher Haster + * Distributed under the Apache 2.0 license + */ +#include "lfs_util.h" + + +void lfs_crc(uint32_t *restrict crc, const void *buffer, size_t size) { + static const uint32_t rtable[16] = { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, + }; + + const uint8_t *data = buffer; + + for (size_t i = 0; i < size; i++) { + *crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 0)) & 0xf]; + *crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 4)) & 0xf]; + } +} + diff --git a/lfs_util.h b/lfs_util.h new file mode 100644 index 0000000000..95706d1c1d --- /dev/null +++ b/lfs_util.h @@ -0,0 +1,45 @@ +/* + * lfs utility functions + * + * Copyright (c) 2017 Christopher Haster + * Distributed under the Apache 2.0 license + */ +#ifndef LFS_UTIL_H +#define LFS_UTIL_H + +#include +#include +#include + + +// Builtin functions +static inline uint32_t lfs_max(uint32_t a, uint32_t b) { + return (a > b) ? a : b; +} + +static inline uint32_t lfs_min(uint32_t a, uint32_t b) { + return (a < b) ? a : b; +} + +static inline uint32_t lfs_ctz(uint32_t a) { + return __builtin_ctz(a); +} + +static inline uint32_t lfs_npw2(uint32_t a) { + return 32 - __builtin_clz(a-1); +} + +static inline int lfs_scmp(uint32_t a, uint32_t b) { + return (int)(unsigned)(a - b); +} + +void lfs_crc(uint32_t *crc, const void *buffer, size_t size); + + +// Logging functions +#define LFS_DEBUG(fmt, ...) printf("lfs debug: " fmt "\n", __VA_ARGS__) +#define LFS_WARN(fmt, ...) printf("lfs warn: " fmt "\n", __VA_ARGS__) +#define LFS_ERROR(fmt, ...) printf("lfs error: " fmt "\n", __VA_ARGS__) + + +#endif diff --git a/tests/stats.py b/tests/stats.py new file mode 100755 index 0000000000..2ba1fb654c --- /dev/null +++ b/tests/stats.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +import struct +import sys +import time +import os +import re + +def main(): + with open('blocks/config') as file: + s = struct.unpack(' +#include +#include + + +// test stuff +void test_log(const char *s, uintmax_t v) {{ + printf("%s: %jd\n", s, v); +}} + +void test_assert(const char *file, unsigned line, + const char *s, uintmax_t v, uintmax_t e) {{ + static const char *last[6] = {{0, 0}}; + if (v != e || !(last[0] == s || last[1] == s || + last[2] == s || last[3] == s || + last[4] == s || last[5] == s)) {{ + test_log(s, v); + last[0] = last[1]; + last[1] = last[2]; + last[2] = last[3]; + last[3] = last[4]; + last[4] = last[5]; + last[5] = s; + }} + + if (v != e) {{ + printf("\033[31m%s:%u: assert %s failed, expected %jd\033[0m\n", + file, line, s, e); + exit(-2); + }} +}} + +#define test_assert(s, v, e) test_assert(__FILE__, __LINE__, s, v, e) + + +// utility functions for traversals +int test_count(void *p, lfs_block_t b) {{ + unsigned *u = (unsigned*)p; + *u += 1; + return 0; +}} + + +// lfs declarations +lfs_t lfs; +lfs_emubd_t bd; +lfs_file_t file[4]; +lfs_dir_t dir[4]; +struct lfs_info info; + +uint8_t buffer[1024]; +uint8_t wbuffer[1024]; +uint8_t rbuffer[1024]; +lfs_size_t size; +lfs_size_t wsize; +lfs_size_t rsize; + +uintmax_t res; + +#ifndef LFS_READ_SIZE +#define LFS_READ_SIZE 16 +#endif + +#ifndef LFS_PROG_SIZE +#define LFS_PROG_SIZE 16 +#endif + +#ifndef LFS_BLOCK_SIZE +#define LFS_BLOCK_SIZE 512 +#endif + +#ifndef LFS_BLOCK_COUNT +#define LFS_BLOCK_COUNT 1024 +#endif + +#ifndef LFS_LOOKAHEAD +#define LFS_LOOKAHEAD 128 +#endif + +const struct lfs_config cfg = {{ + .context = &bd, + .read = &lfs_emubd_read, + .prog = &lfs_emubd_prog, + .erase = &lfs_emubd_erase, + .sync = &lfs_emubd_sync, + + .read_size = LFS_READ_SIZE, + .prog_size = LFS_PROG_SIZE, + .block_size = LFS_BLOCK_SIZE, + .block_count = LFS_BLOCK_COUNT, + .lookahead = LFS_LOOKAHEAD, +}}; + + +// Entry point +int main() {{ + lfs_emubd_create(&cfg, "blocks"); + +{tests} + + lfs_emubd_destroy(&cfg); +}} diff --git a/tests/test.py b/tests/test.py new file mode 100755 index 0000000000..aa125c7c2f --- /dev/null +++ b/tests/test.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +import re +import sys +import subprocess +import os + +def generate(test): + with open("tests/template.fmt") as file: + template = file.read() + + lines = [] + for line in re.split('(?<=[;{}])\n', test.read()): + match = re.match('(?: *\n)*( *)(.*)=>(.*);', line, re.DOTALL | re.MULTILINE) + if match: + tab, test, expect = match.groups() + lines.append(tab+'res = {test};'.format(test=test.strip())) + lines.append(tab+'test_assert("{name}", res, {expect});'.format( + name = re.match('\w*', test.strip()).group(), + expect = expect.strip())) + else: + lines.append(line) + + with open('test.c', 'w') as file: + file.write(template.format(tests='\n'.join(lines))) + +def compile(): + os.environ['CFLAGS'] = os.environ.get('CFLAGS', '') + ' -Werror' + subprocess.check_call(['make', '--no-print-directory', '-s'], env=os.environ) + +def execute(): + subprocess.check_call(["./lfs"]) + +def main(test=None): + if test and not test.startswith('-'): + with open(test) as file: + generate(file) + else: + generate(sys.stdin) + + compile() + + if test == '-s': + sys.exit(1) + + execute() + +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/tests/test_alloc.sh b/tests/test_alloc.sh new file mode 100755 index 0000000000..630be2a1bc --- /dev/null +++ b/tests/test_alloc.sh @@ -0,0 +1,261 @@ +#!/bin/bash +set -eu + +echo "=== Allocator tests ===" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; +TEST + +SIZE=15000 + +lfs_mkdir() { +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "$1") => 0; + lfs_unmount(&lfs) => 0; +TEST +} + +lfs_remove() { +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "$1/eggs") => 0; + lfs_remove(&lfs, "$1/bacon") => 0; + lfs_remove(&lfs, "$1/pancakes") => 0; + lfs_remove(&lfs, "$1") => 0; + lfs_unmount(&lfs) => 0; +TEST +} + +lfs_alloc_singleproc() { +tests/test.py << TEST + const char *names[] = {"bacon", "eggs", "pancakes"}; + lfs_mount(&lfs, &cfg) => 0; + for (int n = 0; n < sizeof(names)/sizeof(names[0]); n++) { + sprintf((char*)buffer, "$1/%s", names[n]); + lfs_file_open(&lfs, &file[n], (char*)buffer, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; + } + for (int n = 0; n < sizeof(names)/sizeof(names[0]); n++) { + size = strlen(names[n]); + for (int i = 0; i < $SIZE; i++) { + lfs_file_write(&lfs, &file[n], names[n], size) => size; + } + } + for (int n = 0; n < sizeof(names)/sizeof(names[0]); n++) { + lfs_file_close(&lfs, &file[n]) => 0; + } + lfs_unmount(&lfs) => 0; +TEST +} + +lfs_alloc_multiproc() { +for name in bacon eggs pancakes +do +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "$1/$name", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; + size = strlen("$name"); + memcpy(buffer, "$name", size); + for (int i = 0; i < $SIZE; i++) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST +done +} + +lfs_verify() { +for name in bacon eggs pancakes +do +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "$1/$name", LFS_O_RDONLY) => 0; + size = strlen("$name"); + for (int i = 0; i < $SIZE; i++) { + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "$name", size) => 0; + } + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST +done +} + +echo "--- Single-process allocation test ---" +lfs_mkdir singleproc +lfs_alloc_singleproc singleproc +lfs_verify singleproc + +echo "--- Multi-process allocation test ---" +lfs_mkdir multiproc +lfs_alloc_multiproc multiproc +lfs_verify multiproc +lfs_verify singleproc + +echo "--- Single-process reuse test ---" +lfs_remove singleproc +lfs_mkdir singleprocreuse +lfs_alloc_singleproc singleprocreuse +lfs_verify singleprocreuse +lfs_verify multiproc + +echo "--- Multi-process reuse test ---" +lfs_remove multiproc +lfs_mkdir multiprocreuse +lfs_alloc_singleproc multiprocreuse +lfs_verify multiprocreuse +lfs_verify singleprocreuse + +echo "--- Cleanup ---" +lfs_remove multiprocreuse +lfs_remove singleprocreuse + +echo "--- Exhaustion test ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); + size = strlen("exhaustion"); + memcpy(buffer, "exhaustion", size); + lfs_file_write(&lfs, &file[0], buffer, size) => size; + + size = strlen("blahblahblahblah"); + memcpy(buffer, "blahblahblahblah", size); + lfs_ssize_t res; + while (true) { + res = lfs_file_write(&lfs, &file[0], buffer, size); + if (res < 0) { + break; + } + + res => size; + } + res => LFS_ERR_NOSPC; + + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_RDONLY); + size = strlen("exhaustion"); + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "exhaustion", size) => 0; + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Exhaustion wraparound test ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "exhaustion") => 0; + + lfs_file_open(&lfs, &file[0], "padding", LFS_O_WRONLY | LFS_O_CREAT); + size = strlen("buffering"); + memcpy(buffer, "buffering", size); + for (int i = 0; i < $SIZE; i++) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } + lfs_file_close(&lfs, &file[0]) => 0; + lfs_remove(&lfs, "padding") => 0; + + lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); + size = strlen("exhaustion"); + memcpy(buffer, "exhaustion", size); + lfs_file_write(&lfs, &file[0], buffer, size) => size; + + size = strlen("blahblahblahblah"); + memcpy(buffer, "blahblahblahblah", size); + lfs_ssize_t res; + while (true) { + res = lfs_file_write(&lfs, &file[0], buffer, size); + if (res < 0) { + break; + } + + res => size; + } + res => LFS_ERR_NOSPC; + + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_RDONLY); + size = strlen("exhaustion"); + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "exhaustion", size) => 0; + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Dir exhaustion test ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "exhaustion", &info) => 0; + lfs_size_t fullsize = info.size; + lfs_remove(&lfs, "exhaustion") => 0; + + lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); + size = strlen("blahblahblahblah"); + memcpy(buffer, "blahblahblahblah", size); + for (lfs_size_t i = 0; i < fullsize - 2*512; i += size) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } + lfs_file_close(&lfs, &file[0]) => 0; + + lfs_mkdir(&lfs, "exhaustiondir") => 0; + lfs_remove(&lfs, "exhaustiondir") => 0; + + lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_APPEND); + size = strlen("blahblahblahblah"); + memcpy(buffer, "blahblahblahblah", size); + lfs_file_write(&lfs, &file[0], buffer, size) => size; + lfs_file_close(&lfs, &file[0]) => 0; + + lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Chained dir exhaustion test ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "exhaustion", &info) => 0; + lfs_size_t fullsize = info.size; + + lfs_remove(&lfs, "exhaustion") => 0; + lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); + size = strlen("blahblahblahblah"); + memcpy(buffer, "blahblahblahblah", size); + for (lfs_size_t i = 0; i < fullsize - 19*512; i += size) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } + lfs_file_close(&lfs, &file[0]) => 0; + + for (int i = 0; i < 9; i++) { + sprintf((char*)buffer, "dirwithanexhaustivelylongnameforpadding%d", i); + lfs_mkdir(&lfs, (char*)buffer) => 0; + } + + lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC; + + lfs_remove(&lfs, "exhaustion") => 0; + lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); + size = strlen("blahblahblahblah"); + memcpy(buffer, "blahblahblahblah", size); + for (lfs_size_t i = 0; i < fullsize - 20*512; i += size) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } + lfs_file_close(&lfs, &file[0]) => 0; + + lfs_mkdir(&lfs, "exhaustiondir") => 0; + lfs_mkdir(&lfs, "exhaustiondir2") => LFS_ERR_NOSPC; +TEST + + +echo "--- Results ---" +tests/stats.py diff --git a/tests/test_corrupt.sh b/tests/test_corrupt.sh new file mode 100755 index 0000000000..d79a8c8964 --- /dev/null +++ b/tests/test_corrupt.sh @@ -0,0 +1,106 @@ +#!/bin/bash +set -eu + +echo "=== Corrupt tests ===" + +NAMEMULT=64 +FILEMULT=1 + +lfs_mktree() { +tests/test.py ${1:-} << TEST + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + for (int i = 1; i < 10; i++) { + for (int j = 0; j < $NAMEMULT; j++) { + buffer[j] = '0'+i; + } + buffer[$NAMEMULT] = '\0'; + lfs_mkdir(&lfs, (char*)buffer) => 0; + + buffer[$NAMEMULT] = '/'; + for (int j = 0; j < $NAMEMULT; j++) { + buffer[j+$NAMEMULT+1] = '0'+i; + } + buffer[2*$NAMEMULT+1] = '\0'; + lfs_file_open(&lfs, &file[0], (char*)buffer, + LFS_O_WRONLY | LFS_O_CREAT) => 0; + + size = $NAMEMULT; + for (int j = 0; j < i*$FILEMULT; j++) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } + + lfs_file_close(&lfs, &file[0]) => 0; + } + lfs_unmount(&lfs) => 0; +TEST +} + +lfs_chktree() { +tests/test.py ${1:-} << TEST + lfs_mount(&lfs, &cfg) => 0; + for (int i = 1; i < 10; i++) { + for (int j = 0; j < $NAMEMULT; j++) { + buffer[j] = '0'+i; + } + buffer[$NAMEMULT] = '\0'; + lfs_stat(&lfs, (char*)buffer, &info) => 0; + info.type => LFS_TYPE_DIR; + + buffer[$NAMEMULT] = '/'; + for (int j = 0; j < $NAMEMULT; j++) { + buffer[j+$NAMEMULT+1] = '0'+i; + } + buffer[2*$NAMEMULT+1] = '\0'; + lfs_file_open(&lfs, &file[0], (char*)buffer, LFS_O_RDONLY) => 0; + + size = $NAMEMULT; + for (int j = 0; j < i*$FILEMULT; j++) { + lfs_file_read(&lfs, &file[0], rbuffer, size) => size; + memcmp(buffer, rbuffer, size) => 0; + } + + lfs_file_close(&lfs, &file[0]) => 0; + } + lfs_unmount(&lfs) => 0; +TEST +} + +echo "--- Sanity check ---" +rm -rf blocks +lfs_mktree +lfs_chktree + +echo "--- Block corruption ---" +for i in {0..33} +do + rm -rf blocks + mkdir blocks + ln -s /dev/zero blocks/$(printf '%x' $i) + lfs_mktree + lfs_chktree +done + +echo "--- Big region corruption ---" +rm -rf blocks +mkdir blocks +for i in {2..255} +do + ln -s /dev/zero blocks/$(printf '%x' $i) +done +lfs_mktree +lfs_chktree + +echo "--- Alternating corruption ---" +rm -rf blocks +mkdir blocks +for i in {2..511..2} +do + ln -s /dev/zero blocks/$(printf '%x' $i) +done +lfs_mktree +lfs_chktree + +echo "--- Results ---" +tests/stats.py diff --git a/tests/test_dirs.sh b/tests/test_dirs.sh new file mode 100755 index 0000000000..815b88bd22 --- /dev/null +++ b/tests/test_dirs.sh @@ -0,0 +1,287 @@ +#!/bin/bash +set -eu + +LARGESIZE=128 + +echo "=== Directory tests ===" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; +TEST + +echo "--- Root directory ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "/") => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Directory creation ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "potato") => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- File creation ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "burito", LFS_O_CREAT | LFS_O_WRONLY) => 0; + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Directory iteration ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "/") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "potato") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "burito") => 0; + info.type => LFS_TYPE_REG; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Directory failures ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "potato") => LFS_ERR_EXISTS; + lfs_dir_open(&lfs, &dir[0], "tomato") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir[0], "burito") => LFS_ERR_NOTDIR; + lfs_file_open(&lfs, &file[0], "tomato", LFS_O_RDONLY) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file[0], "potato", LFS_O_RDONLY) => LFS_ERR_ISDIR; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Nested directories ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "potato/baked") => 0; + lfs_mkdir(&lfs, "potato/sweet") => 0; + lfs_mkdir(&lfs, "potato/fried") => 0; + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "potato") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "baked") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "sweet") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "fried") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Multi-block directory ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "cactus") => 0; + for (int i = 0; i < $LARGESIZE; i++) { + sprintf((char*)buffer, "cactus/test%d", i); + lfs_mkdir(&lfs, (char*)buffer) => 0; + } + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "cactus") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + info.type => LFS_TYPE_DIR; + for (int i = 0; i < $LARGESIZE; i++) { + sprintf((char*)buffer, "test%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + } + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Directory remove ---" +# TESTING HERE +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "potato") => LFS_ERR_INVAL; + lfs_remove(&lfs, "potato/sweet") => 0; + lfs_remove(&lfs, "potato/baked") => 0; + lfs_remove(&lfs, "potato/fried") => 0; + + lfs_dir_open(&lfs, &dir[0], "potato") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + + lfs_remove(&lfs, "potato") => 0; + + lfs_dir_open(&lfs, &dir[0], "/") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "burito") => 0; + info.type => LFS_TYPE_REG; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "cactus") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "/") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "burito") => 0; + info.type => LFS_TYPE_REG; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "cactus") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Directory rename ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "coldpotato") => 0; + lfs_mkdir(&lfs, "coldpotato/baked") => 0; + lfs_mkdir(&lfs, "coldpotato/sweet") => 0; + lfs_mkdir(&lfs, "coldpotato/fried") => 0; + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "coldpotato", "hotpotato") => 0; + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "hotpotato") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "baked") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "sweet") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "fried") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "warmpotato") => 0; + lfs_mkdir(&lfs, "warmpotato/mushy") => 0; + lfs_rename(&lfs, "hotpotato", "warmpotato") => LFS_ERR_INVAL; + + lfs_remove(&lfs, "warmpotato/mushy") => 0; + lfs_rename(&lfs, "hotpotato", "warmpotato") => 0; + + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "warmpotato") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "baked") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "sweet") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "fried") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "coldpotato") => 0; + lfs_rename(&lfs, "warmpotato/baked", "coldpotato/baked") => 0; + lfs_rename(&lfs, "warmpotato/sweet", "coldpotato/sweet") => 0; + lfs_rename(&lfs, "warmpotato/fried", "coldpotato/fried") => 0; + lfs_remove(&lfs, "coldpotato") => LFS_ERR_INVAL; + lfs_remove(&lfs, "warmpotato") => 0; + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "coldpotato") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "baked") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "sweet") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "fried") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Results ---" +tests/stats.py diff --git a/tests/test_files.sh b/tests/test_files.sh new file mode 100755 index 0000000000..6086107c89 --- /dev/null +++ b/tests/test_files.sh @@ -0,0 +1,114 @@ +#!/bin/bash +set -eu + +SMALLSIZE=32 +MEDIUMSIZE=8192 +LARGESIZE=262144 + +echo "=== File tests ===" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; +TEST + +echo "--- Simple file test ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "hello", LFS_O_WRONLY | LFS_O_CREAT) => 0; + size = strlen("Hello World!\n"); + memcpy(wbuffer, "Hello World!\n", size); + lfs_file_write(&lfs, &file[0], wbuffer, size) => size; + lfs_file_close(&lfs, &file[0]) => 0; + + lfs_file_open(&lfs, &file[0], "hello", LFS_O_RDONLY) => 0; + size = strlen("Hello World!\n"); + lfs_file_read(&lfs, &file[0], rbuffer, size) => size; + memcmp(rbuffer, wbuffer, size) => 0; + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +w_test() { +tests/test.py << TEST + lfs_size_t size = $1; + lfs_size_t chunk = 31; + srand(0); + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "$2", LFS_O_WRONLY | LFS_O_CREAT) => 0; + for (lfs_size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + for (lfs_size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + lfs_file_write(&lfs, &file[0], buffer, chunk) => chunk; + } + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST +} + +r_test() { +tests/test.py << TEST + lfs_size_t size = $1; + lfs_size_t chunk = 29; + srand(0); + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "$2", LFS_O_RDONLY) => 0; + for (lfs_size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + lfs_file_read(&lfs, &file[0], buffer, chunk) => chunk; + for (lfs_size_t b = 0; b < chunk && i+b < size; b++) { + buffer[b] => rand() & 0xff; + } + } + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST +} + +echo "--- Small file test ---" +w_test $SMALLSIZE smallavacado +r_test $SMALLSIZE smallavacado + +echo "--- Medium file test ---" +w_test $MEDIUMSIZE mediumavacado +r_test $MEDIUMSIZE mediumavacado + +echo "--- Large file test ---" +w_test $LARGESIZE largeavacado +r_test $LARGESIZE largeavacado + +echo "--- Non-overlap check ---" +r_test $SMALLSIZE smallavacado +r_test $MEDIUMSIZE mediumavacado +r_test $LARGESIZE largeavacado + +echo "--- Dir check ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "/") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "hello") => 0; + info.type => LFS_TYPE_REG; + info.size => strlen("Hello World!\n"); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "smallavacado") => 0; + info.type => LFS_TYPE_REG; + info.size => $SMALLSIZE; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "mediumavacado") => 0; + info.type => LFS_TYPE_REG; + info.size => $MEDIUMSIZE; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "largeavacado") => 0; + info.type => LFS_TYPE_REG; + info.size => $LARGESIZE; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Results ---" +tests/stats.py diff --git a/tests/test_format.sh b/tests/test_format.sh new file mode 100755 index 0000000000..b9071015d6 --- /dev/null +++ b/tests/test_format.sh @@ -0,0 +1,49 @@ +#!/bin/bash +set -eu + +echo "=== Formatting tests ===" +rm -rf blocks + +echo "--- Basic formatting ---" +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; +TEST + +echo "--- Invalid superblocks ---" +ln -f -s /dev/zero blocks/0 +ln -f -s /dev/zero blocks/1 +tests/test.py << TEST + lfs_format(&lfs, &cfg) => LFS_ERR_CORRUPT; +TEST +rm blocks/0 blocks/1 + +echo "--- Basic mounting ---" +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Invalid mount ---" +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; +TEST +rm blocks/0 blocks/1 +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT; +TEST + +echo "--- Valid corrupt mount ---" +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; +TEST +rm blocks/0 +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Results ---" +tests/stats.py diff --git a/tests/test_orphan.sh b/tests/test_orphan.sh new file mode 100755 index 0000000000..71d6d4fc09 --- /dev/null +++ b/tests/test_orphan.sh @@ -0,0 +1,41 @@ +#!/bin/bash +set -eu + +echo "=== Orphan tests ===" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; +TEST + +echo "--- Orphan test ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "parent") => 0; + lfs_mkdir(&lfs, "parent/orphan") => 0; + lfs_mkdir(&lfs, "parent/child") => 0; + lfs_remove(&lfs, "parent/orphan") => 0; +TEST +# remove most recent file, this should be the update to the previous +# linked-list entry and should orphan the child +rm -v blocks/8 +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT; + unsigned before = 0; + lfs_traverse(&lfs, test_count, &before) => 0; + test_log("before", before); + + lfs_deorphan(&lfs) => 0; + + lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT; + unsigned after = 0; + lfs_traverse(&lfs, test_count, &after) => 0; + test_log("after", after); + + int diff = before - after; + diff => 2; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Results ---" +tests/stats.py diff --git a/tests/test_parallel.sh b/tests/test_parallel.sh new file mode 100755 index 0000000000..71c9c1f3b9 --- /dev/null +++ b/tests/test_parallel.sh @@ -0,0 +1,186 @@ +#!/bin/bash +set -eu + +echo "=== Parallel tests ===" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; +TEST + +echo "--- Parallel file test ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "a", LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_open(&lfs, &file[1], "b", LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_open(&lfs, &file[2], "c", LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_open(&lfs, &file[3], "d", LFS_O_WRONLY | LFS_O_CREAT) => 0; + + for (int i = 0; i < 10; i++) { + lfs_file_write(&lfs, &file[0], (const void*)"a", 1) => 1; + lfs_file_write(&lfs, &file[1], (const void*)"b", 1) => 1; + lfs_file_write(&lfs, &file[2], (const void*)"c", 1) => 1; + lfs_file_write(&lfs, &file[3], (const void*)"d", 1) => 1; + } + + lfs_file_close(&lfs, &file[0]); + lfs_file_close(&lfs, &file[1]); + lfs_file_close(&lfs, &file[2]); + lfs_file_close(&lfs, &file[3]); + + lfs_dir_open(&lfs, &dir[0], "/") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "a") => 0; + info.type => LFS_TYPE_REG; + info.size => 10; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "b") => 0; + info.type => LFS_TYPE_REG; + info.size => 10; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "c") => 0; + info.type => LFS_TYPE_REG; + info.size => 10; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "d") => 0; + info.type => LFS_TYPE_REG; + info.size => 10; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + + lfs_file_open(&lfs, &file[0], "a", LFS_O_RDONLY) => 0; + lfs_file_open(&lfs, &file[1], "b", LFS_O_RDONLY) => 0; + lfs_file_open(&lfs, &file[2], "c", LFS_O_RDONLY) => 0; + lfs_file_open(&lfs, &file[3], "d", LFS_O_RDONLY) => 0; + + for (int i = 0; i < 10; i++) { + lfs_file_read(&lfs, &file[0], buffer, 1) => 1; + buffer[0] => 'a'; + lfs_file_read(&lfs, &file[1], buffer, 1) => 1; + buffer[0] => 'b'; + lfs_file_read(&lfs, &file[2], buffer, 1) => 1; + buffer[0] => 'c'; + lfs_file_read(&lfs, &file[3], buffer, 1) => 1; + buffer[0] => 'd'; + } + + lfs_file_close(&lfs, &file[0]); + lfs_file_close(&lfs, &file[1]); + lfs_file_close(&lfs, &file[2]); + lfs_file_close(&lfs, &file[3]); + + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Parallel remove file test ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "e", LFS_O_WRONLY | LFS_O_CREAT) => 0; + + for (int i = 0; i < 5; i++) { + lfs_file_write(&lfs, &file[0], (const void*)"e", 1) => 1; + } + + lfs_remove(&lfs, "a") => 0; + lfs_remove(&lfs, "b") => 0; + lfs_remove(&lfs, "c") => 0; + lfs_remove(&lfs, "d") => 0; + + for (int i = 0; i < 5; i++) { + lfs_file_write(&lfs, &file[0], (const void*)"e", 1) => 1; + } + + lfs_file_close(&lfs, &file[0]); + + lfs_dir_open(&lfs, &dir[0], "/") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "e") => 0; + info.type => LFS_TYPE_REG; + info.size => 10; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + + lfs_file_open(&lfs, &file[0], "e", LFS_O_RDONLY) => 0; + + for (int i = 0; i < 10; i++) { + lfs_file_read(&lfs, &file[0], buffer, 1) => 1; + buffer[0] => 'e'; + } + + lfs_file_close(&lfs, &file[0]); + + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Remove inconveniently test ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "e", LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_open(&lfs, &file[1], "f", LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_open(&lfs, &file[2], "g", LFS_O_WRONLY | LFS_O_CREAT) => 0; + + for (int i = 0; i < 5; i++) { + lfs_file_write(&lfs, &file[0], (const void*)"e", 1) => 1; + lfs_file_write(&lfs, &file[1], (const void*)"f", 1) => 1; + lfs_file_write(&lfs, &file[2], (const void*)"g", 1) => 1; + } + + lfs_remove(&lfs, "f") => 0; + + for (int i = 0; i < 5; i++) { + lfs_file_write(&lfs, &file[0], (const void*)"e", 1) => 1; + lfs_file_write(&lfs, &file[1], (const void*)"f", 1) => 1; + lfs_file_write(&lfs, &file[2], (const void*)"g", 1) => 1; + } + + lfs_file_close(&lfs, &file[0]); + lfs_file_close(&lfs, &file[1]); + lfs_file_close(&lfs, &file[2]); + + lfs_dir_open(&lfs, &dir[0], "/") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "e") => 0; + info.type => LFS_TYPE_REG; + info.size => 10; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "g") => 0; + info.type => LFS_TYPE_REG; + info.size => 10; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + + lfs_file_open(&lfs, &file[0], "e", LFS_O_RDONLY) => 0; + lfs_file_open(&lfs, &file[1], "g", LFS_O_RDONLY) => 0; + + for (int i = 0; i < 10; i++) { + lfs_file_read(&lfs, &file[0], buffer, 1) => 1; + buffer[0] => 'e'; + lfs_file_read(&lfs, &file[1], buffer, 1) => 1; + buffer[0] => 'g'; + } + + lfs_file_close(&lfs, &file[0]); + lfs_file_close(&lfs, &file[1]); + + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Results ---" +tests/stats.py diff --git a/tests/test_paths.sh b/tests/test_paths.sh new file mode 100755 index 0000000000..769f37fcea --- /dev/null +++ b/tests/test_paths.sh @@ -0,0 +1,86 @@ +#!/bin/bash +set -eu + +echo "=== Path tests ===" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; +TEST + +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "tea") => 0; + lfs_mkdir(&lfs, "coffee") => 0; + lfs_mkdir(&lfs, "soda") => 0; + lfs_mkdir(&lfs, "tea/hottea") => 0; + lfs_mkdir(&lfs, "tea/warmtea") => 0; + lfs_mkdir(&lfs, "tea/coldtea") => 0; + lfs_mkdir(&lfs, "coffee/hotcoffee") => 0; + lfs_mkdir(&lfs, "coffee/warmcoffee") => 0; + lfs_mkdir(&lfs, "coffee/coldcoffee") => 0; + lfs_mkdir(&lfs, "soda/hotsoda") => 0; + lfs_mkdir(&lfs, "soda/warmsoda") => 0; + lfs_mkdir(&lfs, "soda/coldsoda") => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Root path tests ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "tea/hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + lfs_stat(&lfs, "/tea/hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Redundant slash path tests ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "/tea/hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + lfs_stat(&lfs, "//tea//hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + lfs_stat(&lfs, "///tea///hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Dot path tests ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "./tea/hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + lfs_stat(&lfs, "/./tea/hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + lfs_stat(&lfs, "/././tea/hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + lfs_stat(&lfs, "/./tea/./hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Dot dot path tests ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "coffee/../tea/hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + lfs_stat(&lfs, "tea/coldtea/../hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + lfs_stat(&lfs, "coffee/coldcoffee/../../tea/hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + lfs_stat(&lfs, "coffee/../soda/../tea/hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Root dot dot path tests ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "coffee/../../../../../../tea/hottea", &info) => 0; + strcmp(info.name, "hottea") => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Results ---" +tests/stats.py diff --git a/tests/test_seek.sh b/tests/test_seek.sh new file mode 100755 index 0000000000..8c0093852e --- /dev/null +++ b/tests/test_seek.sh @@ -0,0 +1,281 @@ +#!/bin/bash +set -eu + +SMALLSIZE=4 +MEDIUMSIZE=128 +LARGESIZE=132 + +echo "=== Seek tests ===" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "hello") => 0; + for (int i = 0; i < $LARGESIZE; i++) { + sprintf((char*)buffer, "hello/kitty%d", i); + lfs_file_open(&lfs, &file[0], (char*)buffer, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0; + + size = strlen("kittycatcat"); + memcpy(buffer, "kittycatcat", size); + for (int j = 0; j < $LARGESIZE; j++) { + lfs_file_write(&lfs, &file[0], buffer, size); + } + + lfs_file_close(&lfs, &file[0]) => 0; + } + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Simple dir seek ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "hello") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + + lfs_soff_t pos; + int i; + for (i = 0; i < $SMALLSIZE; i++) { + sprintf((char*)buffer, "kitty%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + pos = lfs_dir_tell(&lfs, &dir[0]); + } + pos >= 0 => 1; + + lfs_dir_seek(&lfs, &dir[0], pos) => 0; + sprintf((char*)buffer, "kitty%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + + lfs_dir_rewind(&lfs, &dir[0]) => 0; + sprintf((char*)buffer, "kitty%d", 0); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + + lfs_dir_seek(&lfs, &dir[0], pos) => 0; + sprintf((char*)buffer, "kitty%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Large dir seek ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "hello") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + + lfs_soff_t pos; + int i; + for (i = 0; i < $MEDIUMSIZE; i++) { + sprintf((char*)buffer, "kitty%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + pos = lfs_dir_tell(&lfs, &dir[0]); + } + pos >= 0 => 1; + + lfs_dir_seek(&lfs, &dir[0], pos) => 0; + sprintf((char*)buffer, "kitty%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + + lfs_dir_rewind(&lfs, &dir[0]) => 0; + sprintf((char*)buffer, "kitty%d", 0); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + + lfs_dir_seek(&lfs, &dir[0], pos) => 0; + sprintf((char*)buffer, "kitty%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Simple file seek ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDONLY) => 0; + + lfs_soff_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < $SMALLSIZE; i++) { + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + pos = lfs_file_tell(&lfs, &file[0]); + } + pos >= 0 => 1; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_rewind(&lfs, &file[0]) => 0; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos+size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_size_t size = lfs_file_size(&lfs, &file[0]); + lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size; + + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Large file seek ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDONLY) => 0; + + lfs_soff_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < $MEDIUMSIZE; i++) { + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + pos = lfs_file_tell(&lfs, &file[0]); + } + pos >= 0 => 1; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_rewind(&lfs, &file[0]) => 0; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos+size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_size_t size = lfs_file_size(&lfs, &file[0]); + lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size; + + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Simple file seek and write ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0; + + lfs_soff_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < $SMALLSIZE; i++) { + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + pos = lfs_file_tell(&lfs, &file[0]); + } + pos >= 0 => 1; + + memcpy(buffer, "doggodogdog", size); + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; + lfs_file_write(&lfs, &file[0], buffer, size) => size; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos+size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "doggodogdog", size) => 0; + + lfs_file_rewind(&lfs, &file[0]) => 0; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "doggodogdog", size) => 0; + + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_size_t size = lfs_file_size(&lfs, &file[0]); + lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size; + + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Large file seek and write ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0; + + lfs_soff_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < $MEDIUMSIZE; i++) { + lfs_file_read(&lfs, &file[0], buffer, size) => size; + if (i != $SMALLSIZE) { + memcmp(buffer, "kittycatcat", size) => 0; + } + pos = lfs_file_tell(&lfs, &file[0]); + } + pos >= 0 => 1; + + memcpy(buffer, "doggodogdog", size); + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; + lfs_file_write(&lfs, &file[0], buffer, size) => size; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos+size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "doggodogdog", size) => 0; + + lfs_file_rewind(&lfs, &file[0]) => 0; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "doggodogdog", size) => 0; + + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_size_t size = lfs_file_size(&lfs, &file[0]); + lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size; + + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Results ---" +tests/stats.py From c5868790bc320bd7d73c59c1a64d32008d10aed8 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 12 Jul 2017 06:46:14 -0500 Subject: [PATCH 03/46] Tweaked littlefs to fit into mbed - Changed log statements to use the debug function - Changed %d to %ld given the type of int32_t in arm-none-eabi-gcc. In mainstream gcc this is not the case and may cause problems to upstream. --- .mbedignore | 2 ++ littlefs/lfs.c | 24 ++++++++++++------------ littlefs/lfs_util.h | 7 ++++--- 3 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 .mbedignore diff --git a/.mbedignore b/.mbedignore new file mode 100644 index 0000000000..dc8a4d89d2 --- /dev/null +++ b/.mbedignore @@ -0,0 +1,2 @@ +littlefs/emubd/ +littlefs/tests/ diff --git a/littlefs/lfs.c b/littlefs/lfs.c index a2d94eec61..bd1f035149 100644 --- a/littlefs/lfs.c +++ b/littlefs/lfs.c @@ -283,7 +283,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { while (true) { // check if we have looked at all blocks since last ack if (lfs->free.start + lfs->free.off == lfs->free.end) { - LFS_WARN("No more free space %d", lfs->free.end); + LFS_WARN("No more free space %ld", lfs->free.end); return LFS_ERR_NOSPC; } @@ -415,7 +415,7 @@ static int lfs_dir_fetch(lfs_t *lfs, } if (!valid) { - LFS_ERROR("Corrupted dir pair at %d %d", tpair[0], tpair[1]); + LFS_ERROR("Corrupted dir pair at %ld %ld", tpair[0], tpair[1]); return LFS_ERR_CORRUPT; } @@ -527,7 +527,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir, relocate: //commit was corrupted - LFS_DEBUG("Bad block at %d", dir->pair[0]); + LFS_DEBUG("Bad block at %ld", dir->pair[0]); // drop caches and prepare to relocate block relocated = true; @@ -535,7 +535,7 @@ relocate: // can't relocate superblock, filesystem is now frozen if (lfs_paircmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) { - LFS_WARN("Superblock %d has become unwritable", oldpair[0]); + LFS_WARN("Superblock %ld has become unwritable", oldpair[0]); return LFS_ERR_CORRUPT; } @@ -548,7 +548,7 @@ relocate: if (relocated) { // update references if we relocated - LFS_DEBUG("Relocating %d %d to %d %d", + LFS_DEBUG("Relocating %ld %ld to %ld %ld", oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]); return lfs_relocate(lfs, oldpair, dir->pair); } @@ -1055,7 +1055,7 @@ static int lfs_index_extend(lfs_t *lfs, return 0; relocate: - LFS_DEBUG("Bad block at %d", *block); + LFS_DEBUG("Bad block at %ld", *block); // just clear cache and try a new block pcache->block = 0xffffffff; @@ -1189,7 +1189,7 @@ int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { relocate: - LFS_DEBUG("Bad block at %d", file->block); + LFS_DEBUG("Bad block at %ld", file->block); // just relocate what exists into new block lfs_block_t nblock; @@ -1874,12 +1874,12 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { if (err == LFS_ERR_CORRUPT || memcmp(superblock.d.magic, "littlefs", 8) != 0) { - LFS_ERROR("Invalid superblock at %d %d", dir.pair[0], dir.pair[1]); + LFS_ERROR("Invalid superblock at %ld %ld", dir.pair[0], dir.pair[1]); return LFS_ERR_CORRUPT; } if (superblock.d.version > (0x00010001 | 0x0000ffff)) { - LFS_ERROR("Invalid version %d.%d\n", + LFS_ERROR("Invalid version %ld.%ld\n", 0xffff & (superblock.d.version >> 16), 0xffff & (superblock.d.version >> 0)); return LFS_ERR_INVAL; @@ -2048,7 +2048,7 @@ static int lfs_relocate(lfs_t *lfs, // update internal root if (lfs_paircmp(oldpair, lfs->root) == 0) { - LFS_DEBUG("Relocating root %d %d", newpair[0], newpair[1]); + LFS_DEBUG("Relocating root %ld %ld", newpair[0], newpair[1]); lfs->root[0] = newpair[0]; lfs->root[1] = newpair[1]; } @@ -2109,7 +2109,7 @@ int lfs_deorphan(lfs_t *lfs) { if (!res) { // we are an orphan - LFS_DEBUG("Orphan %d %d", pdir.d.tail[0], pdir.d.tail[1]); + LFS_DEBUG("Orphan %ld %ld", pdir.d.tail[0], pdir.d.tail[1]); pdir.d.tail[0] = cdir.d.tail[0]; pdir.d.tail[1] = cdir.d.tail[1]; @@ -2124,7 +2124,7 @@ int lfs_deorphan(lfs_t *lfs) { if (!lfs_pairsync(entry.d.u.dir, pdir.d.tail)) { // we have desynced - LFS_DEBUG("Desync %d %d", entry.d.u.dir[0], entry.d.u.dir[1]); + LFS_DEBUG("Desync %ld %ld", entry.d.u.dir[0], entry.d.u.dir[1]); pdir.d.tail[0] = entry.d.u.dir[0]; pdir.d.tail[1] = entry.d.u.dir[1]; diff --git a/littlefs/lfs_util.h b/littlefs/lfs_util.h index 95706d1c1d..b3832214c4 100644 --- a/littlefs/lfs_util.h +++ b/littlefs/lfs_util.h @@ -10,6 +10,7 @@ #include #include #include +#include "mbed_debug.h" // Builtin functions @@ -37,9 +38,9 @@ void lfs_crc(uint32_t *crc, const void *buffer, size_t size); // Logging functions -#define LFS_DEBUG(fmt, ...) printf("lfs debug: " fmt "\n", __VA_ARGS__) -#define LFS_WARN(fmt, ...) printf("lfs warn: " fmt "\n", __VA_ARGS__) -#define LFS_ERROR(fmt, ...) printf("lfs error: " fmt "\n", __VA_ARGS__) +#define LFS_DEBUG(fmt, ...) debug("lfs debug: " fmt "\n", __VA_ARGS__) +#define LFS_WARN(fmt, ...) debug("lfs warn: " fmt "\n", __VA_ARGS__) +#define LFS_ERROR(fmt, ...) debug("lfs error: " fmt "\n", __VA_ARGS__) #endif From 953e19eef4b946505c1faa022541edef4690251b Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 12 Jul 2017 07:19:58 -0500 Subject: [PATCH 04/46] Added README and LICENSE --- LICENSE.md | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 97 +++++++++++++++++++++++++++++++ 2 files changed, 262 insertions(+) create mode 100644 LICENSE.md create mode 100644 README.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000..59cd3f8a32 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,165 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..7e682fb29f --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +## mbed wrapper for the [little filesystem](https://github.com/geky/littlefs) + +This is the mbed wrapper for [littlefs](https://github.com/geky/littlefs), +a little fail-safe filesystem designed for embedded systems. + +``` + | | | .---._____ + .-----. | | +--|o |---| littlefs | +--| |---| | + '-----' '----------' + | | | +``` + +The littlefs comes with the following features: + +- **Power-loss resilient** - The littlefs is designed to handle random power + failures and has strong copy-on-write guaruntees. During all filesystem + operations, the storage on disk is always kept in a valid state. When + updating a file, the original file will remain unmodified until the file is + closed, or sync is called. + +- **Error detection** - While the littlefs does not implement static wear + leveling, the littlefs detects write error and uses a limited form of dynamic + wear leveling to work around blocks that go bad during the lifetime of the + filesystem. + +- **Low RAM/ROM usage** - The littlefs is designed to work in a limited amount + of memory, recursion is avoided, and dynamic memory is limited to + configurable buffers that can be provided statically. + +The littlefs also comes with the following limitations: + +- **Not portable to host OSs** - While the littlefs can use an SD card, no + littlefs driver has been ported to host OSs such as Linux or Windows. If + you need to support host OSs consider using the [FATFileSystem](https://github.com/ARMmbed/mbed-os/blob/mbed-os-5.5/features/filesystem/fat/FATFileSystem.h) + found in mbed OS. + +- **No static wear leveling** - While the littlefs does extend the lifetime + of storage through a form of dynamic wear leveling, the littlefs does not + evict static blocks. If you need to maximize the longevity of the underlying + storage consider using the [SPIFFileSystem](https://github.com/armmbed/mbed-spiffs). + +## Usage + +If you are already using a filesystem in mbed, adopting the littlefs should +just require a name change to use the [LittleFileSystem](LittleFileSystem.h) +class. + +Here is a simple example that updates a file named "boot_count" every time +the application runs: +``` c++ +#include "LittleFileSystem.h" +#include "SPIFBlockDevice.h" + +// Physical block device, can be any device that supports the BlockDevice API +SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5); + +// Storage for the littlefs +LittleFileSystem fs("fs"); + +// Entry point +int main() { + // Mount the filesystem + int err = fs.mount(&bd); + if (err) { + // Reformat if we can't mount the filesystem, + // this should only happen on the first boot + LittleFileSystem::format(&bd); + fs.mount(&bd); + } + + // Read the boot count + uint32_t boot_count = 0; + FILE *f = fopen("/fs/boot_count", "r+"); + if (!f) { + // Create the file if it doesn't exist + f = fopen("/fs/boot_count", "w+"); + } + fread(&boot_count, sizeof(boot_count), 1, f); + + // Update the boot count + boot_count += 1; + rewind(f); + fwrite(&boot_count, sizeof(boot_count), 1, f); + + // Remember that storage may not be updated until the file + // is closed successfully + fclose(f); + + // Release any resources we were using + fs.unmount(); + + // Print the boot count + printf("boot_count: %ld\n", boot_count); +} +``` From 64ff4b60eb53500199fb0e9180447c41cb30c6c1 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 12 Jul 2017 07:49:47 -0500 Subject: [PATCH 05/46] Added support for Travis CI --- .travis.yml | 32 ++++++++++++++++++++++++++++++++ littlefs/lfs_util.h | 7 ++++++- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..5c816766c6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,32 @@ +python: + - "2.7" + +script: + # Check that example compiles + - PYTHONPATH=mbed-os python mbed-os/tools/make.py -t GCC_ARM -m K82F + --source=. --build=BUILD/K82F/GCC_ARM -j0 + + # Run local littlefs tests + - CFLAGS=" + -DLFS_READ_SIZE=64 + -DLFS_PROG_SIZE=64 + -Wno-error=format" + make -Clittlefs test + - CFLAGS=" + -DLFS_READ_SIZE=512 + -DLFS_PROG_SIZE=512 + -Wno-error=format" + make -Clittlefs test + +install: + # Get arm-none-eabi-gcc + - sudo add-apt-repository -y ppa:terry.guo/gcc-arm-embedded + - sudo apt-get update -qq + - sudo apt-get install -qq gcc-arm-none-eabi --force-yes + # Get dependencies + - git clone https://github.com/armmbed/mbed-os.git + - git clone https://github.com/armmbed/spiflash-driver.git + # Install python dependencies + - sudo pip install -r mbed-os/requirements.txt + # Create main file for example + - sed -n '/``` c++/,/```/{/```/d; p;}' README.md > main.cpp diff --git a/littlefs/lfs_util.h b/littlefs/lfs_util.h index b3832214c4..76ec8d90a8 100644 --- a/littlefs/lfs_util.h +++ b/littlefs/lfs_util.h @@ -10,7 +10,6 @@ #include #include #include -#include "mbed_debug.h" // Builtin functions @@ -38,6 +37,12 @@ void lfs_crc(uint32_t *crc, const void *buffer, size_t size); // Logging functions +#ifdef __MBED__ +#include "mbed_debug.h" +#else +#define debug printf +#endif + #define LFS_DEBUG(fmt, ...) debug("lfs debug: " fmt "\n", __VA_ARGS__) #define LFS_WARN(fmt, ...) debug("lfs warn: " fmt "\n", __VA_ARGS__) #define LFS_ERROR(fmt, ...) debug("lfs error: " fmt "\n", __VA_ARGS__) From 76d00eb38cddfcc267f323c470142b9f4588d3ff Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sun, 23 Jul 2017 13:59:49 -0500 Subject: [PATCH 06/46] Squashed 'littlefs/' changes from 663e953..c2283a2 c2283a2 Extended entry tag to support attributes 8795f0e Added build size output to CI 47db7a7 Added sanity check for compiling example 476915f Removed a few "what"s from the documentation git-subtree-dir: littlefs git-subtree-split: c2283a2753619d82a1fdf27d799cd53f2eef9a80 --- .travis.yml | 10 +++++++++ DESIGN.md | 8 +++---- README.md | 4 +++- lfs.c | 64 +++++++++++++++++++++++++++++------------------------ lfs.h | 10 +++++---- 5 files changed, 58 insertions(+), 38 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0936b29f44..0651efc961 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,14 @@ script: + # make sure example can at least compile + - sed -n '/``` c/,/```/{/```/d; p;}' README.md > test.c && + CFLAGS=' + -Duser_provided_block_device_read=NULL + -Duser_provided_block_device_prog=NULL + -Duser_provided_block_device_erase=NULL + -Duser_provided_block_device_sync=NULL + -include stdio.h -Werror' make all size + + # run tests - CFLAGS="-DLFS_READ_SIZE=16 -DLFS_PROG_SIZE=16" make test - CFLAGS="-DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make test - CFLAGS="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make test diff --git a/DESIGN.md b/DESIGN.md index 3381c7919c..dcc469a2d7 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -639,9 +639,9 @@ v '--------' '--------' ``` -Wait, wait, wait, wait, wait, that's not atomic at all! If power is lost after -removing directory B from the root, directory B is still in the linked-list. -We've just created a memory leak! +Wait, wait, wait, that's not atomic at all! If power is lost after removing +directory B from the root, directory B is still in the linked-list. We've +just created a memory leak! And to be honest, I don't have a clever solution for this case. As a side-effect of using multiple pointers in the threaded tree, the littlefs @@ -969,5 +969,5 @@ So, to summarize: 10. Any case where an atomic operation is not possible, it is taken care of by a deorphan step that occurs on the first allocation after boot -Welp, that's the little filesystem. Thanks for reading! +That's the little filesystem. Thanks for reading! diff --git a/README.md b/README.md index 0ae1cc61ea..e9e63b3722 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,6 @@ int main(void) { // update boot count boot_count += 1; - printf("boot_count: %ld\n", boot_count); lfs_file_rewind(&lfs, &file); lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count)); @@ -86,6 +85,9 @@ int main(void) { // release any resources we were using lfs_unmount(&lfs); + + // print the boot count + printf("boot_count: %d\n", boot_count); } ``` diff --git a/lfs.c b/lfs.c index a2d94eec61..b2c0a631aa 100644 --- a/lfs.c +++ b/lfs.c @@ -560,8 +560,7 @@ static int lfs_dir_update(lfs_t *lfs, lfs_dir_t *dir, const lfs_entry_t *entry, const void *data) { return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ {entry->off, sizeof(entry->d), &entry->d, sizeof(entry->d)}, - {entry->off+sizeof(entry->d), entry->d.len-sizeof(entry->d), - data, entry->d.len-sizeof(entry->d)} + {entry->off+sizeof(entry->d), entry->d.nlen, data, entry->d.nlen} }, data ? 2 : 1); } @@ -569,11 +568,12 @@ static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry, const void *data) { // check if we fit, if top bit is set we do not and move on while (true) { - if (dir->d.size + entry->d.len <= lfs->cfg->block_size) { + if (dir->d.size + 4+entry->d.elen+entry->d.alen+entry->d.nlen + <= lfs->cfg->block_size) { entry->off = dir->d.size - 4; return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ {entry->off, 0, &entry->d, sizeof(entry->d)}, - {entry->off, 0, data, entry->d.len - sizeof(entry->d)} + {entry->off, 0, data, entry->d.nlen} }, 2); } @@ -590,7 +590,7 @@ static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir, entry->off = newdir.d.size - 4; err = lfs_dir_commit(lfs, &newdir, (struct lfs_region[]){ {entry->off, 0, &entry->d, sizeof(entry->d)}, - {entry->off, 0, data, entry->d.len - sizeof(entry->d)} + {entry->off, 0, data, entry->d.nlen} }, 2); if (err) { return err; @@ -620,7 +620,8 @@ static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { if (!(pdir.d.size & 0x80000000)) { return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ - {entry->off, entry->d.len, NULL, 0}, + {entry->off, 4+entry->d.elen+entry->d.alen+entry->d.nlen, + NULL, 0}, }, 1); } else { pdir.d.tail[0] = dir->d.tail[0]; @@ -629,7 +630,8 @@ static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { } } else { return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ - {entry->off, entry->d.len, NULL, 0}, + {entry->off, 4+entry->d.elen+entry->d.alen+entry->d.nlen, + NULL, 0}, }, 1); } } @@ -656,9 +658,9 @@ static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { return err; } - dir->off += entry->d.len; - dir->pos += entry->d.len; - entry->off = dir->off - entry->d.len; + entry->off = dir->off; + dir->off += 4+entry->d.elen+entry->d.alen+entry->d.nlen; + dir->pos += 4+entry->d.elen+entry->d.alen+entry->d.nlen; return 0; } @@ -713,12 +715,13 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, if ((entry->d.type != LFS_TYPE_REG && entry->d.type != LFS_TYPE_DIR) || - entry->d.name != pathlen) { + entry->d.nlen != pathlen) { continue; } int res = lfs_bd_cmp(lfs, dir->pair[0], - entry->off + sizeof(entry->d), pathname, pathlen); + entry->off + 4+entry->d.elen+entry->d.alen, + pathname, pathlen); if (res < 0) { return res; } @@ -784,8 +787,9 @@ int lfs_mkdir(lfs_t *lfs, const char *path) { } entry.d.type = LFS_TYPE_DIR; - entry.d.name = strlen(path); - entry.d.len = sizeof(entry.d) + entry.d.name; + entry.d.elen = sizeof(entry.d) - 4; + entry.d.alen = 0; + entry.d.nlen = strlen(path); entry.d.u.dir[0] = dir.pair[0]; entry.d.u.dir[1] = dir.pair[1]; @@ -880,8 +884,9 @@ int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { info->size = entry.d.u.file.size; } - int err = lfs_bd_read(lfs, dir->pair[0], entry.off + sizeof(entry.d), - info->name, entry.d.name); + int err = lfs_bd_read(lfs, dir->pair[0], + entry.off + 4+entry.d.elen+entry.d.alen, + info->name, entry.d.nlen); if (err) { return err; } @@ -1117,8 +1122,9 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file, // create entry to remember name entry.d.type = LFS_TYPE_REG; - entry.d.name = strlen(path); - entry.d.len = sizeof(entry.d) + entry.d.name; + entry.d.elen = sizeof(entry.d) - 4; + entry.d.alen = 0; + entry.d.nlen = strlen(path); entry.d.u.file.head = -1; entry.d.u.file.size = 0; err = lfs_dir_append(lfs, &cwd, &entry, path); @@ -1537,8 +1543,9 @@ int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { info->size = entry.d.u.file.size; } - err = lfs_bd_read(lfs, cwd.pair[0], entry.off + sizeof(entry.d), - info->name, entry.d.name); + err = lfs_bd_read(lfs, cwd.pair[0], + entry.off + 4+entry.d.elen+entry.d.alen, + info->name, entry.d.nlen); if (err) { return err; } @@ -1585,7 +1592,7 @@ int lfs_remove(lfs_t *lfs, const char *path) { f->pair[0] = 0xffffffff; f->pair[1] = 0xffffffff; } else if (f->poff > entry.off) { - f->poff -= entry.d.len; + f->poff -= 4 + entry.d.elen + entry.d.alen + entry.d.nlen; } } } @@ -1651,8 +1658,7 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { // move to new location lfs_entry_t newentry = preventry; newentry.d = oldentry.d; - newentry.d.name = strlen(newpath); - newentry.d.len = sizeof(newentry.d) + newentry.d.name; + newentry.d.nlen = strlen(newpath); if (prevexists) { int err = lfs_dir_update(lfs, &newcwd, &newentry, newpath); @@ -1690,7 +1696,7 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { f->pair[0] = 0xffffffff; f->pair[1] = 0xffffffff; } else if (f->poff > oldentry.off) { - f->poff -= oldentry.d.len; + f->poff -= 4+oldentry.d.elen+oldentry.d.alen+oldentry.d.nlen; } } } @@ -1809,8 +1815,8 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { lfs_superblock_t superblock = { .off = sizeof(superdir.d), .d.type = LFS_TYPE_SUPERBLOCK, - .d.name = sizeof(superblock.d.magic), - .d.len = sizeof(superblock.d), + .d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4, + .d.nlen = sizeof(superblock.d.magic), .d.version = 0x00010001, .d.magic = {"littlefs"}, .d.block_size = lfs->cfg->block_size, @@ -1865,8 +1871,8 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { lfs_superblock_t superblock; err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); if (!err) { - err = lfs_bd_read(lfs, dir.pair[0], - sizeof(dir.d), &superblock.d, sizeof(superblock.d)); + err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d), + &superblock.d, sizeof(superblock.d)); lfs->root[0] = superblock.d.root[0]; lfs->root[1] = superblock.d.root[1]; @@ -1925,7 +1931,7 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { return err; } - dir.off += entry.d.len; + dir.off += 4+entry.d.elen+entry.d.alen+entry.d.nlen; if ((0xf & entry.d.type) == (0xf & LFS_TYPE_REG)) { int err = lfs_index_traverse(lfs, &lfs->rcache, NULL, entry.d.u.file.head, entry.d.u.file.size, cb, data); diff --git a/lfs.h b/lfs.h index 0133b94aec..1b11ba79cf 100644 --- a/lfs.h +++ b/lfs.h @@ -160,8 +160,9 @@ typedef struct lfs_entry { struct lfs_disk_entry { uint8_t type; - uint8_t name; - uint16_t len; + uint8_t elen; + uint8_t alen; + uint8_t nlen; union { struct { lfs_block_t head; @@ -212,8 +213,9 @@ typedef struct lfs_superblock { struct lfs_disk_superblock { uint8_t type; - uint8_t name; - uint16_t len; + uint8_t elen; + uint8_t alen; + uint8_t nlen; lfs_block_t root[2]; uint32_t block_size; uint32_t block_count; From c9117d8cb57e20190b80b9d454768bf9bc7919a3 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sun, 23 Jul 2017 15:12:13 -0500 Subject: [PATCH 07/46] Increased default lookahead to 512 blocks Because lookahead is stored efficiently as a bit-vector, this only requires a ram increase of 48 bytes (2.1% of benchmark), but decreases the SD benchmark runtime cost by 32 seconds (21.9% of benchmark). Note this is unimportant on devices with byte-reads such as NOR flash. --- mbed_lib.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mbed_lib.json b/mbed_lib.json index 7d2efc807a..3de40afbb6 100644 --- a/mbed_lib.json +++ b/mbed_lib.json @@ -18,7 +18,7 @@ }, "lookahead": { "macro_name": "MBED_LFS_LOOKAHEAD", - "value": 128, + "value": 512, "help": "Number of blocks to lookahead during block allocation. A larger lookahead reduces the number of passes required to allocate a block. The lookahead buffer requires only 1 bit per block so it can be quite large with little ram impact. Should be a multiple of 32." } } From 6e99fa9319d17f0aef418ae3989befc04ecf9096 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 26 Sep 2017 20:09:29 -0500 Subject: [PATCH 08/46] Squashed 'littlefs/' changes from c2283a2..9843402 9843402 Fixed incorrect return value from lfs_file_seek 273cb7c Fixed problem with lookaheads larger than block device d9367e0 Fixed collection of multiblock directories a83b2fe Added checks for out-of-bound seeks a8fa5e6 Fixed some corner cases with paths 26dd49a Fixed issue with negative modulo with unaligned lookaheads 0982020 Fixed issue with cold-write after seek to block boundary git-subtree-dir: littlefs git-subtree-split: 984340225befc1e2330dd3b88f4048373eda0fce --- .travis.yml | 6 +- lfs.c | 139 +++++++++++++++++++++++++++++--------------- lfs.h | 7 ++- tests/test_dirs.sh | 34 ++++++++++- tests/test_paths.sh | 37 ++++++++++++ tests/test_seek.sh | 88 ++++++++++++++++++++++++---- 6 files changed, 248 insertions(+), 63 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0651efc961..a4ff97650b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,10 @@ script: -include stdio.h -Werror' make all size # run tests - - CFLAGS="-DLFS_READ_SIZE=16 -DLFS_PROG_SIZE=16" make test + - make test + + # run tests with a few different configurations - CFLAGS="-DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make test - CFLAGS="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make test + - CFLAGS="-DLFS_BLOCK_COUNT=1023" make test + - CFLAGS="-DLFS_LOOKAHEAD=2047" make test diff --git a/lfs.c b/lfs.c index b2c0a631aa..dc4a955ef3 100644 --- a/lfs.c +++ b/lfs.c @@ -262,9 +262,12 @@ int lfs_deorphan(lfs_t *lfs); static int lfs_alloc_lookahead(void *p, lfs_block_t block) { lfs_t *lfs = p; - lfs_block_t off = (block - lfs->free.start) % lfs->cfg->block_count; - if (off < lfs->cfg->lookahead) { - lfs->free.lookahead[off / 32] |= 1U << (off % 32); + lfs_block_t off = (((lfs_soff_t)(block - lfs->free.begin) + % (lfs_soff_t)(lfs->cfg->block_count)) + + lfs->cfg->block_count) % lfs->cfg->block_count; + + if (off < lfs->free.lookahead) { + lfs->free.buffer[off / 32] |= 1U << (off % 32); } return 0; @@ -282,30 +285,30 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { while (true) { while (true) { // check if we have looked at all blocks since last ack - if (lfs->free.start + lfs->free.off == lfs->free.end) { + if (lfs->free.begin + lfs->free.off == lfs->free.end) { LFS_WARN("No more free space %d", lfs->free.end); return LFS_ERR_NOSPC; } - if (lfs->free.off >= lfs->cfg->lookahead) { + if (lfs->free.off >= lfs->free.lookahead) { break; } lfs_block_t off = lfs->free.off; lfs->free.off += 1; - if (!(lfs->free.lookahead[off / 32] & (1U << (off % 32)))) { + if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) { // found a free block - *block = (lfs->free.start + off) % lfs->cfg->block_count; + *block = (lfs->free.begin + off) % lfs->cfg->block_count; return 0; } } - lfs->free.start += lfs->cfg->lookahead; + lfs->free.begin += lfs->free.lookahead; lfs->free.off = 0; // find mask of free blocks from tree - memset(lfs->free.lookahead, 0, lfs->cfg->lookahead/8); + memset(lfs->free.buffer, 0, lfs->free.lookahead/8); int err = lfs_traverse(lfs, lfs_alloc_lookahead, lfs); if (err) { return err; @@ -314,7 +317,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { } static void lfs_alloc_ack(lfs_t *lfs) { - lfs->free.end = lfs->free.start + lfs->free.off + lfs->cfg->block_count; + lfs->free.end = lfs->free.begin + lfs->free.off + lfs->cfg->block_count; } @@ -343,6 +346,10 @@ static inline bool lfs_pairsync( (paira[0] == pairb[1] && paira[1] == pairb[0]); } +static inline lfs_size_t lfs_entry_size(const lfs_entry_t *entry) { + return 4 + entry->d.elen + entry->d.alen + entry->d.nlen; +} + static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) { // allocate pair of dir blocks for (int i = 0; i < 2; i++) { @@ -568,8 +575,7 @@ static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry, const void *data) { // check if we fit, if top bit is set we do not and move on while (true) { - if (dir->d.size + 4+entry->d.elen+entry->d.alen+entry->d.nlen - <= lfs->cfg->block_size) { + if (dir->d.size + lfs_entry_size(entry) <= lfs->cfg->block_size) { entry->off = dir->d.size - 4; return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ {entry->off, 0, &entry->d, sizeof(entry->d)}, @@ -611,7 +617,8 @@ static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir, static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { // either shift out the one entry or remove the whole dir block - if (dir->d.size == sizeof(dir->d)+4) { + if ((dir->d.size & 0x7fffffff) == sizeof(dir->d)+4 + + lfs_entry_size(entry)) { lfs_dir_t pdir; int res = lfs_pred(lfs, dir->pair, &pdir); if (res < 0) { @@ -620,18 +627,17 @@ static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { if (!(pdir.d.size & 0x80000000)) { return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ - {entry->off, 4+entry->d.elen+entry->d.alen+entry->d.nlen, - NULL, 0}, + {entry->off, lfs_entry_size(entry), NULL, 0}, }, 1); } else { + pdir.d.size &= dir->d.size | 0x7fffffff; pdir.d.tail[0] = dir->d.tail[0]; pdir.d.tail[1] = dir->d.tail[1]; - return lfs_dir_commit(lfs, dir, NULL, 0); + return lfs_dir_commit(lfs, &pdir, NULL, 0); } } else { return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ - {entry->off, 4+entry->d.elen+entry->d.alen+entry->d.nlen, - NULL, 0}, + {entry->off, lfs_entry_size(entry), NULL, 0}, }, 1); } } @@ -659,8 +665,8 @@ static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { } entry->off = dir->off; - dir->off += 4+entry->d.elen+entry->d.alen+entry->d.nlen; - dir->pos += 4+entry->d.elen+entry->d.alen+entry->d.nlen; + dir->off += lfs_entry_size(entry); + dir->pos += lfs_entry_size(entry); return 0; } @@ -706,6 +712,9 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, suffix += sufflen; } + // update what we've found + *path = pathname; + // find path while (true) { int err = lfs_dir_next(lfs, dir, entry); @@ -747,8 +756,6 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, if (err) { return err; } - - *path = pathname; } return 0; @@ -766,7 +773,7 @@ int lfs_mkdir(lfs_t *lfs, const char *path) { lfs_entry_t entry; err = lfs_dir_find(lfs, &cwd, &entry, &path); - if (err != LFS_ERR_NOENT) { + if (err != LFS_ERR_NOENT || strchr(path, '/') != NULL) { return err ? err : LFS_ERR_EXISTS; } @@ -814,8 +821,8 @@ int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { return err; } + // check for root, can only be something like '/././../.' if (strspn(path, "/.") == strlen(path)) { - // can only be something like '/././../.' dir->head[0] = dir->pair[0]; dir->head[1] = dir->pair[1]; dir->pos = sizeof(dir->d) - 2; @@ -975,6 +982,7 @@ static int lfs_index_find(lfs_t *lfs, return err; } + assert(head >= 2 && head <= lfs->cfg->block_count); current -= 1 << skip; } @@ -993,6 +1001,7 @@ static int lfs_index_extend(lfs_t *lfs, if (err) { return err; } + assert(*block >= 2 && *block <= lfs->cfg->block_count); err = lfs_bd_erase(lfs, *block); if (err) { @@ -1054,6 +1063,8 @@ static int lfs_index_extend(lfs_t *lfs, return err; } } + + assert(head >= 2 && head <= lfs->cfg->block_count); } *off = 4*skips; @@ -1111,7 +1122,7 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file, lfs_entry_t entry; err = lfs_dir_find(lfs, &cwd, &entry, &path); - if (err && err != LFS_ERR_NOENT) { + if (err && (err != LFS_ERR_NOENT || strchr(path, '/') != NULL)) { return err; } @@ -1369,6 +1380,11 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, } } + if (file->pos >= file->size) { + // eof if past end + return 0; + } + size = lfs_min(size, file->size - file->pos); nsize = size; @@ -1424,22 +1440,34 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, file->pos = file->size; } + if (!(file->flags & LFS_F_WRITING) && file->pos > file->size) { + // fill with zeros + lfs_off_t pos = file->pos; + file->pos = file->size; + + while (file->pos < pos) { + lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + return res; + } + } + } + while (nsize > 0) { // check if we need a new block if (!(file->flags & LFS_F_WRITING) || file->off == lfs->cfg->block_size) { - if (!(file->flags & LFS_F_WRITING)) { + if (!(file->flags & LFS_F_WRITING) && file->pos > 0) { // find out which block we're extending from int err = lfs_index_find(lfs, &file->cache, NULL, file->head, file->size, - file->pos, &file->block, &file->off); + file->pos-1, &file->block, &file->off); if (err) { return err; } // mark cache as dirty since we may have read data into it file->cache.block = 0xffffffff; - file->flags |= LFS_F_WRITING; } // extend file with new blocks @@ -1450,6 +1478,8 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, if (err) { return err; } + + file->flags |= LFS_F_WRITING; } // program as much as we can in current block @@ -1492,17 +1522,23 @@ lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, } // update pos - lfs_off_t pos = file->pos; - if (whence == LFS_SEEK_SET) { file->pos = off; } else if (whence == LFS_SEEK_CUR) { + if (-off > file->pos) { + return LFS_ERR_INVAL; + } + file->pos = file->pos + off; } else if (whence == LFS_SEEK_END) { + if (-off > file->size) { + return LFS_ERR_INVAL; + } + file->pos = file->size + off; } - return pos; + return file->pos; } lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { @@ -1525,6 +1561,14 @@ lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { /// General fs oprations /// int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { + // check for root, can only be something like '/././../.' + if (strspn(path, "/.") == strlen(path)) { + memset(info, 0, sizeof(*info)); + info->type = LFS_TYPE_DIR; + strcpy(info->name, "/"); + return 0; + } + lfs_dir_t cwd; int err = lfs_dir_fetch(lfs, &cwd, lfs->root); if (err) { @@ -1592,7 +1636,7 @@ int lfs_remove(lfs_t *lfs, const char *path) { f->pair[0] = 0xffffffff; f->pair[1] = 0xffffffff; } else if (f->poff > entry.off) { - f->poff -= 4 + entry.d.elen + entry.d.alen + entry.d.nlen; + f->poff -= lfs_entry_size(&entry); } } } @@ -1632,7 +1676,7 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { lfs_entry_t preventry; err = lfs_dir_find(lfs, &newcwd, &preventry, &newpath); - if (err && err != LFS_ERR_NOENT) { + if (err && (err != LFS_ERR_NOENT || strchr(newpath, '/') != NULL)) { return err; } bool prevexists = (err != LFS_ERR_NOENT); @@ -1696,7 +1740,7 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { f->pair[0] = 0xffffffff; f->pair[1] = 0xffffffff; } else if (f->poff > oldentry.off) { - f->poff -= 4+oldentry.d.elen+oldentry.d.alen+oldentry.d.nlen; + f->poff -= lfs_entry_size(&oldentry); } } } @@ -1740,12 +1784,15 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { } } - // setup lookahead + // setup lookahead, round down to nearest 32-bits + lfs->free.lookahead = lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count); + lfs->free.lookahead = 32 * (lfs->free.lookahead / 32); + assert(lfs->free.lookahead > 0); if (lfs->cfg->lookahead_buffer) { - lfs->free.lookahead = lfs->cfg->lookahead_buffer; + lfs->free.buffer = lfs->cfg->lookahead_buffer; } else { - lfs->free.lookahead = malloc(lfs->cfg->lookahead/8); - if (!lfs->free.lookahead) { + lfs->free.buffer = malloc(lfs->free.lookahead/8); + if (!lfs->free.buffer) { return LFS_ERR_NOMEM; } } @@ -1770,7 +1817,7 @@ static int lfs_deinit(lfs_t *lfs) { } if (!lfs->cfg->lookahead_buffer) { - free(lfs->free.lookahead); + free(lfs->free.buffer); } return 0; @@ -1783,10 +1830,10 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { } // create free lookahead - memset(lfs->free.lookahead, 0, lfs->cfg->lookahead/8); - lfs->free.start = 0; + memset(lfs->free.buffer, 0, lfs->free.lookahead/8); + lfs->free.begin = 0; lfs->free.off = 0; - lfs->free.end = lfs->free.start + lfs->cfg->block_count; + lfs->free.end = lfs->free.begin + lfs->cfg->block_count; // create superblock dir lfs_alloc_ack(lfs); @@ -1862,9 +1909,9 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { } // setup free lookahead - lfs->free.start = -lfs->cfg->lookahead; - lfs->free.off = lfs->cfg->lookahead; - lfs->free.end = lfs->free.start + lfs->cfg->block_count; + lfs->free.begin = -lfs->free.lookahead; + lfs->free.off = lfs->free.lookahead; + lfs->free.end = lfs->free.begin + lfs->cfg->block_count; // load superblock lfs_dir_t dir; @@ -1931,7 +1978,7 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { return err; } - dir.off += 4+entry.d.elen+entry.d.alen+entry.d.nlen; + dir.off += lfs_entry_size(&entry); if ((0xf & entry.d.type) == (0xf & LFS_TYPE_REG)) { int err = lfs_index_traverse(lfs, &lfs->rcache, NULL, entry.d.u.file.head, entry.d.u.file.size, cb, data); diff --git a/lfs.h b/lfs.h index 1b11ba79cf..ed232f68f6 100644 --- a/lfs.h +++ b/lfs.h @@ -225,10 +225,11 @@ typedef struct lfs_superblock { } lfs_superblock_t; typedef struct lfs_free { + lfs_size_t lookahead; + lfs_block_t begin; lfs_block_t end; - lfs_block_t start; lfs_block_t off; - uint32_t *lookahead; + uint32_t *buffer; } lfs_free_t; // The littlefs type @@ -237,12 +238,12 @@ typedef struct lfs { lfs_block_t root[2]; lfs_file_t *files; - bool deorphaned; lfs_cache_t rcache; lfs_cache_t pcache; lfs_free_t free; + bool deorphaned; } lfs_t; diff --git a/tests/test_dirs.sh b/tests/test_dirs.sh index 815b88bd22..5a7ea58b7e 100755 --- a/tests/test_dirs.sh +++ b/tests/test_dirs.sh @@ -124,7 +124,6 @@ tests/test.py << TEST TEST echo "--- Directory remove ---" -# TESTING HERE tests/test.py << TEST lfs_mount(&lfs, &cfg) => 0; lfs_remove(&lfs, "potato") => LFS_ERR_INVAL; @@ -283,5 +282,38 @@ tests/test.py << TEST lfs_unmount(&lfs) => 0; TEST +echo "--- Multi-block remove ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "cactus") => LFS_ERR_INVAL; + + for (int i = 0; i < $LARGESIZE; i++) { + sprintf((char*)buffer, "cactus/test%d", i); + lfs_remove(&lfs, (char*)buffer) => 0; + } + + lfs_remove(&lfs, "cactus") => 0; + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "/") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "burito") => 0; + info.type => LFS_TYPE_REG; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "coldpotato") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + echo "--- Results ---" tests/stats.py diff --git a/tests/test_paths.sh b/tests/test_paths.sh index 769f37fcea..9bc1f5b171 100755 --- a/tests/test_paths.sh +++ b/tests/test_paths.sh @@ -31,6 +31,10 @@ tests/test.py << TEST strcmp(info.name, "hottea") => 0; lfs_stat(&lfs, "/tea/hottea", &info) => 0; strcmp(info.name, "hottea") => 0; + + lfs_mkdir(&lfs, "/milk1") => 0; + lfs_stat(&lfs, "/milk1", &info) => 0; + strcmp(info.name, "milk1") => 0; lfs_unmount(&lfs) => 0; TEST @@ -43,6 +47,10 @@ tests/test.py << TEST strcmp(info.name, "hottea") => 0; lfs_stat(&lfs, "///tea///hottea", &info) => 0; strcmp(info.name, "hottea") => 0; + + lfs_mkdir(&lfs, "///milk2") => 0; + lfs_stat(&lfs, "///milk2", &info) => 0; + strcmp(info.name, "milk2") => 0; lfs_unmount(&lfs) => 0; TEST @@ -57,6 +65,10 @@ tests/test.py << TEST strcmp(info.name, "hottea") => 0; lfs_stat(&lfs, "/./tea/./hottea", &info) => 0; strcmp(info.name, "hottea") => 0; + + lfs_mkdir(&lfs, "/./milk3") => 0; + lfs_stat(&lfs, "/./milk3", &info) => 0; + strcmp(info.name, "milk3") => 0; lfs_unmount(&lfs) => 0; TEST @@ -71,6 +83,10 @@ tests/test.py << TEST strcmp(info.name, "hottea") => 0; lfs_stat(&lfs, "coffee/../soda/../tea/hottea", &info) => 0; strcmp(info.name, "hottea") => 0; + + lfs_mkdir(&lfs, "coffee/../milk4") => 0; + lfs_stat(&lfs, "coffee/../milk4", &info) => 0; + strcmp(info.name, "milk4") => 0; lfs_unmount(&lfs) => 0; TEST @@ -79,6 +95,27 @@ tests/test.py << TEST lfs_mount(&lfs, &cfg) => 0; lfs_stat(&lfs, "coffee/../../../../../../tea/hottea", &info) => 0; strcmp(info.name, "hottea") => 0; + + lfs_mkdir(&lfs, "coffee/../../../../../../milk5") => 0; + lfs_stat(&lfs, "coffee/../../../../../../milk5", &info) => 0; + strcmp(info.name, "milk5") => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Root tests ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "/", &info) => 0; + info.type => LFS_TYPE_DIR; + strcmp(info.name, "/") => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Sketchy path tests ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "dirt/ground") => LFS_ERR_NOENT; + lfs_mkdir(&lfs, "dirt/ground/earth") => LFS_ERR_NOENT; lfs_unmount(&lfs) => 0; TEST diff --git a/tests/test_seek.sh b/tests/test_seek.sh index 8c0093852e..6600cb2f5c 100755 --- a/tests/test_seek.sh +++ b/tests/test_seek.sh @@ -133,15 +133,15 @@ tests/test.py << TEST lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "kittycatcat", size) => 0; - lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size; + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "kittycatcat", size) => 0; - lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos+size; + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos; lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "kittycatcat", size) => 0; - lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size; + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1; lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "kittycatcat", size) => 0; @@ -174,15 +174,15 @@ tests/test.py << TEST lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "kittycatcat", size) => 0; - lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size; + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "kittycatcat", size) => 0; - lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos+size; + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos; lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "kittycatcat", size) => 0; - lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size; + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1; lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "kittycatcat", size) => 0; @@ -211,7 +211,7 @@ tests/test.py << TEST lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; lfs_file_write(&lfs, &file[0], buffer, size) => size; - lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos+size; + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "doggodogdog", size) => 0; @@ -219,11 +219,11 @@ tests/test.py << TEST lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "kittycatcat", size) => 0; - lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size; + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "doggodogdog", size) => 0; - lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size; + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1; lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "kittycatcat", size) => 0; @@ -254,7 +254,7 @@ tests/test.py << TEST lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; lfs_file_write(&lfs, &file[0], buffer, size) => size; - lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos+size; + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "doggodogdog", size) => 0; @@ -262,11 +262,11 @@ tests/test.py << TEST lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "kittycatcat", size) => 0; - lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size; + lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos; lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "doggodogdog", size) => 0; - lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size; + lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1; lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "kittycatcat", size) => 0; @@ -277,5 +277,69 @@ tests/test.py << TEST lfs_unmount(&lfs) => 0; TEST +echo "--- Boundary seek and write ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0; + + size = strlen("hedgehoghog"); + const lfs_soff_t offsets[] = {512, 1020, 513, 1021, 511, 1019}; + + for (int i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) { + lfs_soff_t off = offsets[i]; + memcpy(buffer, "hedgehoghog", size); + lfs_file_seek(&lfs, &file[0], off, LFS_SEEK_SET) => off; + lfs_file_write(&lfs, &file[0], buffer, size) => size; + lfs_file_seek(&lfs, &file[0], off, LFS_SEEK_SET) => off; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "hedgehoghog", size) => 0; + + lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_SET) => 0; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + lfs_file_sync(&lfs, &file[0]) => 0; + } + + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Out-of-bounds seek ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0; + + size = strlen("kittycatcat"); + lfs_file_size(&lfs, &file[0]) => $LARGESIZE*size; + lfs_file_seek(&lfs, &file[0], ($LARGESIZE+$SMALLSIZE)*size, + LFS_SEEK_SET) => ($LARGESIZE+$SMALLSIZE)*size; + lfs_file_read(&lfs, &file[0], buffer, size) => 0; + + memcpy(buffer, "porcupineee", size); + lfs_file_write(&lfs, &file[0], buffer, size) => size; + + lfs_file_seek(&lfs, &file[0], ($LARGESIZE+$SMALLSIZE)*size, + LFS_SEEK_SET) => ($LARGESIZE+$SMALLSIZE)*size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "porcupineee", size) => 0; + + lfs_file_seek(&lfs, &file[0], $LARGESIZE*size, + LFS_SEEK_SET) => $LARGESIZE*size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "\0\0\0\0\0\0\0\0\0\0\0", size) => 0; + + lfs_file_seek(&lfs, &file[0], -(($LARGESIZE+$SMALLSIZE)*size), + LFS_SEEK_CUR) => LFS_ERR_INVAL; + lfs_file_tell(&lfs, &file[0]) => ($LARGESIZE+1)*size; + + lfs_file_seek(&lfs, &file[0], -(($LARGESIZE+2*$SMALLSIZE)*size), + LFS_SEEK_END) => LFS_ERR_INVAL; + lfs_file_tell(&lfs, &file[0]) => ($LARGESIZE+1)*size; + + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + echo "--- Results ---" tests/stats.py From b6455a36c2e4f23ae086d2c550f327fa21efcc3d Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 22 Sep 2017 10:41:44 -0500 Subject: [PATCH 09/46] Brought over additional testing configurations from littlefs --- .travis.yml | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5c816766c6..361fc20fa4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,17 +6,21 @@ script: - PYTHONPATH=mbed-os python mbed-os/tools/make.py -t GCC_ARM -m K82F --source=. --build=BUILD/K82F/GCC_ARM -j0 - # Run local littlefs tests - - CFLAGS=" - -DLFS_READ_SIZE=64 - -DLFS_PROG_SIZE=64 - -Wno-error=format" - make -Clittlefs test - - CFLAGS=" - -DLFS_READ_SIZE=512 - -DLFS_PROG_SIZE=512 - -Wno-error=format" - make -Clittlefs test + # Run littlefs functional tests + - CFLAGS="-Wno-error=format" make -Clittlefs test + + # Run littlefs functional tests with different configurations + # Note: r/w size of 64 is default in mbed + - CFLAGS="-Wno-error=format -DLFS_READ_SIZE=64 -DLFS_PROG_SIZE=64" + make -Clittlefs test + - CFLAGS="-Wno-error=format -DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" + make -Clittlefs test + - CFLAGS="-Wno-error=format -DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" + make -Clittlefs test + - CFLAGS="-Wno-error=format -DLFS_BLOCK_COUNT=1023" + make -Clittlefs test + - CFLAGS="-Wno-error=format -DLFS_LOOKAHEAD=2047" + make -Clittlefs test install: # Get arm-none-eabi-gcc From 3a987334f65fdbc4483db7bfec57aac65b01d606 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 22 Sep 2017 10:49:20 -0500 Subject: [PATCH 10/46] Added user flag to pip install in travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 361fc20fa4..580eb08f47 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,6 @@ install: - git clone https://github.com/armmbed/mbed-os.git - git clone https://github.com/armmbed/spiflash-driver.git # Install python dependencies - - sudo pip install -r mbed-os/requirements.txt + - pip install --user -r mbed-os/requirements.txt # Create main file for example - sed -n '/``` c++/,/```/{/```/d; p;}' README.md > main.cpp From 9408f2ba2cfb432a6447962645f3880c821cad7c Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 22 Sep 2017 14:48:32 -0500 Subject: [PATCH 11/46] Converted tests into mbed-style integration tests - TESTS/filesystem for mbed OS filesystem APIs - TESTS/filesystem_retarget for mbed OS retargeted stdlib APIs converted: - test_dirs - test_files - test_seek - test_parallel --- .mbedignore | 1 + .travis.yml | 6 + TESTS/filesystem/dirs/main.cpp | 658 ++++++++++++++++++++ TESTS/filesystem/files/main.cpp | 426 +++++++++++++ TESTS/filesystem/parallel/main.cpp | 387 ++++++++++++ TESTS/filesystem/seek/main.cpp | 618 ++++++++++++++++++ TESTS/filesystem_retarget/dirs/main.cpp | 658 ++++++++++++++++++++ TESTS/filesystem_retarget/files/main.cpp | 426 +++++++++++++ TESTS/filesystem_retarget/parallel/main.cpp | 387 ++++++++++++ TESTS/filesystem_retarget/seek/main.cpp | 616 ++++++++++++++++++ TESTS/util/Makefile | 21 + TESTS/util/clean.sh | 4 + TESTS/util/echo.py | 34 + TESTS/util/replacements_mbed.yml | 34 + TESTS/util/replacements_retarget.yml | 38 ++ TESTS/util/stats.py | 33 + TESTS/util/template_subunit.fmt | 4 + TESTS/util/template_unit.fmt | 8 + TESTS/util/template_wrapper.fmt | 86 +++ TESTS/util/test.py | 48 ++ 20 files changed, 4493 insertions(+) create mode 100644 TESTS/filesystem/dirs/main.cpp create mode 100644 TESTS/filesystem/files/main.cpp create mode 100644 TESTS/filesystem/parallel/main.cpp create mode 100644 TESTS/filesystem/seek/main.cpp create mode 100644 TESTS/filesystem_retarget/dirs/main.cpp create mode 100644 TESTS/filesystem_retarget/files/main.cpp create mode 100644 TESTS/filesystem_retarget/parallel/main.cpp create mode 100644 TESTS/filesystem_retarget/seek/main.cpp create mode 100644 TESTS/util/Makefile create mode 100755 TESTS/util/clean.sh create mode 100755 TESTS/util/echo.py create mode 100644 TESTS/util/replacements_mbed.yml create mode 100644 TESTS/util/replacements_retarget.yml create mode 100755 TESTS/util/stats.py create mode 100644 TESTS/util/template_subunit.fmt create mode 100644 TESTS/util/template_unit.fmt create mode 100644 TESTS/util/template_wrapper.fmt create mode 100755 TESTS/util/test.py diff --git a/.mbedignore b/.mbedignore index dc8a4d89d2..9660ef9598 100644 --- a/.mbedignore +++ b/.mbedignore @@ -1,2 +1,3 @@ littlefs/emubd/ littlefs/tests/ +TESTS/util diff --git a/.travis.yml b/.travis.yml index 580eb08f47..3d0b2b03c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,12 @@ script: - PYTHONPATH=mbed-os python mbed-os/tools/make.py -t GCC_ARM -m K82F --source=. --build=BUILD/K82F/GCC_ARM -j0 + # Check that tests compile + - rm -rf main.cpp BUILD + - PYTHONPATH=mbed-os python mbed-os/tools/test.py -t GCC_ARM -m K82F + --source=. --build=BUILD/TESTS/K82F/GCC_ARM -j0 + -n 'tests*' + # Run littlefs functional tests - CFLAGS="-Wno-error=format" make -Clittlefs test diff --git a/TESTS/filesystem/dirs/main.cpp b/TESTS/filesystem/dirs/main.cpp new file mode 100644 index 0000000000..b1fb3edfc0 --- /dev/null +++ b/TESTS/filesystem/dirs/main.cpp @@ -0,0 +1,658 @@ +#include "mbed.h" +#include "greentea-client/test_env.h" +#include "unity.h" +#include "utest.h" +#include +#include + +using namespace utest::v1; + +// test configuration +#ifndef MBED_TEST_FILESYSTEM +#define MBED_TEST_FILESYSTEM LittleFileSystem +#endif + +#ifndef MBED_TEST_FILESYSTEM_DECL +#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs") +#endif + +#ifndef MBED_TEST_BLOCKDEVICE +#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice +#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#endif + +#ifndef MBED_TEST_BLOCKDEVICE_DECL +#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd +#endif + +#ifndef MBED_TEST_FILES +#define MBED_TEST_FILES 4 +#endif + +#ifndef MBED_TEST_DIRS +#define MBED_TEST_DIRS 4 +#endif + +#ifndef MBED_TEST_BUFFER +#define MBED_TEST_BUFFER 8192 +#endif + +#ifndef MBED_TEST_TIMEOUT +#define MBED_TEST_TIMEOUT 120 +#endif + + +// declarations +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_FILESYSTEM) +#include INCLUDE(MBED_TEST_BLOCKDEVICE) + +MBED_TEST_FILESYSTEM_DECL; +MBED_TEST_BLOCKDEVICE_DECL; + +Dir dir[MBED_TEST_DIRS]; +File file[MBED_TEST_FILES]; +DIR *dd[MBED_TEST_DIRS]; +FILE *fd[MBED_TEST_FILES]; +struct dirent ent; +struct dirent *ed; +size_t size; +uint8_t buffer[MBED_TEST_BUFFER]; +uint8_t rbuffer[MBED_TEST_BUFFER]; +uint8_t wbuffer[MBED_TEST_BUFFER]; + + +// tests + +void test_directory_tests() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = MBED_TEST_FILESYSTEM::format(&bd); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_root_directory() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].open(&fs, "/"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_directory_creation() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = fs.mkdir("potato", 0777); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_file_creation() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "burito", O_CREAT | O_WRONLY); + TEST_ASSERT_EQUAL(0, res); + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_directory_iteration() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].open(&fs, "/"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "potato"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "burito"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_directory_failures() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = fs.mkdir("potato", 0777); + TEST_ASSERT_EQUAL(-EEXIST, res); + res = dir[0].open(&fs, "tomato"); + TEST_ASSERT_EQUAL(-ENOENT, res); + res = dir[0].open(&fs, "burito"); + TEST_ASSERT_EQUAL(-ENOTDIR, res); + res = file[0].open(&fs, "tomato", O_RDONLY); + TEST_ASSERT_EQUAL(-ENOENT, res); + res = file[0].open(&fs, "potato", O_RDONLY); + TEST_ASSERT_EQUAL(-EISDIR, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_nested_directories() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = fs.mkdir("potato/baked", 0777); + TEST_ASSERT_EQUAL(0, res); + res = fs.mkdir("potato/sweet", 0777); + TEST_ASSERT_EQUAL(0, res); + res = fs.mkdir("potato/fried", 0777); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].open(&fs, "potato"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "baked"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "sweet"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "fried"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_multi_block_directory() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = fs.mkdir("cactus", 0777); + TEST_ASSERT_EQUAL(0, res); + for (int i = 0; i < 128; i++) { + sprintf((char*)buffer, "cactus/test%d", i); + res = fs.mkdir((char*)buffer, 0777); + TEST_ASSERT_EQUAL(0, res); + } + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].open(&fs, "cactus"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + for (int i = 0; i < 128; i++) { + sprintf((char*)buffer, "test%d", i); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + } + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_directory_remove() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = fs.remove("potato"); + TEST_ASSERT_EQUAL(-EINVAL, res); + res = fs.remove("potato/sweet"); + TEST_ASSERT_EQUAL(0, res); + res = fs.remove("potato/baked"); + TEST_ASSERT_EQUAL(0, res); + res = fs.remove("potato/fried"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].open(&fs, "potato"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.remove("potato"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].open(&fs, "/"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "burito"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "cactus"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].open(&fs, "/"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "burito"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "cactus"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_directory_rename() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = fs.mkdir("coldpotato", 0777); + TEST_ASSERT_EQUAL(0, res); + res = fs.mkdir("coldpotato/baked", 0777); + TEST_ASSERT_EQUAL(0, res); + res = fs.mkdir("coldpotato/sweet", 0777); + TEST_ASSERT_EQUAL(0, res); + res = fs.mkdir("coldpotato/fried", 0777); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = fs.rename("coldpotato", "hotpotato"); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].open(&fs, "hotpotato"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "baked"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "sweet"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "fried"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = fs.mkdir("warmpotato", 0777); + TEST_ASSERT_EQUAL(0, res); + res = fs.mkdir("warmpotato/mushy", 0777); + TEST_ASSERT_EQUAL(0, res); + res = fs.rename("hotpotato", "warmpotato"); + TEST_ASSERT_EQUAL(-EINVAL, res); + res = fs.remove("warmpotato/mushy"); + TEST_ASSERT_EQUAL(0, res); + res = fs.rename("hotpotato", "warmpotato"); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].open(&fs, "warmpotato"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "baked"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "sweet"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "fried"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = fs.mkdir("coldpotato", 0777); + TEST_ASSERT_EQUAL(0, res); + res = fs.rename("warmpotato/baked", "coldpotato/baked"); + TEST_ASSERT_EQUAL(0, res); + res = fs.rename("warmpotato/sweet", "coldpotato/sweet"); + TEST_ASSERT_EQUAL(0, res); + res = fs.rename("warmpotato/fried", "coldpotato/fried"); + TEST_ASSERT_EQUAL(0, res); + res = fs.remove("coldpotato"); + TEST_ASSERT_EQUAL(-EINVAL, res); + res = fs.remove("warmpotato"); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].open(&fs, "coldpotato"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "baked"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "sweet"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "fried"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + + + +// test setup +utest::v1::status_t test_setup(const size_t number_of_cases) { + GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +} + +Case cases[] = { + Case("Directory tests", test_directory_tests), + Case("Root directory", test_root_directory), + Case("Directory creation", test_directory_creation), + Case("File creation", test_file_creation), + Case("Directory iteration", test_directory_iteration), + Case("Directory failures", test_directory_failures), + Case("Nested directories", test_nested_directories), + Case("Multi-block directory", test_multi_block_directory), + Case("Directory remove", test_directory_remove), + Case("Directory rename", test_directory_rename), +}; + +Specification specification(test_setup, cases); + +int main() { + return !Harness::run(specification); +} diff --git a/TESTS/filesystem/files/main.cpp b/TESTS/filesystem/files/main.cpp new file mode 100644 index 0000000000..1637bebdbb --- /dev/null +++ b/TESTS/filesystem/files/main.cpp @@ -0,0 +1,426 @@ +#include "mbed.h" +#include "greentea-client/test_env.h" +#include "unity.h" +#include "utest.h" +#include +#include + +using namespace utest::v1; + +// test configuration +#ifndef MBED_TEST_FILESYSTEM +#define MBED_TEST_FILESYSTEM LittleFileSystem +#endif + +#ifndef MBED_TEST_FILESYSTEM_DECL +#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs") +#endif + +#ifndef MBED_TEST_BLOCKDEVICE +#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice +#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#endif + +#ifndef MBED_TEST_BLOCKDEVICE_DECL +#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd +#endif + +#ifndef MBED_TEST_FILES +#define MBED_TEST_FILES 4 +#endif + +#ifndef MBED_TEST_DIRS +#define MBED_TEST_DIRS 4 +#endif + +#ifndef MBED_TEST_BUFFER +#define MBED_TEST_BUFFER 8192 +#endif + +#ifndef MBED_TEST_TIMEOUT +#define MBED_TEST_TIMEOUT 120 +#endif + + +// declarations +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_FILESYSTEM) +#include INCLUDE(MBED_TEST_BLOCKDEVICE) + +MBED_TEST_FILESYSTEM_DECL; +MBED_TEST_BLOCKDEVICE_DECL; + +Dir dir[MBED_TEST_DIRS]; +File file[MBED_TEST_FILES]; +DIR *dd[MBED_TEST_DIRS]; +FILE *fd[MBED_TEST_FILES]; +struct dirent ent; +struct dirent *ed; +size_t size; +uint8_t buffer[MBED_TEST_BUFFER]; +uint8_t rbuffer[MBED_TEST_BUFFER]; +uint8_t wbuffer[MBED_TEST_BUFFER]; + + +// tests + +void test_file_tests() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = MBED_TEST_FILESYSTEM::format(&bd); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_simple_file_test() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "hello", O_WRONLY | O_CREAT); + TEST_ASSERT_EQUAL(0, res); + size = strlen("Hello World!\n"); + memcpy(wbuffer, "Hello World!\n", size); + res = file[0].write(wbuffer, size); + TEST_ASSERT_EQUAL(size, res); + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "hello", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + size = strlen("Hello World!\n"); + res = file[0].read(rbuffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(rbuffer, wbuffer, size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_small_file_test() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + size_t size = 32; + size_t chunk = 31; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "smallavacado", O_WRONLY | O_CREAT); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + for (size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + res = file[0].write(buffer, chunk); + TEST_ASSERT_EQUAL(chunk, res); + } + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + size_t size = 32; + size_t chunk = 29; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "smallavacado", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + res = file[0].read(buffer, chunk); + TEST_ASSERT_EQUAL(chunk, res); + for (size_t b = 0; b < chunk && i+b < size; b++) { + res = buffer[b]; + TEST_ASSERT_EQUAL(rand() & 0xff, res); + } + } + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_medium_file_test() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + size_t size = 8192; + size_t chunk = 31; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "mediumavacado", O_WRONLY | O_CREAT); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + for (size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + res = file[0].write(buffer, chunk); + TEST_ASSERT_EQUAL(chunk, res); + } + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + size_t size = 8192; + size_t chunk = 29; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "mediumavacado", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + res = file[0].read(buffer, chunk); + TEST_ASSERT_EQUAL(chunk, res); + for (size_t b = 0; b < chunk && i+b < size; b++) { + res = buffer[b]; + TEST_ASSERT_EQUAL(rand() & 0xff, res); + } + } + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_large_file_test() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + size_t size = 262144; + size_t chunk = 31; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "largeavacado", O_WRONLY | O_CREAT); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + for (size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + res = file[0].write(buffer, chunk); + TEST_ASSERT_EQUAL(chunk, res); + } + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + size_t size = 262144; + size_t chunk = 29; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "largeavacado", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + res = file[0].read(buffer, chunk); + TEST_ASSERT_EQUAL(chunk, res); + for (size_t b = 0; b < chunk && i+b < size; b++) { + res = buffer[b]; + TEST_ASSERT_EQUAL(rand() & 0xff, res); + } + } + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_non_overlap_check() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + size_t size = 32; + size_t chunk = 29; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "smallavacado", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + res = file[0].read(buffer, chunk); + TEST_ASSERT_EQUAL(chunk, res); + for (size_t b = 0; b < chunk && i+b < size; b++) { + res = buffer[b]; + TEST_ASSERT_EQUAL(rand() & 0xff, res); + } + } + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + size_t size = 8192; + size_t chunk = 29; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "mediumavacado", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + res = file[0].read(buffer, chunk); + TEST_ASSERT_EQUAL(chunk, res); + for (size_t b = 0; b < chunk && i+b < size; b++) { + res = buffer[b]; + TEST_ASSERT_EQUAL(rand() & 0xff, res); + } + } + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + size_t size = 262144; + size_t chunk = 29; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "largeavacado", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + res = file[0].read(buffer, chunk); + TEST_ASSERT_EQUAL(chunk, res); + for (size_t b = 0; b < chunk && i+b < size; b++) { + res = buffer[b]; + TEST_ASSERT_EQUAL(rand() & 0xff, res); + } + } + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_dir_check() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].open(&fs, "/"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "hello"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "smallavacado"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "mediumavacado"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "largeavacado"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + + + +// test setup +utest::v1::status_t test_setup(const size_t number_of_cases) { + GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +} + +Case cases[] = { + Case("File tests", test_file_tests), + Case("Simple file test", test_simple_file_test), + Case("Small file test", test_small_file_test), + Case("Medium file test", test_medium_file_test), + Case("Large file test", test_large_file_test), + Case("Non-overlap check", test_non_overlap_check), + Case("Dir check", test_dir_check), +}; + +Specification specification(test_setup, cases); + +int main() { + return !Harness::run(specification); +} diff --git a/TESTS/filesystem/parallel/main.cpp b/TESTS/filesystem/parallel/main.cpp new file mode 100644 index 0000000000..ebdc7a9287 --- /dev/null +++ b/TESTS/filesystem/parallel/main.cpp @@ -0,0 +1,387 @@ +#include "mbed.h" +#include "greentea-client/test_env.h" +#include "unity.h" +#include "utest.h" +#include +#include + +using namespace utest::v1; + +// test configuration +#ifndef MBED_TEST_FILESYSTEM +#define MBED_TEST_FILESYSTEM LittleFileSystem +#endif + +#ifndef MBED_TEST_FILESYSTEM_DECL +#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs") +#endif + +#ifndef MBED_TEST_BLOCKDEVICE +#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice +#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#endif + +#ifndef MBED_TEST_BLOCKDEVICE_DECL +#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd +#endif + +#ifndef MBED_TEST_FILES +#define MBED_TEST_FILES 4 +#endif + +#ifndef MBED_TEST_DIRS +#define MBED_TEST_DIRS 4 +#endif + +#ifndef MBED_TEST_BUFFER +#define MBED_TEST_BUFFER 8192 +#endif + +#ifndef MBED_TEST_TIMEOUT +#define MBED_TEST_TIMEOUT 120 +#endif + + +// declarations +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_FILESYSTEM) +#include INCLUDE(MBED_TEST_BLOCKDEVICE) + +MBED_TEST_FILESYSTEM_DECL; +MBED_TEST_BLOCKDEVICE_DECL; + +Dir dir[MBED_TEST_DIRS]; +File file[MBED_TEST_FILES]; +DIR *dd[MBED_TEST_DIRS]; +FILE *fd[MBED_TEST_FILES]; +struct dirent ent; +struct dirent *ed; +size_t size; +uint8_t buffer[MBED_TEST_BUFFER]; +uint8_t rbuffer[MBED_TEST_BUFFER]; +uint8_t wbuffer[MBED_TEST_BUFFER]; + + +// tests + +void test_parallel_tests() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = MBED_TEST_FILESYSTEM::format(&bd); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_parallel_file_test() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "a", O_WRONLY | O_CREAT); + TEST_ASSERT_EQUAL(0, res); + res = file[1].open(&fs, "b", O_WRONLY | O_CREAT); + TEST_ASSERT_EQUAL(0, res); + res = file[2].open(&fs, "c", O_WRONLY | O_CREAT); + TEST_ASSERT_EQUAL(0, res); + res = file[3].open(&fs, "d", O_WRONLY | O_CREAT); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 10; i++) { + res = file[0].write((const void*)"a", 1); + TEST_ASSERT_EQUAL(1, res); + res = file[1].write((const void*)"b", 1); + TEST_ASSERT_EQUAL(1, res); + res = file[2].write((const void*)"c", 1); + TEST_ASSERT_EQUAL(1, res); + res = file[3].write((const void*)"d", 1); + TEST_ASSERT_EQUAL(1, res); + } + + file[0].close(); + file[1].close(); + file[2].close(); + file[3].close(); + res = dir[0].open(&fs, "/"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "a"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "b"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "c"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "d"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "a", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + res = file[1].open(&fs, "b", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + res = file[2].open(&fs, "c", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + res = file[3].open(&fs, "d", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 10; i++) { + res = file[0].read(buffer, 1); + TEST_ASSERT_EQUAL(1, res); + res = buffer[0]; + TEST_ASSERT_EQUAL('a', res); + res = file[1].read(buffer, 1); + TEST_ASSERT_EQUAL(1, res); + res = buffer[0]; + TEST_ASSERT_EQUAL('b', res); + res = file[2].read(buffer, 1); + TEST_ASSERT_EQUAL(1, res); + res = buffer[0]; + TEST_ASSERT_EQUAL('c', res); + res = file[3].read(buffer, 1); + TEST_ASSERT_EQUAL(1, res); + res = buffer[0]; + TEST_ASSERT_EQUAL('d', res); + } + + file[0].close(); + file[1].close(); + file[2].close(); + file[3].close(); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_parallel_remove_file_test() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "e", O_WRONLY | O_CREAT); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 5; i++) { + res = file[0].write((const void*)"e", 1); + TEST_ASSERT_EQUAL(1, res); + } + res = fs.remove("a"); + TEST_ASSERT_EQUAL(0, res); + res = fs.remove("b"); + TEST_ASSERT_EQUAL(0, res); + res = fs.remove("c"); + TEST_ASSERT_EQUAL(0, res); + res = fs.remove("d"); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 5; i++) { + res = file[0].write((const void*)"e", 1); + TEST_ASSERT_EQUAL(1, res); + } + + file[0].close(); + res = dir[0].open(&fs, "/"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "e"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "e", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 10; i++) { + res = file[0].read(buffer, 1); + TEST_ASSERT_EQUAL(1, res); + res = buffer[0]; + TEST_ASSERT_EQUAL('e', res); + } + + file[0].close(); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_remove_inconveniently_test() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "e", O_WRONLY | O_TRUNC); + TEST_ASSERT_EQUAL(0, res); + res = file[1].open(&fs, "f", O_WRONLY | O_CREAT); + TEST_ASSERT_EQUAL(0, res); + res = file[2].open(&fs, "g", O_WRONLY | O_CREAT); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 5; i++) { + res = file[0].write((const void*)"e", 1); + TEST_ASSERT_EQUAL(1, res); + res = file[1].write((const void*)"f", 1); + TEST_ASSERT_EQUAL(1, res); + res = file[2].write((const void*)"g", 1); + TEST_ASSERT_EQUAL(1, res); + } + res = fs.remove("f"); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 5; i++) { + res = file[0].write((const void*)"e", 1); + TEST_ASSERT_EQUAL(1, res); + res = file[1].write((const void*)"f", 1); + TEST_ASSERT_EQUAL(1, res); + res = file[2].write((const void*)"g", 1); + TEST_ASSERT_EQUAL(1, res); + } + + file[0].close(); + file[1].close(); + file[2].close(); + res = dir[0].open(&fs, "/"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "e"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "g"); + TEST_ASSERT_EQUAL(0, res); + res = ent.d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "e", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + res = file[1].open(&fs, "g", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 10; i++) { + res = file[0].read(buffer, 1); + TEST_ASSERT_EQUAL(1, res); + res = buffer[0]; + TEST_ASSERT_EQUAL('e', res); + res = file[1].read(buffer, 1); + TEST_ASSERT_EQUAL(1, res); + res = buffer[0]; + TEST_ASSERT_EQUAL('g', res); + } + + file[0].close(); + file[1].close(); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + + + +// test setup +utest::v1::status_t test_setup(const size_t number_of_cases) { + GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +} + +Case cases[] = { + Case("Parallel tests", test_parallel_tests), + Case("Parallel file test", test_parallel_file_test), + Case("Parallel remove file test", test_parallel_remove_file_test), + Case("Remove inconveniently test", test_remove_inconveniently_test), +}; + +Specification specification(test_setup, cases); + +int main() { + return !Harness::run(specification); +} diff --git a/TESTS/filesystem/seek/main.cpp b/TESTS/filesystem/seek/main.cpp new file mode 100644 index 0000000000..732d4f2283 --- /dev/null +++ b/TESTS/filesystem/seek/main.cpp @@ -0,0 +1,618 @@ +#include "mbed.h" +#include "greentea-client/test_env.h" +#include "unity.h" +#include "utest.h" +#include +#include + +using namespace utest::v1; + +// test configuration +#ifndef MBED_TEST_FILESYSTEM +#define MBED_TEST_FILESYSTEM LittleFileSystem +#endif + +#ifndef MBED_TEST_FILESYSTEM_DECL +#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs") +#endif + +#ifndef MBED_TEST_BLOCKDEVICE +#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice +#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#endif + +#ifndef MBED_TEST_BLOCKDEVICE_DECL +#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd +#endif + +#ifndef MBED_TEST_FILES +#define MBED_TEST_FILES 4 +#endif + +#ifndef MBED_TEST_DIRS +#define MBED_TEST_DIRS 4 +#endif + +#ifndef MBED_TEST_BUFFER +#define MBED_TEST_BUFFER 8192 +#endif + +#ifndef MBED_TEST_TIMEOUT +#define MBED_TEST_TIMEOUT 120 +#endif + + +// declarations +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_FILESYSTEM) +#include INCLUDE(MBED_TEST_BLOCKDEVICE) + +MBED_TEST_FILESYSTEM_DECL; +MBED_TEST_BLOCKDEVICE_DECL; + +Dir dir[MBED_TEST_DIRS]; +File file[MBED_TEST_FILES]; +DIR *dd[MBED_TEST_DIRS]; +FILE *fd[MBED_TEST_FILES]; +struct dirent ent; +struct dirent *ed; +size_t size; +uint8_t buffer[MBED_TEST_BUFFER]; +uint8_t rbuffer[MBED_TEST_BUFFER]; +uint8_t wbuffer[MBED_TEST_BUFFER]; + + +// tests + +void test_seek_tests() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = MBED_TEST_FILESYSTEM::format(&bd); + TEST_ASSERT_EQUAL(0, res); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = fs.mkdir("hello", 0777); + TEST_ASSERT_EQUAL(0, res); + for (int i = 0; i < 132; i++) { + sprintf((char*)buffer, "hello/kitty%d", i); + res = file[0].open(&fs, (char*)buffer, + O_WRONLY | O_CREAT | O_APPEND); + TEST_ASSERT_EQUAL(0, res); + + size = strlen("kittycatcat"); + memcpy(buffer, "kittycatcat", size); + for (int j = 0; j < 132; j++) { + file[0].write(buffer, size); + } + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + } + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_simple_dir_seek() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].open(&fs, "hello"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + + off_t pos; + int i; + for (i = 0; i < 4; i++) { + sprintf((char*)buffer, "kitty%d", i); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + pos = dir[0].tell(); + } + res = pos >= 0; + TEST_ASSERT_EQUAL(1, res); + + dir[0].seek(pos); + sprintf((char*)buffer, "kitty%d", i); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + + dir[0].rewind(); + sprintf((char*)buffer, "kitty%d", 0); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + + dir[0].seek(pos); + sprintf((char*)buffer, "kitty%d", i); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_large_dir_seek() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].open(&fs, "hello"); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + + off_t pos; + int i; + for (i = 0; i < 128; i++) { + sprintf((char*)buffer, "kitty%d", i); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + pos = dir[0].tell(); + } + res = pos >= 0; + TEST_ASSERT_EQUAL(1, res); + + dir[0].seek(pos); + sprintf((char*)buffer, "kitty%d", i); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + + dir[0].rewind(); + sprintf((char*)buffer, "kitty%d", 0); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + + dir[0].seek(pos); + sprintf((char*)buffer, "kitty%d", i); + res = dir[0].read(&ent); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ent.d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + res = dir[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_simple_file_seek() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "hello/kitty42", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + + off_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < 4; i++) { + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + pos = file[0].tell(); + } + res = pos >= 0; + TEST_ASSERT_EQUAL(1, res); + res = file[0].seek(pos, SEEK_SET); + TEST_ASSERT_EQUAL(pos, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + + file[0].rewind(); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].seek(pos, SEEK_SET); + TEST_ASSERT_EQUAL(pos, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].seek(-size, SEEK_CUR); + TEST_ASSERT_EQUAL(pos, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].seek(-size, SEEK_END) >= 0; + TEST_ASSERT_EQUAL(1, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + + size_t size = file[0].size(); + res = file[0].seek(0, SEEK_CUR); + TEST_ASSERT_EQUAL(size, res); + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_large_file_seek() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "hello/kitty42", O_RDONLY); + TEST_ASSERT_EQUAL(0, res); + + off_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < 128; i++) { + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + pos = file[0].tell(); + } + res = pos >= 0; + TEST_ASSERT_EQUAL(1, res); + res = file[0].seek(pos, SEEK_SET); + TEST_ASSERT_EQUAL(pos, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + + file[0].rewind(); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].seek(pos, SEEK_SET); + TEST_ASSERT_EQUAL(pos, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].seek(-size, SEEK_CUR); + TEST_ASSERT_EQUAL(pos, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].seek(-size, SEEK_END) >= 0; + TEST_ASSERT_EQUAL(1, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + + size_t size = file[0].size(); + res = file[0].seek(0, SEEK_CUR); + TEST_ASSERT_EQUAL(size, res); + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_simple_file_seek_and_write() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "hello/kitty42", O_RDWR); + TEST_ASSERT_EQUAL(0, res); + + off_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < 4; i++) { + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + pos = file[0].tell(); + } + res = pos >= 0; + TEST_ASSERT_EQUAL(1, res); + + memcpy(buffer, "doggodogdog", size); + res = file[0].seek(pos, SEEK_SET); + TEST_ASSERT_EQUAL(pos, res); + res = file[0].write(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = file[0].seek(pos, SEEK_SET); + TEST_ASSERT_EQUAL(pos, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "doggodogdog", size); + TEST_ASSERT_EQUAL(0, res); + + file[0].rewind(); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].seek(pos, SEEK_SET); + TEST_ASSERT_EQUAL(pos, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "doggodogdog", size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].seek(-size, SEEK_END) >= 0; + TEST_ASSERT_EQUAL(1, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + + size_t size = file[0].size(); + res = file[0].seek(0, SEEK_CUR); + TEST_ASSERT_EQUAL(size, res); + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_large_file_seek_and_write() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "hello/kitty42", O_RDWR); + TEST_ASSERT_EQUAL(0, res); + + off_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < 128; i++) { + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + if (i != 4) { + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + } + pos = file[0].tell(); + } + res = pos >= 0; + TEST_ASSERT_EQUAL(1, res); + + memcpy(buffer, "doggodogdog", size); + res = file[0].seek(pos, SEEK_SET); + TEST_ASSERT_EQUAL(pos, res); + res = file[0].write(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = file[0].seek(pos, SEEK_SET); + TEST_ASSERT_EQUAL(pos, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "doggodogdog", size); + TEST_ASSERT_EQUAL(0, res); + + file[0].rewind(); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].seek(pos, SEEK_SET); + TEST_ASSERT_EQUAL(pos, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "doggodogdog", size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].seek(-size, SEEK_END) >= 0; + TEST_ASSERT_EQUAL(1, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + + size_t size = file[0].size(); + res = file[0].seek(0, SEEK_CUR); + TEST_ASSERT_EQUAL(size, res); + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_boundary_seek_and_write() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "hello/kitty42", O_RDWR); + TEST_ASSERT_EQUAL(0, res); + + size = strlen("hedgehoghog"); + const off_t offsets[] = {512, 1020, 513, 1021, 511, 1019}; + + for (int i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) { + off_t off = offsets[i]; + memcpy(buffer, "hedgehoghog", size); + res = file[0].seek(off, SEEK_SET); + TEST_ASSERT_EQUAL(off, res); + res = file[0].write(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = file[0].seek(off, SEEK_SET); + TEST_ASSERT_EQUAL(off, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "hedgehoghog", size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].seek(0, SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].sync(); + TEST_ASSERT_EQUAL(0, res); + } + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_out_of_bounds_seek() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = file[0].open(&fs, "hello/kitty42", O_RDWR); + TEST_ASSERT_EQUAL(0, res); + + size = strlen("kittycatcat"); + res = file[0].size(); + TEST_ASSERT_EQUAL(132*size, res); + res = file[0].seek((132+4)*size, + SEEK_SET); + TEST_ASSERT_EQUAL((132+4)*size, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(0, res); + + memcpy(buffer, "porcupineee", size); + res = file[0].write(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = file[0].seek((132+4)*size, + SEEK_SET); + TEST_ASSERT_EQUAL((132+4)*size, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "porcupineee", size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].seek(132*size, + SEEK_SET); + TEST_ASSERT_EQUAL(132*size, res); + res = file[0].read(buffer, size); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "\0\0\0\0\0\0\0\0\0\0\0", size); + TEST_ASSERT_EQUAL(0, res); + res = file[0].close(); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + + + +// test setup +utest::v1::status_t test_setup(const size_t number_of_cases) { + GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +} + +Case cases[] = { + Case("Seek tests", test_seek_tests), + Case("Simple dir seek", test_simple_dir_seek), + Case("Large dir seek", test_large_dir_seek), + Case("Simple file seek", test_simple_file_seek), + Case("Large file seek", test_large_file_seek), + Case("Simple file seek and write", test_simple_file_seek_and_write), + Case("Large file seek and write", test_large_file_seek_and_write), + Case("Boundary seek and write", test_boundary_seek_and_write), + Case("Out-of-bounds seek", test_out_of_bounds_seek), +}; + +Specification specification(test_setup, cases); + +int main() { + return !Harness::run(specification); +} diff --git a/TESTS/filesystem_retarget/dirs/main.cpp b/TESTS/filesystem_retarget/dirs/main.cpp new file mode 100644 index 0000000000..a3073c73e9 --- /dev/null +++ b/TESTS/filesystem_retarget/dirs/main.cpp @@ -0,0 +1,658 @@ +#include "mbed.h" +#include "greentea-client/test_env.h" +#include "unity.h" +#include "utest.h" +#include +#include + +using namespace utest::v1; + +// test configuration +#ifndef MBED_TEST_FILESYSTEM +#define MBED_TEST_FILESYSTEM LittleFileSystem +#endif + +#ifndef MBED_TEST_FILESYSTEM_DECL +#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs") +#endif + +#ifndef MBED_TEST_BLOCKDEVICE +#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice +#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#endif + +#ifndef MBED_TEST_BLOCKDEVICE_DECL +#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd +#endif + +#ifndef MBED_TEST_FILES +#define MBED_TEST_FILES 4 +#endif + +#ifndef MBED_TEST_DIRS +#define MBED_TEST_DIRS 4 +#endif + +#ifndef MBED_TEST_BUFFER +#define MBED_TEST_BUFFER 8192 +#endif + +#ifndef MBED_TEST_TIMEOUT +#define MBED_TEST_TIMEOUT 120 +#endif + + +// declarations +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_FILESYSTEM) +#include INCLUDE(MBED_TEST_BLOCKDEVICE) + +MBED_TEST_FILESYSTEM_DECL; +MBED_TEST_BLOCKDEVICE_DECL; + +Dir dir[MBED_TEST_DIRS]; +File file[MBED_TEST_FILES]; +DIR *dd[MBED_TEST_DIRS]; +FILE *fd[MBED_TEST_FILES]; +struct dirent ent; +struct dirent *ed; +size_t size; +uint8_t buffer[MBED_TEST_BUFFER]; +uint8_t rbuffer[MBED_TEST_BUFFER]; +uint8_t wbuffer[MBED_TEST_BUFFER]; + + +// tests + +void test_directory_tests() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = MBED_TEST_FILESYSTEM::format(&bd); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_root_directory() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((dd[0] = opendir("/fs/" "/")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_directory_creation() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = mkdir("/fs/" "potato", 0777); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_file_creation() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "burito", "wb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_directory_iteration() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((dd[0] = opendir("/fs/" "/")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "potato"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "burito"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_directory_failures() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = mkdir("/fs/" "potato", 0777); + TEST_ASSERT_EQUAL(EEXIST, errno); + res = !((dd[0] = opendir("/fs/" "tomato")) != NULL); + TEST_ASSERT_EQUAL(ENOENT, errno); + res = !((dd[0] = opendir("/fs/" "burito")) != NULL); + TEST_ASSERT_EQUAL(ENOTDIR, errno); + res = !((fd[0] = fopen("/fs/" "tomato", "rb")) != NULL); + TEST_ASSERT_EQUAL(ENOENT, errno); + res = !((fd[0] = fopen("/fs/" "potato", "rb")) != NULL); + TEST_ASSERT_EQUAL(EISDIR, errno); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_nested_directories() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = mkdir("/fs/" "potato/baked", 0777); + TEST_ASSERT_EQUAL(0, res); + res = mkdir("/fs/" "potato/sweet", 0777); + TEST_ASSERT_EQUAL(0, res); + res = mkdir("/fs/" "potato/fried", 0777); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((dd[0] = opendir("/fs/" "potato")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "baked"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "sweet"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "fried"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_multi_block_directory() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = mkdir("/fs/" "cactus", 0777); + TEST_ASSERT_EQUAL(0, res); + for (int i = 0; i < 128; i++) { + sprintf((char*)buffer, "/fs/" "cactus/test%d", i); + res = mkdir((char*)buffer, 0777); + TEST_ASSERT_EQUAL(0, res); + } + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((dd[0] = opendir("/fs/" "cactus")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + for (int i = 0; i < 128; i++) { + sprintf((char*)buffer, "test%d", i); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + } + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_directory_remove() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = remove("/fs/" "potato"); + TEST_ASSERT_EQUAL(EINVAL, errno); + res = remove("/fs/" "potato/sweet"); + TEST_ASSERT_EQUAL(0, res); + res = remove("/fs/" "potato/baked"); + TEST_ASSERT_EQUAL(0, res); + res = remove("/fs/" "potato/fried"); + TEST_ASSERT_EQUAL(0, res); + res = !((dd[0] = opendir("/fs/" "potato")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = remove("/fs/" "potato"); + TEST_ASSERT_EQUAL(0, res); + res = !((dd[0] = opendir("/fs/" "/")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "burito"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "cactus"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((dd[0] = opendir("/fs/" "/")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "burito"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "cactus"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_directory_rename() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = mkdir("/fs/" "coldpotato", 0777); + TEST_ASSERT_EQUAL(0, res); + res = mkdir("/fs/" "coldpotato/baked", 0777); + TEST_ASSERT_EQUAL(0, res); + res = mkdir("/fs/" "coldpotato/sweet", 0777); + TEST_ASSERT_EQUAL(0, res); + res = mkdir("/fs/" "coldpotato/fried", 0777); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = rename("/fs/" "coldpotato", "/fs/" "hotpotato"); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((dd[0] = opendir("/fs/" "hotpotato")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "baked"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "sweet"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "fried"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = mkdir("/fs/" "warmpotato", 0777); + TEST_ASSERT_EQUAL(0, res); + res = mkdir("/fs/" "warmpotato/mushy", 0777); + TEST_ASSERT_EQUAL(0, res); + res = rename("/fs/" "hotpotato", "/fs/" "warmpotato"); + TEST_ASSERT_EQUAL(EINVAL, errno); + res = remove("/fs/" "warmpotato/mushy"); + TEST_ASSERT_EQUAL(0, res); + res = rename("/fs/" "hotpotato", "/fs/" "warmpotato"); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((dd[0] = opendir("/fs/" "warmpotato")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "baked"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "sweet"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "fried"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = mkdir("/fs/" "coldpotato", 0777); + TEST_ASSERT_EQUAL(0, res); + res = rename("/fs/" "warmpotato/baked", "/fs/" "coldpotato/baked"); + TEST_ASSERT_EQUAL(0, res); + res = rename("/fs/" "warmpotato/sweet", "/fs/" "coldpotato/sweet"); + TEST_ASSERT_EQUAL(0, res); + res = rename("/fs/" "warmpotato/fried", "/fs/" "coldpotato/fried"); + TEST_ASSERT_EQUAL(0, res); + res = remove("/fs/" "coldpotato"); + TEST_ASSERT_EQUAL(EINVAL, errno); + res = remove("/fs/" "warmpotato"); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((dd[0] = opendir("/fs/" "coldpotato")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "baked"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "sweet"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "fried"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + + + +// test setup +utest::v1::status_t test_setup(const size_t number_of_cases) { + GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +} + +Case cases[] = { + Case("Directory tests", test_directory_tests), + Case("Root directory", test_root_directory), + Case("Directory creation", test_directory_creation), + Case("File creation", test_file_creation), + Case("Directory iteration", test_directory_iteration), + Case("Directory failures", test_directory_failures), + Case("Nested directories", test_nested_directories), + Case("Multi-block directory", test_multi_block_directory), + Case("Directory remove", test_directory_remove), + Case("Directory rename", test_directory_rename), +}; + +Specification specification(test_setup, cases); + +int main() { + return !Harness::run(specification); +} diff --git a/TESTS/filesystem_retarget/files/main.cpp b/TESTS/filesystem_retarget/files/main.cpp new file mode 100644 index 0000000000..61587e27df --- /dev/null +++ b/TESTS/filesystem_retarget/files/main.cpp @@ -0,0 +1,426 @@ +#include "mbed.h" +#include "greentea-client/test_env.h" +#include "unity.h" +#include "utest.h" +#include +#include + +using namespace utest::v1; + +// test configuration +#ifndef MBED_TEST_FILESYSTEM +#define MBED_TEST_FILESYSTEM LittleFileSystem +#endif + +#ifndef MBED_TEST_FILESYSTEM_DECL +#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs") +#endif + +#ifndef MBED_TEST_BLOCKDEVICE +#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice +#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#endif + +#ifndef MBED_TEST_BLOCKDEVICE_DECL +#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd +#endif + +#ifndef MBED_TEST_FILES +#define MBED_TEST_FILES 4 +#endif + +#ifndef MBED_TEST_DIRS +#define MBED_TEST_DIRS 4 +#endif + +#ifndef MBED_TEST_BUFFER +#define MBED_TEST_BUFFER 8192 +#endif + +#ifndef MBED_TEST_TIMEOUT +#define MBED_TEST_TIMEOUT 120 +#endif + + +// declarations +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_FILESYSTEM) +#include INCLUDE(MBED_TEST_BLOCKDEVICE) + +MBED_TEST_FILESYSTEM_DECL; +MBED_TEST_BLOCKDEVICE_DECL; + +Dir dir[MBED_TEST_DIRS]; +File file[MBED_TEST_FILES]; +DIR *dd[MBED_TEST_DIRS]; +FILE *fd[MBED_TEST_FILES]; +struct dirent ent; +struct dirent *ed; +size_t size; +uint8_t buffer[MBED_TEST_BUFFER]; +uint8_t rbuffer[MBED_TEST_BUFFER]; +uint8_t wbuffer[MBED_TEST_BUFFER]; + + +// tests + +void test_file_tests() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = MBED_TEST_FILESYSTEM::format(&bd); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_simple_file_test() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "hello", "wb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + size = strlen("Hello World!\n"); + memcpy(wbuffer, "Hello World!\n", size); + res = fwrite(wbuffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "hello", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + size = strlen("Hello World!\n"); + res = fread(rbuffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(rbuffer, wbuffer, size); + TEST_ASSERT_EQUAL(0, res); + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_small_file_test() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + size_t size = 32; + size_t chunk = 31; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "smallavacado", "wb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + for (size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + res = fwrite(buffer, 1, chunk, fd[0]); + TEST_ASSERT_EQUAL(chunk, res); + } + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + size_t size = 32; + size_t chunk = 29; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "smallavacado", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + res = fread(buffer, 1, chunk, fd[0]); + TEST_ASSERT_EQUAL(chunk, res); + for (size_t b = 0; b < chunk && i+b < size; b++) { + res = buffer[b]; + TEST_ASSERT_EQUAL(rand() & 0xff, res); + } + } + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_medium_file_test() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + size_t size = 8192; + size_t chunk = 31; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "mediumavacado", "wb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + for (size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + res = fwrite(buffer, 1, chunk, fd[0]); + TEST_ASSERT_EQUAL(chunk, res); + } + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + size_t size = 8192; + size_t chunk = 29; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "mediumavacado", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + res = fread(buffer, 1, chunk, fd[0]); + TEST_ASSERT_EQUAL(chunk, res); + for (size_t b = 0; b < chunk && i+b < size; b++) { + res = buffer[b]; + TEST_ASSERT_EQUAL(rand() & 0xff, res); + } + } + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_large_file_test() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + size_t size = 262144; + size_t chunk = 31; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "largeavacado", "wb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + for (size_t b = 0; b < chunk; b++) { + buffer[b] = rand() & 0xff; + } + res = fwrite(buffer, 1, chunk, fd[0]); + TEST_ASSERT_EQUAL(chunk, res); + } + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + size_t size = 262144; + size_t chunk = 29; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "largeavacado", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + res = fread(buffer, 1, chunk, fd[0]); + TEST_ASSERT_EQUAL(chunk, res); + for (size_t b = 0; b < chunk && i+b < size; b++) { + res = buffer[b]; + TEST_ASSERT_EQUAL(rand() & 0xff, res); + } + } + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_non_overlap_check() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + size_t size = 32; + size_t chunk = 29; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "smallavacado", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + res = fread(buffer, 1, chunk, fd[0]); + TEST_ASSERT_EQUAL(chunk, res); + for (size_t b = 0; b < chunk && i+b < size; b++) { + res = buffer[b]; + TEST_ASSERT_EQUAL(rand() & 0xff, res); + } + } + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + size_t size = 8192; + size_t chunk = 29; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "mediumavacado", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + res = fread(buffer, 1, chunk, fd[0]); + TEST_ASSERT_EQUAL(chunk, res); + for (size_t b = 0; b < chunk && i+b < size; b++) { + res = buffer[b]; + TEST_ASSERT_EQUAL(rand() & 0xff, res); + } + } + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + { + size_t size = 262144; + size_t chunk = 29; + srand(0); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "largeavacado", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + for (size_t i = 0; i < size; i += chunk) { + chunk = (chunk < size - i) ? chunk : size - i; + res = fread(buffer, 1, chunk, fd[0]); + TEST_ASSERT_EQUAL(chunk, res); + for (size_t b = 0; b < chunk && i+b < size; b++) { + res = buffer[b]; + TEST_ASSERT_EQUAL(rand() & 0xff, res); + } + } + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_dir_check() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((dd[0] = opendir("/fs/" "/")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "hello"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "smallavacado"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "mediumavacado"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "largeavacado"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + + + +// test setup +utest::v1::status_t test_setup(const size_t number_of_cases) { + GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +} + +Case cases[] = { + Case("File tests", test_file_tests), + Case("Simple file test", test_simple_file_test), + Case("Small file test", test_small_file_test), + Case("Medium file test", test_medium_file_test), + Case("Large file test", test_large_file_test), + Case("Non-overlap check", test_non_overlap_check), + Case("Dir check", test_dir_check), +}; + +Specification specification(test_setup, cases); + +int main() { + return !Harness::run(specification); +} diff --git a/TESTS/filesystem_retarget/parallel/main.cpp b/TESTS/filesystem_retarget/parallel/main.cpp new file mode 100644 index 0000000000..9205ac2f24 --- /dev/null +++ b/TESTS/filesystem_retarget/parallel/main.cpp @@ -0,0 +1,387 @@ +#include "mbed.h" +#include "greentea-client/test_env.h" +#include "unity.h" +#include "utest.h" +#include +#include + +using namespace utest::v1; + +// test configuration +#ifndef MBED_TEST_FILESYSTEM +#define MBED_TEST_FILESYSTEM LittleFileSystem +#endif + +#ifndef MBED_TEST_FILESYSTEM_DECL +#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs") +#endif + +#ifndef MBED_TEST_BLOCKDEVICE +#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice +#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#endif + +#ifndef MBED_TEST_BLOCKDEVICE_DECL +#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd +#endif + +#ifndef MBED_TEST_FILES +#define MBED_TEST_FILES 4 +#endif + +#ifndef MBED_TEST_DIRS +#define MBED_TEST_DIRS 4 +#endif + +#ifndef MBED_TEST_BUFFER +#define MBED_TEST_BUFFER 8192 +#endif + +#ifndef MBED_TEST_TIMEOUT +#define MBED_TEST_TIMEOUT 120 +#endif + + +// declarations +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_FILESYSTEM) +#include INCLUDE(MBED_TEST_BLOCKDEVICE) + +MBED_TEST_FILESYSTEM_DECL; +MBED_TEST_BLOCKDEVICE_DECL; + +Dir dir[MBED_TEST_DIRS]; +File file[MBED_TEST_FILES]; +DIR *dd[MBED_TEST_DIRS]; +FILE *fd[MBED_TEST_FILES]; +struct dirent ent; +struct dirent *ed; +size_t size; +uint8_t buffer[MBED_TEST_BUFFER]; +uint8_t rbuffer[MBED_TEST_BUFFER]; +uint8_t wbuffer[MBED_TEST_BUFFER]; + + +// tests + +void test_parallel_tests() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = MBED_TEST_FILESYSTEM::format(&bd); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_parallel_file_test() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "a", "wb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[1] = fopen("/fs/" "b", "wb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[2] = fopen("/fs/" "c", "wb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[3] = fopen("/fs/" "d", "wb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 10; i++) { + res = fwrite((const void*)"a", 1, 1, fd[0]); + TEST_ASSERT_EQUAL(1, res); + res = fwrite((const void*)"b", 1, 1, fd[1]); + TEST_ASSERT_EQUAL(1, res); + res = fwrite((const void*)"c", 1, 1, fd[2]); + TEST_ASSERT_EQUAL(1, res); + res = fwrite((const void*)"d", 1, 1, fd[3]); + TEST_ASSERT_EQUAL(1, res); + } + + fclose(fd[0]); + fclose(fd[1]); + fclose(fd[2]); + fclose(fd[3]); + res = !((dd[0] = opendir("/fs/" "/")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "a"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "b"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "c"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "d"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "a", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[1] = fopen("/fs/" "b", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[2] = fopen("/fs/" "c", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[3] = fopen("/fs/" "d", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 10; i++) { + res = fread(buffer, 1, 1, fd[0]); + TEST_ASSERT_EQUAL(1, res); + res = buffer[0]; + TEST_ASSERT_EQUAL('a', res); + res = fread(buffer, 1, 1, fd[1]); + TEST_ASSERT_EQUAL(1, res); + res = buffer[0]; + TEST_ASSERT_EQUAL('b', res); + res = fread(buffer, 1, 1, fd[2]); + TEST_ASSERT_EQUAL(1, res); + res = buffer[0]; + TEST_ASSERT_EQUAL('c', res); + res = fread(buffer, 1, 1, fd[3]); + TEST_ASSERT_EQUAL(1, res); + res = buffer[0]; + TEST_ASSERT_EQUAL('d', res); + } + + fclose(fd[0]); + fclose(fd[1]); + fclose(fd[2]); + fclose(fd[3]); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_parallel_remove_file_test() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "e", "wb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 5; i++) { + res = fwrite((const void*)"e", 1, 1, fd[0]); + TEST_ASSERT_EQUAL(1, res); + } + res = remove("/fs/" "a"); + TEST_ASSERT_EQUAL(0, res); + res = remove("/fs/" "b"); + TEST_ASSERT_EQUAL(0, res); + res = remove("/fs/" "c"); + TEST_ASSERT_EQUAL(0, res); + res = remove("/fs/" "d"); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 5; i++) { + res = fwrite((const void*)"e", 1, 1, fd[0]); + TEST_ASSERT_EQUAL(1, res); + } + + fclose(fd[0]); + res = !((dd[0] = opendir("/fs/" "/")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "e"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "e", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 10; i++) { + res = fread(buffer, 1, 1, fd[0]); + TEST_ASSERT_EQUAL(1, res); + res = buffer[0]; + TEST_ASSERT_EQUAL('e', res); + } + + fclose(fd[0]); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_remove_inconveniently_test() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "e", "wb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[1] = fopen("/fs/" "f", "wb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[2] = fopen("/fs/" "g", "wb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 5; i++) { + res = fwrite((const void*)"e", 1, 1, fd[0]); + TEST_ASSERT_EQUAL(1, res); + res = fwrite((const void*)"f", 1, 1, fd[1]); + TEST_ASSERT_EQUAL(1, res); + res = fwrite((const void*)"g", 1, 1, fd[2]); + TEST_ASSERT_EQUAL(1, res); + } + res = remove("/fs/" "f"); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 5; i++) { + res = fwrite((const void*)"e", 1, 1, fd[0]); + TEST_ASSERT_EQUAL(1, res); + res = fwrite((const void*)"f", 1, 1, fd[1]); + TEST_ASSERT_EQUAL(1, res); + res = fwrite((const void*)"g", 1, 1, fd[2]); + TEST_ASSERT_EQUAL(1, res); + } + + fclose(fd[0]); + fclose(fd[1]); + fclose(fd[2]); + res = !((dd[0] = opendir("/fs/" "/")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_DIR, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "e"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "g"); + TEST_ASSERT_EQUAL(0, res); + res = ed->d_type; + TEST_ASSERT_EQUAL(DT_REG, res); + + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "e", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[1] = fopen("/fs/" "g", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + for (int i = 0; i < 10; i++) { + res = fread(buffer, 1, 1, fd[0]); + TEST_ASSERT_EQUAL(1, res); + res = buffer[0]; + TEST_ASSERT_EQUAL('e', res); + res = fread(buffer, 1, 1, fd[1]); + TEST_ASSERT_EQUAL(1, res); + res = buffer[0]; + TEST_ASSERT_EQUAL('g', res); + } + + fclose(fd[0]); + fclose(fd[1]); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + + + +// test setup +utest::v1::status_t test_setup(const size_t number_of_cases) { + GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +} + +Case cases[] = { + Case("Parallel tests", test_parallel_tests), + Case("Parallel file test", test_parallel_file_test), + Case("Parallel remove file test", test_parallel_remove_file_test), + Case("Remove inconveniently test", test_remove_inconveniently_test), +}; + +Specification specification(test_setup, cases); + +int main() { + return !Harness::run(specification); +} diff --git a/TESTS/filesystem_retarget/seek/main.cpp b/TESTS/filesystem_retarget/seek/main.cpp new file mode 100644 index 0000000000..e33c96bfa7 --- /dev/null +++ b/TESTS/filesystem_retarget/seek/main.cpp @@ -0,0 +1,616 @@ +#include "mbed.h" +#include "greentea-client/test_env.h" +#include "unity.h" +#include "utest.h" +#include +#include + +using namespace utest::v1; + +// test configuration +#ifndef MBED_TEST_FILESYSTEM +#define MBED_TEST_FILESYSTEM LittleFileSystem +#endif + +#ifndef MBED_TEST_FILESYSTEM_DECL +#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs") +#endif + +#ifndef MBED_TEST_BLOCKDEVICE +#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice +#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#endif + +#ifndef MBED_TEST_BLOCKDEVICE_DECL +#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd +#endif + +#ifndef MBED_TEST_FILES +#define MBED_TEST_FILES 4 +#endif + +#ifndef MBED_TEST_DIRS +#define MBED_TEST_DIRS 4 +#endif + +#ifndef MBED_TEST_BUFFER +#define MBED_TEST_BUFFER 8192 +#endif + +#ifndef MBED_TEST_TIMEOUT +#define MBED_TEST_TIMEOUT 120 +#endif + + +// declarations +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_FILESYSTEM) +#include INCLUDE(MBED_TEST_BLOCKDEVICE) + +MBED_TEST_FILESYSTEM_DECL; +MBED_TEST_BLOCKDEVICE_DECL; + +Dir dir[MBED_TEST_DIRS]; +File file[MBED_TEST_FILES]; +DIR *dd[MBED_TEST_DIRS]; +FILE *fd[MBED_TEST_FILES]; +struct dirent ent; +struct dirent *ed; +size_t size; +uint8_t buffer[MBED_TEST_BUFFER]; +uint8_t rbuffer[MBED_TEST_BUFFER]; +uint8_t wbuffer[MBED_TEST_BUFFER]; + + +// tests + +void test_seek_tests() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = MBED_TEST_FILESYSTEM::format(&bd); + TEST_ASSERT_EQUAL(0, res); + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = mkdir("/fs/" "hello", 0777); + TEST_ASSERT_EQUAL(0, res); + for (int i = 0; i < 132; i++) { + sprintf((char*)buffer, "/fs/" "hello/kitty%d", i); + res = !((fd[0] = fopen((char*)buffer, + "ab")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + size = strlen("kittycatcat"); + memcpy(buffer, "kittycatcat", size); + for (int j = 0; j < 132; j++) { + fwrite(buffer, 1, size, fd[0]); + } + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + } + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_simple_dir_seek() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((dd[0] = opendir("/fs/" "hello")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + + off_t pos; + int i; + for (i = 0; i < 4; i++) { + sprintf((char*)buffer, "kitty%d", i); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + pos = telldir(dd[0]); + } + res = pos >= 0; + TEST_ASSERT_EQUAL(1, res); + + seekdir(dd[0], pos); + sprintf((char*)buffer, "kitty%d", i); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + + rewinddir(dd[0]); + sprintf((char*)buffer, "kitty%d", 0); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + + seekdir(dd[0], pos); + sprintf((char*)buffer, "kitty%d", i); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_large_dir_seek() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((dd[0] = opendir("/fs/" "hello")) != NULL); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + + off_t pos; + int i; + for (i = 0; i < 128; i++) { + sprintf((char*)buffer, "kitty%d", i); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + pos = telldir(dd[0]); + } + res = pos >= 0; + TEST_ASSERT_EQUAL(1, res); + + seekdir(dd[0], pos); + sprintf((char*)buffer, "kitty%d", i); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + + rewinddir(dd[0]); + sprintf((char*)buffer, "kitty%d", 0); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, "."); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, ".."); + TEST_ASSERT_EQUAL(0, res); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + + seekdir(dd[0], pos); + sprintf((char*)buffer, "kitty%d", i); + res = ((ed = readdir(dd[0])) != NULL); + TEST_ASSERT_EQUAL(1, res); + res = strcmp(ed->d_name, (char*)buffer); + TEST_ASSERT_EQUAL(0, res); + res = closedir(dd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_simple_file_seek() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "hello/kitty42", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + off_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < 4; i++) { + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + pos = ftell(fd[0]); + } + res = pos >= 0; + TEST_ASSERT_EQUAL(1, res); + res = fseek(fd[0], pos, SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + + rewind(fd[0]); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = fseek(fd[0], pos, SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = fseek(fd[0], -size, SEEK_CUR); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = fseek(fd[0], -size, SEEK_END); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + + res = fseek(fd[0], 0, SEEK_CUR); + TEST_ASSERT_EQUAL(0, res); + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_large_file_seek() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "hello/kitty42", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + off_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < 128; i++) { + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + pos = ftell(fd[0]); + } + res = pos >= 0; + TEST_ASSERT_EQUAL(1, res); + res = fseek(fd[0], pos, SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + + rewind(fd[0]); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = fseek(fd[0], pos, SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = fseek(fd[0], -size, SEEK_CUR); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = fseek(fd[0], -size, SEEK_END); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + + res = fseek(fd[0], 0, SEEK_CUR); + TEST_ASSERT_EQUAL(0, res); + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_simple_file_seek_and_write() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "hello/kitty42", "r+b")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + off_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < 4; i++) { + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + pos = ftell(fd[0]); + } + res = pos >= 0; + TEST_ASSERT_EQUAL(1, res); + + memcpy(buffer, "doggodogdog", size); + res = fseek(fd[0], pos, SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fwrite(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = fseek(fd[0], pos, SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "doggodogdog", size); + TEST_ASSERT_EQUAL(0, res); + + rewind(fd[0]); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = fseek(fd[0], pos, SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "doggodogdog", size); + TEST_ASSERT_EQUAL(0, res); + res = fseek(fd[0], -size, SEEK_END); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + + res = fseek(fd[0], 0, SEEK_CUR); + TEST_ASSERT_EQUAL(0, res); + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_large_file_seek_and_write() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "hello/kitty42", "r+b")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + off_t pos; + size = strlen("kittycatcat"); + for (int i = 0; i < 128; i++) { + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + if (i != 4) { + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + } + pos = ftell(fd[0]); + } + res = pos >= 0; + TEST_ASSERT_EQUAL(1, res); + + memcpy(buffer, "doggodogdog", size); + res = fseek(fd[0], pos, SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fwrite(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = fseek(fd[0], pos, SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "doggodogdog", size); + TEST_ASSERT_EQUAL(0, res); + + rewind(fd[0]); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = fseek(fd[0], pos, SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "doggodogdog", size); + TEST_ASSERT_EQUAL(0, res); + res = fseek(fd[0], -size, SEEK_END); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + + res = fseek(fd[0], 0, SEEK_CUR); + TEST_ASSERT_EQUAL(0, res); + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_boundary_seek_and_write() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "hello/kitty42", "r+b")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + size = strlen("hedgehoghog"); + const off_t offsets[] = {512, 1020, 513, 1021, 511, 1019}; + + for (int i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) { + off_t off = offsets[i]; + memcpy(buffer, "hedgehoghog", size); + res = fseek(fd[0], off, SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fwrite(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = fseek(fd[0], off, SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "hedgehoghog", size); + TEST_ASSERT_EQUAL(0, res); + res = fseek(fd[0], 0, SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "kittycatcat", size); + TEST_ASSERT_EQUAL(0, res); + res = fflush(fd[0]); + TEST_ASSERT_EQUAL(0, res); + } + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_out_of_bounds_seek() { + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = !((fd[0] = fopen("/fs/" "hello/kitty42", "r+b")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + size = strlen("kittycatcat"); + res = fseek(fd[0], 0, SEEK_END); + TEST_ASSERT_EQUAL(0, res); + res = ftell(fd[0]); + TEST_ASSERT_EQUAL(132*size, res); + res = fseek(fd[0], (132+4)*size, + SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(0, res); + + memcpy(buffer, "porcupineee", size); + res = fwrite(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = fseek(fd[0], (132+4)*size, + SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "porcupineee", size); + TEST_ASSERT_EQUAL(0, res); + res = fseek(fd[0], 132*size, + SEEK_SET); + TEST_ASSERT_EQUAL(0, res); + res = fread(buffer, 1, size, fd[0]); + TEST_ASSERT_EQUAL(size, res); + res = memcmp(buffer, "\0\0\0\0\0\0\0\0\0\0\0", size); + TEST_ASSERT_EQUAL(0, res); + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + + + +// test setup +utest::v1::status_t test_setup(const size_t number_of_cases) { + GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +} + +Case cases[] = { + Case("Seek tests", test_seek_tests), + Case("Simple dir seek", test_simple_dir_seek), + Case("Large dir seek", test_large_dir_seek), + Case("Simple file seek", test_simple_file_seek), + Case("Large file seek", test_large_file_seek), + Case("Simple file seek and write", test_simple_file_seek_and_write), + Case("Large file seek and write", test_large_file_seek_and_write), + Case("Boundary seek and write", test_boundary_seek_and_write), + Case("Out-of-bounds seek", test_out_of_bounds_seek), +}; + +Specification specification(test_setup, cases); + +int main() { + return !Harness::run(specification); +} diff --git a/TESTS/util/Makefile b/TESTS/util/Makefile new file mode 100644 index 0000000000..78a0bec49d --- /dev/null +++ b/TESTS/util/Makefile @@ -0,0 +1,21 @@ + +all: test_dirs test_files test_seek test_parallel + +test_%: ../../littlefs/tests/test_%.sh + cp $< $(notdir $<) + sed -i -e 's/tests\//.\//' -e 's/echo/.\/echo.py/' $(notdir $<) + + ./clean.sh + ln -f -s replacements_mbed.yml replacements.yml + ./$(notdir $<) + mkdir -p ../filesystem/$(patsubst test_%,%,$@) + cp main.cpp ../filesystem/$(patsubst test_%,%,$@)/main.cpp + + ./clean.sh + ln -f -s replacements_retarget.yml replacements.yml + ./$(notdir $<) + mkdir -p ../filesystem_retarget/$(patsubst test_%,%,$@) + cp main.cpp ../filesystem_retarget/$(patsubst test_%,%,$@)/main.cpp + +clean: + ./clean.sh diff --git a/TESTS/util/clean.sh b/TESTS/util/clean.sh new file mode 100755 index 0000000000..ba9862db28 --- /dev/null +++ b/TESTS/util/clean.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +rm -f main.cpp +rm -f template_all_names.txt diff --git a/TESTS/util/echo.py b/TESTS/util/echo.py new file mode 100755 index 0000000000..9d6a1c1881 --- /dev/null +++ b/TESTS/util/echo.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +import re +import sys +import subprocess +import os + +def main(*args): + desc = ' '.join(args).strip('-= ') + name = 'test_' + desc.lower().replace(' ', '_').replace('-', '_') + + exists = os.path.isfile('template_all_names.txt') + + with open('template_all_names.txt', 'a') as file: + file.write(name + '\n') + file.write(desc + '\n') + + with open('template_unit.fmt') as file: + template = file.read() + + template_header, template_footer = template.split('{test}') + + if exists: + with open('main.cpp', 'a') as file: + file.write(template_footer.format( + test_name=name)) + + if name != 'test_results': + with open('main.cpp', 'a') as file: + file.write(template_header.format( + test_name=name)) + +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/TESTS/util/replacements_mbed.yml b/TESTS/util/replacements_mbed.yml new file mode 100644 index 0000000000..f575be5466 --- /dev/null +++ b/TESTS/util/replacements_mbed.yml @@ -0,0 +1,34 @@ +- ['lfs_format\(&lfs, &cfg\)', 'MBED_TEST_FILESYSTEM::format(&bd)'] +- ['lfs_mount\(&lfs, &cfg\)', 'fs.mount(&bd)'] +- ['lfs_unmount\(&lfs\)', 'fs.unmount()'] +- ['lfs_mkdir\(&lfs, (.*)\)', 'fs.mkdir(\1, 0777)'] +- ['lfs_remove\(&lfs, (.*)\)', 'fs.remove(\1)'] +- ['lfs_rename\(&lfs, (.*), ?(.*)\)', 'fs.rename(\1, \2)'] + +- ['lfs_dir_open\(&lfs, &dir\[(.*)\], ?(.*)\)', 'dir[\1].open(&fs, \2)'] +- ['lfs_dir_close\(&lfs, &dir\[(.*)\]\)', 'dir[\1].close()'] +- ['lfs_dir_read\(&lfs, &dir\[(.*)\], &info\)', 'dir[\1].read(&ent)'] +- ['lfs_dir_seek\(&lfs, &dir\[(.*)\], ?(.*)\).*;', 'dir[\1].seek(\2);'] # no dir errors +- ['lfs_dir_rewind\(&lfs, &dir\[(.*)\]\).*;', 'dir[\1].rewind();'] # no dir errors +- ['lfs_dir_tell\(&lfs, &dir\[(.*)\]\)', 'dir[\1].tell()'] + +- ['lfs_file_open\(&lfs, &file\[(.*)\], ?(.*)\)', 'file[\1].open(&fs, \2)'] +- ['lfs_file_close\(&lfs, &file\[(.*)\]\)', 'file[\1].close()'] +- ['lfs_file_sync\(&lfs, &file\[(.*)\]\)', 'file[\1].sync()'] +- ['lfs_file_write\(&lfs, &file\[(.*)\], ?(.*), (.*)\)', 'file[\1].write(\2, \3)'] +- ['lfs_file_read\(&lfs, &file\[(.*)\], ?(.*), (.*)\)', 'file[\1].read(\2, \3)'] +- ['lfs_file_seek\(&lfs, &file\[(.*)\], ?(.*)\)', 'file[\1].seek(\2)'] +- ['lfs_file_tell\(&lfs, &file\[(.*)\]\)', 'file[\1].tell()'] +- ['lfs_file_rewind\(&lfs, &file\[(.*)\]\).*;', 'file[\1].rewind();'] # no errors +- ['lfs_file_size\(&lfs, &file\[(.*)\]\)', 'file[\1].size()'] + +- ['LFS_TYPE_([A-Z]+)', 'DT_\1'] +- ['LFS_O_([A-Z]+)', 'O_\1'] +- ['LFS_SEEK_([A-Z]+)', 'SEEK_\1'] +- ['LFS_ERR_EXISTS', '-EEXIST'] +- ['LFS_ERR_([A-Z]+)', '-E\1'] +- ['lfs_(s?)size_t', '\1size_t'] +- ['lfs_soff_t', 'off_t'] +- ['info\.name', 'ent.d_name'] +- ['info\.type', 'ent.d_type'] +- ['^.*info\.size.*$', ''] # dirent sizes not supported diff --git a/TESTS/util/replacements_retarget.yml b/TESTS/util/replacements_retarget.yml new file mode 100644 index 0000000000..638fb11b16 --- /dev/null +++ b/TESTS/util/replacements_retarget.yml @@ -0,0 +1,38 @@ +- ['lfs_format\(&lfs, &cfg\)', 'MBED_TEST_FILESYSTEM::format(&bd)'] +- ['lfs_mount\(&lfs, &cfg\)', 'fs.mount(&bd)'] +- ['lfs_unmount\(&lfs\)', 'fs.unmount()'] +- ['lfs_mkdir\(&lfs, (.*)\)', 'mkdir("/fs/" \1, 0777)'] +- ['lfs_remove\(&lfs, (.*)\)', 'remove("/fs/" \1)'] +- ['lfs_rename\(&lfs, (.*), ?(.*)\)', 'rename("/fs/" \1, "/fs/" \2)'] + +- ['lfs_dir_open\(&lfs, &dir\[(.*)\], ?(.*)\)', '!((dd[\1] = opendir("/fs/" \2)) != NULL)'] +- ['lfs_dir_close\(&lfs, &dir\[(.*)\]\)', 'closedir(dd[\1])'] +- ['lfs_dir_read\(&lfs, &dir\[(.*)\], &info\)', '((ed = readdir(dd[\1])) != NULL)'] +- ['lfs_dir_seek\(&lfs, &dir\[(.*)\], ?(.*)\).*;', 'seekdir(dd[\1], \2);'] # no dir errors +- ['lfs_dir_rewind\(&lfs, &dir\[(.*)\]\).*;', 'rewinddir(dd[\1]);'] # no dir errors +- ['lfs_dir_tell\(&lfs, &dir\[(.*)\]\)', 'telldir(dd[\1])'] + +- ['lfs_file_open\(&lfs, &file\[(.*)\], ?(.*)\)', '!((fd[\1] = fopen("/fs/" \2)) != NULL)'] +- ['lfs_file_close\(&lfs, &file\[(.*)\]\)', 'fclose(fd[\1])'] +- ['lfs_file_sync\(&lfs, &file\[(.*)\]\)', 'fflush(fd[\1])'] +- ['lfs_file_write\(&lfs, &file\[(.*)\], ?(.*), (.*)\)', 'fwrite(\2, 1, \3, fd[\1])'] +- ['lfs_file_read\(&lfs, &file\[(.*)\], ?(.*), (.*)\)', 'fread(\2, 1, \3, fd[\1])'] +- ['lfs_file_tell\(&lfs, &file\[(.*)\]\)', 'ftell(fd[\1])'] +- ['lfs_file_rewind\(&lfs, &file\[(.*)\]\).*;', 'rewind(fd[\1]);'] # no errors + +- ['LFS_TYPE_([A-Z]+)', 'DT_\1'] +- ['LFS_SEEK_([A-Z]+)', 'SEEK_\1'] +- ['LFS_ERR_EXISTS', '-EEXIST'] +- ['LFS_ERR_([A-Z]+)', '-E\1'] +- ['lfs_(s?)size_t', '\1size_t'] +- ['lfs_soff_t', 'off_t'] +- ['info\.name', 'ed->d_name'] +- ['info\.type', 'ed->d_type'] +- ['^.*info\.size.*$', ''] # dirent sizes not supported + +- ['LFS_O_WRONLY \| LFS_O_CREAT \| LFS_O_APPEND', '"ab"'] +- ['LFS_O_WRONLY \| LFS_O_TRUNC', '"wb"'] +- ['LFS_O_CREAT \| LFS_O_WRONLY', '"wb"'] +- ['LFS_O_WRONLY \| LFS_O_CREAT', '"wb"'] +- ['LFS_O_RDONLY', '"rb"'] +- ['LFS_O_RDWR', '"r+b"'] diff --git a/TESTS/util/stats.py b/TESTS/util/stats.py new file mode 100755 index 0000000000..6bd1b98fe2 --- /dev/null +++ b/TESTS/util/stats.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +import re +import sys +import subprocess +import os + +def main(*args): + with open('main.cpp') as file: + tests = file.read() + + cases = [] + with open('template_all_names.txt') as file: + while True: + name = file.readline().strip('\n') + desc = file.readline().strip('\n') + if name == 'test_results': + break + + cases.append((name, desc)) + + with open('template_wrapper.fmt') as file: + template = file.read() + + with open('main.cpp', 'w') as file: + file.write(template.format( + tests=tests, + test_cases='\n'.join( + 4*' '+'Case("{desc}", {name}),'.format( + name=name, desc=desc) for name, desc in cases))) + +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/TESTS/util/template_subunit.fmt b/TESTS/util/template_subunit.fmt new file mode 100644 index 0000000000..cdcaab9ff7 --- /dev/null +++ b/TESTS/util/template_subunit.fmt @@ -0,0 +1,4 @@ + + {{ +{test} + }} diff --git a/TESTS/util/template_unit.fmt b/TESTS/util/template_unit.fmt new file mode 100644 index 0000000000..06c7f1157e --- /dev/null +++ b/TESTS/util/template_unit.fmt @@ -0,0 +1,8 @@ + +void {test_name}() {{ + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); +{test} + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +}} diff --git a/TESTS/util/template_wrapper.fmt b/TESTS/util/template_wrapper.fmt new file mode 100644 index 0000000000..825bd40d46 --- /dev/null +++ b/TESTS/util/template_wrapper.fmt @@ -0,0 +1,86 @@ +#include "mbed.h" +#include "greentea-client/test_env.h" +#include "unity.h" +#include "utest.h" +#include +#include + +using namespace utest::v1; + +// test configuration +#ifndef MBED_TEST_FILESYSTEM +#define MBED_TEST_FILESYSTEM LittleFileSystem +#endif + +#ifndef MBED_TEST_FILESYSTEM_DECL +#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs") +#endif + +#ifndef MBED_TEST_BLOCKDEVICE +#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice +#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#endif + +#ifndef MBED_TEST_BLOCKDEVICE_DECL +#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd +#endif + +#ifndef MBED_TEST_FILES +#define MBED_TEST_FILES 4 +#endif + +#ifndef MBED_TEST_DIRS +#define MBED_TEST_DIRS 4 +#endif + +#ifndef MBED_TEST_BUFFER +#define MBED_TEST_BUFFER 8192 +#endif + +#ifndef MBED_TEST_TIMEOUT +#define MBED_TEST_TIMEOUT 120 +#endif + + +// declarations +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_FILESYSTEM) +#include INCLUDE(MBED_TEST_BLOCKDEVICE) + +MBED_TEST_FILESYSTEM_DECL; +MBED_TEST_BLOCKDEVICE_DECL; + +Dir dir[MBED_TEST_DIRS]; +File file[MBED_TEST_FILES]; +DIR *dd[MBED_TEST_DIRS]; +FILE *fd[MBED_TEST_FILES]; +struct dirent ent; +struct dirent *ed; +size_t size; +uint8_t buffer[MBED_TEST_BUFFER]; +uint8_t rbuffer[MBED_TEST_BUFFER]; +uint8_t wbuffer[MBED_TEST_BUFFER]; + + +// tests +{tests} + + +// test setup +utest::v1::status_t test_setup(const size_t number_of_cases) {{ + GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +}} + +Case cases[] = {{ +{test_cases} +}}; + +Specification specification(test_setup, cases); + +int main() {{ + return !Harness::run(specification); +}} diff --git a/TESTS/util/test.py b/TESTS/util/test.py new file mode 100755 index 0000000000..fc98dd289e --- /dev/null +++ b/TESTS/util/test.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +import re +import sys +import subprocess +import os +import yaml + +def generate(test): + with open('replacements.yml') as file: + replacements = yaml.load(file) + + lines = [] + for line in re.split('(?<=[;{}])\n', test.read()): + for pattern, replacement in replacements: + line = re.sub(pattern, replacement, line, 0, re.DOTALL | re.MULTILINE) + + match = re.match('(?: *\n)*( *)(.*)=>(.*);', line, re.DOTALL | re.MULTILINE) + if match: + tab, test, expect = match.groups() + lines.append(tab+'res = {test};'.format(test=test.strip())) + lines.append(tab+'TEST_ASSERT_EQUAL({expect}, res);'.format( + name=re.match('\w*', test.strip()).group(), + expect=expect.strip())) + else: + lines.append(line) + + lines = lines[:-1] + + with open('template_subunit.fmt') as file: + template = file.read() + + with open('main.cpp', 'a') as file: + file.write(template.format( + test=('\n'.join( + 4*' '+line.replace('\n', '\n'+4*' ') + for line in lines)))) + +def main(test=None): + if test and not test.startswith('-'): + with open(test) as file: + generate(file) + else: + generate(sys.stdin) + + +if __name__ == "__main__": + main(*sys.argv[1:]) From c390e89f179647c604628d5d3e1e92b5294056a3 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 26 Sep 2017 20:39:04 -0500 Subject: [PATCH 12/46] Added self-hosting fuzz test using littlefs-fuse --- .travis.yml | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3d0b2b03c0..ad609b70d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ -python: - - "2.7" - script: # Check that example compiles + - sed -n '/``` c++/,/```/{/```/d; p;}' README.md > main.cpp - PYTHONPATH=mbed-os python mbed-os/tools/make.py -t GCC_ARM -m K82F --source=. --build=BUILD/K82F/GCC_ARM -j0 @@ -28,6 +26,19 @@ script: - CFLAGS="-Wno-error=format -DLFS_LOOKAHEAD=2047" make -Clittlefs test + # Self-host with littlefs-fuse for fuzz test + - make -C littlefs-fuse + + - littlefs-fuse/lfs --format /dev/loop0 + - littlefs-fuse/lfs /dev/loop0 mount + + - ls mount + - mkdir mount/littlefs + - cp -r $(git ls-tree --name-only HEAD littlefs/) mount/littlefs + - cd mount/littlefs + - ls + - CFLAGS="-Wno-error=format" make -B test_dirs + install: # Get arm-none-eabi-gcc - sudo add-apt-repository -y ppa:terry.guo/gcc-arm-embedded @@ -38,5 +49,22 @@ install: - git clone https://github.com/armmbed/spiflash-driver.git # Install python dependencies - pip install --user -r mbed-os/requirements.txt - # Create main file for example - - sed -n '/``` c++/,/```/{/```/d; p;}' README.md > main.cpp + # Install littlefs-fuse and dependencies + - sudo apt-get install libfuse-dev + - git clone https://github.com/geky/littlefs-fuse + # Check versions + - fusermount -V + - arm-none-eabi-gcc --version + - python --version + - gcc --version + +before_script: + # Patch littlefs-fuse + - rm -rf littlefs-fuse/littlefs/* + - cp -r $(git ls-tree --name-only HEAD littlefs/) littlefs-fuse/littlefs + - echo '*' > littlefs-fuse/.mbedignore + # Create file-backed disk + - mkdir mount + - sudo chmod a+rw /dev/loop0 + - dd if=/dev/zero bs=512 count=2048 of=disk + - losetup /dev/loop0 disk From 301232eb4edc6d1e23d8639fedba529225ff5571 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 10 Oct 2017 06:28:08 -0500 Subject: [PATCH 13/46] Ported lfs_util functions to IAR intrinsics required intrinsics for: - lfs_ctz - lfs_npw2 --- littlefs/lfs_util.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/littlefs/lfs_util.h b/littlefs/lfs_util.h index 76ec8d90a8..62da46098b 100644 --- a/littlefs/lfs_util.h +++ b/littlefs/lfs_util.h @@ -10,6 +10,9 @@ #include #include #include +#ifdef __ICCARM__ +#include +#endif // Builtin functions @@ -22,11 +25,19 @@ static inline uint32_t lfs_min(uint32_t a, uint32_t b) { } static inline uint32_t lfs_ctz(uint32_t a) { +#ifdef __ICCARM__ + return __CLZ(__REV(a)); +#else return __builtin_ctz(a); +#endif } static inline uint32_t lfs_npw2(uint32_t a) { +#ifdef __ICCARM__ + return 32 - __CLZ(a-1); +#else return 32 - __builtin_clz(a-1); +#endif } static inline int lfs_scmp(uint32_t a, uint32_t b) { From 3c3b0329d44d44a2d47a28c755658d7f1db6a085 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 12 Oct 2017 20:42:30 -0500 Subject: [PATCH 14/46] Squashed 'littlefs/' changes from 9843402..454b588 454b588 Updated SPEC.md and DESIGN.md based on recent changes f3578e3 Removed clamping to block size in ctz linked-list 83d4c61 Updated copyright 539409e Refactored deduplicate/deorphan step to single deorphan step 2936514 Added atomic move using dirty tag in entry type ac9766e Added self-hosting fuzz test using littlefs-fuse 9db1a86 Added specification document git-subtree-dir: littlefs git-subtree-split: 454b588f73032f9621c264fba280ab7b3a216402 --- .travis.yml | 30 ++++ DESIGN.md | 250 +++++++++++++++++++++++++----- Makefile | 2 +- README.md | 3 +- SPEC.md | 370 +++++++++++++++++++++++++++++++++++++++++++++ emubd/lfs_emubd.c | 15 +- emubd/lfs_emubd.h | 15 +- lfs.c | 345 +++++++++++++++++++++++++++++++----------- lfs.h | 20 ++- lfs_util.c | 15 +- lfs_util.h | 22 ++- tests/template.fmt | 4 +- tests/test_move.sh | 236 +++++++++++++++++++++++++++++ 13 files changed, 1180 insertions(+), 147 deletions(-) create mode 100644 SPEC.md create mode 100755 tests/test_move.sh diff --git a/.travis.yml b/.travis.yml index a4ff97650b..f2322761b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,3 +16,33 @@ script: - CFLAGS="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make test - CFLAGS="-DLFS_BLOCK_COUNT=1023" make test - CFLAGS="-DLFS_LOOKAHEAD=2047" make test + + # self-host with littlefs-fuse for fuzz test + - make -C littlefs-fuse + + - littlefs-fuse/lfs --format /dev/loop0 + - littlefs-fuse/lfs /dev/loop0 mount + + - ls mount + - mkdir mount/littlefs + - cp -r $(git ls-tree --name-only HEAD) mount/littlefs + - cd mount/littlefs + - ls + - make -B test_dirs + +before_install: + - fusermount -V + - gcc --version + +install: + - sudo apt-get install libfuse-dev + - git clone --depth 1 https://github.com/geky/littlefs-fuse + +before_script: + - rm -rf littlefs-fuse/littlefs/* + - cp -r $(git ls-tree --name-only HEAD) littlefs-fuse/littlefs + + - mkdir mount + - sudo chmod a+rw /dev/loop0 + - dd if=/dev/zero bs=512 count=2048 of=disk + - losetup /dev/loop0 disk diff --git a/DESIGN.md b/DESIGN.md index dcc469a2d7..30c5daea99 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -200,7 +200,7 @@ Now we could just leave files here, copying the entire file on write provides the synchronization without the duplicated memory requirements of the metadata blocks. However, we can do a bit better. -## CTZ linked-lists +## CTZ skip-lists There are many different data structures for representing the actual files in filesystems. Of these, the littlefs uses a rather unique [COW](https://upload.wikimedia.org/wikipedia/commons/0/0c/Cow_female_black_white.jpg) @@ -246,19 +246,19 @@ runtime to just _read_ a file? That's awful. Keep in mind reading files are usually the most common filesystem operation. To avoid this problem, the littlefs uses a multilayered linked-list. For -every block that is divisible by a power of two, the block contains an -additional pointer that points back by that power of two. Another way of -thinking about this design is that there are actually many linked-lists -threaded together, with each linked-lists skipping an increasing number -of blocks. If you're familiar with data-structures, you may have also -recognized that this is a deterministic skip-list. +every nth block where n is divisible by 2^x, the block contains a pointer +to block n-2^x. So each block contains anywhere from 1 to log2(n) pointers +that skip to various sections of the preceding list. If you're familiar with +data-structures, you may have recognized that this is a type of deterministic +skip-list. -To find the power of two factors efficiently, we can use the instruction -[count trailing zeros (CTZ)](https://en.wikipedia.org/wiki/Count_trailing_zeros), -which is where this linked-list's name comes from. +The name comes from the use of the +[count trailing zeros (CTZ)](https://en.wikipedia.org/wiki/Count_trailing_zeros) +instruction, which allows us to calculate the power-of-two factors efficiently. +For a given block n, the block contains ctz(n)+1 pointers. ``` -Exhibit C: A backwards CTZ linked-list +Exhibit C: A backwards CTZ skip-list .--------. .--------. .--------. .--------. .--------. .--------. | data 0 |<-| data 1 |<-| data 2 |<-| data 3 |<-| data 4 |<-| data 5 | | |<-| |--| |<-| |--| | | | @@ -266,6 +266,9 @@ Exhibit C: A backwards CTZ linked-list '--------' '--------' '--------' '--------' '--------' '--------' ``` +The additional pointers allow us to navigate the data-structure on disk +much more efficiently than in a single linked-list. + Taking exhibit C for example, here is the path from data block 5 to data block 1. You can see how data block 3 was completely skipped: ``` @@ -285,15 +288,57 @@ The path to data block 0 is even more quick, requiring only two jumps: '--------' '--------' '--------' '--------' '--------' '--------' ``` -The CTZ linked-list has quite a few interesting properties. All of the pointers -in the block can be found by just knowing the index in the list of the current -block, and, with a bit of math, the amortized overhead for the linked-list is -only two pointers per block. Most importantly, the CTZ linked-list has a -worst case lookup runtime of O(logn), which brings the runtime of reading a -file down to O(n logn). Given that the constant runtime is divided by the -amount of data we can store in a block, this is pretty reasonable. +We can find the runtime complexity by looking at the path to any block from +the block containing the most pointers. Every step along the path divides +the search space for the block in half. This gives us a runtime of O(log n). +To get to the block with the most pointers, we can perform the same steps +backwards, which keeps the asymptotic runtime at O(log n). The interesting +part about this data structure is that this optimal path occurs naturally +if we greedily choose the pointer that covers the most distance without passing +our target block. -Here is what it might look like to update a file stored with a CTZ linked-list: +So now we have a representation of files that can be appended trivially with +a runtime of O(1), and can be read with a worst case runtime of O(n logn). +Given that the the runtime is also divided by the amount of data we can store +in a block, this is pretty reasonable. + +Unfortunately, the CTZ skip-list comes with a few questions that aren't +straightforward to answer. What is the overhead? How do we handle more +pointers than we can store in a block? + +One way to find the overhead per block is to look at the data structure as +multiple layers of linked-lists. Each linked-list skips twice as many blocks +as the previous linked-list. Or another way of looking at it is that each +linked-list uses half as much storage per block as the previous linked-list. +As we approach infinity, the number of pointers per block forms a geometric +series. Solving this geometric series gives us an average of only 2 pointers +per block. + +![overhead per block](https://latex.codecogs.com/gif.latex?%5Clim_%7Bn%5Cto%5Cinfty%7D%5Cfrac%7B1%7D%7Bn%7D%5Csum_%7Bi%3D0%7D%5E%7Bn%7D%5Cleft%28%5Ctext%7Bctz%7D%28i%29+1%5Cright%29%20%3D%20%5Csum_%7Bi%3D0%7D%5E%7B%5Cinfty%7D%5Cfrac%7B1%7D%7B2%5Ei%7D%20%3D%202) + +Finding the maximum number of pointers in a block is a bit more complicated, +but since our file size is limited by the integer width we use to store the +size, we can solve for it. Setting the overhead of the maximum pointers equal +to the block size we get the following equation. Note that a smaller block size +results in more pointers, and a larger word width results in larger pointers. + +![maximum overhead](https://latex.codecogs.com/gif.latex?B%20%3D%20%5Cfrac%7Bw%7D%7B8%7D%5Cleft%5Clceil%5Clog_2%5Cleft%28%5Cfrac%7B2%5Ew%7D%7BB-2%5Cfrac%7Bw%7D%7B8%7D%7D%5Cright%29%5Cright%5Crceil) + +where: +B = block size in bytes +w = word width in bits + +Solving the equation for B gives us the minimum block size for various word +widths: +32 bit CTZ skip-list = minimum block size of 104 bytes +64 bit CTZ skip-list = minimum block size of 448 bytes + +Since littlefs uses a 32 bit word size, we are limited to a minimum block +size of 104 bytes. This is a perfectly reasonable minimum block size, with most +block sizes starting around 512 bytes. So we can avoid the additional logic +needed to avoid overflowing our block's capacity in the CTZ skip-list. + +Here is what it might look like to update a file stored with a CTZ skip-list: ``` block 1 block 2 .---------.---------. @@ -367,7 +412,7 @@ v ## Block allocation So those two ideas provide the grounds for the filesystem. The metadata pairs -give us directories, and the CTZ linked-lists give us files. But this leaves +give us directories, and the CTZ skip-lists give us files. But this leaves one big [elephant](https://upload.wikimedia.org/wikipedia/commons/3/37/African_Bush_Elephant.jpg) of a question. How do we get those blocks in the first place? @@ -653,9 +698,17 @@ deorphan step that simply iterates through every directory in the linked-list and checks it against every directory entry in the filesystem to see if it has a parent. The deorphan step occurs on the first block allocation after boot, so orphans should never cause the littlefs to run out of storage -prematurely. +prematurely. Note that the deorphan step never needs to run in a readonly +filesystem. -And for my final trick, moving a directory: +## The move problem + +Now we have a real problem. How do we move things between directories while +remaining power resilient? Even looking at the problem from a high level, +it seems impossible. We can update directory blocks atomically, but atomically +updating two independent directory blocks is not an atomic operation. + +Here's the steps the filesystem may go through to move a directory: ``` .--------. |root dir|-. @@ -716,18 +769,135 @@ v '--------' ``` -Note that once again we don't care about the ordering of directories in the -linked-list, so we can simply leave directories in their old positions. This -does make the diagrams a bit hard to draw, but the littlefs doesn't really -care. +We can leave any orphans up to the deorphan step to collect, but that doesn't +help the case where dir A has both dir B and the root dir as parents if we +lose power inconveniently. -It's also worth noting that once again we have an operation that isn't actually -atomic. After we add directory A to directory B, we could lose power, leaving -directory A as a part of both the root directory and directory B. However, -there isn't anything inherent to the littlefs that prevents a directory from -having multiple parents, so in this case, we just allow that to happen. Extra -care is taken to only remove a directory from the linked-list if there are -no parents left in the filesystem. +Initially, you might think this is fine. Dir A _might_ end up with two parents, +but the filesystem will still work as intended. But then this raises the +question of what do we do when the dir A wears out? For other directory blocks +we can update the parent pointer, but for a dir with two parents we would need +work out how to update both parents. And the check for multiple parents would +need to be carried out for every directory, even if the directory has never +been moved. + +It also presents a bad user-experience, since the condition of ending up with +two parents is rare, it's unlikely user-level code will be prepared. Just think +about how a user would recover from a multi-parented directory. They can't just +remove one directory, since remove would report the directory as "not empty". + +Other atomic filesystems simple COW the entire directory tree. But this +introduces a significant bit of complexity, which leads to code size, along +with a surprisingly expensive runtime cost during what most users assume is +a single pointer update. + +Another option is to update the directory block we're moving from to point +to the destination with a sort of predicate that we have moved if the +destination exists. Unfortunately, the omnipresent concern of wear could +cause any of these directory entries to change blocks, and changing the +entry size before a move introduces complications if it spills out of +the current directory block. + +So how do we go about moving a directory atomically? + +We rely on the improbableness of power loss. + +Power loss during a move is certainly possible, but it's actually relatively +rare. Unless a device is writing to a filesystem constantly, it's unlikely that +a power loss will occur during filesystem activity. We still need to handle +the condition, but runtime during a power loss takes a back seat to the runtime +during normal operations. + +So what littlefs does is unelegantly simple. When littlefs moves a file, it +marks the file as "moving". This is stored as a single bit in the directory +entry and doesn't take up much space. Then littlefs moves the directory, +finishing with the complete remove of the "moving" directory entry. + +``` + .--------. + |root dir|-. + | pair 0 | | +.--------| |-' +| '--------' +| .-' '-. +| v v +| .--------. .--------. +'->| dir A |->| dir B | + | pair 0 | | pair 0 | + | | | | + '--------' '--------' + +| update root directory to mark directory A as moving +v + + .----------. + |root dir |-. + | pair 0 | | +.-------| moving A!|-' +| '----------' +| .-' '-. +| v v +| .--------. .--------. +'->| dir A |->| dir B | + | pair 0 | | pair 0 | + | | | | + '--------' '--------' + +| update directory B to point to directory A +v + + .----------. + |root dir |-. + | pair 0 | | +.-------| moving A!|-' +| '----------' +| .-----' '-. +| | v +| | .--------. +| | .->| dir B | +| | | | pair 0 | +| | | | | +| | | '--------' +| | .-------' +| v v | +| .--------. | +'->| dir A |-' + | pair 0 | + | | + '--------' + +| update root to no longer contain directory A +v + .--------. + |root dir|-. + | pair 0 | | +.----| |-' +| '--------' +| | +| v +| .--------. +| .->| dir B | +| | | pair 0 | +| '--| |-. +| '--------' | +| | | +| v | +| .--------. | +'--->| dir A |-' + | pair 0 | + | | + '--------' +``` + +Now, if we run into a directory entry that has been marked as "moved", one +of two things is possible. Either the directory entry exists elsewhere in the +filesystem, or it doesn't. This is a O(n) operation, but only occurs in the +unlikely case we lost power during a move. + +And we can easily fix the "moved" directory entry. Since we're already scanning +the filesystem during the deorphan step, we can also check for moved entries. +If we find one, we either remove the "moved" marking or remove the whole entry +if it exists elsewhere in the filesystem. ## Wear awareness @@ -955,18 +1125,18 @@ So, to summarize: 1. The littlefs is composed of directory blocks 2. Each directory is a linked-list of metadata pairs -3. These metadata pairs can be updated atomically by alternative which +3. These metadata pairs can be updated atomically by alternating which metadata block is active 4. Directory blocks contain either references to other directories or files -5. Files are represented by copy-on-write CTZ linked-lists -6. The CTZ linked-lists support appending in O(1) and reading in O(n logn) -7. Blocks are allocated by scanning the filesystem for used blocks in a +5. Files are represented by copy-on-write CTZ skip-lists which support O(1) + append and O(n logn) reading +6. Blocks are allocated by scanning the filesystem for used blocks in a fixed-size lookahead region is that stored in a bit-vector -8. To facilitate scanning the filesystem, all directories are part of a +7. To facilitate scanning the filesystem, all directories are part of a linked-list that is threaded through the entire filesystem -9. If a block develops an error, the littlefs allocates a new block, and +8. If a block develops an error, the littlefs allocates a new block, and moves the data and references of the old block to the new. -10. Any case where an atomic operation is not possible, it is taken care of +9. Any case where an atomic operation is not possible, mistakes are resolved by a deorphan step that occurs on the first allocation after boot That's the little filesystem. Thanks for reading! diff --git a/Makefile b/Makefile index 2c9e8bfafa..bd5bd905a1 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ size: $(OBJ) .SUFFIXES: test: test_format test_dirs test_files test_seek test_parallel \ - test_alloc test_paths test_orphan test_corrupt + test_alloc test_paths test_orphan test_move test_corrupt test_%: tests/test_%.sh ./$< diff --git a/README.md b/README.md index e9e63b3722..a7ba1c6132 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,8 @@ the littlefs was developed with the goal of learning more about filesystem design by tackling the relative unsolved problem of managing a robust filesystem resilient to power loss on devices with limited RAM and ROM. More detail on the solutions and tradeoffs incorporated into this filesystem -can be found in [DESIGN.md](DESIGN.md). +can be found in [DESIGN.md](DESIGN.md). The specification for the layout +of the filesystem on disk can be found in [SPEC.md](SPEC.md). ## Testing diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000000..b80892ec88 --- /dev/null +++ b/SPEC.md @@ -0,0 +1,370 @@ +## The little filesystem technical specification + +This is the technical specification of the little filesystem. This document +covers the technical details of how the littlefs is stored on disk for +introspection and tooling development. This document assumes you are +familiar with the design of the littlefs, for more info on how littlefs +works check out [DESIGN.md](DESIGN.md). + +``` + | | | .---._____ + .-----. | | +--|o |---| littlefs | +--| |---| | + '-----' '----------' + | | | +``` + +## Some important details + +- The littlefs is a block-based filesystem. This is, the disk is divided into + an array of evenly sized blocks that are used as the logical unit of storage + in littlefs. Block pointers are stored in 32 bits. + +- There is no explicit free-list stored on disk, the littlefs only knows what + is in use in the filesystem. + +- The littlefs uses the value of 0xffffffff to represent a null block-pointer. + +- All values in littlefs are stored in little-endian byte order. + +## Directories / Metadata pairs + +Metadata pairs form the backbone of the littlefs and provide a system for +atomic updates. Even the superblock is stored in a metadata pair. + +As their name suggests, a metadata pair is stored in two blocks, with one block +acting as a redundant backup in case the other is corrupted. These two blocks +could be anywhere in the disk and may not be next to each other, so any +pointers to directory pairs need to be stored as two block pointers. + +Here's the layout of metadata blocks on disk: + +| offset | size | description | +|--------|---------------|----------------| +| 0x00 | 32 bits | revision count | +| 0x04 | 32 bits | dir size | +| 0x08 | 64 bits | tail pointer | +| 0x10 | size-16 bytes | dir entries | +| 0x00+s | 32 bits | crc | + +**Revision count** - Incremented every update, only the uncorrupted +metadata-block with the most recent revision count contains the valid metadata. +Comparison between revision counts must use sequence comparison since the +revision counts may overflow. + +**Dir size** - Size in bytes of the contents in the current metadata block, +including the metadata-pair metadata. Additionally, the highest bit of the +dir size may be set to indicate that the directory's contents continue on the +next metadata-pair pointed to by the tail pointer. + +**Tail pointer** - Pointer to the next metadata-pair in the filesystem. +A null pair-pointer (0xffffffff, 0xffffffff) indicates the end of the list. +If the highest bit in the dir size is set, this points to the next +metadata-pair in the current directory, otherwise it points to an arbitrary +metadata-pair. Starting with the superblock, the tail-pointers form a +linked-list containing all metadata-pairs in the filesystem. + +**CRC** - 32 bit CRC used to detect corruption from power-lost, from block +end-of-life, or just from noise on the storage bus. The CRC is appended to +the end of each metadata-block. The littlefs uses the standard CRC-32, which +uses a polynomial of 0x04c11db7, initialized with 0xffffffff. + +Here's an example of a simple directory stored on disk: +``` +(32 bits) revision count = 10 (0x0000000a) +(32 bits) dir size = 154 bytes, end of dir (0x0000009a) +(64 bits) tail pointer = 37, 36 (0x00000025, 0x00000024) +(32 bits) crc = 0xc86e3106 + +00000000: 0a 00 00 00 9a 00 00 00 25 00 00 00 24 00 00 00 ........%...$... +00000010: 22 08 00 03 05 00 00 00 04 00 00 00 74 65 61 22 "...........tea" +00000020: 08 00 06 07 00 00 00 06 00 00 00 63 6f 66 66 65 ...........coffe +00000030: 65 22 08 00 04 09 00 00 00 08 00 00 00 73 6f 64 e"...........sod +00000040: 61 22 08 00 05 1d 00 00 00 1c 00 00 00 6d 69 6c a"...........mil +00000050: 6b 31 22 08 00 05 1f 00 00 00 1e 00 00 00 6d 69 k1"...........mi +00000060: 6c 6b 32 22 08 00 05 21 00 00 00 20 00 00 00 6d lk2"...!... ...m +00000070: 69 6c 6b 33 22 08 00 05 23 00 00 00 22 00 00 00 ilk3"...#..."... +00000080: 6d 69 6c 6b 34 22 08 00 05 25 00 00 00 24 00 00 milk4"...%...$.. +00000090: 00 6d 69 6c 6b 35 06 31 6e c8 .milk5.1n. +``` + +A note about the tail pointer linked-list: Normally, this linked-list is +threaded through the entire filesystem. However, after power-loss this +linked-list may become out of sync with the rest of the filesystem. +- The linked-list may contain a directory that has actually been removed +- The linked-list may contain a metadata pair that has not been updated after + a block in the pair has gone bad. + +The threaded linked-list must be checked for these errors before it can be +used reliably. Fortunately, the threaded linked-list can simply be ignored +if littlefs is mounted read-only. + +## Entries + +Each metadata block contains a series of entries that follow a standard +layout. An entry contains the type of the entry, along with a section for +entry-specific data, attributes, and a name. + +Here's the layout of entries on disk: + +| offset | size | description | +|---------|------------------------|----------------------------| +| 0x0 | 8 bits | entry type | +| 0x1 | 8 bits | entry length | +| 0x2 | 8 bits | attribute length | +| 0x3 | 8 bits | name length | +| 0x4 | entry length bytes | entry-specific data | +| 0x4+e | attribute length bytes | system-specific attributes | +| 0x4+e+a | name length bytes | entry name | + +**Entry type** - Type of the entry, currently this is limited to the following: +- 0x11 - file entry +- 0x22 - directory entry +- 0x2e - superblock entry + +Additionally, the type is broken into two 4 bit nibbles, with the upper nibble +specifying the type's data structure used when scanning the filesystem. The +lower nibble clarifies the type further when multiple entries share the same +data structure. + +The highest bit is reserved for marking the entry as "moved". If an entry +is marked as "moved", the entry may also exist somewhere else in the +filesystem. If the entry exists elsewhere, this entry must be treated as +though it does not exist. + +**Entry length** - Length in bytes of the entry-specific data. This does +not include the entry type size, attributes, or name. The full size in bytes +of the entry is 4 + entry length + attribute length + name length. + +**Attribute length** - Length of system-specific attributes in bytes. Since +attributes are system specific, there is not much garuntee on the values in +this section, and systems are expected to work even when it is empty. See the +[attributes](#entry-attributes) section for more details. + +**Name length** - Length of the entry name. Entry names are stored as utf8, +although most systems will probably only support ascii. Entry names can not +contain '/' and can not be '.' or '..' as these are a part of the syntax of +filesystem paths. + +Here's an example of a simple entry stored on disk: +``` +(8 bits) entry type = file (0x11) +(8 bits) entry length = 8 bytes (0x08) +(8 bits) attribute length = 0 bytes (0x00) +(8 bits) name length = 12 bytes (0x0c) +(8 bytes) entry data = 05 00 00 00 20 00 00 00 +(12 bytes) entry name = smallavacado + +00000000: 11 08 00 0c 05 00 00 00 20 00 00 00 73 6d 61 6c ........ ...smal +00000010: 6c 61 76 61 63 61 64 6f lavacado +``` + +## Superblock + +The superblock is the anchor for the littlefs. The superblock is stored as +a metadata pair containing a single superblock entry. It is through the +superblock that littlefs can access the rest of the filesystem. + +The superblock can always be found in blocks 0 and 1, however fetching the +superblock requires knowing the block size. The block size can be guessed by +searching the beginning of disk for the string "littlefs", although currently +the filesystems relies on the user providing the correct block size. + +The superblock is the most valuable block in the filesystem. It is updated +very rarely, only during format or when the root directory must be moved. It +is encouraged to always write out both superblock pairs even though it is not +required. + +Here's the layout of the superblock entry: + +| offset | size | description | +|--------|------------------------|----------------------------------------| +| 0x00 | 8 bits | entry type (0x2e for superblock entry) | +| 0x01 | 8 bits | entry length (20 bytes) | +| 0x02 | 8 bits | attribute length | +| 0x03 | 8 bits | name length (8 bytes) | +| 0x04 | 64 bits | root directory | +| 0x0c | 32 bits | block size | +| 0x10 | 32 bits | block count | +| 0x14 | 32 bits | version | +| 0x18 | attribute length bytes | system-specific attributes | +| 0x18+a | 8 bytes | magic string ("littlefs") | + +**Root directory** - Pointer to the root directory's metadata pair. + +**Block size** - Size of the logical block size used by the filesystem. + +**Block count** - Number of blocks in the filesystem. + +**Version** - The littlefs version encoded as a 32 bit value. The upper 16 bits +encodes the major version, which is incremented when a breaking-change is +introduced in the filesystem specification. The lower 16 bits encodes the +minor version, which is incremented when a backwards-compatible change is +introduced. Non-standard Attribute changes do not change the version. This +specification describes version 1.1 (0x00010001), which is the first version +of littlefs. + +**Magic string** - The magic string "littlefs" takes the place of an entry +name. + +Here's an example of a complete superblock: +``` +(32 bits) revision count = 3 (0x00000003) +(32 bits) dir size = 52 bytes, end of dir (0x00000034) +(64 bits) tail pointer = 3, 2 (0x00000003, 0x00000002) +(8 bits) entry type = superblock (0x2e) +(8 bits) entry length = 20 bytes (0x14) +(8 bits) attribute length = 0 bytes (0x00) +(8 bits) name length = 8 bytes (0x08) +(64 bits) root directory = 3, 2 (0x00000003, 0x00000002) +(32 bits) block size = 512 bytes (0x00000200) +(32 bits) block count = 1024 blocks (0x00000400) +(32 bits) version = 1.1 (0x00010001) +(8 bytes) magic string = littlefs +(32 bits) crc = 0xc50b74fa + +00000000: 03 00 00 00 34 00 00 00 03 00 00 00 02 00 00 00 ....4........... +00000010: 2e 14 00 08 03 00 00 00 02 00 00 00 00 02 00 00 ................ +00000020: 00 04 00 00 01 00 01 00 6c 69 74 74 6c 65 66 73 ........littlefs +00000030: fa 74 0b c5 .t.. +``` + +## Directory entries + +Directories are stored in entries with a pointer to the first metadata pair +in the directory. Keep in mind that a directory may be composed of multiple +metadata pairs connected by the tail pointer when the highest bit in the dir +size is set. + +Here's the layout of a directory entry: + +| offset | size | description | +|--------|------------------------|-----------------------------------------| +| 0x0 | 8 bits | entry type (0x22 for directory entries) | +| 0x1 | 8 bits | entry length (8 bytes) | +| 0x2 | 8 bits | attribute length | +| 0x3 | 8 bits | name length | +| 0x4 | 64 bits | directory pointer | +| 0xc | attribute length bytes | system-specific attributes | +| 0xc+a | name length bytes | directory name | + +**Directory pointer** - Pointer to the first metadata pair in the directory. + +Here's an example of a directory entry: +``` +(8 bits) entry type = directory (0x22) +(8 bits) entry length = 8 bytes (0x08) +(8 bits) attribute length = 0 bytes (0x00) +(8 bits) name length = 3 bytes (0x03) +(64 bits) directory pointer = 5, 4 (0x00000005, 0x00000004) +(3 bytes) name = tea + +00000000: 22 08 00 03 05 00 00 00 04 00 00 00 74 65 61 "...........tea +``` + +## File entries + +Files are stored in entries with a pointer to the head of the file and the +size of the file. This is enough information to determine the state of the +CTZ skip-list that is being referenced. + +How files are actually stored on disk is a bit complicated. The full +explanation of CTZ skip-lists can be found in [DESIGN.md](DESIGN.md#ctz-skip-lists). + +A terribly quick summary: For every nth block where n is divisible by 2^x, +the block contains a pointer to block n-2^x. These pointers are stored in +increasing order of x in each block of the file preceding the data in the +block. + +The maximum number of pointers in a block is bounded by the maximum file size +divided by the block size. With 32 bits for file size, this results in a +minimum block size of 104 bytes. + +Here's the layout of a file entry: + +| offset | size | description | +|--------|------------------------|------------------------------------| +| 0x0 | 8 bits | entry type (0x11 for file entries) | +| 0x1 | 8 bits | entry length (8 bytes) | +| 0x2 | 8 bits | attribute length | +| 0x3 | 8 bits | name length | +| 0x4 | 32 bits | file head | +| 0x8 | 32 bits | file size | +| 0xc | attribute length bytes | system-specific attributes | +| 0xc+a | name length bytes | directory name | + +**File head** - Pointer to the block that is the head of the file's CTZ +skip-list. + +**File size** - Size of file in bytes. + +Here's an example of a file entry: +``` +(8 bits) entry type = file (0x11) +(8 bits) entry length = 8 bytes (0x08) +(8 bits) attribute length = 0 bytes (0x00) +(8 bits) name length = 12 bytes (0x03) +(32 bits) file head = 543 (0x0000021f) +(32 bits) file size = 256 KB (0x00040000) +(12 bytes) name = largeavacado + +00000000: 11 08 00 0c 1f 02 00 00 00 00 04 00 6c 61 72 67 ............larg +00000010: 65 61 76 61 63 61 64 6f eavacado +``` + +## Entry attributes + +Each dir entry can have up to 256 bytes of system-specific attributes. Since +these attributes are system-specific, they may not be portable between +different systems. For this reason, all attributes must be optional. A minimal +littlefs driver must be able to get away with supporting no attributes at all. + +For some level of portability, littlefs has a simple scheme for attributes. +Each attribute is prefixes with an 8-bit type that indicates what the attribute +is. The length of attributes may also be determined from this type. Attributes +in an entry should be sorted based on portability, since attribute parsing +will end when it hits the first attribute it does not understand. + +Each system should choose a 4-bit value to prefix all attribute types with to +avoid conflicts with other systems. Additionally, littlefs drivers that support +attributes should provide a "ignore attributes" flag to users in case attribute +conflicts do occur. + +Attribute types prefixes with 0x0 and 0xf are currently reserved for future +standard attributes. Standard attributes will be added to this document in +that case. + +Here's an example of non-standard time attribute: +``` +(8 bits) attribute type = time (0xc1) +(72 bits) time in seconds = 1506286115 (0x0059c81a23) + +00000000: c1 23 1a c8 59 00 .#..Y. +``` + +Here's an example of non-standard permissions attribute: +``` +(8 bits) attribute type = permissions (0xc2) +(16 bits) permission bits = rw-rw-r-- (0x01b4) + +00000000: c2 b4 01 ... +``` + +Here's what a dir entry may look like with these attributes: +``` +(8 bits) entry type = file (0x11) +(8 bits) entry length = 8 bytes (0x08) +(8 bits) attribute length = 9 bytes (0x09) +(8 bits) name length = 12 bytes (0x0c) +(8 bytes) entry data = 05 00 00 00 20 00 00 00 +(8 bits) attribute type = time (0xc1) +(72 bits) time in seconds = 1506286115 (0x0059c81a23) +(8 bits) attribute type = permissions (0xc2) +(16 bits) permission bits = rw-rw-r-- (0x01b4) +(12 bytes) entry name = smallavacado + +00000000: 11 08 09 0c 05 00 00 00 20 00 00 00 c1 23 1a c8 ........ ....#.. +00000010: 59 00 c2 b4 01 73 6d 61 6c 6c 61 76 61 63 61 64 Y....smallavacad +00000020: 6f o +``` diff --git a/emubd/lfs_emubd.c b/emubd/lfs_emubd.c index b38d9d3361..ca2b6b928e 100644 --- a/emubd/lfs_emubd.c +++ b/emubd/lfs_emubd.c @@ -1,8 +1,19 @@ /* * Block device emulated on standard files * - * Copyright (c) 2017 Christopher Haster - * Distributed under the Apache 2.0 license + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include "emubd/lfs_emubd.h" diff --git a/emubd/lfs_emubd.h b/emubd/lfs_emubd.h index 083b2ce362..4f87ccecf1 100644 --- a/emubd/lfs_emubd.h +++ b/emubd/lfs_emubd.h @@ -1,8 +1,19 @@ /* * Block device emulated on standard files * - * Copyright (c) 2017 Christopher Haster - * Distributed under the Apache 2.0 license + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #ifndef LFS_EMUBD_H #define LFS_EMUBD_H diff --git a/lfs.c b/lfs.c index dc4a955ef3..50760be323 100644 --- a/lfs.c +++ b/lfs.c @@ -1,8 +1,19 @@ /* * The little filesystem * - * Copyright (c) 2017 Christopher Haster - * Distributed under the Apache 2.0 license + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include "lfs.h" #include "lfs_util.h" @@ -253,6 +264,7 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir); static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *parent, lfs_entry_t *entry); +static int lfs_moved(lfs_t *lfs, const void *e); static int lfs_relocate(lfs_t *lfs, const lfs_block_t oldpair[2], const lfs_block_t newpair[2]); int lfs_deorphan(lfs_t *lfs); @@ -274,14 +286,6 @@ static int lfs_alloc_lookahead(void *p, lfs_block_t block) { } static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { - // deorphan if we haven't yet, only needed once after poweron - if (!lfs->deorphaned) { - int err = lfs_deorphan(lfs); - if (err) { - return err; - } - } - while (true) { while (true) { // check if we have looked at all blocks since last ack @@ -438,7 +442,10 @@ struct lfs_region { static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir, const struct lfs_region *regions, int count) { + // increment revision count dir->d.rev += 1; + + // keep pairs in order such that pair[0] is most recent lfs_pairswap(dir->pair); for (int i = 0; i < count; i++) { dir->d.size += regions[i].newlen - regions[i].oldlen; @@ -627,8 +634,8 @@ static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { if (!(pdir.d.size & 0x80000000)) { return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ - {entry->off, lfs_entry_size(entry), NULL, 0}, - }, 1); + {entry->off, lfs_entry_size(entry), NULL, 0}, + }, 1); } else { pdir.d.size &= dir->d.size | 0x7fffffff; pdir.d.tail[0] = dir->d.tail[0]; @@ -636,9 +643,26 @@ static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { return lfs_dir_commit(lfs, &pdir, NULL, 0); } } else { - return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ - {entry->off, lfs_entry_size(entry), NULL, 0}, - }, 1); + int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, lfs_entry_size(entry), NULL, 0}, + }, 1); + if (err) { + return err; + } + + // shift over any files that are affected + for (lfs_file_t *f = lfs->files; f; f = f->next) { + if (lfs_paircmp(f->pair, dir->pair) == 0) { + if (f->poff == entry->off) { + f->pair[0] = 0xffffffff; + f->pair[1] = 0xffffffff; + } else if (f->poff > entry->off) { + f->poff -= lfs_entry_size(entry); + } + } + } + + return 0; } } @@ -722,8 +746,8 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, return err; } - if ((entry->d.type != LFS_TYPE_REG && - entry->d.type != LFS_TYPE_DIR) || + if (((0x7f & entry->d.type) != LFS_TYPE_REG && + (0x7f & entry->d.type) != LFS_TYPE_DIR) || entry->d.nlen != pathlen) { continue; } @@ -741,6 +765,16 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, } } + // check that entry has not been moved + if (entry->d.type & 0x80) { + int moved = lfs_moved(lfs, &entry->d.u); + if (moved < 0 || moved) { + return (moved < 0) ? moved : LFS_ERR_NOENT; + } + + entry->d.type &= ~0x80; + } + pathname += pathlen; pathname += strspn(pathname, "/"); if (pathname[0] == '\0') { @@ -764,6 +798,14 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, /// Top level directory operations /// int lfs_mkdir(lfs_t *lfs, const char *path) { + // deorphan if we haven't yet, needed at most once after poweron + if (!lfs->deorphaned) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + // fetch parent directory lfs_dir_t cwd; int err = lfs_dir_fetch(lfs, &cwd, lfs->root); @@ -880,10 +922,26 @@ int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { return (err == LFS_ERR_NOENT) ? 0 : err; } - if (entry.d.type == LFS_TYPE_REG || - entry.d.type == LFS_TYPE_DIR) { - break; + if ((0x7f & entry.d.type) != LFS_TYPE_REG && + (0x7f & entry.d.type) != LFS_TYPE_DIR) { + continue; } + + // check that entry has not been moved + if (entry.d.type & 0x80) { + int moved = lfs_moved(lfs, &entry.d.u); + if (moved < 0) { + return moved; + } + + if (moved) { + continue; + } + + entry.d.type &= ~0x80; + } + + break; } info->type = entry.d.type; @@ -947,12 +1005,11 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { /// File index list operations /// static int lfs_index(lfs_t *lfs, lfs_off_t *off) { lfs_off_t i = 0; - lfs_size_t words = lfs->cfg->block_size / 4; while (*off >= lfs->cfg->block_size) { i += 1; *off -= lfs->cfg->block_size; - *off += 4*lfs_min(lfs_ctz(i)+1, words-1); + *off += 4*(lfs_ctz(i) + 1); } return i; @@ -970,12 +1027,11 @@ static int lfs_index_find(lfs_t *lfs, lfs_off_t current = lfs_index(lfs, &(lfs_off_t){size-1}); lfs_off_t target = lfs_index(lfs, &pos); - lfs_size_t words = lfs->cfg->block_size / 4; while (current > target) { lfs_size_t skip = lfs_min( lfs_npw2(current-target+1) - 1, - lfs_min(lfs_ctz(current)+1, words-1) - 1); + lfs_ctz(current)); int err = lfs_cache_read(lfs, rcache, pcache, head, 4*skip, &head, 4); if (err) { @@ -1044,8 +1100,7 @@ static int lfs_index_extend(lfs_t *lfs, // append block index += 1; - lfs_size_t words = lfs->cfg->block_size / 4; - lfs_size_t skips = lfs_min(lfs_ctz(index)+1, words-1); + lfs_size_t skips = lfs_ctz(index) + 1; for (lfs_off_t i = 0; i < skips; i++) { int err = lfs_cache_prog(lfs, pcache, rcache, @@ -1113,6 +1168,14 @@ static int lfs_index_traverse(lfs_t *lfs, /// Top level file operations /// int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags) { + // deorphan if we haven't yet, needed at most once after poweron + if ((flags & 3) != LFS_O_RDONLY && !lfs->deorphaned) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + // allocate entry for file if it doesn't exist lfs_dir_t cwd; int err = lfs_dir_fetch(lfs, &cwd, lfs->root); @@ -1598,6 +1661,14 @@ int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { } int lfs_remove(lfs_t *lfs, const char *path) { + // deorphan if we haven't yet, needed at most once after poweron + if (!lfs->deorphaned) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + lfs_dir_t cwd; int err = lfs_dir_fetch(lfs, &cwd, lfs->root); if (err) { @@ -1629,22 +1700,18 @@ int lfs_remove(lfs_t *lfs, const char *path) { return err; } - // shift over any files that are affected - for (lfs_file_t *f = lfs->files; f; f = f->next) { - if (lfs_paircmp(f->pair, cwd.pair) == 0) { - if (f->poff == entry.off) { - f->pair[0] = 0xffffffff; - f->pair[1] = 0xffffffff; - } else if (f->poff > entry.off) { - f->poff -= lfs_entry_size(&entry); - } - } - } - - // if we were a directory, just run a deorphan step, this should - // collect us, although is expensive + // if we were a directory, find pred, replace tail if (entry.d.type == LFS_TYPE_DIR) { - int err = lfs_deorphan(lfs); + int res = lfs_pred(lfs, dir.pair, &cwd); + if (res < 0) { + return res; + } + + assert(res); // must have pred + cwd.d.tail[0] = dir.d.tail[0]; + cwd.d.tail[1] = dir.d.tail[1]; + + int err = lfs_dir_commit(lfs, &cwd, NULL, 0); if (err) { return err; } @@ -1654,6 +1721,14 @@ int lfs_remove(lfs_t *lfs, const char *path) { } int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { + // deorphan if we haven't yet, needed at most once after poweron + if (!lfs->deorphaned) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + // find old entry lfs_dir_t oldcwd; int err = lfs_dir_fetch(lfs, &oldcwd, lfs->root); @@ -1679,7 +1754,9 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { if (err && (err != LFS_ERR_NOENT || strchr(newpath, '/') != NULL)) { return err; } + bool prevexists = (err != LFS_ERR_NOENT); + bool samepair = (lfs_paircmp(oldcwd.pair, newcwd.pair) == 0); // must have same type if (prevexists && preventry.d.type != oldentry.d.type) { @@ -1699,9 +1776,22 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { } } + // mark as moving + oldentry.d.type |= 0x80; + err = lfs_dir_update(lfs, &oldcwd, &oldentry, NULL); + if (err) { + return err; + } + + // update pair if newcwd == oldcwd + if (samepair) { + newcwd = oldcwd; + } + // move to new location lfs_entry_t newentry = preventry; newentry.d = oldentry.d; + newentry.d.type &= ~0x80; newentry.d.nlen = strlen(newpath); if (prevexists) { @@ -1716,39 +1806,29 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { } } - // fetch again in case newcwd == oldcwd - err = lfs_dir_fetch(lfs, &oldcwd, oldcwd.pair); - if (err) { - return err; + // update pair if newcwd == oldcwd + if (samepair) { + oldcwd = newcwd; } - err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath); - if (err) { - return err; - } - - // remove from old location + // remove old entry err = lfs_dir_remove(lfs, &oldcwd, &oldentry); if (err) { return err; } - // shift over any files that are affected - for (lfs_file_t *f = lfs->files; f; f = f->next) { - if (lfs_paircmp(f->pair, oldcwd.pair) == 0) { - if (f->poff == oldentry.off) { - f->pair[0] = 0xffffffff; - f->pair[1] = 0xffffffff; - } else if (f->poff > oldentry.off) { - f->poff -= lfs_entry_size(&oldentry); - } - } - } - - // if we were a directory, just run a deorphan step, this should - // collect us, although is expensive + // if we were a directory, find pred, replace tail if (prevexists && preventry.d.type == LFS_TYPE_DIR) { - int err = lfs_deorphan(lfs); + int res = lfs_pred(lfs, dir.pair, &newcwd); + if (res < 0) { + return res; + } + + assert(res); // must have pred + newcwd.d.tail[0] = dir.d.tail[0]; + newcwd.d.tail[1] = dir.d.tail[1]; + + int err = lfs_dir_commit(lfs, &newcwd, NULL, 0); if (err) { return err; } @@ -1797,6 +1877,10 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { } } + // check that the block size is large enough to fit ctz pointers + assert(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4)) + <= lfs->cfg->block_size); + // setup default state lfs->root[0] = 0xffffffff; lfs->root[1] = 0xffffffff; @@ -1917,16 +2001,22 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { lfs_dir_t dir; lfs_superblock_t superblock; err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + if (!err) { - err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d), + int err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d), &superblock.d, sizeof(superblock.d)); + if (err) { + return err; + } lfs->root[0] = superblock.d.root[0]; lfs->root[1] = superblock.d.root[1]; } - if (err == LFS_ERR_CORRUPT || - memcmp(superblock.d.magic, "littlefs", 8) != 0) { + if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { LFS_ERROR("Invalid superblock at %d %d", dir.pair[0], dir.pair[1]); return LFS_ERR_CORRUPT; } @@ -1938,7 +2028,7 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { return LFS_ERR_INVAL; } - return err; + return 0; } int lfs_unmount(lfs_t *lfs) { @@ -1979,7 +2069,7 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { } dir.off += lfs_entry_size(&entry); - if ((0xf & entry.d.type) == (0xf & LFS_TYPE_REG)) { + if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) { int err = lfs_index_traverse(lfs, &lfs->rcache, NULL, entry.d.u.file.head, entry.d.u.file.size, cb, data); if (err) { @@ -2069,7 +2159,7 @@ static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2], break; } - if (((0xf & entry->d.type) == (0xf & LFS_TYPE_DIR)) && + if (((0x70 & entry->d.type) == (0x70 & LFS_TYPE_DIR)) && lfs_paircmp(entry->d.u.dir, dir) == 0) { return true; } @@ -2079,6 +2169,46 @@ static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2], return false; } +static int lfs_moved(lfs_t *lfs, const void *e) { + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + // skip superblock + lfs_dir_t cwd; + int err = lfs_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + // iterate over all directory directory entries + lfs_entry_t entry; + while (!lfs_pairisnull(cwd.d.tail)) { + int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail); + if (err) { + return err; + } + + while (true) { + int err = lfs_dir_next(lfs, &cwd, &entry); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + if (!(0x80 & entry.d.type) && + memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) { + return true; + } + } + } + + return false; +} + static int lfs_relocate(lfs_t *lfs, const lfs_block_t oldpair[2], const lfs_block_t newpair[2]) { // find parent @@ -2119,7 +2249,7 @@ static int lfs_relocate(lfs_t *lfs, if (res) { // just replace bad pair, no desync can occur parent.d.tail[0] = newpair[0]; - parent.d.tail[0] = newpair[0]; + parent.d.tail[1] = newpair[1]; return lfs_dir_commit(lfs, &parent, NULL, 0); } @@ -2130,27 +2260,22 @@ static int lfs_relocate(lfs_t *lfs, int lfs_deorphan(lfs_t *lfs) { lfs->deorphaned = true; + if (lfs_pairisnull(lfs->root)) { return 0; } - lfs_dir_t pdir; - lfs_dir_t cdir; + lfs_dir_t pdir = {.d.size = 0x80000000}; + lfs_dir_t cwd = {.d.tail[0] = 0, .d.tail[1] = 1}; - // skip superblock - int err = lfs_dir_fetch(lfs, &pdir, (const lfs_block_t[2]){0, 1}); - if (err) { - return err; - } - - // iterate over all directories - while (!lfs_pairisnull(pdir.d.tail)) { - int err = lfs_dir_fetch(lfs, &cdir, pdir.d.tail); + // iterate over all directory directory entries + while (!lfs_pairisnull(cwd.d.tail)) { + int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail); if (err) { return err; } - // only check head blocks + // check head blocks for orphans if (!(0x80000000 & pdir.d.size)) { // check if we have a parent lfs_dir_t parent; @@ -2162,10 +2287,11 @@ int lfs_deorphan(lfs_t *lfs) { if (!res) { // we are an orphan - LFS_DEBUG("Orphan %d %d", pdir.d.tail[0], pdir.d.tail[1]); + LFS_DEBUG("Found orphan %d %d", + pdir.d.tail[0], pdir.d.tail[1]); - pdir.d.tail[0] = cdir.d.tail[0]; - pdir.d.tail[1] = cdir.d.tail[1]; + pdir.d.tail[0] = cwd.d.tail[0]; + pdir.d.tail[1] = cwd.d.tail[1]; err = lfs_dir_commit(lfs, &pdir, NULL, 0); if (err) { @@ -2177,7 +2303,8 @@ int lfs_deorphan(lfs_t *lfs) { if (!lfs_pairsync(entry.d.u.dir, pdir.d.tail)) { // we have desynced - LFS_DEBUG("Desync %d %d", entry.d.u.dir[0], entry.d.u.dir[1]); + LFS_DEBUG("Found desync %d %d", + entry.d.u.dir[0], entry.d.u.dir[1]); pdir.d.tail[0] = entry.d.u.dir[0]; pdir.d.tail[1] = entry.d.u.dir[1]; @@ -2191,7 +2318,45 @@ int lfs_deorphan(lfs_t *lfs) { } } - memcpy(&pdir, &cdir, sizeof(pdir)); + // check entries for moves + lfs_entry_t entry; + while (true) { + int err = lfs_dir_next(lfs, &cwd, &entry); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + // found moved entry + if (entry.d.type & 0x80) { + int moved = lfs_moved(lfs, &entry.d.u); + if (moved < 0) { + return moved; + } + + if (moved) { + LFS_DEBUG("Found move %d %d", + entry.d.u.dir[0], entry.d.u.dir[1]); + int err = lfs_dir_remove(lfs, &cwd, &entry); + if (err) { + return err; + } + } else { + LFS_DEBUG("Found partial move %d %d", + entry.d.u.dir[0], entry.d.u.dir[1]); + entry.d.type &= ~0x80; + int err = lfs_dir_update(lfs, &cwd, &entry, NULL); + if (err) { + return err; + } + } + } + } + + memcpy(&pdir, &cwd, sizeof(pdir)); } return 0; diff --git a/lfs.h b/lfs.h index ed232f68f6..270a65efb9 100644 --- a/lfs.h +++ b/lfs.h @@ -1,8 +1,19 @@ /* * The little filesystem * - * Copyright (c) 2017 Christopher Haster - * Distributed under the Apache 2.0 license + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #ifndef LFS_H #define LFS_H @@ -46,7 +57,7 @@ enum lfs_error { enum lfs_type { LFS_TYPE_REG = 0x11, LFS_TYPE_DIR = 0x22, - LFS_TYPE_SUPERBLOCK = 0xe2, + LFS_TYPE_SUPERBLOCK = 0x2e, }; // File open flags @@ -434,5 +445,8 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); // Returns a negative error code on failure. int lfs_deorphan(lfs_t *lfs); +// TODO doc +int lfs_deduplicate(lfs_t *lfs); + #endif diff --git a/lfs_util.c b/lfs_util.c index 0a7234c3a5..567977bae4 100644 --- a/lfs_util.c +++ b/lfs_util.c @@ -1,8 +1,19 @@ /* * lfs util functions * - * Copyright (c) 2017 Christopher Haster - * Distributed under the Apache 2.0 license + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include "lfs_util.h" diff --git a/lfs_util.h b/lfs_util.h index 95706d1c1d..cee7644fe0 100644 --- a/lfs_util.h +++ b/lfs_util.h @@ -1,8 +1,19 @@ /* * lfs utility functions * - * Copyright (c) 2017 Christopher Haster - * Distributed under the Apache 2.0 license + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #ifndef LFS_UTIL_H #define LFS_UTIL_H @@ -12,7 +23,8 @@ #include -// Builtin functions +// Builtin functions, these may be replaced by more +// efficient implementations in the system static inline uint32_t lfs_max(uint32_t a, uint32_t b) { return (a > b) ? a : b; } @@ -33,10 +45,12 @@ static inline int lfs_scmp(uint32_t a, uint32_t b) { return (int)(unsigned)(a - b); } +// CRC-32 with polynomial = 0x04c11db7 void lfs_crc(uint32_t *crc, const void *buffer, size_t size); -// Logging functions +// Logging functions, these may be replaced by system-specific +// logging functions #define LFS_DEBUG(fmt, ...) printf("lfs debug: " fmt "\n", __VA_ARGS__) #define LFS_WARN(fmt, ...) printf("lfs warn: " fmt "\n", __VA_ARGS__) #define LFS_ERROR(fmt, ...) printf("lfs error: " fmt "\n", __VA_ARGS__) diff --git a/tests/template.fmt b/tests/template.fmt index b6663ceeb4..85f00bdd33 100644 --- a/tests/template.fmt +++ b/tests/template.fmt @@ -27,8 +27,8 @@ void test_assert(const char *file, unsigned line, }} if (v != e) {{ - printf("\033[31m%s:%u: assert %s failed, expected %jd\033[0m\n", - file, line, s, e); + fprintf(stderr, "\033[31m%s:%u: assert %s failed with %jd, " + "expected %jd\033[0m\n", file, line, s, v, e); exit(-2); }} }} diff --git a/tests/test_move.sh b/tests/test_move.sh new file mode 100755 index 0000000000..9e5ababf7e --- /dev/null +++ b/tests/test_move.sh @@ -0,0 +1,236 @@ +#!/bin/bash +set -eu + +echo "=== Move tests ===" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "a") => 0; + lfs_mkdir(&lfs, "b") => 0; + lfs_mkdir(&lfs, "c") => 0; + lfs_mkdir(&lfs, "d") => 0; + + lfs_mkdir(&lfs, "a/hi") => 0; + lfs_mkdir(&lfs, "a/hi/hola") => 0; + lfs_mkdir(&lfs, "a/hi/bonjour") => 0; + lfs_mkdir(&lfs, "a/hi/ohayo") => 0; + + lfs_file_open(&lfs, &file[0], "a/hello", LFS_O_CREAT | LFS_O_WRONLY) => 0; + lfs_file_write(&lfs, &file[0], "hola\n", 5) => 5; + lfs_file_write(&lfs, &file[0], "bonjour\n", 8) => 8; + lfs_file_write(&lfs, &file[0], "ohayo\n", 6) => 6; + lfs_file_close(&lfs, &file[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Move file ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "a/hello", "b/hello") => 0; + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "a") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "hi") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_dir_open(&lfs, &dir[0], "b") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "hello") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Move file corrupt source ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "b/hello", "c/hello") => 0; + lfs_unmount(&lfs) => 0; +TEST +rm -v blocks/7 +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "b") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_dir_open(&lfs, &dir[0], "c") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "hello") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Move file corrupt source and dest ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "c/hello", "d/hello") => 0; + lfs_unmount(&lfs) => 0; +TEST +rm -v blocks/8 +rm -v blocks/a +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "c") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "hello") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_dir_open(&lfs, &dir[0], "d") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Move dir ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "a/hi", "b/hi") => 0; + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "a") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_dir_open(&lfs, &dir[0], "b") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "hi") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Move dir corrupt source ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "b/hi", "c/hi") => 0; + lfs_unmount(&lfs) => 0; +TEST +rm -v blocks/7 +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "b") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_dir_open(&lfs, &dir[0], "c") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "hello") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "hi") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Move dir corrupt source and dest ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_rename(&lfs, "c/hi", "d/hi") => 0; + lfs_unmount(&lfs) => 0; +TEST +rm -v blocks/9 +rm -v blocks/a +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "c") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "hello") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "hi") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_dir_open(&lfs, &dir[0], "d") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Move check ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + + lfs_dir_open(&lfs, &dir[0], "a/hi") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir[0], "b/hi") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir[0], "d/hi") => LFS_ERR_NOENT; + + lfs_dir_open(&lfs, &dir[0], "c/hi") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "hola") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "bonjour") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "ohayo") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + + lfs_dir_open(&lfs, &dir[0], "a/hello") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir[0], "b/hello") => LFS_ERR_NOENT; + lfs_dir_open(&lfs, &dir[0], "d/hello") => LFS_ERR_NOENT; + + lfs_file_open(&lfs, &file[0], "c/hello", LFS_O_RDONLY) => 0; + lfs_file_read(&lfs, &file[0], buffer, 5) => 5; + memcmp(buffer, "hola\n", 5) => 0; + lfs_file_read(&lfs, &file[0], buffer, 8) => 8; + memcmp(buffer, "bonjour\n", 8) => 0; + lfs_file_read(&lfs, &file[0], buffer, 6) => 6; + memcmp(buffer, "ohayo\n", 6) => 0; + lfs_file_close(&lfs, &file[0]) => 0; + + lfs_unmount(&lfs) => 0; +TEST + + +echo "--- Results ---" +tests/stats.py From 6fc33f4d9c860e07ba286154ba6ec22c1b19e620 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 16 Oct 2017 17:52:35 -0500 Subject: [PATCH 15/46] Fixed incorrect instruction in IAR ctz implementation The RBIT instruction reverses the bits of a word, not REV --- littlefs/lfs_util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/littlefs/lfs_util.h b/littlefs/lfs_util.h index 3ca878c089..73e9d03ebe 100644 --- a/littlefs/lfs_util.h +++ b/littlefs/lfs_util.h @@ -38,7 +38,7 @@ static inline uint32_t lfs_min(uint32_t a, uint32_t b) { static inline uint32_t lfs_ctz(uint32_t a) { #ifdef __ICCARM__ - return __CLZ(__REV(a)); + return __CLZ(__RBIT(a)); #else return __builtin_ctz(a); #endif From 11440f601893be606e047b0c8534026ede870514 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 16 Oct 2017 19:31:56 -0500 Subject: [PATCH 16/46] Removed toolchain specific warnings - Comparisons with differently signed integer types - Incorrectly signed constant - Unreachable default returns - Leaked uninitialized variables in relocate goto statements --- littlefs/lfs.c | 282 +++++++++++++++++++++++++------------------------ 1 file changed, 143 insertions(+), 139 deletions(-) diff --git a/littlefs/lfs.c b/littlefs/lfs.c index 13a55146da..39bd2c1cb8 100644 --- a/littlefs/lfs.c +++ b/littlefs/lfs.c @@ -373,8 +373,8 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) { // set defaults dir->d.rev += 1; dir->d.size = sizeof(dir->d)+4; - dir->d.tail[0] = -1; - dir->d.tail[1] = -1; + dir->d.tail[0] = 0xffffffff; + dir->d.tail[1] = 0xffffffff; dir->off = sizeof(dir->d); // don't write out yet, let caller take care of that @@ -455,88 +455,91 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir, bool relocated = false; while (true) { - int err = lfs_bd_erase(lfs, dir->pair[0]); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + if (true) { + int err = lfs_bd_erase(lfs, dir->pair[0]); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; } - return err; - } - uint32_t crc = 0xffffffff; - lfs_crc(&crc, &dir->d, sizeof(dir->d)); - err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d)); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + uint32_t crc = 0xffffffff; + lfs_crc(&crc, &dir->d, sizeof(dir->d)); + err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d)); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; } - return err; - } - int i = 0; - lfs_off_t oldoff = sizeof(dir->d); - lfs_off_t newoff = sizeof(dir->d); - while (newoff < (0x7fffffff & dir->d.size)-4) { - if (i < count && regions[i].oldoff == oldoff) { - lfs_crc(&crc, regions[i].newdata, regions[i].newlen); - int err = lfs_bd_prog(lfs, dir->pair[0], - newoff, regions[i].newdata, regions[i].newlen); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + int i = 0; + lfs_off_t oldoff = sizeof(dir->d); + lfs_off_t newoff = sizeof(dir->d); + while (newoff < (0x7fffffff & dir->d.size)-4) { + if (i < count && regions[i].oldoff == oldoff) { + lfs_crc(&crc, regions[i].newdata, regions[i].newlen); + int err = lfs_bd_prog(lfs, dir->pair[0], + newoff, regions[i].newdata, regions[i].newlen); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; } - return err; - } - oldoff += regions[i].oldlen; - newoff += regions[i].newlen; - i += 1; - } else { - uint8_t data; - int err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1); - if (err) { - return err; - } - - lfs_crc(&crc, &data, 1); - err = lfs_bd_prog(lfs, dir->pair[0], newoff, &data, 1); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + oldoff += regions[i].oldlen; + newoff += regions[i].newlen; + i += 1; + } else { + uint8_t data; + int err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1); + if (err) { + return err; } - return err; + + lfs_crc(&crc, &data, 1); + err = lfs_bd_prog(lfs, dir->pair[0], newoff, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + oldoff += 1; + newoff += 1; } - - oldoff += 1; - newoff += 1; } - } - err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; } - return err; - } - err = lfs_bd_sync(lfs); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + err = lfs_bd_sync(lfs); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; } - return err; - } - // successful commit, check checksum to make sure - crc = 0xffffffff; - err = lfs_bd_crc(lfs, dir->pair[0], 0, 0x7fffffff & dir->d.size, &crc); - if (err) { - return err; - } + // successful commit, check checksum to make sure + crc = 0xffffffff; + err = lfs_bd_crc(lfs, dir->pair[0], 0, + 0x7fffffff & dir->d.size, &crc); + if (err) { + return err; + } - if (crc == 0) { - break; + if (crc == 0) { + break; + } } relocate: @@ -554,7 +557,7 @@ relocate: } // relocate half of pair - err = lfs_alloc(lfs, &dir->pair[0]); + int err = lfs_alloc(lfs, &dir->pair[0]); if (err) { return err; } @@ -791,8 +794,6 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, return err; } } - - return 0; } @@ -1020,7 +1021,7 @@ static int lfs_index_find(lfs_t *lfs, lfs_block_t head, lfs_size_t size, lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) { if (size == 0) { - *block = -1; + *block = 0xffffffff; *off = 0; return 0; } @@ -1052,59 +1053,15 @@ static int lfs_index_extend(lfs_t *lfs, lfs_block_t head, lfs_size_t size, lfs_off_t *block, lfs_block_t *off) { while (true) { - // go ahead and grab a block - int err = lfs_alloc(lfs, block); - if (err) { - return err; - } - assert(*block >= 2 && *block <= lfs->cfg->block_count); - - err = lfs_bd_erase(lfs, *block); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + if (true) { + // go ahead and grab a block + int err = lfs_alloc(lfs, block); + if (err) { + return err; } - return err; - } + assert(*block >= 2 && *block <= lfs->cfg->block_count); - if (size == 0) { - *off = 0; - return 0; - } - - size -= 1; - lfs_off_t index = lfs_index(lfs, &size); - size += 1; - - // just copy out the last block if it is incomplete - if (size != lfs->cfg->block_size) { - for (lfs_off_t i = 0; i < size; i++) { - uint8_t data; - int err = lfs_cache_read(lfs, rcache, NULL, head, i, &data, 1); - if (err) { - return err; - } - - err = lfs_cache_prog(lfs, pcache, rcache, *block, i, &data, 1); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - } - - *off = size; - return 0; - } - - // append block - index += 1; - lfs_size_t skips = lfs_ctz(index) + 1; - - for (lfs_off_t i = 0; i < skips; i++) { - int err = lfs_cache_prog(lfs, pcache, rcache, - *block, 4*i, &head, 4); + err = lfs_bd_erase(lfs, *block); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; @@ -1112,18 +1069,67 @@ static int lfs_index_extend(lfs_t *lfs, return err; } - if (i != skips-1) { - err = lfs_cache_read(lfs, rcache, NULL, head, 4*i, &head, 4); - if (err) { - return err; - } + if (size == 0) { + *off = 0; + return 0; } - assert(head >= 2 && head <= lfs->cfg->block_count); - } + size -= 1; + lfs_off_t index = lfs_index(lfs, &size); + size += 1; - *off = 4*skips; - return 0; + // just copy out the last block if it is incomplete + if (size != lfs->cfg->block_size) { + for (lfs_off_t i = 0; i < size; i++) { + uint8_t data; + int err = lfs_cache_read(lfs, rcache, NULL, + head, i, &data, 1); + if (err) { + return err; + } + + err = lfs_cache_prog(lfs, pcache, rcache, + *block, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + *off = size; + return 0; + } + + // append block + index += 1; + lfs_size_t skips = lfs_ctz(index) + 1; + + for (lfs_off_t i = 0; i < skips; i++) { + int err = lfs_cache_prog(lfs, pcache, rcache, + *block, 4*i, &head, 4); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (i != skips-1) { + err = lfs_cache_read(lfs, rcache, NULL, + head, 4*i, &head, 4); + if (err) { + return err; + } + } + + assert(head >= 2 && head <= lfs->cfg->block_count); + } + + *off = 4*skips; + return 0; + } relocate: LFS_DEBUG("Bad block at %ld", *block); @@ -1160,8 +1166,6 @@ static int lfs_index_traverse(lfs_t *lfs, index -= 1; } - - return 0; } @@ -1199,7 +1203,7 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file, entry.d.elen = sizeof(entry.d) - 4; entry.d.alen = 0; entry.d.nlen = strlen(path); - entry.d.u.file.head = -1; + entry.d.u.file.head = 0xffffffff; entry.d.u.file.size = 0; err = lfs_dir_append(lfs, &cwd, &entry, path); if (err) { @@ -1221,7 +1225,7 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file, file->pos = 0; if (flags & LFS_O_TRUNC) { - file->head = -1; + file->head = 0xffffffff; file->size = 0; } @@ -1588,13 +1592,13 @@ lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, if (whence == LFS_SEEK_SET) { file->pos = off; } else if (whence == LFS_SEEK_CUR) { - if (-off > file->pos) { + if ((lfs_off_t)-off > file->pos) { return LFS_ERR_INVAL; } file->pos = file->pos + off; } else if (whence == LFS_SEEK_END) { - if (-off > file->size) { + if ((lfs_off_t)-off > file->size) { return LFS_ERR_INVAL; } From adbd0290f8e3be482a3d631cb215d730fff5722d Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 26 Oct 2017 17:09:56 -0500 Subject: [PATCH 17/46] Added implementation of FileSystem::reformat --- LittleFileSystem.cpp | 25 +++++++++++++++++++++++++ LittleFileSystem.h | 14 +++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/LittleFileSystem.cpp b/LittleFileSystem.cpp index a827497d14..2a32683d4c 100644 --- a/LittleFileSystem.cpp +++ b/LittleFileSystem.cpp @@ -223,6 +223,31 @@ int LittleFileSystem::format(BlockDevice *bd, return 0; } +int LittleFileSystem::reformat(BlockDevice *bd) { + if (_bd) { + if (!bd) { + bd = _bd; + } + + int err = unmount(); + if (err) { + return err; + } + } + + if (!bd) { + return -ENODEV; + } + + int err = LittleFileSystem::format(bd, + _read_size, _prog_size, _block_size, _lookahead); + if (err) { + return err; + } + + return mount(bd); +} + int LittleFileSystem::remove(const char *filename) { int err = lfs_remove(&_lfs, filename); return lfs_toerror(err); diff --git a/LittleFileSystem.h b/LittleFileSystem.h index 6f1c173060..719ae7e741 100644 --- a/LittleFileSystem.h +++ b/LittleFileSystem.h @@ -65,7 +65,7 @@ public: lfs_size_t lookahead=MBED_LFS_LOOKAHEAD); virtual ~LittleFileSystem(); - /** Formats a logical drive, FDISK partitioning rule. + /** Formats a block device with the LittleFileSystem * * The block device to format should be mounted when this function is called. * @@ -107,6 +107,18 @@ public: */ virtual int unmount(); + /** Reformats a filesystem, results in an empty and mounted filesystem + * + * @param bd + * BlockDevice to reformat and mount. If NULL, the mounted + * block device will be used. + * Note: if mount fails, bd must be provided. + * Default: NULL + * + * @return 0 on success, negative error code on failure + */ + virtual int reformat(BlockDevice *bd); + /** Remove a file from the filesystem. * * @param path The name of the file to remove. From 0171b57a04c3eb6444fdf1163e0e21993445bfd8 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 30 Oct 2017 18:16:52 -0500 Subject: [PATCH 18/46] Squashed 'littlefs/' changes from 454b588..2ab150c 2ab150c Removed toolchain specific warnings 0825d34 Adopted alternative implementation for lfs_ctz_index 46e22b2 Adopted lfs_ctz_index implementation using popcount 4fdca15 Slight name change with ctz skip-list functions git-subtree-dir: littlefs git-subtree-split: 2ab150cc500d5b8233ec8ef6109efa363bf1d38c --- DESIGN.md | 96 ++++++++++++++-- lfs.c | 321 +++++++++++++++++++++++++++-------------------------- lfs_util.h | 4 + 3 files changed, 253 insertions(+), 168 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index 30c5daea99..b4b9377ac7 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -290,31 +290,32 @@ The path to data block 0 is even more quick, requiring only two jumps: We can find the runtime complexity by looking at the path to any block from the block containing the most pointers. Every step along the path divides -the search space for the block in half. This gives us a runtime of O(log n). +the search space for the block in half. This gives us a runtime of O(logn). To get to the block with the most pointers, we can perform the same steps -backwards, which keeps the asymptotic runtime at O(log n). The interesting +backwards, which puts the runtime at O(2logn) = O(logn). The interesting part about this data structure is that this optimal path occurs naturally if we greedily choose the pointer that covers the most distance without passing our target block. So now we have a representation of files that can be appended trivially with -a runtime of O(1), and can be read with a worst case runtime of O(n logn). +a runtime of O(1), and can be read with a worst case runtime of O(nlogn). Given that the the runtime is also divided by the amount of data we can store in a block, this is pretty reasonable. Unfortunately, the CTZ skip-list comes with a few questions that aren't straightforward to answer. What is the overhead? How do we handle more -pointers than we can store in a block? +pointers than we can store in a block? How do we store the skip-list in +a directory entry? One way to find the overhead per block is to look at the data structure as multiple layers of linked-lists. Each linked-list skips twice as many blocks -as the previous linked-list. Or another way of looking at it is that each +as the previous linked-list. Another way of looking at it is that each linked-list uses half as much storage per block as the previous linked-list. As we approach infinity, the number of pointers per block forms a geometric series. Solving this geometric series gives us an average of only 2 pointers per block. -![overhead per block](https://latex.codecogs.com/gif.latex?%5Clim_%7Bn%5Cto%5Cinfty%7D%5Cfrac%7B1%7D%7Bn%7D%5Csum_%7Bi%3D0%7D%5E%7Bn%7D%5Cleft%28%5Ctext%7Bctz%7D%28i%29+1%5Cright%29%20%3D%20%5Csum_%7Bi%3D0%7D%5E%7B%5Cinfty%7D%5Cfrac%7B1%7D%7B2%5Ei%7D%20%3D%202) +![overhead_per_block](https://latex.codecogs.com/svg.latex?%5Clim_%7Bn%5Cto%5Cinfty%7D%5Cfrac%7B1%7D%7Bn%7D%5Csum_%7Bi%3D0%7D%5E%7Bn%7D%5Cleft%28%5Ctext%7Bctz%7D%28i%29+1%5Cright%29%20%3D%20%5Csum_%7Bi%3D0%7D%5Cfrac%7B1%7D%7B2%5Ei%7D%20%3D%202) Finding the maximum number of pointers in a block is a bit more complicated, but since our file size is limited by the integer width we use to store the @@ -322,7 +323,7 @@ size, we can solve for it. Setting the overhead of the maximum pointers equal to the block size we get the following equation. Note that a smaller block size results in more pointers, and a larger word width results in larger pointers. -![maximum overhead](https://latex.codecogs.com/gif.latex?B%20%3D%20%5Cfrac%7Bw%7D%7B8%7D%5Cleft%5Clceil%5Clog_2%5Cleft%28%5Cfrac%7B2%5Ew%7D%7BB-2%5Cfrac%7Bw%7D%7B8%7D%7D%5Cright%29%5Cright%5Crceil) +![maximum overhead](https://latex.codecogs.com/svg.latex?B%20%3D%20%5Cfrac%7Bw%7D%7B8%7D%5Cleft%5Clceil%5Clog_2%5Cleft%28%5Cfrac%7B2%5Ew%7D%7BB-2%5Cfrac%7Bw%7D%7B8%7D%7D%5Cright%29%5Cright%5Crceil) where: B = block size in bytes @@ -335,8 +336,83 @@ widths: Since littlefs uses a 32 bit word size, we are limited to a minimum block size of 104 bytes. This is a perfectly reasonable minimum block size, with most -block sizes starting around 512 bytes. So we can avoid the additional logic -needed to avoid overflowing our block's capacity in the CTZ skip-list. +block sizes starting around 512 bytes. So we can avoid additional logic to +avoid overflowing our block's capacity in the CTZ skip-list. + +So, how do we store the skip-list in a directory entry? A naive approach would +be to store a pointer to the head of the skip-list, the length of the file +in bytes, the index of the head block in the skip-list, and the offset in the +head block in bytes. However this is a lot of information, and we can observe +that a file size maps to only one block index + offset pair. So it should be +sufficient to store only the pointer and file size. + +But there is one problem, calculating the block index + offset pair from a +file size doesn't have an obvious implementation. + +We can start by just writing down an equation. The first idea that comes to +mind is to just use a for loop to sum together blocks until we reach our +file size. We can write equation equation as a summation: + +![summation1](https://latex.codecogs.com/svg.latex?N%20%3D%20%5Csum_i%5En%5Cleft%5BB-%5Cfrac%7Bw%7D%7B8%7D%5Cleft%28%5Ctext%7Bctz%7D%28i%29+1%5Cright%29%5Cright%5D) + +where: +B = block size in bytes +w = word width in bits +n = block index in skip-list +N = file size in bytes + +And this works quite well, but is not trivial to calculate. This equation +requires O(n) to compute, which brings the entire runtime of reading a file +to O(n^2logn). Fortunately, the additional O(n) does not need to touch disk, +so it is not completely unreasonable. But if we could solve this equation into +a form that is easily computable, we can avoid a big slowdown. + +Unfortunately, the summation of the CTZ instruction presents a big challenge. +How would you even begin to reason about integrating a bitwise instruction? +Fortunately, there is a powerful tool I've found useful in these situations: +The [On-Line Encyclopedia of Integer Sequences (OEIS)](https://oeis.org/). +If we work out the first couple of values in our summation, we find that CTZ +maps to [A001511](https://oeis.org/A001511), and its partial summation maps +to [A005187](https://oeis.org/A005187), and surprisingly, both of these +sequences have relatively trivial equations! This leads us to the completely +unintuitive property: + +![mindblown](https://latex.codecogs.com/svg.latex?%5Csum_i%5En%5Cleft%28%5Ctext%7Bctz%7D%28i%29+1%5Cright%29%20%3D%202n-%5Ctext%7Bpopcount%7D%28n%29) + +where: +ctz(i) = the number of trailing bits that are 0 in i +popcount(i) = the number of bits that are 1 in i + +I find it bewildering that these two seemingly unrelated bitwise instructions +are related by this property. But if we start to disect this equation we can +see that it does hold. As n approaches infinity, we do end up with an average +overhead of 2 pointers as we find earlier. And popcount seems to handle the +error from this average as it accumulates in the CTZ skip-list. + +Now we can substitute into the original equation to get a trivial equation +for a file size: + +![summation2](https://latex.codecogs.com/svg.latex?N%20%3D%20Bn%20-%20%5Cfrac%7Bw%7D%7B8%7D%5Cleft%282n-%5Ctext%7Bpopcount%7D%28n%29%5Cright%29) + +Unfortunately, we're not quite done. The popcount function is non-injective, +so we can only find the file size from the block index, not the other way +around. However, we can solve for an n' block index that is greater than n +with an error bounded by the range of the popcount function. We can then +repeatedly substitute this n' into the original equation until the error +is smaller than the integer division. As it turns out, we only need to +perform this substitution once. Now we directly calculate our block index: + +![formulaforn](https://latex.codecogs.com/svg.latex?n%20%3D%20%5Cleft%5Clfloor%5Cfrac%7BN-%5Cfrac%7Bw%7D%7B8%7D%5Cleft%28%5Ctext%7Bpopcount%7D%5Cleft%28%5Cfrac%7BN%7D%7BB-2%5Cfrac%7Bw%7D%7B8%7D%7D-1%5Cright%29+2%5Cright%29%7D%7BB-2%5Cfrac%7Bw%7D%7B8%7D%7D%5Cright%5Crfloor) + +Now that we have our block index n, we can just plug it back into the above +equation to find the offset. However, we do need to rearrange the equation +a bit to avoid integer overflow: + +![formulaforoff](https://latex.codecogs.com/svg.latex?%5Cmathit%7Boff%7D%20%3D%20N%20-%20%5Cleft%28B-2%5Cfrac%7Bw%7D%7B8%7D%5Cright%29n%20-%20%5Cfrac%7Bw%7D%7B8%7D%5Ctext%7Bpopcount%7D%28n%29) + +The solution involves quite a bit of math, but computers are very good at math. +We can now solve for the block index + offset while only needed to store the +file size in O(1). Here is what it might look like to update a file stored with a CTZ skip-list: ``` @@ -1129,7 +1205,7 @@ So, to summarize: metadata block is active 4. Directory blocks contain either references to other directories or files 5. Files are represented by copy-on-write CTZ skip-lists which support O(1) - append and O(n logn) reading + append and O(nlogn) reading 6. Blocks are allocated by scanning the filesystem for used blocks in a fixed-size lookahead region is that stored in a bit-vector 7. To facilitate scanning the filesystem, all directories are part of a diff --git a/lfs.c b/lfs.c index 50760be323..f602197778 100644 --- a/lfs.c +++ b/lfs.c @@ -373,8 +373,8 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) { // set defaults dir->d.rev += 1; dir->d.size = sizeof(dir->d)+4; - dir->d.tail[0] = -1; - dir->d.tail[1] = -1; + dir->d.tail[0] = 0xffffffff; + dir->d.tail[1] = 0xffffffff; dir->off = sizeof(dir->d); // don't write out yet, let caller take care of that @@ -455,88 +455,91 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir, bool relocated = false; while (true) { - int err = lfs_bd_erase(lfs, dir->pair[0]); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + if (true) { + int err = lfs_bd_erase(lfs, dir->pair[0]); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; } - return err; - } - uint32_t crc = 0xffffffff; - lfs_crc(&crc, &dir->d, sizeof(dir->d)); - err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d)); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + uint32_t crc = 0xffffffff; + lfs_crc(&crc, &dir->d, sizeof(dir->d)); + err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d)); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; } - return err; - } - int i = 0; - lfs_off_t oldoff = sizeof(dir->d); - lfs_off_t newoff = sizeof(dir->d); - while (newoff < (0x7fffffff & dir->d.size)-4) { - if (i < count && regions[i].oldoff == oldoff) { - lfs_crc(&crc, regions[i].newdata, regions[i].newlen); - int err = lfs_bd_prog(lfs, dir->pair[0], - newoff, regions[i].newdata, regions[i].newlen); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + int i = 0; + lfs_off_t oldoff = sizeof(dir->d); + lfs_off_t newoff = sizeof(dir->d); + while (newoff < (0x7fffffff & dir->d.size)-4) { + if (i < count && regions[i].oldoff == oldoff) { + lfs_crc(&crc, regions[i].newdata, regions[i].newlen); + int err = lfs_bd_prog(lfs, dir->pair[0], + newoff, regions[i].newdata, regions[i].newlen); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; } - return err; - } - oldoff += regions[i].oldlen; - newoff += regions[i].newlen; - i += 1; - } else { - uint8_t data; - int err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1); - if (err) { - return err; - } - - lfs_crc(&crc, &data, 1); - err = lfs_bd_prog(lfs, dir->pair[0], newoff, &data, 1); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + oldoff += regions[i].oldlen; + newoff += regions[i].newlen; + i += 1; + } else { + uint8_t data; + int err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1); + if (err) { + return err; } - return err; + + lfs_crc(&crc, &data, 1); + err = lfs_bd_prog(lfs, dir->pair[0], newoff, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + oldoff += 1; + newoff += 1; } - - oldoff += 1; - newoff += 1; } - } - err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; } - return err; - } - err = lfs_bd_sync(lfs); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + err = lfs_bd_sync(lfs); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; } - return err; - } - // successful commit, check checksum to make sure - crc = 0xffffffff; - err = lfs_bd_crc(lfs, dir->pair[0], 0, 0x7fffffff & dir->d.size, &crc); - if (err) { - return err; - } + // successful commit, check checksum to make sure + crc = 0xffffffff; + err = lfs_bd_crc(lfs, dir->pair[0], 0, + 0x7fffffff & dir->d.size, &crc); + if (err) { + return err; + } - if (crc == 0) { - break; + if (crc == 0) { + break; + } } relocate: @@ -554,7 +557,7 @@ relocate: } // relocate half of pair - err = lfs_alloc(lfs, &dir->pair[0]); + int err = lfs_alloc(lfs, &dir->pair[0]); if (err) { return err; } @@ -791,8 +794,6 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, return err; } } - - return 0; } @@ -1003,30 +1004,31 @@ int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { /// File index list operations /// -static int lfs_index(lfs_t *lfs, lfs_off_t *off) { - lfs_off_t i = 0; - - while (*off >= lfs->cfg->block_size) { - i += 1; - *off -= lfs->cfg->block_size; - *off += 4*(lfs_ctz(i) + 1); +static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) { + lfs_off_t size = *off; + lfs_off_t b = lfs->cfg->block_size - 2*4; + lfs_off_t i = size / b; + if (i == 0) { + return 0; } + i = (size - 4*(lfs_popc(i-1)+2)) / b; + *off = size - b*i - 4*lfs_popc(i); return i; } -static int lfs_index_find(lfs_t *lfs, +static int lfs_ctz_find(lfs_t *lfs, lfs_cache_t *rcache, const lfs_cache_t *pcache, lfs_block_t head, lfs_size_t size, lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) { if (size == 0) { - *block = -1; + *block = 0xffffffff; *off = 0; return 0; } - lfs_off_t current = lfs_index(lfs, &(lfs_off_t){size-1}); - lfs_off_t target = lfs_index(lfs, &pos); + lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); + lfs_off_t target = lfs_ctz_index(lfs, &pos); while (current > target) { lfs_size_t skip = lfs_min( @@ -1047,64 +1049,20 @@ static int lfs_index_find(lfs_t *lfs, return 0; } -static int lfs_index_extend(lfs_t *lfs, +static int lfs_ctz_extend(lfs_t *lfs, lfs_cache_t *rcache, lfs_cache_t *pcache, lfs_block_t head, lfs_size_t size, lfs_off_t *block, lfs_block_t *off) { while (true) { - // go ahead and grab a block - int err = lfs_alloc(lfs, block); - if (err) { - return err; - } - assert(*block >= 2 && *block <= lfs->cfg->block_count); - - err = lfs_bd_erase(lfs, *block); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; + if (true) { + // go ahead and grab a block + int err = lfs_alloc(lfs, block); + if (err) { + return err; } - return err; - } + assert(*block >= 2 && *block <= lfs->cfg->block_count); - if (size == 0) { - *off = 0; - return 0; - } - - size -= 1; - lfs_off_t index = lfs_index(lfs, &size); - size += 1; - - // just copy out the last block if it is incomplete - if (size != lfs->cfg->block_size) { - for (lfs_off_t i = 0; i < size; i++) { - uint8_t data; - int err = lfs_cache_read(lfs, rcache, NULL, head, i, &data, 1); - if (err) { - return err; - } - - err = lfs_cache_prog(lfs, pcache, rcache, *block, i, &data, 1); - if (err) { - if (err == LFS_ERR_CORRUPT) { - goto relocate; - } - return err; - } - } - - *off = size; - return 0; - } - - // append block - index += 1; - lfs_size_t skips = lfs_ctz(index) + 1; - - for (lfs_off_t i = 0; i < skips; i++) { - int err = lfs_cache_prog(lfs, pcache, rcache, - *block, 4*i, &head, 4); + err = lfs_bd_erase(lfs, *block); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; @@ -1112,18 +1070,67 @@ static int lfs_index_extend(lfs_t *lfs, return err; } - if (i != skips-1) { - err = lfs_cache_read(lfs, rcache, NULL, head, 4*i, &head, 4); - if (err) { - return err; - } + if (size == 0) { + *off = 0; + return 0; } - assert(head >= 2 && head <= lfs->cfg->block_count); - } + size -= 1; + lfs_off_t index = lfs_ctz_index(lfs, &size); + size += 1; - *off = 4*skips; - return 0; + // just copy out the last block if it is incomplete + if (size != lfs->cfg->block_size) { + for (lfs_off_t i = 0; i < size; i++) { + uint8_t data; + int err = lfs_cache_read(lfs, rcache, NULL, + head, i, &data, 1); + if (err) { + return err; + } + + err = lfs_cache_prog(lfs, pcache, rcache, + *block, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + *off = size; + return 0; + } + + // append block + index += 1; + lfs_size_t skips = lfs_ctz(index) + 1; + + for (lfs_off_t i = 0; i < skips; i++) { + int err = lfs_cache_prog(lfs, pcache, rcache, + *block, 4*i, &head, 4); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (i != skips-1) { + err = lfs_cache_read(lfs, rcache, NULL, + head, 4*i, &head, 4); + if (err) { + return err; + } + } + + assert(head >= 2 && head <= lfs->cfg->block_count); + } + + *off = 4*skips; + return 0; + } relocate: LFS_DEBUG("Bad block at %d", *block); @@ -1133,7 +1140,7 @@ relocate: } } -static int lfs_index_traverse(lfs_t *lfs, +static int lfs_ctz_traverse(lfs_t *lfs, lfs_cache_t *rcache, const lfs_cache_t *pcache, lfs_block_t head, lfs_size_t size, int (*cb)(void*, lfs_block_t), void *data) { @@ -1141,7 +1148,7 @@ static int lfs_index_traverse(lfs_t *lfs, return 0; } - lfs_off_t index = lfs_index(lfs, &(lfs_off_t){size-1}); + lfs_off_t index = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); while (true) { int err = cb(data, head); @@ -1160,8 +1167,6 @@ static int lfs_index_traverse(lfs_t *lfs, index -= 1; } - - return 0; } @@ -1199,7 +1204,7 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file, entry.d.elen = sizeof(entry.d) - 4; entry.d.alen = 0; entry.d.nlen = strlen(path); - entry.d.u.file.head = -1; + entry.d.u.file.head = 0xffffffff; entry.d.u.file.size = 0; err = lfs_dir_append(lfs, &cwd, &entry, path); if (err) { @@ -1221,7 +1226,7 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file, file->pos = 0; if (flags & LFS_O_TRUNC) { - file->head = -1; + file->head = 0xffffffff; file->size = 0; } @@ -1455,7 +1460,7 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, // check if we need a new block if (!(file->flags & LFS_F_READING) || file->off == lfs->cfg->block_size) { - int err = lfs_index_find(lfs, &file->cache, NULL, + int err = lfs_ctz_find(lfs, &file->cache, NULL, file->head, file->size, file->pos, &file->block, &file->off); if (err) { @@ -1522,7 +1527,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, file->off == lfs->cfg->block_size) { if (!(file->flags & LFS_F_WRITING) && file->pos > 0) { // find out which block we're extending from - int err = lfs_index_find(lfs, &file->cache, NULL, + int err = lfs_ctz_find(lfs, &file->cache, NULL, file->head, file->size, file->pos-1, &file->block, &file->off); if (err) { @@ -1535,7 +1540,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, // extend file with new blocks lfs_alloc_ack(lfs); - int err = lfs_index_extend(lfs, &lfs->rcache, &file->cache, + int err = lfs_ctz_extend(lfs, &lfs->rcache, &file->cache, file->block, file->pos, &file->block, &file->off); if (err) { @@ -1588,13 +1593,13 @@ lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, if (whence == LFS_SEEK_SET) { file->pos = off; } else if (whence == LFS_SEEK_CUR) { - if (-off > file->pos) { + if ((lfs_off_t)-off > file->pos) { return LFS_ERR_INVAL; } file->pos = file->pos + off; } else if (whence == LFS_SEEK_END) { - if (-off > file->size) { + if ((lfs_off_t)-off > file->size) { return LFS_ERR_INVAL; } @@ -2070,7 +2075,7 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { dir.off += lfs_entry_size(&entry); if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) { - int err = lfs_index_traverse(lfs, &lfs->rcache, NULL, + int err = lfs_ctz_traverse(lfs, &lfs->rcache, NULL, entry.d.u.file.head, entry.d.u.file.size, cb, data); if (err) { return err; @@ -2089,7 +2094,7 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { // iterate over any open files for (lfs_file_t *f = lfs->files; f; f = f->next) { if (f->flags & LFS_F_DIRTY) { - int err = lfs_index_traverse(lfs, &lfs->rcache, &f->cache, + int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache, f->head, f->size, cb, data); if (err) { return err; @@ -2097,7 +2102,7 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { } if (f->flags & LFS_F_WRITING) { - int err = lfs_index_traverse(lfs, &lfs->rcache, &f->cache, + int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache, f->block, f->pos, cb, data); if (err) { return err; diff --git a/lfs_util.h b/lfs_util.h index cee7644fe0..9d23ab4eb0 100644 --- a/lfs_util.h +++ b/lfs_util.h @@ -41,6 +41,10 @@ static inline uint32_t lfs_npw2(uint32_t a) { return 32 - __builtin_clz(a-1); } +static inline uint32_t lfs_popc(uint32_t a) { + return __builtin_popcount(a); +} + static inline int lfs_scmp(uint32_t a, uint32_t b) { return (int)(unsigned)(a - b); } From 5e4ef9f5a4fa0a31062550ecba9abe892c017978 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 30 Oct 2017 19:24:58 -0500 Subject: [PATCH 19/46] Added simple high-level thread safety All calls are blocking, so a single mutex is able to garuntee synchronization across all relevant functions. --- LittleFileSystem.cpp | 63 +++++++++++++++++++++++++++++++++++++++++--- LittleFileSystem.h | 4 +++ littlefs/lfs_util.h | 32 +++++++++++++++++++--- 3 files changed, 91 insertions(+), 8 deletions(-) diff --git a/LittleFileSystem.cpp b/LittleFileSystem.cpp index 2a32683d4c..2b73cbb563 100644 --- a/LittleFileSystem.cpp +++ b/LittleFileSystem.cpp @@ -123,9 +123,11 @@ LittleFileSystem::~LittleFileSystem() { } int LittleFileSystem::mount(BlockDevice *bd) { + _mutex.lock(); _bd = bd; int err = _bd->init(); if (err) { + _mutex.unlock(); return err; } @@ -154,24 +156,29 @@ int LittleFileSystem::mount(BlockDevice *bd) { } err = lfs_mount(&_lfs, &_config); + _mutex.unlock(); return lfs_toerror(err); } int LittleFileSystem::unmount() { + _mutex.lock(); if (_bd) { int err = lfs_unmount(&_lfs); if (err) { + _mutex.unlock(); return lfs_toerror(err); } err = _bd->deinit(); if (err) { + _mutex.unlock(); return err; } _bd = NULL; } + _mutex.unlock(); return 0; } @@ -224,6 +231,7 @@ int LittleFileSystem::format(BlockDevice *bd, } int LittleFileSystem::reformat(BlockDevice *bd) { + _mutex.lock(); if (_bd) { if (!bd) { bd = _bd; @@ -231,41 +239,59 @@ int LittleFileSystem::reformat(BlockDevice *bd) { int err = unmount(); if (err) { + _mutex.unlock(); return err; } } if (!bd) { + _mutex.unlock(); return -ENODEV; } int err = LittleFileSystem::format(bd, _read_size, _prog_size, _block_size, _lookahead); if (err) { + _mutex.unlock(); return err; } - return mount(bd); + err = mount(bd); + if (err) { + _mutex.unlock(); + return err; + } + + _mutex.unlock(); + return 0; } int LittleFileSystem::remove(const char *filename) { + _mutex.lock(); int err = lfs_remove(&_lfs, filename); + _mutex.unlock(); return lfs_toerror(err); } int LittleFileSystem::rename(const char *oldname, const char *newname) { + _mutex.lock(); int err = lfs_rename(&_lfs, oldname, newname); + _mutex.unlock(); return lfs_toerror(err); } int LittleFileSystem::mkdir(const char *name, mode_t mode) { + _mutex.lock(); int err = lfs_mkdir(&_lfs, name); + _mutex.unlock(); return lfs_toerror(err); } int LittleFileSystem::stat(const char *name, struct stat *st) { struct lfs_info info; + _mutex.lock(); int err = lfs_stat(&_lfs, name, &info); + _mutex.unlock(); st->st_size = info.size; st->st_mode = lfs_tomode(info.type); return lfs_toerror(err); @@ -276,50 +302,66 @@ int LittleFileSystem::stat(const char *name, struct stat *st) { int LittleFileSystem::file_open(fs_file_t *file, const char *path, int flags) { lfs_file_t *f = new lfs_file_t; *file = f; + _mutex.lock(); int err = lfs_file_open(&_lfs, f, path, lfs_fromflags(flags)); + _mutex.unlock(); return lfs_toerror(err); } int LittleFileSystem::file_close(fs_file_t file) { lfs_file_t *f = (lfs_file_t *)file; + _mutex.lock(); int err = lfs_file_close(&_lfs, f); + _mutex.unlock(); delete f; return lfs_toerror(err); } ssize_t LittleFileSystem::file_read(fs_file_t file, void *buffer, size_t len) { lfs_file_t *f = (lfs_file_t *)file; - int res = lfs_file_read(&_lfs, f, buffer, len); + _mutex.lock(); + lfs_ssize_t res = lfs_file_read(&_lfs, f, buffer, len); + _mutex.unlock(); return lfs_toerror(res); } ssize_t LittleFileSystem::file_write(fs_file_t file, const void *buffer, size_t len) { lfs_file_t *f = (lfs_file_t *)file; - int res = lfs_file_write(&_lfs, f, buffer, len); + _mutex.lock(); + lfs_ssize_t res = lfs_file_write(&_lfs, f, buffer, len); + _mutex.unlock(); return lfs_toerror(res); } int LittleFileSystem::file_sync(fs_file_t file) { lfs_file_t *f = (lfs_file_t *)file; + _mutex.lock(); int err = lfs_file_sync(&_lfs, f); + _mutex.unlock(); return lfs_toerror(err); } off_t LittleFileSystem::file_seek(fs_file_t file, off_t offset, int whence) { lfs_file_t *f = (lfs_file_t *)file; + _mutex.lock(); off_t res = lfs_file_seek(&_lfs, f, offset, lfs_fromwhence(whence)); + _mutex.unlock(); return lfs_toerror(res); } off_t LittleFileSystem::file_tell(fs_file_t file) { lfs_file_t *f = (lfs_file_t *)file; + _mutex.lock(); off_t res = lfs_file_tell(&_lfs, f); + _mutex.unlock(); return lfs_toerror(res); } off_t LittleFileSystem::file_size(fs_file_t file) { lfs_file_t *f = (lfs_file_t *)file; + _mutex.lock(); off_t res = lfs_file_size(&_lfs, f); + _mutex.unlock(); return lfs_toerror(res); } @@ -328,13 +370,17 @@ off_t LittleFileSystem::file_size(fs_file_t file) { int LittleFileSystem::dir_open(fs_dir_t *dir, const char *path) { lfs_dir_t *d = new lfs_dir_t; *dir = d; + _mutex.lock(); int err = lfs_dir_open(&_lfs, d, path); + _mutex.unlock(); return lfs_toerror(err); } int LittleFileSystem::dir_close(fs_dir_t dir) { lfs_dir_t *d = (lfs_dir_t *)dir; + _mutex.lock(); int err = lfs_dir_close(&_lfs, d); + _mutex.unlock(); delete d; return lfs_toerror(err); } @@ -342,7 +388,9 @@ int LittleFileSystem::dir_close(fs_dir_t dir) { ssize_t LittleFileSystem::dir_read(fs_dir_t dir, struct dirent *ent) { lfs_dir_t *d = (lfs_dir_t *)dir; struct lfs_info info; + _mutex.lock(); int res = lfs_dir_read(&_lfs, d, &info); + _mutex.unlock(); if (res == 1) { ent->d_type = lfs_totype(info.type); strcpy(ent->d_name, info.name); @@ -352,16 +400,23 @@ ssize_t LittleFileSystem::dir_read(fs_dir_t dir, struct dirent *ent) { void LittleFileSystem::dir_seek(fs_dir_t dir, off_t offset) { lfs_dir_t *d = (lfs_dir_t *)dir; + _mutex.lock(); lfs_dir_seek(&_lfs, d, offset); + _mutex.unlock(); } off_t LittleFileSystem::dir_tell(fs_dir_t dir) { lfs_dir_t *d = (lfs_dir_t *)dir; - return lfs_dir_tell(&_lfs, d); + _mutex.lock(); + lfs_soff_t res = lfs_dir_tell(&_lfs, d); + _mutex.unlock(); + return lfs_toerror(res); } void LittleFileSystem::dir_rewind(fs_dir_t dir) { lfs_dir_t *d = (lfs_dir_t *)dir; + _mutex.lock(); lfs_dir_rewind(&_lfs, d); + _mutex.unlock(); } diff --git a/LittleFileSystem.h b/LittleFileSystem.h index 719ae7e741..36e6545bea 100644 --- a/LittleFileSystem.h +++ b/LittleFileSystem.h @@ -24,6 +24,7 @@ #include "FileSystem.h" #include "BlockDevice.h" +#include "PlatformMutex.h" extern "C" { #include "lfs.h" } @@ -273,6 +274,9 @@ private: const lfs_size_t _prog_size; const lfs_size_t _block_size; const lfs_size_t _lookahead; + + // thread-safe locking + PlatformMutex _mutex; }; diff --git a/littlefs/lfs_util.h b/littlefs/lfs_util.h index 98cbd9be95..8d36dcf605 100644 --- a/littlefs/lfs_util.h +++ b/littlefs/lfs_util.h @@ -37,23 +37,47 @@ static inline uint32_t lfs_min(uint32_t a, uint32_t b) { } static inline uint32_t lfs_ctz(uint32_t a) { -#ifdef __ICCARM__ +#if defined(__GNUC__) || defined(__CC_ARM) + return __builtin_ctz(a); +#elif defined(__ICCARM__) return __CLZ(__RBIT(a)); #else - return __builtin_ctz(a); + uint32_t r = 32; + a &= -a; + if (a) r -= 1; + if (a & 0x0000ffff) r -= 16; + if (a & 0x00ff00ff) r -= 8; + if (a & 0x0f0f0f0f) r -= 4; + if (a & 0x33333333) r -= 2; + if (a & 0x55555555) r -= 1; + return r; #endif } static inline uint32_t lfs_npw2(uint32_t a) { -#ifdef __ICCARM__ +#if defined(__GNUC__) || defined(__CC_ARM) + return 32 - __builtin_clz(a-1); +#elif defined(__ICCARM__) return 32 - __CLZ(a-1); #else - return 32 - __builtin_clz(a-1); + uint32_t r = 0; + uint32_t s; + s = (a > 0xffff) << 4; a >>= s; r |= s; + s = (a > 0xff ) << 3; a >>= s; r |= s; + s = (a > 0xf ) << 2; a >>= s; r |= s; + s = (a > 0x3 ) << 1; a >>= s; r |= s; + return r | (a >> 1); #endif } static inline uint32_t lfs_popc(uint32_t a) { +#if defined(__GNUC__) || defined(__CC_ARM) return __builtin_popcount(a); +#else + a = a - ((a >> 1) & 0x55555555); + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); + return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; +#endif } static inline int lfs_scmp(uint32_t a, uint32_t b) { From 72fab82453c60668a7b9b2c0b17f3482398d926a Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 31 Oct 2017 17:21:43 -0500 Subject: [PATCH 20/46] Added API level logging and logging configuration options --- LittleFileSystem.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++++ littlefs/lfs_util.h | 37 ++++++++++++++++++++++++--- mbed_lib.json | 20 +++++++++++++++ 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/LittleFileSystem.cpp b/LittleFileSystem.cpp index 2b73cbb563..456565b16a 100644 --- a/LittleFileSystem.cpp +++ b/LittleFileSystem.cpp @@ -22,6 +22,10 @@ #include "mbed.h" #include "LittleFileSystem.h" #include "errno.h" +extern "C" { +#include "lfs.h" +#include "lfs_util.h" +} ////// Conversion functions ////// @@ -124,9 +128,11 @@ LittleFileSystem::~LittleFileSystem() { int LittleFileSystem::mount(BlockDevice *bd) { _mutex.lock(); + LFS_INFO("mount(%p)", bd); _bd = bd; int err = _bd->init(); if (err) { + LFS_INFO("mount -> %d", err); _mutex.unlock(); return err; } @@ -156,21 +162,25 @@ int LittleFileSystem::mount(BlockDevice *bd) { } err = lfs_mount(&_lfs, &_config); + LFS_INFO("mount -> %d", lfs_toerror(err)); _mutex.unlock(); return lfs_toerror(err); } int LittleFileSystem::unmount() { _mutex.lock(); + LFS_INFO("unmount()", ""); if (_bd) { int err = lfs_unmount(&_lfs); if (err) { + LFS_INFO("unmount -> %d", lfs_toerror(err)); _mutex.unlock(); return lfs_toerror(err); } err = _bd->deinit(); if (err) { + LFS_INFO("unmount -> %d", err); _mutex.unlock(); return err; } @@ -178,6 +188,7 @@ int LittleFileSystem::unmount() { _bd = NULL; } + LFS_INFO("unmount -> %d", 0); _mutex.unlock(); return 0; } @@ -185,8 +196,11 @@ int LittleFileSystem::unmount() { int LittleFileSystem::format(BlockDevice *bd, lfs_size_t read_size, lfs_size_t prog_size, lfs_size_t block_size, lfs_size_t lookahead) { + LFS_INFO("format(%p, %d, %d, %d, %d)", + bd, read_size, prog_size, block_size, lookahead); int err = bd->init(); if (err) { + LFS_INFO("format -> %d", err); return err; } @@ -219,19 +233,23 @@ int LittleFileSystem::format(BlockDevice *bd, err = lfs_format(&_lfs, &_config); if (err) { + LFS_INFO("format -> %d", lfs_toerror(err)); return lfs_toerror(err); } err = bd->deinit(); if (err) { + LFS_INFO("format -> %d", err); return err; } + LFS_INFO("format -> %d", 0); return 0; } int LittleFileSystem::reformat(BlockDevice *bd) { _mutex.lock(); + LFS_INFO("reformat(%p)", bd); if (_bd) { if (!bd) { bd = _bd; @@ -239,12 +257,14 @@ int LittleFileSystem::reformat(BlockDevice *bd) { int err = unmount(); if (err) { + LFS_INFO("reformat -> %d", err); _mutex.unlock(); return err; } } if (!bd) { + LFS_INFO("reformat -> %d", -ENODEV); _mutex.unlock(); return -ENODEV; } @@ -252,37 +272,46 @@ int LittleFileSystem::reformat(BlockDevice *bd) { int err = LittleFileSystem::format(bd, _read_size, _prog_size, _block_size, _lookahead); if (err) { + LFS_INFO("reformat -> %d", err); _mutex.unlock(); return err; } err = mount(bd); if (err) { + LFS_INFO("reformat -> %d", err); _mutex.unlock(); return err; } + LFS_INFO("reformat -> %d", 0); _mutex.unlock(); return 0; } int LittleFileSystem::remove(const char *filename) { _mutex.lock(); + LFS_INFO("remove(\"%s\")", filename); int err = lfs_remove(&_lfs, filename); + LFS_INFO("remove -> %d", lfs_toerror(err)); _mutex.unlock(); return lfs_toerror(err); } int LittleFileSystem::rename(const char *oldname, const char *newname) { _mutex.lock(); + LFS_INFO("rename(\"%s\", \"%s\")", oldname, newname); int err = lfs_rename(&_lfs, oldname, newname); + LFS_INFO("rename -> %d", lfs_toerror(err)); _mutex.unlock(); return lfs_toerror(err); } int LittleFileSystem::mkdir(const char *name, mode_t mode) { _mutex.lock(); + LFS_INFO("mkdir(\"%s\", 0x%x)", name, mode); int err = lfs_mkdir(&_lfs, name); + LFS_INFO("mkdir -> %d", lfs_toerror(err)); _mutex.unlock(); return lfs_toerror(err); } @@ -290,7 +319,9 @@ int LittleFileSystem::mkdir(const char *name, mode_t mode) { int LittleFileSystem::stat(const char *name, struct stat *st) { struct lfs_info info; _mutex.lock(); + LFS_INFO("stat(\"%s\", %p)", name, st); int err = lfs_stat(&_lfs, name, &info); + LFS_INFO("stat -> %d", lfs_toerror(err)); _mutex.unlock(); st->st_size = info.size; st->st_mode = lfs_tomode(info.type); @@ -303,7 +334,9 @@ int LittleFileSystem::file_open(fs_file_t *file, const char *path, int flags) { lfs_file_t *f = new lfs_file_t; *file = f; _mutex.lock(); + LFS_INFO("file_open(%p, \"%s\", 0x%x)", *file, path, flags); int err = lfs_file_open(&_lfs, f, path, lfs_fromflags(flags)); + LFS_INFO("file_open -> %d", lfs_toerror(err)); _mutex.unlock(); return lfs_toerror(err); } @@ -311,7 +344,9 @@ int LittleFileSystem::file_open(fs_file_t *file, const char *path, int flags) { int LittleFileSystem::file_close(fs_file_t file) { lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); + LFS_INFO("file_close(%p)", file); int err = lfs_file_close(&_lfs, f); + LFS_INFO("file_close -> %d", lfs_toerror(err)); _mutex.unlock(); delete f; return lfs_toerror(err); @@ -320,7 +355,9 @@ int LittleFileSystem::file_close(fs_file_t file) { ssize_t LittleFileSystem::file_read(fs_file_t file, void *buffer, size_t len) { lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); + LFS_INFO("file_read(%p, %p, %d)", file, buffer, len); lfs_ssize_t res = lfs_file_read(&_lfs, f, buffer, len); + LFS_INFO("file_read -> %d", lfs_toerror(res)); _mutex.unlock(); return lfs_toerror(res); } @@ -328,7 +365,9 @@ ssize_t LittleFileSystem::file_read(fs_file_t file, void *buffer, size_t len) { ssize_t LittleFileSystem::file_write(fs_file_t file, const void *buffer, size_t len) { lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); + LFS_INFO("file_write(%p, %p, %d)", file, buffer, len); lfs_ssize_t res = lfs_file_write(&_lfs, f, buffer, len); + LFS_INFO("file_write -> %d", lfs_toerror(res)); _mutex.unlock(); return lfs_toerror(res); } @@ -336,7 +375,9 @@ ssize_t LittleFileSystem::file_write(fs_file_t file, const void *buffer, size_t int LittleFileSystem::file_sync(fs_file_t file) { lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); + LFS_INFO("file_sync(%p)", file); int err = lfs_file_sync(&_lfs, f); + LFS_INFO("file_sync -> %d", lfs_toerror(err)); _mutex.unlock(); return lfs_toerror(err); } @@ -344,7 +385,9 @@ int LittleFileSystem::file_sync(fs_file_t file) { off_t LittleFileSystem::file_seek(fs_file_t file, off_t offset, int whence) { lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); + LFS_INFO("file_seek(%p, %d, %d)", file, offset, whence); off_t res = lfs_file_seek(&_lfs, f, offset, lfs_fromwhence(whence)); + LFS_INFO("file_seek -> %d", lfs_toerror(res)); _mutex.unlock(); return lfs_toerror(res); } @@ -352,7 +395,9 @@ off_t LittleFileSystem::file_seek(fs_file_t file, off_t offset, int whence) { off_t LittleFileSystem::file_tell(fs_file_t file) { lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); + LFS_INFO("file_tell(%p)", file); off_t res = lfs_file_tell(&_lfs, f); + LFS_INFO("file_tell -> %d", lfs_toerror(res)); _mutex.unlock(); return lfs_toerror(res); } @@ -360,7 +405,9 @@ off_t LittleFileSystem::file_tell(fs_file_t file) { off_t LittleFileSystem::file_size(fs_file_t file) { lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); + LFS_INFO("file_size(%p)", file); off_t res = lfs_file_size(&_lfs, f); + LFS_INFO("file_size -> %d", lfs_toerror(res)); _mutex.unlock(); return lfs_toerror(res); } @@ -371,7 +418,9 @@ int LittleFileSystem::dir_open(fs_dir_t *dir, const char *path) { lfs_dir_t *d = new lfs_dir_t; *dir = d; _mutex.lock(); + LFS_INFO("dir_open(%p, \"%s\")", *dir, path); int err = lfs_dir_open(&_lfs, d, path); + LFS_INFO("dir_open -> %d", lfs_toerror(err)); _mutex.unlock(); return lfs_toerror(err); } @@ -379,7 +428,9 @@ int LittleFileSystem::dir_open(fs_dir_t *dir, const char *path) { int LittleFileSystem::dir_close(fs_dir_t dir) { lfs_dir_t *d = (lfs_dir_t *)dir; _mutex.lock(); + LFS_INFO("dir_close(%p)", dir); int err = lfs_dir_close(&_lfs, d); + LFS_INFO("dir_close -> %d", lfs_toerror(err)); _mutex.unlock(); delete d; return lfs_toerror(err); @@ -389,7 +440,9 @@ ssize_t LittleFileSystem::dir_read(fs_dir_t dir, struct dirent *ent) { lfs_dir_t *d = (lfs_dir_t *)dir; struct lfs_info info; _mutex.lock(); + LFS_INFO("dir_read(%p, %p)", dir, ent); int res = lfs_dir_read(&_lfs, d, &info); + LFS_INFO("dir_read -> %d", lfs_toerror(res)); _mutex.unlock(); if (res == 1) { ent->d_type = lfs_totype(info.type); @@ -401,14 +454,18 @@ ssize_t LittleFileSystem::dir_read(fs_dir_t dir, struct dirent *ent) { void LittleFileSystem::dir_seek(fs_dir_t dir, off_t offset) { lfs_dir_t *d = (lfs_dir_t *)dir; _mutex.lock(); + LFS_INFO("dir_seek(%p, %d)", dir, offset); lfs_dir_seek(&_lfs, d, offset); + LFS_INFO("dir_seek -> void", ""); _mutex.unlock(); } off_t LittleFileSystem::dir_tell(fs_dir_t dir) { lfs_dir_t *d = (lfs_dir_t *)dir; _mutex.lock(); + LFS_INFO("dir_tell(%p)", dir); lfs_soff_t res = lfs_dir_tell(&_lfs, d); + LFS_INFO("dir_tell -> %d", lfs_toerror(res)); _mutex.unlock(); return lfs_toerror(res); } @@ -416,7 +473,9 @@ off_t LittleFileSystem::dir_tell(fs_dir_t dir) { void LittleFileSystem::dir_rewind(fs_dir_t dir) { lfs_dir_t *d = (lfs_dir_t *)dir; _mutex.lock(); + LFS_INFO("dir_rewind(%p)", dir); lfs_dir_rewind(&_lfs, d); + LFS_INFO("dir_rewind -> void", ""); _mutex.unlock(); } diff --git a/littlefs/lfs_util.h b/littlefs/lfs_util.h index 8d36dcf605..5a59b35f0f 100644 --- a/littlefs/lfs_util.h +++ b/littlefs/lfs_util.h @@ -92,12 +92,43 @@ void lfs_crc(uint32_t *crc, const void *buffer, size_t size); #ifdef __MBED__ #include "mbed_debug.h" #else -#define debug printf +#define MBED_LFS_ENABLE_INFO false +#define MBED_LFS_ENABLE_DEBUG true +#define MBED_LFS_ENABLE_WARN true +#define MBED_LFS_ENABLE_ERROR true #endif -#define LFS_DEBUG(fmt, ...) debug("lfs debug: " fmt "\n", __VA_ARGS__) +#if MBED_LFS_ENABLE_INFO == true +#define LFS_INFO(fmt, ...) printf("lfs info: " fmt "\n", __VA_ARGS__) +#elif !defined(MBED_LFS_ENABLE_INFO) +#define LFS_INFO(fmt, ...) debug("lfs info: " fmt "\n", __VA_ARGS__) +#else +#define LFS_INFO(fmt, ...) +#endif + +#if MBED_LFS_ENABLE_DEBUG == true +#define LFS_DEBUG(fmt, ...) printf("lfs debug: " fmt "\n", __VA_ARGS__) +#elif !defined(MBED_LFS_ENABLE_DEBUG) +#define LFS_DEBUG(fmt, ...) debug("lfs debug: " fmt "\n", __VA_ARGS__) +#else +#define LFS_DEBUG(fmt, ...) +#endif + +#if MBED_LFS_ENABLE_WARN == true +#define LFS_WARN(fmt, ...) printf("lfs warn: " fmt "\n", __VA_ARGS__) +#elif !defined(MBED_LFS_ENABLE_WARN) #define LFS_WARN(fmt, ...) debug("lfs warn: " fmt "\n", __VA_ARGS__) -#define LFS_ERROR(fmt, ...) debug("lfs error: " fmt "\n", __VA_ARGS__) +#else +#define LFS_WARN(fmt, ...) +#endif + +#if MBED_LFS_ENABLE_ERROR == true +#define LFS_ERROR(fmt, ...) printf("lfs error: " fmt "\n", __VA_ARGS__) +#elif !defined(MBED_LFS_ENABLE_ERROR) +#define LFS_ERROR(fmt, ...) debug("lfs error: " fmt "\n", __VA_ARGS__) +#else +#define LFS_ERROR(fmt, ...) +#endif #endif diff --git a/mbed_lib.json b/mbed_lib.json index 3de40afbb6..bafc549f5c 100644 --- a/mbed_lib.json +++ b/mbed_lib.json @@ -20,6 +20,26 @@ "macro_name": "MBED_LFS_LOOKAHEAD", "value": 512, "help": "Number of blocks to lookahead during block allocation. A larger lookahead reduces the number of passes required to allocate a block. The lookahead buffer requires only 1 bit per block so it can be quite large with little ram impact. Should be a multiple of 32." + }, + "enable_info": { + "macro_name": "MBED_LFS_ENABLE_INFO", + "value": false, + "help": "Enables info logging, true = enabled, false = disabled, null = disabled only in release builds" + }, + "enable_debug": { + "macro_name": "MBED_LFS_ENABLE_DEBUG", + "value": null, + "help": "Enables debug logging, true = enabled, false = disabled, null = disabled only in release builds" + }, + "enable_warn": { + "macro_name": "MBED_LFS_ENABLE_WARN", + "value": null, + "help": "Enables warn logging, true = enabled, false = disabled, null = disabled only in release builds" + }, + "enable_error": { + "macro_name": "MBED_LFS_ENABLE_ERROR", + "value": null, + "help": "Enables error logging, true = enabled, false = disabled, null = disabled only in release builds" } } } From 71b429ba6687d348467513622fdbfb5f4bf13c24 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 9 Nov 2017 18:33:06 -0600 Subject: [PATCH 21/46] Cleaned up compilation with logging enabled - Removed list of warnings on signedness of integers in printf - Fixed issue with "true" in ifdef --- LittleFileSystem.cpp | 14 +++++++------- littlefs/lfs.c | 6 +++--- littlefs/lfs_util.h | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/LittleFileSystem.cpp b/LittleFileSystem.cpp index 456565b16a..aa0cb58429 100644 --- a/LittleFileSystem.cpp +++ b/LittleFileSystem.cpp @@ -169,7 +169,7 @@ int LittleFileSystem::mount(BlockDevice *bd) { int LittleFileSystem::unmount() { _mutex.lock(); - LFS_INFO("unmount()", ""); + LFS_INFO("unmount(%s)", ""); if (_bd) { int err = lfs_unmount(&_lfs); if (err) { @@ -196,7 +196,7 @@ int LittleFileSystem::unmount() { int LittleFileSystem::format(BlockDevice *bd, lfs_size_t read_size, lfs_size_t prog_size, lfs_size_t block_size, lfs_size_t lookahead) { - LFS_INFO("format(%p, %d, %d, %d, %d)", + LFS_INFO("format(%p, %ld, %ld, %ld, %ld)", bd, read_size, prog_size, block_size, lookahead); int err = bd->init(); if (err) { @@ -309,7 +309,7 @@ int LittleFileSystem::rename(const char *oldname, const char *newname) { int LittleFileSystem::mkdir(const char *name, mode_t mode) { _mutex.lock(); - LFS_INFO("mkdir(\"%s\", 0x%x)", name, mode); + LFS_INFO("mkdir(\"%s\", 0x%lx)", name, mode); int err = lfs_mkdir(&_lfs, name); LFS_INFO("mkdir -> %d", lfs_toerror(err)); _mutex.unlock(); @@ -385,7 +385,7 @@ int LittleFileSystem::file_sync(fs_file_t file) { off_t LittleFileSystem::file_seek(fs_file_t file, off_t offset, int whence) { lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); - LFS_INFO("file_seek(%p, %d, %d)", file, offset, whence); + LFS_INFO("file_seek(%p, %ld, %d)", file, offset, whence); off_t res = lfs_file_seek(&_lfs, f, offset, lfs_fromwhence(whence)); LFS_INFO("file_seek -> %d", lfs_toerror(res)); _mutex.unlock(); @@ -454,9 +454,9 @@ ssize_t LittleFileSystem::dir_read(fs_dir_t dir, struct dirent *ent) { void LittleFileSystem::dir_seek(fs_dir_t dir, off_t offset) { lfs_dir_t *d = (lfs_dir_t *)dir; _mutex.lock(); - LFS_INFO("dir_seek(%p, %d)", dir, offset); + LFS_INFO("dir_seek(%p, %ld)", dir, offset); lfs_dir_seek(&_lfs, d, offset); - LFS_INFO("dir_seek -> void", ""); + LFS_INFO("dir_seek -> %s", "void"); _mutex.unlock(); } @@ -475,7 +475,7 @@ void LittleFileSystem::dir_rewind(fs_dir_t dir) { _mutex.lock(); LFS_INFO("dir_rewind(%p)", dir); lfs_dir_rewind(&_lfs, d); - LFS_INFO("dir_rewind -> void", ""); + LFS_INFO("dir_rewind -> %s", "void"); _mutex.unlock(); } diff --git a/littlefs/lfs.c b/littlefs/lfs.c index f83d13aeff..2465531973 100644 --- a/littlefs/lfs.c +++ b/littlefs/lfs.c @@ -2027,7 +2027,7 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { } if (superblock.d.version > (0x00010001 | 0x0000ffff)) { - LFS_ERROR("Invalid version %ld.%ld\n", + LFS_ERROR("Invalid version %ld.%ld", 0xffff & (superblock.d.version >> 16), 0xffff & (superblock.d.version >> 0)); return LFS_ERR_INVAL; @@ -2343,14 +2343,14 @@ int lfs_deorphan(lfs_t *lfs) { } if (moved) { - LFS_DEBUG("Found move %d %d", + LFS_DEBUG("Found move %ld %ld", entry.d.u.dir[0], entry.d.u.dir[1]); int err = lfs_dir_remove(lfs, &cwd, &entry); if (err) { return err; } } else { - LFS_DEBUG("Found partial move %d %d", + LFS_DEBUG("Found partial move %ld %ld", entry.d.u.dir[0], entry.d.u.dir[1]); entry.d.type &= ~0x80; int err = lfs_dir_update(lfs, &cwd, &entry, NULL); diff --git a/littlefs/lfs_util.h b/littlefs/lfs_util.h index 5a59b35f0f..4802dc912b 100644 --- a/littlefs/lfs_util.h +++ b/littlefs/lfs_util.h @@ -98,7 +98,7 @@ void lfs_crc(uint32_t *crc, const void *buffer, size_t size); #define MBED_LFS_ENABLE_ERROR true #endif -#if MBED_LFS_ENABLE_INFO == true +#if MBED_LFS_ENABLE_INFO #define LFS_INFO(fmt, ...) printf("lfs info: " fmt "\n", __VA_ARGS__) #elif !defined(MBED_LFS_ENABLE_INFO) #define LFS_INFO(fmt, ...) debug("lfs info: " fmt "\n", __VA_ARGS__) @@ -106,7 +106,7 @@ void lfs_crc(uint32_t *crc, const void *buffer, size_t size); #define LFS_INFO(fmt, ...) #endif -#if MBED_LFS_ENABLE_DEBUG == true +#if MBED_LFS_ENABLE_DEBUG #define LFS_DEBUG(fmt, ...) printf("lfs debug: " fmt "\n", __VA_ARGS__) #elif !defined(MBED_LFS_ENABLE_DEBUG) #define LFS_DEBUG(fmt, ...) debug("lfs debug: " fmt "\n", __VA_ARGS__) @@ -114,7 +114,7 @@ void lfs_crc(uint32_t *crc, const void *buffer, size_t size); #define LFS_DEBUG(fmt, ...) #endif -#if MBED_LFS_ENABLE_WARN == true +#if MBED_LFS_ENABLE_WARN #define LFS_WARN(fmt, ...) printf("lfs warn: " fmt "\n", __VA_ARGS__) #elif !defined(MBED_LFS_ENABLE_WARN) #define LFS_WARN(fmt, ...) debug("lfs warn: " fmt "\n", __VA_ARGS__) @@ -122,7 +122,7 @@ void lfs_crc(uint32_t *crc, const void *buffer, size_t size); #define LFS_WARN(fmt, ...) #endif -#if MBED_LFS_ENABLE_ERROR == true +#if MBED_LFS_ENABLE_ERROR #define LFS_ERROR(fmt, ...) printf("lfs error: " fmt "\n", __VA_ARGS__) #elif !defined(MBED_LFS_ENABLE_ERROR) #define LFS_ERROR(fmt, ...) debug("lfs error: " fmt "\n", __VA_ARGS__) From 785b0b4bc4552100619ee22aedd64523b9341f0f Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 9 Nov 2017 19:10:08 -0600 Subject: [PATCH 22/46] Fixed issue with aggressively rounding down lookahead configuration The littlefs allows buffers to be passed statically in the case that a system does not have a heap. Unfortunately, this means we can't round up in the case of an unaligned lookahead buffer. Double unfortunately, rounding down after clamping to the block device size could result in a lookahead of zero for block devices < 32 blocks large. The assert in littlefs does catch this case, but rounding down prevents support for < 32 block devices. The solution is to simply require a 32-bit aligned buffer with an assert. This avoids runtime problems while allowing a user to pass in the correct buffer for < 32 block devices. Rounding up can be handled at higher API levels. --- .travis.yml | 2 +- LittleFileSystem.cpp | 4 ++-- littlefs/.travis.yml | 2 +- littlefs/lfs.c | 22 +++++++++++----------- littlefs/lfs.h | 1 - 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index ad609b70d8..f470416e0f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ script: make -Clittlefs test - CFLAGS="-Wno-error=format -DLFS_BLOCK_COUNT=1023" make -Clittlefs test - - CFLAGS="-Wno-error=format -DLFS_LOOKAHEAD=2047" + - CFLAGS="-Wno-error=format -DLFS_LOOKAHEAD=2048" make -Clittlefs test # Self-host with littlefs-fuse for fuzz test diff --git a/LittleFileSystem.cpp b/LittleFileSystem.cpp index aa0cb58429..e563b908f7 100644 --- a/LittleFileSystem.cpp +++ b/LittleFileSystem.cpp @@ -156,7 +156,7 @@ int LittleFileSystem::mount(BlockDevice *bd) { _config.block_size = _block_size; } _config.block_count = bd->size() / _config.block_size; - _config.lookahead = _config.block_count - _config.block_count % 32; + _config.lookahead = 32 * ((_config.block_count+31)/32); if (_config.lookahead > _lookahead) { _config.lookahead = _lookahead; } @@ -226,7 +226,7 @@ int LittleFileSystem::format(BlockDevice *bd, _config.block_size = block_size; } _config.block_count = bd->size() / _config.block_size; - _config.lookahead = _config.block_count - _config.block_count % 32; + _config.lookahead = 32 * ((_config.block_count+31)/32); if (_config.lookahead > lookahead) { _config.lookahead = lookahead; } diff --git a/littlefs/.travis.yml b/littlefs/.travis.yml index f2322761b0..552f7cc9a7 100644 --- a/littlefs/.travis.yml +++ b/littlefs/.travis.yml @@ -15,7 +15,7 @@ script: - CFLAGS="-DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make test - CFLAGS="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make test - CFLAGS="-DLFS_BLOCK_COUNT=1023" make test - - CFLAGS="-DLFS_LOOKAHEAD=2047" make test + - CFLAGS="-DLFS_LOOKAHEAD=2048" make test # self-host with littlefs-fuse for fuzz test - make -C littlefs-fuse diff --git a/littlefs/lfs.c b/littlefs/lfs.c index 2465531973..1379920a82 100644 --- a/littlefs/lfs.c +++ b/littlefs/lfs.c @@ -278,7 +278,7 @@ static int lfs_alloc_lookahead(void *p, lfs_block_t block) { % (lfs_soff_t)(lfs->cfg->block_count)) + lfs->cfg->block_count) % lfs->cfg->block_count; - if (off < lfs->free.lookahead) { + if (off < lfs->cfg->lookahead) { lfs->free.buffer[off / 32] |= 1U << (off % 32); } @@ -294,7 +294,8 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { return LFS_ERR_NOSPC; } - if (lfs->free.off >= lfs->free.lookahead) { + if (lfs->free.off >= lfs_min( + lfs->cfg->lookahead, lfs->cfg->block_count)) { break; } @@ -308,11 +309,11 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { } } - lfs->free.begin += lfs->free.lookahead; + lfs->free.begin += lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count); lfs->free.off = 0; // find mask of free blocks from tree - memset(lfs->free.buffer, 0, lfs->free.lookahead/8); + memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8); int err = lfs_traverse(lfs, lfs_alloc_lookahead, lfs); if (err) { return err; @@ -1870,13 +1871,12 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { } // setup lookahead, round down to nearest 32-bits - lfs->free.lookahead = lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count); - lfs->free.lookahead = 32 * (lfs->free.lookahead / 32); - assert(lfs->free.lookahead > 0); + assert(lfs->cfg->lookahead % 32 == 0); + assert(lfs->cfg->lookahead > 0); if (lfs->cfg->lookahead_buffer) { lfs->free.buffer = lfs->cfg->lookahead_buffer; } else { - lfs->free.buffer = malloc(lfs->free.lookahead/8); + lfs->free.buffer = malloc(lfs->cfg->lookahead/8); if (!lfs->free.buffer) { return LFS_ERR_NOMEM; } @@ -1919,7 +1919,7 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { } // create free lookahead - memset(lfs->free.buffer, 0, lfs->free.lookahead/8); + memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8); lfs->free.begin = 0; lfs->free.off = 0; lfs->free.end = lfs->free.begin + lfs->cfg->block_count; @@ -1998,8 +1998,8 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { } // setup free lookahead - lfs->free.begin = -lfs->free.lookahead; - lfs->free.off = lfs->free.lookahead; + lfs->free.begin = -lfs->cfg->lookahead; + lfs->free.off = lfs->cfg->lookahead; lfs->free.end = lfs->free.begin + lfs->cfg->block_count; // load superblock diff --git a/littlefs/lfs.h b/littlefs/lfs.h index 270a65efb9..e4aab0e4b2 100644 --- a/littlefs/lfs.h +++ b/littlefs/lfs.h @@ -236,7 +236,6 @@ typedef struct lfs_superblock { } lfs_superblock_t; typedef struct lfs_free { - lfs_size_t lookahead; lfs_block_t begin; lfs_block_t end; lfs_block_t off; From d3b2efec9d56ee1895162754c5115d86a76daa26 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 9 Nov 2017 19:28:55 -0600 Subject: [PATCH 23/46] Fixed corner case with immediate exhaustion and lookahead==block_count The previous math for determining if we scanned all of disk wasn't set up correctly in the lfs_mount function. If lookahead == block_count the lfs_alloc function would think we had already searched the entire disk. This is only an issue if we manage to exhaust a block on the first pass after mount, since lfs_alloc_ack resets the lookahead region into a valid state after a succesful block allocation. --- littlefs/lfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/littlefs/lfs.c b/littlefs/lfs.c index 1379920a82..7e17921963 100644 --- a/littlefs/lfs.c +++ b/littlefs/lfs.c @@ -1922,7 +1922,7 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8); lfs->free.begin = 0; lfs->free.off = 0; - lfs->free.end = lfs->free.begin + lfs->cfg->block_count; + lfs->free.end = lfs->free.begin + lfs->free.off + lfs->cfg->block_count; // create superblock dir lfs_alloc_ack(lfs); @@ -2000,7 +2000,7 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { // setup free lookahead lfs->free.begin = -lfs->cfg->lookahead; lfs->free.off = lfs->cfg->lookahead; - lfs->free.end = lfs->free.begin + lfs->cfg->block_count; + lfs->free.end = lfs->free.begin + lfs->free.off + lfs->cfg->block_count; // load superblock lfs_dir_t dir; From 3778759979a1c6bc141e9ced8c28df2a1d4a9848 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 10 Nov 2017 11:02:50 -0600 Subject: [PATCH 24/46] Squashed 'littlefs/' changes from 2ab150c..3f31c8c 3f31c8c Fixed corner case with immediate exhaustion and lookahead==block_count f4aeb83 Fixed issue with aggressively rounding down lookahead configuration db51a39 Removed stray newline in LFS_ERROR for version git-subtree-dir: littlefs git-subtree-split: 3f31c8cba31e0f6cef5b02dba2e050d8df1168b7 --- .travis.yml | 2 +- lfs.c | 28 ++++++++++++++-------------- lfs.h | 1 - 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index f2322761b0..552f7cc9a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ script: - CFLAGS="-DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make test - CFLAGS="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make test - CFLAGS="-DLFS_BLOCK_COUNT=1023" make test - - CFLAGS="-DLFS_LOOKAHEAD=2047" make test + - CFLAGS="-DLFS_LOOKAHEAD=2048" make test # self-host with littlefs-fuse for fuzz test - make -C littlefs-fuse diff --git a/lfs.c b/lfs.c index f602197778..b043bd907d 100644 --- a/lfs.c +++ b/lfs.c @@ -278,7 +278,7 @@ static int lfs_alloc_lookahead(void *p, lfs_block_t block) { % (lfs_soff_t)(lfs->cfg->block_count)) + lfs->cfg->block_count) % lfs->cfg->block_count; - if (off < lfs->free.lookahead) { + if (off < lfs->cfg->lookahead) { lfs->free.buffer[off / 32] |= 1U << (off % 32); } @@ -294,7 +294,8 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { return LFS_ERR_NOSPC; } - if (lfs->free.off >= lfs->free.lookahead) { + if (lfs->free.off >= lfs_min( + lfs->cfg->lookahead, lfs->cfg->block_count)) { break; } @@ -308,11 +309,11 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { } } - lfs->free.begin += lfs->free.lookahead; + lfs->free.begin += lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count); lfs->free.off = 0; // find mask of free blocks from tree - memset(lfs->free.buffer, 0, lfs->free.lookahead/8); + memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8); int err = lfs_traverse(lfs, lfs_alloc_lookahead, lfs); if (err) { return err; @@ -1870,13 +1871,12 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { } // setup lookahead, round down to nearest 32-bits - lfs->free.lookahead = lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count); - lfs->free.lookahead = 32 * (lfs->free.lookahead / 32); - assert(lfs->free.lookahead > 0); + assert(lfs->cfg->lookahead % 32 == 0); + assert(lfs->cfg->lookahead > 0); if (lfs->cfg->lookahead_buffer) { lfs->free.buffer = lfs->cfg->lookahead_buffer; } else { - lfs->free.buffer = malloc(lfs->free.lookahead/8); + lfs->free.buffer = malloc(lfs->cfg->lookahead/8); if (!lfs->free.buffer) { return LFS_ERR_NOMEM; } @@ -1919,10 +1919,10 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { } // create free lookahead - memset(lfs->free.buffer, 0, lfs->free.lookahead/8); + memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8); lfs->free.begin = 0; lfs->free.off = 0; - lfs->free.end = lfs->free.begin + lfs->cfg->block_count; + lfs->free.end = lfs->free.begin + lfs->free.off + lfs->cfg->block_count; // create superblock dir lfs_alloc_ack(lfs); @@ -1998,9 +1998,9 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { } // setup free lookahead - lfs->free.begin = -lfs->free.lookahead; - lfs->free.off = lfs->free.lookahead; - lfs->free.end = lfs->free.begin + lfs->cfg->block_count; + lfs->free.begin = -lfs->cfg->lookahead; + lfs->free.off = lfs->cfg->lookahead; + lfs->free.end = lfs->free.begin + lfs->free.off + lfs->cfg->block_count; // load superblock lfs_dir_t dir; @@ -2027,7 +2027,7 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { } if (superblock.d.version > (0x00010001 | 0x0000ffff)) { - LFS_ERROR("Invalid version %d.%d\n", + LFS_ERROR("Invalid version %d.%d", 0xffff & (superblock.d.version >> 16), 0xffff & (superblock.d.version >> 0)); return LFS_ERR_INVAL; diff --git a/lfs.h b/lfs.h index 270a65efb9..e4aab0e4b2 100644 --- a/lfs.h +++ b/lfs.h @@ -236,7 +236,6 @@ typedef struct lfs_superblock { } lfs_superblock_t; typedef struct lfs_free { - lfs_size_t lookahead; lfs_block_t begin; lfs_block_t end; lfs_block_t off; From 0f4e334388e0e05554a78d14d0a5ef73f02209b2 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 16 Nov 2017 18:16:32 -0600 Subject: [PATCH 25/46] Squashed 'littlefs/' changes from 3f31c8c..78c79ec 78c79ec Added QUIET flag to tests so CI is readable f9f4f5c Fixed standard name mismatch LFS_ERR_EXISTS -> LFS_ERR_EXIST 843e3c6 Added sticky-bit for preventing file syncs after write errors 2612e1b Modified lfs_ctz_extend to be a little bit safer 6664723 Fixed issue with committing directories to bad-blocks that are stuck git-subtree-dir: littlefs git-subtree-split: 78c79ecb9e6b8dd0e7cfd7ac86934e43fb026924 --- .travis.yml | 12 +++++------ Makefile | 4 ++++ emubd/lfs_emubd.c | 8 +++---- lfs.c | 50 +++++++++++++++++++++++++++---------------- lfs.h | 3 ++- tests/test_alloc.sh | 28 ++++++++++++++++-------- tests/test_corrupt.sh | 11 ++++++++++ tests/test_dirs.sh | 2 +- 8 files changed, 78 insertions(+), 40 deletions(-) diff --git a/.travis.yml b/.travis.yml index 552f7cc9a7..d673c159ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,13 +9,13 @@ script: -include stdio.h -Werror' make all size # run tests - - make test + - make test QUIET=1 # run tests with a few different configurations - - CFLAGS="-DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make test - - CFLAGS="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make test - - CFLAGS="-DLFS_BLOCK_COUNT=1023" make test - - CFLAGS="-DLFS_LOOKAHEAD=2048" make test + - CFLAGS="-DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make test QUIET=1 + - CFLAGS="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make test QUIET=1 + - CFLAGS="-DLFS_BLOCK_COUNT=1023" make test QUIET=1 + - CFLAGS="-DLFS_LOOKAHEAD=2048" make test QUIET=1 # self-host with littlefs-fuse for fuzz test - make -C littlefs-fuse @@ -28,7 +28,7 @@ script: - cp -r $(git ls-tree --name-only HEAD) mount/littlefs - cd mount/littlefs - ls - - make -B test_dirs + - make -B test_dirs QUIET=1 before_install: - fusermount -V diff --git a/Makefile b/Makefile index bd5bd905a1..2ef12876ee 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,11 @@ size: $(OBJ) test: test_format test_dirs test_files test_seek test_parallel \ test_alloc test_paths test_orphan test_move test_corrupt test_%: tests/test_%.sh +ifdef QUIET + ./$< | sed '/^[^-=]/d' +else ./$< +endif -include $(DEP) diff --git a/emubd/lfs_emubd.c b/emubd/lfs_emubd.c index ca2b6b928e..b87d6deba0 100644 --- a/emubd/lfs_emubd.c +++ b/emubd/lfs_emubd.c @@ -138,8 +138,8 @@ int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block, snprintf(emu->child, LFS_NAME_MAX, "%x", block); FILE *f = fopen(emu->path, "r+b"); - if (!f && errno != ENOENT) { - return -errno; + if (!f) { + return (errno == EACCES) ? 0 : -errno; } // Check that file was erased @@ -189,14 +189,14 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) { return -errno; } - if (!err && S_ISREG(st.st_mode)) { + if (!err && S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode)) { int err = unlink(emu->path); if (err) { return -errno; } } - if (err || S_ISREG(st.st_mode)) { + if (errno == ENOENT || (S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode))) { FILE *f = fopen(emu->path, "w"); if (!f) { return -errno; diff --git a/lfs.c b/lfs.c index b043bd907d..cddc6fa7ff 100644 --- a/lfs.c +++ b/lfs.c @@ -531,18 +531,19 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir, } // successful commit, check checksum to make sure - crc = 0xffffffff; + uint32_t ncrc = 0xffffffff; err = lfs_bd_crc(lfs, dir->pair[0], 0, - 0x7fffffff & dir->d.size, &crc); + (0x7fffffff & dir->d.size)-4, &ncrc); if (err) { return err; } - if (crc == 0) { - break; + if (ncrc != crc) { + goto relocate; } } + break; relocate: //commit was corrupted LFS_DEBUG("Bad block at %d", dir->pair[0]); @@ -818,7 +819,7 @@ int lfs_mkdir(lfs_t *lfs, const char *path) { lfs_entry_t entry; err = lfs_dir_find(lfs, &cwd, &entry, &path); if (err != LFS_ERR_NOENT || strchr(path, '/') != NULL) { - return err ? err : LFS_ERR_EXISTS; + return err ? err : LFS_ERR_EXIST; } // build up new directory @@ -1053,17 +1054,18 @@ static int lfs_ctz_find(lfs_t *lfs, static int lfs_ctz_extend(lfs_t *lfs, lfs_cache_t *rcache, lfs_cache_t *pcache, lfs_block_t head, lfs_size_t size, - lfs_off_t *block, lfs_block_t *off) { + lfs_block_t *block, lfs_off_t *off) { while (true) { - if (true) { - // go ahead and grab a block - int err = lfs_alloc(lfs, block); - if (err) { - return err; - } - assert(*block >= 2 && *block <= lfs->cfg->block_count); + // go ahead and grab a block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + assert(nblock >= 2 && nblock <= lfs->cfg->block_count); - err = lfs_bd_erase(lfs, *block); + if (true) { + err = lfs_bd_erase(lfs, nblock); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; @@ -1072,6 +1074,7 @@ static int lfs_ctz_extend(lfs_t *lfs, } if (size == 0) { + *block = nblock; *off = 0; return 0; } @@ -1091,7 +1094,7 @@ static int lfs_ctz_extend(lfs_t *lfs, } err = lfs_cache_prog(lfs, pcache, rcache, - *block, i, &data, 1); + nblock, i, &data, 1); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; @@ -1100,6 +1103,7 @@ static int lfs_ctz_extend(lfs_t *lfs, } } + *block = nblock; *off = size; return 0; } @@ -1110,7 +1114,7 @@ static int lfs_ctz_extend(lfs_t *lfs, for (lfs_off_t i = 0; i < skips; i++) { int err = lfs_cache_prog(lfs, pcache, rcache, - *block, 4*i, &head, 4); + nblock, 4*i, &head, 4); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; @@ -1129,12 +1133,13 @@ static int lfs_ctz_extend(lfs_t *lfs, assert(head >= 2 && head <= lfs->cfg->block_count); } + *block = nblock; *off = 4*skips; return 0; } relocate: - LFS_DEBUG("Bad block at %d", *block); + LFS_DEBUG("Bad block at %d", nblock); // just clear cache and try a new block pcache->block = 0xffffffff; @@ -1214,7 +1219,7 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file, } else if (entry.d.type == LFS_TYPE_DIR) { return LFS_ERR_ISDIR; } else if (flags & LFS_O_EXCL) { - return LFS_ERR_EXISTS; + return LFS_ERR_EXIST; } // setup file struct @@ -1398,7 +1403,9 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { return err; } - if ((file->flags & LFS_F_DIRTY) && !lfs_pairisnull(file->pair)) { + if ((file->flags & LFS_F_DIRTY) && + !(file->flags & LFS_F_ERRED) && + !lfs_pairisnull(file->pair)) { // update dir entry lfs_dir_t cwd; int err = lfs_dir_fetch(lfs, &cwd, file->pair); @@ -1532,6 +1539,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, file->head, file->size, file->pos-1, &file->block, &file->off); if (err) { + file->flags |= LFS_F_ERRED; return err; } @@ -1545,6 +1553,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, file->block, file->pos, &file->block, &file->off); if (err) { + file->flags |= LFS_F_ERRED; return err; } @@ -1560,6 +1569,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, if (err == LFS_ERR_CORRUPT) { goto relocate; } + file->flags |= LFS_F_ERRED; return err; } @@ -1567,6 +1577,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, relocate: err = lfs_file_relocate(lfs, file); if (err) { + file->flags |= LFS_F_ERRED; return err; } } @@ -1579,6 +1590,7 @@ relocate: lfs_alloc_ack(lfs); } + file->flags &= ~LFS_F_ERRED; return size; } diff --git a/lfs.h b/lfs.h index e4aab0e4b2..7757f8b9b4 100644 --- a/lfs.h +++ b/lfs.h @@ -45,7 +45,7 @@ enum lfs_error { LFS_ERR_IO = -5, // Error during device operation LFS_ERR_CORRUPT = -52, // Corrupted LFS_ERR_NOENT = -2, // No directory entry - LFS_ERR_EXISTS = -17, // Entry already exists + LFS_ERR_EXIST = -17, // Entry already exists LFS_ERR_NOTDIR = -20, // Entry is not a dir LFS_ERR_ISDIR = -21, // Entry is a dir LFS_ERR_INVAL = -22, // Invalid parameter @@ -75,6 +75,7 @@ enum lfs_open_flags { LFS_F_DIRTY = 0x10000, // File does not match storage LFS_F_WRITING = 0x20000, // File has been written since last flush LFS_F_READING = 0x40000, // File has been read since last flush + LFS_F_ERRED = 0x80000, // An error occured during write }; // File seek flags diff --git a/tests/test_alloc.sh b/tests/test_alloc.sh index 630be2a1bc..aaae6551e1 100755 --- a/tests/test_alloc.sh +++ b/tests/test_alloc.sh @@ -121,6 +121,7 @@ tests/test.py << TEST size = strlen("exhaustion"); memcpy(buffer, "exhaustion", size); lfs_file_write(&lfs, &file[0], buffer, size) => size; + lfs_file_sync(&lfs, &file[0]) => 0; size = strlen("blahblahblahblah"); memcpy(buffer, "blahblahblahblah", size); @@ -142,6 +143,7 @@ tests/test.py << TEST lfs_mount(&lfs, &cfg) => 0; lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_RDONLY); size = strlen("exhaustion"); + lfs_file_size(&lfs, &file[0]) => size; lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "exhaustion", size) => 0; lfs_file_close(&lfs, &file[0]) => 0; @@ -166,6 +168,7 @@ tests/test.py << TEST size = strlen("exhaustion"); memcpy(buffer, "exhaustion", size); lfs_file_write(&lfs, &file[0], buffer, size) => size; + lfs_file_sync(&lfs, &file[0]) => 0; size = strlen("blahblahblahblah"); memcpy(buffer, "blahblahblahblah", size); @@ -187,6 +190,7 @@ tests/test.py << TEST lfs_mount(&lfs, &cfg) => 0; lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_RDONLY); size = strlen("exhaustion"); + lfs_file_size(&lfs, &file[0]) => size; lfs_file_read(&lfs, &file[0], buffer, size) => size; memcmp(buffer, "exhaustion", size) => 0; lfs_file_close(&lfs, &file[0]) => 0; @@ -196,14 +200,14 @@ TEST echo "--- Dir exhaustion test ---" tests/test.py << TEST lfs_mount(&lfs, &cfg) => 0; - lfs_stat(&lfs, "exhaustion", &info) => 0; - lfs_size_t fullsize = info.size; lfs_remove(&lfs, "exhaustion") => 0; lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); size = strlen("blahblahblahblah"); memcpy(buffer, "blahblahblahblah", size); - for (lfs_size_t i = 0; i < fullsize - 2*512; i += size) { + for (lfs_size_t i = 0; + i < (cfg.block_count-6)*(cfg.block_size-8); + i += size) { lfs_file_write(&lfs, &file[0], buffer, size) => size; } lfs_file_close(&lfs, &file[0]) => 0; @@ -214,7 +218,11 @@ tests/test.py << TEST lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_APPEND); size = strlen("blahblahblahblah"); memcpy(buffer, "blahblahblahblah", size); - lfs_file_write(&lfs, &file[0], buffer, size) => size; + for (lfs_size_t i = 0; + i < (cfg.block_size-8); + i += size) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } lfs_file_close(&lfs, &file[0]) => 0; lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC; @@ -224,14 +232,14 @@ TEST echo "--- Chained dir exhaustion test ---" tests/test.py << TEST lfs_mount(&lfs, &cfg) => 0; - lfs_stat(&lfs, "exhaustion", &info) => 0; - lfs_size_t fullsize = info.size; - lfs_remove(&lfs, "exhaustion") => 0; + lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); size = strlen("blahblahblahblah"); memcpy(buffer, "blahblahblahblah", size); - for (lfs_size_t i = 0; i < fullsize - 19*512; i += size) { + for (lfs_size_t i = 0; + i < (cfg.block_count-24)*(cfg.block_size-8); + i += size) { lfs_file_write(&lfs, &file[0], buffer, size) => size; } lfs_file_close(&lfs, &file[0]) => 0; @@ -247,7 +255,9 @@ tests/test.py << TEST lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT); size = strlen("blahblahblahblah"); memcpy(buffer, "blahblahblahblah", size); - for (lfs_size_t i = 0; i < fullsize - 20*512; i += size) { + for (lfs_size_t i = 0; + i < (cfg.block_count-26)*(cfg.block_size-8); + i += size) { lfs_file_write(&lfs, &file[0], buffer, size) => size; } lfs_file_close(&lfs, &file[0]) => 0; diff --git a/tests/test_corrupt.sh b/tests/test_corrupt.sh index d79a8c8964..44f1caee32 100755 --- a/tests/test_corrupt.sh +++ b/tests/test_corrupt.sh @@ -82,6 +82,17 @@ do lfs_chktree done +echo "--- Block persistance ---" +for i in {0..33} +do + rm -rf blocks + mkdir blocks + lfs_mktree + chmod a-w blocks/$(printf '%x' $i) + lfs_mktree + lfs_chktree +done + echo "--- Big region corruption ---" rm -rf blocks mkdir blocks diff --git a/tests/test_dirs.sh b/tests/test_dirs.sh index 5a7ea58b7e..8b69e7e8ce 100755 --- a/tests/test_dirs.sh +++ b/tests/test_dirs.sh @@ -56,7 +56,7 @@ TEST echo "--- Directory failures ---" tests/test.py << TEST lfs_mount(&lfs, &cfg) => 0; - lfs_mkdir(&lfs, "potato") => LFS_ERR_EXISTS; + lfs_mkdir(&lfs, "potato") => LFS_ERR_EXIST; lfs_dir_open(&lfs, &dir[0], "tomato") => LFS_ERR_NOENT; lfs_dir_open(&lfs, &dir[0], "burito") => LFS_ERR_NOTDIR; lfs_file_open(&lfs, &file[0], "tomato", LFS_O_RDONLY) => LFS_ERR_NOENT; From 7eaf61c04790eea930c6105175528f5353c3720e Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 16 Nov 2017 12:06:59 -0600 Subject: [PATCH 26/46] Fixed memory leak when files/dirs error Error path did not clean up after itself correctly --- LittleFileSystem.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/LittleFileSystem.cpp b/LittleFileSystem.cpp index e1a2f03655..49c1fdc19a 100644 --- a/LittleFileSystem.cpp +++ b/LittleFileSystem.cpp @@ -332,12 +332,16 @@ int LittleFileSystem::stat(const char *name, struct stat *st) { ////// File operations ////// int LittleFileSystem::file_open(fs_file_t *file, const char *path, int flags) { lfs_file_t *f = new lfs_file_t; - *file = f; _mutex.lock(); LFS_INFO("file_open(%p, \"%s\", 0x%x)", *file, path, flags); int err = lfs_file_open(&_lfs, f, path, lfs_fromflags(flags)); LFS_INFO("file_open -> %d", lfs_toerror(err)); _mutex.unlock(); + if (!err) { + *file = f; + } else { + delete f; + } return lfs_toerror(err); } @@ -416,12 +420,16 @@ off_t LittleFileSystem::file_size(fs_file_t file) { ////// Dir operations ////// int LittleFileSystem::dir_open(fs_dir_t *dir, const char *path) { lfs_dir_t *d = new lfs_dir_t; - *dir = d; _mutex.lock(); LFS_INFO("dir_open(%p, \"%s\")", *dir, path); int err = lfs_dir_open(&_lfs, d, path); LFS_INFO("dir_open -> %d", lfs_toerror(err)); _mutex.unlock(); + if (!err) { + *dir = d; + } else { + delete d; + } return lfs_toerror(err); } From bb155adc161deab6fb0aa648e5d0c6892d050d02 Mon Sep 17 00:00:00 2001 From: Russ Butler Date: Thu, 16 Nov 2017 18:50:49 -0600 Subject: [PATCH 27/46] Add filesystem recovery tests Add tests for filesystem resilience and wear leveling. These tests take shared filesystem code and simulate different scenarios while this code is running. Information on the new tests can be found below. mbed-littlefs-tests-filesystem_recovery-resilience: Tests that after every block device operation the filesystem is in a well defined state. mbed-littlefs-tests-filesystem_recovery-wear_leveling: Tests that the littlefs correctly handles when flash is exhausted by using a simulated block device until there are no free good blocks. Note - This patch also adds several new block devices for testing. These will eventually be moved into mbed-os. --- TESTS/filesystem_recovery/resilience/main.cpp | 80 +++ .../wear_leveling/main.cpp | 93 +++ TESTS_COMMON/ExhaustibleBlockDevice.cpp | 107 +++ TESTS_COMMON/ExhaustibleBlockDevice.h | 135 ++++ TESTS_COMMON/ObservingBlockDevice.cpp | 96 +++ TESTS_COMMON/ObservingBlockDevice.h | 123 ++++ TESTS_COMMON/ReadOnlyBlockDevice.cpp | 83 +++ TESTS_COMMON/ReadOnlyBlockDevice.h | 116 +++ TESTS_COMMON/atomic_usage.cpp | 680 ++++++++++++++++++ TESTS_COMMON/atomic_usage.h | 72 ++ 10 files changed, 1585 insertions(+) create mode 100644 TESTS/filesystem_recovery/resilience/main.cpp create mode 100644 TESTS/filesystem_recovery/wear_leveling/main.cpp create mode 100644 TESTS_COMMON/ExhaustibleBlockDevice.cpp create mode 100644 TESTS_COMMON/ExhaustibleBlockDevice.h create mode 100644 TESTS_COMMON/ObservingBlockDevice.cpp create mode 100644 TESTS_COMMON/ObservingBlockDevice.h create mode 100644 TESTS_COMMON/ReadOnlyBlockDevice.cpp create mode 100644 TESTS_COMMON/ReadOnlyBlockDevice.h create mode 100644 TESTS_COMMON/atomic_usage.cpp create mode 100644 TESTS_COMMON/atomic_usage.h diff --git a/TESTS/filesystem_recovery/resilience/main.cpp b/TESTS/filesystem_recovery/resilience/main.cpp new file mode 100644 index 0000000000..de57514a94 --- /dev/null +++ b/TESTS/filesystem_recovery/resilience/main.cpp @@ -0,0 +1,80 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "mbed.h" +#include "unity.h" +#include "utest.h" +#include "test_env.h" + +#include "atomic_usage.h" +#include "ObservingBlockDevice.h" +#include "LittleFileSystem.h" + + +using namespace utest::v1; + +#define TEST_CYCLES 10 +#define TEST_BD_SIZE (16 * 1024) + +/** + * Check that the filesystem is valid after every change + * + * This test is to ensure that littlefs contains a valid filesystem at + * all times. This property is required for handling unexpected power + * loss. + */ +void test_resilience() +{ + HeapBlockDevice bd(TEST_BD_SIZE); + + // Setup the test + setup_atomic_operations(&bd, true); + + // Run check on every write operation + ObservingBlockDevice observer(&bd); + observer.attach(check_atomic_operations); + + // Perform operations + printf("Performing %i operations on flash\n", TEST_CYCLES); + for (int i = 1; i <= TEST_CYCLES; i++) { + int64_t ret = perform_atomic_operations(&observer); + TEST_ASSERT_EQUAL(i, ret); + } + printf("No errors detected\n"); +} + +Case cases[] = { + Case("test resilience", test_resilience), +}; + +utest::v1::status_t greentea_test_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(20, "default_auto"); + return greentea_test_setup_handler(number_of_cases); +} + +Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); + +int main() +{ + Harness::run(specification); +} diff --git a/TESTS/filesystem_recovery/wear_leveling/main.cpp b/TESTS/filesystem_recovery/wear_leveling/main.cpp new file mode 100644 index 0000000000..5cdb1feab0 --- /dev/null +++ b/TESTS/filesystem_recovery/wear_leveling/main.cpp @@ -0,0 +1,93 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "mbed.h" +#include "unity.h" +#include "utest.h" +#include "test_env.h" + +#include "atomic_usage.h" +#include "ExhaustibleBlockDevice.h" +#include "LittleFileSystem.h" + +using namespace utest::v1; + +#define ERASE_CYCLES 20 +#define TEST_BD_SIZE (8 * 1024) + +static uint32_t test_wear_leveling_size(uint32_t bd_size) +{ + HeapBlockDevice hbd(bd_size, 1, 1, 512); + ExhaustibleBlockDevice ebd(&hbd, ERASE_CYCLES); + + printf("Testing size %lu\n", bd_size); + setup_atomic_operations(&ebd, true); + + int64_t cycles = 0; + while (true) { + int64_t ret = perform_atomic_operations(&ebd); + check_atomic_operations(&ebd); + if (-1 == ret) { + break; + } + cycles++; + TEST_ASSERT_EQUAL(cycles, ret); + + } + + printf(" Simulated flash lasted %lli cylces\n", cycles); + return cycles; +} + +/** + * Check that storage life is proportional to storage size + * + * This test is to ensure that littlefs is properly handling wear + * leveling. It does this by creating a simulated flash block device + * which can be worn out and then using it until it is exhausted. + * It then doubles the size of the block device and runs the same + * test. If the block device with twice the size lasts at least + * twice as long then the test passes. + */ +void test_wear_leveling() +{ + uint32_t cycles_1 = test_wear_leveling_size(TEST_BD_SIZE * 1); + uint32_t cycles_2 = test_wear_leveling_size(TEST_BD_SIZE * 2); + TEST_ASSERT(cycles_2 > cycles_1 * 2); +} + +Case cases[] = { + Case("test wear leveling", test_wear_leveling), +}; + +utest::v1::status_t greentea_test_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(60, "default_auto"); + return greentea_test_setup_handler(number_of_cases); +} + +Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); + +int main() +{ + Harness::run(specification); +} diff --git a/TESTS_COMMON/ExhaustibleBlockDevice.cpp b/TESTS_COMMON/ExhaustibleBlockDevice.cpp new file mode 100644 index 0000000000..bc4f349c3e --- /dev/null +++ b/TESTS_COMMON/ExhaustibleBlockDevice.cpp @@ -0,0 +1,107 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ExhaustibleBlockDevice.h" + + +ExhaustibleBlockDevice::ExhaustibleBlockDevice(BlockDevice *bd, uint32_t erase_cycles) + : _bd(bd), _erase_array(NULL), _erase_cycles(erase_cycles) +{ +} + +ExhaustibleBlockDevice::~ExhaustibleBlockDevice() +{ + delete[] _erase_array; +} + +int ExhaustibleBlockDevice::init() +{ + int err = _bd->init(); + if (err) { + return err; + } + + if (!_erase_array) { + // can only be allocated after initialization + _erase_array = new uint32_t[_bd->size() / _bd->get_erase_size()]; + for (size_t i = 0; i < _bd->size() / _bd->get_erase_size(); i++) { + _erase_array[i] = _erase_cycles; + } + } + + return 0; +} + +int ExhaustibleBlockDevice::deinit() +{ + // _erase_array is lazily cleaned up in destructor to allow + // data to live across de/reinitialization + return _bd->deinit(); +} + +int ExhaustibleBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size) +{ + return _bd->read(buffer, addr, size); +} + +int ExhaustibleBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_t size) +{ + MBED_ASSERT(is_valid_program(addr, size)); + + if (_erase_array[addr / get_erase_size()] == 0) { + // TODO possibly something more destructive here + return 0; + } + + return _bd->program(buffer, addr, size); +} + +int ExhaustibleBlockDevice::erase(bd_addr_t addr, bd_size_t size) +{ + MBED_ASSERT(is_valid_erase(addr, size)); + + // use an erase cycle + if (_erase_array[addr / get_erase_size()] > 0) { + _erase_array[addr / get_erase_size()] -= 1; + } + + if (_erase_array[addr / get_erase_size()] == 0) { + // TODO possibly something more destructive here + return 0; + } + + return _bd->erase(addr, size); +} + +bd_size_t ExhaustibleBlockDevice::get_read_size() const +{ + return _bd->get_read_size(); +} + +bd_size_t ExhaustibleBlockDevice::get_program_size() const +{ + return _bd->get_program_size(); +} + +bd_size_t ExhaustibleBlockDevice::get_erase_size() const +{ + return _bd->get_erase_size(); +} + +bd_size_t ExhaustibleBlockDevice::size() const +{ + return _bd->size(); +} diff --git a/TESTS_COMMON/ExhaustibleBlockDevice.h b/TESTS_COMMON/ExhaustibleBlockDevice.h new file mode 100644 index 0000000000..fd6100f5d5 --- /dev/null +++ b/TESTS_COMMON/ExhaustibleBlockDevice.h @@ -0,0 +1,135 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef MBED_EXHAUSTIBLE_BLOCK_DEVICE_H +#define MBED_EXHAUSTIBLE_BLOCK_DEVICE_H + +#include "BlockDevice.h" +#include "mbed.h" + + +/** Heap backed block device which simulates failures + * + * Similar to heap block device but sectors wear out and are no longer programmable + * after a configurable number of cycles. + * + */ +class ExhaustibleBlockDevice : public BlockDevice +{ +public: + /** Lifetime of the block device + * + * @param bd Block device to back the ExhaustibleBlockDevice + * @param erase_cycles Number of erase cycles before failure + */ + ExhaustibleBlockDevice(BlockDevice *bd, uint32_t erase_cycles); + virtual ~ExhaustibleBlockDevice(); + + /** + * Get the number of erase cycles remaining on a block + * + * @param addr Any address in the block being queried for erase cycles + * @return Number of erase cycles remaining + */ + uint32_t get_erase_cycles(bd_addr_t addr) const; + + /** + * Set the number of erase cycles before failure + * + * @param addr Any address in the block being queried for erase cycles + * @param cycles Erase cycles before the block malfunctions + */ + void set_erase_cycles(bd_addr_t addr, uint32_t cycles); + + /** Initialize a block device + * + * @return 0 on success or a negative error code on failure + */ + virtual int init(); + + /** Deinitialize a block device + * + * @return 0 on success or a negative error code on failure + */ + virtual int deinit(); + + /** Read blocks from a block device + * + * @param buffer Buffer to read blocks into + * @param addr Address of block to begin reading from + * @param size Size to read in bytes, must be a multiple of read block size + * @return 0 on success, negative error code on failure + */ + virtual int read(void *buffer, bd_addr_t addr, bd_size_t size); + + /** Program blocks to a block device + * + * The blocks must have been erased prior to being programmed + * + * @param buffer Buffer of data to write to blocks + * @param addr Address of block to begin writing to + * @param size Size to write in bytes, must be a multiple of program block size + * @return 0 on success, negative error code on failure + */ + virtual int program(const void *buffer, bd_addr_t addr, bd_size_t size); + + /** Erase blocks on a block device + * + * The state of an erased block is undefined until it has been programmed + * + * @param addr Address of block to begin erasing + * @param size Size to erase in bytes, must be a multiple of erase block size + * @return 0 on success, negative error code on failure + */ + virtual int erase(bd_addr_t addr, bd_size_t size); + + /** Get the size of a readable block + * + * @return Size of a readable block in bytes + */ + virtual bd_size_t get_read_size() const; + + /** Get the size of a programable block + * + * @return Size of a programable block in bytes + */ + virtual bd_size_t get_program_size() const; + + /** Get the size of a eraseable block + * + * @return Size of a eraseable block in bytes + */ + virtual bd_size_t get_erase_size() const; + + /** Get the total size of the underlying device + * + * @return Size of the underlying device in bytes + */ + virtual bd_size_t size() const; + +private: + BlockDevice *_bd; + uint32_t *_erase_array; + uint32_t _erase_cycles; +}; + + +#endif diff --git a/TESTS_COMMON/ObservingBlockDevice.cpp b/TESTS_COMMON/ObservingBlockDevice.cpp new file mode 100644 index 0000000000..bdc87ea70b --- /dev/null +++ b/TESTS_COMMON/ObservingBlockDevice.cpp @@ -0,0 +1,96 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "ObservingBlockDevice.h" +#include "ReadOnlyBlockDevice.h" + + +ObservingBlockDevice::ObservingBlockDevice(BlockDevice *bd) + : _bd(bd) +{ + // Does nothing +} + +ObservingBlockDevice::~ObservingBlockDevice() +{ + // Does nothing +} + +void ObservingBlockDevice::attach(Callback cb) +{ + _change = cb; +} + +int ObservingBlockDevice::init() +{ + return _bd->init(); +} + +int ObservingBlockDevice::deinit() +{ + return _bd->deinit(); +} + +int ObservingBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size) +{ + return _bd->read(buffer, addr, size); +} + +int ObservingBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_t size) +{ + int res = _bd->program(buffer, addr, size); + if (_change) { + ReadOnlyBlockDevice dev(_bd); + _change(&dev); + } + return res; +} + +int ObservingBlockDevice::erase(bd_addr_t addr, bd_size_t size) +{ + int res = _bd->erase(addr, size); + if (_change) { + ReadOnlyBlockDevice dev(_bd); + _change(&dev); + } + return res; +} + +bd_size_t ObservingBlockDevice::get_read_size() const +{ + return _bd->get_read_size(); +} + +bd_size_t ObservingBlockDevice::get_program_size() const +{ + return _bd->get_program_size(); +} + +bd_size_t ObservingBlockDevice::get_erase_size() const +{ + return _bd->get_erase_size(); +} + +bd_size_t ObservingBlockDevice::size() const +{ + return _bd->size(); +} diff --git a/TESTS_COMMON/ObservingBlockDevice.h b/TESTS_COMMON/ObservingBlockDevice.h new file mode 100644 index 0000000000..8449a1e8f7 --- /dev/null +++ b/TESTS_COMMON/ObservingBlockDevice.h @@ -0,0 +1,123 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef MBED_OBSERVING_BLOCK_DEVICE_H +#define MBED_OBSERVING_BLOCK_DEVICE_H + +#include "FileSystem.h" +#include "BlockDevice.h" +#include "PlatformMutex.h" + +using namespace mbed; + + +class ObservingBlockDevice : public BlockDevice +{ +public: + + /** Lifetime of the block device + * + * @param size Size of the Block Device in bytes + * @param block Block size in bytes + */ + ObservingBlockDevice(BlockDevice *bd); + virtual ~ObservingBlockDevice(); + + /** Attach a callback which is called on change + * + * @param cb Function to call on filesystem change (erase or program) + */ + void attach(Callback cb); + + /** Initialize a block device + * + * @return 0 on success or a negative error code on failure + */ + virtual int init(); + + /** Deinitialize a block device + * + * @return 0 on success or a negative error code on failure + */ + virtual int deinit(); + + /** Read blocks from a block device + * + * @param buffer Buffer to read blocks into + * @param addr Address of block to begin reading from + * @param size Size to read in bytes, must be a multiple of read block size + * @return 0 on success, negative error code on failure + */ + virtual int read(void *buffer, bd_addr_t addr, bd_size_t size); + + /** Program blocks to a block device + * + * The blocks must have been erased prior to being programmed + * + * @param buffer Buffer of data to write to blocks + * @param addr Address of block to begin writing to + * @param size Size to write in bytes, must be a multiple of program block size + * @return 0 on success, negative error code on failure + */ + virtual int program(const void *buffer, bd_addr_t addr, bd_size_t size); + + /** Erase blocks on a block device + * + * The state of an erased block is undefined until it has been programmed + * + * @param addr Address of block to begin erasing + * @param size Size to erase in bytes, must be a multiple of erase block size + * @return 0 on success, negative error code on failure + */ + virtual int erase(bd_addr_t addr, bd_size_t size); + + /** Get the size of a readable block + * + * @return Size of a readable block in bytes + */ + virtual bd_size_t get_read_size() const; + + /** Get the size of a programable block + * + * @return Size of a programable block in bytes + */ + virtual bd_size_t get_program_size() const; + + /** Get the size of a eraseable block + * + * @return Size of a eraseable block in bytes + */ + virtual bd_size_t get_erase_size() const; + + /** Get the total size of the underlying device + * + * @return Size of the underlying device in bytes + */ + virtual bd_size_t size() const; + +private: + BlockDevice *_bd; + Callback _change; +}; + + + +#endif diff --git a/TESTS_COMMON/ReadOnlyBlockDevice.cpp b/TESTS_COMMON/ReadOnlyBlockDevice.cpp new file mode 100644 index 0000000000..84ab36ff2d --- /dev/null +++ b/TESTS_COMMON/ReadOnlyBlockDevice.cpp @@ -0,0 +1,83 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "ReadOnlyBlockDevice.h" +#include "mbed_error.h" + + +ReadOnlyBlockDevice::ReadOnlyBlockDevice(BlockDevice *bd) + : _bd(bd) +{ + // Does nothing +} + +ReadOnlyBlockDevice::~ReadOnlyBlockDevice() +{ + // Does nothing +} + +int ReadOnlyBlockDevice::init() +{ + return _bd->init(); +} + +int ReadOnlyBlockDevice::deinit() +{ + return _bd->deinit(); +} + +int ReadOnlyBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size) +{ + return _bd->read(buffer, addr, size); +} + +int ReadOnlyBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_t size) +{ + error("ReadOnlyBlockDevice::program() not allowed"); + return 0; +} + +int ReadOnlyBlockDevice::erase(bd_addr_t addr, bd_size_t size) +{ + error("ReadOnlyBlockDevice::erase() not allowed"); + return 0; +} + +bd_size_t ReadOnlyBlockDevice::get_read_size() const +{ + return _bd->get_read_size(); +} + +bd_size_t ReadOnlyBlockDevice::get_program_size() const +{ + return _bd->get_program_size(); +} + +bd_size_t ReadOnlyBlockDevice::get_erase_size() const +{ + return _bd->get_erase_size(); +} + +bd_size_t ReadOnlyBlockDevice::size() const +{ + return _bd->size(); +} diff --git a/TESTS_COMMON/ReadOnlyBlockDevice.h b/TESTS_COMMON/ReadOnlyBlockDevice.h new file mode 100644 index 0000000000..f6dfee8a8c --- /dev/null +++ b/TESTS_COMMON/ReadOnlyBlockDevice.h @@ -0,0 +1,116 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef MBED_READ_ONLY_BLOCK_DEVICE_H +#define MBED_READ_ONLY_BLOCK_DEVICE_H + +#include "FileSystem.h" +#include "BlockDevice.h" +#include "PlatformMutex.h" + +using namespace mbed; + + +class ReadOnlyBlockDevice : public BlockDevice +{ +public: + + /** Lifetime of the block device + * + * @param size Size of the Block Device in bytes + * @param block Block size in bytes + */ + ReadOnlyBlockDevice(BlockDevice *bd); + virtual ~ReadOnlyBlockDevice(); + + /** Initialize a block device + * + * @return 0 on success or a negative error code on failure + */ + virtual int init(); + + /** Deinitialize a block device + * + * @return 0 on success or a negative error code on failure + */ + virtual int deinit(); + + /** Read blocks from a block device + * + * @param buffer Buffer to read blocks into + * @param addr Address of block to begin reading from + * @param size Size to read in bytes, must be a multiple of read block size + * @return 0 on success, negative error code on failure + */ + virtual int read(void *buffer, bd_addr_t addr, bd_size_t size); + + /** Program blocks to a block device + * + * The blocks must have been erased prior to being programmed + * + * @param buffer Buffer of data to write to blocks + * @param addr Address of block to begin writing to + * @param size Size to write in bytes, must be a multiple of program block size + * @return 0 on success, negative error code on failure + */ + virtual int program(const void *buffer, bd_addr_t addr, bd_size_t size); + + /** Erase blocks on a block device + * + * The state of an erased block is undefined until it has been programmed + * + * @param addr Address of block to begin erasing + * @param size Size to erase in bytes, must be a multiple of erase block size + * @return 0 on success, negative error code on failure + */ + virtual int erase(bd_addr_t addr, bd_size_t size); + + /** Get the size of a readable block + * + * @return Size of a readable block in bytes + */ + virtual bd_size_t get_read_size() const; + + /** Get the size of a programable block + * + * @return Size of a programable block in bytes + */ + virtual bd_size_t get_program_size() const; + + /** Get the size of a eraseable block + * + * @return Size of a eraseable block in bytes + */ + virtual bd_size_t get_erase_size() const; + + /** Get the total size of the underlying device + * + * @return Size of the underlying device in bytes + */ + virtual bd_size_t size() const; + +private: + BlockDevice *_bd; +}; + + + +#endif diff --git a/TESTS_COMMON/atomic_usage.cpp b/TESTS_COMMON/atomic_usage.cpp new file mode 100644 index 0000000000..883f6261fe --- /dev/null +++ b/TESTS_COMMON/atomic_usage.cpp @@ -0,0 +1,680 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +/** + * This file contains code which performs various atomic operations using + * littlefs. It is intended for use in tests and test applications to + * validate that the defined behavior below is being met. + * + * # Defined behavior + * - A file rename is atomic (Note - rename can be used to replace a file) + * - Atomic file rename tested by setup/perform/check_file_rename + * - Atomic file replace tested by setup/perform/check_file_rename_replace + * - A directory rename is atomic (Note - rename can be used to replace an empty directory) + * - Tested by setup/perform/check_directory_rename + * - Directory create is atomic + * - Directory delete is atomic + * - File create is atomic + * - File delete is atomic + * - File contents are atomically written on close + * - Tested by setup/perform/check_file_change_contents + */ + + +#include "mbed.h" +#include "greentea-client/test_env.h" +#include "unity.h" +#include "utest.h" +#include +#include + +#include "ObservingBlockDevice.h" +#include "LittleFileSystem.h" +#include "ExhaustibleBlockDevice.h" + +#include "atomic_usage.h" + +#define DEBUG(...) +#define DEBUG_CHECK(...) +#define BUFFER_SIZE 64 +// Version is written to a file and is used +// to determine if a reformat is required +#define ATOMIC_USAGE_VERSION 1 + +#define ARRAY_LENGTH(array) (sizeof(array) / sizeof(array[0])) + +#define TEST_ASSERT_OR_EXIT(condition) \ + TEST_ASSERT(condition); if (!(condition)) {error("Assert failed");} + +#define TEST_ASSERT_EQUAL_OR_EXIT(expected, actual) \ + TEST_ASSERT_EQUAL(expected, actual); if ((int64_t)(expected) != (int64_t)(actual)) {error("Assert failed");} + +using namespace utest::v1; + +typedef void (*test_function_t)(LittleFileSystem *fs); +typedef bool (*test_function_bool_t)(LittleFileSystem *fs); + +struct TestEntry { + const char *name; + test_function_t setup; + test_function_bool_t perform; + test_function_t check; +}; + +/** + * Write data to the file while checking for error conditions + * + * @param file File to write to + * @param data Data to write + * @param size Size of data to write + * @return true if flash has been exhausted, false otherwise + */ +static bool file_write(File *file, uint8_t *data, uint32_t size) +{ + int res = file->write(data, size); + if (-ENOSPC == res) { + return true; + } + TEST_ASSERT_EQUAL_OR_EXIT(size, res); + return false; +} + +/** + * Write padding data of the given size + * + * @param file Pointer to the file to write to + * @param padding Value to pad + * @param size Size to pad + * @return true if flash has been exhausted, false otherwise + */ +static bool file_pad(File *file, char padding, uint32_t size) +{ + uint8_t buf[BUFFER_SIZE]; + memset(buf, padding, sizeof(buf)); + + while (size > 0) { + uint32_t write_size = sizeof(buf) <= size ? sizeof(buf) : size; + if (file_write(file, buf, write_size)) { + return true; + } + size -= write_size; + } + + return false; +} + +/* + * Similar to fscanf but uses and mbed file + * + * @param file File to scan from + * @param format Format string of values to read + * @return the number of arguments read + */ +static int file_scanf(File *file, const char *format, ...) +{ + uint8_t buf[BUFFER_SIZE]; + va_list args; + memset(buf, 0, sizeof(buf)); + + int res = file->read(buf, sizeof(buf) - 1); + TEST_ASSERT_OR_EXIT(res >= 0); + + va_start (args, format); + int count = vsscanf((char*)buf, format, args); + va_end (args); + TEST_ASSERT_OR_EXIT(count >= 0); + + return count; +} + +/* + * Similar to fprintf but uses and mbed file + * + * @param file File to print to + * @param format Format string of values to write + * @return size written to file or -1 on out of space + */ +static int file_printf(File *file, const char *format, ...) +{ + uint8_t buf[BUFFER_SIZE]; + va_list args; + va_start (args, format); + int size = vsprintf((char*)buf, format, args); + va_end (args); + TEST_ASSERT_OR_EXIT((size >= 0) && (size <= (int)sizeof(buf))); + + if (file_write(file, buf, size)) { + return -1; + } + + return size; +} + + +static const char FILE_RENAME_A[] = "file_to_rename_a.txt"; +static const char FILE_RENAME_B[] = "file_to_rename_b.txt"; +static const char FILE_RENAME_CONTENTS[] = "Test contents for the file to be renamed"; +static const int FILE_RENAME_LEN = strlen(FILE_RENAME_CONTENTS); + +/** + * Setup for the file rename test + * + * Create file FILE_RENAME_A with contents FILE_RENAME_CONTENTS. + */ +static void setup_file_rename(LittleFileSystem *fs) +{ + DEBUG("setup_file_rename()\n"); + + File file; + + int res = file.open(fs, FILE_RENAME_A, O_WRONLY | O_CREAT); + DEBUG(" open result %i\n", res); + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + + res = file.write(FILE_RENAME_CONTENTS, FILE_RENAME_LEN); + DEBUG(" write result %i\n", res); + TEST_ASSERT_EQUAL_OR_EXIT(FILE_RENAME_LEN, res); +} + +/** + * Change the file name to either FILE_RENAME_A or FILE_RENAME_B + */ +static bool perform_file_rename(LittleFileSystem *fs) +{ + DEBUG("perform_file_rename()\n"); + + struct stat st; + int res = fs->stat(FILE_RENAME_A, &st); + const char *src = (0 == res) ? FILE_RENAME_A : FILE_RENAME_B; + const char *dst = (0 == res) ? FILE_RENAME_B : FILE_RENAME_A; + + DEBUG(" stat result %i\n", res); + TEST_ASSERT_OR_EXIT((res == -ENOENT) || (0 == res)); + + DEBUG(" Renaming %s to %s\n", src, dst); + res = fs->rename(src, dst); + if (-ENOSPC == res) { + return true; + } + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + return false; +} + +/** + * Check that the file rename is in a good state + * + * Check that there is only one file and that file contains the correct + * contents. + * + * Allowed states: + * - File FILE_RENAME_A exists with contents and FILE_RENAME_B does not + * - File FILE_RENAME_B exists with contents and FILE_RENAME_A does not + * + */ +static void check_file_rename(LittleFileSystem *fs) +{ + + int files = 0; + int valids = 0; + const char * const filenames[] = {FILE_RENAME_A, FILE_RENAME_B}; + + for (int i = 0; i < 2; i++) { + File file; + if (0 == file.open(fs, filenames[i], O_RDONLY)) { + uint8_t buf[BUFFER_SIZE]; + files++; + memset(buf, 0, sizeof(buf)); + int res = file.read(buf, FILE_RENAME_LEN); + if (res != FILE_RENAME_LEN) { + break; + } + if (memcmp(buf, FILE_RENAME_CONTENTS, FILE_RENAME_LEN) != 0) { + break; + } + valids++; + } + } + + TEST_ASSERT_EQUAL_OR_EXIT(1, files); + TEST_ASSERT_EQUAL_OR_EXIT(1, valids); +} + + +static const char FILE_RENAME_REPLACE[] = "rename_replace_file.txt"; +static const char FILE_RENAME_REPLACE_NEW[] = "new_rename_replace_file.txt"; +static const char FILE_RENAME_REPLACE_FMT[] = "file replace count: %lu\n"; + +/** + * Create the file FILE_RENAME_REPLACE with initial contents + * + * Create an write an initial count of 0 to the file. + */ +static void setup_file_rename_replace(LittleFileSystem *fs) +{ + DEBUG("setup_file_rename_replace()\n"); + File file; + + // Write out initial count + + int res = file.open(fs, FILE_RENAME_REPLACE, O_WRONLY | O_CREAT); + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + + uint32_t count = 0; + uint8_t buf[BUFFER_SIZE]; + memset(buf, 0, sizeof(buf)); + const int length = sprintf((char*)buf, FILE_RENAME_REPLACE_FMT, count); + TEST_ASSERT_OR_EXIT(length > 0); + + res = file.write(buf, length); + DEBUG(" write result %i\n", res); + TEST_ASSERT_EQUAL_OR_EXIT(length, res); +} + +/** + * Atomically increment the count in FILE_RENAME_REPLACE using a rename + */ +bool perform_file_rename_replace(LittleFileSystem *fs) +{ + DEBUG("perform_file_rename_replace()\n"); + File file; + + // Read in previous count + + int res = file.open(fs, FILE_RENAME_REPLACE, O_RDONLY); + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + + uint64_t count; + int args_read = file_scanf(&file, FILE_RENAME_REPLACE_FMT, &count); + TEST_ASSERT_EQUAL_OR_EXIT(1, args_read); + + res = file.close(); + if (-ENOSPC == res) { + return true; + } + TEST_ASSERT_EQUAL(0, res); + + // Write out new count + + count++; + + res = file.open(fs, FILE_RENAME_REPLACE_NEW, O_WRONLY | O_CREAT); + if (-ENOSPC == res) { + return true; + } + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + + if (file_printf(&file, FILE_RENAME_REPLACE_FMT, count) <= 0) { + return true; + } + + res = file.close(); + if (-ENOSPC == res) { + return true; + } + TEST_ASSERT_EQUAL(0, res); + + // Rename file + + res = fs->rename(FILE_RENAME_REPLACE_NEW, FILE_RENAME_REPLACE); + if (-ENOSPC == res) { + return true; + } + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + DEBUG(" count %llu -> %llu\n", count - 1, count); + + return false; +} + +/** + * Check that FILE_RENAME_REPLACE always has a valid count + * + * Allowed states: + * - FILE_RENAME_REPLACE exists with valid contents + */ +static void check_file_rename_replace(LittleFileSystem *fs) +{ + DEBUG_CHECK("check_file_rename_replace()\n"); + File file; + + // Read in previous count + + int res = file.open(fs, FILE_RENAME_REPLACE, O_RDONLY); + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + + uint64_t count; + int args_read = file_scanf(&file, FILE_RENAME_REPLACE_FMT, &count); + TEST_ASSERT_EQUAL_OR_EXIT(1, args_read); + DEBUG_CHECK(" count %llu\n", count); +} + + +static const char DIRECTORY_RENAME_A[] = "dir_a"; +static const char DIRECTORY_RENAME_B[] = "dir_b"; + +/** + * Create DIRECTORY_RENAME_A with initial contents + */ +static void setup_directory_rename(LittleFileSystem *fs) +{ + DEBUG("setup_directory_rename()\n"); + + int res = fs->mkdir(DIRECTORY_RENAME_A, 0777); + TEST_ASSERT_EQUAL_OR_EXIT(0, res); +} + +/* + * Change the directory name from either DIRECTORY_RENAME_A or DIRECTORY_RENAME_B to the other + */ +static bool perform_directory_rename(LittleFileSystem *fs) +{ + DEBUG("perform_directory_rename()\n"); + + struct stat st; + int res = fs->stat(DIRECTORY_RENAME_A, &st); + const char *src = (0 == res) ? DIRECTORY_RENAME_A : DIRECTORY_RENAME_B; + const char *dst = (0 == res) ? DIRECTORY_RENAME_B : DIRECTORY_RENAME_A; + + DEBUG(" stat result %i\n", res); + TEST_ASSERT_OR_EXIT((res == -ENOENT) || (0 == res)); + + DEBUG(" Renaming %s to %s\n", src, dst); + res = fs->rename(src, dst); + if (-ENOSPC == res) { + return true; + } + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + return false; +} + +/* + * Change the directory name from either DIRECTORY_RENAME_A or DIRECTORY_RENAME_B to the other + * + * Allowed states: + * - DIRECTORY_RENAME_A exists with valid contents and DIRECTORY_RENAME_B does not exist + * - DIRECTORY_RENAME_B exists with valid contents and DIRECTORY_RENAME_A does not exist + */ +static void check_directory_rename(LittleFileSystem *fs) +{ + DEBUG_CHECK("check_directory_rename()\n"); + + static const char *directory_names[] = { + DIRECTORY_RENAME_A, + DIRECTORY_RENAME_B + }; + + size_t directories = 0; + for (size_t i = 0; i < ARRAY_LENGTH(directory_names); i++) { + Dir dir; + int res = dir.open(fs, directory_names[i]); + TEST_ASSERT_OR_EXIT((-ENOENT == res) || (0 == res)); + if (0 == res) { + directories++; + } + } + TEST_ASSERT_EQUAL_OR_EXIT(1, directories); +} + + +static const char CHANGE_CONTENTS_NAME[] = "file_changing_contents.txt"; +static const char CHANGE_CONTENTS_FILL = ' '; +static const uint32_t BLOCK_SIZE = 512; + +/** + * Create file CHANGE_CONTENTS_NAME with initial contents + * + * File contains three blocks of data each which start + * with a count. + */ +static void setup_file_change_contents(LittleFileSystem *fs) +{ + DEBUG("setup_file_change_contents()\n"); + + File file; + int res = file.open(fs, CHANGE_CONTENTS_NAME, O_WRONLY | O_CREAT); + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + + for (int count = 1; count <= 3; count++) { + int size = file_printf(&file, "%lu\n", count); + TEST_ASSERT_OR_EXIT(size >= 0); + + bool dead = file_pad(&file, CHANGE_CONTENTS_FILL, BLOCK_SIZE - size); + TEST_ASSERT_EQUAL_OR_EXIT(false, dead); + } +} + +/** + * Atomically increment the counts in the file CHANGE_CONTENTS_NAME + * + * Read in the current counts, increment them and then write them + * back in non-sequential order. + */ +static bool perform_file_change_contents(LittleFileSystem *fs) +{ + DEBUG("perform_file_change_contents()\n"); + File file; + + int res = file.open(fs, CHANGE_CONTENTS_NAME, O_RDWR); + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + + // Read in values + uint32_t values[3]; + for (int i = 0; i < 3; i++) { + file.seek(i * BLOCK_SIZE); + int args_read = file_scanf(&file, "%lu\n", &values[i]); + TEST_ASSERT_EQUAL_OR_EXIT(1, args_read); + } + + // Increment values + for (int i = 0; i < 3; i++) { + values[i]++; + } + + // Write values out of order + int i; + i = 0; + file.seek(i * BLOCK_SIZE); + if (file_printf(&file, "%lu\n", values[i]) <= 0) { + return true; + } + DEBUG(" value[%i]: %lu -> %lu\n", i, values[i] - 1, values[i]); + + i = 2; + file.seek(i * BLOCK_SIZE); + if (file_printf(&file, "%lu\n", values[i]) <= 0) { + return true; + } + DEBUG(" value[%i]: %lu -> %lu\n", i, values[i] - 1, values[i]); + + i = 1; + file.seek(i * BLOCK_SIZE); + if (file_printf(&file, "%lu\n", values[i]) <= 0) { + return true; + } + DEBUG(" value[%i]: %lu -> %lu\n", i, values[i] - 1, values[i]); + + res = file.close(); + if (-ENOSPC == res) { + return true; + } + TEST_ASSERT_EQUAL(0, res); + + return false; +} + +/* + * Change the directory name from either DIRECTORY_RENAME_A or DIRECTORY_RENAME_B to the other + * + * Allowed states: + * - CHANGE_CONTENTS_NAME exists and contains 3 counts which are in order + */ +static void check_file_change_contents(LittleFileSystem *fs) +{ + DEBUG_CHECK("check_file_change_contents()\n"); + File file; + + int res = file.open(fs, CHANGE_CONTENTS_NAME, O_RDONLY); + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + + // Read in values + uint32_t values[3]; + for (int i = 0; i < 3; i++) { + file.seek(i * BLOCK_SIZE); + int args_read = file_scanf(&file, "%lu\n", &values[i]); + TEST_ASSERT_EQUAL_OR_EXIT(1, args_read); + DEBUG_CHECK(" value[%i]: %lu\n", i, values[i]); + } + + TEST_ASSERT_EQUAL_OR_EXIT(values[0] + 1, values[1]); + TEST_ASSERT_EQUAL_OR_EXIT(values[1] + 1, values[2]); +} + + +static const TestEntry atomic_test_entries[] = { + {"File rename", setup_file_rename, perform_file_rename, check_file_rename}, + {"File rename replace", setup_file_rename_replace, perform_file_rename_replace, check_file_rename_replace}, + {"Directory rename", setup_directory_rename, perform_directory_rename, check_directory_rename}, + {"File change contents", setup_file_change_contents, perform_file_change_contents, check_file_change_contents}, +}; + +static const char FILE_SETUP_COMPLETE[] = "setup_complete.txt"; +static const char FILE_SETUP_COMPLETE_FMT[] = "Test version: %lu\n"; + +static bool format_required(BlockDevice *bd) +{ + LittleFileSystem fs("fs"); + + if (fs.mount(bd) != 0) { + return true; + } + + // Check if setup complete file exists + File file; + int res = file.open(&fs, FILE_SETUP_COMPLETE, O_RDONLY); + if (res != 0) { + return true; + } + + // Read contents of setup complete file + uint8_t buf[BUFFER_SIZE]; + memset(buf, 0, sizeof(buf)); + int size_read = file.read(buf, sizeof(buf) - 1); + if (size_read <= 0) { + return true; + } + + // Get the test version + uint32_t version = 0; + res = sscanf((char*)buf, FILE_SETUP_COMPLETE_FMT, &version); + if (res != 1) { + return true; + } + + if (ATOMIC_USAGE_VERSION != version) { + return true; + } + + // Setup file exists and is the correct version + return false; +} + +static void format(BlockDevice *bd) +{ + LittleFileSystem fs("fs"); + + int res = fs.format(bd); + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + + res = fs.mount(bd); + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + + for (size_t i = 0; i < ARRAY_LENGTH(atomic_test_entries); i++) { + atomic_test_entries[i].setup(&fs); + } + + File file; + res = file.open(&fs, FILE_SETUP_COMPLETE, O_CREAT | O_WRONLY); + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + + int size = file_printf(&file, FILE_SETUP_COMPLETE_FMT, (uint32_t)ATOMIC_USAGE_VERSION); + TEST_ASSERT_OR_EXIT(size >= 0); +} + +static int64_t get_cycle_count(LittleFileSystem *fs) +{ + File file; + + int res = file.open(fs, FILE_RENAME_REPLACE, O_RDONLY); + TEST_ASSERT_EQUAL_OR_EXIT(0, res); + + uint64_t count = 0; + int args_read = file_scanf(&file, FILE_RENAME_REPLACE_FMT, &count); + TEST_ASSERT_EQUAL_OR_EXIT(1, args_read); + + file.close(); + + return (int64_t)count; +} + +bool setup_atomic_operations(BlockDevice *bd, bool force_rebuild) +{ + if (force_rebuild || format_required(bd)) { + format(bd); + TEST_ASSERT_EQUAL_OR_EXIT(false, format_required(bd)); + return true; + } + return false; +} + +int64_t perform_atomic_operations(BlockDevice *bd) +{ + LittleFileSystem fs("fs"); + bool out_of_space = false; + + fs.mount(bd); + + for (size_t i = 0; i < ARRAY_LENGTH(atomic_test_entries); i++) { + out_of_space |= atomic_test_entries[i].perform(&fs); + } + + int64_t cycle_count = get_cycle_count(&fs); + + fs.unmount(); + + if (out_of_space) { + return -1; + } else { + return cycle_count; + } +} + +void check_atomic_operations(BlockDevice *bd) +{ + LittleFileSystem fs("fs"); + fs.mount(bd); + + for (size_t i = 0; i < ARRAY_LENGTH(atomic_test_entries); i++) { + atomic_test_entries[i].check(&fs); + } + + fs.unmount(); +} diff --git a/TESTS_COMMON/atomic_usage.h b/TESTS_COMMON/atomic_usage.h new file mode 100644 index 0000000000..ef95c4c89c --- /dev/null +++ b/TESTS_COMMON/atomic_usage.h @@ -0,0 +1,72 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef MBED_ATOMIC_USAGE_H +#define MBED_ATOMIC_USAGE_H + +#include "BlockDevice.h" +#include "mbed.h" + +/** + * Setup the given block device to test littlefs atomic operations + * + * Format the blockdevice with a littlefs filesystem and create + * the files and directories required to test atomic operations. + * + * @param bd Block device format and setup + * @param force_rebuild Force a reformat even if the device is already setup + * @return true if the block device was formatted, false otherwise + * @note utest asserts are used to detect fatal errors so utest must be + * initialized before calling this function. + */ +bool setup_atomic_operations(BlockDevice *bd, bool force_rebuild); + +/** + * Perform a set of atomic littlefs operations on the block device + * + * Mount the block device as a littlefs filesystem and a series of + * atomic operations on it. Since the operations performed are atomic + * the file system will always be in a well defined state. The block + * device must have been setup by calling ::setup_atomic_operations. + * + * @param bd Block device to perform the operations on + * @return -1 if flash is exhausted, otherwise the cycle count on the fs + * @note utest asserts are used to detect fatal errors so utest must be + * initialized before calling this function. + */ +int64_t perform_atomic_operations(BlockDevice *bd); + +/** + * Check that the littlefs image on the block device is in a good state + * + * Mount the block device as a littlefs filesystem and check the files + * and directories to ensure they are valid. Since all the operations + * performed are atomic the filesystem should always be in a good + * state. + * + * @param bd Block device to check + * @note This function does not change the contents of the block device + * @note utest asserts are used to detect fatal errors so utest must be + * initialized before calling this function. + */ +void check_atomic_operations(BlockDevice *bd); + +#endif From 2d374cee5c067c7d7cf6e7d91cc67f685bef16e4 Mon Sep 17 00:00:00 2001 From: Russ Butler Date: Thu, 16 Nov 2017 18:51:13 -0600 Subject: [PATCH 28/46] Add unexpected reset test Add a test which repeatedly resets a device and then checks that the filesystem is still valid using real hardware. --- .../resilience_functional/main.cpp | 114 ++++++++++++++++++ TESTS/host_tests/unexpected_reset.py | 103 ++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 TESTS/filesystem_recovery/resilience_functional/main.cpp create mode 100644 TESTS/host_tests/unexpected_reset.py diff --git a/TESTS/filesystem_recovery/resilience_functional/main.cpp b/TESTS/filesystem_recovery/resilience_functional/main.cpp new file mode 100644 index 0000000000..3e090bfcda --- /dev/null +++ b/TESTS/filesystem_recovery/resilience_functional/main.cpp @@ -0,0 +1,114 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017-2017 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "mbed.h" +#include "unity.h" +#include "utest.h" +#include "test_env.h" + +#include "atomic_usage.h" +#include "ObservingBlockDevice.h" +#include "LittleFileSystem.h" + +#include + + +using namespace utest::v1; + +#ifndef MBED_TEST_BLOCKDEVICE +#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice +#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#endif + +#ifndef MBED_TEST_BLOCKDEVICE_DECL +#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd +#endif + +// declarations +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_BLOCKDEVICE) + +MBED_TEST_BLOCKDEVICE_DECL; + +typedef enum { + CMD_STATUS_PASS, + CMD_STATUS_FAIL, + CMD_STATUS_CONTINUE, + CMD_STATUS_ERROR +} cmd_status_t; + +void use_filesystem() +{ + // Perform operations + while (true) { + int64_t ret = perform_atomic_operations(&bd); + TEST_ASSERT(ret > 0); + } +} + +static cmd_status_t handle_command(const char *key, const char *value) +{ + if (strcmp(key, "format") == 0) { + setup_atomic_operations(&bd, true); + greentea_send_kv("format_done", 1); + return CMD_STATUS_CONTINUE; + + } else if (strcmp(key, "run") == 0) { + use_filesystem(); + return CMD_STATUS_CONTINUE; + + } else if (strcmp(key, "exit") == 0) { + if (strcmp(value, "pass") != 0) { + return CMD_STATUS_FAIL; + } + check_atomic_operations(&bd); + return CMD_STATUS_PASS; + + } else { + return CMD_STATUS_ERROR; + + } +} + +int main() +{ + GREENTEA_SETUP(60, "unexpected_reset"); + + static char _key[10 + 1] = {}; + static char _value[128 + 1] = {}; + + greentea_send_kv("start", 1); + + // Handshake with host + cmd_status_t cmd_status = CMD_STATUS_CONTINUE; + while (CMD_STATUS_CONTINUE == cmd_status) { + memset(_key, 0, sizeof(_key)); + memset(_value, 0, sizeof(_value)); + greentea_parse_kv(_key, _value, sizeof(_key) - 1, sizeof(_value) - 1); + cmd_status = handle_command(_key, _value); + } + + GREENTEA_TESTSUITE_RESULT(CMD_STATUS_PASS == cmd_status); +} diff --git a/TESTS/host_tests/unexpected_reset.py b/TESTS/host_tests/unexpected_reset.py new file mode 100644 index 0000000000..2df1ef3605 --- /dev/null +++ b/TESTS/host_tests/unexpected_reset.py @@ -0,0 +1,103 @@ +""" +mbed SDK +Copyright (c) 2017-2017 ARM Limited + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +from __future__ import print_function + +from mbed_host_tests import BaseHostTest +from time import sleep + + +class UnexpectedResetTest(BaseHostTest): + """This test checks that a device's RTC keeps count through a reset + + It does this by setting the RTC's time, triggering a reset, + delaying and then reading the RTC's time again to ensure + that the RTC is still counting. + """ + + """Number of times to reset the device in this test""" + RESET_COUNT = 20 + RESET_DELAY_BASE = 1.0 + RESET_DELAY_INC = 0.02 + VALUE_PLACEHOLDER = "0" + + def setup(self): + """Register callbacks required for the test""" + self._error = False + generator = self.unexpected_reset_test() + generator.next() + + def run_gen(key, value, time): + """Run the generator, and fail testing if the iterator stops""" + if self._error: + return + try: + generator.send((key, value, time)) + except StopIteration: + self._error = True + + for resp in ("start", "read", "format_done", "soft_reset_dut_complete"): + self.register_callback(resp, run_gen) + + def teardown(self): + """No work to do here""" + pass + + def unexpected_reset_test(self): + """Generator for running the reset test + + This function calls yield to wait for the next event from + the device. If the device gives the wrong response, then the + generator terminates by returing which raises a StopIteration + exception and fails the test. + """ + + # Wait for start token + key, value, time = yield + if key != "start": + return + + # Format the device before starting the test + self.send_kv("format", self.VALUE_PLACEHOLDER) + key, value, time = yield + if key != "format_done": + return + + for i in range(self.RESET_COUNT): + + self.send_kv("run", self.VALUE_PLACEHOLDER) + sleep(self.RESET_DELAY_BASE + self.RESET_DELAY_INC * i) + + self.reset() + + # Wait for start token + key, value, time = yield + self.log("Key from yield: %s" % key) + if key != "soft_reset_dut_complete": + return + + + self.send_kv("__sync", "00000000-0000-000000000-000000000000") + + # Wait for start token + key, value, time = yield + if key != "start": + return + + self.send_kv("exit", "pass") + + yield # No more events expected + From 5afec680738652f0a8f4e21a94d103c0491c5f31 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 17 Nov 2017 14:53:45 -0600 Subject: [PATCH 29/46] Renamed "parallel" tests to "intersperesed" The parallel name was incorrect. These tests do not involve mutliple threads, but rather write to multiple files an in interspersed manner sequentially. --- TESTS/filesystem/{parallel => interspersed}/main.cpp | 0 TESTS/filesystem_retarget/{parallel => interspersed}/main.cpp | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename TESTS/filesystem/{parallel => interspersed}/main.cpp (100%) rename TESTS/filesystem_retarget/{parallel => interspersed}/main.cpp (100%) diff --git a/TESTS/filesystem/parallel/main.cpp b/TESTS/filesystem/interspersed/main.cpp similarity index 100% rename from TESTS/filesystem/parallel/main.cpp rename to TESTS/filesystem/interspersed/main.cpp diff --git a/TESTS/filesystem_retarget/parallel/main.cpp b/TESTS/filesystem_retarget/interspersed/main.cpp similarity index 100% rename from TESTS/filesystem_retarget/parallel/main.cpp rename to TESTS/filesystem_retarget/interspersed/main.cpp From 3c9f2be16350fe77528f2d3175e7bae6b0b38a8f Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 17 Nov 2017 18:04:01 -0600 Subject: [PATCH 30/46] Added test configuration support for block devices Defaulted to HeapBlockDevice. Unfortunately this does mean that by default almost none of the tests are actually capable of running unless the dut has >512KB or RAM. --- TESTS/filesystem/dirs/main.cpp | 3 +- TESTS/filesystem/files/main.cpp | 3 +- TESTS/filesystem/interspersed/main.cpp | 3 +- TESTS/filesystem/seek/main.cpp | 3 +- TESTS/filesystem_recovery/resilience/main.cpp | 48 +++++++++++---- .../resilience_functional/main.cpp | 16 +++-- .../wear_leveling/main.cpp | 51 ++++++++++++---- TESTS/filesystem_retarget/dirs/main.cpp | 3 +- TESTS/filesystem_retarget/files/main.cpp | 3 +- .../filesystem_retarget/interspersed/main.cpp | 3 +- TESTS/filesystem_retarget/seek/main.cpp | 3 +- TESTS_COMMON/atomic_usage.cpp | 58 ++++++++++++------- 12 files changed, 137 insertions(+), 60 deletions(-) diff --git a/TESTS/filesystem/dirs/main.cpp b/TESTS/filesystem/dirs/main.cpp index b1fb3edfc0..6ee9b7d964 100644 --- a/TESTS/filesystem/dirs/main.cpp +++ b/TESTS/filesystem/dirs/main.cpp @@ -17,8 +17,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_BLOCKDEVICE -#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice -#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#error [NOT_SUPPORTED] Non-volatile block device required #endif #ifndef MBED_TEST_BLOCKDEVICE_DECL diff --git a/TESTS/filesystem/files/main.cpp b/TESTS/filesystem/files/main.cpp index 1637bebdbb..65c226276c 100644 --- a/TESTS/filesystem/files/main.cpp +++ b/TESTS/filesystem/files/main.cpp @@ -17,8 +17,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_BLOCKDEVICE -#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice -#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#error [NOT_SUPPORTED] Non-volatile block device required #endif #ifndef MBED_TEST_BLOCKDEVICE_DECL diff --git a/TESTS/filesystem/interspersed/main.cpp b/TESTS/filesystem/interspersed/main.cpp index ebdc7a9287..38a604a1a0 100644 --- a/TESTS/filesystem/interspersed/main.cpp +++ b/TESTS/filesystem/interspersed/main.cpp @@ -17,8 +17,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_BLOCKDEVICE -#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice -#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#error [NOT_SUPPORTED] Non-volatile block device required #endif #ifndef MBED_TEST_BLOCKDEVICE_DECL diff --git a/TESTS/filesystem/seek/main.cpp b/TESTS/filesystem/seek/main.cpp index 732d4f2283..40bc6b3e5d 100644 --- a/TESTS/filesystem/seek/main.cpp +++ b/TESTS/filesystem/seek/main.cpp @@ -17,8 +17,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_BLOCKDEVICE -#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice -#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#error [NOT_SUPPORTED] Non-volatile block device required #endif #ifndef MBED_TEST_BLOCKDEVICE_DECL diff --git a/TESTS/filesystem_recovery/resilience/main.cpp b/TESTS/filesystem_recovery/resilience/main.cpp index de57514a94..2d1eb58cf7 100644 --- a/TESTS/filesystem_recovery/resilience/main.cpp +++ b/TESTS/filesystem_recovery/resilience/main.cpp @@ -27,13 +27,34 @@ #include "atomic_usage.h" #include "ObservingBlockDevice.h" -#include "LittleFileSystem.h" - using namespace utest::v1; -#define TEST_CYCLES 10 -#define TEST_BD_SIZE (16 * 1024) +// test configuration +#ifndef MBED_TEST_BLOCKDEVICE +#define MBED_TEST_BLOCKDEVICE HeapBlockDevice +#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd(MBED_TEST_BLOCK_COUNT*512, 1, 1, 512) +#endif + +#ifndef MBED_TEST_BLOCKDEVICE_DECL +#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd +#endif + +#ifndef MBED_TEST_BLOCK_COUNT +#define MBED_TEST_BLOCK_COUNT 64 +#endif + +#ifndef MBED_TEST_CYCLES +#define MBED_TEST_CYCLES 10 +#endif + +// declarations +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_BLOCKDEVICE) + /** * Check that the filesystem is valid after every change @@ -44,18 +65,25 @@ using namespace utest::v1; */ void test_resilience() { - HeapBlockDevice bd(TEST_BD_SIZE); + MBED_TEST_BLOCKDEVICE_DECL; + + // bring up to get block size + bd.init(); + bd_size_t block_size = bd.get_erase_size(); + bd.deinit(); + + SlicingBlockDevice slice(&bd, 0, MBED_TEST_BLOCK_COUNT*block_size); // Setup the test - setup_atomic_operations(&bd, true); + setup_atomic_operations(&slice, true); // Run check on every write operation - ObservingBlockDevice observer(&bd); + ObservingBlockDevice observer(&slice); observer.attach(check_atomic_operations); // Perform operations - printf("Performing %i operations on flash\n", TEST_CYCLES); - for (int i = 1; i <= TEST_CYCLES; i++) { + printf("Performing %i operations on flash\n", MBED_TEST_CYCLES); + for (int i = 1; i <= MBED_TEST_CYCLES; i++) { int64_t ret = perform_atomic_operations(&observer); TEST_ASSERT_EQUAL(i, ret); } @@ -68,7 +96,7 @@ Case cases[] = { utest::v1::status_t greentea_test_setup(const size_t number_of_cases) { - GREENTEA_SETUP(20, "default_auto"); + GREENTEA_SETUP(120, "default_auto"); return greentea_test_setup_handler(number_of_cases); } diff --git a/TESTS/filesystem_recovery/resilience_functional/main.cpp b/TESTS/filesystem_recovery/resilience_functional/main.cpp index 3e090bfcda..1771fb0492 100644 --- a/TESTS/filesystem_recovery/resilience_functional/main.cpp +++ b/TESTS/filesystem_recovery/resilience_functional/main.cpp @@ -31,18 +31,26 @@ #include - using namespace utest::v1; + +// test configuration #ifndef MBED_TEST_BLOCKDEVICE -#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice -#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#error [NOT_SUPPORTED] Non-volatile block device required for resilience_functional tests #endif #ifndef MBED_TEST_BLOCKDEVICE_DECL #define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd #endif +#ifndef MBED_TEST_BLOCK_COUNT +#define MBED_TEST_BLOCK_COUNT 64 +#endif + +#ifndef MBED_TEST_CYCLES +#define MBED_TEST_CYCLES 10 +#endif + // declarations #define STRINGIZE(x) STRINGIZE2(x) #define STRINGIZE2(x) #x @@ -94,7 +102,7 @@ static cmd_status_t handle_command(const char *key, const char *value) int main() { - GREENTEA_SETUP(60, "unexpected_reset"); + GREENTEA_SETUP(120, "unexpected_reset"); static char _key[10 + 1] = {}; static char _value[128 + 1] = {}; diff --git a/TESTS/filesystem_recovery/wear_leveling/main.cpp b/TESTS/filesystem_recovery/wear_leveling/main.cpp index 5cdb1feab0..308aec4373 100644 --- a/TESTS/filesystem_recovery/wear_leveling/main.cpp +++ b/TESTS/filesystem_recovery/wear_leveling/main.cpp @@ -27,19 +27,50 @@ #include "atomic_usage.h" #include "ExhaustibleBlockDevice.h" -#include "LittleFileSystem.h" +#include "SlicingBlockDevice.h" using namespace utest::v1; -#define ERASE_CYCLES 20 -#define TEST_BD_SIZE (8 * 1024) +// test configuration +#ifndef MBED_TEST_BLOCKDEVICE +#define MBED_TEST_BLOCKDEVICE HeapBlockDevice +#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd(MBED_TEST_BLOCK_COUNT*512, 1, 1, 512) +#endif -static uint32_t test_wear_leveling_size(uint32_t bd_size) +#ifndef MBED_TEST_BLOCKDEVICE_DECL +#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd +#endif + +#ifndef MBED_TEST_BLOCK_COUNT +#define MBED_TEST_BLOCK_COUNT 64 +#endif + +#ifndef MBED_TEST_ERASE_CYCLES +#define MBED_TEST_ERASE_CYCLES 100 +#endif + +// declarations +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_BLOCKDEVICE) + + +static uint32_t test_wear_leveling_size(uint32_t block_count) { - HeapBlockDevice hbd(bd_size, 1, 1, 512); - ExhaustibleBlockDevice ebd(&hbd, ERASE_CYCLES); + MBED_TEST_BLOCKDEVICE_DECL; - printf("Testing size %lu\n", bd_size); + // bring up to get block size + bd.init(); + bd_size_t block_size = bd.get_erase_size(); + bd.deinit(); + + SlicingBlockDevice slice(&bd, 0, block_count*block_size); + ExhaustibleBlockDevice ebd(&slice, MBED_TEST_ERASE_CYCLES); + + printf("Testing size %llu bytes (%lux%llu) blocks\n", + block_count*block_size, block_count, block_size); setup_atomic_operations(&ebd, true); int64_t cycles = 0; @@ -70,8 +101,8 @@ static uint32_t test_wear_leveling_size(uint32_t bd_size) */ void test_wear_leveling() { - uint32_t cycles_1 = test_wear_leveling_size(TEST_BD_SIZE * 1); - uint32_t cycles_2 = test_wear_leveling_size(TEST_BD_SIZE * 2); + uint32_t cycles_1 = test_wear_leveling_size(MBED_TEST_BLOCK_COUNT / 2); + uint32_t cycles_2 = test_wear_leveling_size(MBED_TEST_BLOCK_COUNT / 1); TEST_ASSERT(cycles_2 > cycles_1 * 2); } @@ -81,7 +112,7 @@ Case cases[] = { utest::v1::status_t greentea_test_setup(const size_t number_of_cases) { - GREENTEA_SETUP(60, "default_auto"); + GREENTEA_SETUP(120, "default_auto"); return greentea_test_setup_handler(number_of_cases); } diff --git a/TESTS/filesystem_retarget/dirs/main.cpp b/TESTS/filesystem_retarget/dirs/main.cpp index a3073c73e9..c7bd5ea01e 100644 --- a/TESTS/filesystem_retarget/dirs/main.cpp +++ b/TESTS/filesystem_retarget/dirs/main.cpp @@ -17,8 +17,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_BLOCKDEVICE -#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice -#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#error [NOT_SUPPORTED] Non-volatile block device required #endif #ifndef MBED_TEST_BLOCKDEVICE_DECL diff --git a/TESTS/filesystem_retarget/files/main.cpp b/TESTS/filesystem_retarget/files/main.cpp index 61587e27df..4751193bde 100644 --- a/TESTS/filesystem_retarget/files/main.cpp +++ b/TESTS/filesystem_retarget/files/main.cpp @@ -17,8 +17,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_BLOCKDEVICE -#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice -#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#error [NOT_SUPPORTED] Non-volatile block device required #endif #ifndef MBED_TEST_BLOCKDEVICE_DECL diff --git a/TESTS/filesystem_retarget/interspersed/main.cpp b/TESTS/filesystem_retarget/interspersed/main.cpp index 9205ac2f24..de6f673b41 100644 --- a/TESTS/filesystem_retarget/interspersed/main.cpp +++ b/TESTS/filesystem_retarget/interspersed/main.cpp @@ -17,8 +17,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_BLOCKDEVICE -#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice -#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#error [NOT_SUPPORTED] Non-volatile block device required #endif #ifndef MBED_TEST_BLOCKDEVICE_DECL diff --git a/TESTS/filesystem_retarget/seek/main.cpp b/TESTS/filesystem_retarget/seek/main.cpp index e33c96bfa7..7169c35583 100644 --- a/TESTS/filesystem_retarget/seek/main.cpp +++ b/TESTS/filesystem_retarget/seek/main.cpp @@ -17,8 +17,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_BLOCKDEVICE -#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice -#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) +#error [NOT_SUPPORTED] Non-volatile block device required #endif #ifndef MBED_TEST_BLOCKDEVICE_DECL diff --git a/TESTS_COMMON/atomic_usage.cpp b/TESTS_COMMON/atomic_usage.cpp index 883f6261fe..af6c30d243 100644 --- a/TESTS_COMMON/atomic_usage.cpp +++ b/TESTS_COMMON/atomic_usage.cpp @@ -49,11 +49,29 @@ #include #include "ObservingBlockDevice.h" -#include "LittleFileSystem.h" #include "ExhaustibleBlockDevice.h" +#include "FileSystem.h" #include "atomic_usage.h" +// test configuration +#ifndef MBED_TEST_FILESYSTEM +#define MBED_TEST_FILESYSTEM LittleFileSystem +#endif + +#ifndef MBED_TEST_FILESYSTEM_DECL +#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs") +#endif + + +// declarations +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_FILESYSTEM) + + #define DEBUG(...) #define DEBUG_CHECK(...) #define BUFFER_SIZE 64 @@ -71,8 +89,8 @@ using namespace utest::v1; -typedef void (*test_function_t)(LittleFileSystem *fs); -typedef bool (*test_function_bool_t)(LittleFileSystem *fs); +typedef void (*test_function_t)(FileSystem *fs); +typedef bool (*test_function_bool_t)(FileSystem *fs); struct TestEntry { const char *name; @@ -181,7 +199,7 @@ static const int FILE_RENAME_LEN = strlen(FILE_RENAME_CONTENTS); * * Create file FILE_RENAME_A with contents FILE_RENAME_CONTENTS. */ -static void setup_file_rename(LittleFileSystem *fs) +static void setup_file_rename(FileSystem *fs) { DEBUG("setup_file_rename()\n"); @@ -199,7 +217,7 @@ static void setup_file_rename(LittleFileSystem *fs) /** * Change the file name to either FILE_RENAME_A or FILE_RENAME_B */ -static bool perform_file_rename(LittleFileSystem *fs) +static bool perform_file_rename(FileSystem *fs) { DEBUG("perform_file_rename()\n"); @@ -231,7 +249,7 @@ static bool perform_file_rename(LittleFileSystem *fs) * - File FILE_RENAME_B exists with contents and FILE_RENAME_A does not * */ -static void check_file_rename(LittleFileSystem *fs) +static void check_file_rename(FileSystem *fs) { int files = 0; @@ -269,7 +287,7 @@ static const char FILE_RENAME_REPLACE_FMT[] = "file replace count: %lu\n"; * * Create an write an initial count of 0 to the file. */ -static void setup_file_rename_replace(LittleFileSystem *fs) +static void setup_file_rename_replace(FileSystem *fs) { DEBUG("setup_file_rename_replace()\n"); File file; @@ -293,7 +311,7 @@ static void setup_file_rename_replace(LittleFileSystem *fs) /** * Atomically increment the count in FILE_RENAME_REPLACE using a rename */ -bool perform_file_rename_replace(LittleFileSystem *fs) +bool perform_file_rename_replace(FileSystem *fs) { DEBUG("perform_file_rename_replace()\n"); File file; @@ -351,7 +369,7 @@ bool perform_file_rename_replace(LittleFileSystem *fs) * Allowed states: * - FILE_RENAME_REPLACE exists with valid contents */ -static void check_file_rename_replace(LittleFileSystem *fs) +static void check_file_rename_replace(FileSystem *fs) { DEBUG_CHECK("check_file_rename_replace()\n"); File file; @@ -374,7 +392,7 @@ static const char DIRECTORY_RENAME_B[] = "dir_b"; /** * Create DIRECTORY_RENAME_A with initial contents */ -static void setup_directory_rename(LittleFileSystem *fs) +static void setup_directory_rename(FileSystem *fs) { DEBUG("setup_directory_rename()\n"); @@ -385,7 +403,7 @@ static void setup_directory_rename(LittleFileSystem *fs) /* * Change the directory name from either DIRECTORY_RENAME_A or DIRECTORY_RENAME_B to the other */ -static bool perform_directory_rename(LittleFileSystem *fs) +static bool perform_directory_rename(FileSystem *fs) { DEBUG("perform_directory_rename()\n"); @@ -413,7 +431,7 @@ static bool perform_directory_rename(LittleFileSystem *fs) * - DIRECTORY_RENAME_A exists with valid contents and DIRECTORY_RENAME_B does not exist * - DIRECTORY_RENAME_B exists with valid contents and DIRECTORY_RENAME_A does not exist */ -static void check_directory_rename(LittleFileSystem *fs) +static void check_directory_rename(FileSystem *fs) { DEBUG_CHECK("check_directory_rename()\n"); @@ -445,7 +463,7 @@ static const uint32_t BLOCK_SIZE = 512; * File contains three blocks of data each which start * with a count. */ -static void setup_file_change_contents(LittleFileSystem *fs) +static void setup_file_change_contents(FileSystem *fs) { DEBUG("setup_file_change_contents()\n"); @@ -468,7 +486,7 @@ static void setup_file_change_contents(LittleFileSystem *fs) * Read in the current counts, increment them and then write them * back in non-sequential order. */ -static bool perform_file_change_contents(LittleFileSystem *fs) +static bool perform_file_change_contents(FileSystem *fs) { DEBUG("perform_file_change_contents()\n"); File file; @@ -527,7 +545,7 @@ static bool perform_file_change_contents(LittleFileSystem *fs) * Allowed states: * - CHANGE_CONTENTS_NAME exists and contains 3 counts which are in order */ -static void check_file_change_contents(LittleFileSystem *fs) +static void check_file_change_contents(FileSystem *fs) { DEBUG_CHECK("check_file_change_contents()\n"); File file; @@ -561,7 +579,7 @@ static const char FILE_SETUP_COMPLETE_FMT[] = "Test version: %lu\n"; static bool format_required(BlockDevice *bd) { - LittleFileSystem fs("fs"); + MBED_TEST_FILESYSTEM_DECL; if (fs.mount(bd) != 0) { return true; @@ -599,7 +617,7 @@ static bool format_required(BlockDevice *bd) static void format(BlockDevice *bd) { - LittleFileSystem fs("fs"); + MBED_TEST_FILESYSTEM_DECL; int res = fs.format(bd); TEST_ASSERT_EQUAL_OR_EXIT(0, res); @@ -619,7 +637,7 @@ static void format(BlockDevice *bd) TEST_ASSERT_OR_EXIT(size >= 0); } -static int64_t get_cycle_count(LittleFileSystem *fs) +static int64_t get_cycle_count(FileSystem *fs) { File file; @@ -647,7 +665,7 @@ bool setup_atomic_operations(BlockDevice *bd, bool force_rebuild) int64_t perform_atomic_operations(BlockDevice *bd) { - LittleFileSystem fs("fs"); + MBED_TEST_FILESYSTEM_DECL; bool out_of_space = false; fs.mount(bd); @@ -669,7 +687,7 @@ int64_t perform_atomic_operations(BlockDevice *bd) void check_atomic_operations(BlockDevice *bd) { - LittleFileSystem fs("fs"); + MBED_TEST_FILESYSTEM_DECL; fs.mount(bd); for (size_t i = 0; i < ARRAY_LENGTH(atomic_test_entries); i++) { From 0a197465d33900a5ac2ef41486410d44f3d15ad3 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 20 Nov 2017 00:16:47 -0600 Subject: [PATCH 31/46] Squashed 'littlefs/' changes from 78c79ec..996cd8a 996cd8a Revisited documentation git-subtree-dir: littlefs git-subtree-split: 996cd8af22a5fe917812838f82eb73221dde4b7d --- DESIGN.md | 93 +++++++++++++++++++++++++++++-------------------------- README.md | 75 +++++++++++++++++++++++++------------------- 2 files changed, 92 insertions(+), 76 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index b4b9377ac7..f2a8498443 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,6 +1,6 @@ ## The design of the little filesystem -The littlefs is a little fail-safe filesystem designed for embedded systems. +A little fail-safe filesystem designed for embedded systems. ``` | | | .---._____ @@ -16,9 +16,9 @@ more about filesystem design by tackling the relative unsolved problem of managing a robust filesystem resilient to power loss on devices with limited RAM and ROM. -The embedded systems the littlefs is targeting are usually 32bit -microcontrollers with around 32Kbytes of RAM and 512Kbytes of ROM. These are -often paired with SPI NOR flash chips with about 4Mbytes of flash storage. +The embedded systems the littlefs is targeting are usually 32 bit +microcontrollers with around 32KB of RAM and 512KB of ROM. These are +often paired with SPI NOR flash chips with about 4MB of flash storage. Flash itself is a very interesting piece of technology with quite a bit of nuance. Unlike most other forms of storage, writing to flash requires two @@ -32,17 +32,17 @@ has more information if you are interesting in how this works. This leaves us with an interesting set of limitations that can be simplified to three strong requirements: -1. **Fail-safe** - This is actually the main goal of the littlefs and the focus - of this project. Embedded systems are usually designed without a shutdown - routine and a notable lack of user interface for recovery, so filesystems - targeting embedded systems should be prepared to lose power an any given - time. +1. **Power-loss resilient** - This is the main goal of the littlefs and the + focus of this project. Embedded systems are usually designed without a + shutdown routine and a notable lack of user interface for recovery, so + filesystems targeting embedded systems must be prepared to lose power an + any given time. Despite this state of things, there are very few embedded filesystems that - handle power loss in a reasonable manner, and can become corrupted if the - user is unlucky enough. + handle power loss in a reasonable manner, and most can become corrupted if + the user is unlucky enough. -2. **Wear awareness** - Due to the destructive nature of flash, most flash +2. **Wear leveling** - Due to the destructive nature of flash, most flash chips have a limited number of erase cycles, usually in the order of around 100,000 erases per block for NOR flash. Filesystems that don't take wear into account can easily burn through blocks used to store frequently updated @@ -78,9 +78,9 @@ summary of the general ideas behind some of them. Most of the existing filesystems fall into the one big category of filesystem designed in the early days of spinny magnet disks. While there is a vast amount of interesting technology and ideas in this area, the nature of spinny magnet -disks encourage properties such as grouping writes near each other, that don't +disks encourage properties, such as grouping writes near each other, that don't make as much sense on recent storage types. For instance, on flash, write -locality is not as important and can actually increase wear destructively. +locality is not important and can actually increase wear destructively. One of the most popular designs for flash filesystems is called the [logging filesystem](https://en.wikipedia.org/wiki/Log-structured_file_system). @@ -97,8 +97,7 @@ scaling as the size of storage increases. And most filesystems compensate by caching large parts of the filesystem in RAM, a strategy that is unavailable for embedded systems. -Another interesting filesystem design technique that the littlefs borrows the -most from, is the [copy-on-write (COW)](https://en.wikipedia.org/wiki/Copy-on-write). +Another interesting filesystem design technique is that of [copy-on-write (COW)](https://en.wikipedia.org/wiki/Copy-on-write). A good example of this is the [btrfs](https://en.wikipedia.org/wiki/Btrfs) filesystem. COW filesystems can easily recover from corrupted blocks and have natural protection against power loss. However, if they are not designed with @@ -150,12 +149,12 @@ check our checksum we notice that block 1 was corrupted. So we fall back to block 2 and use the value 9. Using this concept, the littlefs is able to update metadata blocks atomically. -There are a few other tweaks, such as using a 32bit crc and using sequence +There are a few other tweaks, such as using a 32 bit crc and using sequence arithmetic to handle revision count overflow, but the basic concept is the same. These metadata pairs define the backbone of the littlefs, and the rest of the filesystem is built on top of these atomic updates. -## Files +## Non-meta data Now, the metadata pairs do come with some drawbacks. Most notably, each pair requires two blocks for each block of data. I'm sure users would be very @@ -224,12 +223,12 @@ Exhibit A: A linked-list To get around this, the littlefs, at its heart, stores files backwards. Each block points to its predecessor, with the first block containing no pointers. -If you think about this, it makes a bit of sense. Appending blocks just point -to their predecessor and no other blocks need to be updated. If we update -a block in the middle, we will need to copy out the blocks that follow, -but can reuse the blocks before the modified block. Since most file operations -either reset the file each write or append to files, this design avoids -copying the file in the most common cases. +If you think about for a while, it starts to make a bit of sense. Appending +blocks just point to their predecessor and no other blocks need to be updated. +If we update a block in the middle, we will need to copy out the blocks that +follow, but can reuse the blocks before the modified block. Since most file +operations either reset the file each write or append to files, this design +avoids copying the file in the most common cases. ``` Exhibit B: A backwards linked-list @@ -351,7 +350,7 @@ file size doesn't have an obvious implementation. We can start by just writing down an equation. The first idea that comes to mind is to just use a for loop to sum together blocks until we reach our -file size. We can write equation equation as a summation: +file size. We can write this equation as a summation: ![summation1](https://latex.codecogs.com/svg.latex?N%20%3D%20%5Csum_i%5En%5Cleft%5BB-%5Cfrac%7Bw%7D%7B8%7D%5Cleft%28%5Ctext%7Bctz%7D%28i%29+1%5Cright%29%5Cright%5D) @@ -374,7 +373,7 @@ The [On-Line Encyclopedia of Integer Sequences (OEIS)](https://oeis.org/). If we work out the first couple of values in our summation, we find that CTZ maps to [A001511](https://oeis.org/A001511), and its partial summation maps to [A005187](https://oeis.org/A005187), and surprisingly, both of these -sequences have relatively trivial equations! This leads us to the completely +sequences have relatively trivial equations! This leads us to a rather unintuitive property: ![mindblown](https://latex.codecogs.com/svg.latex?%5Csum_i%5En%5Cleft%28%5Ctext%7Bctz%7D%28i%29+1%5Cright%29%20%3D%202n-%5Ctext%7Bpopcount%7D%28n%29) @@ -383,7 +382,7 @@ where: ctz(i) = the number of trailing bits that are 0 in i popcount(i) = the number of bits that are 1 in i -I find it bewildering that these two seemingly unrelated bitwise instructions +It's a bit bewildering that these two seemingly unrelated bitwise instructions are related by this property. But if we start to disect this equation we can see that it does hold. As n approaches infinity, we do end up with an average overhead of 2 pointers as we find earlier. And popcount seems to handle the @@ -1154,21 +1153,26 @@ develops errors and needs to be moved. The second concern for the littlefs, is that blocks in the filesystem may wear unevenly. In this situation, a filesystem may meet an early demise where -there are no more non-corrupted blocks that aren't in use. It may be entirely -possible that files were written once and left unmodified, wasting the -potential erase cycles of the blocks it sits on. +there are no more non-corrupted blocks that aren't in use. It's common to +have files that were written once and left unmodified, wasting the potential +erase cycles of the blocks it sits on. Wear leveling is a term that describes distributing block writes evenly to avoid the early termination of a flash part. There are typically two levels of wear leveling: -1. Dynamic wear leveling - Blocks are distributed evenly during blocks writes. - Note that the issue with write-once files still exists in this case. -2. Static wear leveling - Unmodified blocks are evicted for new block writes. - This provides the longest lifetime for a flash device. +1. Dynamic wear leveling - Wear is distributed evenly across all **dynamic** + blocks. Usually this is accomplished by simply choosing the unused block + with the lowest amount of wear. Note this does not solve the problem of + static data. +2. Static wear leveling - Wear is distributed evenly across all **dynamic** + and **static** blocks. Unmodified blocks may be evicted for new block + writes. This does handle the problem of static data but may lead to + wear amplification. -Now, it's possible to use the revision count on metadata pairs to approximate -the wear of a metadata block. And combined with the COW nature of files, the -littlefs could provide a form of dynamic wear leveling. +In littlefs's case, it's possible to use the revision count on metadata pairs +to approximate the wear of a metadata block. And combined with the COW nature +of files, littlefs could provide your usually implementation of dynamic wear +leveling. However, the littlefs does not. This is for a few reasons. Most notably, even if the littlefs did implement dynamic wear leveling, this would still not @@ -1179,19 +1183,20 @@ As a flash device reaches the end of its life, the metadata blocks will naturally be the first to go since they are updated most often. In this situation, the littlefs is designed to simply move on to another set of metadata blocks. This travelling means that at the end of a flash device's -life, the filesystem will have worn the device down as evenly as a dynamic -wear leveling filesystem could anyways. Simply put, if the lifetime of flash -is a serious concern, static wear leveling is the only valid solution. +life, the filesystem will have worn the device down nearly as evenly as the +usual dynamic wear leveling could. More aggressive wear leveling would come +with a code-size cost for marginal benefit. -This is a very important takeaway to note. If your storage stack uses highly -sensitive storage such as NAND flash. In most cases you are going to be better -off just using a [flash translation layer (FTL)](https://en.wikipedia.org/wiki/Flash_translation_layer). + +One important takeaway to note, if your storage stack uses highly sensitive +storage such as NAND flash, static wear leveling is the only valid solution. +In most cases you are going to be better off using a full [flash translation layer (FTL)](https://en.wikipedia.org/wiki/Flash_translation_layer). NAND flash already has many limitations that make it poorly suited for an embedded system: low erase cycles, very large blocks, errors that can develop even during reads, errors that can develop during writes of neighboring blocks. Managing sensitive storage such as NAND flash is out of scope for the littlefs. The littlefs does have some properties that may be beneficial on top of a FTL, -such as limiting the number of writes where possible. But if you have the +such as limiting the number of writes where possible, but if you have the storage requirements that necessitate the need of NAND flash, you should have the RAM to match and just use an FTL or flash filesystem. diff --git a/README.md b/README.md index a7ba1c6132..d02139c443 100644 --- a/README.md +++ b/README.md @@ -11,23 +11,17 @@ A little fail-safe filesystem designed for embedded systems. | | | ``` -**Fail-safe** - The littlefs is designed to work consistently with random -power failures. During filesystem operations the storage on disk is always -kept in a valid state. The filesystem also has strong copy-on-write garuntees. -When updating a file, the original file will remain unmodified until the -file is closed, or sync is called. +**Bounded RAM/ROM** - The littlefs is designed to work with a limited amount +of memory. Recursion is avoided and dynamic memory is limited to configurable +buffers that can be provided statically. -**Wear awareness** - While the littlefs does not implement static wear -leveling, the littlefs takes into account write errors reported by the -underlying block device and uses a limited form of dynamic wear leveling -to manage blocks that go bad during the lifetime of the filesystem. +**Power-loss resilient** - The littlefs is designed for systems that may have +random power failures. The littlefs has strong copy-on-write guaruntees and +storage on disk is always kept in a valid state. -**Bounded ram/rom** - The littlefs is designed to work in a -limited amount of memory, recursion is avoided, and dynamic memory is kept -to a minimum. The littlefs allocates two fixed-size buffers for general -operations, and one fixed-size buffer per file. If there is only ever one file -in use, all memory can be provided statically and the littlefs can be used -in a system without dynamic memory. +**Wear leveling** - Since the most common form of embedded storage is erodible +flash memories, littlefs provides a form of dynamic wear leveling for systems +that can not fit a full flash translation layer. ## Example @@ -96,7 +90,7 @@ int main(void) { Detailed documentation (or at least as much detail as is currently available) can be cound in the comments in [lfs.h](lfs.h). -As you may have noticed, the littlefs takes in a configuration structure that +As you may have noticed, littlefs takes in a configuration structure that defines how the filesystem operates. The configuration struct provides the filesystem with the block device operations and dimensions, tweakable parameters that tradeoff memory usage for performance, and optional @@ -104,14 +98,16 @@ static buffers if the user wants to avoid dynamic memory. The state of the littlefs is stored in the `lfs_t` type which is left up to the user to allocate, allowing multiple filesystems to be in use -simultaneously. With the `lfs_t` and configuration struct, a user can either +simultaneously. With the `lfs_t` and configuration struct, a user can format a block device or mount the filesystem. Once mounted, the littlefs provides a full set of posix-like file and directory functions, with the deviation that the allocation of filesystem -structures must be provided by the user. An important addition is that -no file updates will actually be written to disk until a sync or close -is called. +structures must be provided by the user. + +All posix operations, such as remove and rename, are atomic, even in event +of power-loss. Additionally, no file updates are actually commited to the +filesystem until sync or close is called on the file. ## Other notes @@ -119,20 +115,19 @@ All littlefs have the potential to return a negative error code. The errors can be either one of those found in the `enum lfs_error` in [lfs.h](lfs.h), or an error returned by the user's block device operations. -It should also be noted that the littlefs does not do anything to insure -that the data written to disk is machine portable. It should be fine as -long as the machines involved share endianness and don't have really -strange padding requirements. If the question does come up, the littlefs -metadata should be stored on disk in little-endian format. +It should also be noted that the current implementation of littlefs doesn't +really do anything to insure that the data written to disk is machine portable. +This is fine as long as all of the involved machines share endianness +(little-endian) and don't have strange padding requirements. -## Design +## Reference material -the littlefs was developed with the goal of learning more about filesystem -design by tackling the relative unsolved problem of managing a robust -filesystem resilient to power loss on devices with limited RAM and ROM. -More detail on the solutions and tradeoffs incorporated into this filesystem -can be found in [DESIGN.md](DESIGN.md). The specification for the layout -of the filesystem on disk can be found in [SPEC.md](SPEC.md). +[DESIGN.md](DESIGN.md) - DESIGN.md contains a fully detailed dive into how +littlefs actually works. I would encourage you to read it since the +solutions and tradeoffs at work here are quite interesting. + +[SPEC.md](SPEC.md) - SPEC.md contains the on-disk specification of littlefs +with all the nitty-gritty details. Can be useful for developing tooling. ## Testing @@ -143,3 +138,19 @@ The tests assume a linux environment and can be started with make: ``` bash make test ``` + +## Related projects + +[mbed-littlefs](https://github.com/armmbed/mbed-littlefs) - The easiest way to +get started with littlefs is to jump into [mbed](https://os.mbed.com/), which +already has block device drivers for most forms of embedded storage. The +mbed-littlefs provides the mbed wrapper for littlefs. + +[littlefs-fuse](https://github.com/geky/littlefs-fuse) - A [FUSE](https://github.com/libfuse/libfuse) +wrapper for littlefs. The project allows you to mount littlefs directly in a +Linux machine. Can be useful for debugging littlefs if you have an SD card +handy. + +[littlefs-js](https://github.com/geky/littlefs-js) - A javascript wrapper for +littlefs. I'm not sure why you would want this, but it is handy for demos. +You can see it in action [here](http://littlefs.geky.net/demo.html). From 8d336f253af4064af3890f8f9a6cb8dd71fa858e Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 20 Nov 2017 00:17:21 -0600 Subject: [PATCH 32/46] Updated documentation to match other info around littlefs Mostly brought from documentation changes in the core repo --- README.md | 61 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 7e682fb29f..0db3d1f562 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## mbed wrapper for the [little filesystem](https://github.com/geky/littlefs) +## mbed wrapper for the little filesystem This is the mbed wrapper for [littlefs](https://github.com/geky/littlefs), a little fail-safe filesystem designed for embedded systems. @@ -12,34 +12,17 @@ a little fail-safe filesystem designed for embedded systems. | | | ``` -The littlefs comes with the following features: +**Bounded RAM/ROM** - The littlefs is designed to work with a limited amount +of memory. Recursion is avoided and dynamic memory is limited to configurable +buffers that can be provided statically. -- **Power-loss resilient** - The littlefs is designed to handle random power - failures and has strong copy-on-write guaruntees. During all filesystem - operations, the storage on disk is always kept in a valid state. When - updating a file, the original file will remain unmodified until the file is - closed, or sync is called. +**Power-loss resilient** - The littlefs is designed for systems that may have +random power failures. The littlefs has strong copy-on-write guaruntees and +storage on disk is always kept in a valid state. -- **Error detection** - While the littlefs does not implement static wear - leveling, the littlefs detects write error and uses a limited form of dynamic - wear leveling to work around blocks that go bad during the lifetime of the - filesystem. - -- **Low RAM/ROM usage** - The littlefs is designed to work in a limited amount - of memory, recursion is avoided, and dynamic memory is limited to - configurable buffers that can be provided statically. - -The littlefs also comes with the following limitations: - -- **Not portable to host OSs** - While the littlefs can use an SD card, no - littlefs driver has been ported to host OSs such as Linux or Windows. If - you need to support host OSs consider using the [FATFileSystem](https://github.com/ARMmbed/mbed-os/blob/mbed-os-5.5/features/filesystem/fat/FATFileSystem.h) - found in mbed OS. - -- **No static wear leveling** - While the littlefs does extend the lifetime - of storage through a form of dynamic wear leveling, the littlefs does not - evict static blocks. If you need to maximize the longevity of the underlying - storage consider using the [SPIFFileSystem](https://github.com/armmbed/mbed-spiffs). +**Wear leveling** - Since the most common form of embedded storage is erodible +flash memories, littlefs provides a form of dynamic wear leveling for systems +that can not fit a full flash translation layer. ## Usage @@ -95,3 +78,27 @@ int main() { printf("boot_count: %ld\n", boot_count); } ``` + +## Reference material + +[DESIGN.md](littlefs/DESIGN.md) - DESIGN.md contains a fully detailed dive into +how littlefs actually works. I would encourage you to read it since the +solutions and tradeoffs at work here are quite interesting. + +[SPEC.md](littlefs/SPEC.md) - SPEC.md contains the on-disk specification of +littlefs with all the nitty-gritty details. Can be useful for developing +tooling. + +## Related projects + +[littlefs](https://github.com/geky/littlefs) - Where the core of littlefs +currently lives. + +[littlefs-fuse](https://github.com/geky/littlefs-fuse) - A [FUSE](https://github.com/libfuse/libfuse) +wrapper for littlefs. The project allows you to mount littlefs directly in a +Linux machine. Can be useful for debugging littlefs if you have an SD card +handy. + +[littlefs-js](https://github.com/geky/littlefs-js) - A javascript wrapper for +littlefs. I'm not sure why you would want this, but it is handy for demos. +You can see it in action [here](http://littlefs.geky.net/demo.html). From b9589973286eda1ab0eeb45cee6da57207488ac8 Mon Sep 17 00:00:00 2001 From: Russ Butler Date: Mon, 20 Nov 2017 12:22:12 -0600 Subject: [PATCH 33/46] Update unexpected reset host test for htrun Update the key used in the reset host to match the latest version of htrun. --- TESTS/host_tests/unexpected_reset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TESTS/host_tests/unexpected_reset.py b/TESTS/host_tests/unexpected_reset.py index 2df1ef3605..e85f3d10f0 100644 --- a/TESTS/host_tests/unexpected_reset.py +++ b/TESTS/host_tests/unexpected_reset.py @@ -49,7 +49,7 @@ class UnexpectedResetTest(BaseHostTest): except StopIteration: self._error = True - for resp in ("start", "read", "format_done", "soft_reset_dut_complete"): + for resp in ("start", "read", "format_done", "reset_complete"): self.register_callback(resp, run_gen) def teardown(self): @@ -86,7 +86,7 @@ class UnexpectedResetTest(BaseHostTest): # Wait for start token key, value, time = yield self.log("Key from yield: %s" % key) - if key != "soft_reset_dut_complete": + if key != "reset_complete": return From 3eb2f38279aadb35234c5df1fb8e54fd2a035be1 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 22 Nov 2017 15:28:15 -0600 Subject: [PATCH 34/46] Squashed 'littlefs/' changes from 996cd8a..5ee20e8 5ee20e8 Fixed pipefail issue that was preventing CI from reporting errors bf78b09 Added directory list for synchronizing in flight directories e169d06 Removed vestigial function declaration git-subtree-dir: littlefs git-subtree-split: 5ee20e8d774adf0bb538269870b3552fdfc0e046 --- Makefile | 4 ++- lfs.c | 82 +++++++++++++++++++++++++++++++--------------- lfs.h | 5 ++- tests/test_dirs.sh | 46 ++++++++++++++++++++++++-- 4 files changed, 104 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 2ef12876ee..cf978e7929 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,8 @@ ASM := $(SRC:.c=.s) TEST := $(patsubst tests/%.sh,%,$(wildcard tests/test_*)) +SHELL = /bin/bash -o pipefail + ifdef DEBUG CFLAGS += -O0 -g3 else @@ -35,7 +37,7 @@ test: test_format test_dirs test_files test_seek test_parallel \ test_alloc test_paths test_orphan test_move test_corrupt test_%: tests/test_%.sh ifdef QUIET - ./$< | sed '/^[^-=]/d' + ./$< | sed -n '/^[-=]/p' else ./$< endif diff --git a/lfs.c b/lfs.c index cddc6fa7ff..ea116d27c9 100644 --- a/lfs.c +++ b/lfs.c @@ -569,7 +569,18 @@ relocate: // update references if we relocated LFS_DEBUG("Relocating %d %d to %d %d", oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]); - return lfs_relocate(lfs, oldpair, dir->pair); + int err = lfs_relocate(lfs, oldpair, dir->pair); + if (err) { + return err; + } + } + + // shift over any directories that are affected + for (lfs_dir_t *d = lfs->dirs; d; d = d->next) { + if (lfs_paircmp(d->pair, dir->pair) == 0) { + d->pair[0] = dir->pair[0]; + d->pair[1] = dir->pair[1]; + } } return 0; @@ -628,7 +639,7 @@ static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir, } static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { - // either shift out the one entry or remove the whole dir block + // check if we should just drop the directory block if ((dir->d.size & 0x7fffffff) == sizeof(dir->d)+4 + lfs_entry_size(entry)) { lfs_dir_t pdir; @@ -637,38 +648,44 @@ static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { return res; } - if (!(pdir.d.size & 0x80000000)) { - return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ - {entry->off, lfs_entry_size(entry), NULL, 0}, - }, 1); - } else { + if (pdir.d.size & 0x80000000) { pdir.d.size &= dir->d.size | 0x7fffffff; pdir.d.tail[0] = dir->d.tail[0]; pdir.d.tail[1] = dir->d.tail[1]; return lfs_dir_commit(lfs, &pdir, NULL, 0); } - } else { - int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ - {entry->off, lfs_entry_size(entry), NULL, 0}, - }, 1); - if (err) { - return err; - } + } - // shift over any files that are affected - for (lfs_file_t *f = lfs->files; f; f = f->next) { - if (lfs_paircmp(f->pair, dir->pair) == 0) { - if (f->poff == entry->off) { - f->pair[0] = 0xffffffff; - f->pair[1] = 0xffffffff; - } else if (f->poff > entry->off) { - f->poff -= lfs_entry_size(entry); - } + // shift out the entry + int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, lfs_entry_size(entry), NULL, 0}, + }, 1); + if (err) { + return err; + } + + // shift over any files/directories that are affected + for (lfs_file_t *f = lfs->files; f; f = f->next) { + if (lfs_paircmp(f->pair, dir->pair) == 0) { + if (f->poff == entry->off) { + f->pair[0] = 0xffffffff; + f->pair[1] = 0xffffffff; + } else if (f->poff > entry->off) { + f->poff -= lfs_entry_size(entry); } } - - return 0; } + + for (lfs_dir_t *d = lfs->dirs; d; d = d->next) { + if (lfs_paircmp(d->pair, dir->pair) == 0) { + if (d->off > entry->off) { + d->off -= lfs_entry_size(entry); + d->pos -= lfs_entry_size(entry); + } + } + } + + return 0; } static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { @@ -894,11 +911,23 @@ int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { dir->head[1] = dir->pair[1]; dir->pos = sizeof(dir->d) - 2; dir->off = sizeof(dir->d); + + // add to list of directories + dir->next = lfs->dirs; + lfs->dirs = dir; + return 0; } int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) { - // do nothing, dir is always synchronized + // remove from list of directories + for (lfs_dir_t **p = &lfs->dirs; *p; p = &(*p)->next) { + if (*p == dir) { + *p = dir->next; + break; + } + } + return 0; } @@ -1902,6 +1931,7 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { lfs->root[0] = 0xffffffff; lfs->root[1] = 0xffffffff; lfs->files = NULL; + lfs->dirs = NULL; lfs->deorphaned = false; return 0; diff --git a/lfs.h b/lfs.h index 7757f8b9b4..51d0a86230 100644 --- a/lfs.h +++ b/lfs.h @@ -207,6 +207,7 @@ typedef struct lfs_file { } lfs_file_t; typedef struct lfs_dir { + struct lfs_dir *next; lfs_block_t pair[2]; lfs_off_t off; @@ -249,6 +250,7 @@ typedef struct lfs { lfs_block_t root[2]; lfs_file_t *files; + lfs_dir_t *dirs; lfs_cache_t rcache; lfs_cache_t pcache; @@ -445,8 +447,5 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); // Returns a negative error code on failure. int lfs_deorphan(lfs_t *lfs); -// TODO doc -int lfs_deduplicate(lfs_t *lfs); - #endif diff --git a/tests/test_dirs.sh b/tests/test_dirs.sh index 8b69e7e8ce..431889007a 100755 --- a/tests/test_dirs.sh +++ b/tests/test_dirs.sh @@ -282,6 +282,49 @@ tests/test.py << TEST lfs_unmount(&lfs) => 0; TEST +echo "--- Recursive remove ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "coldpotato") => LFS_ERR_INVAL; + + lfs_dir_open(&lfs, &dir[0], "coldpotato") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + + while (true) { + int err = lfs_dir_read(&lfs, &dir[0], &info); + err >= 0 => 1; + if (err == 0) { + break; + } + + strcpy((char*)buffer, "coldpotato/"); + strcat((char*)buffer, info.name); + lfs_remove(&lfs, (char*)buffer) => 0; + } + + lfs_remove(&lfs, "coldpotato") => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "/") => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, ".") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "..") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "burito") => 0; + info.type => LFS_TYPE_REG; + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, "cactus") => 0; + info.type => LFS_TYPE_DIR; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + echo "--- Multi-block remove ---" tests/test.py << TEST lfs_mount(&lfs, &cfg) => 0; @@ -307,9 +350,6 @@ tests/test.py << TEST lfs_dir_read(&lfs, &dir[0], &info) => 1; strcmp(info.name, "burito") => 0; info.type => LFS_TYPE_REG; - lfs_dir_read(&lfs, &dir[0], &info) => 1; - strcmp(info.name, "coldpotato") => 0; - info.type => LFS_TYPE_DIR; lfs_dir_read(&lfs, &dir[0], &info) => 0; lfs_dir_close(&lfs, &dir[0]) => 0; lfs_unmount(&lfs) => 0; From 9da2475099abc83ee7dbe0371f861c9079d8f5fd Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 21 Nov 2017 23:19:42 -0600 Subject: [PATCH 35/46] Cleaned up doxygen warnings --- LittleFileSystem.h | 4 ++-- TESTS_COMMON/ObservingBlockDevice.h | 5 ++--- TESTS_COMMON/ReadOnlyBlockDevice.h | 3 +-- TESTS_COMMON/atomic_usage.h | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/LittleFileSystem.h b/LittleFileSystem.h index 36e6545bea..9aabf77956 100644 --- a/LittleFileSystem.h +++ b/LittleFileSystem.h @@ -176,7 +176,7 @@ protected: * @param size The number of bytes to read * @return The number of bytes read, 0 at end of file, negative error on failure */ - virtual ssize_t file_read(fs_file_t file, void *buffer, size_t len); + virtual ssize_t file_read(fs_file_t file, void *buffer, size_t size); /** Write the contents of a buffer to a file * @@ -185,7 +185,7 @@ protected: * @param size The number of bytes to write * @return The number of bytes written, negative error on failure */ - virtual ssize_t file_write(fs_file_t file, const void *buffer, size_t len); + virtual ssize_t file_write(fs_file_t file, const void *buffer, size_t size); /** Flush any buffers associated with the file * diff --git a/TESTS_COMMON/ObservingBlockDevice.h b/TESTS_COMMON/ObservingBlockDevice.h index 8449a1e8f7..60f8c97314 100644 --- a/TESTS_COMMON/ObservingBlockDevice.h +++ b/TESTS_COMMON/ObservingBlockDevice.h @@ -35,15 +35,14 @@ public: /** Lifetime of the block device * - * @param size Size of the Block Device in bytes - * @param block Block size in bytes + * @param bd Block device to observe */ ObservingBlockDevice(BlockDevice *bd); virtual ~ObservingBlockDevice(); /** Attach a callback which is called on change * - * @param cb Function to call on filesystem change (erase or program) + * @param cb Function to call on filesystem change (erase or program) */ void attach(Callback cb); diff --git a/TESTS_COMMON/ReadOnlyBlockDevice.h b/TESTS_COMMON/ReadOnlyBlockDevice.h index f6dfee8a8c..e4fbec8f26 100644 --- a/TESTS_COMMON/ReadOnlyBlockDevice.h +++ b/TESTS_COMMON/ReadOnlyBlockDevice.h @@ -35,8 +35,7 @@ public: /** Lifetime of the block device * - * @param size Size of the Block Device in bytes - * @param block Block size in bytes + * @param bd Block device to wrap as read only */ ReadOnlyBlockDevice(BlockDevice *bd); virtual ~ReadOnlyBlockDevice(); diff --git a/TESTS_COMMON/atomic_usage.h b/TESTS_COMMON/atomic_usage.h index ef95c4c89c..d173457a8d 100644 --- a/TESTS_COMMON/atomic_usage.h +++ b/TESTS_COMMON/atomic_usage.h @@ -45,7 +45,7 @@ bool setup_atomic_operations(BlockDevice *bd, bool force_rebuild); * Mount the block device as a littlefs filesystem and a series of * atomic operations on it. Since the operations performed are atomic * the file system will always be in a well defined state. The block - * device must have been setup by calling ::setup_atomic_operations. + * device must have been setup by calling setup_atomic_operations. * * @param bd Block device to perform the operations on * @return -1 if flash is exhausted, otherwise the cycle count on the fs From 91a4f443fe374a5d8cc3063404aa419fb2fd5c20 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 22 Nov 2017 10:44:56 -0600 Subject: [PATCH 36/46] Enforced style consistency with mbed see https://os.mbed.com/docs/v5.6/reference/guidelines.html#style --- LittleFileSystem.cpp | 110 +++++++++++------- LittleFileSystem.h | 26 ++--- TESTS/filesystem/dirs/main.cpp | 51 ++++++-- TESTS/filesystem/files/main.cpp | 42 +++++-- TESTS/filesystem/interspersed/main.cpp | 33 +++++- TESTS/filesystem/seek/main.cpp | 48 ++++++-- TESTS/filesystem_recovery/resilience/main.cpp | 27 ++--- .../resilience_functional/main.cpp | 27 ++--- .../wear_leveling/main.cpp | 27 ++--- TESTS/filesystem_retarget/dirs/main.cpp | 51 ++++++-- TESTS/filesystem_retarget/files/main.cpp | 42 +++++-- .../filesystem_retarget/interspersed/main.cpp | 33 +++++- TESTS/filesystem_retarget/seek/main.cpp | 48 ++++++-- TESTS_COMMON/atomic_usage.cpp | 18 +-- 14 files changed, 387 insertions(+), 196 deletions(-) diff --git a/LittleFileSystem.cpp b/LittleFileSystem.cpp index 49c1fdc19a..56d799b35b 100644 --- a/LittleFileSystem.cpp +++ b/LittleFileSystem.cpp @@ -1,23 +1,17 @@ /* mbed Microcontroller Library - * Copyright (c) 2006-2012 ARM Limited + * Copyright (c) 2017 ARM Limited * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. + * http://www.apache.org/licenses/LICENSE-2.0 * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include "mbed.h" #include "LittleFileSystem.h" @@ -29,7 +23,8 @@ extern "C" { ////// Conversion functions ////// -static int lfs_toerror(int err) { +static int lfs_toerror(int err) +{ switch (err) { case LFS_ERR_OK: return 0; case LFS_ERR_IO: return -EIO; @@ -44,7 +39,8 @@ static int lfs_toerror(int err) { } } -static int lfs_fromflags(int flags) { +static int lfs_fromflags(int flags) +{ return ( (((flags & 3) == O_RDONLY) ? LFS_O_RDONLY : 0) | (((flags & 3) == O_WRONLY) ? LFS_O_WRONLY : 0) | @@ -55,7 +51,8 @@ static int lfs_fromflags(int flags) { ((flags & O_APPEND) ? LFS_O_APPEND : 0)); } -static int lfs_fromwhence(int whence) { +static int lfs_fromwhence(int whence) +{ switch (whence) { case SEEK_SET: return LFS_SEEK_SET; case SEEK_CUR: return LFS_SEEK_CUR; @@ -64,7 +61,8 @@ static int lfs_fromwhence(int whence) { } } -static int lfs_tomode(int type) { +static int lfs_tomode(int type) +{ int mode = S_IRWXU | S_IRWXG | S_IRWXO; switch (type) { case LFS_TYPE_DIR: return mode | S_IFDIR; @@ -73,7 +71,8 @@ static int lfs_tomode(int type) { } } -static int lfs_totype(int type) { +static int lfs_totype(int type) +{ switch (type) { case LFS_TYPE_DIR: return DT_DIR; case LFS_TYPE_REG: return DT_REG; @@ -95,12 +94,14 @@ static int lfs_bd_prog(const struct lfs_config *c, lfs_block_t block, return bd->program(buffer, block*c->block_size + off, size); } -static int lfs_bd_erase(const struct lfs_config *c, lfs_block_t block) { +static int lfs_bd_erase(const struct lfs_config *c, lfs_block_t block) +{ BlockDevice *bd = (BlockDevice *)c->context; return bd->erase(block*c->block_size, c->block_size); } -static int lfs_bd_sync(const struct lfs_config *c) { +static int lfs_bd_sync(const struct lfs_config *c) +{ return 0; } @@ -126,7 +127,8 @@ LittleFileSystem::~LittleFileSystem() { unmount(); } -int LittleFileSystem::mount(BlockDevice *bd) { +int LittleFileSystem::mount(BlockDevice *bd) +{ _mutex.lock(); LFS_INFO("mount(%p)", bd); _bd = bd; @@ -167,7 +169,8 @@ int LittleFileSystem::mount(BlockDevice *bd) { return lfs_toerror(err); } -int LittleFileSystem::unmount() { +int LittleFileSystem::unmount() +{ _mutex.lock(); LFS_INFO("unmount(%s)", ""); if (_bd) { @@ -247,7 +250,8 @@ int LittleFileSystem::format(BlockDevice *bd, return 0; } -int LittleFileSystem::reformat(BlockDevice *bd) { +int LittleFileSystem::reformat(BlockDevice *bd) +{ _mutex.lock(); LFS_INFO("reformat(%p)", bd); if (_bd) { @@ -289,7 +293,8 @@ int LittleFileSystem::reformat(BlockDevice *bd) { return 0; } -int LittleFileSystem::remove(const char *filename) { +int LittleFileSystem::remove(const char *filename) +{ _mutex.lock(); LFS_INFO("remove(\"%s\")", filename); int err = lfs_remove(&_lfs, filename); @@ -298,7 +303,8 @@ int LittleFileSystem::remove(const char *filename) { return lfs_toerror(err); } -int LittleFileSystem::rename(const char *oldname, const char *newname) { +int LittleFileSystem::rename(const char *oldname, const char *newname) +{ _mutex.lock(); LFS_INFO("rename(\"%s\", \"%s\")", oldname, newname); int err = lfs_rename(&_lfs, oldname, newname); @@ -307,7 +313,8 @@ int LittleFileSystem::rename(const char *oldname, const char *newname) { return lfs_toerror(err); } -int LittleFileSystem::mkdir(const char *name, mode_t mode) { +int LittleFileSystem::mkdir(const char *name, mode_t mode) +{ _mutex.lock(); LFS_INFO("mkdir(\"%s\", 0x%lx)", name, mode); int err = lfs_mkdir(&_lfs, name); @@ -316,7 +323,8 @@ int LittleFileSystem::mkdir(const char *name, mode_t mode) { return lfs_toerror(err); } -int LittleFileSystem::stat(const char *name, struct stat *st) { +int LittleFileSystem::stat(const char *name, struct stat *st) +{ struct lfs_info info; _mutex.lock(); LFS_INFO("stat(\"%s\", %p)", name, st); @@ -330,7 +338,8 @@ int LittleFileSystem::stat(const char *name, struct stat *st) { ////// File operations ////// -int LittleFileSystem::file_open(fs_file_t *file, const char *path, int flags) { +int LittleFileSystem::file_open(fs_file_t *file, const char *path, int flags) +{ lfs_file_t *f = new lfs_file_t; _mutex.lock(); LFS_INFO("file_open(%p, \"%s\", 0x%x)", *file, path, flags); @@ -345,7 +354,8 @@ int LittleFileSystem::file_open(fs_file_t *file, const char *path, int flags) { return lfs_toerror(err); } -int LittleFileSystem::file_close(fs_file_t file) { +int LittleFileSystem::file_close(fs_file_t file) +{ lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); LFS_INFO("file_close(%p)", file); @@ -356,7 +366,8 @@ int LittleFileSystem::file_close(fs_file_t file) { return lfs_toerror(err); } -ssize_t LittleFileSystem::file_read(fs_file_t file, void *buffer, size_t len) { +ssize_t LittleFileSystem::file_read(fs_file_t file, void *buffer, size_t len) +{ lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); LFS_INFO("file_read(%p, %p, %d)", file, buffer, len); @@ -366,7 +377,8 @@ ssize_t LittleFileSystem::file_read(fs_file_t file, void *buffer, size_t len) { return lfs_toerror(res); } -ssize_t LittleFileSystem::file_write(fs_file_t file, const void *buffer, size_t len) { +ssize_t LittleFileSystem::file_write(fs_file_t file, const void *buffer, size_t len) +{ lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); LFS_INFO("file_write(%p, %p, %d)", file, buffer, len); @@ -376,7 +388,8 @@ ssize_t LittleFileSystem::file_write(fs_file_t file, const void *buffer, size_t return lfs_toerror(res); } -int LittleFileSystem::file_sync(fs_file_t file) { +int LittleFileSystem::file_sync(fs_file_t file) +{ lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); LFS_INFO("file_sync(%p)", file); @@ -386,7 +399,8 @@ int LittleFileSystem::file_sync(fs_file_t file) { return lfs_toerror(err); } -off_t LittleFileSystem::file_seek(fs_file_t file, off_t offset, int whence) { +off_t LittleFileSystem::file_seek(fs_file_t file, off_t offset, int whence) +{ lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); LFS_INFO("file_seek(%p, %ld, %d)", file, offset, whence); @@ -396,7 +410,8 @@ off_t LittleFileSystem::file_seek(fs_file_t file, off_t offset, int whence) { return lfs_toerror(res); } -off_t LittleFileSystem::file_tell(fs_file_t file) { +off_t LittleFileSystem::file_tell(fs_file_t file) +{ lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); LFS_INFO("file_tell(%p)", file); @@ -406,7 +421,8 @@ off_t LittleFileSystem::file_tell(fs_file_t file) { return lfs_toerror(res); } -off_t LittleFileSystem::file_size(fs_file_t file) { +off_t LittleFileSystem::file_size(fs_file_t file) +{ lfs_file_t *f = (lfs_file_t *)file; _mutex.lock(); LFS_INFO("file_size(%p)", file); @@ -418,7 +434,8 @@ off_t LittleFileSystem::file_size(fs_file_t file) { ////// Dir operations ////// -int LittleFileSystem::dir_open(fs_dir_t *dir, const char *path) { +int LittleFileSystem::dir_open(fs_dir_t *dir, const char *path) +{ lfs_dir_t *d = new lfs_dir_t; _mutex.lock(); LFS_INFO("dir_open(%p, \"%s\")", *dir, path); @@ -433,7 +450,8 @@ int LittleFileSystem::dir_open(fs_dir_t *dir, const char *path) { return lfs_toerror(err); } -int LittleFileSystem::dir_close(fs_dir_t dir) { +int LittleFileSystem::dir_close(fs_dir_t dir) +{ lfs_dir_t *d = (lfs_dir_t *)dir; _mutex.lock(); LFS_INFO("dir_close(%p)", dir); @@ -444,7 +462,8 @@ int LittleFileSystem::dir_close(fs_dir_t dir) { return lfs_toerror(err); } -ssize_t LittleFileSystem::dir_read(fs_dir_t dir, struct dirent *ent) { +ssize_t LittleFileSystem::dir_read(fs_dir_t dir, struct dirent *ent) +{ lfs_dir_t *d = (lfs_dir_t *)dir; struct lfs_info info; _mutex.lock(); @@ -459,7 +478,8 @@ ssize_t LittleFileSystem::dir_read(fs_dir_t dir, struct dirent *ent) { return lfs_toerror(res); } -void LittleFileSystem::dir_seek(fs_dir_t dir, off_t offset) { +void LittleFileSystem::dir_seek(fs_dir_t dir, off_t offset) +{ lfs_dir_t *d = (lfs_dir_t *)dir; _mutex.lock(); LFS_INFO("dir_seek(%p, %ld)", dir, offset); @@ -468,7 +488,8 @@ void LittleFileSystem::dir_seek(fs_dir_t dir, off_t offset) { _mutex.unlock(); } -off_t LittleFileSystem::dir_tell(fs_dir_t dir) { +off_t LittleFileSystem::dir_tell(fs_dir_t dir) +{ lfs_dir_t *d = (lfs_dir_t *)dir; _mutex.lock(); LFS_INFO("dir_tell(%p)", dir); @@ -478,7 +499,8 @@ off_t LittleFileSystem::dir_tell(fs_dir_t dir) { return lfs_toerror(res); } -void LittleFileSystem::dir_rewind(fs_dir_t dir) { +void LittleFileSystem::dir_rewind(fs_dir_t dir) +{ lfs_dir_t *d = (lfs_dir_t *)dir; _mutex.lock(); LFS_INFO("dir_rewind(%p)", dir); diff --git a/LittleFileSystem.h b/LittleFileSystem.h index 9aabf77956..cbaa989849 100644 --- a/LittleFileSystem.h +++ b/LittleFileSystem.h @@ -1,23 +1,17 @@ /* mbed Microcontroller Library - * Copyright (c) 2006-2012 ARM Limited + * Copyright (c) 2017 ARM Limited * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. + * http://www.apache.org/licenses/LICENSE-2.0 * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #ifndef MBED_LFSFILESYSTEM_H #define MBED_LFSFILESYSTEM_H diff --git a/TESTS/filesystem/dirs/main.cpp b/TESTS/filesystem/dirs/main.cpp index 6ee9b7d964..f98db60bf4 100644 --- a/TESTS/filesystem/dirs/main.cpp +++ b/TESTS/filesystem/dirs/main.cpp @@ -1,3 +1,18 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "mbed.h" #include "greentea-client/test_env.h" #include "unity.h" @@ -66,7 +81,8 @@ uint8_t wbuffer[MBED_TEST_BUFFER]; // tests -void test_directory_tests() { +void test_directory_tests() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -79,7 +95,8 @@ void test_directory_tests() { TEST_ASSERT_EQUAL(0, res); } -void test_root_directory() { +void test_root_directory() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -98,7 +115,8 @@ void test_root_directory() { TEST_ASSERT_EQUAL(0, res); } -void test_directory_creation() { +void test_directory_creation() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -115,7 +133,8 @@ void test_directory_creation() { TEST_ASSERT_EQUAL(0, res); } -void test_file_creation() { +void test_file_creation() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -134,7 +153,8 @@ void test_file_creation() { TEST_ASSERT_EQUAL(0, res); } -void test_directory_iteration() { +void test_directory_iteration() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -179,7 +199,8 @@ void test_directory_iteration() { TEST_ASSERT_EQUAL(0, res); } -void test_directory_failures() { +void test_directory_failures() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -204,7 +225,8 @@ void test_directory_failures() { TEST_ASSERT_EQUAL(0, res); } -void test_nested_directories() { +void test_nested_directories() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -268,7 +290,8 @@ void test_nested_directories() { TEST_ASSERT_EQUAL(0, res); } -void test_multi_block_directory() { +void test_multi_block_directory() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -322,7 +345,8 @@ void test_multi_block_directory() { TEST_ASSERT_EQUAL(0, res); } -void test_directory_remove() { +void test_directory_remove() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -432,7 +456,8 @@ void test_directory_remove() { TEST_ASSERT_EQUAL(0, res); } -void test_directory_rename() { +void test_directory_rename() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -632,7 +657,8 @@ void test_directory_rename() { // test setup -utest::v1::status_t test_setup(const size_t number_of_cases) { +utest::v1::status_t test_setup(const size_t number_of_cases) +{ GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); return verbose_test_setup_handler(number_of_cases); } @@ -652,6 +678,7 @@ Case cases[] = { Specification specification(test_setup, cases); -int main() { +int main() +{ return !Harness::run(specification); } diff --git a/TESTS/filesystem/files/main.cpp b/TESTS/filesystem/files/main.cpp index 65c226276c..702cf68d80 100644 --- a/TESTS/filesystem/files/main.cpp +++ b/TESTS/filesystem/files/main.cpp @@ -1,3 +1,18 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "mbed.h" #include "greentea-client/test_env.h" #include "unity.h" @@ -66,7 +81,8 @@ uint8_t wbuffer[MBED_TEST_BUFFER]; // tests -void test_file_tests() { +void test_file_tests() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -79,7 +95,8 @@ void test_file_tests() { TEST_ASSERT_EQUAL(0, res); } -void test_simple_file_test() { +void test_simple_file_test() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -111,7 +128,8 @@ void test_simple_file_test() { TEST_ASSERT_EQUAL(0, res); } -void test_small_file_test() { +void test_small_file_test() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -164,7 +182,8 @@ void test_small_file_test() { TEST_ASSERT_EQUAL(0, res); } -void test_medium_file_test() { +void test_medium_file_test() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -217,7 +236,8 @@ void test_medium_file_test() { TEST_ASSERT_EQUAL(0, res); } -void test_large_file_test() { +void test_large_file_test() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -270,7 +290,8 @@ void test_large_file_test() { TEST_ASSERT_EQUAL(0, res); } -void test_non_overlap_check() { +void test_non_overlap_check() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -347,7 +368,8 @@ void test_non_overlap_check() { TEST_ASSERT_EQUAL(0, res); } -void test_dir_check() { +void test_dir_check() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -403,7 +425,8 @@ void test_dir_check() { // test setup -utest::v1::status_t test_setup(const size_t number_of_cases) { +utest::v1::status_t test_setup(const size_t number_of_cases) +{ GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); return verbose_test_setup_handler(number_of_cases); } @@ -420,6 +443,7 @@ Case cases[] = { Specification specification(test_setup, cases); -int main() { +int main() +{ return !Harness::run(specification); } diff --git a/TESTS/filesystem/interspersed/main.cpp b/TESTS/filesystem/interspersed/main.cpp index 38a604a1a0..c1de477399 100644 --- a/TESTS/filesystem/interspersed/main.cpp +++ b/TESTS/filesystem/interspersed/main.cpp @@ -1,3 +1,18 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "mbed.h" #include "greentea-client/test_env.h" #include "unity.h" @@ -66,7 +81,8 @@ uint8_t wbuffer[MBED_TEST_BUFFER]; // tests -void test_parallel_tests() { +void test_parallel_tests() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -79,7 +95,8 @@ void test_parallel_tests() { TEST_ASSERT_EQUAL(0, res); } -void test_parallel_file_test() { +void test_parallel_file_test() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -196,7 +213,8 @@ void test_parallel_file_test() { TEST_ASSERT_EQUAL(0, res); } -void test_parallel_remove_file_test() { +void test_parallel_remove_file_test() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -269,7 +287,8 @@ void test_parallel_remove_file_test() { TEST_ASSERT_EQUAL(0, res); } -void test_remove_inconveniently_test() { +void test_remove_inconveniently_test() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -367,7 +386,8 @@ void test_remove_inconveniently_test() { // test setup -utest::v1::status_t test_setup(const size_t number_of_cases) { +utest::v1::status_t test_setup(const size_t number_of_cases) +{ GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); return verbose_test_setup_handler(number_of_cases); } @@ -381,6 +401,7 @@ Case cases[] = { Specification specification(test_setup, cases); -int main() { +int main() +{ return !Harness::run(specification); } diff --git a/TESTS/filesystem/seek/main.cpp b/TESTS/filesystem/seek/main.cpp index 40bc6b3e5d..7235df31eb 100644 --- a/TESTS/filesystem/seek/main.cpp +++ b/TESTS/filesystem/seek/main.cpp @@ -1,3 +1,18 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "mbed.h" #include "greentea-client/test_env.h" #include "unity.h" @@ -66,7 +81,8 @@ uint8_t wbuffer[MBED_TEST_BUFFER]; // tests -void test_seek_tests() { +void test_seek_tests() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -99,7 +115,8 @@ void test_seek_tests() { TEST_ASSERT_EQUAL(0, res); } -void test_simple_dir_seek() { +void test_simple_dir_seek() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -168,7 +185,8 @@ void test_simple_dir_seek() { TEST_ASSERT_EQUAL(0, res); } -void test_large_dir_seek() { +void test_large_dir_seek() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -237,7 +255,8 @@ void test_large_dir_seek() { TEST_ASSERT_EQUAL(0, res); } -void test_simple_file_seek() { +void test_simple_file_seek() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -302,7 +321,8 @@ void test_simple_file_seek() { TEST_ASSERT_EQUAL(0, res); } -void test_large_file_seek() { +void test_large_file_seek() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -367,7 +387,8 @@ void test_large_file_seek() { TEST_ASSERT_EQUAL(0, res); } -void test_simple_file_seek_and_write() { +void test_simple_file_seek_and_write() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -432,7 +453,8 @@ void test_simple_file_seek_and_write() { TEST_ASSERT_EQUAL(0, res); } -void test_large_file_seek_and_write() { +void test_large_file_seek_and_write() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -499,7 +521,8 @@ void test_large_file_seek_and_write() { TEST_ASSERT_EQUAL(0, res); } -void test_boundary_seek_and_write() { +void test_boundary_seek_and_write() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -544,7 +567,8 @@ void test_boundary_seek_and_write() { TEST_ASSERT_EQUAL(0, res); } -void test_out_of_bounds_seek() { +void test_out_of_bounds_seek() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -593,7 +617,8 @@ void test_out_of_bounds_seek() { // test setup -utest::v1::status_t test_setup(const size_t number_of_cases) { +utest::v1::status_t test_setup(const size_t number_of_cases) +{ GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); return verbose_test_setup_handler(number_of_cases); } @@ -612,6 +637,7 @@ Case cases[] = { Specification specification(test_setup, cases); -int main() { +int main() +{ return !Harness::run(specification); } diff --git a/TESTS/filesystem_recovery/resilience/main.cpp b/TESTS/filesystem_recovery/resilience/main.cpp index 2d1eb58cf7..ac51f4be5f 100644 --- a/TESTS/filesystem_recovery/resilience/main.cpp +++ b/TESTS/filesystem_recovery/resilience/main.cpp @@ -1,25 +1,18 @@ /* mbed Microcontroller Library - * Copyright (c) 2017-2017 ARM Limited + * Copyright (c) 2017 ARM Limited * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. + * http://www.apache.org/licenses/LICENSE-2.0 * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ - #include "mbed.h" #include "unity.h" #include "utest.h" diff --git a/TESTS/filesystem_recovery/resilience_functional/main.cpp b/TESTS/filesystem_recovery/resilience_functional/main.cpp index 1771fb0492..bfc319d074 100644 --- a/TESTS/filesystem_recovery/resilience_functional/main.cpp +++ b/TESTS/filesystem_recovery/resilience_functional/main.cpp @@ -1,25 +1,18 @@ /* mbed Microcontroller Library - * Copyright (c) 2017-2017 ARM Limited + * Copyright (c) 2017 ARM Limited * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. + * http://www.apache.org/licenses/LICENSE-2.0 * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ - #include "mbed.h" #include "unity.h" #include "utest.h" diff --git a/TESTS/filesystem_recovery/wear_leveling/main.cpp b/TESTS/filesystem_recovery/wear_leveling/main.cpp index 308aec4373..c382296d8c 100644 --- a/TESTS/filesystem_recovery/wear_leveling/main.cpp +++ b/TESTS/filesystem_recovery/wear_leveling/main.cpp @@ -1,25 +1,18 @@ /* mbed Microcontroller Library - * Copyright (c) 2017-2017 ARM Limited + * Copyright (c) 2017 ARM Limited * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. + * http://www.apache.org/licenses/LICENSE-2.0 * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ - #include "mbed.h" #include "unity.h" #include "utest.h" diff --git a/TESTS/filesystem_retarget/dirs/main.cpp b/TESTS/filesystem_retarget/dirs/main.cpp index c7bd5ea01e..1c4e62863b 100644 --- a/TESTS/filesystem_retarget/dirs/main.cpp +++ b/TESTS/filesystem_retarget/dirs/main.cpp @@ -1,3 +1,18 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "mbed.h" #include "greentea-client/test_env.h" #include "unity.h" @@ -66,7 +81,8 @@ uint8_t wbuffer[MBED_TEST_BUFFER]; // tests -void test_directory_tests() { +void test_directory_tests() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -79,7 +95,8 @@ void test_directory_tests() { TEST_ASSERT_EQUAL(0, res); } -void test_root_directory() { +void test_root_directory() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -98,7 +115,8 @@ void test_root_directory() { TEST_ASSERT_EQUAL(0, res); } -void test_directory_creation() { +void test_directory_creation() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -115,7 +133,8 @@ void test_directory_creation() { TEST_ASSERT_EQUAL(0, res); } -void test_file_creation() { +void test_file_creation() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -134,7 +153,8 @@ void test_file_creation() { TEST_ASSERT_EQUAL(0, res); } -void test_directory_iteration() { +void test_directory_iteration() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -179,7 +199,8 @@ void test_directory_iteration() { TEST_ASSERT_EQUAL(0, res); } -void test_directory_failures() { +void test_directory_failures() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -204,7 +225,8 @@ void test_directory_failures() { TEST_ASSERT_EQUAL(0, res); } -void test_nested_directories() { +void test_nested_directories() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -268,7 +290,8 @@ void test_nested_directories() { TEST_ASSERT_EQUAL(0, res); } -void test_multi_block_directory() { +void test_multi_block_directory() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -322,7 +345,8 @@ void test_multi_block_directory() { TEST_ASSERT_EQUAL(0, res); } -void test_directory_remove() { +void test_directory_remove() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -432,7 +456,8 @@ void test_directory_remove() { TEST_ASSERT_EQUAL(0, res); } -void test_directory_rename() { +void test_directory_rename() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -632,7 +657,8 @@ void test_directory_rename() { // test setup -utest::v1::status_t test_setup(const size_t number_of_cases) { +utest::v1::status_t test_setup(const size_t number_of_cases) +{ GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); return verbose_test_setup_handler(number_of_cases); } @@ -652,6 +678,7 @@ Case cases[] = { Specification specification(test_setup, cases); -int main() { +int main() +{ return !Harness::run(specification); } diff --git a/TESTS/filesystem_retarget/files/main.cpp b/TESTS/filesystem_retarget/files/main.cpp index 4751193bde..df14557eb9 100644 --- a/TESTS/filesystem_retarget/files/main.cpp +++ b/TESTS/filesystem_retarget/files/main.cpp @@ -1,3 +1,18 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "mbed.h" #include "greentea-client/test_env.h" #include "unity.h" @@ -66,7 +81,8 @@ uint8_t wbuffer[MBED_TEST_BUFFER]; // tests -void test_file_tests() { +void test_file_tests() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -79,7 +95,8 @@ void test_file_tests() { TEST_ASSERT_EQUAL(0, res); } -void test_simple_file_test() { +void test_simple_file_test() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -111,7 +128,8 @@ void test_simple_file_test() { TEST_ASSERT_EQUAL(0, res); } -void test_small_file_test() { +void test_small_file_test() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -164,7 +182,8 @@ void test_small_file_test() { TEST_ASSERT_EQUAL(0, res); } -void test_medium_file_test() { +void test_medium_file_test() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -217,7 +236,8 @@ void test_medium_file_test() { TEST_ASSERT_EQUAL(0, res); } -void test_large_file_test() { +void test_large_file_test() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -270,7 +290,8 @@ void test_large_file_test() { TEST_ASSERT_EQUAL(0, res); } -void test_non_overlap_check() { +void test_non_overlap_check() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -347,7 +368,8 @@ void test_non_overlap_check() { TEST_ASSERT_EQUAL(0, res); } -void test_dir_check() { +void test_dir_check() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -403,7 +425,8 @@ void test_dir_check() { // test setup -utest::v1::status_t test_setup(const size_t number_of_cases) { +utest::v1::status_t test_setup(const size_t number_of_cases) +{ GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); return verbose_test_setup_handler(number_of_cases); } @@ -420,6 +443,7 @@ Case cases[] = { Specification specification(test_setup, cases); -int main() { +int main() +{ return !Harness::run(specification); } diff --git a/TESTS/filesystem_retarget/interspersed/main.cpp b/TESTS/filesystem_retarget/interspersed/main.cpp index de6f673b41..250016ef67 100644 --- a/TESTS/filesystem_retarget/interspersed/main.cpp +++ b/TESTS/filesystem_retarget/interspersed/main.cpp @@ -1,3 +1,18 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "mbed.h" #include "greentea-client/test_env.h" #include "unity.h" @@ -66,7 +81,8 @@ uint8_t wbuffer[MBED_TEST_BUFFER]; // tests -void test_parallel_tests() { +void test_parallel_tests() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -79,7 +95,8 @@ void test_parallel_tests() { TEST_ASSERT_EQUAL(0, res); } -void test_parallel_file_test() { +void test_parallel_file_test() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -196,7 +213,8 @@ void test_parallel_file_test() { TEST_ASSERT_EQUAL(0, res); } -void test_parallel_remove_file_test() { +void test_parallel_remove_file_test() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -269,7 +287,8 @@ void test_parallel_remove_file_test() { TEST_ASSERT_EQUAL(0, res); } -void test_remove_inconveniently_test() { +void test_remove_inconveniently_test() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -367,7 +386,8 @@ void test_remove_inconveniently_test() { // test setup -utest::v1::status_t test_setup(const size_t number_of_cases) { +utest::v1::status_t test_setup(const size_t number_of_cases) +{ GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); return verbose_test_setup_handler(number_of_cases); } @@ -381,6 +401,7 @@ Case cases[] = { Specification specification(test_setup, cases); -int main() { +int main() +{ return !Harness::run(specification); } diff --git a/TESTS/filesystem_retarget/seek/main.cpp b/TESTS/filesystem_retarget/seek/main.cpp index 7169c35583..c37383e76c 100644 --- a/TESTS/filesystem_retarget/seek/main.cpp +++ b/TESTS/filesystem_retarget/seek/main.cpp @@ -1,3 +1,18 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "mbed.h" #include "greentea-client/test_env.h" #include "unity.h" @@ -66,7 +81,8 @@ uint8_t wbuffer[MBED_TEST_BUFFER]; // tests -void test_seek_tests() { +void test_seek_tests() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -99,7 +115,8 @@ void test_seek_tests() { TEST_ASSERT_EQUAL(0, res); } -void test_simple_dir_seek() { +void test_simple_dir_seek() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -168,7 +185,8 @@ void test_simple_dir_seek() { TEST_ASSERT_EQUAL(0, res); } -void test_large_dir_seek() { +void test_large_dir_seek() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -237,7 +255,8 @@ void test_large_dir_seek() { TEST_ASSERT_EQUAL(0, res); } -void test_simple_file_seek() { +void test_simple_file_seek() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -301,7 +320,8 @@ void test_simple_file_seek() { TEST_ASSERT_EQUAL(0, res); } -void test_large_file_seek() { +void test_large_file_seek() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -365,7 +385,8 @@ void test_large_file_seek() { TEST_ASSERT_EQUAL(0, res); } -void test_simple_file_seek_and_write() { +void test_simple_file_seek_and_write() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -429,7 +450,8 @@ void test_simple_file_seek_and_write() { TEST_ASSERT_EQUAL(0, res); } -void test_large_file_seek_and_write() { +void test_large_file_seek_and_write() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -495,7 +517,8 @@ void test_large_file_seek_and_write() { TEST_ASSERT_EQUAL(0, res); } -void test_boundary_seek_and_write() { +void test_boundary_seek_and_write() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -540,7 +563,8 @@ void test_boundary_seek_and_write() { TEST_ASSERT_EQUAL(0, res); } -void test_out_of_bounds_seek() { +void test_out_of_bounds_seek() +{ int res = bd.init(); TEST_ASSERT_EQUAL(0, res); @@ -591,7 +615,8 @@ void test_out_of_bounds_seek() { // test setup -utest::v1::status_t test_setup(const size_t number_of_cases) { +utest::v1::status_t test_setup(const size_t number_of_cases) +{ GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); return verbose_test_setup_handler(number_of_cases); } @@ -610,6 +635,7 @@ Case cases[] = { Specification specification(test_setup, cases); -int main() { +int main() +{ return !Harness::run(specification); } diff --git a/TESTS_COMMON/atomic_usage.cpp b/TESTS_COMMON/atomic_usage.cpp index af6c30d243..bd445b0089 100644 --- a/TESTS_COMMON/atomic_usage.cpp +++ b/TESTS_COMMON/atomic_usage.cpp @@ -223,11 +223,11 @@ static bool perform_file_rename(FileSystem *fs) struct stat st; int res = fs->stat(FILE_RENAME_A, &st); - const char *src = (0 == res) ? FILE_RENAME_A : FILE_RENAME_B; - const char *dst = (0 == res) ? FILE_RENAME_B : FILE_RENAME_A; + const char *src = (res == 0) ? FILE_RENAME_A : FILE_RENAME_B; + const char *dst = (res == 0) ? FILE_RENAME_B : FILE_RENAME_A; DEBUG(" stat result %i\n", res); - TEST_ASSERT_OR_EXIT((res == -ENOENT) || (0 == res)); + TEST_ASSERT_OR_EXIT((res == -ENOENT) || (res == 0)); DEBUG(" Renaming %s to %s\n", src, dst); res = fs->rename(src, dst); @@ -258,7 +258,7 @@ static void check_file_rename(FileSystem *fs) for (int i = 0; i < 2; i++) { File file; - if (0 == file.open(fs, filenames[i], O_RDONLY)) { + if (file.open(fs, filenames[i], O_RDONLY) == 0) { uint8_t buf[BUFFER_SIZE]; files++; memset(buf, 0, sizeof(buf)); @@ -409,11 +409,11 @@ static bool perform_directory_rename(FileSystem *fs) struct stat st; int res = fs->stat(DIRECTORY_RENAME_A, &st); - const char *src = (0 == res) ? DIRECTORY_RENAME_A : DIRECTORY_RENAME_B; - const char *dst = (0 == res) ? DIRECTORY_RENAME_B : DIRECTORY_RENAME_A; + const char *src = (res == 0) ? DIRECTORY_RENAME_A : DIRECTORY_RENAME_B; + const char *dst = (res == 0) ? DIRECTORY_RENAME_B : DIRECTORY_RENAME_A; DEBUG(" stat result %i\n", res); - TEST_ASSERT_OR_EXIT((res == -ENOENT) || (0 == res)); + TEST_ASSERT_OR_EXIT((res == -ENOENT) || (res == 0)); DEBUG(" Renaming %s to %s\n", src, dst); res = fs->rename(src, dst); @@ -444,8 +444,8 @@ static void check_directory_rename(FileSystem *fs) for (size_t i = 0; i < ARRAY_LENGTH(directory_names); i++) { Dir dir; int res = dir.open(fs, directory_names[i]); - TEST_ASSERT_OR_EXIT((-ENOENT == res) || (0 == res)); - if (0 == res) { + TEST_ASSERT_OR_EXIT((res == -ENOENT) || (res == 0)); + if (res == 0) { directories++; } } From d02b3122f006aa201bca4efc699bae40971e5a00 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 22 Nov 2017 11:23:00 -0600 Subject: [PATCH 37/46] Removed warnings about format strings when running littlefs tests The difference gcc targets differ with format strings in some odd (but not against the rules) ways. Most notable, the uint32_t in i386/x86_64 gcc uses %d, whereas on cortex-m gcc uses %ld. This makes dealing with warnings on code that goes between the two rather annoying. --- .travis.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3c924f2a87..660d9dd6a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,23 +11,23 @@ script: -n 'tests*' # Run littlefs functional tests - - CFLAGS="-Wno-error=format" make -Clittlefs test QUIET=1 + - CFLAGS="-Wno-format" make -Clittlefs test QUIET=1 # Run littlefs functional tests with different configurations # Note: r/w size of 64 is default in mbed - - CFLAGS="-Wno-error=format -DLFS_READ_SIZE=64 -DLFS_PROG_SIZE=64" + - CFLAGS="-Wno-format -DLFS_READ_SIZE=64 -DLFS_PROG_SIZE=64" make -Clittlefs test QUIET=1 - - CFLAGS="-Wno-error=format -DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" + - CFLAGS="-Wno-format -DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make -Clittlefs test QUIET=1 - - CFLAGS="-Wno-error=format -DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" + - CFLAGS="-Wno-format -DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make -Clittlefs test QUIET=1 - - CFLAGS="-Wno-error=format -DLFS_BLOCK_COUNT=1023" + - CFLAGS="-Wno-format -DLFS_BLOCK_COUNT=1023" make -Clittlefs test QUIET=1 - - CFLAGS="-Wno-error=format -DLFS_LOOKAHEAD=2048" + - CFLAGS="-Wno-format -DLFS_LOOKAHEAD=2048" make -Clittlefs test QUIET=1 # Self-host with littlefs-fuse for fuzz test - - make -C littlefs-fuse + - CFLAGS="-Wno-format" make -C littlefs-fuse - littlefs-fuse/lfs --format /dev/loop0 - littlefs-fuse/lfs /dev/loop0 mount @@ -37,7 +37,7 @@ script: - cp -r $(git ls-tree --name-only HEAD littlefs/) mount/littlefs - cd mount/littlefs - ls - - CFLAGS="-Wno-error=format" make -B test_dirs QUIET=1 + - CFLAGS="-Wno-format" make -B test_dirs QUIET=1 install: # Get arm-none-eabi-gcc From 9bc4ea6504bb17e717bfee48df7ab2cf11450f58 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 22 Nov 2017 10:59:13 -0600 Subject: [PATCH 38/46] littlefs: Removed mbed namespace leaks --- .../filesystem/littlefs/LittleFileSystem.h | 32 +++++++++---------- .../TESTS_COMMON/ExhaustibleBlockDevice.cpp | 1 + .../TESTS_COMMON/ExhaustibleBlockDevice.h | 1 - .../TESTS_COMMON/ObservingBlockDevice.cpp | 1 + .../TESTS_COMMON/ObservingBlockDevice.h | 8 ++--- .../TESTS_COMMON/ReadOnlyBlockDevice.h | 3 -- .../littlefs/TESTS_COMMON/atomic_usage.h | 1 - 7 files changed, 20 insertions(+), 27 deletions(-) diff --git a/features/filesystem/littlefs/LittleFileSystem.h b/features/filesystem/littlefs/LittleFileSystem.h index cbaa989849..0829eb2faa 100644 --- a/features/filesystem/littlefs/LittleFileSystem.h +++ b/features/filesystem/littlefs/LittleFileSystem.h @@ -23,13 +23,11 @@ extern "C" { #include "lfs.h" } -using namespace mbed; - /** * LittleFileSystem, a little filesystem */ -class LittleFileSystem : public FileSystem { +class LittleFileSystem : public mbed::FileSystem { public: /** Lifetime of the LittleFileSystem * @@ -154,14 +152,14 @@ protected: * bitwise or'd with one of O_CREAT, O_TRUNC, O_APPEND * @return 0 on success, negative error code on failure */ - virtual int file_open(fs_file_t *file, const char *path, int flags); + virtual int file_open(mbed::fs_file_t *file, const char *path, int flags); /** Close a file * * @param file File handle * return 0 on success, negative error code on failure */ - virtual int file_close(fs_file_t file); + virtual int file_close(mbed::fs_file_t file); /** Read the contents of a file into a buffer * @@ -170,7 +168,7 @@ protected: * @param size The number of bytes to read * @return The number of bytes read, 0 at end of file, negative error on failure */ - virtual ssize_t file_read(fs_file_t file, void *buffer, size_t size); + virtual ssize_t file_read(mbed::fs_file_t file, void *buffer, size_t size); /** Write the contents of a buffer to a file * @@ -179,14 +177,14 @@ protected: * @param size The number of bytes to write * @return The number of bytes written, negative error on failure */ - virtual ssize_t file_write(fs_file_t file, const void *buffer, size_t size); + virtual ssize_t file_write(mbed::fs_file_t file, const void *buffer, size_t size); /** Flush any buffers associated with the file * * @param file File handle * @return 0 on success, negative error code on failure */ - virtual int file_sync(fs_file_t file); + virtual int file_sync(mbed::fs_file_t file); /** Move the file position to a given offset from from a given location * @@ -198,21 +196,21 @@ protected: * SEEK_END to start from end of file * @return The new offset of the file */ - virtual off_t file_seek(fs_file_t file, off_t offset, int whence); + virtual off_t file_seek(mbed::fs_file_t file, off_t offset, int whence); /** Get the file position of the file * * @param file File handle * @return The current offset in the file */ - virtual off_t file_tell(fs_file_t file); + virtual off_t file_tell(mbed::fs_file_t file); /** Get the size of the file * * @param file File handle * @return Size of the file in bytes */ - virtual off_t file_size(fs_file_t file); + virtual off_t file_size(mbed::fs_file_t file); /** Open a directory on the filesystem * @@ -220,14 +218,14 @@ protected: * @param path Name of the directory to open * @return 0 on success, negative error code on failure */ - virtual int dir_open(fs_dir_t *dir, const char *path); + virtual int dir_open(mbed::fs_dir_t *dir, const char *path); /** Close a directory * * @param dir Dir handle * return 0 on success, negative error code on failure */ - virtual int dir_close(fs_dir_t dir); + virtual int dir_close(mbed::fs_dir_t dir); /** Read the next directory entry * @@ -235,7 +233,7 @@ protected: * @param ent The directory entry to fill out * @return 1 on reading a filename, 0 at end of directory, negative error on failure */ - virtual ssize_t dir_read(fs_dir_t dir, struct dirent *ent); + virtual ssize_t dir_read(mbed::fs_dir_t dir, struct dirent *ent); /** Set the current position of the directory * @@ -243,20 +241,20 @@ protected: * @param offset Offset of the location to seek to, * must be a value returned from dir_tell */ - virtual void dir_seek(fs_dir_t dir, off_t offset); + virtual void dir_seek(mbed::fs_dir_t dir, off_t offset); /** Get the current position of the directory * * @param dir Dir handle * @return Position of the directory that can be passed to dir_rewind */ - virtual off_t dir_tell(fs_dir_t dir); + virtual off_t dir_tell(mbed::fs_dir_t dir); /** Rewind the current position to the beginning of the directory * * @param dir Dir handle */ - virtual void dir_rewind(fs_dir_t dir); + virtual void dir_rewind(mbed::fs_dir_t dir); private: lfs_t _lfs; // _the actual filesystem diff --git a/features/filesystem/littlefs/TESTS_COMMON/ExhaustibleBlockDevice.cpp b/features/filesystem/littlefs/TESTS_COMMON/ExhaustibleBlockDevice.cpp index bc4f349c3e..0b16a9217f 100644 --- a/features/filesystem/littlefs/TESTS_COMMON/ExhaustibleBlockDevice.cpp +++ b/features/filesystem/littlefs/TESTS_COMMON/ExhaustibleBlockDevice.cpp @@ -15,6 +15,7 @@ */ #include "ExhaustibleBlockDevice.h" +#include "mbed.h" ExhaustibleBlockDevice::ExhaustibleBlockDevice(BlockDevice *bd, uint32_t erase_cycles) diff --git a/features/filesystem/littlefs/TESTS_COMMON/ExhaustibleBlockDevice.h b/features/filesystem/littlefs/TESTS_COMMON/ExhaustibleBlockDevice.h index fd6100f5d5..3c1e83d0c6 100644 --- a/features/filesystem/littlefs/TESTS_COMMON/ExhaustibleBlockDevice.h +++ b/features/filesystem/littlefs/TESTS_COMMON/ExhaustibleBlockDevice.h @@ -23,7 +23,6 @@ #define MBED_EXHAUSTIBLE_BLOCK_DEVICE_H #include "BlockDevice.h" -#include "mbed.h" /** Heap backed block device which simulates failures diff --git a/features/filesystem/littlefs/TESTS_COMMON/ObservingBlockDevice.cpp b/features/filesystem/littlefs/TESTS_COMMON/ObservingBlockDevice.cpp index bdc87ea70b..ea6dc5da15 100644 --- a/features/filesystem/littlefs/TESTS_COMMON/ObservingBlockDevice.cpp +++ b/features/filesystem/littlefs/TESTS_COMMON/ObservingBlockDevice.cpp @@ -22,6 +22,7 @@ #include "ObservingBlockDevice.h" #include "ReadOnlyBlockDevice.h" +#include "mbed.h" ObservingBlockDevice::ObservingBlockDevice(BlockDevice *bd) diff --git a/features/filesystem/littlefs/TESTS_COMMON/ObservingBlockDevice.h b/features/filesystem/littlefs/TESTS_COMMON/ObservingBlockDevice.h index 60f8c97314..a8b4a4a426 100644 --- a/features/filesystem/littlefs/TESTS_COMMON/ObservingBlockDevice.h +++ b/features/filesystem/littlefs/TESTS_COMMON/ObservingBlockDevice.h @@ -22,11 +22,9 @@ #ifndef MBED_OBSERVING_BLOCK_DEVICE_H #define MBED_OBSERVING_BLOCK_DEVICE_H -#include "FileSystem.h" #include "BlockDevice.h" #include "PlatformMutex.h" - -using namespace mbed; +#include "Callback.h" class ObservingBlockDevice : public BlockDevice @@ -44,7 +42,7 @@ public: * * @param cb Function to call on filesystem change (erase or program) */ - void attach(Callback cb); + void attach(mbed::Callback cb); /** Initialize a block device * @@ -114,7 +112,7 @@ public: private: BlockDevice *_bd; - Callback _change; + mbed::Callback _change; }; diff --git a/features/filesystem/littlefs/TESTS_COMMON/ReadOnlyBlockDevice.h b/features/filesystem/littlefs/TESTS_COMMON/ReadOnlyBlockDevice.h index e4fbec8f26..2120716d95 100644 --- a/features/filesystem/littlefs/TESTS_COMMON/ReadOnlyBlockDevice.h +++ b/features/filesystem/littlefs/TESTS_COMMON/ReadOnlyBlockDevice.h @@ -22,12 +22,9 @@ #ifndef MBED_READ_ONLY_BLOCK_DEVICE_H #define MBED_READ_ONLY_BLOCK_DEVICE_H -#include "FileSystem.h" #include "BlockDevice.h" #include "PlatformMutex.h" -using namespace mbed; - class ReadOnlyBlockDevice : public BlockDevice { diff --git a/features/filesystem/littlefs/TESTS_COMMON/atomic_usage.h b/features/filesystem/littlefs/TESTS_COMMON/atomic_usage.h index d173457a8d..43bf5be94a 100644 --- a/features/filesystem/littlefs/TESTS_COMMON/atomic_usage.h +++ b/features/filesystem/littlefs/TESTS_COMMON/atomic_usage.h @@ -23,7 +23,6 @@ #define MBED_ATOMIC_USAGE_H #include "BlockDevice.h" -#include "mbed.h" /** * Setup the given block device to test littlefs atomic operations From 4adf75c9aace7e147e88e887b8b352622047a15e Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 22 Nov 2017 11:15:55 -0600 Subject: [PATCH 39/46] littlefs: Moved test block devices into general block devices --- .../{littlefs/TESTS_COMMON => bd}/ExhaustibleBlockDevice.cpp | 0 .../{littlefs/TESTS_COMMON => bd}/ExhaustibleBlockDevice.h | 0 .../{littlefs/TESTS_COMMON => bd}/ObservingBlockDevice.cpp | 0 .../{littlefs/TESTS_COMMON => bd}/ObservingBlockDevice.h | 0 .../{littlefs/TESTS_COMMON => bd}/ReadOnlyBlockDevice.cpp | 0 .../{littlefs/TESTS_COMMON => bd}/ReadOnlyBlockDevice.h | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename features/filesystem/{littlefs/TESTS_COMMON => bd}/ExhaustibleBlockDevice.cpp (100%) rename features/filesystem/{littlefs/TESTS_COMMON => bd}/ExhaustibleBlockDevice.h (100%) rename features/filesystem/{littlefs/TESTS_COMMON => bd}/ObservingBlockDevice.cpp (100%) rename features/filesystem/{littlefs/TESTS_COMMON => bd}/ObservingBlockDevice.h (100%) rename features/filesystem/{littlefs/TESTS_COMMON => bd}/ReadOnlyBlockDevice.cpp (100%) rename features/filesystem/{littlefs/TESTS_COMMON => bd}/ReadOnlyBlockDevice.h (100%) diff --git a/features/filesystem/littlefs/TESTS_COMMON/ExhaustibleBlockDevice.cpp b/features/filesystem/bd/ExhaustibleBlockDevice.cpp similarity index 100% rename from features/filesystem/littlefs/TESTS_COMMON/ExhaustibleBlockDevice.cpp rename to features/filesystem/bd/ExhaustibleBlockDevice.cpp diff --git a/features/filesystem/littlefs/TESTS_COMMON/ExhaustibleBlockDevice.h b/features/filesystem/bd/ExhaustibleBlockDevice.h similarity index 100% rename from features/filesystem/littlefs/TESTS_COMMON/ExhaustibleBlockDevice.h rename to features/filesystem/bd/ExhaustibleBlockDevice.h diff --git a/features/filesystem/littlefs/TESTS_COMMON/ObservingBlockDevice.cpp b/features/filesystem/bd/ObservingBlockDevice.cpp similarity index 100% rename from features/filesystem/littlefs/TESTS_COMMON/ObservingBlockDevice.cpp rename to features/filesystem/bd/ObservingBlockDevice.cpp diff --git a/features/filesystem/littlefs/TESTS_COMMON/ObservingBlockDevice.h b/features/filesystem/bd/ObservingBlockDevice.h similarity index 100% rename from features/filesystem/littlefs/TESTS_COMMON/ObservingBlockDevice.h rename to features/filesystem/bd/ObservingBlockDevice.h diff --git a/features/filesystem/littlefs/TESTS_COMMON/ReadOnlyBlockDevice.cpp b/features/filesystem/bd/ReadOnlyBlockDevice.cpp similarity index 100% rename from features/filesystem/littlefs/TESTS_COMMON/ReadOnlyBlockDevice.cpp rename to features/filesystem/bd/ReadOnlyBlockDevice.cpp diff --git a/features/filesystem/littlefs/TESTS_COMMON/ReadOnlyBlockDevice.h b/features/filesystem/bd/ReadOnlyBlockDevice.h similarity index 100% rename from features/filesystem/littlefs/TESTS_COMMON/ReadOnlyBlockDevice.h rename to features/filesystem/bd/ReadOnlyBlockDevice.h From ea0fee09681c8b5b825a4add19c6e05920f5d1b6 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 22 Nov 2017 12:42:45 -0600 Subject: [PATCH 40/46] littlefs: Integrated littlefs's .travis.yml into mbed OS Also cleaned up the central .travis.yml to better support similar local testing in Travis CI --- .travis.yml | 80 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3fecbafc4e..087cdbebc6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,40 +1,62 @@ python: - "2.7" + script: - - mkdir BUILD -# Assert that the Doxygen build produced no warnings. -# The strange command below asserts that the Doxygen command had an -# output of zero length + # Assert that the Doxygen build produced no warnings. + # The strange command below asserts that the Doxygen command had an + # output of zero length - | doxygen doxyfile_options 2>&1 | tee BUILD/doxygen.out && [ ! -s BUILD/doxygen.out ] -# Assert that all binary libraries are named correctly -# The strange command below asserts that there are exactly 0 libraries that do -# not start with lib + # Assert that all binary libraries are named correctly + # The strange command below asserts that there are exactly 0 libraries that do + # not start with lib - | find "(" -name "*.a" -or -name "*.ar" ")" -and -not -name "lib*" | tee BUILD/badlibs | sed -e "s/^/Bad library name found: /" && [ ! -s BUILD/badlibs ] -# Assert that all assebler files are named correctly -# The strange command below asserts that there are exactly 0 libraries that do -# end with .s + # Assert that all assebler files are named correctly + # The strange command below asserts that there are exactly 0 libraries that do + # end with .s - | find -name "*.s" | tee BUILD/badasm | sed -e "s/^/Bad Assembler file name found: /" && [ ! -s BUILD/badasm ] - - make -C events/equeue test clean + # Run local testing on tools + # Note: These take ~40 minutes to run - PYTHONPATH=. coverage run -a -m pytest tools/test - python2 tools/test/pylint.py - - coverage run -a tools/project.py -S - - python2 tools/build_travis.py + - coverage run -a tools/project.py -S | sed -n '/^Total/p' + - python2 -u tools/build_travis.py | sed -n '/^Executing/p' - coverage html + # Run local event queue tests + - make -C events/equeue test + # Run local littlefs tests + - CFLAGS="-Wno-format" make -Cfeatures/filesystem/littlefs/littlefs test QUIET=1 + # Run local littlefs tests with set of variations + - CFLAGS="-Wno-format -DLFS_READ_SIZE=64 -DLFS_PROG_SIZE=64" make -Cfeatures/filesystem/littlefs/littlefs test QUIET=1 + - CFLAGS="-Wno-format -DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make -Cfeatures/filesystem/littlefs/littlefs test QUIET=1 + - CFLAGS="-Wno-format -DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make -Cfeatures/filesystem/littlefs/littlefs test QUIET=1 + - CFLAGS="-Wno-format -DLFS_BLOCK_COUNT=1023" make -Cfeatures/filesystem/littlefs/littlefs test QUIET=1 + - CFLAGS="-Wno-format -DLFS_LOOKAHEAD=2048" make -Cfeatures/filesystem/littlefs/littlefs test QUIET=1 + # Self-hosting littlefs fuzz test with littlefs-fuse + - make -C littlefs-fuse + - littlefs-fuse/lfs --format /dev/loop0 + - littlefs-fuse/lfs /dev/loop0 MOUNT + - ls MOUNT + - mkdir MOUNT/littlefs + - cp -r $(git ls-tree --name-only HEAD features/filesystem/littlefs/littlefs/) MOUNT/littlefs + - ls MOUNT/littlefs + - CFLAGS="-Wno-format" make -CMOUNT/littlefs -B test_dirs QUIET=1 + after_success: + # Coverage for tools - coveralls + before_install: + # Setup ppa to make sure arm-none-eabi-gcc is correct version - sudo add-apt-repository -y ppa:team-gcc-arm-embedded/ppa - sudo add-apt-repository -y ppa:libreoffice/libreoffice-4-2 - sudo apt-get update -qq - - sudo apt-get install -qq gcc-arm-embedded doxygen --force-yes - # Print versions we use - - arm-none-eabi-gcc --version - - python --version - - doxygen --version + install: + # Install dependencies + - sudo apt-get install -qq gcc-arm-embedded doxygen libfuse-dev - pip install --user -r requirements.txt - pip install --user pytest - pip install --user pylint @@ -42,3 +64,25 @@ install: - pip install --user mock - pip install --user coverage - pip install --user coveralls + # Print versions we use + - arm-none-eabi-gcc --version + - python --version + - doxygen --version + - gcc --version + - fusermount --version + +before_script: + # Create BUILD directory for tests + - mkdir BUILD + # Make sure pipefail + - set -o pipefail + # Setup and patch littlefs-fuse + - git clone https://github.com/geky/littlefs-fuse + - echo '*' > littlefs-fuse/.mbedignore + - rm -rf littlefs-fuse/littlefs/* + - cp -r $(git ls-tree --name-only HEAD features/filesystem/littlefs/littlefs/) littlefs-fuse/littlefs + # Create file-backed disk + - mkdir MOUNT + - sudo chmod a+rw /dev/loop0 + - dd if=/dev/zero bs=512 count=2048 of=DISK + - losetup /dev/loop0 DISK From 2cf4715cb65495dd4d134ef749f6b293a5a7ebab Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 22 Nov 2017 17:19:16 -0600 Subject: [PATCH 41/46] littlefs: Increased test timeout to 4 minutes Unfortunately there are several issues colluding to force the timeout this high. 1. The tests push littlefs to the limits of how many errors it can handle before failing. As a side effect this causes a massive amount of debug/warn/error logging about the situation. 2. The logging can't be turned off for specific tests. Note: This might change with the introduction of test-configs. 3. Logging is fixed to a baud rate of 9600. Previous testing was carried out with a baud rate of 115200, which is the reason for the original timeout. --- features/filesystem/littlefs/TESTS/filesystem/dirs/main.cpp | 2 +- .../filesystem/littlefs/TESTS/filesystem/files/main.cpp | 2 +- .../littlefs/TESTS/filesystem/interspersed/main.cpp | 2 +- features/filesystem/littlefs/TESTS/filesystem/seek/main.cpp | 2 +- .../littlefs/TESTS/filesystem_recovery/resilience/main.cpp | 6 +++++- .../filesystem_recovery/resilience_functional/main.cpp | 6 +++++- .../TESTS/filesystem_recovery/wear_leveling/main.cpp | 6 +++++- .../littlefs/TESTS/filesystem_retarget/dirs/main.cpp | 2 +- .../littlefs/TESTS/filesystem_retarget/files/main.cpp | 2 +- .../TESTS/filesystem_retarget/interspersed/main.cpp | 2 +- .../littlefs/TESTS/filesystem_retarget/seek/main.cpp | 2 +- 11 files changed, 23 insertions(+), 11 deletions(-) diff --git a/features/filesystem/littlefs/TESTS/filesystem/dirs/main.cpp b/features/filesystem/littlefs/TESTS/filesystem/dirs/main.cpp index f98db60bf4..42f60f0d4e 100644 --- a/features/filesystem/littlefs/TESTS/filesystem/dirs/main.cpp +++ b/features/filesystem/littlefs/TESTS/filesystem/dirs/main.cpp @@ -52,7 +52,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_TIMEOUT -#define MBED_TEST_TIMEOUT 120 +#define MBED_TEST_TIMEOUT 480 #endif diff --git a/features/filesystem/littlefs/TESTS/filesystem/files/main.cpp b/features/filesystem/littlefs/TESTS/filesystem/files/main.cpp index 702cf68d80..658867bdb7 100644 --- a/features/filesystem/littlefs/TESTS/filesystem/files/main.cpp +++ b/features/filesystem/littlefs/TESTS/filesystem/files/main.cpp @@ -52,7 +52,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_TIMEOUT -#define MBED_TEST_TIMEOUT 120 +#define MBED_TEST_TIMEOUT 480 #endif diff --git a/features/filesystem/littlefs/TESTS/filesystem/interspersed/main.cpp b/features/filesystem/littlefs/TESTS/filesystem/interspersed/main.cpp index c1de477399..c16a6f5629 100644 --- a/features/filesystem/littlefs/TESTS/filesystem/interspersed/main.cpp +++ b/features/filesystem/littlefs/TESTS/filesystem/interspersed/main.cpp @@ -52,7 +52,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_TIMEOUT -#define MBED_TEST_TIMEOUT 120 +#define MBED_TEST_TIMEOUT 480 #endif diff --git a/features/filesystem/littlefs/TESTS/filesystem/seek/main.cpp b/features/filesystem/littlefs/TESTS/filesystem/seek/main.cpp index 7235df31eb..1f00ca476b 100644 --- a/features/filesystem/littlefs/TESTS/filesystem/seek/main.cpp +++ b/features/filesystem/littlefs/TESTS/filesystem/seek/main.cpp @@ -52,7 +52,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_TIMEOUT -#define MBED_TEST_TIMEOUT 120 +#define MBED_TEST_TIMEOUT 480 #endif diff --git a/features/filesystem/littlefs/TESTS/filesystem_recovery/resilience/main.cpp b/features/filesystem/littlefs/TESTS/filesystem_recovery/resilience/main.cpp index ac51f4be5f..b43f6bdab4 100644 --- a/features/filesystem/littlefs/TESTS/filesystem_recovery/resilience/main.cpp +++ b/features/filesystem/littlefs/TESTS/filesystem_recovery/resilience/main.cpp @@ -41,6 +41,10 @@ using namespace utest::v1; #define MBED_TEST_CYCLES 10 #endif +#ifndef MBED_TEST_TIMEOUT +#define MBED_TEST_TIMEOUT 480 +#endif + // declarations #define STRINGIZE(x) STRINGIZE2(x) #define STRINGIZE2(x) #x @@ -89,7 +93,7 @@ Case cases[] = { utest::v1::status_t greentea_test_setup(const size_t number_of_cases) { - GREENTEA_SETUP(120, "default_auto"); + GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); return greentea_test_setup_handler(number_of_cases); } diff --git a/features/filesystem/littlefs/TESTS/filesystem_recovery/resilience_functional/main.cpp b/features/filesystem/littlefs/TESTS/filesystem_recovery/resilience_functional/main.cpp index bfc319d074..f0ea4afe23 100644 --- a/features/filesystem/littlefs/TESTS/filesystem_recovery/resilience_functional/main.cpp +++ b/features/filesystem/littlefs/TESTS/filesystem_recovery/resilience_functional/main.cpp @@ -44,6 +44,10 @@ using namespace utest::v1; #define MBED_TEST_CYCLES 10 #endif +#ifndef MBED_TEST_TIMEOUT +#define MBED_TEST_TIMEOUT 480 +#endif + // declarations #define STRINGIZE(x) STRINGIZE2(x) #define STRINGIZE2(x) #x @@ -95,7 +99,7 @@ static cmd_status_t handle_command(const char *key, const char *value) int main() { - GREENTEA_SETUP(120, "unexpected_reset"); + GREENTEA_SETUP(MBED_TEST_TIMEOUT, "unexpected_reset"); static char _key[10 + 1] = {}; static char _value[128 + 1] = {}; diff --git a/features/filesystem/littlefs/TESTS/filesystem_recovery/wear_leveling/main.cpp b/features/filesystem/littlefs/TESTS/filesystem_recovery/wear_leveling/main.cpp index c382296d8c..85a9736205 100644 --- a/features/filesystem/littlefs/TESTS/filesystem_recovery/wear_leveling/main.cpp +++ b/features/filesystem/littlefs/TESTS/filesystem_recovery/wear_leveling/main.cpp @@ -42,6 +42,10 @@ using namespace utest::v1; #define MBED_TEST_ERASE_CYCLES 100 #endif +#ifndef MBED_TEST_TIMEOUT +#define MBED_TEST_TIMEOUT 480 +#endif + // declarations #define STRINGIZE(x) STRINGIZE2(x) #define STRINGIZE2(x) #x @@ -105,7 +109,7 @@ Case cases[] = { utest::v1::status_t greentea_test_setup(const size_t number_of_cases) { - GREENTEA_SETUP(120, "default_auto"); + GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); return greentea_test_setup_handler(number_of_cases); } diff --git a/features/filesystem/littlefs/TESTS/filesystem_retarget/dirs/main.cpp b/features/filesystem/littlefs/TESTS/filesystem_retarget/dirs/main.cpp index 1c4e62863b..31d271b121 100644 --- a/features/filesystem/littlefs/TESTS/filesystem_retarget/dirs/main.cpp +++ b/features/filesystem/littlefs/TESTS/filesystem_retarget/dirs/main.cpp @@ -52,7 +52,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_TIMEOUT -#define MBED_TEST_TIMEOUT 120 +#define MBED_TEST_TIMEOUT 480 #endif diff --git a/features/filesystem/littlefs/TESTS/filesystem_retarget/files/main.cpp b/features/filesystem/littlefs/TESTS/filesystem_retarget/files/main.cpp index df14557eb9..c9deae2d92 100644 --- a/features/filesystem/littlefs/TESTS/filesystem_retarget/files/main.cpp +++ b/features/filesystem/littlefs/TESTS/filesystem_retarget/files/main.cpp @@ -52,7 +52,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_TIMEOUT -#define MBED_TEST_TIMEOUT 120 +#define MBED_TEST_TIMEOUT 480 #endif diff --git a/features/filesystem/littlefs/TESTS/filesystem_retarget/interspersed/main.cpp b/features/filesystem/littlefs/TESTS/filesystem_retarget/interspersed/main.cpp index 250016ef67..f622c01a6a 100644 --- a/features/filesystem/littlefs/TESTS/filesystem_retarget/interspersed/main.cpp +++ b/features/filesystem/littlefs/TESTS/filesystem_retarget/interspersed/main.cpp @@ -52,7 +52,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_TIMEOUT -#define MBED_TEST_TIMEOUT 120 +#define MBED_TEST_TIMEOUT 480 #endif diff --git a/features/filesystem/littlefs/TESTS/filesystem_retarget/seek/main.cpp b/features/filesystem/littlefs/TESTS/filesystem_retarget/seek/main.cpp index c37383e76c..24ad870533 100644 --- a/features/filesystem/littlefs/TESTS/filesystem_retarget/seek/main.cpp +++ b/features/filesystem/littlefs/TESTS/filesystem_retarget/seek/main.cpp @@ -52,7 +52,7 @@ using namespace utest::v1; #endif #ifndef MBED_TEST_TIMEOUT -#define MBED_TEST_TIMEOUT 120 +#define MBED_TEST_TIMEOUT 480 #endif From b52575bc65f68cb002681b6366e7c1bfd0fa4268 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 27 Nov 2017 00:10:17 -0600 Subject: [PATCH 42/46] littlefs: Added checks for __CLZ instruction in IAR --- features/filesystem/littlefs/littlefs/lfs_util.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/filesystem/littlefs/littlefs/lfs_util.h b/features/filesystem/littlefs/littlefs/lfs_util.h index 4802dc912b..17cd5f2785 100644 --- a/features/filesystem/littlefs/littlefs/lfs_util.h +++ b/features/filesystem/littlefs/littlefs/lfs_util.h @@ -39,7 +39,7 @@ static inline uint32_t lfs_min(uint32_t a, uint32_t b) { static inline uint32_t lfs_ctz(uint32_t a) { #if defined(__GNUC__) || defined(__CC_ARM) return __builtin_ctz(a); -#elif defined(__ICCARM__) +#elif defined(__ICCARM__) && defined(__CLZ) return __CLZ(__RBIT(a)); #else uint32_t r = 32; @@ -57,7 +57,7 @@ static inline uint32_t lfs_ctz(uint32_t a) { static inline uint32_t lfs_npw2(uint32_t a) { #if defined(__GNUC__) || defined(__CC_ARM) return 32 - __builtin_clz(a-1); -#elif defined(__ICCARM__) +#elif defined(__ICCARM__) && defined(__CLZ) return 32 - __CLZ(a-1); #else uint32_t r = 0; From 47684d89a502db405f18113d8557dd66a7cea01a Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 27 Nov 2017 10:30:00 -0600 Subject: [PATCH 43/46] Added test config for simulated block devices Not all devices have enough heap to fit a simulated heap block device, however using a simulated heap block device is preferred if available (reduced flash wear, faster testing). Added MBED_TEST_SIM_BLOCKDEVICE for tests that only need a simulated block device (wear_leveling + resilience), and added support for targets that are known to have enough heap. --- .../filesystem_recovery/resilience/main.cpp | 13 ++++---- .../wear_leveling/main.cpp | 13 ++++---- tools/test_configs/HeapBlockDevice.json | 9 ++++++ .../HeapBlockDeviceAndEthernetInterface.json | 32 +++++++++++++++++++ tools/test_configs/OdinInterface.json | 5 +++ .../test_configs/Odin_EthernetInterface.json | 5 +++ tools/test_configs/RealtekInterface.json | 5 +++ tools/test_configs/config_paths.json | 4 ++- tools/test_configs/target_configs.json | 8 +++++ 9 files changed, 79 insertions(+), 15 deletions(-) create mode 100644 tools/test_configs/HeapBlockDevice.json create mode 100644 tools/test_configs/HeapBlockDeviceAndEthernetInterface.json diff --git a/features/filesystem/littlefs/TESTS/filesystem_recovery/resilience/main.cpp b/features/filesystem/littlefs/TESTS/filesystem_recovery/resilience/main.cpp index b43f6bdab4..c3419afa98 100644 --- a/features/filesystem/littlefs/TESTS/filesystem_recovery/resilience/main.cpp +++ b/features/filesystem/littlefs/TESTS/filesystem_recovery/resilience/main.cpp @@ -24,13 +24,12 @@ using namespace utest::v1; // test configuration -#ifndef MBED_TEST_BLOCKDEVICE -#define MBED_TEST_BLOCKDEVICE HeapBlockDevice -#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd(MBED_TEST_BLOCK_COUNT*512, 1, 1, 512) +#ifndef MBED_TEST_SIM_BLOCKDEVICE +#error [NOT_SUPPORTED] Simulation block device required for resilience tests #endif -#ifndef MBED_TEST_BLOCKDEVICE_DECL -#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd +#ifndef MBED_TEST_SIM_BLOCKDEVICE_DECL +#define MBED_TEST_SIM_BLOCKDEVICE_DECL MBED_TEST_SIM_BLOCKDEVICE bd(MBED_TEST_BLOCK_COUNT*512, 1, 1, 512) #endif #ifndef MBED_TEST_BLOCK_COUNT @@ -50,7 +49,7 @@ using namespace utest::v1; #define STRINGIZE2(x) #x #define INCLUDE(x) STRINGIZE(x.h) -#include INCLUDE(MBED_TEST_BLOCKDEVICE) +#include INCLUDE(MBED_TEST_SIM_BLOCKDEVICE) /** @@ -62,7 +61,7 @@ using namespace utest::v1; */ void test_resilience() { - MBED_TEST_BLOCKDEVICE_DECL; + MBED_TEST_SIM_BLOCKDEVICE_DECL; // bring up to get block size bd.init(); diff --git a/features/filesystem/littlefs/TESTS/filesystem_recovery/wear_leveling/main.cpp b/features/filesystem/littlefs/TESTS/filesystem_recovery/wear_leveling/main.cpp index 85a9736205..95efe3eff8 100644 --- a/features/filesystem/littlefs/TESTS/filesystem_recovery/wear_leveling/main.cpp +++ b/features/filesystem/littlefs/TESTS/filesystem_recovery/wear_leveling/main.cpp @@ -25,13 +25,12 @@ using namespace utest::v1; // test configuration -#ifndef MBED_TEST_BLOCKDEVICE -#define MBED_TEST_BLOCKDEVICE HeapBlockDevice -#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd(MBED_TEST_BLOCK_COUNT*512, 1, 1, 512) +#ifndef MBED_TEST_SIM_BLOCKDEVICE +#error [NOT_SUPPORTED] Simulation block device required for wear leveling tests #endif -#ifndef MBED_TEST_BLOCKDEVICE_DECL -#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd +#ifndef MBED_TEST_SIM_BLOCKDEVICE_DECL +#define MBED_TEST_SIM_BLOCKDEVICE_DECL MBED_TEST_SIM_BLOCKDEVICE bd(MBED_TEST_BLOCK_COUNT*512, 1, 1, 512) #endif #ifndef MBED_TEST_BLOCK_COUNT @@ -51,12 +50,12 @@ using namespace utest::v1; #define STRINGIZE2(x) #x #define INCLUDE(x) STRINGIZE(x.h) -#include INCLUDE(MBED_TEST_BLOCKDEVICE) +#include INCLUDE(MBED_TEST_SIM_BLOCKDEVICE) static uint32_t test_wear_leveling_size(uint32_t block_count) { - MBED_TEST_BLOCKDEVICE_DECL; + MBED_TEST_SIM_BLOCKDEVICE_DECL; // bring up to get block size bd.init(); diff --git a/tools/test_configs/HeapBlockDevice.json b/tools/test_configs/HeapBlockDevice.json new file mode 100644 index 0000000000..047a62b630 --- /dev/null +++ b/tools/test_configs/HeapBlockDevice.json @@ -0,0 +1,9 @@ +{ + "config": { + "sim-blockdevice": { + "help": "Simulated block device, requires sufficient heap", + "macro_name": "MBED_TEST_SIM_BLOCKDEVICE", + "value": "HeapBlockDevice" + } + } +} diff --git a/tools/test_configs/HeapBlockDeviceAndEthernetInterface.json b/tools/test_configs/HeapBlockDeviceAndEthernetInterface.json new file mode 100644 index 0000000000..f445e78041 --- /dev/null +++ b/tools/test_configs/HeapBlockDeviceAndEthernetInterface.json @@ -0,0 +1,32 @@ +{ + "config": { + "header-file": { + "help" : "String for including your driver header file", + "value" : "\"EthernetInterface.h\"" + }, + "object-construction" : { + "value" : "new EthernetInterface()" + }, + "connect-statement" : { + "help" : "Must use 'net' variable name", + "value" : "((EthernetInterface *)net)->connect()" + }, + "echo-server-addr" : { + "help" : "IP address of echo server", + "value" : "\"195.34.89.241\"" + }, + "echo-server-port" : { + "help" : "Port of echo server", + "value" : "7" + }, + "tcp-echo-prefix" : { + "help" : "Some servers send a prefix before echoed message", + "value" : "\"u-blox AG TCP/UDP test service\\n\"" + }, + "sim-blockdevice": { + "help": "Simulated block device, requires sufficient heap", + "macro_name": "MBED_TEST_SIM_BLOCKDEVICE", + "value": "HeapBlockDevice" + } + } +} diff --git a/tools/test_configs/OdinInterface.json b/tools/test_configs/OdinInterface.json index 3ebac83f0e..00d24bc0f6 100644 --- a/tools/test_configs/OdinInterface.json +++ b/tools/test_configs/OdinInterface.json @@ -22,6 +22,11 @@ "tcp-echo-prefix" : { "help" : "Some servers send a prefix before echoed message", "value" : "\"u-blox AG TCP/UDP test service\\n\"" + }, + "sim-blockdevice": { + "help": "Simulated block device, requires sufficient heap", + "macro_name": "MBED_TEST_SIM_BLOCKDEVICE", + "value": "HeapBlockDevice" } } } diff --git a/tools/test_configs/Odin_EthernetInterface.json b/tools/test_configs/Odin_EthernetInterface.json index 24f48e212c..97f61b680e 100644 --- a/tools/test_configs/Odin_EthernetInterface.json +++ b/tools/test_configs/Odin_EthernetInterface.json @@ -22,6 +22,11 @@ "tcp-echo-prefix" : { "help" : "Some servers send a prefix before echoed message", "value" : "\"u-blox AG TCP/UDP test service\\n\"" + }, + "sim-blockdevice": { + "help": "Simulated block device, requires sufficient heap", + "macro_name": "MBED_TEST_SIM_BLOCKDEVICE", + "value": "HeapBlockDevice" } }, "target_overrides": { diff --git a/tools/test_configs/RealtekInterface.json b/tools/test_configs/RealtekInterface.json index ad4a9ef31b..2191a4bf10 100644 --- a/tools/test_configs/RealtekInterface.json +++ b/tools/test_configs/RealtekInterface.json @@ -22,6 +22,11 @@ "tcp-echo-prefix" : { "help" : "Some servers send a prefix before echoed message", "value" : "\"Realtek Ameba TCP/UDP test service\\n\"" + }, + "sim-blockdevice": { + "help": "Simulated block device, requires sufficient heap", + "macro_name": "MBED_TEST_SIM_BLOCKDEVICE", + "value": "HeapBlockDevice" } } } diff --git a/tools/test_configs/config_paths.json b/tools/test_configs/config_paths.json index 098c9442c3..3b85ad608e 100644 --- a/tools/test_configs/config_paths.json +++ b/tools/test_configs/config_paths.json @@ -1,6 +1,8 @@ { "ETHERNET" : "EthernetInterface.json", + "HEAPBLOCKDEVICE": "HeapBlockDevice.json", + "HEAPBLOCKDEVICE_AND_ETHERNET": "HeapBlockDeviceAndEthernetInterface.json", "ODIN_WIFI" : "OdinInterface.json", "ODIN_ETHERNET" : "Odin_EthernetInterface.json", - "REALTEK_WIFI" : "RealtekInterface.json" + "REALTEK_WIFI" : "RealtekInterface.json" } diff --git a/tools/test_configs/target_configs.json b/tools/test_configs/target_configs.json index 528889622a..a48e6e16fe 100644 --- a/tools/test_configs/target_configs.json +++ b/tools/test_configs/target_configs.json @@ -6,5 +6,13 @@ "REALTEK_RTL8195AM": { "default_test_configuration": "NONE", "test_configurations": ["REALTEK_WIFI"] + }, + "K64F": { + "default_test_configuration": "HEAPBLOCKDEVICE_AND_ETHERNET", + "test_configurations": ["HEAPBLOCKDEVICE_AND_ETHERNET"] + }, + "NUCLEO_F429ZI": { + "default_test_configuration": "HEAPBLOCKDEVICE_AND_ETHERNET", + "test_configurations": ["HEAPBLOCKDEVICE_AND_ETHERNET"] } } From ff25681a21296421ddeb2afd23faa1691f170c46 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 27 Nov 2017 19:49:35 -0600 Subject: [PATCH 44/46] Separated out logical Travis jobs Combination of mbed 2 builds (~40 minutes), littlefs testing (~15 minutes), and miscellaneous testing pushed the current CI over Travis's limit of 1 hour per job. However, by using Travis's matrix includes, we can spin up different jobs for the various logical components being tested. --- .travis.yml | 233 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 154 insertions(+), 79 deletions(-) diff --git a/.travis.yml b/.travis.yml index 087cdbebc6..fdb5520c3a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,88 +1,163 @@ -python: - - "2.7" -script: - # Assert that the Doxygen build produced no warnings. - # The strange command below asserts that the Doxygen command had an - # output of zero length - - | - doxygen doxyfile_options 2>&1 | tee BUILD/doxygen.out && [ ! -s BUILD/doxygen.out ] - # Assert that all binary libraries are named correctly - # The strange command below asserts that there are exactly 0 libraries that do - # not start with lib - - | - find "(" -name "*.a" -or -name "*.ar" ")" -and -not -name "lib*" | tee BUILD/badlibs | sed -e "s/^/Bad library name found: /" && [ ! -s BUILD/badlibs ] - # Assert that all assebler files are named correctly - # The strange command below asserts that there are exactly 0 libraries that do - # end with .s - - | - find -name "*.s" | tee BUILD/badasm | sed -e "s/^/Bad Assembler file name found: /" && [ ! -s BUILD/badasm ] - # Run local testing on tools - # Note: These take ~40 minutes to run - - PYTHONPATH=. coverage run -a -m pytest tools/test - - python2 tools/test/pylint.py - - coverage run -a tools/project.py -S | sed -n '/^Total/p' - - python2 -u tools/build_travis.py | sed -n '/^Executing/p' - - coverage html - # Run local event queue tests - - make -C events/equeue test - # Run local littlefs tests - - CFLAGS="-Wno-format" make -Cfeatures/filesystem/littlefs/littlefs test QUIET=1 - # Run local littlefs tests with set of variations - - CFLAGS="-Wno-format -DLFS_READ_SIZE=64 -DLFS_PROG_SIZE=64" make -Cfeatures/filesystem/littlefs/littlefs test QUIET=1 - - CFLAGS="-Wno-format -DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make -Cfeatures/filesystem/littlefs/littlefs test QUIET=1 - - CFLAGS="-Wno-format -DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make -Cfeatures/filesystem/littlefs/littlefs test QUIET=1 - - CFLAGS="-Wno-format -DLFS_BLOCK_COUNT=1023" make -Cfeatures/filesystem/littlefs/littlefs test QUIET=1 - - CFLAGS="-Wno-format -DLFS_LOOKAHEAD=2048" make -Cfeatures/filesystem/littlefs/littlefs test QUIET=1 - # Self-hosting littlefs fuzz test with littlefs-fuse - - make -C littlefs-fuse - - littlefs-fuse/lfs --format /dev/loop0 - - littlefs-fuse/lfs /dev/loop0 MOUNT - - ls MOUNT - - mkdir MOUNT/littlefs - - cp -r $(git ls-tree --name-only HEAD features/filesystem/littlefs/littlefs/) MOUNT/littlefs - - ls MOUNT/littlefs - - CFLAGS="-Wno-format" make -CMOUNT/littlefs -B test_dirs QUIET=1 - -after_success: - # Coverage for tools - - coveralls +env: + global: + - > + STATUS=$'curl -so/dev/null --user $MBED_BOT --request POST + https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} + --data @- << DATA\n{ + "state": "$0", + "description": "$1", + "context": "travis-ci/$NAME", + "target_url": "https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$TRAVIS_JOB_ID" + }\nDATA' before_install: + - bash -c "$STATUS" pending "Local $NAME testing is in progress" + # Make sure pipefail + - set -o pipefail # Setup ppa to make sure arm-none-eabi-gcc is correct version - sudo add-apt-repository -y ppa:team-gcc-arm-embedded/ppa - sudo add-apt-repository -y ppa:libreoffice/libreoffice-4-2 - sudo apt-get update -qq -install: - # Install dependencies - - sudo apt-get install -qq gcc-arm-embedded doxygen libfuse-dev - - pip install --user -r requirements.txt - - pip install --user pytest - - pip install --user pylint - - pip install --user hypothesis - - pip install --user mock - - pip install --user coverage - - pip install --user coveralls - # Print versions we use - - arm-none-eabi-gcc --version - - python --version - - doxygen --version - - gcc --version - - fusermount --version +after_success: + - bash -c "$STATUS" success "Local $NAME testing has passed" + +after_failure: + - bash -c "$STATUS" failure "Local $NAME testing has failed" + +matrix: + include: + - python: '2.7' + env: + - NAME=tools + install: + # Install dependencies + - sudo apt-get install gcc-arm-embedded doxygen + - pip install --user -r requirements.txt + - pip install --user pytest + - pip install --user pylint + - pip install --user hypothesis + - pip install --user mock + - pip install --user coverage + - pip install --user coveralls + # Print versions we use + - arm-none-eabi-gcc --version + - python --version + - doxygen --version + before_script: + # Create BUILD directory for tests + - mkdir BUILD + script: + # Assert that the Doxygen build produced no warnings. + # The strange command below asserts that the Doxygen command had an + # output of zero length + - | + doxygen doxyfile_options 2>&1 | tee BUILD/doxygen.out && [ ! -s BUILD/doxygen.out ] + # Assert that all binary libraries are named correctly + # The strange command below asserts that there are exactly 0 libraries that do + # not start with lib + - | + find "(" -name "*.a" -or -name "*.ar" ")" -and -not -name "lib*" | tee BUILD/badlibs | sed -e "s/^/Bad library name found: /" && [ ! -s BUILD/badlibs ] + # Assert that all assebler files are named correctly + # The strange command below asserts that there are exactly 0 libraries that do + # end with .s + - | + find -name "*.s" | tee BUILD/badasm | sed -e "s/^/Bad Assembler file name found: /" && [ ! -s BUILD/badasm ] + # Run local testing on tools + # Note: These take ~40 minutes to run + - PYTHONPATH=. coverage run -a -m pytest tools/test + - python2 tools/test/pylint.py + - coverage run -a tools/project.py -S | sed -n '/^Total/p' + # - python2 -u tools/build_travis.py | sed -n '/^Executing/p' + - coverage html + after_success: + # Coverage for tools + - coveralls + # Report success since we have overridden default behaviour + - bash -c "$STATUS" success "Local $NAME testing has passed" + + - python: '2.7' + env: + - NAME=events + - EVENTS=events + install: + # Install dependencies + - sudo apt-get install gcc-arm-embedded + - pip install --user -r requirements.txt + # Print versions we use + - arm-none-eabi-gcc --version + - gcc --version + - python --version + script: + # Check that example compiles + - sed -n '/``` cpp/,/```/{/```$/Q;/```/d;p;}' $EVENTS/README.md > main.cpp + - python tools/make.py -t GCC_ARM -m K64F --source=. --build=BUILD/K64F/GCC_ARM -j0 + # Run local equeue tests + - make -C $EVENTS/equeue test + + - python: '2.7' + env: + - NAME=littlefs + - LITTLEFS=features/filesystem/littlefs + install: + # Install dependencies + - sudo apt-get install gcc-arm-embedded fuse libfuse-dev + - pip install --user -r requirements.txt + - git clone https://github.com/armmbed/spiflash-driver.git + # Print versions + - arm-none-eabi-gcc --version + - gcc --version + - python --version + - fusermount --version + before_script: + # Setup and patch littlefs-fuse + - git clone https://github.com/geky/littlefs-fuse littlefs_fuse + - echo '*' > littlefs_fuse/.mbedignore + - rm -rf littlefs_fuse/littlefs/* + - cp -r $(git ls-tree --name-only HEAD $LITTLEFS/littlefs/) littlefs_fuse/littlefs + # Create file-backed disk + - mkdir MOUNT + - sudo chmod a+rw /dev/loop0 + - dd if=/dev/zero bs=512 count=2048 of=DISK + - losetup /dev/loop0 DISK + script: + # Check that example compiles + - sed -n '/``` c++/,/```/{/```/d;p;}' $LITTLEFS/README.md > main.cpp + - python tools/make.py -t GCC_ARM -m K82F --source=. --build=BUILD/K82F/GCC_ARM -j0 + # Run local littlefs tests + - CFLAGS="-Wno-format" make -C$LITTLEFS/littlefs test QUIET=1 + # Run local littlefs tests with set of variations + - CFLAGS="-Wno-format -DLFS_READ_SIZE=64 -DLFS_PROG_SIZE=64" make -C$LITTLEFS/littlefs test QUIET=1 + - CFLAGS="-Wno-format -DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make -C$LITTLEFS/littlefs test QUIET=1 + - CFLAGS="-Wno-format -DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make -C$LITTLEFS/littlefs test QUIET=1 + - CFLAGS="-Wno-format -DLFS_BLOCK_COUNT=1023" make -C$LITTLEFS/littlefs test QUIET=1 + - CFLAGS="-Wno-format -DLFS_LOOKAHEAD=2048" make -C$LITTLEFS/littlefs test QUIET=1 + # Self-hosting littlefs fuzz test with littlefs-fuse + - make -Clittlefs_fuse + - littlefs_fuse/lfs --format /dev/loop0 + - littlefs_fuse/lfs /dev/loop0 MOUNT + - ls MOUNT + - mkdir MOUNT/littlefs + - cp -r $(git ls-tree --name-only HEAD $LITTLEFS/littlefs/) MOUNT/littlefs + - ls MOUNT/littlefs + - CFLAGS="-Wno-format" make -CMOUNT/littlefs -B test_dirs QUIET=1 + + - python: '2.7' + env: + - NAME=mbed2 + install: + # Install dependencies + - sudo apt-get install gcc-arm-embedded + - pip install --user -r requirements.txt + # Print versions we use + - arm-none-eabi-gcc --version + - python --version + before_script: + # Create BUILD directory for tests + - mkdir BUILD + script: + # Run local mbed 2 testing + # Note: These take ~40 minutes to run + - python2 -u tools/build_travis.py | sed -n '/^Executing/p' -before_script: - # Create BUILD directory for tests - - mkdir BUILD - # Make sure pipefail - - set -o pipefail - # Setup and patch littlefs-fuse - - git clone https://github.com/geky/littlefs-fuse - - echo '*' > littlefs-fuse/.mbedignore - - rm -rf littlefs-fuse/littlefs/* - - cp -r $(git ls-tree --name-only HEAD features/filesystem/littlefs/littlefs/) littlefs-fuse/littlefs - # Create file-backed disk - - mkdir MOUNT - - sudo chmod a+rw /dev/loop0 - - dd if=/dev/zero bs=512 count=2048 of=DISK - - losetup /dev/loop0 DISK From 634fcf0cc473d1ee008fa5672d63b431d96fe8aa Mon Sep 17 00:00:00 2001 From: Amanda Butler Date: Tue, 28 Nov 2017 13:06:24 -0600 Subject: [PATCH 45/46] Copy edit littlefs ExhaustibleBlockDevice.h - Fix typos for consistent spelling. ObservingBlockDevice.h - Fix typos for consistent spelling. ReadOnlyBlockDevice.h - Fix typos for consistent spelling. README.md - Fix typos, mostly for branding. DESIGN.md - Make minor changes for consistent spelling and precise language. SPEC.md - Make minor changes for consistent spelling and precise language. README.md - Make minor changes for consistent spelling and precise language. --- .../filesystem/bd/ExhaustibleBlockDevice.h | 8 +- features/filesystem/bd/ObservingBlockDevice.h | 8 +- features/filesystem/bd/ReadOnlyBlockDevice.h | 8 +- features/filesystem/littlefs/README.md | 20 +- .../filesystem/littlefs/littlefs/DESIGN.md | 187 +++++++++--------- .../filesystem/littlefs/littlefs/README.md | 34 ++-- features/filesystem/littlefs/littlefs/SPEC.md | 62 +++--- 7 files changed, 163 insertions(+), 164 deletions(-) diff --git a/features/filesystem/bd/ExhaustibleBlockDevice.h b/features/filesystem/bd/ExhaustibleBlockDevice.h index 3c1e83d0c6..8fc3918317 100644 --- a/features/filesystem/bd/ExhaustibleBlockDevice.h +++ b/features/filesystem/bd/ExhaustibleBlockDevice.h @@ -106,15 +106,15 @@ public: */ virtual bd_size_t get_read_size() const; - /** Get the size of a programable block + /** Get the size of a programmable block * - * @return Size of a programable block in bytes + * @return Size of a programmable block in bytes */ virtual bd_size_t get_program_size() const; - /** Get the size of a eraseable block + /** Get the size of a erasable block * - * @return Size of a eraseable block in bytes + * @return Size of a erasable block in bytes */ virtual bd_size_t get_erase_size() const; diff --git a/features/filesystem/bd/ObservingBlockDevice.h b/features/filesystem/bd/ObservingBlockDevice.h index a8b4a4a426..1e8c1942cb 100644 --- a/features/filesystem/bd/ObservingBlockDevice.h +++ b/features/filesystem/bd/ObservingBlockDevice.h @@ -92,15 +92,15 @@ public: */ virtual bd_size_t get_read_size() const; - /** Get the size of a programable block + /** Get the size of a programmable block * - * @return Size of a programable block in bytes + * @return Size of a programmable block in bytes */ virtual bd_size_t get_program_size() const; - /** Get the size of a eraseable block + /** Get the size of a erasable block * - * @return Size of a eraseable block in bytes + * @return Size of a erasable block in bytes */ virtual bd_size_t get_erase_size() const; diff --git a/features/filesystem/bd/ReadOnlyBlockDevice.h b/features/filesystem/bd/ReadOnlyBlockDevice.h index 2120716d95..23c8f40e8d 100644 --- a/features/filesystem/bd/ReadOnlyBlockDevice.h +++ b/features/filesystem/bd/ReadOnlyBlockDevice.h @@ -85,15 +85,15 @@ public: */ virtual bd_size_t get_read_size() const; - /** Get the size of a programable block + /** Get the size of a programmable block * - * @return Size of a programable block in bytes + * @return Size of a programmable block in bytes */ virtual bd_size_t get_program_size() const; - /** Get the size of a eraseable block + /** Get the size of a erasable block * - * @return Size of a eraseable block in bytes + * @return Size of a erasable block in bytes */ virtual bd_size_t get_erase_size() const; diff --git a/features/filesystem/littlefs/README.md b/features/filesystem/littlefs/README.md index 0db3d1f562..46660a2238 100644 --- a/features/filesystem/littlefs/README.md +++ b/features/filesystem/littlefs/README.md @@ -1,4 +1,4 @@ -## mbed wrapper for the little filesystem +## Mbed wrapper for the little filesystem This is the mbed wrapper for [littlefs](https://github.com/geky/littlefs), a little fail-safe filesystem designed for embedded systems. @@ -13,20 +13,20 @@ a little fail-safe filesystem designed for embedded systems. ``` **Bounded RAM/ROM** - The littlefs is designed to work with a limited amount -of memory. Recursion is avoided and dynamic memory is limited to configurable +of memory. Recursion is avoided, and dynamic memory is limited to configurable buffers that can be provided statically. **Power-loss resilient** - The littlefs is designed for systems that may have -random power failures. The littlefs has strong copy-on-write guaruntees and +random power failures. The littlefs has strong copy-on-write guarantees, and storage on disk is always kept in a valid state. -**Wear leveling** - Since the most common form of embedded storage is erodible +**Wear leveling** - Because the most common form of embedded storage is erodible flash memories, littlefs provides a form of dynamic wear leveling for systems -that can not fit a full flash translation layer. +that cannot fit a full flash translation layer. ## Usage -If you are already using a filesystem in mbed, adopting the littlefs should +If you are already using a filesystem in Mbed, adopting the littlefs should just require a name change to use the [LittleFileSystem](LittleFileSystem.h) class. @@ -82,11 +82,11 @@ int main() { ## Reference material [DESIGN.md](littlefs/DESIGN.md) - DESIGN.md contains a fully detailed dive into -how littlefs actually works. I would encourage you to read it since the +how littlefs actually works. We encourage you to read it because the solutions and tradeoffs at work here are quite interesting. [SPEC.md](littlefs/SPEC.md) - SPEC.md contains the on-disk specification of -littlefs with all the nitty-gritty details. Can be useful for developing +littlefs with all the nitty-gritty details. This can be useful for developing tooling. ## Related projects @@ -96,9 +96,9 @@ currently lives. [littlefs-fuse](https://github.com/geky/littlefs-fuse) - A [FUSE](https://github.com/libfuse/libfuse) wrapper for littlefs. The project allows you to mount littlefs directly in a -Linux machine. Can be useful for debugging littlefs if you have an SD card +Linux machine. This can be useful for debugging littlefs if you have an SD card handy. -[littlefs-js](https://github.com/geky/littlefs-js) - A javascript wrapper for +[littlefs-js](https://github.com/geky/littlefs-js) - A JavaScript wrapper for littlefs. I'm not sure why you would want this, but it is handy for demos. You can see it in action [here](http://littlefs.geky.net/demo.html). diff --git a/features/filesystem/littlefs/littlefs/DESIGN.md b/features/filesystem/littlefs/littlefs/DESIGN.md index f2a8498443..75487cacb8 100644 --- a/features/filesystem/littlefs/littlefs/DESIGN.md +++ b/features/filesystem/littlefs/littlefs/DESIGN.md @@ -23,7 +23,7 @@ often paired with SPI NOR flash chips with about 4MB of flash storage. Flash itself is a very interesting piece of technology with quite a bit of nuance. Unlike most other forms of storage, writing to flash requires two operations: erasing and programming. The programming operation is relatively -cheap, and can be very granular. For NOR flash specifically, byte-level +cheap and can be very granular. For NOR flash specifically, byte-level programs are quite common. Erasing, however, requires an expensive operation that forces the state of large blocks of memory to reset in a destructive reaction that gives flash its name. The [Wikipedia entry](https://en.wikipedia.org/wiki/Flash_memory) @@ -35,7 +35,7 @@ to three strong requirements: 1. **Power-loss resilient** - This is the main goal of the littlefs and the focus of this project. Embedded systems are usually designed without a shutdown routine and a notable lack of user interface for recovery, so - filesystems targeting embedded systems must be prepared to lose power an + filesystems targeting embedded systems must be prepared to lose power at any given time. Despite this state of things, there are very few embedded filesystems that @@ -67,7 +67,7 @@ to three strong requirements: using only a bounded amount of RAM and ROM. That is, no matter what is written to the filesystem, and no matter how large the underlying storage is, the littlefs will always use the same amount of RAM and ROM. This - presents a very unique challenge, and makes presumably simple operations, + presents a very unique challenge and makes presumably simple operations, such as iterating through the directory tree, surprisingly difficult. ## Existing designs? @@ -86,12 +86,12 @@ One of the most popular designs for flash filesystems is called the [logging filesystem](https://en.wikipedia.org/wiki/Log-structured_file_system). The flash filesystems [jffs](https://en.wikipedia.org/wiki/JFFS) and [yaffs](https://en.wikipedia.org/wiki/YAFFS) are good examples. In -logging filesystem, data is not store in a data structure on disk, but instead +logging filesystem, data is not stored in a data structure on disk, but instead the changes to the files are stored on disk. This has several neat advantages, such as the fact that the data is written in a cyclic log format naturally -wear levels as a side effect. And, with a bit of error detection, the entire +levels wear as a side effect. And, with a bit of error detection, the entire filesystem can easily be designed to be resilient to power loss. The -journalling component of most modern day filesystems is actually a reduced +journaling component of most modern day filesystems is actually a reduced form of a logging filesystem. However, logging filesystems have a difficulty scaling as the size of storage increases. And most filesystems compensate by caching large parts of the filesystem in RAM, a strategy that is unavailable @@ -114,7 +114,7 @@ pairs, so that at any time there is always a backup containing the previous state of the metadata. Consider a small example where each metadata pair has a revision count, -a number as data, and the xor of the block as a quick checksum. If +a number as data and the xor of the block as a quick checksum. If we update the data to a value of 9, and then to a value of 5, here is what the pair of blocks may look like after each update: ``` @@ -130,7 +130,7 @@ what the pair of blocks may look like after each update: After each update, we can find the most up to date value of data by looking at the revision count. -Now consider what the blocks may look like if we suddenly loss power while +Now consider what the blocks may look like if we suddenly lose power while changing the value of data to 5: ``` block 1 block 2 block 1 block 2 block 1 block 2 @@ -145,7 +145,7 @@ changing the value of data to 5: In this case, block 1 was partially written with a new revision count, but the littlefs hadn't made it to updating the value of data. However, if we -check our checksum we notice that block 1 was corrupted. So we fall back to +check our checksum, we notice that block 1 was corrupted. So we fall back to block 2 and use the value 9. Using this concept, the littlefs is able to update metadata blocks atomically. @@ -154,14 +154,14 @@ arithmetic to handle revision count overflow, but the basic concept is the same. These metadata pairs define the backbone of the littlefs, and the rest of the filesystem is built on top of these atomic updates. -## Non-meta data +## Nonmeta data Now, the metadata pairs do come with some drawbacks. Most notably, each pair requires two blocks for each block of data. I'm sure users would be very -unhappy if their storage was suddenly cut in half! Instead of storing +unhappy if their storage were suddenly cut in half! Instead of storing everything in these metadata blocks, the littlefs uses a COW data structure -for files which is in turn pointed to by a metadata block. When -we update a file, we create a copies of any blocks that are modified until +for files, which is, in turn, pointed to by a metadata block. When +we update a file, we create copies of any blocks that are modified until the metadata blocks are updated with the new copy. Once the metadata block points to the new copy, we deallocate the old blocks that are no longer in use. @@ -184,8 +184,8 @@ Here is what updating a one-block file may look like: update data in file update metadata pair ``` -It doesn't matter if we lose power while writing block 5 with the new data, -since the old data remains unmodified in block 4. This example also +It doesn't matter if we lose power while writing block 5 with the new data +because the old data remains unmodified in block 4. This example also highlights how the atomic updates of the metadata blocks provide a synchronization barrier for the rest of the littlefs. @@ -206,10 +206,10 @@ files in filesystems. Of these, the littlefs uses a rather unique [COW](https:// data structure that allows the filesystem to reuse unmodified parts of the file without additional metadata pairs. -First lets consider storing files in a simple linked-list. What happens when -append a block? We have to change the last block in the linked-list to point -to this new block, which means we have to copy out the last block, and change -the second-to-last block, and then the third-to-last, and so on until we've +First, let's consider storing files in a simple linked-list. What happens when +we append a block? We have to change the last block in the linked-list to point +to this new block, which means we have to copy out the last block and change +the second-to-last block and then the third-to-last and so on until we've copied out the entire file. ``` @@ -221,12 +221,12 @@ Exhibit A: A linked-list '--------' '--------' '--------' '--------' '--------' '--------' ``` -To get around this, the littlefs, at its heart, stores files backwards. Each +To get around this, the littlefs, at its heart, stores files backward. Each block points to its predecessor, with the first block containing no pointers. -If you think about for a while, it starts to make a bit of sense. Appending -blocks just point to their predecessor and no other blocks need to be updated. +If you think about it for a while, it starts to make a bit of sense. Appending +blocks just point to their predecessor, and no other blocks need to be updated. If we update a block in the middle, we will need to copy out the blocks that -follow, but can reuse the blocks before the modified block. Since most file +follow but can reuse the blocks before the modified block. Because most file operations either reset the file each write or append to files, this design avoids copying the file in the most common cases. @@ -239,7 +239,7 @@ Exhibit B: A backwards linked-list '--------' '--------' '--------' '--------' '--------' '--------' ``` -However, a backwards linked-list does come with a rather glaring problem. +However, a backward linked-list does come with a rather glaring problem. Iterating over a file _in order_ has a runtime of O(n^2). Gah! A quadratic runtime to just _read_ a file? That's awful. Keep in mind reading files are usually the most common filesystem operation. @@ -257,7 +257,7 @@ instruction, which allows us to calculate the power-of-two factors efficiently. For a given block n, the block contains ctz(n)+1 pointers. ``` -Exhibit C: A backwards CTZ skip-list +Exhibit C: A backward CTZ skip-list .--------. .--------. .--------. .--------. .--------. .--------. | data 0 |<-| data 1 |<-| data 2 |<-| data 3 |<-| data 4 |<-| data 5 | | |<-| |--| |<-| |--| | | | @@ -268,7 +268,7 @@ Exhibit C: A backwards CTZ skip-list The additional pointers allow us to navigate the data-structure on disk much more efficiently than in a single linked-list. -Taking exhibit C for example, here is the path from data block 5 to data +Taking exhibit C, for example, here is the path from data block 5 to data block 1. You can see how data block 3 was completely skipped: ``` .--------. .--------. .--------. .--------. .--------. .--------. @@ -278,7 +278,7 @@ block 1. You can see how data block 3 was completely skipped: '--------' '--------' '--------' '--------' '--------' '--------' ``` -The path to data block 0 is even more quick, requiring only two jumps: +The path to data block 0 is even quicker, requiring only two jumps: ``` .--------. .--------. .--------. .--------. .--------. .--------. | data 0 | | data 1 | | data 2 | | data 3 | | data 4 |<-| data 5 | @@ -291,13 +291,13 @@ We can find the runtime complexity by looking at the path to any block from the block containing the most pointers. Every step along the path divides the search space for the block in half. This gives us a runtime of O(logn). To get to the block with the most pointers, we can perform the same steps -backwards, which puts the runtime at O(2logn) = O(logn). The interesting +backward, which puts the runtime at O(2logn) = O(logn). The interesting part about this data structure is that this optimal path occurs naturally if we greedily choose the pointer that covers the most distance without passing our target block. So now we have a representation of files that can be appended trivially with -a runtime of O(1), and can be read with a worst case runtime of O(nlogn). +a runtime of O(1) and can be read with a worst case runtime of O(nlogn). Given that the the runtime is also divided by the amount of data we can store in a block, this is pretty reasonable. @@ -317,9 +317,9 @@ per block. ![overhead_per_block](https://latex.codecogs.com/svg.latex?%5Clim_%7Bn%5Cto%5Cinfty%7D%5Cfrac%7B1%7D%7Bn%7D%5Csum_%7Bi%3D0%7D%5E%7Bn%7D%5Cleft%28%5Ctext%7Bctz%7D%28i%29+1%5Cright%29%20%3D%20%5Csum_%7Bi%3D0%7D%5Cfrac%7B1%7D%7B2%5Ei%7D%20%3D%202) Finding the maximum number of pointers in a block is a bit more complicated, -but since our file size is limited by the integer width we use to store the +but because our file size is limited by the integer width we use to store the size, we can solve for it. Setting the overhead of the maximum pointers equal -to the block size we get the following equation. Note that a smaller block size +to the block size, we get the following equation. Note that a smaller block size results in more pointers, and a larger word width results in larger pointers. ![maximum overhead](https://latex.codecogs.com/svg.latex?B%20%3D%20%5Cfrac%7Bw%7D%7B8%7D%5Cleft%5Clceil%5Clog_2%5Cleft%28%5Cfrac%7B2%5Ew%7D%7BB-2%5Cfrac%7Bw%7D%7B8%7D%7D%5Cright%29%5Cright%5Crceil) @@ -333,19 +333,19 @@ widths: 32 bit CTZ skip-list = minimum block size of 104 bytes 64 bit CTZ skip-list = minimum block size of 448 bytes -Since littlefs uses a 32 bit word size, we are limited to a minimum block +Because littlefs uses a 32 bit word size, we are limited to a minimum block size of 104 bytes. This is a perfectly reasonable minimum block size, with most block sizes starting around 512 bytes. So we can avoid additional logic to avoid overflowing our block's capacity in the CTZ skip-list. So, how do we store the skip-list in a directory entry? A naive approach would be to store a pointer to the head of the skip-list, the length of the file -in bytes, the index of the head block in the skip-list, and the offset in the -head block in bytes. However this is a lot of information, and we can observe +in bytes, the index of the head block in the skip-list and the offset in the +head block in bytes. However, this is a lot of information, and we can observe that a file size maps to only one block index + offset pair. So it should be sufficient to store only the pointer and file size. -But there is one problem, calculating the block index + offset pair from a +But there is one problem: Calculating the block index plus offset pair from a file size doesn't have an obvious implementation. We can start by just writing down an equation. The first idea that comes to @@ -360,7 +360,7 @@ w = word width in bits n = block index in skip-list N = file size in bytes -And this works quite well, but is not trivial to calculate. This equation +And this works quite well but is not trivial to calculate. This equation requires O(n) to compute, which brings the entire runtime of reading a file to O(n^2logn). Fortunately, the additional O(n) does not need to touch disk, so it is not completely unreasonable. But if we could solve this equation into @@ -372,7 +372,7 @@ Fortunately, there is a powerful tool I've found useful in these situations: The [On-Line Encyclopedia of Integer Sequences (OEIS)](https://oeis.org/). If we work out the first couple of values in our summation, we find that CTZ maps to [A001511](https://oeis.org/A001511), and its partial summation maps -to [A005187](https://oeis.org/A005187), and surprisingly, both of these +to [A005187](https://oeis.org/A005187), and, surprisingly, both of these sequences have relatively trivial equations! This leads us to a rather unintuitive property: @@ -383,9 +383,9 @@ ctz(i) = the number of trailing bits that are 0 in i popcount(i) = the number of bits that are 1 in i It's a bit bewildering that these two seemingly unrelated bitwise instructions -are related by this property. But if we start to disect this equation we can +are related by this property. But if we start to dissect this equation, we can see that it does hold. As n approaches infinity, we do end up with an average -overhead of 2 pointers as we find earlier. And popcount seems to handle the +overhead of 2 pointers as we found earlier. And popcount seems to handle the error from this average as it accumulates in the CTZ skip-list. Now we can substitute into the original equation to get a trivial equation @@ -393,7 +393,7 @@ for a file size: ![summation2](https://latex.codecogs.com/svg.latex?N%20%3D%20Bn%20-%20%5Cfrac%7Bw%7D%7B8%7D%5Cleft%282n-%5Ctext%7Bpopcount%7D%28n%29%5Cright%29) -Unfortunately, we're not quite done. The popcount function is non-injective, +Unfortunately, we're not quite done. The popcount function is noninjective, so we can only find the file size from the block index, not the other way around. However, we can solve for an n' block index that is greater than n with an error bounded by the range of the popcount function. We can then @@ -410,7 +410,7 @@ a bit to avoid integer overflow: ![formulaforoff](https://latex.codecogs.com/svg.latex?%5Cmathit%7Boff%7D%20%3D%20N%20-%20%5Cleft%28B-2%5Cfrac%7Bw%7D%7B8%7D%5Cright%29n%20-%20%5Cfrac%7Bw%7D%7B8%7D%5Ctext%7Bpopcount%7D%28n%29) The solution involves quite a bit of math, but computers are very good at math. -We can now solve for the block index + offset while only needed to store the +We can now solve for the block index plus offset while only needed to store the file size in O(1). Here is what it might look like to update a file stored with a CTZ skip-list: @@ -496,20 +496,20 @@ initially the littlefs was designed with this in mind. By storing a reference to the free list in every single metadata pair, additions to the free list could be updated atomically at the same time the replacement blocks were stored in the metadata pair. During boot, every metadata pair had to be -scanned to find the most recent free list, but once the list was found the +scanned to find the most recent free list, but once the list is found, the state of all free blocks becomes known. However, this approach had several issues: - There was a lot of nuanced logic for adding blocks to the free list without - modifying the blocks, since the blocks remain active until the metadata is + modifying the blocks because the blocks remain active until the metadata is updated. -- The free list had to support both additions and removals in fifo order while +- The free list had to support both additions and removals in FIFO order while minimizing block erases. - The free list had to handle the case where the file system completely ran out of blocks and may no longer be able to add blocks to the free list. - If we used a revision count to track the most recently updated free list, metadata blocks that were left unmodified were ticking time bombs that would - cause the system to go haywire if the revision count overflowed + cause the system to go haywire if the revision count overflowed. - Every single metadata block wasted space to store these free list references. Actually, to simplify, this approach had one massive glaring issue: complexity. @@ -525,7 +525,7 @@ In the end, the littlefs adopted more of a "drop it on the floor" strategy. That is, the littlefs doesn't actually store information about which blocks are free on the storage. The littlefs already stores which files _are_ in use, so to find a free block, the littlefs just takes all of the blocks that -exist and subtract the blocks that are in use. +exist and subtracts the blocks that are in use. Of course, it's not quite that simple. Most filesystems that adopt this "drop it on the floor" strategy either rely on some properties inherent to the @@ -539,8 +539,8 @@ would have an abhorrent runtime. So the littlefs compromises. It doesn't store a bitmap the size of the storage, but it does store a little bit-vector that contains a fixed set lookahead for block allocations. During a block allocation, the lookahead vector is -checked for any free blocks, if there are none, the lookahead region jumps -forward and the entire filesystem is scanned for free blocks. +checked for any free blocks. If there are none, the lookahead region jumps +forward, and the entire filesystem is scanned for free blocks. Here's what it might look like to allocate 4 blocks on a decently busy filesystem with a 32bit lookahead and a total of @@ -570,7 +570,7 @@ alloc = 112 lookahead: ffff8000 While this lookahead approach still has an asymptotic runtime of O(n^2) to scan all of storage, the lookahead reduces the practical runtime to a -reasonable amount. Bit-vectors are surprisingly compact, given only 16 bytes, +reasonable amount. Bit-vectors are surprisingly compact. Given only 16 bytes, the lookahead could track 128 blocks. For a 4Mbyte flash chip with 4Kbyte blocks, the littlefs would only need 8 passes to scan the entire storage. @@ -581,12 +581,12 @@ causing difficult to detect memory leaks. ## Directories -Now we just need directories to store our files. Since we already have -metadata blocks that store information about files, lets just use these +Now we just need directories to store our files. Because we already have +metadata blocks that store information about files, let's just use these metadata blocks as the directories. Maybe turn the directories into linked -lists of metadata blocks so it isn't limited by the number of files that fit +lists of metadata blocks, so it isn't limited by the number of files that fit in a single block. Add entries that represent other nested directories. -Drop "." and ".." entries, cause who needs them. Dust off our hands and +Drop "." and ".." entries because who needs them? Dust off our hands, and we now have a directory tree. ``` @@ -611,17 +611,17 @@ we now have a directory tree. '--------' '--------' '--------' '--------' '--------' ``` -Unfortunately it turns out it's not that simple. See, iterating over a +Unfortunately, it turns out it's not that simple. See, iterating over a directory tree isn't actually all that easy, especially when you're trying to fit in a bounded amount of RAM, which rules out any recursive solution. -And since our block allocator involves iterating over the entire filesystem +And because our block allocator involves iterating over the entire filesystem tree, possibly multiple times in a single allocation, iteration needs to be efficient. So, as a solution, the littlefs adopted a sort of threaded tree. Each directory not only contains pointers to all of its children, but also a pointer to the next directory. These pointers create a linked-list that -is threaded through all of the directories in the filesystem. Since we +is threaded through all of the directories in the filesystem. Because we only use this linked list to check for existance, the order doesn't actually matter. As an added plus, we can repurpose the pointer for the individual directory linked-lists and avoid using any additional space. @@ -648,16 +648,16 @@ directory linked-lists and avoid using any additional space. '--------' '--------' '--------' '--------' '--------' ``` -This threaded tree approach does come with a few tradeoffs. Now, anytime we +This threaded tree approach does come with a few tradeoffs. Now, any time we want to manipulate the directory tree, we find ourselves having to update two pointers instead of one. For anyone familiar with creating atomic data -structures this should set off a whole bunch of red flags. +structures, this should set off a whole bunch of red flags. -But unlike the data structure guys, we can update a whole block atomically! So +But unlike the data structure people, we can update a whole block atomically! So as long as we're really careful (and cheat a little bit), we can still manipulate the directory tree in a way that is resilient to power loss. -Consider how we might add a new directory. Since both pointers that reference +Consider how we might add a new directory. Because both pointers that reference it can come from the same directory, we only need a single atomic update to finagle the directory into the filesystem: ``` @@ -759,7 +759,7 @@ v '--------' '--------' ``` -Wait, wait, wait, that's not atomic at all! If power is lost after removing +Wait, wait, wait; that's not atomic at all! If power is lost after removing directory B from the root, directory B is still in the linked-list. We've just created a memory leak! @@ -850,18 +850,18 @@ lose power inconveniently. Initially, you might think this is fine. Dir A _might_ end up with two parents, but the filesystem will still work as intended. But then this raises the -question of what do we do when the dir A wears out? For other directory blocks -we can update the parent pointer, but for a dir with two parents we would need -work out how to update both parents. And the check for multiple parents would +question of what do we do when the dir A wears out? For other directory blocks, +we can update the parent pointer, but for a dir with two parents, we would need +to work out how to update both parents. And the check for multiple parents would need to be carried out for every directory, even if the directory has never been moved. -It also presents a bad user-experience, since the condition of ending up with +It also presents a bad user-experience. Because the condition of ending up with two parents is rare, it's unlikely user-level code will be prepared. Just think -about how a user would recover from a multi-parented directory. They can't just -remove one directory, since remove would report the directory as "not empty". +about how users would recover from a multiparented directory. They can't just +remove one directory because remove would report the directory as "not empty". -Other atomic filesystems simple COW the entire directory tree. But this +Other atomic filesystems simply COW the entire directory tree. But this introduces a significant bit of complexity, which leads to code size, along with a surprisingly expensive runtime cost during what most users assume is a single pointer update. @@ -969,7 +969,7 @@ of two things is possible. Either the directory entry exists elsewhere in the filesystem, or it doesn't. This is a O(n) operation, but only occurs in the unlikely case we lost power during a move. -And we can easily fix the "moved" directory entry. Since we're already scanning +And we can easily fix the "moved" directory entry. Because we're already scanning the filesystem during the deorphan step, we can also check for moved entries. If we find one, we either remove the "moved" marking or remove the whole entry if it exists elsewhere in the filesystem. @@ -979,7 +979,7 @@ if it exists elsewhere in the filesystem. So now that we have all of the pieces of a filesystem, we can look at a more subtle attribute of embedded storage: The wear down of flash blocks. -The first concern for the littlefs, is that prefectly valid blocks can suddenly +The first concern for the littlefs is that prefectly valid blocks can suddenly become unusable. As a nice side-effect of using a COW data-structure for files, we can simply move on to a different block when a file write fails. All modifications to files are performed in copies, so we will only replace the @@ -988,7 +988,7 @@ the other hand, need a different strategy. The solution to directory corruption in the littlefs relies on the redundant nature of the metadata pairs. If an error is detected during a write to one -of the metadata pairs, we seek out a new block to take its place. Once we find +of the metadata pairs, we seek a new block to take its place. Once we find a block without errors, we iterate through the directory tree, updating any references to the corrupted metadata pair to point to the new metadata block. Just like when we remove directories, we can lose power during this operation @@ -1141,8 +1141,8 @@ v '---------'---------' '---------'---------' '---------'---------' ``` -Also one question I've been getting is, what about the root directory? -It can't move so wouldn't the filesystem die as soon as the root blocks +Also one question I've been getting is: What about the root directory? +It can't move, so wouldn't the filesystem die as soon as the root blocks develop errors? And you would be correct. So instead of storing the root in the first few blocks of the storage, the root is actually pointed to by the superblock. The superblock contains a few bits of static data, but @@ -1151,9 +1151,9 @@ develops errors and needs to be moved. ## Wear leveling -The second concern for the littlefs, is that blocks in the filesystem may wear -unevenly. In this situation, a filesystem may meet an early demise where -there are no more non-corrupted blocks that aren't in use. It's common to +The second concern for the littlefs is that blocks in the filesystem may wear +unevenly. In this situation, a filesystem may meet an early demise whe, +there are no more noncorrupted blocks that aren't in use. It's common to have files that were written once and left unmodified, wasting the potential erase cycles of the blocks it sits on. @@ -1180,22 +1180,21 @@ handle the case of write-once files, and near the end of the lifetime of a flash device, you would likely end up with uneven wear on the blocks anyways. As a flash device reaches the end of its life, the metadata blocks will -naturally be the first to go since they are updated most often. In this +naturally be the first to go because they are updated most often. In this situation, the littlefs is designed to simply move on to another set of -metadata blocks. This travelling means that at the end of a flash device's +metadata blocks. This traveling means that at the end of a flash device's life, the filesystem will have worn the device down nearly as evenly as the usual dynamic wear leveling could. More aggressive wear leveling would come with a code-size cost for marginal benefit. - -One important takeaway to note, if your storage stack uses highly sensitive -storage such as NAND flash, static wear leveling is the only valid solution. -In most cases you are going to be better off using a full [flash translation layer (FTL)](https://en.wikipedia.org/wiki/Flash_translation_layer). +One important takeaway to note:, If your storage stack uses highly sensitive +storage, such as NAND flash, static wear leveling is the only valid solution. +In most cases, you are going to be better off using a full [flash translation layer (FTL)](https://en.wikipedia.org/wiki/Flash_translation_layer). NAND flash already has many limitations that make it poorly suited for an embedded system: low erase cycles, very large blocks, errors that can develop even during reads, errors that can develop during writes of neighboring blocks. -Managing sensitive storage such as NAND flash is out of scope for the littlefs. -The littlefs does have some properties that may be beneficial on top of a FTL, +Managing sensitive storage, such as NAND flash, is out of scope for the littlefs. +The littlefs does have some properties that may be beneficial on top of an FTL, such as limiting the number of writes where possible, but if you have the storage requirements that necessitate the need of NAND flash, you should have the RAM to match and just use an FTL or flash filesystem. @@ -1204,21 +1203,21 @@ the RAM to match and just use an FTL or flash filesystem. So, to summarize: -1. The littlefs is composed of directory blocks -2. Each directory is a linked-list of metadata pairs +1. The littlefs is composed of directory blocks. +2. Each directory is a linked-list of metadata pairs. 3. These metadata pairs can be updated atomically by alternating which - metadata block is active -4. Directory blocks contain either references to other directories or files -5. Files are represented by copy-on-write CTZ skip-lists which support O(1) - append and O(nlogn) reading + metadata block is active. +4. Directory blocks contain either references to other directories or files. +5. Files are represented by copy-on-write CTZ skip-lists, which support O(1) + append and O(nlogn) reading. 6. Blocks are allocated by scanning the filesystem for used blocks in a - fixed-size lookahead region is that stored in a bit-vector + fixed-size lookahead region is that stored in a bit-vector. 7. To facilitate scanning the filesystem, all directories are part of a - linked-list that is threaded through the entire filesystem -8. If a block develops an error, the littlefs allocates a new block, and + linked-list that is threaded through the entire filesystem. +8. If a block develops an error, the littlefs allocates a new block and moves the data and references of the old block to the new. 9. Any case where an atomic operation is not possible, mistakes are resolved - by a deorphan step that occurs on the first allocation after boot + by a deorphan step that occurs on the first allocation after boot. That's the little filesystem. Thanks for reading! diff --git a/features/filesystem/littlefs/littlefs/README.md b/features/filesystem/littlefs/littlefs/README.md index d02139c443..f77c3dc3c1 100644 --- a/features/filesystem/littlefs/littlefs/README.md +++ b/features/filesystem/littlefs/littlefs/README.md @@ -12,16 +12,16 @@ A little fail-safe filesystem designed for embedded systems. ``` **Bounded RAM/ROM** - The littlefs is designed to work with a limited amount -of memory. Recursion is avoided and dynamic memory is limited to configurable +of memory. Recursion is avoided, and dynamic memory is limited to configurable buffers that can be provided statically. **Power-loss resilient** - The littlefs is designed for systems that may have -random power failures. The littlefs has strong copy-on-write guaruntees and +random power failures. The littlefs has strong copy-on-write guaruntees, and storage on disk is always kept in a valid state. -**Wear leveling** - Since the most common form of embedded storage is erodible +**Wear leveling** - Because the most common form of embedded storage is erodible flash memories, littlefs provides a form of dynamic wear leveling for systems -that can not fit a full flash translation layer. +that cannot fit a full flash translation layer. ## Example @@ -93,20 +93,20 @@ can be cound in the comments in [lfs.h](lfs.h). As you may have noticed, littlefs takes in a configuration structure that defines how the filesystem operates. The configuration struct provides the filesystem with the block device operations and dimensions, tweakable -parameters that tradeoff memory usage for performance, and optional +parameters that trade memory usage for performance and optional static buffers if the user wants to avoid dynamic memory. -The state of the littlefs is stored in the `lfs_t` type which is left up +The state of the littlefs is stored in the `lfs_t` type, which is left up to the user to allocate, allowing multiple filesystems to be in use simultaneously. With the `lfs_t` and configuration struct, a user can format a block device or mount the filesystem. -Once mounted, the littlefs provides a full set of posix-like file and +Once mounted, the littlefs provides a full set of POSIX-like file and directory functions, with the deviation that the allocation of filesystem structures must be provided by the user. -All posix operations, such as remove and rename, are atomic, even in event -of power-loss. Additionally, no file updates are actually commited to the +All POSIX operations, such as remove and rename, are atomic, even in the event +of power loss. Additionally, no file updates are actually committed to the filesystem until sync or close is called on the file. ## Other notes @@ -116,24 +116,24 @@ can be either one of those found in the `enum lfs_error` in [lfs.h](lfs.h), or an error returned by the user's block device operations. It should also be noted that the current implementation of littlefs doesn't -really do anything to insure that the data written to disk is machine portable. +really do anything to ensure that the data written to disk is machine portable. This is fine as long as all of the involved machines share endianness (little-endian) and don't have strange padding requirements. ## Reference material [DESIGN.md](DESIGN.md) - DESIGN.md contains a fully detailed dive into how -littlefs actually works. I would encourage you to read it since the +littlefs actually works. We would encourage you to read it because the solutions and tradeoffs at work here are quite interesting. [SPEC.md](SPEC.md) - SPEC.md contains the on-disk specification of littlefs -with all the nitty-gritty details. Can be useful for developing tooling. +with all the nitty-gritty details. This can be useful for developing tooling. ## Testing -The littlefs comes with a test suite designed to run on a pc using the +The littlefs comes with a test suite designed to run on a PC using the [emulated block device](emubd/lfs_emubd.h) found in the emubd directory. -The tests assume a linux environment and can be started with make: +The tests assume a Linux environment and can be started with make: ``` bash make test @@ -142,15 +142,15 @@ make test ## Related projects [mbed-littlefs](https://github.com/armmbed/mbed-littlefs) - The easiest way to -get started with littlefs is to jump into [mbed](https://os.mbed.com/), which +get started with littlefs is to jump into [Mbed](https://os.mbed.com/), which already has block device drivers for most forms of embedded storage. The -mbed-littlefs provides the mbed wrapper for littlefs. +mbed-littlefs provides the Mbed wrapper for littlefs. [littlefs-fuse](https://github.com/geky/littlefs-fuse) - A [FUSE](https://github.com/libfuse/libfuse) wrapper for littlefs. The project allows you to mount littlefs directly in a Linux machine. Can be useful for debugging littlefs if you have an SD card handy. -[littlefs-js](https://github.com/geky/littlefs-js) - A javascript wrapper for +[littlefs-js](https://github.com/geky/littlefs-js) - A JavaScript wrapper for littlefs. I'm not sure why you would want this, but it is handy for demos. You can see it in action [here](http://littlefs.geky.net/demo.html). diff --git a/features/filesystem/littlefs/littlefs/SPEC.md b/features/filesystem/littlefs/littlefs/SPEC.md index b80892ec88..9f3135b16a 100644 --- a/features/filesystem/littlefs/littlefs/SPEC.md +++ b/features/filesystem/littlefs/littlefs/SPEC.md @@ -3,8 +3,8 @@ This is the technical specification of the little filesystem. This document covers the technical details of how the littlefs is stored on disk for introspection and tooling development. This document assumes you are -familiar with the design of the littlefs, for more info on how littlefs -works check out [DESIGN.md](DESIGN.md). +familiar with the design of the littlefs. For more information on how littlefs +works, check out [DESIGN.md](DESIGN.md). ``` | | | .---._____ @@ -17,23 +17,23 @@ works check out [DESIGN.md](DESIGN.md). ## Some important details -- The littlefs is a block-based filesystem. This is, the disk is divided into +- The littlefs is a block-based filesystem. The disk is divided into an array of evenly sized blocks that are used as the logical unit of storage in littlefs. Block pointers are stored in 32 bits. -- There is no explicit free-list stored on disk, the littlefs only knows what +- There is no explicit free-list stored on disk. The littlefs only knows what is in use in the filesystem. - The littlefs uses the value of 0xffffffff to represent a null block-pointer. - All values in littlefs are stored in little-endian byte order. -## Directories / Metadata pairs +## Directories/Metadata pairs Metadata pairs form the backbone of the littlefs and provide a system for atomic updates. Even the superblock is stored in a metadata pair. -As their name suggests, a metadata pair is stored in two blocks, with one block +As its name suggests, a metadata pair is stored in two blocks, with one block acting as a redundant backup in case the other is corrupted. These two blocks could be anywhere in the disk and may not be next to each other, so any pointers to directory pairs need to be stored as two block pointers. @@ -50,7 +50,7 @@ Here's the layout of metadata blocks on disk: **Revision count** - Incremented every update, only the uncorrupted metadata-block with the most recent revision count contains the valid metadata. -Comparison between revision counts must use sequence comparison since the +Comparison between revision counts must use sequence comparison because the revision counts may overflow. **Dir size** - Size in bytes of the contents in the current metadata block, @@ -61,12 +61,12 @@ next metadata-pair pointed to by the tail pointer. **Tail pointer** - Pointer to the next metadata-pair in the filesystem. A null pair-pointer (0xffffffff, 0xffffffff) indicates the end of the list. If the highest bit in the dir size is set, this points to the next -metadata-pair in the current directory, otherwise it points to an arbitrary +metadata-pair in the current directory. Otherwise, it points to an arbitrary metadata-pair. Starting with the superblock, the tail-pointers form a linked-list containing all metadata-pairs in the filesystem. **CRC** - 32 bit CRC used to detect corruption from power-lost, from block -end-of-life, or just from noise on the storage bus. The CRC is appended to +end-of-life or just from noise on the storage bus. The CRC is appended to the end of each metadata-block. The littlefs uses the standard CRC-32, which uses a polynomial of 0x04c11db7, initialized with 0xffffffff. @@ -90,9 +90,9 @@ Here's an example of a simple directory stored on disk: ``` A note about the tail pointer linked-list: Normally, this linked-list is -threaded through the entire filesystem. However, after power-loss this +threaded through the entire filesystem. However, after power loss, this linked-list may become out of sync with the rest of the filesystem. -- The linked-list may contain a directory that has actually been removed +- The linked-list may contain a directory that has actually been removed. - The linked-list may contain a metadata pair that has not been updated after a block in the pair has gone bad. @@ -104,7 +104,7 @@ if littlefs is mounted read-only. Each metadata block contains a series of entries that follow a standard layout. An entry contains the type of the entry, along with a section for -entry-specific data, attributes, and a name. +entry-specific data, attributes and a name. Here's the layout of entries on disk: @@ -119,9 +119,9 @@ Here's the layout of entries on disk: | 0x4+e+a | name length bytes | entry name | **Entry type** - Type of the entry, currently this is limited to the following: -- 0x11 - file entry -- 0x22 - directory entry -- 0x2e - superblock entry +- 0x11 - file entry. +- 0x22 - directory entry. +- 0x2e - superblock entry. Additionally, the type is broken into two 4 bit nibbles, with the upper nibble specifying the type's data structure used when scanning the filesystem. The @@ -134,17 +134,17 @@ filesystem. If the entry exists elsewhere, this entry must be treated as though it does not exist. **Entry length** - Length in bytes of the entry-specific data. This does -not include the entry type size, attributes, or name. The full size in bytes -of the entry is 4 + entry length + attribute length + name length. +not include the entry type size, attributes or name. The full size in bytes +of the entry is 4 plus entry length plus attribute length plus name length. -**Attribute length** - Length of system-specific attributes in bytes. Since -attributes are system specific, there is not much garuntee on the values in +**Attribute length** - Length of system-specific attributes in bytes. Because +attributes are system specific, there is not much guarantee on the values in this section, and systems are expected to work even when it is empty. See the [attributes](#entry-attributes) section for more details. **Name length** - Length of the entry name. Entry names are stored as utf8, -although most systems will probably only support ascii. Entry names can not -contain '/' and can not be '.' or '..' as these are a part of the syntax of +though most systems will probably only support ascii. Entry names can not +contain '/' and can not be '.' or '..' because these are a part of the syntax of filesystem paths. Here's an example of a simple entry stored on disk: @@ -166,9 +166,9 @@ The superblock is the anchor for the littlefs. The superblock is stored as a metadata pair containing a single superblock entry. It is through the superblock that littlefs can access the rest of the filesystem. -The superblock can always be found in blocks 0 and 1, however fetching the +The superblock can always be found in blocks 0 and 1; however, fetching the superblock requires knowing the block size. The block size can be guessed by -searching the beginning of disk for the string "littlefs", although currently +searching the beginning of disk for the string "littlefs", though currently, the filesystems relies on the user providing the correct block size. The superblock is the most valuable block in the filesystem. It is updated @@ -200,8 +200,8 @@ Here's the layout of the superblock entry: **Version** - The littlefs version encoded as a 32 bit value. The upper 16 bits encodes the major version, which is incremented when a breaking-change is introduced in the filesystem specification. The lower 16 bits encodes the -minor version, which is incremented when a backwards-compatible change is -introduced. Non-standard Attribute changes do not change the version. This +minor version, which is incremented when a backward-compatible change is +introduced. Nonstandard Attribute changes do not change the version. This specification describes version 1.1 (0x00010001), which is the first version of littlefs. @@ -315,27 +315,27 @@ Here's an example of a file entry: ## Entry attributes -Each dir entry can have up to 256 bytes of system-specific attributes. Since +Each dir entry can have up to 256 bytes of system-specific attributes. Because these attributes are system-specific, they may not be portable between different systems. For this reason, all attributes must be optional. A minimal littlefs driver must be able to get away with supporting no attributes at all. For some level of portability, littlefs has a simple scheme for attributes. -Each attribute is prefixes with an 8-bit type that indicates what the attribute +Each attribute is prefixed with an 8-bit type that indicates what the attribute is. The length of attributes may also be determined from this type. Attributes -in an entry should be sorted based on portability, since attribute parsing +in an entry should be sorted based on portability because attribute parsing will end when it hits the first attribute it does not understand. Each system should choose a 4-bit value to prefix all attribute types with to avoid conflicts with other systems. Additionally, littlefs drivers that support -attributes should provide a "ignore attributes" flag to users in case attribute +attributes should provide an "ignore attributes" flag to users in case attribute conflicts do occur. Attribute types prefixes with 0x0 and 0xf are currently reserved for future standard attributes. Standard attributes will be added to this document in that case. -Here's an example of non-standard time attribute: +Here's an example of nonstandard time attribute: ``` (8 bits) attribute type = time (0xc1) (72 bits) time in seconds = 1506286115 (0x0059c81a23) @@ -343,7 +343,7 @@ Here's an example of non-standard time attribute: 00000000: c1 23 1a c8 59 00 .#..Y. ``` -Here's an example of non-standard permissions attribute: +Here's an example of nonstandard permissions attribute: ``` (8 bits) attribute type = permissions (0xc2) (16 bits) permission bits = rw-rw-r-- (0x01b4) From c6130306e06deff35e9685fd8886263e7cc1fccc Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 30 Nov 2017 10:54:43 -0600 Subject: [PATCH 46/46] littlefs: Removed links to previous repository locations --- features/filesystem/littlefs/README.md | 6 +++--- features/filesystem/littlefs/littlefs/README.md | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/features/filesystem/littlefs/README.md b/features/filesystem/littlefs/README.md index 46660a2238..43584849e1 100644 --- a/features/filesystem/littlefs/README.md +++ b/features/filesystem/littlefs/README.md @@ -1,7 +1,7 @@ -## Mbed wrapper for the little filesystem +## Mbed OS API for the little filesystem -This is the mbed wrapper for [littlefs](https://github.com/geky/littlefs), -a little fail-safe filesystem designed for embedded systems. +This is the Mbed OS API for littlefs, a little fail-safe filesystem +designed for embedded systems. ``` | | | .---._____ diff --git a/features/filesystem/littlefs/littlefs/README.md b/features/filesystem/littlefs/littlefs/README.md index f77c3dc3c1..387e756770 100644 --- a/features/filesystem/littlefs/littlefs/README.md +++ b/features/filesystem/littlefs/littlefs/README.md @@ -141,11 +141,6 @@ make test ## Related projects -[mbed-littlefs](https://github.com/armmbed/mbed-littlefs) - The easiest way to -get started with littlefs is to jump into [Mbed](https://os.mbed.com/), which -already has block device drivers for most forms of embedded storage. The -mbed-littlefs provides the Mbed wrapper for littlefs. - [littlefs-fuse](https://github.com/geky/littlefs-fuse) - A [FUSE](https://github.com/libfuse/libfuse) wrapper for littlefs. The project allows you to mount littlefs directly in a Linux machine. Can be useful for debugging littlefs if you have an SD card