From 670b9fbf9978a19f7143a7133f8fda69537c1b99 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 14 Jun 2024 01:53:31 -0500 Subject: [PATCH] t: Implemented rudimentary lfsr_traversal_t and related functions This adds the lfsr_traversal_t object, which encapsulates a traversal over all blocks in the filesystem. This replaces the earlier lfs_fs_traverse function, but is sort of "inside-out" in that instead of taking a callback, an lfsr_traversal_t object can be read from to return lfs_tinfo structs that describe the blocks in our system: lfsr_traversal_open(&lfs, &t) => 0; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; tinfo.btype => LFS_BTYPE_MDIR; tinfo.block => 0x0; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; tinfo.btype => LFS_BTYPE_MDIR; tinfo.block => 0x1; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; tinfo.btype => LFS_BTYPE_DATA; tinfo.block => 0x42; lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; lfsr_traversal_close(&lfs, &t) => 0; This is more flexible, allowing for aborted traversals, yielding, rewinding, etc, but also more complicated to implement, since it requires all traversal state to be stored explicitly. Fortunately, since we needed to reimplement filesystem traversals anyways, I was able to build this into the new system from the start using a small state machine to drive the traversal internally. So all that was really needed was a bit of window dressing, adding LFS_TYPE_TRAVERSAL to track open traversals, logic to handle invalidating traversals on file close, mutation, etc... Which, uh, that last one is not implemented yet. Interactions with other filesystem operations gets messy, so I figured I'd go ahead and commit what is currently working. Ugh, and tests. The biggest downside of adding lfsr_traversal_t is how many more corner-cases it adds to the system... lfsr_traversal_t is going to be a work-in-progress for a bit... --- lfsr_traversal_t also adds a really interesting path towards more access to advanced low-level operations, such as checking metadata/data checksums, incrementally progressing the garbage collector, even repairing bad metadata/data blocks eventually. Currently implemented is LFS_T_CKMETADATA and LFS_T_CKDATA to check metadata and data checksums respectively. This is the first feature that actually allows you to validate data checksums. Code changes so far: code stack before: 33886 2560 after: 34226 (+1.0%) 2560 (+0.0%) --- lfs.c | 750 +++++++++------- lfs.h | 155 +++- tests/test_alloc.toml | 136 ++- tests/test_btree.toml | 64 +- tests/test_mtree.toml | 296 +++---- tests/test_traversal.toml | 1705 +++++++++++++++++++++++++++++++++++++ 6 files changed, 2526 insertions(+), 580 deletions(-) create mode 100644 tests/test_traversal.toml diff --git a/lfs.c b/lfs.c index 55961f8d..8c06ff6e 100644 --- a/lfs.c +++ b/lfs.c @@ -1703,19 +1703,6 @@ typedef struct lfsr_shrubcommit lfsr_shrubcommit_t; LFSR_ATTR_(_tag, _weight, (const lfsr_shrub_t*){_shrub}, 0) - -// generalized info returned by traveral functions -typedef struct lfsr_tinfo { - lfsr_tag_t tag; - union { - lfsr_data_t data; - lfsr_mdir_t mdir; - lfsr_rbyd_t rbyd; - lfsr_bptr_t bptr; - } u; -} lfsr_tinfo_t; - - //struct lfsr_attr_from { // const lfsr_rbyd_t *rbyd; // const struct lfsr_attr *attrs; @@ -1989,6 +1976,26 @@ static int lfsr_data_readbptr(lfs_t *lfs, lfsr_data_t *data, return 0; } +// check the contents of a bptr +static int lfsr_bptr_ck(lfs_t *lfs, const lfsr_bptr_t *bptr) { + uint32_t cksum = 0; + int err = lfsr_bd_cksum(lfs, bptr->data.u.disk.block, 0, 0, bptr->cksize, + &cksum); + if (err) { + return err; + } + + // test that our cksum matches what's expected + if (cksum != bptr->cksum) { + LFS_ERROR("Found bptr cksum mismatch bptr 0x%"PRIx32".%"PRIx32", " + "cksum %08"PRIx32" (!= %08"PRIx32")", + bptr->data.u.disk.block, bptr->cksize, cksum, bptr->cksum); + return LFS_ERR_CORRUPT; + } + + return 0; +} + //// other endianness operations @@ -2366,7 +2373,7 @@ static int lfsr_rbyd_fetch(lfs_t *lfs, lfsr_rbyd_t *rbyd, } // a more aggressive fetch when checksum is known -static int lfsr_rbyd_fetchvalidate(lfs_t *lfs, lfsr_rbyd_t *rbyd, +static int lfsr_rbyd_fetchck(lfs_t *lfs, lfsr_rbyd_t *rbyd, lfs_block_t block, lfs_size_t trunk, uint32_t cksum) { int err = lfsr_rbyd_fetch(lfs, rbyd, block, trunk); @@ -4366,7 +4373,7 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, // // a funny benefit is we cache the root of our btree this way if (!lfsr_rbyd_isfetched(&rbyd)) { - int err = lfsr_rbyd_fetchvalidate(lfs, &rbyd, + int err = lfsr_rbyd_fetchck(lfs, &rbyd, rbyd.blocks[0], lfsr_rbyd_trunk(&rbyd), rbyd.cksum); if (err) { @@ -4949,11 +4956,6 @@ static lfs_scmp_t lfsr_btree_namelookup(lfs_t *lfs, const lfsr_btree_t *btree, // // note this is different from iteration, iteration should use // lfsr_btree_lookupnext, traversal includes inner btree nodes -typedef struct lfsr_btraversal { - lfsr_bid_t bid; - lfsr_srid_t rid; - lfsr_rbyd_t branch; -} lfsr_btraversal_t; #define LFSR_BTRAVERSAL() \ ((lfsr_btraversal_t){ \ @@ -4962,35 +4964,40 @@ typedef struct lfsr_btraversal { .branch.trunk=0, \ .branch.weight=0}) +typedef struct lfsr_btinfo { + lfsr_bid_t bid; + lfsr_tag_t tag; + union { + lfsr_rbyd_t rbyd; + lfsr_data_t data; + lfsr_bptr_t bptr; + } u; +} lfsr_btinfo_t; + static int lfsr_btree_traverse_(lfs_t *lfs, const lfsr_btree_t *btree, - lfsr_btraversal_t *t, - lfsr_bid_t *bid_, lfsr_tinfo_t *tinfo_) { + lfsr_btraversal_t *bt, lfsr_btinfo_t *btinfo) { // explicitly traverse the root even if weight=0 - if (t->branch.trunk == 0 + if (bt->branch.trunk == 0 // unless we don't even have a root yet && lfsr_rbyd_trunk(btree) != 0 // or are a shrub && !lfsr_rbyd_isshrub(btree)) { - t->rid = t->bid; - t->branch = *btree; + bt->rid = bt->bid; + bt->branch = *btree; // traverse the root - if (t->rid == 0) { - if (bid_) { - *bid_ = btree->weight-1; - } - if (tinfo_) { - tinfo_->tag = LFSR_TAG_BRANCH; - tinfo_->u.rbyd = t->branch; - } + if (bt->rid == 0) { + btinfo->bid = btree->weight-1; + btinfo->tag = LFSR_TAG_BRANCH; + btinfo->u.rbyd = bt->branch; return 0; } } // need to restart from the root? - if (t->rid >= (lfsr_srid_t)t->branch.weight) { - t->rid = t->bid; - t->branch = *btree; + if (bt->rid >= (lfsr_srid_t)bt->branch.weight) { + bt->rid = bt->bid; + bt->branch = *btree; } // descend down the tree @@ -4999,14 +5006,14 @@ static int lfsr_btree_traverse_(lfs_t *lfs, const lfsr_btree_t *btree, lfsr_tag_t tag__; lfsr_rid_t weight__; lfsr_data_t data__; - int err = lfsr_rbyd_lookupnext(lfs, &t->branch, t->rid, 0, + int err = lfsr_rbyd_lookupnext(lfs, &bt->branch, bt->rid, 0, &rid__, &tag__, &weight__, &data__); if (err) { return err; } if (lfsr_tag_suptype(tag__) == LFSR_TAG_NAME) { - err = lfsr_rbyd_sublookup(lfs, &t->branch, rid__, LFSR_TAG_STRUCT, + err = lfsr_rbyd_sublookup(lfs, &bt->branch, rid__, LFSR_TAG_STRUCT, &tag__, &data__); if (err) { LFS_ASSERT(err != LFS_ERR_NOENT); @@ -5017,26 +5024,22 @@ static int lfsr_btree_traverse_(lfs_t *lfs, const lfsr_btree_t *btree, // found another branch if (tag__ == LFSR_TAG_BRANCH) { // adjust rid with subtree's weight - t->rid -= (rid__ - (weight__-1)); + bt->rid -= (rid__ - (weight__-1)); // fetch the next branch err = lfsr_data_readbranch(lfs, &data__, weight__, - &t->branch); + &bt->branch); if (err) { return err; } - LFS_ASSERT((lfsr_bid_t)t->branch.weight == weight__); + LFS_ASSERT((lfsr_bid_t)bt->branch.weight == weight__); // return inner btree nodes if this is the first time we've // seen them - if (t->rid == 0) { - if (bid_) { - *bid_ = t->bid + (rid__ - t->rid); - } - if (tinfo_) { - tinfo_->tag = LFSR_TAG_BRANCH; - tinfo_->u.rbyd = t->branch; - } + if (bt->rid == 0) { + btinfo->bid = bt->bid + (rid__ - bt->rid); + btinfo->tag = LFSR_TAG_BRANCH; + btinfo->u.rbyd = bt->branch; return 0; } @@ -5046,27 +5049,21 @@ static int lfsr_btree_traverse_(lfs_t *lfs, const lfsr_btree_t *btree, // // note this effectively traverses a full leaf without redoing // the btree walk - lfsr_bid_t bid__ = t->bid + (rid__ - t->rid); - t->bid = bid__ + 1; - t->rid = rid__ + 1; + lfsr_bid_t bid__ = bt->bid + (rid__ - bt->rid); + bt->bid = bid__ + 1; + bt->rid = rid__ + 1; - if (bid_) { - *bid_ = bid__; - } - if (tinfo_) { - tinfo_->tag = tag__; - tinfo_->u.data = data__; - } + btinfo->bid = bid__; + btinfo->tag = tag__; + btinfo->u.data = data__; return 0; } } } static int lfsr_btree_traverse(lfs_t *lfs, const lfsr_btree_t *btree, - lfsr_btraversal_t *t, - lfsr_bid_t *bid_, lfsr_tinfo_t *tinfo_) { - return lfsr_btree_traverse_(lfs, btree, t, - bid_, tinfo_); + lfsr_btraversal_t *bt, lfsr_btinfo_t *btinfo) { + return lfsr_btree_traverse_(lfs, btree, bt, btinfo); } @@ -7685,113 +7682,151 @@ static int lfsr_mtree_pathlookup(lfs_t *lfs, const lfsr_mtree_t *mtree, /// Traversal stuff /// -// incremental filesystem traversal -typedef struct lfsr_traversal { - // core traversal state - uint8_t flags; - uint8_t state; - union { - // cycle detection state, only valid when traversing the mroot chain - struct { - lfsr_mptr_t mptr; - lfs_block_t step; - uint8_t power; - } mtortoise; - // btree 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; - } u; - // we really don't want to pay the RAM cost for a full file, - // so only store the relevant bits, is this a hack? yes - struct { - lfsr_omdir_t o; - const struct lfs_file_config *cfg; - lfsr_bshrub_t bshrub; - } file; - lfsr_btraversal_t bt; -} lfsr_traversal_t; - -enum { - // traverse all blocks in the filesystem - LFSR_TRAVERSAL_ALL = 0x1, - // validate checksums while traversing - LFSR_TRAVERSAL_VALIDATE = 0x2, -}; +//// incremental filesystem traversal +//typedef struct lfsr_traversal { +// // core traversal state +// uint8_t flags; +// uint8_t state; +// union { +// // cycle detection state, only valid when traversing the mroot chain +// struct { +// lfsr_mptr_t mptr; +// lfs_block_t step; +// uint8_t power; +// } mtortoise; +// // btree 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; +// } u; +// // we really don't want to pay the RAM cost for a full file, +// // so only store the relevant bits, is this a hack? yes +// struct { +// lfsr_omdir_t o; +// const struct lfs_file_config *cfg; +// lfsr_bshrub_t bshrub; +// } file; +// lfsr_btraversal_t bt; +//} lfsr_traversal_t; +// +//enum { +// // traverse all blocks in the filesystem +// LFSR_TRAVERSAL_ALL = 0x1, +// // validate checksums while traversing +// LFSR_TRAVERSAL_VALIDATE = 0x2, +//}; // traversing littlefs is a bit complex, so we use a state machine to keep // track of where we are +// +// note the lower two bits are reserved so upper layers can iterate over +// redund blocks easily enum { - LFSR_TRAVERSAL_MROOTANCHOR = 0, - LFSR_TRAVERSAL_MROOTCHAIN = 1, - LFSR_TRAVERSAL_MTREE = 2, - LFSR_TRAVERSAL_MDIR = 3, - LFSR_TRAVERSAL_MDIRBTREE = 4, - LFSR_TRAVERSAL_OPENED = 5, - LFSR_TRAVERSAL_OPENEDBTREE = 6, - LFSR_TRAVERSAL_DONE = 7, + LFSR_TSTATE_MROOTANCHOR = 0 << 2, + LFSR_TSTATE_MROOTCHAIN = 1 << 2, + LFSR_TSTATE_MTREE = 2 << 2, + LFSR_TSTATE_MDIR = 3 << 2, + LFSR_TSTATE_MDIRBTREE = 4 << 2, + LFSR_TSTATE_OMDIR = 5 << 2, + LFSR_TSTATE_OMDIRBTREE = 6 << 2, + LFSR_TSTATE_DONE = 7 << 2, }; -#define LFSR_TRAVERSAL(_flags) \ - ((lfsr_traversal_t){ \ - .flags=_flags, \ - .state=LFSR_TRAVERSAL_MROOTANCHOR, \ +#define LFSR_MTRAVERSAL(_flags) \ + ((lfsr_mtraversal_t){ \ + .o.type=LFS_TYPE_TRAVERSAL, \ + .o.state=LFSR_TSTATE_MROOTANCHOR, \ + .o.flags=_flags, \ + .o.mdir.mid=-1, \ .u.mtortoise.mptr={{0, 0}}, \ .u.mtortoise.step=0, \ .u.mtortoise.power=0}) -static inline bool lfsr_traversal_isall(const lfsr_traversal_t *t) { - return t->flags & LFSR_TRAVERSAL_ALL; +static inline bool lfsr_t_ismtreeonly(uint32_t flags) { + return flags & LFS_T_MTREEONLY; } -static inline bool lfsr_traversal_isvalidate(const lfsr_traversal_t *t) { - return t->flags & LFSR_TRAVERSAL_VALIDATE; +static inline bool lfsr_t_islookahead(uint32_t flags) { + return flags & LFS_T_LOOKAHEAD; } -// needed in lfsr_traversal_read +static inline bool lfsr_t_iscompact(uint32_t flags) { + return flags & LFS_T_COMPACT; +} + +static inline bool lfsr_t_isckmetadata(uint32_t flags) { + return flags & LFS_T_CKMETADATA; +} + +static inline bool lfsr_t_isckdata(uint32_t flags) { + return flags & LFS_T_CKDATA; +} + +static inline bool lfsr_t_isdirty(uint32_t flags) { + return flags & LFS_T_DIRTY; +} + +static inline bool lfsr_t_iscorruptmetadata(uint32_t flags) { + return flags & LFS_T_CORRUPTMETADATA; +} + +static inline bool lfsr_t_iscorruptdata(uint32_t flags) { + return flags & LFS_T_CORRUPTDATA; +} + + +typedef struct lfsr_mtinfo { + lfsr_tag_t tag; + union { + lfsr_mdir_t mdir; + lfsr_rbyd_t rbyd; + lfsr_data_t data; + lfsr_bptr_t bptr; + } u; +} lfsr_mtinfo_t; + +// needed in lfsr_fs_traverse_ static int lfsr_bshrub_traverse(lfs_t *lfs, const lfsr_file_t *file, - lfsr_btraversal_t *t, - lfsr_bid_t *bid_, lfsr_tinfo_t *tinfo_); + lfsr_btraversal_t *bt, lfsr_btinfo_t *btinfo); -static int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *t, - lfsr_tinfo_t *tinfo_) { +// low-level traversal _only_ finds blocks +static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, + lfsr_mtinfo_t *mtinfo) { while (true) { - switch (t->state) { + switch (mt->o.state >> 2) { // start with the mrootanchor 0x{0,1} // // note we make sure to include all mroots in our mroot chain! // - case LFSR_TRAVERSAL_MROOTANCHOR:; + case LFSR_TSTATE_MROOTANCHOR >> 2:; // fetch the first mroot 0x{0,1} - int err = lfsr_mdir_fetch(lfs, &t->file.o.mdir, + int err = lfsr_mdir_fetch(lfs, &mt->o.mdir, -1, &LFSR_MPTR_MROOTANCHOR()); if (err) { return err; } // transition to traversing the mroot chain - t->state = LFSR_TRAVERSAL_MROOTCHAIN; + mt->o.state = LFSR_TSTATE_MROOTCHAIN; - if (tinfo_) { - tinfo_->tag = LFSR_TAG_MDIR; - tinfo_->u.mdir = t->file.o.mdir; - } + mtinfo->tag = LFSR_TAG_MDIR; + mtinfo->u.mdir = mt->o.mdir; return 0; // traverse the mroot chain, checking for mroot/mtree/mdir - case LFSR_TRAVERSAL_MROOTCHAIN:; - // lookup mroot, if we find one this is a fake mroot + case LFSR_TSTATE_MROOTCHAIN >> 2:; + // lookup mroot, if we find one this just an mroot chain link lfsr_tag_t tag; lfsr_data_t data; - err = lfsr_mdir_sublookup(lfs, &t->file.o.mdir, + 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 (err == LFS_ERR_NOENT) { - t->file.o.mdir.mid = 0; - t->state = LFSR_TRAVERSAL_MDIR; + mt->o.mdir.mid = 0; + mt->o.state = LFSR_TSTATE_MDIR; continue; } return err; @@ -7811,30 +7846,28 @@ static int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *t, // inner nodes require checksums of their pointers, so creating // a valid cycle is actually quite difficult // - if (lfsr_mptr_cmp(&mptr, &t->u.mtortoise.mptr) == 0) { + if (lfsr_mptr_cmp(&mptr, &mt->u.mtortoise.mptr) == 0) { LFS_ERROR("Cycle detected during mtree traversal " "0x{%"PRIx32",%"PRIx32"}", mptr.blocks[0], mptr.blocks[1]); return LFS_ERR_CORRUPT; } - if (t->u.mtortoise.step == (1U << t->u.mtortoise.power)) { - t->u.mtortoise.mptr = mptr; - t->u.mtortoise.step = 0; - t->u.mtortoise.power += 1; + if (mt->u.mtortoise.step == (1U << mt->u.mtortoise.power)) { + mt->u.mtortoise.mptr = mptr; + mt->u.mtortoise.step = 0; + mt->u.mtortoise.power += 1; } - t->u.mtortoise.step += 1; + mt->u.mtortoise.step += 1; // fetch this mroot - err = lfsr_mdir_fetch(lfs, &t->file.o.mdir, -1, &mptr); + err = lfsr_mdir_fetch(lfs, &mt->o.mdir, -1, &mptr); if (err) { return err; } - if (tinfo_) { - tinfo_->tag = LFSR_TAG_MDIR; - tinfo_->u.mdir = t->file.o.mdir; - } + mtinfo->tag = LFSR_TAG_MDIR; + mtinfo->u.mdir = mt->o.mdir; return 0; // found an mdir? @@ -7846,18 +7879,16 @@ static int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *t, return err; } - err = lfsr_mdir_fetch(lfs, &t->file.o.mdir, 0, &mptr); + err = lfsr_mdir_fetch(lfs, &mt->o.mdir, 0, &mptr); if (err) { return err; } // transition to mdir traversal next - t->state = LFSR_TRAVERSAL_MDIR; + mt->o.state = LFSR_TSTATE_MDIR; - if (tinfo_) { - tinfo_->tag = LFSR_TAG_MDIR; - tinfo_->u.mdir = t->file.o.mdir; - } + mtinfo->tag = LFSR_TAG_MDIR; + mtinfo->u.mdir = mt->o.mdir; return 0; // found an mtree? @@ -7870,26 +7901,12 @@ static int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *t, return err; } - // validate our btree nodes if requested, this just means we - // need to do a full rbyd fetch and make sure the checksums - // match - if (lfsr_traversal_isvalidate(t)) { - err = lfsr_rbyd_fetchvalidate(lfs, &mtree, - mtree.blocks[0], mtree.trunk, - mtree.cksum); - if (err) { - return err; - } - } - // transition to traversing the mtree - t->state = LFSR_TRAVERSAL_MTREE; - t->u.mt = LFSR_BTRAVERSAL(); + mt->o.state = LFSR_TSTATE_MTREE; + mt->u.mt = LFSR_BTRAVERSAL(); - if (tinfo_) { - tinfo_->tag = LFSR_TAG_BRANCH; - tinfo_->u.rbyd = mtree; - } + mtinfo->tag = LFSR_TAG_BRANCH; + mtinfo->u.rbyd = mtree; return 0; } else { @@ -7898,25 +7915,23 @@ static int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *t, } // traverse the mtree, including both inner btree nodes and mdirs - case LFSR_TRAVERSAL_MTREE:; + case LFSR_TSTATE_MTREE >> 2:; // no mtree? transition to traversing any opened mdirs if (lfsr_mtree_ismptr(&lfs->mtree)) { - t->u.o = lfs->opened; - t->state = LFSR_TRAVERSAL_OPENED; + mt->u.o = lfs->opened; + mt->o.state = LFSR_TSTATE_OMDIR; continue; } // traverse through the mtree - lfsr_bid_t bid; - lfsr_tinfo_t tinfo; - err = lfsr_btree_traverse(lfs, &lfs->mtree.u.btree, - &t->u.mt, - &bid, &tinfo); + lfsr_btinfo_t btinfo; + err = lfsr_btree_traverse(lfs, &lfs->mtree.u.btree, &mt->u.mt, + &btinfo); if (err) { // end of mtree? transition to traversing any opened mdirs if (err == LFS_ERR_NOENT) { - t->u.o = lfs->opened; - t->state = LFSR_TRAVERSAL_OPENED; + mt->u.o = lfs->opened; + mt->o.state = LFSR_TSTATE_OMDIR; continue; } return err; @@ -7926,72 +7941,58 @@ static int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *t, // 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 (tinfo.tag == LFSR_TAG_BRANCH - && tinfo.u.rbyd.blocks[0] == lfs->mtree.u.btree.blocks[0]) { + if (btinfo.tag == LFSR_TAG_BRANCH + && btinfo.u.rbyd.blocks[0] + == lfs->mtree.u.btree.blocks[0]) { continue; } // inner btree nodes already decoded - if (tinfo.tag == LFSR_TAG_BRANCH) { - // validate our btree nodes if requested, this just means we - // need to do a full rbyd fetch and make sure the checksums - // match - if (lfsr_traversal_isvalidate(t)) { - err = lfsr_rbyd_fetchvalidate(lfs, &tinfo.u.rbyd, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk, - tinfo.u.rbyd.cksum); - if (err) { - return err; - } - } - - if (tinfo_) { - *tinfo_ = tinfo; - } + if (btinfo.tag == LFSR_TAG_BRANCH) { + mtinfo->tag = LFSR_TAG_BRANCH; + mtinfo->u.rbyd = btinfo.u.rbyd; return 0; // fetch mdir if we're on a leaf - } else if (tinfo.tag == LFSR_TAG_MDIR) { + } else if (btinfo.tag == LFSR_TAG_MDIR) { lfsr_mptr_t mptr; - err = lfsr_data_readmptr(lfs, &tinfo.u.data, &mptr); + err = lfsr_data_readmptr(lfs, &btinfo.u.data, &mptr); if (err) { return err; } - err = lfsr_mdir_fetch(lfs, &t->file.o.mdir, - LFSR_MID(lfs, bid, 0), + err = lfsr_mdir_fetch(lfs, &mt->o.mdir, + LFSR_MID(lfs, btinfo.bid, 0), &mptr); if (err) { return err; } // transition to mdir traversal next - t->state = LFSR_TRAVERSAL_MDIR; + mt->o.state = LFSR_TSTATE_MDIR; - if (tinfo_) { - tinfo_->tag = LFSR_TAG_MDIR; - tinfo_->u.mdir = t->file.o.mdir; - } + mtinfo->tag = LFSR_TAG_MDIR; + mtinfo->u.mdir = mt->o.mdir; return 0; } else { - LFS_ERROR("Weird mtree entry? 0x%"PRIx32, tinfo.tag); + LFS_ERROR("Weird mtree entry? 0x%"PRIx32, mtinfo->tag); return LFS_ERR_CORRUPT; } // scan for blocks/btrees in the current mdir - case LFSR_TRAVERSAL_MDIR:; + case LFSR_TSTATE_MDIR >> 2:; // not traversing all blocks? have we exceeded our mdir's weight? // return to mtree traversal - if (!lfsr_traversal_isall(t) - || lfsr_mid_rid(lfs, t->file.o.mdir.mid) - >= (lfsr_srid_t)t->file.o.mdir.rbyd.weight) { - t->state = LFSR_TRAVERSAL_MTREE; + if (lfsr_t_ismtreeonly(mt->o.flags) + || lfsr_mid_rid(lfs, mt->o.mdir.mid) + >= (lfsr_srid_t)mt->o.mdir.rbyd.weight) { + mt->o.state = LFSR_TSTATE_MTREE; continue; } // do we have a block/btree? - err = lfsr_mdir_lookupnext(lfs, &t->file.o.mdir, LFSR_TAG_DATA, + err = lfsr_mdir_lookupnext(lfs, &mt->o.mdir, LFSR_TAG_DATA, &tag, &data); if (err && err != LFS_ERR_NOENT) { return err; @@ -7999,15 +8000,15 @@ static int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *t, // found a direct block? if (err != LFS_ERR_NOENT && tag == LFSR_TAG_BLOCK) { - err = lfsr_data_readbptr(lfs, &data, &t->file.bshrub.u.bptr); + err = lfsr_data_readbptr(lfs, &data, &mt->bshrub.u.bptr); if (err) { return err; } // found a bshrub (inlined btree)? } else if (err != LFS_ERR_NOENT && tag == LFSR_TAG_BSHRUB) { - err = lfsr_data_readshrub(lfs, &data, &t->file.o.mdir, - &t->file.bshrub.u.bshrub); + err = lfsr_data_readshrub(lfs, &data, &mt->o.mdir, + &mt->bshrub.u.bshrub); if (err) { return err; } @@ -8015,62 +8016,63 @@ static int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *t, // found a btree? } else if (err != LFS_ERR_NOENT && tag == LFSR_TAG_BTREE) { err = lfsr_data_readbtree(lfs, &data, - &t->file.bshrub.u.btree); + &mt->bshrub.u.btree); if (err) { return err; } // no? continue to next file } else { - t->file.o.mdir.mid += 1; + mt->o.mdir.mid += 1; continue; } // start traversing - t->bt = LFSR_BTRAVERSAL(); - t->state = LFSR_TRAVERSAL_MDIRBTREE; + mt->bt = LFSR_BTRAVERSAL(); + mt->o.state = LFSR_TSTATE_MDIRBTREE; continue; // scan for blocks/btrees in our opened file list - case LFSR_TRAVERSAL_OPENED:; + case LFSR_TSTATE_OMDIR >> 2:; // not traversing all blocks? reached end of opened file list? - if (!lfsr_traversal_isall(t) || !t->u.o) { - t->state = LFSR_TRAVERSAL_DONE; + if (lfsr_t_ismtreeonly(mt->o.flags) || !mt->u.o) { + mt->o.state = LFSR_TSTATE_DONE; continue; } // skip non-files - if (t->u.o->type != LFS_TYPE_REG) { - t->u.o = t->u.o->next; + if (mt->u.o->type != LFS_TYPE_REG) { + mt->u.o = mt->u.o->next; continue; } // start traversing the file - const lfsr_file_t *file = (const lfsr_file_t*)t->u.o; - t->file.o.mdir = file->o.mdir; - t->file.bshrub = file->bshrub; - t->bt = LFSR_BTRAVERSAL(); - t->state = LFSR_TRAVERSAL_OPENEDBTREE; + const lfsr_file_t *file = (const lfsr_file_t*)mt->u.o; + mt->o.mdir = file->o.mdir; + mt->bshrub = file->bshrub; + mt->bt = LFSR_BTRAVERSAL(); + mt->o.state = LFSR_TSTATE_OMDIRBTREE; continue; // traverse any file btrees, including both inner btree nodes and // block pointers - case LFSR_TRAVERSAL_MDIRBTREE:; - case LFSR_TRAVERSAL_OPENEDBTREE:; + case LFSR_TSTATE_MDIRBTREE >> 2:; + case LFSR_TSTATE_OMDIRBTREE >> 2:; // traverse through our file - err = lfsr_bshrub_traverse(lfs, (const lfsr_file_t*)&t->file, - &t->bt, - NULL, &tinfo); + err = lfsr_bshrub_traverse(lfs, (const lfsr_file_t*)mt, &mt->bt, + &btinfo); if (err) { if (err == LFS_ERR_NOENT) { // end of btree? go to next file - if (t->state == LFSR_TRAVERSAL_MDIRBTREE) { - t->file.o.mdir.mid += 1; - t->state = LFSR_TRAVERSAL_MDIR; + if ((mt->o.state >> 2) + == (LFSR_TSTATE_MDIRBTREE >> 2)) { + mt->o.mdir.mid += 1; + mt->o.state = LFSR_TSTATE_MDIR; continue; - } else if (t->state == LFSR_TRAVERSAL_OPENEDBTREE) { - t->u.o = t->u.o->next; - t->state = LFSR_TRAVERSAL_OPENED; + } else if ((mt->o.state >> 2) + == (LFSR_TSTATE_OMDIRBTREE >> 2)) { + mt->u.o = mt->u.o->next; + mt->o.state = LFSR_TSTATE_OMDIR; continue; } else { LFS_UNREACHABLE(); @@ -8080,42 +8082,26 @@ static int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *t, } // found an inner btree node? - if (tinfo.tag == LFSR_TAG_BRANCH) { - // validate our btree nodes if requested, this just means we - // need to do a full rbyd fetch and make sure the checksums - // match - if (lfsr_traversal_isvalidate(t)) { - err = lfsr_rbyd_fetchvalidate(lfs, &tinfo.u.rbyd, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk, - tinfo.u.rbyd.cksum); - if (err) { - return err; - } - } - - if (tinfo_) { - *tinfo_ = tinfo; - } + if (btinfo.tag == LFSR_TAG_BRANCH) { + mtinfo->tag = LFSR_TAG_BRANCH; + mtinfo->u.rbyd = btinfo.u.rbyd; return 0; // found inlined data? ignore this - } else if (tinfo.tag == LFSR_TAG_DATA) { + } else if (btinfo.tag == LFSR_TAG_DATA) { continue; // found an indirect block? - } else if (tinfo.tag == LFSR_TAG_BLOCK) { - // TODO validate? - - if (tinfo_) { - *tinfo_ = tinfo; - } + } else if (btinfo.tag == LFSR_TAG_BLOCK) { + mtinfo->tag = LFSR_TAG_BLOCK; + mtinfo->u.bptr = btinfo.u.bptr; return 0; } else { LFS_UNREACHABLE(); } - case LFSR_TRAVERSAL_DONE:; + case LFSR_TSTATE_DONE >> 2:; return LFS_ERR_NOENT; default:; @@ -8124,6 +8110,38 @@ static int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *t, } } +// high-level traversal, handle extra features here +static int lfsr_fs_traverse(lfs_t *lfs, lfsr_mtraversal_t *mt, + lfsr_mtinfo_t *mtinfo) { + int err = lfsr_fs_traverse_(lfs, mt, mtinfo); + if (err) { + return err; + } + + + // validate btree nodes? note mdirs are already validated + if (lfsr_t_isckmetadata(mt->o.flags) + && mtinfo->tag == LFSR_TAG_BRANCH) { + err = lfsr_rbyd_fetchck(lfs, &mtinfo->u.rbyd, + mtinfo->u.rbyd.blocks[0], mtinfo->u.rbyd.trunk, + mtinfo->u.rbyd.cksum); + if (err) { + return err; + } + } + + // validate data blocks? + if (lfsr_t_isckdata(mt->o.flags) + && mtinfo->tag == LFSR_TAG_BLOCK) { + err = lfsr_bptr_ck(lfs, &mtinfo->u.bptr); + if (err) { + return err; + } + } + + return 0; +} + /// Superblock things /// @@ -8580,10 +8598,11 @@ static int lfsr_mountinited(lfs_t *lfs) { // we do validate btree inner nodes here, how can we trust our // mdirs are valid if we haven't checked the btree inner nodes at // least once? - lfsr_traversal_t t = LFSR_TRAVERSAL(LFSR_TRAVERSAL_VALIDATE); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL( + LFS_T_MTREEONLY | LFS_T_CKMETADATA); while (true) { - lfsr_tinfo_t tinfo; - int err = lfsr_traversal_read(lfs, &t, &tinfo); + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(lfs, &mt, &mtinfo); if (err) { if (err == LFS_ERR_NOENT) { break; @@ -8592,12 +8611,13 @@ static int lfsr_mountinited(lfs_t *lfs) { } // found an mdir? - if (tinfo.tag == LFSR_TAG_MDIR) { + if (mtinfo.tag == LFSR_TAG_MDIR) { // found an mroot? - if (tinfo.u.mdir.mid == -1) { + if (mtinfo.u.mdir.mid == -1) { // check for the magic string, all mroot should have this lfsr_data_t data; - int err = lfsr_mdir_lookup(lfs, &tinfo.u.mdir, LFSR_TAG_MAGIC, + int err = lfsr_mdir_lookup(lfs, + &mtinfo.u.mdir, LFSR_TAG_MAGIC, &data); if (err) { if (err == LFS_ERR_NOENT) { @@ -8618,14 +8638,14 @@ static int lfsr_mountinited(lfs_t *lfs) { } // are we the last mroot? - err = lfsr_mdir_lookup(lfs, &tinfo.u.mdir, LFSR_TAG_MROOT, + err = lfsr_mdir_lookup(lfs, &mtinfo.u.mdir, LFSR_TAG_MROOT, NULL); if (err && err != LFS_ERR_NOENT) { return err; } if (err == LFS_ERR_NOENT) { // track active mroot - lfs->mroot = tinfo.u.mdir; + lfs->mroot = mtinfo.u.mdir; // mount/validate config in active mroot err = lfsr_mountmroot(lfs, &lfs->mroot); @@ -8638,25 +8658,25 @@ static int lfsr_mountinited(lfs_t *lfs) { // found a direct mdir? keep track of this if (lfsr_mtree_isnull(&lfs->mtree)) { lfs->mtree = LFSR_MTREE_MPTR( - *lfsr_mdir_mptr(&tinfo.u.mdir), + *lfsr_mdir_mptr(&mtinfo.u.mdir), (1 << lfs->mdir_bits)); } } // toss our cksum into the filesystem seed for pseudorandom // numbers - lfs->seed ^= tinfo.u.mdir.rbyd.cksum; + lfs->seed ^= mtinfo.u.mdir.rbyd.cksum; // collect any gdeltas from this mdir - err = lfsr_fs_consumegdelta(lfs, &tinfo.u.mdir); + err = lfsr_fs_consumegdelta(lfs, &mtinfo.u.mdir); if (err) { return err; } // check for any orphaned files - for (lfs_size_t rid = 0; rid < tinfo.u.mdir.rbyd.weight; rid++) { + for (lfs_size_t rid = 0; rid < mtinfo.u.mdir.rbyd.weight; rid++) { lfsr_tag_t tag; - err = lfsr_rbyd_sublookup(lfs, &tinfo.u.mdir.rbyd, + err = lfsr_rbyd_sublookup(lfs, &mtinfo.u.mdir.rbyd, rid, LFSR_TAG_NAME, &tag, NULL); if (err) { @@ -8670,7 +8690,7 @@ static int lfsr_mountinited(lfs_t *lfs) { if (tag == LFSR_TAG_ORPHAN) { LFS_DEBUG("Found orphaned file " "%"PRId32".%"PRId32, - lfsr_mid_bid(lfs, tinfo.u.mdir.mid) + lfsr_mid_bid(lfs, mtinfo.u.mdir.mid) >> lfs->mdir_bits, rid); lfs->hasorphans = true; @@ -8680,7 +8700,7 @@ static int lfsr_mountinited(lfs_t *lfs) { // TODO switch to readonly? LFS_ERROR("Found unknown file type " "%"PRId32".%"PRId32" 0x%"PRIx16, - lfsr_mid_bid(lfs, tinfo.u.mdir.mid) + lfsr_mid_bid(lfs, mtinfo.u.mdir.mid) >> lfs->mdir_bits, rid, lfsr_tag_subtype(tag)); @@ -8689,10 +8709,10 @@ static int lfsr_mountinited(lfs_t *lfs) { } // found an mtree inner-node? - } else if (tinfo.tag == LFSR_TAG_BRANCH) { + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { // found the root of the mtree? keep track of this if (lfsr_mtree_isnull(&lfs->mtree)) { - lfs->mtree.u.btree = tinfo.u.rbyd; + lfs->mtree.u.btree = mtinfo.u.rbyd; } } else { @@ -8980,10 +9000,10 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase) { // traverse the filesystem, building up knowledge of what blocks are // in use in our lookahead window - lfsr_traversal_t t = LFSR_TRAVERSAL(LFSR_TRAVERSAL_ALL); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL(0); while (true) { - lfsr_tinfo_t tinfo; - int err = lfsr_traversal_read(lfs, &t, &tinfo); + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(lfs, &mt, &mtinfo); if (err) { if (err == LFS_ERR_NOENT) { break; @@ -8991,18 +9011,16 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase) { return err; } - // TODO add block pointers here? - // mark any blocks we see at in-use, including any btree/mdir blocks - if (tinfo.tag == LFSR_TAG_MDIR) { - lfs_alloc_setinuse(lfs, tinfo.u.mdir.rbyd.blocks[1]); - lfs_alloc_setinuse(lfs, tinfo.u.mdir.rbyd.blocks[0]); + if (mtinfo.tag == LFSR_TAG_MDIR) { + lfs_alloc_setinuse(lfs, mtinfo.u.mdir.rbyd.blocks[1]); + lfs_alloc_setinuse(lfs, mtinfo.u.mdir.rbyd.blocks[0]); - } else if (tinfo.tag == LFSR_TAG_BRANCH) { - lfs_alloc_setinuse(lfs, tinfo.u.rbyd.blocks[0]); + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { + lfs_alloc_setinuse(lfs, mtinfo.u.rbyd.blocks[0]); - } else if (tinfo.tag == LFSR_TAG_BLOCK) { - lfs_alloc_setinuse(lfs, tinfo.u.bptr.data.u.disk.block); + } else if (mtinfo.tag == LFSR_TAG_BLOCK) { + lfs_alloc_setinuse(lfs, mtinfo.u.bptr.data.u.disk.block); } else { LFS_UNREACHABLE(); @@ -9025,10 +9043,10 @@ int lfsr_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { lfs_ssize_t lfsr_fs_size(lfs_t *lfs) { lfs_size_t count = 0; - lfsr_traversal_t t = LFSR_TRAVERSAL(LFSR_TRAVERSAL_ALL); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL(0); while (true) { - lfsr_tinfo_t tinfo; - int err = lfsr_traversal_read(lfs, &t, &tinfo); + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(lfs, &mt, &mtinfo); if (err) { if (err == LFS_ERR_NOENT) { break; @@ -9036,16 +9054,14 @@ lfs_ssize_t lfsr_fs_size(lfs_t *lfs) { return err; } - // TODO add block pointers here? - // count the number of blocks we see, yes this may result in duplicates - if (tinfo.tag == LFSR_TAG_MDIR) { + if (mtinfo.tag == LFSR_TAG_MDIR) { count += 2; - } else if (tinfo.tag == LFSR_TAG_BRANCH) { + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { count += 1; - } else if (tinfo.tag == LFSR_TAG_BLOCK) { + } else if (mtinfo.tag == LFSR_TAG_BLOCK) { count += 1; } else { @@ -9252,6 +9268,119 @@ failed:; +/// High-level filesystem traversal /// + +int lfsr_traversal_open(lfs_t *lfs, lfsr_traversal_t *traversal, + uint32_t flags) { + // already open? + LFS_ASSERT(!lfsr_opened_isopen(lfs, &traversal->mt.o)); + // some flags don't make sense when only traversing the mtree + LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_islookahead(flags)); + LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_isckdata(flags)); + // these flags are returned by tinfo, not provided here + LFS_ASSERT(!lfsr_t_isdirty(flags)); + LFS_ASSERT(!lfsr_t_iscorruptmetadata(flags)); + LFS_ASSERT(!lfsr_t_iscorruptdata(flags)); + + // some flags mutate the filesystem + if (lfsr_t_iscompact(flags)) { + // prepare our filesystem for writing + int err = lfsr_fs_mkconsistent(lfs); + if (err) { + return err; + } + } + + // setup traversal state + traversal->mt = LFSR_MTRAVERSAL(flags); + traversal->count = 0; + + // add to tracked mdirs + lfsr_opened_add(lfs, &traversal->mt.o); + return 0; +} + +int lfsr_traversal_close(lfs_t *lfs, lfsr_traversal_t *traversal) { + LFS_ASSERT(lfsr_opened_isopen(lfs, &traversal->mt.o)); + + // remove from tracked mdirs + lfsr_opened_remove(lfs, &traversal->mt.o); + return 0; +} + +int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *traversal, + struct lfs_tinfo *tinfo) { + LFS_ASSERT(lfsr_opened_isopen(lfs, &traversal->mt.o)); + + while (true) { + // some redund blocks left over? + if (traversal->count > 0) { + // write our traversal info + tinfo->flags = traversal->mt.o.flags + & LFS_T_DIRTY + & LFS_T_CORRUPTMETADATA + & LFS_T_CORRUPTDATA; + tinfo->btype = traversal->btype; + tinfo->block = traversal->blocks[0]; + + traversal->blocks[0] = traversal->blocks[1]; + traversal->count -= 1; + return 0; + } + + // find next block + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(lfs, &traversal->mt, &mtinfo); + if (err) { + return err; + } + + // figure out type/blocks + if (mtinfo.tag == LFSR_TAG_MDIR) { + traversal->btype = LFS_BTYPE_MDIR; + traversal->blocks[0] = mtinfo.u.mdir.rbyd.blocks[0]; + traversal->blocks[1] = mtinfo.u.mdir.rbyd.blocks[1]; + traversal->count = 2; + + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { + traversal->btype = LFS_BTYPE_BTREE; + traversal->blocks[0] = mtinfo.u.rbyd.blocks[0]; + traversal->count = 1; + + } else if (mtinfo.tag == LFSR_TAG_BLOCK) { + traversal->btype = LFS_BTYPE_DATA; + traversal->blocks[0] = mtinfo.u.bptr.data.u.disk.block; + traversal->count = 1; + + } else { + LFS_UNREACHABLE(); + } + } +} + +int lfsr_traversal_rewind(lfs_t *lfs, lfsr_traversal_t *traversal) { + LFS_ASSERT(lfsr_opened_isopen(lfs, &traversal->mt.o)); + + // reset traversal state + traversal->mt.o.flags &= ~LFS_T_DIRTY + & ~LFS_T_CORRUPTMETADATA + & ~LFS_T_CORRUPTDATA; + traversal->mt.o.state = LFSR_TSTATE_MROOTANCHOR; + traversal->mt.o.mdir.mid = -1; + traversal->mt.u.mtortoise.mptr.blocks[0] = 0; + traversal->mt.u.mtortoise.mptr.blocks[1] = 0; + traversal->mt.u.mtortoise.step = 0; + traversal->mt.u.mtortoise.power = 0; + traversal->count = 0; + + return 0; +} + + + + + + /// Directory operations /// @@ -10547,8 +10676,7 @@ static int lfsr_bshrub_lookupnext(lfs_t *lfs, const lfsr_file_t *file, } static int lfsr_bshrub_traverse(lfs_t *lfs, const lfsr_file_t *file, - lfsr_btraversal_t *t, - lfsr_bid_t *bid_, lfsr_tinfo_t *tinfo_) { + lfsr_btraversal_t *bt, lfsr_btinfo_t *btinfo) { // bnull/bsprout do nothing if (lfsr_bshrub_isbnull(&file->bshrub) || lfsr_bshrub_isbsprout(&file->o.mdir, &file->bshrub)) { @@ -10557,36 +10685,32 @@ static int lfsr_bshrub_traverse(lfs_t *lfs, const lfsr_file_t *file, // block pointer? if (lfsr_bshrub_isbptr(&file->o.mdir, &file->bshrub)) { - if (t->bid > 0) { + if (bt->bid > 0) { return LFS_ERR_NOENT; } - if (bid_) { - *bid_ = lfsr_data_size(file->bshrub.u.bptr.data)-1; - } - if (tinfo_) { - tinfo_->tag = LFSR_TAG_BLOCK; - tinfo_->u.bptr = file->bshrub.u.bptr; - } + btinfo->bid = lfsr_data_size(file->bshrub.u.bptr.data)-1; + btinfo->tag = LFSR_TAG_BLOCK; + btinfo->u.bptr = file->bshrub.u.bptr; return 0; // bshrub/btree? } else if (lfsr_bshrub_isbshruborbtree(&file->bshrub)) { - int err = lfsr_btree_traverse_(lfs, &file->bshrub.u.btree, t, - bid_, tinfo_); + int err = lfsr_btree_traverse_(lfs, &file->bshrub.u.btree, bt, + btinfo); if (err) { return err; } // decode bptrs - if (tinfo_ && tinfo_->tag == LFSR_TAG_BLOCK) { + if (btinfo->tag == LFSR_TAG_BLOCK) { lfsr_bptr_t bptr; - err = lfsr_data_readbptr(lfs, &tinfo_->u.data, + err = lfsr_data_readbptr(lfs, &btinfo->u.data, &bptr); if (err) { return err; } - tinfo_->u.bptr = bptr; + btinfo->u.bptr = bptr; } return 0; diff --git a/lfs.h b/lfs.h index 81630d63..677ddca6 100644 --- a/lfs.h +++ b/lfs.h @@ -120,6 +120,7 @@ enum lfs_type { // internally used types LFS_TYPE_BOOKMARK = 4, + LFS_TYPE_TRAVERSAL = 5, }; // File open flags @@ -152,6 +153,35 @@ enum lfs_whence_flags { LFS_SEEK_END = 2, // Seek relative to the end of the file }; +// Block types +enum lfs_btype { + LFS_BTYPE_MDIR = 1, + LFS_BTYPE_BTREE = 2, + LFS_BTYPE_DATA = 3, +// TODO +// LFS_BTYPE_PARITY = 4, +// LFS_BTYPE_BAD = 5, +}; + +// Traversal flags +enum lfs_traversal_flags { + // traversal open flags + LFS_T_MTREEONLY = 0x0010, // Only traverse the mtree + LFS_T_LOOKAHEAD = 0x0020, // Populate lookahead buffer + LFS_T_COMPACT = 0x0040, // Compact metadata logs + LFS_T_CKMETADATA = 0x0080, // Check metadata checksums + LFS_T_CKDATA = 0x0100, // Check data checksums +// TODO +// LFS_T_REPAIRMETADATA = 0x0100, // Repair metadata blocks +// LFS_T_REPAIRDATA = 0x0200, // Repair data blocks + + // flags set in tinfo by lfsr_traversal_read + LFS_T_DIRTY = 0x0001, // Filesystem mutated +// TODO should we have CORRUPTMETADATA vs just error? + LFS_T_CORRUPTMETADATA = 0x0002, // Found corrupted metadata + LFS_T_CORRUPTDATA = 0x0004, // Found corrupted data +}; + // Configuration provided during initialization of the littlefs struct lfs_config { @@ -340,6 +370,18 @@ struct lfs_fsinfo { lfs_size_t file_limit; }; +// Traversal info structure +struct lfs_tinfo { + // Traversal flags + uint8_t flags; + + // Type of the block + uint8_t btype; + + // Block address + lfs_block_t block; +}; + //// Custom attribute structure, used to describe custom attributes //// committed atomically during file writes. //struct lfs_attr { @@ -434,6 +476,7 @@ typedef struct lfsr_mdir { typedef struct lfsr_omdir { struct lfsr_omdir *next; uint8_t type; + uint8_t state; uint16_t flags; lfsr_mdir_t mdir; } lfsr_omdir_t; @@ -464,24 +507,6 @@ typedef struct lfsr_data { } u; } lfsr_data_t; -// littlefs directory type - -//typedef struct lfs_dir { -// struct lfs_dir *next; -// uint16_t id; -// uint8_t type; -// lfs_mdir_t m; -// -// lfs_off_t pos; -// lfs_block_t head[2]; -//} lfs_dir_t; - -typedef struct lfsr_dir { - lfsr_omdir_t o; - lfsr_did_t did; - lfs_off_t pos; -} lfsr_dir_t; - // littlefs file type //typedef struct lfs_file { @@ -553,6 +578,64 @@ typedef struct lfsr_file { lfs_size_t eoff; } lfsr_file_t; +// littlefs directory type + +//typedef struct lfs_dir { +// struct lfs_dir *next; +// uint16_t id; +// uint8_t type; +// lfs_mdir_t m; +// +// lfs_off_t pos; +// lfs_block_t head[2]; +//} lfs_dir_t; + +typedef struct lfsr_dir { + lfsr_omdir_t o; + lfsr_did_t did; + lfs_off_t pos; +} lfsr_dir_t; + +// littlefs traversal type + +typedef struct lfsr_btraversal { + lfsr_bid_t bid; + lfsr_srid_t rid; + lfsr_rbyd_t branch; +} lfsr_btraversal_t; + +typedef struct lfsr_mtraversal { + // core state machine in o.state + lfsr_omdir_t o; + // we really don't want to pay the RAM cost for a full file, + // so only store the relevant bits, is this a hack? yes + const struct lfs_file_config *cfg; + lfsr_bshrub_t bshrub; + + union { + // cycle detection state, only valid when traversing the mroot chain + struct { + lfsr_mptr_t mptr; + 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; + } u; + // btree traversal state + lfsr_btraversal_t bt; +} lfsr_mtraversal_t; + +typedef struct lfsr_traversal { + // lfsr_mtraversal_t contains most of what we need + lfsr_mtraversal_t mt; + uint8_t btype; + uint8_t count; + lfs_block_t blocks[2]; +} lfsr_traversal_t; + //typedef struct lfs_superblock { // uint32_t version; // lfs_size_t block_size; @@ -927,8 +1010,8 @@ int lfsr_dir_close(lfs_t *lfs, lfsr_dir_t *dir); // Read an entry in the directory // // Fills out the info structure, based on the specified file or directory. -// Returns a positive value on success, 0 at the end of directory, -// or a negative error code on failure. +// Returns 0 on success, LFS_ERR_NOENT at the end of directory, or a +// negative error code on failure. //int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info); int lfsr_dir_read(lfs_t *lfs, lfsr_dir_t *dir, struct lfs_info *info); @@ -957,6 +1040,38 @@ lfs_soff_t lfsr_dir_tell(lfs_t *lfs, lfsr_dir_t *dir); int lfsr_dir_rewind(lfs_t *lfs, lfsr_dir_t *dir); +/// Traversal operations /// + +// Open a traversal +// +// Once open, a traversal can be read from to iterate over all blocks in +// the filesystem. +// +// Returns a negative error code on failure. +int lfsr_traversal_open(lfs_t *lfs, lfsr_traversal_t *traversal, + uint32_t flags); + +// Close a traversal +// +// Releases any allocated resources. +// Returns a negative error code on failure. +int lfsr_traversal_close(lfs_t *lfs, lfsr_traversal_t *traversal); + +// Progress the traversal and read an entry +// +// Fills out the tinfo structure. +// +// Returns 0 on success, LFS_ERR_NOENT at the end of traversal, or a +// negative error code on failure. +int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *traversal, + struct lfs_tinfo *tinfo); + +// Reset the traversal +// +// Returns a negative error code on failure. +int lfsr_traversal_rewind(lfs_t *lfs, lfsr_traversal_t *traversal); + + /// Filesystem-level filesystem operations // Find on-disk info about the filesystem diff --git a/tests/test_alloc.toml b/tests/test_alloc.toml index 1bee6fc5..0b685ea6 100644 --- a/tests/test_alloc.toml +++ b/tests/test_alloc.toml @@ -108,7 +108,7 @@ code = ''' # clobber tests test that our traversal algorithm works [cases.test_alloc_clobber_dirs] defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] -defines.VALIDATE = [false, true] +defines.CKMETADATA = [false, true] defines.REMOUNT = [false, true] in = 'lfs.c' code = ''' @@ -166,43 +166,43 @@ code = ''' uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); - lfsr_traversal_t traversal = LFSR_TRAVERSAL( - (VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL( + (CKMETADATA) ? LFS_T_CKMETADATA : 0); for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfsr_tinfo_t tinfo; - int err = lfsr_traversal_read(&lfs, &traversal, &tinfo); + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(&lfs, &mt, &mtinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } - if (tinfo.tag == LFSR_TAG_MDIR) { + if (mtinfo.tag == LFSR_TAG_MDIR) { printf("traversal: 0x%x mdir 0x{%x,%x}\n", - tinfo.tag, - tinfo.u.mdir.rbyd.blocks[0], - tinfo.u.mdir.rbyd.blocks[1]); + mtinfo.tag, + mtinfo.u.mdir.rbyd.blocks[0], + mtinfo.u.mdir.rbyd.blocks[1]); // keep track of seen blocks - seen[tinfo.u.mdir.rbyd.blocks[1] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8); - seen[tinfo.u.mdir.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[1] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[1] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[0] % 8); - } else if (tinfo.tag == LFSR_TAG_BRANCH) { + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { printf("traversal: 0x%x btree 0x%x.%x\n", - tinfo.tag, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk); + mtinfo.tag, + mtinfo.u.rbyd.blocks[0], mtinfo.u.rbyd.trunk); // keep track of seen blocks - seen[tinfo.u.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.rbyd.blocks[0] % 8); + seen[mtinfo.u.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.rbyd.blocks[0] % 8); } else { // this shouldn't happen - printf("traversal: 0x%x\n", tinfo.tag); + printf("traversal: 0x%x\n", mtinfo.tag); assert(false); } } @@ -271,7 +271,7 @@ defines.SIZE = [ '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] -defines.VALIDATE = [false, true] +defines.CKMETADATA = [false, true] defines.REMOUNT = [false, true] in = 'lfs.c' if = '(SIZE*N)/BLOCK_SIZE <= 32' @@ -334,53 +334,52 @@ code = ''' uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); - lfsr_traversal_t traversal = LFSR_TRAVERSAL( - ((VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0) - | LFSR_TRAVERSAL_ALL); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL( + ((CKMETADATA) ? LFS_T_CKMETADATA : 0)); for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfsr_tinfo_t tinfo; - int err = lfsr_traversal_read(&lfs, &traversal, &tinfo); + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(&lfs, &mt, &mtinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } - if (tinfo.tag == LFSR_TAG_MDIR) { + if (mtinfo.tag == LFSR_TAG_MDIR) { printf("traversal: 0x%x mdir 0x{%x,%x}\n", - tinfo.tag, - tinfo.u.mdir.rbyd.blocks[0], - tinfo.u.mdir.rbyd.blocks[1]); + mtinfo.tag, + mtinfo.u.mdir.rbyd.blocks[0], + mtinfo.u.mdir.rbyd.blocks[1]); // keep track of seen blocks - seen[tinfo.u.mdir.rbyd.blocks[1] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8); - seen[tinfo.u.mdir.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[1] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[1] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[0] % 8); - } else if (tinfo.tag == LFSR_TAG_BRANCH) { + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { printf("traversal: 0x%x btree 0x%x.%x\n", - tinfo.tag, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk); + mtinfo.tag, + mtinfo.u.rbyd.blocks[0], mtinfo.u.rbyd.trunk); // keep track of seen blocks - seen[tinfo.u.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.rbyd.blocks[0] % 8); + seen[mtinfo.u.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.rbyd.blocks[0] % 8); - } else if (tinfo.tag == LFSR_TAG_BLOCK) { + } else if (mtinfo.tag == LFSR_TAG_BLOCK) { printf("traversal: 0x%x block 0x%x\n", - tinfo.tag, - tinfo.u.bptr.data.u.disk.block); + mtinfo.tag, + mtinfo.u.bptr.data.u.disk.block); // keep track of seen blocks - seen[tinfo.u.bptr.data.u.disk.block / 8] - |= 1 << (tinfo.u.bptr.data.u.disk.block % 8); + seen[mtinfo.u.bptr.data.u.disk.block / 8] + |= 1 << (mtinfo.u.bptr.data.u.disk.block % 8); } else { // this shouldn't happen - printf("traversal: 0x%x\n", tinfo.tag); + printf("traversal: 0x%x\n", mtinfo.tag); assert(false); } } @@ -445,7 +444,7 @@ defines.SIZE = [ '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] -defines.VALIDATE = [false, true] +defines.CKMETADATA = [false, true] in = 'lfs.c' if = '(SIZE*N)/BLOCK_SIZE <= 32' code = ''' @@ -489,53 +488,52 @@ code = ''' uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); - lfsr_traversal_t traversal = LFSR_TRAVERSAL( - ((VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0) - | LFSR_TRAVERSAL_ALL); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL( + ((CKMETADATA) ? LFS_T_CKMETADATA : 0)); for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfsr_tinfo_t tinfo; - int err = lfsr_traversal_read(&lfs, &traversal, &tinfo); + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(&lfs, &mt, &mtinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } - if (tinfo.tag == LFSR_TAG_MDIR) { + if (mtinfo.tag == LFSR_TAG_MDIR) { printf("traversal: 0x%x mdir 0x{%x,%x}\n", - tinfo.tag, - tinfo.u.mdir.rbyd.blocks[0], - tinfo.u.mdir.rbyd.blocks[1]); + mtinfo.tag, + mtinfo.u.mdir.rbyd.blocks[0], + mtinfo.u.mdir.rbyd.blocks[1]); // keep track of seen blocks - seen[tinfo.u.mdir.rbyd.blocks[1] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8); - seen[tinfo.u.mdir.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[1] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[1] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[0] % 8); - } else if (tinfo.tag == LFSR_TAG_BRANCH) { + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { printf("traversal: 0x%x btree 0x%x.%x\n", - tinfo.tag, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk); + mtinfo.tag, + mtinfo.u.rbyd.blocks[0], mtinfo.u.rbyd.trunk); // keep track of seen blocks - seen[tinfo.u.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.rbyd.blocks[0] % 8); + seen[mtinfo.u.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.rbyd.blocks[0] % 8); - } else if (tinfo.tag == LFSR_TAG_BLOCK) { + } else if (mtinfo.tag == LFSR_TAG_BLOCK) { printf("traversal: 0x%x block 0x%x\n", - tinfo.tag, - tinfo.u.bptr.data.u.disk.block); + mtinfo.tag, + mtinfo.u.bptr.data.u.disk.block); // keep track of seen blocks - seen[tinfo.u.bptr.data.u.disk.block / 8] - |= 1 << (tinfo.u.bptr.data.u.disk.block % 8); + seen[mtinfo.u.bptr.data.u.disk.block / 8] + |= 1 << (mtinfo.u.bptr.data.u.disk.block % 8); } else { // this shouldn't happen - printf("traversal: 0x%x\n", tinfo.tag); + printf("traversal: 0x%x\n", mtinfo.tag); assert(false); } } diff --git a/tests/test_btree.toml b/tests/test_btree.toml index 88c48e36..485c4900 100644 --- a/tests/test_btree.toml +++ b/tests/test_btree.toml @@ -4092,41 +4092,39 @@ code = ''' uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); - lfsr_btraversal_t traversal = LFSR_BTRAVERSAL(); + lfsr_btraversal_t bt = LFSR_BTRAVERSAL(); for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i <= 2*N); - lfsr_bid_t bid; - lfsr_tinfo_t tinfo; - int err = lfsr_btree_traverse(&lfs, &btree, &traversal, - &bid, &tinfo); + lfsr_btinfo_t btinfo; + int err = lfsr_btree_traverse(&lfs, &btree, &bt, &btinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } - if (tinfo.tag == LFSR_TAG_BRANCH) { + if (btinfo.tag == LFSR_TAG_BRANCH) { printf("traversal: %d 0x%x btree 0x%x.%x\n", - bid, - tinfo.tag, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk); + btinfo.bid, + btinfo.tag, + btinfo.u.rbyd.blocks[0], btinfo.u.rbyd.trunk); // keep track of seen blocks - seen[tinfo.u.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.rbyd.blocks[0] % 8); + seen[btinfo.u.rbyd.blocks[0] / 8] + |= 1 << (btinfo.u.rbyd.blocks[0] % 8); - } else if (tinfo.tag == LFSR_TAG_DATA) { + } else if (btinfo.tag == LFSR_TAG_DATA) { printf("traversal: %d 0x%x data %d\n", - bid, - tinfo.tag, - lfsr_data_size(tinfo.u.data)); + btinfo.bid, + btinfo.tag, + lfsr_data_size(btinfo.u.data)); } else { // well this shouldn't happen printf("traversal: %d 0x%x\n", - bid, - tinfo.tag); + btinfo.bid, + btinfo.tag); assert(false); } } @@ -4242,41 +4240,39 @@ code = ''' uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); - lfsr_btraversal_t traversal = LFSR_BTRAVERSAL(); + lfsr_btraversal_t bt = LFSR_BTRAVERSAL(); for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i <= 2*N); - lfsr_bid_t bid; - lfsr_tinfo_t tinfo; - int err = lfsr_btree_traverse(&lfs, &btree, &traversal, - &bid, &tinfo); + lfsr_btinfo_t btinfo; + int err = lfsr_btree_traverse(&lfs, &btree, &bt, &btinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } - if (tinfo.tag == LFSR_TAG_BRANCH) { + if (btinfo.tag == LFSR_TAG_BRANCH) { printf("traversal: %d 0x%x btree 0x%x.%x\n", - bid, - tinfo.tag, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk); + btinfo.bid, + btinfo.tag, + btinfo.u.rbyd.blocks[0], btinfo.u.rbyd.trunk); // keep track of seen blocks - seen[tinfo.u.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.rbyd.blocks[0] % 8); + seen[btinfo.u.rbyd.blocks[0] / 8] + |= 1 << (btinfo.u.rbyd.blocks[0] % 8); - } else if (tinfo.tag == LFSR_TAG_DATA) { + } else if (btinfo.tag == LFSR_TAG_DATA) { printf("traversal: %d 0x%x data %d\n", - bid, - tinfo.tag, - lfsr_data_size(tinfo.u.data)); + btinfo.bid, + btinfo.tag, + lfsr_data_size(btinfo.u.data)); } else { // well this shouldn't happen printf("traversal: %d 0x%x\n", - bid, - tinfo.tag); + btinfo.bid, + btinfo.tag); assert(false); } } diff --git a/tests/test_mtree.toml b/tests/test_mtree.toml index 78113c75..99013053 100644 --- a/tests/test_mtree.toml +++ b/tests/test_mtree.toml @@ -3340,7 +3340,7 @@ code = ''' # test specific corner cases [cases.test_mtree_traversal] -defines.VALIDATE = [false, true] +defines.CKMETADATA = [false, true] in = 'lfs.c' code = ''' lfs_t lfs; @@ -3360,43 +3360,44 @@ code = ''' uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); - lfsr_traversal_t traversal = LFSR_TRAVERSAL( - (VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL( + LFS_T_MTREEONLY + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0)); for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfsr_tinfo_t tinfo; - int err = lfsr_traversal_read(&lfs, &traversal, &tinfo); + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(&lfs, &mt, &mtinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } - if (tinfo.tag == LFSR_TAG_MDIR) { + if (mtinfo.tag == LFSR_TAG_MDIR) { printf("traversal: 0x%x mdir 0x{%x,%x}\n", - tinfo.tag, - tinfo.u.mdir.rbyd.blocks[0], - tinfo.u.mdir.rbyd.blocks[1]); + mtinfo.tag, + mtinfo.u.mdir.rbyd.blocks[0], + mtinfo.u.mdir.rbyd.blocks[1]); // keep track of seen blocks - seen[tinfo.u.mdir.rbyd.blocks[1] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8); - seen[tinfo.u.mdir.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[1] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[1] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[0] % 8); - } else if (tinfo.tag == LFSR_TAG_BRANCH) { + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { printf("traversal: 0x%x btree 0x%x.%x\n", - tinfo.tag, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk); + mtinfo.tag, + mtinfo.u.rbyd.blocks[0], mtinfo.u.rbyd.trunk); // keep track of seen blocks - seen[tinfo.u.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.rbyd.blocks[0] % 8); + seen[mtinfo.u.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.rbyd.blocks[0] % 8); } else { // this shouldn't happen - printf("traversal: 0x%x\n", tinfo.tag); + printf("traversal: 0x%x\n", mtinfo.tag); assert(false); } } @@ -3438,7 +3439,7 @@ code = ''' ''' [cases.test_mtree_traversal_uninline] -defines.VALIDATE = [false, true] +defines.CKMETADATA = [false, true] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' in = 'lfs.c' @@ -3474,43 +3475,44 @@ code = ''' uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); - lfsr_traversal_t traversal = LFSR_TRAVERSAL( - (VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL( + LFS_T_MTREEONLY + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0)); for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfsr_tinfo_t tinfo; - int err = lfsr_traversal_read(&lfs, &traversal, &tinfo); + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(&lfs, &mt, &mtinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } - if (tinfo.tag == LFSR_TAG_MDIR) { + if (mtinfo.tag == LFSR_TAG_MDIR) { printf("traversal: 0x%x mdir 0x{%x,%x}\n", - tinfo.tag, - tinfo.u.mdir.rbyd.blocks[0], - tinfo.u.mdir.rbyd.blocks[1]); + mtinfo.tag, + mtinfo.u.mdir.rbyd.blocks[0], + mtinfo.u.mdir.rbyd.blocks[1]); // keep track of seen blocks - seen[tinfo.u.mdir.rbyd.blocks[1] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8); - seen[tinfo.u.mdir.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[1] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[1] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[0] % 8); - } else if (tinfo.tag == LFSR_TAG_BRANCH) { + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { printf("traversal: 0x%x btree 0x%x.%x\n", - tinfo.tag, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk); + mtinfo.tag, + mtinfo.u.rbyd.blocks[0], mtinfo.u.rbyd.trunk); // keep track of seen blocks - seen[tinfo.u.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.rbyd.blocks[0] % 8); + seen[mtinfo.u.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.rbyd.blocks[0] % 8); } else { // this shouldn't happen - printf("traversal: 0x%x\n", tinfo.tag); + printf("traversal: 0x%x\n", mtinfo.tag); assert(false); } } @@ -3568,7 +3570,7 @@ code = ''' ''' [cases.test_mtree_traversal_uninline_split] -defines.VALIDATE = [false, true] +defines.CKMETADATA = [false, true] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' in = 'lfs.c' @@ -3610,43 +3612,44 @@ code = ''' uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); - lfsr_traversal_t traversal = LFSR_TRAVERSAL( - (VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL( + LFS_T_MTREEONLY + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0)); for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfsr_tinfo_t tinfo; - int err = lfsr_traversal_read(&lfs, &traversal, &tinfo); + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(&lfs, &mt, &mtinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } - if (tinfo.tag == LFSR_TAG_MDIR) { + if (mtinfo.tag == LFSR_TAG_MDIR) { printf("traversal: 0x%x mdir 0x{%x,%x}\n", - tinfo.tag, - tinfo.u.mdir.rbyd.blocks[0], - tinfo.u.mdir.rbyd.blocks[1]); + mtinfo.tag, + mtinfo.u.mdir.rbyd.blocks[0], + mtinfo.u.mdir.rbyd.blocks[1]); // keep track of seen blocks - seen[tinfo.u.mdir.rbyd.blocks[1] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8); - seen[tinfo.u.mdir.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[1] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[1] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[0] % 8); - } else if (tinfo.tag == LFSR_TAG_BRANCH) { + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { printf("traversal: 0x%x btree 0x%x.%x\n", - tinfo.tag, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk); + mtinfo.tag, + mtinfo.u.rbyd.blocks[0], mtinfo.u.rbyd.trunk); // keep track of seen blocks - seen[tinfo.u.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.rbyd.blocks[0] % 8); + seen[mtinfo.u.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.rbyd.blocks[0] % 8); } else { // this shouldn't happen - printf("traversal: 0x%x\n", tinfo.tag); + printf("traversal: 0x%x\n", mtinfo.tag); assert(false); } } @@ -3708,7 +3711,7 @@ code = ''' ''' [cases.test_mtree_traversal_split] -defines.VALIDATE = [false, true] +defines.CKMETADATA = [false, true] # this should be set so only one entry can fit in a metadata block defines.SIZE = 'BLOCK_SIZE / 4' in = 'lfs.c' @@ -3767,43 +3770,44 @@ code = ''' uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); - lfsr_traversal_t traversal = LFSR_TRAVERSAL( - (VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL( + LFS_T_MTREEONLY + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0)); for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfsr_tinfo_t tinfo; - int err = lfsr_traversal_read(&lfs, &traversal, &tinfo); + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(&lfs, &mt, &mtinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } - if (tinfo.tag == LFSR_TAG_MDIR) { + if (mtinfo.tag == LFSR_TAG_MDIR) { printf("traversal: 0x%x mdir 0x{%x,%x}\n", - tinfo.tag, - tinfo.u.mdir.rbyd.blocks[0], - tinfo.u.mdir.rbyd.blocks[1]); + mtinfo.tag, + mtinfo.u.mdir.rbyd.blocks[0], + mtinfo.u.mdir.rbyd.blocks[1]); // keep track of seen blocks - seen[tinfo.u.mdir.rbyd.blocks[1] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8); - seen[tinfo.u.mdir.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[1] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[1] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[0] % 8); - } else if (tinfo.tag == LFSR_TAG_BRANCH) { + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { printf("traversal: 0x%x btree 0x%x.%x\n", - tinfo.tag, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk); + mtinfo.tag, + mtinfo.u.rbyd.blocks[0], mtinfo.u.rbyd.trunk); // keep track of seen blocks - seen[tinfo.u.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.rbyd.blocks[0] % 8); + seen[mtinfo.u.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.rbyd.blocks[0] % 8); } else { // this shouldn't happen - printf("traversal: 0x%x\n", tinfo.tag); + printf("traversal: 0x%x\n", mtinfo.tag); assert(false); } } @@ -3877,7 +3881,7 @@ code = ''' ''' [cases.test_mtree_traversal_extend] -defines.VALIDATE = [false, true] +defines.CKMETADATA = [false, true] # make it so blocks relocate every two compacts defines.BLOCK_RECYCLES = 0 in = 'lfs.c' @@ -3908,43 +3912,44 @@ code = ''' uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); - lfsr_traversal_t traversal = LFSR_TRAVERSAL( - (VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL( + LFS_T_MTREEONLY + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0)); for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfsr_tinfo_t tinfo; - int err = lfsr_traversal_read(&lfs, &traversal, &tinfo); + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(&lfs, &mt, &mtinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } - if (tinfo.tag == LFSR_TAG_MDIR) { + if (mtinfo.tag == LFSR_TAG_MDIR) { printf("traversal: 0x%x mdir 0x{%x,%x}\n", - tinfo.tag, - tinfo.u.mdir.rbyd.blocks[0], - tinfo.u.mdir.rbyd.blocks[1]); + mtinfo.tag, + mtinfo.u.mdir.rbyd.blocks[0], + mtinfo.u.mdir.rbyd.blocks[1]); // keep track of seen blocks - seen[tinfo.u.mdir.rbyd.blocks[1] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8); - seen[tinfo.u.mdir.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[1] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[1] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[0] % 8); - } else if (tinfo.tag == LFSR_TAG_BRANCH) { + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { printf("traversal: 0x%x btree 0x%x.%x\n", - tinfo.tag, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk); + mtinfo.tag, + mtinfo.u.rbyd.blocks[0], mtinfo.u.rbyd.trunk); // keep track of seen blocks - seen[tinfo.u.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.rbyd.blocks[0] % 8); + seen[mtinfo.u.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.rbyd.blocks[0] % 8); } else { // this shouldn't happen - printf("traversal: 0x%x\n", tinfo.tag); + printf("traversal: 0x%x\n", mtinfo.tag); assert(false); } } @@ -3988,7 +3993,7 @@ code = ''' # larger traversal tests [cases.test_mtree_traversal_many] defines.N = [5, 10, 20, 40, 80, 160, 320] -defines.VALIDATE = [false, true] +defines.CKMETADATA = [false, true] defines.FORCE_COMPACTION = [false, true] in = 'lfs.c' code = ''' @@ -4024,43 +4029,44 @@ code = ''' uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); - lfsr_traversal_t traversal = LFSR_TRAVERSAL( - (VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL( + LFS_T_MTREEONLY + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0)); for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfsr_tinfo_t tinfo; - int err = lfsr_traversal_read(&lfs, &traversal, &tinfo); + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(&lfs, &mt, &mtinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } - if (tinfo.tag == LFSR_TAG_MDIR) { + if (mtinfo.tag == LFSR_TAG_MDIR) { printf("traversal: 0x%x mdir 0x{%x,%x}\n", - tinfo.tag, - tinfo.u.mdir.rbyd.blocks[0], - tinfo.u.mdir.rbyd.blocks[1]); + mtinfo.tag, + mtinfo.u.mdir.rbyd.blocks[0], + mtinfo.u.mdir.rbyd.blocks[1]); // keep track of seen blocks - seen[tinfo.u.mdir.rbyd.blocks[1] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8); - seen[tinfo.u.mdir.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[1] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[1] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[0] % 8); - } else if (tinfo.tag == LFSR_TAG_BRANCH) { + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { printf("traversal: 0x%x btree 0x%x.%x\n", - tinfo.tag, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk); + mtinfo.tag, + mtinfo.u.rbyd.blocks[0], mtinfo.u.rbyd.trunk); // keep track of seen blocks - seen[tinfo.u.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.rbyd.blocks[0] % 8); + seen[mtinfo.u.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.rbyd.blocks[0] % 8); } else { // this shouldn't happen - printf("traversal: 0x%x\n", tinfo.tag); + printf("traversal: 0x%x\n", mtinfo.tag); assert(false); } } @@ -4118,7 +4124,7 @@ code = ''' [cases.test_mtree_traversal_fuzz] defines.N = [5, 10, 20, 40, 80, 160] -defines.VALIDATE = [false, true] +defines.CKMETADATA = [false, true] defines.FORCE_COMPACTION = [false, true] defines.SEED = 'range(100)' fuzz = 'SEED' @@ -4175,43 +4181,44 @@ code = ''' uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); - lfsr_traversal_t traversal = LFSR_TRAVERSAL( - (VALIDATE) ? LFSR_TRAVERSAL_VALIDATE : 0); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL( + LFS_T_MTREEONLY + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0)); for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfsr_tinfo_t tinfo; - int err = lfsr_traversal_read(&lfs, &traversal, &tinfo); + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(&lfs, &mt, &mtinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { break; } - if (tinfo.tag == LFSR_TAG_MDIR) { + if (mtinfo.tag == LFSR_TAG_MDIR) { printf("traversal: 0x%x mdir 0x{%x,%x}\n", - tinfo.tag, - tinfo.u.mdir.rbyd.blocks[0], - tinfo.u.mdir.rbyd.blocks[1]); + mtinfo.tag, + mtinfo.u.mdir.rbyd.blocks[0], + mtinfo.u.mdir.rbyd.blocks[1]); // keep track of seen blocks - seen[tinfo.u.mdir.rbyd.blocks[1] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[1] % 8); - seen[tinfo.u.mdir.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.mdir.rbyd.blocks[0] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[1] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[1] % 8); + seen[mtinfo.u.mdir.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.mdir.rbyd.blocks[0] % 8); - } else if (tinfo.tag == LFSR_TAG_BRANCH) { + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { printf("traversal: 0x%x btree 0x%x.%x\n", - tinfo.tag, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk); + mtinfo.tag, + mtinfo.u.rbyd.blocks[0], mtinfo.u.rbyd.trunk); // keep track of seen blocks - seen[tinfo.u.rbyd.blocks[0] / 8] - |= 1 << (tinfo.u.rbyd.blocks[0] % 8); + seen[mtinfo.u.rbyd.blocks[0] / 8] + |= 1 << (mtinfo.u.rbyd.blocks[0] % 8); } else { // this shouldn't happen - printf("traversal: 0x%x\n", tinfo.tag); + printf("traversal: 0x%x\n", mtinfo.tag); assert(false); } } @@ -4297,32 +4304,33 @@ code = ''' LFSR_DATA_MPTR(&LFSR_MPTR_MROOTANCHOR())))) => 0; // technically, cycle detection only needs to work when we're validating - lfsr_traversal_t traversal = LFSR_TRAVERSAL(LFSR_TRAVERSAL_VALIDATE); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL( + LFS_T_MTREEONLY | LFS_T_CKMETADATA); for (lfs_block_t i = 0;; i++) { // assert that we detect the cycle in a reasonable number of iterations assert(i < 2*BLOCK_COUNT); - lfsr_tinfo_t tinfo; - int err = lfsr_traversal_read(&lfs, &traversal, &tinfo); + lfsr_mtinfo_t mtinfo; + int err = lfsr_fs_traverse(&lfs, &mt, &mtinfo); assert(!err || err == LFS_ERR_CORRUPT); if (err == LFS_ERR_CORRUPT) { break; } - if (tinfo.tag == LFSR_TAG_MDIR) { + if (mtinfo.tag == LFSR_TAG_MDIR) { printf("traversal: 0x%x mdir 0x{%x,%x}\n", - tinfo.tag, - tinfo.u.mdir.rbyd.blocks[0], - tinfo.u.mdir.rbyd.blocks[1]); + mtinfo.tag, + mtinfo.u.mdir.rbyd.blocks[0], + mtinfo.u.mdir.rbyd.blocks[1]); - } else if (tinfo.tag == LFSR_TAG_BRANCH) { + } else if (mtinfo.tag == LFSR_TAG_BRANCH) { printf("traversal: 0x%x btree 0x%x.%x\n", - tinfo.tag, - tinfo.u.rbyd.blocks[0], tinfo.u.rbyd.trunk); + mtinfo.tag, + mtinfo.u.rbyd.blocks[0], mtinfo.u.rbyd.trunk); } else { // this shouldn't happen - printf("traversal: 0x%x\n", tinfo.tag); + printf("traversal: 0x%x\n", mtinfo.tag); assert(false); } } diff --git a/tests/test_traversal.toml b/tests/test_traversal.toml new file mode 100644 index 00000000..6da19584 --- /dev/null +++ b/tests/test_traversal.toml @@ -0,0 +1,1705 @@ +# Test incremental traversal things +after = [ + 'test_dirs', + 'test_files', + 'test_fwrite', + 'test_forphans', + 'test_alloc' +] + +# a simple traversal test +[cases.test_traversal_simple] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.flags == 0); + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.flags == 0); + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# can we rewind? +[cases.test_traversal_rewind] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.flags == 0); + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.flags == 0); + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + + lfsr_traversal_rewind(&lfs, &t) => 0; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.flags == 0); + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.flags == 0); + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# test that we don't get extra anything after end of traversal +[cases.test_traversal_idempotent] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.flags == 0); + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.flags == 0); + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT; + + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; +''' + + + +# some simple traversal tests with clobbering +[cases.test_traversal_clobber_dirs] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many directories + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_mkdir(&lfs, name) => 0; + } + + // traverse to find all blocks in use + uint8_t *seen = malloc((BLOCK_COUNT+7)/8); + memset(seen, 0, (BLOCK_COUNT+7)/8); + + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + + printf("traversal: btype %d block 0x%x\n", + tinfo.btype, + tinfo.block); + assert(tinfo.flags == 0); + assert(tinfo.btype == LFS_BTYPE_MDIR + || tinfo.btype == LFS_BTYPE_BTREE); + // keep track of seen blocks + seen[tinfo.block / 8] |= 1 << (tinfo.block % 8); + } + lfsr_traversal_close(&lfs, &t) => 0; + + // clobber every other block + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { + if (!(seen[block / 8] & (1 << (block % 8)))) { + CFG->erase(CFG, block) => 0; + CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0; + } + } + free(seen); + + // then check that we can read our directories after clobbering + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_clobber_files] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "file%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + } + + // traverse to find all blocks in use + uint8_t *seen = malloc((BLOCK_COUNT+7)/8); + memset(seen, 0, (BLOCK_COUNT+7)/8); + + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + + printf("traversal: btype %d block 0x%x\n", + tinfo.btype, + tinfo.block); + assert(tinfo.flags == 0); + assert(tinfo.btype == LFS_BTYPE_MDIR + || tinfo.btype == LFS_BTYPE_BTREE + || tinfo.btype == LFS_BTYPE_DATA); + // keep track of seen blocks + seen[tinfo.block / 8] |= 1 << (tinfo.block % 8); + } + lfsr_traversal_close(&lfs, &t) => 0; + + // clobber every other block + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { + if (!(seen[block / 8] & (1 << (block % 8)))) { + CFG->erase(CFG, block) => 0; + CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0; + } + } + free(seen); + + // then check that reading our files still works after clobbering + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // check with stat + char name[256]; + sprintf(name, "file%03x", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // try reading the file, note we reset prng above + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + uint8_t rbuf[SIZE]; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_clobber_open_files] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + lfsr_file_t files[N]; + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "file%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_open(&lfs, &files[i], name, + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; + } + + // traverse to find all blocks in use + uint8_t *seen = malloc((BLOCK_COUNT+7)/8); + memset(seen, 0, (BLOCK_COUNT+7)/8); + + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + + printf("traversal: btype %d block 0x%x\n", + tinfo.btype, + tinfo.block); + assert(tinfo.flags == 0); + assert(tinfo.btype == LFS_BTYPE_MDIR + || tinfo.btype == LFS_BTYPE_BTREE + || tinfo.btype == LFS_BTYPE_DATA); + // keep track of seen blocks + seen[tinfo.block / 8] |= 1 << (tinfo.block % 8); + } + lfsr_traversal_close(&lfs, &t) => 0; + + // clobber every other block + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { + if (!(seen[block / 8] & (1 << (block % 8)))) { + CFG->erase(CFG, block) => 0; + CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0; + } + } + free(seen); + + // then check that reading our files still works after clobbering + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // try reading the file, note we reset prng above + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_rewind(&lfs, &files[i]) => 0; + lfsr_file_read(&lfs, &files[i], rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + + // and everything is fine after saving the files + for (lfs_size_t i = 0; i < N; i++) { + lfsr_file_close(&lfs, &files[i]) => 0; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // check with stat + char name[256]; + sprintf(name, "file%03x", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // try reading the file, note we reset prng above + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + uint8_t rbuf[SIZE]; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + lfsr_unmount(&lfs) => 0; +''' + + + +# a bit more aggressive rewind tests +[cases.test_traversal_rewind_clobber_dirs] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many directories + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_mkdir(&lfs, name) => 0; + } + + // traverse to find all blocks in use + uint8_t *seen = malloc((BLOCK_COUNT+7)/8); + memset(seen, 0, (BLOCK_COUNT+7)/8); + + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + lfs_block_t r = 0; + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + if (i == r) { + lfsr_traversal_rewind(&lfs, &t) => 0; + memset(seen, 0, (BLOCK_COUNT+7)/8); + r += 1; + i = -1; + continue; + } + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + + printf("traversal: btype %d block 0x%x\n", + tinfo.btype, + tinfo.block); + assert(tinfo.flags == 0); + assert(tinfo.btype == LFS_BTYPE_MDIR + || tinfo.btype == LFS_BTYPE_BTREE); + // keep track of seen blocks + seen[tinfo.block / 8] |= 1 << (tinfo.block % 8); + } + lfsr_traversal_close(&lfs, &t) => 0; + + // clobber every other block + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { + if (!(seen[block / 8] & (1 << (block % 8)))) { + CFG->erase(CFG, block) => 0; + CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0; + } + } + free(seen); + + // then check that we can read our directories after clobbering + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_rewind_clobber_files] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "file%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + } + + // traverse to find all blocks in use + uint8_t *seen = malloc((BLOCK_COUNT+7)/8); + memset(seen, 0, (BLOCK_COUNT+7)/8); + + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + lfs_block_t r = 0; + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + if (i == r) { + lfsr_traversal_rewind(&lfs, &t) => 0; + memset(seen, 0, (BLOCK_COUNT+7)/8); + r += 1; + i = -1; + continue; + } + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + + printf("traversal: btype %d block 0x%x\n", + tinfo.btype, + tinfo.block); + assert(tinfo.flags == 0); + assert(tinfo.btype == LFS_BTYPE_MDIR + || tinfo.btype == LFS_BTYPE_BTREE + || tinfo.btype == LFS_BTYPE_DATA); + // keep track of seen blocks + seen[tinfo.block / 8] |= 1 << (tinfo.block % 8); + } + lfsr_traversal_close(&lfs, &t) => 0; + + // clobber every other block + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { + if (!(seen[block / 8] & (1 << (block % 8)))) { + CFG->erase(CFG, block) => 0; + CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0; + } + } + free(seen); + + // then check that reading our files still works after clobbering + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // check with stat + char name[256]; + sprintf(name, "file%03x", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // try reading the file, note we reset prng above + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + uint8_t rbuf[SIZE]; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_rewind_clobber_open_files] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + lfsr_file_t files[N]; + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "file%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_open(&lfs, &files[i], name, + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; + } + + // traverse to find all blocks in use + uint8_t *seen = malloc((BLOCK_COUNT+7)/8); + memset(seen, 0, (BLOCK_COUNT+7)/8); + + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + lfs_block_t r = 0; + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + if (i == r) { + lfsr_traversal_rewind(&lfs, &t) => 0; + memset(seen, 0, (BLOCK_COUNT+7)/8); + r += 1; + i = -1; + continue; + } + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + + printf("traversal: btype %d block 0x%x\n", + tinfo.btype, + tinfo.block); + assert(tinfo.flags == 0); + assert(tinfo.btype == LFS_BTYPE_MDIR + || tinfo.btype == LFS_BTYPE_BTREE + || tinfo.btype == LFS_BTYPE_DATA); + // keep track of seen blocks + seen[tinfo.block / 8] |= 1 << (tinfo.block % 8); + } + lfsr_traversal_close(&lfs, &t) => 0; + + // clobber every other block + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + for (lfs_block_t block = 0; block < BLOCK_COUNT; block++) { + if (!(seen[block / 8] & (1 << (block % 8)))) { + CFG->erase(CFG, block) => 0; + CFG->prog(CFG, block, 0, clobber_buf, BLOCK_SIZE) => 0; + } + } + free(seen); + + // then check that reading our files still works after clobbering + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // try reading the file, note we reset prng above + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_rewind(&lfs, &files[i]) => 0; + lfsr_file_read(&lfs, &files[i], rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + + // and everything is fine after saving the files + for (lfs_size_t i = 0; i < N; i++) { + lfsr_file_close(&lfs, &files[i]) => 0; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // check with stat + char name[256]; + sprintf(name, "file%03x", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // try reading the file, note we reset prng above + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + uint8_t rbuf[SIZE]; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + lfsr_unmount(&lfs) => 0; +''' + + + +# check that we can detect every clobbered mdir +[cases.test_traversal_ckmdir_dirs] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +code = ''' + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many directories + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_mkdir(&lfs, name) => 0; + } + + // traverse to find blocks + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t k = 0; + lfs_block_t c = 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + lfsr_traversal_close(&lfs, &t) => 0; + lfsr_unmount(&lfs) => 0; + goto done; + } + + assert(tinfo.flags == 0); + if (tinfo.btype == LFS_BTYPE_MDIR) { + if (k == i) { + // clobber this block + printf("clobbering 0x%x\n", tinfo.block); + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + CFG->erase(CFG, tinfo.block) => 0; + CFG->prog(CFG, tinfo.block, 0, + clobber_buf, BLOCK_SIZE) => 0; + if (c == 2-1) { + lfsr_traversal_close(&lfs, &t) => 0; + goto clobbered; + } else { + c += 1; + } + } else { + k += 1; + } + } + } + + clobbered:; + // traverse again, we should detect the clobbered metadata + lfsr_traversal_open(&lfs, &t, LFS_T_CKMETADATA) => 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_CORRUPT); + // found the clobbered metadata? + if (err == LFS_ERR_CORRUPT) { + break; + } + + assert(tinfo.flags == 0); + } + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; + } +done:; +''' + +[cases.test_traversal_ckmdir_files] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "file%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + } + + // traverse to find blocks + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t k = 0; + lfs_block_t c = 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + lfsr_traversal_close(&lfs, &t) => 0; + lfsr_unmount(&lfs) => 0; + goto done; + } + + assert(tinfo.flags == 0); + if (tinfo.btype == LFS_BTYPE_MDIR) { + if (k == i) { + // clobber this block + printf("clobbering 0x%x\n", tinfo.block); + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + CFG->erase(CFG, tinfo.block) => 0; + CFG->prog(CFG, tinfo.block, 0, + clobber_buf, BLOCK_SIZE) => 0; + if (c == 2-1) { + lfsr_traversal_close(&lfs, &t) => 0; + goto clobbered; + } else { + c += 1; + } + } else { + k += 1; + } + } + } + + clobbered:; + // traverse again, we should detect the clobbered metadata + lfsr_traversal_open(&lfs, &t, LFS_T_CKMETADATA) => 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_CORRUPT); + // found the clobbered metadata? + if (err == LFS_ERR_CORRUPT) { + break; + } + + assert(tinfo.flags == 0); + } + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; + } +done:; +''' + +[cases.test_traversal_ckmdir_open_files] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + lfsr_file_t files[N]; + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "file%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_open(&lfs, &files[i], name, + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; + } + + // traverse to find blocks + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t k = 0; + lfs_block_t c = 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + lfsr_traversal_close(&lfs, &t) => 0; + for (lfs_size_t i = 0; i < N; i++) { + lfsr_file_close(&lfs, &files[i]) => 0; + } + lfsr_unmount(&lfs) => 0; + goto done; + } + + assert(tinfo.flags == 0); + if (tinfo.btype == LFS_BTYPE_MDIR) { + if (k == i) { + // clobber this block + printf("clobbering 0x%x\n", tinfo.block); + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + CFG->erase(CFG, tinfo.block) => 0; + CFG->prog(CFG, tinfo.block, 0, + clobber_buf, BLOCK_SIZE) => 0; + if (c == 2-1) { + lfsr_traversal_close(&lfs, &t) => 0; + goto clobbered; + } else { + c += 1; + } + } else { + k += 1; + } + } + } + + clobbered:; + // traverse again, we should detect the clobbered metadata + lfsr_traversal_open(&lfs, &t, LFS_T_CKMETADATA) => 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_CORRUPT); + // found the clobbered metadata? + if (err == LFS_ERR_CORRUPT) { + break; + } + + assert(tinfo.flags == 0); + } + lfsr_traversal_close(&lfs, &t) => 0; + + for (lfs_size_t i = 0; i < N; i++) { + lfsr_file_desync(&lfs, &files[i]) => 0; + lfsr_file_close(&lfs, &files[i]) => 0; + } + lfsr_unmount(&lfs) => 0; + } +done:; +''' + + + +# check that we can detect every clobbered btree +[cases.test_traversal_ckbtree_dirs] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +code = ''' + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many directories + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_mkdir(&lfs, name) => 0; + } + + // traverse to find blocks + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t k = 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + lfsr_traversal_close(&lfs, &t) => 0; + lfsr_unmount(&lfs) => 0; + goto done; + } + + assert(tinfo.flags == 0); + if (tinfo.btype == LFS_BTYPE_BTREE) { + if (k == i) { + // clobber this block + printf("clobbering 0x%x\n", tinfo.block); + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + CFG->erase(CFG, tinfo.block) => 0; + CFG->prog(CFG, tinfo.block, 0, + clobber_buf, BLOCK_SIZE) => 0; + lfsr_traversal_close(&lfs, &t) => 0; + goto clobbered; + } else { + k += 1; + } + } + } + + clobbered:; + // traverse again, we should detect the clobbered metadata + lfsr_traversal_open(&lfs, &t, LFS_T_CKMETADATA) => 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_CORRUPT); + // found the clobbered metadata? + if (err == LFS_ERR_CORRUPT) { + break; + } + + assert(tinfo.flags == 0); + } + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; + } +done:; +''' + +[cases.test_traversal_ckbtree_files] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "file%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + } + + // traverse to find blocks + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t k = 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + lfsr_traversal_close(&lfs, &t) => 0; + lfsr_unmount(&lfs) => 0; + goto done; + } + + assert(tinfo.flags == 0); + if (tinfo.btype == LFS_BTYPE_BTREE) { + if (k == i) { + // clobber this block + printf("clobbering 0x%x\n", tinfo.block); + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + CFG->erase(CFG, tinfo.block) => 0; + CFG->prog(CFG, tinfo.block, 0, + clobber_buf, BLOCK_SIZE) => 0; + lfsr_traversal_close(&lfs, &t) => 0; + goto clobbered; + } else { + k += 1; + } + } + } + + clobbered:; + // traverse again, we should detect the clobbered metadata + lfsr_traversal_open(&lfs, &t, LFS_T_CKMETADATA) => 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_CORRUPT); + // found the clobbered metadata? + if (err == LFS_ERR_CORRUPT) { + break; + } + + assert(tinfo.flags == 0); + } + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; + } +done:; +''' + +[cases.test_traversal_ckbtree_open_files] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + lfsr_file_t files[N]; + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "file%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_open(&lfs, &files[i], name, + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; + } + + // traverse to find blocks + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t k = 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + lfsr_traversal_close(&lfs, &t) => 0; + for (lfs_size_t i = 0; i < N; i++) { + lfsr_file_close(&lfs, &files[i]) => 0; + } + lfsr_unmount(&lfs) => 0; + goto done; + } + + assert(tinfo.flags == 0); + if (tinfo.btype == LFS_BTYPE_BTREE) { + if (k == i) { + // clobber this block + printf("clobbering 0x%x\n", tinfo.block); + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + CFG->erase(CFG, tinfo.block) => 0; + CFG->prog(CFG, tinfo.block, 0, + clobber_buf, BLOCK_SIZE) => 0; + lfsr_traversal_close(&lfs, &t) => 0; + goto clobbered; + } else { + k += 1; + } + } + } + + clobbered:; + // traverse again, we should detect the clobbered metadata + lfsr_traversal_open(&lfs, &t, LFS_T_CKMETADATA) => 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_CORRUPT); + // found the clobbered metadata? + if (err == LFS_ERR_CORRUPT) { + break; + } + + assert(tinfo.flags == 0); + } + lfsr_traversal_close(&lfs, &t) => 0; + + for (lfs_size_t i = 0; i < N; i++) { + lfsr_file_desync(&lfs, &files[i]) => 0; + lfsr_file_close(&lfs, &files[i]) => 0; + } + lfsr_unmount(&lfs) => 0; + } +done:; +''' + + + +# check that we can detect every clobbered data block +# TODO mdirs? +[cases.test_traversal_ckdata_dirs] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +code = ''' + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many directories + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_mkdir(&lfs, name) => 0; + } + + // traverse to find blocks + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t k = 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + lfsr_traversal_close(&lfs, &t) => 0; + lfsr_unmount(&lfs) => 0; + goto done; + } + + assert(tinfo.flags == 0); + if (tinfo.btype == LFS_BTYPE_DATA) { + if (k == i) { + // clobber this block + printf("clobbering 0x%x\n", tinfo.block); + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + CFG->erase(CFG, tinfo.block) => 0; + CFG->prog(CFG, tinfo.block, 0, + clobber_buf, BLOCK_SIZE) => 0; + lfsr_traversal_close(&lfs, &t) => 0; + goto clobbered; + } else { + k += 1; + } + } + } + + clobbered:; + // traverse again, we should detect the clobbered metadata + lfsr_traversal_open(&lfs, &t, LFS_T_CKDATA) => 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_CORRUPT); + // found the clobbered metadata? + if (err == LFS_ERR_CORRUPT) { + break; + } + + assert(tinfo.flags == 0); + } + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; + } +done:; +''' + +[cases.test_traversal_ckdata_files] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "file%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + } + + // traverse to find blocks + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t k = 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + lfsr_traversal_close(&lfs, &t) => 0; + lfsr_unmount(&lfs) => 0; + goto done; + } + + assert(tinfo.flags == 0); + if (tinfo.btype == LFS_BTYPE_DATA) { + if (k == i) { + // clobber this block + printf("clobbering 0x%x\n", tinfo.block); + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + CFG->erase(CFG, tinfo.block) => 0; + CFG->prog(CFG, tinfo.block, 0, + clobber_buf, BLOCK_SIZE) => 0; + lfsr_traversal_close(&lfs, &t) => 0; + goto clobbered; + } else { + k += 1; + } + } + } + + clobbered:; + // traverse again, we should detect the clobbered metadata + lfsr_traversal_open(&lfs, &t, LFS_T_CKDATA) => 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_CORRUPT); + // found the clobbered metadata? + if (err == LFS_ERR_CORRUPT) { + break; + } + + assert(tinfo.flags == 0); + } + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; + } +done:; +''' + +[cases.test_traversal_ckdata_open_files] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + assert(i < 2*BLOCK_COUNT); + + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + lfsr_file_t files[N]; + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "file%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_open(&lfs, &files[i], name, + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; + } + + // traverse to find blocks + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t k = 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + lfsr_traversal_close(&lfs, &t) => 0; + for (lfs_size_t i = 0; i < N; i++) { + lfsr_file_close(&lfs, &files[i]) => 0; + } + lfsr_unmount(&lfs) => 0; + goto done; + } + + assert(tinfo.flags == 0); + if (tinfo.btype == LFS_BTYPE_DATA) { + if (k == i) { + // clobber this block + printf("clobbering 0x%x\n", tinfo.block); + uint8_t clobber_buf[BLOCK_SIZE]; + memset(clobber_buf, 0xcc, BLOCK_SIZE); + CFG->erase(CFG, tinfo.block) => 0; + CFG->prog(CFG, tinfo.block, 0, + clobber_buf, BLOCK_SIZE) => 0; + lfsr_traversal_close(&lfs, &t) => 0; + goto clobbered; + } else { + k += 1; + } + } + } + + clobbered:; + // traverse again, we should detect the clobbered metadata + lfsr_traversal_open(&lfs, &t, LFS_T_CKDATA) => 0; + for (lfs_block_t j = 0;; j++) { + assert(j < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_CORRUPT); + // found the clobbered metadata? + if (err == LFS_ERR_CORRUPT) { + break; + } + + assert(tinfo.flags == 0); + } + lfsr_traversal_close(&lfs, &t) => 0; + + for (lfs_size_t i = 0; i < N; i++) { + lfsr_file_desync(&lfs, &files[i]) => 0; + lfsr_file_close(&lfs, &files[i]) => 0; + } + lfsr_unmount(&lfs) => 0; + } +done:; +'''