diff --git a/lfs.c b/lfs.c index 089202a3..e3c24da5 100644 --- a/lfs.c +++ b/lfs.c @@ -8265,6 +8265,10 @@ static inline bool lfsr_f_isdirty(uint32_t flags) { return flags & LFS_F_DIRTY; } +static inline bool lfsr_f_ismutated(uint32_t flags) { + return flags & LFS_F_MUTATED; +} + // needed in lfsr_mtree_traverse_ @@ -8637,6 +8641,9 @@ static int lfsr_mtree_traverse(lfs_t *lfs, lfsr_traversal_t *t, return 0; } +// needed in lfsr_mtree_gc +static int lfsr_mdir_fixorphans(lfs_t *lfs, lfsr_mdir_t *mdir); + // high-level mutating traversal, handle extra features that require // mutation here, upper layers should call lfs_alloc_ckpoint as needed static int lfsr_mtree_gc(lfs_t *lfs, lfsr_traversal_t *t, @@ -8645,6 +8652,7 @@ static int lfsr_mtree_gc(lfs_t *lfs, lfsr_traversal_t *t, // lfsr_mtree_gc to work correctly LFS_ASSERT(lfsr_omdir_isopen(lfs, &t->o.o)); +again:; lfsr_tag_t tag; lfsr_bptr_t bptr; int err = lfsr_mtree_traverse(lfs, t, @@ -8653,6 +8661,32 @@ static int lfsr_mtree_gc(lfs_t *lfs, lfsr_traversal_t *t, return err; } + // keep track of dirty flag before mutation + bool dirty = t->o.o.flags & LFS_F_DIRTY; + + // mkconsistencing mdirs? + if (lfsr_t_ismkconsistent(t->o.o.flags) + && tag == LFSR_TAG_MDIR + && lfs->hasorphans) { + lfsr_mdir_t *mdir = (lfsr_mdir_t*)bptr.data.u.buffer; + int err = lfsr_mdir_fixorphans(lfs, mdir); + if (err) { + return err; + } + + // did this drop our mdir? + if (mdir->mid != -1 && mdir->rbyd.weight == 0) { + t->o.o.flags &= ~LFS_F_ZOMBIE; + t->o.o.state = LFSR_TSTATE_MDIRS; + + // downgrade any new dirty flags + t->o.o.flags |= (t->o.o.flags & LFS_F_DIRTY) ? LFS_F_MUTATED : 0; + t->o.o.flags &= ~LFS_F_DIRTY; + t->o.o.flags |= (dirty) ? LFS_F_DIRTY : 0; + goto again; + } + } + // compacting mdirs? if (lfsr_t_iscompact(t->o.o.flags) && tag == LFSR_TAG_MDIR @@ -8677,9 +8711,6 @@ static int lfsr_mtree_gc(lfs_t *lfs, lfsr_traversal_t *t, if (err) { return err; } - - // mark as dirty - t->o.o.flags |= LFS_F_DIRTY; } // compacting btree nodes? @@ -8755,11 +8786,13 @@ static int lfsr_mtree_gc(lfs_t *lfs, lfsr_traversal_t *t, // reset to btree root t->u.bt.branch = &t->o.bshrub.u.btree; t->u.bt.rid = t->u.bt.bid; - - // mark as dirty - t->o.o.flags |= LFS_F_DIRTY; } + // downgrade any new dirty flags + t->o.o.flags |= (t->o.o.flags & LFS_F_DIRTY) ? LFS_F_MUTATED : 0; + t->o.o.flags &= ~LFS_F_DIRTY; + t->o.o.flags |= (dirty) ? LFS_F_DIRTY : 0; + if (tag_) { *tag_ = tag; } @@ -12381,7 +12414,7 @@ static int lfsr_mountinited(lfs_t *lfs) { // found an orphaned file? if (tag == LFSR_TAG_ORPHAN) { - LFS_DEBUG("Found orphaned file " + LFS_DEBUG("Found orphan " "%"PRId32".%"PRId32, lfsr_mid_bid(lfs, mdir->mid) >> lfs->mdir_bits, rid); @@ -12667,49 +12700,76 @@ static int lfsr_fs_fixgrm(lfs_t *lfs) { return 0; } +static int lfsr_mdir_fixorphans(lfs_t *lfs, lfsr_mdir_t *mdir) { + // save the current mid + lfsr_mid_t mid = mdir->mid; + + // iterate through mids looking for orphans + mdir->mid = LFSR_MID(lfs, mdir->mid, 0); + int err; + while (lfsr_mid_rid(lfs, mdir->mid) < (lfsr_srid_t)mdir->rbyd.weight) { + // is this mid open? well we're not an orphan then, skip + if (lfsr_omdir_ismidopen(lfs, mdir->mid)) { + mdir->mid += 1; + continue; + } + + // is this mid marked as an orphan? + err = lfsr_mdir_lookup(lfs, mdir, LFSR_TAG_ORPHAN, + NULL); + if (err) { + if (err == LFS_ERR_NOENT) { + mdir->mid += 1; + continue; + } + goto failed; + } + + // we found an orphaned file, remove + LFS_DEBUG("Fixing orphan %"PRId32".%"PRId32, + lfsr_mid_bid(lfs, mdir->mid) >> lfs->mdir_bits, + lfsr_mid_rid(lfs, mdir->mid)); + + err = lfsr_mdir_commit(lfs, mdir, LFSR_ATTRS( + LFSR_ATTR(LFSR_TAG_RM, -1, LFSR_DATA_NULL()))); + if (err) { + goto failed; + } + } + + // restore the current mid + mdir->mid = mid; + return 0; + +failed:; + // restore the current mid + mdir->mid = mid; + return err; +} + static int lfsr_fs_fixorphans(lfs_t *lfs) { - // traverse the filesystem and remove any orphaned files + // iterate through the filesystem and remove any orphaned files // // note this never takes longer than lfsr_mount // - lfsr_mdir_t mdir = {.mid=0, .rbyd.weight=0}; - while (true) { - // next mdir? - if (lfsr_mid_rid(lfs, mdir.mid) >= (lfsr_srid_t)mdir.rbyd.weight) { - int err = lfsr_mtree_lookup(lfs, lfsr_mid_bid(lfs, mdir.mid) + 1, - &mdir); - if (err) { - if (err == LFS_ERR_NOENT) { - break; - } - return err; - } + for (lfsr_mid_t mid = 0; + mid < lfsr_mtree_weight(lfs); + mid += (1 << lfs->mdir_bits)) { + lfsr_mdir_t mdir; + int err = lfsr_mtree_lookup(lfs, mid, + &mdir); + if (err) { + LFS_ASSERT(err != LFS_ERR_NOENT); + return err; } - // is this mid open? well we're not an orphan then, skip - if (!lfsr_omdir_ismidopen(lfs, mdir.mid)) { - // are we an orphan file? - int err = lfsr_mdir_lookup(lfs, &mdir, LFSR_TAG_ORPHAN, - NULL); - if (err && err != LFS_ERR_NOENT) { - return err; - } - - if (err != LFS_ERR_NOENT) { - // remove orphaned file - err = lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS( - LFSR_ATTR(LFSR_TAG_RM, -1, LFSR_DATA_NULL()))); - if (err) { - return err; - } - - continue; - } + err = lfsr_mdir_fixorphans(lfs, &mdir); + if (err) { + return err; } - - mdir.mid += 1; } + // done, no more orphans lfs->hasorphans = false; return 0; } @@ -12717,7 +12777,6 @@ static int lfsr_fs_fixorphans(lfs_t *lfs) { // prepare the filesystem for mutation int lfsr_fs_mkconsistent(lfs_t *lfs) { // fix pending grms - bool wasinconsistent = false; if (lfsr_grm_count(lfs) > 0) { if (lfsr_grm_count(lfs) == 2) { LFS_DEBUG("Fixing grm %"PRId32".%"PRId32" %"PRId32".%"PRId32, @@ -12725,12 +12784,11 @@ int lfsr_fs_mkconsistent(lfs_t *lfs) { lfsr_mid_rid(lfs, lfs->grm.mids[0]), lfsr_mid_bid(lfs, lfs->grm.mids[1]) >> lfs->mdir_bits, lfsr_mid_rid(lfs, lfs->grm.mids[1])); - } else { + } else if (lfsr_grm_count(lfs) == 1) { LFS_DEBUG("Fixing grm %"PRId32".%"PRId32, lfsr_mid_bid(lfs, lfs->grm.mids[0]) >> lfs->mdir_bits, lfsr_mid_rid(lfs, lfs->grm.mids[0])); } - wasinconsistent = true; int err = lfsr_fs_fixgrm(lfs); if (err) { @@ -12745,7 +12803,6 @@ int lfsr_fs_mkconsistent(lfs_t *lfs) { // if (lfs->hasorphans) { LFS_DEBUG("Fixing orphans..."); - wasinconsistent = true; int err = lfsr_fs_fixorphans(lfs); if (err) { @@ -12753,9 +12810,6 @@ int lfsr_fs_mkconsistent(lfs_t *lfs) { } } - if (wasinconsistent) { - LFS_DEBUG("littlefs is now consistent"); - } return 0; } @@ -12829,16 +12883,6 @@ int lfsr_traversal_open(lfs_t *lfs, lfsr_traversal_t *t, uint32_t flags) { // these flags are internal and shouldn't be provided by the user LFS_ASSERT(!lfsr_f_isdirty(flags)); - // some flags mutate the filesystem - if (lfsr_t_ismkconsistent(flags) - || lfsr_t_iscompact(flags)) { - // prepare our filesystem for writing - int err = lfsr_fs_mkconsistent(lfs); - if (err) { - return err; - } - } - // setup traversal state t->o.o.type = LFS_TYPE_TRAVERSAL; t->o.o.flags = flags; @@ -12866,6 +12910,36 @@ int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *t, struct lfs_tinfo *tinfo) { LFS_ASSERT(lfsr_omdir_isopen(lfs, &t->o.o)); + // check for pending grms every step, just in case some other + // operation introduced new grms + if (lfsr_t_ismkconsistent(t->o.o.flags) + && lfsr_grm_count(lfs) > 0) { + if (lfsr_grm_count(lfs) == 2) { + LFS_DEBUG("Fixing grm %"PRId32".%"PRId32" %"PRId32".%"PRId32, + lfsr_mid_bid(lfs, lfs->grm.mids[0]) >> lfs->mdir_bits, + lfsr_mid_rid(lfs, lfs->grm.mids[0]), + lfsr_mid_bid(lfs, lfs->grm.mids[1]) >> lfs->mdir_bits, + lfsr_mid_rid(lfs, lfs->grm.mids[1])); + } else if (lfsr_grm_count(lfs) == 1) { + LFS_DEBUG("Fixing grm %"PRId32".%"PRId32, + lfsr_mid_bid(lfs, lfs->grm.mids[0]) >> lfs->mdir_bits, + lfsr_mid_rid(lfs, lfs->grm.mids[0])); + } + + // keep track of dirty flag before mutation + bool dirty = t->o.o.flags & LFS_F_DIRTY; + + int err = lfsr_fs_fixgrm(lfs); + if (err) { + return err; + } + + // downgrade any new dirty flags + t->o.o.flags |= (t->o.o.flags & LFS_F_DIRTY) ? LFS_F_MUTATED : 0; + t->o.o.flags &= ~LFS_F_DIRTY; + t->o.o.flags |= (dirty) ? LFS_F_DIRTY : 0; + } + while (true) { // some redund blocks left over? if (t->blocks[0] != -1) { @@ -12915,9 +12989,16 @@ int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *t, } done:; + // was mkconsistent successful? + if (lfsr_t_ismkconsistent(t->o.o.flags) + && !lfsr_f_isdirty(t->o.o.flags)) { + lfs->hasorphans = false; + } + // was a lookahead scan successful? if (lfsr_t_islookahead(t->o.o.flags) - && !lfsr_f_isdirty(t->o.o.flags)) { + && !lfsr_f_isdirty(t->o.o.flags) + && !lfsr_f_ismutated(t->o.o.flags)) { lfs_alloc_markfree(lfs); } @@ -12960,7 +13041,7 @@ static void lfsr_traversal_clobber(lfs_t *lfs, lfsr_traversal_t *t) { static int lfsr_traversal_rewind_(lfs_t *lfs, lfsr_traversal_t *t) { (void)lfs; // reset traversal - t->o.o.flags &= ~LFS_F_DIRTY; + t->o.o.flags &= ~LFS_F_DIRTY & ~LFS_F_MUTATED; t->o.o.state = LFSR_TSTATE_MROOTANCHOR; t->o.o.mdir.mid = -1; t->o.o.mdir.rbyd.weight = 0; diff --git a/lfs.h b/lfs.h index 7a75496d..ff0eab9e 100644 --- a/lfs.h +++ b/lfs.h @@ -179,7 +179,8 @@ enum lfs_traversal_flags { // LFS_T_REPAIRDATA = 0x0c00, // Repair metadata + data blocks // internally used flags - LFS_F_DIRTY = 0x1000, // Filesystem has been modified + LFS_F_DIRTY = 0x1000, // Filesystem modified during traversal + LFS_F_MUTATED = 0x4000, // Filesystem modified by traversal }; diff --git a/tests/test_traversal.toml b/tests/test_traversal.toml index 41fc3ab7..7eeda349 100644 --- a/tests/test_traversal.toml +++ b/tests/test_traversal.toml @@ -9,10 +9,11 @@ after = [ # a simple traversal test [cases.test_traversal_simple] -defines.CKMETA = [false, true] -defines.CKDATA = [false, true] +defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; @@ -21,10 +22,11 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) + ((MKCONSISTENT) ? LFS_T_MKCONSISTENT : 0) | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) - | ((COMPACT) ? LFS_T_COMPACT : 0)) => 0; + | ((COMPACT) ? LFS_T_COMPACT : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; assert(tinfo.btype == LFS_BTYPE_MDIR); @@ -40,10 +42,11 @@ code = ''' # can we rewind? [cases.test_traversal_rewind] -defines.CKMETA = [false, true] -defines.CKDATA = [false, true] +defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; @@ -52,10 +55,11 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) + ((MKCONSISTENT) ? LFS_T_MKCONSISTENT : 0) | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) - | ((COMPACT) ? LFS_T_COMPACT : 0)) => 0; + | ((COMPACT) ? LFS_T_COMPACT : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; assert(tinfo.btype == LFS_BTYPE_MDIR); @@ -80,10 +84,11 @@ code = ''' # test that we don't get extra anything after end of traversal [cases.test_traversal_idempotent] -defines.CKMETA = [false, true] -defines.CKDATA = [false, true] +defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; @@ -92,10 +97,11 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) + ((MKCONSISTENT) ? LFS_T_MKCONSISTENT : 0) | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) - | ((COMPACT) ? LFS_T_COMPACT : 0)) => 0; + | ((COMPACT) ? LFS_T_COMPACT : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; assert(tinfo.btype == LFS_BTYPE_MDIR); @@ -1664,9 +1670,9 @@ done:; # test that we detect filesystem mutation during traversal [cases.test_traversal_mutation] defines.WHEN = [0, 1, 2] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; @@ -1675,9 +1681,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; if (WHEN == 0) { lfsr_file_t file; @@ -1717,9 +1723,9 @@ code = ''' # test that we don't get extra anything after end of traversal [cases.test_traversal_mutation_idempotent] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; @@ -1728,9 +1734,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; assert(tinfo.btype == LFS_BTYPE_MDIR); @@ -1773,9 +1779,9 @@ code = ''' [cases.test_traversal_mutation_mkdir] defines.WHEN = [0, 1, 2] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; @@ -1784,9 +1790,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; if (WHEN == 0) { lfsr_mkdir(&lfs, "spider") => 0; @@ -1817,9 +1823,9 @@ code = ''' [cases.test_traversal_mutation_rm] defines.WHEN = [0, 1, 2] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; @@ -1834,9 +1840,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; if (WHEN == 0) { lfsr_remove(&lfs, "spider") => 0; @@ -1867,9 +1873,9 @@ code = ''' [cases.test_traversal_mutation_mv] defines.WHEN = [0, 1, 2] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; @@ -1884,9 +1890,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; if (WHEN == 0) { lfsr_rename(&lfs, "spider", "scorpion") => 0; @@ -1917,9 +1923,9 @@ code = ''' # some more complex mutation tests [cases.test_traversal_mutation_fwrite] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', @@ -1949,9 +1955,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; while (true) { // rewrite the file every step of the traversal @@ -1985,9 +1991,9 @@ code = ''' ''' [cases.test_traversal_mutation_fwrite_open] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', @@ -2020,9 +2026,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; while (true) { // rewrite the file every step of the traversal @@ -2068,9 +2074,9 @@ code = ''' # these assume quite a bit more and may be a bit fragile... # [cases.test_traversal_mutation_file_bsprout] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = 'FILE_BUFFER_SIZE/2' defines.TRUNC = [false, true] code = ''' @@ -2103,9 +2109,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -2146,9 +2152,9 @@ code = ''' ''' [cases.test_traversal_mutation_file_btree] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' defines.INLINE_SIZE = 0 defines.TRUNC = [false, true] @@ -2182,9 +2188,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -2239,9 +2245,9 @@ code = ''' ''' [cases.test_traversal_mutation_file_bshrub] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' defines.TRUNC = [false, true] code = ''' @@ -2274,9 +2280,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -2325,9 +2331,9 @@ code = ''' ''' [cases.test_traversal_mutation_orphan_bsprout] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = 'FILE_BUFFER_SIZE/2' code = ''' lfs_t lfs; @@ -2358,9 +2364,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -2402,9 +2408,9 @@ code = ''' ''' [cases.test_traversal_mutation_orphan_btree] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' defines.INLINE_SIZE = 0 code = ''' @@ -2436,9 +2442,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -2494,9 +2500,9 @@ code = ''' ''' [cases.test_traversal_mutation_orphan_bshrub] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' code = ''' lfs_t lfs; @@ -2527,9 +2533,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -2579,9 +2585,9 @@ code = ''' ''' [cases.test_traversal_mutation_close_bsprout] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = 'FILE_BUFFER_SIZE/2' defines.DESYNC = [false, true] code = ''' @@ -2613,9 +2619,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -2660,9 +2666,9 @@ code = ''' ''' [cases.test_traversal_mutation_close_btree] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' defines.INLINE_SIZE = 0 defines.DESYNC = [false, true] @@ -2695,9 +2701,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -2756,9 +2762,9 @@ code = ''' ''' [cases.test_traversal_mutation_close_bshrub] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' defines.DESYNC = [false, true] code = ''' @@ -2790,9 +2796,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -2845,9 +2851,9 @@ code = ''' ''' [cases.test_traversal_mutation_rm_bshrub] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' code = ''' lfs_t lfs; @@ -2879,9 +2885,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -2920,9 +2926,9 @@ code = ''' ''' [cases.test_traversal_mutation_mv_src_bshrub] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' code = ''' lfs_t lfs; @@ -2963,9 +2969,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -3014,9 +3020,9 @@ code = ''' ''' [cases.test_traversal_mutation_mv_dst_bshrub] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' code = ''' lfs_t lfs; @@ -3057,9 +3063,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -3103,9 +3109,9 @@ code = ''' ''' [cases.test_traversal_mutation_mroot_split] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' code = ''' lfs_t lfs; @@ -3137,9 +3143,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -3198,9 +3204,9 @@ code = ''' ''' [cases.test_traversal_mutation_mroot_split_bshrub_l] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' code = ''' lfs_t lfs; @@ -3241,9 +3247,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -3311,9 +3317,9 @@ code = ''' ''' [cases.test_traversal_mutation_mroot_split_bshrub_r] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' code = ''' lfs_t lfs; @@ -3354,9 +3360,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -3419,9 +3425,9 @@ code = ''' ''' [cases.test_traversal_mutation_mroot_extend] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' # force early relocations defines.BLOCK_RECYCLES = 0 @@ -3455,9 +3461,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -3509,9 +3515,9 @@ code = ''' ''' [cases.test_traversal_mutation_mroot_extend_bshrub] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' # force early relocations defines.BLOCK_RECYCLES = 0 @@ -3545,9 +3551,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -3598,9 +3604,9 @@ code = ''' ''' [cases.test_traversal_mutation_mroot_relocate] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' # force early relocations defines.BLOCK_RECYCLES = 0 @@ -3643,9 +3649,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -3702,9 +3708,9 @@ code = ''' ''' [cases.test_traversal_mutation_mroot_relocate_bshrub] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' # force early relocations defines.BLOCK_RECYCLES = 0 @@ -3747,9 +3753,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroots struct lfs_tinfo tinfo; @@ -3805,9 +3811,9 @@ code = ''' ''' [cases.test_traversal_mutation_mtree_split] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' code = ''' lfs_t lfs; @@ -3879,9 +3885,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -3971,9 +3977,9 @@ code = ''' ''' [cases.test_traversal_mutation_mtree_split_bshrub_l] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' code = ''' lfs_t lfs; @@ -4045,9 +4051,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -4144,9 +4150,9 @@ code = ''' ''' [cases.test_traversal_mutation_mtree_split_bshrub_r] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' code = ''' lfs_t lfs; @@ -4218,9 +4224,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -4311,9 +4317,9 @@ code = ''' ''' [cases.test_traversal_mutation_mtree_extend] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' code = ''' lfs_t lfs; @@ -4391,9 +4397,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -4475,9 +4481,9 @@ code = ''' ''' [cases.test_traversal_mutation_mtree_extend_bshrub] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' code = ''' lfs_t lfs; @@ -4555,9 +4561,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -4646,9 +4652,9 @@ code = ''' ''' [cases.test_traversal_mutation_mtree_relocate] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' # force early relocations defines.BLOCK_RECYCLES = 0 @@ -4731,9 +4737,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; @@ -4816,9 +4822,9 @@ code = ''' ''' [cases.test_traversal_mutation_mtree_relocate_bshrub] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = '2*BLOCK_SIZE' # force early relocations defines.BLOCK_RECYCLES = 0 @@ -4901,9 +4907,9 @@ code = ''' // try traversing lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroots struct lfs_tinfo tinfo; @@ -4997,13 +5003,12 @@ code = ''' ''' - # test traversals with mdir compaction [cases.test_traversal_compact] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = 'FILE_BUFFER_SIZE/2' # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' @@ -5034,9 +5039,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; @@ -5072,9 +5077,9 @@ code = ''' ''' [cases.test_traversal_compact_mrootchain] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = 'FILE_BUFFER_SIZE/2' # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' @@ -5126,9 +5131,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mrootanchor struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; @@ -5172,9 +5177,9 @@ code = ''' ''' [cases.test_traversal_compact_mroot_extend] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = 'FILE_BUFFER_SIZE/2' # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' @@ -5208,9 +5213,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // it's a bit unclear if we should follow the mroot or stay on the // mroot anchor during extends, so if this breaks in the future // I wouldn't worry too much about it @@ -5248,9 +5253,9 @@ code = ''' ''' [cases.test_traversal_compact_mroot_split] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = 'FILE_BUFFER_SIZE/2' # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' @@ -5309,9 +5314,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; @@ -5368,9 +5373,9 @@ code = ''' ''' [cases.test_traversal_compact_mtree] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = 'FILE_BUFFER_SIZE/2' # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' @@ -5478,9 +5483,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; @@ -5550,9 +5555,9 @@ code = ''' ''' [cases.test_traversal_compact_mtree_split] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = 'FILE_BUFFER_SIZE/2' # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' @@ -5655,9 +5660,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; @@ -5744,9 +5749,9 @@ code = ''' # btree/bshrub compactions are quite a bit more difficult [cases.test_traversal_compact_mtree_btree] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] defines.SIZE = 'FILE_BUFFER_SIZE/2' # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' @@ -5837,9 +5842,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; @@ -5903,9 +5908,9 @@ code = ''' ''' [cases.test_traversal_compact_btree] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] # limit files to very simple btrees defines.INLINE_SIZE = 0 defines.CRYSTAL_THRESH = -1 @@ -5941,9 +5946,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; @@ -5983,9 +5988,9 @@ code = ''' ''' [cases.test_traversal_compact_btree_open] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] # limit files to very simple btrees defines.INLINE_SIZE = 0 defines.CRYSTAL_THRESH = -1 @@ -6021,9 +6026,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; @@ -6062,9 +6067,9 @@ code = ''' ''' [cases.test_traversal_compact_btree_orphan] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] # limit files to very simple btrees defines.INLINE_SIZE = 0 defines.CRYSTAL_THRESH = -1 @@ -6098,9 +6103,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; @@ -6143,9 +6148,9 @@ code = ''' ''' [cases.test_traversal_compact_btree_desync] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] # limit files to very simple btrees defines.INLINE_SIZE = 0 defines.CRYSTAL_THRESH = -1 @@ -6200,9 +6205,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; @@ -6253,9 +6258,9 @@ code = ''' ''' [cases.test_traversal_compact_bshrub] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] # this configuration should create a 2-layer bshrub, which may be # a bit delicate defines.INLINE_SIZE = 'BLOCK_SIZE/4' @@ -6312,9 +6317,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; @@ -6370,9 +6375,9 @@ code = ''' ''' [cases.test_traversal_compact_bshrub_open] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] # this configuration should create a 2-layer bshrub, which may be # a bit delicate defines.INLINE_SIZE = 'BLOCK_SIZE/4' @@ -6429,9 +6434,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; @@ -6488,9 +6493,9 @@ code = ''' ''' [cases.test_traversal_compact_bshrub_orphan] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] # this configuration should create a 2-layer bshrub, which may be # a bit delicate defines.INLINE_SIZE = 'BLOCK_SIZE/4' @@ -6545,9 +6550,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; @@ -6604,9 +6609,9 @@ code = ''' ''' [cases.test_traversal_compact_bshrub_desync] +defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] -defines.LOOKAHEAD = [false, true] # this configuration should create a 2-layer bshrub, which may be # a bit delicate defines.INLINE_SIZE = 'BLOCK_SIZE/4' @@ -6682,9 +6687,9 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) | ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) - | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // traverse mroot struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; @@ -6747,6 +6752,1525 @@ code = ''' ''' +# test traversals with mkconsistent + +[cases.test_traversal_mkconsistent] +defines.LOOKAHEAD = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +defines.SIZE = 'FILE_BUFFER_SIZE/2' +# <=2 => grm-able +# >2 => requires orphans +defines.ORPHANS = [0, 1, 2, 3, 100] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create two files + lfsr_file_t file1; + lfsr_file_open(&lfs, &file1, "cuttlefish", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf1[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file1) => 0; + + lfsr_file_t file2; + lfsr_file_open(&lfs, &file2, "octopus", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf2[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file2) => 0; + + // create this many orphaned files + // + // anytime we close a not-yet-created desync file, we create an + // orphan, but note we need these to be different files, and we need + // to close them after all open calls, otherwise we just end up with + // one orphan (littlefs is eager to clean up orphans) + // + lfsr_file_t orphans[ORPHANS]; + for (lfs_size_t i = 0; i < ORPHANS; i++) { + char name[256]; + sprintf(name, "jellyfish%03x", i); + lfsr_file_open(&lfs, &orphans[i], name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; + } + for (lfs_size_t i = 0; i < ORPHANS; i++) { + lfsr_file_close(&lfs, &orphans[i]) => 0; + } + + // try traversing with mkconsistent + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + LFS_T_MKCONSISTENT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + // traverse mroot + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + if (ORPHANS > 3) { + // traverse mtree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + } + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_close(&lfs, &t) => 0; + + // we should have cleaned up all grms/orphans + assert(lfs.grm.mids[0] == -1); + assert(lfs.grm.mids[1] == -1); + assert(lfs.hasorphans == false); + + // which means there shouldn't be that many files left + assert((lfs.mtree.u.weight & 0x7fffffff) <= (2 << lfs.mdir_bits)); + assert(file1.o.o.mdir.rbyd.weight <= 3); + assert(file2.o.o.mdir.rbyd.weight <= 3); + + // check we can still read the files + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0; + lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0; + } + + lfsr_file_rewind(&lfs, &file1) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + + lfsr_file_rewind(&lfs, &file2) => 0; + lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + } + + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mkconsistent_conflict] +defines.LOOKAHEAD = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +defines.SIZE = 'FILE_BUFFER_SIZE/2' +# <=2 => grm-able +# >2 => requires orphans +defines.ORPHANS = [0, 1, 2, 3, 100] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create two files + lfsr_file_t file1; + lfsr_file_open(&lfs, &file1, "cuttlefish", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf1[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file1) => 0; + + lfsr_file_t file2; + lfsr_file_open(&lfs, &file2, "octopus", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf2[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file2) => 0; + + // try traversing with mkconsistent + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + LFS_T_MKCONSISTENT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + // traverse mroot + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + + // create this many orphaned files + // + // anytime we close a not-yet-created desync file, we create an + // orphan, but note we need these to be different files, and we need + // to close them after all open calls, otherwise we just end up with + // one orphan (littlefs is eager to clean up orphans) + // + lfsr_file_t orphans[ORPHANS]; + for (lfs_size_t i = 0; i < ORPHANS; i++) { + char name[256]; + sprintf(name, "jellyfish%03x", i); + lfsr_file_open(&lfs, &orphans[i], name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; + } + for (lfs_size_t i = 0; i < ORPHANS; i++) { + lfsr_file_close(&lfs, &orphans[i]) => 0; + } + + // keep traversing + if (ORPHANS <= 3) { + // traverse mroot + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + } else { + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + } + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_close(&lfs, &t) => 0; + + // we should be able to clean up grms + assert(lfs.grm.mids[0] == -1); + assert(lfs.grm.mids[1] == -1); + // if we introduce actual orphans, me _must not_ clear the orphan flag + if (ORPHANS > 3) { + assert(lfs.hasorphans == true); + } + + // check we can still read the files + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0; + lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0; + } + + lfsr_file_rewind(&lfs, &file1) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + + lfsr_file_rewind(&lfs, &file2) => 0; + lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + } + + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mkconsistent_btree] +defines.LOOKAHEAD = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +# limit files to very simple btrees +defines.INLINE_SIZE = 0 +defines.CRYSTAL_THRESH = -1 +defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8' +defines.SIZE = '2*FRAGMENT_SIZE' +# <=2 => grm-able +# >2 => requires orphans +defines.ORPHANS = [0, 1, 2, 3, 100] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create two files + lfsr_file_t file1; + lfsr_file_open(&lfs, &file1, "cuttlefish", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf1[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file1) => 0; + + lfsr_file_t file2; + lfsr_file_open(&lfs, &file2, "octopus", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf2[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file2) => 0; + + // create this many orphaned files + // + // anytime we close a not-yet-created desync file, we create an + // orphan, but note we need these to be different files, and we need + // to close them after all open calls, otherwise we just end up with + // one orphan (littlefs is eager to clean up orphans) + // + lfsr_file_t orphans[ORPHANS]; + for (lfs_size_t i = 0; i < ORPHANS; i++) { + char name[256]; + sprintf(name, "jellyfish%03x", i); + lfsr_file_open(&lfs, &orphans[i], name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; + } + for (lfs_size_t i = 0; i < ORPHANS; i++) { + lfsr_file_close(&lfs, &orphans[i]) => 0; + } + + // try traversing with mkconsistent + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + LFS_T_MKCONSISTENT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + // traverse mroot + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + if (ORPHANS <= 3) { + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } else { + // traverse mtree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_close(&lfs, &t) => 0; + + // we should have cleaned up all grms/orphans + assert(lfs.grm.mids[0] == -1); + assert(lfs.grm.mids[1] == -1); + assert(lfs.hasorphans == false); + + // which means there shouldn't be that many files left + assert((lfs.mtree.u.weight & 0x7fffffff) <= (2 << lfs.mdir_bits)); + assert(file1.o.o.mdir.rbyd.weight <= 3); + assert(file2.o.o.mdir.rbyd.weight <= 3); + + // check we can still read the files + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0; + lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0; + } + + lfsr_file_rewind(&lfs, &file1) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + + lfsr_file_rewind(&lfs, &file2) => 0; + lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + } + + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mkconsistent_btree_orphan] +defines.LOOKAHEAD = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +# limit files to very simple btrees +defines.INLINE_SIZE = 0 +defines.CRYSTAL_THRESH = -1 +defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8' +defines.SIZE = '2*FRAGMENT_SIZE' +# <=2 => grm-able +# >2 => requires orphans +defines.ORPHANS = [0, 1, 2, 3, 100] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create two files + lfsr_file_t file1; + lfsr_file_open(&lfs, &file1, "cuttlefish", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf1[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; + + lfsr_file_t file2; + lfsr_file_open(&lfs, &file2, "octopus", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf2[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; + + // create this many orphaned files + // + // anytime we close a not-yet-created desync file, we create an + // orphan, but note we need these to be different files, and we need + // to close them after all open calls, otherwise we just end up with + // one orphan (littlefs is eager to clean up orphans) + // + lfsr_file_t orphans[ORPHANS]; + for (lfs_size_t i = 0; i < ORPHANS; i++) { + char name[256]; + sprintf(name, "jellyfish%03x", i); + lfsr_file_open(&lfs, &orphans[i], name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; + } + for (lfs_size_t i = 0; i < ORPHANS; i++) { + lfsr_file_close(&lfs, &orphans[i]) => 0; + } + + // try traversing with mkconsistent + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + LFS_T_MKCONSISTENT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + // traverse mroot + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + if (ORPHANS <= 3) { + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } else { + // traverse mtree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_close(&lfs, &t) => 0; + + // we should have cleaned up all grms/orphans + assert(lfs.grm.mids[0] == -1); + assert(lfs.grm.mids[1] == -1); + assert(lfs.hasorphans == false); + + // which means there shouldn't be that many files left + assert((lfs.mtree.u.weight & 0x7fffffff) <= (2 << lfs.mdir_bits)); + assert(file1.o.o.mdir.rbyd.weight <= 3); + assert(file2.o.o.mdir.rbyd.weight <= 3); + + // check we can still read the files + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0; + lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0; + } + + lfsr_file_rewind(&lfs, &file1) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + + lfsr_file_rewind(&lfs, &file2) => 0; + lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + } + + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mkconsistent_bshrub] +defines.LOOKAHEAD = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +# this configuration should create a 2-layer bshrub, which may be +# a bit delicate +defines.INLINE_SIZE = 'BLOCK_SIZE/4' +defines.CRYSTAL_THRESH = -1 +defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8' +defines.SIZE = 'BLOCK_SIZE' +# <=2 => grm-able +# >2 => requires orphans +defines.ORPHANS = [0, 1, 2, 3, 100] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create two files + lfsr_file_t file1; + lfsr_file_open(&lfs, &file1, "cuttlefish", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf1[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file1) => 0; + + lfsr_file_t file2; + lfsr_file_open(&lfs, &file2, "octopus", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf2[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file2) => 0; + + // create this many orphaned files + // + // anytime we close a not-yet-created desync file, we create an + // orphan, but note we need these to be different files, and we need + // to close them after all open calls, otherwise we just end up with + // one orphan (littlefs is eager to clean up orphans) + // + lfsr_file_t orphans[ORPHANS]; + for (lfs_size_t i = 0; i < ORPHANS; i++) { + char name[256]; + sprintf(name, "jellyfish%03x", i); + lfsr_file_open(&lfs, &orphans[i], name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; + } + for (lfs_size_t i = 0; i < ORPHANS; i++) { + lfsr_file_close(&lfs, &orphans[i]) => 0; + } + + // try traversing with mkconsistent + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + LFS_T_MKCONSISTENT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + // traverse mroot + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + if (ORPHANS <= 3) { + // traverse bshrub + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse bshrub + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } else { + // traverse mtree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + // traverse bshrub + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + // traverse bshrub + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_close(&lfs, &t) => 0; + + // we should have cleaned up all grms/orphans + assert(lfs.grm.mids[0] == -1); + assert(lfs.grm.mids[1] == -1); + assert(lfs.hasorphans == false); + + // which means there shouldn't be that many files left + assert((lfs.mtree.u.weight & 0x7fffffff) <= (2 << lfs.mdir_bits)); + assert(file1.o.o.mdir.rbyd.weight <= 3); + assert(file2.o.o.mdir.rbyd.weight <= 3); + + // check we can still read the files + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0; + lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0; + } + + lfsr_file_rewind(&lfs, &file1) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + + lfsr_file_rewind(&lfs, &file2) => 0; + lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + } + + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mkconsistent_bshrub_orphan] +defines.LOOKAHEAD = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +# this configuration should create a 2-layer bshrub, which may be +# a bit delicate +defines.INLINE_SIZE = 'BLOCK_SIZE/4' +defines.CRYSTAL_THRESH = -1 +defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8' +defines.SIZE = 'BLOCK_SIZE' +# <=2 => grm-able +# >2 => requires orphans +defines.ORPHANS = [0, 1, 2, 3, 100] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create two files + lfsr_file_t file1; + lfsr_file_open(&lfs, &file1, "cuttlefish", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf1[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; + + lfsr_file_t file2; + lfsr_file_open(&lfs, &file2, "octopus", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf2[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; + + // create this many orphaned files + // + // anytime we close a not-yet-created desync file, we create an + // orphan, but note we need these to be different files, and we need + // to close them after all open calls, otherwise we just end up with + // one orphan (littlefs is eager to clean up orphans) + // + lfsr_file_t orphans[ORPHANS]; + for (lfs_size_t i = 0; i < ORPHANS; i++) { + char name[256]; + sprintf(name, "jellyfish%03x", i); + lfsr_file_open(&lfs, &orphans[i], name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; + } + for (lfs_size_t i = 0; i < ORPHANS; i++) { + lfsr_file_close(&lfs, &orphans[i]) => 0; + } + + // try traversing with mkconsistent + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + LFS_T_MKCONSISTENT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + // traverse mroot + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + if (ORPHANS <= 3) { + // traverse bshrub + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse bshrub + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } else { + // traverse mtree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + // traverse bshrub + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + // traverse bshrub + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_close(&lfs, &t) => 0; + + // we should have cleaned up all grms/orphans + assert(lfs.grm.mids[0] == -1); + assert(lfs.grm.mids[1] == -1); + assert(lfs.hasorphans == false); + + // which means there shouldn't be that many files left + assert((lfs.mtree.u.weight & 0x7fffffff) <= (2 << lfs.mdir_bits)); + assert(file1.o.o.mdir.rbyd.weight <= 3); + assert(file2.o.o.mdir.rbyd.weight <= 3); + + // check we can still read the files + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0; + lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0; + } + + lfsr_file_rewind(&lfs, &file1) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + + lfsr_file_rewind(&lfs, &file2) => 0; + lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + } + + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mkconsistent_compact] +defines.LOOKAHEAD = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +defines.SIZE = 'FILE_BUFFER_SIZE/2' +# <=2 => grm-able +# >2 => requires orphans +defines.ORPHANS = [0, 1, 2, 3, 100] +# set compact thresh to minimum +defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create two files + lfsr_file_t file1; + lfsr_file_open(&lfs, &file1, "cuttlefish", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf1[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file1) => 0; + + lfsr_file_t file2; + lfsr_file_open(&lfs, &file2, "octopus", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf2[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file2) => 0; + + // create this many orphaned files + // + // anytime we close a not-yet-created desync file, we create an + // orphan, but note we need these to be different files, and we need + // to close them after all open calls, otherwise we just end up with + // one orphan (littlefs is eager to clean up orphans) + // + lfsr_file_t orphans[ORPHANS]; + for (lfs_size_t i = 0; i < ORPHANS; i++) { + char name[256]; + sprintf(name, "jellyfish%03x", i); + lfsr_file_open(&lfs, &orphans[i], name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; + } + for (lfs_size_t i = 0; i < ORPHANS; i++) { + lfsr_file_close(&lfs, &orphans[i]) => 0; + } + + // write to our mdirs until >gc_compact_thresh full + // + // hack, don't use the internals like this + while ((file1.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) { + lfsr_file_rewind(&lfs, &file1) => 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file1) => 0; + } + + while ((file2.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) { + lfsr_file_rewind(&lfs, &file2) => 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file2) => 0; + } + + // try traversing with mkconsistent + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + LFS_T_MKCONSISTENT + | LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + // traverse mroot + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + if (ORPHANS > 3) { + // traverse mtree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + } + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_close(&lfs, &t) => 0; + + // we should have cleaned up all grms/orphans + assert(lfs.grm.mids[0] == -1); + assert(lfs.grm.mids[1] == -1); + assert(lfs.hasorphans == false); + + // which means there shouldn't be that many files left + assert((lfs.mtree.u.weight & 0x7fffffff) <= (2 << lfs.mdir_bits)); + assert(file1.o.o.mdir.rbyd.weight <= 3); + assert(file2.o.o.mdir.rbyd.weight <= 3); + + // mdirs should have been compacted + assert((file1.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH); + assert((file2.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH); + + // check we can still read the files + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0; + lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0; + } + + lfsr_file_rewind(&lfs, &file1) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + + lfsr_file_rewind(&lfs, &file2) => 0; + lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + } + + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mkconsistent_compact_conflict] +defines.LOOKAHEAD = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +defines.SIZE = 'FILE_BUFFER_SIZE/2' +# <=2 => grm-able +# >2 => requires orphans +defines.ORPHANS = [0, 1, 2, 3, 100] +# set compact thresh to minimum +defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create two files + lfsr_file_t file1; + lfsr_file_open(&lfs, &file1, "cuttlefish", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf1[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file1) => 0; + + lfsr_file_t file2; + lfsr_file_open(&lfs, &file2, "octopus", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf2[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file2) => 0; + + // write to our mdirs until >gc_compact_thresh full + // + // hack, don't use the internals like this + while ((file1.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) { + lfsr_file_rewind(&lfs, &file1) => 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file1) => 0; + } + + while ((file2.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) { + lfsr_file_rewind(&lfs, &file2) => 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file2) => 0; + } + + // try traversing with mkconsistent + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + LFS_T_MKCONSISTENT + | LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + // traverse mroot + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + + // create this many orphaned files + // + // anytime we close a not-yet-created desync file, we create an + // orphan, but note we need these to be different files, and we need + // to close them after all open calls, otherwise we just end up with + // one orphan (littlefs is eager to clean up orphans) + // + lfsr_file_t orphans[ORPHANS]; + for (lfs_size_t i = 0; i < ORPHANS; i++) { + char name[256]; + sprintf(name, "jellyfish%03x", i); + lfsr_file_open(&lfs, &orphans[i], name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; + } + for (lfs_size_t i = 0; i < ORPHANS; i++) { + lfsr_file_close(&lfs, &orphans[i]) => 0; + } + + // keep traversing + if (ORPHANS <= 3) { + // traverse mroot + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + } else { + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + } + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_close(&lfs, &t) => 0; + + // we should be able to clean up grms + assert(lfs.grm.mids[0] == -1); + assert(lfs.grm.mids[1] == -1); + // if we introduce actual orphans, me _must not_ clear the orphan flag + if (ORPHANS > 3) { + assert(lfs.hasorphans == true); + } + + // mdirs should have been compacted + assert((file1.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH); + assert((file2.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH); + + // check we can still read the files + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0; + lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0; + } + + lfsr_file_rewind(&lfs, &file1) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + + lfsr_file_rewind(&lfs, &file2) => 0; + lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + } + + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mkconsistent_compact_btree] +defines.LOOKAHEAD = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +# limit files to very simple btrees +defines.INLINE_SIZE = 0 +defines.CRYSTAL_THRESH = -1 +defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8' +defines.SIZE = '2*FRAGMENT_SIZE' +# <=2 => grm-able +# >2 => requires orphans +defines.ORPHANS = [0, 1, 2, 3, 100] +# set compact thresh to minimum +defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create two files + lfsr_file_t file1; + lfsr_file_open(&lfs, &file1, "cuttlefish", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf1[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file1) => 0; + + lfsr_file_t file2; + lfsr_file_open(&lfs, &file2, "octopus", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf2[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file2) => 0; + + // create this many orphaned files + // + // anytime we close a not-yet-created desync file, we create an + // orphan, but note we need these to be different files, and we need + // to close them after all open calls, otherwise we just end up with + // one orphan (littlefs is eager to clean up orphans) + // + lfsr_file_t orphans[ORPHANS]; + for (lfs_size_t i = 0; i < ORPHANS; i++) { + char name[256]; + sprintf(name, "jellyfish%03x", i); + lfsr_file_open(&lfs, &orphans[i], name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; + } + for (lfs_size_t i = 0; i < ORPHANS; i++) { + lfsr_file_close(&lfs, &orphans[i]) => 0; + } + + // write to our mdirs until >gc_compact_thresh full + // + // hack, don't use the internals like this + while ((file1.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) { + lfsr_file_rewind(&lfs, &file1) => 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file1) => 0; + } + + while ((file2.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) { + lfsr_file_rewind(&lfs, &file2) => 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file2) => 0; + } + + // try traversing with mkconsistent + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + LFS_T_MKCONSISTENT + | LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + // traverse mroot + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + if (ORPHANS <= 3) { + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } else { + // traverse mtree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_close(&lfs, &t) => 0; + + // we should have cleaned up all grms/orphans + assert(lfs.grm.mids[0] == -1); + assert(lfs.grm.mids[1] == -1); + assert(lfs.hasorphans == false); + + // which means there shouldn't be that many files left + assert((lfs.mtree.u.weight & 0x7fffffff) <= (2 << lfs.mdir_bits)); + assert(file1.o.o.mdir.rbyd.weight <= 3); + assert(file2.o.o.mdir.rbyd.weight <= 3); + + // mdirs should have been compacted + assert((file1.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH); + assert((file2.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH); + + // check we can still read the files + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0; + lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0; + } + + lfsr_file_rewind(&lfs, &file1) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + + lfsr_file_rewind(&lfs, &file2) => 0; + lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + } + + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mkconsistent_compact_bshrub] +defines.LOOKAHEAD = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +# this configuration should create a 2-layer bshrub, which may be +# a bit delicate +defines.INLINE_SIZE = 'BLOCK_SIZE/4' +defines.CRYSTAL_THRESH = -1 +defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8' +defines.SIZE = 'BLOCK_SIZE' +# <=2 => grm-able +# >2 => requires orphans +defines.ORPHANS = [0, 1, 2, 3, 100] +# set compact thresh to minimum +defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create two files + lfsr_file_t file1; + lfsr_file_open(&lfs, &file1, "cuttlefish", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf1[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file1) => 0; + + lfsr_file_t file2; + lfsr_file_open(&lfs, &file2, "octopus", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf2[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file2) => 0; + + // create this many orphaned files + // + // anytime we close a not-yet-created desync file, we create an + // orphan, but note we need these to be different files, and we need + // to close them after all open calls, otherwise we just end up with + // one orphan (littlefs is eager to clean up orphans) + // + lfsr_file_t orphans[ORPHANS]; + for (lfs_size_t i = 0; i < ORPHANS; i++) { + char name[256]; + sprintf(name, "jellyfish%03x", i); + lfsr_file_open(&lfs, &orphans[i], name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; + } + for (lfs_size_t i = 0; i < ORPHANS; i++) { + lfsr_file_close(&lfs, &orphans[i]) => 0; + } + + // write to our mdirs until >gc_compact_thresh full + // + // hack, don't use the internals like this + while ((file1.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) { + lfsr_file_rewind(&lfs, &file1) => 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file1) => 0; + } + + while ((file2.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) { + lfsr_file_rewind(&lfs, &file2) => 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; + lfsr_file_sync(&lfs, &file2) => 0; + } + + // try traversing with mkconsistent + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + LFS_T_MKCONSISTENT + | LFS_T_COMPACT + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + // needs two passes to compact both bshrubs and mdirs + lfsr_traversal_rewind(&lfs, &t) => 0; + // traverse mroot + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + if (ORPHANS <= 3) { + // compacting our bshrub nodes may cause them to split, so we may + // need to traverse more nodes than we started with + for (lfs_size_t i = 0; i < 9; i++) { + // traverse bshrub + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } + } else { + // traverse mtree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + // traverse bshrub + for (lfs_size_t i = 0; i < 5; i++) { + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + // traverse bshrub + for (lfs_size_t i = 0; i < 5; i++) { + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } + } + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + + // needs two passes to compact both bshrubs and mdirs + lfsr_traversal_rewind(&lfs, &t) => 0; + // traverse mroot + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + if (ORPHANS <= 3) { + // compacting our bshrub nodes may cause them to split, so we may + // need to traverse more nodes than we started with + for (lfs_size_t i = 0; i < 6; i++) { + // traverse bshrub + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } + } else { + // traverse mtree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + // traverse bshrub + for (lfs_size_t i = 0; i < 3; i++) { + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } + // traverse mdirs + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + // traverse bshrub + for (lfs_size_t i = 0; i < 3; i++) { + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + } + } + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_close(&lfs, &t) => 0; + + // we should have cleaned up all grms/orphans + assert(lfs.grm.mids[0] == -1); + assert(lfs.grm.mids[1] == -1); + assert(lfs.hasorphans == false); + + // which means there shouldn't be that many files left + assert((lfs.mtree.u.weight & 0x7fffffff) <= (2 << lfs.mdir_bits)); + assert(file1.o.o.mdir.rbyd.weight <= 3); + assert(file2.o.o.mdir.rbyd.weight <= 3); + + // mdirs should have been compacted + assert((file1.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH); + assert((file2.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH); + + // check we can still read the files + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0; + lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0; + } + + lfsr_file_rewind(&lfs, &file1) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + + lfsr_file_rewind(&lfs, &file2) => 0; + lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + } + + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + lfsr_unmount(&lfs) => 0; +''' + + + + # many/fuzz tests mixed with traversals @@ -6757,10 +8281,11 @@ code = ''' [cases.test_traversal_spam_dir_many] # traverse steps between each op defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] -defines.CKMETA = [true] -defines.CKDATA = [true] +defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] +defines.CKMETA = [true] +defines.CKDATA = [true] # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] @@ -6773,10 +8298,11 @@ code = ''' // open a traversal lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) + ((MKCONSISTENT) ? LFS_T_MKCONSISTENT : 0) | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) - | ((COMPACT) ? LFS_T_COMPACT : 0)) => 0; + | ((COMPACT) ? LFS_T_COMPACT : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // make this many directories for (lfs_size_t i = 0; i < N; i++) { @@ -6865,10 +8391,11 @@ code = ''' [cases.test_traversal_spam_dir_fuzz] # traverse steps between each op defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] -defines.CKMETA = [true] -defines.CKDATA = [true] +defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] +defines.CKMETA = [true] +defines.CKDATA = [true] # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] @@ -6888,10 +8415,11 @@ code = ''' // open a traversal lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) + ((MKCONSISTENT) ? LFS_T_MKCONSISTENT : 0) | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) - | ((COMPACT) ? LFS_T_COMPACT : 0)) => 0; + | ((COMPACT) ? LFS_T_COMPACT : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; uint32_t prng = SEED; for (lfs_size_t i = 0; i < OPS; i++) { @@ -7044,10 +8572,11 @@ code = ''' [cases.test_traversal_spam_file_many] # traverse steps between each op defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] -defines.CKMETA = [true] -defines.CKDATA = [true] +defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] +defines.CKMETA = [true] +defines.CKDATA = [true] # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' defines.N = [1, 2, 4, 8, 16, 32, 64] @@ -7070,10 +8599,11 @@ code = ''' // open a traversal lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) + ((MKCONSISTENT) ? LFS_T_MKCONSISTENT : 0) | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) - | ((COMPACT) ? LFS_T_COMPACT : 0)) => 0; + | ((COMPACT) ? LFS_T_COMPACT : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; // create this many files uint32_t prng = 42; @@ -7146,10 +8676,11 @@ code = ''' [cases.test_traversal_spam_file_fuzz] # traverse steps between each op defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] -defines.CKMETA = [true] -defines.CKDATA = [true] +defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] +defines.CKMETA = [true] +defines.CKDATA = [true] # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' defines.N = [1, 2, 4, 8, 16, 32, 64] @@ -7180,10 +8711,11 @@ code = ''' // open a traversal lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) + ((MKCONSISTENT) ? LFS_T_MKCONSISTENT : 0) | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) - | ((COMPACT) ? LFS_T_COMPACT : 0)) => 0; + | ((COMPACT) ? LFS_T_COMPACT : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; uint32_t prng = SEED; for (lfs_size_t i = 0; i < OPS; i++) { @@ -7387,10 +8919,11 @@ code = ''' [cases.test_traversal_spam_fwrite_fuzz] # traverse steps between each op defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] -defines.CKMETA = [true] -defines.CKDATA = [true] +defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] +defines.CKMETA = [true] +defines.CKDATA = [true] # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' defines.OPS = 20 @@ -7453,10 +8986,11 @@ code = ''' // open a traversal lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) + ((MKCONSISTENT) ? LFS_T_MKCONSISTENT : 0) | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) - | ((COMPACT) ? LFS_T_COMPACT : 0)) => 0; + | ((COMPACT) ? LFS_T_COMPACT : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; for (lfs_size_t i = 0; i < OPS; i++) { // choose a random location @@ -7548,10 +9082,11 @@ code = ''' [cases.test_traversal_spam_orphanzombie_fuzz] # traverse steps between each op defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] -defines.CKMETA = [true] -defines.CKDATA = [true] +defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] +defines.CKMETA = [true] +defines.CKDATA = [true] # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' defines.N = [1, 2, 4, 8, 16, 32, 64] @@ -7592,10 +9127,11 @@ code = ''' // open a traversal lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) + ((MKCONSISTENT) ? LFS_T_MKCONSISTENT : 0) | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) - | ((COMPACT) ? LFS_T_COMPACT : 0)) => 0; + | ((COMPACT) ? LFS_T_COMPACT : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; uint32_t prng = SEED; for (lfs_size_t i = 0; i < OPS; i++) { @@ -7917,10 +9453,11 @@ code = ''' [cases.test_traversal_spam_orphanzombiedir_fuzz] # traverse steps between each op defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] -defines.CKMETA = [true] -defines.CKDATA = [true] +defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] +defines.CKMETA = [true] +defines.CKDATA = [true] # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' defines.N = [1, 2, 4, 8, 16, 32, 64] @@ -7962,10 +9499,11 @@ code = ''' // open a traversal lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, - ((CKMETA) ? LFS_T_CKMETA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0) + ((MKCONSISTENT) ? LFS_T_MKCONSISTENT : 0) | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0) - | ((COMPACT) ? LFS_T_COMPACT : 0)) => 0; + | ((COMPACT) ? LFS_T_COMPACT : 0) + | ((CKMETA) ? LFS_T_CKMETA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; uint32_t prng = SEED; for (lfs_size_t i = 0; i < OPS; i++) {