diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ee5acd8..111131d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -102,7 +102,7 @@ jobs: # sizes table i=0 j=0 - for c in "" readonly threadsafe migrate error-asserts + for c in "" readonly threadsafe multiversion migrate error-asserts do # per-config results c_or_default=${c:-default} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 826191a8..7c6c5e12 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -170,6 +170,27 @@ jobs: cp lfs2.data.csv sizes/${{matrix.arch}}-threadsafe.data.csv cp lfs2.stack.csv sizes/${{matrix.arch}}-threadsafe.stack.csv cp lfs2.structs.csv sizes/${{matrix.arch}}-threadsafe.structs.csv + - name: sizes-multiversion + run: | + make clean + CFLAGS="$CFLAGS \ + -DLFS2_NO_ASSERT \ + -DLFS2_NO_DEBUG \ + -DLFS2_NO_WARN \ + -DLFS2_NO_ERROR \ + -DLFS2_MULTIVERSION" \ + make lfs2.code.csv lfs2.data.csv lfs2.stack.csv lfs2.structs.csv + ./scripts/structs.py -u lfs2.structs.csv + ./scripts/summary.py lfs2.code.csv lfs2.data.csv lfs2.stack.csv \ + -bfunction \ + -fcode=code_size \ + -fdata=data_size \ + -fstack=stack_limit --max=stack_limit + mkdir -p sizes + cp lfs2.code.csv sizes/${{matrix.arch}}-multiversion.code.csv + cp lfs2.data.csv sizes/${{matrix.arch}}-multiversion.data.csv + cp lfs2.stack.csv sizes/${{matrix.arch}}-multiversion.stack.csv + cp lfs2.structs.csv sizes/${{matrix.arch}}-multiversion.structs.csv - name: sizes-migrate run: | make clean @@ -353,6 +374,42 @@ jobs: run: | CFLAGS="$CFLAGS -DLFS2_NO_INTRINSICS" make test + # run LFS2_MULTIVERSION tests + test-multiversion: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v2 + - name: install + run: | + # need a few things + sudo apt-get update -qq + sudo apt-get install -qq gcc python3 python3-pip + pip3 install toml + gcc --version + python3 --version + - name: test-multiversion + run: | + CFLAGS="$CFLAGS -DLFS2_MULTIVERSION" make test + + # run tests on the older version lfs22.0 + test-lfs22_0: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v2 + - name: install + run: | + # need a few things + sudo apt-get update -qq + sudo apt-get install -qq gcc python3 python3-pip + pip3 install toml + gcc --version + python3 --version + - name: test-lfs22_0 + run: | + CFLAGS="$CFLAGS -DLFS2_MULTIVERSION" \ + TESTFLAGS="$TESTFLAGS -DDISK_VERSION=0x00020000" \ + make test + # run under Valgrind to check for memory errors test-valgrind: runs-on: ubuntu-22.04 @@ -371,7 +428,8 @@ jobs: # on one geometry - name: test-valgrind run: | - TESTFLAGS="$TESTFLAGS --valgrind -Gdefault -Pnone" make test + TESTFLAGS="$TESTFLAGS --valgrind --context=1024 -Gdefault -Pnone" \ + make test # test that compilation is warning free under clang # run with Clang, mostly to check for Clang-specific warnings @@ -684,7 +742,7 @@ jobs: # sizes table i=0 j=0 - for c in "" readonly threadsafe migrate error-asserts + for c in "" readonly threadsafe multiversion migrate error-asserts do # per-config results c_or_default=${c:-default} diff --git a/README.md b/README.md index 421f0b07..b70e1d0f 100644 --- a/README.md +++ b/README.md @@ -250,6 +250,10 @@ License Identifiers that are here available: http://spdx.org/licenses/ MCUs. It offers static wear-leveling and power-resilience with only a fixed _O(|address|)_ pointer structure stored on each block and in RAM. +- [ChaN's FatFs] - A lightweight reimplementation of the infamous FAT filesystem + for microcontroller-scale devices. Due to limitations of FAT it can't provide + power-loss resilience, but it does allow easy interop with PCs. + - [chamelon] - A pure-OCaml implementation of (most of) littlefs, designed for use with the MirageOS library operating system project. It is interoperable with the reference implementation, with some caveats. @@ -266,6 +270,7 @@ License Identifiers that are here available: http://spdx.org/licenses/ [LittleFileSystem]: https://os.mbed.com/docs/mbed-os/latest/apis/littlefilesystem.html [SPIFFS]: https://github.com/pellepl/spiffs [Dhara]: https://github.com/dlbeer/dhara +[ChaN's FatFs]: http://elm-chan.org/fsw/ff/00index_e.html [littlefs-python]: https://pypi.org/project/littlefs-python/ [littlefs2-rust]: https://crates.io/crates/littlefs2 [chamelon]: https://github.com/yomimono/chamelon diff --git a/lfs2.c b/lfs2.c index ef476855..6efa8b4f 100644 --- a/lfs2.c +++ b/lfs2.c @@ -415,11 +415,11 @@ static inline uint8_t lfs2_gstate_getorphans(const lfs2_gstate_t *a) { static inline bool lfs2_gstate_hasmove(const lfs2_gstate_t *a) { return lfs2_tag_type1(a->tag); } +#endif static inline bool lfs2_gstate_needssuperblock(const lfs2_gstate_t *a) { return lfs2_tag_size(a->tag) >> 9; } -#endif static inline bool lfs2_gstate_hasmovehere(const lfs2_gstate_t *a, const lfs2_block_t *pair) { @@ -518,6 +518,28 @@ static void lfs2_mlist_append(lfs2_t *lfs2, struct lfs2_mlist *mlist) { lfs2->mlist = mlist; } +// some other filesystem operations +static uint32_t lfs2_fs_disk_version(lfs2_t *lfs2) { + (void)lfs2; +#ifdef LFS2_MULTIVERSION + if (lfs2->cfg->disk_version) { + return lfs2->cfg->disk_version; + } else +#endif + { + return LFS2_DISK_VERSION; + } +} + +static uint16_t lfs2_fs_disk_version_major(lfs2_t *lfs2) { + return 0xffff & (lfs2_fs_disk_version(lfs2) >> 16); + +} + +static uint16_t lfs2_fs_disk_version_minor(lfs2_t *lfs2) { + return 0xffff & (lfs2_fs_disk_version(lfs2) >> 0); +} + /// Internal operations predeclared here /// #ifndef LFS2_READONLY @@ -535,7 +557,6 @@ static int lfs2_file_outline(lfs2_t *lfs2, lfs2_file_t *file); static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file); static int lfs2_fs_deorphan(lfs2_t *lfs2, bool powerloss); -static void lfs2_fs_prepsuperblock(lfs2_t *lfs2, bool needssuperblock); static int lfs2_fs_preporphans(lfs2_t *lfs2, int8_t orphans); static void lfs2_fs_prepmove(lfs2_t *lfs2, uint16_t id, const lfs2_block_t pair[2]); @@ -546,6 +567,8 @@ static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t dir[2], static int lfs2_fs_forceconsistency(lfs2_t *lfs2); #endif +static void lfs2_fs_prepsuperblock(lfs2_t *lfs2, bool needssuperblock); + #ifdef LFS2_MIGRATE static int lfs21_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data); @@ -1110,7 +1133,8 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, // next commit not yet programmed? if (!lfs2_tag_isvalid(tag)) { - maybeerased = true; + // we only might be erased if the last tag was a crc + maybeerased = (lfs2_tag_type2(ptag) == LFS2_TYPE_CCRC); break; // out of range? } else if (off + lfs2_tag_dsize(tag) > lfs2->cfg->block_size) { @@ -1155,14 +1179,11 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, dir->tail[1] = temptail[1]; dir->split = tempsplit; - // reset crc + // reset crc, hasfcrc crc = 0xffffffff; continue; } - // fcrc is only valid when last tag was a crc - hasfcrc = false; - // crc the entry first, hopefully leaving it in the cache err = lfs2_bd_crc(lfs2, NULL, &lfs2->rcache, lfs2->cfg->block_size, @@ -1256,20 +1277,33 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, // did we end on a valid commit? we may have an erased block dir->erased = false; - if (maybeerased && hasfcrc && dir->off % lfs2->cfg->prog_size == 0) { - // check for an fcrc matching the next prog's erased state, if - // this failed most likely a previous prog was interrupted, we - // need a new erase - uint32_t fcrc_ = 0xffffffff; - int err = lfs2_bd_crc(lfs2, - NULL, &lfs2->rcache, lfs2->cfg->block_size, - dir->pair[0], dir->off, fcrc.size, &fcrc_); - if (err && err != LFS2_ERR_CORRUPT) { - return err; - } + if (maybeerased && dir->off % lfs2->cfg->prog_size == 0) { + #ifdef LFS2_MULTIVERSION + // note versions < lfs22.1 did not have fcrc tags, if + // we're < lfs22.1 treat missing fcrc as erased data + // + // we don't strictly need to do this, but otherwise writing + // to lfs22.0 disks becomes very inefficient + if (lfs2_fs_disk_version(lfs2) < 0x00020001) { + dir->erased = true; - // found beginning of erased part? - dir->erased = (fcrc_ == fcrc.crc); + } else + #endif + if (hasfcrc) { + // check for an fcrc matching the next prog's erased state, if + // this failed most likely a previous prog was interrupted, we + // need a new erase + uint32_t fcrc_ = 0xffffffff; + int err = lfs2_bd_crc(lfs2, + NULL, &lfs2->rcache, lfs2->cfg->block_size, + dir->pair[0], dir->off, fcrc.size, &fcrc_); + if (err && err != LFS2_ERR_CORRUPT) { + return err; + } + + // found beginning of erased part? + dir->erased = (fcrc_ == fcrc.crc); + } } // synthetic move @@ -1605,22 +1639,34 @@ static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) { return err; } - // find the expected fcrc, don't bother avoiding a reread - // of the eperturb, it should still be in our cache - struct lfs2_fcrc fcrc = {.size=lfs2->cfg->prog_size, .crc=0xffffffff}; - err = lfs2_bd_crc(lfs2, - NULL, &lfs2->rcache, lfs2->cfg->prog_size, - commit->block, noff, fcrc.size, &fcrc.crc); - if (err && err != LFS2_ERR_CORRUPT) { - return err; - } + #ifdef LFS2_MULTIVERSION + // unfortunately fcrcs break mdir fetching < lfs22.1, so only write + // these if we're a >= lfs22.1 filesystem + if (lfs2_fs_disk_version(lfs2) <= 0x00020000) { + // don't write fcrc + } else + #endif + { + // find the expected fcrc, don't bother avoiding a reread + // of the eperturb, it should still be in our cache + struct lfs2_fcrc fcrc = { + .size = lfs2->cfg->prog_size, + .crc = 0xffffffff + }; + err = lfs2_bd_crc(lfs2, + NULL, &lfs2->rcache, lfs2->cfg->prog_size, + commit->block, noff, fcrc.size, &fcrc.crc); + if (err && err != LFS2_ERR_CORRUPT) { + return err; + } - lfs2_fcrc_tole32(&fcrc); - err = lfs2_dir_commitattr(lfs2, commit, - LFS2_MKTAG(LFS2_TYPE_FCRC, 0x3ff, sizeof(struct lfs2_fcrc)), - &fcrc); - if (err) { - return err; + lfs2_fcrc_tole32(&fcrc); + err = lfs2_dir_commitattr(lfs2, commit, + LFS2_MKTAG(LFS2_TYPE_FCRC, 0x3ff, sizeof(struct lfs2_fcrc)), + &fcrc); + if (err) { + return err; + } } } @@ -4051,6 +4097,15 @@ static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2->cfg = cfg; int err = 0; +#ifdef LFS2_MULTIVERSION + // this driver only supports minor version < current minor version + LFS2_ASSERT(!lfs2->cfg->disk_version || ( + (0xffff & (lfs2->cfg->disk_version >> 16)) + == LFS2_DISK_VERSION_MAJOR + && (0xffff & (lfs2->cfg->disk_version >> 0)) + <= LFS2_DISK_VERSION_MINOR)); +#endif + // check that bool is a truthy-preserving type // // note the most common reason for this failure is a before-c99 compiler, @@ -4208,7 +4263,7 @@ static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { // write one superblock lfs2_superblock_t superblock = { - .version = LFS2_DISK_VERSION, + .version = lfs2_fs_disk_version(lfs2), .block_size = lfs2->cfg->block_size, .block_count = lfs2->cfg->block_count, .name_max = lfs2->name_max, @@ -4306,12 +4361,14 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { // check version uint16_t major_version = (0xffff & (superblock.version >> 16)); uint16_t minor_version = (0xffff & (superblock.version >> 0)); - if ((major_version != LFS2_DISK_VERSION_MAJOR || - minor_version > LFS2_DISK_VERSION_MINOR)) { + if (major_version != lfs2_fs_disk_version_major(lfs2) + || minor_version > lfs2_fs_disk_version_minor(lfs2)) { LFS2_ERROR("Invalid version " "v%"PRIu16".%"PRIu16" != v%"PRIu16".%"PRIu16, - major_version, minor_version, - LFS2_DISK_VERSION_MAJOR, LFS2_DISK_VERSION_MINOR); + major_version, + minor_version, + lfs2_fs_disk_version_major(lfs2), + lfs2_fs_disk_version_minor(lfs2)); err = LFS2_ERR_INVAL; goto cleanup; } @@ -4319,16 +4376,16 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { // found older minor version? set an in-device only bit in the // gstate so we know we need to rewrite the superblock before // the first write - if (minor_version < LFS2_DISK_VERSION_MINOR) { + if (minor_version < lfs2_fs_disk_version_minor(lfs2)) { LFS2_DEBUG("Found older minor version " "v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16, - major_version, minor_version, - LFS2_DISK_VERSION_MAJOR, LFS2_DISK_VERSION_MINOR); - #ifndef LFS2_READONLY + major_version, + minor_version, + lfs2_fs_disk_version_major(lfs2), + lfs2_fs_disk_version_minor(lfs2)); // note this bit is reserved on disk, so fetching more gstate // will not interfere here lfs2_fs_prepsuperblock(lfs2, true); - #endif } // check superblock configuration @@ -4421,6 +4478,42 @@ static int lfs2_rawunmount(lfs2_t *lfs2) { /// Filesystem filesystem operations /// +static int lfs2_fs_rawstat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) { + // if the superblock is up-to-date, we must be on the most recent + // minor version of littlefs + if (!lfs2_gstate_needssuperblock(&lfs2->gstate)) { + fsinfo->disk_version = lfs2_fs_disk_version(lfs2); + + // otherwise we need to read the minor version on disk + } else { + // fetch the superblock + lfs2_mdir_t dir; + int err = lfs2_dir_fetch(lfs2, &dir, lfs2->root); + if (err) { + return err; + } + + lfs2_superblock_t superblock; + lfs2_stag_t tag = lfs2_dir_get(lfs2, &dir, LFS2_MKTAG(0x7ff, 0x3ff, 0), + LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; + } + lfs2_superblock_fromle32(&superblock); + + // read the on-disk version + fsinfo->disk_version = superblock.version; + } + + // other on-disk configuration, we cache all of these for internal use + fsinfo->name_max = lfs2->name_max; + fsinfo->file_max = lfs2->file_max; + fsinfo->attr_max = lfs2->attr_max; + + return 0; +} + int lfs2_fs_rawtraverse(lfs2_t *lfs2, int (*cb)(void *data, lfs2_block_t block), void *data, bool includeorphans) { @@ -4631,12 +4724,10 @@ static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t pair[2], } #endif -#ifndef LFS2_READONLY static void lfs2_fs_prepsuperblock(lfs2_t *lfs2, bool needssuperblock) { lfs2->gstate.tag = (lfs2->gstate.tag & ~LFS2_MKTAG(0, 0, 0x200)) | (uint32_t)needssuperblock << 9; } -#endif #ifndef LFS2_READONLY static int lfs2_fs_preporphans(lfs2_t *lfs2, int8_t orphans) { @@ -4678,7 +4769,7 @@ static int lfs2_fs_desuperblock(lfs2_t *lfs2) { // write a new superblock lfs2_superblock_t superblock = { - .version = LFS2_DISK_VERSION, + .version = lfs2_fs_disk_version(lfs2), .block_size = lfs2->cfg->block_size, .block_count = lfs2->cfg->block_count, .name_max = lfs2->name_max, @@ -4934,6 +5025,7 @@ static lfs2_ssize_t lfs2_fs_rawsize(lfs2_t *lfs2) { return size; } + #ifdef LFS2_MIGRATE ////// Migration from littelfs v1 below this ////// @@ -6053,6 +6145,20 @@ int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir) { return err; } +int lfs2_fs_stat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_fs_stat(%p, %p)", (void*)lfs2, (void*)fsinfo); + + err = lfs2_fs_rawstat(lfs2, fsinfo); + + LFS2_TRACE("lfs2_fs_stat -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} + lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2) { int err = LFS2_LOCK(lfs2->cfg); if (err) { diff --git a/lfs2.h b/lfs2.h index b58cc22f..cb660f15 100644 --- a/lfs2.h +++ b/lfs2.h @@ -21,7 +21,7 @@ extern "C" // Software library version // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS2_VERSION 0x00020006 +#define LFS2_VERSION 0x00020007 #define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16)) #define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0)) @@ -263,6 +263,14 @@ struct lfs2_config { // can help bound the metadata compaction time. Must be <= block_size. // Defaults to block_size when zero. lfs2_size_t metadata_max; + +#ifdef LFS2_MULTIVERSION + // On-disk version to use when writing in the form of 16-bit major version + // + 16-bit minor version. This limiting metadata to what is supported by + // older minor versions. Note that some features will be lost. Defaults to + // to the most recent minor version when zero. + uint32_t disk_version; +#endif }; // File info structure @@ -280,6 +288,21 @@ struct lfs2_info { char name[LFS2_NAME_MAX+1]; }; +// Filesystem info structure +struct lfs2_fsinfo { + // On-disk version. + uint32_t disk_version; + + // Upper limit on the length of file names in bytes. + lfs2_size_t name_max; + + // Upper limit on the size of files in bytes. + lfs2_size_t file_max; + + // Upper limit on the size of custom attributes in bytes. + lfs2_size_t attr_max; +}; + // Custom attribute structure, used to describe custom attributes // committed atomically during file writes. struct lfs2_attr { @@ -659,6 +682,12 @@ int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir); /// Filesystem-level filesystem operations +// Find on-disk info about the filesystem +// +// Fills out the fsinfo structure based on the filesystem found on-disk. +// Returns a negative error code on failure. +int lfs2_fs_stat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo); + // Finds the current size of the filesystem // // Note: Result is best effort. If files share COW structures, the returned diff --git a/runners/test_runner.c b/runners/test_runner.c index 2efcadbc..10a2cc77 100644 --- a/runners/test_runner.c +++ b/runners/test_runner.c @@ -1346,6 +1346,9 @@ static void run_powerloss_none( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + #ifdef LFS2_MULTIVERSION + .disk_version = DISK_VERSION, + #endif }; struct lfs2_emubd_config bdcfg = { @@ -1415,6 +1418,9 @@ static void run_powerloss_linear( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + #ifdef LFS2_MULTIVERSION + .disk_version = DISK_VERSION, + #endif }; struct lfs2_emubd_config bdcfg = { @@ -1501,6 +1507,9 @@ static void run_powerloss_log( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + #ifdef LFS2_MULTIVERSION + .disk_version = DISK_VERSION, + #endif }; struct lfs2_emubd_config bdcfg = { @@ -1585,6 +1594,9 @@ static void run_powerloss_cycles( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + #ifdef LFS2_MULTIVERSION + .disk_version = DISK_VERSION, + #endif }; struct lfs2_emubd_config bdcfg = { @@ -1767,6 +1779,9 @@ static void run_powerloss_exhaustive( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + #ifdef LFS2_MULTIVERSION + .disk_version = DISK_VERSION, + #endif }; struct lfs2_emubd_config bdcfg = { diff --git a/runners/test_runner.h b/runners/test_runner.h index 61cf4d10..60efea92 100644 --- a/runners/test_runner.h +++ b/runners/test_runner.h @@ -91,6 +91,7 @@ intmax_t test_define(size_t define); #define ERASE_CYCLES_i 8 #define BADBLOCK_BEHAVIOR_i 9 #define POWERLOSS_BEHAVIOR_i 10 +#define DISK_VERSION_i 11 #define READ_SIZE TEST_DEFINE(READ_SIZE_i) #define PROG_SIZE TEST_DEFINE(PROG_SIZE_i) @@ -103,6 +104,7 @@ intmax_t test_define(size_t define); #define ERASE_CYCLES TEST_DEFINE(ERASE_CYCLES_i) #define BADBLOCK_BEHAVIOR TEST_DEFINE(BADBLOCK_BEHAVIOR_i) #define POWERLOSS_BEHAVIOR TEST_DEFINE(POWERLOSS_BEHAVIOR_i) +#define DISK_VERSION TEST_DEFINE(DISK_VERSION_i) #define TEST_IMPLICIT_DEFINES \ TEST_DEF(READ_SIZE, PROG_SIZE) \ @@ -115,9 +117,10 @@ intmax_t test_define(size_t define); TEST_DEF(ERASE_VALUE, 0xff) \ TEST_DEF(ERASE_CYCLES, 0) \ TEST_DEF(BADBLOCK_BEHAVIOR, LFS2_EMUBD_BADBLOCK_PROGERROR) \ - TEST_DEF(POWERLOSS_BEHAVIOR, LFS2_EMUBD_POWERLOSS_NOOP) + TEST_DEF(POWERLOSS_BEHAVIOR, LFS2_EMUBD_POWERLOSS_NOOP) \ + TEST_DEF(DISK_VERSION, 0) -#define TEST_IMPLICIT_DEFINE_COUNT 11 +#define TEST_IMPLICIT_DEFINE_COUNT 12 #define TEST_GEOMETRY_DEFINE_COUNT 4 diff --git a/tests/test_compat.toml b/tests/test_compat.toml index 552b6c00..f7806d7a 100644 --- a/tests/test_compat.toml +++ b/tests/test_compat.toml @@ -22,14 +22,16 @@ code = ''' #define STRINGIZE_(x) #x #include STRINGIZE(LFS2P) #else -#define LFS2P_VERSION LFS2_VERSION -#define LFS2P_VERSION_MAJOR LFS2_VERSION_MAJOR -#define LFS2P_VERSION_MINOR LFS2_VERSION_MINOR +#define LFS2P_DISK_VERSION LFS2_DISK_VERSION +#define LFS2P_DISK_VERSION_MAJOR LFS2_DISK_VERSION_MAJOR +#define LFS2P_DISK_VERSION_MINOR LFS2_DISK_VERSION_MINOR #define lfs2p_t lfs2_t #define lfs2p_config lfs2_config #define lfs2p_format lfs2_format #define lfs2p_mount lfs2_mount #define lfs2p_unmount lfs2_unmount +#define lfs2p_fsinfo lfs2_fsinfo +#define lfs2p_fs_stat lfs2_fs_stat #define lfs2p_dir_t lfs2_dir_t #define lfs2p_info lfs2_info #define LFS2P_TYPE_REG LFS2_TYPE_REG @@ -58,7 +60,10 @@ code = ''' # test we can mount in a new version [cases.test_compat_forward_mount] -if = 'LFS2_VERSION_MAJOR == LFS2P_VERSION_MAJOR' +if = ''' + LFS2_DISK_VERSION_MAJOR == LFS2P_DISK_VERSION_MAJOR + && DISK_VERSION == 0 +''' code = ''' // create the previous version struct lfs2p_config cfgp; @@ -74,13 +79,22 @@ code = ''' // now test the new mount lfs2_t lfs2; lfs2_mount(&lfs2, cfg) => 0; + + // we should be able to read the version using lfs2_fs_stat + struct lfs2_fsinfo fsinfo; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2P_DISK_VERSION); + lfs2_unmount(&lfs2) => 0; ''' # test we can read dirs in a new version [cases.test_compat_forward_read_dirs] defines.COUNT = 5 -if = 'LFS2_VERSION_MAJOR == LFS2P_VERSION_MAJOR' +if = ''' + LFS2_DISK_VERSION_MAJOR == LFS2P_DISK_VERSION_MAJOR + && DISK_VERSION == 0 +''' code = ''' // create the previous version struct lfs2p_config cfgp; @@ -102,6 +116,11 @@ code = ''' lfs2_t lfs2; lfs2_mount(&lfs2, cfg) => 0; + // we should be able to read the version using lfs2_fs_stat + struct lfs2_fsinfo fsinfo; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2P_DISK_VERSION); + // can we list the directories? lfs2_dir_t dir; lfs2_dir_open(&lfs2, &dir, "/") => 0; @@ -132,7 +151,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 -if = 'LFS2_VERSION_MAJOR == LFS2P_VERSION_MAJOR' +if = ''' + LFS2_DISK_VERSION_MAJOR == LFS2P_DISK_VERSION_MAJOR + && DISK_VERSION == 0 +''' code = ''' // create the previous version struct lfs2p_config cfgp; @@ -166,6 +188,11 @@ code = ''' lfs2_t lfs2; lfs2_mount(&lfs2, cfg) => 0; + // we should be able to read the version using lfs2_fs_stat + struct lfs2_fsinfo fsinfo; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2P_DISK_VERSION); + // can we list the files? lfs2_dir_t dir; lfs2_dir_open(&lfs2, &dir, "/") => 0; @@ -214,7 +241,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 -if = 'LFS2_VERSION_MAJOR == LFS2P_VERSION_MAJOR' +if = ''' + LFS2_DISK_VERSION_MAJOR == LFS2P_DISK_VERSION_MAJOR + && DISK_VERSION == 0 +''' code = ''' // create the previous version struct lfs2p_config cfgp; @@ -251,6 +281,11 @@ code = ''' lfs2_t lfs2; lfs2_mount(&lfs2, cfg) => 0; + // we should be able to read the version using lfs2_fs_stat + struct lfs2_fsinfo fsinfo; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2P_DISK_VERSION); + // can we list the directories? lfs2_dir_t dir; lfs2_dir_open(&lfs2, &dir, "/") => 0; @@ -321,7 +356,10 @@ code = ''' # test we can write dirs in a new version [cases.test_compat_forward_write_dirs] defines.COUNT = 10 -if = 'LFS2_VERSION_MAJOR == LFS2P_VERSION_MAJOR' +if = ''' + LFS2_DISK_VERSION_MAJOR == LFS2P_DISK_VERSION_MAJOR + && DISK_VERSION == 0 +''' code = ''' // create the previous version struct lfs2p_config cfgp; @@ -343,6 +381,11 @@ code = ''' lfs2_t lfs2; lfs2_mount(&lfs2, cfg) => 0; + // we should be able to read the version using lfs2_fs_stat + struct lfs2_fsinfo fsinfo; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2P_DISK_VERSION); + // write another COUNT/2 dirs for (lfs2_size_t i = COUNT/2; i < COUNT; i++) { char name[8]; @@ -380,7 +423,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 -if = 'LFS2_VERSION_MAJOR == LFS2P_VERSION_MAJOR' +if = ''' + LFS2_DISK_VERSION_MAJOR == LFS2P_DISK_VERSION_MAJOR + && DISK_VERSION == 0 +''' code = ''' // create the previous version struct lfs2p_config cfgp; @@ -420,6 +466,11 @@ code = ''' lfs2_t lfs2; lfs2_mount(&lfs2, cfg) => 0; + // we should be able to read the version using lfs2_fs_stat + struct lfs2_fsinfo fsinfo; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2P_DISK_VERSION); + // write half COUNT files prng = 42; for (lfs2_size_t i = 0; i < COUNT; i++) { @@ -494,7 +545,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 -if = 'LFS2_VERSION_MAJOR == LFS2P_VERSION_MAJOR' +if = ''' + LFS2_DISK_VERSION_MAJOR == LFS2P_DISK_VERSION_MAJOR + && DISK_VERSION == 0 +''' code = ''' // create the previous version struct lfs2p_config cfgp; @@ -537,6 +591,11 @@ code = ''' lfs2_t lfs2; lfs2_mount(&lfs2, cfg) => 0; + // we should be able to read the version using lfs2_fs_stat + struct lfs2_fsinfo fsinfo; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2P_DISK_VERSION); + // write half COUNT files prng = 42; for (lfs2_size_t i = 0; i < COUNT; i++) { @@ -636,7 +695,10 @@ code = ''' # test we can mount in an old version [cases.test_compat_backward_mount] -if = 'LFS2_VERSION == LFS2P_VERSION' +if = ''' + LFS2_DISK_VERSION == LFS2P_DISK_VERSION + && DISK_VERSION == 0 +''' code = ''' // create the new version lfs2_t lfs2; @@ -651,13 +713,17 @@ code = ''' memcpy(&cfgp, cfg, sizeof(cfgp)); lfs2p_t lfs2p; lfs2p_mount(&lfs2p, &cfgp) => 0; + lfs2p_unmount(&lfs2p) => 0; ''' # test we can read dirs in an old version [cases.test_compat_backward_read_dirs] defines.COUNT = 5 -if = 'LFS2_VERSION == LFS2P_VERSION' +if = ''' + LFS2_DISK_VERSION == LFS2P_DISK_VERSION + && DISK_VERSION == 0 +''' code = ''' // create the new version lfs2_t lfs2; @@ -709,7 +775,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 -if = 'LFS2_VERSION == LFS2P_VERSION' +if = ''' + LFS2_DISK_VERSION == LFS2P_DISK_VERSION + && DISK_VERSION == 0 +''' code = ''' // create the new version lfs2_t lfs2; @@ -791,7 +860,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 -if = 'LFS2_VERSION == LFS2P_VERSION' +if = ''' + LFS2_DISK_VERSION == LFS2P_DISK_VERSION + && DISK_VERSION == 0 +''' code = ''' // create the new version lfs2_t lfs2; @@ -898,7 +970,10 @@ code = ''' # test we can write dirs in an old version [cases.test_compat_backward_write_dirs] defines.COUNT = 10 -if = 'LFS2_VERSION == LFS2P_VERSION' +if = ''' + LFS2_DISK_VERSION == LFS2P_DISK_VERSION + && DISK_VERSION == 0 +''' code = ''' // create the new version lfs2_t lfs2; @@ -957,7 +1032,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 -if = 'LFS2_VERSION == LFS2P_VERSION' +if = ''' + LFS2_DISK_VERSION == LFS2P_DISK_VERSION + && DISK_VERSION == 0 +''' code = ''' // create the previous version lfs2_t lfs2; @@ -1071,7 +1149,10 @@ code = ''' defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 -if = 'LFS2_VERSION == LFS2P_VERSION' +if = ''' + LFS2_DISK_VERSION == LFS2P_DISK_VERSION + && DISK_VERSION == 0 +''' code = ''' // create the previous version lfs2_t lfs2; @@ -1280,7 +1361,10 @@ code = ''' # test that we correctly bump the minor version [cases.test_compat_minor_bump] in = 'lfs2.c' -if = 'LFS2_DISK_VERSION_MINOR > 0' +if = ''' + LFS2_DISK_VERSION_MINOR > 0 + && DISK_VERSION == 0 +''' code = ''' // create a superblock lfs2_t lfs2; @@ -1316,45 +1400,54 @@ code = ''' // mount should still work lfs2_mount(&lfs2, cfg) => 0; + + struct lfs2_fsinfo fsinfo; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2_DISK_VERSION-1); + lfs2_file_open(&lfs2, &file, "test", LFS2_O_RDONLY) => 0; uint8_t buffer[8]; lfs2_file_read(&lfs2, &file, buffer, 8) => 8; assert(memcmp(buffer, "testtest", 8) == 0); lfs2_file_close(&lfs2, &file) => 0; + + // minor version should be unchanged + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2_DISK_VERSION-1); + lfs2_unmount(&lfs2) => 0; // if we write, we need to bump the minor version lfs2_mount(&lfs2, cfg) => 0; + + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2_DISK_VERSION-1); + lfs2_file_open(&lfs2, &file, "test", LFS2_O_WRONLY | LFS2_O_TRUNC) => 0; lfs2_file_write(&lfs2, &file, "teeeeest", 8) => 8; lfs2_file_close(&lfs2, &file) => 0; - // minor version should have changed - 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_INLINESTRUCT, 0, sizeof(superblock)), - &superblock) - => LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)); - lfs2_superblock_fromle32(&superblock); - assert((superblock.version >> 16) & 0xffff == LFS2_DISK_VERSION_MAJOR); - assert((superblock.version >> 0) & 0xffff == LFS2_DISK_VERSION_MINOR); + // minor version should be changed + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2_DISK_VERSION); + lfs2_unmount(&lfs2) => 0; // and of course mount should still work lfs2_mount(&lfs2, cfg) => 0; + + // minor version should have changed + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2_DISK_VERSION); + lfs2_file_open(&lfs2, &file, "test", LFS2_O_RDONLY) => 0; lfs2_file_read(&lfs2, &file, buffer, 8) => 8; assert(memcmp(buffer, "teeeeest", 8) == 0); lfs2_file_close(&lfs2, &file) => 0; - // minor version should have changed - 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_INLINESTRUCT, 0, sizeof(superblock)), - &superblock) - => LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)); - lfs2_superblock_fromle32(&superblock); - assert((superblock.version >> 16) & 0xffff == LFS2_DISK_VERSION_MAJOR); - assert((superblock.version >> 0) & 0xffff == LFS2_DISK_VERSION_MINOR); + // yep, still changed + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2_DISK_VERSION); + lfs2_unmount(&lfs2) => 0; ''' diff --git a/tests/test_powerloss.toml b/tests/test_powerloss.toml index 0491b9c3..a0a5e175 100644 --- a/tests/test_powerloss.toml +++ b/tests/test_powerloss.toml @@ -90,7 +90,10 @@ code = ''' # partial prog, may not be byte in order! [cases.test_powerloss_partial_prog] -if = "PROG_SIZE < BLOCK_SIZE" +if = ''' + PROG_SIZE < BLOCK_SIZE + && (DISK_VERSION == 0 || DISK_VERSION >= 0x00020001) +''' defines.BYTE_OFF = ["0", "PROG_SIZE-1", "PROG_SIZE/2"] defines.BYTE_VALUE = [0x33, 0xcc] in = "lfs2.c" diff --git a/tests/test_superblocks.toml b/tests/test_superblocks.toml index 9bab9c45..5d4923ae 100644 --- a/tests/test_superblocks.toml +++ b/tests/test_superblocks.toml @@ -34,6 +34,54 @@ code = ''' lfs2_mount(&lfs2, cfg) => LFS2_ERR_CORRUPT; ''' +# test we can read superblock info through lfs2_fs_stat +[cases.test_superblocks_stat] +if = 'DISK_VERSION == 0' +code = ''' + lfs2_t lfs2; + lfs2_format(&lfs2, cfg) => 0; + + // test we can mount and read fsinfo + lfs2_mount(&lfs2, cfg) => 0; + + struct lfs2_fsinfo fsinfo; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2_DISK_VERSION); + assert(fsinfo.name_max == LFS2_NAME_MAX); + assert(fsinfo.file_max == LFS2_FILE_MAX); + assert(fsinfo.attr_max == LFS2_ATTR_MAX); + + lfs2_unmount(&lfs2) => 0; +''' + +[cases.test_superblocks_stat_tweaked] +if = 'DISK_VERSION == 0' +defines.TWEAKED_NAME_MAX = 63 +defines.TWEAKED_FILE_MAX = '(1 << 16)-1' +defines.TWEAKED_ATTR_MAX = 512 +code = ''' + // create filesystem with tweaked params + struct lfs2_config tweaked_cfg = *cfg; + tweaked_cfg.name_max = TWEAKED_NAME_MAX; + tweaked_cfg.file_max = TWEAKED_FILE_MAX; + tweaked_cfg.attr_max = TWEAKED_ATTR_MAX; + + lfs2_t lfs2; + lfs2_format(&lfs2, &tweaked_cfg) => 0; + + // test we can mount and read these params with the original config + lfs2_mount(&lfs2, cfg) => 0; + + struct lfs2_fsinfo fsinfo; + lfs2_fs_stat(&lfs2, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS2_DISK_VERSION); + assert(fsinfo.name_max == TWEAKED_NAME_MAX); + assert(fsinfo.file_max == TWEAKED_FILE_MAX); + assert(fsinfo.attr_max == TWEAKED_ATTR_MAX); + + lfs2_unmount(&lfs2) => 0; +''' + # expanding superblock [cases.test_superblocks_expand] defines.BLOCK_CYCLES = [32, 33, 1]