# Test GC things # most of the GC logic is tested in test_traversal, we just test # GC-API specific things here after = ['test_traversal'] # test that lookahead can make progress in isolation [cases.test_gc_lookahead_progress] defines.CKMETA = [false, true] defines.CKDATA = [false, true] defines.GC_FLAGS = ''' LFS_GC_LOOKAHEAD | ((CKMETA) ? LFS_GC_CKMETA : 0) | ((CKDATA) ? LFS_GC_CKDATA : 0) ''' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] ifdef = 'LFS_GC' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint32_t prng = 42; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "spider", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // expect dirty initial state or else our test doesn't work struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags & LFS_I_CANLOOKAHEAD); assert(lfs.omdirs != &lfs.gc.t.o.o); // run GC until we make progress for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops LFS_ASSERT(i < 2*BLOCK_COUNT); lfsr_gc(&lfs) => 0; lfsr_fs_stat(&lfs, &fsinfo) => 0; if (!(fsinfo.flags & LFS_I_CANLOOKAHEAD)) { break; } } // check the file contents lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; ''' # test that lookahead dirtying still works with the GC API [cases.test_gc_lookahead_mutation] defines.CKMETA = [false, true] defines.CKDATA = [false, true] defines.GC_FLAGS = ''' LFS_GC_LOOKAHEAD | ((CKMETA) ? LFS_GC_CKMETA : 0) | ((CKDATA) ? LFS_GC_CKDATA : 0) ''' defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] # we need something to keep the traversal running if = 'CKMETA || CKDATA' ifdef = 'LFS_GC' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint32_t prng = 42; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "spider", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // expect dirty initial state or else our test doesn't work struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags & LFS_I_CANLOOKAHEAD); assert(lfs.omdirs != &lfs.gc.t.o.o); // run GC one step lfsr_gc(&lfs) => 0; assert(lfs.omdirs == &lfs.gc.t.o.o); // mutate the filesystem lfsr_file_open(&lfs, &file, "spider", LFS_O_WRONLY | LFS_O_TRUNC) => 0; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // run GC until our traversal is done while (lfs.omdirs == &lfs.gc.t.o.o) { lfsr_gc(&lfs) => 0; } // we should _not_ make progress lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags & LFS_I_CANLOOKAHEAD); // check the file contents lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; ''' # test that compact can make progress in isolation [cases.test_gc_compact_progress] defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] defines.GC_FLAGS = ''' LFS_GC_COMPACT | ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0) | ((CKMETA) ? LFS_GC_CKMETA : 0) | ((CKDATA) ? LFS_GC_CKDATA : 0) ''' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] ifdef = 'LFS_GC' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint32_t prng = 42; // write to our mdir until >gc_compact_thresh full lfsr_file_t file; lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // hack, don't use the internals like this uint8_t wbuf[SIZE]; while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) { lfsr_file_rewind(&lfs, &file) => 0; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &file) => 0; } // expect dirty initial state or else our test doesn't work struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags & LFS_I_UNCOMPACTED); assert(lfs.omdirs != &lfs.gc.t.o.o); // run GC until we make progress for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops LFS_ASSERT(i < 2*BLOCK_COUNT); lfsr_gc(&lfs) => 0; lfsr_fs_stat(&lfs, &fsinfo) => 0; if (!(fsinfo.flags & LFS_I_UNCOMPACTED)) { break; } } // mdir should have been compacted assert((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH); // check we can still read the file for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0; } lfsr_file_rewind(&lfs, &file) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; ''' # test that compact dirtying still works with the GC API [cases.test_gc_compact_mutation] defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] defines.GC_FLAGS = ''' LFS_GC_COMPACT | ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0) | ((CKMETA) ? LFS_GC_CKMETA : 0) | ((CKDATA) ? LFS_GC_CKDATA : 0) ''' # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] # we need something to keep the traversal running if = 'CKMETA || CKDATA' ifdef = 'LFS_GC' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint32_t prng = 42; // write to our mdir until >gc_compact_thresh full lfsr_file_t file; lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // hack, don't use the internals like this uint8_t wbuf[SIZE]; while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) { lfsr_file_rewind(&lfs, &file) => 0; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &file) => 0; } // expect dirty initial state or else our test doesn't work struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags & LFS_I_UNCOMPACTED); assert(lfs.omdirs != &lfs.gc.t.o.o); // run GC one traversal + one step while (true) { lfsr_gc(&lfs) => 0; // internal traversal done? if (lfs.omdirs != &lfs.gc.t.o.o) { break; } } lfsr_gc(&lfs) => 0; assert(lfs.omdirs == &lfs.gc.t.o.o); // mutate the filesystem lfsr_file_rewind(&lfs, &file) => 0; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &file) => 0; // run GC until our traversal is done (twice for compact) while (lfs.omdirs == &lfs.gc.t.o.o) { lfsr_gc(&lfs) => 0; } // we should _not_ make progress lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags & LFS_I_UNCOMPACTED); // check we can still read the file for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0; } lfsr_file_rewind(&lfs, &file) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; ''' # test that mkconsistent can make progress in isolation [cases.test_gc_mkconsistent_progress] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] defines.GC_FLAGS = ''' LFS_GC_MKCONSISTENT | ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0) | ((COMPACT) ? LFS_GC_COMPACT : 0) | ((CKMETA) ? LFS_GC_CKMETA : 0) | ((CKDATA) ? LFS_GC_CKDATA : 0) ''' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] defines.SIZE = 'FILE_BUFFER_SIZE/2' # <=2 => grm-able # >2 => requires orphans defines.ORPHANS = [1, 2, 3, 100] ifdef = 'LFS_GC' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint32_t prng = 42; // create two files lfsr_file_t file1; lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf1[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; lfsr_file_sync(&lfs, &file1) => 0; lfsr_file_t file2; lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf2[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; lfsr_file_sync(&lfs, &file2) => 0; // create this many orphaned files // // anytime we close a not-yet-created desync file, we create an // orphan, but note we need these to be different files, and we need // to close them after all open calls, otherwise we just end up with // one orphan (littlefs is eager to clean up orphans) // lfsr_file_t orphans[ORPHANS]; for (lfs_size_t i = 0; i < ORPHANS; i++) { char name[256]; sprintf(name, "jellyfish%03x", i); lfsr_file_open(&lfs, &orphans[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; } for (lfs_size_t i = 0; i < ORPHANS; i++) { lfsr_file_close(&lfs, &orphans[i]) => 0; } // expect dirty initial state or else our test doesn't work struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags & LFS_I_INCONSISTENT); assert(lfs.omdirs != &lfs.gc.t.o.o); // run GC until we make progress for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops LFS_ASSERT(i < 2*BLOCK_COUNT); lfsr_gc(&lfs) => 0; lfsr_fs_stat(&lfs, &fsinfo) => 0; if (!(fsinfo.flags & LFS_I_INCONSISTENT)) { break; } } // check we can still read the files for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_file_close(&lfs, &file1) => 0; lfsr_file_close(&lfs, &file2) => 0; lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0; lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0; } lfsr_file_rewind(&lfs, &file1) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf1, SIZE) == 0); lfsr_file_rewind(&lfs, &file2) => 0; lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf2, SIZE) == 0); } lfsr_file_close(&lfs, &file1) => 0; lfsr_file_close(&lfs, &file2) => 0; lfsr_unmount(&lfs) => 0; ''' # test that an explicit lfsr_fs_mkconsistent call also works, this calls # the same logic internally [cases.test_gc_mkconsistent_explicit] defines.SIZE = 'FILE_BUFFER_SIZE/2' # <=2 => grm-able # >2 => requires orphans defines.ORPHANS = [1, 2, 3, 100] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint32_t prng = 42; // create two files lfsr_file_t file1; lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf1[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; lfsr_file_sync(&lfs, &file1) => 0; lfsr_file_t file2; lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf2[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; lfsr_file_sync(&lfs, &file2) => 0; // create this many orphaned files // // anytime we close a not-yet-created desync file, we create an // orphan, but note we need these to be different files, and we need // to close them after all open calls, otherwise we just end up with // one orphan (littlefs is eager to clean up orphans) // lfsr_file_t orphans[ORPHANS]; for (lfs_size_t i = 0; i < ORPHANS; i++) { char name[256]; sprintf(name, "jellyfish%03x", i); lfsr_file_open(&lfs, &orphans[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; } for (lfs_size_t i = 0; i < ORPHANS; i++) { lfsr_file_close(&lfs, &orphans[i]) => 0; } // expect dirty initial state or else our test doesn't work struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags & LFS_I_INCONSISTENT); #ifdef LFS_GC assert(lfs.omdirs != &lfs.gc.t.o.o); #endif // call lfsr_fs_mkconsistent lfsr_fs_mkconsistent(&lfs) => 0; // we should have made progress lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(!(fsinfo.flags & LFS_I_INCONSISTENT)); // check we can still read the files for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_file_close(&lfs, &file1) => 0; lfsr_file_close(&lfs, &file2) => 0; lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0; lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0; } lfsr_file_rewind(&lfs, &file1) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf1, SIZE) == 0); lfsr_file_rewind(&lfs, &file2) => 0; lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf2, SIZE) == 0); } lfsr_file_close(&lfs, &file1) => 0; lfsr_file_close(&lfs, &file2) => 0; lfsr_unmount(&lfs) => 0; ''' # test that mkconsistent dirtying still works with the GC API [cases.test_gc_mkconsistent_mutation] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] defines.GC_FLAGS = ''' LFS_GC_MKCONSISTENT | ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0) | ((COMPACT) ? LFS_GC_COMPACT : 0) | ((CKMETA) ? LFS_GC_CKMETA : 0) | ((CKDATA) ? LFS_GC_CKDATA : 0) ''' defines.SIZE = 'FILE_BUFFER_SIZE/2' # <=2 => grm-able # >2 => requires orphans defines.ORPHANS = [3, 100] # we need something to keep the traversal running if = 'CKMETA || CKDATA' ifdef = 'LFS_GC' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint32_t prng = 42; // create two files lfsr_file_t file1; lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf1[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE; lfsr_file_sync(&lfs, &file1) => 0; lfsr_file_t file2; lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf2[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE; lfsr_file_sync(&lfs, &file2) => 0; // create at least 3 orphans so GC will start lfsr_file_t orphans[ORPHANS]; for (lfs_size_t i = 0; i < 3; i++) { char name[256]; sprintf(name, "jellyfish%03x", i); lfsr_file_open(&lfs, &orphans[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; } for (lfs_size_t i = 0; i < 3; i++) { lfsr_file_close(&lfs, &orphans[i]) => 0; } // run GC one step assert(lfs.omdirs != &lfs.gc.t.o.o); lfsr_gc(&lfs) => 0; assert(lfs.omdirs == &lfs.gc.t.o.o); // create the rest of the orphans after GC has started for (lfs_size_t i = 0; i < ORPHANS; i++) { char name[256]; sprintf(name, "jellyfish%03x", i); lfsr_file_open(&lfs, &orphans[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; } for (lfs_size_t i = 0; i < ORPHANS; i++) { lfsr_file_close(&lfs, &orphans[i]) => 0; } // we should now have dirty state struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags & LFS_I_INCONSISTENT); // run GC until our traversal is done while (lfs.omdirs == &lfs.gc.t.o.o) { lfsr_gc(&lfs) => 0; } // we should _not_ make progress lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags & LFS_I_INCONSISTENT); // check we can still read the files for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_file_close(&lfs, &file1) => 0; lfsr_file_close(&lfs, &file2) => 0; lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0; lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0; } lfsr_file_rewind(&lfs, &file1) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf1, SIZE) == 0); lfsr_file_rewind(&lfs, &file2) => 0; lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf2, SIZE) == 0); } lfsr_file_close(&lfs, &file1) => 0; lfsr_file_close(&lfs, &file2) => 0; lfsr_unmount(&lfs) => 0; ''' # test we can detect at least fully clobbered blocks # # these are tested more thoroughly in test_ck [cases.test_gc_ckmeta] defines.GC_FLAGS = 'LFS_GC_CKMETA' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] if = '(SIZE*N)/BLOCK_SIZE <= 32' ifdef = 'LFS_GC' code = ''' lfs_block_t i = 0; while (true) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting filesystem uint32_t prng = 42; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "squid%03x", i); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; } // traverse to find blocks lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t k = 0; for (lfs_block_t j = 0;; j++) { assert(j < 2*BLOCK_COUNT); struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; goto done; } // this gets a bit tricky be cause we need to clobber both // blocks in mdir pairs if (tinfo.btype == LFS_BTYPE_MDIR || tinfo.btype == LFS_BTYPE_BTREE) { if (k == i || k == i+1) { // clobber this block printf("clobbering 0x%x\n", tinfo.block); uint8_t clobber_buf[BLOCK_SIZE]; memset(clobber_buf, 0xcc, BLOCK_SIZE); CFG->erase(CFG, tinfo.block) => 0; CFG->prog(CFG, tinfo.block, 0, clobber_buf, BLOCK_SIZE) => 0; if (tinfo.btype != LFS_BTYPE_MDIR || k == i+1) { i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1; lfsr_traversal_close(&lfs, &t) => 0; goto clobbered; } } k += 1; } } clobbered:; // running lfsr_gc should eventually find the clobbered block for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops LFS_ASSERT(i < 2*BLOCK_COUNT); int err = lfsr_gc(&lfs); assert(!err || err == LFS_ERR_CORRUPT); // found it if (err == LFS_ERR_CORRUPT) { break; } } lfsr_unmount(&lfs) => 0; } done:; ''' [cases.test_gc_ckdata] defines.GC_FLAGS = 'LFS_GC_CKDATA' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] if = '(SIZE*N)/BLOCK_SIZE <= 32' ifdef = 'LFS_GC' code = ''' lfs_block_t i = 0; while (true) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting filesystem uint32_t prng = 42; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "squid%03x", i); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; } // traverse to find blocks lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t k = 0; for (lfs_block_t j = 0;; j++) { assert(j < 2*BLOCK_COUNT); struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; goto done; } // this gets a bit tricky be cause we need to clobber both // blocks in mdir pairs if (tinfo.btype == LFS_BTYPE_MDIR || tinfo.btype == LFS_BTYPE_BTREE || tinfo.btype == LFS_BTYPE_DATA) { if (k == i || k == i+1) { // clobber this block printf("clobbering 0x%x\n", tinfo.block); uint8_t clobber_buf[BLOCK_SIZE]; memset(clobber_buf, 0xcc, BLOCK_SIZE); CFG->erase(CFG, tinfo.block) => 0; CFG->prog(CFG, tinfo.block, 0, clobber_buf, BLOCK_SIZE) => 0; if (tinfo.btype != LFS_BTYPE_MDIR || k == i+1) { i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1; lfsr_traversal_close(&lfs, &t) => 0; goto clobbered; } } k += 1; } } clobbered:; // running lfsr_gc should eventually find the clobbered block // // note LFS_GC_CKDATA implies LFS_GC_CKMETA for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops LFS_ASSERT(i < 2*BLOCK_COUNT); int err = lfsr_gc(&lfs); assert(!err || err == LFS_ERR_CORRUPT); // found it if (err == LFS_ERR_CORRUPT) { break; } } lfsr_unmount(&lfs) => 0; } done:; ''' # test that our explicit functions (lfsr_fs_ckmeta/ckdata) work as well, # these call the same logic internally [cases.test_gc_ckmeta_explicit] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] if = '(SIZE*N)/BLOCK_SIZE <= 32' code = ''' lfs_block_t i = 0; while (true) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting filesystem uint32_t prng = 42; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "squid%03x", i); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; } // traverse to find blocks lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t k = 0; for (lfs_block_t j = 0;; j++) { assert(j < 2*BLOCK_COUNT); struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; goto done; } // this gets a bit tricky be cause we need to clobber both // blocks in mdir pairs if (tinfo.btype == LFS_BTYPE_MDIR || tinfo.btype == LFS_BTYPE_BTREE) { if (k == i || k == i+1) { // clobber this block printf("clobbering 0x%x\n", tinfo.block); uint8_t clobber_buf[BLOCK_SIZE]; memset(clobber_buf, 0xcc, BLOCK_SIZE); CFG->erase(CFG, tinfo.block) => 0; CFG->prog(CFG, tinfo.block, 0, clobber_buf, BLOCK_SIZE) => 0; if (tinfo.btype != LFS_BTYPE_MDIR || k == i+1) { i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1; lfsr_traversal_close(&lfs, &t) => 0; goto clobbered; } } k += 1; } } clobbered:; // lfsr_fs_ckmeta should find the clobbered block lfsr_fs_ckmeta(&lfs) => LFS_ERR_CORRUPT; lfsr_unmount(&lfs) => 0; } done:; ''' [cases.test_gc_ckdata_explicit] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] if = '(SIZE*N)/BLOCK_SIZE <= 32' code = ''' lfs_block_t i = 0; while (true) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting filesystem uint32_t prng = 42; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "squid%03x", i); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; } // traverse to find blocks lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t k = 0; for (lfs_block_t j = 0;; j++) { assert(j < 2*BLOCK_COUNT); struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; goto done; } // this gets a bit tricky be cause we need to clobber both // blocks in mdir pairs if (tinfo.btype == LFS_BTYPE_MDIR || tinfo.btype == LFS_BTYPE_BTREE || tinfo.btype == LFS_BTYPE_DATA) { if (k == i || k == i+1) { // clobber this block printf("clobbering 0x%x\n", tinfo.block); uint8_t clobber_buf[BLOCK_SIZE]; memset(clobber_buf, 0xcc, BLOCK_SIZE); CFG->erase(CFG, tinfo.block) => 0; CFG->prog(CFG, tinfo.block, 0, clobber_buf, BLOCK_SIZE) => 0; if (tinfo.btype != LFS_BTYPE_MDIR || k == i+1) { i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1; lfsr_traversal_close(&lfs, &t) => 0; goto clobbered; } } k += 1; } } clobbered:; // lfsr_fs_ckdata should find the clobbered block lfsr_fs_ckdata(&lfs) => LFS_ERR_CORRUPT; lfsr_unmount(&lfs) => 0; } done:; ''' # test we can detect fully clobbered blocks after a ck pass, if we call # lfsr_gc_unck [cases.test_gc_ckmeta_unck] # AFTER=0 => after running lfsr_gc once # AFTER=1 => after running lfsr_gc to completion # AFTER=2 => after lfsr_fs_ckmeta # AFTER=3 => after remounting with LFS_M_CKMETA defines.AFTER = [0, 1, 2, 3] defines.GC_FLAGS = 'LFS_GC_CKMETA' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] if = '(SIZE*N)/BLOCK_SIZE <= 32' ifdef = 'LFS_GC' code = ''' lfs_block_t i = 0; while (true) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting filesystem uint32_t prng = 42; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "squid%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; } // run lfsr_gc before clobbering, this should not find anything // run lfsr_gc once if (AFTER == 0) { lfsr_gc(&lfs) => 0; // run lfsr_gc to completion } else if (AFTER == 1) { while (true) { struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; if (!(fsinfo.flags & LFS_I_CANCKMETA)) { break; } lfsr_gc(&lfs) => 0; } // run lfsr_fs_ckmeta } else if (AFTER == 2) { lfsr_fs_ckmeta(&lfs) => 0; struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(!(fsinfo.flags & LFS_I_CANCKMETA)); // remount with LFS_M_CKMETA } else if (AFTER == 3) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKMETA, CFG) => 0; struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(!(fsinfo.flags & LFS_I_CANCKMETA)); } else { assert(false); } // traverse to find blocks lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t k = 0; for (lfs_block_t j = 0;; j++) { assert(j < 2*BLOCK_COUNT); struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; goto done; } // this gets a bit tricky be cause we need to clobber both // blocks in mdir pairs if (tinfo.btype == LFS_BTYPE_MDIR || tinfo.btype == LFS_BTYPE_BTREE) { if (k == i || k == i+1) { // clobber this block printf("clobbering 0x%x\n", tinfo.block); uint8_t clobber_buf[BLOCK_SIZE]; memset(clobber_buf, 0xcc, BLOCK_SIZE); CFG->erase(CFG, tinfo.block) => 0; CFG->prog(CFG, tinfo.block, 0, clobber_buf, BLOCK_SIZE) => 0; if (tinfo.btype != LFS_BTYPE_MDIR || k == i+1) { i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1; lfsr_traversal_close(&lfs, &t) => 0; goto clobbered; } } k += 1; } } clobbered:; // clear relevant ck flags lfsr_gc_unck(&lfs, LFS_I_CANCKMETA) => 0; // running lfsr_gc should eventually find the clobbered block for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops LFS_ASSERT(i < 2*BLOCK_COUNT); int err = lfsr_gc(&lfs); assert(!err || err == LFS_ERR_CORRUPT); // found it if (err == LFS_ERR_CORRUPT) { break; } } lfsr_unmount(&lfs) => 0; } done:; ''' [cases.test_gc_ckdata_unck] # AFTER=0 => after running lfsr_gc once # AFTER=1 => after running lfsr_gc to completion # AFTER=2 => after lfsr_fs_ckdata # AFTER=3 => after remounting with LFS_M_CKDATA defines.AFTER = [0, 1, 2] defines.GC_FLAGS = 'LFS_GC_CKDATA' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] if = '(SIZE*N)/BLOCK_SIZE <= 32' ifdef = 'LFS_GC' code = ''' lfs_block_t i = 0; while (true) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting filesystem uint32_t prng = 42; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "squid%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; } // run lfsr_gc before clobbering, this should not find anything // run lfsr_gc once if (AFTER == 0) { lfsr_gc(&lfs) => 0; // run lfsr_gc to completion } else if (AFTER == 1) { while (true) { struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; if (!(fsinfo.flags & LFS_I_CANCKDATA)) { break; } lfsr_gc(&lfs) => 0; } // run lfsr_fs_ckdata } else if (AFTER == 2) { lfsr_fs_ckdata(&lfs) => 0; struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(!(fsinfo.flags & LFS_I_CANCKDATA)); // remount with LFS_M_CKDATA } else if (AFTER == 3) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKDATA, CFG) => 0; struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(!(fsinfo.flags & LFS_I_CANCKDATA)); } else { assert(false); } // traverse to find blocks lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t k = 0; for (lfs_block_t j = 0;; j++) { assert(j < 2*BLOCK_COUNT); struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; goto done; } // this gets a bit tricky be cause we need to clobber both // blocks in mdir pairs if (tinfo.btype == LFS_BTYPE_MDIR || tinfo.btype == LFS_BTYPE_BTREE || tinfo.btype == LFS_BTYPE_DATA) { if (k == i || k == i+1) { // clobber this block printf("clobbering 0x%x\n", tinfo.block); uint8_t clobber_buf[BLOCK_SIZE]; memset(clobber_buf, 0xcc, BLOCK_SIZE); CFG->erase(CFG, tinfo.block) => 0; CFG->prog(CFG, tinfo.block, 0, clobber_buf, BLOCK_SIZE) => 0; if (tinfo.btype != LFS_BTYPE_MDIR || k == i+1) { i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1; lfsr_traversal_close(&lfs, &t) => 0; goto clobbered; } } k += 1; } } clobbered:; // clear relevant ck flags lfsr_gc_unck(&lfs, LFS_I_CANCKDATA) => 0; // running lfsr_gc should eventually find the clobbered block // // note LFS_GC_CKDATA implies LFS_GC_CKMETA for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops LFS_ASSERT(i < 2*BLOCK_COUNT); int err = lfsr_gc(&lfs); assert(!err || err == LFS_ERR_CORRUPT); // found it if (err == LFS_ERR_CORRUPT) { break; } } lfsr_unmount(&lfs) => 0; } done:; ''' # pseudo-fuzz test that dirtying still works with the GC API [cases.test_gc_mutation] defines.N = 100 defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] defines.GC_FLAGS = ''' ((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) ''' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] ifdef = 'LFS_GC' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint32_t prng = 42; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "spider", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; for (uint32_t i = 0; i < N; i++) { // rewrite the file every gc cycle lfsr_file_open(&lfs, &file, "spider", LFS_O_WRONLY | LFS_O_TRUNC) => 0; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // gc! lfsr_gc(&lfs) => 0; } // check the file contents lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; ''' # pseudo-fuzz test that spamming lfsr_gc_unck doesn't break anything [cases.test_gc_mutation_unck] defines.N = 100 defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] defines.GC_FLAGS = ''' ((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) ''' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] ifdef = 'LFS_GC' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint32_t prng = 42; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "spider", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; for (uint32_t i = 0; i < N; i++) { // rewrite the file every gc cycle lfsr_file_open(&lfs, &file, "spider", LFS_O_WRONLY | LFS_O_TRUNC) => 0; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; // choose a random set of flags to unck every cycle uint32_t flags = GC_FLAGS & TEST_PRNG(&prng); lfsr_gc_unck(&lfs, flags) => 0; // gc! lfsr_gc(&lfs) => 0; } // check the file contents lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; ''' # 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] defines.UNCK = [false, true] defines.GC_FLAGS = ''' ((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) ''' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] ifdef = 'LFS_GC' code = ''' // test creating directories lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, 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_gc(&lfs) => 0; // unck to keep things interesting? if (UNCK) { lfsr_gc_unck(&lfs, LFS_I_CANCKMETA | LFS_I_CANCKDATA) => 0; } } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, 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] defines.UNCK = [false, true] defines.GC_FLAGS = ''' ((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) ''' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] # 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' ifdef = 'LFS_GC' code = ''' // test fuzz with dirs lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, 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_gc(&lfs) => 0; // unck to keep things interesting? if (UNCK) { lfsr_gc_unck(&lfs, LFS_I_CANCKMETA | LFS_I_CANCKDATA) => 0; } } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, 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] defines.UNCK = [false, true] defines.GC_FLAGS = ''' ((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) ''' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] # 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' ifdef = 'LFS_GC' code = ''' // test creating files lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, 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_gc(&lfs) => 0; // unck to keep things interesting? if (UNCK) { lfsr_gc_unck(&lfs, LFS_I_CANCKMETA | LFS_I_CANCKDATA) => 0; } } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, 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] defines.UNCK = [false, true] defines.GC_FLAGS = ''' ((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) ''' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] # 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' ifdef = 'LFS_GC' code = ''' // test fuzz with files lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, 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_gc(&lfs) => 0; // unck to keep things interesting? if (UNCK) { lfsr_gc_unck(&lfs, LFS_I_CANCKMETA | LFS_I_CANCKDATA) => 0; } } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, 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] defines.UNCK = [false, true] defines.GC_FLAGS = ''' ((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) ''' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] # 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', ] ifdef = 'LFS_GC' code = ''' // test with complex file writes lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, 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_gc(&lfs) => 0; // unck to keep things interesting? if (UNCK) { lfsr_gc_unck(&lfs, LFS_I_CANCKMETA | LFS_I_CANCKDATA) => 0; } } lfsr_file_close(&lfs, &file) => 0; for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, 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_uz_fuzz] defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] defines.UNCK = [false, true] defines.GC_FLAGS = ''' ((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) ''' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] # 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' ifdef = 'LFS_GC' code = ''' // test with uncreats, zombies, etc lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, 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 uncreat; 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 uncreat = true; uint32_t wprng = 0; for (lfs_size_t j = 0; j < sim_size; j++) { if (sim[j] == x) { uncreat = false; wprng = sim_prngs[j]; break; } } // choose a random seed if we don't exist if (uncreat) { 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]->uncreat = uncreat; 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 (uncreat) { 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]->uncreat = 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_gc(&lfs) => 0; // unck to keep things interesting? if (UNCK) { lfsr_gc_unck(&lfs, LFS_I_CANCKMETA | LFS_I_CANCKDATA) => 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_uzd_fuzz] defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] defines.UNCK = [false, true] defines.GC_FLAGS = ''' ((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) ''' defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] # 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' ifdef = 'LFS_GC' code = ''' // test with uncreats, zombies, dirs, etc lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, 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 uncreat; 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 uncreat = 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; } uncreat = false; wprng = sim_prngs[j]; break; } } // choose a random seed if we don't exist if (uncreat) { 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]->uncreat = uncreat; 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 (uncreat) { 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]->uncreat = 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_gc(&lfs) => 0; // unck to keep things interesting? if (UNCK) { lfsr_gc_unck(&lfs, LFS_I_CANCKMETA | LFS_I_CANCKDATA) => 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; '''