diff --git a/.mbedignore b/.mbedignore new file mode 100644 index 00000000000..9660ef95982 --- /dev/null +++ b/.mbedignore @@ -0,0 +1,3 @@ +littlefs/emubd/ +littlefs/tests/ +TESTS/util diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000000..660d9dd6a6b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,70 @@ +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 + + # 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-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-format -DLFS_READ_SIZE=64 -DLFS_PROG_SIZE=64" + make -Clittlefs test QUIET=1 + - CFLAGS="-Wno-format -DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" + make -Clittlefs test QUIET=1 + - CFLAGS="-Wno-format -DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" + make -Clittlefs test QUIET=1 + - CFLAGS="-Wno-format -DLFS_BLOCK_COUNT=1023" + make -Clittlefs test QUIET=1 + - CFLAGS="-Wno-format -DLFS_LOOKAHEAD=2048" + make -Clittlefs test QUIET=1 + + # Self-host with littlefs-fuse for fuzz test + - CFLAGS="-Wno-format" 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-format" make -B test_dirs QUIET=1 + +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 + - pip install --user -r mbed-os/requirements.txt + # 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 diff --git a/LittleFileSystem.cpp b/LittleFileSystem.cpp new file mode 100644 index 00000000000..23c749c1dd8 --- /dev/null +++ b/LittleFileSystem.cpp @@ -0,0 +1,591 @@ +/* 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 "filesystem/mbed_filesystem.h" +#include "LittleFileSystem.h" +#include "errno.h" +#include "lfs.h" +#include "lfs_util.h" +#include "MbedCRC.h" + +namespace mbed { + +extern "C" void lfs_crc(uint32_t *crc, const void *buffer, size_t size) +{ + uint32_t initial_xor = *crc; + MbedCRC ct(initial_xor, 0x0, true, false); + ct.compute((void *)buffer, size, (uint32_t *) crc); +} + +////// 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_EXIST: + 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; + case LFS_ERR_CORRUPT: + return -EILSEQ; + 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, (bd_addr_t)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, (bd_addr_t)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((bd_addr_t)block * c->block_size, c->block_size); +} + +static int lfs_bd_sync(const struct lfs_config *c) +{ + BlockDevice *bd = (BlockDevice *)c->context; + return bd->sync(); +} + + +////// 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) +{ + _mutex.lock(); + LFS_INFO("mount(%p)", bd); + _bd = bd; + int err = _bd->init(); + if (err) { + _bd = NULL; + LFS_INFO("mount -> %d", err); + _mutex.unlock(); + 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 = 32 * ((_config.block_count + 31) / 32); + if (_config.lookahead > _lookahead) { + _config.lookahead = _lookahead; + } + + err = lfs_mount(&_lfs, &_config); + if (err) { + _bd = NULL; + LFS_INFO("mount -> %d", lfs_toerror(err)); + _mutex.unlock(); + return lfs_toerror(err); + } + + _mutex.unlock(); + LFS_INFO("mount -> %d", 0); + return 0; +} + +int LittleFileSystem::unmount() +{ + _mutex.lock(); + LFS_INFO("unmount(%s)", ""); + int res = 0; + if (_bd) { + int err = lfs_unmount(&_lfs); + if (err && !res) { + res = lfs_toerror(err); + } + + err = _bd->deinit(); + if (err && !res) { + res = err; + } + + _bd = NULL; + } + + LFS_INFO("unmount -> %d", res); + _mutex.unlock(); + return res; +} + +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, %ld, %ld, %ld, %ld)", + bd, read_size, prog_size, block_size, lookahead); + int err = bd->init(); + if (err) { + LFS_INFO("format -> %d", 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 = 32 * ((_config.block_count + 31) / 32); + if (_config.lookahead > lookahead) { + _config.lookahead = lookahead; + } + + 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; + } + + 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; + } + + 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%lx)", name, mode); + int err = lfs_mkdir(&_lfs, name); + LFS_INFO("mkdir -> %d", lfs_toerror(err)); + _mutex.unlock(); + return lfs_toerror(err); +} + +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); + return lfs_toerror(err); +} + +static int lfs_statvfs_count(void *p, lfs_block_t b) +{ + *(lfs_size_t *)p += 1; + return 0; +} + +int LittleFileSystem::statvfs(const char *name, struct statvfs *st) +{ + memset(st, 0, sizeof(struct statvfs)); + + lfs_size_t in_use = 0; + _mutex.lock(); + LFS_INFO("statvfs(\"%s\", %p)", name, st); + int err = lfs_traverse(&_lfs, lfs_statvfs_count, &in_use); + LFS_INFO("statvfs -> %d", lfs_toerror(err)); + _mutex.unlock(); + if (err) { + return err; + } + + st->f_bsize = _config.block_size; + st->f_frsize = _config.block_size; + st->f_blocks = _config.block_count; + st->f_bfree = _config.block_count - in_use; + st->f_bavail = _config.block_count - in_use; + st->f_namemax = LFS_NAME_MAX; + return 0; +} + +////// File operations ////// +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); + 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); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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); + 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); +} + +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); +} + +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); +} + +int LittleFileSystem::file_truncate(fs_file_t file, off_t length) +{ + lfs_file_t *f = (lfs_file_t *)file; + _mutex.lock(); + LFS_INFO("file_truncate(%p)", file); + int err = lfs_file_truncate(&_lfs, f, length); + LFS_INFO("file_truncate -> %d", lfs_toerror(err)); + _mutex.unlock(); + return lfs_toerror(err); +} + + +////// Dir operations ////// +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); + 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); +} + +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); +} + +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); + 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; + _mutex.lock(); + LFS_INFO("dir_seek(%p, %ld)", dir, offset); + lfs_dir_seek(&_lfs, d, offset); + LFS_INFO("dir_seek -> %s", "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); +} + +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 -> %s", "void"); + _mutex.unlock(); +} + +} // namespace mbed diff --git a/LittleFileSystem.h b/LittleFileSystem.h new file mode 100644 index 00000000000..c4fdaaee2dc --- /dev/null +++ b/LittleFileSystem.h @@ -0,0 +1,312 @@ +/* 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. + */ + +/** \addtogroup storage */ +/** @{*/ + +#ifndef MBED_LFSFILESYSTEM_H +#define MBED_LFSFILESYSTEM_H + +#include "FileSystem.h" +#include "BlockDevice.h" +#include "PlatformMutex.h" +#include "lfs.h" + +namespace mbed { + +/** + * LittleFileSystem, a little file system + * + * Synchronization level: Thread safe + */ +class LittleFileSystem : public mbed::FileSystem { +public: + /** Lifetime of the LittleFileSystem + * + * @param name Name of the file system in the tree. + * @param bd Block device to mount. Mounted immediately if not NULL. + * @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, mbed::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(); + + /** Format a block device with the LittleFileSystem. + * + * The block device to format should be mounted when this function is called. + * + * @param bd This is the block device that will be formatted. + * @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(mbed::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); + + /** Mount a file system to a block device. + * + * @param bd Block device to mount to. + * @return 0 on success, negative error code on failure. + */ + virtual int mount(mbed::BlockDevice *bd); + + /** Unmount a file system from the underlying block device. + * + * @return 0 on success, negative error code on failure + */ + virtual int unmount(); + + /** Reformat a file system. Results in an empty and mounted file system. + * + * @param bd + * Block device to reformat and mount. If NULL, the mounted + * Block device is used. + * Note: If mount fails, bd must be provided. + * Default: NULL + * + * @return 0 on success, negative error code on failure + */ + virtual int reformat(mbed::BlockDevice *bd); + + /** Remove a file from the file system. + * + * @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 file system. + * + * @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 file system. + * + * @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); + + /** Store information about the mounted file system in a statvfs structure. + * + * @param path The name of the file to find information about. + * @param buf The stat buffer to write to. + * @return 0 on success, negative error code on failure + */ + virtual int statvfs(const char *path, struct statvfs *buf); + +protected: +#if !(DOXYGEN_ONLY) + /** Open a file on the file system. + * + * @param file Destination of the newly created handle to the referenced file. + * @param path The name of the file to open. + * @param flags The flags that trigger opening of the file. These flags are O_RDONLY, O_WRONLY, and O_RDWR, + * with an O_CREAT, O_TRUNC, or O_APPEND bitwise OR operator. + * @return 0 on success, negative error code on failure. + */ + 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(mbed::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(mbed::fs_file_t file, void *buffer, size_t size); + + /** 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(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(mbed::fs_file_t file); + + /** Move the file position to a given offset 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(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(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(mbed::fs_file_t file); + + /** Truncate or extend a file. + * + * The file's length is set to the specified value. The seek pointer is + * not changed. If the file is extended, the extended area appears as if + * it were zero-filled. + * + * @param file File handle. + * @param length The requested new length for the file + * + * @return Zero on success, negative error code on failure + */ + virtual int file_truncate(mbed::fs_file_t file, off_t length); + + /** Open a directory on the file system. + * + * @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(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(mbed::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(mbed::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(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(mbed::fs_dir_t dir); + + /** Rewind the current position to the beginning of the directory + * + * @param dir Dir handle + */ + virtual void dir_rewind(mbed::fs_dir_t dir); +#endif //!(DOXYGEN_ONLY) + +private: + lfs_t _lfs; // The actual file system + struct lfs_config _config; + mbed::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; + + // thread-safe locking + PlatformMutex _mutex; +}; + +} // namespace mbed + +// Added "using" for backwards compatibility +#ifndef MBED_NO_GLOBAL_USING_DIRECTIVE +using mbed::LittleFileSystem; +#endif + +#endif + +/** @}*/ diff --git a/README.md b/README.md new file mode 100644 index 00000000000..43584849e11 --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +## Mbed OS API for the little filesystem + +This is the Mbed OS API for littlefs, a little fail-safe filesystem +designed for embedded systems. + +``` + | | | .---._____ + .-----. | | +--|o |---| littlefs | +--| |---| | + '-----' '----------' + | | | +``` + +**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 for systems that may have +random power failures. The littlefs has strong copy-on-write guarantees, and +storage on disk is always kept in a valid state. + +**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 cannot fit a full flash translation layer. + +## 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); +} +``` + +## Reference material + +[DESIGN.md](littlefs/DESIGN.md) - DESIGN.md contains a fully detailed dive into +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. This 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. 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. 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/TESTS/COMMON/atomic_usage.cpp b/TESTS/COMMON/atomic_usage.cpp new file mode 100644 index 00000000000..181e9383fda --- /dev/null +++ b/TESTS/COMMON/atomic_usage.cpp @@ -0,0 +1,698 @@ +/* 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 "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 +// 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)(FileSystem *fs); +typedef bool (*test_function_bool_t)(FileSystem *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(FileSystem *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(FileSystem *fs) +{ + DEBUG("perform_file_rename()\n"); + + struct stat st; + int res = fs->stat(FILE_RENAME_A, &st); + 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) || (res == 0)); + + 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(FileSystem *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 (file.open(fs, filenames[i], O_RDONLY) == 0) { + 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(FileSystem *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(FileSystem *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(FileSystem *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(FileSystem *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(FileSystem *fs) +{ + DEBUG("perform_directory_rename()\n"); + + struct stat st; + int res = fs->stat(DIRECTORY_RENAME_A, &st); + 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) || (res == 0)); + + 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(FileSystem *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((res == -ENOENT) || (res == 0)); + if (res == 0) { + 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(FileSystem *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(FileSystem *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(FileSystem *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) +{ + MBED_TEST_FILESYSTEM_DECL; + + 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) +{ + MBED_TEST_FILESYSTEM_DECL; + + 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(FileSystem *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) +{ + MBED_TEST_FILESYSTEM_DECL; + 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) +{ + MBED_TEST_FILESYSTEM_DECL; + 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 00000000000..43bf5be94a6 --- /dev/null +++ b/TESTS/COMMON/atomic_usage.h @@ -0,0 +1,71 @@ +/* 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" + +/** + * 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 diff --git a/TESTS/filesystem/dirs/main.cpp b/TESTS/filesystem/dirs/main.cpp new file mode 100644 index 00000000000..8225baba11e --- /dev/null +++ b/TESTS/filesystem/dirs/main.cpp @@ -0,0 +1,683 @@ +/* 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" +#include "utest.h" +#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 +#error [NOT_SUPPORTED] Non-volatile block device required +#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 480 +#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(-ENOTEMPTY, 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(-ENOTEMPTY, 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(-ENOTEMPTY, 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 00000000000..e852a0ee7b3 --- /dev/null +++ b/TESTS/filesystem/files/main.cpp @@ -0,0 +1,449 @@ +/* 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" +#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 +#error [NOT_SUPPORTED] Non-volatile block device required +#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 480 +#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/interspersed/main.cpp b/TESTS/filesystem/interspersed/main.cpp new file mode 100644 index 00000000000..51f198af4e7 --- /dev/null +++ b/TESTS/filesystem/interspersed/main.cpp @@ -0,0 +1,407 @@ +/* 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" +#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 +#error [NOT_SUPPORTED] Non-volatile block device required +#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 480 +#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 00000000000..0b41321c31d --- /dev/null +++ b/TESTS/filesystem/seek/main.cpp @@ -0,0 +1,643 @@ +/* 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" +#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 +#error [NOT_SUPPORTED] Non-volatile block device required +#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 480 +#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_integration/format/main.cpp b/TESTS/filesystem_integration/format/main.cpp new file mode 100644 index 00000000000..eab5359c680 --- /dev/null +++ b/TESTS/filesystem_integration/format/main.cpp @@ -0,0 +1,199 @@ +/* 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" +#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 +#error [NOT_SUPPORTED] Non-volatile block device required +#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 480 +#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 for integration level format operations + +void test_format() +{ + 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_mount() +{ + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_bad_mount() +{ + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = bd.erase(0, 2 * bd.get_erase_size()); + TEST_ASSERT_EQUAL(0, res); + memset(buffer, 0, bd.get_program_size()); + for (int i = 0; i < 2 * bd.get_erase_size(); i += bd.get_program_size()) { + res = bd.program(buffer, i, bd.get_program_size()); + TEST_ASSERT_EQUAL(0, res); + } + } + + { + res = fs.mount(&bd); + TEST_ASSERT_NOT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_bad_mount_then_reformat() +{ + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_NOT_EQUAL(0, res); + + res = fs.reformat(&bd); + TEST_ASSERT_EQUAL(0, res); + + res = fs.unmount(); + TEST_ASSERT_EQUAL(0, res); + } + + res = bd.deinit(); + TEST_ASSERT_EQUAL(0, res); +} + +void test_good_mount_then_reformat() +{ + int res = bd.init(); + TEST_ASSERT_EQUAL(0, res); + + { + res = fs.mount(&bd); + TEST_ASSERT_EQUAL(0, res); + + res = fs.reformat(&bd); + 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("Test format", test_format), + Case("Test mount", test_mount), + Case("Test bad mount", test_bad_mount), + Case("Test bad mount than reformat", test_bad_mount_then_reformat), + Case("Test good mount than reformat", test_good_mount_then_reformat), +}; + +Specification specification(test_setup, cases); + +int main() +{ + return !Harness::run(specification); +} diff --git a/TESTS/filesystem_recovery/resilience/main.cpp b/TESTS/filesystem_recovery/resilience/main.cpp new file mode 100644 index 00000000000..802fb65a841 --- /dev/null +++ b/TESTS/filesystem_recovery/resilience/main.cpp @@ -0,0 +1,104 @@ +/* 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 "unity.h" +#include "utest.h" +#include "test_env.h" + +#include "atomic_usage.h" +#include "ObservingBlockDevice.h" + +using namespace utest::v1; + +// test configuration +#ifndef MBED_TEST_SIM_BLOCKDEVICE +#error [NOT_SUPPORTED] Simulation block device required for resilience tests +#endif + +#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 +#define MBED_TEST_BLOCK_COUNT 64 +#endif + +#ifndef MBED_TEST_CYCLES +#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 +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_SIM_BLOCKDEVICE) + + +/** + * 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() +{ + MBED_TEST_SIM_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(&slice, true); + + // Run check on every write operation + ObservingBlockDevice observer(&slice); + observer.attach(check_atomic_operations); + + // Perform operations + 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); + } + 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(MBED_TEST_TIMEOUT, "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/resilience_functional/main.cpp b/TESTS/filesystem_recovery/resilience_functional/main.cpp new file mode 100644 index 00000000000..f0ea4afe233 --- /dev/null +++ b/TESTS/filesystem_recovery/resilience_functional/main.cpp @@ -0,0 +1,119 @@ +/* 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 "unity.h" +#include "utest.h" +#include "test_env.h" + +#include "atomic_usage.h" +#include "ObservingBlockDevice.h" +#include "LittleFileSystem.h" + +#include + +using namespace utest::v1; + + +// test configuration +#ifndef MBED_TEST_BLOCKDEVICE +#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 + +#ifndef MBED_TEST_TIMEOUT +#define MBED_TEST_TIMEOUT 480 +#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(MBED_TEST_TIMEOUT, "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/filesystem_recovery/wear_leveling/main.cpp b/TESTS/filesystem_recovery/wear_leveling/main.cpp new file mode 100644 index 00000000000..a9b8973ce89 --- /dev/null +++ b/TESTS/filesystem_recovery/wear_leveling/main.cpp @@ -0,0 +1,120 @@ +/* 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 "unity.h" +#include "utest.h" +#include "test_env.h" + +#include "atomic_usage.h" +#include "ExhaustibleBlockDevice.h" +#include "SlicingBlockDevice.h" + +using namespace utest::v1; + +// test configuration +#ifndef MBED_TEST_SIM_BLOCKDEVICE +#error [NOT_SUPPORTED] Simulation block device required for wear leveling tests +#endif + +#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 +#define MBED_TEST_BLOCK_COUNT 64 +#endif + +#ifndef MBED_TEST_ERASE_CYCLES +#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 +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_SIM_BLOCKDEVICE) + + +static uint32_t test_wear_leveling_size(uint32_t block_count) +{ + MBED_TEST_SIM_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, 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; + 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(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); +} + +Case cases[] = { + Case("test wear leveling", test_wear_leveling), +}; + +utest::v1::status_t greentea_test_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(MBED_TEST_TIMEOUT, "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_retarget/dirs/main.cpp b/TESTS/filesystem_retarget/dirs/main.cpp new file mode 100644 index 00000000000..8793040512b --- /dev/null +++ b/TESTS/filesystem_retarget/dirs/main.cpp @@ -0,0 +1,684 @@ +/* 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 +#include "mbed.h" +#include "greentea-client/test_env.h" +#include "unity.h" +#include "utest.h" +#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 +#error [NOT_SUPPORTED] Non-volatile block device required +#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 480 +#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(ENOTEMPTY, 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(ENOTEMPTY, 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(ENOTEMPTY, 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 00000000000..120eff16128 --- /dev/null +++ b/TESTS/filesystem_retarget/files/main.cpp @@ -0,0 +1,449 @@ +/* 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" +#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 +#error [NOT_SUPPORTED] Non-volatile block device required +#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 480 +#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/interspersed/main.cpp b/TESTS/filesystem_retarget/interspersed/main.cpp new file mode 100644 index 00000000000..46ee79d4474 --- /dev/null +++ b/TESTS/filesystem_retarget/interspersed/main.cpp @@ -0,0 +1,407 @@ +/* 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" +#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 +#error [NOT_SUPPORTED] Non-volatile block device required +#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 480 +#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 00000000000..6e904fce209 --- /dev/null +++ b/TESTS/filesystem_retarget/seek/main.cpp @@ -0,0 +1,641 @@ +/* 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" +#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 +#error [NOT_SUPPORTED] Non-volatile block device required +#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 480 +#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/host_tests/unexpected_reset.py b/TESTS/host_tests/unexpected_reset.py new file mode 100644 index 00000000000..e85f3d10f01 --- /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", "reset_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 != "reset_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 + diff --git a/TESTS/util/Makefile b/TESTS/util/Makefile new file mode 100644 index 00000000000..78a0bec49df --- /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 00000000000..ba9862db28a --- /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 00000000000..9d6a1c1881c --- /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 00000000000..03ad693f5cc --- /dev/null +++ b/TESTS/util/replacements_mbed.yml @@ -0,0 +1,33 @@ +- ['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_([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 00000000000..bb4a1e004e2 --- /dev/null +++ b/TESTS/util/replacements_retarget.yml @@ -0,0 +1,37 @@ +- ['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_([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 00000000000..6bd1b98fe2f --- /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 00000000000..cdcaab9ff76 --- /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 00000000000..06c7f1157ed --- /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 00000000000..825bd40d463 --- /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 00000000000..fc98dd289e4 --- /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:]) diff --git a/littlefs/.gitignore b/littlefs/.gitignore new file mode 100644 index 00000000000..36f92cd81cb --- /dev/null +++ b/littlefs/.gitignore @@ -0,0 +1,9 @@ +# Compilation output +*.o +*.d +*.a + +# Testing things +blocks/ +lfs +test.c diff --git a/littlefs/.travis.yml b/littlefs/.travis.yml new file mode 100644 index 00000000000..b28ec1d01b6 --- /dev/null +++ b/littlefs/.travis.yml @@ -0,0 +1,229 @@ +# Environment variables +env: + global: + - CFLAGS=-Werror + +# Common test script +script: + # make sure example can at least compile + - sed -n '/``` c/,/```/{/```/d; p;}' README.md > test.c && + make all 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" + + # run tests + - make test QUIET=1 + + # run tests with a few different configurations + - make test QUIET=1 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 -DLFS_LOOKAHEAD=2048" + + - make clean test QUIET=1 CFLAGS+="-DLFS_NO_INTRINSICS" + + # compile and find the code size with the smallest configuration + - make clean size + OBJ="$(ls lfs*.o | tr '\n' ' ')" + CFLAGS+="-DLFS_NO_ASSERT -DLFS_NO_DEBUG -DLFS_NO_WARN -DLFS_NO_ERROR" + | tee sizes + + # update status if we succeeded, compare with master if possible + - | + if [ "$TRAVIS_TEST_RESULT" -eq 0 ] + then + CURR=$(tail -n1 sizes | awk '{print $1}') + PREV=$(curl -u $GEKY_BOT_STATUSES https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/master \ + | jq -re "select(.sha != \"$TRAVIS_COMMIT\") + | .statuses[] | select(.context == \"$STAGE/$NAME\").description + | capture(\"code size is (?[0-9]+)\").size" \ + || echo 0) + + STATUS="Passed, code size is ${CURR}B" + if [ "$PREV" -ne 0 ] + then + STATUS="$STATUS ($(python -c "print '%+.2f' % (100*($CURR-$PREV)/$PREV.0)")%)" + fi + fi + +# CI matrix +jobs: + include: + # native testing + - stage: test + env: + - STAGE=test + - NAME=littlefs-x86 + + # cross-compile with ARM (thumb mode) + - stage: test + env: + - STAGE=test + - NAME=littlefs-arm + - CC="arm-linux-gnueabi-gcc --static -mthumb" + - EXEC="qemu-arm" + install: + - sudo apt-get install gcc-arm-linux-gnueabi qemu-user + - arm-linux-gnueabi-gcc --version + - qemu-arm -version + + # cross-compile with PowerPC + - stage: test + env: + - STAGE=test + - NAME=littlefs-powerpc + - CC="powerpc-linux-gnu-gcc --static" + - EXEC="qemu-ppc" + install: + - sudo apt-get install gcc-powerpc-linux-gnu qemu-user + - powerpc-linux-gnu-gcc --version + - qemu-ppc -version + + # cross-compile with MIPS + - stage: test + env: + - STAGE=test + - NAME=littlefs-mips + - CC="mips-linux-gnu-gcc --static" + - EXEC="qemu-mips" + install: + - sudo add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial main universe" + - sudo apt-get -qq update + - sudo apt-get install gcc-mips-linux-gnu qemu-user + - mips-linux-gnu-gcc --version + - qemu-mips -version + + # self-host with littlefs-fuse for fuzz test + - stage: test + env: + - STAGE=test + - NAME=littlefs-fuse + install: + - sudo apt-get install libfuse-dev + - git clone --depth 1 https://github.com/geky/littlefs-fuse + - fusermount -V + - gcc --version + before_script: + # setup disk for littlefs-fuse + - 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 + script: + # self-host 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 test_files QUIET=1 + + # Automatically update releases + - stage: deploy + env: + - STAGE=deploy + - NAME=deploy + script: + # Find version defined in lfs.h + - LFS_VERSION=$(grep -ox '#define LFS_VERSION .*' lfs.h | cut -d ' ' -f3) + - LFS_VERSION_MAJOR=$((0xffff & ($LFS_VERSION >> 16))) + - LFS_VERSION_MINOR=$((0xffff & ($LFS_VERSION >> 0))) + # Grab latests patch from repo tags, default to 0, needs finagling to get past github's pagination api + - PREV_URL=https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs/tags/v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR. + - PREV_URL=$(curl -u "$GEKY_BOT_RELEASES" "$PREV_URL" -I + | sed -n '/^Link/{s/.*<\(.*\)>; rel="last"/\1/;p;q0};$q1' + || echo $PREV_URL) + - LFS_VERSION_PATCH=$(curl -u "$GEKY_BOT_RELEASES" "$PREV_URL" + | jq 'map(.ref | match("\\bv.*\\..*\\.(.*)$";"g") + .captures[].string | tonumber) | max + 1' + || echo 0) + # We have our new version + - LFS_VERSION="v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR.$LFS_VERSION_PATCH" + - echo "VERSION $LFS_VERSION" + - | + # Check that we're the most recent commit + CURRENT_COMMIT=$(curl -f -u "$GEKY_BOT_RELEASES" \ + https://api.github.com/repos/$TRAVIS_REPO_SLUG/commits/master \ + | jq -re '.sha') + if [ "$TRAVIS_COMMIT" == "$CURRENT_COMMIT" ] + then + # Create a simple tag + curl -f -u "$GEKY_BOT_RELEASES" -X POST \ + https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs \ + -d "{ + \"ref\": \"refs/tags/$LFS_VERSION\", + \"sha\": \"$TRAVIS_COMMIT\" + }" + # Minor release? + if [[ "$LFS_VERSION" == *.0 ]] + then + # Build release notes + PREV=$(git tag --sort=-v:refname -l "v*.0" | head -1) + if [ ! -z "$PREV" ] + then + echo "PREV $PREV" + CHANGES=$'### Changes\n\n'$( \ + git log --oneline $PREV.. --grep='^Merge' --invert-grep) + printf "CHANGES\n%s\n\n" "$CHANGES" + fi + # Create the release + curl -f -u "$GEKY_BOT_RELEASES" -X POST \ + https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases \ + -d "{ + \"tag_name\": \"$LFS_VERSION\", + \"name\": \"${LFS_VERSION%.0}\", + \"draft\": true, + \"body\": $(jq -sR '.' <<< "$CHANGES") + }" + fi + fi + +# Manage statuses +before_install: + - | + curl -u $GEKY_BOT_STATUSES -X POST \ + https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \ + -d "{ + \"context\": \"$STAGE/$NAME\", + \"state\": \"pending\", + \"description\": \"${STATUS:-In progress}\", + \"target_url\": \"https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$TRAVIS_JOB_ID\" + }" + +after_failure: + - | + curl -u $GEKY_BOT_STATUSES -X POST \ + https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \ + -d "{ + \"context\": \"$STAGE/$NAME\", + \"state\": \"failure\", + \"description\": \"${STATUS:-Failed}\", + \"target_url\": \"https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$TRAVIS_JOB_ID\" + }" + +after_success: + - | + curl -u $GEKY_BOT_STATUSES -X POST \ + https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \ + -d "{ + \"context\": \"$STAGE/$NAME\", + \"state\": \"success\", + \"description\": \"${STATUS:-Passed}\", + \"target_url\": \"https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$TRAVIS_JOB_ID\" + }" + +# Job control +stages: + - name: test + - name: deploy + if: branch = master AND type = push diff --git a/littlefs/DESIGN.md b/littlefs/DESIGN.md new file mode 100644 index 00000000000..04a119f2719 --- /dev/null +++ b/littlefs/DESIGN.md @@ -0,0 +1,1225 @@ +## The design of the little filesystem + +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 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 +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 interested in how this works. + +This leaves us with an interesting set of limitations that can be simplified +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 at any given time. + + Despite this state of things, there are very few embedded filesystems that + handle power loss in a reasonable manner, and most can become corrupted if + the user is unlucky enough. + +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 + 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 + as a whole. + +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. Here is 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 important and can actually increase wear. + +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 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 +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 +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 inappropriate +for embedded systems. + +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 +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 lose 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 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. + +## 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 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 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 new data to block 5, +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. + +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 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) +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 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. + +``` +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 backward. Each +block points to its predecessor, with the first block containing no pointers. +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. 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. + +``` +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 cost of O(n^2). Gah! A quadratic +runtime to just _read_ a file? That's awful. Keep in mind reading files is +usually the most common filesystem operation. + +To avoid this problem, the littlefs uses a multilayered linked-list. For +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. + +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 backward CTZ skip-list +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 |<-| data 1 |<-| data 2 |<-| data 3 |<-| data 4 |<-| data 5 | +| |<-| |--| |<-| |--| | | | +| |<-| |--| |--| |--| | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +The additional pointers allow us to navigate the data-structure on disk +much more efficiently than in a singly 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: +``` +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 | | data 1 |<-| data 2 | | data 3 | | data 4 |<-| data 5 | +| | | | | |<-| |--| | | | +| | | | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +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 | +| | | | | | | | | | | | +| |<-| |--| |--| |--| | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +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 puts the runtime at O(2 log n) = 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. + +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 log n). +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? 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. 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/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 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 +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) + +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 + +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 +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 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 +mind is to just use a for loop to sum together blocks until we reach our +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) + +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^2 log n). 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 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) + +where: +ctz(x) = the number of trailing bits that are 0 in x +popcount(x) = the number of bits that are 1 in x + +It's a bit bewildering that these two seemingly unrelated bitwise instructions +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 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 +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 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 +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. +Now we can solve for both the block index and offset from the file size in O(1). + +Here is what it might look like to update a file stored with a CTZ skip-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 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? + +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 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 because 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 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 +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. 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 +in a single block. Add entries that represent other nested directories. +Drop "." and ".." entries because 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 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. 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. + +``` + .--------. + |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, 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. + +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. 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: +``` + .--------. + |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; 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. Note that the deorphan step never needs to run in a read-only +filesystem. + +## 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|-. + | 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 | + | | + '--------' +``` + +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. + +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 +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. Because the condition of ending up with +two parents is rare, it's unlikely user-level code will be prepared. Just think +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 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. + +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 inelegantly 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. 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. + +## 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 perfectly 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 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'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 - 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. + +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 usual 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 +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 because they are updated most often. In this +situation, the littlefs is designed to simply move on to another set of +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). +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 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. + +## 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 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(n log n) reading +6. Blocks are allocated by scanning the filesystem for used blocks in a + fixed-size lookahead region that is 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 + 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. + +That's the little filesystem. Thanks for reading! + diff --git a/littlefs/LICENSE.md b/littlefs/LICENSE.md new file mode 100644 index 00000000000..ed69bea4743 --- /dev/null +++ b/littlefs/LICENSE.md @@ -0,0 +1,24 @@ +Copyright (c) 2017, Arm Limited. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. +- Neither the name of ARM nor the names of its contributors may be used to + endorse or promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/littlefs/Makefile b/littlefs/Makefile new file mode 100644 index 00000000000..17d3616cc9f --- /dev/null +++ b/littlefs/Makefile @@ -0,0 +1,69 @@ +TARGET = lfs.a +ifneq ($(wildcard test.c main.c),) +override TARGET = lfs +endif + +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_*)) + +SHELL = /bin/bash -o pipefail + +ifdef DEBUG +override CFLAGS += -O0 -g3 +else +override CFLAGS += -Os +endif +ifdef WORD +override CFLAGS += -m$(WORD) +endif +override CFLAGS += -I. +override CFLAGS += -std=c99 -Wall -pedantic +override CFLAGS += -Wshadow -Wunused-parameter -Wjump-misses-init -Wsign-compare + + +all: $(TARGET) + +asm: $(ASM) + +size: $(OBJ) + $(SIZE) -t $^ + +.SUFFIXES: +test: test_format test_dirs test_files test_seek test_truncate \ + test_interspersed test_alloc test_paths test_orphan test_move test_corrupt + @rm test.c +test_%: tests/test_%.sh + +ifdef QUIET + @./$< | sed -n '/^[-=]/p' +else + ./$< +endif + +-include $(DEP) + +lfs: $(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/littlefs/README.md b/littlefs/README.md new file mode 100644 index 00000000000..c7006588309 --- /dev/null +++ b/littlefs/README.md @@ -0,0 +1,192 @@ +## The little filesystem + +A little fail-safe filesystem designed for embedded systems. + +``` + | | | .---._____ + .-----. | | +--|o |---| littlefs | +--| |---| | + '-----' '----------' + | | | +``` + +**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 for systems that may have +random power failures. The littlefs has strong copy-on-write guarantees, and +storage on disk is always kept in a valid state. + +**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 cannot fit a full flash translation layer. + +## 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; + 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); + + // print the boot count + printf("boot_count: %d\n", boot_count); +} +``` + +## Usage + +Detailed documentation (or at least as much detail as is currently available) +can be found 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 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 +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 +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 committed to the +filesystem until sync or close is called on the file. + +## 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. + +In the configuration struct, the `prog` and `erase` function provided by the +user may return a `LFS_ERR_CORRUPT` error if the implementation already can +detect corrupt blocks. However, the wear leveling does not depend on the return +code of these functions, instead all data is read back and checked for +integrity. + +If your storage caches writes, make sure that the provided `sync` function +flushes all the data to memory and ensures that the next read fetches the data +from memory, otherwise data integrity can not be guaranteed. If the `write` +function does not perform caching, and therefore each `read` or `write` call +hits the memory, the `sync` function can simply return 0. + +## Reference material + +[DESIGN.md](DESIGN.md) - DESIGN.md contains a fully detailed dive into how +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. This can be useful for developing tooling. + +## 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 +``` + +## License + +The littlefs is provided under the [BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html) +license. See [LICENSE.md](LICENSE.md) for more information. Contributions to +this project are accepted under the same license. + +Individual files contain the following tag instead of the full license text. + + SPDX-License-Identifier: BSD-3-Clause + +This enables machine processing of license information based on the SPDX +License Identifiers that are here available: http://spdx.org/licenses/ + +## Related projects + +[Mbed OS](https://github.com/ARMmbed/mbed-os/tree/master/features/filesystem/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 +littlefs is available in Mbed OS as the [LittleFileSystem](https://os.mbed.com/docs/latest/reference/littlefilesystem.html) +class. + +[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 on 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). + +[mklfs](https://github.com/whitecatboard/Lua-RTOS-ESP32/tree/master/components/mklfs/src) - +A command line tool built by the [Lua RTOS](https://github.com/whitecatboard/Lua-RTOS-ESP32) +guys for making littlefs images from a host PC. Supports Windows, Mac OS, +and Linux. + +[SPIFFS](https://github.com/pellepl/spiffs) - Another excellent embedded +filesystem for NOR flash. As a more traditional logging filesystem with full +static wear-leveling, SPIFFS will likely outperform littlefs on small +memories such as the internal flash on microcontrollers. + +[Dhara](https://github.com/dlbeer/dhara) - An interesting NAND flash +translation layer designed for small MCUs. It offers static wear-leveling and +power-resilience with only a fixed O(|address|) pointer structure stored on +each block and in RAM. diff --git a/littlefs/SPEC.md b/littlefs/SPEC.md new file mode 100644 index 00000000000..74611a92818 --- /dev/null +++ b/littlefs/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 information on how littlefs +works, check out [DESIGN.md](DESIGN.md). + +``` + | | | .---._____ + .-----. | | +--|o |---| littlefs | +--| |---| | + '-----' '----------' + | | | +``` + +## Some important details + +- 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 + 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 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. + +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 because 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 plus entry length plus attribute length plus name length. + +**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, +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: +``` +(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", 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 +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 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. + +**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. 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 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 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 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 nonstandard 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 nonstandard 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/littlefs/emubd/.mbedignore b/littlefs/emubd/.mbedignore new file mode 100644 index 00000000000..904f0ed67c0 --- /dev/null +++ b/littlefs/emubd/.mbedignore @@ -0,0 +1 @@ +lfs_emubd.* diff --git a/littlefs/emubd/lfs_emubd.c b/littlefs/emubd/lfs_emubd.c new file mode 100644 index 00000000000..e44602c2f1b --- /dev/null +++ b/littlefs/emubd/lfs_emubd.c @@ -0,0 +1,247 @@ +/* + * Block device emulated on standard files + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "emubd/lfs_emubd.h" + +#include +#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 && errno != ENOENT) { + return -errno; + } + + if (errno == ENOENT) { + memset(&emu->stats, 0x0, sizeof(emu->stats)); + } else { + 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, "%" PRIx32, 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, "%" PRIx32, block); + + FILE *f = fopen(emu->path, "r+b"); + if (!f) { + return (errno == EACCES) ? 0 : -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, "%" PRIx32, block); + struct stat st; + int err = stat(emu->path, &st); + if (err && errno != ENOENT) { + return -errno; + } + + if (!err && S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode)) { + err = unlink(emu->path); + if (err) { + return -errno; + } + } + + if (err || (S_ISREG(st.st_mode) && (S_IWUSR & 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/littlefs/emubd/lfs_emubd.h b/littlefs/emubd/lfs_emubd.h new file mode 100644 index 00000000000..0fd43875fdf --- /dev/null +++ b/littlefs/emubd/lfs_emubd.h @@ -0,0 +1,87 @@ +/* + * Block device emulated on standard files + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_EMUBD_H +#define LFS_EMUBD_H + +#include "lfs.h" +#include "lfs_util.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + +// 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); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/littlefs/lfs.c b/littlefs/lfs.c new file mode 100644 index 00000000000..c3bbac9c807 --- /dev/null +++ b/littlefs/lfs.c @@ -0,0 +1,2595 @@ +/* + * The little filesystem + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "lfs.h" +#include "lfs_util.h" + +#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; + LFS_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 inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) { + // do not zero, cheaper if cache is readonly or only going to be + // written with identical data (during relocates) + (void)lfs; + rcache->block = 0xffffffff; +} + +static inline void lfs_cache_zero(lfs_t *lfs, lfs_cache_t *pcache) { + // zero to avoid information leak + memset(pcache->buffer, 0xff, lfs->cfg->prog_size); + pcache->block = 0xffffffff; +} + +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; + } + } + + lfs_cache_zero(lfs, pcache); + } + + 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; + LFS_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 + LFS_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_cache_drop(lfs, &lfs->rcache); + + 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_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); + + +/// Block allocator /// +static int lfs_alloc_lookahead(void *p, lfs_block_t block) { + lfs_t *lfs = p; + + lfs_block_t off = ((block - lfs->free.off) + + lfs->cfg->block_count) % lfs->cfg->block_count; + + if (off < lfs->free.size) { + lfs->free.buffer[off / 32] |= 1U << (off % 32); + } + + return 0; +} + +static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { + while (true) { + while (lfs->free.i != lfs->free.size) { + lfs_block_t off = lfs->free.i; + lfs->free.i += 1; + lfs->free.ack -= 1; + + if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) { + // found a free block + *block = (lfs->free.off + off) % lfs->cfg->block_count; + + // eagerly find next off so an alloc ack can + // discredit old lookahead blocks + while (lfs->free.i != lfs->free.size && + (lfs->free.buffer[lfs->free.i / 32] + & (1U << (lfs->free.i % 32)))) { + lfs->free.i += 1; + lfs->free.ack -= 1; + } + + return 0; + } + } + + // check if we have looked at all blocks since last ack + if (lfs->free.ack == 0) { + LFS_WARN("No more free space %" PRIu32, + lfs->free.i + lfs->free.off); + return LFS_ERR_NOSPC; + } + + lfs->free.off = (lfs->free.off + lfs->free.size) + % lfs->cfg->block_count; + lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->free.ack); + lfs->free.i = 0; + + // find mask of free blocks from tree + memset(lfs->free.buffer, 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.ack = lfs->cfg->block_count; +} + + +/// Endian swapping functions /// +static void lfs_dir_fromle32(struct lfs_disk_dir *d) { + d->rev = lfs_fromle32(d->rev); + d->size = lfs_fromle32(d->size); + d->tail[0] = lfs_fromle32(d->tail[0]); + d->tail[1] = lfs_fromle32(d->tail[1]); +} + +static void lfs_dir_tole32(struct lfs_disk_dir *d) { + d->rev = lfs_tole32(d->rev); + d->size = lfs_tole32(d->size); + d->tail[0] = lfs_tole32(d->tail[0]); + d->tail[1] = lfs_tole32(d->tail[1]); +} + +static void lfs_entry_fromle32(struct lfs_disk_entry *d) { + d->u.dir[0] = lfs_fromle32(d->u.dir[0]); + d->u.dir[1] = lfs_fromle32(d->u.dir[1]); +} + +static void lfs_entry_tole32(struct lfs_disk_entry *d) { + d->u.dir[0] = lfs_tole32(d->u.dir[0]); + d->u.dir[1] = lfs_tole32(d->u.dir[1]); +} + +static void lfs_superblock_fromle32(struct lfs_disk_superblock *d) { + d->root[0] = lfs_fromle32(d->root[0]); + d->root[1] = lfs_fromle32(d->root[1]); + d->block_size = lfs_fromle32(d->block_size); + d->block_count = lfs_fromle32(d->block_count); + d->version = lfs_fromle32(d->version); +} + +static void lfs_superblock_tole32(struct lfs_disk_superblock *d) { + d->root[0] = lfs_tole32(d->root[0]); + d->root[1] = lfs_tole32(d->root[1]); + d->block_size = lfs_tole32(d->block_size); + d->block_count = lfs_tole32(d->block_count); + d->version = lfs_tole32(d->version); +} + + +/// 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 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++) { + 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 && err != LFS_ERR_CORRUPT) { + return err; + } + + if (err != LFS_ERR_CORRUPT) { + dir->d.rev = lfs_fromle32(dir->d.rev); + } + + // set defaults + dir->d.rev += 1; + dir->d.size = sizeof(dir->d)+4; + 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 + 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)); + lfs_dir_fromle32(&test); + if (err) { + if (err == LFS_ERR_CORRUPT) { + continue; + } + 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_dir_tole32(&test); + lfs_crc(&crc, &test, sizeof(test)); + lfs_dir_fromle32(&test); + err = lfs_bd_crc(lfs, tpair[i], sizeof(test), + (0x7fffffff & test.size) - sizeof(test), &crc); + if (err) { + if (err == LFS_ERR_CORRUPT) { + continue; + } + 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 %" PRIu32 " %" PRIu32 , + 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) { + // 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; + } + + const lfs_block_t oldpair[2] = {dir->pair[0], dir->pair[1]}; + bool relocated = false; + + while (true) { + if (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_dir_tole32(&dir->d); + lfs_crc(&crc, &dir->d, sizeof(dir->d)); + err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d)); + lfs_dir_fromle32(&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); + 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; + 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; + } + } + + crc = lfs_tole32(crc); + err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4); + crc = lfs_fromle32(crc); + 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 + uint32_t ncrc = 0xffffffff; + err = lfs_bd_crc(lfs, dir->pair[0], 0, + (0x7fffffff & dir->d.size)-4, &ncrc); + if (err) { + return err; + } + + if (ncrc != crc) { + goto relocate; + } + } + + break; +relocate: + //commit was corrupted + LFS_DEBUG("Bad block at %" PRIu32, dir->pair[0]); + + // drop caches and prepare to relocate block + relocated = true; + lfs_cache_drop(lfs, &lfs->pcache); + + // can't relocate superblock, filesystem is now frozen + if (lfs_paircmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) { + LFS_WARN("Superblock %" PRIu32 " has become unwritable", + oldpair[0]); + return LFS_ERR_CORRUPT; + } + + // relocate half of pair + int err = lfs_alloc(lfs, &dir->pair[0]); + if (err) { + return err; + } + } + + if (relocated) { + // update references if we relocated + LFS_DEBUG("Relocating %" PRIu32 " %" PRIu32 " to %" PRIu32 " %" PRIu32, + oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]); + 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; +} + +static int lfs_dir_update(lfs_t *lfs, lfs_dir_t *dir, + lfs_entry_t *entry, const void *data) { + lfs_entry_tole32(&entry->d); + int err = 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.nlen, data, entry->d.nlen} + }, data ? 2 : 1); + lfs_entry_fromle32(&entry->d); + return err; +} + +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 + lfs_entry_size(entry) <= lfs->cfg->block_size) { + entry->off = dir->d.size - 4; + + lfs_entry_tole32(&entry->d); + int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, 0, &entry->d, sizeof(entry->d)}, + {entry->off, 0, data, entry->d.nlen} + }, 2); + lfs_entry_fromle32(&entry->d); + return err; + } + + // we need to allocate a new dir block + if (!(0x80000000 & dir->d.size)) { + lfs_dir_t olddir = *dir; + int err = lfs_dir_alloc(lfs, dir); + if (err) { + return err; + } + + dir->d.tail[0] = olddir.d.tail[0]; + dir->d.tail[1] = olddir.d.tail[1]; + entry->off = dir->d.size - 4; + lfs_entry_tole32(&entry->d); + err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, 0, &entry->d, sizeof(entry->d)}, + {entry->off, 0, data, entry->d.nlen} + }, 2); + lfs_entry_fromle32(&entry->d); + if (err) { + return err; + } + + olddir.d.size |= 0x80000000; + olddir.d.tail[0] = dir->pair[0]; + olddir.d.tail[1] = dir->pair[1]; + return lfs_dir_commit(lfs, &olddir, 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) { + // 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; + int res = lfs_pred(lfs, dir->pair, &pdir); + if (res < 0) { + return res; + } + + 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); + } + } + + // 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); + } + } + } + + 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) { + 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)); + lfs_entry_fromle32(&entry->d); + if (err) { + return err; + } + + entry->off = dir->off; + dir->off += lfs_entry_size(entry); + dir->pos += lfs_entry_size(entry); + 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; + entry->d.type = LFS_TYPE_DIR; + entry->d.elen = sizeof(entry->d) - 4; + entry->d.alen = 0; + entry->d.nlen = 0; + entry->d.u.dir[0] = lfs->root[0]; + entry->d.u.dir[1] = lfs->root[1]; + + 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; + } + + // found path + if (pathname[0] == '\0') { + return 0; + } + + // update what we've found + *path = pathname; + + // 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; + } + + // find entry matching name + while (true) { + err = lfs_dir_next(lfs, dir, entry); + if (err) { + return err; + } + + if (((0x7f & entry->d.type) != LFS_TYPE_REG && + (0x7f & entry->d.type) != LFS_TYPE_DIR) || + entry->d.nlen != pathlen) { + continue; + } + + int res = lfs_bd_cmp(lfs, dir->pair[0], + entry->off + 4+entry->d.elen+entry->d.alen, + pathname, pathlen); + if (res < 0) { + return res; + } + + // found match + if (res) { + break; + } + } + + // check that entry has not been moved + if (!lfs->moving && 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; + } + + // to next name + pathname += pathlen; + } +} + + +/// 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; + lfs_entry_t entry; + int err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err != LFS_ERR_NOENT || strchr(path, '/') != NULL) { + return err ? err : LFS_ERR_EXIST; + } + + // 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.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]; + + 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]; + + lfs_entry_t entry; + int 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); + + // 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) { + // 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; +} + +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 ((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; + if (info->type == LFS_TYPE_REG) { + info->size = entry.d.u.file.size; + } + + 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; + } + + 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; + } + + 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) { + (void)lfs; + 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_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_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 = 0xffffffff; + *off = 0; + return 0; + } + + 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( + lfs_npw2(current-target+1) - 1, + lfs_ctz(current)); + + int err = lfs_cache_read(lfs, rcache, pcache, head, 4*skip, &head, 4); + head = lfs_fromle32(head); + if (err) { + return err; + } + + LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); + current -= 1 << skip; + } + + *block = head; + *off = pos; + return 0; +} + +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_block_t *block, lfs_off_t *off) { + while (true) { + // go ahead and grab a block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + LFS_ASSERT(nblock >= 2 && nblock <= lfs->cfg->block_count); + + if (true) { + err = lfs_bd_erase(lfs, nblock); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (size == 0) { + *block = nblock; + *off = 0; + return 0; + } + + size -= 1; + lfs_off_t index = lfs_ctz_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; + err = lfs_cache_read(lfs, rcache, NULL, + head, i, &data, 1); + if (err) { + return err; + } + + err = lfs_cache_prog(lfs, pcache, rcache, + nblock, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + *block = nblock; + *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++) { + head = lfs_tole32(head); + err = lfs_cache_prog(lfs, pcache, rcache, + nblock, 4*i, &head, 4); + head = lfs_fromle32(head); + 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); + head = lfs_fromle32(head); + if (err) { + return err; + } + } + + LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); + } + + *block = nblock; + *off = 4*skips; + return 0; + } + +relocate: + LFS_DEBUG("Bad block at %" PRIu32, nblock); + + // just clear cache and try a new block + lfs_cache_drop(lfs, &lfs->pcache); + } +} + +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) { + if (size == 0) { + return 0; + } + + lfs_off_t index = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); + + while (true) { + int err = cb(data, head); + if (err) { + return err; + } + + if (index == 0) { + return 0; + } + + lfs_block_t heads[2]; + int count = 2 - (index & 1); + err = lfs_cache_read(lfs, rcache, pcache, head, 0, &heads, count*4); + heads[0] = lfs_fromle32(heads[0]); + heads[1] = lfs_fromle32(heads[1]); + if (err) { + return err; + } + + for (int i = 0; i < count-1; i++) { + err = cb(data, heads[i]); + if (err) { + return err; + } + } + + head = heads[count-1]; + index -= count; + } +} + + +/// Top level file operations /// +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *cfg) { + // 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; + lfs_entry_t entry; + int err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err && (err != LFS_ERR_NOENT || strchr(path, '/') != NULL)) { + 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.elen = sizeof(entry.d) - 4; + entry.d.alen = 0; + entry.d.nlen = strlen(path); + entry.d.u.file.head = 0xffffffff; + 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_EXIST; + } + + // setup file struct + file->cfg = cfg; + 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) { + if (file->size != 0) { + file->flags |= LFS_F_DIRTY; + } + file->head = 0xffffffff; + file->size = 0; + } + + // allocate buffer if needed + file->cache.block = 0xffffffff; + if (file->cfg && file->cfg->buffer) { + file->cache.buffer = file->cfg->buffer; + } else if (lfs->cfg->file_buffer) { + if (lfs->files) { + // already in use + return LFS_ERR_NOMEM; + } + file->cache.buffer = lfs->cfg->file_buffer; + } else if ((file->flags & 3) == LFS_O_RDONLY) { + file->cache.buffer = lfs_malloc(lfs->cfg->read_size); + if (!file->cache.buffer) { + return LFS_ERR_NOMEM; + } + } else { + file->cache.buffer = lfs_malloc(lfs->cfg->prog_size); + if (!file->cache.buffer) { + return LFS_ERR_NOMEM; + } + } + + // zero to avoid information leak + lfs_cache_drop(lfs, &file->cache); + if ((file->flags & 3) != LFS_O_RDONLY) { + lfs_cache_zero(lfs, &file->cache); + } + + // add to list of files + file->next = lfs->files; + lfs->files = file; + + return 0; +} + +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags) { + return lfs_file_opencfg(lfs, file, path, flags, NULL); +} + +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 (!(file->cfg && file->cfg->buffer) && !lfs->cfg->file_buffer) { + lfs_free(file->cache.buffer); + } + + return err; +} + +static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { +relocate: + LFS_DEBUG("Bad block at %" PRIu32, 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_cache_zero(lfs, &lfs->pcache); + + 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 + lfs_cache_drop(lfs, &file->cache); + 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_cache_drop(lfs, &lfs->rcache); + + 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) { + lfs_cache_drop(lfs, &orig.cache); + lfs_cache_drop(lfs, &lfs->rcache); + } + } + + // 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) && + !(file->flags & LFS_F_ERRED) && + !lfs_pairisnull(file->pair)) { + // update dir entry + lfs_dir_t cwd; + 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)); + lfs_entry_fromle32(&entry.d); + if (err) { + return err; + } + + LFS_ASSERT(entry.d.type == LFS_TYPE_REG); + 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_BADF; + } + + if (file->flags & LFS_F_WRITING) { + // flush out any writes + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + } + + if (file->pos >= file->size) { + // eof if past end + return 0; + } + + 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_ctz_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_BADF; + } + + 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; + } + + if (file->pos + size > LFS_FILE_MAX) { + // larger than file limit? + return LFS_ERR_FBIG; + } + + 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) && file->pos > 0) { + // find out which block we're extending from + int err = lfs_ctz_find(lfs, &file->cache, NULL, + file->head, file->size, + file->pos-1, &file->block, &file->off); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + + // mark cache as dirty since we may have read data into it + lfs_cache_zero(lfs, &file->cache); + } + + // extend file with new blocks + lfs_alloc_ack(lfs); + int err = lfs_ctz_extend(lfs, &lfs->rcache, &file->cache, + file->block, file->pos, + &file->block, &file->off); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + + file->flags |= LFS_F_WRITING; + } + + // 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; + } + file->flags |= LFS_F_ERRED; + return err; + } + + break; +relocate: + err = lfs_file_relocate(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + + lfs_alloc_ack(lfs); + } + + file->flags &= ~LFS_F_ERRED; + 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; + } + + // find new pos + lfs_soff_t npos = file->pos; + if (whence == LFS_SEEK_SET) { + npos = off; + } else if (whence == LFS_SEEK_CUR) { + npos = file->pos + off; + } else if (whence == LFS_SEEK_END) { + npos = file->size + off; + } + + if (npos < 0 || npos > LFS_FILE_MAX) { + // file position out of range + return LFS_ERR_INVAL; + } + + // update pos + file->pos = npos; + return npos; +} + +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { + if ((file->flags & 3) == LFS_O_RDONLY) { + return LFS_ERR_BADF; + } + + lfs_off_t oldsize = lfs_file_size(lfs, file); + if (size < oldsize) { + // need to flush since directly changing metadata + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + // lookup new head in ctz skip list + err = lfs_ctz_find(lfs, &file->cache, NULL, + file->head, file->size, + size, &file->head, &(lfs_off_t){0}); + if (err) { + return err; + } + + file->size = size; + file->flags |= LFS_F_DIRTY; + } else if (size > oldsize) { + lfs_off_t pos = file->pos; + + // flush+seek if not already at end + if (file->pos != oldsize) { + int err = lfs_file_seek(lfs, file, 0, LFS_SEEK_END); + if (err < 0) { + return err; + } + } + + // fill with zeros + while (file->pos < size) { + lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + return res; + } + } + + // restore pos + int err = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET); + if (err < 0) { + return err; + } + } + + return 0; +} + +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { + (void)lfs; + 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) { + (void)lfs; + if (file->flags & LFS_F_WRITING) { + return lfs_max(file->pos, file->size); + } else { + return file->size; + } +} + + +/// General fs operations /// +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { + lfs_dir_t cwd; + lfs_entry_t entry; + int 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; + } + + if (lfs_paircmp(entry.d.u.dir, lfs->root) == 0) { + strcpy(info->name, "/"); + } else { + 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; + } + } + + return 0; +} + +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; + lfs_entry_t entry; + int 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 + 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_NOTEMPTY; + } + } + + // remove the entry + err = lfs_dir_remove(lfs, &cwd, &entry); + if (err) { + return err; + } + + // if we were a directory, find pred, replace tail + if (entry.d.type == LFS_TYPE_DIR) { + int res = lfs_pred(lfs, dir.pair, &cwd); + if (res < 0) { + return res; + } + + LFS_ASSERT(res); // must have pred + cwd.d.tail[0] = dir.d.tail[0]; + cwd.d.tail[1] = dir.d.tail[1]; + + err = lfs_dir_commit(lfs, &cwd, NULL, 0); + if (err) { + return err; + } + } + + return 0; +} + +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; + lfs_entry_t oldentry; + int err = lfs_dir_find(lfs, &oldcwd, &oldentry, &(const char *){oldpath}); + if (err) { + return err; + } + + // mark as moving + oldentry.d.type |= 0x80; + err = lfs_dir_update(lfs, &oldcwd, &oldentry, NULL); + if (err) { + return err; + } + + // allocate new entry + lfs_dir_t newcwd; + lfs_entry_t preventry; + err = lfs_dir_find(lfs, &newcwd, &preventry, &newpath); + if (err && (err != LFS_ERR_NOENT || strchr(newpath, '/') != NULL)) { + return err; + } + + // must have same type + bool prevexists = (err != LFS_ERR_NOENT); + if (prevexists && preventry.d.type != (0x7f & oldentry.d.type)) { + return LFS_ERR_ISDIR; + } + + 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 + 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_NOTEMPTY; + } + } + + // move to new location + lfs_entry_t newentry = preventry; + newentry.d = oldentry.d; + newentry.d.type &= ~0x80; + newentry.d.nlen = strlen(newpath); + + if (prevexists) { + err = lfs_dir_update(lfs, &newcwd, &newentry, newpath); + if (err) { + return err; + } + } else { + err = lfs_dir_append(lfs, &newcwd, &newentry, newpath); + if (err) { + return err; + } + } + + // fetch old pair again in case dir block changed + lfs->moving = true; + err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath); + if (err) { + return err; + } + lfs->moving = false; + + // remove old entry + err = lfs_dir_remove(lfs, &oldcwd, &oldentry); + if (err) { + return err; + } + + // if we were a directory, find pred, replace tail + if (prevexists && preventry.d.type == LFS_TYPE_DIR) { + int res = lfs_pred(lfs, dir.pair, &newcwd); + if (res < 0) { + return res; + } + + LFS_ASSERT(res); // must have pred + newcwd.d.tail[0] = dir.d.tail[0]; + newcwd.d.tail[1] = dir.d.tail[1]; + + err = lfs_dir_commit(lfs, &newcwd, NULL, 0); + if (err) { + return err; + } + } + + return 0; +} + + +/// Filesystem operations /// +static void lfs_deinit(lfs_t *lfs) { + // free allocated memory + if (!lfs->cfg->read_buffer) { + lfs_free(lfs->rcache.buffer); + } + + if (!lfs->cfg->prog_buffer) { + lfs_free(lfs->pcache.buffer); + } + + if (!lfs->cfg->lookahead_buffer) { + lfs_free(lfs->free.buffer); + } +} + +static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { + lfs->cfg = cfg; + + // setup read cache + if (lfs->cfg->read_buffer) { + lfs->rcache.buffer = lfs->cfg->read_buffer; + } else { + lfs->rcache.buffer = lfs_malloc(lfs->cfg->read_size); + if (!lfs->rcache.buffer) { + goto cleanup; + } + } + + // setup program cache + if (lfs->cfg->prog_buffer) { + lfs->pcache.buffer = lfs->cfg->prog_buffer; + } else { + lfs->pcache.buffer = lfs_malloc(lfs->cfg->prog_size); + if (!lfs->pcache.buffer) { + goto cleanup; + } + } + + // zero to avoid information leaks + lfs_cache_zero(lfs, &lfs->pcache); + lfs_cache_drop(lfs, &lfs->rcache); + + // setup lookahead, round down to nearest 32-bits + LFS_ASSERT(lfs->cfg->lookahead % 32 == 0); + LFS_ASSERT(lfs->cfg->lookahead > 0); + if (lfs->cfg->lookahead_buffer) { + lfs->free.buffer = lfs->cfg->lookahead_buffer; + } else { + lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead/8); + if (!lfs->free.buffer) { + goto cleanup; + } + } + + // check that program and read sizes are multiples of the block size + LFS_ASSERT(lfs->cfg->prog_size % lfs->cfg->read_size == 0); + LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->prog_size == 0); + + // check that the block size is large enough to fit ctz pointers + LFS_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; + lfs->files = NULL; + lfs->dirs = NULL; + lfs->deorphaned = false; + lfs->moving = false; + + return 0; + +cleanup: + lfs_deinit(lfs); + return LFS_ERR_NOMEM; +} + +int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { + int err = 0; + if (true) { + err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + // create free lookahead + memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8); + lfs->free.off = 0; + lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count); + lfs->free.i = 0; + lfs_alloc_ack(lfs); + + // create superblock dir + lfs_dir_t superdir; + err = lfs_dir_alloc(lfs, &superdir); + if (err) { + goto cleanup; + } + + // write root directory + lfs_dir_t root; + err = lfs_dir_alloc(lfs, &root); + if (err) { + goto cleanup; + } + + err = lfs_dir_commit(lfs, &root, NULL, 0); + if (err) { + goto cleanup; + } + + 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.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4, + .d.nlen = sizeof(superblock.d.magic), + .d.version = LFS_DISK_VERSION, + .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 + lfs_superblock_tole32(&superblock.d); + bool valid = false; + for (int i = 0; i < 2; i++) { + 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) { + goto cleanup; + } + + valid = valid || !err; + } + + if (!valid) { + err = LFS_ERR_CORRUPT; + goto cleanup; + } + + // sanity check that fetch works + err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + + lfs_alloc_ack(lfs); + } + +cleanup: + lfs_deinit(lfs); + return err; +} + +int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { + int err = 0; + if (true) { + err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + // setup free lookahead + lfs->free.off = 0; + lfs->free.size = 0; + lfs->free.i = 0; + lfs_alloc_ack(lfs); + + // 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_ERR_CORRUPT) { + goto cleanup; + } + + if (!err) { + err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d), + &superblock.d, sizeof(superblock.d)); + lfs_superblock_fromle32(&superblock.d); + if (err) { + goto cleanup; + } + + lfs->root[0] = superblock.d.root[0]; + lfs->root[1] = superblock.d.root[1]; + if (lfs_paircmp(lfs->root, dir.d.tail) != 0) { + err = LFS_ERR_CORRUPT; + goto cleanup; + } + } + + if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { + LFS_ERROR("Invalid superblock at %d %d", 0, 1); + err = LFS_ERR_CORRUPT; + goto cleanup; + } + + uint16_t major_version = (0xffff & (superblock.d.version >> 16)); + uint16_t minor_version = (0xffff & (superblock.d.version >> 0)); + if ((major_version != LFS_DISK_VERSION_MAJOR || + minor_version > LFS_DISK_VERSION_MINOR)) { + LFS_ERROR("Invalid version %d.%d", major_version, minor_version); + err = LFS_ERR_INVAL; + goto cleanup; + } + + // verify that no metadata pairs are corrupt + while (!lfs_pairisnull(dir.d.tail)) { + err = lfs_dir_fetch(lfs, &dir, dir.d.tail); + if (err) { + goto cleanup; + } + } + + // succuessfully mounted + return 0; + } + +cleanup: + lfs_deinit(lfs); + return err; +} + +int lfs_unmount(lfs_t *lfs) { + lfs_deinit(lfs); + return 0; +} + + +/// 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) { + err = lfs_bd_read(lfs, dir.pair[0], dir.off, + &entry.d, sizeof(entry.d)); + lfs_entry_fromle32(&entry.d); + if (err) { + return err; + } + + dir.off += lfs_entry_size(&entry); + if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) { + err = lfs_ctz_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_ctz_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_ctz_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; + } + + 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) { + err = lfs_dir_next(lfs, parent, entry); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + if (((0x70 & entry->d.type) == (0x70 & LFS_TYPE_DIR)) && + lfs_paircmp(entry->d.u.dir, dir) == 0) { + return true; + } + } + } + + 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)) { + err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail); + if (err) { + return err; + } + + while (true) { + 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 + 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 %" PRIu32 " %" PRIu32, + 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[1] = newpair[1]; + + 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 = {.d.size = 0x80000000}; + lfs_dir_t cwd = {.d.tail[0] = 0, .d.tail[1] = 1}; + + // iterate over all directory directory entries + for (lfs_size_t i = 0; i < lfs->cfg->block_count; i++) { + if (lfs_pairisnull(cwd.d.tail)) { + return 0; + } + + int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail); + if (err) { + return err; + } + + // check head blocks for orphans + 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("Found orphan %" PRIu32 " %" PRIu32, + pdir.d.tail[0], pdir.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) { + return err; + } + + return 0; + } + + if (!lfs_pairsync(entry.d.u.dir, pdir.d.tail)) { + // we have desynced + LFS_DEBUG("Found desync %" PRIu32 " %" PRIu32, + 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; + } + + return 0; + } + } + + // check entries for moves + lfs_entry_t entry; + while (true) { + 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 %" PRIu32 " %" PRIu32, + entry.d.u.dir[0], entry.d.u.dir[1]); + err = lfs_dir_remove(lfs, &cwd, &entry); + if (err) { + return err; + } + } else { + LFS_DEBUG("Found partial move %" PRIu32 " %" PRIu32, + entry.d.u.dir[0], entry.d.u.dir[1]); + entry.d.type &= ~0x80; + err = lfs_dir_update(lfs, &cwd, &entry, NULL); + if (err) { + return err; + } + } + } + } + + memcpy(&pdir, &cwd, sizeof(pdir)); + } + + // If we reached here, we have more directory pairs than blocks in the + // filesystem... So something must be horribly wrong + return LFS_ERR_CORRUPT; +} diff --git a/littlefs/lfs.h b/littlefs/lfs.h new file mode 100644 index 00000000000..58ba9548bcd --- /dev/null +++ b/littlefs/lfs.h @@ -0,0 +1,501 @@ +/* + * The little filesystem + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_H +#define LFS_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/// Version info /// + +// Software library version +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_VERSION 0x00010007 +#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) +#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) + +// Version of On-disk data structures +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_DISK_VERSION 0x00010001 +#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) +#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) + + +/// 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 + +// Max file size in bytes +#ifndef LFS_FILE_MAX +#define LFS_FILE_MAX 2147483647 +#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_EXIST = -17, // Entry already exists + LFS_ERR_NOTDIR = -20, // Entry is not a dir + LFS_ERR_ISDIR = -21, // Entry is a dir + LFS_ERR_NOTEMPTY = -39, // Dir is not empty + LFS_ERR_BADF = -9, // Bad file number + LFS_ERR_FBIG = -27, // File too large + 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 = 0x2e, +}; + +// 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 + LFS_F_ERRED = 0x80000, // An error occurred during write +}; + +// 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. + // May 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. + // May return LFS_ERR_CORRUPT if the block should be considered bad. + 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. + // Must be a multiple of the read size. + 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. + // Must be a multiple of the program size. + 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; +}; + +// Optional configuration provided during lfs_file_opencfg +struct lfs_file_config { + // Optional, statically allocated buffer for files. Must be program sized. + // If NULL, malloc will be used by default. + void *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 elen; + uint8_t alen; + uint8_t nlen; + 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; + + const struct lfs_file_config *cfg; + 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 { + struct lfs_dir *next; + 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 elen; + uint8_t alen; + uint8_t nlen; + 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 off; + lfs_block_t size; + lfs_block_t i; + lfs_block_t ack; + uint32_t *buffer; +} lfs_free_t; + +// The littlefs type +typedef struct lfs { + const struct lfs_config *cfg; + + lfs_block_t root[2]; + lfs_file_t *files; + lfs_dir_t *dirs; + + lfs_cache_t rcache; + lfs_cache_t pcache; + + lfs_free_t free; + bool deorphaned; + bool moving; +} 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. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// 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. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// 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. +// +// 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); + +// Open a file with extra configuration +// +// 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. +// +// The config struct provides additional config options per file as described +// above. The config struct must be allocated while the file is open, and the +// config struct must be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *config); + +// 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); + +// Truncates the size of the file to the specified size +// +// Returns a negative error code on failure. +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); + +// 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 occurred 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); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/littlefs/lfs_util.c b/littlefs/lfs_util.c new file mode 100644 index 00000000000..113632b66bc --- /dev/null +++ b/littlefs/lfs_util.c @@ -0,0 +1,28 @@ +/* + * lfs util functions + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "lfs_util.h" + + +// Only compile if user does not provide custom config +#ifndef LFS_CONFIG +#ifndef __MBED__ +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]; + } +} +#endif +#endif \ No newline at end of file diff --git a/littlefs/lfs_util.h b/littlefs/lfs_util.h new file mode 100644 index 00000000000..50e25daa7fa --- /dev/null +++ b/littlefs/lfs_util.h @@ -0,0 +1,214 @@ +/* + * lfs utility functions + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_UTIL_H +#define LFS_UTIL_H + +// Users can override lfs_util.h with their own configuration by defining +// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h). +// +// If LFS_CONFIG is used, none of the default utils will be emitted and must be +// provided by the config file. To start I would suggest copying lfs_util.h and +// modifying as needed. +#ifdef LFS_CONFIG +#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) +#define LFS_STRINGIZE2(x) #x +#include LFS_STRINGIZE(LFS_CONFIG) +#else + +// System includes +#include +#include +#include + +#ifndef LFS_NO_MALLOC +#include +#endif +#ifndef LFS_NO_ASSERT +#include +#endif +#if !defined(LFS_NO_INFO) || !defined(LFS_NO_DEBUG) || !defined(LFS_NO_WARN) || !defined(LFS_NO_ERROR) +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + + +// Macros, may be replaced by system specific wrappers. Arguments to these +// macros must not have side-effects as the macros can be removed for a smaller +// code footprint + +#ifdef __MBED__ +#include "mbed_debug.h" +#include "mbed_assert.h" +#else +#define MBED_LFS_ENABLE_INFO false +#define MBED_LFS_ENABLE_DEBUG true +#define MBED_LFS_ENABLE_WARN true +#define MBED_LFS_ENABLE_ERROR true +#define MBED_LFS_ENABLE_ASSERT true +#define MBED_LFS_INTRINSICS true +#endif + +// Logging functions +#if !defined(LFS_NO_INFO) && MBED_LFS_ENABLE_INFO +#define LFS_INFO(fmt, ...) printf("lfs info:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#elif !defined(LFS_NO_INFO) && !defined(MBED_LFS_ENABLE_INFO) +#define LFS_INFO(fmt, ...) debug("lfs info:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +#define LFS_INFO(fmt, ...) +#endif + +#if !defined(LFS_NO_DEBUG) && MBED_LFS_ENABLE_DEBUG +#define LFS_DEBUG(fmt, ...) printf("lfs debug:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#elif !defined(LFS_NO_DEBUG) && !defined(MBED_LFS_ENABLE_DEBUG) +#define LFS_DEBUG(fmt, ...) debug("lfs debug:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +#define LFS_DEBUG(fmt, ...) +#endif + +#if !defined(LFS_NO_WARN) && MBED_LFS_ENABLE_WARN +#define LFS_WARN(fmt, ...) printf("lfs warn:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#elif !defined(LFS_NO_WARN) && !defined(MBED_LFS_ENABLE_WARN) +#define LFS_WARN(fmt, ...) debug("lfs warn:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +#define LFS_WARN(fmt, ...) +#endif + +#if !defined(LFS_NO_ERROR) && MBED_LFS_ENABLE_ERROR +#define LFS_ERROR(fmt, ...) printf("lfs error:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#elif !defined(LFS_NO_ERROR) && !defined(MBED_LFS_ENABLE_ERROR) +#define LFS_ERROR(fmt, ...) debug("lfs error:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +#define LFS_ERROR(fmt, ...) +#endif + +// Runtime assertions +#if !defined(LFS_NO_ASSERT) && MBED_LFS_ENABLE_ASSERT +#define LFS_ASSERT(test) assert(test) +#elif !defined(LFS_NO_ASSERT) && !defined(MBED_LFS_ENABLE_ASSERT) +#define LFS_ASSERT(test) MBED_ASSERT(test) +#else +#define LFS_ASSERT(test) +#endif + + +// Builtin functions, these may be replaced by more efficient +// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more +// expensive basic C implementation for debugging purposes + +// Min/max functions for unsigned 32-bit numbers +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; +} + +// Find the next smallest power of 2 less than or equal to a +static inline uint32_t lfs_npw2(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && MBED_LFS_INTRINSICS && \ + (defined(__GNUC__) || defined(__CC_ARM)) + return 32 - __builtin_clz(a-1); +#else + uint32_t r = 0; + uint32_t s; + a -= 1; + 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)) + 1; +#endif +} + +// Count the number of trailing binary zeros in a +// lfs_ctz(0) may be undefined +static inline uint32_t lfs_ctz(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && MBED_LFS_INTRINSICS && \ + defined(__GNUC__) + return __builtin_ctz(a); +#else + return lfs_npw2((a & -a) + 1) - 1; +#endif +} + +// Count the number of binary ones in a +static inline uint32_t lfs_popc(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && MBED_LFS_INTRINSICS && \ + (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 +} + +// Find the sequence comparison of a and b, this is the distance +// between a and b ignoring overflow +static inline int lfs_scmp(uint32_t a, uint32_t b) { + return (int)(unsigned)(a - b); +} + +// Convert from 32-bit little-endian to native order +static inline uint32_t lfs_fromle32(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && MBED_LFS_INTRINSICS && ( \ + (defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + return a; +#elif !defined(LFS_NO_INTRINSICS) && MBED_LFS_INTRINSICS && ( \ + (defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + return __builtin_bswap32(a); +#else + return (((uint8_t*)&a)[0] << 0) | + (((uint8_t*)&a)[1] << 8) | + (((uint8_t*)&a)[2] << 16) | + (((uint8_t*)&a)[3] << 24); +#endif +} + +// Convert to 32-bit little-endian from native order +static inline uint32_t lfs_tole32(uint32_t a) { + return lfs_fromle32(a); +} + +// Calculate CRC-32 with polynomial = 0x04c11db7 +void lfs_crc(uint32_t *crc, const void *buffer, size_t size); + +// Allocate memory, only used if buffers are not provided to littlefs +static inline void *lfs_malloc(size_t size) { +#ifndef LFS_NO_MALLOC + return malloc(size); +#else + (void)size; + return NULL; +#endif +} + +// Deallocate memory, only used if buffers are not provided to littlefs +static inline void lfs_free(void *p) { +#ifndef LFS_NO_MALLOC + free(p); +#else + (void)p; +#endif +} + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif +#endif diff --git a/littlefs/tests/stats.py b/littlefs/tests/stats.py new file mode 100755 index 00000000000..2ba1fb654cb --- /dev/null +++ b/littlefs/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 +static void test_log(const char *s, uintmax_t v) {{ + printf("%s: %jd\n", s, v); +}} + +static 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) {{ + fprintf(stderr, "\033[31m%s:%u: assert %s failed with %jd, " + "expected %jd\033[0m\n", file, line, s, v, e); + exit(-2); + }} +}} + +#define test_assert(s, v, e) test_assert(__FILE__, __LINE__, s, v, e) + + +// utility functions for traversals +static int __attribute__((used)) test_count(void *p, lfs_block_t b) {{ + (void)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 test; + +#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(void) {{ + lfs_emubd_create(&cfg, "blocks"); + +{tests} + + lfs_emubd_destroy(&cfg); +}} diff --git a/littlefs/tests/test.py b/littlefs/tests/test.py new file mode 100755 index 00000000000..24b0d1a37b6 --- /dev/null +++ b/littlefs/tests/test.py @@ -0,0 +1,61 @@ +#!/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+'test = {test};'.format(test=test.strip())) + lines.append(tab+'test_assert("{name}", test, {expect});'.format( + name = re.match('\w*', test.strip()).group(), + expect = expect.strip())) + else: + lines.append(line) + + # Create test file + with open('test.c', 'w') as file: + file.write(template.format(tests='\n'.join(lines))) + + # Remove build artifacts to force rebuild + try: + os.remove('test.o') + os.remove('lfs') + except OSError: + pass + +def compile(): + subprocess.check_call([ + os.environ.get('MAKE', 'make'), + '--no-print-directory', '-s']) + +def execute(): + if 'EXEC' in os.environ: + subprocess.check_call([os.environ['EXEC'], "./lfs"]) + else: + 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/littlefs/tests/test_alloc.sh b/littlefs/tests/test_alloc.sh new file mode 100755 index 00000000000..6b3b181f788 --- /dev/null +++ b/littlefs/tests/test_alloc.sh @@ -0,0 +1,428 @@ +#!/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 (unsigned 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 (unsigned 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 (unsigned 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; + lfs_file_sync(&lfs, &file[0]) => 0; + + 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_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; + 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; + lfs_file_sync(&lfs, &file[0]) => 0; + + 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_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; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Dir exhaustion test ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + 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 < (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; + + 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); + 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; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Chained dir exhaustion test ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + 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 < (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; + + 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 < (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; + + lfs_mkdir(&lfs, "exhaustiondir") => 0; + lfs_mkdir(&lfs, "exhaustiondir2") => LFS_ERR_NOSPC; +TEST + +echo "--- Split dir test ---" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + + // create one block whole for half a directory + lfs_file_open(&lfs, &file[0], "bump", LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file[0], (void*)"hi", 2) => 2; + lfs_file_close(&lfs, &file[0]) => 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 < (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; + + // open hole + lfs_remove(&lfs, "bump") => 0; + + lfs_mkdir(&lfs, "splitdir") => 0; + lfs_file_open(&lfs, &file[0], "splitdir/bump", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file[0], buffer, size) => LFS_ERR_NOSPC; + lfs_file_close(&lfs, &file[0]) => 0; + + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Outdated lookahead test ---" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + + // fill completely with two files + lfs_file_open(&lfs, &file[0], "exhaustion1", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + size = strlen("blahblahblahblah"); + memcpy(buffer, "blahblahblahblah", size); + for (lfs_size_t i = 0; + i < ((cfg.block_count-4)/2)*(cfg.block_size-8); + i += size) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } + lfs_file_close(&lfs, &file[0]) => 0; + + lfs_file_open(&lfs, &file[0], "exhaustion2", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + size = strlen("blahblahblahblah"); + memcpy(buffer, "blahblahblahblah", size); + for (lfs_size_t i = 0; + i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8); + i += size) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } + lfs_file_close(&lfs, &file[0]) => 0; + + // remount to force reset of lookahead + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + + // rewrite one file + lfs_file_open(&lfs, &file[0], "exhaustion1", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_sync(&lfs, &file[0]) => 0; + size = strlen("blahblahblahblah"); + memcpy(buffer, "blahblahblahblah", size); + for (lfs_size_t i = 0; + i < ((cfg.block_count-4)/2)*(cfg.block_size-8); + i += size) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } + lfs_file_close(&lfs, &file[0]) => 0; + + // rewrite second file, this requires lookahead does not + // use old population + lfs_file_open(&lfs, &file[0], "exhaustion2", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_sync(&lfs, &file[0]) => 0; + size = strlen("blahblahblahblah"); + memcpy(buffer, "blahblahblahblah", size); + for (lfs_size_t i = 0; + i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8); + i += size) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } + lfs_file_close(&lfs, &file[0]) => 0; +TEST + +echo "--- Outdated lookahead and split dir test ---" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; + + lfs_mount(&lfs, &cfg) => 0; + + // fill completely with two files + lfs_file_open(&lfs, &file[0], "exhaustion1", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + size = strlen("blahblahblahblah"); + memcpy(buffer, "blahblahblahblah", size); + for (lfs_size_t i = 0; + i < ((cfg.block_count-4)/2)*(cfg.block_size-8); + i += size) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } + lfs_file_close(&lfs, &file[0]) => 0; + + lfs_file_open(&lfs, &file[0], "exhaustion2", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + size = strlen("blahblahblahblah"); + memcpy(buffer, "blahblahblahblah", size); + for (lfs_size_t i = 0; + i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8); + i += size) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } + lfs_file_close(&lfs, &file[0]) => 0; + + // remount to force reset of lookahead + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + + // rewrite one file with a hole of one block + lfs_file_open(&lfs, &file[0], "exhaustion1", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + lfs_file_sync(&lfs, &file[0]) => 0; + size = strlen("blahblahblahblah"); + memcpy(buffer, "blahblahblahblah", size); + for (lfs_size_t i = 0; + i < ((cfg.block_count-4)/2 - 1)*(cfg.block_size-8); + i += size) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } + lfs_file_close(&lfs, &file[0]) => 0; + + // try to allocate a directory, should fail! + lfs_mkdir(&lfs, "split") => LFS_ERR_NOSPC; + + // file should not fail + lfs_file_open(&lfs, &file[0], "notasplit", + LFS_O_WRONLY | LFS_O_CREAT) => 0; + lfs_file_write(&lfs, &file[0], "hi", 2) => 2; + lfs_file_close(&lfs, &file[0]) => 0; + + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Results ---" +tests/stats.py diff --git a/littlefs/tests/test_corrupt.sh b/littlefs/tests/test_corrupt.sh new file mode 100755 index 00000000000..44f1caee320 --- /dev/null +++ b/littlefs/tests/test_corrupt.sh @@ -0,0 +1,117 @@ +#!/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 "--- 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 +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/littlefs/tests/test_dirs.sh b/littlefs/tests/test_dirs.sh new file mode 100755 index 00000000000..874808d4d36 --- /dev/null +++ b/littlefs/tests/test_dirs.sh @@ -0,0 +1,484 @@ +#!/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_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; + 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; + info.type => LFS_TYPE_DIR; + } + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Directory remove ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "potato") => LFS_ERR_NOTEMPTY; + 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_NOTEMPTY; + + 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_NOTEMPTY; + 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 "--- Recursive remove ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOTEMPTY; + + 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 rename ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < $LARGESIZE; i++) { + sprintf((char*)buffer, "cactus/test%d", i); + sprintf((char*)wbuffer, "cactus/tedd%d", i); + lfs_rename(&lfs, (char*)buffer, (char*)wbuffer) => 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, "tedd%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + info.type => LFS_TYPE_DIR; + } + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Multi-block remove ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "cactus") => LFS_ERR_NOTEMPTY; + + for (int i = 0; i < $LARGESIZE; i++) { + sprintf((char*)buffer, "cactus/tedd%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) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Multi-block directory with files ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "prickly-pear") => 0; + for (int i = 0; i < $LARGESIZE; i++) { + sprintf((char*)buffer, "prickly-pear/test%d", i); + lfs_file_open(&lfs, &file[0], (char*)buffer, + LFS_O_WRONLY | LFS_O_CREAT) => 0; + size = 6; + memcpy(wbuffer, "Hello", size); + lfs_file_write(&lfs, &file[0], wbuffer, size) => size; + lfs_file_close(&lfs, &file[0]) => 0; + } + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "prickly-pear") => 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; + info.type => LFS_TYPE_REG; + info.size => 6; + } + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Multi-block rename with files ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + for (int i = 0; i < $LARGESIZE; i++) { + sprintf((char*)buffer, "prickly-pear/test%d", i); + sprintf((char*)wbuffer, "prickly-pear/tedd%d", i); + lfs_rename(&lfs, (char*)buffer, (char*)wbuffer) => 0; + } + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir[0], "prickly-pear") => 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, "tedd%d", i); + lfs_dir_read(&lfs, &dir[0], &info) => 1; + strcmp(info.name, (char*)buffer) => 0; + info.type => LFS_TYPE_REG; + info.size => 6; + } + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Multi-block remove with files ---" +tests/test.py << TEST + lfs_mount(&lfs, &cfg) => 0; + lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOTEMPTY; + + for (int i = 0; i < $LARGESIZE; i++) { + sprintf((char*)buffer, "prickly-pear/tedd%d", i); + lfs_remove(&lfs, (char*)buffer) => 0; + } + + lfs_remove(&lfs, "prickly-pear") => 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) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Results ---" +tests/stats.py diff --git a/littlefs/tests/test_files.sh b/littlefs/tests/test_files.sh new file mode 100755 index 00000000000..bbecea9285b --- /dev/null +++ b/littlefs/tests/test_files.sh @@ -0,0 +1,158 @@ +#!/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 + size = $1; + lfs_size_t chunk = 31; + srand(0); + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file[0], "$2", + ${3:-LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC}) => 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 + size = $1; + lfs_size_t chunk = 29; + srand(0); + lfs_mount(&lfs, &cfg) => 0; + lfs_stat(&lfs, "$2", &info) => 0; + info.type => LFS_TYPE_REG; + info.size => size; + lfs_file_open(&lfs, &file[0], "$2", ${3:-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 "--- Zero file test ---" +w_test 0 noavacado +r_test 0 noavacado + +echo "--- Truncate small test ---" +w_test $SMALLSIZE mediumavacado +r_test $SMALLSIZE mediumavacado +w_test $MEDIUMSIZE mediumavacado +r_test $MEDIUMSIZE mediumavacado + +echo "--- Truncate zero test ---" +w_test $SMALLSIZE noavacado +r_test $SMALLSIZE noavacado +w_test 0 noavacado +r_test 0 noavacado + +echo "--- Non-overlap check ---" +r_test $SMALLSIZE smallavacado +r_test $MEDIUMSIZE mediumavacado +r_test $LARGESIZE largeavacado +r_test 0 noavacado + +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) => 1; + strcmp(info.name, "noavacado") => 0; + info.type => LFS_TYPE_REG; + info.size => 0; + lfs_dir_read(&lfs, &dir[0], &info) => 0; + lfs_dir_close(&lfs, &dir[0]) => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Many file test ---" +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; +TEST +tests/test.py << TEST + // Create 300 files of 6 bytes + lfs_mount(&lfs, &cfg) => 0; + lfs_mkdir(&lfs, "directory") => 0; + for (unsigned i = 0; i < 300; i++) { + snprintf((char*)buffer, sizeof(buffer), "file_%03d", i); + lfs_file_open(&lfs, &file[0], (char*)buffer, LFS_O_WRONLY | LFS_O_CREAT) => 0; + size = 6; + memcpy(wbuffer, "Hello", size); + lfs_file_write(&lfs, &file[0], wbuffer, size) => size; + lfs_file_close(&lfs, &file[0]) => 0; + } + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Results ---" +tests/stats.py diff --git a/littlefs/tests/test_format.sh b/littlefs/tests/test_format.sh new file mode 100755 index 00000000000..b9071015d6c --- /dev/null +++ b/littlefs/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/littlefs/tests/test_interspersed.sh b/littlefs/tests/test_interspersed.sh new file mode 100755 index 00000000000..52e24bc689b --- /dev/null +++ b/littlefs/tests/test_interspersed.sh @@ -0,0 +1,186 @@ +#!/bin/bash +set -eu + +echo "=== Interspersed tests ===" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; +TEST + +echo "--- Interspersed 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 "--- Interspersed 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/littlefs/tests/test_move.sh b/littlefs/tests/test_move.sh new file mode 100755 index 00000000000..9e5ababf7e0 --- /dev/null +++ b/littlefs/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 diff --git a/littlefs/tests/test_orphan.sh b/littlefs/tests/test_orphan.sh new file mode 100755 index 00000000000..71d6d4fc09f --- /dev/null +++ b/littlefs/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/littlefs/tests/test_paths.sh b/littlefs/tests/test_paths.sh new file mode 100755 index 00000000000..79c4e66571d --- /dev/null +++ b/littlefs/tests/test_paths.sh @@ -0,0 +1,143 @@ +#!/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_mkdir(&lfs, "/milk1") => 0; + lfs_stat(&lfs, "/milk1", &info) => 0; + strcmp(info.name, "milk1") => 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_mkdir(&lfs, "///milk2") => 0; + lfs_stat(&lfs, "///milk2", &info) => 0; + strcmp(info.name, "milk2") => 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_mkdir(&lfs, "/./milk3") => 0; + lfs_stat(&lfs, "/./milk3", &info) => 0; + strcmp(info.name, "milk3") => 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_mkdir(&lfs, "coffee/../milk4") => 0; + lfs_stat(&lfs, "coffee/../milk4", &info) => 0; + strcmp(info.name, "milk4") => 0; + lfs_unmount(&lfs) => 0; +TEST + +echo "--- Trailing 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, "tea") => 0; + lfs_stat(&lfs, "tea/hottea/../.", &info) => 0; + strcmp(info.name, "tea") => 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_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_mkdir(&lfs, "/") => LFS_ERR_EXIST; + lfs_file_open(&lfs, &file[0], "/", LFS_O_WRONLY | LFS_O_CREAT) + => LFS_ERR_ISDIR; + 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 + +echo "--- Results ---" +tests/stats.py diff --git a/littlefs/tests/test_seek.sh b/littlefs/tests/test_seek.sh new file mode 100755 index 00000000000..aa8e64326bf --- /dev/null +++ b/littlefs/tests/test_seek.sh @@ -0,0 +1,361 @@ +#!/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], 0, LFS_SEEK_CUR) => 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) => 3*size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + 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; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + 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; + + 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], 0, LFS_SEEK_CUR) => 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) => 3*size; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + 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; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + 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; + + 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; + 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) => 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) >= 0 => 1; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + 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; + 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) => 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) >= 0 => 1; + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "kittycatcat", size) => 0; + + 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 "--- 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 (unsigned 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 diff --git a/littlefs/tests/test_truncate.sh b/littlefs/tests/test_truncate.sh new file mode 100755 index 00000000000..053b2e0ee0c --- /dev/null +++ b/littlefs/tests/test_truncate.sh @@ -0,0 +1,158 @@ +#!/bin/bash +set -eu + +SMALLSIZE=32 +MEDIUMSIZE=2048 +LARGESIZE=8192 + +echo "=== Truncate tests ===" +rm -rf blocks +tests/test.py << TEST + lfs_format(&lfs, &cfg) => 0; +TEST + +truncate_test() { +STARTSIZES="$1" +STARTSEEKS="$2" +HOTSIZES="$3" +COLDSIZES="$4" +tests/test.py << TEST + static const lfs_off_t startsizes[] = {$STARTSIZES}; + static const lfs_off_t startseeks[] = {$STARTSEEKS}; + static const lfs_off_t hotsizes[] = {$HOTSIZES}; + + lfs_mount(&lfs, &cfg) => 0; + + for (unsigned i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) { + sprintf((char*)buffer, "hairyhead%d", i); + lfs_file_open(&lfs, &file[0], (const char*)buffer, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + + strcpy((char*)buffer, "hair"); + size = strlen((char*)buffer); + for (lfs_off_t j = 0; j < startsizes[i]; j += size) { + lfs_file_write(&lfs, &file[0], buffer, size) => size; + } + lfs_file_size(&lfs, &file[0]) => startsizes[i]; + + if (startseeks[i] != startsizes[i]) { + lfs_file_seek(&lfs, &file[0], + startseeks[i], LFS_SEEK_SET) => startseeks[i]; + } + + lfs_file_truncate(&lfs, &file[0], hotsizes[i]) => 0; + lfs_file_size(&lfs, &file[0]) => hotsizes[i]; + + lfs_file_close(&lfs, &file[0]) => 0; + } + + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + static const lfs_off_t startsizes[] = {$STARTSIZES}; + static const lfs_off_t hotsizes[] = {$HOTSIZES}; + static const lfs_off_t coldsizes[] = {$COLDSIZES}; + + lfs_mount(&lfs, &cfg) => 0; + + for (unsigned i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) { + sprintf((char*)buffer, "hairyhead%d", i); + lfs_file_open(&lfs, &file[0], (const char*)buffer, LFS_O_RDWR) => 0; + lfs_file_size(&lfs, &file[0]) => hotsizes[i]; + + size = strlen("hair"); + lfs_off_t j = 0; + for (; j < startsizes[i] && j < hotsizes[i]; j += size) { + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "hair", size) => 0; + } + + for (; j < hotsizes[i]; j += size) { + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "\0\0\0\0", size) => 0; + } + + lfs_file_truncate(&lfs, &file[0], coldsizes[i]) => 0; + lfs_file_size(&lfs, &file[0]) => coldsizes[i]; + + lfs_file_close(&lfs, &file[0]) => 0; + } + + lfs_unmount(&lfs) => 0; +TEST +tests/test.py << TEST + static const lfs_off_t startsizes[] = {$STARTSIZES}; + static const lfs_off_t hotsizes[] = {$HOTSIZES}; + static const lfs_off_t coldsizes[] = {$COLDSIZES}; + + lfs_mount(&lfs, &cfg) => 0; + + for (unsigned i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) { + sprintf((char*)buffer, "hairyhead%d", i); + lfs_file_open(&lfs, &file[0], (const char*)buffer, LFS_O_RDONLY) => 0; + lfs_file_size(&lfs, &file[0]) => coldsizes[i]; + + size = strlen("hair"); + lfs_off_t j = 0; + for (; j < startsizes[i] && j < hotsizes[i] && j < coldsizes[i]; + j += size) { + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "hair", size) => 0; + } + + for (; j < coldsizes[i]; j += size) { + lfs_file_read(&lfs, &file[0], buffer, size) => size; + memcmp(buffer, "\0\0\0\0", size) => 0; + } + + lfs_file_close(&lfs, &file[0]) => 0; + } + + lfs_unmount(&lfs) => 0; +TEST +} + +echo "--- Cold shrinking truncate ---" +truncate_test \ + "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ + "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ + "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ + " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" + +echo "--- Cold expanding truncate ---" +truncate_test \ + " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ + " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ + " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ + "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" + +echo "--- Warm shrinking truncate ---" +truncate_test \ + "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ + "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ + " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ + " 0, 0, 0, 0, 0" + +echo "--- Warm expanding truncate ---" +truncate_test \ + " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ + " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ + "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ + "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" + +echo "--- Mid-file shrinking truncate ---" +truncate_test \ + "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ + " $LARGESIZE, $LARGESIZE, $LARGESIZE, $LARGESIZE, $LARGESIZE" \ + " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ + " 0, 0, 0, 0, 0" + +echo "--- Mid-file expanding truncate ---" +truncate_test \ + " 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \ + " 0, 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE" \ + "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \ + "2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" + +echo "--- Results ---" +tests/stats.py diff --git a/mbed_lib.json b/mbed_lib.json new file mode 100644 index 00000000000..2cbbe466b9d --- /dev/null +++ b/mbed_lib.json @@ -0,0 +1,55 @@ +{ + "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": 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." + }, + "intrinsics": { + "macro_name": "MBED_LFS_INTRINSICS", + "value": true, + "help": "Enable intrinsics for bit operations such as ctz, popc, and le32 conversion. Can be disabled to help debug toolchain issues" + }, + "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" + }, + "enable_assert": { + "macro_name": "MBED_LFS_ENABLE_ASSERT", + "value": null, + "help": "Enables asserts, true = enabled, false = disabled, null = disabled only in release builds" + } + } +}