From 9ad59dcfe60df4f337ceedf256158e530dcd83b2 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 11 Jun 2024 16:13:58 -0500 Subject: [PATCH] (Re)implemented lfsr_fs_grow, variable block counts, etc Well this turned into a never-ending can of worms... I guess the good news is our newly added lfsr_grow_incr_* tests are _very_ good at finding post-error-resume bugs. Implementation-wise, this was fairly straightforward thanks to prior work by BrianPugh, kaetemi, and myself: 1. Made block_count pseudo-optional by adding lfs.block_count so we can mutate it based on what we find on-disk. This was done a bit different from the previous implementation, instead of setting block_count=0 to read the block_count from disk, we allow any block_count <= the configured block_count. This matches how we handle name_limit/file_limit/etc, and allows users to mount a filesystem with unknown block_count while asserting an upper bound. 2. Added lfsr_fs_grow, which can grow the filesystem. The is basically the same as the previous implementation except we're a bit more careful with the lookahead buffer. I thought the previous impl might have been broken w.r.t. lookahead buffer, but fortunately it's only broken in a way that makes us think newly available blocks are temporarily in-use. Which is a bit funny. One interesting thing that came out with more aggressive tests is that it's possible to get locked-up in lfsr_fs_preparemutation trying to clean up grms/orphans before we change the filesystem size. Fortunately it turns out we don't _really_ need to call lfsr_fs_preparemutation here. This gets a bit delicate, but means we should always be able to grow a full filesystem. To test this I've added both the simple grow/error tests from the previous version, as well as a set of fuzz tests (a la test_relocations and friends) that incrementally grow the filesystem when encountering LFS_ERR_NOSPC. These have a surprising amount coverage, testing lfsr_fs_grow, lfsr_fs_stat, lfsr_fs_size, and resuming operations after encountering an error. Which also means they found bugs: - lfs_alloc_setinuse was not broken before, because lookahead.start was always a multiple of lookahead_size. But now with lfs_alloc_discard, this invariant may not be true. I've just changed all lookahead.start updates to mod block_count. This adds a bit of code, but is much easier to reason about. While fixing this, I also added an assert to never allocate blocks {0,1} in lfs_alloc. This is a good assert to have, but did require some tweaks to test_btree to avoid these blocks. - We were incorrectly patching grms in lfsr_mdir_commit when mdelta=0. Funnily enough we also proceed to ignore the patched grm most of the time when mdelta=0, so this went unnoticed. - It turns out we're completely ignoring rid=-1 attrs if we split the mroot. Not sure how this was missed. It's a bit important. Note this is still broken. Fixing this requires some rather invasive changes to lfsr_mdir_commit's internal logic that should probably be in another commit... Note again fwrite_fuzz is omitted. Currently the state of data in opened files is undefined after a failed write, so this wouldn't really be testing anything interesting... More features = more code, and all of this bug fixing meant several things contributed to code/stack changes in this commit: code stack before: 33654 2592 +variable block_count: 33646 (-0.0%) 2584 (+0.0%) +lfsr_fs_grow: 33818 (+0.5%) 2584 (-0.3%) +lookahead-start-fix: 33842 (+0.6%) 2584 (-0.3%) +grm-patch-fix (after): 33850 (+0.6%) 2584 (-0.3%) Wild that variable block_count actually saves code/stack. I guess the indirect lfs->cfg->block_count load can get costly... --- lfs.c | 141 +- lfs.h | 13 + tests/test_badblocks.toml | 18 +- tests/test_btree.toml | 300 ++-- tests/test_grow.toml | 3100 +++++++++++++++++++++++++++++++++++++ 5 files changed, 3383 insertions(+), 189 deletions(-) create mode 100644 tests/test_grow.toml diff --git a/lfs.c b/lfs.c index d3876e29..5d33394c 100644 --- a/lfs.c +++ b/lfs.c @@ -38,7 +38,7 @@ typedef int lfs_scmp_t; static int lfsr_bd_read__(lfs_t *lfs, lfs_block_t block, lfs_size_t off, void *buffer, lfs_size_t size) { // must be in-bounds - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(block < lfs->block_count); LFS_ASSERT(off+size <= lfs->cfg->block_size); // must be aligned LFS_ASSERT(off % lfs->cfg->read_size == 0); @@ -59,7 +59,7 @@ static int lfsr_bd_read__(lfs_t *lfs, lfs_block_t block, lfs_size_t off, static int lfsr_bd_prog__(lfs_t *lfs, lfs_block_t block, lfs_size_t off, const void *buffer, lfs_size_t size) { // must be in-bounds - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(block < lfs->block_count); LFS_ASSERT(off+size <= lfs->cfg->block_size); // must be aligned LFS_ASSERT(off % lfs->cfg->prog_size == 0); @@ -79,7 +79,7 @@ static int lfsr_bd_prog__(lfs_t *lfs, lfs_block_t block, lfs_size_t off, static int lfsr_bd_erase__(lfs_t *lfs, lfs_block_t block) { // must be in-bounds - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(block < lfs->block_count); // bd erase int err = lfs->cfg->erase(lfs->cfg, block); @@ -126,7 +126,7 @@ static int lfsr_bd_readnext(lfs_t *lfs, lfs_size_t size, const uint8_t **buffer_, lfs_size_t *size_) { // must be in-bounds - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(block < lfs->block_count); LFS_ASSERT(off+size <= lfs->cfg->block_size); lfs_size_t hint_ = lfs_max(hint, size); // make sure hint >= size @@ -196,7 +196,7 @@ static int lfsr_bd_read(lfs_t *lfs, lfs_block_t block, lfs_size_t off, lfs_size_t hint, void *buffer, lfs_size_t size) { // must be in-bounds - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(block < lfs->block_count); LFS_ASSERT(off+size <= lfs->cfg->block_size); lfs_size_t off_ = off; @@ -349,7 +349,7 @@ static int lfsr_bd_prog_(lfs_t *lfs, lfs_block_t block, lfs_size_t off, static int lfsr_bd_flush(lfs_t *lfs, uint32_t *cksum_, bool align) { if (lfs->pcache.size != 0) { // must be in-bounds - LFS_ASSERT(lfs->pcache.block < lfs->cfg->block_count); + LFS_ASSERT(lfs->pcache.block < lfs->block_count); // must be aligned LFS_ASSERT(lfs->pcache.off % lfs->cfg->prog_size == 0); lfs_size_t size = lfs_alignup(lfs->pcache.size, lfs->cfg->prog_size); @@ -378,7 +378,7 @@ static int lfsr_bd_prognext(lfs_t *lfs, lfs_block_t block, lfs_size_t off, uint8_t **buffer_, lfs_size_t *size_, uint32_t *cksum_, bool align) { // must be in-bounds - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(block < lfs->block_count); LFS_ASSERT(off+size <= lfs->cfg->block_size); while (true) { @@ -434,7 +434,7 @@ static int lfsr_bd_prog(lfs_t *lfs, lfs_block_t block, lfs_size_t off, const void *buffer, lfs_size_t size, uint32_t *cksum_, bool align) { // must be in-bounds - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(block < lfs->block_count); LFS_ASSERT(off+size <= lfs->cfg->block_size); lfs_size_t off_ = off; @@ -519,7 +519,7 @@ static int lfsr_bd_sync(lfs_t *lfs) { static int lfsr_bd_erase(lfs_t *lfs, lfs_block_t block) { // must be in-bounds - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(block < lfs->block_count); // invalidate any relevant caches if (lfs->pcache.block == block) { @@ -540,7 +540,7 @@ static int lfsr_bd_cksum(lfs_t *lfs, lfs_size_t size, uint32_t *cksum_) { // must be in-bounds - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(block < lfs->block_count); LFS_ASSERT(off+size <= lfs->cfg->block_size); lfs_size_t off_ = off; @@ -569,7 +569,7 @@ static lfs_scmp_t lfsr_bd_cmp(lfs_t *lfs, lfs_block_t block, lfs_size_t off, lfs_size_t hint, const void *buffer, lfs_size_t size) { // must be in-bounds - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(block < lfs->block_count); LFS_ASSERT(off+size <= lfs->cfg->block_size); lfs_size_t off_ = off; @@ -607,9 +607,9 @@ static int lfsr_bd_cpy(lfs_t *lfs, // we don't really use hint here because we go through our pcache (void)hint; // must be in-bounds - LFS_ASSERT(dst_block < lfs->cfg->block_count); + LFS_ASSERT(dst_block < lfs->block_count); LFS_ASSERT(dst_off+size <= lfs->cfg->block_size); - LFS_ASSERT(src_block < lfs->cfg->block_count); + LFS_ASSERT(src_block < lfs->block_count); LFS_ASSERT(src_off+size <= lfs->cfg->block_size); lfs_size_t dst_off_ = dst_off; @@ -651,7 +651,7 @@ static int lfsr_bd_set(lfs_t *lfs, lfs_block_t block, lfs_size_t off, uint8_t c, lfs_size_t size, uint32_t *cksum_, bool align) { // must be in-bounds - LFS_ASSERT(block < lfs->cfg->block_count); + LFS_ASSERT(block < lfs->block_count); LFS_ASSERT(off+size <= lfs->cfg->block_size); lfs_size_t off_ = off; @@ -7095,8 +7095,9 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, for (int j = 0; j < 2; j++) { if (lfsr_mid_bid(lfs, lfs->grm.mids[j]) == lfsr_mid_bid(lfs, lfs_smax32(mdir->mid, 0))) { - if (lfsr_mid_rid(lfs, lfs->grm.mids[j]) - >= (lfsr_srid_t)mdir_[0].rbyd.weight) { + if (mdelta > 0 + && lfsr_mid_rid(lfs, lfs->grm.mids[j]) + >= (lfsr_srid_t)mdir_[0].rbyd.weight) { lfs->grm.mids[j] += (1 << lfs->mdir_bits) - mdir_[0].rbyd.weight; } @@ -8425,6 +8426,8 @@ static int lfsr_mountmroot(lfs_t *lfs, const lfsr_mdir_t *mroot) { return err; } + // either block_size matches or it doesn't, we don't support variable + // block_sizes if (geometry.block_size != lfs->cfg->block_size) { LFS_ERROR("Incompatible block size %"PRId32" (!= %"PRId32")", geometry.block_size, @@ -8432,13 +8435,16 @@ static int lfsr_mountmroot(lfs_t *lfs, const lfsr_mdir_t *mroot) { return LFS_ERR_NOTSUP; } - if (geometry.block_count != lfs->cfg->block_count) { - LFS_ERROR("Incompatible block count %"PRId32" (!= %"PRId32")", + // on-disk block_count must be <= configured block_count + if (geometry.block_count > lfs->cfg->block_count) { + LFS_ERROR("Incompatible block count %"PRId32" (> %"PRId32")", geometry.block_count, lfs->cfg->block_count); return LFS_ERR_NOTSUP; } + lfs->block_count = geometry.block_count; + // read the name limit lfs_size_t name_limit = 0xff; err = lfsr_mdir_lookup(lfs, mroot, LFSR_TAG_NAMELIMIT, @@ -8648,7 +8654,7 @@ static int lfsr_mountinited(lfs_t *lfs) { // the purpose of this is to avoid bad wear patterns such as always // allocating blocks near the beginning of disk after a power-loss // - lfs->lookahead.start = lfs->seed % lfs->cfg->block_count; + lfs->lookahead.start = lfs->seed % lfs->block_count; // TODO should the consumegdelta above take gstate/gdelta as a parameter? // keep track of the current gstate on disk @@ -8776,7 +8782,7 @@ int lfsr_mount(lfs_t *lfs, const struct lfs_config *cfg) { LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR, lfs->cfg->block_size, - lfs->cfg->block_count, + lfs->block_count, lfs->mroot.rbyd.blocks[0], lfs->mroot.rbyd.blocks[1], lfsr_rbyd_trunk(&lfs->mroot.rbyd), @@ -8804,7 +8810,7 @@ int lfsr_format(lfs_t *lfs, const struct lfs_config *cfg) { LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR, lfs->cfg->block_size, - lfs->cfg->block_count); + lfs->block_count); err = lfsr_formatinited(lfs); if (err) { @@ -8824,13 +8830,22 @@ int lfsr_format(lfs_t *lfs, const struct lfs_config *cfg) { // the filesystem, either in the mtree or in tracked mdirs. After a // checkpoint, the block allocator may realloc any untracked blocks. static void lfs_alloc_ckpoint(lfs_t *lfs) { - lfs->lookahead.ckpoint = lfs->cfg->block_count; + lfs->lookahead.ckpoint = lfs->block_count; +} + +// Discard lookahead state, this is necessary if block_count changes +static void lfs_alloc_discard(lfs_t *lfs) { + lfs->lookahead.start = (lfs->lookahead.start + lfs->lookahead.next) + % lfs->block_count; + lfs->lookahead.next = 0; + lfs->lookahead.size = 0; + lfs->lookahead.ckpoint = 0; } static inline void lfs_alloc_setinuse(lfs_t *lfs, lfs_block_t block) { // translate to lookahead-relative - lfs_block_t rel = ((block + lfs->cfg->block_count) - lfs->lookahead.start) - % lfs->cfg->block_count; + lfs_block_t rel = (block + lfs->block_count - lfs->lookahead.start) + % lfs->block_count; if (rel < lfs->lookahead.size) { // mark as in-use lfs->lookahead.buffer[rel / 8] |= 1 << (rel % 8); @@ -8845,7 +8860,10 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase) { & (1 << (lfs->lookahead.next % 8)))) { // found a free block *block = (lfs->lookahead.start + lfs->lookahead.next) - % lfs->cfg->block_count; + % lfs->block_count; + + // we should never alloc blocks {0,1} + LFS_ASSERT(*block != 0 && *block != 1); // erase requested? if (erase) { @@ -8890,7 +8908,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase) { if (lfs->lookahead.ckpoint <= 0) { LFS_ERROR("No more free space 0x%"PRIx32, (lfs->lookahead.start + lfs->lookahead.next) - % lfs->cfg->block_count); + % lfs->block_count); return LFS_ERR_NOSPC; } @@ -8900,7 +8918,8 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase) { // note we limit the lookahead window to at most the amount of blocks // checkpointed, this prevents the above math from underflowing // - lfs->lookahead.start += lfs->lookahead.size; + lfs->lookahead.start = (lfs->lookahead.start + lfs->lookahead.size) + % lfs->block_count; lfs->lookahead.next = 0; lfs->lookahead.size = lfs_min32( 8*lfs->cfg->lookahead_size, @@ -8941,12 +8960,13 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block, bool erase) { } + /// Other filesystem things /// int lfsr_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { fsinfo->disk_version = LFS_DISK_VERSION; fsinfo->block_size = lfs->cfg->block_size; - fsinfo->block_count = lfs->cfg->block_count; + fsinfo->block_count = lfs->block_count; fsinfo->name_limit = lfs->name_limit; fsinfo->file_limit = lfs->file_limit; return 0; @@ -8986,8 +9006,7 @@ lfs_ssize_t lfsr_fs_size(lfs_t *lfs) { } - -/// Prepare the filesystem for mutation /// +// consistency stuff static int lfsr_fs_fixgrm(lfs_t *lfs) { while (lfsr_grm_hasrm(&lfs->grm)) { @@ -9080,6 +9099,7 @@ static int lfsr_fs_fixorphans(lfs_t *lfs) { return 0; } +// prepare the filesystem for mutation static int lfsr_fs_preparemutation(lfs_t *lfs) { // checkpoint the allocator lfs_alloc_ckpoint(lfs); @@ -9141,6 +9161,64 @@ int lfsr_fs_mkconsistent(lfs_t *lfs) { } +int lfsr_fs_grow(lfs_t *lfs, lfs_size_t block_count_) { + // shrinking the filesystem is not supported + LFS_ASSERT(block_count_ >= lfs->block_count); + + // do nothing if block_count doesn't change + if (block_count_ == lfs->block_count) { + return 0; + } + + // Note we do _not_ call lfsr_fs_preparemutation here. This is a bit + // scary, but we should be ok as long as we patch grms in + // lfsr_mdir_commit and only commit to the mroot. + // + // Calling lfsr_fs_preparemutation risks locking our filesystem up trying + // to fix grms/orphans before we can commit the new filesystem size. If + // we don't, we should always be able to recover a stuck filesystem with + // lfsr_fs_grow. + // + + LFS_DEBUG("Growing littlefs %"PRId32"x%"PRId32" -> %"PRId32"x%"PRId32, + lfs->cfg->block_size, lfs->block_count, + lfs->cfg->block_size, block_count_); + + // keep track of our current block_count in case we fail + lfs_size_t block_count = lfs->block_count; + + // we can use the new blocks immediately as long as the commit + // with the new block_count is atomic + lfs->block_count = block_count_; + // discard stale lookahead buffer + lfs_alloc_discard(lfs); + lfs_alloc_ckpoint(lfs); + + // update our on-disk config + int err = lfsr_mdir_commit(lfs, &lfs->mroot, LFSR_ATTRS( + LFSR_ATTR( + LFSR_TAG_GEOMETRY, 0, + LFSR_DATA_GEOMETRY((&(lfsr_geometry_t){ + lfs->cfg->block_size, + block_count_}))))); + if (err) { + goto failed; + } + + return 0; + +failed:; + // restore block_count + lfs->block_count = block_count; + // discard clobbered lookahead buffer + lfs_alloc_discard(lfs); + + return err; +} + + + + /// Directory operations /// // needed in lfsr_mkdir @@ -15658,6 +15736,9 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { // fragment_size must be <= block_size/4 LFS_ASSERT(lfs->cfg->fragment_size <= lfs->cfg->block_size/4); + // setup block_count so we can mutate it + lfs->block_count = lfs->cfg->block_count; + // setup read cache lfs->rcache.block = 0; lfs->rcache.off = 0; diff --git a/lfs.h b/lfs.h index ec603d00..e1fbac1d 100644 --- a/lfs.h +++ b/lfs.h @@ -601,6 +601,7 @@ typedef struct lfsr_grm { // The littlefs filesystem type typedef struct lfs { const struct lfs_config *cfg; + lfs_size_t block_count; lfs_size_t name_limit; lfs_off_t file_limit; @@ -997,6 +998,18 @@ lfs_ssize_t lfsr_fs_size(lfs_t *lfs); int lfsr_fs_mkconsistent(lfs_t *lfs); #endif +#ifndef LFS_READONLY +// Change the number of blocks used by the filesystem +// +// This changes the number of blocks we are currently using and updates +// the superblock with the new block count. +// +// Note: This is irreversible. +// +// Returns a negative error code on failure. +int lfsr_fs_grow(lfs_t *lfs, lfs_size_t block_count); +#endif + #ifndef LFS_READONLY #ifdef LFS_MIGRATE // Attempts to migrate a previous version of littlefs diff --git a/tests/test_badblocks.toml b/tests/test_badblocks.toml index 4492ae46..61b28b27 100644 --- a/tests/test_badblocks.toml +++ b/tests/test_badblocks.toml @@ -46,10 +46,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -1742,10 +1742,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -3438,10 +3438,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree diff --git a/tests/test_btree.toml b/tests/test_btree.toml index 74f049e9..88c48e36 100644 --- a/tests/test_btree.toml +++ b/tests/test_btree.toml @@ -13,10 +13,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create an empty tree @@ -44,10 +44,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a single-entry tree @@ -85,10 +85,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a two-entry tree @@ -134,10 +134,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a two-entry tree @@ -184,10 +184,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a two-entry tree @@ -242,10 +242,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a two-entry tree @@ -303,10 +303,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree with N elements @@ -353,10 +353,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree with N elements @@ -405,10 +405,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -487,10 +487,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree with N elements @@ -555,10 +555,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -690,10 +690,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a single-entry tree @@ -735,10 +735,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a two-entry tree @@ -793,10 +793,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a two-entry tree @@ -865,10 +865,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree with N elements @@ -922,10 +922,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -1008,10 +1008,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree with N elements @@ -1081,10 +1081,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -1224,10 +1224,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a single-entry tree @@ -1280,10 +1280,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a single-entry tree @@ -1352,10 +1352,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a single-entry tree @@ -1424,10 +1424,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a single-entry tree @@ -1515,10 +1515,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree with N elements @@ -1594,10 +1594,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree with N elements @@ -1676,10 +1676,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -1764,10 +1764,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree with N elements @@ -1870,10 +1870,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -2020,10 +2020,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree with N elements @@ -2079,10 +2079,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -2168,10 +2168,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree with N elements @@ -2228,10 +2228,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -2404,10 +2404,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree @@ -2471,10 +2471,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree @@ -2541,10 +2541,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree @@ -2604,10 +2604,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree @@ -2684,10 +2684,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -2790,10 +2790,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -2956,10 +2956,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a zero-entry tree @@ -2989,10 +2989,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a single-entry tree @@ -3041,10 +3041,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a two-entry tree @@ -3107,10 +3107,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a two-entry tree @@ -3187,10 +3187,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a two-entry tree @@ -3268,10 +3268,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree with N elements @@ -3341,10 +3341,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -3455,10 +3455,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree with N elements @@ -3529,10 +3529,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -3685,10 +3685,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -3842,10 +3842,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree @@ -4047,10 +4047,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a tree with N elements @@ -4169,10 +4169,10 @@ code = ''' lfs_init(&lfs, CFG) => 0; // create free lookahead memset(lfs.lookahead.buffer, 0, CFG->lookahead_size); - lfs.lookahead.start = 0; + lfs.lookahead.start = 2; lfs.lookahead.size = lfs_min(8*CFG->lookahead_size, - CFG->block_count); - lfs.lookahead.next = 0; + CFG->block_count-2); + lfs.lookahead.next = 2; lfs_alloc_ckpoint(&lfs); // create a btree diff --git a/tests/test_grow.toml b/tests/test_grow.toml new file mode 100644 index 00000000..7b1e92fe --- /dev/null +++ b/tests/test_grow.toml @@ -0,0 +1,3100 @@ +# Test variable block counts and grow related things +after = [ + 'test_dirs', + 'test_files', + 'test_fwrite', + 'test_forphans', + 'test_alloc', +] + + +# test we can mount a filesystem with fewer blocks +[cases.test_grow_mount_smaller] +defines.SMALLER_BLOCK_COUNT = [ + 'BLOCK_COUNT-1', + 'BLOCK_COUNT/2', + 'BLOCK_COUNT/4', + '2', +] +defines.BIGGER_BLOCK_COUNT = [ + 'BLOCK_COUNT', + 'BLOCK_COUNT-1', + 'BLOCK_COUNT/2', + 'BLOCK_COUNT/4', +] +if = 'BIGGER_BLOCK_COUNT > SMALLER_BLOCK_COUNT' +code = ''' + // create a smaller fs + struct lfs_config cfg = *CFG; + cfg.block_count = SMALLER_BLOCK_COUNT; + lfs_t lfs; + lfsr_format(&lfs, &cfg) => 0; + lfsr_mount(&lfs, &cfg) => 0; + + // fsstat up to date? + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == SMALLER_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // do some work + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf[1024] = "Hello World!"; + lfs_size_t wsize = strlen((char*)wbuf); + lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + 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 == wsize); + uint8_t rbuf[1024]; + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + assert(memcmp(rbuf, wbuf, wsize) == 0); + + lfsr_unmount(&lfs) => 0; + + ////// + + // try to mount with a bigger block count + cfg = *CFG; + cfg.block_count = BIGGER_BLOCK_COUNT; + lfsr_mount(&lfs, &cfg) => 0; + + // fsstat up to date? + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == SMALLER_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // file still exists? + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + assert(memcmp(rbuf, wbuf, wsize) == 0); + + // do some work + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + strcpy((char*)wbuf, "Chris was here!"); + wsize = strlen((char*)wbuf); + lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + assert(memcmp(rbuf, wbuf, wsize) == 0); + + lfsr_unmount(&lfs) => 0; + + // stays after a mount? + lfsr_mount(&lfs, &cfg) => 0; + + // fsstat up to date? + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == SMALLER_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // file still exists? + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + assert(memcmp(rbuf, wbuf, wsize) == 0); + + lfsr_unmount(&lfs) => 0; +''' + +# test we _can't_ mount a filesystem with more blocks +[cases.test_grow_mount_bigger] +defines.SMALLER_BLOCK_COUNT = [ + 'BLOCK_COUNT-1', + 'BLOCK_COUNT/2', + 'BLOCK_COUNT/4', + '2', +] +defines.BIGGER_BLOCK_COUNT = [ + 'BLOCK_COUNT', + 'BLOCK_COUNT-1', + 'BLOCK_COUNT/2', + 'BLOCK_COUNT/4', +] +if = 'BIGGER_BLOCK_COUNT > SMALLER_BLOCK_COUNT' +code = ''' + // create a bigger fs + struct lfs_config cfg = *CFG; + cfg.block_count = BIGGER_BLOCK_COUNT; + lfs_t lfs; + lfsr_format(&lfs, &cfg) => 0; + lfsr_mount(&lfs, &cfg) => 0; + + // fsstat up to date? + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BIGGER_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // do some work + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf[1024] = "Hello World!"; + lfs_size_t wsize = strlen((char*)wbuf); + lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + 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 == wsize); + uint8_t rbuf[1024]; + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + assert(memcmp(rbuf, wbuf, wsize) == 0); + + lfsr_unmount(&lfs) => 0; + + ////// + + // try to mount with a smaller block count + cfg = *CFG; + cfg.block_count = SMALLER_BLOCK_COUNT; + lfsr_mount(&lfs, &cfg) => LFS_ERR_NOTSUP; +''' + +# test we can grow a filesystem +[cases.test_grow_grow] +defines.SMALLER_BLOCK_COUNT = [ + 'BLOCK_COUNT-1', + 'BLOCK_COUNT/2', + 'BLOCK_COUNT/4', + '2', +] +defines.BIGGER_BLOCK_COUNT = [ + 'BLOCK_COUNT', + 'BLOCK_COUNT-1', + 'BLOCK_COUNT/2', + 'BLOCK_COUNT/4', +] +if = 'BIGGER_BLOCK_COUNT > SMALLER_BLOCK_COUNT' +code = ''' + // create a smaller fs + struct lfs_config cfg = *CFG; + cfg.block_count = SMALLER_BLOCK_COUNT; + lfs_t lfs; + lfsr_format(&lfs, &cfg) => 0; + lfsr_mount(&lfs, &cfg) => 0; + + // fsstat up to date? + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == SMALLER_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // do some work + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf[1024] = "Hello World!"; + lfs_size_t wsize = strlen((char*)wbuf); + lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + 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 == wsize); + uint8_t rbuf[1024]; + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + assert(memcmp(rbuf, wbuf, wsize) == 0); + + lfsr_unmount(&lfs) => 0; + + ////// + + // try to grow our filesystem + cfg = *CFG; + cfg.block_count = BIGGER_BLOCK_COUNT; + lfsr_mount(&lfs, &cfg) => 0; + + lfsr_fs_grow(&lfs, BIGGER_BLOCK_COUNT) => 0; + + // fsstat up to date? + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BIGGER_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // file still exists? + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + assert(memcmp(rbuf, wbuf, wsize) == 0); + + // do some work + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + strcpy((char*)wbuf, "Chris was here!"); + wsize = strlen((char*)wbuf); + lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + assert(memcmp(rbuf, wbuf, wsize) == 0); + + lfsr_unmount(&lfs) => 0; + + // stays after a mount? + lfsr_mount(&lfs, &cfg) => 0; + + // fsstat up to date? + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == BIGGER_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // file still exists? + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + assert(memcmp(rbuf, wbuf, wsize) == 0); + + lfsr_unmount(&lfs) => 0; +''' + +# growing to the same size should do nothing +[cases.test_grow_noop] +defines.SMALLER_BLOCK_COUNT = [ + 'BLOCK_COUNT', + 'BLOCK_COUNT-1', + 'BLOCK_COUNT/2', + 'BLOCK_COUNT/4', + '2', +] +code = ''' + // create a smaller fs + struct lfs_config cfg = *CFG; + cfg.block_count = SMALLER_BLOCK_COUNT; + lfs_t lfs; + lfsr_format(&lfs, &cfg) => 0; + lfsr_mount(&lfs, &cfg) => 0; + + // fsstat up to date? + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == SMALLER_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // do some work + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + uint8_t wbuf[1024] = "Hello World!"; + lfs_size_t wsize = strlen((char*)wbuf); + lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + 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 == wsize); + uint8_t rbuf[1024]; + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + assert(memcmp(rbuf, wbuf, wsize) == 0); + + lfsr_unmount(&lfs) => 0; + + ////// + + // try to grow to same size + cfg = *CFG; + cfg.block_count = SMALLER_BLOCK_COUNT; + lfsr_mount(&lfs, &cfg) => 0; + + lfsr_fs_grow(&lfs, SMALLER_BLOCK_COUNT) => 0; + + // fsstat up to date? + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == SMALLER_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // file still exists? + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + assert(memcmp(rbuf, wbuf, wsize) == 0); + + // do some work + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_TRUNC) => 0; + strcpy((char*)wbuf, "Chris was here!"); + wsize = strlen((char*)wbuf); + lfsr_file_write(&lfs, &file, wbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + assert(memcmp(rbuf, wbuf, wsize) == 0); + + lfsr_unmount(&lfs) => 0; + + // stays after a mount? + lfsr_mount(&lfs, &cfg) => 0; + + // fsstat up to date? + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == SMALLER_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // file still exists? + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == wsize); + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, wsize) => wsize; + lfsr_file_close(&lfs, &file) => 0; + assert(memcmp(rbuf, wbuf, wsize) == 0); + + lfsr_unmount(&lfs) => 0; +''' + + + +# These tests try various fuzz tests while incrementally growing the +# filesystem. When encountering LFS_ERR_NOSPC, the filesystem is grown by +# one block. Hopefully this will catch most grow-related bugs. +# + +[cases.test_grow_incr_dir_many] +defines.INIT_BLOCK_COUNT = 7 # TODO need to fix mroot split issue +defines.REMOUNT = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +code = ''' + // start with a small number of blocks + struct lfs_config cfg = *CFG; + cfg.block_count = INIT_BLOCK_COUNT; + lfs_t lfs; + lfsr_format(&lfs, &cfg) => 0; + // mount with maximum block count + lfsr_mount(&lfs, CFG) => 0; + + // fsstat up to date? + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == INIT_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // make this many directories + for (lfs_size_t i = 0; i < N; i++) { + again:; + char name[256]; + sprintf(name, "dir%03x", i); + int err = lfsr_mkdir(&lfs, name); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + continue; + + grow:; + // try growing the filesystem + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.block_count >= INIT_BLOCK_COUNT); + assert(fsinfo.block_count <= BLOCK_COUNT); + lfs_ssize_t used = lfsr_fs_size(&lfs); + assert(used >= 0); + + // we may need to grow multiple blocks before the system gets unstuck + lfs_size_t block_count_ = fsinfo.block_count; + while (true) { + assert(block_count_ < BLOCK_COUNT); + block_count_ += 1; + err = lfsr_fs_grow(&lfs, block_count_); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + continue; + } + + break; + } + + printf("grew %d/%d -> %d/%d\n", + used, fsinfo.block_count, + used, block_count_); + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // fsstat up to date? + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == block_count_); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + goto again; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // grm should be zero here + assert(lfs.grm_p[0] == 0); + + // check that our mkdir worked + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_dir_open(&lfs, &dir, name) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + } + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_grow_incr_dir_fuzz] +defines.INIT_BLOCK_COUNT = 7 # TODO need to fix mroot split issue +defines.REMOUNT = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] +defines.OPS = 1024 +defines.SEED = 'range(10)' +fuzz = 'SEED' +code = ''' + // start with a small number of blocks + struct lfs_config cfg = *CFG; + cfg.block_count = INIT_BLOCK_COUNT; + lfs_t lfs; + lfsr_format(&lfs, &cfg) => 0; + // mount with maximum block count + lfsr_mount(&lfs, CFG) => 0; + + // fsstat up to date? + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == INIT_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // make mroot split, TODO fix this + for (lfs_size_t i = 0; i < 24; i++) { + char name[256]; + sprintf(name, "splitme%03x", i); + lfsr_mkdir(&lfs, name) => 0; + } + for (lfs_size_t i = 0; i < 24; i++) { + char name[256]; + sprintf(name, "splitme%03x", i); + lfsr_remove(&lfs, name) => 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 < OPS; i++) { + again:; + uint32_t prng_ = prng; + // choose a pseudo-random op, either mkdir, remove, or rename + uint8_t op = TEST_PRNG(&prng_) % 3; + + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number, truncate to 3 hexadecimals + lfs_size_t x = TEST_PRNG(&prng_) % N; + + // create a directory here + char name[256]; + sprintf(name, "dir%03x", x); + int err = lfsr_mkdir(&lfs, name); + assert(!err || err == LFS_ERR_EXIST || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // 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; + } + } + + } else if (op == 1) { + // choose a pseudo-random entry to delete + lfs_size_t j = TEST_PRNG(&prng_) % sim_size; + lfs_size_t x = sim[j]; + + // remove this directory + char name[256]; + sprintf(name, "dir%03x", x); + int err = lfsr_remove(&lfs, name); + assert(!err || err == LFS_ERR_NOSPC || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + sim_size -= 1; + + } else { + // choose a pseudo-random entry to rename, and a pseudo-random + // number to rename to + lfs_size_t j = TEST_PRNG(&prng_) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng_) % N; + + // rename this directory + char old_name[256]; + sprintf(old_name, "dir%03x", x); + char new_name[256]; + sprintf(new_name, "dir%03x", y); + int err = lfsr_rename(&lfs, old_name, new_name); + assert(!err || err == LFS_ERR_NOSPC || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // already seen and not a noop? + if (k < sim_size && sim[k] == y && x != y) { + // just delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + sim_size -= 1; + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + sim[k] = y; + } + break; + } + } + } + + prng = prng_; + continue; + + grow:; + // try growing the filesystem + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.block_count >= INIT_BLOCK_COUNT); + assert(fsinfo.block_count <= BLOCK_COUNT); + lfs_ssize_t used = lfsr_fs_size(&lfs); + assert(used >= 0); + + // we may need to grow multiple blocks before the system gets unstuck + lfs_size_t block_count_ = fsinfo.block_count; + while (true) { + assert(block_count_ < BLOCK_COUNT); + block_count_ += 1; + int err = lfsr_fs_grow(&lfs, block_count_); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + continue; + } + + break; + } + + printf("grew %d/%d -> %d/%d\n", + used, fsinfo.block_count, + used, block_count_); + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // fsstat up to date? + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == block_count_); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + goto again; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // grm should be zero here + assert(lfs.grm_p[0] == 0); + + // test that our directories match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + char name2[256]; + sprintf(name2, "dir%03x", sim[j]); + assert(strcmp(info.name, name2) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + + // clean up sim/lfs + free(sim); + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_grow_incr_file_many] +defines.INIT_BLOCK_COUNT = 7 # TODO need to fix mroot split issue +defines.REMOUNT = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + // start with a small number of blocks + struct lfs_config cfg = *CFG; + cfg.block_count = INIT_BLOCK_COUNT; + lfs_t lfs; + lfsr_format(&lfs, &cfg) => 0; + // mount with maximum block count + lfsr_mount(&lfs, CFG) => 0; + + // fsstat up to date? + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == INIT_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // make mroot split, TODO fix this + for (lfs_size_t i = 0; i < 24; i++) { + char name[256]; + sprintf(name, "splitme%03x", i); + lfsr_mkdir(&lfs, name) => 0; + } + for (lfs_size_t i = 0; i < 24; i++) { + char name[256]; + sprintf(name, "splitme%03x", i); + lfsr_remove(&lfs, name) => 0; + } + + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + again:; + uint32_t prng_ = prng; + // create this many files + char name[256]; + sprintf(name, "amethyst%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); + } + + lfsr_file_t file; + int err = lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + lfs_ssize_t d = lfsr_file_write(&lfs, &file, wbuf, SIZE); + assert(d == SIZE || d == LFS_ERR_NOSPC); + if (d == LFS_ERR_NOSPC) { + lfsr_file_close(&lfs, &file) => 0; + goto grow; + } + err = lfsr_file_close(&lfs, &file); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + prng = prng_; + continue; + + grow:; + // try growing the filesystem + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.block_count >= INIT_BLOCK_COUNT); + assert(fsinfo.block_count <= BLOCK_COUNT); + lfs_ssize_t used = lfsr_fs_size(&lfs); + assert(used >= 0); + + // we may need to grow multiple blocks before the system gets unstuck + lfs_size_t block_count_ = fsinfo.block_count; + while (true) { + assert(block_count_ < BLOCK_COUNT); + block_count_ += 1; + err = lfsr_fs_grow(&lfs, block_count_); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + continue; + } + + break; + } + + printf("grew %d/%d -> %d/%d\n", + used, fsinfo.block_count, + used, block_count_); + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // fsstat up to date? + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == block_count_); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + goto again; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our writes worked + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // check with stat + char name[256]; + sprintf(name, "amethyst%03x", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // try reading the file, note we reset prng above + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + uint8_t rbuf[SIZE]; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_grow_incr_file_fuzz] +defines.INIT_BLOCK_COUNT = 7 # TODO need to fix mroot split issue +defines.REMOUNT = [false, true] +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = 1024 +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 'range(10)' +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +code = ''' + // start with a small number of blocks + struct lfs_config cfg = *CFG; + cfg.block_count = INIT_BLOCK_COUNT; + lfs_t lfs; + lfsr_format(&lfs, &cfg) => 0; + // mount with maximum block count + lfsr_mount(&lfs, CFG) => 0; + + // fsstat up to date? + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == INIT_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // make mroot split, TODO fix this + for (lfs_size_t i = 0; i < 24; i++) { + char name[256]; + sprintf(name, "splitme%03x", i); + lfsr_mkdir(&lfs, name) => 0; + } + for (lfs_size_t i = 0; i < 24; i++) { + char name[256]; + sprintf(name, "splitme%03x", i); + lfsr_remove(&lfs, name) => 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 < OPS; i++) { + again:; + uint32_t prng_ = prng; + // choose which operation to do + uint8_t op = TEST_PRNG(&prng_) % 3; + + // creating a new file? + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng_) % N; + // associate each file with a prng that generates its contents + uint32_t wprng = TEST_PRNG(&prng_); + + // create a file here + char name[256]; + sprintf(name, "amethyst%03x", x); + uint8_t wbuf[SIZE]; + uint32_t wprng_ = wprng; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + + lfsr_file_t file; + int err = lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + lfs_ssize_t d = lfsr_file_write(&lfs, &file, wbuf, SIZE); + assert(d == SIZE || d == LFS_ERR_NOSPC); + if (d == LFS_ERR_NOSPC) { + lfsr_file_close(&lfs, &file) => 0; + goto grow; + } + err = lfsr_file_close(&lfs, &file); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // 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; + } + } + + // deleting a file? + } else if (op == 1) { + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng_) % sim_size; + lfs_size_t x = sim[j]; + + // delete this file + char name[256]; + sprintf(name, "amethyst%03x", x); + int err = lfsr_remove(&lfs, name); + assert(!err || err == LFS_ERR_NOSPC || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // renaming a file? + } else { + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng_) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng_) % N; + uint32_t wprng = sim_prngs[j]; + + // rename this file + char old_name[256]; + sprintf(old_name, "amethyst%03x", x); + char new_name[256]; + sprintf(new_name, "amethyst%03x", y); + int err = lfsr_rename(&lfs, old_name, new_name); + assert(!err || err == LFS_ERR_NOSPC || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; + } + break; + } + } + } + + prng = prng_; + continue; + + grow:; + // try growing the filesystem + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.block_count >= INIT_BLOCK_COUNT); + assert(fsinfo.block_count <= BLOCK_COUNT); + lfs_ssize_t used = lfsr_fs_size(&lfs); + assert(used >= 0); + + // we may need to grow multiple blocks before the system gets unstuck + lfs_size_t block_count_ = fsinfo.block_count; + while (true) { + assert(block_count_ < BLOCK_COUNT); + block_count_ += 1; + int err = lfsr_fs_grow(&lfs, block_count_); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + continue; + } + + break; + } + + printf("grew %d/%d -> %d/%d\n", + used, fsinfo.block_count, + used, block_count_); + + // remount? + if (REMOUNT) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // fsstat up to date? + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == block_count_); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + goto again; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our files match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // check the file contents + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_grow_incr_orphanzombie_fuzz] +defines.INIT_BLOCK_COUNT = 7 # TODO need to fix mroot split issue +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = 1024 +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 'range(10)' +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +code = ''' + // start with a small number of blocks + struct lfs_config cfg = *CFG; + cfg.block_count = INIT_BLOCK_COUNT; + lfs_t lfs; + lfsr_format(&lfs, &cfg) => 0; + // mount with maximum block count + lfsr_mount(&lfs, CFG) => 0; + + // fsstat up to date? + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == INIT_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // make mroot split, TODO fix this + for (lfs_size_t i = 0; i < 24; i++) { + char name[256]; + sprintf(name, "splitme%03x", i); + lfsr_mkdir(&lfs, name) => 0; + } + for (lfs_size_t i = 0; i < 24; i++) { + char name[256]; + sprintf(name, "splitme%03x", i); + lfsr_remove(&lfs, name) => 0; + } + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; + + typedef struct sim_file { + lfs_size_t x; + bool orphan; + bool zombie; + uint32_t prng; + lfsr_file_t file; + } sim_file_t; + sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); + lfs_size_t sim_file_count = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + again:; + uint32_t prng_ = prng; + nonsense:; + // choose which operation to do + uint8_t op = TEST_PRNG(&prng_) % 5; + + // open a new file? + if (op == 0) { + if (sim_file_count >= N) { + goto nonsense; + } + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng_) % N; + + // already exists? + bool orphan = true; + uint32_t wprng = 0; + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim[j] == x) { + orphan = false; + wprng = sim_prngs[j]; + break; + } + } + // choose a random seed if we don't exist + if (orphan) { + wprng = TEST_PRNG(&prng_); + } + + lfs_size_t j = sim_file_count; + sim_files[j] = malloc(sizeof(sim_file_t)); + + // open the actual file + char name[256]; + sprintf(name, "batman%03x", x); + int err = lfsr_file_open(&lfs, &sim_files[j]->file, name, + LFS_O_RDWR | LFS_O_CREAT); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + free(sim_files[j]); + goto grow; + } + + // write some initial data if we don't exist + if (orphan) { + uint8_t wbuf[SIZE]; + uint32_t wprng_ = wprng; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + lfs_ssize_t d = lfsr_file_write(&lfs, &sim_files[j]->file, + wbuf, SIZE); + LFS_ASSERT(d == SIZE || d == LFS_ERR_NOSPC); + if (d == LFS_ERR_NOSPC) { + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + free(sim_files[j]); + goto grow; + } + } + + // open in our sim + sim_files[j]->x = x; + sim_files[j]->orphan = orphan; + sim_files[j]->zombie = false; + sim_files[j]->prng = wprng; + sim_file_count++; + + // 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_); + + // write to the file + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t wbuf[SIZE]; + uint32_t wprng_ = wprng; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + lfs_ssize_t d = lfsr_file_write(&lfs, &sim_files[j]->file, + wbuf, SIZE); + assert(d == SIZE || d == LFS_ERR_NOSPC); + if (d == LFS_ERR_NOSPC) { + goto grow; + } + int err = lfsr_file_sync(&lfs, &sim_files[j]->file); + assert(err == ((!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT) + || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // update sim + sim_files[j]->prng = wprng; + if (!sim_files[j]->zombie) { + // insert into our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + // new prng + sim_prngs[k] = wprng; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = wprng; + } + break; + } + } + + // update related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x && !sim_files[k]->zombie) { + sim_files[k]->orphan = false; + sim_files[k]->prng = wprng; + } + } + } + + // 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 this file + char name[256]; + sprintf(name, "batman%03x", x); + int err = lfsr_remove(&lfs, name); + assert(!err || err == LFS_ERR_NOSPC || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // 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]; + + // rename this file + char old_name[256]; + sprintf(old_name, "batman%03x", x); + char new_name[256]; + sprintf(new_name, "batman%03x", y); + int err = lfsr_rename(&lfs, old_name, new_name); + assert(!err || err == LFS_ERR_NOSPC || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; + } + break; + } + } + + // update any related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + // move source files + if (sim_files[k]->x == x) { + sim_files[k]->x = y; + + // mark target files as zombied + } else if (sim_files[k]->x == y) { + sim_files[k]->zombie = true; + } + } + } + + prng = prng_; + continue; + + grow:; + // try growing the filesystem + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.block_count >= INIT_BLOCK_COUNT); + assert(fsinfo.block_count <= BLOCK_COUNT); + lfs_ssize_t used = lfsr_fs_size(&lfs); + assert(used >= 0); + + // we may need to grow multiple blocks before the system gets unstuck + lfs_size_t block_count_ = fsinfo.block_count; + while (true) { + assert(block_count_ < BLOCK_COUNT); + block_count_ += 1; + int err = lfsr_fs_grow(&lfs, block_count_); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + continue; + } + + break; + } + + printf("grew %d/%d -> %d/%d\n", + used, fsinfo.block_count, + used, block_count_); + + // fsstat up to date? + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == block_count_); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + goto again; + } + + // check that disk matches our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + // check that our file handles match our simulation + for (lfs_size_t j = 0; j < sim_file_count; j++) { + uint32_t wprng = sim_files[j]->prng; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + for (lfs_size_t j = 0; j < sim_file_count; j++) { + lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + free(sim_files[j]); + } + free(sim_files); + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_grow_incr_orphanzombiedir_fuzz] +defines.INIT_BLOCK_COUNT = 7 # TODO need to fix mroot split issue +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = 1024 +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 'range(10)' +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +code = ''' + // start with a small number of blocks + struct lfs_config cfg = *CFG; + cfg.block_count = INIT_BLOCK_COUNT; + lfs_t lfs; + lfsr_format(&lfs, &cfg) => 0; + // mount with maximum block count + lfsr_mount(&lfs, CFG) => 0; + + // fsstat up to date? + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == INIT_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + // make mroot split, TODO fix this + for (lfs_size_t i = 0; i < 24; i++) { + char name[256]; + sprintf(name, "splitme%03x", i); + lfsr_mkdir(&lfs, name) => 0; + } + for (lfs_size_t i = 0; i < 24; i++) { + char name[256]; + sprintf(name, "splitme%03x", i); + lfsr_remove(&lfs, name) => 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++) { + again:; + uint32_t prng_ = prng; + 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_); + } + + lfs_size_t j = sim_file_count; + sim_files[j] = malloc(sizeof(sim_file_t)); + + // open the actual file + char name[256]; + sprintf(name, "batman%03x", x); + int err = lfsr_file_open(&lfs, &sim_files[j]->file, name, + LFS_O_RDWR | LFS_O_CREAT); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + free(sim_files[j]); + goto grow; + } + + // write some initial data if we don't exist + if (orphan) { + uint8_t wbuf[SIZE]; + uint32_t wprng_ = wprng; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + lfs_ssize_t d = lfsr_file_write(&lfs, &sim_files[j]->file, + wbuf, SIZE); + assert(d == SIZE || d == LFS_ERR_NOSPC); + if (d == LFS_ERR_NOSPC) { + free(sim_files[j]); + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + goto grow; + } + } + + // open in our sim + sim_files[j]->x = x; + sim_files[j]->orphan = orphan; + sim_files[j]->zombie = false; + sim_files[j]->prng = wprng; + sim_file_count++; + + // 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_); + + // write to the file + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t wbuf[SIZE]; + uint32_t wprng_ = wprng; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); + } + lfs_ssize_t d = lfsr_file_write(&lfs, &sim_files[j]->file, + wbuf, SIZE); + assert(d == SIZE || d == LFS_ERR_NOSPC); + if (d == LFS_ERR_NOSPC) { + goto grow; + } + int err = lfsr_file_sync(&lfs, &sim_files[j]->file); + assert(err == ((!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT) + || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // 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; + } + } + } + + // 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 this file + char name[256]; + sprintf(name, "batman%03x", x); + int err = lfsr_remove(&lfs, name); + assert(!err || err == LFS_ERR_NOSPC || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // 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; + } + } + + // 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]; + + 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; + } + } + break; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "batman%03x", x); + char new_name[256]; + sprintf(new_name, "batman%03x", y); + int err = lfsr_rename(&lfs, old_name, new_name); + assert(!err || err == LFS_ERR_NOSPC || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + 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; + } + } + + // toss a directory into the mix + } else if (op == 5) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng_) % N; + + 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; + } + break; + } + } + + // make the directory + char name[256]; + sprintf(name, "batman%03x", x); + int err = lfsr_mkdir(&lfs, name); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // insert into our sim, use negative numbers for dirs + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // 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; + } + } + } + + prng = prng_; + continue; + + grow:; + // try growing the filesystem + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.block_count >= INIT_BLOCK_COUNT); + assert(fsinfo.block_count <= BLOCK_COUNT); + lfs_ssize_t used = lfsr_fs_size(&lfs); + assert(used >= 0); + + // we may need to grow multiple blocks before the system gets unstuck + lfs_size_t block_count_ = fsinfo.block_count; + while (true) { + assert(block_count_ < BLOCK_COUNT); + block_count_ += 1; + int err = lfsr_fs_grow(&lfs, block_count_); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + continue; + } + + break; + } + + printf("grew %d/%d -> %d/%d\n", + used, fsinfo.block_count, + used, block_count_); + + // fsstat up to date? + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == block_count_); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + goto again; + } + + // 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_desync(&lfs, &sim_files[j]->file) => 0; + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + free(sim_files[j]); + } + free(sim_files); + lfsr_unmount(&lfs) => 0; +''' + +# A general purpose powerloss fuzz test +# +# +# Under powerloss, we can't really keep track of a sim reliably/ +# efficiently, instead just do random operations, store a counter in a +# special file so we know how much progress has been made, and hope for +# the best. Most likely an internal assert will trigger if anything goes +# wrong. +# +[cases.test_grow_incr_file_pl_fuzz] +defines.INIT_BLOCK_COUNT = 7 # TODO need to fix mroot split issue +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = 256 +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 'range(10)' +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +reentrant = true +code = ''' + // format once per test + lfs_t lfs; + int err = lfsr_mount(&lfs, CFG); + if (err) { + // start with a small number of blocks + struct lfs_config cfg = *CFG; + cfg.block_count = INIT_BLOCK_COUNT; + lfsr_format(&lfs, &cfg) => 0; + // mount with maximum block count + lfsr_mount(&lfs, CFG) => 0; + + // fsstat up to date? + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == INIT_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + } + + // make mroot split, TODO fix this + struct lfs_info info; + err = lfsr_stat(&lfs, "split", &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + if (lfs.mtree.u.weight == 0x80000000) { + for (lfs_size_t i = 0; i < 24; i++) { + char name[256]; + sprintf(name, "splitme%03x", i); + err = lfsr_mkdir(&lfs, name); + assert(!err || err == LFS_ERR_EXIST); + } + } + for (lfs_size_t i = 0; i < 24; i++) { + char name[256]; + sprintf(name, "splitme%03x", i); + err = lfsr_remove(&lfs, name); + assert(!err || err == LFS_ERR_NOENT); + } + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "split", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + } + + // keep some test state on disk to survive powerloss + typedef struct fuzz_state { + lfs_size_t i; + uint32_t prng; + } fuzz_state_t; + fuzz_state_t state = {.i = 0, .prng = SEED}; + + lfsr_file_t state_file; + err = lfsr_file_open(&lfs, &state_file, "state", LFS_O_RDONLY); + assert(!err || err == LFS_ERR_NOENT); + if (!err) { + lfsr_file_read(&lfs, &state_file, + &state, sizeof(state)) => sizeof(state); + lfsr_file_close(&lfs, &state_file) => 0; + } + + uint32_t prng = state.prng; + for (lfs_size_t i = state.i; i < OPS; i++) { + again:; + uint32_t prng_ = prng; + // choose which operation to do + uint8_t op = TEST_PRNG(&prng_) % 3; + + // keep test files in a separate directory + err = lfsr_mkdir(&lfs, "test"); + assert(!err || err == LFS_ERR_EXIST || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // how many files do we have? + lfs_size_t count = 0; + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "test") => 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); + while (true) { + err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info.name) == strlen("amethyst...")); + assert(memcmp(info.name, "amethyst", strlen("amethyst")) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + count++; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // creating a new file? + if (op == 0 || count == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng_) % N; + uint32_t wprng = TEST_PRNG(&prng_); + + // create a file here + char name[256]; + sprintf(name, "test/amethyst%03x", x); + uint8_t wbuf[SIZE]; + uint8_t ck = 0; + for (lfs_size_t j = 0; j < SIZE-1; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + ck = (ck + (wbuf[j] - 'a')) % 26; + } + // make the sum equal to 'a' mod 26 + if (SIZE > 0) { + wbuf[SIZE-1] = 'a' + ((26 - ck) % 26); + } + + lfsr_file_t file; + err = lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + lfs_ssize_t d = lfsr_file_write(&lfs, &file, wbuf, SIZE); + assert(d == SIZE || d == LFS_ERR_NOSPC); + if (d == LFS_ERR_NOSPC) { + lfsr_file_close(&lfs, &file) => 0; + goto grow; + } + err = lfsr_file_close(&lfs, &file); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // deleting a file? + } else if (op == 1) { + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng_) % count; + // find the file + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // delete this file + char name[256]; + assert(strlen(info.name) == strlen("amethyst...")); + sprintf(name, "test/%s", info.name); + err = lfsr_remove(&lfs, name); + assert(!err || err == LFS_ERR_NOSPC || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // renaming a file? + } else { + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng_) % count; + lfs_size_t y = TEST_PRNG(&prng_) % N; + // find the file + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // rename this file + char old_name[256]; + assert(strlen(info.name) == strlen("amethyst...")); + sprintf(old_name, "test/%s", info.name); + char new_name[256]; + sprintf(new_name, "test/amethyst%03x", y); + err = lfsr_rename(&lfs, old_name, new_name); + assert(!err || err == LFS_ERR_NOSPC || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + } + + // update our state file + state.i = i; + state.prng = prng_; + err = lfsr_file_open(&lfs, &state_file, "state", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + lfs_ssize_t d = lfsr_file_write(&lfs, &state_file, + &state, sizeof(state)); + assert(d == sizeof(state) || d == LFS_ERR_NOSPC); + if (d == LFS_ERR_NOSPC) { + lfsr_file_close(&lfs, &state_file) => 0; + goto grow; + } + err = lfsr_file_close(&lfs, &state_file); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + prng = prng_; + continue; + + grow:; + // try growing the filesystem + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.block_count >= INIT_BLOCK_COUNT); + assert(fsinfo.block_count <= BLOCK_COUNT); + lfs_ssize_t used = lfsr_fs_size(&lfs); + assert(used >= 0); + + // we may need to grow multiple blocks before the system gets unstuck + lfs_size_t block_count_ = fsinfo.block_count; + while (true) { + assert(block_count_ < BLOCK_COUNT); + block_count_ += 1; + err = lfsr_fs_grow(&lfs, block_count_); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + continue; + } + + break; + } + + printf("grew %d/%d -> %d/%d\n", + used, fsinfo.block_count, + used, block_count_); + + // fsstat up to date? + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == block_count_); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + goto again; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that things look more-or-less ok + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "test") => 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); + while (true) { + int err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info.name) == strlen("amethyst...")); + assert(memcmp(info.name, "amethyst", strlen("amethyst")) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // at least try to read the files + char name[256]; + sprintf(name, "test/%s", info.name); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + // all data should be lowercase ascii + for (lfs_size_t j = 0; j < SIZE; j++) { + assert(rbuf[j] >= 'a' && rbuf[j] <= 'z'); + } + // sum should be equal to 'a' mod 26 + uint8_t ck = 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + ck = (ck + (rbuf[j] - 'a')) % 26; + } + assert(ck == 0); + lfsr_file_close(&lfs, &file) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + } + + lfsr_unmount(&lfs) => 0; +''' + +# A general purpose powerloss fuzz test, with directories! +# +# Under powerloss, we can't really keep track of a sim reliably/ +# efficiently, instead just do random operations, store a counter in a +# special file so we know how much progress has been made, and hope for +# the best. Most likely an internal assert will trigger if anything goes +# wrong. +# +[cases.test_grow_incr_filedir_pl_fuzz] +defines.INIT_BLOCK_COUNT = 7 # TODO need to fix mroot split issue +# note dirs x files grows O(n^2) +defines.N = [1, 2, 4, 8] +defines.M = 'N' +defines.OPS = 256 +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 'range(10)' +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +reentrant = true +code = ''' + // format once per test + lfs_t lfs; + int err = lfsr_mount(&lfs, CFG); + if (err) { + // start with a small number of blocks + struct lfs_config cfg = *CFG; + cfg.block_count = INIT_BLOCK_COUNT; + lfsr_format(&lfs, &cfg) => 0; + // mount with maximum block count + lfsr_mount(&lfs, CFG) => 0; + + // fsstat up to date? + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == INIT_BLOCK_COUNT); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + } + + // make mroot split, TODO fix this + struct lfs_info info; + err = lfsr_stat(&lfs, "split", &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + if (lfs.mtree.u.weight == 0x80000000) { + for (lfs_size_t i = 0; i < 24; i++) { + char name[256]; + sprintf(name, "splitme%03x", i); + err = lfsr_mkdir(&lfs, name); + assert(!err || err == LFS_ERR_EXIST); + } + } + for (lfs_size_t i = 0; i < 24; i++) { + char name[256]; + sprintf(name, "splitme%03x", i); + err = lfsr_remove(&lfs, name); + assert(!err || err == LFS_ERR_NOENT); + } + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "split", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_close(&lfs, &file) => 0; + } + + // keep some test state on disk to survive powerloss + typedef struct fuzz_state { + lfs_size_t i; + uint32_t prng; + } fuzz_state_t; + fuzz_state_t state = {.i = 0, .prng = SEED}; + + lfsr_file_t state_file; + err = lfsr_file_open(&lfs, &state_file, "state", LFS_O_RDONLY); + assert(!err || err == LFS_ERR_NOENT); + if (!err) { + lfsr_file_read(&lfs, &state_file, + &state, sizeof(state)) => sizeof(state); + lfsr_file_close(&lfs, &state_file) => 0; + } + + uint32_t prng = state.prng; + for (lfs_size_t i = state.i; i < OPS; i++) { + again:; + uint32_t prng_ = prng; + // choose which operation to do + uint8_t op = TEST_PRNG(&prng_) % 6; + + // keep test files in a separate directory + err = lfsr_mkdir(&lfs, "test"); + assert(!err || err == LFS_ERR_EXIST || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // how many dirs do we have? + lfs_size_t dir_count = 0; + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "test") => 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); + while (true) { + err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info.name) == strlen("quartz...")); + assert(memcmp(info.name, "quartz", strlen("quartz")) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + dir_count++; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // dir op? + if (op < 3 || dir_count == 0) { + // creating a new dir? + if (op == 0 || dir_count == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng_) % N; + + // create a dir here + char name[256]; + sprintf(name, "test/quartz%03x", x); + err = lfsr_mkdir(&lfs, name); + assert(!err || err == LFS_ERR_EXIST || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // deleting a dir? + } else if (op == 1) { + // choose a random dir to delete + lfs_size_t j = TEST_PRNG(&prng_) % dir_count; + // find the dir + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // try to delete this dir, ignore non-empty dirs! + char name[256]; + assert(strlen(info.name) == strlen("quartz...")); + sprintf(name, "test/%s", info.name); + err = lfsr_remove(&lfs, name); + assert(!err + || err == LFS_ERR_NOTEMPTY + || err == LFS_ERR_NOSPC + || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // renaming a dir? + } else { + // choose a random dir to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng_) % dir_count; + lfs_size_t y = TEST_PRNG(&prng_) % N; + // find the dir + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // rename this dir, ignore conflicts! + char old_name[256]; + assert(strlen(info.name) == strlen("quartz...")); + sprintf(old_name, "test/%s", info.name); + char new_name[256]; + sprintf(new_name, "test/quartz%03x", y); + err = lfsr_rename(&lfs, old_name, new_name); + assert(!err + || err == LFS_ERR_NOTEMPTY + || err == LFS_ERR_NOSPC + || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + } + + // file op? + } else { + // choose a pseudo-random dir + lfs_size_t dir_i = TEST_PRNG(&prng_) % dir_count; + // find the dir + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= dir_i; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + char dir_path[256]; + sprintf(dir_path, "test/%s", info.name); + + // how many files do we have? + lfs_size_t count = 0; + lfsr_dir_open(&lfs, &dir, dir_path) => 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); + while (true) { + err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info.name) == strlen("amethyst...")); + assert(memcmp( + info.name, + "amethyst", strlen("amethyst")) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + count++; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // creating a new file? + if (op == 3 || count == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng_) % M; + uint32_t wprng = TEST_PRNG(&prng_); + + // create a file here + char name[256]; + sprintf(name, "%s/amethyst%03x", dir_path, x); + uint8_t wbuf[SIZE]; + uint8_t ck = 0; + for (lfs_size_t j = 0; j < SIZE-1; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + ck = (ck + (wbuf[j] - 'a')) % 26; + } + // make the sum equal to 'a' mod 26 + if (SIZE > 0) { + wbuf[SIZE-1] = 'a' + ((26 - ck) % 26); + } + + lfsr_file_t file; + err = lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + lfs_ssize_t d = lfsr_file_write(&lfs, &file, wbuf, SIZE); + assert(d == SIZE || d == LFS_ERR_NOSPC); + if (d == LFS_ERR_NOSPC) { + lfsr_file_close(&lfs, &file) => 0; + goto grow; + } + err = lfsr_file_close(&lfs, &file); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // deleting a file? + } else if (op == 4) { + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng_) % count; + // find the file + lfsr_dir_open(&lfs, &dir, dir_path) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // delete this file + char name[256]; + assert(strlen(info.name) == strlen("amethyst...")); + sprintf(name, "%s/%s", dir_path, info.name); + err = lfsr_remove(&lfs, name); + assert(!err || err == LFS_ERR_NOSPC || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + // renaming a file? + } else { + // choose a random file to rename + lfs_size_t j = TEST_PRNG(&prng_) % count; + // find the file + lfsr_dir_open(&lfs, &dir, dir_path) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + for (lfs_size_t k = 0; k <= j; k++) { + lfsr_dir_read(&lfs, &dir, &info) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // choose a random dir to rename to + lfs_size_t dir_j = TEST_PRNG(&prng_) % dir_count; + // find the dir + struct lfs_info info_; + lfsr_dir_open(&lfs, &dir, "test") => 0; + lfsr_dir_read(&lfs, &dir, &info_) => 0; + lfsr_dir_read(&lfs, &dir, &info_) => 0; + for (lfs_size_t k = 0; k <= dir_j; k++) { + lfsr_dir_read(&lfs, &dir, &info_) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + + // choose a random file to rename to + lfs_size_t y = TEST_PRNG(&prng_) % M; + + // rename this file + char old_name[256]; + assert(strlen(info.name) == strlen("amethyst...")); + sprintf(old_name, "%s/%s", dir_path, info.name); + char new_name[256]; + sprintf(new_name, "test/%s/amethyst%03x", info_.name, y); + err = lfsr_rename(&lfs, old_name, new_name); + assert(!err || err == LFS_ERR_NOSPC || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + } + } + + // update our state file + state.i = i; + state.prng = prng_; + err = lfsr_file_open(&lfs, &state_file, "state", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + lfs_ssize_t d = lfsr_file_write(&lfs, &state_file, + &state, sizeof(state)); + assert(d == sizeof(state) || d == LFS_ERR_NOSPC); + if (d == LFS_ERR_NOSPC) { + lfsr_file_close(&lfs, &state_file) => 0; + goto grow; + } + err = lfsr_file_close(&lfs, &state_file); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + goto grow; + } + + prng = prng_; + continue; + + grow:; + // try growing the filesystem + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.block_count >= INIT_BLOCK_COUNT); + assert(fsinfo.block_count <= BLOCK_COUNT); + lfs_ssize_t used = lfsr_fs_size(&lfs); + assert(used >= 0); + + // we may need to grow multiple blocks before the system gets unstuck + lfs_size_t block_count_ = fsinfo.block_count; + while (true) { + assert(block_count_ < BLOCK_COUNT); + block_count_ += 1; + err = lfsr_fs_grow(&lfs, block_count_); + assert(!err || err == LFS_ERR_NOSPC); + if (err == LFS_ERR_NOSPC) { + continue; + } + + break; + } + + printf("grew %d/%d -> %d/%d\n", + used, fsinfo.block_count, + used, block_count_); + + // fsstat up to date? + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.disk_version == LFS_DISK_VERSION); + assert(fsinfo.block_size == BLOCK_SIZE); + assert(fsinfo.block_count == block_count_); + assert(fsinfo.name_limit == LFS_NAME_MAX); + assert(fsinfo.file_limit == LFS_FILE_MAX); + + goto again; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that things look more-or-less ok + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "test") => 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); + while (true) { + int err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info.name) == strlen("quartz...")); + assert(memcmp(info.name, "quartz", strlen("quartz")) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + + // check that our dirs look more-or-less ok + char name[256]; + sprintf(name, "test/%s", info.name); + lfsr_dir_t dir_; + lfsr_dir_open(&lfs, &dir_, name) => 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); + + while (true) { + err = lfsr_dir_read(&lfs, &dir_, &info_); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + assert(strlen(info_.name) == strlen("amethyst...")); + assert(memcmp( + info_.name, + "amethyst", strlen("amethyst")) == 0); + assert(info_.type == LFS_TYPE_REG); + assert(info_.size == SIZE); + + // at least try to read the files + sprintf(name, "test/%s/%s", info.name, info_.name); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + // all data should be lowercase ascii + for (lfs_size_t j = 0; j < SIZE; j++) { + assert(rbuf[j] >= 'a' && rbuf[j] <= 'z'); + } + // sum should be equal to 'a' mod 26 + uint8_t ck = 0; + for (lfs_size_t j = 0; j < SIZE; j++) { + ck = (ck + (rbuf[j] - 'a')) % 26; + } + assert(ck == 0); + lfsr_file_close(&lfs, &file) => 0; + } + lfsr_dir_close(&lfs, &dir_) => 0; + } + lfsr_dir_close(&lfs, &dir) => 0; + } + + lfsr_unmount(&lfs) => 0; +''' +