From e744106f77b76a2fc236f47c877f2bd203d2cc64 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 17 Jun 2024 15:57:21 -0500 Subject: [PATCH] t: Implemented a simple traversal clobber scheme This adds lfsr_opened_clobber which can be called to clobber any open traversals related to an mid, or all traversals if mid=-1. Clobbering here means throw away any in-progress btraversals and move to the next mid. We need to do this in several places to avoid outdated references to btrees. The other option would be to treat traversals like additional unsynced file handles, add them to the lookahead buffer, copy shrubs during compaction, etc, but I don't think we want to pay this cost since the underlying data is otherwise inaccessible. No reason to check/repair blocks we're not using anymore... To make this work, LFS_BTRAVERSAL(bid) now supports resuming from a specific bid, in lfsr_mtraversal_t we use this to resume mtree traversal from a specific mid when clobbered. --- Other changes: - lfsr_mdir_commit now marks all removed mdirs with LFS_F_ZOMBIE, and updating related dir positions is done in lfsr_remove/lfsr_rename. I was originally planning to use LFS_F_ZOMBIE to clobber traversals as well, but it didn't work out. - Added lfsr_fs_weight, which returns the effective mdir/mtree weight, including inlined-in-mroot mdirs. - Fixed did-mask miscalculation in lfsr_mkdir where fs/mtree weight wasn't shifted by mdir_bits. This probably just went unnoticed during some mid refactoring. - Changed traversals to only traverse _unsynced_ opened files. No reason to traverse files we know match disk. This also makes is so only unsynced files need to worry about clobbering traversals. This has the catch that we need to point to the traversing file handle somehow so we can clobber correctly. The (hacky?) solution is to point to the next pointer itself, which tells us both where to go next, and what file handle we are currently traversing. - Moved LFS_F_UNSYNC flags to before file operations, instead of after. This is needed for the above traverse-unsync-only logic in case we alloc in the middle of a file operation. Code changes: code stack before: 34454 2544 after: 34682 (+0.7%) 2544 (+0.0%) Also added some specific tests over corner cases caused by traversing and mutating the filesystem at the same time. Unfortunately these aren't passing yet. Our mid-clobbering logic doesn't handle mid insertion correctly, so we end up clobbering more traversals than we need to... --- lfs.c | 315 ++++++---- lfs.h | 12 +- tests/test_traversal.toml | 1136 ++++++++++++++++++++++++++++++++++++- 3 files changed, 1334 insertions(+), 129 deletions(-) diff --git a/lfs.c b/lfs.c index 0b8104b7..6afb1723 100644 --- a/lfs.c +++ b/lfs.c @@ -5208,6 +5208,28 @@ static bool lfsr_mid_isopen(lfs_t *lfs, lfsr_smid_t mid) { return false; } +// needed in lfsr_opened_clobber +static void lfsr_fs_traverseclobber(lfs_t *lfs, lfsr_mtraversal_t *mt); + +// clobber any traversals that match our mid, or all traversals if mid=-1 +static void lfsr_opened_clobber(lfs_t *lfs, lfsr_smid_t mid, bool dirty) { + for (lfsr_omdir_t *o = lfs->opened; o; o = o->next) { + if (o->type == LFS_TYPE_TRAVERSAL) { + // mark as dirty + o->flags |= (dirty) ? LFS_F_DIRTY : 0; + + // clobber if mid matches + if (mid == -1 || o->mdir.mid == mid) { + lfsr_traversal_t *t = (lfsr_traversal_t*)o; + lfsr_fs_traverseclobber(lfs, &t->mt); + // and clear any pending blocks + t->blocks[0] = -1; + t->blocks[1] = -1; + } + } + } +} + /// shrub/sprout things /// @@ -5562,7 +5584,7 @@ static lfsr_data_t lfsr_data_fromgrm(const lfsr_grm_t *grm, } // required by lfsr_data_readgrm -static inline lfsr_mid_t lfsr_mtree_weight(const lfsr_mtree_t *mtree); +static inline lfsr_mid_t lfsr_fs_weight(lfs_t *lfs); static int lfsr_data_readgrm(lfs_t *lfs, lfsr_data_t *data, lfsr_grm_t *grm) { @@ -5588,9 +5610,7 @@ static int lfsr_data_readgrm(lfs_t *lfs, lfsr_data_t *data, if (err) { return err; } - LFS_ASSERT((lfsr_mid_t)grm->mids[i] < lfs_max( - lfsr_mtree_weight(&lfs->mtree), - 1 << lfs->mdir_bits)); + LFS_ASSERT((lfsr_mid_t)grm->mids[i] < lfsr_fs_weight(lfs)); } return 0; @@ -6825,6 +6845,8 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, // checkpoint the allocator lfs_alloc_ckpoint(lfs); + // clobber any related traversals + lfsr_opened_clobber(lfs, mdir->mid, true); // play out any attrs that affect our grm _before_ committing to disk, // keep in mind we revert to on-disk gstate if we run into an error @@ -7325,7 +7347,9 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, return err; } - // success? update in-device state, we must not error at this point + /////////////////////////////////////////////////////////////////////// + // success? update in-device state, we must not error at this point! // + /////////////////////////////////////////////////////////////////////// // toss our cksum into the filesystem seed for pseudorandom numbers if (mdelta >= 0) { @@ -7338,6 +7362,12 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, // update any gstate changes lfsr_fs_commitgdelta(lfs); + // if mtree/mroot changed, clobber all traversals, too much has changed + if (lfsr_mdir_cmp(&mroot_, &lfs->mroot) != 0 + || lfsr_mtree_cmp(&mtree_, &lfs->mtree) != 0) { + lfsr_opened_clobber(lfs, -1, true); + } + // play out any attrs that affect internal state mid_ = mdir->mid; for (lfs_size_t i = 0; i < attr_count; i++) { @@ -7350,10 +7380,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, if (o->mdir.mid < mid_ - attrs[i].weight) { // we should not be removing opened regular files LFS_ASSERT(o->type != LFS_TYPE_REG); - if (o->type == LFS_TYPE_DIR) { - ((lfsr_dir_t*)o)->pos - += (mid_ - attrs[i].weight) - o->mdir.mid; - } + o->flags |= LFS_F_ZOMBIE; o->mdir.mid = mid_; } else { o->mdir.mid += attrs[i].weight; @@ -7739,10 +7766,9 @@ enum { LFSR_TSTATE_MROOTCHAIN = 1, LFSR_TSTATE_MTREE = 2, LFSR_TSTATE_MDIR = 3, - LFSR_TSTATE_MDIRBTREE = 4, - LFSR_TSTATE_OMDIR = 5, - LFSR_TSTATE_OMDIRBTREE = 6, - LFSR_TSTATE_DONE = 7, + LFSR_TSTATE_OMDIR = 4, + LFSR_TSTATE_BTREE = 5, + LFSR_TSTATE_DONE = 6, }; #define LFSR_MTRAVERSAL(_flags) \ @@ -7755,17 +7781,6 @@ enum { .u.mtortoise.step=0, \ .u.mtortoise.power=0}) -static void lfsr_fs_traverserewind(lfs_t *lfs, lfsr_mtraversal_t *mt) { - (void)lfs; - mt->o.flags &= ~LFS_F_DIRTY; - mt->o.state = LFSR_TSTATE_MROOTANCHOR; - mt->o.mdir.mid = -1; - mt->u.mtortoise.mptr.blocks[0] = 0; - mt->u.mtortoise.mptr.blocks[1] = 0; - mt->u.mtortoise.step = 0; - mt->u.mtortoise.power = 0; -} - static inline bool lfsr_t_ismtreeonly(uint32_t flags) { return flags & LFS_T_MTREEONLY; } @@ -7798,11 +7813,39 @@ static inline bool lfsr_f_isdirty(uint32_t flags) { return flags & LFS_F_DIRTY; } +static inline lfsr_mid_t lfsr_fs_weight(lfs_t *lfs) { + return lfs_max( + lfsr_mtree_weight(&lfs->mtree), + 1 << lfs->mdir_bits); +} + +static void lfsr_fs_traverserewind(lfs_t *lfs, lfsr_mtraversal_t *mt) { + (void)lfs; + mt->o.flags &= ~LFS_F_DIRTY; + mt->o.state = LFSR_TSTATE_MROOTANCHOR; + mt->o.mdir.mid = -1; + mt->u.mtortoise.mptr.blocks[0] = 0; + mt->u.mtortoise.mptr.blocks[1] = 0; + mt->u.mtortoise.step = 0; + mt->u.mtortoise.power = 0; +} + +static void lfsr_fs_traverseclobber(lfs_t *lfs, lfsr_mtraversal_t *mt) { + (void)lfs; + // increment the mid (to make progress) and reset to the mtree + mt->o.state = LFSR_TSTATE_MTREE; + mt->o.mdir.mid = lfs_min( + mt->o.mdir.mid + 1, + lfsr_fs_weight(lfs)); + mt->u.bt = LFSR_BTRAVERSAL(mt->o.mdir.mid); +} + // alias mtinfo=btinfo typedef lfsr_btinfo_t lfsr_mtinfo_t; // needed in lfsr_fs_traverse_ +static inline bool lfsr_f_isunsync(uint32_t flags); static int lfsr_bshrub_traverse(lfs_t *lfs, const lfsr_file_t *file, lfsr_btraversal_t *bt, lfsr_bid_t *bid_, lfsr_btinfo_t *btinfo); @@ -7833,15 +7876,15 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, // traverse the mroot chain, checking for mroot/mtree/mdir case LFSR_TSTATE_MROOTCHAIN:; - // lookup mroot, if we find one this just an mroot chain link + // lookup mroot, if we find one this is not the active mroot lfsr_tag_t tag; lfsr_data_t data; err = lfsr_mdir_sublookup(lfs, &mt->o.mdir, LFSR_TAG_STRUCT, &tag, &data); if (err) { - // if we have no mtree/mdir (inlined mdir), we need to traverse - // any files in our mroot next + // if we have no mtree/mdir (inlined mdir), we need to + // traverse any files in our mroot next if (err == LFS_ERR_NOENT) { mt->o.mdir.mid = 0; mt->o.state = LFSR_TSTATE_MDIR; @@ -7860,9 +7903,9 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, // detect cycles with Brent's algorithm // - // note we only check for cycles in the mroot chain, the btree - // inner nodes require checksums of their pointers, so creating - // a valid cycle is actually quite difficult + // note we only check for cycles in the mroot chain, the + // btree inner nodes require checksums of their pointers, + // so creating a valid cycle is actually quite difficult // if (lfsr_mptr_cmp(&mptr, &mt->u.mtortoise.mptr) == 0) { LFS_ERROR("Cycle detected during mtree traversal " @@ -7902,7 +7945,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, return err; } - // transition to mdir traversal next + // transition to traversing the mdir mt->o.state = LFSR_TSTATE_MDIR; mtinfo->tag = LFSR_TAG_MDIR; @@ -7911,8 +7954,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, // found an mtree? } else if (tag == LFSR_TAG_MTREE) { - // read the root of the mtree and return it, lfs->mtree may not - // be initialized yet + // fetch the root of the mtree lfsr_btree_t mtree; err = lfsr_data_readbtree(lfs, &data, &mtree); if (err) { @@ -7920,51 +7962,69 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, } // transition to traversing the mtree + mt->u.bt = LFSR_BTRAVERSAL(0); + mt->o.mdir.mid = 0; mt->o.state = LFSR_TSTATE_MTREE; - mt->u.mt = LFSR_BTRAVERSAL(0); - mtinfo->tag = LFSR_TAG_BRANCH; - mtinfo->u.rbyd = mtree; + // go ahead and traverse the root + // + // this avoids an annoying situation where the mtree is + // uninitialized in mountinited, but we really don't want + // to store another copy of the mtree somewhere because + // we're often traversing in extremely deep call stacks + // such as lfs_alloc + // + // after traversing the mtree root, mountinited should + // initialize lfs->mtree and we can switch to that + // + err = lfsr_btree_traverse(lfs, &mtree, &mt->u.bt, + NULL, mtinfo); + if (err) { + LFS_ASSERT(err != LFS_ERR_NOENT); + return err; + } + + LFS_ASSERT(mtinfo->tag == LFSR_TAG_BRANCH); return 0; } else { - LFS_ERROR("Weird mtree entry? 0x%"PRIx32, tag); + LFS_ERROR("Weird mroot entry? 0x%"PRIx32, tag); return LFS_ERR_CORRUPT; } // traverse the mtree, including both inner btree nodes and mdirs case LFSR_TSTATE_MTREE:; - // no mtree? transition to traversing any opened mdirs - if (lfsr_mtree_ismptr(&lfs->mtree)) { - mt->u.o = lfs->opened; - mt->o.state = LFSR_TSTATE_OMDIR; + // end of mtree? guess we're done + if (mt->o.mdir.mid >= (lfsr_smid_t)lfsr_fs_weight(lfs)) { + mt->o.state = LFSR_TSTATE_DONE; + continue; + } + + // inlined mroot? transition to mdir traversal + if (lfsr_mtree_isnull(&lfs->mtree)) { + mt->o.mdir.rbyd = lfs->mroot.rbyd; + mt->o.state = LFSR_TSTATE_MDIR; + continue; + + // direct mdir? transition to mdir traversal + } else if (lfsr_mtree_ismptr(&lfs->mtree)) { + err = lfsr_mdir_fetch(lfs, &mt->o.mdir, + mt->o.mdir.mid, &lfs->mtree.u.mptr.mptr); + if (err) { + return err; + } + mt->o.state = LFSR_TSTATE_MDIR; continue; } // traverse through the mtree - lfsr_bid_t bid; - err = lfsr_btree_traverse(lfs, &lfs->mtree.u.btree, &mt->u.mt, - &bid, mtinfo); + err = lfsr_btree_traverse(lfs, &lfs->mtree.u.btree, &mt->u.bt, + NULL, mtinfo); if (err) { - // end of mtree? transition to traversing any opened mdirs - if (err == LFS_ERR_NOENT) { - mt->u.o = lfs->opened; - mt->o.state = LFSR_TSTATE_OMDIR; - continue; - } + LFS_ASSERT(err != LFS_ERR_NOENT); return err; } - // wait is this the mtree's root? skip this, we assume we've already - // seen it above (this gets a bit weird because 1. mtree may be - // uninitialized in mountinited and 2. stack really matters since - // we're at the bottom of lfs_alloc) - if (mtinfo->tag == LFSR_TAG_BRANCH - && mtinfo->u.rbyd.blocks[0] - == lfs->mtree.u.btree.blocks[0]) { - continue; - } - // inner btree nodes already decoded if (mtinfo->tag == LFSR_TAG_BRANCH) { return 0; @@ -7978,8 +8038,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, } err = lfsr_mdir_fetch(lfs, &mt->o.mdir, - LFSR_MID(lfs, bid, 0), - &mptr); + mt->o.mdir.mid, &mptr); if (err) { return err; } @@ -8003,6 +8062,9 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, if (lfsr_t_ismtreeonly(mt->o.flags) || lfsr_mid_rid(lfs, mt->o.mdir.mid) >= (lfsr_srid_t)mt->o.mdir.rbyd.weight) { + // resume from our mid + mt->o.mdir.mid = lfsr_mid_bid(lfs, mt->o.mdir.mid) + 1; + mt->u.bt = LFSR_BTRAVERSAL(mt->o.mdir.mid); mt->o.state = LFSR_TSTATE_MTREE; continue; } @@ -8037,60 +8099,63 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, return err; } - // no? continue to next file + // no? next we need to check any opened files } else { - mt->o.mdir.mid += 1; + mt->ot = &lfs->opened; + mt->o.state = LFSR_TSTATE_OMDIR; continue; } // start traversing - mt->bt = LFSR_BTRAVERSAL(0); - mt->o.state = LFSR_TSTATE_MDIRBTREE; + mt->u.bt = LFSR_BTRAVERSAL(0); + mt->ot = &lfs->opened; + mt->o.state = LFSR_TSTATE_BTREE; continue; // scan for blocks/btrees in our opened file list case LFSR_TSTATE_OMDIR:; - // not traversing all blocks? reached end of opened file list? - if (lfsr_t_ismtreeonly(mt->o.flags) || !mt->u.o) { - mt->o.state = LFSR_TSTATE_DONE; + // reached end of opened files? return to mdir traversal + lfsr_omdir_t *o = *mt->ot; + if (!o) { + mt->o.mdir.mid += 1; + mt->o.state = LFSR_TSTATE_MDIR; continue; } - // skip non-files - if (mt->u.o->type != LFS_TYPE_REG) { - mt->u.o = mt->u.o->next; + // skip unrelated files, we only care about unsync reg files + // associated with the current mid + // + // we traverse mids separately to make recovery from clobbered + // traversals easier, which means this grows O(n^2) if you have + // literally every file open, but other things grow O(n^2) with + // this list anyways + // + if (o->mdir.mid != mt->o.mdir.mid + || o->type != LFS_TYPE_REG + || !lfsr_f_isunsync(o->flags)) { + mt->ot = &o->next; continue; } // start traversing the file - const lfsr_file_t *file = (const lfsr_file_t*)mt->u.o; - mt->o.mdir = file->o.mdir; + const lfsr_file_t *file = (const lfsr_file_t*)o; mt->bshrub = file->bshrub; - mt->bt = LFSR_BTRAVERSAL(0); - mt->o.state = LFSR_TSTATE_OMDIRBTREE; + mt->u.bt = LFSR_BTRAVERSAL(0); + mt->ot = &o->next; + mt->o.state = LFSR_TSTATE_BTREE; continue; // traverse any file btrees, including both inner btree nodes and // block pointers - case LFSR_TSTATE_MDIRBTREE:; - case LFSR_TSTATE_OMDIRBTREE:; + case LFSR_TSTATE_BTREE:; // traverse through our file - err = lfsr_bshrub_traverse(lfs, (const lfsr_file_t*)mt, &mt->bt, + err = lfsr_bshrub_traverse(lfs, (const lfsr_file_t*)mt, &mt->u.bt, NULL, mtinfo); if (err) { if (err == LFS_ERR_NOENT) { - // end of btree? go to next file - if (mt->o.state == LFSR_TSTATE_MDIRBTREE) { - mt->o.mdir.mid += 1; - mt->o.state = LFSR_TSTATE_MDIR; - continue; - } else if (mt->o.state == LFSR_TSTATE_OMDIRBTREE) { - mt->u.o = mt->u.o->next; - mt->o.state = LFSR_TSTATE_OMDIR; - continue; - } else { - LFS_UNREACHABLE(); - } + // end of btree? go to next opened file + mt->o.state = LFSR_TSTATE_OMDIR; + continue; } return err; } @@ -8124,7 +8189,8 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, static void lfs_alloc_markinuse(lfs_t *lfs, lfs_block_t block); // high-level immutable traversal, handle extra features here, -// but no mutation! +// but no mutation! (we're called in lfs_alloc, so things would end up +// recursive) static int lfsr_fs_traverse(lfs_t *lfs, lfsr_mtraversal_t *mt, lfsr_mtinfo_t *mtinfo) { int err = lfsr_fs_traverse_(lfs, mt, mtinfo); @@ -8132,7 +8198,6 @@ static int lfsr_fs_traverse(lfs_t *lfs, lfsr_mtraversal_t *mt, return err; } - // validate btree nodes? note mdirs are already validated if (lfsr_t_isckmetadata(mt->o.flags) && mtinfo->tag == LFSR_TAG_BRANCH) { @@ -9144,9 +9209,7 @@ static int lfsr_fs_fixgrm(lfs_t *lfs) { while (lfsr_grm_hasrm(&lfs->grm)) { // find our mdir lfsr_mdir_t mdir; - LFS_ASSERT(lfs->grm.mids[0] < lfs_smax( - lfsr_mtree_weight(&lfs->mtree), - 1 << lfs->mdir_bits)); + LFS_ASSERT((lfsr_mid_t)lfs->grm.mids[0] < lfsr_fs_weight(lfs)); int err = lfsr_mtree_lookup(lfs, &lfs->mtree, lfs->grm.mids[0], &mdir); if (err) { @@ -9475,9 +9538,6 @@ int lfsr_traversal_rewind(lfs_t *lfs, lfsr_traversal_t *t) { /// Directory operations /// -// needed in lfsr_mkdir -static inline bool lfsr_f_iszombie(uint32_t flags); - int lfsr_mkdir(lfs_t *lfs, const char *path) { // prepare our filesystem for writing int err = lfsr_fs_mkconsistent(lfs); @@ -9553,10 +9613,12 @@ int lfsr_mkdir(lfs_t *lfs, const char *path) { // // Note we also need to be careful to catch integer overflow. // - lfsr_did_t dmask = (1 << lfs_min( - lfs_nlog2(lfsr_mtree_weight(&lfs->mtree)) - + lfs_nlog2(lfs->cfg->block_size/32), - 31)) - 1; + lfsr_did_t dmask + = (1 << lfs_min( + lfs_nlog2(lfsr_fs_weight(lfs) >> lfs->mdir_bits) + + lfs_nlog2(lfs->cfg->block_size/32), + 31) + ) - 1; lfsr_did_t did_ = lfs_crc32c(0, path, lfs_strlen(path)) & dmask; // Check if we have a collision. If we do, search for the next @@ -9642,6 +9704,9 @@ int lfsr_mkdir(lfs_t *lfs, const char *path) { return 0; } +// needed in lfsr_remove +static inline bool lfsr_f_iszombie(uint32_t flags); + int lfsr_remove(lfs_t *lfs, const char *path) { // prepare our filesystem for writing int err = lfsr_fs_mkconsistent(lfs); @@ -9747,7 +9812,7 @@ int lfsr_remove(lfs_t *lfs, const char *path) { | LFS_F_UNSYNC | LFS_O_DESYNC; - // mark any removed dirs as zombies + // mark any removed dirs as zombied } else if (did_ && o->type == LFS_TYPE_DIR && ((lfsr_dir_t*)o)->did == did_) { @@ -9757,7 +9822,11 @@ int lfsr_remove(lfs_t *lfs, const char *path) { } else if (o->type == LFS_TYPE_DIR && ((lfsr_dir_t*)o)->did == did && o->mdir.mid >= mdir.mid) { - ((lfsr_dir_t*)o)->pos -= 1; + if (lfsr_f_iszombie(o->flags)) { + o->flags &= ~LFS_F_ZOMBIE; + } else { + ((lfsr_dir_t*)o)->pos -= 1; + } } } @@ -9916,7 +9985,7 @@ int lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path) { && o->mdir.mid == lfs->grm.mids[0]) { o->mdir = new_mdir; - // mark any removed dirs as zombies + // mark any removed dirs as zombied } else if (new_did_ && o->type == LFS_TYPE_DIR && ((lfsr_dir_t*)o)->did == new_did_) { @@ -9932,7 +10001,11 @@ int lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path) { if (((lfsr_dir_t*)o)->did == old_did && o->mdir.mid >= lfs->grm.mids[0]) { - ((lfsr_dir_t*)o)->pos -= 1; + if (o->mdir.mid == lfs->grm.mids[0]) { + o->mdir.mid += 1; + } else { + ((lfsr_dir_t*)o)->pos -= 1; + } } } } @@ -10583,6 +10656,12 @@ int lfsr_file_close(lfs_t *lfs, lfsr_file_t *file) { err = lfsr_file_sync(lfs, file); } + // if we're unsync, we need to clobber any traversals that may be + // referencing our bshrub/memory, but we don't need to mark as dirty + if (lfsr_f_isunsync(file->o.flags)) { + lfsr_opened_clobber(lfs, file->o.mdir.mid, false); + } + // remove from tracked mdirs lfsr_opened_remove(lfs, &file->o); @@ -11903,6 +11982,10 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file, // checkpoint the allocator lfs_alloc_ckpoint(lfs); + // clobber any related traversals + lfsr_opened_clobber(lfs, file->o.mdir.mid, true); + // mark as unsynced in case we fail + file->o.flags |= LFS_F_UNSYNC; // update pos if we are appending lfs_off_t pos = file->pos; @@ -12006,8 +12089,6 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file, file->o.flags &= ~LFS_F_UNFLUSH; } - // mark as unsynced - file->o.flags |= LFS_F_UNSYNC; // update our pos file->pos = pos; @@ -12061,6 +12142,8 @@ int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file) { // checkpoint the allocator lfs_alloc_ckpoint(lfs); + // clobber any related traversals + lfsr_opened_clobber(lfs, file->o.mdir.mid, true); // flush our buffer if it contains any unwritten data int err; @@ -12320,6 +12403,10 @@ int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) { // checkpoint the allocator lfs_alloc_ckpoint(lfs); + // clobber any related traversals + lfsr_opened_clobber(lfs, file->o.mdir.mid, true); + // mark as unsynced in case we fail + file->o.flags |= LFS_F_UNSYNC; // does our file become small? int err; @@ -12379,9 +12466,6 @@ int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) { size_ - lfs_min(file->buffer.pos, size_)); } - // mark as unsynced - file->o.flags |= LFS_F_UNSYNC; - // flush if requested // // this seems unreachable, but it's possible if we transition from @@ -12425,6 +12509,10 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) { // checkpoint the allocator lfs_alloc_ckpoint(lfs); + // clobber any related traversals + lfsr_opened_clobber(lfs, file->o.mdir.mid, true); + // mark as unsynced in case we fail + file->o.flags |= LFS_F_UNSYNC; // does our file become small? int err; @@ -12512,9 +12600,6 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) { file->buffer.pos); } - // mark as unsynced - file->o.flags |= LFS_F_UNSYNC; - // flush if requested // // this seems unreachable, but it's possible if we transition from diff --git a/lfs.h b/lfs.h index 9c334d43..3187f77b 100644 --- a/lfs.h +++ b/lfs.h @@ -181,6 +181,7 @@ enum lfs_traversal_flags { // internally used flags LFS_F_DIRTY = 0x1000, // Filesystem has been modified + //LFS_F_ZOMBIE = 0x8000, // File has been removed }; @@ -610,6 +611,9 @@ typedef struct lfsr_mtraversal { const struct lfs_file_config *cfg; lfsr_bshrub_t bshrub; + // opened file state, we use an indirect pointer here so we + // always point to data associated with the current mid + lfsr_omdir_t **ot; union { // cycle detection state, only valid when traversing the mroot chain struct { @@ -617,13 +621,9 @@ typedef struct lfsr_mtraversal { lfs_block_t step; uint8_t power; } mtortoise; - // mtree traversal state, only valid when traversing the mtree - lfsr_btraversal_t mt; - // opened file state, only valid when traversing opened files - const lfsr_omdir_t *o; + // btree traversal state + lfsr_btraversal_t bt; } u; - // btree traversal state - lfsr_btraversal_t bt; } lfsr_mtraversal_t; typedef struct lfsr_traversal { diff --git a/tests/test_traversal.toml b/tests/test_traversal.toml index 22f496b6..69df1cdb 100644 --- a/tests/test_traversal.toml +++ b/tests/test_traversal.toml @@ -2164,13 +2164,1129 @@ code = ''' ''' +# test specific cases where we need to clobber traversals +# +# these assume quite a bit more and may be a bit fragile... +# +[cases.test_traversal_mutation_file_bsprout] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +defines.SIZE = 'FILE_BUFFER_SIZE/2' +defines.TRUNC = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create two files + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | 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, &file, wbuf1, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "tarantula", + LFS_O_WRONLY | 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, &file, wbuf2, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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); + + // rewrite the file + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY + | ((TRUNC) ? LFS_O_TRUNC : 0)) => 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf1, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // we should be at end of traversal now + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + + // check the file contents + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "tarantula", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_file_btree] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +defines.SIZE = '2*BLOCK_SIZE' +defines.INLINE_SIZE = 0 +defines.TRUNC = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create two files + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | 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, &file, wbuf1, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "tarantula", + LFS_O_WRONLY | 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, &file, wbuf2, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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); + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse one data block + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + + // rewrite the file + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY + | ((TRUNC) ? LFS_O_TRUNC : 0)) => 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf1, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse two data blocks + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + // we should be at end of traversal now + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + + // check the file contents + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "tarantula", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_file_bshrub] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +defines.SIZE = '2*BLOCK_SIZE' +defines.TRUNC = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create two files + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | 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, &file, wbuf1, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "tarantula", + LFS_O_WRONLY | 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, &file, wbuf2, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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); + // traverse one data block + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + + // rewrite the file + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY + | ((TRUNC) ? LFS_O_TRUNC : 0)) => 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf1, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // traverse two data blocks + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + // we should be at end of traversal now + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + + // check the file contents + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "tarantula", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_orphan_bsprout] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +defines.SIZE = 'FILE_BUFFER_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, "spider", + LFS_O_WRONLY | 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, "tarantula", + LFS_O_WRONLY | 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; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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); + + // rewrite the file + 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; + + // we should be at end of traversal now + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + + // check the file contents + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "tarantula", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_orphan_btree] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +defines.SIZE = '2*BLOCK_SIZE' +defines.INLINE_SIZE = 0 +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, "spider", + LFS_O_WRONLY | 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, "tarantula", + LFS_O_WRONLY | 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; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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); + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse one data block + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + + // rewrite the file + 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; + + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse two data blocks + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + // we should be at end of traversal now + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + + // check the file contents + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "tarantula", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_orphan_bshrub] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +defines.SIZE = '2*BLOCK_SIZE' +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, "spider", + LFS_O_WRONLY | 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, "tarantula", + LFS_O_WRONLY | 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; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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); + // traverse one data block + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + + // rewrite the file + 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; + + // traverse two data blocks + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + // we should be at end of traversal now + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_file_close(&lfs, &file1) => 0; + lfsr_file_close(&lfs, &file2) => 0; + + // check the file contents + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "tarantula", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_close_bsprout] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +defines.SIZE = 'FILE_BUFFER_SIZE/2' +defines.DESYNC = [false, true] +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, "spider", + LFS_O_WRONLY | 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, "tarantula", + LFS_O_WRONLY | 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; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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); + + // close the file + if (DESYNC) { + lfsr_file_desync(&lfs, &file1) => 0; + } + lfsr_file_close(&lfs, &file1) => 0; + + // we should be at end of traversal now + lfsr_traversal_read(&lfs, &t, &tinfo) + => (DESYNC) ? LFS_ERR_NOENT : LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_file_close(&lfs, &file2) => 0; + + // check the file contents + lfsr_file_t file; + if (DESYNC) { + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => LFS_ERR_NOENT; + } else { + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + lfsr_file_open(&lfs, &file, "tarantula", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_close_btree] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +defines.SIZE = '2*BLOCK_SIZE' +defines.INLINE_SIZE = 0 +defines.DESYNC = [false, true] +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, "spider", + LFS_O_WRONLY | 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, "tarantula", + LFS_O_WRONLY | 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; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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); + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse one data block + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + + // close the file + if (DESYNC) { + lfsr_file_desync(&lfs, &file1) => 0; + } + lfsr_file_close(&lfs, &file1) => 0; + + // traverse btree + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse two data blocks + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + // we should be at end of traversal now + lfsr_traversal_read(&lfs, &t, &tinfo) + => (DESYNC) ? LFS_ERR_NOENT : LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_file_close(&lfs, &file2) => 0; + + // check the file contents + lfsr_file_t file; + if (DESYNC) { + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => LFS_ERR_NOENT; + } else { + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + lfsr_file_open(&lfs, &file, "tarantula", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_close_bshrub] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +defines.SIZE = '2*BLOCK_SIZE' +defines.DESYNC = [false, true] +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, "spider", + LFS_O_WRONLY | 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, "tarantula", + LFS_O_WRONLY | 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; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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); + // traverse one data block + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + + // close the file + if (DESYNC) { + lfsr_file_desync(&lfs, &file1) => 0; + } + lfsr_file_close(&lfs, &file1) => 0; + + // traverse two data blocks + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + // we should be at end of traversal now + lfsr_traversal_read(&lfs, &t, &tinfo) + => (DESYNC) ? LFS_ERR_NOENT : LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_file_close(&lfs, &file2) => 0; + + // check the file contents + lfsr_file_t file; + if (DESYNC) { + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => LFS_ERR_NOENT; + } else { + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + lfsr_file_open(&lfs, &file, "tarantula", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_mroot_split] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +defines.SIZE = '2*BLOCK_SIZE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create two files + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | 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, &file, wbuf1, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "wolfspider", + LFS_O_WRONLY | 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, &file, wbuf2, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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 enough files for mroot to split + lfs_size_t i = 0; + while (lfs.mtree.u.weight == 0x80000000) { + char name[256]; + sprintf(name, "uloborus%03x", i); + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + i += 1; + } + + // traverse the mtree root + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_BTREE); + // traverse one 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); + // and two data blocks + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + // and another 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); + // and another two data blocks + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + // we should be at end of traversal now + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + + // check the file contents + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "wolfspider", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_mroot_split_l] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +defines.SIZE = '2*BLOCK_SIZE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create three files + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | 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, &file, wbuf1, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "tarantula", + LFS_O_WRONLY | 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, &file, wbuf2, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "wolfspider", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf3[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf3[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf3, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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); + // traverse a data block + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + + // create enough files for mroot to split + lfs_size_t i = 0; + while (lfs.mtree.u.weight == 0x80000000) { + char name[256]; + sprintf(name, "uloborus%03x", i); + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + i += 1; + } + + // traverse one 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); + // traverse two data blocks + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + // and another 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); + // and another two data blocks + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + // we should be at end of traversal now + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + + // check the file contents + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "tarantula", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "wolfspider", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf3, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_mroot_split_r] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +defines.SIZE = '2*BLOCK_SIZE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create three files + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | 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, &file, wbuf1, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "wolfspider", + LFS_O_WRONLY | 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, &file, wbuf2, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "zodarion", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf3[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf3[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf3, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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); + // traverse two data blocks + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + // and another data block + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + + // create enough files for mroot to split + lfs_size_t i = 0; + while (lfs.mtree.u.weight == 0x80000000) { + char name[256]; + sprintf(name, "uloborus%03x", i); + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + i += 1; + } + + // and another 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); + // and another two data blocks + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_DATA); + // we should be at end of traversal now + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + + // check the file contents + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "wolfspider", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "zoldarion", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf3, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + + +#[cases.test_traversal_mutation_mroot_chain_split] +#[cases.test_traversal_mutation_mroot_chain_split_l] +#[cases.test_traversal_mutation_mroot_chain_split_r] +#[cases.test_traversal_mutation_mtree_split] +#[cases.test_traversal_mutation_mtree_split_l] +#[cases.test_traversal_mutation_mtree_split_r] + + # many/fuzz tests mixed with traversals # # these should hopefully test a bunch of messy traversal state +# -# TODO actually implement this -[cases.test_traversal_mutation_dir_many] +[cases.test_traversal_spam_dir_many] # traverse steps between each op defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] defines.EXCL = [false, true] @@ -2276,7 +3392,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -[cases.test_traversal_mutation_dir_fuzz] +[cases.test_traversal_spam_dir_fuzz] # traverse steps between each op defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] defines.EXCL = [false, true] @@ -2453,7 +3569,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -[cases.test_traversal_mutation_file_many] +[cases.test_traversal_spam_file_many] # traverse steps between each op defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] defines.EXCL = [false, true] @@ -2553,7 +3669,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -[cases.test_traversal_mutation_file_fuzz] +[cases.test_traversal_spam_file_fuzz] # traverse steps between each op defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] defines.EXCL = [false, true] @@ -2792,7 +3908,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -[cases.test_traversal_mutation_fwrite_fuzz] +[cases.test_traversal_spam_fwrite_fuzz] # traverse steps between each op defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] defines.EXCL = [false, true] @@ -2951,7 +4067,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -[cases.test_traversal_mutation_orphanzombie_fuzz] +[cases.test_traversal_spam_orphanzombie_fuzz] # traverse steps between each op defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] defines.EXCL = [false, true] @@ -3122,6 +4238,8 @@ code = ''' // close the file without affected disk lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + // clobber closed files to try to catch lingering references + memset(&sim_files[j]->file, 0xcc, sizeof(lfsr_file_t)); // remove from list free(sim_files[j]); @@ -3316,7 +4434,7 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -[cases.test_traversal_mutation_orphanzombiedir_fuzz] +[cases.test_traversal_spam_orphanzombiedir_fuzz] # traverse steps between each op defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] defines.EXCL = [false, true] @@ -3494,6 +4612,8 @@ code = ''' // close the file without affected disk lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + // clobber closed files to try to catch lingering references + memset(&sim_files[j]->file, 0xcc, sizeof(lfsr_file_t)); // remove from list free(sim_files[j]);