diff --git a/README.md b/README.md index b70e1d0f..3cad7512 100644 --- a/README.md +++ b/README.md @@ -258,6 +258,9 @@ License Identifiers that are here available: http://spdx.org/licenses/ use with the MirageOS library operating system project. It is interoperable with the reference implementation, with some caveats. +- [nim-littlefs] - A Nim wrapper and API for littlefs. Includes a fuse + implementation based on [littlefs-fuse] + [BSD-3-Clause]: https://spdx.org/licenses/BSD-3-Clause.html [littlefs-disk-img-viewer]: https://github.com/tniessen/littlefs-disk-img-viewer [littlefs-fuse]: https://github.com/geky/littlefs-fuse @@ -274,3 +277,4 @@ License Identifiers that are here available: http://spdx.org/licenses/ [littlefs-python]: https://pypi.org/project/littlefs-python/ [littlefs2-rust]: https://crates.io/crates/littlefs2 [chamelon]: https://github.com/yomimono/chamelon +[nim-littlefs]: https://github.com/Graveflo/nim-littlefs diff --git a/bd/lfs2_emubd.c b/bd/lfs2_emubd.c index 463f236e..bef73cd6 100644 --- a/bd/lfs2_emubd.c +++ b/bd/lfs2_emubd.c @@ -129,6 +129,8 @@ int lfs2_emubd_create(const struct lfs2_config *cfg, bd->proged = 0; bd->erased = 0; bd->power_cycles = bd->cfg->power_cycles; + bd->ooo_block = -1; + bd->ooo_data = NULL; bd->disk = NULL; if (bd->cfg->disk_path) { @@ -195,6 +197,7 @@ int lfs2_emubd_destroy(const struct lfs2_config *cfg) { free(bd->blocks); // clean up other resources + lfs2_emubd_decblock(bd->ooo_data); if (bd->disk) { bd->disk->rc -= 1; if (bd->disk->rc == 0) { @@ -209,6 +212,75 @@ int lfs2_emubd_destroy(const struct lfs2_config *cfg) { } +// powerloss hook +static int lfs2_emubd_powerloss(const struct lfs2_config *cfg) { + lfs2_emubd_t *bd = cfg->context; + + // emulate out-of-order writes? + lfs2_emubd_block_t *ooo_data = NULL; + if (bd->cfg->powerloss_behavior == LFS2_EMUBD_POWERLOSS_OOO + && bd->ooo_block != -1) { + // since writes between syncs are allowed to be out-of-order, it + // shouldn't hurt to restore the first write on powerloss, right? + ooo_data = bd->blocks[bd->ooo_block]; + bd->blocks[bd->ooo_block] = lfs2_emubd_incblock(bd->ooo_data); + + // mirror to disk file? + if (bd->disk + && (bd->blocks[bd->ooo_block] + || bd->cfg->erase_value != -1)) { + off_t res1 = lseek(bd->disk->fd, + (off_t)bd->ooo_block*bd->cfg->erase_size, + SEEK_SET); + if (res1 < 0) { + return -errno; + } + + ssize_t res2 = write(bd->disk->fd, + (bd->blocks[bd->ooo_block]) + ? bd->blocks[bd->ooo_block]->data + : bd->disk->scratch, + bd->cfg->erase_size); + if (res2 < 0) { + return -errno; + } + } + } + + // simulate power loss + bd->cfg->powerloss_cb(bd->cfg->powerloss_data); + + // if we continue, undo out-of-order write emulation + if (bd->cfg->powerloss_behavior == LFS2_EMUBD_POWERLOSS_OOO + && bd->ooo_block != -1) { + lfs2_emubd_decblock(bd->blocks[bd->ooo_block]); + bd->blocks[bd->ooo_block] = ooo_data; + + // mirror to disk file? + if (bd->disk + && (bd->blocks[bd->ooo_block] + || bd->cfg->erase_value != -1)) { + off_t res1 = lseek(bd->disk->fd, + (off_t)bd->ooo_block*bd->cfg->erase_size, + SEEK_SET); + if (res1 < 0) { + return -errno; + } + + ssize_t res2 = write(bd->disk->fd, + (bd->blocks[bd->ooo_block]) + ? bd->blocks[bd->ooo_block]->data + : bd->disk->scratch, + bd->cfg->erase_size); + if (res2 < 0) { + return -errno; + } + } + } + + return 0; +} + // block device API @@ -344,8 +416,11 @@ int lfs2_emubd_prog(const struct lfs2_config *cfg, lfs2_block_t block, if (bd->power_cycles > 0) { bd->power_cycles -= 1; if (bd->power_cycles == 0) { - // simulate power loss - bd->cfg->powerloss_cb(bd->cfg->powerloss_data); + int err = lfs2_emubd_powerloss(cfg); + if (err) { + LFS2_EMUBD_TRACE("lfs2_emubd_prog -> %d", err); + return err; + } } } @@ -361,10 +436,17 @@ int lfs2_emubd_erase(const struct lfs2_config *cfg, lfs2_block_t block) { // check if erase is valid LFS2_ASSERT(block < bd->cfg->erase_count); + // emulate out-of-order writes? save first write + if (bd->cfg->powerloss_behavior == LFS2_EMUBD_POWERLOSS_OOO + && bd->ooo_block == -1) { + bd->ooo_block = block; + bd->ooo_data = lfs2_emubd_incblock(bd->blocks[block]); + } + // get the block lfs2_emubd_block_t *b = lfs2_emubd_mutblock(cfg, &bd->blocks[block]); if (!b) { - LFS2_EMUBD_TRACE("lfs2_emubd_prog -> %d", LFS2_ERR_NOMEM); + LFS2_EMUBD_TRACE("lfs2_emubd_erase -> %d", LFS2_ERR_NOMEM); return LFS2_ERR_NOMEM; } @@ -430,8 +512,11 @@ int lfs2_emubd_erase(const struct lfs2_config *cfg, lfs2_block_t block) { if (bd->power_cycles > 0) { bd->power_cycles -= 1; if (bd->power_cycles == 0) { - // simulate power loss - bd->cfg->powerloss_cb(bd->cfg->powerloss_data); + int err = lfs2_emubd_powerloss(cfg); + if (err) { + LFS2_EMUBD_TRACE("lfs2_emubd_erase -> %d", err); + return err; + } } } @@ -441,14 +526,21 @@ int lfs2_emubd_erase(const struct lfs2_config *cfg, lfs2_block_t block) { int lfs2_emubd_sync(const struct lfs2_config *cfg) { LFS2_EMUBD_TRACE("lfs2_emubd_sync(%p)", (void*)cfg); + lfs2_emubd_t *bd = cfg->context; - // do nothing - (void)cfg; + // emulate out-of-order writes? reset first write, writes + // cannot be out-of-order across sync + if (bd->cfg->powerloss_behavior == LFS2_EMUBD_POWERLOSS_OOO) { + lfs2_emubd_decblock(bd->ooo_data); + bd->ooo_block = -1; + bd->ooo_data = NULL; + } LFS2_EMUBD_TRACE("lfs2_emubd_sync -> %d", 0); return 0; } + /// Additional extended API for driving test features /// static int lfs2_emubd_crc_(const struct lfs2_config *cfg, @@ -633,6 +725,8 @@ int lfs2_emubd_copy(const struct lfs2_config *cfg, lfs2_emubd_t *copy) { copy->proged = bd->proged; copy->erased = bd->erased; copy->power_cycles = bd->power_cycles; + copy->ooo_block = bd->ooo_block; + copy->ooo_data = lfs2_emubd_incblock(bd->ooo_data); copy->disk = bd->disk; if (copy->disk) { copy->disk->rc += 1; diff --git a/bd/lfs2_emubd.h b/bd/lfs2_emubd.h index 9edab15b..e4964bce 100644 --- a/bd/lfs2_emubd.h +++ b/bd/lfs2_emubd.h @@ -36,17 +36,18 @@ extern "C" // Not that read-noop is not allowed. Read _must_ return a consistent (but // may be arbitrary) value on every read. typedef enum lfs2_emubd_badblock_behavior { - LFS2_EMUBD_BADBLOCK_PROGERROR, - LFS2_EMUBD_BADBLOCK_ERASEERROR, - LFS2_EMUBD_BADBLOCK_READERROR, - LFS2_EMUBD_BADBLOCK_PROGNOOP, - LFS2_EMUBD_BADBLOCK_ERASENOOP, + LFS2_EMUBD_BADBLOCK_PROGERROR = 0, // Error on prog + LFS2_EMUBD_BADBLOCK_ERASEERROR = 1, // Error on erase + LFS2_EMUBD_BADBLOCK_READERROR = 2, // Error on read + LFS2_EMUBD_BADBLOCK_PROGNOOP = 3, // Prog does nothing silently + LFS2_EMUBD_BADBLOCK_ERASENOOP = 4, // Erase does nothing silently } lfs2_emubd_badblock_behavior_t; // Mode determining how power-loss behaves during testing. For now this // only supports a noop behavior, leaving the data on-disk untouched. typedef enum lfs2_emubd_powerloss_behavior { - LFS2_EMUBD_POWERLOSS_NOOP, + LFS2_EMUBD_POWERLOSS_NOOP = 0, // Progs are atomic + LFS2_EMUBD_POWERLOSS_OOO = 1, // Blocks are written out-of-order } lfs2_emubd_powerloss_behavior_t; // Type for measuring read/program/erase operations @@ -152,6 +153,8 @@ typedef struct lfs2_emubd { lfs2_emubd_io_t proged; lfs2_emubd_io_t erased; lfs2_emubd_powercycles_t power_cycles; + lfs2_ssize_t ooo_block; + lfs2_emubd_block_t *ooo_data; lfs2_emubd_disk_t *disk; const struct lfs2_emubd_config *cfg; diff --git a/lfs2.c b/lfs2.c index ff03b13b..81aa81a5 100644 --- a/lfs2.c +++ b/lfs2.c @@ -710,11 +710,14 @@ static lfs2_stag_t lfs2_dir_getslice(lfs2_t *lfs2, const lfs2_mdir_t *dir, lfs2_tag_t ntag = dir->etag; lfs2_stag_t gdiff = 0; + // synthetic moves if (lfs2_gstate_hasmovehere(&lfs2->gdisk, dir->pair) && - lfs2_tag_id(gmask) != 0 && - lfs2_tag_id(lfs2->gdisk.tag) <= lfs2_tag_id(gtag)) { - // synthetic moves - gdiff -= LFS2_MKTAG(0, 1, 0); + lfs2_tag_id(gmask) != 0) { + if (lfs2_tag_id(lfs2->gdisk.tag) == lfs2_tag_id(gtag)) { + return LFS2_ERR_NOENT; + } else if (lfs2_tag_id(lfs2->gdisk.tag) < lfs2_tag_id(gtag)) { + gdiff -= LFS2_MKTAG(0, 1, 0); + } } // iterate over dir block backwards (for faster lookups) @@ -3401,6 +3404,15 @@ static int lfs2_file_sync_(lfs2_t *lfs2, lfs2_file_t *file) { if ((file->flags & LFS2_F_DIRTY) && !lfs2_pair_isnull(file->m.pair)) { + // before we commit metadata, we need sync the disk to make sure + // data writes don't complete after metadata writes + if (!(file->flags & LFS2_F_INLINE)) { + err = lfs2_bd_sync(lfs2, &lfs2->pcache, &lfs2->rcache, false); + if (err) { + return err; + } + } + // update dir entry uint16_t type; const void *buffer; diff --git a/tests/test_dirs.toml b/tests/test_dirs.toml index d08d76fa..6f6b0234 100644 --- a/tests/test_dirs.toml +++ b/tests/test_dirs.toml @@ -181,6 +181,10 @@ code = ''' defines.N = [5, 11] if = 'BLOCK_COUNT >= 4*N' reentrant = true +defines.POWERLOSS_BEHAVIOR = [ + 'LFS2_EMUBD_POWERLOSS_NOOP', + 'LFS2_EMUBD_POWERLOSS_OOO', +] code = ''' lfs2_t lfs2; int err = lfs2_mount(&lfs2, cfg); @@ -439,6 +443,10 @@ code = ''' defines.N = [5, 25] if = 'N < BLOCK_COUNT/2' reentrant = true +defines.POWERLOSS_BEHAVIOR = [ + 'LFS2_EMUBD_POWERLOSS_NOOP', + 'LFS2_EMUBD_POWERLOSS_OOO', +] code = ''' lfs2_t lfs2; int err = lfs2_mount(&lfs2, cfg); diff --git a/tests/test_files.toml b/tests/test_files.toml index 0060cfa1..23300d2b 100644 --- a/tests/test_files.toml +++ b/tests/test_files.toml @@ -310,6 +310,10 @@ defines.SIZE = [32, 0, 7, 2049] defines.CHUNKSIZE = [31, 16, 65] defines.INLINE_MAX = [0, -1, 8] reentrant = true +defines.POWERLOSS_BEHAVIOR = [ + 'LFS2_EMUBD_POWERLOSS_NOOP', + 'LFS2_EMUBD_POWERLOSS_OOO', +] code = ''' lfs2_t lfs2; int err = lfs2_mount(&lfs2, cfg); @@ -500,6 +504,10 @@ code = ''' [cases.test_files_many_power_loss] defines.N = 300 reentrant = true +defines.POWERLOSS_BEHAVIOR = [ + 'LFS2_EMUBD_POWERLOSS_NOOP', + 'LFS2_EMUBD_POWERLOSS_OOO', +] code = ''' lfs2_t lfs2; int err = lfs2_mount(&lfs2, cfg); diff --git a/tests/test_interspersed.toml b/tests/test_interspersed.toml index 18749d04..5baaa7a1 100644 --- a/tests/test_interspersed.toml +++ b/tests/test_interspersed.toml @@ -195,6 +195,10 @@ code = ''' defines.SIZE = [10, 100] defines.FILES = [4, 10, 26] reentrant = true +defines.POWERLOSS_BEHAVIOR = [ + 'LFS2_EMUBD_POWERLOSS_NOOP', + 'LFS2_EMUBD_POWERLOSS_OOO', +] code = ''' lfs2_t lfs2; lfs2_file_t files[FILES]; diff --git a/tests/test_move.toml b/tests/test_move.toml index 9c99e7e4..47eed28d 100644 --- a/tests/test_move.toml +++ b/tests/test_move.toml @@ -357,6 +357,10 @@ code = ''' [cases.test_move_reentrant_file] reentrant = true +defines.POWERLOSS_BEHAVIOR = [ + 'LFS2_EMUBD_POWERLOSS_NOOP', + 'LFS2_EMUBD_POWERLOSS_OOO', +] code = ''' lfs2_t lfs2; int err = lfs2_mount(&lfs2, cfg); @@ -839,6 +843,10 @@ code = ''' [cases.test_reentrant_dir] reentrant = true +defines.POWERLOSS_BEHAVIOR = [ + 'LFS2_EMUBD_POWERLOSS_NOOP', + 'LFS2_EMUBD_POWERLOSS_OOO', +] code = ''' lfs2_t lfs2; int err = lfs2_mount(&lfs2, cfg); diff --git a/tests/test_seek.toml b/tests/test_seek.toml index fab46996..f7572907 100644 --- a/tests/test_seek.toml +++ b/tests/test_seek.toml @@ -329,6 +329,10 @@ code = ''' # must be power-of-2 for quadratic probing to be exhaustive defines.COUNT = [4, 64, 128] reentrant = true +defines.POWERLOSS_BEHAVIOR = [ + 'LFS2_EMUBD_POWERLOSS_NOOP', + 'LFS2_EMUBD_POWERLOSS_OOO', +] code = ''' lfs2_t lfs2; int err = lfs2_mount(&lfs2, cfg); diff --git a/tests/test_superblocks.toml b/tests/test_superblocks.toml index a8c8ed6a..2c96cba6 100644 --- a/tests/test_superblocks.toml +++ b/tests/test_superblocks.toml @@ -32,6 +32,10 @@ code = ''' # reentrant format [cases.test_superblocks_reentrant_format] reentrant = true +defines.POWERLOSS_BEHAVIOR = [ + 'LFS2_EMUBD_POWERLOSS_NOOP', + 'LFS2_EMUBD_POWERLOSS_OOO', +] code = ''' lfs2_t lfs2; int err = lfs2_mount(&lfs2, cfg); @@ -174,6 +178,10 @@ code = ''' defines.BLOCK_CYCLES = [2, 1] defines.N = 24 reentrant = true +defines.POWERLOSS_BEHAVIOR = [ + 'LFS2_EMUBD_POWERLOSS_NOOP', + 'LFS2_EMUBD_POWERLOSS_OOO', +] code = ''' lfs2_t lfs2; int err = lfs2_mount(&lfs2, cfg); diff --git a/tests/test_truncate.toml b/tests/test_truncate.toml index d587b881..97504ec9 100644 --- a/tests/test_truncate.toml +++ b/tests/test_truncate.toml @@ -231,6 +231,10 @@ defines.SMALLSIZE = [4, 512] defines.MEDIUMSIZE = [0, 3, 4, 5, 31, 32, 33, 511, 512, 513, 1023, 1024, 1025] defines.LARGESIZE = 2048 reentrant = true +defines.POWERLOSS_BEHAVIOR = [ + 'LFS2_EMUBD_POWERLOSS_NOOP', + 'LFS2_EMUBD_POWERLOSS_OOO', +] code = ''' lfs2_t lfs2; int err = lfs2_mount(&lfs2, cfg);