diff --git a/lfs.c b/lfs.c index 8c06ff6e..8b8c24e0 100644 --- a/lfs.c +++ b/lfs.c @@ -2109,7 +2109,7 @@ static int lfsr_bptr_ck(lfs_t *lfs, const lfsr_bptr_t *bptr) { // predeclare block allocator functions -static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase); +static lfs_sblock_t lfs_alloc(lfs_t *lfs, bool erase); static void lfs_alloc_ckpoint(lfs_t *lfs); @@ -2152,12 +2152,16 @@ static inline int lfsr_rbyd_cmp( // allocate an rbyd block static int lfsr_rbyd_alloc(lfs_t *lfs, lfsr_rbyd_t *rbyd) { - *rbyd = (lfsr_rbyd_t){.weight=0, .trunk=0, .eoff=0, .cksum=0}; - int err = lfs_alloc(lfs, &rbyd->blocks[0], true); - if (err) { - return err; + lfs_sblock_t block = lfs_alloc(lfs, true); + if (block < 0) { + return block; } + rbyd->blocks[0] = block; + rbyd->trunk = 0; + rbyd->weight = 0; + rbyd->eoff = 0; + rbyd->cksum = 0; return 0; } @@ -6016,10 +6020,11 @@ static int lfsr_mdir_alloc__(lfs_t *lfs, lfsr_mdir_t *mdir, if (all) { // allocate one block without an erase - int err = lfs_alloc(lfs, &mdir->rbyd.blocks[1], false); - if (err) { - return err; + lfs_sblock_t block = lfs_alloc(lfs, false); + if (block < 0) { + return block; } + mdir->rbyd.blocks[1] = block; } // read the new revision count @@ -6039,10 +6044,11 @@ static int lfsr_mdir_alloc__(lfs_t *lfs, lfsr_mdir_t *mdir, relocate:; // allocate another block with an erase - err = lfs_alloc(lfs, &mdir->rbyd.blocks[0], true); - if (err) { - return err; + lfs_sblock_t block = lfs_alloc(lfs, true); + if (block < 0) { + return block; } + mdir->rbyd.blocks[0] = block; mdir->rbyd.weight = 0; mdir->rbyd.trunk = 0; mdir->rbyd.eoff = 0; @@ -7718,18 +7724,15 @@ static int lfsr_mtree_pathlookup(lfs_t *lfs, const lfsr_mtree_t *mtree, // 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_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, + LFSR_TSTATE_MROOTANCHOR = 0, + LFSR_TSTATE_MROOTCHAIN = 1, + LFSR_TSTATE_MTREE = 2, + LFSR_TSTATE_MDIR = 3, + LFSR_TSTATE_MDIRBTREE = 4, + LFSR_TSTATE_OMDIR = 5, + LFSR_TSTATE_OMDIRBTREE = 6, + LFSR_TSTATE_DONE = 7, }; #define LFSR_MTRAVERSAL(_flags) \ @@ -7742,10 +7745,29 @@ enum { .u.mtortoise.step=0, \ .u.mtortoise.power=0}) +static void lfsr_fs_traverserewind(lfs_t *lfs, lfsr_mtraversal_t *mt) { + (void)lfs; + mt->o.flags &= ~LFS_F_DIRTY; + mt->o.state = LFSR_TSTATE_MROOTANCHOR; + mt->o.mdir.mid = -1; + mt->u.mtortoise.mptr.blocks[0] = 0; + mt->u.mtortoise.mptr.blocks[1] = 0; + mt->u.mtortoise.step = 0; + mt->u.mtortoise.power = 0; +} + static inline bool lfsr_t_ismtreeonly(uint32_t flags) { return flags & LFS_T_MTREEONLY; } +static inline bool lfsr_t_isexcl(uint32_t flags) { + return flags & LFS_T_EXCL; +} + +static inline bool lfsr_t_ismkconsistent(uint32_t flags) { + return flags & LFS_T_MKCONSISTENT; +} + static inline bool lfsr_t_islookahead(uint32_t flags) { return flags & LFS_T_LOOKAHEAD; } @@ -7762,16 +7784,8 @@ 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; +static inline bool lfsr_f_isdirty(uint32_t flags) { + return flags & LFS_F_DIRTY; } @@ -7793,12 +7807,12 @@ static int lfsr_bshrub_traverse(lfs_t *lfs, const lfsr_file_t *file, static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, lfsr_mtinfo_t *mtinfo) { while (true) { - switch (mt->o.state >> 2) { + switch (mt->o.state) { // start with the mrootanchor 0x{0,1} // // note we make sure to include all mroots in our mroot chain! // - case LFSR_TSTATE_MROOTANCHOR >> 2:; + case LFSR_TSTATE_MROOTANCHOR:; // fetch the first mroot 0x{0,1} int err = lfsr_mdir_fetch(lfs, &mt->o.mdir, -1, &LFSR_MPTR_MROOTANCHOR()); @@ -7814,7 +7828,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, return 0; // traverse the mroot chain, checking for mroot/mtree/mdir - case LFSR_TSTATE_MROOTCHAIN >> 2:; + case LFSR_TSTATE_MROOTCHAIN:; // lookup mroot, if we find one this just an mroot chain link lfsr_tag_t tag; lfsr_data_t data; @@ -7915,7 +7929,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, } // traverse the mtree, including both inner btree nodes and mdirs - case LFSR_TSTATE_MTREE >> 2:; + case LFSR_TSTATE_MTREE:; // no mtree? transition to traversing any opened mdirs if (lfsr_mtree_ismptr(&lfs->mtree)) { mt->u.o = lfs->opened; @@ -7981,7 +7995,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, } // scan for blocks/btrees in the current mdir - case LFSR_TSTATE_MDIR >> 2:; + case LFSR_TSTATE_MDIR:; // not traversing all blocks? have we exceeded our mdir's weight? // return to mtree traversal if (lfsr_t_ismtreeonly(mt->o.flags) @@ -8033,7 +8047,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, continue; // scan for blocks/btrees in our opened file list - case LFSR_TSTATE_OMDIR >> 2:; + case LFSR_TSTATE_OMDIR:; // not traversing all blocks? reached end of opened file list? if (lfsr_t_ismtreeonly(mt->o.flags) || !mt->u.o) { mt->o.state = LFSR_TSTATE_DONE; @@ -8056,21 +8070,19 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, // traverse any file btrees, including both inner btree nodes and // block pointers - case LFSR_TSTATE_MDIRBTREE >> 2:; - case LFSR_TSTATE_OMDIRBTREE >> 2:; + case LFSR_TSTATE_MDIRBTREE:; + case LFSR_TSTATE_OMDIRBTREE:; // traverse through our file 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 ((mt->o.state >> 2) - == (LFSR_TSTATE_MDIRBTREE >> 2)) { + if (mt->o.state == LFSR_TSTATE_MDIRBTREE) { mt->o.mdir.mid += 1; mt->o.state = LFSR_TSTATE_MDIR; continue; - } else if ((mt->o.state >> 2) - == (LFSR_TSTATE_OMDIRBTREE >> 2)) { + } else if (mt->o.state == LFSR_TSTATE_OMDIRBTREE) { mt->u.o = mt->u.o->next; mt->o.state = LFSR_TSTATE_OMDIR; continue; @@ -8101,7 +8113,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, LFS_UNREACHABLE(); } - case LFSR_TSTATE_DONE >> 2:; + case LFSR_TSTATE_DONE:; return LFS_ERR_NOENT; default:; @@ -8110,7 +8122,11 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt, } } -// high-level traversal, handle extra features here +// needed in lfsr_fs_traverse +static void lfs_alloc_markinuse(lfs_t *lfs, lfs_block_t block); + +// high-level immutable traversal, handle extra features here, +// but no mutation! static int lfsr_fs_traverse(lfs_t *lfs, lfsr_mtraversal_t *mt, lfsr_mtinfo_t *mtinfo) { int err = lfsr_fs_traverse_(lfs, mt, mtinfo); @@ -8139,9 +8155,34 @@ static int lfsr_fs_traverse(lfs_t *lfs, lfsr_mtraversal_t *mt, } } + // track in-use blocks + if (lfsr_t_islookahead(mt->o.flags)) { + if (mtinfo->tag == LFSR_TAG_MDIR) { + lfs_alloc_markinuse(lfs, mtinfo->u.mdir.rbyd.blocks[0]); + lfs_alloc_markinuse(lfs, mtinfo->u.mdir.rbyd.blocks[1]); + + } else if (mtinfo->tag == LFSR_TAG_BRANCH) { + lfs_alloc_markinuse(lfs, mtinfo->u.rbyd.blocks[0]); + + } else if (mtinfo->tag == LFSR_TAG_BLOCK) { + lfs_alloc_markinuse(lfs, mtinfo->u.bptr.data.u.disk.block); + + } else { + LFS_UNREACHABLE(); + } + } + return 0; } +// high-level mutating traversal, handle extra features that require +// mutation here, upper layers should call lfs_alloc_ckpoint as needed +static int lfsr_fs_traversemut(lfs_t *lfs, lfsr_mtraversal_t *mt, + lfsr_mtinfo_t *mtinfo) { + // TODO + return lfsr_fs_traverse(lfs, mt, mtinfo); +} + /// Superblock things /// @@ -8898,81 +8939,128 @@ int lfsr_format(lfs_t *lfs, const struct lfs_config *cfg) { /// Block allocator /// -// Allocations should call this when all allocated blocks are committed to -// the filesystem, either in the mtree or in tracked mdirs. After a -// checkpoint, the block allocator may realloc any untracked blocks. +// checkpoint the allocator +// +// operations that need to alloc should call this to indicate all in-use +// blocks are either committed into the filesystem or tracked by an opened +// mdir static void lfs_alloc_ckpoint(lfs_t *lfs) { lfs->lookahead.ckpoint = lfs->block_count; + + // mark all opened traversals as dirty + for (lfsr_omdir_t *o = lfs->opened; o; o = o->next) { + if (o->type == LFS_TYPE_TRAVERSAL) { + o->flags |= LFS_F_DIRTY; + } + } } -// Discard lookahead state, this is necessary if block_count changes +// discard any lookahead state, this is necessary if block_count changes static void lfs_alloc_discard(lfs_t *lfs) { + // go ahead shift to next available block, we probably want the next + // scan to start here lfs->lookahead.start = (lfs->lookahead.start + lfs->lookahead.next) % lfs->block_count; lfs->lookahead.next = 0; lfs->lookahead.size = 0; - lfs->lookahead.ckpoint = 0; } -static inline void lfs_alloc_setinuse(lfs_t *lfs, lfs_block_t block) { +// shift the lookahead buffer to try to allocate more blocks, may do nothing +static void lfs_alloc_shift(lfs_t *lfs) { + // do nothing if shifting would make no progress + if (lfs->lookahead.next > 0) { + // discard already shifts to the next block + lfs_alloc_discard(lfs); + } + + // zero lookahead buffer + if (lfs->lookahead.size == 0) { + lfs_memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size); + } + + // don't update size until a successful lookahead scan +} + +// mark a block as in-use +static void lfs_alloc_markinuse(lfs_t *lfs, lfs_block_t block) { // translate to lookahead-relative - lfs_block_t rel = (block + lfs->block_count - lfs->lookahead.start) + lfs_block_t mark = (block + lfs->block_count - lfs->lookahead.start) % lfs->block_count; - if (rel < lfs->lookahead.size) { + if (mark < 8*lfs->cfg->lookahead_size) { // mark as in-use - lfs->lookahead.buffer[rel / 8] |= 1 << (rel % 8); + lfs->lookahead.buffer[mark / 8] |= 1 << (mark % 8); } } -static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase) { +// needed in lfs_alloc_markfree +static lfs_sblock_t lfs_alloc_findnext(lfs_t *lfs); + +// mark any not-in-use blocks as free +static void lfs_alloc_markfree(lfs_t *lfs) { + // make lookahead buffer usable + lfs->lookahead.size = lfs_min( + 8*lfs->cfg->lookahead_size, + lfs->lookahead.ckpoint); + + // eagerly find the next free block so shift can make progress + lfs_alloc_findnext(lfs); +} + +// find next free block in lookahead buffer, if there is one +static lfs_sblock_t lfs_alloc_findnext(lfs_t *lfs) { + while (lfs->lookahead.next < lfs->lookahead.size) { + if (!(lfs->lookahead.buffer[lfs->lookahead.next / 8] + & (1 << (lfs->lookahead.next % 8)))) { + // found a free block + return (lfs->lookahead.start + lfs->lookahead.next) + % lfs->block_count; + } + + lfs->lookahead.next += 1; + lfs->lookahead.ckpoint -= 1; + } + + return LFS_ERR_NOSPC; +} + +static lfs_sblock_t lfs_alloc(lfs_t *lfs, bool erase) { while (true) { // scan our lookahead buffer for free blocks - while (lfs->lookahead.next < lfs->lookahead.size) { - if (!(lfs->lookahead.buffer[lfs->lookahead.next / 8] - & (1 << (lfs->lookahead.next % 8)))) { - // found a free block - *block = (lfs->lookahead.start + lfs->lookahead.next) - % lfs->block_count; + lfs_sblock_t block = lfs_alloc_findnext(lfs); + if (block < 0 && block != LFS_ERR_NOSPC) { + return block; + } - // we should never alloc blocks {0,1} - LFS_ASSERT(*block != 0 && *block != 1); + if (block != LFS_ERR_NOSPC) { + // we should never alloc blocks {0,1} + LFS_ASSERT(block != 0 && block != 1); - // erase requested? - if (erase) { - int err = lfsr_bd_erase(lfs, *block); - if (err) { - // bad erase? try another block - if (err == LFS_ERR_CORRUPT) { - goto next; - } - return err; + // erase requested? + if (erase) { + int err = lfsr_bd_erase(lfs, block); + if (err) { + // bad erase? try another block + if (err == LFS_ERR_CORRUPT) { + lfs->lookahead.next += 1; + lfs->lookahead.ckpoint -= 1; + continue; } + return err; } - - // eagerly find next free block to maximize how many blocks - // lfs_alloc_ckpoint makes available for scanning - while (true) { - lfs->lookahead.next += 1; - lfs->lookahead.ckpoint -= 1; - - if (lfs->lookahead.next >= lfs->lookahead.size - || !(lfs->lookahead.buffer[lfs->lookahead.next / 8] - & (1 << (lfs->lookahead.next % 8)))) { - break; - } - } - - return 0; } - next:; + // eagerly find the next free block to maximize how many blocks + // lfs_alloc_ckpoint makes available for scanning lfs->lookahead.next += 1; lfs->lookahead.ckpoint -= 1; + lfs_alloc_findnext(lfs); + + return block; } // In order to keep our block allocator from spinning forever when our // filesystem is full, we mark points where there are no in-flight - // allocations with a checkpoint before starting a set of allocaitons. + // allocations with a checkpoint before starting a set of allocations. // // If we've looked at all blocks since the last checkpoint, we report // the filesystem as out of storage. @@ -8984,48 +9072,27 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase) { return LFS_ERR_NOSPC; } - // No blocks in our lookahead buffer, we need to scan the filesystem for - // unused blocks in the next lookahead window. - // - // note we limit the lookahead window to at most the amount of blocks - // checkpointed, this prevents the above math from underflowing - // - lfs->lookahead.start = (lfs->lookahead.start + lfs->lookahead.size) - % lfs->block_count; - lfs->lookahead.next = 0; - lfs->lookahead.size = lfs_min( - 8*lfs->cfg->lookahead_size, - lfs->lookahead.ckpoint); - lfs_memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size); + // no blocks in our lookahead buffer, we need to scan the filesystem + // for unused blocks in the next lookahead window + lfs_alloc_shift(lfs); // traverse the filesystem, building up knowledge of what blocks are // in use in our lookahead window - lfsr_mtraversal_t mt = LFSR_MTRAVERSAL(0); + lfsr_mtraversal_t mt = LFSR_MTRAVERSAL(LFS_T_LOOKAHEAD); while (true) { lfsr_mtinfo_t mtinfo; int err = lfsr_fs_traverse(lfs, &mt, &mtinfo); if (err) { + LFS_ASSERT(err != LFS_ERR_BUSY); if (err == LFS_ERR_NOENT) { break; } return err; } - - // mark any blocks we see at in-use, including any btree/mdir blocks - 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 (mtinfo.tag == LFSR_TAG_BRANCH) { - lfs_alloc_setinuse(lfs, mtinfo.u.rbyd.blocks[0]); - - } else if (mtinfo.tag == LFSR_TAG_BLOCK) { - lfs_alloc_setinuse(lfs, mtinfo.u.bptr.data.u.disk.block); - - } else { - LFS_UNREACHABLE(); - } } + + // mark anything not seen as free + lfs_alloc_markfree(lfs); } } @@ -9270,20 +9337,21 @@ failed:; /// High-level filesystem traversal /// -int lfsr_traversal_open(lfs_t *lfs, lfsr_traversal_t *traversal, - uint32_t flags) { +// needed in lfsr_traversal_open +static int lfsr_traversal_rewind_(lfs_t *lfs, lfsr_traversal_t *t); + +int lfsr_traversal_open(lfs_t *lfs, lfsr_traversal_t *t, uint32_t flags) { // already open? - LFS_ASSERT(!lfsr_opened_isopen(lfs, &traversal->mt.o)); + LFS_ASSERT(!lfsr_opened_isopen(lfs, &t->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)); + // these flags are internal and shouldn't be provided by the user + LFS_ASSERT(!lfsr_f_isdirty(flags)); // some flags mutate the filesystem - if (lfsr_t_iscompact(flags)) { + if (lfsr_t_ismkconsistent(flags) + || lfsr_t_iscompact(flags)) { // prepare our filesystem for writing int err = lfsr_fs_mkconsistent(lfs); if (err) { @@ -9292,90 +9360,115 @@ int lfsr_traversal_open(lfs_t *lfs, lfsr_traversal_t *traversal, } // setup traversal state - traversal->mt = LFSR_MTRAVERSAL(flags); - traversal->count = 0; + t->mt.o.type = LFS_TYPE_TRAVERSAL; + t->mt.o.flags = flags; + + // let rewind initialize/reset things + int err = lfsr_traversal_rewind_(lfs, t); + if (err) { + return err; + } // add to tracked mdirs - lfsr_opened_add(lfs, &traversal->mt.o); + lfsr_opened_add(lfs, &t->mt.o); return 0; } -int lfsr_traversal_close(lfs_t *lfs, lfsr_traversal_t *traversal) { - LFS_ASSERT(lfsr_opened_isopen(lfs, &traversal->mt.o)); +int lfsr_traversal_close(lfs_t *lfs, lfsr_traversal_t *t) { + LFS_ASSERT(lfsr_opened_isopen(lfs, &t->mt.o)); // remove from tracked mdirs - lfsr_opened_remove(lfs, &traversal->mt.o); + lfsr_opened_remove(lfs, &t->mt.o); return 0; } -int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *traversal, +int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *t, struct lfs_tinfo *tinfo) { - LFS_ASSERT(lfsr_opened_isopen(lfs, &traversal->mt.o)); + LFS_ASSERT(lfsr_opened_isopen(lfs, &t->mt.o)); + + // traversal dirty and excl? terminate early + if (lfsr_t_isexcl(t->mt.o.flags) + && lfsr_f_isdirty(t->mt.o.flags)) { + return LFS_ERR_BUSY; + } while (true) { // some redund blocks left over? - if (traversal->count > 0) { + if (t->blocks[0] != -1) { // 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]; + tinfo->btype = t->btype; + tinfo->block = t->blocks[0]; - traversal->blocks[0] = traversal->blocks[1]; - traversal->count -= 1; + t->blocks[0] = t->blocks[1]; + t->blocks[1] = -1; return 0; } // find next block lfsr_mtinfo_t mtinfo; - int err = lfsr_fs_traverse(lfs, &traversal->mt, &mtinfo); + int err = lfsr_fs_traversemut(lfs, &t->mt, &mtinfo); if (err) { + // end of traversal? + if (err == LFS_ERR_NOENT) { + goto done; + } 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; + t->btype = LFS_BTYPE_MDIR; + t->blocks[0] = mtinfo.u.mdir.rbyd.blocks[0]; + t->blocks[1] = mtinfo.u.mdir.rbyd.blocks[1]; } else if (mtinfo.tag == LFSR_TAG_BRANCH) { - traversal->btype = LFS_BTYPE_BTREE; - traversal->blocks[0] = mtinfo.u.rbyd.blocks[0]; - traversal->count = 1; + t->btype = LFS_BTYPE_BTREE; + t->blocks[0] = mtinfo.u.rbyd.blocks[0]; + t->blocks[1] = -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; + t->btype = LFS_BTYPE_DATA; + t->blocks[0] = mtinfo.u.bptr.data.u.disk.block; + t->blocks[1] = -1; } else { LFS_UNREACHABLE(); } } + +done:; + // was a lookahead scan successful? + if (lfsr_t_islookahead(t->mt.o.flags) + && !lfsr_f_isdirty(t->mt.o.flags)) { + lfs_alloc_markfree(lfs); + } + + // return BUSY if we're dirty, NOENT if we're clean + return (lfsr_f_isdirty(t->mt.o.flags)) + ? LFS_ERR_BUSY + : LFS_ERR_NOENT; } -int lfsr_traversal_rewind(lfs_t *lfs, lfsr_traversal_t *traversal) { - LFS_ASSERT(lfsr_opened_isopen(lfs, &traversal->mt.o)); - +static int lfsr_traversal_rewind_(lfs_t *lfs, lfsr_traversal_t *t) { // 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; + lfsr_fs_traverserewind(lfs, &t->mt); + t->blocks[0] = -1; + t->blocks[1] = -1; + + // shift the lookahead buffer if requested + if (lfsr_t_islookahead(t->mt.o.flags)) { + lfs_alloc_shift(lfs); + } return 0; } +int lfsr_traversal_rewind(lfs_t *lfs, lfsr_traversal_t *t) { + LFS_ASSERT(lfsr_opened_isopen(lfs, &t->mt.o)); + + return lfsr_traversal_rewind_(lfs, t); +} + @@ -11400,12 +11493,12 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file, // // note if we relocate, we rewrite the entire block from block_start // using what we can find in our tree - int err = lfs_alloc(lfs, &bptr.data.u.disk.block, true); - if (err) { - return err; + lfs_sblock_t block = lfs_alloc(lfs, true); + if (block < 0) { + return block; } - bptr.data = LFSR_DATA_DISK(bptr.data.u.disk.block, 0, 0); + bptr.data = LFSR_DATA_DISK(block, 0, 0); bptr.cksize = 0; bptr.cksum = 0; @@ -11435,7 +11528,7 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file, lfs_ssize_t d_ = lfs_min( d, size - (pos_ - pos)); - err = lfsr_bd_prog(lfs, bptr.data.u.disk.block, + int err = lfsr_bd_prog(lfs, bptr.data.u.disk.block, bptr.cksize, &buffer[pos_ - pos], d_, &bptr.cksum, true); @@ -11463,7 +11556,7 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file, lfsr_tag_t tag_; lfsr_bid_t weight_; lfsr_bptr_t bptr_; - err = lfsr_bshrub_lookupnext(lfs, file, pos_, + int err = lfsr_bshrub_lookupnext(lfs, file, pos_, &bid_, &tag_, &weight_, &bptr_); if (err) { LFS_ASSERT(err != LFS_ERR_NOENT); @@ -11518,7 +11611,7 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file, } // found a hole? fill with zeros - err = lfsr_bd_set(lfs, bptr.data.u.disk.block, bptr.cksize, + int err = lfsr_bd_set(lfs, bptr.data.u.disk.block, bptr.cksize, 0, d, &bptr.cksum, true); if (err) { @@ -11543,7 +11636,7 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file, bptr.cksize -= d; // finalize our write - err = lfsr_bd_flush(lfs, &bptr.cksum, true); + int err = lfsr_bd_flush(lfs, &bptr.cksum, true); if (err) { // bad prog? try another block if (err == LFS_ERR_CORRUPT) { diff --git a/lfs.h b/lfs.h index 677ddca6..9c334d43 100644 --- a/lfs.h +++ b/lfs.h @@ -43,6 +43,7 @@ typedef uint32_t lfs_off_t; typedef int32_t lfs_soff_t; typedef uint32_t lfs_block_t; +typedef int32_t lfs_sblock_t; typedef uint32_t lfsr_rid_t; typedef int32_t lfsr_srid_t; @@ -97,6 +98,7 @@ enum lfs_error { LFS_ERR_UNKNOWN = -1, // Unknown error LFS_ERR_INVAL = -22, // Invalid parameter LFS_ERR_NOTSUP = -95, // Operation not supported + LFS_ERR_BUSY = -16, // Device or resource busy LFS_ERR_IO = -5, // Error during device operation LFS_ERR_CORRUPT = -84, // Corrupted LFS_ERR_NOENT = -2, // No directory entry @@ -166,20 +168,19 @@ enum lfs_btype { // 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 + LFS_T_MTREEONLY = 0x0008, // Only traverse the mtree + LFS_T_EXCL = 0x0010, // Terminate if filesystem modified + LFS_T_MKCONSISTENT = 0x0020, // Make the filesystem consistent + LFS_T_LOOKAHEAD = 0x0040, // Populate lookahead buffer + LFS_T_COMPACT = 0x0080, // Compact metadata logs + LFS_T_CKMETADATA = 0x0100, // Check metadata checksums + LFS_T_CKDATA = 0x0200, // Check data checksums // TODO -// LFS_T_REPAIRMETADATA = 0x0100, // Repair metadata blocks -// LFS_T_REPAIRDATA = 0x0200, // Repair data blocks +// LFS_T_REPAIRMETADATA = 0x0400, // Repair metadata blocks +// LFS_T_REPAIRDATA = 0x0800, // 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 + // internally used flags + LFS_F_DIRTY = 0x1000, // Filesystem has been modified }; @@ -372,9 +373,6 @@ struct lfs_fsinfo { // Traversal info structure struct lfs_tinfo { - // Traversal flags - uint8_t flags; - // Type of the block uint8_t btype; @@ -632,8 +630,7 @@ 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]; + lfs_sblock_t blocks[2]; } lfsr_traversal_t; //typedef struct lfs_superblock { @@ -1061,8 +1058,8 @@ int lfsr_traversal_close(lfs_t *lfs, lfsr_traversal_t *traversal); // // Fills out the tinfo structure. // -// Returns 0 on success, LFS_ERR_NOENT at the end of traversal, or a -// negative error code on failure. +// Returns 0 on success, LFS_ERR_NOENT at the end of traversal, LFS_ERR_BUSY +// if filesystem has been modified, or a negative error code on failure. int lfsr_traversal_read(lfs_t *lfs, lfsr_traversal_t *traversal, struct lfs_tinfo *tinfo); diff --git a/scripts/dbgerr.py b/scripts/dbgerr.py index 216222cb..3287c7f5 100755 --- a/scripts/dbgerr.py +++ b/scripts/dbgerr.py @@ -6,6 +6,7 @@ ERRS = [ ('UNKNOWN', -1, "Unknown error" ), ('INVAL', -22, "Invalid parameter" ), ('NOTSUP', -95, "Operation not supported" ), + ('BUSY', -16, "Device or resource busy" ), ('IO', -5, "Error during device operation" ), ('CORRUPT', -84, "Corrupted" ), ('NOENT', -2, "No directory entry" ), diff --git a/tests/test_alloc.toml b/tests/test_alloc.toml index 0b685ea6..18ff4d2f 100644 --- a/tests/test_alloc.toml +++ b/tests/test_alloc.toml @@ -27,11 +27,10 @@ code = ''' lfs_alloc_ckpoint(&lfs); lfs_size_t alloced = 0; while (true) { - lfs_block_t block; - int err = lfs_alloc(&lfs, &block, ERASE); - assert(!err || err == LFS_ERR_NOSPC); + lfs_sblock_t block = lfs_alloc(&lfs, ERASE); + assert(block >= 0 || block == LFS_ERR_NOSPC); - if (err == LFS_ERR_NOSPC) { + if (block == LFS_ERR_NOSPC) { break; } alloced += 1; @@ -61,11 +60,10 @@ code = ''' lfs_alloc_ckpoint(&lfs); lfs_size_t alloced = 0; while (true) { - lfs_block_t block; - int err = lfs_alloc(&lfs, &block, ERASE); - assert(!err || err == LFS_ERR_NOSPC); + lfs_sblock_t block = lfs_alloc(&lfs, ERASE); + assert(block >= 0 || block == LFS_ERR_NOSPC); - if (err == LFS_ERR_NOSPC) { + if (block == LFS_ERR_NOSPC) { break; } alloced += 1; @@ -83,11 +81,10 @@ code = ''' lfs_alloc_ckpoint(&lfs); alloced = 0; while (true) { - lfs_block_t block; - int err = lfs_alloc(&lfs, &block, ERASE); - assert(!err || err == LFS_ERR_NOSPC); + lfs_sblock_t block = lfs_alloc(&lfs, ERASE); + assert(block >= 0 || block == LFS_ERR_NOSPC); - if (err == LFS_ERR_NOSPC) { + if (block == LFS_ERR_NOSPC) { break; } alloced += 1; diff --git a/tests/test_traversal.toml b/tests/test_traversal.toml index 6da19584..22f496b6 100644 --- a/tests/test_traversal.toml +++ b/tests/test_traversal.toml @@ -11,6 +11,7 @@ after = [ [cases.test_traversal_simple] defines.CKMETADATA = [false, true] defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; @@ -20,14 +21,13 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, ((CKMETADATA) ? LFS_T_CKMETADATA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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; @@ -40,6 +40,7 @@ code = ''' [cases.test_traversal_rewind] defines.CKMETADATA = [false, true] defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; @@ -49,25 +50,22 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, ((CKMETADATA) ? LFS_T_CKMETADATA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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; @@ -80,6 +78,7 @@ code = ''' [cases.test_traversal_idempotent] defines.CKMETADATA = [false, true] defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, CFG) => 0; @@ -89,14 +88,13 @@ code = ''' lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, ((CKMETADATA) ? LFS_T_CKMETADATA : 0) - | ((CKDATA) ? LFS_T_CKDATA : 0)) => 0; + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 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; @@ -154,7 +152,6 @@ code = ''' 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 @@ -276,7 +273,6 @@ code = ''' 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); @@ -391,7 +387,6 @@ code = ''' 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); @@ -517,7 +512,6 @@ code = ''' 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 @@ -648,7 +642,6 @@ code = ''' 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); @@ -772,7 +765,6 @@ code = ''' 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); @@ -886,7 +878,6 @@ code = ''' goto done; } - assert(tinfo.flags == 0); if (tinfo.btype == LFS_BTYPE_MDIR) { if (k == i) { // clobber this block @@ -921,8 +912,6 @@ code = ''' if (err == LFS_ERR_CORRUPT) { break; } - - assert(tinfo.flags == 0); } lfsr_traversal_close(&lfs, &t) => 0; @@ -989,7 +978,6 @@ code = ''' goto done; } - assert(tinfo.flags == 0); if (tinfo.btype == LFS_BTYPE_MDIR) { if (k == i) { // clobber this block @@ -1024,8 +1012,6 @@ code = ''' if (err == LFS_ERR_CORRUPT) { break; } - - assert(tinfo.flags == 0); } lfsr_traversal_close(&lfs, &t) => 0; @@ -1094,7 +1080,6 @@ code = ''' goto done; } - assert(tinfo.flags == 0); if (tinfo.btype == LFS_BTYPE_MDIR) { if (k == i) { // clobber this block @@ -1129,8 +1114,6 @@ code = ''' if (err == LFS_ERR_CORRUPT) { break; } - - assert(tinfo.flags == 0); } lfsr_traversal_close(&lfs, &t) => 0; @@ -1180,7 +1163,6 @@ code = ''' goto done; } - assert(tinfo.flags == 0); if (tinfo.btype == LFS_BTYPE_BTREE) { if (k == i) { // clobber this block @@ -1211,8 +1193,6 @@ code = ''' if (err == LFS_ERR_CORRUPT) { break; } - - assert(tinfo.flags == 0); } lfsr_traversal_close(&lfs, &t) => 0; @@ -1278,7 +1258,6 @@ code = ''' goto done; } - assert(tinfo.flags == 0); if (tinfo.btype == LFS_BTYPE_BTREE) { if (k == i) { // clobber this block @@ -1309,8 +1288,6 @@ code = ''' if (err == LFS_ERR_CORRUPT) { break; } - - assert(tinfo.flags == 0); } lfsr_traversal_close(&lfs, &t) => 0; @@ -1378,7 +1355,6 @@ code = ''' goto done; } - assert(tinfo.flags == 0); if (tinfo.btype == LFS_BTYPE_BTREE) { if (k == i) { // clobber this block @@ -1409,8 +1385,6 @@ code = ''' if (err == LFS_ERR_CORRUPT) { break; } - - assert(tinfo.flags == 0); } lfsr_traversal_close(&lfs, &t) => 0; @@ -1426,7 +1400,6 @@ 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 = ''' @@ -1461,7 +1434,6 @@ code = ''' goto done; } - assert(tinfo.flags == 0); if (tinfo.btype == LFS_BTYPE_DATA) { if (k == i) { // clobber this block @@ -1492,8 +1464,6 @@ code = ''' if (err == LFS_ERR_CORRUPT) { break; } - - assert(tinfo.flags == 0); } lfsr_traversal_close(&lfs, &t) => 0; @@ -1559,7 +1529,6 @@ code = ''' goto done; } - assert(tinfo.flags == 0); if (tinfo.btype == LFS_BTYPE_DATA) { if (k == i) { // clobber this block @@ -1590,8 +1559,6 @@ code = ''' if (err == LFS_ERR_CORRUPT) { break; } - - assert(tinfo.flags == 0); } lfsr_traversal_close(&lfs, &t) => 0; @@ -1659,7 +1626,6 @@ code = ''' goto done; } - assert(tinfo.flags == 0); if (tinfo.btype == LFS_BTYPE_DATA) { if (k == i) { // clobber this block @@ -1690,8 +1656,6 @@ code = ''' if (err == LFS_ERR_CORRUPT) { break; } - - assert(tinfo.flags == 0); } lfsr_traversal_close(&lfs, &t) => 0; @@ -1703,3 +1667,2095 @@ code = ''' } done:; ''' + + + +# test that we detect filesystem mutation during traversal +[cases.test_traversal_mutation] +defines.WHEN = [0, 1, 2] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [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) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + + if (WHEN == 0) { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + } + + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + + if (WHEN == 1) { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + } + + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + + if (WHEN == 2) { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + } + + // final read should return BUSY + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_excl] +defines.WHEN = [0, 1, 2] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [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, + LFS_T_EXCL + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + + if (WHEN == 0) { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + + // read should immediately error + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + goto done; + } + + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + + if (WHEN == 1) { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + + // read should immediately error + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + goto done; + } + + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + + if (WHEN == 2) { + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + + // read should immediately error + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + lfsr_traversal_close(&lfs, &t) => 0; + goto done; + } + +done:; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_mkdir] +defines.WHEN = [0, 1, 2] +defines.EXCL = [false, true] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [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, + ((EXCL) ? LFS_T_EXCL : 0) + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + + if (WHEN == 0) { + lfsr_mkdir(&lfs, "spider") => 0; + + if (EXCL) { + // read should immediately error + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + goto done; + } + } + + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + + if (WHEN == 1) { + lfsr_mkdir(&lfs, "spider") => 0; + + if (EXCL) { + // read should immediately error + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + goto done; + } + } + + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + + if (WHEN == 2) { + lfsr_mkdir(&lfs, "spider") => 0; + + if (EXCL) { + // read should immediately error + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + goto done; + } + } + + // final read should return BUSY + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; +done:; + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_remove] +defines.WHEN = [0, 1, 2] +defines.EXCL = [false, true] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // make a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((EXCL) ? LFS_T_EXCL : 0) + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + + if (WHEN == 0) { + lfsr_remove(&lfs, "spider") => 0; + + if (EXCL) { + // read should immediately error + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + goto done; + } + } + + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + + if (WHEN == 1) { + lfsr_remove(&lfs, "spider") => 0; + + if (EXCL) { + // read should immediately error + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + goto done; + } + } + + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + + if (WHEN == 2) { + lfsr_remove(&lfs, "spider") => 0; + + if (EXCL) { + // read should immediately error + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + goto done; + } + } + + // final read should return BUSY + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; +done:; + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_rename] +defines.WHEN = [0, 1, 2] +defines.EXCL = [false, true] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // make a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((EXCL) ? LFS_T_EXCL : 0) + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + + if (WHEN == 0) { + lfsr_rename(&lfs, "spider", "scorpion") => 0; + + if (EXCL) { + // read should immediately error + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + goto done; + } + } + + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + + if (WHEN == 1) { + lfsr_rename(&lfs, "spider", "scorpion") => 0; + + if (EXCL) { + // read should immediately error + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + goto done; + } + } + + lfsr_traversal_read(&lfs, &t, &tinfo) => 0; + assert(tinfo.btype == LFS_BTYPE_MDIR); + assert(tinfo.block == 0 || tinfo.block == 1); + + if (WHEN == 2) { + lfsr_rename(&lfs, "spider", "scorpion") => 0; + + if (EXCL) { + // read should immediately error + struct lfs_tinfo tinfo; + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; + goto done; + } + } + + // final read should return BUSY + lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_BUSY; +done:; + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# some more complex mutation tests +[cases.test_traversal_mutation_fwrite] +defines.EXCL = [false, true] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +defines.SIZE = [ + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((EXCL) ? LFS_T_EXCL : 0) + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + + while (true) { + // rewrite the file every step of the traversal + lfsr_file_open(&lfs, &file, "spider", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // step traversal + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_BUSY); + if (err == LFS_ERR_BUSY) { + break; + } + } + + lfsr_traversal_close(&lfs, &t) => 0; + + // check the file contents + lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_fwrite_open] +defines.EXCL = [false, true] +defines.CKMETADATA = [false, true] +defines.CKDATA = [false, true] +defines.LOOKAHEAD = [false, true] +defines.SIZE = [ + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +defines.SYNC = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "spider", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // try traversing + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((EXCL) ? LFS_T_EXCL : 0) + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + + while (true) { + // rewrite the file every step of the traversal + lfsr_file_rewind(&lfs, &file) => 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // step traversal + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_BUSY); + if (err == LFS_ERR_BUSY) { + break; + } + } + + lfsr_traversal_close(&lfs, &t) => 0; + + // check the file contents + lfsr_file_rewind(&lfs, &file) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + // and after close? + lfsr_file_open(&lfs, &file, "spider", 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; +''' + + + +# many/fuzz tests mixed with traversals +# +# these should hopefully test a bunch of messy traversal state + +# TODO actually implement this +[cases.test_traversal_mutation_dir_many] +# traverse steps between each op +defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] +defines.EXCL = [false, true] +defines.CKMETADATA = [true] +defines.CKDATA = [true] +defines.LOOKAHEAD = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] +code = ''' + // test creating directories + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // open a traversal + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((EXCL) ? LFS_T_EXCL : 0) + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + + // make this many directories + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + int err = lfsr_mkdir(&lfs, name); + assert(!err || (TEST_PLS && err == LFS_ERR_EXIST)); + + // step the traversal + for (lfs_size_t s = 0; s < STEPS; s++) { + struct lfs_tinfo tinfo; + err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT || err == LFS_ERR_BUSY); + // restart traversal + if (err == LFS_ERR_NOENT || err == LFS_ERR_BUSY) { + lfsr_traversal_rewind(&lfs, &t) => 0; + } + } + } + + lfsr_traversal_close(&lfs, &t) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // grm should be zero here + assert(lfs.grm_p[0] == 0); + + // check that our mkdir worked + 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; + + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_dir_open(&lfs, &dir, name) => 0; + 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); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + } + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_dir_fuzz] +# traverse steps between each op +defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] +defines.EXCL = [false, true] +defines.CKMETADATA = [true] +defines.CKDATA = [true] +defines.LOOKAHEAD = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] +defines.OPS = '2*N' +defines.SEED = 42 +fuzz = 'SEED' +code = ''' + // test fuzz with dirs + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + lfs_size_t sim_size = 0; + + // open a traversal + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((EXCL) ? LFS_T_EXCL : 0) + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + // choose a pseudo-random op, either mkdir, remove, or rename + uint8_t op = TEST_PRNG(&prng) % 3; + + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number, truncate to 3 hexadecimals + lfs_size_t x = TEST_PRNG(&prng) % N; + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // do nothing + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + sim_size += 1; + sim[j] = x; + } + break; + } + } + + // create a directory here + char name[256]; + sprintf(name, "dir%03x", x); + int err = lfsr_mkdir(&lfs, name); + assert(!err || err == LFS_ERR_EXIST); + + } else if (op == 1) { + // choose a pseudo-random entry to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + sim_size -= 1; + + // remove this directory + char name[256]; + sprintf(name, "dir%03x", x); + lfsr_remove(&lfs, name) => 0; + + } else { + // choose a pseudo-random entry to rename, and a pseudo-random + // number to rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // already seen and not a noop? + if (k < sim_size && sim[k] == y && x != y) { + // just delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + sim_size -= 1; + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + sim[k] = y; + } + break; + } + } + + // rename this directory + char old_name[256]; + sprintf(old_name, "dir%03x", x); + char new_name[256]; + sprintf(new_name, "dir%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + + // step the traversal + for (lfs_size_t s = 0; s < STEPS; s++) { + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT || err == LFS_ERR_BUSY); + // restart traversal + if (err == LFS_ERR_NOENT || err == LFS_ERR_BUSY) { + lfsr_traversal_rewind(&lfs, &t) => 0; + } + } + } + + lfsr_traversal_close(&lfs, &t) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // grm should be zero here + assert(lfs.grm_p[0] == 0); + + // test that our directories match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + char name2[256]; + sprintf(name2, "dir%03x", sim[j]); + assert(strcmp(info.name, name2) == 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 j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + 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; + } + + // clean up sim/lfs + free(sim); + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_file_many] +# traverse steps between each op +defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] +defines.EXCL = [false, true] +defines.CKMETADATA = [true] +defines.CKDATA = [true] +defines.LOOKAHEAD = [false, true] +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', + '4*BLOCK_SIZE', +] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + // test creating files + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // open a traversal + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((EXCL) ? LFS_T_EXCL : 0) + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "amethyst%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; + + // step the traversal + for (lfs_size_t s = 0; s < STEPS; s++) { + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT || err == LFS_ERR_BUSY); + // restart traversal + if (err == LFS_ERR_NOENT || err == LFS_ERR_BUSY) { + lfsr_traversal_rewind(&lfs, &t) => 0; + } + } + } + + lfsr_traversal_close(&lfs, &t) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our writes worked + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // check with stat + char name[256]; + sprintf(name, "amethyst%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_mutation_file_fuzz] +# traverse steps between each op +defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] +defines.EXCL = [false, true] +defines.CKMETADATA = [true] +defines.CKDATA = [true] +defines.LOOKAHEAD = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = '2*N' +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +code = ''' + // test fuzz with files + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; + + // open a traversal + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((EXCL) ? LFS_T_EXCL : 0) + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 3; + + // creating a new file? + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + // associate each file with a prng that generates its contents + uint32_t wprng = TEST_PRNG(&prng); + + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // new prng + sim_prngs[j] = wprng; + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + memmove(&sim_prngs[j+1], &sim_prngs[j], + (sim_size-j)*sizeof(uint32_t)); + sim_size += 1; + sim[j] = x; + sim_prngs[j] = wprng; + } + break; + } + } + + // create a file here + char name[256]; + sprintf(name, "amethyst%03x", x); + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // deleting a file? + } else if (op == 1) { + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // delete this file + char name[256]; + sprintf(name, "amethyst%03x", x); + lfsr_remove(&lfs, name) => 0; + + // renaming a file? + } else { + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; + } + break; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "amethyst%03x", x); + char new_name[256]; + sprintf(new_name, "amethyst%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + + // step the traversal + for (lfs_size_t s = 0; s < STEPS; s++) { + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT || err == LFS_ERR_BUSY); + // restart traversal + if (err == LFS_ERR_NOENT || err == LFS_ERR_BUSY) { + lfsr_traversal_rewind(&lfs, &t) => 0; + } + } + } + + lfsr_traversal_close(&lfs, &t) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our files match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + 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); + } + + 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 j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // check the file contents + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_fwrite_fuzz] +# traverse steps between each op +defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] +defines.EXCL = [false, true] +defines.CKMETADATA = [true] +defines.CKDATA = [true] +defines.LOOKAHEAD = [false, true] +defines.OPS = 20 +defines.SIZE = [ + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +# chunk is more an upper limit here +defines.CHUNK = [32, 8, 1] +# INIT=0 => no init +# INIT=1 => fill with data +# INIT=2 => truncate to size +defines.INIT = [0, 1, 2] +defines.SYNC = [false, true] +defines.SEED = 42 +fuzz = 'SEED' +if = [ + 'CHUNK <= SIZE', + # this just saves testing time + 'SIZE <= 4*1024*FRAGMENT_SIZE', +] +code = ''' + // test with complex file writes + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + // simulate our file in ram + uint8_t sim[SIZE]; + lfs_off_t size; + uint32_t prng = SEED; + if (INIT == 0) { + memset(sim, 0, SIZE); + size = 0; + } else if (INIT == 1) { + for (lfs_size_t i = 0; i < SIZE; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; + size = SIZE; + } else { + memset(sim, 0, SIZE); + lfsr_file_truncate(&lfs, &file, SIZE) => 0; + size = SIZE; + } + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // open a traversal + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((EXCL) ? LFS_T_EXCL : 0) + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + + for (lfs_size_t i = 0; i < OPS; i++) { + // choose a random location + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + // and a random size, up to the chunk size + lfs_size_t chunk = lfs_min( + (TEST_PRNG(&prng) % (CHUNK+1-1)) + 1, + SIZE - off); + + // update sim + for (lfs_size_t j = 0; j < chunk; j++) { + sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); + } + size = lfs_max(size, off+chunk); + + // update file + lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; + lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk; + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // step the traversal + for (lfs_size_t s = 0; s < STEPS; s++) { + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT || err == LFS_ERR_BUSY); + // restart traversal + if (err == LFS_ERR_NOENT || err == LFS_ERR_BUSY) { + lfsr_traversal_rewind(&lfs, &t) => 0; + } + } + } + + lfsr_traversal_close(&lfs, &t) => 0; + + lfsr_file_close(&lfs, &file) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == size); + + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + 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); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == size); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => size; + // try reading + uint8_t rbuf[2*SIZE]; + memset(rbuf, 0xaa, 2*SIZE); + lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size; + // does our file match our simulation? + assert(memcmp(rbuf, sim, size) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_orphanzombie_fuzz] +# traverse steps between each op +defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] +defines.EXCL = [false, true] +defines.CKMETADATA = [true] +defines.CKDATA = [true] +defines.LOOKAHEAD = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = '2*N' +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +code = ''' + // test with orphans, zombies, etc + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; + + typedef struct sim_file { + lfs_size_t x; + bool orphan; + bool zombie; + uint32_t prng; + lfsr_file_t file; + } sim_file_t; + sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); + lfs_size_t sim_file_count = 0; + + // open a traversal + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((EXCL) ? LFS_T_EXCL : 0) + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + nonsense:; + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 5; + + // open a new file? + if (op == 0) { + if (sim_file_count >= N) { + goto nonsense; + } + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // already exists? + bool orphan = true; + uint32_t wprng = 0; + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim[j] == x) { + orphan = false; + wprng = sim_prngs[j]; + break; + } + } + // choose a random seed if we don't exist + if (orphan) { + wprng = TEST_PRNG(&prng); + } + + // open in our sim + lfs_size_t j = sim_file_count; + sim_files[j] = malloc(sizeof(sim_file_t)); + sim_files[j]->x = x; + sim_files[j]->orphan = orphan; + sim_files[j]->zombie = false; + sim_files[j]->prng = wprng; + sim_file_count++; + + // open the actual file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_file_open(&lfs, &sim_files[j]->file, name, + LFS_O_RDWR | LFS_O_CREAT) => 0; + + // write some initial data if we don't exist + if (orphan) { + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + } + + // write/rewrite a file? + } else if (op == 1) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + lfs_size_t x = sim_files[j]->x; + // choose a random seed + uint32_t wprng = TEST_PRNG(&prng); + + // update sim + sim_files[j]->prng = wprng; + if (!sim_files[j]->zombie) { + // insert into our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + // new prng + sim_prngs[k] = wprng; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = wprng; + } + break; + } + } + + // update related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x && !sim_files[k]->zombie) { + sim_files[k]->orphan = false; + sim_files[k]->prng = wprng; + } + } + } + + // write to the file + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + lfsr_file_sync(&lfs, &sim_files[j]->file) + => (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT; + + // close a file? + } else if (op == 2) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + + // this doesn't really test anything, but if we don't close + // files eventually everything will end up zombies + + // close the file without affected disk + lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + + // remove from list + free(sim_files[j]); + sim_files[j] = sim_files[sim_file_count-1]; + sim_file_count -= 1; + + // remove a file? + } else if (op == 3) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // delete this file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_remove(&lfs, name) => 0; + + // rename a file? + } else if (op == 4) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; + } + break; + } + } + + // update any related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + // move source files + if (sim_files[k]->x == x) { + sim_files[k]->x = y; + + // mark target files as zombied + } else if (sim_files[k]->x == y) { + sim_files[k]->zombie = true; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "batman%03x", x); + char new_name[256]; + sprintf(new_name, "batman%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + + // step the traversal + for (lfs_size_t s = 0; s < STEPS; s++) { + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT || err == LFS_ERR_BUSY); + // restart traversal + if (err == LFS_ERR_NOENT || err == LFS_ERR_BUSY) { + lfsr_traversal_rewind(&lfs, &t) => 0; + } + } + } + + lfsr_traversal_close(&lfs, &t) => 0; + + // check that disk matches our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + 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); + } + + 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 j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + // check that our file handles match our simulation + for (lfs_size_t j = 0; j < sim_file_count; j++) { + uint32_t wprng = sim_files[j]->prng; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + for (lfs_size_t j = 0; j < sim_file_count; j++) { + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + free(sim_files[j]); + } + free(sim_files); + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_traversal_mutation_orphanzombiedir_fuzz] +# traverse steps between each op +defines.STEPS = [1, 2, 4, 8, 16, 32, 64, 128] +defines.EXCL = [false, true] +defines.CKMETADATA = [true] +defines.CKDATA = [true] +defines.LOOKAHEAD = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = '2*N' +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +code = ''' + // test with orphans, zombies, dirs, etc + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + bool *sim_isdirs = malloc(N*sizeof(bool)); + lfs_size_t sim_size = 0; + + typedef struct sim_file { + lfs_size_t x; + bool orphan; + bool zombie; + uint32_t prng; + lfsr_file_t file; + } sim_file_t; + sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); + lfs_size_t sim_file_count = 0; + + // open a traversal + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, + ((EXCL) ? LFS_T_EXCL : 0) + | ((CKMETADATA) ? LFS_T_CKMETADATA : 0) + | ((CKDATA) ? LFS_T_CKDATA : 0) + | ((LOOKAHEAD) ? LFS_T_LOOKAHEAD : 0)) => 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + nonsense:; + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 8; + + // open a new file? + if (op == 0) { + if (sim_file_count >= N) { + goto nonsense; + } + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // already exists? + bool orphan = true; + uint32_t wprng = 0; + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim[j] == x) { + if (sim_isdirs[j]) { + goto nonsense; + } + orphan = false; + wprng = sim_prngs[j]; + break; + } + } + // choose a random seed if we don't exist + if (orphan) { + wprng = TEST_PRNG(&prng); + } + + // open in our sim + lfs_size_t j = sim_file_count; + sim_files[j] = malloc(sizeof(sim_file_t)); + sim_files[j]->x = x; + sim_files[j]->orphan = orphan; + sim_files[j]->zombie = false; + sim_files[j]->prng = wprng; + sim_file_count++; + + // open the actual file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_file_open(&lfs, &sim_files[j]->file, name, + LFS_O_RDWR | LFS_O_CREAT) => 0; + + // write some initial data if we don't exist + if (orphan) { + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + } + + // write/rewrite a file? + } else if (op == 1) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + lfs_size_t x = sim_files[j]->x; + // choose a random seed + uint32_t wprng = TEST_PRNG(&prng); + + // update sim + sim_files[j]->prng = wprng; + if (!sim_files[j]->zombie) { + // insert into our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + // new prng + sim_prngs[k] = wprng; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = wprng; + sim_isdirs[k] = false; + } + break; + } + } + + // update related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x && !sim_files[k]->zombie) { + sim_files[k]->orphan = false; + sim_files[k]->prng = wprng; + } + } + } + + // write to the file + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + lfsr_file_sync(&lfs, &sim_files[j]->file) + => (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT; + + // close a file? + } else if (op == 2) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + + // this doesn't really test anything, but if we don't close + // files eventually everything will end up zombies + + // close the file without affected disk + lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + + // remove from list + free(sim_files[j]); + sim_files[j] = sim_files[sim_file_count-1]; + sim_file_count -= 1; + + // remove a file? + } else if (op == 3) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + sim_size -= 1; + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // delete this file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_remove(&lfs, name) => 0; + + // rename a file? + } else if (op == 4) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + bool isdir = sim_isdirs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // type mismatch? + if (sim_isdirs[k] != isdir) { + goto nonsense; + } + + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim[k] = y; + sim_prngs[k] = wprng; + sim_isdirs[k] = isdir; + } + break; + } + } + + // update any related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + // move source files + if (sim_files[k]->x == x) { + sim_files[k]->x = y; + + // mark target files as zombied + } else if (sim_files[k]->x == y) { + sim_files[k]->zombie = true; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "batman%03x", x); + char new_name[256]; + sprintf(new_name, "batman%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + + // toss a directory into the mix + } else if (op == 5) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // insert into our sim, use negative numbers for dirs + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + goto nonsense; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = 0; + sim_isdirs[k] = true; + } + break; + } + } + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // make the directory + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_mkdir(&lfs, name) => 0; + } + + // step the traversal + for (lfs_size_t s = 0; s < STEPS; s++) { + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err || err == LFS_ERR_NOENT || err == LFS_ERR_BUSY); + // restart traversal + if (err == LFS_ERR_NOENT || err == LFS_ERR_BUSY) { + lfsr_traversal_rewind(&lfs, &t) => 0; + } + } + } + + lfsr_traversal_close(&lfs, &t) => 0; + + // check that disk matches our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + if (sim_isdirs[j]) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + } + + 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 j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + if (sim_isdirs[j]) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim_isdirs[j]) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => LFS_ERR_ISDIR; + + } else { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + // check that our file handles match our simulation + for (lfs_size_t j = 0; j < sim_file_count; j++) { + uint32_t wprng = sim_files[j]->prng; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + for (lfs_size_t j = 0; j < sim_file_count; j++) { + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + free(sim_files[j]); + } + free(sim_files); + lfsr_unmount(&lfs) => 0; +''' +