5502fe55ab
Ckfetches implements what might be your first idea on how to check
checksums in a filesystem: Check each block/mdir on first access
(fetch) to make sure the data is sound.
Unfortunately, there are two problems with this approach, both which
come from the fact that blocks are big and can't fit in RAM:
1. We still have a checksum-read hole.
We can't keep a whole block around in RAM, so reads after a fetch may
need to reread from disk, at which point new bit-errors may slip in
undetected.
This is especially problematic for traversing our rbyds, which
involves a lot of small reads in a block.
2. Ckfetches may have a surprisingly negative performance impact.
Consider the case of reading a large file with a bunch of small
reads. Because we don't cache blocks, each read may need a btree
lookup, and a full block fetch. On paper this can quickly end up
O(b^2), which is not great.
Though this is helped by the file buffer. It will be interesting to
benchmark and see if this theoretical O(b^2) translates to poor
performance in practice.
Note ckreads has this same performance issue.
Still, despite these problems, ckfetches may be useful for cases where
you just want an extra layer of safety, or don't care about the tiny
chance an error is introduced between a fetch an subsequent read.
---
Like ckprogs/ckreads, ckfetches is an opt-in feature, and requires both
1. defining LFS_CKFETCHES, and 2. passing LFS_M_CKFETCHES during mount.
This is a bit of a quick implementation to get testing in place, so the
code cost is probably higher than strictly necessary. If we can refactor
the code internally to avoid all the duplicate lfsr_rbyd_fetchck/
lfsr_bptr_ck calls, we can probably bring this down a bit:
code stack
before: 36428 2680
yes-ckfetches: 36848 (+1.2%) 2680 (+0.0%)
no-ckfetches: 36428 (+0.0%) 2680 (+0.0%)
Oh, and also added lfs_emubd_flipbit to allow tests to manually flip
bits themselves. LFS_EMUBD_BADBLOCK_PROGFLIP is quick to find the above
mentioned checksum-read hole.
This could be done manually with read+erase+prog, but no reason to make
it harder than it needs to be.
1482 lines
48 KiB
TOML
1482 lines
48 KiB
TOML
# Test checksum validation things
|
|
after = ['test_traversal', 'test_gc', 'test_mount']
|
|
|
|
|
|
|
|
# Test filesystem-level checksum things
|
|
|
|
# test we can detect at least fully clobbered blocks
|
|
[cases.test_ck_ckmeta_easy]
|
|
# 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.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
code = '''
|
|
lfs_block_t i = 0;
|
|
while (true) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, 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;
|
|
for (lfs_block_t j = 0;; j++) {
|
|
assert(j < 2*BLOCK_COUNT);
|
|
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
goto done;
|
|
}
|
|
|
|
// this gets a bit tricky be cause we need to clobber both
|
|
// blocks in mdir pairs
|
|
if (tinfo.btype == LFS_BTYPE_MDIR
|
|
|| tinfo.btype == LFS_BTYPE_BTREE) {
|
|
if (k == i || k == i+1) {
|
|
// clobber this block
|
|
printf("clobbering 0x%x\n", tinfo.block);
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
CFG->erase(CFG, tinfo.block) => 0;
|
|
CFG->prog(CFG, tinfo.block, 0,
|
|
clobber_buf, BLOCK_SIZE) => 0;
|
|
if (tinfo.btype != LFS_BTYPE_MDIR || k == i+1) {
|
|
i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1;
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
goto clobbered;
|
|
}
|
|
}
|
|
k += 1;
|
|
}
|
|
}
|
|
|
|
clobbered:;
|
|
// find clobbered blocks with lfsr_fs_ckmeta
|
|
if (METHOD == 0) {
|
|
lfsr_fs_ckmeta(&lfs) => LFS_ERR_CORRUPT;
|
|
|
|
// find clobbered blocks with lfsr_fs_gc
|
|
} else if (METHOD == 1) {
|
|
lfsr_fs_gc(&lfs, -1, LFS_GC_CKMETA) => LFS_ERR_CORRUPT;
|
|
|
|
// 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_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
|
|
// find clobbered blocks with lfsr_mount
|
|
} else if (METHOD == 3) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs,
|
|
LFS_M_RDWR
|
|
| LFS_M_CKMETA,
|
|
CFG) => LFS_ERR_CORRUPT;
|
|
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
if (METHOD != 3) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
}
|
|
}
|
|
done:;
|
|
'''
|
|
|
|
[cases.test_ck_ckdata_easy]
|
|
# 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.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
code = '''
|
|
lfs_block_t i = 0;
|
|
while (true) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, 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;
|
|
for (lfs_block_t j = 0;; j++) {
|
|
assert(j < 2*BLOCK_COUNT);
|
|
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
goto done;
|
|
}
|
|
|
|
// this gets a bit tricky be cause we need to clobber both
|
|
// blocks in mdir pairs
|
|
if (tinfo.btype == LFS_BTYPE_MDIR
|
|
|| tinfo.btype == LFS_BTYPE_BTREE
|
|
|| tinfo.btype == LFS_BTYPE_DATA) {
|
|
if (k == i || k == i+1) {
|
|
// clobber this block
|
|
printf("clobbering 0x%x\n", tinfo.block);
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
CFG->erase(CFG, tinfo.block) => 0;
|
|
CFG->prog(CFG, tinfo.block, 0,
|
|
clobber_buf, BLOCK_SIZE) => 0;
|
|
if (tinfo.btype != LFS_BTYPE_MDIR || k == i+1) {
|
|
i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1;
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
goto clobbered;
|
|
}
|
|
}
|
|
k += 1;
|
|
}
|
|
}
|
|
|
|
clobbered:;
|
|
// find clobbered blocks with lfsr_fs_ckmeta
|
|
if (METHOD == 0) {
|
|
lfsr_fs_ckdata(&lfs) => LFS_ERR_CORRUPT;
|
|
|
|
// find clobbered blocks with lfsr_fs_gc
|
|
} else if (METHOD == 1) {
|
|
lfsr_fs_gc(&lfs, -1, LFS_GC_CKDATA) => LFS_ERR_CORRUPT;
|
|
|
|
// 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_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
|
|
// find clobbered blocks with lfsr_mount
|
|
} else if (METHOD == 3) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs,
|
|
LFS_M_RDWR
|
|
| LFS_M_CKDATA,
|
|
CFG) => LFS_ERR_CORRUPT;
|
|
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
if (METHOD != 3) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
}
|
|
}
|
|
done:;
|
|
'''
|
|
|
|
|
|
# Test file-level checksum things
|
|
|
|
# test we can detect at least fully clobbered blocks
|
|
[cases.test_ck_file_ckmeta_easy]
|
|
# METHOD=0 => lfsr_file_ckmeta
|
|
# METHOD=1 => lfsr_file_close+open+ckmeta
|
|
# METHOD=2 => lfsr_file_close+open
|
|
defines.METHOD = [0, 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',
|
|
]
|
|
code = '''
|
|
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;
|
|
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) {
|
|
if (k == i) {
|
|
// clobber this block
|
|
printf("clobbering 0x%x\n", tinfo.block);
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
CFG->erase(CFG, tinfo.block) => 0;
|
|
CFG->prog(CFG, tinfo.block, 0,
|
|
clobber_buf, BLOCK_SIZE) => 0;
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
goto clobbered;
|
|
}
|
|
k += 1;
|
|
}
|
|
}
|
|
|
|
clobbered:;
|
|
// find clobbered blocks with lfsr_file_ckmeta
|
|
if (METHOD == 0) {
|
|
lfsr_file_ckmeta(&lfs, &file) => LFS_ERR_CORRUPT;
|
|
|
|
// 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;
|
|
lfsr_file_ckmeta(&lfs, &file) => LFS_ERR_CORRUPT;
|
|
|
|
// find clobbered blocks with lfsr_file_close+open
|
|
} else if (METHOD == 2) {
|
|
lfsr_file_open(&lfs, &file, "octopus",
|
|
LFS_O_RDONLY | LFS_O_CKMETA) => LFS_ERR_CORRUPT;
|
|
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
if (METHOD != 2) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
lfsr_unmount(&lfs) => 0;
|
|
}
|
|
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.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',
|
|
]
|
|
code = '''
|
|
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;
|
|
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) {
|
|
if (k == i) {
|
|
// clobber this block
|
|
printf("clobbering 0x%x\n", tinfo.block);
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
CFG->erase(CFG, tinfo.block) => 0;
|
|
CFG->prog(CFG, tinfo.block, 0,
|
|
clobber_buf, BLOCK_SIZE) => 0;
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
goto clobbered;
|
|
}
|
|
k += 1;
|
|
}
|
|
}
|
|
|
|
clobbered:;
|
|
// find clobbered blocks with lfsr_file_ckmeta
|
|
if (METHOD == 0) {
|
|
lfsr_file_ckdata(&lfs, &file) => LFS_ERR_CORRUPT;
|
|
|
|
// 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;
|
|
lfsr_file_ckdata(&lfs, &file) => LFS_ERR_CORRUPT;
|
|
|
|
// find clobbered blocks with lfsr_file_close+open
|
|
} else if (METHOD == 2) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
lfsr_file_open(&lfs, &file, "octopus",
|
|
LFS_O_RDONLY | LFS_O_CKDATA) => LFS_ERR_CORRUPT;
|
|
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
if (METHOD != 2) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
lfsr_unmount(&lfs) => 0;
|
|
}
|
|
done:;
|
|
'''
|
|
|
|
|
|
|
|
# Some simple ckprog tests
|
|
#
|
|
# We test these much more aggressively in test_badblocks
|
|
|
|
# test every single-bit error in block 0/1
|
|
[cases.test_ck_ckprogs_mroot]
|
|
defines.BADBLOCK = [0, 1]
|
|
defines.BADBIT = -1
|
|
defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP'
|
|
# this should stay inlined
|
|
defines.SIZE = 'BLOCK_SIZE/16'
|
|
ifdef = 'LFS_CKPROGS'
|
|
code = '''
|
|
// test all bad bits in the mroot
|
|
for (lfs_size_t i = 0;
|
|
i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1);
|
|
i++) {
|
|
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
|
|
|
// mark our badbit as bad
|
|
lfs_emubd_markbadbit(CFG, BADBLOCK, badbit) => 0;
|
|
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
|
(lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8);
|
|
|
|
// formatting the filesystem may already find the bit error
|
|
lfs_t lfs;
|
|
int err = lfsr_format(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt;
|
|
}
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0;
|
|
|
|
{
|
|
// create a file
|
|
lfsr_file_t file;
|
|
err = lfsr_file_open(&lfs, &file, "stygiomedusa",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
goto corrupt_mounted;
|
|
}
|
|
uint32_t prng = 42;
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE);
|
|
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
|
if (res == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
err = lfsr_file_close(&lfs, &file);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// if we made it here without erroring we should be able to
|
|
// read our file
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0;
|
|
}
|
|
|
|
lfsr_file_open(&lfs, &file, "stygiomedusa", LFS_O_RDONLY) => 0;
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
}
|
|
|
|
corrupt_mounted:;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
corrupt:;
|
|
// reset badbit
|
|
lfs_emubd_markgood(CFG, BADBLOCK) => 0;
|
|
}
|
|
'''
|
|
|
|
# test every single-bit error in a file's data block
|
|
[cases.test_ck_ckprogs_data]
|
|
defines.BADBIT = -1
|
|
defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP'
|
|
# this should create a single block file
|
|
defines.SIZE = 'BLOCK_SIZE'
|
|
ifdef = 'LFS_CKPROGS'
|
|
code = '''
|
|
// first we need to figure out where the data block will actually
|
|
// end up, fortunately our block randomization is intentionally
|
|
// consistent
|
|
|
|
// format
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKPROGS, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0;
|
|
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "stygiomedusa",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint32_t prng = 42;
|
|
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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// find the data block
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, 0) => 0;
|
|
lfs_block_t badblock;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
|
|
if (tinfo.btype == LFS_BTYPE_DATA) {
|
|
badblock = tinfo.block;
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// now test all bad bits in the data block
|
|
for (lfs_size_t i = 0;
|
|
i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1);
|
|
i++) {
|
|
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
|
|
|
// mark our badbit as bad
|
|
lfs_emubd_markbadbit(CFG, badblock, badbit) => 0;
|
|
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
|
badblock, badbit/8, badbit, badbit/8, badbit%8);
|
|
|
|
// format
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKPROGS, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0;
|
|
|
|
{
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "stygiomedusa",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint32_t prng = 42;
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE);
|
|
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
|
if (res == LFS_ERR_CORRUPT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
goto corrupt_mounted;
|
|
}
|
|
int err = lfsr_file_close(&lfs, &file);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// if we made it here without erroring we should be able to
|
|
// read our file
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0;
|
|
}
|
|
|
|
lfsr_file_open(&lfs, &file, "stygiomedusa", LFS_O_RDONLY) => 0;
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
}
|
|
|
|
corrupt_mounted:;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// reset badbit
|
|
lfs_emubd_markgood(CFG, badblock) => 0;
|
|
}
|
|
'''
|
|
|
|
# test every single-bit error in a file's btree node
|
|
[cases.test_ck_ckprogs_btree]
|
|
defines.BADBIT = -1
|
|
defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP'
|
|
# force the file to create a btree
|
|
defines.INLINE_SIZE = 0
|
|
defines.CRYSTAL_THRESH = -1
|
|
defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8'
|
|
defines.SIZE = '2*FRAGMENT_SIZE'
|
|
ifdef = 'LFS_CKPROGS'
|
|
code = '''
|
|
// first we need to figure out where the btree block will actually
|
|
// end up, fortunately our block randomization is intentionally
|
|
// consistent
|
|
|
|
// format
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKPROGS, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0;
|
|
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "stygiomedusa",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint32_t prng = 42;
|
|
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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// find the btree block
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, 0) => 0;
|
|
lfs_block_t badblock;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
|
|
if (tinfo.btype == LFS_BTYPE_BTREE) {
|
|
badblock = tinfo.block;
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// now test all bad bits in the btree block
|
|
for (lfs_size_t i = 0;
|
|
i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1);
|
|
i++) {
|
|
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
|
|
|
// mark our badbit as bad
|
|
lfs_emubd_markbadbit(CFG, badblock, badbit) => 0;
|
|
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
|
badblock, badbit/8, badbit, badbit/8, badbit%8);
|
|
|
|
// format
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKPROGS, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0;
|
|
|
|
{
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "stygiomedusa",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint32_t prng = 42;
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE);
|
|
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
|
if (res == LFS_ERR_CORRUPT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
goto corrupt_mounted;
|
|
}
|
|
int err = lfsr_file_close(&lfs, &file);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// if we made it here without erroring we should be able to
|
|
// read our file
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0;
|
|
}
|
|
|
|
lfsr_file_open(&lfs, &file, "stygiomedusa", LFS_O_RDONLY) => 0;
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
}
|
|
|
|
corrupt_mounted:;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// reset badbit
|
|
lfs_emubd_markgood(CFG, badblock) => 0;
|
|
}
|
|
'''
|
|
|
|
|
|
|
|
# Some simple ckread tests
|
|
|
|
# These tests were originally intended to test all single-bit
|
|
# metastability errors with ckreads, however they quickly found that
|
|
# ckreads can't actually guarantee single-bit error-detection since
|
|
# the bit flip may alter the leb128 encoded size field and find a new,
|
|
# erronous, parity bit.
|
|
#
|
|
# For example, one bit flip:
|
|
#
|
|
# 40 0c 00 12 80 0d ff ff
|
|
# '----.----' ^--------------------.
|
|
# '- altble 0xc w0 -18 parity=1
|
|
#
|
|
# 40 0c 80 12 80 0d ff ff
|
|
# '-------.-------' ^----------------------.
|
|
# '- altble 0xc w2304 -1664 parity=1
|
|
#
|
|
# This doesn't make ckreads _completely_ useless, just mostly useless.
|
|
# We can still use it to check parity bits, but without a systematic
|
|
# proof.
|
|
#
|
|
# So for now these tests are sort of in stasis, limited to testing
|
|
# metastability in areas we know we can detect (revision counts, raw
|
|
# data blocks, etc). Maybe future features will make them more useful.
|
|
#
|
|
|
|
# test every single-bit error in block 0/1
|
|
[cases.test_ck_ckreads_mroot]
|
|
defines.BADBLOCK = [0, 1]
|
|
defines.BADBIT = -1
|
|
defines.BADBLOCK_BEHAVIOR = [
|
|
'LFS_EMUBD_BADBLOCK_PROGFLIP',
|
|
'LFS_EMUBD_BADBLOCK_READFLIP',
|
|
]
|
|
# this should stay inlined
|
|
defines.SIZE = 'BLOCK_SIZE/16'
|
|
ifdef = 'LFS_CKREADS'
|
|
code = '''
|
|
// test all bad bits in the mroot
|
|
for (lfs_size_t i = 0;
|
|
// we can't detect metastable tags, so limit read-flips
|
|
// to our revision count
|
|
i < ((BADBIT == -1) ? 8*4 : 1);
|
|
i++) {
|
|
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
|
|
|
// reset the bd prng every run for reproducibility
|
|
lfs_emubd_seed(CFG, 42) => 0;
|
|
|
|
// mark our badbit as bad
|
|
lfs_emubd_markbadbit(CFG, BADBLOCK, badbit) => 0;
|
|
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
|
(lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8);
|
|
|
|
// With metastability, basically any filesystem operation can
|
|
// return LFS_ERR_CORRUPT. This is ok, what we're really testing
|
|
// for is no internal/external asserts failing.
|
|
|
|
// format
|
|
lfs_t lfs;
|
|
int err = lfsr_format(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt;
|
|
}
|
|
err = lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt;
|
|
}
|
|
|
|
{
|
|
// create a file
|
|
lfsr_file_t file;
|
|
err = lfsr_file_open(&lfs, &file, "bathykorus",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
uint32_t prng = 42;
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE);
|
|
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
|
if (res == LFS_ERR_CORRUPT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
goto corrupt_mounted;
|
|
}
|
|
err = lfsr_file_close(&lfs, &file);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// try to read our file
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
err = lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt;
|
|
}
|
|
}
|
|
|
|
// yes reads can fail here
|
|
err = lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_RDONLY);
|
|
assert(!err
|
|
|| err == LFS_ERR_CORRUPT
|
|
// bit errors can also cause our fs state to "rollback",
|
|
// which is not great but we can't solve this with ckreads
|
|
// alone
|
|
|| err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_CORRUPT || err == LFS_ERR_NOENT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
uint8_t rbuf[SIZE];
|
|
lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE);
|
|
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
|
if (res == LFS_ERR_CORRUPT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
goto corrupt_mounted;
|
|
}
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
}
|
|
|
|
corrupt_mounted:;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
corrupt:;
|
|
// reset badbit
|
|
lfs_emubd_markgood(CFG, BADBLOCK) => 0;
|
|
}
|
|
'''
|
|
|
|
# test every single-bit error in a file's data block
|
|
[cases.test_ck_ckreads_data]
|
|
defines.BADBIT = -1
|
|
defines.BADBLOCK_BEHAVIOR = [
|
|
'LFS_EMUBD_BADBLOCK_PROGFLIP',
|
|
'LFS_EMUBD_BADBLOCK_READFLIP',
|
|
]
|
|
# this should create a single block file
|
|
defines.SIZE = 'BLOCK_SIZE'
|
|
ifdef = 'LFS_CKREADS'
|
|
code = '''
|
|
// first we need to figure out where the data block will actually
|
|
// end up, fortunately our block randomization is intentionally
|
|
// consistent
|
|
|
|
// format
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKREADS, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG) => 0;
|
|
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "bathykorus",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint32_t prng = 42;
|
|
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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// find the data block
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, 0) => 0;
|
|
lfs_block_t badblock;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
|
|
if (tinfo.btype == LFS_BTYPE_DATA) {
|
|
badblock = tinfo.block;
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// now test all bad bits in the data block
|
|
for (lfs_size_t i = 0;
|
|
i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1);
|
|
i++) {
|
|
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
|
|
|
// reset the bd prng every run for reproducibility
|
|
lfs_emubd_seed(CFG, 42) => 0;
|
|
|
|
// mark our badbit as bad
|
|
lfs_emubd_markbadbit(CFG, badblock, badbit) => 0;
|
|
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
|
badblock, badbit/8, badbit, badbit/8, badbit%8);
|
|
|
|
// With metastability, basically any filesystem operation can
|
|
// return LFS_ERR_CORRUPT. This is ok, what we're really testing
|
|
// for is no internal/external asserts failing.
|
|
|
|
// format
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKREADS, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG) => 0;
|
|
|
|
{
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "bathykorus",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint32_t prng = 42;
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE);
|
|
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
|
if (res == LFS_ERR_CORRUPT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
goto corrupt_mounted;
|
|
}
|
|
int err = lfsr_file_close(&lfs, &file);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// try to read our file
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG) => 0;
|
|
}
|
|
|
|
// yes reads can fail here
|
|
err = lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_RDONLY);
|
|
assert(!err
|
|
|| err == LFS_ERR_CORRUPT
|
|
// bit errors can also cause our fs state to "rollback",
|
|
// which is not great but we can't solve this with ckreads
|
|
// alone
|
|
|| err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_CORRUPT || err == LFS_ERR_NOENT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
uint8_t rbuf[SIZE];
|
|
lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE);
|
|
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
|
if (res == LFS_ERR_CORRUPT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
goto corrupt_mounted;
|
|
}
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
}
|
|
|
|
corrupt_mounted:;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// reset badbit
|
|
lfs_emubd_markgood(CFG, badblock) => 0;
|
|
}
|
|
'''
|
|
|
|
# test every single-bit error in a file's btree node
|
|
[cases.test_ck_ckreads_btree]
|
|
defines.BADBIT = -1
|
|
defines.BADBLOCK_BEHAVIOR = [
|
|
'LFS_EMUBD_BADBLOCK_PROGFLIP',
|
|
'LFS_EMUBD_BADBLOCK_READFLIP',
|
|
]
|
|
# force the file to create a btree
|
|
defines.INLINE_SIZE = 0
|
|
defines.CRYSTAL_THRESH = -1
|
|
defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8'
|
|
defines.SIZE = '2*FRAGMENT_SIZE'
|
|
ifdef = 'LFS_CKREADS'
|
|
code = '''
|
|
// first we need to figure out where the btree block will actually
|
|
// end up, fortunately our block randomization is intentionally
|
|
// consistent
|
|
|
|
// format
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKREADS, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG) => 0;
|
|
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "bathykorus",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint32_t prng = 42;
|
|
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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// find the btree block
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, 0) => 0;
|
|
lfs_block_t badblock;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
|
|
if (tinfo.btype == LFS_BTYPE_BTREE) {
|
|
badblock = tinfo.block;
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// now test all bad bits in the btree block
|
|
for (lfs_size_t i = 0;
|
|
// we can't detect metastable tags, so limit read-flips
|
|
// to our revision count
|
|
i < ((BADBIT == -1) ? 8*4 : 1);
|
|
i++) {
|
|
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
|
|
|
// reset the bd prng every run for reproducibility
|
|
lfs_emubd_seed(CFG, 42) => 0;
|
|
|
|
// mark our badbit as bad
|
|
lfs_emubd_markbadbit(CFG, badblock, badbit) => 0;
|
|
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
|
badblock, badbit/8, badbit, badbit/8, badbit%8);
|
|
|
|
// With metastability, basically any filesystem operation can
|
|
// return LFS_ERR_CORRUPT. This is ok, what we're really testing
|
|
// for is no internal/external asserts failing.
|
|
|
|
// format
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKREADS, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG) => 0;
|
|
|
|
{
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "bathykorus",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint32_t prng = 42;
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE);
|
|
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
|
if (res == LFS_ERR_CORRUPT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
goto corrupt_mounted;
|
|
}
|
|
int err = lfsr_file_close(&lfs, &file);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// try to read our file
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG) => 0;
|
|
}
|
|
|
|
// yes reads can fail here
|
|
err = lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_RDONLY);
|
|
assert(!err
|
|
|| err == LFS_ERR_CORRUPT
|
|
// bit errors can also cause our fs state to "rollback",
|
|
// which is not great but we can't solve this with ckreads
|
|
// alone
|
|
|| err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_CORRUPT || err == LFS_ERR_NOENT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
uint8_t rbuf[SIZE];
|
|
lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE);
|
|
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
|
if (res == LFS_ERR_CORRUPT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
goto corrupt_mounted;
|
|
}
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
}
|
|
|
|
corrupt_mounted:;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// reset badbit
|
|
lfs_emubd_markgood(CFG, badblock) => 0;
|
|
}
|
|
'''
|
|
|
|
|
|
|
|
# Some simple ckfetches tests
|
|
|
|
# test every single-bit error in block 0/1
|
|
[cases.test_ck_ckfetches_mroot]
|
|
defines.BADBLOCK = [0, 1]
|
|
defines.BADBIT = -1
|
|
# this should stay inlined
|
|
defines.SIZE = 'BLOCK_SIZE/16'
|
|
ifdef = 'LFS_CKFETCHES'
|
|
code = '''
|
|
// test all bad bits in the mroot
|
|
for (lfs_size_t i = 0;
|
|
i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1);
|
|
i++) {
|
|
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
|
|
|
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
|
(lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8);
|
|
|
|
// format
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKFETCHES, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKFETCHES, CFG) => 0;
|
|
|
|
{
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "tripedalia",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint32_t prng = 42;
|
|
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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// try to read our file
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// flip our badbit
|
|
lfs_emubd_flipbit(CFG, BADBLOCK, badbit) => 0;
|
|
|
|
int err = lfsr_mount(&lfs,
|
|
LFS_M_RDWR | LFS_M_CKFETCHES, CFG);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt;
|
|
}
|
|
}
|
|
|
|
// yes reads can fail here
|
|
int err = lfsr_file_open(&lfs, &file,
|
|
"tripedalia", LFS_O_RDONLY);
|
|
assert(!err
|
|
// bit errors can also cause our fs state to "rollback",
|
|
// which is not great but we can't solve this with
|
|
// ckfetches alone
|
|
|| err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
}
|
|
|
|
corrupt_mounted:;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
corrupt:;
|
|
// reset badbit
|
|
lfs_emubd_markgood(CFG, BADBLOCK) => 0;
|
|
}
|
|
'''
|
|
|
|
# test every single-bit error in a file's data block
|
|
[cases.test_ck_ckfetches_data]
|
|
defines.BADBIT = -1
|
|
# this should create a single block file
|
|
defines.SIZE = 'BLOCK_SIZE'
|
|
ifdef = 'LFS_CKFETCHES'
|
|
code = '''
|
|
// first we need to figure out where the data block will actually
|
|
// end up, fortunately our block randomization is intentionally
|
|
// consistent
|
|
|
|
// format
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKFETCHES, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKFETCHES, CFG) => 0;
|
|
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "tripedalia",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint32_t prng = 42;
|
|
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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// find the data block
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, 0) => 0;
|
|
lfs_block_t badblock;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
|
|
if (tinfo.btype == LFS_BTYPE_DATA) {
|
|
badblock = tinfo.block;
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// now test all bad bits in the data block
|
|
for (lfs_size_t i = 0;
|
|
i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1);
|
|
i++) {
|
|
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
|
|
|
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
|
badblock, badbit/8, badbit, badbit/8, badbit%8);
|
|
|
|
// format
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKFETCHES, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKFETCHES, CFG) => 0;
|
|
|
|
{
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "tripedalia",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint32_t prng = 42;
|
|
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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// flip our badbit
|
|
lfs_emubd_flipbit(CFG, badblock, badbit) => 0;
|
|
|
|
// try to read our file
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKFETCHES, CFG) => 0;
|
|
}
|
|
|
|
// yes reads can fail here
|
|
int err = lfsr_file_open(&lfs, &file,
|
|
"tripedalia", LFS_O_RDONLY);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
uint8_t rbuf[SIZE];
|
|
lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE);
|
|
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
|
if (res == LFS_ERR_CORRUPT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
goto corrupt_mounted;
|
|
}
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
}
|
|
|
|
corrupt_mounted:;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// reset badbit
|
|
lfs_emubd_markgood(CFG, badblock) => 0;
|
|
}
|
|
'''
|
|
|
|
# test every single-bit error in a file's btree node
|
|
[cases.test_ck_ckfetches_btree]
|
|
defines.BADBIT = -1
|
|
# force the file to create a btree
|
|
defines.INLINE_SIZE = 0
|
|
defines.CRYSTAL_THRESH = -1
|
|
defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8'
|
|
defines.SIZE = '2*FRAGMENT_SIZE'
|
|
ifdef = 'LFS_CKFETCHES'
|
|
code = '''
|
|
// first we need to figure out where the btree block will actually
|
|
// end up, fortunately our block randomization is intentionally
|
|
// consistent
|
|
|
|
// format
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKFETCHES, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKFETCHES, CFG) => 0;
|
|
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "tripedalia",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint32_t prng = 42;
|
|
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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// find the btree block
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, 0) => 0;
|
|
lfs_block_t badblock;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
|
|
if (tinfo.btype == LFS_BTYPE_BTREE) {
|
|
badblock = tinfo.block;
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// now test all bad bits in the btree block
|
|
for (lfs_size_t i = 0;
|
|
i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1);
|
|
i++) {
|
|
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
|
|
|
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
|
badblock, badbit/8, badbit, badbit/8, badbit%8);
|
|
|
|
// format
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKFETCHES, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKFETCHES, CFG) => 0;
|
|
|
|
{
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "tripedalia",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint32_t prng = 42;
|
|
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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// flip our badbit
|
|
lfs_emubd_flipbit(CFG, badblock, badbit) => 0;
|
|
|
|
// try to read our file
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKFETCHES, CFG) => 0;
|
|
}
|
|
|
|
// yes reads can fail here
|
|
int err = lfsr_file_open(&lfs, &file,
|
|
"tripedalia", LFS_O_RDONLY);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
uint8_t rbuf[SIZE];
|
|
lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE);
|
|
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
|
if (res == LFS_ERR_CORRUPT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
goto corrupt_mounted;
|
|
}
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
}
|
|
|
|
corrupt_mounted:;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// reset badbit
|
|
lfs_emubd_markgood(CFG, badblock) => 0;
|
|
}
|
|
'''
|