109bd4e0ab
This just exposes the gcksum to the user, but exposing the gcksum allows
the user to store it externally for an extra layer of protection against
filesystem corruption.
As far as I'm aware this is the only real way to protect against global
rollback issues, which is a problem for any filesystem with logs (aka
any powerloss-resilient filesystem).
This required a comically small amount of code:
code stack ctx
before: 38492 2624 640
after: 38500 (+0.0%) 2624 (+0.0%) 640 (+0.0%)
4560 lines
153 KiB
TOML
4560 lines
153 KiB
TOML
# Test checksum validation things
|
|
after = ['test_traversal', 'test_gc', 'test_mount']
|
|
|
|
|
|
code = '''
|
|
// naive crc32c
|
|
static uint32_t test_ck_naive_crc32c(
|
|
uint32_t crc, const void *buffer, size_t size) {
|
|
const uint8_t *buffer_ = buffer;
|
|
crc ^= 0xffffffff;
|
|
|
|
for (size_t i = 0; i < size; i++) {
|
|
crc = crc ^ buffer_[i];
|
|
for (size_t j = 0; j < 8; j++) {
|
|
crc = (crc >> 1) ^ ((crc & 1) ? 0x82f63b78 : 0);
|
|
}
|
|
}
|
|
|
|
crc ^= 0xffffffff;
|
|
return crc;
|
|
}
|
|
|
|
// naive crc32c multiplication
|
|
static uint32_t test_ck_naive_crc32c_mul(uint32_t a, uint32_t b) {
|
|
// pmul
|
|
uint64_t r = 0;
|
|
for (int i = 0; i < 32; i++) {
|
|
if (b & (1 << i)) {
|
|
r ^= (uint64_t)a << i;
|
|
}
|
|
}
|
|
|
|
// mod crc32c
|
|
for (int i = 0; i < 31; i++) {
|
|
r = (r >> 1) ^ ((r & 1) ? 0x82f63b78 : 0);
|
|
}
|
|
|
|
return (uint32_t)r;
|
|
}
|
|
'''
|
|
|
|
|
|
# let's first check that our crc32c math probably works
|
|
|
|
# try some random inputs and compare with a naive implementation
|
|
[cases.test_ck_crc32c]
|
|
defines.SIZE = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SEED = 'range(10)'
|
|
defines.N = 1000
|
|
fuzz = 'SEED'
|
|
code = '''
|
|
uint32_t prng = SEED;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
uint8_t buffer[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
buffer[j] = TEST_PRNG(&prng);
|
|
}
|
|
|
|
uint32_t a = test_ck_naive_crc32c(0, buffer, SIZE);
|
|
uint32_t b = lfs_crc32c(0, buffer, SIZE);
|
|
assert(a == b);
|
|
}
|
|
'''
|
|
|
|
# test incremental crc32cs
|
|
[cases.test_ck_crc32c_incr]
|
|
defines.SIZE = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SEED = 'range(10)'
|
|
defines.N = 1000
|
|
fuzz = 'SEED'
|
|
code = '''
|
|
uint32_t prng = SEED;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
uint8_t buffer[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
buffer[j] = TEST_PRNG(&prng);
|
|
}
|
|
|
|
uint32_t a = lfs_crc32c(0, buffer, SIZE);
|
|
uint32_t b = 0;
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
b = lfs_crc32c(b, &buffer[j], 1);
|
|
}
|
|
assert(a == b);
|
|
}
|
|
'''
|
|
|
|
# try some random inputs and compare with a naive implementation
|
|
[cases.test_ck_crc32c_mul]
|
|
defines.SEED = 'range(10)'
|
|
defines.N = 1000
|
|
fuzz = 'SEED'
|
|
code = '''
|
|
uint32_t prng = SEED;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
uint32_t x = TEST_PRNG(&prng);
|
|
uint32_t y = TEST_PRNG(&prng);
|
|
|
|
uint32_t a = test_ck_naive_crc32c_mul(x, y);
|
|
uint32_t b = lfs_crc32c_mul(x, y);
|
|
assert(a == b);
|
|
}
|
|
'''
|
|
|
|
# test that multiplication is distributive
|
|
[cases.test_ck_crc32c_mul_dist]
|
|
defines.SEED = 'range(10)'
|
|
defines.N = 1000
|
|
fuzz = 'SEED'
|
|
code = '''
|
|
uint32_t prng = SEED;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
uint32_t x = TEST_PRNG(&prng);
|
|
uint32_t y = TEST_PRNG(&prng);
|
|
uint32_t z = TEST_PRNG(&prng);
|
|
|
|
uint32_t a = lfs_crc32c_mul(x, y ^ z);
|
|
uint32_t b = lfs_crc32c_mul(x, y) ^ lfs_crc32c_mul(x, z);
|
|
assert(a == b);
|
|
}
|
|
'''
|
|
|
|
|
|
# Test filesystem-level checksum things
|
|
|
|
# test that lfsr_fs_cksum doesn't do anything weird
|
|
[cases.test_ck_cksum]
|
|
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',
|
|
'LFS_IFDEF_GC(true, METHOD != 1)',
|
|
]
|
|
code = '''
|
|
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;
|
|
}
|
|
|
|
// get the filesystem cksum
|
|
uint32_t gcksum;
|
|
lfsr_fs_cksum(&lfs, &gcksum) => 0;
|
|
printf("cksum: %08x\n", gcksum);
|
|
|
|
// test that the cksum remains the same after a remount
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t gcksum_;
|
|
lfsr_fs_cksum(&lfs, &gcksum_) => 0;
|
|
assert(gcksum_ == gcksum);
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# 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.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',
|
|
]
|
|
if = [
|
|
'(SIZE*N)/BLOCK_SIZE <= 32',
|
|
'LFS_IFDEF_GC(true, METHOD != 1)',
|
|
]
|
|
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) {
|
|
#ifdef LFS_GC
|
|
lfsr_fs_gc(&lfs) => LFS_ERR_CORRUPT;
|
|
#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_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.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',
|
|
]
|
|
if = [
|
|
'(SIZE*N)/BLOCK_SIZE <= 32',
|
|
'LFS_IFDEF_GC(true, METHOD != 1)',
|
|
]
|
|
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) {
|
|
#ifdef LFS_GC
|
|
lfsr_fs_gc(&lfs) => LFS_ERR_CORRUPT;
|
|
#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_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 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:;
|
|
// save the current gcksum
|
|
uint32_t gcksum;
|
|
lfsr_fs_cksum(&lfs, &gcksum) => 0;
|
|
|
|
// 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) {
|
|
uint32_t gcksum_;
|
|
lfsr_fs_cksum(&lfs, &gcksum_) => 0;
|
|
assert(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:;
|
|
// save the current gcksum
|
|
uint32_t gcksum = lfs.gcksum;
|
|
lfsr_fs_cksum(&lfs, &gcksum) => 0;
|
|
|
|
// 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) {
|
|
uint32_t gcksum_;
|
|
lfsr_fs_cksum(&lfs, &gcksum_) => 0;
|
|
assert(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
|
|
|
|
# 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, 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',
|
|
]
|
|
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:;
|
|
'''
|
|
|
|
[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, 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',
|
|
]
|
|
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:;
|
|
'''
|
|
|
|
# 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
|
|
|
|
# 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;
|
|
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
|
(lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8);
|
|
|
|
// mark our badbit as bad
|
|
lfs_emubd_markbadbit(CFG, BADBLOCK, badbit) => 0;
|
|
|
|
// 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, "physalia",
|
|
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, "physalia", 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, "physalia",
|
|
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);
|
|
|
|
// mark our badbit as bad
|
|
lfs_emubd_markbadbit(CFG, badblock, badbit) => 0;
|
|
|
|
// 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, "physalia",
|
|
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, "physalia", 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, "physalia",
|
|
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);
|
|
|
|
// mark our badbit as bad
|
|
lfs_emubd_markbadbit(CFG, badblock, badbit) => 0;
|
|
|
|
// 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, "physalia",
|
|
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, "physalia", 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 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, "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;
|
|
|
|
// 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,
|
|
"stygiomedusa", 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, "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;
|
|
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, "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;
|
|
|
|
// 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,
|
|
"stygiomedusa", 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, "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;
|
|
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, "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;
|
|
|
|
// 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,
|
|
"stygiomedusa", 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;
|
|
}
|
|
'''
|
|
|
|
|
|
|
|
# Some simple ckparity tests
|
|
|
|
# These tests were originally intended to test all single-bit
|
|
# metastability errors with ckparity, however they quickly found that
|
|
# ckparity 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 ckparity _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. Maybe future features
|
|
# will make them more useful.
|
|
#
|
|
|
|
# test some single-bit errors in block 0/1
|
|
[cases.test_ck_ckparity_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_CKPARITY'
|
|
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 + first tag
|
|
i < ((BADBIT == -1) ? 8*6 : 1);
|
|
i++) {
|
|
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
|
|
|
// reset the bd prng every run for reproducibility
|
|
lfs_emubd_seed(CFG, 42);
|
|
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
|
(lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8);
|
|
|
|
// mark our badbit as bad
|
|
lfs_emubd_markbadbit(CFG, BADBLOCK, badbit) => 0;
|
|
|
|
// 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_CKPARITY, CFG);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt;
|
|
}
|
|
err = lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPARITY, 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, "tripedalia",
|
|
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_CKPARITY, CFG);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt;
|
|
}
|
|
}
|
|
|
|
// yes reads can fail here
|
|
err = lfsr_file_open(&lfs, &file, "tripedalia", 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
|
|
// ckparity 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 some single-bit errors in a file's btree node
|
|
[cases.test_ck_ckparity_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_CKPARITY'
|
|
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_CKPARITY, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPARITY, 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;
|
|
// we can't detect metastable tags, so limit read-flips
|
|
// to our revision count + first tag
|
|
i < ((BADBIT == -1) ? 8*6 : 1);
|
|
i++) {
|
|
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
|
|
|
// reset the bd prng every run for reproducibility
|
|
lfs_emubd_seed(CFG, 42);
|
|
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
|
badblock, badbit/8, badbit, badbit/8, badbit%8);
|
|
|
|
// mark our badbit as bad
|
|
lfs_emubd_markbadbit(CFG, badblock, badbit) => 0;
|
|
|
|
// 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_CKPARITY, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPARITY, 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);
|
|
}
|
|
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_CKPARITY, CFG) => 0;
|
|
}
|
|
|
|
// yes reads can fail here
|
|
err = lfsr_file_open(&lfs, &file, "tripedalia", 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
|
|
// ckparity 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 ckdatacksums tests
|
|
|
|
# test every single-bit error in a file's data block
|
|
[cases.test_ck_ckdatacksums_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_CKDATACKSUMS'
|
|
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_CKDATACKSUMS, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKDATACKSUMS, 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);
|
|
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
|
badblock, badbit/8, badbit, badbit/8, badbit%8);
|
|
|
|
// mark our badbit as bad
|
|
lfs_emubd_markbadbit(CFG, badblock, badbit) => 0;
|
|
|
|
// 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_CKDATACKSUMS, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKDATACKSUMS, 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_CKDATACKSUMS,
|
|
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;
|
|
}
|
|
'''
|
|
|
|
|
|
|
|
|
|
## High-level error spam tests
|
|
#
|
|
# we basically just throw errors at filesystem operations until they
|
|
# error with either LFS_ERR_CORRUPT or LFS_ERR_NOSPC
|
|
#
|
|
# TODO revisit these when ckredund is implemented, ckredund should
|
|
# finally close the ckread hole
|
|
|
|
# fuzz errors with fuzz dirs
|
|
[cases.test_ck_spam_dir_fuzz]
|
|
# METHOD=0 => ckprogs
|
|
# METHOD=1 => ckdata
|
|
# METHOD=2 => ckmeta+ckfetches
|
|
# METHOD=3 => ckmeta+ckdatacksums
|
|
defines.METHOD = [0, 1, 2, 3]
|
|
defines.PERIOD = 10
|
|
# protecting the mrootanchor encourages more interesting failures, and
|
|
# simulates storage with hardened {0,1} blocks
|
|
defines.PROTECTED_MROOTANCHOR = [false, true]
|
|
# we can't reliably detect bit errors in erased blocks, we rely on
|
|
# future progs failing if this happens
|
|
defines.ERASE_VALUE = -1
|
|
defines.BADBLOCK_BEHAVIOR = '''
|
|
(METHOD == 0)
|
|
? LFS_EMUBD_BADBLOCK_PROGFLIP
|
|
: LFS_EMUBD_BADBLOCK_MANUAL
|
|
'''
|
|
defines.CKMETA = 'METHOD == 2 || METHOD == 3'
|
|
defines.CKDATA = 'METHOD == 1'
|
|
defines.MTREEONLY = 'METHOD == 2'
|
|
defines.CKPROGS = 'METHOD == 0'
|
|
defines.CKFETCHES = 'METHOD == 2'
|
|
defines.CKPARITY = false
|
|
defines.CKDATACKSUMS = 'METHOD == 3'
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256]
|
|
defines.SEED = 'range(10)'
|
|
fuzz = 'SEED'
|
|
if = [
|
|
'LFS_IFDEF_CKPROGS(true, !CKPROGS)',
|
|
'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)',
|
|
'LFS_IFDEF_CKPARITY(true, !CKPARITY)',
|
|
'LFS_IFDEF_CKDATACKSUMS(true, !CKDATACKSUMS)',
|
|
]
|
|
code = '''
|
|
// seed our block device with our seed so we have different error
|
|
// bit patterns
|
|
uint32_t prng = SEED;
|
|
lfs_emubd_seed(CFG, TEST_PRNG(&prng));
|
|
|
|
// create a permutation of blocks to test against
|
|
//
|
|
// precalculating the permutation avoids issues around running out
|
|
// of blocks to randomly select
|
|
uint32_t badblocks[BLOCK_COUNT];
|
|
TEST_PERMUTATION(TEST_PRNG(&prng), badblocks, BLOCK_COUNT);
|
|
|
|
// test fuzz with dirs
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs,
|
|
LFS_F_RDWR
|
|
| ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_F_CKPROGS, -1) : 0)
|
|
| ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_F_CKFETCHES, -1) : 0)
|
|
| ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_F_CKPARITY, -1) : 0)
|
|
| ((CKDATACKSUMS)
|
|
? LFS_IFDEF_CKDATACKSUMS(LFS_F_CKDATACKSUMS, -1)
|
|
: 0),
|
|
CFG) => 0;
|
|
lfsr_mount(&lfs,
|
|
LFS_M_RDWR
|
|
| ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) : 0)
|
|
| ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) : 0)
|
|
| ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_M_CKPARITY, -1) : 0)
|
|
| ((CKDATACKSUMS)
|
|
? LFS_IFDEF_CKDATACKSUMS(LFS_M_CKDATACKSUMS, -1)
|
|
: 0),
|
|
CFG) => 0;
|
|
|
|
// set up a simulation to compare against
|
|
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
|
|
lfs_size_t sim_size = 0;
|
|
|
|
// keep adding errors until we either run out of blocks or detect
|
|
// corruption
|
|
lfs_size_t i = 0;
|
|
for (; i < PERIOD*BLOCK_COUNT; i++) {
|
|
// add an error?
|
|
if (i % PERIOD == 0
|
|
// protected mrootanchor? (just makes things more interesting)
|
|
&& !(PROTECTED_MROOTANCHOR && badblocks[i/PERIOD] < 2)) {
|
|
lfs_block_t badblock = badblocks[i/PERIOD];
|
|
printf("badblock: 0x%x\n", badblock);
|
|
|
|
// our different error-detection methods detect different
|
|
// types of errors, so we implement errors for each one a
|
|
// bit differently
|
|
|
|
// mark our badblock as bad
|
|
lfs_emubd_markbad(CFG, badblock) => 0;
|
|
|
|
// manually flipping? flip all badbits in badblocks
|
|
if (BADBLOCK_BEHAVIOR == LFS_EMUBD_BADBLOCK_MANUAL) {
|
|
lfs_emubd_flip(CFG) => 0;
|
|
}
|
|
|
|
// run ckdata?
|
|
if (CKDATA) {
|
|
int err = lfsr_fs_ckdata(&lfs);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
// run ckmeta?
|
|
} else if (CKMETA && !MTREEONLY) {
|
|
int err = lfsr_fs_ckmeta(&lfs);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
// run ckmeta mtreeonly?
|
|
} else if (CKMETA && MTREEONLY) {
|
|
// need an explicit traversal for this
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t,
|
|
LFS_T_MTREEONLY | 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 corrupt_mounted;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
}
|
|
}
|
|
|
|
// keep testing...
|
|
|
|
// choose a pseudo-random op, either mkdir, remove, or rename
|
|
uint8_t op = TEST_PRNG(&prng) % 3;
|
|
|
|
if (op == 0 || sim_size == 0) {
|
|
// choose a pseudo-random number, truncate to 3 hexadecimals
|
|
lfs_size_t x = TEST_PRNG(&prng) % N;
|
|
// insert into our sim
|
|
for (lfs_size_t j = 0;; j++) {
|
|
if (j >= sim_size || sim[j] >= x) {
|
|
// already seen?
|
|
if (j < sim_size && sim[j] == x) {
|
|
// do nothing
|
|
} else {
|
|
// insert
|
|
memmove(&sim[j+1], &sim[j],
|
|
(sim_size-j)*sizeof(lfs_size_t));
|
|
sim_size += 1;
|
|
sim[j] = x;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// create a directory here
|
|
char name[256];
|
|
sprintf(name, "dir%03x", x);
|
|
int err = lfsr_mkdir(&lfs, name);
|
|
assert(!err || err == LFS_ERR_EXIST || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
} else if (op == 1) {
|
|
// choose a pseudo-random entry to delete
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
// delete from our sim
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
sim_size -= 1;
|
|
|
|
// remove this directory
|
|
char name[256];
|
|
sprintf(name, "dir%03x", x);
|
|
int err = lfsr_remove(&lfs, name);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
} else {
|
|
// choose a pseudo-random entry to rename, and a pseudo-random
|
|
// number to rename to
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
lfs_size_t y = TEST_PRNG(&prng) % N;
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= y) {
|
|
// already seen and not a noop?
|
|
if (k < sim_size && sim[k] == y && x != y) {
|
|
// just delete the original entry
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
sim_size -= 1;
|
|
} else {
|
|
// first delete
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// then insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
sim[k] = y;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// rename this directory
|
|
char old_name[256];
|
|
sprintf(old_name, "dir%03x", x);
|
|
char new_name[256];
|
|
sprintf(new_name, "dir%03x", y);
|
|
int err = lfsr_rename(&lfs, old_name, new_name);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs,
|
|
LFS_M_RDWR
|
|
| ((CKPROGS)
|
|
? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1)
|
|
: 0)
|
|
| ((CKFETCHES)
|
|
? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1)
|
|
: 0)
|
|
| ((CKPARITY)
|
|
? LFS_IFDEF_CKPARITY(LFS_M_CKPARITY, -1)
|
|
: 0)
|
|
| ((CKDATACKSUMS)
|
|
? LFS_IFDEF_CKDATACKSUMS(LFS_M_CKDATACKSUMS, -1)
|
|
: 0),
|
|
CFG) => 0;
|
|
}
|
|
|
|
// grm should be zero here
|
|
assert(lfs.grm_p[0] == 0);
|
|
|
|
// test that our directories match our simulation
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "dir%03x", sim[j]);
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, name, &info) => 0;
|
|
char name2[256];
|
|
sprintf(name2, "dir%03x", sim[j]);
|
|
assert(strcmp(info.name, name2) == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
}
|
|
|
|
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);
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "dir%03x", sim[j]);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
}
|
|
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
|
lfsr_dir_close(&lfs, &dir) => 0;
|
|
}
|
|
|
|
corrupt_mounted:;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// clean up sim
|
|
free(sim);
|
|
// how many errors did we survive?
|
|
printf("survived %d block errors!\n", (int)(i/PERIOD));
|
|
'''
|
|
|
|
# fuzz errors with fuzz files
|
|
[cases.test_ck_spam_file_fuzz]
|
|
# METHOD=0 => ckprogs
|
|
# METHOD=1 => ckdata
|
|
# METHOD=2 => ckmeta+ckfetches
|
|
# METHOD=3 => ckmeta+ckdatacksums
|
|
defines.METHOD = [0, 1, 2, 3]
|
|
defines.PERIOD = 10
|
|
# protecting the mrootanchor encourages more interesting failures, and
|
|
# simulates storage with hardened {0,1} blocks
|
|
defines.PROTECTED_MROOTANCHOR = [false, true]
|
|
# we can't reliably detect bit errors in erased blocks, we rely on
|
|
# future progs failing if this happens
|
|
defines.ERASE_VALUE = -1
|
|
defines.BADBLOCK_BEHAVIOR = '''
|
|
(METHOD == 0)
|
|
? LFS_EMUBD_BADBLOCK_PROGFLIP
|
|
: LFS_EMUBD_BADBLOCK_MANUAL
|
|
'''
|
|
defines.CKMETA = 'METHOD == 2 || METHOD == 3'
|
|
defines.CKDATA = 'METHOD == 1'
|
|
defines.MTREEONLY = 'METHOD == 2'
|
|
defines.CKPROGS = 'METHOD == 0'
|
|
defines.CKFETCHES = 'METHOD == 2'
|
|
defines.CKPARITY = false
|
|
defines.CKDATACKSUMS = 'METHOD == 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',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.SEED = 'range(10)'
|
|
fuzz = 'SEED'
|
|
if = [
|
|
'LFS_IFDEF_CKPROGS(true, !CKPROGS)',
|
|
'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)',
|
|
'LFS_IFDEF_CKPARITY(true, !CKPARITY)',
|
|
'LFS_IFDEF_CKDATACKSUMS(true, !CKDATACKSUMS)',
|
|
'(SIZE*N)/BLOCK_SIZE <= 16',
|
|
]
|
|
code = '''
|
|
// seed our block device with our seed so we have different error
|
|
// bit patterns
|
|
uint32_t prng = SEED;
|
|
lfs_emubd_seed(CFG, TEST_PRNG(&prng));
|
|
|
|
// create a permutation of blocks to test against
|
|
//
|
|
// precalculating the permutation avoids issues around running out
|
|
// of blocks to randomly select
|
|
uint32_t badblocks[BLOCK_COUNT];
|
|
TEST_PERMUTATION(TEST_PRNG(&prng), badblocks, BLOCK_COUNT);
|
|
|
|
// test fuzz with files
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs,
|
|
LFS_F_RDWR
|
|
| ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_F_CKPROGS, -1) : 0)
|
|
| ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_F_CKFETCHES, -1) : 0)
|
|
| ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_F_CKPARITY, -1) : 0)
|
|
| ((CKDATACKSUMS)
|
|
? LFS_IFDEF_CKDATACKSUMS(LFS_F_CKDATACKSUMS, -1)
|
|
: 0),
|
|
CFG) => 0;
|
|
lfsr_mount(&lfs,
|
|
LFS_M_RDWR
|
|
| ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) : 0)
|
|
| ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) : 0)
|
|
| ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_M_CKPARITY, -1) : 0)
|
|
| ((CKDATACKSUMS)
|
|
? LFS_IFDEF_CKDATACKSUMS(LFS_M_CKDATACKSUMS, -1)
|
|
: 0),
|
|
CFG) => 0;
|
|
|
|
// set up a simulation to compare against
|
|
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
|
|
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
|
|
lfs_size_t sim_size = 0;
|
|
|
|
// keep adding errors until we either run out of blocks or detect
|
|
// corruption
|
|
lfs_size_t i = 0;
|
|
for (; i < PERIOD*BLOCK_COUNT; i++) {
|
|
// add an error?
|
|
if (i % PERIOD == 0
|
|
// protected mrootanchor? (just makes things more interesting)
|
|
&& !(PROTECTED_MROOTANCHOR && badblocks[i/PERIOD] < 2)) {
|
|
lfs_block_t badblock = badblocks[i/PERIOD];
|
|
printf("badblock: 0x%x\n", badblock);
|
|
|
|
// our different error-detection methods detect different
|
|
// types of errors, so we implement errors for each one a
|
|
// bit differently
|
|
|
|
// mark our badblock as bad
|
|
lfs_emubd_markbad(CFG, badblock) => 0;
|
|
|
|
// manually flipping? flip all badbits in badblocks
|
|
if (BADBLOCK_BEHAVIOR == LFS_EMUBD_BADBLOCK_MANUAL) {
|
|
lfs_emubd_flip(CFG) => 0;
|
|
}
|
|
|
|
// run ckdata?
|
|
if (CKDATA) {
|
|
int err = lfsr_fs_ckdata(&lfs);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
// run ckmeta?
|
|
} else if (CKMETA && !MTREEONLY) {
|
|
int err = lfsr_fs_ckmeta(&lfs);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
// run ckmeta mtreeonly?
|
|
} else if (CKMETA && MTREEONLY) {
|
|
// need an explicit traversal for this
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t,
|
|
LFS_T_MTREEONLY | 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 corrupt_mounted;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
}
|
|
}
|
|
|
|
// keep testing...
|
|
|
|
// choose which operation to do
|
|
uint8_t op = TEST_PRNG(&prng) % 3;
|
|
|
|
// creating a new file?
|
|
if (op == 0 || sim_size == 0) {
|
|
// choose a pseudo-random number
|
|
lfs_size_t x = TEST_PRNG(&prng) % N;
|
|
// associate each file with a prng that generates its contents
|
|
uint32_t wprng = TEST_PRNG(&prng);
|
|
|
|
// insert into our sim
|
|
for (lfs_size_t j = 0;; j++) {
|
|
if (j >= sim_size || sim[j] >= x) {
|
|
// already seen?
|
|
if (j < sim_size && sim[j] == x) {
|
|
// new prng
|
|
sim_prngs[j] = wprng;
|
|
} else {
|
|
// insert
|
|
memmove(&sim[j+1], &sim[j],
|
|
(sim_size-j)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j+1], &sim_prngs[j],
|
|
(sim_size-j)*sizeof(uint32_t));
|
|
sim_size += 1;
|
|
sim[j] = x;
|
|
sim_prngs[j] = wprng;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// create a file here
|
|
char name[256];
|
|
sprintf(name, "amethyst%03x", x);
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
|
|
lfsr_file_t file;
|
|
int err = lfsr_file_open(&lfs, &file, name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err) {
|
|
goto corrupt_mounted;
|
|
}
|
|
lfs_ssize_t d = lfsr_file_write(&lfs, &file, wbuf, SIZE);
|
|
assert(d == SIZE || d == LFS_ERR_NOSPC);
|
|
if (d == LFS_ERR_NOSPC) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
goto corrupt_mounted;
|
|
}
|
|
err = lfsr_file_close(&lfs, &file);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// deleting a file?
|
|
} else if (op == 1) {
|
|
// choose a random file to delete
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
// delete from our sim
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
sim_size -= 1;
|
|
|
|
// delete this file
|
|
char name[256];
|
|
sprintf(name, "amethyst%03x", x);
|
|
int err = lfsr_remove(&lfs, name);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// renaming a file?
|
|
} else {
|
|
// choose a random file to rename, and a random number to
|
|
// rename to
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
lfs_size_t y = TEST_PRNG(&prng) % N;
|
|
uint32_t wprng = sim_prngs[j];
|
|
|
|
// update our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= y) {
|
|
// renaming and replacing
|
|
if (k < sim_size && sim[k] == y && x != y) {
|
|
// delete the original entry
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
sim_size -= 1;
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// update the prng
|
|
sim_prngs[k] = wprng;
|
|
// just renaming
|
|
} else {
|
|
// first delete
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// then insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
sim[k] = y;
|
|
sim_prngs[k] = wprng;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// rename this file
|
|
char old_name[256];
|
|
sprintf(old_name, "amethyst%03x", x);
|
|
char new_name[256];
|
|
sprintf(new_name, "amethyst%03x", y);
|
|
int err = lfsr_rename(&lfs, old_name, new_name);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs,
|
|
LFS_M_RDWR
|
|
| ((CKPROGS)
|
|
? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1)
|
|
: 0)
|
|
| ((CKFETCHES)
|
|
? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1)
|
|
: 0)
|
|
| ((CKPARITY)
|
|
? LFS_IFDEF_CKPARITY(LFS_M_CKPARITY, -1)
|
|
: 0)
|
|
| ((CKDATACKSUMS)
|
|
? LFS_IFDEF_CKDATACKSUMS(LFS_M_CKDATACKSUMS, -1)
|
|
: 0),
|
|
CFG) => 0;
|
|
}
|
|
|
|
// check that our files match our simulation
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "amethyst%03x", sim[j]);
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
}
|
|
|
|
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);
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "amethyst%03x", sim[j]);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
}
|
|
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
|
lfsr_dir_close(&lfs, &dir) => 0;
|
|
|
|
// check the file contents
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "amethyst%03x", sim[j]);
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
|
|
|
uint32_t wprng = sim_prngs[j];
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
|
|
uint8_t rbuf[SIZE];
|
|
lfs_ssize_t d = lfsr_file_read(&lfs, &file, rbuf, SIZE);
|
|
assert(d == SIZE
|
|
|| (d == LFS_ERR_CORRUPT && (METHOD == 2 || METHOD == 3)));
|
|
if (d == 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;
|
|
|
|
// clean up sim
|
|
free(sim);
|
|
free(sim_prngs);
|
|
// how many errors did we survive?
|
|
printf("survived %d block errors!\n", (int)(i/PERIOD));
|
|
'''
|
|
|
|
# fuzz errors with more complex file writes
|
|
[cases.test_ck_spam_fwrite_fuzz]
|
|
# METHOD=0 => ckprogs
|
|
# METHOD=1 => ckdata
|
|
# METHOD=2 => ckmeta+ckfetches
|
|
# METHOD=3 => ckmeta+ckdatacksums
|
|
defines.METHOD = [0, 1, 2, 3]
|
|
defines.PERIOD = 10
|
|
# protecting the mrootanchor encourages more interesting failures, and
|
|
# simulates storage with hardened {0,1} blocks
|
|
defines.PROTECTED_MROOTANCHOR = [false, true]
|
|
# we can't reliably detect bit errors in erased blocks, we rely on
|
|
# future progs failing if this happens
|
|
defines.ERASE_VALUE = -1
|
|
defines.BADBLOCK_BEHAVIOR = '''
|
|
(METHOD == 0)
|
|
? LFS_EMUBD_BADBLOCK_PROGFLIP
|
|
: LFS_EMUBD_BADBLOCK_MANUAL
|
|
'''
|
|
defines.CKMETA = 'METHOD == 2 || METHOD == 3'
|
|
defines.CKDATA = 'METHOD == 1'
|
|
# note we need a full ckmeta if we have open files, ckfetches does not
|
|
# recheck open btrees
|
|
defines.MTREEONLY = false
|
|
defines.CKPROGS = 'METHOD == 0'
|
|
defines.CKFETCHES = 'METHOD == 2'
|
|
defines.CKPARITY = false
|
|
defines.CKDATACKSUMS = 'METHOD == 3'
|
|
defines.SIZE = [
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
# chunk is more an upper limit here
|
|
defines.CHUNK = 64
|
|
# INIT=0 => no init
|
|
# INIT=1 => fill with data
|
|
# INIT=2 => truncate to size
|
|
defines.INIT = [0, 1, 2]
|
|
defines.SYNC = [false, true]
|
|
defines.SEED = 'range(10)'
|
|
fuzz = 'SEED'
|
|
if = [
|
|
'LFS_IFDEF_CKPROGS(true, !CKPROGS)',
|
|
'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)',
|
|
'LFS_IFDEF_CKPARITY(true, !CKPARITY)',
|
|
'LFS_IFDEF_CKDATACKSUMS(true, !CKDATACKSUMS)',
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
// seed our block device with our seed so we have different error
|
|
// bit patterns
|
|
uint32_t prng = SEED;
|
|
lfs_emubd_seed(CFG, TEST_PRNG(&prng));
|
|
|
|
// create a permutation of blocks to test against
|
|
//
|
|
// precalculating the permutation avoids issues around running out
|
|
// of blocks to randomly select
|
|
uint32_t badblocks[BLOCK_COUNT];
|
|
TEST_PERMUTATION(TEST_PRNG(&prng), badblocks, BLOCK_COUNT);
|
|
|
|
// test with complex file writes
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs,
|
|
LFS_F_RDWR
|
|
| ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_F_CKPROGS, -1) : 0)
|
|
| ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_F_CKFETCHES, -1) : 0)
|
|
| ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_F_CKPARITY, -1) : 0)
|
|
| ((CKDATACKSUMS)
|
|
? LFS_IFDEF_CKDATACKSUMS(LFS_F_CKDATACKSUMS, -1)
|
|
: 0),
|
|
CFG) => 0;
|
|
lfsr_mount(&lfs,
|
|
LFS_M_RDWR
|
|
| ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) : 0)
|
|
| ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) : 0)
|
|
| ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_M_CKPARITY, -1) : 0)
|
|
| ((CKDATACKSUMS)
|
|
? LFS_IFDEF_CKDATACKSUMS(LFS_M_CKDATACKSUMS, -1)
|
|
: 0),
|
|
CFG) => 0;
|
|
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "hello",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
// simulate our file in ram
|
|
uint8_t sim[SIZE];
|
|
lfs_off_t size;
|
|
if (INIT == 0) {
|
|
memset(sim, 0, SIZE);
|
|
size = 0;
|
|
} else if (INIT == 1) {
|
|
for (lfs_size_t i = 0; i < SIZE; i++) {
|
|
sim[i] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &file, sim, SIZE) => SIZE;
|
|
size = SIZE;
|
|
} else {
|
|
memset(sim, 0, SIZE);
|
|
lfsr_file_truncate(&lfs, &file, SIZE) => 0;
|
|
size = SIZE;
|
|
}
|
|
|
|
// sync?
|
|
if (SYNC) {
|
|
lfsr_file_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
// keep adding errors until we either run out of blocks or detect
|
|
// corruption
|
|
lfs_size_t i = 0;
|
|
for (; i < PERIOD*BLOCK_COUNT; i++) {
|
|
// add an error?
|
|
if (i % PERIOD == 0
|
|
// protected mrootanchor? (just makes things more interesting)
|
|
&& !(PROTECTED_MROOTANCHOR && badblocks[i/PERIOD] < 2)) {
|
|
lfs_block_t badblock = badblocks[i/PERIOD];
|
|
printf("badblock: 0x%x\n", badblock);
|
|
|
|
// our different error-detection methods detect different
|
|
// types of errors, so we implement errors for each one a
|
|
// bit differently
|
|
|
|
// mark our badblock as bad
|
|
lfs_emubd_markbad(CFG, badblock) => 0;
|
|
|
|
// manually flipping? flip all badbits in badblocks
|
|
if (BADBLOCK_BEHAVIOR == LFS_EMUBD_BADBLOCK_MANUAL) {
|
|
lfs_emubd_flip(CFG) => 0;
|
|
}
|
|
|
|
// run ckdata?
|
|
if (CKDATA) {
|
|
int err = lfsr_fs_ckdata(&lfs);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_open;
|
|
}
|
|
// run ckmeta?
|
|
} else if (CKMETA && !MTREEONLY) {
|
|
int err = lfsr_fs_ckmeta(&lfs);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_open;
|
|
}
|
|
// run ckmeta mtreeonly?
|
|
} else if (CKMETA && MTREEONLY) {
|
|
// need an explicit traversal for this
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t,
|
|
LFS_T_MTREEONLY | 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 corrupt_open;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
}
|
|
}
|
|
|
|
// keep testing...
|
|
|
|
// choose a random location
|
|
lfs_off_t off = TEST_PRNG(&prng) % SIZE;
|
|
// and a random size, up to the chunk size
|
|
lfs_size_t chunk = lfs_min(
|
|
TEST_PRNG(&prng) % CHUNK,
|
|
SIZE - off);
|
|
|
|
// update sim
|
|
for (lfs_size_t j = 0; j < chunk; j++) {
|
|
sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
if (chunk != 0) {
|
|
size = lfs_max(size, off+chunk);
|
|
}
|
|
|
|
// update file
|
|
lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
|
|
lfs_ssize_t d = lfsr_file_write(&lfs, &file, &sim[off], chunk);
|
|
assert(d == (lfs_ssize_t)chunk
|
|
|| d == LFS_ERR_NOSPC
|
|
|| (d == LFS_ERR_CORRUPT && (METHOD == 2 || METHOD == 3)));
|
|
if (d == LFS_ERR_NOSPC || d == LFS_ERR_CORRUPT) {
|
|
goto corrupt_open;
|
|
}
|
|
|
|
// sync?
|
|
if (SYNC) {
|
|
int err = lfsr_file_sync(&lfs, &file);
|
|
assert(!err
|
|
|| err == LFS_ERR_NOSPC
|
|
|| (err == LFS_ERR_CORRUPT
|
|
&& (METHOD == 2 || METHOD == 3)));
|
|
if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_open;
|
|
}
|
|
}
|
|
}
|
|
|
|
int err = lfsr_file_close(&lfs, &file);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs,
|
|
LFS_M_RDWR
|
|
| ((CKPROGS)
|
|
? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1)
|
|
: 0)
|
|
| ((CKFETCHES)
|
|
? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1)
|
|
: 0)
|
|
| ((CKPARITY)
|
|
? LFS_IFDEF_CKPARITY(LFS_M_CKPARITY, -1)
|
|
: 0)
|
|
| ((CKDATACKSUMS)
|
|
? LFS_IFDEF_CKDATACKSUMS(LFS_M_CKDATACKSUMS, -1)
|
|
: 0),
|
|
CFG) => 0;
|
|
}
|
|
|
|
// check our file with stat
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, "hello", &info) => 0;
|
|
assert(strcmp(info.name, "hello") == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == size);
|
|
|
|
// and with dir read
|
|
lfsr_dir_t dir;
|
|
lfsr_dir_open(&lfs, &dir, "/") => 0;
|
|
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);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, "hello") == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == size);
|
|
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
|
lfsr_dir_close(&lfs, &dir) => 0;
|
|
|
|
// try reading our file
|
|
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
|
|
// is size correct?
|
|
lfsr_file_size(&lfs, &file) => size;
|
|
// try reading
|
|
uint8_t rbuf[2*SIZE];
|
|
memset(rbuf, 0xaa, 2*SIZE);
|
|
lfsr_file_read(&lfs, &file, rbuf, 2*SIZE) => size;
|
|
// does our file match our simulation?
|
|
assert(memcmp(rbuf, sim, size) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
goto corrupt_mounted;
|
|
corrupt_open:;
|
|
lfsr_file_desync(&lfs, &file) => 0;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
corrupt_mounted:;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// how many errors did we survive?
|
|
printf("survived %d block errors! (%d)\n", (int)(i/PERIOD), i);
|
|
'''
|
|
|
|
# fuzz errors with uncreats, zombies, etc
|
|
[cases.test_ck_spam_uz_fuzz]
|
|
# METHOD=0 => ckprogs
|
|
# METHOD=1 => ckdata
|
|
# METHOD=2 => ckmeta+ckfetches
|
|
# METHOD=3 => ckmeta+ckdatacksums
|
|
defines.METHOD = [0, 1, 2, 3]
|
|
defines.PERIOD = 10
|
|
# protecting the mrootanchor encourages more interesting failures, and
|
|
# simulates storage with hardened {0,1} blocks
|
|
defines.PROTECTED_MROOTANCHOR = [false, true]
|
|
# we can't reliably detect bit errors in erased blocks, we rely on
|
|
# future progs failing if this happens
|
|
defines.ERASE_VALUE = -1
|
|
defines.BADBLOCK_BEHAVIOR = '''
|
|
(METHOD == 0)
|
|
? LFS_EMUBD_BADBLOCK_PROGFLIP
|
|
: LFS_EMUBD_BADBLOCK_MANUAL
|
|
'''
|
|
defines.CKMETA = 'METHOD == 2 || METHOD == 3'
|
|
defines.CKDATA = 'METHOD == 1'
|
|
# note we need a full ckmeta if we have open files, ckfetches does not
|
|
# recheck open btrees
|
|
defines.MTREEONLY = false
|
|
defines.CKPROGS = 'METHOD == 0'
|
|
defines.CKFETCHES = 'METHOD == 2'
|
|
defines.CKPARITY = false
|
|
defines.CKDATACKSUMS = 'METHOD == 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',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.SEED = 'range(10)'
|
|
fuzz = 'SEED'
|
|
if = [
|
|
'LFS_IFDEF_CKPROGS(true, !CKPROGS)',
|
|
'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)',
|
|
'LFS_IFDEF_CKPARITY(true, !CKPARITY)',
|
|
'LFS_IFDEF_CKDATACKSUMS(true, !CKDATACKSUMS)',
|
|
'(SIZE*N)/BLOCK_SIZE <= 16',
|
|
]
|
|
code = '''
|
|
// seed our block device with our seed so we have different error
|
|
// bit patterns
|
|
uint32_t prng = SEED;
|
|
lfs_emubd_seed(CFG, TEST_PRNG(&prng));
|
|
|
|
// create a permutation of blocks to test against
|
|
//
|
|
// precalculating the permutation avoids issues around running out
|
|
// of blocks to randomly select
|
|
uint32_t badblocks[BLOCK_COUNT];
|
|
TEST_PERMUTATION(TEST_PRNG(&prng), badblocks, BLOCK_COUNT);
|
|
|
|
// test with uncreats, zombies, etc
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs,
|
|
LFS_F_RDWR
|
|
| ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_F_CKPROGS, -1) : 0)
|
|
| ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_F_CKFETCHES, -1) : 0)
|
|
| ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_F_CKPARITY, -1) : 0)
|
|
| ((CKDATACKSUMS)
|
|
? LFS_IFDEF_CKDATACKSUMS(LFS_F_CKDATACKSUMS, -1)
|
|
: 0),
|
|
CFG) => 0;
|
|
lfsr_mount(&lfs,
|
|
LFS_M_RDWR
|
|
| ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) : 0)
|
|
| ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) : 0)
|
|
| ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_M_CKPARITY, -1) : 0)
|
|
| ((CKDATACKSUMS)
|
|
? LFS_IFDEF_CKDATACKSUMS(LFS_M_CKDATACKSUMS, -1)
|
|
: 0),
|
|
CFG) => 0;
|
|
|
|
// set up a simulation to compare against
|
|
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
|
|
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
|
|
lfs_size_t sim_size = 0;
|
|
|
|
typedef struct sim_file {
|
|
lfs_size_t x;
|
|
bool uncreat;
|
|
bool zombie;
|
|
uint32_t prng;
|
|
lfsr_file_t file;
|
|
} sim_file_t;
|
|
sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*));
|
|
lfs_size_t sim_file_count = 0;
|
|
|
|
// keep adding errors until we either run out of blocks or detect
|
|
// corruption
|
|
lfs_size_t i = 0;
|
|
for (; i < PERIOD*BLOCK_COUNT; i++) {
|
|
// add an error?
|
|
if (i % PERIOD == 0
|
|
// protected mrootanchor? (just makes things more interesting)
|
|
&& !(PROTECTED_MROOTANCHOR && badblocks[i/PERIOD] < 2)) {
|
|
lfs_block_t badblock = badblocks[i/PERIOD];
|
|
printf("badblock: 0x%x\n", badblock);
|
|
|
|
// our different error-detection methods detect different
|
|
// types of errors, so we implement errors for each one a
|
|
// bit differently
|
|
|
|
// mark our badblock as bad
|
|
lfs_emubd_markbad(CFG, badblock) => 0;
|
|
|
|
// manually flipping? flip all badbits in badblocks
|
|
if (BADBLOCK_BEHAVIOR == LFS_EMUBD_BADBLOCK_MANUAL) {
|
|
lfs_emubd_flip(CFG) => 0;
|
|
}
|
|
|
|
// run ckdata?
|
|
if (CKDATA) {
|
|
int err = lfsr_fs_ckdata(&lfs);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
// run ckmeta?
|
|
} else if (CKMETA && !MTREEONLY) {
|
|
int err = lfsr_fs_ckmeta(&lfs);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
// run ckmeta mtreeonly?
|
|
} else if (CKMETA && MTREEONLY) {
|
|
// need an explicit traversal for this
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t,
|
|
LFS_T_MTREEONLY | 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 corrupt_mounted;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
}
|
|
}
|
|
|
|
// keep testing...
|
|
|
|
nonsense:;
|
|
// choose which operation to do
|
|
uint8_t op = TEST_PRNG(&prng) % 5;
|
|
|
|
// open a new file?
|
|
if (op == 0) {
|
|
if (sim_file_count >= N) {
|
|
goto nonsense;
|
|
}
|
|
// choose a pseudo-random number
|
|
lfs_size_t x = TEST_PRNG(&prng) % N;
|
|
|
|
// already exists?
|
|
bool uncreat = true;
|
|
uint32_t wprng = 0;
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
if (sim[j] == x) {
|
|
uncreat = false;
|
|
wprng = sim_prngs[j];
|
|
break;
|
|
}
|
|
}
|
|
// choose a random seed if we don't exist
|
|
if (uncreat) {
|
|
wprng = TEST_PRNG(&prng);
|
|
}
|
|
|
|
// open in our sim
|
|
lfs_size_t j = sim_file_count;
|
|
sim_files[j] = malloc(sizeof(sim_file_t));
|
|
sim_files[j]->x = x;
|
|
sim_files[j]->uncreat = uncreat;
|
|
sim_files[j]->zombie = false;
|
|
sim_files[j]->prng = wprng;
|
|
|
|
// open the actual file
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
int err = lfsr_file_open(&lfs, &sim_files[j]->file, name,
|
|
LFS_O_RDWR | LFS_O_CREAT);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
sim_file_count++;
|
|
|
|
// write some initial data if we don't exist
|
|
if (uncreat) {
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t k = 0; k < SIZE; k++) {
|
|
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
lfs_ssize_t d = lfsr_file_write(&lfs, &sim_files[j]->file,
|
|
wbuf, SIZE);
|
|
assert(d == SIZE || d == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
}
|
|
|
|
// write/rewrite a file?
|
|
} else if (op == 1) {
|
|
if (sim_file_count == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file handle
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
|
|
lfs_size_t x = sim_files[j]->x;
|
|
// choose a random seed
|
|
uint32_t wprng = TEST_PRNG(&prng);
|
|
|
|
// update sim
|
|
sim_files[j]->prng = wprng;
|
|
if (!sim_files[j]->zombie) {
|
|
// insert into our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= x) {
|
|
// already seen?
|
|
if (k < sim_size && sim[k] == x) {
|
|
// new prng
|
|
sim_prngs[k] = wprng;
|
|
} else {
|
|
// insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
sim_size += 1;
|
|
sim[k] = x;
|
|
sim_prngs[k] = wprng;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update related sim files
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
|
|
sim_files[k]->uncreat = false;
|
|
sim_files[k]->prng = wprng;
|
|
}
|
|
}
|
|
}
|
|
|
|
// write to the file
|
|
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t k = 0; k < SIZE; k++) {
|
|
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
lfs_ssize_t d = lfsr_file_write(&lfs, &sim_files[j]->file,
|
|
wbuf, SIZE);
|
|
assert(d == SIZE
|
|
|| d == LFS_ERR_NOSPC
|
|
|| (d == LFS_ERR_CORRUPT && (METHOD == 2 || METHOD == 3)));
|
|
if (d == LFS_ERR_NOSPC || d == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
int err = lfsr_file_sync(&lfs, &sim_files[j]->file);
|
|
assert(err == ((!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT)
|
|
|| err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// close a file?
|
|
} else if (op == 2) {
|
|
if (sim_file_count == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file handle
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
|
|
|
|
// this doesn't really test anything, but if we don't close
|
|
// files eventually everything will end up zombies
|
|
|
|
// close the file without affected disk
|
|
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
|
|
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
|
|
|
|
// remove from list
|
|
free(sim_files[j]);
|
|
sim_files[j] = sim_files[sim_file_count-1];
|
|
sim_file_count -= 1;
|
|
|
|
// remove a file?
|
|
} else if (op == 3) {
|
|
if (sim_size == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file to delete
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
|
|
// delete from our sim
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
sim_size -= 1;
|
|
|
|
// mark any related sim files as zombied
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
|
|
// delete this file
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
int err = lfsr_remove(&lfs, name);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// rename a file?
|
|
} else if (op == 4) {
|
|
if (sim_size == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file to rename, and a random number to
|
|
// rename to
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
lfs_size_t y = TEST_PRNG(&prng) % N;
|
|
uint32_t wprng = sim_prngs[j];
|
|
|
|
// update our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= y) {
|
|
// renaming and replacing
|
|
if (k < sim_size && sim[k] == y && x != y) {
|
|
// delete the original entry
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
sim_size -= 1;
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// update the prng
|
|
sim_prngs[k] = wprng;
|
|
// just renaming
|
|
} else {
|
|
// first delete
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// then insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
sim[k] = y;
|
|
sim_prngs[k] = wprng;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update any related sim files
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
// move source files
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->x = y;
|
|
|
|
// mark target files as zombied
|
|
} else if (sim_files[k]->x == y) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
|
|
// rename this file
|
|
char old_name[256];
|
|
sprintf(old_name, "batman%03x", x);
|
|
char new_name[256];
|
|
sprintf(new_name, "batman%03x", y);
|
|
int err = lfsr_rename(&lfs, old_name, new_name);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
}
|
|
}
|
|
|
|
// check that disk matches our simulation
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
}
|
|
|
|
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);
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
}
|
|
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
|
lfsr_dir_close(&lfs, &dir) => 0;
|
|
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
|
|
|
uint32_t wprng = sim_prngs[j];
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
|
|
uint8_t rbuf[SIZE];
|
|
lfs_ssize_t d = lfsr_file_read(&lfs, &file, rbuf, SIZE);
|
|
assert(d == SIZE
|
|
|| (d == LFS_ERR_CORRUPT && (METHOD == 2 || METHOD == 3)));
|
|
if (d == LFS_ERR_CORRUPT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
goto corrupt_mounted;
|
|
}
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
// check that our file handles match our simulation
|
|
for (lfs_size_t j = 0; j < sim_file_count; j++) {
|
|
uint32_t wprng = sim_files[j]->prng;
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
|
|
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
|
|
uint8_t rbuf[SIZE];
|
|
lfs_ssize_t d = lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE);
|
|
assert(d == SIZE
|
|
|| (d == LFS_ERR_CORRUPT && (METHOD == 2 || METHOD == 3)));
|
|
if (d == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
}
|
|
|
|
corrupt_mounted:;
|
|
// clean up sim/lfs
|
|
free(sim);
|
|
free(sim_prngs);
|
|
for (lfs_size_t j = 0; j < sim_file_count; j++) {
|
|
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
|
|
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
|
|
free(sim_files[j]);
|
|
}
|
|
free(sim_files);
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// how many errors did we survive?
|
|
printf("survived %d block errors!\n", (int)(i/PERIOD));
|
|
'''
|
|
|
|
# fuzz errors with uncreats, zombies, dirs, etc
|
|
[cases.test_ck_spam_uzd_fuzz]
|
|
# METHOD=0 => ckprogs
|
|
# METHOD=1 => ckdata
|
|
# METHOD=2 => ckmeta+ckfetches
|
|
# METHOD=3 => ckmeta+ckdatacksums
|
|
defines.METHOD = [0, 1, 2, 3]
|
|
defines.PERIOD = 10
|
|
# protecting the mrootanchor encourages more interesting failures, and
|
|
# simulates storage with hardened {0,1} blocks
|
|
defines.PROTECTED_MROOTANCHOR = [false, true]
|
|
# we can't reliably detect bit errors in erased blocks, we rely on
|
|
# future progs failing if this happens
|
|
defines.ERASE_VALUE = -1
|
|
defines.BADBLOCK_BEHAVIOR = '''
|
|
(METHOD == 0)
|
|
? LFS_EMUBD_BADBLOCK_PROGFLIP
|
|
: LFS_EMUBD_BADBLOCK_MANUAL
|
|
'''
|
|
defines.CKMETA = 'METHOD == 2 || METHOD == 3'
|
|
defines.CKDATA = 'METHOD == 1'
|
|
# note we need a full ckmeta if we have open files, ckfetches does not
|
|
# recheck open btrees
|
|
defines.MTREEONLY = false
|
|
defines.CKPROGS = 'METHOD == 0'
|
|
defines.CKFETCHES = 'METHOD == 2'
|
|
defines.CKPARITY = false
|
|
defines.CKDATACKSUMS = 'METHOD == 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',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.SEED = 'range(10)'
|
|
fuzz = 'SEED'
|
|
if = [
|
|
'LFS_IFDEF_CKPROGS(true, !CKPROGS)',
|
|
'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)',
|
|
'LFS_IFDEF_CKPARITY(true, !CKPARITY)',
|
|
'LFS_IFDEF_CKDATACKSUMS(true, !CKDATACKSUMS)',
|
|
'(SIZE*N)/BLOCK_SIZE <= 16',
|
|
]
|
|
code = '''
|
|
// seed our block device with our seed so we have different error
|
|
// bit patterns
|
|
uint32_t prng = SEED;
|
|
lfs_emubd_seed(CFG, TEST_PRNG(&prng));
|
|
|
|
// create a permutation of blocks to test against
|
|
//
|
|
// precalculating the permutation avoids issues around running out
|
|
// of blocks to randomly select
|
|
uint32_t badblocks[BLOCK_COUNT];
|
|
TEST_PERMUTATION(TEST_PRNG(&prng), badblocks, BLOCK_COUNT);
|
|
|
|
// test with uncreats, zombies, dirs, etc
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs,
|
|
LFS_F_RDWR
|
|
| ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_F_CKPROGS, -1) : 0)
|
|
| ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_F_CKFETCHES, -1) : 0)
|
|
| ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_F_CKPARITY, -1) : 0)
|
|
| ((CKDATACKSUMS)
|
|
? LFS_IFDEF_CKDATACKSUMS(LFS_F_CKDATACKSUMS, -1)
|
|
: 0),
|
|
CFG) => 0;
|
|
lfsr_mount(&lfs,
|
|
LFS_M_RDWR
|
|
| ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) : 0)
|
|
| ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) : 0)
|
|
| ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_M_CKPARITY, -1) : 0)
|
|
| ((CKDATACKSUMS)
|
|
? LFS_IFDEF_CKDATACKSUMS(LFS_M_CKDATACKSUMS, -1)
|
|
: 0),
|
|
CFG) => 0;
|
|
|
|
// set up a simulation to compare against
|
|
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
|
|
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
|
|
bool *sim_isdirs = malloc(N*sizeof(bool));
|
|
lfs_size_t sim_size = 0;
|
|
|
|
typedef struct sim_file {
|
|
lfs_size_t x;
|
|
bool uncreat;
|
|
bool zombie;
|
|
uint32_t prng;
|
|
lfsr_file_t file;
|
|
} sim_file_t;
|
|
sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*));
|
|
lfs_size_t sim_file_count = 0;
|
|
|
|
// keep adding errors until we either run out of blocks or detect
|
|
// corruption
|
|
lfs_size_t i = 0;
|
|
for (; i < PERIOD*BLOCK_COUNT; i++) {
|
|
// add an error?
|
|
if (i % PERIOD == 0
|
|
// protected mrootanchor? (just makes things more interesting)
|
|
&& !(PROTECTED_MROOTANCHOR && badblocks[i/PERIOD] < 2)) {
|
|
lfs_block_t badblock = badblocks[i/PERIOD];
|
|
printf("badblock: 0x%x\n", badblock);
|
|
|
|
// our different error-detection methods detect different
|
|
// types of errors, so we implement errors for each one a
|
|
// bit differently
|
|
|
|
// mark our badblock as bad
|
|
lfs_emubd_markbad(CFG, badblock) => 0;
|
|
|
|
// manually flipping? flip all badbits in badblocks
|
|
if (BADBLOCK_BEHAVIOR == LFS_EMUBD_BADBLOCK_MANUAL) {
|
|
lfs_emubd_flip(CFG) => 0;
|
|
}
|
|
|
|
// run ckdata?
|
|
if (CKDATA) {
|
|
int err = lfsr_fs_ckdata(&lfs);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
// run ckmeta?
|
|
} else if (CKMETA && !MTREEONLY) {
|
|
int err = lfsr_fs_ckmeta(&lfs);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
// run ckmeta mtreeonly?
|
|
} else if (CKMETA && MTREEONLY) {
|
|
// need an explicit traversal for this
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t,
|
|
LFS_T_MTREEONLY | 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 corrupt_mounted;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
}
|
|
}
|
|
|
|
// keep testing...
|
|
|
|
nonsense:;
|
|
// choose which operation to do
|
|
uint8_t op = TEST_PRNG(&prng) % 8;
|
|
|
|
// open a new file?
|
|
if (op == 0) {
|
|
if (sim_file_count >= N) {
|
|
goto nonsense;
|
|
}
|
|
// choose a pseudo-random number
|
|
lfs_size_t x = TEST_PRNG(&prng) % N;
|
|
|
|
// already exists?
|
|
bool uncreat = true;
|
|
uint32_t wprng = 0;
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
if (sim[j] == x) {
|
|
if (sim_isdirs[j]) {
|
|
goto nonsense;
|
|
}
|
|
uncreat = false;
|
|
wprng = sim_prngs[j];
|
|
break;
|
|
}
|
|
}
|
|
// choose a random seed if we don't exist
|
|
if (uncreat) {
|
|
wprng = TEST_PRNG(&prng);
|
|
}
|
|
|
|
// open in our sim
|
|
lfs_size_t j = sim_file_count;
|
|
sim_files[j] = malloc(sizeof(sim_file_t));
|
|
sim_files[j]->x = x;
|
|
sim_files[j]->uncreat = uncreat;
|
|
sim_files[j]->zombie = false;
|
|
sim_files[j]->prng = wprng;
|
|
|
|
// open the actual file
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
int err = lfsr_file_open(&lfs, &sim_files[j]->file, name,
|
|
LFS_O_RDWR | LFS_O_CREAT);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
sim_file_count++;
|
|
|
|
// write some initial data if we don't exist
|
|
if (uncreat) {
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t k = 0; k < SIZE; k++) {
|
|
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
lfs_ssize_t d = lfsr_file_write(&lfs, &sim_files[j]->file,
|
|
wbuf, SIZE);
|
|
assert(d == SIZE || d == LFS_ERR_NOSPC);
|
|
if (d == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
}
|
|
|
|
// write/rewrite a file?
|
|
} else if (op == 1) {
|
|
if (sim_file_count == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file handle
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
|
|
lfs_size_t x = sim_files[j]->x;
|
|
// choose a random seed
|
|
uint32_t wprng = TEST_PRNG(&prng);
|
|
|
|
// update sim
|
|
sim_files[j]->prng = wprng;
|
|
if (!sim_files[j]->zombie) {
|
|
// insert into our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= x) {
|
|
// already seen?
|
|
if (k < sim_size && sim[k] == x) {
|
|
// new prng
|
|
sim_prngs[k] = wprng;
|
|
} else {
|
|
// insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim_size += 1;
|
|
sim[k] = x;
|
|
sim_prngs[k] = wprng;
|
|
sim_isdirs[k] = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update related sim files
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
|
|
sim_files[k]->uncreat = false;
|
|
sim_files[k]->prng = wprng;
|
|
}
|
|
}
|
|
}
|
|
|
|
// write to the file
|
|
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t k = 0; k < SIZE; k++) {
|
|
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
lfs_ssize_t d = lfsr_file_write(&lfs, &sim_files[j]->file,
|
|
wbuf, SIZE);
|
|
assert(d == SIZE
|
|
|| d == LFS_ERR_NOSPC
|
|
|| (d == LFS_ERR_CORRUPT && (METHOD == 2 || METHOD == 3)));
|
|
if (d == LFS_ERR_NOSPC || d == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
int err = lfsr_file_sync(&lfs, &sim_files[j]->file);
|
|
assert(err == ((!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT)
|
|
|| err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// close a file?
|
|
} else if (op == 2) {
|
|
if (sim_file_count == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file handle
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
|
|
|
|
// this doesn't really test anything, but if we don't close
|
|
// files eventually everything will end up zombies
|
|
|
|
// close the file without affected disk
|
|
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
|
|
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
|
|
|
|
// remove from list
|
|
free(sim_files[j]);
|
|
sim_files[j] = sim_files[sim_file_count-1];
|
|
sim_file_count -= 1;
|
|
|
|
// remove a file?
|
|
} else if (op == 3) {
|
|
if (sim_size == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file to delete
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
|
|
// delete from our sim
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
sim_size -= 1;
|
|
|
|
// mark any related sim files as zombied
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
|
|
// delete this file
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
int err = lfsr_remove(&lfs, name);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// rename a file?
|
|
} else if (op == 4) {
|
|
if (sim_size == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file to rename, and a random number to
|
|
// rename to
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
lfs_size_t y = TEST_PRNG(&prng) % N;
|
|
uint32_t wprng = sim_prngs[j];
|
|
bool isdir = sim_isdirs[j];
|
|
|
|
// update our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= y) {
|
|
// renaming and replacing
|
|
if (k < sim_size && sim[k] == y && x != y) {
|
|
// type mismatch?
|
|
if (sim_isdirs[k] != isdir) {
|
|
goto nonsense;
|
|
}
|
|
|
|
// delete the original entry
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
sim_size -= 1;
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// update the prng
|
|
sim_prngs[k] = wprng;
|
|
// just renaming
|
|
} else {
|
|
// first delete
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// then insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim[k] = y;
|
|
sim_prngs[k] = wprng;
|
|
sim_isdirs[k] = isdir;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update any related sim files
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
// move source files
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->x = y;
|
|
|
|
// mark target files as zombied
|
|
} else if (sim_files[k]->x == y) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
|
|
// rename this file
|
|
char old_name[256];
|
|
sprintf(old_name, "batman%03x", x);
|
|
char new_name[256];
|
|
sprintf(new_name, "batman%03x", y);
|
|
int err = lfsr_rename(&lfs, old_name, new_name);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// toss a directory into the mix
|
|
} else if (op == 5) {
|
|
// choose a pseudo-random number
|
|
lfs_size_t x = TEST_PRNG(&prng) % N;
|
|
|
|
// insert into our sim, use negative numbers for dirs
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= x) {
|
|
// already seen?
|
|
if (k < sim_size && sim[k] == x) {
|
|
goto nonsense;
|
|
} else {
|
|
// insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim_size += 1;
|
|
sim[k] = x;
|
|
sim_prngs[k] = 0;
|
|
sim_isdirs[k] = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// mark any related sim files as zombied
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
|
|
// make the directory
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
int err = lfsr_mkdir(&lfs, name);
|
|
assert(!err || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
}
|
|
}
|
|
|
|
// check that disk matches our simulation
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
if (sim_isdirs[j]) {
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
} else {
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
}
|
|
}
|
|
|
|
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);
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
if (sim_isdirs[j]) {
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
} else {
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
}
|
|
}
|
|
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
|
lfsr_dir_close(&lfs, &dir) => 0;
|
|
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
if (sim_isdirs[j]) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY)
|
|
=> LFS_ERR_ISDIR;
|
|
|
|
} else {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
|
|
|
uint32_t wprng = sim_prngs[j];
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
|
|
uint8_t rbuf[SIZE];
|
|
lfs_ssize_t d = lfsr_file_read(&lfs, &file, rbuf, SIZE);
|
|
assert(d == SIZE
|
|
|| (d == LFS_ERR_CORRUPT && (METHOD == 2 || METHOD == 3)));
|
|
if (d == LFS_ERR_CORRUPT) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
goto corrupt_mounted;
|
|
}
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
}
|
|
|
|
// check that our file handles match our simulation
|
|
for (lfs_size_t j = 0; j < sim_file_count; j++) {
|
|
uint32_t wprng = sim_files[j]->prng;
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
|
|
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
|
|
uint8_t rbuf[SIZE];
|
|
lfs_ssize_t d = lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE);
|
|
assert(d == SIZE
|
|
|| (d == LFS_ERR_CORRUPT && (METHOD == 2 || METHOD == 3)));
|
|
if (d == LFS_ERR_CORRUPT) {
|
|
goto corrupt_mounted;
|
|
}
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
}
|
|
|
|
corrupt_mounted:;
|
|
// clean up sim/lfs
|
|
free(sim);
|
|
free(sim_prngs);
|
|
for (lfs_size_t j = 0; j < sim_file_count; j++) {
|
|
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
|
|
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
|
|
free(sim_files[j]);
|
|
}
|
|
free(sim_files);
|
|
lfsr_unmount(&lfs) => 0;
|
|
// how many errors did we survive?
|
|
printf("survived %d block errors!\n", (int)(i/PERIOD));
|
|
'''
|