diff --git a/lfs.c b/lfs.c index cdeed281..a555d555 100644 --- a/lfs.c +++ b/lfs.c @@ -5924,6 +5924,19 @@ static inline bool lfsr_f_hasorphans(uint32_t flags) { return flags & LFS_F_ORPHANS; } +// on-demand flags + +// needed in lfsr_fs_isinconsistent +static inline uint8_t lfsr_grm_count(const lfs_t *lfs); + +static bool lfsr_fs_isinconsistent(const lfs_t *lfs) { + return lfsr_grm_count(lfs) > 0 || lfsr_f_hasorphans(lfs->flags); +} + +static bool lfsr_fs_canlookahead(const lfs_t *lfs) { + return lfs->lookahead.next > 0 || lfs->lookahead.size == 0; +} + /// opened mdir things /// @@ -6044,7 +6057,7 @@ static inline uint8_t lfsr_grm_count_(const lfsr_grm_t *grm) { return (grm->mids[0] >= 0) + (grm->mids[1] >= 0); } -static inline uint8_t lfsr_grm_count(lfs_t *lfs) { +static inline uint8_t lfsr_grm_count(const lfs_t *lfs) { return lfsr_grm_count_(&lfs->grm); } @@ -6061,7 +6074,7 @@ static inline lfsr_smid_t lfsr_grm_pop(lfs_t *lfs) { return mid; } -static inline bool lfsr_grm_ismidrm(lfs_t *lfs, lfsr_smid_t mid) { +static inline bool lfsr_grm_ismidrm(const lfs_t *lfs, lfsr_smid_t mid) { return lfs->grm.mids[0] == mid || lfs->grm.mids[1] == mid; } @@ -12572,16 +12585,38 @@ static int lfsr_mountinited(lfs_t *lfs) { int lfsr_mount(lfs_t *lfs, uint32_t flags, const struct lfs_config *cfg) { - int err = lfs_init(lfs, flags, cfg); + // 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)); + // unknown flags? + LFS_ASSERT((flags + & ~LFS_M_RDWR + & ~LFS_M_RDONLY + & ~LFS_M_CKPROGS + & ~LFS_M_MTREEONLY + & ~LFS_M_MKCONSISTENT + & ~LFS_M_LOOKAHEAD + & ~LFS_M_COMPACT + & ~LFS_M_CKMETA + & ~LFS_M_CKDATA) == 0); + + int err = lfs_init(lfs, + // strip out the one-time traversal flags + flags + & ~LFS_M_MTREEONLY + & ~LFS_M_MKCONSISTENT + & ~LFS_M_LOOKAHEAD + & ~LFS_M_COMPACT + & ~LFS_M_CKMETA + & ~LFS_M_CKDATA, + cfg); if (err) { return err; } err = lfsr_mountinited(lfs); if (err) { - // make sure we clean up on error - lfs_deinit(lfs); - return err; + goto failed; } // TODO this should use any configured values @@ -12599,7 +12634,84 @@ int lfsr_mount(lfs_t *lfs, uint32_t flags, lfsr_mtree_weight_(&lfs->mtree) >> lfs->mdir_bits, 1 << lfs->mdir_bits); + // 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])); + } + + err = lfsr_fs_fixgrm(lfs); + if (err) { + goto failed; + } + } + + // run gc until all requested mount work is done + bool mutated = true; + while ((lfsr_t_ismkconsistent(flags) + && lfsr_f_hasorphans(lfs->flags)) + || (lfsr_t_islookahead(flags) + && lfsr_fs_canlookahead(lfs)) + || (lfsr_t_iscompact(flags) + && lfsr_i_isuncompacted(lfs->flags)) + || (lfsr_t_isckmeta(flags) + && mutated) + || (lfsr_t_isckdata(flags) + && mutated)) { + + // do we really need a full traversal? + uint32_t flags_ = flags; + if (!((lfsr_t_islookahead(flags) + && lfsr_fs_canlookahead(lfs)) + || (lfsr_t_iscompact(flags) + && lfsr_i_isuncompacted(lfs->flags)) + || (lfsr_t_isckmeta(flags) + && mutated) + || (lfsr_t_isckdata(flags) + && mutated))) { + flags_ |= LFS_GC_MTREEONLY; + } + + lfsr_traversal_t t = LFSR_TRAVERSAL(flags_); + lfsr_omdir_open(lfs, &t.o.o); + + // shift the lookahead buffer if requested + if (lfsr_t_islookahead(t.o.o.flags)) { + lfs_alloc_shift(lfs); + lfs_alloc_ckpoint(lfs); + } + + while (true) { + err = lfsr_mtree_gc(lfs, &t, + NULL, NULL); + if (err) { + lfsr_omdir_close(lfs, &t.o.o); + if (err == LFS_ERR_NOENT) { + break; + } + goto failed; + } + } + + // mutated? we need another pass for ckmeta/ckdata + mutated = lfsr_f_ismutated(t.o.o.flags); + } + return 0; + +failed:; + // make sure we clean up on error + lfs_deinit(lfs); + return err; } int lfsr_unmount(lfs_t *lfs) { @@ -12723,15 +12835,9 @@ int lfsr_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) { LFS_I_RDONLY | LFS_I_CKPROGS | LFS_I_UNCOMPACTED); - // some flags we calculate on demand - if (lfsr_grm_count(lfs) > 0 || lfsr_f_hasorphans(lfs->flags)) { - fsinfo->flags |= LFS_I_INCONSISTENT; - } - - if (lfs->lookahead.next > 0 || lfs->lookahead.size == 0) { - fsinfo->flags |= LFS_I_CANLOOKAHEAD; - } + fsinfo->flags |= (lfsr_fs_isinconsistent(lfs)) ? LFS_I_INCONSISTENT : 0; + fsinfo->flags |= (lfsr_fs_canlookahead(lfs)) ? LFS_I_CANLOOKAHEAD : 0; // return filesystem config, this may come from disk fsinfo->block_size = lfs->cfg->block_size; @@ -12950,9 +13056,10 @@ int lfsr_fs_gc(lfs_t *lfs, uint32_t flags) { } // do we need to do anything? - if (!((lfsr_t_ismkconsistent(flags) && lfsr_f_hasorphans(lfs->flags)) + if (!((lfsr_t_ismkconsistent(flags) + && lfsr_f_hasorphans(lfs->flags)) || (lfsr_t_islookahead(flags) - && (lfs->lookahead.next > 0 || lfs->lookahead.size == 0)) + && lfsr_fs_canlookahead(lfs)) || (lfsr_t_iscompact(flags) && lfsr_i_isuncompacted(lfs->flags)) || lfsr_t_isckmeta(flags) @@ -12961,8 +13068,10 @@ int lfsr_fs_gc(lfs_t *lfs, uint32_t flags) { } // do we really need a full traversal? - if (!(lfsr_t_islookahead(flags) - || lfsr_t_iscompact(flags) + if (!((lfsr_t_islookahead(flags) + && lfsr_fs_canlookahead(lfs)) + || (lfsr_t_iscompact(flags) + && lfsr_i_isuncompacted(lfs->flags)) || lfsr_t_isckmeta(flags) || lfsr_t_isckdata(flags))) { flags |= LFS_GC_MTREEONLY; diff --git a/lfs.h b/lfs.h index dd3d0bfd..8460100a 100644 --- a/lfs.h +++ b/lfs.h @@ -117,14 +117,21 @@ enum lfs_error { enum lfs_mount_flags { LFS_M_RDWR = 0x0000, // Mount the filesystem as read and write LFS_M_RDONLY = 0x0001, // Mount the filesystem as read only - LFS_M_CKPROGS = 0x0010, // Check progs by reading back progged data + LFS_M_CKPROGS = 0x0008, // Check progs by reading back progged data + + LFS_M_MTREEONLY = 0x0010, // Only traverse the mtree + LFS_M_MKCONSISTENT = 0x0020, // Make the filesystem consistent + LFS_M_LOOKAHEAD = 0x0040, // Populate lookahead buffer + LFS_M_COMPACT = 0x0080, // Compact metadata logs + LFS_M_CKMETA = 0x0100, // Check metadata checksums + LFS_M_CKDATA = 0x0200, // Check metadata + data checksums }; // Filesystem info flags enum lfs_fsinfo_flags { // mount flags LFS_I_RDONLY = 0x0001, // Filesystem mounted read only - LFS_I_CKPROGS = 0x0010, // Check progs by reading back progged data + LFS_I_CKPROGS = 0x0008, // Check progs by reading back progged data // state flags LFS_I_INCONSISTENT = 0x0100, // Filesystem needs mkconsistent to write diff --git a/tests/test_gc.toml b/tests/test_gc.toml index d79442c6..7b13f1e3 100644 --- a/tests/test_gc.toml +++ b/tests/test_gc.toml @@ -1127,6 +1127,209 @@ code = ''' ''' +# test we can detect at least fully clobbered blocks +[cases.test_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' +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, 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_fs_gc should eventually find the clobbered block + while (true) { + int err = lfsr_fs_gc(&lfs, LFS_GC_CKMETA); + assert(!err || err == LFS_ERR_CORRUPT); + // found it + if (err == LFS_ERR_CORRUPT) { + break; + } + + // we should find the clobbered block before finishing the + // traversal + assert(lfs.omdirs == &lfs.gc.o.o); + } + + lfsr_unmount(&lfs) => 0; + } +done:; +''' + +[cases.test_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' +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, 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_fs_gc should eventually find the clobbered block + // + // note LFS_GC_CKDATA implies LFS_GC_CKMETA + while (true) { + int err = lfsr_fs_gc(&lfs, LFS_GC_CKDATA); + assert(!err || err == LFS_ERR_CORRUPT); + // found it + if (err == LFS_ERR_CORRUPT) { + break; + } + + // we should find the clobbered block before finishing the + // traversal + assert(lfs.omdirs == &lfs.gc.o.o); + } + + lfsr_unmount(&lfs) => 0; + } +done:; +''' + + # pseudo-fuzz test that dirtying still works with the GC API [cases.test_gc_mutation] defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000] diff --git a/tests/test_mount.toml b/tests/test_mount.toml index 7294e7ee..48ea5e93 100644 --- a/tests/test_mount.toml +++ b/tests/test_mount.toml @@ -34,4 +34,390 @@ code = ''' ''' +# test that on-mount traversals do what they say they do + +[cases.test_mount_t_lookahead] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + + // by default we need a lookahead scan + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.flags == ( + LFS_I_CANLOOKAHEAD + | LFS_I_UNCOMPACTED)); + lfsr_unmount(&lfs) => 0; + + // with LFS_M_LOOKAHEAD, mount performs a lookahead scan + lfsr_mount(&lfs, + LFS_M_RDWR + | LFS_M_LOOKAHEAD + | ((CKMETA) ? LFS_M_CKMETA : 0) + | ((CKDATA) ? LFS_M_CKDATA : 0), + CFG) => 0; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.flags == LFS_I_UNCOMPACTED); + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mount_t_compact] +defines.LOOKAHEAD = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +defines.SIZE = [ + 'FILE_BUFFER_SIZE/2', + '2*FILE_BUFFER_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '8*BLOCK_SIZE', +] +# set compact thresh to minimum +defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // first lets create a compactable filesystem + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + + // write to our mdir until >gc_compact_thresh full + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "jellyfish", + LFS_O_WRONLY | 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; + } + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; + + // by default mount does not compact + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.flags == ( + LFS_I_CANLOOKAHEAD + | LFS_I_UNCOMPACTED)); + lfsr_unmount(&lfs) => 0; + + // with LFS_M_COMPACT, mount compact any uncompacted blocks + lfsr_mount(&lfs, + LFS_M_RDWR + | LFS_M_COMPACT + | ((LOOKAHEAD) ? LFS_M_LOOKAHEAD : 0) + | ((CKMETA) ? LFS_M_CKMETA : 0) + | ((CKDATA) ? LFS_M_CKDATA : 0), + CFG) => 0; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.flags == ((!LOOKAHEAD) ? LFS_I_CANLOOKAHEAD : 0)); + + // mdir should have been compacted + lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0; + assert((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH); + + // check we can still read the file + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_mount_t_mkconsistent] +defines.LOOKAHEAD = [false, true] +defines.COMPACT = [false, true] +defines.CKMETA = [false, true] +defines.CKDATA = [false, true] +defines.SIZE = 'FILE_BUFFER_SIZE/2' +# <=2 => grm-able +# >2 => requires orphans +defines.ORPHANS = [0, 1, 2, 3, 100] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + + uint32_t prng = 42; + + // first lets create some orphans + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + + // create two files + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "cuttlefish", + LFS_O_WRONLY | 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, &file, wbuf1, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "octopus", + LFS_O_WRONLY | 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, &file, wbuf2, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 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; + } + + lfsr_unmount(&lfs) => 0; + + // by default we clean up orphans lazily + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + struct lfs_fsinfo fsinfo; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.flags == ( + ((ORPHANS > 0) ? LFS_I_INCONSISTENT : 0) + | LFS_I_CANLOOKAHEAD + | LFS_I_UNCOMPACTED)); + lfsr_unmount(&lfs) => 0; + + // with LFS_M_MKCONSISTENT, mount cleans up orphans eagerly + lfsr_mount(&lfs, + LFS_M_RDWR + | LFS_M_MKCONSISTENT + | ((LOOKAHEAD) ? LFS_M_LOOKAHEAD : 0) + | ((COMPACT) ? LFS_M_COMPACT : 0) + | ((CKMETA) ? LFS_M_CKMETA : 0) + | ((CKDATA) ? LFS_M_CKDATA : 0), + CFG) => 0; + lfsr_fs_stat(&lfs, &fsinfo) => 0; + assert(fsinfo.flags == ( + ((!LOOKAHEAD) ? LFS_I_CANLOOKAHEAD : 0) + | ((!COMPACT) ? LFS_I_UNCOMPACTED : 0))); + + // check we can still read the files + lfsr_file_open(&lfs, &file, "cuttlefish", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf1, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_file_open(&lfs, &file, "octopus", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf2, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# test we can detect at least fully clobbered blocks +[cases.test_mount_t_ckmeta] +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, 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; + lfsr_unmount(&lfs) => 0; + goto clobbered; + } + } + k += 1; + } + } + + clobbered:; + // mount with LFS_M_CKMETA, we should detect clobbered blocks + lfsr_mount(&lfs, + LFS_M_RDWR + | LFS_M_CKMETA, + CFG) => LFS_ERR_CORRUPT; + } +done:; +''' + +[cases.test_mount_t_ckdata] +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, 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; + lfsr_unmount(&lfs) => 0; + goto clobbered; + } + } + k += 1; + } + } + + clobbered:; + // mount with LFS_M_CKDATA, we should detect clobbered blocks + // + // note LFS_M_CKDATA implies LFS_M_CKMETA + lfsr_mount(&lfs, + LFS_M_RDWR + | LFS_M_CKDATA, + CFG) => LFS_ERR_CORRUPT; + } +done:; +''' + + + # TODO should we move test_incompat here? diff --git a/tests/test_traversal.toml b/tests/test_traversal.toml index aa9dc9fa..231b8c2e 100644 --- a/tests/test_traversal.toml +++ b/tests/test_traversal.toml @@ -1442,9 +1442,8 @@ code = ''' clobber_buf, BLOCK_SIZE) => 0; lfsr_traversal_close(&lfs, &t) => 0; goto clobbered; - } else { - k += 1; } + k += 1; } } @@ -1537,9 +1536,8 @@ code = ''' clobber_buf, BLOCK_SIZE) => 0; lfsr_traversal_close(&lfs, &t) => 0; goto clobbered; - } else { - k += 1; } + k += 1; } } @@ -1634,9 +1632,8 @@ code = ''' clobber_buf, BLOCK_SIZE) => 0; lfsr_traversal_close(&lfs, &t) => 0; goto clobbered; - } else { - k += 1; } + k += 1; } }