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