From 57e9c3b7063b4914f4814f2f6b91e9e07dea9b5b Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 15 Jan 2025 03:34:44 -0600 Subject: [PATCH] Check gcksum during traversals, harder ckmeta/ckdata tests This adds a check that the on-disk gcksum matches the in-RAM gcksum in lfsr_mtree_traverse, so ckmeta/ckdata scans should now be able to at least detect global-rollback issues that occur while mounted. This also moves the LFS_I_CKMETA/CKDATA flag clearing logic from lfsr_mtree_gc -> lfsr_mtree_traverse. There's no reason to not clear these flags if we've made a successful traversal. We weren't actually calling lfsr_mtree_traverse with the right flags for this to matter, but it does let us drop an explicit flag clear in lfsr_fs_ck. --- These changes were a part of adding the harder versions of our ckmeta/ ckdata tests, where we flip individual bits instead of clobbering the entire block. These are more realistic errors and stress our gcksum system. Recalculating the gcksum required another gcksum copy in lfsr_traversal_t, which adds a bit of code and ctx to our incremental-gc build: code stack ctx default before: 38428 2640 644 default after: 38560 (+0.3%) 2640 (+0.0%) 644 (+0.0%) gc before: 38484 2640 788 gc after: 38616 (+0.3%) 2640 (+0.0%) 792 (+0.5%) Unfortunately we can't easily abuse the copies in lfs_t since multiple traversals may be open at once. --- bd/lfs_emubd.c | 19 ++ lfs.c | 89 +++-- lfs.h | 2 + tests/test_ck.toml | 810 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 886 insertions(+), 34 deletions(-) diff --git a/bd/lfs_emubd.c b/bd/lfs_emubd.c index 1fdb4235..38f85b98 100644 --- a/bd/lfs_emubd.c +++ b/bd/lfs_emubd.c @@ -1261,6 +1261,25 @@ int lfs_emubd_flipbit(const struct lfs_config *cfg, // flip the bit b->data[bit/8] ^= 1 << (bit%8); + // mirror to disk file? + if (bd->disk) { + off_t res1 = lseek(bd->disk->fd, + (off_t)block*cfg->block_size + (off_t)(bit/8), + SEEK_SET); + if (res1 < 0) { + int err = -errno; + LFS_EMUBD_TRACE("lfs_emubd_flipbit -> %d", err); + return err; + } + + ssize_t res2 = write(bd->disk->fd, &b->data[bit/8], 1); + if (res2 < 0) { + int err = -errno; + LFS_EMUBD_TRACE("lfs_emubd_flipbit -> %d", err); + return err; + } + } + LFS_EMUBD_TRACE("lfs_emubd_flipbit -> %d", 0); return 0; } diff --git a/lfs.c b/lfs.c index 25c260a9..da2befbb 100644 --- a/lfs.c +++ b/lfs.c @@ -9236,6 +9236,7 @@ static void lfsr_traversal_init(lfsr_traversal_t *t, uint32_t flags) { t->u.mtortoise.blocks[1] = -1; t->u.mtortoise.step = 0; t->u.mtortoise.power = 0; + t->gcksum = 0; } // low-level traversal _only_ finds blocks @@ -9563,17 +9564,29 @@ static int lfsr_mtree_traverse(lfs_t *lfs, lfsr_traversal_t *t, int err = lfsr_mtree_traverse_(lfs, t, &tag, &bptr); if (err) { + // end of traversal? + if (err == LFS_ERR_NOENT) { + goto eot; + } return err; } + // recalculate gcksum? + if ((lfsr_t_isckmeta(t->o.o.flags) + || lfsr_t_isckdata(t->o.o.flags)) + && tag == LFSR_TAG_MDIR) { + lfsr_mdir_t *mdir = (lfsr_mdir_t*)bptr.data.u.buffer; + t->gcksum ^= mdir->rbyd.cksum; + } + // validate btree nodes? note mdirs are already validated if ((lfsr_t_isckmeta(t->o.o.flags) || lfsr_t_isckdata(t->o.o.flags)) + && tag == LFSR_TAG_BRANCH // note ckfetches already validates btree nodes && LFS_IFDEF_CKFETCHES( !lfsr_m_isckfetches(lfs->flags), - true) - && tag == LFSR_TAG_BRANCH) { + true)) { lfsr_rbyd_t *rbyd = (lfsr_rbyd_t*)bptr.data.u.buffer; err = lfsr_rbyd_fetchck(lfs, rbyd, rbyd->blocks[0], rbyd->trunk, @@ -9599,6 +9612,35 @@ static int lfsr_mtree_traverse(lfs_t *lfs, lfsr_traversal_t *t, *bptr_ = bptr; } return 0; + +eot:; + // compare gcksum with in-RAM gcksum + if ((lfsr_t_isckmeta(t->o.o.flags) + || lfsr_t_isckdata(t->o.o.flags)) + && t->gcksum != lfs->gcksum) { + LFS_ERROR("Found gcksum mismatch, cksum %08"PRIx32" (!= %08"PRIx32")", + t->gcksum, + lfs->gcksum); + return LFS_ERR_CORRUPT; + } + + // was ckmeta/ckdata successful? we only consider our filesystem + // checked if we weren't mutated + if (lfsr_t_isckmeta(t->o.o.flags) + && !lfsr_t_ismtreeonly(t->o.o.flags) + && !lfsr_t_isdirty(t->o.o.flags) + && !lfsr_t_ismutated(t->o.o.flags)) { + lfs->flags &= ~LFS_I_CKMETA; + } + if (lfsr_t_isckdata(t->o.o.flags) + && !lfsr_t_ismtreeonly(t->o.o.flags) + && !lfsr_t_isdirty(t->o.o.flags) + && !lfsr_t_ismutated(t->o.o.flags)) { + // note ckdata implies ckmeta + lfs->flags &= ~LFS_I_CKDATA & ~LFS_I_CKMETA; + } + + return LFS_ERR_NOENT; } // needed in lfsr_mtree_gc @@ -9693,11 +9735,6 @@ dropped:; } return 0; -failed:; - // swap back dirty/mutated flags - t->o.o.flags = lfsr_t_swapdirty(t->o.o.flags); - return err; - eot:; // was lookahead scan successful? if (lfsr_t_islookahead(t->o.o.flags) @@ -9721,23 +9758,12 @@ eot:; lfs->flags &= ~LFS_I_COMPACT; } - // was ckmeta/ckdata successful? we only consider our filesystem - // checked if we weren't mutated - if (lfsr_t_isckmeta(t->o.o.flags) - && !lfsr_t_ismtreeonly(t->o.o.flags) - && !lfsr_t_isdirty(t->o.o.flags) - && !lfsr_t_ismutated(t->o.o.flags)) { - lfs->flags &= ~LFS_I_CKMETA; - } - if (lfsr_t_isckdata(t->o.o.flags) - && !lfsr_t_ismtreeonly(t->o.o.flags) - && !lfsr_t_isdirty(t->o.o.flags) - && !lfsr_t_ismutated(t->o.o.flags)) { - // note ckdata implies ckmeta - lfs->flags &= ~LFS_I_CKDATA & ~LFS_I_CKMETA; - } - return LFS_ERR_NOENT; + +failed:; + // swap back dirty/mutated flags + t->o.o.flags = lfsr_t_swapdirty(t->o.o.flags); + return err; } @@ -11109,7 +11135,9 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, | LFS_O_APPEND | LFS_O_FLUSH | LFS_O_SYNC - | LFS_O_DESYNC)) == 0); + | LFS_O_DESYNC + | LFS_O_CKMETA + | LFS_O_CKDATA)) == 0); // writeable files require a writeable filesystem LFS_ASSERT(!lfsr_m_isrdonly(lfs->flags) || lfsr_o_isrdonly(flags)); // these flags require a writable file @@ -13857,6 +13885,7 @@ static int lfsr_mountmroot(lfs_t *lfs, const lfsr_mdir_t *mroot) { static int lfsr_mountinited(lfs_t *lfs) { // zero gdeltas, we'll read these from our mdirs + lfs->gcksum = 0; lfsr_fs_flushgdelta(lfs); // default to no mtree, this is allowed and implies all files are inlined @@ -13940,7 +13969,7 @@ static int lfsr_mountinited(lfs_t *lfs) { lfs->seed ^= mdir->rbyd.cksum; // build gcksum out of mdir cksums - lfs->gcksum_p ^= mdir->rbyd.cksum; + lfs->gcksum ^= mdir->rbyd.cksum; // collect any gdeltas from this mdir err = lfsr_fs_consumegdelta(lfs, mdir); @@ -13961,9 +13990,6 @@ static int lfsr_mountinited(lfs_t *lfs) { } } - // keep track of the current gcksum - lfs->gcksum = lfs->gcksum_p; - // validate gcksum by comparing its cube against the gcksumdeltas // // The use of cksum^3 here is important to avoid trivial @@ -13997,6 +14023,9 @@ static int lfsr_mountinited(lfs_t *lfs) { return LFS_ERR_CORRUPT; } + // keep track of the current gcksum + lfs->gcksum_p = lfs->gcksum; + // once we've mounted and derived a pseudo-random seed, initialize our // block allocator // @@ -14505,7 +14534,7 @@ int lfsr_fs_mkconsistent(lfs_t *lfs) { static int lfsr_fs_ck(lfs_t *lfs, uint32_t flags) { // we leave this up to lfsr_mtree_traverse lfsr_traversal_t t; - lfsr_traversal_init(&t, flags);; + lfsr_traversal_init(&t, flags); while (true) { int err = lfsr_mtree_traverse(lfs, &t, NULL, NULL); @@ -14517,8 +14546,6 @@ static int lfsr_fs_ck(lfs_t *lfs, uint32_t flags) { } } - // clear relevant ck flags - lfs->flags &= ~flags; return 0; } diff --git a/lfs.h b/lfs.h index 7f8e27e1..5818902c 100644 --- a/lfs.h +++ b/lfs.h @@ -776,6 +776,8 @@ typedef struct lfsr_traversal { lfsr_btraversal_t bt; } u; + // recalculate gcksum when traversing with ckmeta + uint32_t gcksum; // pending blocks, only used in lfsr_traversal_read lfs_sblock_t blocks[2]; } lfsr_traversal_t; diff --git a/tests/test_ck.toml b/tests/test_ck.toml index c0a7389c..1c16f297 100644 --- a/tests/test_ck.toml +++ b/tests/test_ck.toml @@ -399,6 +399,514 @@ code = ''' done:; ''' +# test some more interesting errors +[cases.test_ck_ckmeta_hard] +# METHOD=0 => lfsr_fs_ckmeta +# METHOD=1 => lfsr_fs_gc +# METHOD=2 => lfsr_traversal_read +# METHOD=3 => lfsr_mount +defines.METHOD = [0, 1, 2, 3] +defines.GC_FLAGS = 'LFS_GC_CKMETA' +defines.GC_STEPS = -1 +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', +] +defines.SEED = 42 +defines.M = 100 +fuzz = 'SEED' +if = [ + '(SIZE*N)/BLOCK_SIZE <= 32', + 'LFS_IFDEF_GC(true, METHOD != 1)', +] +code = ''' + uint32_t prng_ = SEED; + for (lfs_block_t i = 0;; i++) { + // 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; + lfs_block_t badblock; + 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; + } + + if (tinfo.btype == LFS_BTYPE_MDIR + || tinfo.btype == LFS_BTYPE_BTREE) { + // found an interesting block? + if (k == i) { + badblock = tinfo.block; + lfsr_traversal_close(&lfs, &t) => 0; + goto clobber; + } + k += 1; + } + } + + clobber:; + // TODO API for this? + // save the current gcksum + uint32_t gcksum = lfs.gcksum; + + // try flipping some bits + for (lfs_size_t j = 0; j < M; j++) { + // choose a bit + lfs_size_t badbit = TEST_PRNG(&prng_) % (BLOCK_SIZE*8); + // flip + printf("flipping 0x%x.%x+%x\n", badblock, badbit/8, badbit%8); + lfs_emubd_flipbit(CFG, badblock, badbit) => 0; + + // find clobbered blocks with lfsr_fs_ckmeta + if (METHOD == 0) { + int err = lfsr_fs_ckmeta(&lfs); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto detected; + } + + // find clobbered blocks with lfsr_fs_gc + } else if (METHOD == 1) { + #ifdef LFS_GC + int err = lfsr_fs_gc(&lfs); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto detected; + } + #else + LFS_UNREACHABLE(); + #endif + + // find clobbered blocks with lfsr_traversal_read + } else if (METHOD == 2) { + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, LFS_T_CKMETA) => 0; + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + LFS_ASSERT(i < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err + || err == LFS_ERR_NOENT + || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_NOENT) { + break; + } + if (err == LFS_ERR_CORRUPT) { + lfsr_traversal_close(&lfs, &t) => 0; + goto detected; + } + } + lfsr_traversal_close(&lfs, &t) => 0; + + // find clobbered blocks with lfsr_mount + } else if (METHOD == 3) { + lfsr_unmount(&lfs) => 0; + int err = lfsr_mount(&lfs, + LFS_M_RDWR + | LFS_M_CKMETA, + CFG); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto detected; + } + + } else { + assert(false); + } + + goto undetected; + undetected:; + // It's ok to not always find the error, since our + // filesystem contains padding we don't care about, but in + // that case we should be able to read all of our files. + // + // Well... most of our files at least... Rollback issues + // mean we can end up in any of our previous filesystem + // states, but our gcksum should at least prevent this from + // corrupting our filesystem. This is a fundamental issue + // for any filesystem with logs (AKA any powerloss-resilient + // filesystem). + // + 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); + lfs_size_t found = 0; + for (lfs_size_t i = 0;; i++) { + int err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + found += 1; + char name[256]; + sprintf(name, "squid%03x", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfsr_dir_close(&lfs, &dir) => 0; + + // we should at least detect rollback if we don't lose + // power/remount + if (METHOD != 3) { + assert(found == N); + } + + // if we do lose power/remount, at least the gcksum should + // end up different, this allows detecting rollback if + // stored externally + if (found != N) { + // TODO API for this? + assert(lfs.gcksum != gcksum); + } + + // test we can read the files that survived + prng = 42; + for (lfs_size_t i = 0; i < found; 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; + 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; + } + + // unflip our bit + lfs_emubd_flipbit(CFG, badblock, badbit) => 0; + + // clear any ck flags for gc + lfsr_fs_unck(&lfs, GC_FLAGS) => 0; + continue; + + detected:; + // unflip our bit + lfs_emubd_flipbit(CFG, badblock, badbit) => 0; + + // remount if we ended up unmounted + if (METHOD == 3) { + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + } + + // clear any ck flags for gc + lfsr_fs_unck(&lfs, GC_FLAGS) => 0; + continue; + } + + lfsr_unmount(&lfs) => 0; + } +done:; +''' + +[cases.test_ck_ckdata_hard] +# METHOD=0 => lfsr_fs_ckdata +# METHOD=1 => lfsr_fs_gc +# METHOD=2 => lfsr_traversal_read +# METHOD=3 => lfsr_mount +defines.METHOD = [0, 1, 2, 3] +defines.GC_FLAGS = 'LFS_GC_CKDATA' +defines.GC_STEPS = -1 +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', +] +defines.SEED = 42 +defines.M = 100 +fuzz = 'SEED' +if = [ + '(SIZE*N)/BLOCK_SIZE <= 32', + 'LFS_IFDEF_GC(true, METHOD != 1)', +] +code = ''' + uint32_t prng_ = SEED; + for (lfs_block_t i = 0;; i++) { + // 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; + lfs_block_t badblock; + 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; + } + + if (tinfo.btype == LFS_BTYPE_MDIR + || tinfo.btype == LFS_BTYPE_BTREE + || tinfo.btype == LFS_BTYPE_DATA) { + // found an interesting block? + if (k == i) { + badblock = tinfo.block; + lfsr_traversal_close(&lfs, &t) => 0; + goto clobber; + } + k += 1; + } + } + + clobber:; + // TODO API for this? + // save the current gcksum + uint32_t gcksum = lfs.gcksum; + + // try flipping some bits + for (lfs_size_t j = 0; j < M; j++) { + // choose a bit + lfs_size_t badbit = TEST_PRNG(&prng_) % (BLOCK_SIZE*8); + // flip + printf("flipping 0x%x.%x+%x\n", badblock, badbit/8, badbit%8); + lfs_emubd_flipbit(CFG, badblock, badbit) => 0; + + // find clobbered blocks with lfsr_fs_ckdata + if (METHOD == 0) { + int err = lfsr_fs_ckdata(&lfs); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto detected; + } + + // find clobbered blocks with lfsr_fs_gc + } else if (METHOD == 1) { + #ifdef LFS_GC + int err = lfsr_fs_gc(&lfs); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto detected; + } + #else + LFS_UNREACHABLE(); + #endif + + // find clobbered blocks with lfsr_traversal_read + } else if (METHOD == 2) { + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, LFS_T_CKDATA) => 0; + for (lfs_block_t i = 0;; i++) { + // a bit hacky, but this catches infinite loops + LFS_ASSERT(i < 2*BLOCK_COUNT); + + struct lfs_tinfo tinfo; + int err = lfsr_traversal_read(&lfs, &t, &tinfo); + assert(!err + || err == LFS_ERR_NOENT + || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_NOENT) { + break; + } + if (err == LFS_ERR_CORRUPT) { + lfsr_traversal_close(&lfs, &t) => 0; + goto detected; + } + } + lfsr_traversal_close(&lfs, &t) => 0; + + // find clobbered blocks with lfsr_mount + } else if (METHOD == 3) { + lfsr_unmount(&lfs) => 0; + int err = lfsr_mount(&lfs, + LFS_M_RDWR + | LFS_M_CKDATA, + CFG); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto detected; + } + + } else { + assert(false); + } + + goto undetected; + undetected:; + // It's ok to not always find the error, since our + // filesystem contains padding we don't care about, but in + // that case we should be able to read all of our files. + // + // Well... most of our files at least... Rollback issues + // mean we can end up in any of our previous filesystem + // states, but our gcksum should at least prevent this from + // corrupting our filesystem. This is a fundamental issue + // for any filesystem with logs (AKA any powerloss-resilient + // filesystem). + // + 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); + lfs_size_t found = 0; + for (lfs_size_t i = 0;; i++) { + int err = lfsr_dir_read(&lfs, &dir, &info); + assert(!err || err == LFS_ERR_NOENT); + if (err == LFS_ERR_NOENT) { + break; + } + found += 1; + char name[256]; + sprintf(name, "squid%03x", i); + assert(strcmp(info.name, name) == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == SIZE); + } + lfsr_dir_close(&lfs, &dir) => 0; + + // we should at least detect rollback if we don't lose + // power/remount + if (METHOD != 3) { + assert(found == N); + } + + // if we do lose power/remount, at least the gcksum should + // end up different, this allows detecting rollback if + // stored externally + if (found != N) { + // TODO API for this? + assert(lfs.gcksum != gcksum); + } + + // test we can read the files that survived + prng = 42; + for (lfs_size_t i = 0; i < found; 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; + 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; + } + + // unflip our bit + lfs_emubd_flipbit(CFG, badblock, badbit) => 0; + + // clear any ck flags for gc + lfsr_fs_unck(&lfs, GC_FLAGS) => 0; + continue; + + detected:; + // unflip our bit + lfs_emubd_flipbit(CFG, badblock, badbit) => 0; + + // remount if we ended up unmounted + if (METHOD == 3) { + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + } + + // clear any ck flags for gc + lfsr_fs_unck(&lfs, GC_FLAGS) => 0; + continue; + } + + lfsr_unmount(&lfs) => 0; + } +done:; +''' + # Test file-level checksum things @@ -407,7 +915,7 @@ done:; # METHOD=0 => lfsr_file_ckmeta # METHOD=1 => lfsr_file_close+open+ckmeta # METHOD=2 => lfsr_file_close+open -defines.METHOD = [0, 1] +defines.METHOD = [0, 1, 2] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.SIZE = [ '0', @@ -500,12 +1008,11 @@ code = ''' done:; ''' -# test we can detect at least fully clobbered blocks [cases.test_ck_file_ckdata_easy] # METHOD=0 => lfsr_file_ckdata # METHOD=1 => lfsr_file_close+open+ckdata # METHOD=2 => lfsr_file_close+open -defines.METHOD = [0, 1] +defines.METHOD = [0, 1, 2] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.SIZE = [ '0', @@ -600,6 +1107,303 @@ code = ''' done:; ''' +# test some more interesting errors +[cases.test_ck_file_ckmeta_hard] +# METHOD=0 => lfsr_file_ckmeta +# METHOD=1 => lfsr_file_close+open+ckmeta +# METHOD=2 => lfsr_file_close+open +defines.METHOD = [0, 1, 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', + '8*BLOCK_SIZE', +] +defines.SEED = 42 +defines.M = 100 +fuzz = 'SEED' +code = ''' + uint32_t prng_ = SEED; + for (lfs_block_t i = 0;; i++) { + // 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 file + uint32_t prng = 42; + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "octopus", + LFS_O_RDWR | 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; + + // traverse to find blocks + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t k = 0; + lfs_block_t badblock; + 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_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + goto done; + } + + if (tinfo.btype == LFS_BTYPE_BTREE) { + // found an interesting block? + if (k == i) { + badblock = tinfo.block; + lfsr_traversal_close(&lfs, &t) => 0; + goto clobber; + } + k += 1; + } + } + + clobber:; + // try flipping some bits + for (lfs_size_t j = 0; j < M; j++) { + // choose a bit + lfs_size_t badbit = TEST_PRNG(&prng_) % (BLOCK_SIZE*8); + // flip + printf("flipping 0x%x.%x+%x\n", badblock, badbit/8, badbit%8); + lfs_emubd_flipbit(CFG, badblock, badbit) => 0; + + // find clobbered blocks with lfsr_file_ckmeta + if (METHOD == 0) { + int err = lfsr_file_ckmeta(&lfs, &file); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto detected; + } + + // find clobbered blocks with lfsr_file_close+open+ckmeta + } else if (METHOD == 1) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "octopus", LFS_O_RDONLY) => 0; + int err = lfsr_file_ckmeta(&lfs, &file); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto detected; + } + + // find clobbered blocks with lfsr_file_close+open + } else if (METHOD == 2) { + lfsr_file_close(&lfs, &file) => 0; + int err = lfsr_file_open(&lfs, &file, "octopus", + LFS_O_RDONLY | LFS_O_CKMETA); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto detected; + } + + } else { + assert(false); + } + + goto undetected; + undetected:; + // It's ok to not always find the error, since our + // filesystem contains padding we don't care about, but in + // that case we should be able to read our file. + prng = 42; + { + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_rewind(&lfs, &file) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + } + + // unflip our bit + lfs_emubd_flipbit(CFG, badblock, badbit) => 0; + continue; + + detected:; + // unflip our bit + lfs_emubd_flipbit(CFG, badblock, badbit) => 0; + + // reopen our file if we ended up closed + if (METHOD == 2) { + lfsr_file_open(&lfs, &file, "octopus", LFS_O_RDWR) => 0; + } + } + + lfsr_file_close(&lfs, &file) => 0; + } +done:; +''' + +[cases.test_ck_file_ckdata_hard] +# METHOD=0 => lfsr_file_ckdata +# METHOD=1 => lfsr_file_close+open+ckdata +# METHOD=2 => lfsr_file_close+open +defines.METHOD = [0, 1, 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', + '8*BLOCK_SIZE', +] +defines.SEED = 42 +defines.M = 100 +fuzz = 'SEED' +code = ''' + uint32_t prng_ = SEED; + for (lfs_block_t i = 0;; i++) { + // 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 file + uint32_t prng = 42; + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "octopus", + LFS_O_RDWR | 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; + + // traverse to find blocks + lfsr_traversal_t t; + lfsr_traversal_open(&lfs, &t, 0) => 0; + lfs_block_t k = 0; + lfs_block_t badblock; + 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_file_close(&lfs, &file) => 0; + lfsr_unmount(&lfs) => 0; + goto done; + } + + if (tinfo.btype == LFS_BTYPE_BTREE + || tinfo.btype == LFS_BTYPE_DATA) { + // found an interesting block? + if (k == i) { + badblock = tinfo.block; + lfsr_traversal_close(&lfs, &t) => 0; + goto clobber; + } + k += 1; + } + } + + clobber:; + // try flipping some bits + for (lfs_size_t j = 0; j < M; j++) { + // choose a bit + lfs_size_t badbit = TEST_PRNG(&prng_) % (BLOCK_SIZE*8); + // flip + printf("flipping 0x%x.%x+%x\n", badblock, badbit/8, badbit%8); + lfs_emubd_flipbit(CFG, badblock, badbit) => 0; + + // find clobbered blocks with lfsr_file_ckdata + if (METHOD == 0) { + int err = lfsr_file_ckdata(&lfs, &file); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto detected; + } + + // find clobbered blocks with lfsr_file_close+open+ckmeta + } else if (METHOD == 1) { + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "octopus", LFS_O_RDONLY) => 0; + int err = lfsr_file_ckdata(&lfs, &file); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto detected; + } + + // find clobbered blocks with lfsr_file_close+open + } else if (METHOD == 2) { + lfsr_file_close(&lfs, &file) => 0; + int err = lfsr_file_open(&lfs, &file, "octopus", + LFS_O_RDONLY | LFS_O_CKDATA); + assert(!err || err == LFS_ERR_CORRUPT); + if (err == LFS_ERR_CORRUPT) { + goto detected; + } + + } else { + assert(false); + } + + goto undetected; + undetected:; + // It's ok to not always find the error, since our + // filesystem contains padding we don't care about, but in + // that case we should be able to read our file. + prng = 42; + { + uint8_t wbuf[SIZE]; + for (lfs_size_t j = 0; j < SIZE; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + + uint8_t rbuf[SIZE]; + lfsr_file_rewind(&lfs, &file) => 0; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, wbuf, SIZE) == 0); + } + + // unflip our bit + lfs_emubd_flipbit(CFG, badblock, badbit) => 0; + continue; + + detected:; + // unflip our bit + lfs_emubd_flipbit(CFG, badblock, badbit) => 0; + + // reopen our file if we ended up closed + if (METHOD == 2) { + lfsr_file_open(&lfs, &file, "octopus", LFS_O_RDWR) => 0; + } + } + + lfsr_file_close(&lfs, &file) => 0; + } +done:; +''' + # Some simple ckprog tests