From fe11a334162bda6ab43cc956813b5f1706fe59e6 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sun, 26 May 2024 23:17:41 -0500 Subject: [PATCH] (Re)implemented bad prog/erase recovery This (re)implements the heavy-hitting tests in test_badblocks that rakes filesystem operations over various types of prog/erase failures: - test_badblocks_[one|region|alternating]_btree - force tall B-trees - test_badblocks_[one|region|alternating]_dirs - large mtree - test_badblocks_[one|region|alternating]_files - mixed mtree + files - test_badblocks_[one|region|alternating]_fwrite_fuzz - complex files - test_badblocks_[one|region|alternating]_orphanzombiedir_fuzz - complex - test_badblocks_mrootanchor - uh, format fails, cheap test though Where: - test_badblocks_one_* - runs with every possible bad block - test_badblocks_region_* - runs with a large region of bad blocks - test_badblocks_alternating_* - runs with alternating bad blocks, this one is rough for block pair allocations This required quite a bit of rewiring of internal block allocations. I knew this would eventually need to be (re)implemented, but the jump from infallible to fallible progs everywhere was still quite involved: - lfs_alloc no longer returns LFS_ERR_CORRUPT if erase fails, instead it will keep searching for a block where an erase "sticks" or return LFS_ERR_NOENT. This simplifies above layers. This actually turned out to be required since the lookahead traversal can also return LFS_ERR_CORRUPT... which needs to be treated as a hard error and bail. - In lfsr_btree_commit_ all inner-node compactions needed alloc loops. This really complements B-tree's copy-on-write behavior, but does make lfsr_btree_commit_ a bit of a goto soup... - Same for lfsr_btree_commit/lfsr_bshrub_commit, but fortunately there are nice and self-contained. - lfsr_mdir_alloc__/lfsr_mdir_swap__ needed a bit of an overhaul to be able to handle bad progs. lfsr_mdir_alloc__ now takes a bool `all` parameter to know if it should allocate one or two of the mdir blocks. You could argue it's simpler/cheaper to always allocate two blocks at a time, but this could lead to premature filesystem death on unfortunate bad block patterns. test_badblocks_alternating_* specifically tests for this. Note we still allocate both on relocation, but only on the first commit attempt. This also rearranges things to move the overcompacting logic out of lfsr_mdir_swap__ and into lfsr_mdir_commit_, since we only want to overcompact after trying to program all possible free blocks. - lfsr_file_flush_ now needs to rewrite the entire block of data if a prog fails, even if appending an existing data block. Humorously, this was really easy, since we already align everything to any existing blocks as a part of our crystallization algorithm. Almost too easy... (no new code! only a couple gotos! scary!) Note some of these may be transformable into simpler while loops, but I decided to avoid this and prefer explicit `relocate` gotos because: 1. in some functions these end up deeply nested in existing loops and I was already bitten by a shadowed continue, 2. the "good" path does not loop, with a loop you need an easy to miss break and the intention is less clear, and 3. consistency is good. We are _not_ testing read errors yet. This is because we no longer read back progs and the relaxed rcache/pcache alignment requirements make this a bit difficult to (re)implement. User feedback also suggests we may want to make this optional... So need to think on how to address this. Some other notes: - Our low-level bd wrappers, lfsr_bd_*__, now log bad ops via LFS_DEBUG. - Overcompaction is now an LFS_WARN. - The pcache is now correctly dropped if we error during flush. - I noticed lfsr_btree_alloc double allocated for new B-trees, it doesn't now, maybe change this function? - Our B-tree tests all stop on LFS_ERR_NOSPC, but this isn't guaranteed since our filesystem isn't in a valid state. We should make sure none of our B-tree tests actually rely on this... Honestly, considering how much new logic was introduced, this really did not impact code cost as much as I thought it would. Probably thanks to the underlying data structures being built to easily discard blocks in the first place: code stack before: 33474 2640 after: 33618 (+0.4%) 2648 (+0.3%) --- lfs.c | 357 +++-- tests/test_badblocks.toml | 3077 +++++++++++++++++++++++++++++++++++++ 2 files changed, 3339 insertions(+), 95 deletions(-) diff --git a/lfs.c b/lfs.c index 16399f3f..ac2f301a 100644 --- a/lfs.c +++ b/lfs.c @@ -48,6 +48,8 @@ static int lfsr_bd_read__(lfs_t *lfs, lfs_block_t block, lfs_size_t off, int err = lfs->cfg->read(lfs->cfg, block, off, buffer, size); LFS_ASSERT(err <= 0); if (err) { + LFS_DEBUG("Bad read 0x%"PRIx32".%"PRIx32" %"PRIu32" (%d)", + block, off, size, err); return err; } @@ -67,6 +69,8 @@ static int lfsr_bd_prog__(lfs_t *lfs, lfs_block_t block, lfs_size_t off, int err = lfs->cfg->prog(lfs->cfg, block, off, buffer, size); LFS_ASSERT(err <= 0); if (err) { + LFS_DEBUG("Bad prog 0x%"PRIx32".%"PRIx32" %"PRIu32" (%d)", + block, off, size, err); return err; } @@ -81,6 +85,8 @@ static int lfsr_bd_erase__(lfs_t *lfs, lfs_block_t block) { int err = lfs->cfg->erase(lfs->cfg, block); LFS_ASSERT(err <= 0); if (err) { + LFS_DEBUG("Bad erase 0x%"PRIx32" (%d)", + block, err); return err; } @@ -92,6 +98,7 @@ static int lfsr_bd_sync__(lfs_t *lfs) { int err = lfs->cfg->sync(lfs->cfg); LFS_ASSERT(err <= 0); if (err) { + LFS_DEBUG("Bad sync (%d)", err); return err; } @@ -304,6 +311,9 @@ static int lfsr_bd_flush(lfs_t *lfs, uint32_t *cksum_) { lfs->pcache.off, lfs->pcache.buffer, aligned_size, cksum_); if (err) { + // if an error occurs we really don't want to flush our + // cache again + lfsr_bd_droppcache(lfs); return err; } @@ -4027,7 +4037,11 @@ static int lfsr_data_readbtree(lfs_t *lfs, lfsr_data_t *data, // core btree operations static int lfsr_btree_alloc(lfs_t *lfs, lfsr_btree_t *btree) { - return lfsr_rbyd_alloc(lfs, btree); + (void)lfs; + // TODO do we need this function then? + // allocate lazily + *btree = (lfsr_btree_t){.trunk=0, .weight=0}; + return 0; } static int lfsr_btree_lookupnext_(lfs_t *lfs, const lfsr_btree_t *btree, @@ -4286,8 +4300,7 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, int err = lfsr_rbyd_appendattrs(lfs, &rbyd_, rid, -1, -1, attrs, attr_count); if (err) { - // TODO wait should we also move if there is corruption here? - if (err == LFS_ERR_RANGE) { + if (err == LFS_ERR_RANGE || err == LFS_ERR_CORRUPT) { goto compact; } return err; @@ -4295,54 +4308,13 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, err = lfsr_rbyd_appendcksum(lfs, &rbyd_); if (err) { - if (err == LFS_ERR_RANGE) { + if (err == LFS_ERR_RANGE || err == LFS_ERR_CORRUPT) { goto compact; } - // TODO wait should we also move if there is corruption here? return err; } - finalize:; - // done? - if (!lfsr_rbyd_trunk(&parent)) { - LFS_ASSERT(bid == 0); - *btree = rbyd_; - *attr_count_ = 0; - return 0; - } - - // is our parent the root and is the root degenerate? - if (rbyd.weight == btree->weight) { - // collapse the root, decreasing the height of the tree - *btree = rbyd_; - *attr_count_ = 0; - return 0; - } - - // prepare commit to parent, tail recursing upwards - // - // note that since we defer merges to compaction time, we can - // end up removing an rbyd here - attr_count = 0; - bid -= pid - (rbyd.weight-1); - if (rbyd_.weight == 0) { - scratch->attrs[attr_count++] = LFSR_ATTR( - LFSR_TAG_RM, -rbyd.weight, LFSR_DATA_NULL()); - } else { - scratch->attrs[attr_count++] = LFSR_ATTR( - LFSR_TAG_BRANCH, 0, - LFSR_DATA_BRANCH_(&rbyd_, scratch->buf)); - if (rbyd_.weight != rbyd.weight) { - scratch->attrs[attr_count++] = LFSR_ATTR( - LFSR_TAG_GROW, -rbyd.weight + rbyd_.weight, - LFSR_DATA_NULL()); - } - } - attrs = scratch->attrs; - - rbyd = parent; - rid = pid; - continue; + goto recurse; compact:; // estimate our compacted size @@ -4471,6 +4443,7 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, } } + compact_relocate:; // allocate a new rbyd err = lfsr_rbyd_alloc(lfs, &rbyd_); if (err) { @@ -4481,6 +4454,10 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, err = lfsr_rbyd_compact(lfs, &rbyd_, &rbyd, -1, -1); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto compact_relocate; + } return err; } @@ -4490,6 +4467,10 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, attrs, attr_count); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto compact_relocate; + } return err; } @@ -4497,32 +4478,35 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, err = lfsr_rbyd_appendcksum(lfs, &rbyd_); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto compact_relocate; + } return err; } - goto finalize; + goto recurse; split:; // we should have something to split here LFS_ASSERT(split_rid > 0 && split_rid < (lfsr_srid_t)rbyd.weight); + split_relocate_l:; // allocate a new rbyd err = lfsr_rbyd_alloc(lfs, &rbyd_); if (err) { return err; } - // allocate a sibling - err = lfsr_rbyd_alloc(lfs, &sibling); - if (err) { - return err; - } - // copy over tags < split_rid err = lfsr_rbyd_compact(lfs, &rbyd_, &rbyd, -1, split_rid); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto split_relocate_l; + } return err; } @@ -4534,6 +4518,10 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, attrs, attr_count); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto split_relocate_l; + } return err; } @@ -4541,13 +4529,28 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, err = lfsr_rbyd_appendcksum(lfs, &rbyd_); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto split_relocate_l; + } return err; } - + + split_relocate_r:; + // allocate a sibling + err = lfsr_rbyd_alloc(lfs, &sibling); + if (err) { + return err; + } + // copy over tags >= split_rid err = lfsr_rbyd_compact(lfs, &sibling, &rbyd, split_rid, -1); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto split_relocate_r; + } return err; } @@ -4559,13 +4562,21 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, attrs, attr_count); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto split_relocate_r; + } return err; } - + // finalize commit err = lfsr_rbyd_appendcksum(lfs, &sibling); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto split_relocate_r; + } return err; } @@ -4575,7 +4586,7 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, if (rbyd_.weight == 0) { rbyd_ = sibling; } - goto finalize; + goto recurse; } // lookup first name in sibling to use as the split name @@ -4642,6 +4653,7 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, continue; merge:; + merge_relocate:; // allocate a new rbyd err = lfsr_rbyd_alloc(lfs, &rbyd_); if (err) { @@ -4652,18 +4664,30 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, err = lfsr_rbyd_appendcompactrbyd(lfs, &rbyd_, &rbyd, -1, -1); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto merge_relocate; + } return err; } err = lfsr_rbyd_appendcompactrbyd(lfs, &rbyd_, &sibling, -1, -1); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto merge_relocate; + } return err; } err = lfsr_rbyd_appendcompaction(lfs, &rbyd_, 0); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto merge_relocate; + } return err; } @@ -4672,6 +4696,11 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, err = lfsr_rbyd_appendattrs(lfs, &rbyd_, rid, -1, -1, attrs, attr_count); if (err) { + LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto merge_relocate; + } return err; } @@ -4679,6 +4708,10 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, err = lfsr_rbyd_appendcksum(lfs, &rbyd_); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto merge_relocate; + } return err; } @@ -4711,6 +4744,48 @@ static int lfsr_btree_commit_(lfs_t *lfs, lfsr_btree_t *btree, rbyd = parent; rid = pid + sibling.weight; continue; + + recurse:; + // done? + if (!lfsr_rbyd_trunk(&parent)) { + LFS_ASSERT(bid == 0); + *btree = rbyd_; + *attr_count_ = 0; + return 0; + } + + // is our parent the root and is the root degenerate? + if (rbyd.weight == btree->weight) { + // collapse the root, decreasing the height of the tree + *btree = rbyd_; + *attr_count_ = 0; + return 0; + } + + // prepare commit to parent, tail recursing upwards + // + // note that since we defer merges to compaction time, we can + // end up removing an rbyd here + attr_count = 0; + bid -= pid - (rbyd.weight-1); + if (rbyd_.weight == 0) { + scratch->attrs[attr_count++] = LFSR_ATTR( + LFSR_TAG_RM, -rbyd.weight, LFSR_DATA_NULL()); + } else { + scratch->attrs[attr_count++] = LFSR_ATTR( + LFSR_TAG_BRANCH, 0, + LFSR_DATA_BRANCH_(&rbyd_, scratch->buf)); + if (rbyd_.weight != rbyd.weight) { + scratch->attrs[attr_count++] = LFSR_ATTR( + LFSR_TAG_GROW, -rbyd.weight + rbyd_.weight, + LFSR_DATA_NULL()); + } + } + attrs = scratch->attrs; + + rbyd = parent; + rid = pid; + continue; } } @@ -4729,6 +4804,7 @@ static int lfsr_btree_commit(lfs_t *lfs, lfsr_btree_t *btree, if (err == LFS_ERR_RANGE) { LFS_ASSERT(attr_count > 0); + relocate:; lfsr_rbyd_t rbyd; err = lfsr_rbyd_alloc(lfs, &rbyd); if (err) { @@ -4738,6 +4814,10 @@ static int lfsr_btree_commit(lfs_t *lfs, lfsr_btree_t *btree, err = lfsr_rbyd_commit(lfs, &rbyd, bid, attrs, attr_count); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } return err; } @@ -5865,23 +5945,19 @@ static int lfsr_mtree_seek(lfs_t *lfs, const lfsr_mtree_t *mtree, // making lfsr_mdir_commit quite involved and a bit of a mess. // low-level mdir operations needed by lfsr_mdir_commit -static int lfsr_mdir_alloc__(lfs_t *lfs, lfsr_mdir_t *mdir, lfsr_smid_t mid) { +static int lfsr_mdir_alloc__(lfs_t *lfs, lfsr_mdir_t *mdir, + lfsr_smid_t mid, bool all) { // assign the mid mdir->mid = mid; - // allocate two blocks - for (int i = 0; i < 2; i++) { - int err = lfs_alloc(lfs, &mdir->rbyd.blocks[i], false); + if (all) { + // allocate one block without an erase + int err = lfs_alloc(lfs, &mdir->rbyd.blocks[1], false); if (err) { return err; } } - mdir->rbyd.weight = 0; - mdir->rbyd.trunk = 0; - mdir->rbyd.eoff = 0; - mdir->rbyd.cksum = 0; - // read the new revision count // // we use whatever is on-disk to avoid needing to rewrite the @@ -5894,19 +5970,27 @@ static int lfsr_mdir_alloc__(lfs_t *lfs, lfsr_mdir_t *mdir, lfsr_smid_t mid) { } // note we allow corrupt errors here, as long as they are consistent rev = (err != LFS_ERR_CORRUPT) ? lfs_fromle32_(&rev) : 0; - // reset recycle bits in revision count and increment rev = lfsr_rev_init(lfs, rev); - // erase, preparing for compact - err = lfsr_bd_erase(lfs, mdir->rbyd.blocks[0]); +relocate:; + // allocate another block with an erase + err = lfs_alloc(lfs, &mdir->rbyd.blocks[0], true); if (err) { return err; } + mdir->rbyd.weight = 0; + mdir->rbyd.trunk = 0; + mdir->rbyd.eoff = 0; + mdir->rbyd.cksum = 0; // write our revision count err = lfsr_rbyd_appendrev(lfs, &mdir->rbyd, rev); if (err) { + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } return err; } @@ -5927,21 +6011,12 @@ static int lfsr_mdir_swap__(lfs_t *lfs, lfsr_mdir_t *mdir_, } // note we allow corrupt errors here, as long as they are consistent rev = (err != LFS_ERR_CORRUPT) ? lfs_fromle32_(&rev) : 0; + // increment our revision count + rev = lfsr_rev_inc(lfs, rev); // decide if we need to relocate if (!force && lfsr_rev_needsrelocation(lfs, rev)) { - // alloc a new mdir - err = lfsr_mdir_alloc__(lfs, mdir_, mdir->mid); - if (err != LFS_ERR_NOSPC) { - return err; - } - - // no more blocks? wear-leveling falls apart here, but - // we can not relocate - LFS_WARN("Overcompacting mdir %"PRId32" " - "0x{%"PRIx32",%"PRIx32"}", - mdir->mid >> lfs->mdir_bits, - mdir->rbyd.blocks[0], mdir->rbyd.blocks[1]); + return LFS_ERR_NOSPC; } // swap our blocks @@ -5959,7 +6034,7 @@ static int lfsr_mdir_swap__(lfs_t *lfs, lfsr_mdir_t *mdir_, } // increment our revision count and write it to our rbyd - err = lfsr_rbyd_appendrev(lfs, &mdir_->rbyd, lfsr_rev_inc(lfs, rev)); + err = lfsr_rbyd_appendrev(lfs, &mdir_->rbyd, rev); if (err) { return err; } @@ -6505,7 +6580,7 @@ static int lfsr_mdir_commit_(lfs_t *lfs, lfsr_mdir_t *mdir, int err = lfsr_mdir_commit__(lfs, mdir, start_rid, end_rid, mid, attrs, attr_count); if (err) { - if (err == LFS_ERR_RANGE) { + if (err == LFS_ERR_RANGE || err == LFS_ERR_CORRUPT) { goto compact; } return err; @@ -6531,14 +6606,46 @@ compact:; // swap blocks, increment revision count lfsr_mdir_t mdir_; err = lfsr_mdir_swap__(lfs, &mdir_, mdir, false); - if (err) { + if (err && err != LFS_ERR_NOSPC + && err != LFS_ERR_CORRUPT) { return err; } + bool overcompactable = (err != LFS_ERR_CORRUPT); + bool all = true; +relocate:; + // relocate? bad prog? ok, try allocating a new mdir + if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { + err = lfsr_mdir_alloc__(lfs, &mdir_, mdir->mid, all); + if (err && !(err == LFS_ERR_NOSPC && overcompactable)) { + return err; + } + all = false; + + // no more blocks? wear-leveling falls apart here, but we can try + // without relocating + if (err == LFS_ERR_NOSPC) { + LFS_WARN("Overcompacting mdir %"PRId32" " + "0x{%"PRIx32",%"PRIx32"}", + mdir->mid >> lfs->mdir_bits, + mdir->rbyd.blocks[0], mdir->rbyd.blocks[1]); + overcompactable = false; + + err = lfsr_mdir_swap__(lfs, &mdir_, mdir, true); + if (err) { + return err; + } + } + } + // compact our mdir err = lfsr_mdir_compact__(lfs, &mdir_, mdir, start_rid, end_rid); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } return err; } @@ -6553,6 +6660,10 @@ compact:; mid, attrs, attr_count); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } return err; } @@ -6670,8 +6781,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfsr_srid_t split_rid; int err = lfsr_mdir_commit_(lfs, &mdir_[0], -1, -1, &split_rid, mdir->mid, attrs, attr_count); - if (err - && err != LFS_ERR_RANGE + if (err && err != LFS_ERR_RANGE && err != LFS_ERR_NOENT) { goto failed; } @@ -6707,12 +6817,15 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, // shrubs are staged correctly bool left = lfsr_mid_rid(lfs, mdir->mid) < split_rid; + bool all = true; + split_relocate:; // alloc and compact into new mdirs err = lfsr_mdir_alloc__(lfs, &mdir_[i^left], - lfs_smax32(mdir->mid, 0)); + lfs_smax32(mdir->mid, 0), all); if (err) { goto failed; } + all = false; err = lfsr_mdir_compact__(lfs, &mdir_[i^left], mdir, @@ -6720,6 +6833,10 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, ((i^left) == 0) ? split_rid : -1); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto split_relocate; + } goto failed; } @@ -6729,6 +6846,10 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, mdir->mid, attrs, attr_count); if (err && err != LFS_ERR_NOENT) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto split_relocate; + } goto failed; } } @@ -6758,7 +6879,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, "0x{%"PRIx32",%"PRIx32"}", mdir_[1].mid >> lfs->mdir_bits, mdir_[1].rbyd.blocks[0], mdir_[1].rbyd.blocks[1]); - goto drop; + goto dropped; // one sibling reduced to zero } else if (mdir_[0].rbyd.weight == 0) { @@ -6767,7 +6888,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, mdir_[0].mid >> lfs->mdir_bits, mdir_[0].rbyd.blocks[0], mdir_[0].rbyd.blocks[1]); mdir_[0].rbyd = mdir_[1].rbyd; - goto relocate; + goto relocated; // other sibling reduced to zero } else if (mdir_[1].rbyd.weight == 0) { @@ -6775,7 +6896,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, "0x{%"PRIx32",%"PRIx32"}", mdir_[1].mid >> lfs->mdir_bits, mdir_[1].rbyd.blocks[0], mdir_[1].rbyd.blocks[1]); - goto relocate; + goto relocated; } // no siblings reduced to zero, update our mtree @@ -6859,7 +6980,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, goto failed; } - drop:; + dropped:; mdelta = -(1 << lfs->mdir_bits); // we should never drop a direct mdir, because we always have our @@ -6888,7 +7009,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, mdir->rbyd.blocks[0], mdir->rbyd.blocks[1], mdir_[0].rbyd.blocks[0], mdir_[0].rbyd.blocks[1]); - relocate:; + relocated:; // new mtree? if (lfsr_mtree_ismptr(&lfs->mtree)) { mtree_ = LFSR_MTREE_MPTR( @@ -8612,10 +8733,15 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase) { // found a free block *block = (lfs->lookahead.start + lfs->lookahead.next) % lfs->cfg->block_count; + // 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; } } @@ -8629,11 +8755,14 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase) { if (lfs->lookahead.next >= lfs->lookahead.size || !(lfs->lookahead.buffer[lfs->lookahead.next / 8] & (1 << (lfs->lookahead.next % 8)))) { - return 0; + break; } } + + return 0; } + next:; lfs->lookahead.next += 1; lfs->lookahead.ckpoint -= 1; } @@ -10334,7 +10463,7 @@ static int lfsr_bshrub_commit(lfs_t *lfs, lfsr_file_t *file, // if ((lfs_size_t)estimate > lfs->cfg->shrub_size/2 || estimate + commit_estimate > lfs->cfg->shrub_size) { - goto evict; + goto relocate; } } @@ -10372,7 +10501,7 @@ static int lfsr_bshrub_commit(lfs_t *lfs, lfsr_file_t *file, LFS_ASSERT(lfsr_shrub_trunk(&file->bshrub.u.bshrub)); return 0; -evict:; +relocate:; // convert to btree lfsr_rbyd_t rbyd; err = lfsr_rbyd_alloc(lfs, &rbyd); @@ -10386,6 +10515,10 @@ evict:; &file->bshrub.u.btree, -1, -1); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } return err; } } @@ -10393,11 +10526,21 @@ evict:; err = lfsr_rbyd_appendattrs(lfs, &rbyd, bid, -1, -1, attrs, attr_count); if (err) { + LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } return err; } err = lfsr_rbyd_appendcksum(lfs, &rbyd); if (err) { + LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } return err; } @@ -10866,7 +11009,11 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file, } } + relocate:; // allocate a new block + // + // 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; @@ -10877,7 +11024,7 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file, bptr.cksum = 0; compact:; - // compact data into our new block + // compact data into our block // // eagerly merge any right neighbors we see unless that would // put us over our block size @@ -10908,6 +11055,10 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file, &bptr.cksum); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } return err; } @@ -10964,6 +11115,10 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file, &bptr.cksum); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } return err; } @@ -10982,6 +11137,10 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file, &bptr.cksum); if (err) { LFS_ASSERT(err != LFS_ERR_RANGE); + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } return err; } @@ -10999,6 +11158,10 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file, d, &bptr.cksum); if (err) { + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } return err; } @@ -11009,6 +11172,10 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file, // finalize our write err = lfsr_bd_flush(lfs, &bptr.cksum); if (err) { + // bad prog? try another block + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } return err; } diff --git a/tests/test_badblocks.toml b/tests/test_badblocks.toml index 2859da5e..99e45060 100644 --- a/tests/test_badblocks.toml +++ b/tests/test_badblocks.toml @@ -1,3 +1,3080 @@ +# Bad-block related tests +after = ['test_dirs', 'test_files', 'test_fwrite', 'test_forphans'] + + +# B-tree's ridiculous branching factor is great for performance, but it makes +# them a bit of a pain to test, here we test them explicitly +[cases.test_badblocks_one_btree] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK = -1 +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +defines.SEED = 42 +# maximize lookahead buffer to avoid alloc scans +defines.LOOKAHEAD_SIZE = 'lfs_alignup(BLOCK_COUNT / 8, 8)' +in = 'lfs.c' +code = ''' + // test all possible bad blocks + for (lfs_size_t i = 0; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + // mark our badblock as bad + lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + printf("--- badblock: 0x%x ---\n", badblock); + + // test creating a btree + lfs_t lfs; + lfs_init(&lfs, CFG) => 0; + // create free lookahead + memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); + lfs.lookahead.start = 0; + lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, + CFG->block_count); + lfs.lookahead.next = 0; + lfs_alloc_ckpoint(&lfs); + + // create a btree + lfsr_btree_t btree; + lfsr_btree_alloc(&lfs, &btree) => 0; + + // set up a simulation to compare against + char *sim = malloc(N); + lfs_size_t sim_size = 0; + memset(sim, 0, N); + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose a pseudo-random bid + lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1); + + // add to btree + lfsr_btree_commit(&lfs, &btree, bid, LFSR_ATTRS( + LFSR_ATTR( + LFSR_TAG_DATA, +1, + LFSR_DATA_BUF(&(uint8_t){'a'+(i % 26)}, 1)))) => 0; + + // add to sim + memmove(&sim[bid+1], &sim[bid], sim_size-bid); + sim[bid] = 'a'+(i % 26); + sim_size += 1; + } + + // check that btree matches sim + printf("expd: ["); + bool first = true; + for (lfs_size_t i = 0; i < sim_size; i++) { + if (!first) { + printf(", "); + } + first = false; + printf("%c", sim[i]); + } + printf("]\n"); + printf("btree: w%d 0x%x.%x\n", + btree.weight, + btree.blocks[0], + btree.trunk); + assert(btree.weight == sim_size); + + uint8_t buffer[4]; + lfsr_tag_t tag_; + lfs_size_t weight_; + lfsr_data_t data_; + for (lfs_size_t i = 0; i < sim_size; i++) { + lfsr_btree_lookup(&lfs, &btree, i, + &tag_, &weight_, &data_) => 0; + lfsr_data_read(&lfs, &data_, buffer, 4) => 1; + assert(tag_ == LFSR_TAG_DATA); + assert(weight_ == 1); + assert(memcmp(buffer, &sim[i], 1) == 0); + } + + // and no extra elements + lfsr_btree_lookup(&lfs, &btree, sim_size, + &tag_, &weight_, &data_) => LFS_ERR_NOENT; + + // clean up sim + free(sim); + lfs_deinit(&lfs) => 0; + + // reset badblock + lfs_emubd_setwear(CFG, badblock, 0) => 0; + } +''' + +# this should cause cascading failures +[cases.test_badblocks_region_btree] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +defines.SEED = 42 +# maximize lookahead buffer to avoid alloc scans +defines.LOOKAHEAD_SIZE = 'lfs_alignup(BLOCK_COUNT / 8, 8)' +in = 'lfs.c' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + } else { + lfs_emubd_setwear(CFG, i + BLOCK_COUNT/2, 0xffffffff) => 0; + } + } + + // test creating a btree + lfs_t lfs; + lfs_init(&lfs, CFG) => 0; + // create free lookahead + memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); + lfs.lookahead.start = 0; + lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, + CFG->block_count); + lfs.lookahead.next = 0; + lfs_alloc_ckpoint(&lfs); + + // create a btree + lfsr_btree_t btree; + lfsr_btree_alloc(&lfs, &btree) => 0; + + // set up a simulation to compare against + char *sim = malloc(N); + lfs_size_t sim_size = 0; + memset(sim, 0, N); + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose a pseudo-random bid + lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1); + + // add to btree + lfsr_btree_commit(&lfs, &btree, bid, LFSR_ATTRS( + LFSR_ATTR( + LFSR_TAG_DATA, +1, + LFSR_DATA_BUF(&(uint8_t){'a'+(i % 26)}, 1)))) => 0; + + // add to sim + memmove(&sim[bid+1], &sim[bid], sim_size-bid); + sim[bid] = 'a'+(i % 26); + sim_size += 1; + } + + // check that btree matches sim + printf("expd: ["); + bool first = true; + for (lfs_size_t i = 0; i < sim_size; i++) { + if (!first) { + printf(", "); + } + first = false; + printf("%c", sim[i]); + } + printf("]\n"); + printf("btree: w%d 0x%x.%x\n", + btree.weight, + btree.blocks[0], + btree.trunk); + assert(btree.weight == sim_size); + + uint8_t buffer[4]; + lfsr_tag_t tag_; + lfs_size_t weight_; + lfsr_data_t data_; + for (lfs_size_t i = 0; i < sim_size; i++) { + lfsr_btree_lookup(&lfs, &btree, i, + &tag_, &weight_, &data_) => 0; + lfsr_data_read(&lfs, &data_, buffer, 4) => 1; + assert(tag_ == LFSR_TAG_DATA); + assert(weight_ == 1); + assert(memcmp(buffer, &sim[i], 1) == 0); + } + + // and no extra elements + lfsr_btree_lookup(&lfs, &btree, sim_size, + &tag_, &weight_, &data_) => LFS_ERR_NOENT; + + // clean up sim + free(sim); + lfs_deinit(&lfs) => 0; +''' + +# this should cause cascading failures +[cases.test_badblocks_alternating_btree] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +defines.SEED = 42 +# maximize lookahead buffer to avoid alloc scans +defines.LOOKAHEAD_SIZE = 'lfs_alignup(BLOCK_COUNT / 8, 8)' +in = 'lfs.c' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + } else { + lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + } + } + + // test creating a btree + lfs_t lfs; + lfs_init(&lfs, CFG) => 0; + // create free lookahead + memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); + lfs.lookahead.start = 0; + lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, + CFG->block_count); + lfs.lookahead.next = 0; + lfs_alloc_ckpoint(&lfs); + + // create a btree + lfsr_btree_t btree; + lfsr_btree_alloc(&lfs, &btree) => 0; + + // set up a simulation to compare against + char *sim = malloc(N); + lfs_size_t sim_size = 0; + memset(sim, 0, N); + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose a pseudo-random bid + lfs_size_t bid = TEST_PRNG(&prng) % (sim_size+1); + + // add to btree + lfsr_btree_commit(&lfs, &btree, bid, LFSR_ATTRS( + LFSR_ATTR( + LFSR_TAG_DATA, +1, + LFSR_DATA_BUF(&(uint8_t){'a'+(i % 26)}, 1)))) => 0; + + // add to sim + memmove(&sim[bid+1], &sim[bid], sim_size-bid); + sim[bid] = 'a'+(i % 26); + sim_size += 1; + } + + // check that btree matches sim + printf("expd: ["); + bool first = true; + for (lfs_size_t i = 0; i < sim_size; i++) { + if (!first) { + printf(", "); + } + first = false; + printf("%c", sim[i]); + } + printf("]\n"); + printf("btree: w%d 0x%x.%x\n", + btree.weight, + btree.blocks[0], + btree.trunk); + assert(btree.weight == sim_size); + + uint8_t buffer[4]; + lfsr_tag_t tag_; + lfs_size_t weight_; + lfsr_data_t data_; + for (lfs_size_t i = 0; i < sim_size; i++) { + lfsr_btree_lookup(&lfs, &btree, i, + &tag_, &weight_, &data_) => 0; + lfsr_data_read(&lfs, &data_, buffer, 4) => 1; + assert(tag_ == LFSR_TAG_DATA); + assert(weight_ == 1); + assert(memcmp(buffer, &sim[i], 1) == 0); + } + + // and no extra elements + lfsr_btree_lookup(&lfs, &btree, sim_size, + &tag_, &weight_, &data_) => LFS_ERR_NOENT; + + // clean up sim + free(sim); + lfs_deinit(&lfs) => 0; +''' + + + +# similar tests, but now over a real filesystem +[cases.test_badblocks_one_dirs] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK = -1 +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +# do more ops than dirs to encourage rename collisions +defines.DENSITY = 2 +defines.REMOUNT = [false, true] +defines.SEED = 42 +code = ''' + // test all possible bad blocks + for (lfs_size_t i = 2; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + // mark our badblock as bad + lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + printf("--- badblock: 0x%x ---\n", badblock); + + // test creating directories + 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; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY); + + // 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); + } + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 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; + 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 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; + + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + 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; + } + + // clean up sim/lfs + free(sim); + lfsr_unmount(&lfs) => 0; + + // reset badblock + lfs_emubd_setwear(CFG, badblock, 0) => 0; + } +''' + +# this should cause cascading failures +[cases.test_badblocks_region_dirs] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +# do more ops than dirs to encourage rename collisions +defines.DENSITY = 2 +defines.REMOUNT = [false, true] +defines.SEED = 42 +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (i >= 2) { + lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + } + } else { + if (i+BLOCK_COUNT/2 >= 2) { + lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + } + } + } + + // test creating directories + 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; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY); + + // 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); + } + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 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; + 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 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; + + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + 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; + } + + // clean up sim/lfs + free(sim); + lfsr_unmount(&lfs) => 0; +''' + +# this should cause cascading failures +[cases.test_badblocks_alternating_dirs] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +# do more ops than dirs to encourage rename collisions +defines.DENSITY = 2 +defines.REMOUNT = [false, true] +defines.SEED = 42 +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (2*i+0 >= 2) { + lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + } + } else { + if (2*i+1 >= 2) { + lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + } + } + } + + // test creating directories + 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; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY); + + // 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); + } + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 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; + 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 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; + + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + 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; + } + + // clean up sim/lfs + free(sim); + lfsr_unmount(&lfs) => 0; +''' + + +# with files +[cases.test_badblocks_one_files] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK = -1 +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.N = [1, 2, 4, 8, 16, 32, 64] +# do more ops than dirs to encourage rename collisions +defines.DENSITY = 2 +defines.SIZE = [ + '0', + 'FBUFFER_SIZE/2', + '2*FBUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.REMOUNT = [false, true] +defines.SEED = 42 +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + // test all possible bad blocks + for (lfs_size_t i = 2; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + // mark our badblock as bad + lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + printf("--- badblock: 0x%x ---\n", badblock); + + // test creating 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; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY); + // 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; + } + + // 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; + + // reset badblock + lfs_emubd_setwear(CFG, badblock, 0) => 0; + } +''' + +# this should cause cascading failures +[cases.test_badblocks_region_files] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +# do more ops than dirs to encourage rename collisions +defines.DENSITY = 2 +defines.SIZE = [ + '0', + 'FBUFFER_SIZE/2', + '2*FBUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.REMOUNT = [false, true] +defines.SEED = 42 +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (i >= 2) { + lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + } + } else { + if (i+BLOCK_COUNT/2 >= 2) { + lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + } + } + } + + // test creating 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; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY); + // 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; + } + + // 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; +''' + +# this should cause cascading failures +[cases.test_badblocks_alternating_files] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +# do more ops than dirs to encourage rename collisions +defines.DENSITY = 2 +defines.SIZE = [ + '0', + 'FBUFFER_SIZE/2', + '2*FBUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.REMOUNT = [false, true] +defines.SEED = 42 +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (2*i+0 >= 2) { + lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + } + } else { + if (2*i+1 >= 2) { + lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + } + } + } + + // test creating 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; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < N; i++) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % ((N+DENSITY-1) / DENSITY); + // 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; + } + + // 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; +''' + + +# with more complex file writes +[cases.test_badblocks_one_fwrite_fuzz] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK = -1 +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.N = 20 +defines.SEED = 42 +defines.SIZE = [ + 'FBUFFER_SIZE/2', + '2*FBUFFER_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.REMOUNT = [false, true] +if = [ + 'CHUNK <= SIZE', + # this just saves testing time + 'SIZE <= 4*1024*FRAGMENT_SIZE', +] +code = ''' + // test all possible bad blocks + for (lfs_size_t i = 2; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + // mark our badblock as bad + lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + printf("--- badblock: 0x%x ---\n", badblock); + + // 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; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + + for (lfs_size_t i = 0; i < N; 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_min32( + TEST_PRNG(&prng) % CHUNK, + SIZE - off); + + // update sim + for (lfs_size_t j = 0; j < chunk; j++) { + sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); + } + if (chunk != 0) { + size = lfs_max32(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; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + } + lfsr_file_close(&lfs, &file) => 0; + + // 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; + + // reset badblock + lfs_emubd_setwear(CFG, badblock, 0) => 0; + } +''' + +# this should cause cascading failures +[cases.test_badblocks_region_fwrite_fuzz] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.MIRROR = [false, true] +defines.N = 20 +defines.SEED = 42 +defines.SIZE = [ + 'FBUFFER_SIZE/2', + '2*FBUFFER_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.REMOUNT = [false, true] +if = [ + 'CHUNK <= SIZE', + # this just saves testing time + 'SIZE <= 4*1024*FRAGMENT_SIZE', +] +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (i >= 2) { + lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + } + } else { + if (i+BLOCK_COUNT/2 >= 2) { + lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + } + } + } + + // 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; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + + for (lfs_size_t i = 0; i < N; 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_min32( + TEST_PRNG(&prng) % CHUNK, + SIZE - off); + + // update sim + for (lfs_size_t j = 0; j < chunk; j++) { + sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); + } + if (chunk != 0) { + size = lfs_max32(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; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + } + lfsr_file_close(&lfs, &file) => 0; + + // 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; +''' + +# this should cause cascading failures +[cases.test_badblocks_alternating_fwrite_fuzz] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.MIRROR = [false, true] +defines.N = 20 +defines.SEED = 42 +defines.SIZE = [ + 'FBUFFER_SIZE/2', + '2*FBUFFER_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.REMOUNT = [false, true] +if = [ + 'CHUNK <= SIZE', + # this just saves testing time + 'SIZE <= 4*1024*FRAGMENT_SIZE', +] +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (2*i+0 >= 2) { + lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + } + } else { + if (2*i+1 >= 2) { + lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + } + } + } + + // 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; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + + for (lfs_size_t i = 0; i < N; 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_min32( + TEST_PRNG(&prng) % CHUNK, + SIZE - off); + + // update sim + for (lfs_size_t j = 0; j < chunk; j++) { + sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); + } + if (chunk != 0) { + size = lfs_max32(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; + } + + // remount? + if (REMOUNT) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY) => 0; + } + } + lfsr_file_close(&lfs, &file) => 0; + + // 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; +''' + + +# with orphans, zombies, dirs, etc +[cases.test_badblocks_one_orphanzombiedir_fuzz] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK = -1 +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = 1024 +defines.SIZE = [ + '0', + 'FBUFFER_SIZE/2', + '2*FBUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + // test all possible bad blocks + for (lfs_size_t i = 2; + i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); + i++) { + lfs_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; + // mark our badblock as bad + lfs_emubd_setwear(CFG, badblock, 0xffffffff) => 0; + printf("--- badblock: 0x%x ---\n", badblock); + + // 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; + + 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; + } + } + + // 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; + + // reset badblock + lfs_emubd_setwear(CFG, badblock, 0) => 0; + } +''' + +# this should cause cascading failures +[cases.test_badblocks_region_orphanzombiedir_fuzz] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = 1024 +defines.SIZE = [ + '0', + 'FBUFFER_SIZE/2', + '2*FBUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (i >= 2) { + lfs_emubd_setwear(CFG, i, 0xffffffff) => 0; + } + } else { + if (i+BLOCK_COUNT/2 >= 2) { + lfs_emubd_setwear(CFG, i+BLOCK_COUNT/2, 0xffffffff) => 0; + } + } + } + + // 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; + + 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; + } + } + + // 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; +''' + +# this should cause cascading failures +[cases.test_badblocks_alternating_orphanzombiedir_fuzz] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +defines.MIRROR = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = 1024 +defines.SIZE = [ + '0', + 'FBUFFER_SIZE/2', + '2*FBUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + // test a large region of bad blocks + for (lfs_size_t i = 0; i < BLOCK_COUNT/2; i++) { + // mark our badblock as bad + if (!MIRROR) { + if (2*i+0 >= 2) { + lfs_emubd_setwear(CFG, 2*i+0, 0xffffffff) => 0; + } + } else { + if (2*i+1 >= 2) { + lfs_emubd_setwear(CFG, 2*i+1, 0xffffffff) => 0; + } + } + } + + // 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; + + 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; + } + } + + // 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; +''' + + +# other corner cases + +# test formatting with 0 or 1 bad, this should just error +[cases.test_badblocks_mrootanchor] +defines.ERASE_CYCLES = 0xffffffff +defines.BADBLOCKS = [0x1, 0x2, 0x3] +defines.BADBLOCK_BEHAVIOR = [ + 'LFS_EMUBD_BADBLOCK_PROGERROR', + 'LFS_EMUBD_BADBLOCK_ERASEERROR', +# TODO +# 'LFS_EMUBD_BADBLOCK_READERROR', +# 'LFS_EMUBD_BADBLOCK_PROGNOOP', +# 'LFS_EMUBD_BADBLOCK_ERASENOOP', +] +code = ''' + if (BADBLOCKS & 0x1) { + lfs_emubd_setwear(CFG, 0, 0xffffffff) => 0; + } + if (BADBLOCKS & 0x2) { + lfs_emubd_setwear(CFG, 1, 0xffffffff) => 0; + } + + lfs_t lfs; + lfsr_format(&lfs, CFG) => LFS_ERR_CORRUPT; +''' + + + + + + + + + + + + + + + + + + + + + + + + + ## bad blocks with block cycles should be tested in test_relocations #if = '(int32_t)BLOCK_CYCLES == -1' #