185f209dbf
Added some code, though we don't _really_ care:
code stack
before: 37872 3048
after: 38060 (+0.5%) 3056 (+0.3%)
Also interesting to note the difference in testing time, this highlights
_some_ of the performance cost of ckreads:
with ckreads: 1135.92s
without ckreads: 821.24s
1173 lines
38 KiB
TOML
1173 lines
38 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, 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, 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, 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, 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'
|
|
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, 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'
|
|
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, 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, 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'
|
|
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, 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, 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
|
|
#
|
|
# We test these much more aggressively in test_badblocks
|
|
|
|
# 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'
|
|
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, 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
|
|
// metastability 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'
|
|
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, 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, 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
|
|
// metastability 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'
|
|
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, 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, 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
|
|
// metastability 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;
|
|
}
|
|
'''
|