diff --git a/lfs.c b/lfs.c index 0fd4a77a..d2f3b5e9 100644 --- a/lfs.c +++ b/lfs.c @@ -4887,6 +4887,36 @@ static int lfs_fs_forceconsistency(lfs_t *lfs) { } #endif +#ifndef LFS_READONLY +int lfs_fs_rawmkconsistent(lfs_t *lfs) { + // lfs_fs_forceconsistency does most of the work here + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + + // do we have any pending gstate? + lfs_gstate_t delta = {0}; + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gstate); + if (!lfs_gstate_iszero(&delta)) { + // lfs_dir_commit will implicitly write out any pending gstate + lfs_mdir_t root; + err = lfs_dir_fetch(lfs, &root, lfs->root); + if (err) { + return err; + } + + err = lfs_dir_commit(lfs, &root, NULL, 0); + if (err) { + return err; + } + } + + return 0; +} +#endif + static int lfs_fs_size_count(void *p, lfs_block_t block) { (void)block; lfs_size_t *size = p; @@ -6052,6 +6082,22 @@ int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void *, lfs_block_t), void *data) { return err; } +#ifndef LFS_READONLY +int lfs_fs_mkconsistent(lfs_t *lfs) { + int err = LFS_LOCK(lfs->cfg); + if (err) { + return err; + } + LFS_TRACE("lfs_fs_mkconsistent(%p)", (void*)lfs); + + err = lfs_fs_rawmkconsistent(lfs); + + LFS_TRACE("lfs_fs_mkconsistent -> %d", err); + LFS_UNLOCK(lfs->cfg); + return err; +} +#endif + #ifdef LFS_MIGRATE int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { int err = LFS_LOCK(cfg); diff --git a/lfs.h b/lfs.h index f539f6af..eb5c355d 100644 --- a/lfs.h +++ b/lfs.h @@ -676,6 +676,18 @@ lfs_ssize_t lfs_fs_size(lfs_t *lfs); // Returns a negative error code on failure. int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); +#ifndef LFS_READONLY +// Attempt to make the filesystem consistent and ready for writing +// +// Calling this function is not required, consistency will be implicitly +// enforced on the first operation that writes to the filesystem, but this +// function allows the work to be performed earlier and without other +// filesystem changes. +// +// Returns a negative error code on failure. +int lfs_fs_mkconsistent(lfs_t *lfs); +#endif + #ifndef LFS_READONLY #ifdef LFS_MIGRATE // Attempts to migrate a previous version of littlefs diff --git a/tests/test_orphans.toml b/tests/test_orphans.toml index 1cce2fb7..2c8405aa 100644 --- a/tests/test_orphans.toml +++ b/tests/test_orphans.toml @@ -126,6 +126,83 @@ code = ''' lfs_unmount(&lfs) => 0; ''' +# test that we can persist gstate with lfs_fs_mkconsistent +[cases.test_orphans_mkconsistent_no_orphans] +in = 'lfs.c' +code = ''' + lfs_t lfs; + lfs_format(&lfs, cfg) => 0; + + lfs_mount(&lfs, cfg) => 0; + // mark the filesystem as having orphans + lfs_fs_preporphans(&lfs, +1) => 0; + lfs_mdir_t mdir; + lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; + lfs_dir_commit(&lfs, &mdir, NULL, 0) => 0; + + // we should have orphans at this state + assert(lfs_gstate_hasorphans(&lfs.gstate)); + lfs_unmount(&lfs) => 0; + + // mount + lfs_mount(&lfs, cfg) => 0; + // we should detect orphans + assert(lfs_gstate_hasorphans(&lfs.gstate)); + // force consistency + lfs_fs_mkconsistent(&lfs) => 0; + // we should no longer have orphans + assert(!lfs_gstate_hasorphans(&lfs.gstate)); + + // remount + lfs_unmount(&lfs) => 0; + lfs_mount(&lfs, cfg) => 0; + // we should still have no orphans + assert(!lfs_gstate_hasorphans(&lfs.gstate)); + lfs_unmount(&lfs) => 0; +''' + +[cases.test_orphans_mkconsistent_one_orphan] +in = 'lfs.c' +code = ''' + lfs_t lfs; + lfs_format(&lfs, cfg) => 0; + + lfs_mount(&lfs, cfg) => 0; + // create an orphan + lfs_mdir_t orphan; + lfs_alloc_ack(&lfs); + lfs_dir_alloc(&lfs, &orphan) => 0; + lfs_dir_commit(&lfs, &orphan, NULL, 0) => 0; + + // append our orphan and mark the filesystem as having orphans + lfs_fs_preporphans(&lfs, +1) => 0; + lfs_mdir_t mdir; + lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; + lfs_pair_tole32(orphan.pair); + lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), orphan.pair})) => 0; + + // we should have orphans at this state + assert(lfs_gstate_hasorphans(&lfs.gstate)); + lfs_unmount(&lfs) => 0; + + // mount + lfs_mount(&lfs, cfg) => 0; + // we should detect orphans + assert(lfs_gstate_hasorphans(&lfs.gstate)); + // force consistency + lfs_fs_mkconsistent(&lfs) => 0; + // we should no longer have orphans + assert(!lfs_gstate_hasorphans(&lfs.gstate)); + + // remount + lfs_unmount(&lfs) => 0; + lfs_mount(&lfs, cfg) => 0; + // we should still have no orphans + assert(!lfs_gstate_hasorphans(&lfs.gstate)); + lfs_unmount(&lfs) => 0; +''' + # reentrant testing for orphans, basically just spam mkdir/remove [cases.test_orphans_reentrant] reentrant = true