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.
This commit is contained in:
Christopher Haster
2025-01-15 03:34:44 -06:00
parent 1c5adf71b3
commit 57e9c3b706
4 changed files with 886 additions and 34 deletions
+807 -3
View File
@@ -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