b5e503ca85
So now calling lfsr_file_sync on zombied files is a noop:
// create a file
lfsr_file_t a;
lfsr_file_open(&lfs, &a, "a",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
// remove, creating a zombie
lfsr_remove(&lfs, "a") => 0;
// sync, this is now a noop (previously LFS_ERR_NOENT)
lfsr_file_sync(&lfs, &a) => 0;
// close is also a noop
lfsr_file_close(&lfs, &a) => 0;
I've been on the fence on this for a while, on one hand erroring
provides more information to the user, on the other hand a noop is less
surprising if the user comes from other systems.
Ended up making this a noop. I figured minimizing surprises is good API
design, and the user can always use lfsr_stat to check if the file still
exists.
This also matches POSIX, and, perhaps more importantly, the current
version of littlefs.
---
Note that lfsr_file_resync still errors with LFS_ERR_NOENT. It's hard to
argue the file "matches the state of disk" otherwise.
Code changes minimal:
code stack ctx
before: 35784 2440 640
after: 35780 (-0.0%) 2440 (+0.0%) 640 (+0.0%)
4748 lines
160 KiB
TOML
4748 lines
160 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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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));
|
|
bool *sim_isstickys = malloc(N*sizeof(bool));
|
|
lfs_size_t sim_size = 0;
|
|
|
|
typedef struct sim_file {
|
|
lfs_size_t x;
|
|
bool sticky;
|
|
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 exist = false;
|
|
uint32_t wprng = 0;
|
|
bool sticky = true;
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
if (sim[j] == x) {
|
|
exist = true;
|
|
wprng = sim_prngs[j];
|
|
sticky = sim_isstickys[j];
|
|
break;
|
|
}
|
|
}
|
|
// choose a random seed if we don't exist
|
|
if (!exist) {
|
|
wprng = TEST_PRNG(&prng);
|
|
sticky = true;
|
|
}
|
|
|
|
lfs_size_t j = sim_file_count;
|
|
sim_files[j] = malloc(sizeof(sim_file_t));
|
|
|
|
// 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) {
|
|
free(sim_files[j]);
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// write some initial data if we don't exist
|
|
if (!exist || sticky) {
|
|
uint8_t wbuf[SIZE];
|
|
uint32_t wprng_ = wprng;
|
|
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) {
|
|
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
|
|
free(sim_files[j]);
|
|
goto corrupt_mounted;
|
|
}
|
|
}
|
|
|
|
// open in our sim
|
|
sim_files[j]->x = x;
|
|
sim_files[j]->sticky = sticky;
|
|
sim_files[j]->zombie = false;
|
|
sim_files[j]->prng = wprng;
|
|
sim_file_count++;
|
|
|
|
// 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_isstickys[k+1], &sim_isstickys[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim_size += 1;
|
|
sim[k] = x;
|
|
sim_prngs[k] = wprng;
|
|
sim_isstickys[k] = sticky;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
|
|
// write to the file
|
|
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
|
|
uint8_t wbuf[SIZE];
|
|
uint32_t wprng_ = wprng;
|
|
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 == 0 || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// update sim
|
|
sim_files[j]->prng = wprng;
|
|
if (!sim_files[j]->zombie) {
|
|
// update in our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (sim[k] == x) {
|
|
// new prng
|
|
sim_prngs[k] = wprng;
|
|
// no longer sticky
|
|
sim_isstickys[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) {
|
|
// new prng
|
|
sim_files[k]->prng = wprng;
|
|
// no longer sticky
|
|
sim_files[k]->sticky = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
lfs_size_t x = sim_files[j]->x;
|
|
bool sticky = sim_files[j]->sticky;
|
|
bool zombie = sim_files[j]->zombie;
|
|
|
|
// 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;
|
|
// clobber closed files to try to catch lingering references
|
|
memset(&sim_files[j]->file, 0xcc, sizeof(lfsr_file_t));
|
|
|
|
// remove from list
|
|
free(sim_files[j]);
|
|
sim_files[j] = sim_files[sim_file_count-1];
|
|
sim_file_count -= 1;
|
|
|
|
// update our sim
|
|
if (sticky && !zombie) {
|
|
// orphaned?
|
|
bool orphan = true;
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
|
|
orphan = false;
|
|
}
|
|
}
|
|
|
|
// if we were never synced, delete from sim
|
|
if (orphan) {
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (sim[k] == x) {
|
|
memmove(&sim[k], &sim[k+1],
|
|
(sim_size-(k+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k], &sim_prngs[k+1],
|
|
(sim_size-(k+1))*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[k], &sim_isstickys[k+1],
|
|
(sim_size-(k+1))*sizeof(bool));
|
|
sim_size -= 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 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;
|
|
}
|
|
|
|
// 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_isstickys[j], &sim_isstickys[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;
|
|
}
|
|
}
|
|
|
|
// 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 sticky = sim_isstickys[j];
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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));
|
|
memmove(&sim_isstickys[j], &sim_isstickys[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
sim_size -= 1;
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// update the prng/sticky
|
|
sim_prngs[k] = wprng;
|
|
sim_isstickys[k] = sticky;
|
|
// 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_isstickys[j], &sim_isstickys[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_isstickys[k+1], &sim_isstickys[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim[k] = y;
|
|
sim_prngs[k] = wprng;
|
|
sim_isstickys[k] = sticky;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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_isstickys[j]) {
|
|
assert(info.type == LFS_TYPE_STICKYNOTE);
|
|
assert(info.size == 0);
|
|
} 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_isstickys[j]) {
|
|
assert(info.type == LFS_TYPE_STICKYNOTE);
|
|
assert(info.size == 0);
|
|
} 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++) {
|
|
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];
|
|
if (sim_isstickys[j]) {
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => 0;
|
|
} else {
|
|
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);
|
|
free(sim_isstickys);
|
|
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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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_isstickys = malloc(N*sizeof(bool));
|
|
bool *sim_isdirs = malloc(N*sizeof(bool));
|
|
lfs_size_t sim_size = 0;
|
|
|
|
typedef struct sim_file {
|
|
lfs_size_t x;
|
|
bool sticky;
|
|
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 exist = true;
|
|
uint32_t wprng = 0;
|
|
bool sticky = true;
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
if (sim[j] == x) {
|
|
if (sim_isdirs[j]) {
|
|
goto nonsense;
|
|
}
|
|
exist = true;
|
|
wprng = sim_prngs[j];
|
|
sticky = sim_isstickys[j];
|
|
break;
|
|
}
|
|
}
|
|
// choose a random seed if we don't exist
|
|
if (!exist) {
|
|
wprng = TEST_PRNG(&prng);
|
|
sticky = true;
|
|
}
|
|
|
|
lfs_size_t j = sim_file_count;
|
|
sim_files[j] = malloc(sizeof(sim_file_t));
|
|
|
|
// 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) {
|
|
free(sim_files[j]);
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// write some initial data if we don't exist
|
|
if (!exist || sticky) {
|
|
uint8_t wbuf[SIZE];
|
|
uint32_t wprng_ = wprng;
|
|
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) {
|
|
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
|
|
free(sim_files[j]);
|
|
goto corrupt_mounted;
|
|
}
|
|
}
|
|
|
|
// open in our sim
|
|
sim_files[j]->x = x;
|
|
sim_files[j]->sticky = sticky;
|
|
sim_files[j]->zombie = false;
|
|
sim_files[j]->prng = wprng;
|
|
sim_file_count++;
|
|
|
|
// 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_isstickys[k+1], &sim_isstickys[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim_size += 1;
|
|
sim[k] = x;
|
|
sim_prngs[k] = wprng;
|
|
sim_isstickys[k] = sticky;
|
|
sim_isdirs[k] = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
|
|
// write to the file
|
|
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
|
|
uint8_t wbuf[SIZE];
|
|
uint32_t wprng_ = wprng;
|
|
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 == 0 || err == LFS_ERR_NOSPC);
|
|
if (err == LFS_ERR_NOSPC) {
|
|
goto corrupt_mounted;
|
|
}
|
|
|
|
// update sim
|
|
sim_files[j]->prng = wprng;
|
|
if (!sim_files[j]->zombie) {
|
|
// update in our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= x) {
|
|
// new prng
|
|
sim_prngs[k] = wprng;
|
|
// no longer sticky
|
|
sim_isstickys[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) {
|
|
// new prng
|
|
sim_files[k]->prng = wprng;
|
|
// no longer sticky
|
|
sim_files[k]->sticky = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
lfs_size_t x = sim_files[j]->x;
|
|
lfs_size_t sticky = sim_files[j]->sticky;
|
|
lfs_size_t zombie = sim_files[j]->zombie;
|
|
|
|
// 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;
|
|
// clobber closed files to try to catch lingering references
|
|
memset(&sim_files[j]->file, 0xcc, sizeof(lfsr_file_t));
|
|
|
|
// remove from list
|
|
free(sim_files[j]);
|
|
sim_files[j] = sim_files[sim_file_count-1];
|
|
sim_file_count -= 1;
|
|
|
|
// update our sim
|
|
if (sticky && !zombie) {
|
|
// orphaned?
|
|
bool orphan = true;
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
|
|
orphan = false;
|
|
}
|
|
}
|
|
|
|
// if we were never synced, delete from sim
|
|
if (orphan) {
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (sim[k] == x) {
|
|
memmove(&sim[k], &sim[k+1],
|
|
(sim_size-(k+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k], &sim_prngs[k+1],
|
|
(sim_size-(k+1))*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[k], &sim_isstickys[k+1],
|
|
(sim_size-(k+1))*sizeof(bool));
|
|
memmove(&sim_isdirs[k], &sim_isdirs[k+1],
|
|
(sim_size-(k+1))*sizeof(bool));
|
|
sim_size -= 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 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;
|
|
}
|
|
|
|
// 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_isstickys[j], &sim_isstickys[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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 sticky = sim_isstickys[j];
|
|
bool dir = sim_isdirs[j];
|
|
|
|
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] != dir) {
|
|
goto nonsense;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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));
|
|
memmove(&sim_isstickys[j], &sim_isstickys[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
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/sticky/dir
|
|
sim_prngs[k] = wprng;
|
|
sim_isstickys[k] = sticky;
|
|
sim_isdirs[k] = dir;
|
|
// 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_isstickys[j], &sim_isstickys[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
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_isstickys[k+1], &sim_isstickys[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim[k] = y;
|
|
sim_prngs[k] = wprng;
|
|
sim_isstickys[k] = sticky;
|
|
sim_isdirs[k] = dir;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
// toss a directory into the mix
|
|
} else if (op == 5) {
|
|
// choose a pseudo-random number
|
|
lfs_size_t x = TEST_PRNG(&prng) % N;
|
|
|
|
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;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// insert into our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= x) {
|
|
// 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_isstickys[k+1], &sim_isstickys[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
assert(info.size == 0);
|
|
} else if (sim_isstickys[j]) {
|
|
assert(info.type == LFS_TYPE_STICKYNOTE);
|
|
assert(info.size == 0);
|
|
} 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);
|
|
assert(info.size == 0);
|
|
} else if (sim_isstickys[j]) {
|
|
assert(info.type == LFS_TYPE_STICKYNOTE);
|
|
assert(info.size == 0);
|
|
} 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];
|
|
if (sim_isstickys[j]) {
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => 0;
|
|
} else {
|
|
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);
|
|
free(sim_isstickys);
|
|
free(sim_isdirs);
|
|
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));
|
|
'''
|