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:
+807
-3
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user