LittleFSv2: Bring in v2.2

pull/12783/head
Veijo Pesonen 2020-04-09 15:14:47 +03:00
parent 72d1918a6c
commit 3dfbe139f2
70 changed files with 27035 additions and 0 deletions

View File

@ -0,0 +1,3 @@
littlefs/emubd/
littlefs/tests/
TESTS/util

View File

@ -0,0 +1,40 @@
language: python
python: 2.7
script:
# Check that example compiles
- sed -n '/``` c++/,/```/{/```/d; p;}' README.md > main.cpp
- PYTHONPATH=mbed-os python mbed-os/tools/make.py -t GCC_ARM -m K82F
--source=. --build=BUILD/K82F/GCC_ARM -j0
# 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
- make -Clittlefs test QUIET=1
# Run littlefs functional tests with different configurations
# Note: r/w size of 64 is default in mbed
- make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_READ_SIZE=64 -DLFS2_CACHE_SIZE=64"
- make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_READ_SIZE=1 -DLFS2_CACHE_SIZE=1"
- make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_READ_SIZE=512 -DLFS2_CACHE_SIZE=512 -DLFS2_BLOCK_CYCLES=16"
- make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_READ_SIZE=8 -DLFS2_CACHE_SIZE=16 -DLFS2_BLOCK_CYCLES=2"
- make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_BLOCK_COUNT=1023 -DLFS2_LOOKAHEAD_SIZE=256"
install:
# Get arm-none-eabi-gcc
- sudo add-apt-repository -y ppa:team-gcc-arm-embedded/ppa
- sudo apt-get update -qq
- sudo apt-get install -qq gcc-arm-embedded
# Get dependencies
- git clone https://github.com/armmbed/mbed-os.git
# Install python dependencies
- pip install -r mbed-os/requirements.txt
- sudo apt-get install python3 python3-pip
- sudo pip3 install toml
# Check versions
- arm-none-eabi-gcc --version
- gcc --version

View File

@ -0,0 +1,512 @@
/* 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 "LittleFileSystem2.h"
#include "errno.h"
#include "lfs2.h"
#include "lfs2_util.h"
#include "MbedCRC.h"
namespace mbed {
extern "C" uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size)
{
uint32_t initial_xor = lfs2_rbit(crc);
MbedCRC<POLY_32BIT_ANSI, 32, CrcMode::TABLE> ct(initial_xor, 0x0, true, true);
ct.compute((void *)buffer, size, &crc);
return crc;
}
////// Conversion functions //////
static int lfs2_toerror(int err)
{
switch (err) {
case LFS2_ERR_OK:
return 0;
case LFS2_ERR_IO:
return -EIO;
case LFS2_ERR_NOENT:
return -ENOENT;
case LFS2_ERR_EXIST:
return -EEXIST;
case LFS2_ERR_NOTDIR:
return -ENOTDIR;
case LFS2_ERR_ISDIR:
return -EISDIR;
case LFS2_ERR_INVAL:
return -EINVAL;
case LFS2_ERR_NOSPC:
return -ENOSPC;
case LFS2_ERR_NOMEM:
return -ENOMEM;
case LFS2_ERR_CORRUPT:
return -EILSEQ;
default:
return err;
}
}
static int lfs2_fromflags(int flags)
{
return (
(((flags & 3) == O_RDONLY) ? LFS2_O_RDONLY : 0) |
(((flags & 3) == O_WRONLY) ? LFS2_O_WRONLY : 0) |
(((flags & 3) == O_RDWR) ? LFS2_O_RDWR : 0) |
((flags & O_CREAT) ? LFS2_O_CREAT : 0) |
((flags & O_EXCL) ? LFS2_O_EXCL : 0) |
((flags & O_TRUNC) ? LFS2_O_TRUNC : 0) |
((flags & O_APPEND) ? LFS2_O_APPEND : 0));
}
static int lfs2_fromwhence(int whence)
{
switch (whence) {
case SEEK_SET:
return LFS2_SEEK_SET;
case SEEK_CUR:
return LFS2_SEEK_CUR;
case SEEK_END:
return LFS2_SEEK_END;
default:
return whence;
}
}
static int lfs2_tomode(int type)
{
int mode = S_IRWXU | S_IRWXG | S_IRWXO;
switch (type) {
case LFS2_TYPE_DIR:
return mode | S_IFDIR;
case LFS2_TYPE_REG:
return mode | S_IFREG;
default:
return 0;
}
}
static int lfs2_totype(int type)
{
switch (type) {
case LFS2_TYPE_DIR:
return DT_DIR;
case LFS2_TYPE_REG:
return DT_REG;
default:
return DT_UNKNOWN;
}
}
////// Block device operations //////
static int lfs2_bd_read(const struct lfs2_config *c, lfs2_block_t block,
lfs2_off_t off, void *buffer, lfs2_size_t size)
{
BlockDevice *bd = (BlockDevice *)c->context;
return bd->read(buffer, (bd_addr_t)block * c->block_size + off, size);
}
static int lfs2_bd_prog(const struct lfs2_config *c, lfs2_block_t block,
lfs2_off_t off, const void *buffer, lfs2_size_t size)
{
BlockDevice *bd = (BlockDevice *)c->context;
return bd->program(buffer, (bd_addr_t)block * c->block_size + off, size);
}
static int lfs2_bd_erase(const struct lfs2_config *c, lfs2_block_t block)
{
BlockDevice *bd = (BlockDevice *)c->context;
return bd->erase((bd_addr_t)block * c->block_size, c->block_size);
}
static int lfs2_bd_sync(const struct lfs2_config *c)
{
BlockDevice *bd = (BlockDevice *)c->context;
return bd->sync();
}
////// Generic filesystem operations //////
// Filesystem implementation (See LittleFileSystem2.h)
LittleFileSystem2::LittleFileSystem2(const char *name, BlockDevice *bd,
lfs2_size_t block_size, uint32_t block_cycles,
lfs2_size_t cache_size, lfs2_size_t lookahead_size)
: FileSystem(name)
{
memset(&_config, 0, sizeof(_config));
_config.block_size = block_size;
_config.block_cycles = block_cycles;
_config.cache_size = cache_size;
_config.lookahead_size = lookahead_size;
if (bd) {
mount(bd);
}
}
LittleFileSystem2::~LittleFileSystem2()
{
// nop if unmounted
unmount();
}
int LittleFileSystem2::mount(BlockDevice *bd)
{
_mutex.lock();
_bd = bd;
int err = _bd->init();
if (err) {
_bd = NULL;
_mutex.unlock();
return err;
}
_config.context = bd;
_config.read = lfs2_bd_read;
_config.prog = lfs2_bd_prog;
_config.erase = lfs2_bd_erase;
_config.sync = lfs2_bd_sync;
_config.read_size = bd->get_read_size();
_config.prog_size = bd->get_program_size();
_config.block_size = lfs2_max(_config.block_size, (lfs2_size_t)bd->get_erase_size());
_config.block_count = bd->size() / _config.block_size;
_config.block_cycles = _config.block_cycles;
_config.cache_size = lfs2_max(_config.cache_size, _config.prog_size);
_config.lookahead_size = lfs2_min(_config.lookahead_size, 8 * ((_config.block_count + 63) / 64));
err = lfs2_mount(&_lfs, &_config);
if (err) {
_bd = NULL;
_mutex.unlock();
return lfs2_toerror(err);
}
_mutex.unlock();
return 0;
}
int LittleFileSystem2::unmount()
{
_mutex.lock();
int res = 0;
if (_bd) {
int err = lfs2_unmount(&_lfs);
if (err && !res) {
res = lfs2_toerror(err);
}
err = _bd->deinit();
if (err && !res) {
res = err;
}
_bd = NULL;
}
_mutex.unlock();
return res;
}
int LittleFileSystem2::format(BlockDevice *bd,
lfs2_size_t block_size, uint32_t block_cycles,
lfs2_size_t cache_size, lfs2_size_t lookahead_size)
{
int err = bd->init();
if (err) {
return err;
}
lfs2_t _lfs;
struct lfs2_config _config;
memset(&_config, 0, sizeof(_config));
_config.context = bd;
_config.read = lfs2_bd_read;
_config.prog = lfs2_bd_prog;
_config.erase = lfs2_bd_erase;
_config.sync = lfs2_bd_sync;
_config.read_size = bd->get_read_size();
_config.prog_size = bd->get_program_size();
_config.block_size = lfs2_max(block_size, (lfs2_size_t)bd->get_erase_size());
_config.block_count = bd->size() / _config.block_size;
_config.block_cycles = block_cycles;
_config.cache_size = lfs2_max(cache_size, _config.prog_size);
_config.lookahead_size = lfs2_min(lookahead_size, 8 * ((_config.block_count + 63) / 64));
err = lfs2_format(&_lfs, &_config);
if (err) {
return lfs2_toerror(err);
}
err = bd->deinit();
if (err) {
return err;
}
return 0;
}
int LittleFileSystem2::reformat(BlockDevice *bd)
{
_mutex.lock();
if (_bd) {
if (!bd) {
bd = _bd;
}
int err = unmount();
if (err) {
_mutex.unlock();
return err;
}
}
if (!bd) {
_mutex.unlock();
return -ENODEV;
}
int err = LittleFileSystem2::format(bd,
_config.block_size,
_config.block_cycles,
_config.cache_size,
_config.lookahead_size);
if (err) {
_mutex.unlock();
return err;
}
err = mount(bd);
if (err) {
_mutex.unlock();
return err;
}
_mutex.unlock();
return 0;
}
int LittleFileSystem2::remove(const char *filename)
{
_mutex.lock();
int err = lfs2_remove(&_lfs, filename);
_mutex.unlock();
return lfs2_toerror(err);
}
int LittleFileSystem2::rename(const char *oldname, const char *newname)
{
_mutex.lock();
int err = lfs2_rename(&_lfs, oldname, newname);
_mutex.unlock();
return lfs2_toerror(err);
}
int LittleFileSystem2::mkdir(const char *name, mode_t mode)
{
_mutex.lock();
int err = lfs2_mkdir(&_lfs, name);
_mutex.unlock();
return lfs2_toerror(err);
}
int LittleFileSystem2::stat(const char *name, struct stat *st)
{
struct lfs2_info info;
_mutex.lock();
int err = lfs2_stat(&_lfs, name, &info);
_mutex.unlock();
st->st_size = info.size;
st->st_mode = lfs2_tomode(info.type);
return lfs2_toerror(err);
}
int LittleFileSystem2::statvfs(const char *name, struct statvfs *st)
{
memset(st, 0, sizeof(struct statvfs));
lfs2_ssize_t in_use = 0;
_mutex.lock();
in_use = lfs2_fs_size(&_lfs);
_mutex.unlock();
if (in_use < 0) {
return in_use;
}
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 = LFS2_NAME_MAX;
return 0;
}
////// File operations //////
int LittleFileSystem2::file_open(fs_file_t *file, const char *path, int flags)
{
lfs2_file_t *f = new lfs2_file_t;
_mutex.lock();
int err = lfs2_file_open(&_lfs, f, path, lfs2_fromflags(flags));
_mutex.unlock();
if (!err) {
*file = f;
} else {
delete f;
}
return lfs2_toerror(err);
}
int LittleFileSystem2::file_close(fs_file_t file)
{
lfs2_file_t *f = (lfs2_file_t *)file;
_mutex.lock();
int err = lfs2_file_close(&_lfs, f);
_mutex.unlock();
delete f;
return lfs2_toerror(err);
}
ssize_t LittleFileSystem2::file_read(fs_file_t file, void *buffer, size_t len)
{
lfs2_file_t *f = (lfs2_file_t *)file;
_mutex.lock();
lfs2_ssize_t res = lfs2_file_read(&_lfs, f, buffer, len);
_mutex.unlock();
return lfs2_toerror(res);
}
ssize_t LittleFileSystem2::file_write(fs_file_t file, const void *buffer, size_t len)
{
lfs2_file_t *f = (lfs2_file_t *)file;
_mutex.lock();
lfs2_ssize_t res = lfs2_file_write(&_lfs, f, buffer, len);
_mutex.unlock();
return lfs2_toerror(res);
}
int LittleFileSystem2::file_sync(fs_file_t file)
{
lfs2_file_t *f = (lfs2_file_t *)file;
_mutex.lock();
int err = lfs2_file_sync(&_lfs, f);
_mutex.unlock();
return lfs2_toerror(err);
}
off_t LittleFileSystem2::file_seek(fs_file_t file, off_t offset, int whence)
{
lfs2_file_t *f = (lfs2_file_t *)file;
_mutex.lock();
off_t res = lfs2_file_seek(&_lfs, f, offset, lfs2_fromwhence(whence));
_mutex.unlock();
return lfs2_toerror(res);
}
off_t LittleFileSystem2::file_tell(fs_file_t file)
{
lfs2_file_t *f = (lfs2_file_t *)file;
_mutex.lock();
off_t res = lfs2_file_tell(&_lfs, f);
_mutex.unlock();
return lfs2_toerror(res);
}
off_t LittleFileSystem2::file_size(fs_file_t file)
{
lfs2_file_t *f = (lfs2_file_t *)file;
_mutex.lock();
off_t res = lfs2_file_size(&_lfs, f);
_mutex.unlock();
return lfs2_toerror(res);
}
int LittleFileSystem2::file_truncate(fs_file_t file, off_t length)
{
lfs2_file_t *f = (lfs2_file_t *)file;
_mutex.lock();
int err = lfs2_file_truncate(&_lfs, f, length);
_mutex.unlock();
return lfs2_toerror(err);
}
////// Dir operations //////
int LittleFileSystem2::dir_open(fs_dir_t *dir, const char *path)
{
lfs2_dir_t *d = new lfs2_dir_t;
_mutex.lock();
int err = lfs2_dir_open(&_lfs, d, path);
_mutex.unlock();
if (!err) {
*dir = d;
} else {
delete d;
}
return lfs2_toerror(err);
}
int LittleFileSystem2::dir_close(fs_dir_t dir)
{
lfs2_dir_t *d = (lfs2_dir_t *)dir;
_mutex.lock();
int err = lfs2_dir_close(&_lfs, d);
_mutex.unlock();
delete d;
return lfs2_toerror(err);
}
ssize_t LittleFileSystem2::dir_read(fs_dir_t dir, struct dirent *ent)
{
lfs2_dir_t *d = (lfs2_dir_t *)dir;
struct lfs2_info info;
_mutex.lock();
int res = lfs2_dir_read(&_lfs, d, &info);
_mutex.unlock();
if (res == 1) {
ent->d_type = lfs2_totype(info.type);
strcpy(ent->d_name, info.name);
}
return lfs2_toerror(res);
}
void LittleFileSystem2::dir_seek(fs_dir_t dir, off_t offset)
{
lfs2_dir_t *d = (lfs2_dir_t *)dir;
_mutex.lock();
lfs2_dir_seek(&_lfs, d, offset);
_mutex.unlock();
}
off_t LittleFileSystem2::dir_tell(fs_dir_t dir)
{
lfs2_dir_t *d = (lfs2_dir_t *)dir;
_mutex.lock();
lfs2_soff_t res = lfs2_dir_tell(&_lfs, d);
_mutex.unlock();
return lfs2_toerror(res);
}
void LittleFileSystem2::dir_rewind(fs_dir_t dir)
{
lfs2_dir_t *d = (lfs2_dir_t *)dir;
_mutex.lock();
lfs2_dir_rewind(&_lfs, d);
_mutex.unlock();
}
} // namespace mbed

View File

@ -0,0 +1,308 @@
/* 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_LFS2FILESYSTEM_H
#define MBED_LFS2FILESYSTEM_H
#include "FileSystem.h"
#include "BlockDevice.h"
#include "PlatformMutex.h"
#include "lfs2.h"
namespace mbed {
/**
* LittleFileSystem2, a little file system
*
* Synchronization level: Thread safe
*/
class LittleFileSystem2 : public mbed::FileSystem {
public:
/** Lifetime of the LittleFileSystem2
*
* @param name Name of the file system in the tree.
* @param bd Block device to mount. Mounted immediately if not NULL.
* @param block_size
* Size of a logical block. This does not impact ram consumption and
* may be larger than the physical erase block. If the physical erase
* block is larger, littlefs will use that instead. Larger values will
* be faster but waste more storage when files are not aligned to a
* block size.
* @param block_cycles
* Number of erase cycles before a block is forcefully evicted. Larger
* values are more efficient but cause less even wear distribution. 0
* disables dynamic wear-leveling.
* @param cache_size
* Size of read/program caches. Each file uses 1 cache, and littlefs
* allocates 2 caches for internal operations. Larger values should be
* faster but uses more RAM.
* @param lookahead_size
* Size of the lookahead buffer. A larger lookahead reduces the
* allocation scans and results in a faster filesystem but uses
* more RAM.
*/
LittleFileSystem2(const char *name = NULL, mbed::BlockDevice *bd = NULL,
lfs2_size_t block_size = MBED_LFS2_BLOCK_SIZE,
uint32_t block_cycles = MBED_LFS2_BLOCK_CYCLES,
lfs2_size_t cache_size = MBED_LFS2_CACHE_SIZE,
lfs2_size_t lookahead = MBED_LFS2_LOOKAHEAD_SIZE);
virtual ~LittleFileSystem2();
/** Format a block device with the LittleFileSystem2.
*
* 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 block_size
* Size of a logical block. This does not impact ram consumption and
* may be larger than the physical erase block. If the physical erase
* block is larger, littlefs will use that instead. Larger values will
* be faster but waste more storage when files are not aligned to a
* block size.
* @param block_cycles
* Number of erase cycles before a block is forcefully evicted. Larger
* values are more efficient but cause less even wear distribution. 0
* disables dynamic wear-leveling.
* @param cache_size
* Size of read/program caches. Each file uses 1 cache, and littlefs
* allocates 2 caches for internal operations. Larger values should be
* faster but uses more RAM.
* @param lookahead_size
* Size of the lookahead buffer. A larger lookahead reduces the
* allocation scans and results in a faster filesystem but uses
* more RAM.
*/
static int format(mbed::BlockDevice *bd,
lfs2_size_t block_size = MBED_LFS2_BLOCK_SIZE,
uint32_t block_cycles = MBED_LFS2_BLOCK_CYCLES,
lfs2_size_t cache_size = MBED_LFS2_CACHE_SIZE,
lfs2_size_t lookahead_size = MBED_LFS2_LOOKAHEAD_SIZE);
/** 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:
lfs2_t _lfs; // The actual file system
struct lfs2_config _config;
mbed::BlockDevice *_bd; // The block device
// thread-safe locking
PlatformMutex _mutex;
};
} // namespace mbed
// Added "using" for backwards compatibility
#ifndef MBED_NO_GLOBAL_USING_DIRECTIVE
using mbed::LittleFileSystem2;
#endif
#endif
/** @}*/

View File

@ -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 [LittleFileSystem2](LittleFileSystem2.h)
class.
Here is a simple example that updates a file named "boot_count" every time
the application runs:
``` c++
#include "LittleFileSystem2.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
LittleFileSystem2 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
LittleFileSystem2::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).

View File

@ -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 <stdlib.h>
#include <errno.h>
#include "ObservingBlockDevice.h"
#include "ExhaustibleBlockDevice.h"
#include "FileSystem.h"
#include "atomic_usage.h"
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem2
#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();
}

View File

@ -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

View File

@ -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 <stdlib.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem2
#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, "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, "potato");
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_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, "fried");
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(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%03d", 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%03d", 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, "fried");
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(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, "fried");
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(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, "fried");
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(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);
}

View File

@ -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 <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem2
#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, "largeavacado");
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, "smallavacado");
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);
}

View File

@ -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 <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem2
#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);
}

View File

@ -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 <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem2
#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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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/kitty042", 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/kitty042", 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/kitty042", 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/kitty042", 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/kitty042", 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/kitty042", 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);
}

View File

@ -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 <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem2
#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);
}

View File

@ -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);
}

View File

@ -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 "LittleFileSystem2.h"
#include <string.h>
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);
}

View File

@ -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);
}

View File

@ -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 <errno.h>
#include "mbed.h"
#include "greentea-client/test_env.h"
#include "unity.h"
#include "utest.h"
#include <stdlib.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem2
#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, "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, "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(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, "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(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(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%03d", 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%03d", 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, "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(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(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, "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(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(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, "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(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(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);
}

View File

@ -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 <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem2
#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, "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(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, "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(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);
}

View File

@ -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 <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem2
#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);
}

View File

@ -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 <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem2
#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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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/kitty042", "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/kitty042", "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/kitty042", "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/kitty042", "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/kitty042", "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/kitty042", "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);
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,4 @@
#!/bin/bash
rm -f main.cpp
rm -f template_all_names.txt

View File

@ -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:])

View File

@ -0,0 +1,33 @@
- ['lfs2_format\(&lfs2, &cfg\)', 'MBED_TEST_FILESYSTEM::format(&bd)']
- ['lfs2_mount\(&lfs2, &cfg\)', 'fs.mount(&bd)']
- ['lfs2_unmount\(&lfs2\)', 'fs.unmount()']
- ['lfs2_mkdir\(&lfs2, (.*)\)', 'fs.mkdir(\1, 0777)']
- ['lfs2_remove\(&lfs2, (.*)\)', 'fs.remove(\1)']
- ['lfs2_rename\(&lfs2, (.*), ?(.*)\)', 'fs.rename(\1, \2)']
- ['lfs2_dir_open\(&lfs2, &dir\[(.*)\], ?(.*)\)', 'dir[\1].open(&fs, \2)']
- ['lfs2_dir_close\(&lfs2, &dir\[(.*)\]\)', 'dir[\1].close()']
- ['lfs2_dir_read\(&lfs2, &dir\[(.*)\], &info\)', 'dir[\1].read(&ent)']
- ['lfs2_dir_seek\(&lfs2, &dir\[(.*)\], ?(.*)\).*;', 'dir[\1].seek(\2);'] # no dir errors
- ['lfs2_dir_rewind\(&lfs2, &dir\[(.*)\]\).*;', 'dir[\1].rewind();'] # no dir errors
- ['lfs2_dir_tell\(&lfs2, &dir\[(.*)\]\)', 'dir[\1].tell()']
- ['lfs2_file_open\(&lfs2, &file\[(.*)\], ?(.*)\)', 'file[\1].open(&fs, \2)']
- ['lfs2_file_close\(&lfs2, &file\[(.*)\]\)', 'file[\1].close()']
- ['lfs2_file_sync\(&lfs2, &file\[(.*)\]\)', 'file[\1].sync()']
- ['lfs2_file_write\(&lfs2, &file\[(.*)\], ?(.*), (.*)\)', 'file[\1].write(\2, \3)']
- ['lfs2_file_read\(&lfs2, &file\[(.*)\], ?(.*), (.*)\)', 'file[\1].read(\2, \3)']
- ['lfs2_file_seek\(&lfs2, &file\[(.*)\], ?(.*)\)', 'file[\1].seek(\2)']
- ['lfs2_file_tell\(&lfs2, &file\[(.*)\]\)', 'file[\1].tell()']
- ['lfs2_file_rewind\(&lfs2, &file\[(.*)\]\).*;', 'file[\1].rewind();'] # no errors
- ['lfs2_file_size\(&lfs2, &file\[(.*)\]\)', 'file[\1].size()']
- ['LFS2_TYPE_([A-Z]+)', 'DT_\1']
- ['LFS2_O_([A-Z]+)', 'O_\1']
- ['LFS2_SEEK_([A-Z]+)', 'SEEK_\1']
- ['LFS2_ERR_([A-Z]+)', '-E\1']
- ['lfs2_(s?)size_t', '\1size_t']
- ['lfs2_soff_t', 'off_t']
- ['info\.name', 'ent.d_name']
- ['info\.type', 'ent.d_type']
- ['^.*info\.size.*$', ''] # dirent sizes not supported

View File

@ -0,0 +1,37 @@
- ['lfs2_format\(&lfs2, &cfg\)', 'MBED_TEST_FILESYSTEM::format(&bd)']
- ['lfs2_mount\(&lfs2, &cfg\)', 'fs.mount(&bd)']
- ['lfs2_unmount\(&lfs2\)', 'fs.unmount()']
- ['lfs2_mkdir\(&lfs2, (.*)\)', 'mkdir("/fs/" \1, 0777)']
- ['lfs2_remove\(&lfs2, (.*)\)', 'remove("/fs/" \1)']
- ['lfs2_rename\(&lfs2, (.*), ?(.*)\)', 'rename("/fs/" \1, "/fs/" \2)']
- ['lfs2_dir_open\(&lfs2, &dir\[(.*)\], ?(.*)\)', '!((dd[\1] = opendir("/fs/" \2)) != NULL)']
- ['lfs2_dir_close\(&lfs2, &dir\[(.*)\]\)', 'closedir(dd[\1])']
- ['lfs2_dir_read\(&lfs2, &dir\[(.*)\], &info\)', '((ed = readdir(dd[\1])) != NULL)']
- ['lfs2_dir_seek\(&lfs2, &dir\[(.*)\], ?(.*)\).*;', 'seekdir(dd[\1], \2);'] # no dir errors
- ['lfs2_dir_rewind\(&lfs2, &dir\[(.*)\]\).*;', 'rewinddir(dd[\1]);'] # no dir errors
- ['lfs2_dir_tell\(&lfs2, &dir\[(.*)\]\)', 'telldir(dd[\1])']
- ['lfs2_file_open\(&lfs2, &file\[(.*)\], ?(.*)\)', '!((fd[\1] = fopen("/fs/" \2)) != NULL)']
- ['lfs2_file_close\(&lfs2, &file\[(.*)\]\)', 'fclose(fd[\1])']
- ['lfs2_file_sync\(&lfs2, &file\[(.*)\]\)', 'fflush(fd[\1])']
- ['lfs2_file_write\(&lfs2, &file\[(.*)\], ?(.*), (.*)\)', 'fwrite(\2, 1, \3, fd[\1])']
- ['lfs2_file_read\(&lfs2, &file\[(.*)\], ?(.*), (.*)\)', 'fread(\2, 1, \3, fd[\1])']
- ['lfs2_file_tell\(&lfs2, &file\[(.*)\]\)', 'ftell(fd[\1])']
- ['lfs2_file_rewind\(&lfs2, &file\[(.*)\]\).*;', 'rewind(fd[\1]);'] # no errors
- ['LFS2_TYPE_([A-Z]+)', 'DT_\1']
- ['LFS2_SEEK_([A-Z]+)', 'SEEK_\1']
- ['LFS2_ERR_([A-Z]+)', '-E\1']
- ['lfs2_(s?)size_t', '\1size_t']
- ['lfs2_soff_t', 'off_t']
- ['info\.name', 'ed->d_name']
- ['info\.type', 'ed->d_type']
- ['^.*info\.size.*$', ''] # dirent sizes not supported
- ['LFS2_O_WRONLY \| LFS2_O_CREAT \| LFS2_O_APPEND', '"ab"']
- ['LFS2_O_WRONLY \| LFS2_O_TRUNC', '"wb"']
- ['LFS2_O_CREAT \| LFS2_O_WRONLY', '"wb"']
- ['LFS2_O_WRONLY \| LFS2_O_CREAT', '"wb"']
- ['LFS2_O_RDONLY', '"rb"']
- ['LFS2_O_RDWR', '"r+b"']

View File

@ -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:])

View File

@ -0,0 +1,4 @@
{{
{test}
}}

View File

@ -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);
}}

View File

@ -0,0 +1,86 @@
#include "mbed.h"
#include "greentea-client/test_env.h"
#include "unity.h"
#include "utest.h"
#include <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem2
#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);
}}

View File

@ -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:])

View File

@ -0,0 +1,12 @@
# Compilation output
*.o
*.d
*.a
# Testing things
blocks/
lfs2
test.c
tests/*.toml.*
scripts/__pycache__
.gdb_history

View File

@ -0,0 +1,429 @@
# environment variables
env:
global:
- CFLAGS=-Werror
- MAKEFLAGS=-j
# cache installation dirs
cache:
pip: true
directories:
- $HOME/.cache/apt
# common installation
_: &install-common
# need toml, also pip3 isn't installed by default?
- sudo apt-get install python3 python3-pip
- sudo pip3 install toml
# setup a ram-backed disk to speed up reentrant tests
- mkdir disks
- sudo mount -t tmpfs -o size=100m tmpfs disks
- export TFLAGS="$TFLAGS --disk=disks/disk"
# test cases
_: &test-example
# 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"
# default tests
_: &test-default
# normal+reentrant tests
- make test TFLAGS+="-nrk"
# common real-life geometries
_: &test-nor
# NOR flash: read/prog = 1 block = 4KiB
- make test TFLAGS+="-nrk -DLFS2_READ_SIZE=1 -DLFS2_BLOCK_SIZE=4096"
_: &test-emmc
# eMMC: read/prog = 512 block = 512
- make test TFLAGS+="-nrk -DLFS2_READ_SIZE=512 -DLFS2_BLOCK_SIZE=512"
_: &test-nand
# NAND flash: read/prog = 4KiB block = 32KiB
- make test TFLAGS+="-nrk -DLFS2_READ_SIZE=4096 -DLFS2_BLOCK_SIZE=\(32*1024\)"
# other extreme geometries that are useful for testing various corner cases
_: &test-no-intrinsics
- make test TFLAGS+="-nrk -DLFS2_NO_INTRINSICS"
_: &test-no-inline
- make test TFLAGS+="-nrk -DLFS2_INLINE_MAX=0"
_: &test-byte-writes
- make test TFLAGS+="-nrk -DLFS2_READ_SIZE=1 -DLFS2_CACHE_SIZE=1"
_: &test-block-cycles
- make test TFLAGS+="-nrk -DLFS2_BLOCK_CYCLES=1"
_: &test-odd-block-count
- make test TFLAGS+="-nrk -DLFS2_BLOCK_COUNT=1023 -DLFS2_LOOKAHEAD_SIZE=256"
_: &test-odd-block-size
- make test TFLAGS+="-nrk -DLFS2_READ_SIZE=11 -DLFS2_BLOCK_SIZE=704"
# report size
_: &report-size
# compile and find the code size with the smallest configuration
- make -j1 clean size
OBJ="$(ls lfs2*.c | sed 's/\.c/\.o/' | tr '\n' ' ')"
CFLAGS+="-DLFS2_NO_ASSERT -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_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 == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\").description
| capture(\"code size is (?<size>[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
# stage control
stages:
- name: test
- name: deploy
if: branch = master AND type = push
# job control
jobs:
# native testing
- &x86
stage: test
env:
- NAME=littlefs-x86
install: *install-common
script: [*test-example, *report-size]
- {<<: *x86, script: [*test-default, *report-size]}
- {<<: *x86, script: [*test-nor, *report-size]}
- {<<: *x86, script: [*test-emmc, *report-size]}
- {<<: *x86, script: [*test-nand, *report-size]}
- {<<: *x86, script: [*test-no-intrinsics, *report-size]}
- {<<: *x86, script: [*test-no-inline, *report-size]}
- {<<: *x86, script: [*test-byte-writes, *report-size]}
- {<<: *x86, script: [*test-block-cycles, *report-size]}
- {<<: *x86, script: [*test-odd-block-count, *report-size]}
- {<<: *x86, script: [*test-odd-block-size, *report-size]}
# cross-compile with ARM (thumb mode)
- &arm
stage: test
env:
- NAME=littlefs-arm
- CC="arm-linux-gnueabi-gcc --static -mthumb"
- TFLAGS="$TFLAGS --exec=qemu-arm"
install:
- *install-common
- sudo apt-get install
gcc-arm-linux-gnueabi
libc6-dev-armel-cross
qemu-user
- arm-linux-gnueabi-gcc --version
- qemu-arm -version
script: [*test-example, *report-size]
- {<<: *arm, script: [*test-default, *report-size]}
- {<<: *arm, script: [*test-nor, *report-size]}
- {<<: *arm, script: [*test-emmc, *report-size]}
- {<<: *arm, script: [*test-nand, *report-size]}
- {<<: *arm, script: [*test-no-intrinsics, *report-size]}
- {<<: *arm, script: [*test-no-inline, *report-size]}
# it just takes way to long to run byte-level writes in qemu,
# note this is still tested in the native tests
#- {<<: *arm, script: [*test-byte-writes, *report-size]}
- {<<: *arm, script: [*test-block-cycles, *report-size]}
- {<<: *arm, script: [*test-odd-block-count, *report-size]}
- {<<: *arm, script: [*test-odd-block-size, *report-size]}
# cross-compile with MIPS
- &mips
stage: test
env:
- NAME=littlefs-mips
- CC="mips-linux-gnu-gcc --static"
- TFLAGS="$TFLAGS --exec=qemu-mips"
install:
- *install-common
- sudo apt-get install
gcc-mips-linux-gnu
libc6-dev-mips-cross
qemu-user
- mips-linux-gnu-gcc --version
- qemu-mips -version
script: [*test-example, *report-size]
- {<<: *mips, script: [*test-default, *report-size]}
- {<<: *mips, script: [*test-nor, *report-size]}
- {<<: *mips, script: [*test-emmc, *report-size]}
- {<<: *mips, script: [*test-nand, *report-size]}
- {<<: *mips, script: [*test-no-intrinsics, *report-size]}
- {<<: *mips, script: [*test-no-inline, *report-size]}
# it just takes way to long to run byte-level writes in qemu,
# note this is still tested in the native tests
#- {<<: *mips, script: [*test-byte-writes, *report-size]}
- {<<: *mips, script: [*test-block-cycles, *report-size]}
- {<<: *mips, script: [*test-odd-block-count, *report-size]}
- {<<: *mips, script: [*test-odd-block-size, *report-size]}
# cross-compile with PowerPC
- &powerpc
stage: test
env:
- NAME=littlefs-powerpc
- CC="powerpc-linux-gnu-gcc --static"
- TFLAGS="$TFLAGS --exec=qemu-ppc"
install:
- *install-common
- sudo apt-get install
gcc-powerpc-linux-gnu
libc6-dev-powerpc-cross
qemu-user
- powerpc-linux-gnu-gcc --version
- qemu-ppc -version
script: [*test-example, *report-size]
- {<<: *powerpc, script: [*test-default, *report-size]}
- {<<: *powerpc, script: [*test-nor, *report-size]}
- {<<: *powerpc, script: [*test-emmc, *report-size]}
- {<<: *powerpc, script: [*test-nand, *report-size]}
- {<<: *powerpc, script: [*test-no-intrinsics, *report-size]}
- {<<: *powerpc, script: [*test-no-inline, *report-size]}
# it just takes way to long to run byte-level writes in qemu,
# note this is still tested in the native tests
#- {<<: *powerpc, script: [*test-byte-writes, *report-size]}
- {<<: *powerpc, script: [*test-block-cycles, *report-size]}
- {<<: *powerpc, script: [*test-odd-block-count, *report-size]}
- {<<: *powerpc, script: [*test-odd-block-size, *report-size]}
# test under valgrind, checking for memory errors
- &valgrind
stage: test
env:
- NAME=littlefs-valgrind
install:
- *install-common
- sudo apt-get install valgrind
- valgrind --version
script:
- make test TFLAGS+="-k --valgrind"
# self-host with littlefs-fuse for fuzz test
- stage: test
env:
- NAME=littlefs-fuse
if: branch !~ -prefix$
install:
- *install-common
- sudo apt-get install libfuse-dev
- git clone --depth 1 https://github.com/geky/littlefs-fuse -b v2
- fusermount -V
- gcc --version
# 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=128K of=disk
- losetup /dev/loop0 disk
script:
# self-host test
- make -C littlefs-fuse
- littlefs-fuse/lfs2 --format /dev/loop0
- littlefs-fuse/lfs2 /dev/loop0 mount
- ls mount
- mkdir mount/littlefs
- cp -r $(git ls-tree --name-only HEAD) mount/littlefs
- cd mount/littlefs
- stat .
- ls -flh
- make -B test
# test migration using littlefs-fuse
- stage: test
env:
- NAME=littlefs-migration
if: branch !~ -prefix$
install:
- *install-common
- sudo apt-get install libfuse-dev
- git clone --depth 1 https://github.com/geky/littlefs-fuse -b v2 v2
- git clone --depth 1 https://github.com/geky/littlefs-fuse -b v1 v1
- fusermount -V
- gcc --version
# setup disk for littlefs-fuse
- rm -rf v2/littlefs/*
- cp -r $(git ls-tree --name-only HEAD) v2/littlefs
- mkdir mount
- sudo chmod a+rw /dev/loop0
- dd if=/dev/zero bs=512 count=128K of=disk
- losetup /dev/loop0 disk
script:
# compile v1 and v2
- make -C v1
- make -C v2
# run self-host test with v1
- v1/lfs2 --format /dev/loop0
- v1/lfs2 /dev/loop0 mount
- ls mount
- mkdir mount/littlefs
- cp -r $(git ls-tree --name-only HEAD) mount/littlefs
- cd mount/littlefs
- stat .
- ls -flh
- make -B test
# attempt to migrate
- cd ../..
- fusermount -u mount
- v2/lfs2 --migrate /dev/loop0
- v2/lfs2 /dev/loop0 mount
# run self-host test with v2 right where we left off
- ls mount
- cd mount/littlefs
- stat .
- ls -flh
- make -B test
# automatically create releases
- stage: deploy
env:
- NAME=deploy
script:
- |
bash << 'SCRIPT'
set -ev
# Find version defined in lfs2.h
LFS2_VERSION=$(grep -ox '#define LFS2_VERSION .*' lfs2.h | cut -d ' ' -f3)
LFS2_VERSION_MAJOR=$((0xffff & ($LFS2_VERSION >> 16)))
LFS2_VERSION_MINOR=$((0xffff & ($LFS2_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$LFS2_VERSION_MAJOR.$LFS2_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)
LFS2_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
LFS2_VERSION="v$LFS2_VERSION_MAJOR.$LFS2_VERSION_MINOR.$LFS2_VERSION_PATCH"
echo "VERSION $LFS2_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')
[ "$TRAVIS_COMMIT" == "$CURRENT_COMMIT" ] || exit 0
# Create major branch
git branch v$LFS2_VERSION_MAJOR HEAD
# Create major prefix branch
git config user.name "geky bot"
git config user.email "bot@geky.net"
git fetch https://github.com/$TRAVIS_REPO_SLUG.git \
--depth=50 v$LFS2_VERSION_MAJOR-prefix || true
./scripts/prefix.py lfs2$LFS2_VERSION_MAJOR
git branch v$LFS2_VERSION_MAJOR-prefix $( \
git commit-tree $(git write-tree) \
$(git rev-parse --verify -q FETCH_HEAD | sed -e 's/^/-p /') \
-p HEAD \
-m "Generated v$LFS2_VERSION_MAJOR prefixes")
git reset --hard
# Update major version branches (vN and vN-prefix)
git push --atomic https://$GEKY_BOT_RELEASES@github.com/$TRAVIS_REPO_SLUG.git \
v$LFS2_VERSION_MAJOR \
v$LFS2_VERSION_MAJOR-prefix
# Build release notes
PREV=$(git tag --sort=-v:refname -l "v*" | head -1)
if [ ! -z "$PREV" ]
then
echo "PREV $PREV"
CHANGES=$(git log --oneline $PREV.. --grep='^Merge' --invert-grep)
printf "CHANGES\n%s\n\n" "$CHANGES"
fi
case ${GEKY_BOT_DRAFT:-minor} in
true) DRAFT=true ;;
minor) DRAFT=$(jq -R 'endswith(".0")' <<< "$LFS2_VERSION") ;;
false) DRAFT=false ;;
esac
# Create the release and patch version tag (vN.N.N)
curl -f -u "$GEKY_BOT_RELEASES" -X POST \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases \
-d "{
\"tag_name\": \"$LFS2_VERSION\",
\"name\": \"${LFS2_VERSION%.0}\",
\"target_commitish\": \"$TRAVIS_COMMIT\",
\"draft\": $DRAFT,
\"body\": $(jq -sR '.' <<< "$CHANGES")
}" #"
SCRIPT
# manage statuses
before_install:
- |
# don't clobber other (not us) failures
if ! curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
| jq -e ".statuses[] | select(
.context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\" and
.state == \"failure\" and
(.target_url | endswith(\"$TRAVIS_JOB_NUMBER\") | not))"
then
curl -u "$GEKY_BOT_STATUSES" -X POST \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
-d "{
\"context\": \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\",
\"state\": \"pending\",
\"description\": \"${STATUS:-In progress}\",
\"target_url\": \"$TRAVIS_JOB_WEB_URL#$TRAVIS_JOB_NUMBER\"
}"
fi
after_failure:
- |
# don't clobber other (not us) failures
if ! curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
| jq -e ".statuses[] | select(
.context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\" and
.state == \"failure\" and
(.target_url | endswith(\"$TRAVIS_JOB_NUMBER\") | not))"
then
curl -u "$GEKY_BOT_STATUSES" -X POST \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
-d "{
\"context\": \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\",
\"state\": \"failure\",
\"description\": \"${STATUS:-Failed}\",
\"target_url\": \"$TRAVIS_JOB_WEB_URL#$TRAVIS_JOB_NUMBER\"
}"
fi
after_success:
- |
# don't clobber other (not us) failures
# only update if we were last job to mark in progress,
# this isn't perfect but is probably good enough
if ! curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
| jq -e ".statuses[] | select(
.context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\" and
(.state == \"failure\" or .state == \"pending\") and
(.target_url | endswith(\"$TRAVIS_JOB_NUMBER\") | not))"
then
curl -u "$GEKY_BOT_STATUSES" -X POST \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
-d "{
\"context\": \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\",
\"state\": \"success\",
\"description\": \"${STATUS:-Passed}\",
\"target_url\": \"$TRAVIS_JOB_WEB_URL#$TRAVIS_JOB_NUMBER\"
}"
fi

File diff suppressed because it is too large Load Diff

View File

@ -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.

View File

@ -0,0 +1,69 @@
TARGET = lfs2.a
ifneq ($(wildcard test.c main.c),)
override TARGET = lfs2
endif
CC ?= gcc
AR ?= ar
SIZE ?= size
SRC += $(wildcard *.c bd/*.c)
OBJ := $(SRC:.c=.o)
DEP := $(SRC:.c=.d)
ASM := $(SRC:.c=.s)
ifdef DEBUG
override CFLAGS += -O0 -g3
else
override CFLAGS += -Os
endif
ifdef WORD
override CFLAGS += -m$(WORD)
endif
ifdef TRACE
override CFLAGS += -DLFS2_YES_TRACE
endif
override CFLAGS += -I.
override CFLAGS += -std=c99 -Wall -pedantic
override CFLAGS += -Wextra -Wshadow -Wjump-misses-init -Wundef
# Remove missing-field-initializers because of GCC bug
override CFLAGS += -Wno-missing-field-initializers
ifdef VERBOSE
override TFLAGS += -v
endif
all: $(TARGET)
asm: $(ASM)
size: $(OBJ)
$(SIZE) -t $^
test:
./scripts/test.py $(TFLAGS)
.SECONDEXPANSION:
test%: tests/test$$(firstword $$(subst \#, ,%)).toml
./scripts/test.py $@ $(TFLAGS)
-include $(DEP)
lfs2: $(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)
rm -f tests/*.toml.*

View File

@ -0,0 +1,252 @@
## littlefs
A little fail-safe filesystem designed for microcontrollers.
```
| | | .---._____
.-----. | |
--|o |---| littlefs |
--| |---| |
'-----' '----------'
| | |
```
**Power-loss resilience** - littlefs is designed to handle random power
failures. All file operations have strong copy-on-write guarantees and if
power is lost the filesystem will fall back to the last known good state.
**Dynamic wear leveling** - littlefs is designed with flash in mind, and
provides wear leveling over dynamic blocks. Additionally, littlefs can
detect bad blocks and work around them.
**Bounded RAM/ROM** - littlefs is designed to work with a small amount of
memory. RAM usage is strictly bounded, which means RAM consumption does not
change as the filesystem grows. The filesystem contains no unbounded
recursion and dynamic memory is limited to configurable buffers that can be
provided statically.
## 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 "lfs2.h"
// variables used by the filesystem
lfs2_t lfs2;
lfs2_file_t file;
// configuration of the filesystem is provided by this struct
const struct lfs2_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,
.cache_size = 16,
.lookahead_size = 16,
.block_cycles = 500,
};
// entry point
int main(void) {
// mount the filesystem
int err = lfs2_mount(&lfs2, &cfg);
// reformat if we can't mount the filesystem
// this should only happen on the first boot
if (err) {
lfs2_format(&lfs2, &cfg);
lfs2_mount(&lfs2, &cfg);
}
// read current count
uint32_t boot_count = 0;
lfs2_file_open(&lfs2, &file, "boot_count", LFS2_O_RDWR | LFS2_O_CREAT);
lfs2_file_read(&lfs2, &file, &boot_count, sizeof(boot_count));
// update boot count
boot_count += 1;
lfs2_file_rewind(&lfs2, &file);
lfs2_file_write(&lfs2, &file, &boot_count, sizeof(boot_count));
// remember the storage is not updated until the file is closed successfully
lfs2_file_close(&lfs2, &file);
// release any resources we were using
lfs2_unmount(&lfs2);
// 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 [lfs2.h](lfs2.h).
littlefs takes in a configuration structure that defines how the filesystem
operates. The configuration struct provides the filesystem with the block
device operations and dimensions, tweakable parameters that tradeoff memory
usage for performance, and optional static buffers if the user wants to avoid
dynamic memory.
The state of the littlefs is stored in the `lfs2_t` type which is left up
to the user to allocate, allowing multiple filesystems to be in use
simultaneously. With the `lfs2_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, file updates are not actually committed to
the filesystem until sync or close is called on the file.
## Other notes
Littlefs is written in C, and specifically should compile with any compiler
that conforms to the `C99` standard.
All littlefs calls have the potential to return a negative error code. The
errors can be either one of those found in the `enum lfs2_error` in
[lfs2.h](lfs2.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 `LFS2_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.
## Design
At a high level, littlefs is a block based filesystem that uses small logs to
store metadata and larger copy-on-write (COW) structures to store file data.
In littlefs, these ingredients form a sort of two-layered cake, with the small
logs (called metadata pairs) providing fast updates to metadata anywhere on
storage, while the COW structures store file data compactly and without any
wear amplification cost.
Both of these data structures are built out of blocks, which are fed by a
common block allocator. By limiting the number of erases allowed on a block
per allocation, the allocator provides dynamic wear leveling over the entire
filesystem.
```
root
.--------.--------.
| A'| B'| |
| | |-> |
| | | |
'--------'--------'
.----' '--------------.
A v B v
.--------.--------. .--------.--------.
| C'| D'| | | E'|new| |
| | |-> | | | E'|-> |
| | | | | | | |
'--------'--------' '--------'--------'
.-' '--. | '------------------.
v v .-' v
.--------. .--------. v .--------.
| C | | D | .--------. write | new E |
| | | | | E | ==> | |
| | | | | | | |
'--------' '--------' | | '--------'
'--------' .-' |
.-' '-. .-------------|------'
v v v v
.--------. .--------. .--------.
| F | | G | | new F |
| | | | | |
| | | | | |
'--------' '--------' '--------'
```
More details on how littlefs works can be found in [DESIGN.md](DESIGN.md) and
[SPEC.md](SPEC.md).
- [DESIGN.md](DESIGN.md) - A fully detailed dive into how littlefs works.
I would suggest reading it as the tradeoffs at work are quite interesting.
- [SPEC.md](SPEC.md) - The on-disk specification of littlefs with all the
nitty-gritty details. May be useful for tooling development.
## Testing
The littlefs comes with a test suite designed to run on a PC using the
[emulated block device](emubd/lfs2_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] 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
- [littlefs-fuse] - A [FUSE] 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] - 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][littlefs-js-demo].
- [mklfs] - A command line tool built by the [Lua RTOS] guys for making
littlefs images from a host PC. Supports Windows, Mac OS, and Linux.
- [Mbed OS] - The easiest way to get started with littlefs is to jump into Mbed
which already has block device drivers for most forms of embedded storage.
littlefs is available in Mbed OS as the [LittleFileSystem] class.
- [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] - 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.
[BSD-3-Clause]: https://spdx.org/licenses/BSD-3-Clause.html
[littlefs-fuse]: https://github.com/geky/littlefs-fuse
[FUSE]: https://github.com/libfuse/libfuse
[littlefs-js]: https://github.com/geky/littlefs-js
[littlefs-js-demo]:http://littlefs.geky.net/demo.html
[mklfs]: https://github.com/whitecatboard/Lua-RTOS-ESP32/tree/master/components/mklfs/src
[Lua RTOS]: https://github.com/whitecatboard/Lua-RTOS-ESP32
[Mbed OS]: https://github.com/armmbed/mbed-os
[LittleFileSystem]: https://os.mbed.com/docs/mbed-os/v5.12/apis/littlefilesystem.html
[SPIFFS]: https://github.com/pellepl/spiffs
[Dhara]: https://github.com/dlbeer/dhara

View File

@ -0,0 +1,787 @@
## littlefs 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. This document assumes you are familiar with the
design of the littlefs, for more info on how littlefs works check
out [DESIGN.md](DESIGN.md).
```
| | | .---._____
.-----. | |
--|o |---| littlefs |
--| |---| |
'-----' '----------'
| | |
```
## Some quick notes
- 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.
- Block pointers are stored in 32 bits, with the special value `0xffffffff`
representing a null block address.
- In addition to the logical block size (which usually matches the erase
block size), littlefs also uses a program block size and read block size.
These determine the alignment of block device operations, but don't need
to be consistent for portability.
- By default, all values in littlefs are stored in little-endian byte order.
## Directories / Metadata pairs
Metadata pairs form the backbone of littlefs and provide a system for
distributed atomic updates. Even the superblock is stored in a metadata pair.
As their name suggests, a metadata pair is stored in two blocks, with one block
providing a backup during erase cycles in case power is lost. These two blocks
are not necessarily sequential and may be anywhere on disk, so a "pointer" to a
metadata pair is stored as two block pointers.
On top of this, each metadata block behaves as an appendable log, containing a
variable number of commits. Commits can be appended to the metadata log in
order to update the metadata without requiring an erase cycles. Note that
successive commits may supersede the metadata in previous commits. Only the
most recent metadata should be considered valid.
The high-level layout of a metadata block is fairly simple:
```
.---------------------------------------.
.-| revision count | entries | \
| |-------------------+ | |
| | | |
| | | +-- 1st commit
| | | |
| | +-------------------| |
| | | CRC | /
| |-------------------+-------------------|
| | entries | \
| | | |
| | | +-- 2nd commit
| | +-------------------+--------------| |
| | | CRC | padding | /
| |----+-------------------+--------------|
| | entries | \
| | | |
| | | +-- 3rd commit
| | +-------------------+---------| |
| | | CRC | | /
| |---------+-------------------+ |
| | unwritten storage | more commits
| | | |
| | | v
| | |
| | |
| '---------------------------------------'
'---------------------------------------'
```
Each metadata block contains a 32-bit revision count followed by a number of
commits. Each commit contains a variable number of metadata entries followed
by a 32-bit CRC.
Note also that entries aren't necessarily word-aligned. This allows us to
store metadata more compactly, however we can only write to addresses that are
aligned to our program block size. This means each commit may have padding for
alignment.
Metadata block fields:
1. **Revision count (32-bits)** - Incremented every erase cycle. If both blocks
contain valid commits, only the block with the most recent revision count
should be used. Sequence comparison must be used to avoid issues with
integer overflow.
2. **CRC (32-bits)** - Detects corruption from power-loss or other write
issues. Uses a CRC-32 with a polynomial of `0x04c11db7` initialized
with `0xffffffff`.
Entries themselves are stored as a 32-bit tag followed by a variable length
blob of data. But exactly how these tags are stored is a little bit tricky.
Metadata blocks support both forward and backward iteration. In order to do
this without duplicating the space for each tag, neighboring entries have their
tags XORed together, starting with `0xffffffff`.
```
Forward iteration Backward iteration
.-------------------. 0xffffffff .-------------------.
| revision count | | | revision count |
|-------------------| v |-------------------|
| tag ~A |---> xor -> tag A | tag ~A |---> xor -> 0xffffffff
|-------------------| | |-------------------| ^
| data A | | | data A | |
| | | | | |
| | | | | |
|-------------------| v |-------------------| |
| tag AxB |---> xor -> tag B | tag AxB |---> xor -> tag A
|-------------------| | |-------------------| ^
| data B | | | data B | |
| | | | | |
| | | | | |
|-------------------| v |-------------------| |
| tag BxC |---> xor -> tag C | tag BxC |---> xor -> tag B
|-------------------| |-------------------| ^
| data C | | data C | |
| | | | tag C
| | | |
| | | |
'-------------------' '-------------------'
```
One last thing to note before we get into the details around tag encoding. Each
tag contains a valid bit used to indicate if the tag and containing commit is
valid. This valid bit is the first bit found in the tag and the commit and can
be used to tell if we've attempted to write to the remaining space in the
block.
Here's a more complete example of metadata block containing 4 entries:
```
.---------------------------------------.
.-| revision count | tag ~A | \
| |-------------------+-------------------| |
| | data A | |
| | | |
| |-------------------+-------------------| |
| | tag AxB | data B | <--. |
| |-------------------+ | | |
| | | | +-- 1st commit
| | +-------------------+---------| | |
| | | tag BxC | | <-.| |
| |---------+-------------------+ | || |
| | data C | || |
| | | || |
| |-------------------+-------------------| || |
| | tag CxCRC | CRC | || /
| |-------------------+-------------------| ||
| | tag CRCxA' | data A' | || \
| |-------------------+ | || |
| | | || |
| | +-------------------+----| || +-- 2nd commit
| | | tag CRCxA' | | || |
| |--------------+-------------------+----| || |
| | CRC | padding | || /
| |--------------+----+-------------------| ||
| | tag CRCxA'' | data A'' | <---. \
| |-------------------+ | ||| |
| | | ||| |
| | +-------------------+---------| ||| |
| | | tag A''xD | | < ||| |
| |---------+-------------------+ | |||| +-- 3rd commit
| | data D | |||| |
| | +---------| |||| |
| | | tag Dx| |||| |
| |---------+-------------------+---------| |||| |
| |CRC | CRC | | |||| /
| |---------+-------------------+ | ||||
| | unwritten storage | |||| more commits
| | | |||| |
| | | |||| v
| | | ||||
| | | ||||
| '---------------------------------------' ||||
'---------------------------------------' |||'- most recent A
||'-- most recent B
|'--- most recent C
'---- most recent D
```
## Metadata tags
So in littlefs, 32-bit tags describe every type of metadata. And this means
_every_ type of metadata, including file entries, directory fields, and
global state. Even the CRCs used to mark the end of commits get their own tag.
Because of this, the tag format contains some densely packed information. Note
that there are multiple levels of types which break down into more info:
```
[---- 32 ----]
[1|-- 11 --|-- 10 --|-- 10 --]
^. ^ . ^ ^- length
|. | . '------------ id
|. '-----.------------------ type (type3)
'.-----------.------------------ valid bit
[-3-|-- 8 --]
^ ^- chunk
'------- type (type1)
```
Before we go further, there's one important thing to note. These tags are
**not** stored in little-endian. Tags stored in commits are actually stored
in big-endian (and is the only thing in littlefs stored in big-endian). This
little bit of craziness comes from the fact that the valid bit must be the
first bit in a commit, and when converted to little-endian, the valid bit finds
itself in byte 4. We could restructure the tag to store the valid bit lower,
but, because none of the fields are byte-aligned, this would be more
complicated than just storing the tag in big-endian.
Another thing to note is that both the tags `0x00000000` and `0xffffffff` are
invalid and can be used for null values.
Metadata tag fields:
1. **Valid bit (1-bit)** - Indicates if the tag is valid.
2. **Type3 (11-bits)** - Type of the tag. This field is broken down further
into a 3-bit abstract type and an 8-bit chunk field. Note that the value
`0x000` is invalid and not assigned a type.
3. **Type1 (3-bits)** - Abstract type of the tag. Groups the tags into
8 categories that facilitate bitmasked lookups.
4. **Chunk (8-bits)** - Chunk field used for various purposes by the different
abstract types. type1+chunk+id form a unique identifier for each tag in the
metadata block.
5. **Id (10-bits)** - File id associated with the tag. Each file in a metadata
block gets a unique id which is used to associate tags with that file. The
special value `0x3ff` is used for any tags that are not associated with a
file, such as directory and global metadata.
6. **Length (10-bits)** - Length of the data in bytes. The special value
`0x3ff` indicates that this tag has been deleted.
## Metadata types
What follows is an exhaustive list of metadata in littlefs.
---
#### `0x401` LFS2_TYPE_CREATE
Creates a new file with this id. Note that files in a metadata block
don't necessarily need a create tag. All a create does is move over any
files using this id. In this sense a create is similar to insertion into
an imaginary array of files.
The create and delete tags allow littlefs to keep files in a directory
ordered alphabetically by filename.
---
#### `0x4ff` LFS2_TYPE_DELETE
Deletes the file with this id. An inverse to create, this tag moves over
any files neighboring this id similar to a deletion from an imaginary
array of files.
---
#### `0x0xx` LFS2_TYPE_NAME
Associates the id with a file name and file type.
The data contains the file name stored as an ASCII string (may be expanded to
UTF8 in the future).
The chunk field in this tag indicates an 8-bit file type which can be one of
the following.
Currently, the name tag must precede any other tags associated with the id and
can not be reassigned without deleting the file.
Layout of the name tag:
```
tag data
[-- 32 --][--- variable length ---]
[1| 3| 8 | 10 | 10 ][--- (size * 8) ---]
^ ^ ^ ^ ^- size ^- file name
| | | '------ id
| | '----------- file type
| '-------------- type1 (0x0)
'----------------- valid bit
```
Name fields:
1. **file type (8-bits)** - Type of the file.
2. **file name** - File name stored as an ASCII string.
---
#### `0x001` LFS2_TYPE_REG
Initializes the id + name as a regular file.
How each file is stored depends on its struct tag, which is described below.
---
#### `0x002` LFS2_TYPE_DIR
Initializes the id + name as a directory.
Directories in littlefs are stored on disk as a linked-list of metadata pairs,
each pair containing any number of files in alphabetical order. A pointer to
the directory is stored in the struct tag, which is described below.
---
#### `0x0ff` LFS2_TYPE_SUPERBLOCK
Initializes the id as a superblock entry.
The superblock entry is a special entry used to store format-time configuration
and identify the filesystem.
The name is a bit of a misnomer. While the superblock entry serves the same
purpose as a superblock found in other filesystems, in littlefs the superblock
does not get a dedicated block. Instead, the superblock entry is duplicated
across a linked-list of metadata pairs rooted on the blocks 0 and 1. The last
metadata pair doubles as the root directory of the filesystem.
```
.--------. .--------. .--------. .--------. .--------.
.| super |->| super |->| super |->| super |->| file B |
|| block | || block | || block | || block | || file C |
|| | || | || | || file A | || file D |
|'--------' |'--------' |'--------' |'--------' |'--------'
'--------' '--------' '--------' '--------' '--------'
\----------------+----------------/ \----------+----------/
superblock pairs root directory
```
The filesystem starts with only the root directory. The superblock metadata
pairs grow every time the root pair is compacted in order to prolong the
life of the device exponentially.
The contents of the superblock entry are stored in a name tag with the
superblock type and an inline-struct tag. The name tag contains the magic
string "littlefs", while the inline-struct tag contains version and
configuration information.
Layout of the superblock name tag and inline-struct tag:
```
tag data
[-- 32 --][-- 32 --|-- 32 --]
[1|- 11 -| 10 | 10 ][--- 64 ---]
^ ^ ^ ^- size (8) ^- magic string ("littlefs")
| | '------ id (0)
| '------------ type (0x0ff)
'----------------- valid bit
tag data
[-- 32 --][-- 32 --|-- 32 --|-- 32 --]
[1|- 11 -| 10 | 10 ][-- 32 --|-- 32 --|-- 32 --]
^ ^ ^ ^ ^- version ^- block size ^- block count
| | | | [-- 32 --|-- 32 --|-- 32 --]
| | | | [-- 32 --|-- 32 --|-- 32 --]
| | | | ^- name max ^- file max ^- attr max
| | | '- size (24)
| | '------ id (0)
| '------------ type (0x201)
'----------------- valid bit
```
Superblock fields:
1. **Magic string (8-bytes)** - Magic string indicating the presence of
littlefs on the device. Must be the string "littlefs".
2. **Version (32-bits)** - The version of littlefs at format time. The version
is encoded in a 32-bit value with the upper 16-bits containing the major
version, and the lower 16-bits containing the minor version.
This specification describes version 2.0 (`0x00020000`).
3. **Block size (32-bits)** - Size of the logical block size used by the
filesystem in bytes.
4. **Block count (32-bits)** - Number of blocks in the filesystem.
5. **Name max (32-bits)** - Maximum size of file names in bytes.
6. **File max (32-bits)** - Maximum size of files in bytes.
7. **Attr max (32-bits)** - Maximum size of file attributes in bytes.
The superblock must always be the first entry (id 0) in a metadata pair as well
as be the first entry written to the block. This means that the superblock
entry can be read from a device using offsets alone.
---
#### `0x2xx` LFS2_TYPE_STRUCT
Associates the id with an on-disk data structure.
The exact layout of the data depends on the data structure type stored in the
chunk field and can be one of the following.
Any type of struct supersedes all other structs associated with the id. For
example, appending a ctz-struct replaces an inline-struct on the same file.
---
#### `0x200` LFS2_TYPE_DIRSTRUCT
Gives the id a directory data structure.
Directories in littlefs are stored on disk as a linked-list of metadata pairs,
each pair containing any number of files in alphabetical order.
```
|
v
.--------. .--------. .--------. .--------. .--------. .--------.
.| file A |->| file D |->| file G |->| file I |->| file J |->| file M |
|| file B | || file E | || file H | || | || file K | || file N |
|| file C | || file F | || | || | || file L | || |
|'--------' |'--------' |'--------' |'--------' |'--------' |'--------'
'--------' '--------' '--------' '--------' '--------' '--------'
```
The dir-struct tag contains only the pointer to the first metadata-pair in the
directory. The directory size is not known without traversing the directory.
The pointer to the next metadata-pair in the directory is stored in a tail tag,
which is described below.
Layout of the dir-struct tag:
```
tag data
[-- 32 --][-- 32 --|-- 32 --]
[1|- 11 -| 10 | 10 ][--- 64 ---]
^ ^ ^ ^- size (8) ^- metadata pair
| | '------ id
| '------------ type (0x200)
'----------------- valid bit
```
Dir-struct fields:
1. **Metadata pair (8-bytes)** - Pointer to the first metadata-pair
in the directory.
---
#### `0x201` LFS2_TYPE_INLINESTRUCT
Gives the id an inline data structure.
Inline structs store small files that can fit in the metadata pair. In this
case, the file data is stored directly in the tag's data area.
Layout of the inline-struct tag:
```
tag data
[-- 32 --][--- variable length ---]
[1|- 11 -| 10 | 10 ][--- (size * 8) ---]
^ ^ ^ ^- size ^- inline data
| | '------ id
| '------------ type (0x201)
'----------------- valid bit
```
Inline-struct fields:
1. **Inline data** - File data stored directly in the metadata-pair.
---
#### `0x202` LFS2_TYPE_CTZSTRUCT
Gives the id a CTZ skip-list data structure.
CTZ skip-lists store files that can not fit in the metadata pair. These files
are stored in a skip-list in reverse, with a pointer to the head of the
skip-list. Note that the head of the skip-list and the file size is enough
information to read the file.
How exactly CTZ skip-lists work is a bit complicated. A full explanation can be
found in the [DESIGN.md](DESIGN.md#ctz-skip-lists).
A quick summary: For every _n_&zwj;th block where _n_ is divisible by
2&zwj;_&#739;_, that block contains a pointer to block _n_-2&zwj;_&#739;_.
These pointers are stored in increasing order of _x_ in each block of the file
before the actual data.
```
|
v
.--------. .--------. .--------. .--------. .--------. .--------.
| A |<-| D |<-| G |<-| J |<-| M |<-| P |
| B |<-| E |--| H |<-| K |--| N | | Q |
| C |<-| F |--| I |--| L |--| O | | |
'--------' '--------' '--------' '--------' '--------' '--------'
block 0 block 1 block 2 block 3 block 4 block 5
1 skip 2 skips 1 skip 3 skips 1 skip
```
Note that 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.
Layout of the CTZ-struct tag:
```
tag data
[-- 32 --][-- 32 --|-- 32 --]
[1|- 11 -| 10 | 10 ][-- 32 --|-- 32 --]
^ ^ ^ ^ ^ ^- file size
| | | | '-------------------- file head
| | | '- size (8)
| | '------ id
| '------------ type (0x202)
'----------------- valid bit
```
CTZ-struct fields:
1. **File head (32-bits)** - Pointer to the block that is the head of the
file's CTZ skip-list.
2. **File size (32-bits)** - Size of the file in bytes.
---
#### `0x3xx` LFS2_TYPE_USERATTR
Attaches a user attribute to an id.
littlefs has a concept of "user attributes". These are small user-provided
attributes that can be used to store things like timestamps, hashes,
permissions, etc.
Each user attribute is uniquely identified by an 8-bit type which is stored in
the chunk field, and the user attribute itself can be found in the tag's data.
There are currently no standard user attributes and a portable littlefs
implementation should work with any user attributes missing.
Layout of the user-attr tag:
```
tag data
[-- 32 --][--- variable length ---]
[1| 3| 8 | 10 | 10 ][--- (size * 8) ---]
^ ^ ^ ^ ^- size ^- attr data
| | | '------ id
| | '----------- attr type
| '-------------- type1 (0x3)
'----------------- valid bit
```
User-attr fields:
1. **Attr type (8-bits)** - Type of the user attributes.
2. **Attr data** - The data associated with the user attribute.
---
#### `0x6xx` LFS2_TYPE_TAIL
Provides the tail pointer for the metadata pair itself.
The metadata pair's tail pointer is used in littlefs for a linked-list
containing all metadata pairs. The chunk field contains the type of the tail,
which indicates if the following metadata pair is a part of the directory
(hard-tail) or only used to traverse the filesystem (soft-tail).
```
.--------.
.| dir A |-.
||softtail| |
.--------| |-'
| |'--------'
| '---|--|-'
| .-' '-------------.
| v v
| .--------. .--------. .--------.
'->| dir B |->| dir B |->| dir C |
||hardtail| ||softtail| || |
|| | || | || |
|'--------' |'--------' |'--------'
'--------' '--------' '--------'
```
Currently any type supersedes any other preceding tails in the metadata pair,
but this may change if additional metadata pair state is added.
A note about the metadata pair linked-list: Normally, this linked-list contains
every metadata pair in the filesystem. However, there are some operations that
can cause this linked-list to become out of sync if a power-loss were to occur.
When this happens, littlefs sets the "sync" flag in the global state. How
exactly this flag is stored is described below.
When the sync flag is set:
1. The linked-list may contain an orphaned directory that has been removed in
the filesystem.
2. The linked-list may contain a metadata pair with a bad block that has been
replaced in the filesystem.
If the sync flag is set, the threaded linked-list must be checked for these
errors before it can be used reliably. Note that the threaded linked-list can
be ignored if littlefs is mounted read-only.
Layout of the tail tag:
```
tag data
[-- 32 --][-- 32 --|-- 32 --]
[1| 3| 8 | 10 | 10 ][--- 64 ---]
^ ^ ^ ^ ^- size (8) ^- metadata pair
| | | '------ id
| | '---------- tail type
| '------------- type1 (0x6)
'---------------- valid bit
```
Tail fields:
1. **Tail type (8-bits)** - Type of the tail pointer.
2. **Metadata pair (8-bytes)** - Pointer to the next metadata-pair.
---
#### `0x600` LFS2_TYPE_SOFTTAIL
Provides a tail pointer that points to the next metadata pair in the
filesystem.
In this case, the next metadata pair is not a part of our current directory
and should only be followed when traversing the entire filesystem.
---
#### `0x601` LFS2_TYPE_HARDTAIL
Provides a tail pointer that points to the next metadata pair in the
directory.
In this case, the next metadata pair belongs to the current directory. Note
that because directories in littlefs are sorted alphabetically, the next
metadata pair should only contain filenames greater than any filename in the
current pair.
---
#### `0x7xx` LFS2_TYPE_GSTATE
Provides delta bits for global state entries.
littlefs has a concept of "global state". This is a small set of state that
can be updated by a commit to _any_ metadata pair in the filesystem.
The way this works is that the global state is stored as a set of deltas
distributed across the filesystem such that the global state can be found by
the xor-sum of these deltas.
```
.--------. .--------. .--------. .--------. .--------.
.| |->| gdelta |->| |->| gdelta |->| gdelta |
|| | || 0x23 | || | || 0xff | || 0xce |
|| | || | || | || | || |
|'--------' |'--------' |'--------' |'--------' |'--------'
'--------' '----|---' '--------' '----|---' '----|---'
v v v
0x00 --> xor ------------------> xor ------> xor --> gstate = 0x12
```
Note that storing globals this way is very expensive in terms of storage usage,
so any global state should be kept very small.
The size and format of each piece of global state depends on the type, which
is stored in the chunk field. Currently, the only global state is move state,
which is outlined below.
---
#### `0x7ff` LFS2_TYPE_MOVESTATE
Provides delta bits for the global move state.
The move state in littlefs is used to store info about operations that could
cause to filesystem to go out of sync if the power is lost. The operations
where this could occur is moves of files between metadata pairs and any
operation that changes metadata pairs on the threaded linked-list.
In the case of moves, the move state contains a tag + metadata pair describing
the source of the ongoing move. If this tag is non-zero, that means that power
was lost during a move, and the file exists in two different locations. If this
happens, the source of the move should be considered deleted, and the move
should be completed (the source should be deleted) before any other write
operations to the filesystem.
In the case of operations to the threaded linked-list, a single "sync" bit is
used to indicate that a modification is ongoing. If this sync flag is set, the
threaded linked-list will need to be checked for errors before it can be used
reliably. The exact cases to check for are described above in the tail tag.
Layout of the move state:
```
tag data
[-- 32 --][-- 32 --|-- 32 --|-- 32 --]
[1|- 11 -| 10 | 10 ][1|- 11 -| 10 | 10 |--- 64 ---]
^ ^ ^ ^ ^ ^ ^ ^- padding (0) ^- metadata pair
| | | | | | '------ move id
| | | | | '------------ move type
| | | | '----------------- sync bit
| | | |
| | | '- size (12)
| | '------ id (0x3ff)
| '------------ type (0x7ff)
'----------------- valid bit
```
Move state fields:
1. **Sync bit (1-bit)** - Indicates if the metadata pair threaded linked-list
is in-sync. If set, the threaded linked-list should be checked for errors.
2. **Move type (11-bits)** - Type of move being performed. Must be either
`0x000`, indicating no move, or `0x4ff` indicating the source file should
be deleted.
3. **Move id (10-bits)** - The file id being moved.
4. **Metadata pair (8-bytes)** - Pointer to the metadata-pair containing
the move.
---
#### `0x5xx` LFS2_TYPE_CRC
Last but not least, the CRC tag marks the end of a commit and provides a
checksum for any commits to the metadata block.
The first 32-bits of the data contain a CRC-32 with a polynomial of
`0x04c11db7` initialized with `0xffffffff`. This CRC provides a checksum for
all metadata since the previous CRC tag, including the CRC tag itself. For
the first commit, this includes the revision count for the metadata block.
However, the size of the data is not limited to 32-bits. The data field may
larger to pad the commit to the next program-aligned boundary.
In addition, the CRC tag's chunk field contains a set of flags which can
change the behaviour of commits. Currently the only flag in use is the lowest
bit, which determines the expected state of the valid bit for any following
tags. This is used to guarantee that unwritten storage in a metadata block
will be detected as invalid.
Layout of the CRC tag:
```
tag data
[-- 32 --][-- 32 --|--- variable length ---]
[1| 3| 8 | 10 | 10 ][-- 32 --|--- (size * 8 - 32) ---]
^ ^ ^ ^ ^ ^- crc ^- padding
| | | | '- size
| | | '------ id (0x3ff)
| | '----------- valid state
| '-------------- type1 (0x5)
'----------------- valid bit
```
CRC fields:
1. **Valid state (1-bit)** - Indicates the expected value of the valid bit for
any tags in the next commit.
2. **CRC (32-bits)** - CRC-32 with a polynomial of `0x04c11db7` initialized
with `0xffffffff`.
3. **Padding** - Padding to the next program-aligned boundary. No guarantees
are made about the contents.
---

View File

@ -0,0 +1,205 @@
/*
* Block device emulated in a file
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "bd/lfs2_filebd.h"
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
int lfs2_filebd_createcfg(const struct lfs2_config *cfg, const char *path,
const struct lfs2_filebd_config *bdcfg) {
LFS2_FILEBD_TRACE("lfs2_filebd_createcfg(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
"\"%s\", "
"%p {.erase_value=%"PRId32"})",
(void*)cfg, cfg->context,
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
path, (void*)bdcfg, bdcfg->erase_value);
lfs2_filebd_t *bd = cfg->context;
bd->cfg = bdcfg;
// open file
bd->fd = open(path, O_RDWR | O_CREAT, 0666);
if (bd->fd < 0) {
int err = -errno;
LFS2_FILEBD_TRACE("lfs2_filebd_createcfg -> %d", err);
return err;
}
LFS2_FILEBD_TRACE("lfs2_filebd_createcfg -> %d", 0);
return 0;
}
int lfs2_filebd_create(const struct lfs2_config *cfg, const char *path) {
LFS2_FILEBD_TRACE("lfs2_filebd_create(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
"\"%s\")",
(void*)cfg, cfg->context,
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
path);
static const struct lfs2_filebd_config defaults = {.erase_value=-1};
int err = lfs2_filebd_createcfg(cfg, path, &defaults);
LFS2_FILEBD_TRACE("lfs2_filebd_create -> %d", err);
return err;
}
int lfs2_filebd_destroy(const struct lfs2_config *cfg) {
LFS2_FILEBD_TRACE("lfs2_filebd_destroy(%p)", (void*)cfg);
lfs2_filebd_t *bd = cfg->context;
int err = close(bd->fd);
if (err < 0) {
err = -errno;
LFS2_FILEBD_TRACE("lfs2_filebd_destroy -> %d", err);
return err;
}
LFS2_FILEBD_TRACE("lfs2_filebd_destroy -> %d", 0);
return 0;
}
int lfs2_filebd_read(const struct lfs2_config *cfg, lfs2_block_t block,
lfs2_off_t off, void *buffer, lfs2_size_t size) {
LFS2_FILEBD_TRACE("lfs2_filebd_read(%p, "
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size);
lfs2_filebd_t *bd = cfg->context;
// check if read is valid
LFS2_ASSERT(off % cfg->read_size == 0);
LFS2_ASSERT(size % cfg->read_size == 0);
LFS2_ASSERT(block < cfg->block_count);
// zero for reproducability (in case file is truncated)
if (bd->cfg->erase_value != -1) {
memset(buffer, bd->cfg->erase_value, size);
}
// read
off_t res1 = lseek(bd->fd,
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
if (res1 < 0) {
int err = -errno;
LFS2_FILEBD_TRACE("lfs2_filebd_read -> %d", err);
return err;
}
ssize_t res2 = read(bd->fd, buffer, size);
if (res2 < 0) {
int err = -errno;
LFS2_FILEBD_TRACE("lfs2_filebd_read -> %d", err);
return err;
}
LFS2_FILEBD_TRACE("lfs2_filebd_read -> %d", 0);
return 0;
}
int lfs2_filebd_prog(const struct lfs2_config *cfg, lfs2_block_t block,
lfs2_off_t off, const void *buffer, lfs2_size_t size) {
LFS2_FILEBD_TRACE("lfs2_filebd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size);
lfs2_filebd_t *bd = cfg->context;
// check if write is valid
LFS2_ASSERT(off % cfg->prog_size == 0);
LFS2_ASSERT(size % cfg->prog_size == 0);
LFS2_ASSERT(block < cfg->block_count);
// check that data was erased? only needed for testing
if (bd->cfg->erase_value != -1) {
off_t res1 = lseek(bd->fd,
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
if (res1 < 0) {
int err = -errno;
LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", err);
return err;
}
for (lfs2_off_t i = 0; i < size; i++) {
uint8_t c;
ssize_t res2 = read(bd->fd, &c, 1);
if (res2 < 0) {
int err = -errno;
LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", err);
return err;
}
LFS2_ASSERT(c == bd->cfg->erase_value);
}
}
// program data
off_t res1 = lseek(bd->fd,
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
if (res1 < 0) {
int err = -errno;
LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", err);
return err;
}
ssize_t res2 = write(bd->fd, buffer, size);
if (res2 < 0) {
int err = -errno;
LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", err);
return err;
}
LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", 0);
return 0;
}
int lfs2_filebd_erase(const struct lfs2_config *cfg, lfs2_block_t block) {
LFS2_FILEBD_TRACE("lfs2_filebd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
lfs2_filebd_t *bd = cfg->context;
// check if erase is valid
LFS2_ASSERT(block < cfg->block_count);
// erase, only needed for testing
if (bd->cfg->erase_value != -1) {
off_t res1 = lseek(bd->fd, (off_t)block*cfg->block_size, SEEK_SET);
if (res1 < 0) {
int err = -errno;
LFS2_FILEBD_TRACE("lfs2_filebd_erase -> %d", err);
return err;
}
for (lfs2_off_t i = 0; i < cfg->block_size; i++) {
ssize_t res2 = write(bd->fd, &(uint8_t){bd->cfg->erase_value}, 1);
if (res2 < 0) {
int err = -errno;
LFS2_FILEBD_TRACE("lfs2_filebd_erase -> %d", err);
return err;
}
}
}
LFS2_FILEBD_TRACE("lfs2_filebd_erase -> %d", 0);
return 0;
}
int lfs2_filebd_sync(const struct lfs2_config *cfg) {
LFS2_FILEBD_TRACE("lfs2_filebd_sync(%p)", (void*)cfg);
// file sync
lfs2_filebd_t *bd = cfg->context;
int err = fsync(bd->fd);
if (err) {
err = -errno;
LFS2_FILEBD_TRACE("lfs2_filebd_sync -> %d", 0);
return err;
}
LFS2_FILEBD_TRACE("lfs2_filebd_sync -> %d", 0);
return 0;
}

View File

@ -0,0 +1,73 @@
/*
* Block device emulated in a file
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef LFS2_FILEBD_H
#define LFS2_FILEBD_H
#include "lfs2.h"
#include "lfs2_util.h"
#ifdef __cplusplus
extern "C"
{
#endif
// Block device specific tracing
#ifdef LFS2_FILEBD_YES_TRACE
#define LFS2_FILEBD_TRACE(...) LFS2_TRACE(__VA_ARGS__)
#else
#define LFS2_FILEBD_TRACE(...)
#endif
// filebd config (optional)
struct lfs2_filebd_config {
// 8-bit erase value to use for simulating erases. -1 does not simulate
// erases, which can speed up testing by avoiding all the extra block-device
// operations to store the erase value.
int32_t erase_value;
};
// filebd state
typedef struct lfs2_filebd {
int fd;
const struct lfs2_filebd_config *cfg;
} lfs2_filebd_t;
// Create a file block device using the geometry in lfs2_config
int lfs2_filebd_create(const struct lfs2_config *cfg, const char *path);
int lfs2_filebd_createcfg(const struct lfs2_config *cfg, const char *path,
const struct lfs2_filebd_config *bdcfg);
// Clean up memory associated with block device
int lfs2_filebd_destroy(const struct lfs2_config *cfg);
// Read a block
int lfs2_filebd_read(const struct lfs2_config *cfg, lfs2_block_t block,
lfs2_off_t off, void *buffer, lfs2_size_t size);
// Program a block
//
// The block must have previously been erased.
int lfs2_filebd_prog(const struct lfs2_config *cfg, lfs2_block_t block,
lfs2_off_t off, const void *buffer, lfs2_size_t size);
// Erase a block
//
// A block must be erased before being programmed. The
// state of an erased block is undefined.
int lfs2_filebd_erase(const struct lfs2_config *cfg, lfs2_block_t block);
// Sync the block device
int lfs2_filebd_sync(const struct lfs2_config *cfg);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@ -0,0 +1,140 @@
/*
* Block device emulated in RAM
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "bd/lfs2_rambd.h"
int lfs2_rambd_createcfg(const struct lfs2_config *cfg,
const struct lfs2_rambd_config *bdcfg) {
LFS2_RAMBD_TRACE("lfs2_rambd_createcfg(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
"%p {.erase_value=%"PRId32", .buffer=%p})",
(void*)cfg, cfg->context,
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
(void*)bdcfg, bdcfg->erase_value, bdcfg->buffer);
lfs2_rambd_t *bd = cfg->context;
bd->cfg = bdcfg;
// allocate buffer?
if (bd->cfg->buffer) {
bd->buffer = bd->cfg->buffer;
} else {
bd->buffer = lfs2_malloc(cfg->block_size * cfg->block_count);
if (!bd->buffer) {
LFS2_RAMBD_TRACE("lfs2_rambd_createcfg -> %d", LFS2_ERR_NOMEM);
return LFS2_ERR_NOMEM;
}
}
// zero for reproducability?
if (bd->cfg->erase_value != -1) {
memset(bd->buffer, bd->cfg->erase_value,
cfg->block_size * cfg->block_count);
}
LFS2_RAMBD_TRACE("lfs2_rambd_createcfg -> %d", 0);
return 0;
}
int lfs2_rambd_create(const struct lfs2_config *cfg) {
LFS2_RAMBD_TRACE("lfs2_rambd_create(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"})",
(void*)cfg, cfg->context,
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count);
static const struct lfs2_rambd_config defaults = {.erase_value=-1};
int err = lfs2_rambd_createcfg(cfg, &defaults);
LFS2_RAMBD_TRACE("lfs2_rambd_create -> %d", err);
return err;
}
int lfs2_rambd_destroy(const struct lfs2_config *cfg) {
LFS2_RAMBD_TRACE("lfs2_rambd_destroy(%p)", (void*)cfg);
// clean up memory
lfs2_rambd_t *bd = cfg->context;
if (!bd->cfg->buffer) {
lfs2_free(bd->buffer);
}
LFS2_RAMBD_TRACE("lfs2_rambd_destroy -> %d", 0);
return 0;
}
int lfs2_rambd_read(const struct lfs2_config *cfg, lfs2_block_t block,
lfs2_off_t off, void *buffer, lfs2_size_t size) {
LFS2_RAMBD_TRACE("lfs2_rambd_read(%p, "
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size);
lfs2_rambd_t *bd = cfg->context;
// check if read is valid
LFS2_ASSERT(off % cfg->read_size == 0);
LFS2_ASSERT(size % cfg->read_size == 0);
LFS2_ASSERT(block < cfg->block_count);
// read data
memcpy(buffer, &bd->buffer[block*cfg->block_size + off], size);
LFS2_RAMBD_TRACE("lfs2_rambd_read -> %d", 0);
return 0;
}
int lfs2_rambd_prog(const struct lfs2_config *cfg, lfs2_block_t block,
lfs2_off_t off, const void *buffer, lfs2_size_t size) {
LFS2_RAMBD_TRACE("lfs2_rambd_prog(%p, "
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size);
lfs2_rambd_t *bd = cfg->context;
// check if write is valid
LFS2_ASSERT(off % cfg->prog_size == 0);
LFS2_ASSERT(size % cfg->prog_size == 0);
LFS2_ASSERT(block < cfg->block_count);
// check that data was erased? only needed for testing
if (bd->cfg->erase_value != -1) {
for (lfs2_off_t i = 0; i < size; i++) {
LFS2_ASSERT(bd->buffer[block*cfg->block_size + off + i] ==
bd->cfg->erase_value);
}
}
// program data
memcpy(&bd->buffer[block*cfg->block_size + off], buffer, size);
LFS2_RAMBD_TRACE("lfs2_rambd_prog -> %d", 0);
return 0;
}
int lfs2_rambd_erase(const struct lfs2_config *cfg, lfs2_block_t block) {
LFS2_RAMBD_TRACE("lfs2_rambd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
lfs2_rambd_t *bd = cfg->context;
// check if erase is valid
LFS2_ASSERT(block < cfg->block_count);
// erase, only needed for testing
if (bd->cfg->erase_value != -1) {
memset(&bd->buffer[block*cfg->block_size],
bd->cfg->erase_value, cfg->block_size);
}
LFS2_RAMBD_TRACE("lfs2_rambd_erase -> %d", 0);
return 0;
}
int lfs2_rambd_sync(const struct lfs2_config *cfg) {
LFS2_RAMBD_TRACE("lfs2_rambd_sync(%p)", (void*)cfg);
// sync does nothing because we aren't backed by anything real
(void)cfg;
LFS2_RAMBD_TRACE("lfs2_rambd_sync -> %d", 0);
return 0;
}

View File

@ -0,0 +1,75 @@
/*
* Block device emulated in RAM
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef LFS2_RAMBD_H
#define LFS2_RAMBD_H
#include "lfs2.h"
#include "lfs2_util.h"
#ifdef __cplusplus
extern "C"
{
#endif
// Block device specific tracing
#ifdef LFS2_RAMBD_YES_TRACE
#define LFS2_RAMBD_TRACE(...) LFS2_TRACE(__VA_ARGS__)
#else
#define LFS2_RAMBD_TRACE(...)
#endif
// rambd config (optional)
struct lfs2_rambd_config {
// 8-bit erase value to simulate erasing with. -1 indicates no erase
// occurs, which is still a valid block device
int32_t erase_value;
// Optional statically allocated buffer for the block device.
void *buffer;
};
// rambd state
typedef struct lfs2_rambd {
uint8_t *buffer;
const struct lfs2_rambd_config *cfg;
} lfs2_rambd_t;
// Create a RAM block device using the geometry in lfs2_config
int lfs2_rambd_create(const struct lfs2_config *cfg);
int lfs2_rambd_createcfg(const struct lfs2_config *cfg,
const struct lfs2_rambd_config *bdcfg);
// Clean up memory associated with block device
int lfs2_rambd_destroy(const struct lfs2_config *cfg);
// Read a block
int lfs2_rambd_read(const struct lfs2_config *cfg, lfs2_block_t block,
lfs2_off_t off, void *buffer, lfs2_size_t size);
// Program a block
//
// The block must have previously been erased.
int lfs2_rambd_prog(const struct lfs2_config *cfg, lfs2_block_t block,
lfs2_off_t off, const void *buffer, lfs2_size_t size);
// Erase a block
//
// A block must be erased before being programmed. The
// state of an erased block is undefined.
int lfs2_rambd_erase(const struct lfs2_config *cfg, lfs2_block_t block);
// Sync the block device
int lfs2_rambd_sync(const struct lfs2_config *cfg);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@ -0,0 +1,302 @@
/*
* Testing block device, wraps filebd and rambd while providing a bunch
* of hooks for testing littlefs in various conditions.
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "bd/lfs2_testbd.h"
#include <stdlib.h>
int lfs2_testbd_createcfg(const struct lfs2_config *cfg, const char *path,
const struct lfs2_testbd_config *bdcfg) {
LFS2_TESTBD_TRACE("lfs2_testbd_createcfg(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
"\"%s\", "
"%p {.erase_value=%"PRId32", .erase_cycles=%"PRIu32", "
".badblock_behavior=%"PRIu8", .power_cycles=%"PRIu32", "
".buffer=%p, .wear_buffer=%p})",
(void*)cfg, cfg->context,
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
path, (void*)bdcfg, bdcfg->erase_value, bdcfg->erase_cycles,
bdcfg->badblock_behavior, bdcfg->power_cycles,
bdcfg->buffer, bdcfg->wear_buffer);
lfs2_testbd_t *bd = cfg->context;
bd->cfg = bdcfg;
// setup testing things
bd->persist = path;
bd->power_cycles = bd->cfg->power_cycles;
if (bd->cfg->erase_cycles) {
if (bd->cfg->wear_buffer) {
bd->wear = bd->cfg->wear_buffer;
} else {
bd->wear = lfs2_malloc(sizeof(lfs2_testbd_wear_t)*cfg->block_count);
if (!bd->wear) {
LFS2_TESTBD_TRACE("lfs2_testbd_createcfg -> %d", LFS2_ERR_NOMEM);
return LFS2_ERR_NOMEM;
}
}
memset(bd->wear, 0, sizeof(lfs2_testbd_wear_t) * cfg->block_count);
}
// create underlying block device
if (bd->persist) {
bd->u.file.cfg = (struct lfs2_filebd_config){
.erase_value = bd->cfg->erase_value,
};
int err = lfs2_filebd_createcfg(cfg, path, &bd->u.file.cfg);
LFS2_TESTBD_TRACE("lfs2_testbd_createcfg -> %d", err);
return err;
} else {
bd->u.ram.cfg = (struct lfs2_rambd_config){
.erase_value = bd->cfg->erase_value,
.buffer = bd->cfg->buffer,
};
int err = lfs2_rambd_createcfg(cfg, &bd->u.ram.cfg);
LFS2_TESTBD_TRACE("lfs2_testbd_createcfg -> %d", err);
return err;
}
}
int lfs2_testbd_create(const struct lfs2_config *cfg, const char *path) {
LFS2_TESTBD_TRACE("lfs2_testbd_create(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
"\"%s\")",
(void*)cfg, cfg->context,
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
path);
static const struct lfs2_testbd_config defaults = {.erase_value=-1};
int err = lfs2_testbd_createcfg(cfg, path, &defaults);
LFS2_TESTBD_TRACE("lfs2_testbd_create -> %d", err);
return err;
}
int lfs2_testbd_destroy(const struct lfs2_config *cfg) {
LFS2_TESTBD_TRACE("lfs2_testbd_destroy(%p)", (void*)cfg);
lfs2_testbd_t *bd = cfg->context;
if (bd->cfg->erase_cycles && !bd->cfg->wear_buffer) {
lfs2_free(bd->wear);
}
if (bd->persist) {
int err = lfs2_filebd_destroy(cfg);
LFS2_TESTBD_TRACE("lfs2_testbd_destroy -> %d", err);
return err;
} else {
int err = lfs2_rambd_destroy(cfg);
LFS2_TESTBD_TRACE("lfs2_testbd_destroy -> %d", err);
return err;
}
}
/// Internal mapping to block devices ///
static int lfs2_testbd_rawread(const struct lfs2_config *cfg, lfs2_block_t block,
lfs2_off_t off, void *buffer, lfs2_size_t size) {
lfs2_testbd_t *bd = cfg->context;
if (bd->persist) {
return lfs2_filebd_read(cfg, block, off, buffer, size);
} else {
return lfs2_rambd_read(cfg, block, off, buffer, size);
}
}
static int lfs2_testbd_rawprog(const struct lfs2_config *cfg, lfs2_block_t block,
lfs2_off_t off, const void *buffer, lfs2_size_t size) {
lfs2_testbd_t *bd = cfg->context;
if (bd->persist) {
return lfs2_filebd_prog(cfg, block, off, buffer, size);
} else {
return lfs2_rambd_prog(cfg, block, off, buffer, size);
}
}
static int lfs2_testbd_rawerase(const struct lfs2_config *cfg,
lfs2_block_t block) {
lfs2_testbd_t *bd = cfg->context;
if (bd->persist) {
return lfs2_filebd_erase(cfg, block);
} else {
return lfs2_rambd_erase(cfg, block);
}
}
static int lfs2_testbd_rawsync(const struct lfs2_config *cfg) {
lfs2_testbd_t *bd = cfg->context;
if (bd->persist) {
return lfs2_filebd_sync(cfg);
} else {
return lfs2_rambd_sync(cfg);
}
}
/// block device API ///
int lfs2_testbd_read(const struct lfs2_config *cfg, lfs2_block_t block,
lfs2_off_t off, void *buffer, lfs2_size_t size) {
LFS2_TESTBD_TRACE("lfs2_testbd_read(%p, "
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size);
lfs2_testbd_t *bd = cfg->context;
// check if read is valid
LFS2_ASSERT(off % cfg->read_size == 0);
LFS2_ASSERT(size % cfg->read_size == 0);
LFS2_ASSERT(block < cfg->block_count);
// block bad?
if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles &&
bd->cfg->badblock_behavior == LFS2_TESTBD_BADBLOCK_READERROR) {
LFS2_TESTBD_TRACE("lfs2_testbd_read -> %d", LFS2_ERR_CORRUPT);
return LFS2_ERR_CORRUPT;
}
// read
int err = lfs2_testbd_rawread(cfg, block, off, buffer, size);
LFS2_TESTBD_TRACE("lfs2_testbd_read -> %d", err);
return err;
}
int lfs2_testbd_prog(const struct lfs2_config *cfg, lfs2_block_t block,
lfs2_off_t off, const void *buffer, lfs2_size_t size) {
LFS2_TESTBD_TRACE("lfs2_testbd_prog(%p, "
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size);
lfs2_testbd_t *bd = cfg->context;
// check if write is valid
LFS2_ASSERT(off % cfg->prog_size == 0);
LFS2_ASSERT(size % cfg->prog_size == 0);
LFS2_ASSERT(block < cfg->block_count);
// block bad?
if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles) {
if (bd->cfg->badblock_behavior ==
LFS2_TESTBD_BADBLOCK_PROGERROR) {
LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", LFS2_ERR_CORRUPT);
return LFS2_ERR_CORRUPT;
} else if (bd->cfg->badblock_behavior ==
LFS2_TESTBD_BADBLOCK_PROGNOOP ||
bd->cfg->badblock_behavior ==
LFS2_TESTBD_BADBLOCK_ERASENOOP) {
LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", 0);
return 0;
}
}
// prog
int err = lfs2_testbd_rawprog(cfg, block, off, buffer, size);
if (err) {
LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", err);
return err;
}
// lose power?
if (bd->power_cycles > 0) {
bd->power_cycles -= 1;
if (bd->power_cycles == 0) {
// sync to make sure we persist the last changes
assert(lfs2_testbd_rawsync(cfg) == 0);
// simulate power loss
exit(33);
}
}
LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", 0);
return 0;
}
int lfs2_testbd_erase(const struct lfs2_config *cfg, lfs2_block_t block) {
LFS2_TESTBD_TRACE("lfs2_testbd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
lfs2_testbd_t *bd = cfg->context;
// check if erase is valid
LFS2_ASSERT(block < cfg->block_count);
// block bad?
if (bd->cfg->erase_cycles) {
if (bd->wear[block] >= bd->cfg->erase_cycles) {
if (bd->cfg->badblock_behavior ==
LFS2_TESTBD_BADBLOCK_ERASEERROR) {
LFS2_TESTBD_TRACE("lfs2_testbd_erase -> %d", LFS2_ERR_CORRUPT);
return LFS2_ERR_CORRUPT;
} else if (bd->cfg->badblock_behavior ==
LFS2_TESTBD_BADBLOCK_ERASENOOP) {
LFS2_TESTBD_TRACE("lfs2_testbd_erase -> %d", 0);
return 0;
}
} else {
// mark wear
bd->wear[block] += 1;
}
}
// erase
int err = lfs2_testbd_rawerase(cfg, block);
if (err) {
LFS2_TESTBD_TRACE("lfs2_testbd_erase -> %d", err);
return err;
}
// lose power?
if (bd->power_cycles > 0) {
bd->power_cycles -= 1;
if (bd->power_cycles == 0) {
// sync to make sure we persist the last changes
assert(lfs2_testbd_rawsync(cfg) == 0);
// simulate power loss
exit(33);
}
}
LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", 0);
return 0;
}
int lfs2_testbd_sync(const struct lfs2_config *cfg) {
LFS2_TESTBD_TRACE("lfs2_testbd_sync(%p)", (void*)cfg);
int err = lfs2_testbd_rawsync(cfg);
LFS2_TESTBD_TRACE("lfs2_testbd_sync -> %d", err);
return err;
}
/// simulated wear operations ///
lfs2_testbd_swear_t lfs2_testbd_getwear(const struct lfs2_config *cfg,
lfs2_block_t block) {
LFS2_TESTBD_TRACE("lfs2_testbd_getwear(%p, %"PRIu32")", (void*)cfg, block);
lfs2_testbd_t *bd = cfg->context;
// check if block is valid
LFS2_ASSERT(bd->cfg->erase_cycles);
LFS2_ASSERT(block < cfg->block_count);
LFS2_TESTBD_TRACE("lfs2_testbd_getwear -> %"PRIu32, bd->wear[block]);
return bd->wear[block];
}
int lfs2_testbd_setwear(const struct lfs2_config *cfg,
lfs2_block_t block, lfs2_testbd_wear_t wear) {
LFS2_TESTBD_TRACE("lfs2_testbd_setwear(%p, %"PRIu32")", (void*)cfg, block);
lfs2_testbd_t *bd = cfg->context;
// check if block is valid
LFS2_ASSERT(bd->cfg->erase_cycles);
LFS2_ASSERT(block < cfg->block_count);
bd->wear[block] = wear;
LFS2_TESTBD_TRACE("lfs2_testbd_setwear -> %d", 0);
return 0;
}

View File

@ -0,0 +1,141 @@
/*
* Testing block device, wraps filebd and rambd while providing a bunch
* of hooks for testing littlefs in various conditions.
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef LFS2_TESTBD_H
#define LFS2_TESTBD_H
#include "lfs2.h"
#include "lfs2_util.h"
#include "bd/lfs2_rambd.h"
#include "bd/lfs2_filebd.h"
#ifdef __cplusplus
extern "C"
{
#endif
// Block device specific tracing
#ifdef LFS2_TESTBD_YES_TRACE
#define LFS2_TESTBD_TRACE(...) LFS2_TRACE(__VA_ARGS__)
#else
#define LFS2_TESTBD_TRACE(...)
#endif
// Mode determining how "bad blocks" behave during testing. This simulates
// some real-world circumstances such as progs not sticking (prog-noop),
// a readonly disk (erase-noop), and ECC failures (read-error).
//
// Not that read-noop is not allowed. Read _must_ return a consistent (but
// may be arbitrary) value on every read.
enum lfs2_testbd_badblock_behavior {
LFS2_TESTBD_BADBLOCK_PROGERROR,
LFS2_TESTBD_BADBLOCK_ERASEERROR,
LFS2_TESTBD_BADBLOCK_READERROR,
LFS2_TESTBD_BADBLOCK_PROGNOOP,
LFS2_TESTBD_BADBLOCK_ERASENOOP,
};
// Type for measuring wear
typedef uint32_t lfs2_testbd_wear_t;
typedef int32_t lfs2_testbd_swear_t;
// testbd config, this is required for testing
struct lfs2_testbd_config {
// 8-bit erase value to use for simulating erases. -1 does not simulate
// erases, which can speed up testing by avoiding all the extra block-device
// operations to store the erase value.
int32_t erase_value;
// Number of erase cycles before a block becomes "bad". The exact behavior
// of bad blocks is controlled by the badblock_mode.
uint32_t erase_cycles;
// The mode determining how bad blocks fail
uint8_t badblock_behavior;
// Number of write operations (erase/prog) before forcefully killing
// the program with exit. Simulates power-loss. 0 disables.
uint32_t power_cycles;
// Optional buffer for RAM block device.
void *buffer;
// Optional buffer for wear
void *wear_buffer;
};
// testbd state
typedef struct lfs2_testbd {
union {
struct {
lfs2_filebd_t bd;
struct lfs2_filebd_config cfg;
} file;
struct {
lfs2_rambd_t bd;
struct lfs2_rambd_config cfg;
} ram;
} u;
bool persist;
uint32_t power_cycles;
lfs2_testbd_wear_t *wear;
const struct lfs2_testbd_config *cfg;
} lfs2_testbd_t;
/// Block device API ///
// Create a test block device using the geometry in lfs2_config
//
// Note that filebd is used if a path is provided, if path is NULL
// testbd will use rambd which can be much faster.
int lfs2_testbd_create(const struct lfs2_config *cfg, const char *path);
int lfs2_testbd_createcfg(const struct lfs2_config *cfg, const char *path,
const struct lfs2_testbd_config *bdcfg);
// Clean up memory associated with block device
int lfs2_testbd_destroy(const struct lfs2_config *cfg);
// Read a block
int lfs2_testbd_read(const struct lfs2_config *cfg, lfs2_block_t block,
lfs2_off_t off, void *buffer, lfs2_size_t size);
// Program a block
//
// The block must have previously been erased.
int lfs2_testbd_prog(const struct lfs2_config *cfg, lfs2_block_t block,
lfs2_off_t off, const void *buffer, lfs2_size_t size);
// Erase a block
//
// A block must be erased before being programmed. The
// state of an erased block is undefined.
int lfs2_testbd_erase(const struct lfs2_config *cfg, lfs2_block_t block);
// Sync the block device
int lfs2_testbd_sync(const struct lfs2_config *cfg);
/// Additional extended API for driving test features ///
// Get simulated wear on a given block
lfs2_testbd_swear_t lfs2_testbd_getwear(const struct lfs2_config *cfg,
lfs2_block_t block);
// Manually set simulated wear on a given block
int lfs2_testbd_setwear(const struct lfs2_config *cfg,
lfs2_block_t block, lfs2_testbd_wear_t wear);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,655 @@
/*
* The little filesystem
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef LFS2_H
#define LFS2_H
#include <stdint.h>
#include <stdbool.h>
#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 LFS2_VERSION 0x00020002
#define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16))
#define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0))
// Version of On-disk data structures
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS2_DISK_VERSION 0x00020000
#define LFS2_DISK_VERSION_MAJOR (0xffff & (LFS2_DISK_VERSION >> 16))
#define LFS2_DISK_VERSION_MINOR (0xffff & (LFS2_DISK_VERSION >> 0))
/// Definitions ///
// Type definitions
typedef uint32_t lfs2_size_t;
typedef uint32_t lfs2_off_t;
typedef int32_t lfs2_ssize_t;
typedef int32_t lfs2_soff_t;
typedef uint32_t lfs2_block_t;
// Maximum name size in bytes, may be redefined to reduce the size of the
// info struct. Limited to <= 1022. Stored in superblock and must be
// respected by other littlefs drivers.
#ifndef LFS2_NAME_MAX
#define LFS2_NAME_MAX 255
#endif
// Maximum size of a file in bytes, may be redefined to limit to support other
// drivers. Limited on disk to <= 4294967296. However, above 2147483647 the
// functions lfs2_file_seek, lfs2_file_size, and lfs2_file_tell will return
// incorrect values due to using signed integers. Stored in superblock and
// must be respected by other littlefs drivers.
#ifndef LFS2_FILE_MAX
#define LFS2_FILE_MAX 2147483647
#endif
// Maximum size of custom attributes in bytes, may be redefined, but there is
// no real benefit to using a smaller LFS2_ATTR_MAX. Limited to <= 1022.
#ifndef LFS2_ATTR_MAX
#define LFS2_ATTR_MAX 1022
#endif
// Possible error codes, these are negative to allow
// valid positive return values
enum lfs2_error {
LFS2_ERR_OK = 0, // No error
LFS2_ERR_IO = -5, // Error during device operation
LFS2_ERR_CORRUPT = -84, // Corrupted
LFS2_ERR_NOENT = -2, // No directory entry
LFS2_ERR_EXIST = -17, // Entry already exists
LFS2_ERR_NOTDIR = -20, // Entry is not a dir
LFS2_ERR_ISDIR = -21, // Entry is a dir
LFS2_ERR_NOTEMPTY = -39, // Dir is not empty
LFS2_ERR_BADF = -9, // Bad file number
LFS2_ERR_FBIG = -27, // File too large
LFS2_ERR_INVAL = -22, // Invalid parameter
LFS2_ERR_NOSPC = -28, // No space left on device
LFS2_ERR_NOMEM = -12, // No more memory available
LFS2_ERR_NOATTR = -61, // No data/attr available
LFS2_ERR_NAMETOOLONG = -36, // File name too long
};
// File types
enum lfs2_type {
// file types
LFS2_TYPE_REG = 0x001,
LFS2_TYPE_DIR = 0x002,
// internally used types
LFS2_TYPE_SPLICE = 0x400,
LFS2_TYPE_NAME = 0x000,
LFS2_TYPE_STRUCT = 0x200,
LFS2_TYPE_USERATTR = 0x300,
LFS2_TYPE_FROM = 0x100,
LFS2_TYPE_TAIL = 0x600,
LFS2_TYPE_GLOBALS = 0x700,
LFS2_TYPE_CRC = 0x500,
// internally used type specializations
LFS2_TYPE_CREATE = 0x401,
LFS2_TYPE_DELETE = 0x4ff,
LFS2_TYPE_SUPERBLOCK = 0x0ff,
LFS2_TYPE_DIRSTRUCT = 0x200,
LFS2_TYPE_CTZSTRUCT = 0x202,
LFS2_TYPE_INLINESTRUCT = 0x201,
LFS2_TYPE_SOFTTAIL = 0x600,
LFS2_TYPE_HARDTAIL = 0x601,
LFS2_TYPE_MOVESTATE = 0x7ff,
// internal chip sources
LFS2_FROM_NOOP = 0x000,
LFS2_FROM_MOVE = 0x101,
LFS2_FROM_USERATTRS = 0x102,
};
// File open flags
enum lfs2_open_flags {
// open flags
LFS2_O_RDONLY = 1, // Open a file as read only
LFS2_O_WRONLY = 2, // Open a file as write only
LFS2_O_RDWR = 3, // Open a file as read and write
LFS2_O_CREAT = 0x0100, // Create a file if it does not exist
LFS2_O_EXCL = 0x0200, // Fail if a file already exists
LFS2_O_TRUNC = 0x0400, // Truncate the existing file to zero size
LFS2_O_APPEND = 0x0800, // Move to end of file on every write
// internally used flags
LFS2_F_DIRTY = 0x010000, // File does not match storage
LFS2_F_WRITING = 0x020000, // File has been written since last flush
LFS2_F_READING = 0x040000, // File has been read since last flush
LFS2_F_ERRED = 0x080000, // An error occured during write
LFS2_F_INLINE = 0x100000, // Currently inlined in directory entry
LFS2_F_OPENED = 0x200000, // File has been opened
};
// File seek flags
enum lfs2_whence_flags {
LFS2_SEEK_SET = 0, // Seek relative to an absolute position
LFS2_SEEK_CUR = 1, // Seek relative to the current file position
LFS2_SEEK_END = 2, // Seek relative to the end of the file
};
// Configuration provided during initialization of the littlefs
struct lfs2_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 lfs2_config *c, lfs2_block_t block,
lfs2_off_t off, void *buffer, lfs2_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 LFS2_ERR_CORRUPT if the block should be considered bad.
int (*prog)(const struct lfs2_config *c, lfs2_block_t block,
lfs2_off_t off, const void *buffer, lfs2_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 LFS2_ERR_CORRUPT if the block should be considered bad.
int (*erase)(const struct lfs2_config *c, lfs2_block_t block);
// Sync the state of the underlying block device. Negative error codes
// are propogated to the user.
int (*sync)(const struct lfs2_config *c);
// Minimum size of a block read. All read operations will be a
// multiple of this value.
lfs2_size_t read_size;
// Minimum size of a block program. All program operations will be a
// multiple of this value.
lfs2_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, non-inlined files
// take up at minimum one block. Must be a multiple of the read
// and program sizes.
lfs2_size_t block_size;
// Number of erasable blocks on the device.
lfs2_size_t block_count;
// Number of erase cycles before littlefs evicts metadata logs and moves
// the metadata to another block. Suggested values are in the
// range 100-1000, with large values having better performance at the cost
// of less consistent wear distribution.
//
// Set to -1 to disable block-level wear-leveling.
int32_t block_cycles;
// Size of block caches. Each cache buffers a portion of a block in RAM.
// The littlefs needs a read cache, a program cache, and one additional
// cache per file. Larger caches can improve performance by storing more
// data and reducing the number of disk accesses. Must be a multiple of
// the read and program sizes, and a factor of the block size.
lfs2_size_t cache_size;
// Size of the lookahead buffer in bytes. A larger lookahead buffer
// increases the number of blocks found during an allocation pass. The
// lookahead buffer is stored as a compact bitmap, so each byte of RAM
// can track 8 blocks. Must be a multiple of 8.
lfs2_size_t lookahead_size;
// Optional statically allocated read buffer. Must be cache_size.
// By default lfs2_malloc is used to allocate this buffer.
void *read_buffer;
// Optional statically allocated program buffer. Must be cache_size.
// By default lfs2_malloc is used to allocate this buffer.
void *prog_buffer;
// Optional statically allocated lookahead buffer. Must be lookahead_size
// and aligned to a 32-bit boundary. By default lfs2_malloc is used to
// allocate this buffer.
void *lookahead_buffer;
// Optional upper limit on length of file names in bytes. No downside for
// larger names except the size of the info struct which is controlled by
// the LFS2_NAME_MAX define. Defaults to LFS2_NAME_MAX when zero. Stored in
// superblock and must be respected by other littlefs drivers.
lfs2_size_t name_max;
// Optional upper limit on files in bytes. No downside for larger files
// but must be <= LFS2_FILE_MAX. Defaults to LFS2_FILE_MAX when zero. Stored
// in superblock and must be respected by other littlefs drivers.
lfs2_size_t file_max;
// Optional upper limit on custom attributes in bytes. No downside for
// larger attributes size but must be <= LFS2_ATTR_MAX. Defaults to
// LFS2_ATTR_MAX when zero.
lfs2_size_t attr_max;
};
// File info structure
struct lfs2_info {
// Type of the file, either LFS2_TYPE_REG or LFS2_TYPE_DIR
uint8_t type;
// Size of the file, only valid for REG files. Limited to 32-bits.
lfs2_size_t size;
// Name of the file stored as a null-terminated string. Limited to
// LFS2_NAME_MAX+1, which can be changed by redefining LFS2_NAME_MAX to
// reduce RAM. LFS2_NAME_MAX is stored in superblock and must be
// respected by other littlefs drivers.
char name[LFS2_NAME_MAX+1];
};
// Custom attribute structure, used to describe custom attributes
// committed atomically during file writes.
struct lfs2_attr {
// 8-bit type of attribute, provided by user and used to
// identify the attribute
uint8_t type;
// Pointer to buffer containing the attribute
void *buffer;
// Size of attribute in bytes, limited to LFS2_ATTR_MAX
lfs2_size_t size;
};
// Optional configuration provided during lfs2_file_opencfg
struct lfs2_file_config {
// Optional statically allocated file buffer. Must be cache_size.
// By default lfs2_malloc is used to allocate this buffer.
void *buffer;
// Optional list of custom attributes related to the file. If the file
// is opened with read access, these attributes will be read from disk
// during the open call. If the file is opened with write access, the
// attributes will be written to disk every file sync or close. This
// write occurs atomically with update to the file's contents.
//
// Custom attributes are uniquely identified by an 8-bit type and limited
// to LFS2_ATTR_MAX bytes. When read, if the stored attribute is smaller
// than the buffer, it will be padded with zeros. If the stored attribute
// is larger, then it will be silently truncated. If the attribute is not
// found, it will be created implicitly.
struct lfs2_attr *attrs;
// Number of custom attributes in the list
lfs2_size_t attr_count;
};
/// internal littlefs data structures ///
typedef struct lfs2_cache {
lfs2_block_t block;
lfs2_off_t off;
lfs2_size_t size;
uint8_t *buffer;
} lfs2_cache_t;
typedef struct lfs2_mdir {
lfs2_block_t pair[2];
uint32_t rev;
lfs2_off_t off;
uint32_t etag;
uint16_t count;
bool erased;
bool split;
lfs2_block_t tail[2];
} lfs2_mdir_t;
// littlefs directory type
typedef struct lfs2_dir {
struct lfs2_dir *next;
uint16_t id;
uint8_t type;
lfs2_mdir_t m;
lfs2_off_t pos;
lfs2_block_t head[2];
} lfs2_dir_t;
// littlefs file type
typedef struct lfs2_file {
struct lfs2_file *next;
uint16_t id;
uint8_t type;
lfs2_mdir_t m;
struct lfs2_ctz {
lfs2_block_t head;
lfs2_size_t size;
} ctz;
uint32_t flags;
lfs2_off_t pos;
lfs2_block_t block;
lfs2_off_t off;
lfs2_cache_t cache;
const struct lfs2_file_config *cfg;
} lfs2_file_t;
typedef struct lfs2_superblock {
uint32_t version;
lfs2_size_t block_size;
lfs2_size_t block_count;
lfs2_size_t name_max;
lfs2_size_t file_max;
lfs2_size_t attr_max;
} lfs2_superblock_t;
typedef struct lfs2_gstate {
uint32_t tag;
lfs2_block_t pair[2];
} lfs2_gstate_t;
// The littlefs filesystem type
typedef struct lfs2 {
lfs2_cache_t rcache;
lfs2_cache_t pcache;
lfs2_block_t root[2];
struct lfs2_mlist {
struct lfs2_mlist *next;
uint16_t id;
uint8_t type;
lfs2_mdir_t m;
} *mlist;
uint32_t seed;
lfs2_gstate_t gstate;
lfs2_gstate_t gdisk;
lfs2_gstate_t gdelta;
struct lfs2_free {
lfs2_block_t off;
lfs2_block_t size;
lfs2_block_t i;
lfs2_block_t ack;
uint32_t *buffer;
} free;
const struct lfs2_config *cfg;
lfs2_size_t name_max;
lfs2_size_t file_max;
lfs2_size_t attr_max;
#ifdef LFS2_MIGRATE
struct lfs21 *lfs21;
#endif
} lfs2_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 lfs2_format(lfs2_t *lfs2, const struct lfs2_config *config);
// Mounts a littlefs
//
// Requires a littlefs object and config struct. Multiple filesystems
// may be mounted simultaneously with multiple littlefs objects. Both
// lfs2 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 lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *config);
// Unmounts a littlefs
//
// Does nothing besides releasing any allocated resources.
// Returns a negative error code on failure.
int lfs2_unmount(lfs2_t *lfs2);
/// General operations ///
// Removes a file or directory
//
// If removing a directory, the directory must be empty.
// Returns a negative error code on failure.
int lfs2_remove(lfs2_t *lfs2, 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 lfs2_rename(lfs2_t *lfs2, 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 lfs2_stat(lfs2_t *lfs2, const char *path, struct lfs2_info *info);
// Get a custom attribute
//
// Custom attributes are uniquely identified by an 8-bit type and limited
// to LFS2_ATTR_MAX bytes. When read, if the stored attribute is smaller than
// the buffer, it will be padded with zeros. If the stored attribute is larger,
// then it will be silently truncated. If no attribute is found, the error
// LFS2_ERR_NOATTR is returned and the buffer is filled with zeros.
//
// Returns the size of the attribute, or a negative error code on failure.
// Note, the returned size is the size of the attribute on disk, irrespective
// of the size of the buffer. This can be used to dynamically allocate a buffer
// or check for existance.
lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path,
uint8_t type, void *buffer, lfs2_size_t size);
// Set custom attributes
//
// Custom attributes are uniquely identified by an 8-bit type and limited
// to LFS2_ATTR_MAX bytes. If an attribute is not found, it will be
// implicitly created.
//
// Returns a negative error code on failure.
int lfs2_setattr(lfs2_t *lfs2, const char *path,
uint8_t type, const void *buffer, lfs2_size_t size);
// Removes a custom attribute
//
// If an attribute is not found, nothing happens.
//
// Returns a negative error code on failure.
int lfs2_removeattr(lfs2_t *lfs2, const char *path, uint8_t type);
/// File operations ///
// Open a file
//
// The mode that the file is opened in is determined by the flags, which
// are values from the enum lfs2_open_flags that are bitwise-ored together.
//
// Returns a negative error code on failure.
int lfs2_file_open(lfs2_t *lfs2, lfs2_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 lfs2_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 lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file,
const char *path, int flags,
const struct lfs2_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 lfs2_file_close(lfs2_t *lfs2, lfs2_file_t *file);
// Synchronize a file on storage
//
// Any pending writes are written out to storage.
// Returns a negative error code on failure.
int lfs2_file_sync(lfs2_t *lfs2, lfs2_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.
lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file,
void *buffer, lfs2_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.
lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file,
const void *buffer, lfs2_size_t size);
// Change the position of the file
//
// The change in position is determined by the offset and whence flag.
// Returns the new position of the file, or a negative error code on failure.
lfs2_soff_t lfs2_file_seek(lfs2_t *lfs2, lfs2_file_t *file,
lfs2_soff_t off, int whence);
// Truncates the size of the file to the specified size
//
// Returns a negative error code on failure.
int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size);
// Return the position of the file
//
// Equivalent to lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_CUR)
// Returns the position of the file, or a negative error code on failure.
lfs2_soff_t lfs2_file_tell(lfs2_t *lfs2, lfs2_file_t *file);
// Change the position of the file to the beginning of the file
//
// Equivalent to lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_SET)
// Returns a negative error code on failure.
int lfs2_file_rewind(lfs2_t *lfs2, lfs2_file_t *file);
// Return the size of the file
//
// Similar to lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_END)
// Returns the size of the file, or a negative error code on failure.
lfs2_soff_t lfs2_file_size(lfs2_t *lfs2, lfs2_file_t *file);
/// Directory operations ///
// Create a directory
//
// Returns a negative error code on failure.
int lfs2_mkdir(lfs2_t *lfs2, 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 lfs2_dir_open(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path);
// Close a directory
//
// Releases any allocated resources.
// Returns a negative error code on failure.
int lfs2_dir_close(lfs2_t *lfs2, lfs2_dir_t *dir);
// Read an entry in the directory
//
// Fills out the info structure, based on the specified file or directory.
// Returns a positive value on success, 0 at the end of directory,
// or a negative error code on failure.
int lfs2_dir_read(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_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 lfs2_dir_seek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_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.
lfs2_soff_t lfs2_dir_tell(lfs2_t *lfs2, lfs2_dir_t *dir);
// Change the position of the directory to the beginning of the directory
//
// Returns a negative error code on failure.
int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir);
/// Filesystem-level filesystem operations
// Finds the current size of the filesystem
//
// Note: Result is best effort. If files share COW structures, the returned
// size may be larger than the filesystem actually is.
//
// Returns the number of allocated blocks, or a negative error code on failure.
lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2);
// 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 lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data);
#ifdef LFS2_MIGRATE
// Attempts to migrate a previous version of littlefs
//
// Behaves similarly to the lfs2_format function. Attempts to mount
// the previous version of littlefs and update the filesystem so it can be
// mounted with the current version of 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 lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg);
#endif
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@ -0,0 +1,33 @@
/*
* lfs2 util functions
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "lfs2_util.h"
// Only compile if user does not provide custom config
#ifndef LFS2_CONFIG
#ifndef __MBED__
// Software CRC implementation with small lookup table
uint32_t lfs2_crc(uint32_t 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];
}
return crc;
}
#endif
#endif

View File

@ -0,0 +1,283 @@
/*
* lfs2 utility functions
*
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef LFS2_UTIL_H
#define LFS2_UTIL_H
// Users can override lfs2_util.h with their own configuration by defining
// LFS2_CONFIG as a header file to include (-DLFS2_CONFIG=lfs2_config.h).
//
// If LFS2_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 lfs2_util.h
// and modifying as needed.
#ifdef LFS2_CONFIG
#define LFS2_STRINGIZE(x) LFS2_STRINGIZE2(x)
#define LFS2_STRINGIZE2(x) #x
#include LFS2_STRINGIZE(LFS2_CONFIG)
#else
// System includes
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
#ifndef LFS2_NO_MALLOC
#include <stdlib.h>
#endif
#ifndef LFS2_NO_ASSERT
#include <assert.h>
#endif
#if !defined(LFS2_NO_DEBUG) || \
!defined(LFS2_NO_WARN) || \
!defined(LFS2_NO_ERROR) || \
defined(LFS2_YES_TRACE)
#include <stdio.h>
#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"
#include "cmsis_compiler.h"
#else
#define MBED_LFS2_ENABLE_INFO false
#define MBED_LFS2_ENABLE_DEBUG true
#define MBED_LFS2_ENABLE_WARN true
#define MBED_LFS2_ENABLE_ERROR true
#define MBED_LFS2_ENABLE_ASSERT true
#define MBED_LFS2_INTRINSICS true
#endif
// Logging functions
#if defined(LFS2_YES_TRACE) && MBED_LFS2_ENABLE_TRACE
#define LFS2_TRACE_(fmt, ...) \
printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS2_TRACE(...) LFS2_TRACE_(__VA_ARGS__, "")
#elif defined(LFS2_YES_TRACE) && !defined(MBED_LFS2_ENABLE_TRACE)
#define LFS2_TRACE_(fmt, ...) \
debug("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS2_TRACE(...) LFS2_TRACE_(__VA_ARGS__, "")
#else
#define LFS2_TRACE(...)
#endif
#if !defined(LFS2_NO_DEBUG) && MBED_LFS2_ENABLE_DEBUG
#define LFS2_DEBUG_(fmt, ...) \
printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS2_DEBUG(...) LFS2_DEBUG_(__VA_ARGS__, "")
#elif !defined(LFS2_NO_DEBUG) && !defined(MBED_LFS2_ENABLE_DEBUG)
#define LFS2_DEBUG_(fmt, ...) \
debug("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS2_DEBUG(...) LFS2_DEBUG_(__VA_ARGS__, "")
#else
#define LFS2_DEBUG(...)
#endif
#if !defined(LFS2_NO_WARN) && MBED_LFS2_ENABLE_WARN
#define LFS2_WARN_(fmt, ...) \
printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS2_WARN(...) LFS2_WARN_(__VA_ARGS__, "")
#elif !defined(LFS2_NO_WARN) && !defined(MBED_LFS2_ENABLE_WARN)
#define LFS2_WARN_(fmt, ...) \
debug("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS2_WARN(...) LFS2_WARN_(__VA_ARGS__, "")
#else
#define LFS2_WARN(...)
#endif
#if !defined(LFS2_NO_ERROR) && MBED_LFS2_ENABLE_ERROR
#define LFS2_ERROR_(fmt, ...) \
printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS2_ERROR(...) LFS2_ERROR_(__VA_ARGS__, "")
#elif !defined(LFS2_NO_ERROR) && !defined(MBED_LFS2_ENABLE_ERROR)
#define LFS2_ERROR_(fmt, ...) \
debug("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS2_ERROR(...) LFS2_ERROR_(__VA_ARGS__, "")
#else
#define LFS2_ERROR(...)
#endif
// Runtime assertions
#if !defined(LFS2_NO_ASSERT) && MBED_LFS2_ENABLE_ASSERT
#define LFS2_ASSERT(test) assert(test)
#elif !defined(LFS2_NO_ASSERT) && !defined(MBED_LFS2_ENABLE_ASSERT)
#define LFS2_ASSERT(test) MBED_ASSERT(test)
#else
#define LFS2_ASSERT(test)
#endif
// Builtin functions, these may be replaced by more efficient
// toolchain-specific implementations. LFS2_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 lfs2_max(uint32_t a, uint32_t b) {
return (a > b) ? a : b;
}
static inline uint32_t lfs2_min(uint32_t a, uint32_t b) {
return (a < b) ? a : b;
}
// Align to nearest multiple of a size
static inline uint32_t lfs2_aligndown(uint32_t a, uint32_t alignment) {
return a - (a % alignment);
}
static inline uint32_t lfs2_alignup(uint32_t a, uint32_t alignment) {
return lfs2_aligndown(a + alignment-1, alignment);
}
// Find the smallest power of 2 greater than or equal to a
static inline uint32_t lfs2_npw2(uint32_t a) {
#if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_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
// lfs2_ctz(0) may be undefined
static inline uint32_t lfs2_ctz(uint32_t a) {
#if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && \
defined(__GNUC__)
return __builtin_ctz(a);
#else
return lfs2_npw2((a & -a) + 1) - 1;
#endif
}
// Count the number of binary ones in a
static inline uint32_t lfs2_popc(uint32_t a) {
#if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_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 lfs2_scmp(uint32_t a, uint32_t b) {
return (int)(unsigned)(a - b);
}
// Convert between 32-bit little-endian and native order
static inline uint32_t lfs2_fromle32(uint32_t a) {
#if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && ( \
(defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
return a;
#elif !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && ( \
(defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __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
}
static inline uint32_t lfs2_tole32(uint32_t a) {
return lfs2_fromle32(a);
}
// Reverse the bits in a
static inline uint32_t lfs2_rbit(uint32_t a) {
#if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && \
defined(__MBED__)
return __RBIT(a);
#else
a = ((a & 0xaaaaaaaa) >> 1) | ((a & 0x55555555) << 1);
a = ((a & 0xcccccccc) >> 2) | ((a & 0x33333333) << 2);
a = ((a & 0xf0f0f0f0) >> 4) | ((a & 0x0f0f0f0f) << 4);
a = ((a & 0xff00ff00) >> 8) | ((a & 0x00ff00ff) << 8);
a = (a >> 16) | (a << 16);
return a;
#endif
}
// Convert between 32-bit big-endian and native order
static inline uint32_t lfs2_frombe32(uint32_t a) {
#if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && ( \
(defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
return __builtin_bswap32(a);
#elif !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && ( \
(defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
return a;
#else
return (((uint8_t*)&a)[0] << 24) |
(((uint8_t*)&a)[1] << 16) |
(((uint8_t*)&a)[2] << 8) |
(((uint8_t*)&a)[3] << 0);
#endif
}
static inline uint32_t lfs2_tobe32(uint32_t a) {
return lfs2_frombe32(a);
}
// Calculate CRC-32 with polynomial = 0x04c11db7
uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size);
// Allocate memory, only used if buffers are not provided to littlefs
// Note, memory must be 64-bit aligned
static inline void *lfs2_malloc(size_t size) {
#ifndef LFS2_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 lfs2_free(void *p) {
#ifndef LFS2_NO_MALLOC
free(p);
#else
(void)p;
#endif
}
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif
#endif

View File

@ -0,0 +1,383 @@
#!/usr/bin/env python3
import re
import sys
PATTERN = ['LFS2_ASSERT', 'assert']
PREFIX = 'LFS2'
MAXWIDTH = 16
ASSERT = "__{PREFIX}_ASSERT_{TYPE}_{COMP}"
FAIL = """
__attribute__((unused))
static void __{prefix}_assert_fail_{type}(
const char *file, int line, const char *comp,
{ctype} lh, size_t lsize,
{ctype} rh, size_t rsize) {{
printf("%s:%d:assert: assert failed with ", file, line);
__{prefix}_assert_print_{type}(lh, lsize);
printf(", expected %s ", comp);
__{prefix}_assert_print_{type}(rh, rsize);
printf("\\n");
fflush(NULL);
raise(SIGABRT);
}}
"""
COMP = {
'==': 'eq',
'!=': 'ne',
'<=': 'le',
'>=': 'ge',
'<': 'lt',
'>': 'gt',
}
TYPE = {
'int': {
'ctype': 'intmax_t',
'fail': FAIL,
'print': """
__attribute__((unused))
static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{
(void)size;
printf("%"PRIiMAX, v);
}}
""",
'assert': """
#define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh)
do {{
__typeof__(lh) _lh = lh;
__typeof__(lh) _rh = (__typeof__(lh))rh;
if (!(_lh {op} _rh)) {{
__{prefix}_assert_fail_{type}(file, line, "{comp}",
(intmax_t)_lh, 0, (intmax_t)_rh, 0);
}}
}} while (0)
"""
},
'bool': {
'ctype': 'bool',
'fail': FAIL,
'print': """
__attribute__((unused))
static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{
(void)size;
printf("%s", v ? "true" : "false");
}}
""",
'assert': """
#define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh)
do {{
bool _lh = !!(lh);
bool _rh = !!(rh);
if (!(_lh {op} _rh)) {{
__{prefix}_assert_fail_{type}(file, line, "{comp}",
_lh, 0, _rh, 0);
}}
}} while (0)
"""
},
'mem': {
'ctype': 'const void *',
'fail': FAIL,
'print': """
__attribute__((unused))
static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{
const uint8_t *s = v;
printf("\\\"");
for (size_t i = 0; i < size && i < {maxwidth}; i++) {{
if (s[i] >= ' ' && s[i] <= '~') {{
printf("%c", s[i]);
}} else {{
printf("\\\\x%02x", s[i]);
}}
}}
if (size > {maxwidth}) {{
printf("...");
}}
printf("\\\"");
}}
""",
'assert': """
#define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh, size)
do {{
const void *_lh = lh;
const void *_rh = rh;
if (!(memcmp(_lh, _rh, size) {op} 0)) {{
__{prefix}_assert_fail_{type}(file, line, "{comp}",
_lh, size, _rh, size);
}}
}} while (0)
"""
},
'str': {
'ctype': 'const char *',
'fail': FAIL,
'print': """
__attribute__((unused))
static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{
__{prefix}_assert_print_mem(v, size);
}}
""",
'assert': """
#define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh)
do {{
const char *_lh = lh;
const char *_rh = rh;
if (!(strcmp(_lh, _rh) {op} 0)) {{
__{prefix}_assert_fail_{type}(file, line, "{comp}",
_lh, strlen(_lh), _rh, strlen(_rh));
}}
}} while (0)
"""
}
}
def mkdecls(outf, maxwidth=16):
outf.write("#include <stdio.h>\n")
outf.write("#include <stdbool.h>\n")
outf.write("#include <stdint.h>\n")
outf.write("#include <inttypes.h>\n")
outf.write("#include <signal.h>\n")
for type, desc in sorted(TYPE.items()):
format = {
'type': type.lower(), 'TYPE': type.upper(),
'ctype': desc['ctype'],
'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(),
'maxwidth': maxwidth,
}
outf.write(re.sub('\s+', ' ',
desc['print'].strip().format(**format))+'\n')
outf.write(re.sub('\s+', ' ',
desc['fail'].strip().format(**format))+'\n')
for op, comp in sorted(COMP.items()):
format.update({
'comp': comp.lower(), 'COMP': comp.upper(),
'op': op,
})
outf.write(re.sub('\s+', ' ',
desc['assert'].strip().format(**format))+'\n')
def mkassert(type, comp, lh, rh, size=None):
format = {
'type': type.lower(), 'TYPE': type.upper(),
'comp': comp.lower(), 'COMP': comp.upper(),
'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(),
'lh': lh.strip(' '),
'rh': rh.strip(' '),
'size': size,
}
if size:
return ((ASSERT + '(__FILE__, __LINE__, {lh}, {rh}, {size})')
.format(**format))
else:
return ((ASSERT + '(__FILE__, __LINE__, {lh}, {rh})')
.format(**format))
# simple recursive descent parser
LEX = {
'ws': [r'(?:\s|\n|#.*?\n|//.*?\n|/\*.*?\*/)+'],
'assert': PATTERN,
'string': [r'"(?:\\.|[^"])*"', r"'(?:\\.|[^'])\'"],
'arrow': ['=>'],
'paren': ['\(', '\)'],
'op': ['strcmp', 'memcmp', '->'],
'comp': ['==', '!=', '<=', '>=', '<', '>'],
'logic': ['\&\&', '\|\|'],
'sep': [':', ';', '\{', '\}', ','],
}
class ParseFailure(Exception):
def __init__(self, expected, found):
self.expected = expected
self.found = found
def __str__(self):
return "expected %r, found %s..." % (
self.expected, repr(self.found)[:70])
class Parse:
def __init__(self, inf, lexemes):
p = '|'.join('(?P<%s>%s)' % (n, '|'.join(l))
for n, l in lexemes.items())
p = re.compile(p, re.DOTALL)
data = inf.read()
tokens = []
while True:
m = p.search(data)
if m:
if m.start() > 0:
tokens.append((None, data[:m.start()]))
tokens.append((m.lastgroup, m.group()))
data = data[m.end():]
else:
tokens.append((None, data))
break
self.tokens = tokens
self.off = 0
def lookahead(self, *pattern):
if self.off < len(self.tokens):
token = self.tokens[self.off]
if token[0] in pattern or token[1] in pattern:
self.m = token[1]
return self.m
self.m = None
return self.m
def accept(self, *patterns):
m = self.lookahead(*patterns)
if m is not None:
self.off += 1
return m
def expect(self, *patterns):
m = self.accept(*patterns)
if not m:
raise ParseFailure(patterns, self.tokens[self.off:])
return m
def push(self):
return self.off
def pop(self, state):
self.off = state
def passert(p):
def pastr(p):
p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
p.expect('strcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
lh = pexpr(p) ; p.accept('ws')
p.expect(',') ; p.accept('ws')
rh = pexpr(p) ; p.accept('ws')
p.expect(')') ; p.accept('ws')
comp = p.expect('comp') ; p.accept('ws')
p.expect('0') ; p.accept('ws')
p.expect(')')
return mkassert('str', COMP[comp], lh, rh)
def pamem(p):
p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
p.expect('memcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
lh = pexpr(p) ; p.accept('ws')
p.expect(',') ; p.accept('ws')
rh = pexpr(p) ; p.accept('ws')
p.expect(',') ; p.accept('ws')
size = pexpr(p) ; p.accept('ws')
p.expect(')') ; p.accept('ws')
comp = p.expect('comp') ; p.accept('ws')
p.expect('0') ; p.accept('ws')
p.expect(')')
return mkassert('mem', COMP[comp], lh, rh, size)
def paint(p):
p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
lh = pexpr(p) ; p.accept('ws')
comp = p.expect('comp') ; p.accept('ws')
rh = pexpr(p) ; p.accept('ws')
p.expect(')')
return mkassert('int', COMP[comp], lh, rh)
def pabool(p):
p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
lh = pexprs(p) ; p.accept('ws')
p.expect(')')
return mkassert('bool', 'eq', lh, 'true')
def pa(p):
return p.expect('assert')
state = p.push()
lastf = None
for pa in [pastr, pamem, paint, pabool, pa]:
try:
return pa(p)
except ParseFailure as f:
p.pop(state)
lastf = f
else:
raise lastf
def pexpr(p):
res = []
while True:
if p.accept('('):
res.append(p.m)
while True:
res.append(pexprs(p))
if p.accept('sep'):
res.append(p.m)
else:
break
res.append(p.expect(')'))
elif p.lookahead('assert'):
res.append(passert(p))
elif p.accept('assert', 'ws', 'string', 'op', None):
res.append(p.m)
else:
return ''.join(res)
def pexprs(p):
res = []
while True:
res.append(pexpr(p))
if p.accept('comp', 'logic', ','):
res.append(p.m)
else:
return ''.join(res)
def pstmt(p):
ws = p.accept('ws') or ''
lh = pexprs(p)
if p.accept('=>'):
rh = pexprs(p)
return ws + mkassert('int', 'eq', lh, rh)
else:
return ws + lh
def main(args):
inf = open(args.input, 'r') if args.input else sys.stdin
outf = open(args.output, 'w') if args.output else sys.stdout
lexemes = LEX.copy()
if args.pattern:
lexemes['assert'] = args.pattern
p = Parse(inf, lexemes)
# write extra verbose asserts
mkdecls(outf, maxwidth=args.maxwidth)
if args.input:
outf.write("#line %d \"%s\"\n" % (1, args.input))
# parse and write out stmt at a time
try:
while True:
outf.write(pstmt(p))
if p.accept('sep'):
outf.write(p.m)
else:
break
except ParseFailure as f:
pass
for i in range(p.off, len(p.tokens)):
outf.write(p.tokens[i][1])
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Cpp step that increases assert verbosity")
parser.add_argument('input', nargs='?',
help="Input C file after cpp.")
parser.add_argument('-o', '--output', required=True,
help="Output C file.")
parser.add_argument('-p', '--pattern', action='append',
help="Patterns to search for starting an assert statement.")
parser.add_argument('--maxwidth', default=MAXWIDTH, type=int,
help="Maximum number of characters to display for strcmp and memcmp.")
main(parser.parse_args())

View File

@ -0,0 +1,61 @@
#!/usr/bin/env python2
# This script replaces prefixes of files, and symbols in that file.
# Useful for creating different versions of the codebase that don't
# conflict at compile time.
#
# example:
# $ ./scripts/prefix.py lfs22
import os
import os.path
import re
import glob
import itertools
import tempfile
import shutil
import subprocess
DEFAULT_PREFIX = "lfs2"
def subn(from_prefix, to_prefix, name):
name, count1 = re.subn('\\b'+from_prefix, to_prefix, name)
name, count2 = re.subn('\\b'+from_prefix.upper(), to_prefix.upper(), name)
name, count3 = re.subn('\\B-D'+from_prefix.upper(),
'-D'+to_prefix.upper(), name)
return name, count1+count2+count3
def main(from_prefix, to_prefix=None, files=None):
if not to_prefix:
from_prefix, to_prefix = DEFAULT_PREFIX, from_prefix
if not files:
files = subprocess.check_output([
'git', 'ls-tree', '-r', '--name-only', 'HEAD']).split()
for oldname in files:
# Rename any matching file names
newname, namecount = subn(from_prefix, to_prefix, oldname)
if namecount:
subprocess.check_call(['git', 'mv', oldname, newname])
# Rename any prefixes in file
count = 0
with open(newname+'~', 'w') as tempf:
with open(newname) as newf:
for line in newf:
line, n = subn(from_prefix, to_prefix, line)
count += n
tempf.write(line)
shutil.copystat(newname, newname+'~')
os.rename(newname+'~', newname)
subprocess.check_call(['git', 'add', newname])
# Summary
print '%s: %d replacements' % (
'%s -> %s' % (oldname, newname) if namecount else oldname,
count)
if __name__ == "__main__":
import sys
sys.exit(main(*sys.argv[1:]))

View File

@ -0,0 +1,26 @@
#!/usr/bin/env python3
import subprocess as sp
def main(args):
with open(args.disk, 'rb') as f:
f.seek(args.block * args.block_size)
block = (f.read(args.block_size)
.ljust(args.block_size, b'\xff'))
# what did you expect?
print("%-8s %-s" % ('off', 'data'))
return sp.run(['xxd', '-g1', '-'], input=block).returncode
if __name__ == "__main__":
import argparse
import sys
parser = argparse.ArgumentParser(
description="Hex dump a specific block in a disk.")
parser.add_argument('disk',
help="File representing the block device.")
parser.add_argument('block_size', type=lambda x: int(x, 0),
help="Size of a block in bytes.")
parser.add_argument('block', type=lambda x: int(x, 0),
help="Address of block to dump.")
sys.exit(main(parser.parse_args()))

View File

@ -0,0 +1,367 @@
#!/usr/bin/env python3
import struct
import binascii
import sys
import itertools as it
TAG_TYPES = {
'splice': (0x700, 0x400),
'create': (0x7ff, 0x401),
'delete': (0x7ff, 0x4ff),
'name': (0x700, 0x000),
'reg': (0x7ff, 0x001),
'dir': (0x7ff, 0x002),
'superblock': (0x7ff, 0x0ff),
'struct': (0x700, 0x200),
'dirstruct': (0x7ff, 0x200),
'ctzstruct': (0x7ff, 0x202),
'inlinestruct': (0x7ff, 0x201),
'userattr': (0x700, 0x300),
'tail': (0x700, 0x600),
'softtail': (0x7ff, 0x600),
'hardtail': (0x7ff, 0x601),
'gstate': (0x700, 0x700),
'movestate': (0x7ff, 0x7ff),
'crc': (0x700, 0x500),
}
class Tag:
def __init__(self, *args):
if len(args) == 1:
self.tag = args[0]
elif len(args) == 3:
if isinstance(args[0], str):
type = TAG_TYPES[args[0]][1]
else:
type = args[0]
if isinstance(args[1], str):
id = int(args[1], 0) if args[1] not in 'x.' else 0x3ff
else:
id = args[1]
if isinstance(args[2], str):
size = int(args[2], str) if args[2] not in 'x.' else 0x3ff
else:
size = args[2]
self.tag = (type << 20) | (id << 10) | size
else:
assert False
@property
def isvalid(self):
return not bool(self.tag & 0x80000000)
@property
def isattr(self):
return not bool(self.tag & 0x40000000)
@property
def iscompactable(self):
return bool(self.tag & 0x20000000)
@property
def isunique(self):
return not bool(self.tag & 0x10000000)
@property
def type(self):
return (self.tag & 0x7ff00000) >> 20
@property
def type1(self):
return (self.tag & 0x70000000) >> 20
@property
def type3(self):
return (self.tag & 0x7ff00000) >> 20
@property
def id(self):
return (self.tag & 0x000ffc00) >> 10
@property
def size(self):
return (self.tag & 0x000003ff) >> 0
@property
def dsize(self):
return 4 + (self.size if self.size != 0x3ff else 0)
@property
def chunk(self):
return self.type & 0xff
@property
def schunk(self):
return struct.unpack('b', struct.pack('B', self.chunk))[0]
def is_(self, type):
return (self.type & TAG_TYPES[type][0]) == TAG_TYPES[type][1]
def mkmask(self):
return Tag(
0x700 if self.isunique else 0x7ff,
0x3ff if self.isattr else 0,
0)
def chid(self, nid):
ntag = Tag(self.type, nid, self.size)
if hasattr(self, 'off'): ntag.off = self.off
if hasattr(self, 'data'): ntag.data = self.data
if hasattr(self, 'crc'): ntag.crc = self.crc
return ntag
def typerepr(self):
if self.is_('crc') and getattr(self, 'crc', 0xffffffff) != 0xffffffff:
return 'crc (bad)'
reverse_types = {v: k for k, v in TAG_TYPES.items()}
for prefix in range(12):
mask = 0x7ff & ~((1 << prefix)-1)
if (mask, self.type & mask) in reverse_types:
type = reverse_types[mask, self.type & mask]
if prefix > 0:
return '%s %#0*x' % (
type, prefix//4, self.type & ((1 << prefix)-1))
else:
return type
else:
return '%02x' % self.type
def idrepr(self):
return repr(self.id) if self.id != 0x3ff else '.'
def sizerepr(self):
return repr(self.size) if self.size != 0x3ff else 'x'
def __repr__(self):
return 'Tag(%r, %d, %d)' % (self.typerepr(), self.id, self.size)
def __lt__(self, other):
return (self.id, self.type) < (other.id, other.type)
def __bool__(self):
return self.isvalid
def __int__(self):
return self.tag
def __index__(self):
return self.tag
class MetadataPair:
def __init__(self, blocks):
if len(blocks) > 1:
self.pair = [MetadataPair([block]) for block in blocks]
self.pair = sorted(self.pair, reverse=True)
self.data = self.pair[0].data
self.rev = self.pair[0].rev
self.tags = self.pair[0].tags
self.ids = self.pair[0].ids
self.log = self.pair[0].log
self.all_ = self.pair[0].all_
return
self.pair = [self]
self.data = blocks[0]
block = self.data
self.rev, = struct.unpack('<I', block[0:4])
crc = binascii.crc32(block[0:4])
# parse tags
corrupt = False
tag = Tag(0xffffffff)
off = 4
self.log = []
self.all_ = []
while len(block) - off >= 4:
ntag, = struct.unpack('>I', block[off:off+4])
tag = Tag(int(tag) ^ ntag)
tag.off = off + 4
tag.data = block[off+4:off+tag.dsize]
if tag.is_('crc'):
crc = binascii.crc32(block[off:off+4+4], crc)
else:
crc = binascii.crc32(block[off:off+tag.dsize], crc)
tag.crc = crc
off += tag.dsize
self.all_.append(tag)
if tag.is_('crc'):
# is valid commit?
if crc != 0xffffffff:
corrupt = True
if not corrupt:
self.log = self.all_.copy()
# reset tag parsing
crc = 0
tag = Tag(int(tag) ^ ((tag.type & 1) << 31))
# find active ids
self.ids = list(it.takewhile(
lambda id: Tag('name', id, 0) in self,
it.count()))
# find most recent tags
self.tags = []
for tag in self.log:
if tag.is_('crc') or tag.is_('splice'):
continue
elif tag.id == 0x3ff:
if tag in self and self[tag] is tag:
self.tags.append(tag)
else:
# id could have change, I know this is messy and slow
# but it works
for id in self.ids:
ntag = tag.chid(id)
if ntag in self and self[ntag] is tag:
self.tags.append(ntag)
self.tags = sorted(self.tags)
def __bool__(self):
return bool(self.log)
def __lt__(self, other):
# corrupt blocks don't count
if not self or not other:
return bool(other)
# use sequence arithmetic to avoid overflow
return not ((other.rev - self.rev) & 0x80000000)
def __contains__(self, args):
try:
self[args]
return True
except KeyError:
return False
def __getitem__(self, args):
if isinstance(args, tuple):
gmask, gtag = args
else:
gmask, gtag = args.mkmask(), args
gdiff = 0
for tag in reversed(self.log):
if (gmask.id != 0 and tag.is_('splice') and
tag.id <= gtag.id - gdiff):
if tag.is_('create') and tag.id == gtag.id - gdiff:
# creation point
break
gdiff += tag.schunk
if ((int(gmask) & int(tag)) ==
(int(gmask) & int(gtag.chid(gtag.id - gdiff)))):
if tag.size == 0x3ff:
# deleted
break
return tag
raise KeyError(gmask, gtag)
def _dump_tags(self, tags, f=sys.stdout, truncate=True):
f.write("%-8s %-8s %-13s %4s %4s" % (
'off', 'tag', 'type', 'id', 'len'))
if truncate:
f.write(' data (truncated)')
f.write('\n')
for tag in tags:
f.write("%08x: %08x %-13s %4s %4s" % (
tag.off, tag,
tag.typerepr(), tag.idrepr(), tag.sizerepr()))
if truncate:
f.write(" %-23s %-8s\n" % (
' '.join('%02x' % c for c in tag.data[:8]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, tag.data[:8]))))
else:
f.write("\n")
for i in range(0, len(tag.data), 16):
f.write(" %08x: %-47s %-16s\n" % (
tag.off+i,
' '.join('%02x' % c for c in tag.data[i:i+16]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, tag.data[i:i+16]))))
def dump_tags(self, f=sys.stdout, truncate=True):
self._dump_tags(self.tags, f=f, truncate=truncate)
def dump_log(self, f=sys.stdout, truncate=True):
self._dump_tags(self.log, f=f, truncate=truncate)
def dump_all(self, f=sys.stdout, truncate=True):
self._dump_tags(self.all_, f=f, truncate=truncate)
def main(args):
blocks = []
with open(args.disk, 'rb') as f:
for block in [args.block1, args.block2]:
if block is None:
continue
f.seek(block * args.block_size)
blocks.append(f.read(args.block_size)
.ljust(args.block_size, b'\xff'))
# find most recent pair
mdir = MetadataPair(blocks)
try:
mdir.tail = mdir[Tag('tail', 0, 0)]
if mdir.tail.size != 8 or mdir.tail.data == 8*b'\xff':
mdir.tail = None
except KeyError:
mdir.tail = None
print("mdir {%s} rev %d%s%s%s" % (
', '.join('%#x' % b
for b in [args.block1, args.block2]
if b is not None),
mdir.rev,
' (was %s)' % ', '.join('%d' % m.rev for m in mdir.pair[1:])
if len(mdir.pair) > 1 else '',
' (corrupted!)' if not mdir else '',
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
if mdir.tail else ''))
if args.all:
mdir.dump_all(truncate=not args.no_truncate)
elif args.log:
mdir.dump_log(truncate=not args.no_truncate)
else:
mdir.dump_tags(truncate=not args.no_truncate)
return 0 if mdir else 1
if __name__ == "__main__":
import argparse
import sys
parser = argparse.ArgumentParser(
description="Dump useful info about metadata pairs in littlefs.")
parser.add_argument('disk',
help="File representing the block device.")
parser.add_argument('block_size', type=lambda x: int(x, 0),
help="Size of a block in bytes.")
parser.add_argument('block1', type=lambda x: int(x, 0),
help="First block address for finding the metadata pair.")
parser.add_argument('block2', nargs='?', type=lambda x: int(x, 0),
help="Second block address for finding the metadata pair.")
parser.add_argument('-l', '--log', action='store_true',
help="Show tags in log.")
parser.add_argument('-a', '--all', action='store_true',
help="Show all tags in log, included tags in corrupted commits.")
parser.add_argument('-T', '--no-truncate', action='store_true',
help="Don't truncate large amounts of data.")
sys.exit(main(parser.parse_args()))

View File

@ -0,0 +1,183 @@
#!/usr/bin/env python3
import struct
import sys
import json
import io
import itertools as it
from readmdir import Tag, MetadataPair
def main(args):
superblock = None
gstate = b'\0\0\0\0\0\0\0\0\0\0\0\0'
dirs = []
mdirs = []
corrupted = []
cycle = False
with open(args.disk, 'rb') as f:
tail = (args.block1, args.block2)
hard = False
while True:
for m in it.chain((m for d in dirs for m in d), mdirs):
if set(m.blocks) == set(tail):
# cycle detected
cycle = m.blocks
if cycle:
break
# load mdir
data = []
blocks = {}
for block in tail:
f.seek(block * args.block_size)
data.append(f.read(args.block_size)
.ljust(args.block_size, b'\xff'))
blocks[id(data[-1])] = block
mdir = MetadataPair(data)
mdir.blocks = tuple(blocks[id(p.data)] for p in mdir.pair)
# fetch some key metadata as a we scan
try:
mdir.tail = mdir[Tag('tail', 0, 0)]
if mdir.tail.size != 8 or mdir.tail.data == 8*b'\xff':
mdir.tail = None
except KeyError:
mdir.tail = None
# have superblock?
try:
nsuperblock = mdir[
Tag(0x7ff, 0x3ff, 0), Tag('superblock', 0, 0)]
superblock = nsuperblock, mdir[Tag('inlinestruct', 0, 0)]
except KeyError:
pass
# have gstate?
try:
ngstate = mdir[Tag('movestate', 0, 0)]
gstate = bytes((a or 0) ^ (b or 0)
for a,b in it.zip_longest(gstate, ngstate.data))
except KeyError:
pass
# corrupted?
if not mdir:
corrupted.append(mdir)
# add to directories
mdirs.append(mdir)
if mdir.tail is None or not mdir.tail.is_('hardtail'):
dirs.append(mdirs)
mdirs = []
if mdir.tail is None:
break
tail = struct.unpack('<II', mdir.tail.data)
hard = mdir.tail.is_('hardtail')
# find paths
dirtable = {}
for dir in dirs:
dirtable[frozenset(dir[0].blocks)] = dir
pending = [("/", dirs[0])]
while pending:
path, dir = pending.pop(0)
for mdir in dir:
for tag in mdir.tags:
if tag.is_('dir'):
try:
npath = tag.data.decode('utf8')
dirstruct = mdir[Tag('dirstruct', tag.id, 0)]
nblocks = struct.unpack('<II', dirstruct.data)
nmdir = dirtable[frozenset(nblocks)]
pending.append(((path + '/' + npath), nmdir))
except KeyError:
pass
dir[0].path = path.replace('//', '/')
# print littlefs + version info
version = ('?', '?')
if superblock:
version = tuple(reversed(
struct.unpack('<HH', superblock[1].data[0:4].ljust(4, b'\xff'))))
print("%-47s%s" % ("littlefs v%s.%s" % version,
"data (truncated, if it fits)"
if not any([args.no_truncate, args.tags, args.log, args.all]) else ""))
# print gstate
print("gstate 0x%s" % ''.join('%02x' % c for c in gstate))
tag = Tag(struct.unpack('<I', gstate[0:4].ljust(4, b'\xff'))[0])
blocks = struct.unpack('<II', gstate[4:4+8].ljust(8, b'\xff'))
if tag.size or not tag.isvalid:
print(" orphans >=%d" % max(tag.size, 1))
if tag.type:
print(" move dir {%#x, %#x} id %d" % (
blocks[0], blocks[1], tag.id))
# print mdir info
for i, dir in enumerate(dirs):
print("dir %s" % (json.dumps(dir[0].path)
if hasattr(dir[0], 'path') else '(orphan)'))
for j, mdir in enumerate(dir):
print("mdir {%#x, %#x} rev %d (was %d)%s%s" % (
mdir.blocks[0], mdir.blocks[1], mdir.rev, mdir.pair[1].rev,
' (corrupted!)' if not mdir else '',
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
if mdir.tail else ''))
f = io.StringIO()
if args.log:
mdir.dump_log(f, truncate=not args.no_truncate)
elif args.all:
mdir.dump_all(f, truncate=not args.no_truncate)
else:
mdir.dump_tags(f, truncate=not args.no_truncate)
lines = list(filter(None, f.getvalue().split('\n')))
for k, line in enumerate(lines):
print("%s %s" % (
' ' if j == len(dir)-1 else
'v' if k == len(lines)-1 else
'|',
line))
errcode = 0
for mdir in corrupted:
errcode = errcode or 1
print("*** corrupted mdir {%#x, %#x}! ***" % (
mdir.blocks[0], mdir.blocks[1]))
if cycle:
errcode = errcode or 2
print("*** cycle detected {%#x, %#x}! ***" % (
cycle[0], cycle[1]))
return errcode
if __name__ == "__main__":
import argparse
import sys
parser = argparse.ArgumentParser(
description="Dump semantic info about the metadata tree in littlefs")
parser.add_argument('disk',
help="File representing the block device.")
parser.add_argument('block_size', type=lambda x: int(x, 0),
help="Size of a block in bytes.")
parser.add_argument('block1', nargs='?', default=0,
type=lambda x: int(x, 0),
help="Optional first block address for finding the superblock.")
parser.add_argument('block2', nargs='?', default=1,
type=lambda x: int(x, 0),
help="Optional second block address for finding the superblock.")
parser.add_argument('-l', '--log', action='store_true',
help="Show tags in log.")
parser.add_argument('-a', '--all', action='store_true',
help="Show all tags in log, included tags in corrupted commits.")
parser.add_argument('-T', '--no-truncate', action='store_true',
help="Show the full contents of files/attrs/tags.")
sys.exit(main(parser.parse_args()))

View File

@ -0,0 +1,778 @@
#!/usr/bin/env python3
# This script manages littlefs tests, which are configured with
# .toml files stored in the tests directory.
#
import toml
import glob
import re
import os
import io
import itertools as it
import collections.abc as abc
import subprocess as sp
import base64
import sys
import copy
import shlex
import pty
import errno
import signal
TESTDIR = 'tests'
RULES = """
define FLATTEN
tests/%$(subst /,.,$(target)): $(target)
./scripts/explode_asserts.py $$< -o $$@
endef
$(foreach target,$(SRC),$(eval $(FLATTEN)))
-include tests/*.d
.SECONDARY:
%.test: %.test.o $(foreach f,$(subst /,.,$(SRC:.c=.o)),%.$f)
$(CC) $(CFLAGS) $^ $(LFLAGS) -o $@
"""
GLOBALS = """
//////////////// AUTOGENERATED TEST ////////////////
#include "lfs2.h"
#include "bd/lfs2_testbd.h"
#include <stdio.h>
extern const char *lfs2_testbd_path;
extern uint32_t lfs2_testbd_cycles;
"""
DEFINES = {
'LFS2_READ_SIZE': 16,
'LFS2_PROG_SIZE': 'LFS2_READ_SIZE',
'LFS2_BLOCK_SIZE': 512,
'LFS2_BLOCK_COUNT': 1024,
'LFS2_BLOCK_CYCLES': -1,
'LFS2_CACHE_SIZE': '(64 % LFS2_PROG_SIZE == 0 ? 64 : LFS2_PROG_SIZE)',
'LFS2_LOOKAHEAD_SIZE': 16,
'LFS2_ERASE_VALUE': 0xff,
'LFS2_ERASE_CYCLES': 0,
'LFS2_BADBLOCK_BEHAVIOR': 'LFS2_TESTBD_BADBLOCK_PROGERROR',
}
PROLOGUE = """
// prologue
__attribute__((unused)) lfs2_t lfs2;
__attribute__((unused)) lfs2_testbd_t bd;
__attribute__((unused)) lfs2_file_t file;
__attribute__((unused)) lfs2_dir_t dir;
__attribute__((unused)) struct lfs2_info info;
__attribute__((unused)) char path[1024];
__attribute__((unused)) uint8_t buffer[1024];
__attribute__((unused)) lfs2_size_t size;
__attribute__((unused)) int err;
__attribute__((unused)) const struct lfs2_config cfg = {
.context = &bd,
.read = lfs2_testbd_read,
.prog = lfs2_testbd_prog,
.erase = lfs2_testbd_erase,
.sync = lfs2_testbd_sync,
.read_size = LFS2_READ_SIZE,
.prog_size = LFS2_PROG_SIZE,
.block_size = LFS2_BLOCK_SIZE,
.block_count = LFS2_BLOCK_COUNT,
.block_cycles = LFS2_BLOCK_CYCLES,
.cache_size = LFS2_CACHE_SIZE,
.lookahead_size = LFS2_LOOKAHEAD_SIZE,
};
__attribute__((unused)) const struct lfs2_testbd_config bdcfg = {
.erase_value = LFS2_ERASE_VALUE,
.erase_cycles = LFS2_ERASE_CYCLES,
.badblock_behavior = LFS2_BADBLOCK_BEHAVIOR,
.power_cycles = lfs2_testbd_cycles,
};
lfs2_testbd_createcfg(&cfg, lfs2_testbd_path, &bdcfg) => 0;
"""
EPILOGUE = """
// epilogue
lfs2_testbd_destroy(&cfg) => 0;
"""
PASS = '\033[32m✓\033[0m'
FAIL = '\033[31m✗\033[0m'
class TestFailure(Exception):
def __init__(self, case, returncode=None, stdout=None, assert_=None):
self.case = case
self.returncode = returncode
self.stdout = stdout
self.assert_ = assert_
class TestCase:
def __init__(self, config, filter=filter,
suite=None, caseno=None, lineno=None, **_):
self.config = config
self.filter = filter
self.suite = suite
self.caseno = caseno
self.lineno = lineno
self.code = config['code']
self.code_lineno = config['code_lineno']
self.defines = config.get('define', {})
self.if_ = config.get('if', None)
self.in_ = config.get('in', None)
def __str__(self):
if hasattr(self, 'permno'):
if any(k not in self.case.defines for k in self.defines):
return '%s#%d#%d (%s)' % (
self.suite.name, self.caseno, self.permno, ', '.join(
'%s=%s' % (k, v) for k, v in self.defines.items()
if k not in self.case.defines))
else:
return '%s#%d#%d' % (
self.suite.name, self.caseno, self.permno)
else:
return '%s#%d' % (
self.suite.name, self.caseno)
def permute(self, class_=None, defines={}, permno=None, **_):
ncase = (class_ or type(self))(self.config)
for k, v in self.__dict__.items():
setattr(ncase, k, v)
ncase.case = self
ncase.perms = [ncase]
ncase.permno = permno
ncase.defines = defines
return ncase
def build(self, f, **_):
# prologue
for k, v in sorted(self.defines.items()):
if k not in self.suite.defines:
f.write('#define %s %s\n' % (k, v))
f.write('void test_case%d(%s) {' % (self.caseno, ','.join(
'\n'+8*' '+'__attribute__((unused)) intmax_t %s' % k
for k in sorted(self.perms[0].defines)
if k not in self.defines)))
f.write(PROLOGUE)
f.write('\n')
f.write(4*' '+'// test case %d\n' % self.caseno)
f.write(4*' '+'#line %d "%s"\n' % (self.code_lineno, self.suite.path))
# test case goes here
f.write(self.code)
# epilogue
f.write(EPILOGUE)
f.write('}\n')
for k, v in sorted(self.defines.items()):
if k not in self.suite.defines:
f.write('#undef %s\n' % k)
def shouldtest(self, **args):
if (self.filter is not None and
len(self.filter) >= 1 and
self.filter[0] != self.caseno):
return False
elif (self.filter is not None and
len(self.filter) >= 2 and
self.filter[1] != self.permno):
return False
elif args.get('no_internal', False) and self.in_ is not None:
return False
elif self.if_ is not None:
if_ = self.if_
while True:
for k, v in sorted(self.defines.items(),
key=lambda x: len(x[0]), reverse=True):
if k in if_:
if_ = if_.replace(k, '(%s)' % v)
break
else:
break
if_ = (
re.sub('(\&\&|\?)', ' and ',
re.sub('(\|\||:)', ' or ',
re.sub('!(?!=)', ' not ', if_))))
return eval(if_)
else:
return True
def test(self, exec=[], persist=False, cycles=None,
gdb=False, failure=None, disk=None, **args):
# build command
cmd = exec + ['./%s.test' % self.suite.path,
repr(self.caseno), repr(self.permno)]
# persist disk or keep in RAM for speed?
if persist:
if not disk:
disk = self.suite.path + '.disk'
if persist != 'noerase':
try:
with open(disk, 'w') as f:
f.truncate(0)
if args.get('verbose', False):
print('truncate --size=0', disk)
except FileNotFoundError:
pass
cmd.append(disk)
# simulate power-loss after n cycles?
if cycles:
cmd.append(str(cycles))
# failed? drop into debugger?
if gdb and failure:
ncmd = ['gdb']
if gdb == 'assert':
ncmd.extend(['-ex', 'r'])
if failure.assert_:
ncmd.extend(['-ex', 'up 2'])
elif gdb == 'main':
ncmd.extend([
'-ex', 'b %s:%d' % (self.suite.path, self.code_lineno),
'-ex', 'r'])
ncmd.extend(['--args'] + cmd)
if args.get('verbose', False):
print(' '.join(shlex.quote(c) for c in ncmd))
signal.signal(signal.SIGINT, signal.SIG_IGN)
sys.exit(sp.call(ncmd))
# run test case!
mpty, spty = pty.openpty()
if args.get('verbose', False):
print(' '.join(shlex.quote(c) for c in cmd))
proc = sp.Popen(cmd, stdout=spty, stderr=spty)
os.close(spty)
mpty = os.fdopen(mpty, 'r', 1)
stdout = []
assert_ = None
try:
while True:
try:
line = mpty.readline()
except OSError as e:
if e.errno == errno.EIO:
break
raise
stdout.append(line)
if args.get('verbose', False):
sys.stdout.write(line)
# intercept asserts
m = re.match(
'^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$'
.format('(?:\033\[[\d;]*.| )*', 'assert'),
line)
if m and assert_ is None:
try:
with open(m.group(1)) as f:
lineno = int(m.group(2))
line = (next(it.islice(f, lineno-1, None))
.strip('\n'))
assert_ = {
'path': m.group(1),
'line': line,
'lineno': lineno,
'message': m.group(3)}
except:
pass
except KeyboardInterrupt:
raise TestFailure(self, 1, stdout, None)
proc.wait()
# did we pass?
if proc.returncode != 0:
raise TestFailure(self, proc.returncode, stdout, assert_)
else:
return PASS
class ValgrindTestCase(TestCase):
def __init__(self, config, **args):
self.leaky = config.get('leaky', False)
super().__init__(config, **args)
def shouldtest(self, **args):
return not self.leaky and super().shouldtest(**args)
def test(self, exec=[], **args):
verbose = args.get('verbose', False)
uninit = (self.defines.get('LFS2_ERASE_VALUE', None) == -1)
exec = [
'valgrind',
'--leak-check=full',
] + (['--undef-value-errors=no'] if uninit else []) + [
] + (['--track-origins=yes'] if not uninit else []) + [
'--error-exitcode=4',
'--error-limit=no',
] + (['--num-callers=1'] if not verbose else []) + [
'-q'] + exec
return super().test(exec=exec, **args)
class ReentrantTestCase(TestCase):
def __init__(self, config, **args):
self.reentrant = config.get('reentrant', False)
super().__init__(config, **args)
def shouldtest(self, **args):
return self.reentrant and super().shouldtest(**args)
def test(self, persist=False, gdb=False, failure=None, **args):
for cycles in it.count(1):
# clear disk first?
if cycles == 1 and persist != 'noerase':
persist = 'erase'
else:
persist = 'noerase'
# exact cycle we should drop into debugger?
if gdb and failure and failure.cycleno == cycles:
return super().test(gdb=gdb, persist=persist, cycles=cycles,
failure=failure, **args)
# run tests, but kill the program after prog/erase has
# been hit n cycles. We exit with a special return code if the
# program has not finished, since this isn't a test failure.
try:
return super().test(persist=persist, cycles=cycles, **args)
except TestFailure as nfailure:
if nfailure.returncode == 33:
continue
else:
nfailure.cycleno = cycles
raise
class TestSuite:
def __init__(self, path, classes=[TestCase], defines={},
filter=None, **args):
self.name = os.path.basename(path)
if self.name.endswith('.toml'):
self.name = self.name[:-len('.toml')]
self.path = path
self.classes = classes
self.defines = defines.copy()
self.filter = filter
with open(path) as f:
# load tests
config = toml.load(f)
# find line numbers
f.seek(0)
linenos = []
code_linenos = []
for i, line in enumerate(f):
if re.match(r'\[\[\s*case\s*\]\]', line):
linenos.append(i+1)
if re.match(r'code\s*=\s*(\'\'\'|""")', line):
code_linenos.append(i+2)
code_linenos.reverse()
# grab global config
for k, v in config.get('define', {}).items():
if k not in self.defines:
self.defines[k] = v
self.code = config.get('code', None)
if self.code is not None:
self.code_lineno = code_linenos.pop()
# create initial test cases
self.cases = []
for i, (case, lineno) in enumerate(zip(config['case'], linenos)):
# code lineno?
if 'code' in case:
case['code_lineno'] = code_linenos.pop()
# merge conditions if necessary
if 'if' in config and 'if' in case:
case['if'] = '(%s) && (%s)' % (config['if'], case['if'])
elif 'if' in config:
case['if'] = config['if']
# initialize test case
self.cases.append(TestCase(case, filter=filter,
suite=self, caseno=i+1, lineno=lineno, **args))
def __str__(self):
return self.name
def __lt__(self, other):
return self.name < other.name
def permute(self, **args):
for case in self.cases:
# lets find all parameterized definitions, in one of [args.D,
# suite.defines, case.defines, DEFINES]. Note that each of these
# can be either a dict of defines, or a list of dicts, expressing
# an initial set of permutations.
pending = [{}]
for inits in [self.defines, case.defines, DEFINES]:
if not isinstance(inits, list):
inits = [inits]
npending = []
for init, pinit in it.product(inits, pending):
ninit = pinit.copy()
for k, v in init.items():
if k not in ninit:
try:
ninit[k] = eval(v)
except:
ninit[k] = v
npending.append(ninit)
pending = npending
# expand permutations
pending = list(reversed(pending))
expanded = []
while pending:
perm = pending.pop()
for k, v in sorted(perm.items()):
if not isinstance(v, str) and isinstance(v, abc.Iterable):
for nv in reversed(v):
nperm = perm.copy()
nperm[k] = nv
pending.append(nperm)
break
else:
expanded.append(perm)
# generate permutations
case.perms = []
for i, (class_, defines) in enumerate(
it.product(self.classes, expanded)):
case.perms.append(case.permute(
class_, defines, permno=i+1, **args))
# also track non-unique defines
case.defines = {}
for k, v in case.perms[0].defines.items():
if all(perm.defines[k] == v for perm in case.perms):
case.defines[k] = v
# track all perms and non-unique defines
self.perms = []
for case in self.cases:
self.perms.extend(case.perms)
self.defines = {}
for k, v in self.perms[0].defines.items():
if all(perm.defines.get(k, None) == v for perm in self.perms):
self.defines[k] = v
return self.perms
def build(self, **args):
# build test files
tf = open(self.path + '.test.c.t', 'w')
tf.write(GLOBALS)
if self.code is not None:
tf.write('#line %d "%s"\n' % (self.code_lineno, self.path))
tf.write(self.code)
tfs = {None: tf}
for case in self.cases:
if case.in_ not in tfs:
tfs[case.in_] = open(self.path+'.'+
case.in_.replace('/', '.')+'.t', 'w')
tfs[case.in_].write('#line 1 "%s"\n' % case.in_)
with open(case.in_) as f:
for line in f:
tfs[case.in_].write(line)
tfs[case.in_].write('\n')
tfs[case.in_].write(GLOBALS)
tfs[case.in_].write('\n')
case.build(tfs[case.in_], **args)
tf.write('\n')
tf.write('const char *lfs2_testbd_path;\n')
tf.write('uint32_t lfs2_testbd_cycles;\n')
tf.write('int main(int argc, char **argv) {\n')
tf.write(4*' '+'int case_ = (argc > 1) ? atoi(argv[1]) : 0;\n')
tf.write(4*' '+'int perm = (argc > 2) ? atoi(argv[2]) : 0;\n')
tf.write(4*' '+'lfs2_testbd_path = (argc > 3) ? argv[3] : NULL;\n')
tf.write(4*' '+'lfs2_testbd_cycles = (argc > 4) ? atoi(argv[4]) : 0;\n')
for perm in self.perms:
# test declaration
tf.write(4*' '+'extern void test_case%d(%s);\n' % (
perm.caseno, ', '.join(
'intmax_t %s' % k for k in sorted(perm.defines)
if k not in perm.case.defines)))
# test call
tf.write(4*' '+
'if (argc < 3 || (case_ == %d && perm == %d)) {'
' test_case%d(%s); '
'}\n' % (perm.caseno, perm.permno, perm.caseno, ', '.join(
str(v) for k, v in sorted(perm.defines.items())
if k not in perm.case.defines)))
tf.write('}\n')
for tf in tfs.values():
tf.close()
# write makefiles
with open(self.path + '.mk', 'w') as mk:
mk.write(RULES.replace(4*' ', '\t'))
mk.write('\n')
# add truely global defines globally
for k, v in sorted(self.defines.items()):
mk.write('%s: override CFLAGS += -D%s=%r\n' % (
self.path+'.test', k, v))
for path in tfs:
if path is None:
mk.write('%s: %s | %s\n' % (
self.path+'.test.c',
self.path,
self.path+'.test.c.t'))
else:
mk.write('%s: %s %s | %s\n' % (
self.path+'.'+path.replace('/', '.'),
self.path, path,
self.path+'.'+path.replace('/', '.')+'.t'))
mk.write('\t./scripts/explode_asserts.py $| -o $@\n')
self.makefile = self.path + '.mk'
self.target = self.path + '.test'
return self.makefile, self.target
def test(self, **args):
# run test suite!
if not args.get('verbose', True):
sys.stdout.write(self.name + ' ')
sys.stdout.flush()
for perm in self.perms:
if not perm.shouldtest(**args):
continue
try:
result = perm.test(**args)
except TestFailure as failure:
perm.result = failure
if not args.get('verbose', True):
sys.stdout.write(FAIL)
sys.stdout.flush()
if not args.get('keep_going', False):
if not args.get('verbose', True):
sys.stdout.write('\n')
raise
else:
perm.result = PASS
if not args.get('verbose', True):
sys.stdout.write(PASS)
sys.stdout.flush()
if not args.get('verbose', True):
sys.stdout.write('\n')
def main(**args):
# figure out explicit defines
defines = {}
for define in args['D']:
k, v, *_ = define.split('=', 2) + ['']
defines[k] = v
# and what class of TestCase to run
classes = []
if args.get('normal', False):
classes.append(TestCase)
if args.get('reentrant', False):
classes.append(ReentrantTestCase)
if args.get('valgrind', False):
classes.append(ValgrindTestCase)
if not classes:
classes = [TestCase]
suites = []
for testpath in args['testpaths']:
# optionally specified test case/perm
testpath, *filter = testpath.split('#')
filter = [int(f) for f in filter]
# figure out the suite's toml file
if os.path.isdir(testpath):
testpath = testpath + '/test_*.toml'
elif os.path.isfile(testpath):
testpath = testpath
elif testpath.endswith('.toml'):
testpath = TESTDIR + '/' + testpath
else:
testpath = TESTDIR + '/' + testpath + '.toml'
# find tests
for path in glob.glob(testpath):
suites.append(TestSuite(path, classes, defines, filter, **args))
# sort for reproducability
suites = sorted(suites)
# generate permutations
for suite in suites:
suite.permute(**args)
# build tests in parallel
print('====== building ======')
makefiles = []
targets = []
for suite in suites:
makefile, target = suite.build(**args)
makefiles.append(makefile)
targets.append(target)
cmd = (['make', '-f', 'Makefile'] +
list(it.chain.from_iterable(['-f', m] for m in makefiles)) +
[target for target in targets])
mpty, spty = pty.openpty()
if args.get('verbose', False):
print(' '.join(shlex.quote(c) for c in cmd))
proc = sp.Popen(cmd, stdout=spty, stderr=spty)
os.close(spty)
mpty = os.fdopen(mpty, 'r', 1)
stdout = []
while True:
try:
line = mpty.readline()
except OSError as e:
if e.errno == errno.EIO:
break
raise
stdout.append(line)
if args.get('verbose', False):
sys.stdout.write(line)
# intercept warnings
m = re.match(
'^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$'
.format('(?:\033\[[\d;]*.| )*', 'warning'),
line)
if m and not args.get('verbose', False):
try:
with open(m.group(1)) as f:
lineno = int(m.group(2))
line = next(it.islice(f, lineno-1, None)).strip('\n')
sys.stdout.write(
"\033[01m{path}:{lineno}:\033[01;35mwarning:\033[m "
"{message}\n{line}\n\n".format(
path=m.group(1), line=line, lineno=lineno,
message=m.group(3)))
except:
pass
proc.wait()
if proc.returncode != 0:
if not args.get('verbose', False):
for line in stdout:
sys.stdout.write(line)
sys.exit(-3)
print('built %d test suites, %d test cases, %d permutations' % (
len(suites),
sum(len(suite.cases) for suite in suites),
sum(len(suite.perms) for suite in suites)))
filtered = 0
for suite in suites:
for perm in suite.perms:
filtered += perm.shouldtest(**args)
if filtered != sum(len(suite.perms) for suite in suites):
print('filtered down to %d permutations' % filtered)
# only requested to build?
if args.get('build', False):
return 0
print('====== testing ======')
try:
for suite in suites:
suite.test(**args)
except TestFailure:
pass
print('====== results ======')
passed = 0
failed = 0
for suite in suites:
for perm in suite.perms:
if not hasattr(perm, 'result'):
continue
if perm.result == PASS:
passed += 1
else:
sys.stdout.write(
"\033[01m{path}:{lineno}:\033[01;31mfailure:\033[m "
"{perm} failed with {returncode}\n".format(
perm=perm, path=perm.suite.path, lineno=perm.lineno,
returncode=perm.result.returncode or 0))
if perm.result.stdout:
if perm.result.assert_:
stdout = perm.result.stdout[:-1]
else:
stdout = perm.result.stdout
for line in stdout[-5:]:
sys.stdout.write(line)
if perm.result.assert_:
sys.stdout.write(
"\033[01m{path}:{lineno}:\033[01;31massert:\033[m "
"{message}\n{line}\n".format(
**perm.result.assert_))
sys.stdout.write('\n')
failed += 1
if args.get('gdb', False):
failure = None
for suite in suites:
for perm in suite.perms:
if getattr(perm, 'result', PASS) != PASS:
failure = perm.result
if failure is not None:
print('======= gdb ======')
# drop into gdb
failure.case.test(failure=failure, **args)
sys.exit(0)
print('tests passed: %d' % passed)
print('tests failed: %d' % failed)
return 1 if failed > 0 else 0
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Run parameterized tests in various configurations.")
parser.add_argument('testpaths', nargs='*', default=[TESTDIR],
help="Description of test(s) to run. By default, this is all tests \
found in the \"{0}\" directory. Here, you can specify a different \
directory of tests, a specific file, a suite by name, and even a \
specific test case by adding brackets. For example \
\"test_dirs[0]\" or \"{0}/test_dirs.toml[0]\".".format(TESTDIR))
parser.add_argument('-D', action='append', default=[],
help="Overriding parameter definitions.")
parser.add_argument('-v', '--verbose', action='store_true',
help="Output everything that is happening.")
parser.add_argument('-k', '--keep-going', action='store_true',
help="Run all tests instead of stopping on first error. Useful for CI.")
parser.add_argument('-p', '--persist', choices=['erase', 'noerase'],
nargs='?', const='erase',
help="Store disk image in a file.")
parser.add_argument('-b', '--build', action='store_true',
help="Only build the tests, do not execute.")
parser.add_argument('-g', '--gdb', choices=['init', 'main', 'assert'],
nargs='?', const='assert',
help="Drop into gdb on test failure.")
parser.add_argument('--no-internal', action='store_true',
help="Don't run tests that require internal knowledge.")
parser.add_argument('-n', '--normal', action='store_true',
help="Run tests normally.")
parser.add_argument('-r', '--reentrant', action='store_true',
help="Run reentrant tests with simulated power-loss.")
parser.add_argument('-V', '--valgrind', action='store_true',
help="Run non-leaky tests under valgrind to check for memory leaks.")
parser.add_argument('-e', '--exec', default=[], type=lambda e: e.split(' '),
help="Run tests with another executable prefixed on the command line.")
parser.add_argument('-d', '--disk',
help="Specify a file to use for persistent/reentrant tests.")
sys.exit(main(**vars(parser.parse_args())))

View File

@ -0,0 +1,653 @@
# allocator tests
# note for these to work there are a number constraints on the device geometry
if = 'LFS2_BLOCK_CYCLES == -1'
[[case]] # parallel allocation test
define.FILES = 3
define.SIZE = '(((LFS2_BLOCK_SIZE-8)*(LFS2_BLOCK_COUNT-6)) / FILES)'
code = '''
const char *names[FILES] = {"bacon", "eggs", "pancakes"};
lfs2_file_t files[FILES];
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "breakfast") => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int n = 0; n < FILES; n++) {
sprintf(path, "breakfast/%s", names[n]);
lfs2_file_open(&lfs2, &files[n], path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
}
for (int n = 0; n < FILES; n++) {
size = strlen(names[n]);
for (lfs2_size_t i = 0; i < SIZE; i += size) {
lfs2_file_write(&lfs2, &files[n], names[n], size) => size;
}
}
for (int n = 0; n < FILES; n++) {
lfs2_file_close(&lfs2, &files[n]) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int n = 0; n < FILES; n++) {
sprintf(path, "breakfast/%s", names[n]);
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
size = strlen(names[n]);
for (lfs2_size_t i = 0; i < SIZE; i += size) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
assert(memcmp(buffer, names[n], size) == 0);
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # serial allocation test
define.FILES = 3
define.SIZE = '(((LFS2_BLOCK_SIZE-8)*(LFS2_BLOCK_COUNT-6)) / FILES)'
code = '''
const char *names[FILES] = {"bacon", "eggs", "pancakes"};
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "breakfast") => 0;
lfs2_unmount(&lfs2) => 0;
for (int n = 0; n < FILES; n++) {
lfs2_mount(&lfs2, &cfg) => 0;
sprintf(path, "breakfast/%s", names[n]);
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
size = strlen(names[n]);
memcpy(buffer, names[n], size);
for (int i = 0; i < SIZE; i += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
}
lfs2_mount(&lfs2, &cfg) => 0;
for (int n = 0; n < FILES; n++) {
sprintf(path, "breakfast/%s", names[n]);
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
size = strlen(names[n]);
for (int i = 0; i < SIZE; i += size) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
assert(memcmp(buffer, names[n], size) == 0);
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # parallel allocation reuse test
define.FILES = 3
define.SIZE = '(((LFS2_BLOCK_SIZE-8)*(LFS2_BLOCK_COUNT-6)) / FILES)'
define.CYCLES = [1, 10]
code = '''
const char *names[FILES] = {"bacon", "eggs", "pancakes"};
lfs2_file_t files[FILES];
lfs2_format(&lfs2, &cfg) => 0;
for (int c = 0; c < CYCLES; c++) {
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "breakfast") => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int n = 0; n < FILES; n++) {
sprintf(path, "breakfast/%s", names[n]);
lfs2_file_open(&lfs2, &files[n], path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
}
for (int n = 0; n < FILES; n++) {
size = strlen(names[n]);
for (int i = 0; i < SIZE; i += size) {
lfs2_file_write(&lfs2, &files[n], names[n], size) => size;
}
}
for (int n = 0; n < FILES; n++) {
lfs2_file_close(&lfs2, &files[n]) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int n = 0; n < FILES; n++) {
sprintf(path, "breakfast/%s", names[n]);
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
size = strlen(names[n]);
for (int i = 0; i < SIZE; i += size) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
assert(memcmp(buffer, names[n], size) == 0);
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int n = 0; n < FILES; n++) {
sprintf(path, "breakfast/%s", names[n]);
lfs2_remove(&lfs2, path) => 0;
}
lfs2_remove(&lfs2, "breakfast") => 0;
lfs2_unmount(&lfs2) => 0;
}
'''
[[case]] # serial allocation reuse test
define.FILES = 3
define.SIZE = '(((LFS2_BLOCK_SIZE-8)*(LFS2_BLOCK_COUNT-6)) / FILES)'
define.CYCLES = [1, 10]
code = '''
const char *names[FILES] = {"bacon", "eggs", "pancakes"};
lfs2_format(&lfs2, &cfg) => 0;
for (int c = 0; c < CYCLES; c++) {
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "breakfast") => 0;
lfs2_unmount(&lfs2) => 0;
for (int n = 0; n < FILES; n++) {
lfs2_mount(&lfs2, &cfg) => 0;
sprintf(path, "breakfast/%s", names[n]);
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
size = strlen(names[n]);
memcpy(buffer, names[n], size);
for (int i = 0; i < SIZE; i += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
}
lfs2_mount(&lfs2, &cfg) => 0;
for (int n = 0; n < FILES; n++) {
sprintf(path, "breakfast/%s", names[n]);
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
size = strlen(names[n]);
for (int i = 0; i < SIZE; i += size) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
assert(memcmp(buffer, names[n], size) == 0);
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int n = 0; n < FILES; n++) {
sprintf(path, "breakfast/%s", names[n]);
lfs2_remove(&lfs2, path) => 0;
}
lfs2_remove(&lfs2, "breakfast") => 0;
lfs2_unmount(&lfs2) => 0;
}
'''
[[case]] # exhaustion test
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
size = strlen("exhaustion");
memcpy(buffer, "exhaustion", size);
lfs2_file_write(&lfs2, &file, buffer, size) => size;
lfs2_file_sync(&lfs2, &file) => 0;
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
lfs2_ssize_t res;
while (true) {
res = lfs2_file_write(&lfs2, &file, buffer, size);
if (res < 0) {
break;
}
res => size;
}
res => LFS2_ERR_NOSPC;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_RDONLY);
size = strlen("exhaustion");
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "exhaustion", size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # exhaustion wraparound test
define.SIZE = '(((LFS2_BLOCK_SIZE-8)*(LFS2_BLOCK_COUNT-4)) / 3)'
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "padding", LFS2_O_WRONLY | LFS2_O_CREAT);
size = strlen("buffering");
memcpy(buffer, "buffering", size);
for (int i = 0; i < SIZE; i += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_remove(&lfs2, "padding") => 0;
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
size = strlen("exhaustion");
memcpy(buffer, "exhaustion", size);
lfs2_file_write(&lfs2, &file, buffer, size) => size;
lfs2_file_sync(&lfs2, &file) => 0;
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
lfs2_ssize_t res;
while (true) {
res = lfs2_file_write(&lfs2, &file, buffer, size);
if (res < 0) {
break;
}
res => size;
}
res => LFS2_ERR_NOSPC;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_RDONLY);
size = strlen("exhaustion");
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "exhaustion", size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_remove(&lfs2, "exhaustion") => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # dir exhaustion test
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// find out max file size
lfs2_mkdir(&lfs2, "exhaustiondir") => 0;
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
int count = 0;
while (true) {
err = lfs2_file_write(&lfs2, &file, buffer, size);
if (err < 0) {
break;
}
count += 1;
}
err => LFS2_ERR_NOSPC;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_remove(&lfs2, "exhaustion") => 0;
lfs2_remove(&lfs2, "exhaustiondir") => 0;
// see if dir fits with max file size
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
for (int i = 0; i < count; i++) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_mkdir(&lfs2, "exhaustiondir") => 0;
lfs2_remove(&lfs2, "exhaustiondir") => 0;
lfs2_remove(&lfs2, "exhaustion") => 0;
// see if dir fits with > max file size
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
for (int i = 0; i < count+1; i++) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_mkdir(&lfs2, "exhaustiondir") => LFS2_ERR_NOSPC;
lfs2_remove(&lfs2, "exhaustion") => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # what if we have a bad block during an allocation scan?
in = "lfs2.c"
define.LFS2_ERASE_CYCLES = 0xffffffff
define.LFS2_BADBLOCK_BEHAVIOR = 'LFS2_TESTBD_BADBLOCK_READERROR'
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// first fill to exhaustion to find available space
lfs2_file_open(&lfs2, &file, "pacman", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
strcpy((char*)buffer, "waka");
size = strlen("waka");
lfs2_size_t filesize = 0;
while (true) {
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, buffer, size);
assert(res == (lfs2_ssize_t)size || res == LFS2_ERR_NOSPC);
if (res == LFS2_ERR_NOSPC) {
break;
}
filesize += size;
}
lfs2_file_close(&lfs2, &file) => 0;
// now fill all but a couple of blocks of the filesystem with data
filesize -= 3*LFS2_BLOCK_SIZE;
lfs2_file_open(&lfs2, &file, "pacman", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
strcpy((char*)buffer, "waka");
size = strlen("waka");
for (lfs2_size_t i = 0; i < filesize/size; i++) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
// also save head of file so we can error during lookahead scan
lfs2_block_t fileblock = file.ctz.head;
lfs2_unmount(&lfs2) => 0;
// remount to force an alloc scan
lfs2_mount(&lfs2, &cfg) => 0;
// but mark the head of our file as a "bad block", this is force our
// scan to bail early
lfs2_testbd_setwear(&cfg, fileblock, 0xffffffff) => 0;
lfs2_file_open(&lfs2, &file, "ghost", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
strcpy((char*)buffer, "chomp");
size = strlen("chomp");
while (true) {
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, buffer, size);
assert(res == (lfs2_ssize_t)size || res == LFS2_ERR_CORRUPT);
if (res == LFS2_ERR_CORRUPT) {
break;
}
}
lfs2_file_close(&lfs2, &file) => 0;
// now reverse the "bad block" and try to write the file again until we
// run out of space
lfs2_testbd_setwear(&cfg, fileblock, 0) => 0;
lfs2_file_open(&lfs2, &file, "ghost", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
strcpy((char*)buffer, "chomp");
size = strlen("chomp");
while (true) {
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, buffer, size);
assert(res == (lfs2_ssize_t)size || res == LFS2_ERR_NOSPC);
if (res == LFS2_ERR_NOSPC) {
break;
}
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
// check that the disk isn't hurt
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "pacman", LFS2_O_RDONLY) => 0;
strcpy((char*)buffer, "waka");
size = strlen("waka");
for (lfs2_size_t i = 0; i < filesize/size; i++) {
uint8_t rbuffer[4];
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
# Below, I don't like these tests. They're fragile and depend _heavily_
# on the geometry of the block device. But they are valuable. Eventually they
# should be removed and replaced with generalized tests.
[[case]] # chained dir exhaustion test
define.LFS2_BLOCK_SIZE = 512
define.LFS2_BLOCK_COUNT = 1024
if = 'LFS2_BLOCK_SIZE == 512 && LFS2_BLOCK_COUNT == 1024'
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// find out max file size
lfs2_mkdir(&lfs2, "exhaustiondir") => 0;
for (int i = 0; i < 10; i++) {
sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
lfs2_mkdir(&lfs2, path) => 0;
}
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
int count = 0;
while (true) {
err = lfs2_file_write(&lfs2, &file, buffer, size);
if (err < 0) {
break;
}
count += 1;
}
err => LFS2_ERR_NOSPC;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_remove(&lfs2, "exhaustion") => 0;
lfs2_remove(&lfs2, "exhaustiondir") => 0;
for (int i = 0; i < 10; i++) {
sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
lfs2_remove(&lfs2, path) => 0;
}
// see that chained dir fails
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
for (int i = 0; i < count+1; i++) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_sync(&lfs2, &file) => 0;
for (int i = 0; i < 10; i++) {
sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
lfs2_mkdir(&lfs2, path) => 0;
}
lfs2_mkdir(&lfs2, "exhaustiondir") => LFS2_ERR_NOSPC;
// shorten file to try a second chained dir
while (true) {
err = lfs2_mkdir(&lfs2, "exhaustiondir");
if (err != LFS2_ERR_NOSPC) {
break;
}
lfs2_ssize_t filesize = lfs2_file_size(&lfs2, &file);
filesize > 0 => true;
lfs2_file_truncate(&lfs2, &file, filesize - size) => 0;
lfs2_file_sync(&lfs2, &file) => 0;
}
err => 0;
lfs2_mkdir(&lfs2, "exhaustiondir2") => LFS2_ERR_NOSPC;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # split dir test
define.LFS2_BLOCK_SIZE = 512
define.LFS2_BLOCK_COUNT = 1024
if = 'LFS2_BLOCK_SIZE == 512 && LFS2_BLOCK_COUNT == 1024'
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// create one block hole for half a directory
lfs2_file_open(&lfs2, &file, "bump", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
for (lfs2_size_t i = 0; i < cfg.block_size; i += 2) {
memcpy(&buffer[i], "hi", 2);
}
lfs2_file_write(&lfs2, &file, buffer, cfg.block_size) => cfg.block_size;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
for (lfs2_size_t i = 0;
i < (cfg.block_count-4)*(cfg.block_size-8);
i += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
// remount to force reset of lookahead
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// open hole
lfs2_remove(&lfs2, "bump") => 0;
lfs2_mkdir(&lfs2, "splitdir") => 0;
lfs2_file_open(&lfs2, &file, "splitdir/bump",
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
for (lfs2_size_t i = 0; i < cfg.block_size; i += 2) {
memcpy(&buffer[i], "hi", 2);
}
lfs2_file_write(&lfs2, &file, buffer, 2*cfg.block_size) => LFS2_ERR_NOSPC;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # outdated lookahead test
define.LFS2_BLOCK_SIZE = 512
define.LFS2_BLOCK_COUNT = 1024
if = 'LFS2_BLOCK_SIZE == 512 && LFS2_BLOCK_COUNT == 1024'
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// fill completely with two files
lfs2_file_open(&lfs2, &file, "exhaustion1",
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
for (lfs2_size_t i = 0;
i < ((cfg.block_count-2)/2)*(cfg.block_size-8);
i += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_file_open(&lfs2, &file, "exhaustion2",
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
for (lfs2_size_t i = 0;
i < ((cfg.block_count-2+1)/2)*(cfg.block_size-8);
i += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
// remount to force reset of lookahead
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// rewrite one file
lfs2_file_open(&lfs2, &file, "exhaustion1",
LFS2_O_WRONLY | LFS2_O_TRUNC) => 0;
lfs2_file_sync(&lfs2, &file) => 0;
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
for (lfs2_size_t i = 0;
i < ((cfg.block_count-2)/2)*(cfg.block_size-8);
i += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
// rewrite second file, this requires lookahead does not
// use old population
lfs2_file_open(&lfs2, &file, "exhaustion2",
LFS2_O_WRONLY | LFS2_O_TRUNC) => 0;
lfs2_file_sync(&lfs2, &file) => 0;
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
for (lfs2_size_t i = 0;
i < ((cfg.block_count-2+1)/2)*(cfg.block_size-8);
i += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # outdated lookahead and split dir test
define.LFS2_BLOCK_SIZE = 512
define.LFS2_BLOCK_COUNT = 1024
if = 'LFS2_BLOCK_SIZE == 512 && LFS2_BLOCK_COUNT == 1024'
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// fill completely with two files
lfs2_file_open(&lfs2, &file, "exhaustion1",
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
for (lfs2_size_t i = 0;
i < ((cfg.block_count-2)/2)*(cfg.block_size-8);
i += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_file_open(&lfs2, &file, "exhaustion2",
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
for (lfs2_size_t i = 0;
i < ((cfg.block_count-2+1)/2)*(cfg.block_size-8);
i += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
// remount to force reset of lookahead
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// rewrite one file with a hole of one block
lfs2_file_open(&lfs2, &file, "exhaustion1",
LFS2_O_WRONLY | LFS2_O_TRUNC) => 0;
lfs2_file_sync(&lfs2, &file) => 0;
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
for (lfs2_size_t i = 0;
i < ((cfg.block_count-2)/2 - 1)*(cfg.block_size-8);
i += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
// try to allocate a directory, should fail!
lfs2_mkdir(&lfs2, "split") => LFS2_ERR_NOSPC;
// file should not fail
lfs2_file_open(&lfs2, &file, "notasplit",
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
lfs2_file_write(&lfs2, &file, "hi", 2) => 2;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''

View File

@ -0,0 +1,304 @@
[[case]] # set/get attribute
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "hello") => 0;
lfs2_file_open(&lfs2, &file, "hello/hello", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
lfs2_file_write(&lfs2, &file, "hello", strlen("hello")) => strlen("hello");
lfs2_file_close(&lfs2, &file);
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
memset(buffer, 0, sizeof(buffer));
lfs2_setattr(&lfs2, "hello", 'A', "aaaa", 4) => 0;
lfs2_setattr(&lfs2, "hello", 'B', "bbbbbb", 6) => 0;
lfs2_setattr(&lfs2, "hello", 'C', "ccccc", 5) => 0;
lfs2_getattr(&lfs2, "hello", 'A', buffer, 4) => 4;
lfs2_getattr(&lfs2, "hello", 'B', buffer+4, 6) => 6;
lfs2_getattr(&lfs2, "hello", 'C', buffer+10, 5) => 5;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "bbbbbb", 6) => 0;
memcmp(buffer+10, "ccccc", 5) => 0;
lfs2_setattr(&lfs2, "hello", 'B', "", 0) => 0;
lfs2_getattr(&lfs2, "hello", 'A', buffer, 4) => 4;
lfs2_getattr(&lfs2, "hello", 'B', buffer+4, 6) => 0;
lfs2_getattr(&lfs2, "hello", 'C', buffer+10, 5) => 5;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
memcmp(buffer+10, "ccccc", 5) => 0;
lfs2_removeattr(&lfs2, "hello", 'B') => 0;
lfs2_getattr(&lfs2, "hello", 'A', buffer, 4) => 4;
lfs2_getattr(&lfs2, "hello", 'B', buffer+4, 6) => LFS2_ERR_NOATTR;
lfs2_getattr(&lfs2, "hello", 'C', buffer+10, 5) => 5;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
memcmp(buffer+10, "ccccc", 5) => 0;
lfs2_setattr(&lfs2, "hello", 'B', "dddddd", 6) => 0;
lfs2_getattr(&lfs2, "hello", 'A', buffer, 4) => 4;
lfs2_getattr(&lfs2, "hello", 'B', buffer+4, 6) => 6;
lfs2_getattr(&lfs2, "hello", 'C', buffer+10, 5) => 5;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "dddddd", 6) => 0;
memcmp(buffer+10, "ccccc", 5) => 0;
lfs2_setattr(&lfs2, "hello", 'B', "eee", 3) => 0;
lfs2_getattr(&lfs2, "hello", 'A', buffer, 4) => 4;
lfs2_getattr(&lfs2, "hello", 'B', buffer+4, 6) => 3;
lfs2_getattr(&lfs2, "hello", 'C', buffer+10, 5) => 5;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "eee\0\0\0", 6) => 0;
memcmp(buffer+10, "ccccc", 5) => 0;
lfs2_setattr(&lfs2, "hello", 'A', buffer, LFS2_ATTR_MAX+1) => LFS2_ERR_NOSPC;
lfs2_setattr(&lfs2, "hello", 'B', "fffffffff", 9) => 0;
lfs2_getattr(&lfs2, "hello", 'A', buffer, 4) => 4;
lfs2_getattr(&lfs2, "hello", 'B', buffer+4, 6) => 9;
lfs2_getattr(&lfs2, "hello", 'C', buffer+10, 5) => 5;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
memset(buffer, 0, sizeof(buffer));
lfs2_getattr(&lfs2, "hello", 'A', buffer, 4) => 4;
lfs2_getattr(&lfs2, "hello", 'B', buffer+4, 9) => 9;
lfs2_getattr(&lfs2, "hello", 'C', buffer+13, 5) => 5;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "fffffffff", 9) => 0;
memcmp(buffer+13, "ccccc", 5) => 0;
lfs2_file_open(&lfs2, &file, "hello/hello", LFS2_O_RDONLY) => 0;
lfs2_file_read(&lfs2, &file, buffer, sizeof(buffer)) => strlen("hello");
memcmp(buffer, "hello", strlen("hello")) => 0;
lfs2_file_close(&lfs2, &file);
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # set/get root attribute
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "hello") => 0;
lfs2_file_open(&lfs2, &file, "hello/hello", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
lfs2_file_write(&lfs2, &file, "hello", strlen("hello")) => strlen("hello");
lfs2_file_close(&lfs2, &file);
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
memset(buffer, 0, sizeof(buffer));
lfs2_setattr(&lfs2, "/", 'A', "aaaa", 4) => 0;
lfs2_setattr(&lfs2, "/", 'B', "bbbbbb", 6) => 0;
lfs2_setattr(&lfs2, "/", 'C', "ccccc", 5) => 0;
lfs2_getattr(&lfs2, "/", 'A', buffer, 4) => 4;
lfs2_getattr(&lfs2, "/", 'B', buffer+4, 6) => 6;
lfs2_getattr(&lfs2, "/", 'C', buffer+10, 5) => 5;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "bbbbbb", 6) => 0;
memcmp(buffer+10, "ccccc", 5) => 0;
lfs2_setattr(&lfs2, "/", 'B', "", 0) => 0;
lfs2_getattr(&lfs2, "/", 'A', buffer, 4) => 4;
lfs2_getattr(&lfs2, "/", 'B', buffer+4, 6) => 0;
lfs2_getattr(&lfs2, "/", 'C', buffer+10, 5) => 5;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
memcmp(buffer+10, "ccccc", 5) => 0;
lfs2_removeattr(&lfs2, "/", 'B') => 0;
lfs2_getattr(&lfs2, "/", 'A', buffer, 4) => 4;
lfs2_getattr(&lfs2, "/", 'B', buffer+4, 6) => LFS2_ERR_NOATTR;
lfs2_getattr(&lfs2, "/", 'C', buffer+10, 5) => 5;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
memcmp(buffer+10, "ccccc", 5) => 0;
lfs2_setattr(&lfs2, "/", 'B', "dddddd", 6) => 0;
lfs2_getattr(&lfs2, "/", 'A', buffer, 4) => 4;
lfs2_getattr(&lfs2, "/", 'B', buffer+4, 6) => 6;
lfs2_getattr(&lfs2, "/", 'C', buffer+10, 5) => 5;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "dddddd", 6) => 0;
memcmp(buffer+10, "ccccc", 5) => 0;
lfs2_setattr(&lfs2, "/", 'B', "eee", 3) => 0;
lfs2_getattr(&lfs2, "/", 'A', buffer, 4) => 4;
lfs2_getattr(&lfs2, "/", 'B', buffer+4, 6) => 3;
lfs2_getattr(&lfs2, "/", 'C', buffer+10, 5) => 5;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "eee\0\0\0", 6) => 0;
memcmp(buffer+10, "ccccc", 5) => 0;
lfs2_setattr(&lfs2, "/", 'A', buffer, LFS2_ATTR_MAX+1) => LFS2_ERR_NOSPC;
lfs2_setattr(&lfs2, "/", 'B', "fffffffff", 9) => 0;
lfs2_getattr(&lfs2, "/", 'A', buffer, 4) => 4;
lfs2_getattr(&lfs2, "/", 'B', buffer+4, 6) => 9;
lfs2_getattr(&lfs2, "/", 'C', buffer+10, 5) => 5;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
memset(buffer, 0, sizeof(buffer));
lfs2_getattr(&lfs2, "/", 'A', buffer, 4) => 4;
lfs2_getattr(&lfs2, "/", 'B', buffer+4, 9) => 9;
lfs2_getattr(&lfs2, "/", 'C', buffer+13, 5) => 5;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "fffffffff", 9) => 0;
memcmp(buffer+13, "ccccc", 5) => 0;
lfs2_file_open(&lfs2, &file, "hello/hello", LFS2_O_RDONLY) => 0;
lfs2_file_read(&lfs2, &file, buffer, sizeof(buffer)) => strlen("hello");
memcmp(buffer, "hello", strlen("hello")) => 0;
lfs2_file_close(&lfs2, &file);
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # set/get file attribute
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "hello") => 0;
lfs2_file_open(&lfs2, &file, "hello/hello", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
lfs2_file_write(&lfs2, &file, "hello", strlen("hello")) => strlen("hello");
lfs2_file_close(&lfs2, &file);
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
memset(buffer, 0, sizeof(buffer));
struct lfs2_attr attrs1[] = {
{'A', buffer, 4},
{'B', buffer+4, 6},
{'C', buffer+10, 5},
};
struct lfs2_file_config cfg1 = {.attrs=attrs1, .attr_count=3};
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_WRONLY, &cfg1) => 0;
memcpy(buffer, "aaaa", 4);
memcpy(buffer+4, "bbbbbb", 6);
memcpy(buffer+10, "ccccc", 5);
lfs2_file_close(&lfs2, &file) => 0;
memset(buffer, 0, 15);
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_RDONLY, &cfg1) => 0;
lfs2_file_close(&lfs2, &file) => 0;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "bbbbbb", 6) => 0;
memcmp(buffer+10, "ccccc", 5) => 0;
attrs1[1].size = 0;
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_WRONLY, &cfg1) => 0;
lfs2_file_close(&lfs2, &file) => 0;
memset(buffer, 0, 15);
attrs1[1].size = 6;
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_RDONLY, &cfg1) => 0;
lfs2_file_close(&lfs2, &file) => 0;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
memcmp(buffer+10, "ccccc", 5) => 0;
attrs1[1].size = 6;
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_WRONLY, &cfg1) => 0;
memcpy(buffer+4, "dddddd", 6);
lfs2_file_close(&lfs2, &file) => 0;
memset(buffer, 0, 15);
attrs1[1].size = 6;
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_RDONLY, &cfg1) => 0;
lfs2_file_close(&lfs2, &file) => 0;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "dddddd", 6) => 0;
memcmp(buffer+10, "ccccc", 5) => 0;
attrs1[1].size = 3;
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_WRONLY, &cfg1) => 0;
memcpy(buffer+4, "eee", 3);
lfs2_file_close(&lfs2, &file) => 0;
memset(buffer, 0, 15);
attrs1[1].size = 6;
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_RDONLY, &cfg1) => 0;
lfs2_file_close(&lfs2, &file) => 0;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "eee\0\0\0", 6) => 0;
memcmp(buffer+10, "ccccc", 5) => 0;
attrs1[0].size = LFS2_ATTR_MAX+1;
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_WRONLY, &cfg1)
=> LFS2_ERR_NOSPC;
struct lfs2_attr attrs2[] = {
{'A', buffer, 4},
{'B', buffer+4, 9},
{'C', buffer+13, 5},
};
struct lfs2_file_config cfg2 = {.attrs=attrs2, .attr_count=3};
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_RDWR, &cfg2) => 0;
memcpy(buffer+4, "fffffffff", 9);
lfs2_file_close(&lfs2, &file) => 0;
attrs1[0].size = 4;
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_RDONLY, &cfg1) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
memset(buffer, 0, sizeof(buffer));
struct lfs2_attr attrs3[] = {
{'A', buffer, 4},
{'B', buffer+4, 9},
{'C', buffer+13, 5},
};
struct lfs2_file_config cfg3 = {.attrs=attrs3, .attr_count=3};
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_RDONLY, &cfg3) => 0;
lfs2_file_close(&lfs2, &file) => 0;
memcmp(buffer, "aaaa", 4) => 0;
memcmp(buffer+4, "fffffffff", 9) => 0;
memcmp(buffer+13, "ccccc", 5) => 0;
lfs2_file_open(&lfs2, &file, "hello/hello", LFS2_O_RDONLY) => 0;
lfs2_file_read(&lfs2, &file, buffer, sizeof(buffer)) => strlen("hello");
memcmp(buffer, "hello", strlen("hello")) => 0;
lfs2_file_close(&lfs2, &file);
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # deferred file attributes
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "hello") => 0;
lfs2_file_open(&lfs2, &file, "hello/hello", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
lfs2_file_write(&lfs2, &file, "hello", strlen("hello")) => strlen("hello");
lfs2_file_close(&lfs2, &file);
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_setattr(&lfs2, "hello/hello", 'B', "fffffffff", 9) => 0;
lfs2_setattr(&lfs2, "hello/hello", 'C', "ccccc", 5) => 0;
memset(buffer, 0, sizeof(buffer));
struct lfs2_attr attrs1[] = {
{'B', "gggg", 4},
{'C', "", 0},
{'D', "hhhh", 4},
};
struct lfs2_file_config cfg1 = {.attrs=attrs1, .attr_count=3};
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_WRONLY, &cfg1) => 0;
lfs2_getattr(&lfs2, "hello/hello", 'B', buffer, 9) => 9;
lfs2_getattr(&lfs2, "hello/hello", 'C', buffer+9, 9) => 5;
lfs2_getattr(&lfs2, "hello/hello", 'D', buffer+18, 9) => LFS2_ERR_NOATTR;
memcmp(buffer, "fffffffff", 9) => 0;
memcmp(buffer+9, "ccccc\0\0\0\0", 9) => 0;
memcmp(buffer+18, "\0\0\0\0\0\0\0\0\0", 9) => 0;
lfs2_file_sync(&lfs2, &file) => 0;
lfs2_getattr(&lfs2, "hello/hello", 'B', buffer, 9) => 4;
lfs2_getattr(&lfs2, "hello/hello", 'C', buffer+9, 9) => 0;
lfs2_getattr(&lfs2, "hello/hello", 'D', buffer+18, 9) => 4;
memcmp(buffer, "gggg\0\0\0\0\0", 9) => 0;
memcmp(buffer+9, "\0\0\0\0\0\0\0\0\0", 9) => 0;
memcmp(buffer+18, "hhhh\0\0\0\0\0", 9) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''

View File

@ -0,0 +1,241 @@
# bad blocks with block cycles should be tested in test_relocations
if = 'LFS2_BLOCK_CYCLES == -1'
[[case]] # single bad blocks
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
define.LFS2_ERASE_CYCLES = 0xffffffff
define.LFS2_ERASE_VALUE = [0x00, 0xff, -1]
define.LFS2_BADBLOCK_BEHAVIOR = [
'LFS2_TESTBD_BADBLOCK_PROGERROR',
'LFS2_TESTBD_BADBLOCK_ERASEERROR',
'LFS2_TESTBD_BADBLOCK_READERROR',
'LFS2_TESTBD_BADBLOCK_PROGNOOP',
'LFS2_TESTBD_BADBLOCK_ERASENOOP',
]
define.NAMEMULT = 64
define.FILEMULT = 1
code = '''
for (lfs2_block_t badblock = 2; badblock < LFS2_BLOCK_COUNT; badblock++) {
lfs2_testbd_setwear(&cfg, badblock-1, 0) => 0;
lfs2_testbd_setwear(&cfg, badblock, 0xffffffff) => 0;
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 1; i < 10; i++) {
for (int j = 0; j < NAMEMULT; j++) {
buffer[j] = '0'+i;
}
buffer[NAMEMULT] = '\0';
lfs2_mkdir(&lfs2, (char*)buffer) => 0;
buffer[NAMEMULT] = '/';
for (int j = 0; j < NAMEMULT; j++) {
buffer[j+NAMEMULT+1] = '0'+i;
}
buffer[2*NAMEMULT+1] = '\0';
lfs2_file_open(&lfs2, &file, (char*)buffer,
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
size = NAMEMULT;
for (int j = 0; j < i*FILEMULT; j++) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 1; i < 10; i++) {
for (int j = 0; j < NAMEMULT; j++) {
buffer[j] = '0'+i;
}
buffer[NAMEMULT] = '\0';
lfs2_stat(&lfs2, (char*)buffer, &info) => 0;
info.type => LFS2_TYPE_DIR;
buffer[NAMEMULT] = '/';
for (int j = 0; j < NAMEMULT; j++) {
buffer[j+NAMEMULT+1] = '0'+i;
}
buffer[2*NAMEMULT+1] = '\0';
lfs2_file_open(&lfs2, &file, (char*)buffer, LFS2_O_RDONLY) => 0;
size = NAMEMULT;
for (int j = 0; j < i*FILEMULT; j++) {
uint8_t rbuffer[1024];
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(buffer, rbuffer, size) => 0;
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
}
'''
[[case]] # region corruption (causes cascading failures)
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
define.LFS2_ERASE_CYCLES = 0xffffffff
define.LFS2_ERASE_VALUE = [0x00, 0xff, -1]
define.LFS2_BADBLOCK_BEHAVIOR = [
'LFS2_TESTBD_BADBLOCK_PROGERROR',
'LFS2_TESTBD_BADBLOCK_ERASEERROR',
'LFS2_TESTBD_BADBLOCK_READERROR',
'LFS2_TESTBD_BADBLOCK_PROGNOOP',
'LFS2_TESTBD_BADBLOCK_ERASENOOP',
]
define.NAMEMULT = 64
define.FILEMULT = 1
code = '''
for (lfs2_block_t i = 0; i < (LFS2_BLOCK_COUNT-2)/2; i++) {
lfs2_testbd_setwear(&cfg, i+2, 0xffffffff) => 0;
}
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 1; i < 10; i++) {
for (int j = 0; j < NAMEMULT; j++) {
buffer[j] = '0'+i;
}
buffer[NAMEMULT] = '\0';
lfs2_mkdir(&lfs2, (char*)buffer) => 0;
buffer[NAMEMULT] = '/';
for (int j = 0; j < NAMEMULT; j++) {
buffer[j+NAMEMULT+1] = '0'+i;
}
buffer[2*NAMEMULT+1] = '\0';
lfs2_file_open(&lfs2, &file, (char*)buffer,
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
size = NAMEMULT;
for (int j = 0; j < i*FILEMULT; j++) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 1; i < 10; i++) {
for (int j = 0; j < NAMEMULT; j++) {
buffer[j] = '0'+i;
}
buffer[NAMEMULT] = '\0';
lfs2_stat(&lfs2, (char*)buffer, &info) => 0;
info.type => LFS2_TYPE_DIR;
buffer[NAMEMULT] = '/';
for (int j = 0; j < NAMEMULT; j++) {
buffer[j+NAMEMULT+1] = '0'+i;
}
buffer[2*NAMEMULT+1] = '\0';
lfs2_file_open(&lfs2, &file, (char*)buffer, LFS2_O_RDONLY) => 0;
size = NAMEMULT;
for (int j = 0; j < i*FILEMULT; j++) {
uint8_t rbuffer[1024];
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(buffer, rbuffer, size) => 0;
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # alternating corruption (causes cascading failures)
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
define.LFS2_ERASE_CYCLES = 0xffffffff
define.LFS2_ERASE_VALUE = [0x00, 0xff, -1]
define.LFS2_BADBLOCK_BEHAVIOR = [
'LFS2_TESTBD_BADBLOCK_PROGERROR',
'LFS2_TESTBD_BADBLOCK_ERASEERROR',
'LFS2_TESTBD_BADBLOCK_READERROR',
'LFS2_TESTBD_BADBLOCK_PROGNOOP',
'LFS2_TESTBD_BADBLOCK_ERASENOOP',
]
define.NAMEMULT = 64
define.FILEMULT = 1
code = '''
for (lfs2_block_t i = 0; i < (LFS2_BLOCK_COUNT-2)/2; i++) {
lfs2_testbd_setwear(&cfg, (2*i) + 2, 0xffffffff) => 0;
}
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 1; i < 10; i++) {
for (int j = 0; j < NAMEMULT; j++) {
buffer[j] = '0'+i;
}
buffer[NAMEMULT] = '\0';
lfs2_mkdir(&lfs2, (char*)buffer) => 0;
buffer[NAMEMULT] = '/';
for (int j = 0; j < NAMEMULT; j++) {
buffer[j+NAMEMULT+1] = '0'+i;
}
buffer[2*NAMEMULT+1] = '\0';
lfs2_file_open(&lfs2, &file, (char*)buffer,
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
size = NAMEMULT;
for (int j = 0; j < i*FILEMULT; j++) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 1; i < 10; i++) {
for (int j = 0; j < NAMEMULT; j++) {
buffer[j] = '0'+i;
}
buffer[NAMEMULT] = '\0';
lfs2_stat(&lfs2, (char*)buffer, &info) => 0;
info.type => LFS2_TYPE_DIR;
buffer[NAMEMULT] = '/';
for (int j = 0; j < NAMEMULT; j++) {
buffer[j+NAMEMULT+1] = '0'+i;
}
buffer[2*NAMEMULT+1] = '\0';
lfs2_file_open(&lfs2, &file, (char*)buffer, LFS2_O_RDONLY) => 0;
size = NAMEMULT;
for (int j = 0; j < i*FILEMULT; j++) {
uint8_t rbuffer[1024];
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(buffer, rbuffer, size) => 0;
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
'''
# other corner cases
[[case]] # bad superblocks (corrupt 1 or 0)
define.LFS2_ERASE_CYCLES = 0xffffffff
define.LFS2_ERASE_VALUE = [0x00, 0xff, -1]
define.LFS2_BADBLOCK_BEHAVIOR = [
'LFS2_TESTBD_BADBLOCK_PROGERROR',
'LFS2_TESTBD_BADBLOCK_ERASEERROR',
'LFS2_TESTBD_BADBLOCK_READERROR',
'LFS2_TESTBD_BADBLOCK_PROGNOOP',
'LFS2_TESTBD_BADBLOCK_ERASENOOP',
]
code = '''
lfs2_testbd_setwear(&cfg, 0, 0xffffffff) => 0;
lfs2_testbd_setwear(&cfg, 1, 0xffffffff) => 0;
lfs2_format(&lfs2, &cfg) => LFS2_ERR_NOSPC;
lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT;
'''

View File

@ -0,0 +1,838 @@
[[case]] # root
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # many directory creation
define.N = 'range(0, 100, 3)'
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 0; i < N; i++) {
sprintf(path, "dir%03d", i);
lfs2_mkdir(&lfs2, path) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "dir%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, path) == 0);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # many directory removal
define.N = 'range(3, 100, 11)'
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 0; i < N; i++) {
sprintf(path, "removeme%03d", i);
lfs2_mkdir(&lfs2, path) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "removeme%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, path) == 0);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2);
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 0; i < N; i++) {
sprintf(path, "removeme%03d", i);
lfs2_remove(&lfs2, path) => 0;
}
lfs2_unmount(&lfs2);
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # many directory rename
define.N = 'range(3, 100, 11)'
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 0; i < N; i++) {
sprintf(path, "test%03d", i);
lfs2_mkdir(&lfs2, path) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "test%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, path) == 0);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2);
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 0; i < N; i++) {
char oldpath[128];
char newpath[128];
sprintf(oldpath, "test%03d", i);
sprintf(newpath, "tedd%03d", i);
lfs2_rename(&lfs2, oldpath, newpath) => 0;
}
lfs2_unmount(&lfs2);
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "tedd%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, path) == 0);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2);
'''
[[case]] # reentrant many directory creation/rename/removal
define.N = [5, 11]
reentrant = true
code = '''
err = lfs2_mount(&lfs2, &cfg);
if (err) {
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
}
for (int i = 0; i < N; i++) {
sprintf(path, "hi%03d", i);
err = lfs2_mkdir(&lfs2, path);
assert(err == 0 || err == LFS2_ERR_EXIST);
}
for (int i = 0; i < N; i++) {
sprintf(path, "hello%03d", i);
err = lfs2_remove(&lfs2, path);
assert(err == 0 || err == LFS2_ERR_NOENT);
}
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "hi%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, path) == 0);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
for (int i = 0; i < N; i++) {
char oldpath[128];
char newpath[128];
sprintf(oldpath, "hi%03d", i);
sprintf(newpath, "hello%03d", i);
// YES this can overwrite an existing newpath
lfs2_rename(&lfs2, oldpath, newpath) => 0;
}
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "hello%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, path) == 0);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
for (int i = 0; i < N; i++) {
sprintf(path, "hello%03d", i);
lfs2_remove(&lfs2, path) => 0;
}
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # file creation
define.N = 'range(3, 100, 11)'
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 0; i < N; i++) {
sprintf(path, "file%03d", i);
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "file%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_REG);
assert(strcmp(info.name, path) == 0);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2);
'''
[[case]] # file removal
define.N = 'range(0, 100, 3)'
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 0; i < N; i++) {
sprintf(path, "removeme%03d", i);
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "removeme%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_REG);
assert(strcmp(info.name, path) == 0);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2);
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 0; i < N; i++) {
sprintf(path, "removeme%03d", i);
lfs2_remove(&lfs2, path) => 0;
}
lfs2_unmount(&lfs2);
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # file rename
define.N = 'range(0, 100, 3)'
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 0; i < N; i++) {
sprintf(path, "test%03d", i);
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "test%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_REG);
assert(strcmp(info.name, path) == 0);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2);
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 0; i < N; i++) {
char oldpath[128];
char newpath[128];
sprintf(oldpath, "test%03d", i);
sprintf(newpath, "tedd%03d", i);
lfs2_rename(&lfs2, oldpath, newpath) => 0;
}
lfs2_unmount(&lfs2);
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "tedd%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_REG);
assert(strcmp(info.name, path) == 0);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2);
'''
[[case]] # reentrant file creation/rename/removal
define.N = [5, 25]
reentrant = true
code = '''
err = lfs2_mount(&lfs2, &cfg);
if (err) {
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
}
for (int i = 0; i < N; i++) {
sprintf(path, "hi%03d", i);
lfs2_file_open(&lfs2, &file, path, LFS2_O_CREAT | LFS2_O_WRONLY) => 0;
lfs2_file_close(&lfs2, &file) => 0;
}
for (int i = 0; i < N; i++) {
sprintf(path, "hello%03d", i);
err = lfs2_remove(&lfs2, path);
assert(err == 0 || err == LFS2_ERR_NOENT);
}
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "hi%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_REG);
assert(strcmp(info.name, path) == 0);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
for (int i = 0; i < N; i++) {
char oldpath[128];
char newpath[128];
sprintf(oldpath, "hi%03d", i);
sprintf(newpath, "hello%03d", i);
// YES this can overwrite an existing newpath
lfs2_rename(&lfs2, oldpath, newpath) => 0;
}
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "hello%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_REG);
assert(strcmp(info.name, path) == 0);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
for (int i = 0; i < N; i++) {
sprintf(path, "hello%03d", i);
lfs2_remove(&lfs2, path) => 0;
}
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # nested directories
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "potato") => 0;
lfs2_file_open(&lfs2, &file, "burito",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "potato/baked") => 0;
lfs2_mkdir(&lfs2, "potato/sweet") => 0;
lfs2_mkdir(&lfs2, "potato/fried") => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "potato") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
info.type => LFS2_TYPE_DIR;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
info.type => LFS2_TYPE_DIR;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "baked") == 0);
info.type => LFS2_TYPE_DIR;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "fried") == 0);
info.type => LFS2_TYPE_DIR;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "sweet") == 0);
info.type => LFS2_TYPE_DIR;
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2) => 0;
// try removing?
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_remove(&lfs2, "potato") => LFS2_ERR_NOTEMPTY;
lfs2_unmount(&lfs2) => 0;
// try renaming?
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_rename(&lfs2, "potato", "coldpotato") => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_rename(&lfs2, "coldpotato", "warmpotato") => 0;
lfs2_rename(&lfs2, "warmpotato", "hotpotato") => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_remove(&lfs2, "potato") => LFS2_ERR_NOENT;
lfs2_remove(&lfs2, "coldpotato") => LFS2_ERR_NOENT;
lfs2_remove(&lfs2, "warmpotato") => LFS2_ERR_NOENT;
lfs2_remove(&lfs2, "hotpotato") => LFS2_ERR_NOTEMPTY;
lfs2_unmount(&lfs2) => 0;
// try cross-directory renaming
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "coldpotato") => 0;
lfs2_rename(&lfs2, "hotpotato/baked", "coldpotato/baked") => 0;
lfs2_rename(&lfs2, "coldpotato", "hotpotato") => LFS2_ERR_NOTEMPTY;
lfs2_remove(&lfs2, "coldpotato") => LFS2_ERR_NOTEMPTY;
lfs2_remove(&lfs2, "hotpotato") => LFS2_ERR_NOTEMPTY;
lfs2_rename(&lfs2, "hotpotato/fried", "coldpotato/fried") => 0;
lfs2_rename(&lfs2, "coldpotato", "hotpotato") => LFS2_ERR_NOTEMPTY;
lfs2_remove(&lfs2, "coldpotato") => LFS2_ERR_NOTEMPTY;
lfs2_remove(&lfs2, "hotpotato") => LFS2_ERR_NOTEMPTY;
lfs2_rename(&lfs2, "hotpotato/sweet", "coldpotato/sweet") => 0;
lfs2_rename(&lfs2, "coldpotato", "hotpotato") => 0;
lfs2_remove(&lfs2, "coldpotato") => LFS2_ERR_NOENT;
lfs2_remove(&lfs2, "hotpotato") => LFS2_ERR_NOTEMPTY;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "hotpotato") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
info.type => LFS2_TYPE_DIR;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
info.type => LFS2_TYPE_DIR;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "baked") == 0);
info.type => LFS2_TYPE_DIR;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "fried") == 0);
info.type => LFS2_TYPE_DIR;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "sweet") == 0);
info.type => LFS2_TYPE_DIR;
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2) => 0;
// final remove
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_remove(&lfs2, "hotpotato") => LFS2_ERR_NOTEMPTY;
lfs2_remove(&lfs2, "hotpotato/baked") => 0;
lfs2_remove(&lfs2, "hotpotato") => LFS2_ERR_NOTEMPTY;
lfs2_remove(&lfs2, "hotpotato/fried") => 0;
lfs2_remove(&lfs2, "hotpotato") => LFS2_ERR_NOTEMPTY;
lfs2_remove(&lfs2, "hotpotato/sweet") => 0;
lfs2_remove(&lfs2, "hotpotato") => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
info.type => LFS2_TYPE_DIR;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
info.type => LFS2_TYPE_DIR;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "burito") == 0);
info.type => LFS2_TYPE_REG;
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # recursive remove
define.N = [10, 100]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "prickly-pear") => 0;
for (int i = 0; i < N; i++) {
sprintf(path, "prickly-pear/cactus%03d", i);
lfs2_mkdir(&lfs2, path) => 0;
}
lfs2_dir_open(&lfs2, &dir, "prickly-pear") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "cactus%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, path) == 0);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2);
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_remove(&lfs2, "prickly-pear") => LFS2_ERR_NOTEMPTY;
lfs2_dir_open(&lfs2, &dir, "prickly-pear") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
for (int i = 0; i < N; i++) {
sprintf(path, "cactus%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, path) == 0);
sprintf(path, "prickly-pear/%s", info.name);
lfs2_remove(&lfs2, path) => 0;
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_remove(&lfs2, "prickly-pear") => 0;
lfs2_remove(&lfs2, "prickly-pear") => LFS2_ERR_NOENT;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_remove(&lfs2, "prickly-pear") => LFS2_ERR_NOENT;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # other error cases
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "potato") => 0;
lfs2_file_open(&lfs2, &file, "burito",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "potato") => LFS2_ERR_EXIST;
lfs2_mkdir(&lfs2, "burito") => LFS2_ERR_EXIST;
lfs2_file_open(&lfs2, &file, "burito",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => LFS2_ERR_EXIST;
lfs2_file_open(&lfs2, &file, "potato",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => LFS2_ERR_EXIST;
lfs2_dir_open(&lfs2, &dir, "tomato") => LFS2_ERR_NOENT;
lfs2_dir_open(&lfs2, &dir, "burito") => LFS2_ERR_NOTDIR;
lfs2_file_open(&lfs2, &file, "tomato", LFS2_O_RDONLY) => LFS2_ERR_NOENT;
lfs2_file_open(&lfs2, &file, "potato", LFS2_O_RDONLY) => LFS2_ERR_ISDIR;
lfs2_file_open(&lfs2, &file, "tomato", LFS2_O_WRONLY) => LFS2_ERR_NOENT;
lfs2_file_open(&lfs2, &file, "potato", LFS2_O_WRONLY) => LFS2_ERR_ISDIR;
lfs2_file_open(&lfs2, &file, "potato",
LFS2_O_WRONLY | LFS2_O_CREAT) => LFS2_ERR_ISDIR;
lfs2_mkdir(&lfs2, "/") => LFS2_ERR_EXIST;
lfs2_file_open(&lfs2, &file, "/",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => LFS2_ERR_EXIST;
lfs2_file_open(&lfs2, &file, "/", LFS2_O_RDONLY) => LFS2_ERR_ISDIR;
lfs2_file_open(&lfs2, &file, "/", LFS2_O_WRONLY) => LFS2_ERR_ISDIR;
lfs2_file_open(&lfs2, &file, "/",
LFS2_O_WRONLY | LFS2_O_CREAT) => LFS2_ERR_ISDIR;
// check that errors did not corrupt directory
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_REG);
assert(strcmp(info.name, "burito") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "potato") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2) => 0;
// or on disk
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_REG);
assert(strcmp(info.name, "burito") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(info.type == LFS2_TYPE_DIR);
assert(strcmp(info.name, "potato") == 0);
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # directory seek
define.COUNT = [4, 128, 132]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "hello") => 0;
for (int i = 0; i < COUNT; i++) {
sprintf(path, "hello/kitty%03d", i);
lfs2_mkdir(&lfs2, path) => 0;
}
lfs2_unmount(&lfs2) => 0;
for (int j = 2; j < COUNT; j++) {
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "hello") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_soff_t pos;
for (int i = 0; i < j; i++) {
sprintf(path, "kitty%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
assert(info.type == LFS2_TYPE_DIR);
pos = lfs2_dir_tell(&lfs2, &dir);
assert(pos >= 0);
}
lfs2_dir_seek(&lfs2, &dir, pos) => 0;
sprintf(path, "kitty%03d", j);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_rewind(&lfs2, &dir) => 0;
sprintf(path, "kitty%03d", 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_seek(&lfs2, &dir, pos) => 0;
sprintf(path, "kitty%03d", j);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2) => 0;
}
'''
[[case]] # root seek
define.COUNT = [4, 128, 132]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 0; i < COUNT; i++) {
sprintf(path, "hi%03d", i);
lfs2_mkdir(&lfs2, path) => 0;
}
lfs2_unmount(&lfs2) => 0;
for (int j = 2; j < COUNT; j++) {
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_soff_t pos;
for (int i = 0; i < j; i++) {
sprintf(path, "hi%03d", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
assert(info.type == LFS2_TYPE_DIR);
pos = lfs2_dir_tell(&lfs2, &dir);
assert(pos >= 0);
}
lfs2_dir_seek(&lfs2, &dir, pos) => 0;
sprintf(path, "hi%03d", j);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_rewind(&lfs2, &dir) => 0;
sprintf(path, "hi%03d", 0);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_seek(&lfs2, &dir, pos) => 0;
sprintf(path, "hi%03d", j);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2) => 0;
}
'''

View File

@ -0,0 +1,611 @@
# These tests are for some specific corner cases with neighboring inline files.
# Note that these tests are intended for 512 byte inline sizes. They should
# still pass with other inline sizes but wouldn't be testing anything.
define.LFS2_CACHE_SIZE = 512
if = 'LFS2_CACHE_SIZE % LFS2_PROG_SIZE == 0 && LFS2_CACHE_SIZE == 512'
[[case]] # entry grow test
code = '''
uint8_t wbuffer[1024];
uint8_t rbuffer[1024];
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// write hi0 20
sprintf(path, "hi0"); size = 20;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi1 20
sprintf(path, "hi1"); size = 20;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi2 20
sprintf(path, "hi2"); size = 20;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi3 20
sprintf(path, "hi3"); size = 20;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// read hi1 20
sprintf(path, "hi1"); size = 20;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// write hi1 200
sprintf(path, "hi1"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// read hi0 20
sprintf(path, "hi0"); size = 20;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi1 200
sprintf(path, "hi1"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi2 20
sprintf(path, "hi2"); size = 20;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi3 20
sprintf(path, "hi3"); size = 20;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # entry shrink test
code = '''
uint8_t wbuffer[1024];
uint8_t rbuffer[1024];
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// write hi0 20
sprintf(path, "hi0"); size = 20;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi1 200
sprintf(path, "hi1"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi2 20
sprintf(path, "hi2"); size = 20;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi3 20
sprintf(path, "hi3"); size = 20;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// read hi1 200
sprintf(path, "hi1"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// write hi1 20
sprintf(path, "hi1"); size = 20;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// read hi0 20
sprintf(path, "hi0"); size = 20;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi1 20
sprintf(path, "hi1"); size = 20;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi2 20
sprintf(path, "hi2"); size = 20;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi3 20
sprintf(path, "hi3"); size = 20;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # entry spill test
code = '''
uint8_t wbuffer[1024];
uint8_t rbuffer[1024];
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// write hi0 200
sprintf(path, "hi0"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi1 200
sprintf(path, "hi1"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi2 200
sprintf(path, "hi2"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi3 200
sprintf(path, "hi3"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// read hi0 200
sprintf(path, "hi0"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi1 200
sprintf(path, "hi1"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi2 200
sprintf(path, "hi2"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi3 200
sprintf(path, "hi3"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # entry push spill test
code = '''
uint8_t wbuffer[1024];
uint8_t rbuffer[1024];
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// write hi0 200
sprintf(path, "hi0"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi1 20
sprintf(path, "hi1"); size = 20;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi2 200
sprintf(path, "hi2"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi3 200
sprintf(path, "hi3"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// read hi1 20
sprintf(path, "hi1"); size = 20;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// write hi1 200
sprintf(path, "hi1"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// read hi0 200
sprintf(path, "hi0"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi1 200
sprintf(path, "hi1"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi2 200
sprintf(path, "hi2"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi3 200
sprintf(path, "hi3"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # entry push spill two test
code = '''
uint8_t wbuffer[1024];
uint8_t rbuffer[1024];
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// write hi0 200
sprintf(path, "hi0"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi1 20
sprintf(path, "hi1"); size = 20;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi2 200
sprintf(path, "hi2"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi3 200
sprintf(path, "hi3"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi4 200
sprintf(path, "hi4"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// read hi1 20
sprintf(path, "hi1"); size = 20;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// write hi1 200
sprintf(path, "hi1"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// read hi0 200
sprintf(path, "hi0"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi1 200
sprintf(path, "hi1"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi2 200
sprintf(path, "hi2"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi3 200
sprintf(path, "hi3"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi4 200
sprintf(path, "hi4"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # entry drop test
code = '''
uint8_t wbuffer[1024];
uint8_t rbuffer[1024];
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
// write hi0 200
sprintf(path, "hi0"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi1 200
sprintf(path, "hi1"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi2 200
sprintf(path, "hi2"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
// write hi3 200
sprintf(path, "hi3"); size = 200;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_remove(&lfs2, "hi1") => 0;
lfs2_stat(&lfs2, "hi1", &info) => LFS2_ERR_NOENT;
// read hi0 200
sprintf(path, "hi0"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi2 200
sprintf(path, "hi2"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi3 200
sprintf(path, "hi3"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_remove(&lfs2, "hi2") => 0;
lfs2_stat(&lfs2, "hi2", &info) => LFS2_ERR_NOENT;
// read hi0 200
sprintf(path, "hi0"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
// read hi3 200
sprintf(path, "hi3"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_remove(&lfs2, "hi3") => 0;
lfs2_stat(&lfs2, "hi3", &info) => LFS2_ERR_NOENT;
// read hi0 200
sprintf(path, "hi0"); size = 200;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_remove(&lfs2, "hi0") => 0;
lfs2_stat(&lfs2, "hi0", &info) => LFS2_ERR_NOENT;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # create too big
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
memset(path, 'm', 200);
path[200] = '\0';
size = 400;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
uint8_t wbuffer[1024];
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_close(&lfs2, &file) => 0;
size = 400;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
uint8_t rbuffer[1024];
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # resize too big
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
memset(path, 'm', 200);
path[200] = '\0';
size = 40;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
uint8_t wbuffer[1024];
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_close(&lfs2, &file) => 0;
size = 40;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
uint8_t rbuffer[1024];
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
size = 400;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
memset(wbuffer, 'c', size);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_close(&lfs2, &file) => 0;
size = 400;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''

View File

@ -0,0 +1,288 @@
# Tests for recovering from conditions which shouldn't normally
# happen during normal operation of littlefs
# invalid pointer tests (outside of block_count)
[[case]] # invalid tail-pointer test
define.TAIL_TYPE = ['LFS2_TYPE_HARDTAIL', 'LFS2_TYPE_SOFTTAIL']
define.INVALSET = [0x3, 0x1, 0x2]
in = "lfs2.c"
code = '''
// create littlefs
lfs2_format(&lfs2, &cfg) => 0;
// change tail-pointer to invalid pointers
lfs2_init(&lfs2, &cfg) => 0;
lfs2_mdir_t mdir;
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS(
{LFS2_MKTAG(LFS2_TYPE_HARDTAIL, 0x3ff, 8),
(lfs2_block_t[2]){
(INVALSET & 0x1) ? 0xcccccccc : 0,
(INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0;
lfs2_deinit(&lfs2) => 0;
// test that mount fails gracefully
lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT;
'''
[[case]] # invalid dir pointer test
define.INVALSET = [0x3, 0x1, 0x2]
in = "lfs2.c"
code = '''
// create littlefs
lfs2_format(&lfs2, &cfg) => 0;
// make a dir
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "dir_here") => 0;
lfs2_unmount(&lfs2) => 0;
// change the dir pointer to be invalid
lfs2_init(&lfs2, &cfg) => 0;
lfs2_mdir_t mdir;
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
// make sure id 1 == our directory
lfs2_dir_get(&lfs2, &mdir,
LFS2_MKTAG(0x700, 0x3ff, 0),
LFS2_MKTAG(LFS2_TYPE_NAME, 1, strlen("dir_here")), buffer)
=> LFS2_MKTAG(LFS2_TYPE_DIR, 1, strlen("dir_here"));
assert(memcmp((char*)buffer, "dir_here", strlen("dir_here")) == 0);
// change dir pointer
lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS(
{LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, 8),
(lfs2_block_t[2]){
(INVALSET & 0x1) ? 0xcccccccc : 0,
(INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0;
lfs2_deinit(&lfs2) => 0;
// test that accessing our bad dir fails, note there's a number
// of ways to access the dir, some can fail, but some don't
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_stat(&lfs2, "dir_here", &info) => 0;
assert(strcmp(info.name, "dir_here") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_open(&lfs2, &dir, "dir_here") => LFS2_ERR_CORRUPT;
lfs2_stat(&lfs2, "dir_here/file_here", &info) => LFS2_ERR_CORRUPT;
lfs2_dir_open(&lfs2, &dir, "dir_here/dir_here") => LFS2_ERR_CORRUPT;
lfs2_file_open(&lfs2, &file, "dir_here/file_here",
LFS2_O_RDONLY) => LFS2_ERR_CORRUPT;
lfs2_file_open(&lfs2, &file, "dir_here/file_here",
LFS2_O_WRONLY | LFS2_O_CREAT) => LFS2_ERR_CORRUPT;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # invalid file pointer test
in = "lfs2.c"
define.SIZE = [10, 1000, 100000] # faked file size
code = '''
// create littlefs
lfs2_format(&lfs2, &cfg) => 0;
// make a file
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "file_here",
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
// change the file pointer to be invalid
lfs2_init(&lfs2, &cfg) => 0;
lfs2_mdir_t mdir;
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
// make sure id 1 == our file
lfs2_dir_get(&lfs2, &mdir,
LFS2_MKTAG(0x700, 0x3ff, 0),
LFS2_MKTAG(LFS2_TYPE_NAME, 1, strlen("file_here")), buffer)
=> LFS2_MKTAG(LFS2_TYPE_REG, 1, strlen("file_here"));
assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0);
// change file pointer
lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS(
{LFS2_MKTAG(LFS2_TYPE_CTZSTRUCT, 1, sizeof(struct lfs2_ctz)),
&(struct lfs2_ctz){0xcccccccc, lfs2_tole32(SIZE)}})) => 0;
lfs2_deinit(&lfs2) => 0;
// test that accessing our bad file fails, note there's a number
// of ways to access the dir, some can fail, but some don't
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_stat(&lfs2, "file_here", &info) => 0;
assert(strcmp(info.name, "file_here") == 0);
assert(info.type == LFS2_TYPE_REG);
assert(info.size == SIZE);
lfs2_file_open(&lfs2, &file, "file_here", LFS2_O_RDONLY) => 0;
lfs2_file_read(&lfs2, &file, buffer, SIZE) => LFS2_ERR_CORRUPT;
lfs2_file_close(&lfs2, &file) => 0;
// any allocs that traverse CTZ must unfortunately must fail
if (SIZE > 2*LFS2_BLOCK_SIZE) {
lfs2_mkdir(&lfs2, "dir_here") => LFS2_ERR_CORRUPT;
}
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # invalid pointer in CTZ skip-list test
define.SIZE = ['2*LFS2_BLOCK_SIZE', '3*LFS2_BLOCK_SIZE', '4*LFS2_BLOCK_SIZE']
in = "lfs2.c"
code = '''
// create littlefs
lfs2_format(&lfs2, &cfg) => 0;
// make a file
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "file_here",
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
for (int i = 0; i < SIZE; i++) {
char c = 'c';
lfs2_file_write(&lfs2, &file, &c, 1) => 1;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
// change pointer in CTZ skip-list to be invalid
lfs2_init(&lfs2, &cfg) => 0;
lfs2_mdir_t mdir;
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
// make sure id 1 == our file and get our CTZ structure
lfs2_dir_get(&lfs2, &mdir,
LFS2_MKTAG(0x700, 0x3ff, 0),
LFS2_MKTAG(LFS2_TYPE_NAME, 1, strlen("file_here")), buffer)
=> LFS2_MKTAG(LFS2_TYPE_REG, 1, strlen("file_here"));
assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0);
struct lfs2_ctz ctz;
lfs2_dir_get(&lfs2, &mdir,
LFS2_MKTAG(0x700, 0x3ff, 0),
LFS2_MKTAG(LFS2_TYPE_STRUCT, 1, sizeof(struct lfs2_ctz)), &ctz)
=> LFS2_MKTAG(LFS2_TYPE_CTZSTRUCT, 1, sizeof(struct lfs2_ctz));
lfs2_ctz_fromle32(&ctz);
// rewrite block to contain bad pointer
uint8_t bbuffer[LFS2_BLOCK_SIZE];
cfg.read(&cfg, ctz.head, 0, bbuffer, LFS2_BLOCK_SIZE) => 0;
uint32_t bad = lfs2_tole32(0xcccccccc);
memcpy(&bbuffer[0], &bad, sizeof(bad));
memcpy(&bbuffer[4], &bad, sizeof(bad));
cfg.erase(&cfg, ctz.head) => 0;
cfg.prog(&cfg, ctz.head, 0, bbuffer, LFS2_BLOCK_SIZE) => 0;
lfs2_deinit(&lfs2) => 0;
// test that accessing our bad file fails, note there's a number
// of ways to access the dir, some can fail, but some don't
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_stat(&lfs2, "file_here", &info) => 0;
assert(strcmp(info.name, "file_here") == 0);
assert(info.type == LFS2_TYPE_REG);
assert(info.size == SIZE);
lfs2_file_open(&lfs2, &file, "file_here", LFS2_O_RDONLY) => 0;
lfs2_file_read(&lfs2, &file, buffer, SIZE) => LFS2_ERR_CORRUPT;
lfs2_file_close(&lfs2, &file) => 0;
// any allocs that traverse CTZ must unfortunately must fail
if (SIZE > 2*LFS2_BLOCK_SIZE) {
lfs2_mkdir(&lfs2, "dir_here") => LFS2_ERR_CORRUPT;
}
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # invalid gstate pointer
define.INVALSET = [0x3, 0x1, 0x2]
in = "lfs2.c"
code = '''
// create littlefs
lfs2_format(&lfs2, &cfg) => 0;
// create an invalid gstate
lfs2_init(&lfs2, &cfg) => 0;
lfs2_mdir_t mdir;
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
lfs2_fs_prepmove(&lfs2, 1, (lfs2_block_t [2]){
(INVALSET & 0x1) ? 0xcccccccc : 0,
(INVALSET & 0x2) ? 0xcccccccc : 0});
lfs2_dir_commit(&lfs2, &mdir, NULL, 0) => 0;
lfs2_deinit(&lfs2) => 0;
// test that mount fails gracefully
// mount may not fail, but our first alloc should fail when
// we try to fix the gstate
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "should_fail") => LFS2_ERR_CORRUPT;
lfs2_unmount(&lfs2) => 0;
'''
# cycle detection/recovery tests
[[case]] # metadata-pair threaded-list loop test
in = "lfs2.c"
code = '''
// create littlefs
lfs2_format(&lfs2, &cfg) => 0;
// change tail-pointer to point to ourself
lfs2_init(&lfs2, &cfg) => 0;
lfs2_mdir_t mdir;
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS(
{LFS2_MKTAG(LFS2_TYPE_HARDTAIL, 0x3ff, 8),
(lfs2_block_t[2]){0, 1}})) => 0;
lfs2_deinit(&lfs2) => 0;
// test that mount fails gracefully
lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT;
'''
[[case]] # metadata-pair threaded-list 2-length loop test
in = "lfs2.c"
code = '''
// create littlefs with child dir
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "child") => 0;
lfs2_unmount(&lfs2) => 0;
// find child
lfs2_init(&lfs2, &cfg) => 0;
lfs2_mdir_t mdir;
lfs2_block_t pair[2];
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
lfs2_dir_get(&lfs2, &mdir,
LFS2_MKTAG(0x7ff, 0x3ff, 0),
LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair)
=> LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, sizeof(pair));
lfs2_pair_fromle32(pair);
// change tail-pointer to point to root
lfs2_dir_fetch(&lfs2, &mdir, pair) => 0;
lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS(
{LFS2_MKTAG(LFS2_TYPE_HARDTAIL, 0x3ff, 8),
(lfs2_block_t[2]){0, 1}})) => 0;
lfs2_deinit(&lfs2) => 0;
// test that mount fails gracefully
lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT;
'''
[[case]] # metadata-pair threaded-list 1-length child loop test
in = "lfs2.c"
code = '''
// create littlefs with child dir
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "child") => 0;
lfs2_unmount(&lfs2) => 0;
// find child
lfs2_init(&lfs2, &cfg) => 0;
lfs2_mdir_t mdir;
lfs2_block_t pair[2];
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
lfs2_dir_get(&lfs2, &mdir,
LFS2_MKTAG(0x7ff, 0x3ff, 0),
LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair)
=> LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, sizeof(pair));
lfs2_pair_fromle32(pair);
// change tail-pointer to point to ourself
lfs2_dir_fetch(&lfs2, &mdir, pair) => 0;
lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS(
{LFS2_MKTAG(LFS2_TYPE_HARDTAIL, 0x3ff, 8), pair})) => 0;
lfs2_deinit(&lfs2) => 0;
// test that mount fails gracefully
lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT;
'''

View File

@ -0,0 +1,465 @@
[[case]] # test running a filesystem to exhaustion
define.LFS2_ERASE_CYCLES = 10
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
define.LFS2_BLOCK_CYCLES = 'LFS2_ERASE_CYCLES / 2'
define.LFS2_BADBLOCK_BEHAVIOR = [
'LFS2_TESTBD_BADBLOCK_PROGERROR',
'LFS2_TESTBD_BADBLOCK_ERASEERROR',
'LFS2_TESTBD_BADBLOCK_READERROR',
'LFS2_TESTBD_BADBLOCK_PROGNOOP',
'LFS2_TESTBD_BADBLOCK_ERASENOOP',
]
define.FILES = 10
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "roadrunner") => 0;
lfs2_unmount(&lfs2) => 0;
uint32_t cycle = 0;
while (true) {
lfs2_mount(&lfs2, &cfg) => 0;
for (uint32_t i = 0; i < FILES; i++) {
// chose name, roughly random seed, and random 2^n size
sprintf(path, "roadrunner/test%d", i);
srand(cycle * i);
size = 1 << ((rand() % 10)+2);
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
for (lfs2_size_t j = 0; j < size; j++) {
char c = 'a' + (rand() % 26);
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1);
assert(res == 1 || res == LFS2_ERR_NOSPC);
if (res == LFS2_ERR_NOSPC) {
err = lfs2_file_close(&lfs2, &file);
assert(err == 0 || err == LFS2_ERR_NOSPC);
lfs2_unmount(&lfs2) => 0;
goto exhausted;
}
}
err = lfs2_file_close(&lfs2, &file);
assert(err == 0 || err == LFS2_ERR_NOSPC);
if (err == LFS2_ERR_NOSPC) {
lfs2_unmount(&lfs2) => 0;
goto exhausted;
}
}
for (uint32_t i = 0; i < FILES; i++) {
// check for errors
sprintf(path, "roadrunner/test%d", i);
srand(cycle * i);
size = 1 << ((rand() % 10)+2);
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
for (lfs2_size_t j = 0; j < size; j++) {
char c = 'a' + (rand() % 26);
char r;
lfs2_file_read(&lfs2, &file, &r, 1) => 1;
assert(r == c);
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
cycle += 1;
}
exhausted:
// should still be readable
lfs2_mount(&lfs2, &cfg) => 0;
for (uint32_t i = 0; i < FILES; i++) {
// check for errors
sprintf(path, "roadrunner/test%d", i);
lfs2_stat(&lfs2, path, &info) => 0;
}
lfs2_unmount(&lfs2) => 0;
LFS2_WARN("completed %d cycles", cycle);
'''
[[case]] # test running a filesystem to exhaustion
# which also requires expanding superblocks
define.LFS2_ERASE_CYCLES = 10
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
define.LFS2_BLOCK_CYCLES = 'LFS2_ERASE_CYCLES / 2'
define.LFS2_BADBLOCK_BEHAVIOR = [
'LFS2_TESTBD_BADBLOCK_PROGERROR',
'LFS2_TESTBD_BADBLOCK_ERASEERROR',
'LFS2_TESTBD_BADBLOCK_READERROR',
'LFS2_TESTBD_BADBLOCK_PROGNOOP',
'LFS2_TESTBD_BADBLOCK_ERASENOOP',
]
define.FILES = 10
code = '''
lfs2_format(&lfs2, &cfg) => 0;
uint32_t cycle = 0;
while (true) {
lfs2_mount(&lfs2, &cfg) => 0;
for (uint32_t i = 0; i < FILES; i++) {
// chose name, roughly random seed, and random 2^n size
sprintf(path, "test%d", i);
srand(cycle * i);
size = 1 << ((rand() % 10)+2);
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
for (lfs2_size_t j = 0; j < size; j++) {
char c = 'a' + (rand() % 26);
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1);
assert(res == 1 || res == LFS2_ERR_NOSPC);
if (res == LFS2_ERR_NOSPC) {
err = lfs2_file_close(&lfs2, &file);
assert(err == 0 || err == LFS2_ERR_NOSPC);
lfs2_unmount(&lfs2) => 0;
goto exhausted;
}
}
err = lfs2_file_close(&lfs2, &file);
assert(err == 0 || err == LFS2_ERR_NOSPC);
if (err == LFS2_ERR_NOSPC) {
lfs2_unmount(&lfs2) => 0;
goto exhausted;
}
}
for (uint32_t i = 0; i < FILES; i++) {
// check for errors
sprintf(path, "test%d", i);
srand(cycle * i);
size = 1 << ((rand() % 10)+2);
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
for (lfs2_size_t j = 0; j < size; j++) {
char c = 'a' + (rand() % 26);
char r;
lfs2_file_read(&lfs2, &file, &r, 1) => 1;
assert(r == c);
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
cycle += 1;
}
exhausted:
// should still be readable
lfs2_mount(&lfs2, &cfg) => 0;
for (uint32_t i = 0; i < FILES; i++) {
// check for errors
sprintf(path, "test%d", i);
lfs2_stat(&lfs2, path, &info) => 0;
}
lfs2_unmount(&lfs2) => 0;
LFS2_WARN("completed %d cycles", cycle);
'''
# These are a sort of high-level litmus test for wear-leveling. One definition
# of wear-leveling is that increasing a block device's space translates directly
# into increasing the block devices lifetime. This is something we can actually
# check for.
[[case]] # wear-level test running a filesystem to exhaustion
define.LFS2_ERASE_CYCLES = 20
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
define.LFS2_BLOCK_CYCLES = 'LFS2_ERASE_CYCLES / 2'
define.FILES = 10
code = '''
uint32_t run_cycles[2];
const uint32_t run_block_count[2] = {LFS2_BLOCK_COUNT/2, LFS2_BLOCK_COUNT};
for (int run = 0; run < 2; run++) {
for (lfs2_block_t b = 0; b < LFS2_BLOCK_COUNT; b++) {
lfs2_testbd_setwear(&cfg, b,
(b < run_block_count[run]) ? 0 : LFS2_ERASE_CYCLES) => 0;
}
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "roadrunner") => 0;
lfs2_unmount(&lfs2) => 0;
uint32_t cycle = 0;
while (true) {
lfs2_mount(&lfs2, &cfg) => 0;
for (uint32_t i = 0; i < FILES; i++) {
// chose name, roughly random seed, and random 2^n size
sprintf(path, "roadrunner/test%d", i);
srand(cycle * i);
size = 1 << ((rand() % 10)+2);
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
for (lfs2_size_t j = 0; j < size; j++) {
char c = 'a' + (rand() % 26);
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1);
assert(res == 1 || res == LFS2_ERR_NOSPC);
if (res == LFS2_ERR_NOSPC) {
err = lfs2_file_close(&lfs2, &file);
assert(err == 0 || err == LFS2_ERR_NOSPC);
lfs2_unmount(&lfs2) => 0;
goto exhausted;
}
}
err = lfs2_file_close(&lfs2, &file);
assert(err == 0 || err == LFS2_ERR_NOSPC);
if (err == LFS2_ERR_NOSPC) {
lfs2_unmount(&lfs2) => 0;
goto exhausted;
}
}
for (uint32_t i = 0; i < FILES; i++) {
// check for errors
sprintf(path, "roadrunner/test%d", i);
srand(cycle * i);
size = 1 << ((rand() % 10)+2);
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
for (lfs2_size_t j = 0; j < size; j++) {
char c = 'a' + (rand() % 26);
char r;
lfs2_file_read(&lfs2, &file, &r, 1) => 1;
assert(r == c);
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
cycle += 1;
}
exhausted:
// should still be readable
lfs2_mount(&lfs2, &cfg) => 0;
for (uint32_t i = 0; i < FILES; i++) {
// check for errors
sprintf(path, "roadrunner/test%d", i);
lfs2_stat(&lfs2, path, &info) => 0;
}
lfs2_unmount(&lfs2) => 0;
run_cycles[run] = cycle;
LFS2_WARN("completed %d blocks %d cycles",
run_block_count[run], run_cycles[run]);
}
// check we increased the lifetime by 2x with ~10% error
LFS2_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
'''
[[case]] # wear-level test + expanding superblock
define.LFS2_ERASE_CYCLES = 20
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
define.LFS2_BLOCK_CYCLES = 'LFS2_ERASE_CYCLES / 2'
define.FILES = 10
code = '''
uint32_t run_cycles[2];
const uint32_t run_block_count[2] = {LFS2_BLOCK_COUNT/2, LFS2_BLOCK_COUNT};
for (int run = 0; run < 2; run++) {
for (lfs2_block_t b = 0; b < LFS2_BLOCK_COUNT; b++) {
lfs2_testbd_setwear(&cfg, b,
(b < run_block_count[run]) ? 0 : LFS2_ERASE_CYCLES) => 0;
}
lfs2_format(&lfs2, &cfg) => 0;
uint32_t cycle = 0;
while (true) {
lfs2_mount(&lfs2, &cfg) => 0;
for (uint32_t i = 0; i < FILES; i++) {
// chose name, roughly random seed, and random 2^n size
sprintf(path, "test%d", i);
srand(cycle * i);
size = 1 << ((rand() % 10)+2);
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
for (lfs2_size_t j = 0; j < size; j++) {
char c = 'a' + (rand() % 26);
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1);
assert(res == 1 || res == LFS2_ERR_NOSPC);
if (res == LFS2_ERR_NOSPC) {
err = lfs2_file_close(&lfs2, &file);
assert(err == 0 || err == LFS2_ERR_NOSPC);
lfs2_unmount(&lfs2) => 0;
goto exhausted;
}
}
err = lfs2_file_close(&lfs2, &file);
assert(err == 0 || err == LFS2_ERR_NOSPC);
if (err == LFS2_ERR_NOSPC) {
lfs2_unmount(&lfs2) => 0;
goto exhausted;
}
}
for (uint32_t i = 0; i < FILES; i++) {
// check for errors
sprintf(path, "test%d", i);
srand(cycle * i);
size = 1 << ((rand() % 10)+2);
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
for (lfs2_size_t j = 0; j < size; j++) {
char c = 'a' + (rand() % 26);
char r;
lfs2_file_read(&lfs2, &file, &r, 1) => 1;
assert(r == c);
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
cycle += 1;
}
exhausted:
// should still be readable
lfs2_mount(&lfs2, &cfg) => 0;
for (uint32_t i = 0; i < FILES; i++) {
// check for errors
sprintf(path, "test%d", i);
lfs2_stat(&lfs2, path, &info) => 0;
}
lfs2_unmount(&lfs2) => 0;
run_cycles[run] = cycle;
LFS2_WARN("completed %d blocks %d cycles",
run_block_count[run], run_cycles[run]);
}
// check we increased the lifetime by 2x with ~10% error
LFS2_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
'''
[[case]] # test that we wear blocks roughly evenly
define.LFS2_ERASE_CYCLES = 0xffffffff
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
define.LFS2_BLOCK_CYCLES = [5, 4, 3, 2, 1]
define.CYCLES = 100
define.FILES = 10
if = 'LFS2_BLOCK_CYCLES < CYCLES/10'
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "roadrunner") => 0;
lfs2_unmount(&lfs2) => 0;
uint32_t cycle = 0;
while (cycle < CYCLES) {
lfs2_mount(&lfs2, &cfg) => 0;
for (uint32_t i = 0; i < FILES; i++) {
// chose name, roughly random seed, and random 2^n size
sprintf(path, "roadrunner/test%d", i);
srand(cycle * i);
size = 1 << 4; //((rand() % 10)+2);
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
for (lfs2_size_t j = 0; j < size; j++) {
char c = 'a' + (rand() % 26);
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1);
assert(res == 1 || res == LFS2_ERR_NOSPC);
if (res == LFS2_ERR_NOSPC) {
err = lfs2_file_close(&lfs2, &file);
assert(err == 0 || err == LFS2_ERR_NOSPC);
lfs2_unmount(&lfs2) => 0;
goto exhausted;
}
}
err = lfs2_file_close(&lfs2, &file);
assert(err == 0 || err == LFS2_ERR_NOSPC);
if (err == LFS2_ERR_NOSPC) {
lfs2_unmount(&lfs2) => 0;
goto exhausted;
}
}
for (uint32_t i = 0; i < FILES; i++) {
// check for errors
sprintf(path, "roadrunner/test%d", i);
srand(cycle * i);
size = 1 << 4; //((rand() % 10)+2);
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
for (lfs2_size_t j = 0; j < size; j++) {
char c = 'a' + (rand() % 26);
char r;
lfs2_file_read(&lfs2, &file, &r, 1) => 1;
assert(r == c);
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
cycle += 1;
}
exhausted:
// should still be readable
lfs2_mount(&lfs2, &cfg) => 0;
for (uint32_t i = 0; i < FILES; i++) {
// check for errors
sprintf(path, "roadrunner/test%d", i);
lfs2_stat(&lfs2, path, &info) => 0;
}
lfs2_unmount(&lfs2) => 0;
LFS2_WARN("completed %d cycles", cycle);
// check the wear on our block device
lfs2_testbd_wear_t minwear = -1;
lfs2_testbd_wear_t totalwear = 0;
lfs2_testbd_wear_t maxwear = 0;
// skip 0 and 1 as superblock movement is intentionally avoided
for (lfs2_block_t b = 2; b < LFS2_BLOCK_COUNT; b++) {
lfs2_testbd_wear_t wear = lfs2_testbd_getwear(&cfg, b);
printf("%08x: wear %d\n", b, wear);
assert(wear >= 0);
if (wear < minwear) {
minwear = wear;
}
if (wear > maxwear) {
maxwear = wear;
}
totalwear += wear;
}
lfs2_testbd_wear_t avgwear = totalwear / LFS2_BLOCK_COUNT;
LFS2_WARN("max wear: %d cycles", maxwear);
LFS2_WARN("avg wear: %d cycles", totalwear / LFS2_BLOCK_COUNT);
LFS2_WARN("min wear: %d cycles", minwear);
// find standard deviation^2
lfs2_testbd_wear_t dev2 = 0;
for (lfs2_block_t b = 2; b < LFS2_BLOCK_COUNT; b++) {
lfs2_testbd_wear_t wear = lfs2_testbd_getwear(&cfg, b);
assert(wear >= 0);
lfs2_testbd_swear_t diff = wear - avgwear;
dev2 += diff*diff;
}
dev2 /= totalwear;
LFS2_WARN("std dev^2: %d", dev2);
assert(dev2 < 8);
'''

View File

@ -0,0 +1,486 @@
[[case]] # simple file test
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "hello",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
size = strlen("Hello World!")+1;
strcpy((char*)buffer, "Hello World!");
lfs2_file_write(&lfs2, &file, buffer, size) => size;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "hello", LFS2_O_RDONLY) => 0;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
assert(strcmp((char*)buffer, "Hello World!") == 0);
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # larger files
define.SIZE = [32, 8192, 262144, 0, 7, 8193]
define.CHUNKSIZE = [31, 16, 33, 1, 1023]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
// write
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "avacado",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
srand(1);
for (lfs2_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE-i);
for (lfs2_size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
// read
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => SIZE;
srand(1);
for (lfs2_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE-i);
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
for (lfs2_size_t b = 0; b < chunk; b++) {
assert(buffer[b] == (rand() & 0xff));
}
}
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # rewriting files
define.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
define.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
define.CHUNKSIZE = [31, 16, 1]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
// write
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "avacado",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
srand(1);
for (lfs2_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
for (lfs2_size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
// read
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => SIZE1;
srand(1);
for (lfs2_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
for (lfs2_size_t b = 0; b < chunk; b++) {
assert(buffer[b] == (rand() & 0xff));
}
}
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
// rewrite
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_WRONLY) => 0;
srand(2);
for (lfs2_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE2-i);
for (lfs2_size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
// read
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => lfs2_max(SIZE1, SIZE2);
srand(2);
for (lfs2_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE2-i);
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
for (lfs2_size_t b = 0; b < chunk; b++) {
assert(buffer[b] == (rand() & 0xff));
}
}
if (SIZE1 > SIZE2) {
srand(1);
for (lfs2_size_t b = 0; b < SIZE2; b++) {
rand();
}
for (lfs2_size_t i = SIZE2; i < SIZE1; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
for (lfs2_size_t b = 0; b < chunk; b++) {
assert(buffer[b] == (rand() & 0xff));
}
}
}
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # appending files
define.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
define.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
define.CHUNKSIZE = [31, 16, 1]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
// write
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "avacado",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
srand(1);
for (lfs2_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
for (lfs2_size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
// read
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => SIZE1;
srand(1);
for (lfs2_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
for (lfs2_size_t b = 0; b < chunk; b++) {
assert(buffer[b] == (rand() & 0xff));
}
}
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
// append
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_WRONLY | LFS2_O_APPEND) => 0;
srand(2);
for (lfs2_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE2-i);
for (lfs2_size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
// read
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => SIZE1 + SIZE2;
srand(1);
for (lfs2_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
for (lfs2_size_t b = 0; b < chunk; b++) {
assert(buffer[b] == (rand() & 0xff));
}
}
srand(2);
for (lfs2_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE2-i);
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
for (lfs2_size_t b = 0; b < chunk; b++) {
assert(buffer[b] == (rand() & 0xff));
}
}
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # truncating files
define.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
define.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
define.CHUNKSIZE = [31, 16, 1]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
// write
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "avacado",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
srand(1);
for (lfs2_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
for (lfs2_size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
// read
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => SIZE1;
srand(1);
for (lfs2_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
for (lfs2_size_t b = 0; b < chunk; b++) {
assert(buffer[b] == (rand() & 0xff));
}
}
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
// truncate
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_WRONLY | LFS2_O_TRUNC) => 0;
srand(2);
for (lfs2_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE2-i);
for (lfs2_size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
// read
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => SIZE2;
srand(2);
for (lfs2_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE2-i);
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
for (lfs2_size_t b = 0; b < chunk; b++) {
assert(buffer[b] == (rand() & 0xff));
}
}
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # reentrant file writing
define.SIZE = [32, 0, 7, 2049]
define.CHUNKSIZE = [31, 16, 65]
reentrant = true
code = '''
err = lfs2_mount(&lfs2, &cfg);
if (err) {
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
}
err = lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY);
assert(err == LFS2_ERR_NOENT || err == 0);
if (err == 0) {
// can only be 0 (new file) or full size
size = lfs2_file_size(&lfs2, &file);
assert(size == 0 || size == SIZE);
lfs2_file_close(&lfs2, &file) => 0;
}
// write
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
srand(1);
for (lfs2_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE-i);
for (lfs2_size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
}
lfs2_file_close(&lfs2, &file) => 0;
// read
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => SIZE;
srand(1);
for (lfs2_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE-i);
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
for (lfs2_size_t b = 0; b < chunk; b++) {
assert(buffer[b] == (rand() & 0xff));
}
}
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # reentrant file writing with syncs
define = [
# append (O(n))
{MODE='LFS2_O_APPEND', SIZE=[32, 0, 7, 2049], CHUNKSIZE=[31, 16, 65]},
# truncate (O(n^2))
{MODE='LFS2_O_TRUNC', SIZE=[32, 0, 7, 200], CHUNKSIZE=[31, 16, 65]},
# rewrite (O(n^2))
{MODE=0, SIZE=[32, 0, 7, 200], CHUNKSIZE=[31, 16, 65]},
]
reentrant = true
code = '''
err = lfs2_mount(&lfs2, &cfg);
if (err) {
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
}
err = lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY);
assert(err == LFS2_ERR_NOENT || err == 0);
if (err == 0) {
// with syncs we could be any size, but it at least must be valid data
size = lfs2_file_size(&lfs2, &file);
assert(size <= SIZE);
srand(1);
for (lfs2_size_t i = 0; i < size; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, size-i);
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
for (lfs2_size_t b = 0; b < chunk; b++) {
assert(buffer[b] == (rand() & 0xff));
}
}
lfs2_file_close(&lfs2, &file) => 0;
}
// write
lfs2_file_open(&lfs2, &file, "avacado",
LFS2_O_WRONLY | LFS2_O_CREAT | MODE) => 0;
size = lfs2_file_size(&lfs2, &file);
assert(size <= SIZE);
srand(1);
lfs2_size_t skip = (MODE == LFS2_O_APPEND) ? size : 0;
for (lfs2_size_t b = 0; b < skip; b++) {
rand();
}
for (lfs2_size_t i = skip; i < SIZE; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE-i);
for (lfs2_size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
lfs2_file_sync(&lfs2, &file) => 0;
}
lfs2_file_close(&lfs2, &file) => 0;
// read
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => SIZE;
srand(1);
for (lfs2_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE-i);
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
for (lfs2_size_t b = 0; b < chunk; b++) {
assert(buffer[b] == (rand() & 0xff));
}
}
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # many files
define.N = 300
code = '''
lfs2_format(&lfs2, &cfg) => 0;
// create N files of 7 bytes
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 0; i < N; i++) {
sprintf(path, "file_%03d", i);
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
char wbuffer[1024];
size = 7;
snprintf(wbuffer, size, "Hi %03d", i);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_close(&lfs2, &file) => 0;
char rbuffer[1024];
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
assert(strcmp(rbuffer, wbuffer) == 0);
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # many files with power cycle
define.N = 300
code = '''
lfs2_format(&lfs2, &cfg) => 0;
// create N files of 7 bytes
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 0; i < N; i++) {
sprintf(path, "file_%03d", i);
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
char wbuffer[1024];
size = 7;
snprintf(wbuffer, size, "Hi %03d", i);
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
char rbuffer[1024];
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
assert(strcmp(rbuffer, wbuffer) == 0);
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # many files with power loss
define.N = 300
reentrant = true
code = '''
err = lfs2_mount(&lfs2, &cfg);
if (err) {
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
}
// create N files of 7 bytes
for (int i = 0; i < N; i++) {
sprintf(path, "file_%03d", i);
err = lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY | LFS2_O_CREAT);
char wbuffer[1024];
size = 7;
snprintf(wbuffer, size, "Hi %03d", i);
if ((lfs2_size_t)lfs2_file_size(&lfs2, &file) != size) {
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
}
lfs2_file_close(&lfs2, &file) => 0;
char rbuffer[1024];
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
assert(strcmp(rbuffer, wbuffer) == 0);
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
'''

View File

@ -0,0 +1,244 @@
[[case]] # interspersed file test
define.SIZE = [10, 100]
define.FILES = [4, 10, 26]
code = '''
lfs2_file_t files[FILES];
const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int j = 0; j < FILES; j++) {
sprintf(path, "%c", alphas[j]);
lfs2_file_open(&lfs2, &files[j], path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
}
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < FILES; j++) {
lfs2_file_write(&lfs2, &files[j], &alphas[j], 1) => 1;
}
}
for (int j = 0; j < FILES; j++) {
lfs2_file_close(&lfs2, &files[j]);
}
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS2_TYPE_DIR);
for (int j = 0; j < FILES; j++) {
sprintf(path, "%c", alphas[j]);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
assert(info.type == LFS2_TYPE_REG);
assert(info.size == SIZE);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
for (int j = 0; j < FILES; j++) {
sprintf(path, "%c", alphas[j]);
lfs2_file_open(&lfs2, &files[j], path, LFS2_O_RDONLY) => 0;
}
for (int i = 0; i < 10; i++) {
for (int j = 0; j < FILES; j++) {
lfs2_file_read(&lfs2, &files[j], buffer, 1) => 1;
assert(buffer[0] == alphas[j]);
}
}
for (int j = 0; j < FILES; j++) {
lfs2_file_close(&lfs2, &files[j]);
}
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # interspersed remove file test
define.SIZE = [10, 100]
define.FILES = [4, 10, 26]
code = '''
const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int j = 0; j < FILES; j++) {
sprintf(path, "%c", alphas[j]);
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
for (int i = 0; i < SIZE; i++) {
lfs2_file_write(&lfs2, &file, &alphas[j], 1) => 1;
}
lfs2_file_close(&lfs2, &file);
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "zzz", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
for (int j = 0; j < FILES; j++) {
lfs2_file_write(&lfs2, &file, (const void*)"~", 1) => 1;
lfs2_file_sync(&lfs2, &file) => 0;
sprintf(path, "%c", alphas[j]);
lfs2_remove(&lfs2, path) => 0;
}
lfs2_file_close(&lfs2, &file);
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "zzz") == 0);
assert(info.type == LFS2_TYPE_REG);
assert(info.size == FILES);
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_file_open(&lfs2, &file, "zzz", LFS2_O_RDONLY) => 0;
for (int i = 0; i < FILES; i++) {
lfs2_file_read(&lfs2, &file, buffer, 1) => 1;
assert(buffer[0] == '~');
}
lfs2_file_close(&lfs2, &file);
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # remove inconveniently test
define.SIZE = [10, 100]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_t files[3];
lfs2_file_open(&lfs2, &files[0], "e", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
lfs2_file_open(&lfs2, &files[1], "f", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
lfs2_file_open(&lfs2, &files[2], "g", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
for (int i = 0; i < SIZE/2; i++) {
lfs2_file_write(&lfs2, &files[0], (const void*)"e", 1) => 1;
lfs2_file_write(&lfs2, &files[1], (const void*)"f", 1) => 1;
lfs2_file_write(&lfs2, &files[2], (const void*)"g", 1) => 1;
}
lfs2_remove(&lfs2, "f") => 0;
for (int i = 0; i < SIZE/2; i++) {
lfs2_file_write(&lfs2, &files[0], (const void*)"e", 1) => 1;
lfs2_file_write(&lfs2, &files[1], (const void*)"f", 1) => 1;
lfs2_file_write(&lfs2, &files[2], (const void*)"g", 1) => 1;
}
lfs2_file_close(&lfs2, &files[0]);
lfs2_file_close(&lfs2, &files[1]);
lfs2_file_close(&lfs2, &files[2]);
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "e") == 0);
assert(info.type == LFS2_TYPE_REG);
assert(info.size == SIZE);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "g") == 0);
assert(info.type == LFS2_TYPE_REG);
assert(info.size == SIZE);
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_file_open(&lfs2, &files[0], "e", LFS2_O_RDONLY) => 0;
lfs2_file_open(&lfs2, &files[1], "g", LFS2_O_RDONLY) => 0;
for (int i = 0; i < SIZE; i++) {
lfs2_file_read(&lfs2, &files[0], buffer, 1) => 1;
assert(buffer[0] == 'e');
lfs2_file_read(&lfs2, &files[1], buffer, 1) => 1;
assert(buffer[0] == 'g');
}
lfs2_file_close(&lfs2, &files[0]);
lfs2_file_close(&lfs2, &files[1]);
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # reentrant interspersed file test
define.SIZE = [10, 100]
define.FILES = [4, 10, 26]
reentrant = true
code = '''
lfs2_file_t files[FILES];
const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
err = lfs2_mount(&lfs2, &cfg);
if (err) {
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
}
for (int j = 0; j < FILES; j++) {
sprintf(path, "%c", alphas[j]);
lfs2_file_open(&lfs2, &files[j], path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
}
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < FILES; j++) {
size = lfs2_file_size(&lfs2, &files[j]);
assert((int)size >= 0);
if ((int)size <= i) {
lfs2_file_write(&lfs2, &files[j], &alphas[j], 1) => 1;
lfs2_file_sync(&lfs2, &files[j]) => 0;
}
}
}
for (int j = 0; j < FILES; j++) {
lfs2_file_close(&lfs2, &files[j]);
}
lfs2_dir_open(&lfs2, &dir, "/") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS2_TYPE_DIR);
for (int j = 0; j < FILES; j++) {
sprintf(path, "%c", alphas[j]);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
assert(info.type == LFS2_TYPE_REG);
assert(info.size == SIZE);
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
for (int j = 0; j < FILES; j++) {
sprintf(path, "%c", alphas[j]);
lfs2_file_open(&lfs2, &files[j], path, LFS2_O_RDONLY) => 0;
}
for (int i = 0; i < 10; i++) {
for (int j = 0; j < FILES; j++) {
lfs2_file_read(&lfs2, &files[j], buffer, 1) => 1;
assert(buffer[0] == alphas[j]);
}
}
for (int j = 0; j < FILES; j++) {
lfs2_file_close(&lfs2, &files[j]);
}
lfs2_unmount(&lfs2) => 0;
'''

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,120 @@
[[case]] # orphan test
in = "lfs2.c"
if = 'LFS2_PROG_SIZE <= 0x3fe' # only works with one crc per commit
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "parent") => 0;
lfs2_mkdir(&lfs2, "parent/orphan") => 0;
lfs2_mkdir(&lfs2, "parent/child") => 0;
lfs2_remove(&lfs2, "parent/orphan") => 0;
lfs2_unmount(&lfs2) => 0;
// corrupt the child's most recent commit, this should be the update
// to the linked-list entry, which should orphan the orphan. Note this
// makes a lot of assumptions about the remove operation.
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "parent/child") => 0;
lfs2_block_t block = dir.m.pair[0];
lfs2_dir_close(&lfs2, &dir) => 0;
lfs2_unmount(&lfs2) => 0;
uint8_t bbuffer[LFS2_BLOCK_SIZE];
cfg.read(&cfg, block, 0, bbuffer, LFS2_BLOCK_SIZE) => 0;
int off = LFS2_BLOCK_SIZE-1;
while (off >= 0 && bbuffer[off] == LFS2_ERASE_VALUE) {
off -= 1;
}
memset(&bbuffer[off-3], LFS2_BLOCK_SIZE, 3);
cfg.erase(&cfg, block) => 0;
cfg.prog(&cfg, block, 0, bbuffer, LFS2_BLOCK_SIZE) => 0;
cfg.sync(&cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_stat(&lfs2, "parent/orphan", &info) => LFS2_ERR_NOENT;
lfs2_stat(&lfs2, "parent/child", &info) => 0;
lfs2_fs_size(&lfs2) => 8;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_stat(&lfs2, "parent/orphan", &info) => LFS2_ERR_NOENT;
lfs2_stat(&lfs2, "parent/child", &info) => 0;
lfs2_fs_size(&lfs2) => 8;
// this mkdir should both create a dir and deorphan, so size
// should be unchanged
lfs2_mkdir(&lfs2, "parent/otherchild") => 0;
lfs2_stat(&lfs2, "parent/orphan", &info) => LFS2_ERR_NOENT;
lfs2_stat(&lfs2, "parent/child", &info) => 0;
lfs2_stat(&lfs2, "parent/otherchild", &info) => 0;
lfs2_fs_size(&lfs2) => 8;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_stat(&lfs2, "parent/orphan", &info) => LFS2_ERR_NOENT;
lfs2_stat(&lfs2, "parent/child", &info) => 0;
lfs2_stat(&lfs2, "parent/otherchild", &info) => 0;
lfs2_fs_size(&lfs2) => 8;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # reentrant testing for orphans, basically just spam mkdir/remove
reentrant = true
# TODO fix this case, caused by non-DAG trees
if = '!(DEPTH == 3 && LFS2_CACHE_SIZE != 64)'
define = [
{FILES=6, DEPTH=1, CYCLES=20},
{FILES=26, DEPTH=1, CYCLES=20},
{FILES=3, DEPTH=3, CYCLES=20},
]
code = '''
err = lfs2_mount(&lfs2, &cfg);
if (err) {
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
}
srand(1);
const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
for (int i = 0; i < CYCLES; i++) {
// create random path
char full_path[256];
for (int d = 0; d < DEPTH; d++) {
sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]);
}
// if it does not exist, we create it, else we destroy
int res = lfs2_stat(&lfs2, full_path, &info);
if (res == LFS2_ERR_NOENT) {
// create each directory in turn, ignore if dir already exists
for (int d = 0; d < DEPTH; d++) {
strcpy(path, full_path);
path[2*d+2] = '\0';
err = lfs2_mkdir(&lfs2, path);
assert(!err || err == LFS2_ERR_EXIST);
}
for (int d = 0; d < DEPTH; d++) {
strcpy(path, full_path);
path[2*d+2] = '\0';
lfs2_stat(&lfs2, path, &info) => 0;
assert(strcmp(info.name, &path[2*d+1]) == 0);
assert(info.type == LFS2_TYPE_DIR);
}
} else {
// is valid dir?
assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
assert(info.type == LFS2_TYPE_DIR);
// try to delete path in reverse order, ignore if dir is not empty
for (int d = DEPTH-1; d >= 0; d--) {
strcpy(path, full_path);
path[2*d+2] = '\0';
err = lfs2_remove(&lfs2, path);
assert(!err || err == LFS2_ERR_NOTEMPTY);
}
lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT;
}
}
lfs2_unmount(&lfs2) => 0;
'''

View File

@ -0,0 +1,293 @@
[[case]] # simple path test
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "tea") => 0;
lfs2_mkdir(&lfs2, "tea/hottea") => 0;
lfs2_mkdir(&lfs2, "tea/warmtea") => 0;
lfs2_mkdir(&lfs2, "tea/coldtea") => 0;
lfs2_stat(&lfs2, "tea/hottea", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_stat(&lfs2, "/tea/hottea", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_mkdir(&lfs2, "/milk") => 0;
lfs2_stat(&lfs2, "/milk", &info) => 0;
assert(strcmp(info.name, "milk") == 0);
lfs2_stat(&lfs2, "milk", &info) => 0;
assert(strcmp(info.name, "milk") == 0);
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # redundant slashes
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "tea") => 0;
lfs2_mkdir(&lfs2, "tea/hottea") => 0;
lfs2_mkdir(&lfs2, "tea/warmtea") => 0;
lfs2_mkdir(&lfs2, "tea/coldtea") => 0;
lfs2_stat(&lfs2, "/tea/hottea", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_stat(&lfs2, "//tea//hottea", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_stat(&lfs2, "///tea///hottea", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_mkdir(&lfs2, "////milk") => 0;
lfs2_stat(&lfs2, "////milk", &info) => 0;
assert(strcmp(info.name, "milk") == 0);
lfs2_stat(&lfs2, "milk", &info) => 0;
assert(strcmp(info.name, "milk") == 0);
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # dot path test
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "tea") => 0;
lfs2_mkdir(&lfs2, "tea/hottea") => 0;
lfs2_mkdir(&lfs2, "tea/warmtea") => 0;
lfs2_mkdir(&lfs2, "tea/coldtea") => 0;
lfs2_stat(&lfs2, "./tea/hottea", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_stat(&lfs2, "/./tea/hottea", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_stat(&lfs2, "/././tea/hottea", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_stat(&lfs2, "/./tea/./hottea", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_mkdir(&lfs2, "/./milk") => 0;
lfs2_stat(&lfs2, "/./milk", &info) => 0;
assert(strcmp(info.name, "milk") == 0);
lfs2_stat(&lfs2, "milk", &info) => 0;
assert(strcmp(info.name, "milk") == 0);
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # dot dot path test
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "tea") => 0;
lfs2_mkdir(&lfs2, "tea/hottea") => 0;
lfs2_mkdir(&lfs2, "tea/warmtea") => 0;
lfs2_mkdir(&lfs2, "tea/coldtea") => 0;
lfs2_mkdir(&lfs2, "coffee") => 0;
lfs2_mkdir(&lfs2, "coffee/hotcoffee") => 0;
lfs2_mkdir(&lfs2, "coffee/warmcoffee") => 0;
lfs2_mkdir(&lfs2, "coffee/coldcoffee") => 0;
lfs2_stat(&lfs2, "coffee/../tea/hottea", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_stat(&lfs2, "tea/coldtea/../hottea", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_stat(&lfs2, "coffee/coldcoffee/../../tea/hottea", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_stat(&lfs2, "coffee/../coffee/../tea/hottea", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_mkdir(&lfs2, "coffee/../milk") => 0;
lfs2_stat(&lfs2, "coffee/../milk", &info) => 0;
strcmp(info.name, "milk") => 0;
lfs2_stat(&lfs2, "milk", &info) => 0;
strcmp(info.name, "milk") => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # trailing dot path test
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "tea") => 0;
lfs2_mkdir(&lfs2, "tea/hottea") => 0;
lfs2_mkdir(&lfs2, "tea/warmtea") => 0;
lfs2_mkdir(&lfs2, "tea/coldtea") => 0;
lfs2_stat(&lfs2, "tea/hottea/", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_stat(&lfs2, "tea/hottea/.", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_stat(&lfs2, "tea/hottea/./.", &info) => 0;
assert(strcmp(info.name, "hottea") == 0);
lfs2_stat(&lfs2, "tea/hottea/..", &info) => 0;
assert(strcmp(info.name, "tea") == 0);
lfs2_stat(&lfs2, "tea/hottea/../.", &info) => 0;
assert(strcmp(info.name, "tea") == 0);
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # leading dot path test
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, ".milk") => 0;
lfs2_stat(&lfs2, ".milk", &info) => 0;
strcmp(info.name, ".milk") => 0;
lfs2_stat(&lfs2, "tea/.././.milk", &info) => 0;
strcmp(info.name, ".milk") => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # root dot dot path test
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "tea") => 0;
lfs2_mkdir(&lfs2, "tea/hottea") => 0;
lfs2_mkdir(&lfs2, "tea/warmtea") => 0;
lfs2_mkdir(&lfs2, "tea/coldtea") => 0;
lfs2_mkdir(&lfs2, "coffee") => 0;
lfs2_mkdir(&lfs2, "coffee/hotcoffee") => 0;
lfs2_mkdir(&lfs2, "coffee/warmcoffee") => 0;
lfs2_mkdir(&lfs2, "coffee/coldcoffee") => 0;
lfs2_stat(&lfs2, "coffee/../../../../../../tea/hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs2_mkdir(&lfs2, "coffee/../../../../../../milk") => 0;
lfs2_stat(&lfs2, "coffee/../../../../../../milk", &info) => 0;
strcmp(info.name, "milk") => 0;
lfs2_stat(&lfs2, "milk", &info) => 0;
strcmp(info.name, "milk") => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # invalid path tests
code = '''
lfs2_format(&lfs2, &cfg);
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_stat(&lfs2, "dirt", &info) => LFS2_ERR_NOENT;
lfs2_stat(&lfs2, "dirt/ground", &info) => LFS2_ERR_NOENT;
lfs2_stat(&lfs2, "dirt/ground/earth", &info) => LFS2_ERR_NOENT;
lfs2_remove(&lfs2, "dirt") => LFS2_ERR_NOENT;
lfs2_remove(&lfs2, "dirt/ground") => LFS2_ERR_NOENT;
lfs2_remove(&lfs2, "dirt/ground/earth") => LFS2_ERR_NOENT;
lfs2_mkdir(&lfs2, "dirt/ground") => LFS2_ERR_NOENT;
lfs2_file_open(&lfs2, &file, "dirt/ground", LFS2_O_WRONLY | LFS2_O_CREAT)
=> LFS2_ERR_NOENT;
lfs2_mkdir(&lfs2, "dirt/ground/earth") => LFS2_ERR_NOENT;
lfs2_file_open(&lfs2, &file, "dirt/ground/earth", LFS2_O_WRONLY | LFS2_O_CREAT)
=> LFS2_ERR_NOENT;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # root operations
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_stat(&lfs2, "/", &info) => 0;
assert(strcmp(info.name, "/") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_mkdir(&lfs2, "/") => LFS2_ERR_EXIST;
lfs2_file_open(&lfs2, &file, "/", LFS2_O_WRONLY | LFS2_O_CREAT)
=> LFS2_ERR_ISDIR;
lfs2_remove(&lfs2, "/") => LFS2_ERR_INVAL;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # root representations
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_stat(&lfs2, "/", &info) => 0;
assert(strcmp(info.name, "/") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_stat(&lfs2, "", &info) => 0;
assert(strcmp(info.name, "/") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_stat(&lfs2, ".", &info) => 0;
assert(strcmp(info.name, "/") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_stat(&lfs2, "..", &info) => 0;
assert(strcmp(info.name, "/") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_stat(&lfs2, "//", &info) => 0;
assert(strcmp(info.name, "/") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_stat(&lfs2, "./", &info) => 0;
assert(strcmp(info.name, "/") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # superblock conflict test
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_stat(&lfs2, "littlefs", &info) => LFS2_ERR_NOENT;
lfs2_remove(&lfs2, "littlefs") => LFS2_ERR_NOENT;
lfs2_mkdir(&lfs2, "littlefs") => 0;
lfs2_stat(&lfs2, "littlefs", &info) => 0;
assert(strcmp(info.name, "littlefs") == 0);
assert(info.type == LFS2_TYPE_DIR);
lfs2_remove(&lfs2, "littlefs") => 0;
lfs2_stat(&lfs2, "littlefs", &info) => LFS2_ERR_NOENT;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # max path test
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "coffee") => 0;
lfs2_mkdir(&lfs2, "coffee/hotcoffee") => 0;
lfs2_mkdir(&lfs2, "coffee/warmcoffee") => 0;
lfs2_mkdir(&lfs2, "coffee/coldcoffee") => 0;
memset(path, 'w', LFS2_NAME_MAX+1);
path[LFS2_NAME_MAX+1] = '\0';
lfs2_mkdir(&lfs2, path) => LFS2_ERR_NAMETOOLONG;
lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY | LFS2_O_CREAT)
=> LFS2_ERR_NAMETOOLONG;
memcpy(path, "coffee/", strlen("coffee/"));
memset(path+strlen("coffee/"), 'w', LFS2_NAME_MAX+1);
path[strlen("coffee/")+LFS2_NAME_MAX+1] = '\0';
lfs2_mkdir(&lfs2, path) => LFS2_ERR_NAMETOOLONG;
lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY | LFS2_O_CREAT)
=> LFS2_ERR_NAMETOOLONG;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # really big path test
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_mkdir(&lfs2, "coffee") => 0;
lfs2_mkdir(&lfs2, "coffee/hotcoffee") => 0;
lfs2_mkdir(&lfs2, "coffee/warmcoffee") => 0;
lfs2_mkdir(&lfs2, "coffee/coldcoffee") => 0;
memset(path, 'w', LFS2_NAME_MAX);
path[LFS2_NAME_MAX] = '\0';
lfs2_mkdir(&lfs2, path) => 0;
lfs2_remove(&lfs2, path) => 0;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_remove(&lfs2, path) => 0;
memcpy(path, "coffee/", strlen("coffee/"));
memset(path+strlen("coffee/"), 'w', LFS2_NAME_MAX);
path[strlen("coffee/")+LFS2_NAME_MAX] = '\0';
lfs2_mkdir(&lfs2, path) => 0;
lfs2_remove(&lfs2, path) => 0;
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_remove(&lfs2, path) => 0;
lfs2_unmount(&lfs2) => 0;
'''

View File

@ -0,0 +1,305 @@
# specific corner cases worth explicitly testing for
[[case]] # dangling split dir test
define.ITERATIONS = 20
define.COUNT = 10
define.LFS2_BLOCK_CYCLES = [8, 1]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
// fill up filesystem so only ~16 blocks are left
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "padding", LFS2_O_CREAT | LFS2_O_WRONLY) => 0;
memset(buffer, 0, 512);
while (LFS2_BLOCK_COUNT - lfs2_fs_size(&lfs2) > 16) {
lfs2_file_write(&lfs2, &file, buffer, 512) => 512;
}
lfs2_file_close(&lfs2, &file) => 0;
// make a child dir to use in bounded space
lfs2_mkdir(&lfs2, "child") => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int j = 0; j < ITERATIONS; j++) {
for (int i = 0; i < COUNT; i++) {
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
lfs2_file_open(&lfs2, &file, path, LFS2_O_CREAT | LFS2_O_WRONLY) => 0;
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_dir_open(&lfs2, &dir, "child") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
strcmp(info.name, path) => 0;
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
if (j == ITERATIONS-1) {
break;
}
for (int i = 0; i < COUNT; i++) {
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
lfs2_remove(&lfs2, path) => 0;
}
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_dir_open(&lfs2, &dir, "child") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
strcmp(info.name, path) => 0;
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
for (int i = 0; i < COUNT; i++) {
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
lfs2_remove(&lfs2, path) => 0;
}
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # outdated head test
define.ITERATIONS = 20
define.COUNT = 10
define.LFS2_BLOCK_CYCLES = [8, 1]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
// fill up filesystem so only ~16 blocks are left
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "padding", LFS2_O_CREAT | LFS2_O_WRONLY) => 0;
memset(buffer, 0, 512);
while (LFS2_BLOCK_COUNT - lfs2_fs_size(&lfs2) > 16) {
lfs2_file_write(&lfs2, &file, buffer, 512) => 512;
}
lfs2_file_close(&lfs2, &file) => 0;
// make a child dir to use in bounded space
lfs2_mkdir(&lfs2, "child") => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int j = 0; j < ITERATIONS; j++) {
for (int i = 0; i < COUNT; i++) {
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
lfs2_file_open(&lfs2, &file, path, LFS2_O_CREAT | LFS2_O_WRONLY) => 0;
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_dir_open(&lfs2, &dir, "child") => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
strcmp(info.name, path) => 0;
info.size => 0;
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY) => 0;
lfs2_file_write(&lfs2, &file, "hi", 2) => 2;
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_rewind(&lfs2, &dir) => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
strcmp(info.name, path) => 0;
info.size => 2;
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY) => 0;
lfs2_file_write(&lfs2, &file, "hi", 2) => 2;
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_rewind(&lfs2, &dir) => 0;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
lfs2_dir_read(&lfs2, &dir, &info) => 1;
for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs2_dir_read(&lfs2, &dir, &info) => 1;
strcmp(info.name, path) => 0;
info.size => 2;
}
lfs2_dir_read(&lfs2, &dir, &info) => 0;
lfs2_dir_close(&lfs2, &dir) => 0;
for (int i = 0; i < COUNT; i++) {
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
lfs2_remove(&lfs2, path) => 0;
}
}
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # reentrant testing for relocations, this is the same as the
# orphan testing, except here we also set block_cycles so that
# almost every tree operation needs a relocation
reentrant = true
# TODO fix this case, caused by non-DAG trees
if = '!(DEPTH == 3 && LFS2_CACHE_SIZE != 64)'
define = [
{FILES=6, DEPTH=1, CYCLES=20, LFS2_BLOCK_CYCLES=1},
{FILES=26, DEPTH=1, CYCLES=20, LFS2_BLOCK_CYCLES=1},
{FILES=3, DEPTH=3, CYCLES=20, LFS2_BLOCK_CYCLES=1},
]
code = '''
err = lfs2_mount(&lfs2, &cfg);
if (err) {
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
}
srand(1);
const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
for (int i = 0; i < CYCLES; i++) {
// create random path
char full_path[256];
for (int d = 0; d < DEPTH; d++) {
sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]);
}
// if it does not exist, we create it, else we destroy
int res = lfs2_stat(&lfs2, full_path, &info);
if (res == LFS2_ERR_NOENT) {
// create each directory in turn, ignore if dir already exists
for (int d = 0; d < DEPTH; d++) {
strcpy(path, full_path);
path[2*d+2] = '\0';
err = lfs2_mkdir(&lfs2, path);
assert(!err || err == LFS2_ERR_EXIST);
}
for (int d = 0; d < DEPTH; d++) {
strcpy(path, full_path);
path[2*d+2] = '\0';
lfs2_stat(&lfs2, path, &info) => 0;
assert(strcmp(info.name, &path[2*d+1]) == 0);
assert(info.type == LFS2_TYPE_DIR);
}
} else {
// is valid dir?
assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
assert(info.type == LFS2_TYPE_DIR);
// try to delete path in reverse order, ignore if dir is not empty
for (int d = DEPTH-1; d >= 0; d--) {
strcpy(path, full_path);
path[2*d+2] = '\0';
err = lfs2_remove(&lfs2, path);
assert(!err || err == LFS2_ERR_NOTEMPTY);
}
lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT;
}
}
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # reentrant testing for relocations, but now with random renames!
reentrant = true
# TODO fix this case, caused by non-DAG trees
if = '!(DEPTH == 3 && LFS2_CACHE_SIZE != 64)'
define = [
{FILES=6, DEPTH=1, CYCLES=20, LFS2_BLOCK_CYCLES=1},
{FILES=26, DEPTH=1, CYCLES=20, LFS2_BLOCK_CYCLES=1},
{FILES=3, DEPTH=3, CYCLES=20, LFS2_BLOCK_CYCLES=1},
]
code = '''
err = lfs2_mount(&lfs2, &cfg);
if (err) {
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
}
srand(1);
const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
for (int i = 0; i < CYCLES; i++) {
// create random path
char full_path[256];
for (int d = 0; d < DEPTH; d++) {
sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]);
}
// if it does not exist, we create it, else we destroy
int res = lfs2_stat(&lfs2, full_path, &info);
assert(!res || res == LFS2_ERR_NOENT);
if (res == LFS2_ERR_NOENT) {
// create each directory in turn, ignore if dir already exists
for (int d = 0; d < DEPTH; d++) {
strcpy(path, full_path);
path[2*d+2] = '\0';
err = lfs2_mkdir(&lfs2, path);
assert(!err || err == LFS2_ERR_EXIST);
}
for (int d = 0; d < DEPTH; d++) {
strcpy(path, full_path);
path[2*d+2] = '\0';
lfs2_stat(&lfs2, path, &info) => 0;
assert(strcmp(info.name, &path[2*d+1]) == 0);
assert(info.type == LFS2_TYPE_DIR);
}
} else {
assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
assert(info.type == LFS2_TYPE_DIR);
// create new random path
char new_path[256];
for (int d = 0; d < DEPTH; d++) {
sprintf(&new_path[2*d], "/%c", alpha[rand() % FILES]);
}
// if new path does not exist, rename, otherwise destroy
res = lfs2_stat(&lfs2, new_path, &info);
assert(!res || res == LFS2_ERR_NOENT);
if (res == LFS2_ERR_NOENT) {
// stop once some dir is renamed
for (int d = 0; d < DEPTH; d++) {
strcpy(&path[2*d], &full_path[2*d]);
path[2*d+2] = '\0';
strcpy(&path[128+2*d], &new_path[2*d]);
path[128+2*d+2] = '\0';
err = lfs2_rename(&lfs2, path, path+128);
assert(!err || err == LFS2_ERR_NOTEMPTY);
if (!err) {
strcpy(path, path+128);
}
}
for (int d = 0; d < DEPTH; d++) {
strcpy(path, new_path);
path[2*d+2] = '\0';
lfs2_stat(&lfs2, path, &info) => 0;
assert(strcmp(info.name, &path[2*d+1]) == 0);
assert(info.type == LFS2_TYPE_DIR);
}
lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT;
} else {
// try to delete path in reverse order,
// ignore if dir is not empty
for (int d = DEPTH-1; d >= 0; d--) {
strcpy(path, full_path);
path[2*d+2] = '\0';
err = lfs2_remove(&lfs2, path);
assert(!err || err == LFS2_ERR_NOTEMPTY);
}
lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT;
}
}
}
lfs2_unmount(&lfs2) => 0;
'''

View File

@ -0,0 +1,380 @@
[[case]] # simple file seek
define = [
{COUNT=132, SKIP=4},
{COUNT=132, SKIP=128},
{COUNT=200, SKIP=10},
{COUNT=200, SKIP=100},
{COUNT=4, SKIP=1},
{COUNT=4, SKIP=2},
]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "kitty",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
size = strlen("kittycatcat");
memcpy(buffer, "kittycatcat", size);
for (int j = 0; j < COUNT; j++) {
lfs2_file_write(&lfs2, &file, buffer, size);
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_RDONLY) => 0;
lfs2_soff_t pos = -1;
size = strlen("kittycatcat");
for (int i = 0; i < SKIP; i++) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
pos = lfs2_file_tell(&lfs2, &file);
}
assert(pos >= 0);
lfs2_file_seek(&lfs2, &file, pos, LFS2_SEEK_SET) => pos;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs2_file_rewind(&lfs2, &file) => 0;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_CUR) => size;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs2_file_seek(&lfs2, &file, size, LFS2_SEEK_CUR) => 3*size;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs2_file_seek(&lfs2, &file, pos, LFS2_SEEK_SET) => pos;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs2_file_seek(&lfs2, &file, -size, LFS2_SEEK_CUR) => pos;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs2_file_seek(&lfs2, &file, -size, LFS2_SEEK_END) >= 0 => 1;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
size = lfs2_file_size(&lfs2, &file);
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_CUR) => size;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # simple file seek and write
define = [
{COUNT=132, SKIP=4},
{COUNT=132, SKIP=128},
{COUNT=200, SKIP=10},
{COUNT=200, SKIP=100},
{COUNT=4, SKIP=1},
{COUNT=4, SKIP=2},
]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "kitty",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
size = strlen("kittycatcat");
memcpy(buffer, "kittycatcat", size);
for (int j = 0; j < COUNT; j++) {
lfs2_file_write(&lfs2, &file, buffer, size);
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_RDWR) => 0;
lfs2_soff_t pos = -1;
size = strlen("kittycatcat");
for (int i = 0; i < SKIP; i++) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
pos = lfs2_file_tell(&lfs2, &file);
}
assert(pos >= 0);
memcpy(buffer, "doggodogdog", size);
lfs2_file_seek(&lfs2, &file, pos, LFS2_SEEK_SET) => pos;
lfs2_file_write(&lfs2, &file, buffer, size) => size;
lfs2_file_seek(&lfs2, &file, pos, LFS2_SEEK_SET) => pos;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "doggodogdog", size) => 0;
lfs2_file_rewind(&lfs2, &file) => 0;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs2_file_seek(&lfs2, &file, pos, LFS2_SEEK_SET) => pos;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "doggodogdog", size) => 0;
lfs2_file_seek(&lfs2, &file, -size, LFS2_SEEK_END) >= 0 => 1;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
size = lfs2_file_size(&lfs2, &file);
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_CUR) => size;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # boundary seek and writes
define.COUNT = 132
define.OFFSETS = '"{512, 1020, 513, 1021, 511, 1019, 1441}"'
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "kitty",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
size = strlen("kittycatcat");
memcpy(buffer, "kittycatcat", size);
for (int j = 0; j < COUNT; j++) {
lfs2_file_write(&lfs2, &file, buffer, size);
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_RDWR) => 0;
size = strlen("hedgehoghog");
const lfs2_soff_t offsets[] = OFFSETS;
for (unsigned i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
lfs2_soff_t off = offsets[i];
memcpy(buffer, "hedgehoghog", size);
lfs2_file_seek(&lfs2, &file, off, LFS2_SEEK_SET) => off;
lfs2_file_write(&lfs2, &file, buffer, size) => size;
lfs2_file_seek(&lfs2, &file, off, LFS2_SEEK_SET) => off;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "hedgehoghog", size) => 0;
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_SET) => 0;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs2_file_seek(&lfs2, &file, off, LFS2_SEEK_SET) => off;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "hedgehoghog", size) => 0;
lfs2_file_sync(&lfs2, &file) => 0;
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_SET) => 0;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs2_file_seek(&lfs2, &file, off, LFS2_SEEK_SET) => off;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "hedgehoghog", size) => 0;
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # out of bounds seek
define = [
{COUNT=132, SKIP=4},
{COUNT=132, SKIP=128},
{COUNT=200, SKIP=10},
{COUNT=200, SKIP=100},
{COUNT=4, SKIP=2},
{COUNT=4, SKIP=3},
]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "kitty",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
size = strlen("kittycatcat");
memcpy(buffer, "kittycatcat", size);
for (int j = 0; j < COUNT; j++) {
lfs2_file_write(&lfs2, &file, buffer, size);
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_RDWR) => 0;
size = strlen("kittycatcat");
lfs2_file_size(&lfs2, &file) => COUNT*size;
lfs2_file_seek(&lfs2, &file, (COUNT+SKIP)*size,
LFS2_SEEK_SET) => (COUNT+SKIP)*size;
lfs2_file_read(&lfs2, &file, buffer, size) => 0;
memcpy(buffer, "porcupineee", size);
lfs2_file_write(&lfs2, &file, buffer, size) => size;
lfs2_file_seek(&lfs2, &file, (COUNT+SKIP)*size,
LFS2_SEEK_SET) => (COUNT+SKIP)*size;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "porcupineee", size) => 0;
lfs2_file_seek(&lfs2, &file, COUNT*size,
LFS2_SEEK_SET) => COUNT*size;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "\0\0\0\0\0\0\0\0\0\0\0", size) => 0;
lfs2_file_seek(&lfs2, &file, -((COUNT+SKIP)*size),
LFS2_SEEK_CUR) => LFS2_ERR_INVAL;
lfs2_file_tell(&lfs2, &file) => (COUNT+1)*size;
lfs2_file_seek(&lfs2, &file, -((COUNT+2*SKIP)*size),
LFS2_SEEK_END) => LFS2_ERR_INVAL;
lfs2_file_tell(&lfs2, &file) => (COUNT+1)*size;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # inline write and seek
define.SIZE = [2, 4, 128, 132]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "tinykitty",
LFS2_O_RDWR | LFS2_O_CREAT) => 0;
int j = 0;
int k = 0;
memcpy(buffer, "abcdefghijklmnopqrstuvwxyz", 26);
for (unsigned i = 0; i < SIZE; i++) {
lfs2_file_write(&lfs2, &file, &buffer[j++ % 26], 1) => 1;
lfs2_file_tell(&lfs2, &file) => i+1;
lfs2_file_size(&lfs2, &file) => i+1;
}
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_SET) => 0;
lfs2_file_tell(&lfs2, &file) => 0;
lfs2_file_size(&lfs2, &file) => SIZE;
for (unsigned i = 0; i < SIZE; i++) {
uint8_t c;
lfs2_file_read(&lfs2, &file, &c, 1) => 1;
c => buffer[k++ % 26];
}
lfs2_file_sync(&lfs2, &file) => 0;
lfs2_file_tell(&lfs2, &file) => SIZE;
lfs2_file_size(&lfs2, &file) => SIZE;
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_SET) => 0;
for (unsigned i = 0; i < SIZE; i++) {
lfs2_file_write(&lfs2, &file, &buffer[j++ % 26], 1) => 1;
lfs2_file_tell(&lfs2, &file) => i+1;
lfs2_file_size(&lfs2, &file) => SIZE;
lfs2_file_sync(&lfs2, &file) => 0;
lfs2_file_tell(&lfs2, &file) => i+1;
lfs2_file_size(&lfs2, &file) => SIZE;
if (i < SIZE-2) {
uint8_t c[3];
lfs2_file_seek(&lfs2, &file, -1, LFS2_SEEK_CUR) => i;
lfs2_file_read(&lfs2, &file, &c, 3) => 3;
lfs2_file_tell(&lfs2, &file) => i+3;
lfs2_file_size(&lfs2, &file) => SIZE;
lfs2_file_seek(&lfs2, &file, i+1, LFS2_SEEK_SET) => i+1;
lfs2_file_tell(&lfs2, &file) => i+1;
lfs2_file_size(&lfs2, &file) => SIZE;
}
}
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_SET) => 0;
lfs2_file_tell(&lfs2, &file) => 0;
lfs2_file_size(&lfs2, &file) => SIZE;
for (unsigned i = 0; i < SIZE; i++) {
uint8_t c;
lfs2_file_read(&lfs2, &file, &c, 1) => 1;
c => buffer[k++ % 26];
}
lfs2_file_sync(&lfs2, &file) => 0;
lfs2_file_tell(&lfs2, &file) => SIZE;
lfs2_file_size(&lfs2, &file) => SIZE;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # file seek and write with power-loss
# must be power-of-2 for quadratic probing to be exhaustive
define.COUNT = [4, 64, 128]
reentrant = true
code = '''
err = lfs2_mount(&lfs2, &cfg);
if (err) {
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
}
err = lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_RDONLY);
assert(!err || err == LFS2_ERR_NOENT);
if (!err) {
if (lfs2_file_size(&lfs2, &file) != 0) {
lfs2_file_size(&lfs2, &file) => 11*COUNT;
for (int j = 0; j < COUNT; j++) {
memset(buffer, 0, 11+1);
lfs2_file_read(&lfs2, &file, buffer, 11) => 11;
assert(memcmp(buffer, "kittycatcat", 11) == 0 ||
memcmp(buffer, "doggodogdog", 11) == 0);
}
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
if (lfs2_file_size(&lfs2, &file) == 0) {
for (int j = 0; j < COUNT; j++) {
strcpy((char*)buffer, "kittycatcat");
size = strlen((char*)buffer);
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
}
lfs2_file_close(&lfs2, &file) => 0;
strcpy((char*)buffer, "doggodogdog");
size = strlen((char*)buffer);
lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_RDWR) => 0;
lfs2_file_size(&lfs2, &file) => COUNT*size;
// seek and write using quadratic probing to touch all
// 11-byte words in the file
lfs2_off_t off = 0;
for (int j = 0; j < COUNT; j++) {
off = (5*off + 1) % COUNT;
lfs2_file_seek(&lfs2, &file, off*size, LFS2_SEEK_SET) => off*size;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
assert(memcmp(buffer, "kittycatcat", size) == 0 ||
memcmp(buffer, "doggodogdog", size) == 0);
if (memcmp(buffer, "doggodogdog", size) != 0) {
lfs2_file_seek(&lfs2, &file, off*size, LFS2_SEEK_SET) => off*size;
strcpy((char*)buffer, "doggodogdog");
lfs2_file_write(&lfs2, &file, buffer, size) => size;
lfs2_file_seek(&lfs2, &file, off*size, LFS2_SEEK_SET) => off*size;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
assert(memcmp(buffer, "doggodogdog", size) == 0);
lfs2_file_sync(&lfs2, &file) => 0;
lfs2_file_seek(&lfs2, &file, off*size, LFS2_SEEK_SET) => off*size;
lfs2_file_read(&lfs2, &file, buffer, size) => size;
assert(memcmp(buffer, "doggodogdog", size) == 0);
}
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_RDWR) => 0;
lfs2_file_size(&lfs2, &file) => COUNT*size;
for (int j = 0; j < COUNT; j++) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
assert(memcmp(buffer, "doggodogdog", size) == 0);
}
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''

View File

@ -0,0 +1,127 @@
[[case]] # simple formatting test
code = '''
lfs2_format(&lfs2, &cfg) => 0;
'''
[[case]] # mount/unmount
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # reentrant format
reentrant = true
code = '''
err = lfs2_mount(&lfs2, &cfg);
if (err) {
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
}
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # invalid mount
code = '''
lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT;
'''
[[case]] # expanding superblock
define.LFS2_BLOCK_CYCLES = [32, 33, 1]
define.N = [10, 100, 1000]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (int i = 0; i < N; i++) {
lfs2_file_open(&lfs2, &file, "dummy",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_stat(&lfs2, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS2_TYPE_REG);
lfs2_remove(&lfs2, "dummy") => 0;
}
lfs2_unmount(&lfs2) => 0;
// one last check after power-cycle
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "dummy",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_stat(&lfs2, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS2_TYPE_REG);
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # expanding superblock with power cycle
define.LFS2_BLOCK_CYCLES = [32, 33, 1]
define.N = [10, 100, 1000]
code = '''
lfs2_format(&lfs2, &cfg) => 0;
for (int i = 0; i < N; i++) {
lfs2_mount(&lfs2, &cfg) => 0;
// remove lingering dummy?
err = lfs2_stat(&lfs2, "dummy", &info);
assert(err == 0 || (err == LFS2_ERR_NOENT && i == 0));
if (!err) {
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS2_TYPE_REG);
lfs2_remove(&lfs2, "dummy") => 0;
}
lfs2_file_open(&lfs2, &file, "dummy",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_stat(&lfs2, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS2_TYPE_REG);
lfs2_unmount(&lfs2) => 0;
}
// one last check after power-cycle
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_stat(&lfs2, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS2_TYPE_REG);
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # reentrant expanding superblock
define.LFS2_BLOCK_CYCLES = [2, 1]
define.N = 24
reentrant = true
code = '''
err = lfs2_mount(&lfs2, &cfg);
if (err) {
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
}
for (int i = 0; i < N; i++) {
// remove lingering dummy?
err = lfs2_stat(&lfs2, "dummy", &info);
assert(err == 0 || (err == LFS2_ERR_NOENT && i == 0));
if (!err) {
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS2_TYPE_REG);
lfs2_remove(&lfs2, "dummy") => 0;
}
lfs2_file_open(&lfs2, &file, "dummy",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_stat(&lfs2, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS2_TYPE_REG);
}
lfs2_unmount(&lfs2) => 0;
// one last check after power-cycle
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_stat(&lfs2, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS2_TYPE_REG);
lfs2_unmount(&lfs2) => 0;
'''

View File

@ -0,0 +1,394 @@
[[case]] # simple truncate
define.MEDIUMSIZE = [32, 2048]
define.LARGESIZE = 8192
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "baldynoop",
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
strcpy((char*)buffer, "hair");
size = strlen((char*)buffer);
for (lfs2_off_t j = 0; j < LARGESIZE; j += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_size(&lfs2, &file) => LARGESIZE;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "baldynoop", LFS2_O_RDWR) => 0;
lfs2_file_size(&lfs2, &file) => LARGESIZE;
lfs2_file_truncate(&lfs2, &file, MEDIUMSIZE) => 0;
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "baldynoop", LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
size = strlen("hair");
for (lfs2_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "hair", size) => 0;
}
lfs2_file_read(&lfs2, &file, buffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # truncate and read
define.MEDIUMSIZE = [32, 2048]
define.LARGESIZE = 8192
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "baldyread",
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
strcpy((char*)buffer, "hair");
size = strlen((char*)buffer);
for (lfs2_off_t j = 0; j < LARGESIZE; j += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_size(&lfs2, &file) => LARGESIZE;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "baldyread", LFS2_O_RDWR) => 0;
lfs2_file_size(&lfs2, &file) => LARGESIZE;
lfs2_file_truncate(&lfs2, &file, MEDIUMSIZE) => 0;
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
size = strlen("hair");
for (lfs2_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "hair", size) => 0;
}
lfs2_file_read(&lfs2, &file, buffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "baldyread", LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
size = strlen("hair");
for (lfs2_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "hair", size) => 0;
}
lfs2_file_read(&lfs2, &file, buffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # write, truncate, and read
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "sequence",
LFS2_O_RDWR | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
size = lfs2_min(lfs2.cfg->cache_size, sizeof(buffer)/2);
lfs2_size_t qsize = size / 4;
uint8_t *wb = buffer;
uint8_t *rb = buffer + size;
for (lfs2_off_t j = 0; j < size; ++j) {
wb[j] = j;
}
/* Spread sequence over size */
lfs2_file_write(&lfs2, &file, wb, size) => size;
lfs2_file_size(&lfs2, &file) => size;
lfs2_file_tell(&lfs2, &file) => size;
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_SET) => 0;
lfs2_file_tell(&lfs2, &file) => 0;
/* Chop off the last quarter */
lfs2_size_t trunc = size - qsize;
lfs2_file_truncate(&lfs2, &file, trunc) => 0;
lfs2_file_tell(&lfs2, &file) => 0;
lfs2_file_size(&lfs2, &file) => trunc;
/* Read should produce first 3/4 */
lfs2_file_read(&lfs2, &file, rb, size) => trunc;
memcmp(rb, wb, trunc) => 0;
/* Move to 1/4 */
lfs2_file_size(&lfs2, &file) => trunc;
lfs2_file_seek(&lfs2, &file, qsize, LFS2_SEEK_SET) => qsize;
lfs2_file_tell(&lfs2, &file) => qsize;
/* Chop to 1/2 */
trunc -= qsize;
lfs2_file_truncate(&lfs2, &file, trunc) => 0;
lfs2_file_tell(&lfs2, &file) => qsize;
lfs2_file_size(&lfs2, &file) => trunc;
/* Read should produce second quarter */
lfs2_file_read(&lfs2, &file, rb, size) => trunc - qsize;
memcmp(rb, wb + qsize, trunc - qsize) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # truncate and write
define.MEDIUMSIZE = [32, 2048]
define.LARGESIZE = 8192
code = '''
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "baldywrite",
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
strcpy((char*)buffer, "hair");
size = strlen((char*)buffer);
for (lfs2_off_t j = 0; j < LARGESIZE; j += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_size(&lfs2, &file) => LARGESIZE;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "baldywrite", LFS2_O_RDWR) => 0;
lfs2_file_size(&lfs2, &file) => LARGESIZE;
lfs2_file_truncate(&lfs2, &file, MEDIUMSIZE) => 0;
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
strcpy((char*)buffer, "bald");
size = strlen((char*)buffer);
for (lfs2_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
lfs2_file_open(&lfs2, &file, "baldywrite", LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
size = strlen("bald");
for (lfs2_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "bald", size) => 0;
}
lfs2_file_read(&lfs2, &file, buffer, size) => 0;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # truncate write under powerloss
define.SMALLSIZE = [4, 512]
define.MEDIUMSIZE = [32, 1024]
define.LARGESIZE = 2048
reentrant = true
code = '''
err = lfs2_mount(&lfs2, &cfg);
if (err) {
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
}
err = lfs2_file_open(&lfs2, &file, "baldy", LFS2_O_RDONLY);
assert(!err || err == LFS2_ERR_NOENT);
if (!err) {
size = lfs2_file_size(&lfs2, &file);
assert(size == 0 ||
size == LARGESIZE ||
size == MEDIUMSIZE ||
size == SMALLSIZE);
for (lfs2_off_t j = 0; j < size; j += 4) {
lfs2_file_read(&lfs2, &file, buffer, 4) => 4;
assert(memcmp(buffer, "hair", 4) == 0 ||
memcmp(buffer, "bald", 4) == 0 ||
memcmp(buffer, "comb", 4) == 0);
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_file_open(&lfs2, &file, "baldy",
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
lfs2_file_size(&lfs2, &file) => 0;
strcpy((char*)buffer, "hair");
size = strlen((char*)buffer);
for (lfs2_off_t j = 0; j < LARGESIZE; j += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_size(&lfs2, &file) => LARGESIZE;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_file_open(&lfs2, &file, "baldy", LFS2_O_RDWR) => 0;
lfs2_file_size(&lfs2, &file) => LARGESIZE;
lfs2_file_truncate(&lfs2, &file, MEDIUMSIZE) => 0;
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
strcpy((char*)buffer, "bald");
size = strlen((char*)buffer);
for (lfs2_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_file_open(&lfs2, &file, "baldy", LFS2_O_RDWR) => 0;
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
lfs2_file_truncate(&lfs2, &file, SMALLSIZE) => 0;
lfs2_file_size(&lfs2, &file) => SMALLSIZE;
strcpy((char*)buffer, "comb");
size = strlen((char*)buffer);
for (lfs2_off_t j = 0; j < SMALLSIZE; j += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_size(&lfs2, &file) => SMALLSIZE;
lfs2_file_close(&lfs2, &file) => 0;
lfs2_unmount(&lfs2) => 0;
'''
[[case]] # more aggressive general truncation tests
define.CONFIG = 'range(6)'
define.SMALLSIZE = 32
define.MEDIUMSIZE = 2048
define.LARGESIZE = 8192
code = '''
#define COUNT 5
const struct {
lfs2_off_t startsizes[COUNT];
lfs2_off_t startseeks[COUNT];
lfs2_off_t hotsizes[COUNT];
lfs2_off_t coldsizes[COUNT];
} configs[] = {
// cold shrinking
{{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}},
// cold expanding
{{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}},
// warm shrinking truncate
{{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}},
// warm expanding truncate
{{ 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}},
// mid-file shrinking truncate
{{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}},
// mid-file expanding truncate
{{ 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}},
};
const lfs2_off_t *startsizes = configs[CONFIG].startsizes;
const lfs2_off_t *startseeks = configs[CONFIG].startseeks;
const lfs2_off_t *hotsizes = configs[CONFIG].hotsizes;
const lfs2_off_t *coldsizes = configs[CONFIG].coldsizes;
lfs2_format(&lfs2, &cfg) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (unsigned i = 0; i < COUNT; i++) {
sprintf(path, "hairyhead%d", i);
lfs2_file_open(&lfs2, &file, path,
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
strcpy((char*)buffer, "hair");
size = strlen((char*)buffer);
for (lfs2_off_t j = 0; j < startsizes[i]; j += size) {
lfs2_file_write(&lfs2, &file, buffer, size) => size;
}
lfs2_file_size(&lfs2, &file) => startsizes[i];
if (startseeks[i] != startsizes[i]) {
lfs2_file_seek(&lfs2, &file,
startseeks[i], LFS2_SEEK_SET) => startseeks[i];
}
lfs2_file_truncate(&lfs2, &file, hotsizes[i]) => 0;
lfs2_file_size(&lfs2, &file) => hotsizes[i];
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (unsigned i = 0; i < COUNT; i++) {
sprintf(path, "hairyhead%d", i);
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDWR) => 0;
lfs2_file_size(&lfs2, &file) => hotsizes[i];
size = strlen("hair");
lfs2_off_t j = 0;
for (; j < startsizes[i] && j < hotsizes[i]; j += size) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "hair", size) => 0;
}
for (; j < hotsizes[i]; j += size) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "\0\0\0\0", size) => 0;
}
lfs2_file_truncate(&lfs2, &file, coldsizes[i]) => 0;
lfs2_file_size(&lfs2, &file) => coldsizes[i];
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
lfs2_mount(&lfs2, &cfg) => 0;
for (unsigned i = 0; i < COUNT; i++) {
sprintf(path, "hairyhead%d", i);
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
lfs2_file_size(&lfs2, &file) => coldsizes[i];
size = strlen("hair");
lfs2_off_t j = 0;
for (; j < startsizes[i] && j < hotsizes[i] && j < coldsizes[i];
j += size) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "hair", size) => 0;
}
for (; j < coldsizes[i]; j += size) {
lfs2_file_read(&lfs2, &file, buffer, size) => size;
memcmp(buffer, "\0\0\0\0", size) => 0;
}
lfs2_file_close(&lfs2, &file) => 0;
}
lfs2_unmount(&lfs2) => 0;
'''

View File

@ -0,0 +1,55 @@
{
"name": "littlefs2",
"config": {
"block_size": {
"macro_name": "MBED_LFS2_BLOCK_SIZE",
"value": 512,
"help": "Size of a logical block. This does not impact ram consumption and may be larger than the physical erase block. If the physical erase block is larger, littlefs will use that instead. Larger values will be faster but waste more storage when files are not aligned to a block size."
},
"block_cycles": {
"macro_name": "MBED_LFS2_BLOCK_CYCLES",
"value": 1024,
"help": "Number of erase cycles before a block is forcefully evicted. Larger values are more efficient but cause less even wear distribution. 0 disables dynamic wear-leveling."
},
"cache_size": {
"macro_name": "MBED_LFS2_CACHE_SIZE",
"value": "64",
"help": "Size of read/program caches. Each file uses 1 cache, and littlefs allocates 2 caches for internal operations. Larger values should be faster but uses more RAM."
},
"lookahead_size": {
"macro_name": "MBED_LFS2_LOOKAHEAD_SIZE",
"value": 64,
"help": "Size of the lookahead buffer. A larger lookahead reduces the allocation scans and results in a faster filesystem but uses more RAM."
},
"intrinsics": {
"macro_name": "MBED_LFS2_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_LFS2_ENABLE_INFO",
"value": false,
"help": "Enables info logging, true = enabled, false = disabled, null = disabled only in release builds"
},
"enable_debug": {
"macro_name": "MBED_LFS2_ENABLE_DEBUG",
"value": null,
"help": "Enables debug logging, true = enabled, false = disabled, null = disabled only in release builds"
},
"enable_warn": {
"macro_name": "MBED_LFS2_ENABLE_WARN",
"value": null,
"help": "Enables warn logging, true = enabled, false = disabled, null = disabled only in release builds"
},
"enable_error": {
"macro_name": "MBED_LFS2_ENABLE_ERROR",
"value": null,
"help": "Enables error logging, true = enabled, false = disabled, null = disabled only in release builds"
},
"enable_assert": {
"macro_name": "MBED_LFS2_ENABLE_ASSERT",
"value": null,
"help": "Enables asserts, true = enabled, false = disabled, null = disabled only in release builds"
}
}
}