diff --git a/lfs.c b/lfs.c index bf3bc9a5..f5157e14 100644 --- a/lfs.c +++ b/lfs.c @@ -8229,6 +8229,7 @@ enum { #define LFSR_TRAVERSAL(_flags) \ ((lfsr_traversal_t){ \ + .o.o.type=LFS_TYPE_TRAVERSAL, \ .o.o.state=LFSR_TSTATE_MROOTANCHOR, \ .o.o.flags=_flags, \ .o.o.mdir.mid=-1, \ @@ -12815,6 +12816,96 @@ int lfsr_fs_mkconsistent(lfs_t *lfs) { return 0; } +int lfsr_fs_gc(lfs_t *lfs, uint32_t flags) { + // some flags don't make sense when only traversing the mtree + LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_islookahead(flags)); + LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_isckdata(flags)); + // these flags are internal and shouldn't be provided by the user + LFS_ASSERT(!lfsr_f_isdirty(flags)); + LFS_ASSERT(!lfsr_f_ismutated(flags)); + + // fix pending grms if requested + if (lfsr_t_ismkconsistent(flags) + && lfsr_grm_count(lfs) > 0) { + if (lfsr_grm_count(lfs) == 2) { + LFS_DEBUG("Fixing grm %"PRId32".%"PRId32" %"PRId32".%"PRId32, + lfsr_mid_bid(lfs, lfs->grm.mids[0]) >> lfs->mdir_bits, + lfsr_mid_rid(lfs, lfs->grm.mids[0]), + lfsr_mid_bid(lfs, lfs->grm.mids[1]) >> lfs->mdir_bits, + lfsr_mid_rid(lfs, lfs->grm.mids[1])); + } else if (lfsr_grm_count(lfs) == 1) { + LFS_DEBUG("Fixing grm %"PRId32".%"PRId32, + lfsr_mid_bid(lfs, lfs->grm.mids[0]) >> lfs->mdir_bits, + lfsr_mid_rid(lfs, lfs->grm.mids[0])); + } + + int err = lfsr_fs_fixgrm(lfs); + if (err) { + return err; + } + } + + // we need multiple passes because of potential mutation issues + for (int i = 0;; i++) { + // do we need to do anything? + if (!((lfsr_t_ismkconsistent(flags) && lfs->hasorphans) + || (lfsr_t_islookahead(flags) + && (lfs->lookahead.next > 0 || lfs->lookahead.size == 0)) + || (lfsr_t_iscompact(flags) && i == 0) + || (lfsr_t_isckmeta(flags) && i == 0) + || (lfsr_t_isckdata(flags) && i == 0))) { + break; + } + + if (lfsr_t_islookahead(flags)) { + lfs_alloc_shift(lfs); + } + + lfsr_traversal_t t = LFSR_TRAVERSAL(flags); + // note we need to be tracked for bshrub commits to work + lfsr_omdir_open(lfs, &t.o.o); + while (true) { + // let lfsr_mtree_gc do most of the work + int err = lfsr_mtree_gc(lfs, &t, + NULL, NULL); + if (err) { + if (err == LFS_ERR_NOENT) { + break; + } + lfsr_omdir_close(lfs, &t.o.o); + return err; + } + } + lfsr_omdir_close(lfs, &t.o.o); + + // no more orphans? + if (lfsr_t_ismkconsistent(t.o.o.flags)) { + LFS_ASSERT(!lfsr_f_isdirty(t.o.o.flags)); + lfs->hasorphans = false; + } + + // was lookahead scan successful? + if (lfsr_t_islookahead(t.o.o.flags) + && !lfsr_f_ismutated(t.o.o.flags)) { + LFS_ASSERT(!lfsr_f_isdirty(t.o.o.flags)); + lfs_alloc_markfree(lfs); + } + + // update flags, clear mutated/dirty + flags = t.o.o.flags & ~LFS_F_DIRTY & ~LFS_F_MUTATED; + } + + if (lfsr_t_ismkconsistent(flags)) { + LFS_ASSERT(lfsr_grm_count(lfs) == 0); + LFS_ASSERT(lfs->hasorphans == false); + } + if (lfsr_t_islookahead(flags)) { + LFS_ASSERT(lfs->lookahead.next == 0); + LFS_ASSERT(lfs->lookahead.size > 0); + } + return 0; +} + int lfsr_fs_grow(lfs_t *lfs, lfs_size_t block_count_) { // shrinking the filesystem is not supported @@ -12884,6 +12975,7 @@ int lfsr_traversal_open(lfs_t *lfs, lfsr_traversal_t *t, uint32_t flags) { LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_isckdata(flags)); // these flags are internal and shouldn't be provided by the user LFS_ASSERT(!lfsr_f_isdirty(flags)); + LFS_ASSERT(!lfsr_f_ismutated(flags)); // setup traversal state t->o.o.type = LFS_TYPE_TRAVERSAL; diff --git a/lfs.h b/lfs.h index 0e1eeadb..9eab41d5 100644 --- a/lfs.h +++ b/lfs.h @@ -183,6 +183,19 @@ enum lfs_traversal_flags { LFS_F_MUTATED = 0x4000, // Filesystem modified by traversal }; +// GC flags +enum lfs_gc_flags { + LFS_GC_MTREEONLY = 0x0010, // Only traverse the mtree + LFS_GC_MKCONSISTENT = 0x0020, // Make the filesystem consistent + LFS_GC_LOOKAHEAD = 0x0040, // Populate lookahead buffer + LFS_GC_COMPACT = 0x0080, // Compact metadata logs + LFS_GC_CKMETA = 0x0100, // Check metadata checksums + LFS_GC_CKDATA = 0x0200, // Check metadata + data checksums +// TODO +// LFS_GC_REPAIRMETA = 0x0400, // Repair metadata blocks +// LFS_GC_REPAIRDATA = 0x0800, // Repair metadata + data blocks +}; + // Configuration provided during initialization of the littlefs struct lfs_config { @@ -1114,6 +1127,20 @@ lfs_ssize_t lfsr_fs_size(lfs_t *lfs); int lfsr_fs_mkconsistent(lfs_t *lfs); #endif +#ifndef LFS_READONLY +// Attempt any janitorial work that may be pending. +// +// The exact janitorial work depends on the provided flags. Note that most +// of this work can also be accomplished incrementally via +// lfsr_traversal_read. +// +// Calling this function is not required, but may allow the offloading of +// expensive janitorial work to a less time-critical code path. +// +// Returns a negative error code on failure. +int lfsr_fs_gc(lfs_t *lfs, uint32_t flags); +#endif + #ifndef LFS_READONLY // Change the number of blocks used by the filesystem // diff --git a/tests/test_badblocks.toml b/tests/test_badblocks.toml index d1b1de15..f4e316e8 100644 --- a/tests/test_badblocks.toml +++ b/tests/test_badblocks.toml @@ -5,6 +5,7 @@ after = [ 'test_fwrite', 'test_forphans', 'test_traversal', + 'test_gc', ] diff --git a/tests/test_gc.toml b/tests/test_gc.toml new file mode 100644 index 00000000..18a3da09 --- /dev/null +++ b/tests/test_gc.toml @@ -0,0 +1,1515 @@ +# Test GC things +after = ['test_traversal'] + +# many/fuzz tests mixed with GC +# + +[cases.test_gc_spam_dir_many] +defines.MKCONSISTENT = [false, true] +defines.LOOKAHEAD = [false, true] +defines.COMPACT = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +# set compact thresh to minimum +defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] +code = ''' + // test creating directories + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // make this many directories + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + int err = lfsr_mkdir(&lfs, name); + assert(!err || (TEST_PLS && err == LFS_ERR_EXIST)); + + // gc! + lfsr_fs_gc(&lfs, + ((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0) + | ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0) + | ((COMPACT) ? LFS_GC_COMPACT : 0) + | ((CKMETA) ? LFS_GC_CKMETA : 0) + | ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // grm should be zero here + assert(lfs.grm_p[0] == 0); + + // check that our mkdir worked + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "dir%03x", i); + lfsr_dir_open(&lfs, &dir, name) => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + } + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_gc_spam_dir_fuzz] +defines.MKCONSISTENT = [false, true] +defines.LOOKAHEAD = [false, true] +defines.COMPACT = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +# set compact thresh to minimum +defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] +defines.OPS = '2*N' +defines.SEED = 42 +fuzz = 'SEED' +code = ''' + // test fuzz with dirs + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + lfs_size_t sim_size = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + // choose a pseudo-random op, either mkdir, remove, or rename + uint8_t op = TEST_PRNG(&prng) % 3; + + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number, truncate to 3 hexadecimals + lfs_size_t x = TEST_PRNG(&prng) % N; + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // do nothing + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + sim_size += 1; + sim[j] = x; + } + break; + } + } + + // create a directory here + char name[256]; + sprintf(name, "dir%03x", x); + int err = lfsr_mkdir(&lfs, name); + assert(!err || err == LFS_ERR_EXIST); + + } else if (op == 1) { + // choose a pseudo-random entry to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + sim_size -= 1; + + // remove this directory + char name[256]; + sprintf(name, "dir%03x", x); + lfsr_remove(&lfs, name) => 0; + + } else { + // choose a pseudo-random entry to rename, and a pseudo-random + // number to rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // already seen and not a noop? + if (k < sim_size && sim[k] == y && x != y) { + // just delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + sim_size -= 1; + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + sim[k] = y; + } + break; + } + } + + // rename this directory + char old_name[256]; + sprintf(old_name, "dir%03x", x); + char new_name[256]; + sprintf(new_name, "dir%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + + // gc! + lfsr_fs_gc(&lfs, + ((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0) + | ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0) + | ((COMPACT) ? LFS_GC_COMPACT : 0) + | ((CKMETA) ? LFS_GC_CKMETA : 0) + | ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // grm should be zero here + assert(lfs.grm_p[0] == 0); + + // test that our directories match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + char name2[256]; + sprintf(name2, "dir%03x", sim[j]); + assert(strcmp(info.name, name2) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "dir%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + } + + // clean up sim/lfs + free(sim); + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_gc_spam_file_many] +defines.MKCONSISTENT = [false, true] +defines.LOOKAHEAD = [false, true] +defines.COMPACT = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +# set compact thresh to minimum +defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +if = '(SIZE*N)/BLOCK_SIZE <= 32' +code = ''' + // test creating files + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create this many files + uint32_t prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + char name[256]; + sprintf(name, "amethyst%03x", i); + + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // gc! + lfsr_fs_gc(&lfs, + ((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0) + | ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0) + | ((COMPACT) ? LFS_GC_COMPACT : 0) + | ((CKMETA) ? LFS_GC_CKMETA : 0) + | ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our writes worked + prng = 42; + for (lfs_size_t i = 0; i < N; i++) { + // check with stat + char name[256]; + sprintf(name, "amethyst%03x", i); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + + // try reading the file, note we reset prng above + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + lfsr_file_t file; + uint8_t rbuf[SIZE]; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_gc_spam_file_fuzz] +defines.MKCONSISTENT = [false, true] +defines.LOOKAHEAD = [false, true] +defines.COMPACT = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +# set compact thresh to minimum +defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = '2*N' +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +code = ''' + // test fuzz with files + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 3; + + // creating a new file? + if (op == 0 || sim_size == 0) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + // associate each file with a prng that generates its contents + uint32_t wprng = TEST_PRNG(&prng); + + // insert into our sim + for (lfs_size_t j = 0;; j++) { + if (j >= sim_size || sim[j] >= x) { + // already seen? + if (j < sim_size && sim[j] == x) { + // new prng + sim_prngs[j] = wprng; + } else { + // insert + memmove(&sim[j+1], &sim[j], + (sim_size-j)*sizeof(lfs_size_t)); + memmove(&sim_prngs[j+1], &sim_prngs[j], + (sim_size-j)*sizeof(uint32_t)); + sim_size += 1; + sim[j] = x; + sim_prngs[j] = wprng; + } + break; + } + } + + // create a file here + char name[256]; + sprintf(name, "amethyst%03x", x); + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; + lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // deleting a file? + } else if (op == 1) { + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // delete this file + char name[256]; + sprintf(name, "amethyst%03x", x); + lfsr_remove(&lfs, name) => 0; + + // renaming a file? + } else { + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; + } + break; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "amethyst%03x", x); + char new_name[256]; + sprintf(new_name, "amethyst%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + + // gc! + lfsr_fs_gc(&lfs, + ((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0) + | ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0) + | ((COMPACT) ? LFS_GC_COMPACT : 0) + | ((CKMETA) ? LFS_GC_CKMETA : 0) + | ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0; + } + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check that our files match our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // check the file contents + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "amethyst%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_gc_spam_fwrite_fuzz] +defines.MKCONSISTENT = [false, true] +defines.LOOKAHEAD = [false, true] +defines.COMPACT = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +# set compact thresh to minimum +defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' +defines.OPS = 20 +defines.SIZE = [ + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +# chunk is more an upper limit here +defines.CHUNK = [32, 8, 1] +# INIT=0 => no init +# INIT=1 => fill with data +# INIT=2 => truncate to size +defines.INIT = [0, 1, 2] +defines.SYNC = [false, true] +defines.SEED = 42 +fuzz = 'SEED' +if = [ + 'CHUNK <= SIZE', + # this just saves testing time + 'SIZE <= 4*1024*FRAGMENT_SIZE', +] +code = ''' + // test with complex file writes + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "hello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + // simulate our file in ram + uint8_t sim[SIZE]; + lfs_off_t size; + uint32_t prng = SEED; + if (INIT == 0) { + memset(sim, 0, SIZE); + size = 0; + } else if (INIT == 1) { + for (lfs_size_t i = 0; i < SIZE; i++) { + sim[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE; + size = SIZE; + } else { + memset(sim, 0, SIZE); + lfsr_file_truncate(&lfs, &file, SIZE) => 0; + size = SIZE; + } + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + for (lfs_size_t i = 0; i < OPS; i++) { + // choose a random location + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + // and a random size, up to the chunk size + lfs_size_t chunk = lfs_min( + (TEST_PRNG(&prng) % (CHUNK+1-1)) + 1, + SIZE - off); + + // update sim + for (lfs_size_t j = 0; j < chunk; j++) { + sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); + } + size = lfs_max(size, off+chunk); + + // update file + lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off; + lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk; + + // sync? + if (SYNC) { + lfsr_file_sync(&lfs, &file) => 0; + } + + // gc! + lfsr_fs_gc(&lfs, + ((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0) + | ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0) + | ((COMPACT) ? LFS_GC_COMPACT : 0) + | ((CKMETA) ? LFS_GC_CKMETA : 0) + | ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0; + } + + lfsr_file_close(&lfs, &file) => 0; + + for (int remount = 0; remount < 2; remount++) { + // remount? + if (remount) { + lfsr_unmount(&lfs) => 0; + lfsr_mount(&lfs, CFG) => 0; + } + + // check our file with stat + struct lfs_info info; + lfsr_stat(&lfs, "hello", &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == size); + + // and with dir read + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "hello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == size); + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + // try reading our file + lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0; + // is size correct? + lfsr_file_size(&lfs, &file) => size; + // try reading + uint8_t rbuf[2*SIZE]; + memset(rbuf, 0xaa, 2*SIZE); + lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size; + // does our file match our simulation? + assert(memcmp(rbuf, sim, size) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_gc_spam_orphanzombie_fuzz] +defines.MKCONSISTENT = [false, true] +defines.LOOKAHEAD = [false, true] +defines.COMPACT = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +# set compact thresh to minimum +defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = '2*N' +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +code = ''' + // test with orphans, zombies, etc + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + lfs_size_t sim_size = 0; + + typedef struct sim_file { + lfs_size_t x; + bool orphan; + bool zombie; + uint32_t prng; + lfsr_file_t file; + } sim_file_t; + sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); + lfs_size_t sim_file_count = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + nonsense:; + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 5; + + // open a new file? + if (op == 0) { + if (sim_file_count >= N) { + goto nonsense; + } + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // already exists? + bool orphan = true; + uint32_t wprng = 0; + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim[j] == x) { + orphan = false; + wprng = sim_prngs[j]; + break; + } + } + // choose a random seed if we don't exist + if (orphan) { + wprng = TEST_PRNG(&prng); + } + + // open in our sim + lfs_size_t j = sim_file_count; + sim_files[j] = malloc(sizeof(sim_file_t)); + sim_files[j]->x = x; + sim_files[j]->orphan = orphan; + sim_files[j]->zombie = false; + sim_files[j]->prng = wprng; + sim_file_count++; + + // open the actual file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_file_open(&lfs, &sim_files[j]->file, name, + LFS_O_RDWR | LFS_O_CREAT) => 0; + + // write some initial data if we don't exist + if (orphan) { + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + } + + // write/rewrite a file? + } else if (op == 1) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + lfs_size_t x = sim_files[j]->x; + // choose a random seed + uint32_t wprng = TEST_PRNG(&prng); + + // update sim + sim_files[j]->prng = wprng; + if (!sim_files[j]->zombie) { + // insert into our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + // new prng + sim_prngs[k] = wprng; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = wprng; + } + break; + } + } + + // update related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x && !sim_files[k]->zombie) { + sim_files[k]->orphan = false; + sim_files[k]->prng = wprng; + } + } + } + + // write to the file + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + lfsr_file_sync(&lfs, &sim_files[j]->file) + => (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT; + + // close a file? + } else if (op == 2) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + + // this doesn't really test anything, but if we don't close + // files eventually everything will end up zombies + + // close the file without affected disk + lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + // clobber closed files to try to catch lingering references + memset(&sim_files[j]->file, 0xcc, sizeof(lfsr_file_t)); + + // remove from list + free(sim_files[j]); + sim_files[j] = sim_files[sim_file_count-1]; + sim_file_count -= 1; + + // remove a file? + } else if (op == 3) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // delete this file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_remove(&lfs, name) => 0; + + // rename a file? + } else if (op == 4) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + sim[k] = y; + sim_prngs[k] = wprng; + } + break; + } + } + + // update any related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + // move source files + if (sim_files[k]->x == x) { + sim_files[k]->x = y; + + // mark target files as zombied + } else if (sim_files[k]->x == y) { + sim_files[k]->zombie = true; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "batman%03x", x); + char new_name[256]; + sprintf(new_name, "batman%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + } + + // gc! + lfsr_fs_gc(&lfs, + ((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0) + | ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0) + | ((COMPACT) ? LFS_GC_COMPACT : 0) + | ((CKMETA) ? LFS_GC_CKMETA : 0) + | ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0; + } + + // check that disk matches our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + // check that our file handles match our simulation + for (lfs_size_t j = 0; j < sim_file_count; j++) { + uint32_t wprng = sim_files[j]->prng; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + for (lfs_size_t j = 0; j < sim_file_count; j++) { + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + free(sim_files[j]); + } + free(sim_files); + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_gc_spam_orphanzombiedir_fuzz] +defines.MKCONSISTENT = [false, true] +defines.LOOKAHEAD = [false, true] +defines.COMPACT = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +# set compact thresh to minimum +defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' +defines.N = [1, 2, 4, 8, 16, 32, 64] +defines.OPS = '2*N' +defines.SIZE = [ + '0', + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.SEED = 42 +fuzz = 'SEED' +if = '(SIZE*N)/BLOCK_SIZE <= 16' +code = ''' + // test with orphans, zombies, dirs, etc + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // set up a simulation to compare against + lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); + uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); + bool *sim_isdirs = malloc(N*sizeof(bool)); + lfs_size_t sim_size = 0; + + typedef struct sim_file { + lfs_size_t x; + bool orphan; + bool zombie; + uint32_t prng; + lfsr_file_t file; + } sim_file_t; + sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); + lfs_size_t sim_file_count = 0; + + uint32_t prng = SEED; + for (lfs_size_t i = 0; i < OPS; i++) { + nonsense:; + // choose which operation to do + uint8_t op = TEST_PRNG(&prng) % 8; + + // open a new file? + if (op == 0) { + if (sim_file_count >= N) { + goto nonsense; + } + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // already exists? + bool orphan = true; + uint32_t wprng = 0; + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim[j] == x) { + if (sim_isdirs[j]) { + goto nonsense; + } + orphan = false; + wprng = sim_prngs[j]; + break; + } + } + // choose a random seed if we don't exist + if (orphan) { + wprng = TEST_PRNG(&prng); + } + + // open in our sim + lfs_size_t j = sim_file_count; + sim_files[j] = malloc(sizeof(sim_file_t)); + sim_files[j]->x = x; + sim_files[j]->orphan = orphan; + sim_files[j]->zombie = false; + sim_files[j]->prng = wprng; + sim_file_count++; + + // open the actual file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_file_open(&lfs, &sim_files[j]->file, name, + LFS_O_RDWR | LFS_O_CREAT) => 0; + + // write some initial data if we don't exist + if (orphan) { + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + } + + // write/rewrite a file? + } else if (op == 1) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + lfs_size_t x = sim_files[j]->x; + // choose a random seed + uint32_t wprng = TEST_PRNG(&prng); + + // update sim + sim_files[j]->prng = wprng; + if (!sim_files[j]->zombie) { + // insert into our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + // new prng + sim_prngs[k] = wprng; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = wprng; + sim_isdirs[k] = false; + } + break; + } + } + + // update related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x && !sim_files[k]->zombie) { + sim_files[k]->orphan = false; + sim_files[k]->prng = wprng; + } + } + } + + // write to the file + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t wbuf[SIZE]; + for (lfs_size_t k = 0; k < SIZE; k++) { + wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); + } + lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; + lfsr_file_sync(&lfs, &sim_files[j]->file) + => (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT; + + // close a file? + } else if (op == 2) { + if (sim_file_count == 0) { + goto nonsense; + } + // choose a random file handle + lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; + + // this doesn't really test anything, but if we don't close + // files eventually everything will end up zombies + + // close the file without affected disk + lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + // clobber closed files to try to catch lingering references + memset(&sim_files[j]->file, 0xcc, sizeof(lfsr_file_t)); + + // remove from list + free(sim_files[j]); + sim_files[j] = sim_files[sim_file_count-1]; + sim_file_count -= 1; + + // remove a file? + } else if (op == 3) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to delete + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + + // delete from our sim + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + sim_size -= 1; + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // delete this file + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_remove(&lfs, name) => 0; + + // rename a file? + } else if (op == 4) { + if (sim_size == 0) { + goto nonsense; + } + // choose a random file to rename, and a random number to + // rename to + lfs_size_t j = TEST_PRNG(&prng) % sim_size; + lfs_size_t x = sim[j]; + lfs_size_t y = TEST_PRNG(&prng) % N; + uint32_t wprng = sim_prngs[j]; + bool isdir = sim_isdirs[j]; + + // update our sim + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= y) { + // renaming and replacing + if (k < sim_size && sim[k] == y && x != y) { + // type mismatch? + if (sim_isdirs[k] != isdir) { + goto nonsense; + } + + // delete the original entry + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + sim_size -= 1; + if (k > j) { + k -= 1; + } + // update the prng + sim_prngs[k] = wprng; + // just renaming + } else { + // first delete + memmove(&sim[j], &sim[j+1], + (sim_size-(j+1))*sizeof(lfs_size_t)); + memmove(&sim_prngs[j], &sim_prngs[j+1], + (sim_size-(j+1))*sizeof(uint32_t)); + memmove(&sim_isdirs[j], &sim_isdirs[j+1], + (sim_size-(j+1))*sizeof(bool)); + if (k > j) { + k -= 1; + } + // then insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim[k] = y; + sim_prngs[k] = wprng; + sim_isdirs[k] = isdir; + } + break; + } + } + + // update any related sim files + for (lfs_size_t k = 0; k < sim_file_count; k++) { + // move source files + if (sim_files[k]->x == x) { + sim_files[k]->x = y; + + // mark target files as zombied + } else if (sim_files[k]->x == y) { + sim_files[k]->zombie = true; + } + } + + // rename this file + char old_name[256]; + sprintf(old_name, "batman%03x", x); + char new_name[256]; + sprintf(new_name, "batman%03x", y); + lfsr_rename(&lfs, old_name, new_name) => 0; + + // toss a directory into the mix + } else if (op == 5) { + // choose a pseudo-random number + lfs_size_t x = TEST_PRNG(&prng) % N; + + // insert into our sim, use negative numbers for dirs + for (lfs_size_t k = 0;; k++) { + if (k >= sim_size || sim[k] >= x) { + // already seen? + if (k < sim_size && sim[k] == x) { + goto nonsense; + } else { + // insert + memmove(&sim[k+1], &sim[k], + (sim_size-k)*sizeof(lfs_size_t)); + memmove(&sim_prngs[k+1], &sim_prngs[k], + (sim_size-k)*sizeof(uint32_t)); + memmove(&sim_isdirs[k+1], &sim_isdirs[k], + (sim_size-k)*sizeof(bool)); + sim_size += 1; + sim[k] = x; + sim_prngs[k] = 0; + sim_isdirs[k] = true; + } + break; + } + } + + // mark any related sim files as zombied + for (lfs_size_t k = 0; k < sim_file_count; k++) { + if (sim_files[k]->x == x) { + sim_files[k]->zombie = true; + } + } + + // make the directory + char name[256]; + sprintf(name, "batman%03x", x); + lfsr_mkdir(&lfs, name) => 0; + } + + // gc! + lfsr_fs_gc(&lfs, + ((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0) + | ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0) + | ((COMPACT) ? LFS_GC_COMPACT : 0) + | ((CKMETA) ? LFS_GC_CKMETA : 0) + | ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0; + } + + // check that disk matches our simulation + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + struct lfs_info info; + lfsr_stat(&lfs, name, &info) => 0; + assert(strcmp(info.name, name) == 0); + if (sim_isdirs[j]) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + } + + lfsr_dir_t dir; + lfsr_dir_open(&lfs, &dir, "/") => 0; + struct lfs_info info; + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, ".") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, "..") == 0); + assert(info.type == LFS_TYPE_DIR); + assert(info.size == 0); + for (lfs_size_t j = 0; j < sim_size; j++) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_dir_read(&lfs, &dir, &info) => 0; + assert(strcmp(info.name, name) == 0); + if (sim_isdirs[j]) { + assert(info.type == LFS_TYPE_DIR); + } else { + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + } + lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; + lfsr_dir_close(&lfs, &dir) => 0; + + for (lfs_size_t j = 0; j < sim_size; j++) { + if (sim_isdirs[j]) { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => LFS_ERR_ISDIR; + + } else { + char name[256]; + sprintf(name, "batman%03x", sim[j]); + lfsr_file_t file; + lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; + + uint32_t wprng = sim_prngs[j]; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + } + + // check that our file handles match our simulation + for (lfs_size_t j = 0; j < sim_file_count; j++) { + uint32_t wprng = sim_files[j]->prng; + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); + } + + lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + + // clean up sim/lfs + free(sim); + free(sim_prngs); + for (lfs_size_t j = 0; j < sim_file_count; j++) { + lfsr_file_close(&lfs, &sim_files[j]->file) => 0; + free(sim_files[j]); + } + free(sim_files); + lfsr_unmount(&lfs) => 0; +''' + diff --git a/tests/test_powerloss.toml b/tests/test_powerloss.toml index 4c9b6a7d..e576bb18 100644 --- a/tests/test_powerloss.toml +++ b/tests/test_powerloss.toml @@ -9,6 +9,7 @@ after = [ 'test_files', 'test_forphans', 'test_traversal', + 'test_gc', ]