a53151df1f
These are our current set of general-purpose high-level tests that can
be turned to when needing to test a wide range of filesystem operations.
They were getting a bit hard to keep track of without a consistent
prefix, especially since no individual test suite can actually use all
of them at the same time.
Now, finding these tests is as simple as: ./scripts/test.py -L *_spam_*
I also renamed a couple because their names were starting to get
ridiculous. I mean just look at
test_badblocks_alternating_spam_orphanzombiedir_fuzz...
- *_spam_orphanzombie_fuzz -> *_spam_oz_fuzz
- *_spam_orphanzombiedir_fuzz -> *_spam_ozd_fuzz
- *_spam_file_pl_fuzz -> *_spam_f_pl_fuzz
- *_spam_filedir_pl_fuzz -> *_spam_fd_pl_fuzz
Here are all of the current spam tests and contexts we use them in:
traversal badblocks relocations
| gc ck grow | powerloss exhaustion
dir_many y y y y y y
dir_fuzz y y y y y y y
file_many y y y y y y
file_fuzz y y y y y y y
fwrite_fuzz y y y y y
oz_fuzz y y y y y y y
ozd_fuzz y y y y y y y
f_pl_fuzz y y y
fd_pl_fuzz y y y
3261 lines
106 KiB
TOML
3261 lines
106 KiB
TOML
# Test GC things
|
|
|
|
# most of the GC logic is tested in test_traversal, we just test a few
|
|
# GC-specific things here
|
|
after = ['test_traversal']
|
|
|
|
# test that lookahead can make progress in isolation
|
|
[cases.test_gc_lookahead_progress]
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.SIZE = [
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "spider",
|
|
LFS_O_WRONLY | 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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// expect dirty initial state or else our test doesn't work
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
|
assert(lfs.omdirs != &lfs.gc.o.o);
|
|
|
|
// run GC until we make progress
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
LFS_ASSERT(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_LOOKAHEAD
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
if (!(fsinfo.flags & LFS_I_CANLOOKAHEAD)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// check the file contents
|
|
lfsr_file_open(&lfs, &file, "spider", 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;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that lookahead dirtying still works with the GC API
|
|
[cases.test_gc_lookahead_mutation]
|
|
defines.GC_STEPS = 1
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.SIZE = [
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
# we need something to keep the traversal running
|
|
if = 'CKMETA || CKDATA'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "spider",
|
|
LFS_O_WRONLY | 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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// expect dirty initial state or else our test doesn't work
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
|
assert(lfs.omdirs != &lfs.gc.o.o);
|
|
|
|
// run GC one step
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_LOOKAHEAD
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
assert(lfs.omdirs == &lfs.gc.o.o);
|
|
|
|
// mutate the filesystem
|
|
lfsr_file_open(&lfs, &file, "spider",
|
|
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
|
|
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;
|
|
|
|
// run GC until our traversal is done
|
|
while (lfs.omdirs == &lfs.gc.o.o) {
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_LOOKAHEAD
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
// we should _not_ make progress
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
|
|
|
// check the file contents
|
|
lfsr_file_open(&lfs, &file, "spider", 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;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that adding flags doesn't break lookahead
|
|
[cases.test_gc_lookahead_add_flags]
|
|
defines.GC_STEPS = 1
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.SIZE = [
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
# we need something to keep the traversal running
|
|
if = 'CKMETA || CKDATA'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "spider",
|
|
LFS_O_WRONLY | 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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// expect dirty initial state or else our test doesn't work
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
|
assert(lfs.omdirs != &lfs.gc.o.o);
|
|
|
|
// run GC one step
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
assert(lfs.omdirs == &lfs.gc.o.o);
|
|
|
|
// change flags and run GC until our traversal is done
|
|
while (lfs.omdirs == &lfs.gc.o.o) {
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_LOOKAHEAD
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
// we should _not_ make progress
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
|
|
|
// check the file contents
|
|
lfsr_file_open(&lfs, &file, "spider", 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;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that removing flags invalidates lookahead
|
|
[cases.test_gc_lookahead_remove_flags]
|
|
defines.GC_STEPS = 1
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.SIZE = [
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
# we need something to keep the traversal running
|
|
if = 'CKMETA || CKDATA'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "spider",
|
|
LFS_O_WRONLY | 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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// expect dirty initial state or else our test doesn't work
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
|
assert(lfs.omdirs != &lfs.gc.o.o);
|
|
|
|
// run GC one step
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_LOOKAHEAD
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
assert(lfs.omdirs == &lfs.gc.o.o);
|
|
|
|
// change flags and run GC until our traversal is done
|
|
while (lfs.omdirs == &lfs.gc.o.o) {
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
// we should _not_ make progress
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
|
|
|
// check the file contents
|
|
lfsr_file_open(&lfs, &file, "spider", 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;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
|
|
# test that compact can make progress in isolation
|
|
[cases.test_gc_compact_progress]
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.SIZE = [
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// write to our mdir until >gc_compact_thresh full
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "jellyfish",
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
|
|
// hack, don't use the internals like this
|
|
uint8_t wbuf[SIZE];
|
|
while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) {
|
|
lfsr_file_rewind(&lfs, &file) => 0;
|
|
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_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
// expect dirty initial state or else our test doesn't work
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
|
assert(lfs.omdirs != &lfs.gc.o.o);
|
|
|
|
// run GC until we make progress
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
LFS_ASSERT(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_COMPACT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
if (!(fsinfo.flags & LFS_I_UNCOMPACTED)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// mdir should have been compacted
|
|
assert((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH);
|
|
|
|
// check we can still read the file
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
|
|
}
|
|
|
|
lfsr_file_rewind(&lfs, &file) => 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;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that compact dirtying still works with the GC API
|
|
[cases.test_gc_compact_mutation]
|
|
defines.GC_STEPS = 1
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.SIZE = [
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
# we need something to keep the traversal running
|
|
if = 'CKMETA || CKDATA'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// write to our mdir until >gc_compact_thresh full
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "jellyfish",
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
|
|
// hack, don't use the internals like this
|
|
uint8_t wbuf[SIZE];
|
|
while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) {
|
|
lfsr_file_rewind(&lfs, &file) => 0;
|
|
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_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
// expect dirty initial state or else our test doesn't work
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
|
assert(lfs.omdirs != &lfs.gc.o.o);
|
|
|
|
// run GC one traversal + one step
|
|
while (true) {
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_COMPACT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
|
|
// internal traversal done?
|
|
if (lfs.omdirs != &lfs.gc.o.o) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_COMPACT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
assert(lfs.omdirs == &lfs.gc.o.o);
|
|
|
|
// mutate the filesystem
|
|
lfsr_file_rewind(&lfs, &file) => 0;
|
|
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_sync(&lfs, &file) => 0;
|
|
|
|
// run GC until our traversal is done (twice for compact)
|
|
while (lfs.omdirs == &lfs.gc.o.o) {
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_COMPACT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
// we should _not_ make progress
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
|
|
|
// check we can still read the file
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
|
|
}
|
|
|
|
lfsr_file_rewind(&lfs, &file) => 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;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that adding flags doesn't break compact
|
|
[cases.test_gc_compact_add_flags]
|
|
defines.GC_STEPS = 1
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.SIZE = [
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
# we need something to keep the traversal running
|
|
if = 'CKMETA || CKDATA'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// write to our mdir until >gc_compact_thresh full
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "jellyfish",
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
|
|
// hack, don't use the internals like this
|
|
uint8_t wbuf[SIZE];
|
|
while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) {
|
|
lfsr_file_rewind(&lfs, &file) => 0;
|
|
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_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
// expect dirty initial state or else our test doesn't work
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
|
assert(lfs.omdirs != &lfs.gc.o.o);
|
|
|
|
// run GC one traversal + one step
|
|
while (true) {
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
|
|
// internal traversal done?
|
|
if (lfs.omdirs != &lfs.gc.o.o) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
assert(lfs.omdirs == &lfs.gc.o.o);
|
|
|
|
// change flags and run GC until our traversal is done (twice for compact)
|
|
while (lfs.omdirs == &lfs.gc.o.o) {
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_COMPACT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
// we should _not_ make progress
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
|
|
|
// check we can still read the file
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
|
|
}
|
|
|
|
lfsr_file_rewind(&lfs, &file) => 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;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that removing flags invalidates compact
|
|
[cases.test_gc_compact_remove_flags]
|
|
defines.GC_STEPS = 1
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.SIZE = [
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
# we need something to keep the traversal running
|
|
if = 'CKMETA || CKDATA'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// write to our mdir until >gc_compact_thresh full
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "jellyfish",
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
|
|
// hack, don't use the internals like this
|
|
uint8_t wbuf[SIZE];
|
|
while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) {
|
|
lfsr_file_rewind(&lfs, &file) => 0;
|
|
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_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
// expect dirty initial state or else our test doesn't work
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
|
assert(lfs.omdirs != &lfs.gc.o.o);
|
|
|
|
// run GC one traversal + one step
|
|
while (true) {
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_COMPACT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
|
|
// internal traversal done?
|
|
if (lfs.omdirs != &lfs.gc.o.o) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_COMPACT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
assert(lfs.omdirs == &lfs.gc.o.o);
|
|
|
|
// change flags and run GC until our traversal is done (twice for compact)
|
|
while (lfs.omdirs == &lfs.gc.o.o) {
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
// we should _not_ make progress
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
|
|
|
// check we can still read the file
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
|
|
}
|
|
|
|
lfsr_file_rewind(&lfs, &file) => 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;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
|
|
# test that mkconsistent can make progress in isolation
|
|
[cases.test_gc_mkconsistent_progress]
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.SIZE = 'FILE_BUFFER_SIZE/2'
|
|
# <=2 => grm-able
|
|
# >2 => requires orphans
|
|
defines.ORPHANS = [1, 2, 3, 100]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// create two files
|
|
lfsr_file_t file1;
|
|
lfsr_file_open(&lfs, &file1, "cuttlefish",
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint8_t wbuf1[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE;
|
|
lfsr_file_sync(&lfs, &file1) => 0;
|
|
|
|
lfsr_file_t file2;
|
|
lfsr_file_open(&lfs, &file2, "octopus",
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint8_t wbuf2[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE;
|
|
lfsr_file_sync(&lfs, &file2) => 0;
|
|
|
|
// create this many orphaned files
|
|
//
|
|
// anytime we close a not-yet-created desync file, we create an
|
|
// orphan, but note we need these to be different files, and we need
|
|
// to close them after all open calls, otherwise we just end up with
|
|
// one orphan (littlefs is eager to clean up orphans)
|
|
//
|
|
lfsr_file_t orphans[ORPHANS];
|
|
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
|
char name[256];
|
|
sprintf(name, "jellyfish%03x", i);
|
|
lfsr_file_open(&lfs, &orphans[i], name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0;
|
|
}
|
|
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
|
lfsr_file_close(&lfs, &orphans[i]) => 0;
|
|
}
|
|
|
|
// expect dirty initial state or else our test doesn't work
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
|
assert(lfs.omdirs != &lfs.gc.o.o);
|
|
|
|
// run GC until we make progress
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
LFS_ASSERT(i < 2*BLOCK_COUNT);
|
|
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_MKCONSISTENT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
if (!(fsinfo.flags & LFS_I_INCONSISTENT)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// check we can still read the files
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_file_close(&lfs, &file1) => 0;
|
|
lfsr_file_close(&lfs, &file2) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0;
|
|
lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0;
|
|
}
|
|
|
|
lfsr_file_rewind(&lfs, &file1) => 0;
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf1, SIZE) == 0);
|
|
|
|
lfsr_file_rewind(&lfs, &file2) => 0;
|
|
lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf2, SIZE) == 0);
|
|
}
|
|
|
|
lfsr_file_close(&lfs, &file1) => 0;
|
|
lfsr_file_close(&lfs, &file2) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that an explicit lfsr_fs_mkconsistent call also works, this calls
|
|
# the same logic internally
|
|
[cases.test_gc_mkconsistent_explicit]
|
|
defines.SIZE = 'FILE_BUFFER_SIZE/2'
|
|
# <=2 => grm-able
|
|
# >2 => requires orphans
|
|
defines.ORPHANS = [1, 2, 3, 100]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// create two files
|
|
lfsr_file_t file1;
|
|
lfsr_file_open(&lfs, &file1, "cuttlefish",
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint8_t wbuf1[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE;
|
|
lfsr_file_sync(&lfs, &file1) => 0;
|
|
|
|
lfsr_file_t file2;
|
|
lfsr_file_open(&lfs, &file2, "octopus",
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint8_t wbuf2[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE;
|
|
lfsr_file_sync(&lfs, &file2) => 0;
|
|
|
|
// create this many orphaned files
|
|
//
|
|
// anytime we close a not-yet-created desync file, we create an
|
|
// orphan, but note we need these to be different files, and we need
|
|
// to close them after all open calls, otherwise we just end up with
|
|
// one orphan (littlefs is eager to clean up orphans)
|
|
//
|
|
lfsr_file_t orphans[ORPHANS];
|
|
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
|
char name[256];
|
|
sprintf(name, "jellyfish%03x", i);
|
|
lfsr_file_open(&lfs, &orphans[i], name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0;
|
|
}
|
|
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
|
lfsr_file_close(&lfs, &orphans[i]) => 0;
|
|
}
|
|
|
|
// expect dirty initial state or else our test doesn't work
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
|
assert(lfs.omdirs != &lfs.gc.o.o);
|
|
|
|
// call lfsr_fs_mkconsistent
|
|
lfsr_fs_mkconsistent(&lfs) => 0;
|
|
|
|
// we should have made progress
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(!(fsinfo.flags & LFS_I_INCONSISTENT));
|
|
|
|
// check we can still read the files
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_file_close(&lfs, &file1) => 0;
|
|
lfsr_file_close(&lfs, &file2) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0;
|
|
lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0;
|
|
}
|
|
|
|
lfsr_file_rewind(&lfs, &file1) => 0;
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf1, SIZE) == 0);
|
|
|
|
lfsr_file_rewind(&lfs, &file2) => 0;
|
|
lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf2, SIZE) == 0);
|
|
}
|
|
|
|
lfsr_file_close(&lfs, &file1) => 0;
|
|
lfsr_file_close(&lfs, &file2) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that mkconsistent dirtying still works with the GC API
|
|
[cases.test_gc_mkconsistent_mutation]
|
|
defines.GC_STEPS = 1
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.SIZE = 'FILE_BUFFER_SIZE/2'
|
|
# <=2 => grm-able
|
|
# >2 => requires orphans
|
|
defines.ORPHANS = [3, 100]
|
|
# we need something to keep the traversal running
|
|
if = 'CKMETA || CKDATA'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// create two files
|
|
lfsr_file_t file1;
|
|
lfsr_file_open(&lfs, &file1, "cuttlefish",
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint8_t wbuf1[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE;
|
|
lfsr_file_sync(&lfs, &file1) => 0;
|
|
|
|
lfsr_file_t file2;
|
|
lfsr_file_open(&lfs, &file2, "octopus",
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint8_t wbuf2[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE;
|
|
lfsr_file_sync(&lfs, &file2) => 0;
|
|
|
|
// create at least 3 orphans so GC will start
|
|
lfsr_file_t orphans[ORPHANS];
|
|
for (lfs_size_t i = 0; i < 3; i++) {
|
|
char name[256];
|
|
sprintf(name, "jellyfish%03x", i);
|
|
lfsr_file_open(&lfs, &orphans[i], name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0;
|
|
}
|
|
for (lfs_size_t i = 0; i < 3; i++) {
|
|
lfsr_file_close(&lfs, &orphans[i]) => 0;
|
|
}
|
|
|
|
// run GC one step
|
|
assert(lfs.omdirs != &lfs.gc.o.o);
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_MKCONSISTENT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
assert(lfs.omdirs == &lfs.gc.o.o);
|
|
|
|
// create the rest of the orphans after GC has started
|
|
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
|
char name[256];
|
|
sprintf(name, "jellyfish%03x", i);
|
|
lfsr_file_open(&lfs, &orphans[i], name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0;
|
|
}
|
|
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
|
lfsr_file_close(&lfs, &orphans[i]) => 0;
|
|
}
|
|
|
|
// we should now have dirty state
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
|
|
|
// run GC until our traversal is done
|
|
while (lfs.omdirs == &lfs.gc.o.o) {
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_MKCONSISTENT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
// we should _not_ make progress
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
|
|
|
// check we can still read the files
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_file_close(&lfs, &file1) => 0;
|
|
lfsr_file_close(&lfs, &file2) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0;
|
|
lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0;
|
|
}
|
|
|
|
lfsr_file_rewind(&lfs, &file1) => 0;
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf1, SIZE) == 0);
|
|
|
|
lfsr_file_rewind(&lfs, &file2) => 0;
|
|
lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf2, SIZE) == 0);
|
|
}
|
|
|
|
lfsr_file_close(&lfs, &file1) => 0;
|
|
lfsr_file_close(&lfs, &file2) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that adding flags doesn't break mkconsistent
|
|
[cases.test_gc_mkconsistent_add_flags]
|
|
defines.GC_STEPS = 1
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.SIZE = 'FILE_BUFFER_SIZE/2'
|
|
# <=2 => grm-able
|
|
# >2 => requires orphans
|
|
defines.ORPHANS = [3, 100]
|
|
# we need something to keep the traversal running
|
|
if = 'CKMETA || CKDATA'
|
|
in = 'lfs.c'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// create two files
|
|
lfsr_file_t file1;
|
|
lfsr_file_open(&lfs, &file1, "cuttlefish",
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint8_t wbuf1[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE;
|
|
lfsr_file_sync(&lfs, &file1) => 0;
|
|
|
|
lfsr_file_t file2;
|
|
lfsr_file_open(&lfs, &file2, "octopus",
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint8_t wbuf2[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE;
|
|
lfsr_file_sync(&lfs, &file2) => 0;
|
|
|
|
// create this many orphaned files
|
|
//
|
|
// anytime we close a not-yet-created desync file, we create an
|
|
// orphan, but note we need these to be different files, and we need
|
|
// to close them after all open calls, otherwise we just end up with
|
|
// one orphan (littlefs is eager to clean up orphans)
|
|
//
|
|
lfsr_file_t orphans[ORPHANS];
|
|
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
|
char name[256];
|
|
sprintf(name, "jellyfish%03x", i);
|
|
lfsr_file_open(&lfs, &orphans[i], name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0;
|
|
}
|
|
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
|
lfsr_file_close(&lfs, &orphans[i]) => 0;
|
|
}
|
|
|
|
// expect dirty initial state or else our test doesn't work
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
|
assert(lfs.omdirs != &lfs.gc.o.o);
|
|
|
|
// run GC one step
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
assert(lfs.omdirs == &lfs.gc.o.o);
|
|
|
|
// change flags and run GC until our traversal is done
|
|
while (lfs.omdirs == &lfs.gc.o.o) {
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_MKCONSISTENT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
// we should _not_ make progress
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
|
|
|
// check we can still read the files
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_file_close(&lfs, &file1) => 0;
|
|
lfsr_file_close(&lfs, &file2) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0;
|
|
lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0;
|
|
}
|
|
|
|
lfsr_file_rewind(&lfs, &file1) => 0;
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf1, SIZE) == 0);
|
|
|
|
lfsr_file_rewind(&lfs, &file2) => 0;
|
|
lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf2, SIZE) == 0);
|
|
}
|
|
|
|
lfsr_file_close(&lfs, &file1) => 0;
|
|
lfsr_file_close(&lfs, &file2) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that removing flags invalidates mkconsistent
|
|
[cases.test_gc_mkconsistent_remove_flags]
|
|
defines.GC_STEPS = 1
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.SIZE = 'FILE_BUFFER_SIZE/2'
|
|
# <=2 => grm-able
|
|
# >2 => requires orphans
|
|
defines.ORPHANS = [3, 100]
|
|
# we need something to keep the traversal running
|
|
if = 'CKMETA || CKDATA'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// create two files
|
|
lfsr_file_t file1;
|
|
lfsr_file_open(&lfs, &file1, "cuttlefish",
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint8_t wbuf1[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE;
|
|
lfsr_file_sync(&lfs, &file1) => 0;
|
|
|
|
lfsr_file_t file2;
|
|
lfsr_file_open(&lfs, &file2, "octopus",
|
|
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
uint8_t wbuf2[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE;
|
|
lfsr_file_sync(&lfs, &file2) => 0;
|
|
|
|
// create this many orphaned files
|
|
//
|
|
// anytime we close a not-yet-created desync file, we create an
|
|
// orphan, but note we need these to be different files, and we need
|
|
// to close them after all open calls, otherwise we just end up with
|
|
// one orphan (littlefs is eager to clean up orphans)
|
|
//
|
|
lfsr_file_t orphans[ORPHANS];
|
|
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
|
char name[256];
|
|
sprintf(name, "jellyfish%03x", i);
|
|
lfsr_file_open(&lfs, &orphans[i], name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0;
|
|
}
|
|
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
|
lfsr_file_close(&lfs, &orphans[i]) => 0;
|
|
}
|
|
|
|
// expect dirty initial state or else our test doesn't work
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
|
assert(lfs.omdirs != &lfs.gc.o.o);
|
|
|
|
// run GC one step
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
LFS_GC_MKCONSISTENT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
assert(lfs.omdirs == &lfs.gc.o.o);
|
|
|
|
// change flags and run GC until our traversal is done
|
|
while (lfs.omdirs == &lfs.gc.o.o) {
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
// we should _not_ make progress
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
|
|
|
// check we can still read the files
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_file_close(&lfs, &file1) => 0;
|
|
lfsr_file_close(&lfs, &file2) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0;
|
|
lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0;
|
|
}
|
|
|
|
lfsr_file_rewind(&lfs, &file1) => 0;
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf1, SIZE) == 0);
|
|
|
|
lfsr_file_rewind(&lfs, &file2) => 0;
|
|
lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf2, SIZE) == 0);
|
|
}
|
|
|
|
lfsr_file_close(&lfs, &file1) => 0;
|
|
lfsr_file_close(&lfs, &file2) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
|
|
# test we can detect at least fully clobbered blocks
|
|
#
|
|
# these are tested more thoroughly in test_ck
|
|
[cases.test_gc_ckmeta]
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
code = '''
|
|
lfs_block_t i = 0;
|
|
while (true) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
// create an interesting filesystem
|
|
uint32_t prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "squid%03x", i);
|
|
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
// traverse to find blocks
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, 0) => 0;
|
|
lfs_block_t k = 0;
|
|
for (lfs_block_t j = 0;; j++) {
|
|
assert(j < 2*BLOCK_COUNT);
|
|
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
goto done;
|
|
}
|
|
|
|
// this gets a bit tricky be cause we need to clobber both
|
|
// blocks in mdir pairs
|
|
if (tinfo.btype == LFS_BTYPE_MDIR
|
|
|| tinfo.btype == LFS_BTYPE_BTREE) {
|
|
if (k == i || k == i+1) {
|
|
// clobber this block
|
|
printf("clobbering 0x%x\n", tinfo.block);
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
CFG->erase(CFG, tinfo.block) => 0;
|
|
CFG->prog(CFG, tinfo.block, 0,
|
|
clobber_buf, BLOCK_SIZE) => 0;
|
|
if (tinfo.btype != LFS_BTYPE_MDIR || k == i+1) {
|
|
i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1;
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
goto clobbered;
|
|
}
|
|
}
|
|
k += 1;
|
|
}
|
|
}
|
|
|
|
clobbered:;
|
|
// running lfsr_fs_gc should eventually find the clobbered block
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
LFS_ASSERT(i < 2*BLOCK_COUNT);
|
|
|
|
int err = lfsr_fs_gc(&lfs, GC_STEPS, LFS_GC_CKMETA);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
// found it
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
}
|
|
done:;
|
|
'''
|
|
|
|
[cases.test_gc_ckdata]
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
code = '''
|
|
lfs_block_t i = 0;
|
|
while (true) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
// create an interesting filesystem
|
|
uint32_t prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "squid%03x", i);
|
|
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
// traverse to find blocks
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, 0) => 0;
|
|
lfs_block_t k = 0;
|
|
for (lfs_block_t j = 0;; j++) {
|
|
assert(j < 2*BLOCK_COUNT);
|
|
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
goto done;
|
|
}
|
|
|
|
// this gets a bit tricky be cause we need to clobber both
|
|
// blocks in mdir pairs
|
|
if (tinfo.btype == LFS_BTYPE_MDIR
|
|
|| tinfo.btype == LFS_BTYPE_BTREE
|
|
|| tinfo.btype == LFS_BTYPE_DATA) {
|
|
if (k == i || k == i+1) {
|
|
// clobber this block
|
|
printf("clobbering 0x%x\n", tinfo.block);
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
CFG->erase(CFG, tinfo.block) => 0;
|
|
CFG->prog(CFG, tinfo.block, 0,
|
|
clobber_buf, BLOCK_SIZE) => 0;
|
|
if (tinfo.btype != LFS_BTYPE_MDIR || k == i+1) {
|
|
i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1;
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
goto clobbered;
|
|
}
|
|
}
|
|
k += 1;
|
|
}
|
|
}
|
|
|
|
clobbered:;
|
|
// running lfsr_fs_gc should eventually find the clobbered block
|
|
//
|
|
// note LFS_GC_CKDATA implies LFS_GC_CKMETA
|
|
for (lfs_block_t i = 0;; i++) {
|
|
// a bit hacky, but this catches infinite loops
|
|
LFS_ASSERT(i < 2*BLOCK_COUNT);
|
|
|
|
int err = lfsr_fs_gc(&lfs, GC_STEPS, LFS_GC_CKDATA);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
// found it
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
}
|
|
done:;
|
|
'''
|
|
|
|
# test that our explicit functions (lfsr_fs_ckmeta/ckdata) work as well,
|
|
# these call the same logic internally
|
|
[cases.test_gc_ckmeta_explicit]
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
code = '''
|
|
lfs_block_t i = 0;
|
|
while (true) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
// create an interesting filesystem
|
|
uint32_t prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "squid%03x", i);
|
|
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
// traverse to find blocks
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, 0) => 0;
|
|
lfs_block_t k = 0;
|
|
for (lfs_block_t j = 0;; j++) {
|
|
assert(j < 2*BLOCK_COUNT);
|
|
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
goto done;
|
|
}
|
|
|
|
// this gets a bit tricky be cause we need to clobber both
|
|
// blocks in mdir pairs
|
|
if (tinfo.btype == LFS_BTYPE_MDIR
|
|
|| tinfo.btype == LFS_BTYPE_BTREE) {
|
|
if (k == i || k == i+1) {
|
|
// clobber this block
|
|
printf("clobbering 0x%x\n", tinfo.block);
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
CFG->erase(CFG, tinfo.block) => 0;
|
|
CFG->prog(CFG, tinfo.block, 0,
|
|
clobber_buf, BLOCK_SIZE) => 0;
|
|
if (tinfo.btype != LFS_BTYPE_MDIR || k == i+1) {
|
|
i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1;
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
goto clobbered;
|
|
}
|
|
}
|
|
k += 1;
|
|
}
|
|
}
|
|
|
|
clobbered:;
|
|
// lfsr_fs_ckmeta should find the clobbered block
|
|
lfsr_fs_ckmeta(&lfs) => LFS_ERR_CORRUPT;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
}
|
|
done:;
|
|
'''
|
|
|
|
[cases.test_gc_ckdata_explicit]
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
code = '''
|
|
lfs_block_t i = 0;
|
|
while (true) {
|
|
// a bit hacky, but this catches infinite loops
|
|
assert(i < 2*BLOCK_COUNT);
|
|
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
// create an interesting filesystem
|
|
uint32_t prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "squid%03x", i);
|
|
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
// traverse to find blocks
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, 0) => 0;
|
|
lfs_block_t k = 0;
|
|
for (lfs_block_t j = 0;; j++) {
|
|
assert(j < 2*BLOCK_COUNT);
|
|
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(!err || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
goto done;
|
|
}
|
|
|
|
// this gets a bit tricky be cause we need to clobber both
|
|
// blocks in mdir pairs
|
|
if (tinfo.btype == LFS_BTYPE_MDIR
|
|
|| tinfo.btype == LFS_BTYPE_BTREE
|
|
|| tinfo.btype == LFS_BTYPE_DATA) {
|
|
if (k == i || k == i+1) {
|
|
// clobber this block
|
|
printf("clobbering 0x%x\n", tinfo.block);
|
|
uint8_t clobber_buf[BLOCK_SIZE];
|
|
memset(clobber_buf, 0xcc, BLOCK_SIZE);
|
|
CFG->erase(CFG, tinfo.block) => 0;
|
|
CFG->prog(CFG, tinfo.block, 0,
|
|
clobber_buf, BLOCK_SIZE) => 0;
|
|
if (tinfo.btype != LFS_BTYPE_MDIR || k == i+1) {
|
|
i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1;
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
goto clobbered;
|
|
}
|
|
}
|
|
k += 1;
|
|
}
|
|
}
|
|
|
|
clobbered:;
|
|
// lfsr_fs_ckdata should find the clobbered block
|
|
lfsr_fs_ckdata(&lfs) => LFS_ERR_CORRUPT;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
}
|
|
done:;
|
|
'''
|
|
|
|
|
|
# pseudo-fuzz test that dirtying still works with the GC API
|
|
[cases.test_gc_mutation]
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.N = 100
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.SIZE = [
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "spider",
|
|
LFS_O_WRONLY | 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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (uint32_t i = 0; i < N; i++) {
|
|
// rewrite the file every gc cycle
|
|
lfsr_file_open(&lfs, &file, "spider",
|
|
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
|
|
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;
|
|
|
|
// gc!
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
// check the file contents
|
|
lfsr_file_open(&lfs, &file, "spider", 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;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# pseudo-fuzz test that adding/removing flags doesn't break anything
|
|
[cases.test_gc_changing_flags]
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.N = 100
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.SIZE = [
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
uint32_t prng = 42;
|
|
|
|
// create a file
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "spider",
|
|
LFS_O_WRONLY | 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;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (uint32_t i = 0; i < N; i++) {
|
|
// rewrite the file every gc cycle
|
|
lfsr_file_open(&lfs, &file, "spider",
|
|
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
|
|
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;
|
|
|
|
// choose a new subset of flags every cycle
|
|
uint32_t flags = (
|
|
((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)
|
|
) & TEST_PRNG(&prng);
|
|
|
|
// gc!
|
|
lfsr_fs_gc(&lfs, GC_STEPS, flags) => 0;
|
|
}
|
|
|
|
// check the file contents
|
|
lfsr_file_open(&lfs, &file, "spider", 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;
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
|
|
|
|
# many/fuzz tests mixed with GC
|
|
#
|
|
|
|
[cases.test_gc_spam_dir_many]
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256]
|
|
code = '''
|
|
// test creating directories
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
// make this many directories
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%03x", i);
|
|
int err = lfsr_mkdir(&lfs, name);
|
|
assert(!err || (TEST_PLS && err == LFS_ERR_EXIST));
|
|
|
|
// gc!
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
}
|
|
|
|
// grm should be zero here
|
|
assert(lfs.grm_p[0] == 0);
|
|
|
|
// check that our mkdir worked
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%03x", i);
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 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 i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%03x", i);
|
|
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;
|
|
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "dir%03x", i);
|
|
lfsr_dir_open(&lfs, &dir, name) => 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) => LFS_ERR_NOENT;
|
|
lfsr_dir_close(&lfs, &dir) => 0;
|
|
}
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_gc_spam_dir_fuzz]
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256]
|
|
defines.OPS = '2*N'
|
|
defines.SEED = 42
|
|
fuzz = 'SEED'
|
|
code = '''
|
|
// test fuzz with dirs
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
|
|
uint32_t prng = SEED;
|
|
for (lfs_size_t i = 0; i < OPS; i++) {
|
|
// 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);
|
|
|
|
} 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);
|
|
lfsr_remove(&lfs, name) => 0;
|
|
|
|
} 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);
|
|
lfsr_rename(&lfs, old_name, new_name) => 0;
|
|
}
|
|
|
|
// gc!
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
}
|
|
|
|
// clean up sim/lfs
|
|
free(sim);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_gc_spam_file_many]
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
code = '''
|
|
// test creating files
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
// create this many files
|
|
uint32_t prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "amethyst%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;
|
|
|
|
// gc!
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
}
|
|
|
|
// check that our writes worked
|
|
prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
// check with stat
|
|
char name[256];
|
|
sprintf(name, "amethyst%03x", i);
|
|
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);
|
|
|
|
// try reading the file, note we reset prng above
|
|
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;
|
|
}
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_gc_spam_file_fuzz]
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.OPS = '2*N'
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.SEED = 42
|
|
fuzz = 'SEED'
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 16'
|
|
code = '''
|
|
// test fuzz with files
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
|
|
uint32_t prng = SEED;
|
|
for (lfs_size_t i = 0; i < OPS; i++) {
|
|
// 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;
|
|
lfsr_file_open(&lfs, &file, name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
|
|
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
// 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);
|
|
lfsr_remove(&lfs, name) => 0;
|
|
|
|
// 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);
|
|
lfsr_rename(&lfs, old_name, new_name) => 0;
|
|
}
|
|
|
|
// gc!
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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];
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
}
|
|
|
|
// clean up sim/lfs
|
|
free(sim);
|
|
free(sim_prngs);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_gc_spam_fwrite_fuzz]
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.OPS = 20
|
|
defines.SIZE = [
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
# chunk is more an upper limit here
|
|
defines.CHUNK = [32, 8, 1]
|
|
# 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 = 42
|
|
fuzz = 'SEED'
|
|
if = [
|
|
'CHUNK <= SIZE',
|
|
# this just saves testing time
|
|
'SIZE <= 4*1024*FRAGMENT_SIZE',
|
|
]
|
|
code = '''
|
|
// test with complex file writes
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
uint32_t prng = SEED;
|
|
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;
|
|
}
|
|
|
|
for (lfs_size_t i = 0; i < OPS; i++) {
|
|
// 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+1-1)) + 1,
|
|
SIZE - off);
|
|
|
|
// update sim
|
|
for (lfs_size_t j = 0; j < chunk; j++) {
|
|
sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
size = lfs_max(size, off+chunk);
|
|
|
|
// update file
|
|
lfsr_file_seek(&lfs, &file, off, LFS_SEEK_SET) => off;
|
|
lfsr_file_write(&lfs, &file, &sim[off], chunk) => chunk;
|
|
|
|
// sync?
|
|
if (SYNC) {
|
|
lfsr_file_sync(&lfs, &file) => 0;
|
|
}
|
|
|
|
// gc!
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
|
|
for (int remount = 0; remount < 2; remount++) {
|
|
// remount?
|
|
if (remount) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, 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;
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_gc_spam_oz_fuzz]
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.OPS = '2*N'
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.SEED = 42
|
|
fuzz = 'SEED'
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 16'
|
|
code = '''
|
|
// test with orphans, zombies, etc
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
// set up a simulation to compare against
|
|
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
|
|
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
|
|
lfs_size_t sim_size = 0;
|
|
|
|
typedef struct sim_file {
|
|
lfs_size_t x;
|
|
bool orphan;
|
|
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;
|
|
|
|
uint32_t prng = SEED;
|
|
for (lfs_size_t i = 0; i < OPS; i++) {
|
|
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 orphan = true;
|
|
uint32_t wprng = 0;
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
if (sim[j] == x) {
|
|
orphan = false;
|
|
wprng = sim_prngs[j];
|
|
break;
|
|
}
|
|
}
|
|
// choose a random seed if we don't exist
|
|
if (orphan) {
|
|
wprng = TEST_PRNG(&prng);
|
|
}
|
|
|
|
// open in our sim
|
|
lfs_size_t j = sim_file_count;
|
|
sim_files[j] = malloc(sizeof(sim_file_t));
|
|
sim_files[j]->x = x;
|
|
sim_files[j]->orphan = orphan;
|
|
sim_files[j]->zombie = false;
|
|
sim_files[j]->prng = wprng;
|
|
sim_file_count++;
|
|
|
|
// open the actual file
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
lfsr_file_open(&lfs, &sim_files[j]->file, name,
|
|
LFS_O_RDWR | LFS_O_CREAT) => 0;
|
|
|
|
// write some initial data if we don't exist
|
|
if (orphan) {
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t k = 0; k < SIZE; k++) {
|
|
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE;
|
|
}
|
|
|
|
// write/rewrite a file?
|
|
} else if (op == 1) {
|
|
if (sim_file_count == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file handle
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
|
|
lfs_size_t x = sim_files[j]->x;
|
|
// choose a random seed
|
|
uint32_t wprng = TEST_PRNG(&prng);
|
|
|
|
// update sim
|
|
sim_files[j]->prng = wprng;
|
|
if (!sim_files[j]->zombie) {
|
|
// insert into our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= x) {
|
|
// already seen?
|
|
if (k < sim_size && sim[k] == x) {
|
|
// new prng
|
|
sim_prngs[k] = wprng;
|
|
} else {
|
|
// insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
sim_size += 1;
|
|
sim[k] = x;
|
|
sim_prngs[k] = wprng;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update related sim files
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
|
|
sim_files[k]->orphan = false;
|
|
sim_files[k]->prng = wprng;
|
|
}
|
|
}
|
|
}
|
|
|
|
// write to the file
|
|
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t k = 0; k < SIZE; k++) {
|
|
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE;
|
|
lfsr_file_sync(&lfs, &sim_files[j]->file)
|
|
=> (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT;
|
|
|
|
// close a file?
|
|
} else if (op == 2) {
|
|
if (sim_file_count == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file handle
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
|
|
|
|
// this doesn't really test anything, but if we don't close
|
|
// files eventually everything will end up zombies
|
|
|
|
// close the file without affected disk
|
|
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
|
|
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
|
|
// 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;
|
|
|
|
// remove a file?
|
|
} else if (op == 3) {
|
|
if (sim_size == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file to delete
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
|
|
// delete from our sim
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
sim_size -= 1;
|
|
|
|
// mark any related sim files as zombied
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
|
|
// delete this file
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
lfsr_remove(&lfs, name) => 0;
|
|
|
|
// rename a file?
|
|
} else if (op == 4) {
|
|
if (sim_size == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file to rename, and a random number to
|
|
// rename to
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
lfs_size_t y = TEST_PRNG(&prng) % N;
|
|
uint32_t wprng = sim_prngs[j];
|
|
|
|
// update our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= y) {
|
|
// renaming and replacing
|
|
if (k < sim_size && sim[k] == y && x != y) {
|
|
// delete the original entry
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
sim_size -= 1;
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// update the prng
|
|
sim_prngs[k] = wprng;
|
|
// just renaming
|
|
} else {
|
|
// first delete
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// then insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
sim[k] = y;
|
|
sim_prngs[k] = wprng;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update any related sim files
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
// move source files
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->x = y;
|
|
|
|
// mark target files as zombied
|
|
} else if (sim_files[k]->x == y) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
|
|
// rename this file
|
|
char old_name[256];
|
|
sprintf(old_name, "batman%03x", x);
|
|
char new_name[256];
|
|
sprintf(new_name, "batman%03x", y);
|
|
lfsr_rename(&lfs, old_name, new_name) => 0;
|
|
}
|
|
|
|
// gc!
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
// check that disk matches our simulation
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
}
|
|
|
|
lfsr_dir_t dir;
|
|
lfsr_dir_open(&lfs, &dir, "/") => 0;
|
|
struct lfs_info info;
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, ".") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, "..") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
}
|
|
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
|
lfsr_dir_close(&lfs, &dir) => 0;
|
|
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
|
|
|
uint32_t wprng = sim_prngs[j];
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
|
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];
|
|
lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
}
|
|
|
|
// clean up sim/lfs
|
|
free(sim);
|
|
free(sim_prngs);
|
|
for (lfs_size_t j = 0; j < sim_file_count; j++) {
|
|
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
|
|
free(sim_files[j]);
|
|
}
|
|
free(sim_files);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_gc_spam_ozd_fuzz]
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.OPS = '2*N'
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_BUFFER_SIZE/2',
|
|
'2*FILE_BUFFER_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.SEED = 42
|
|
fuzz = 'SEED'
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 16'
|
|
code = '''
|
|
// test with orphans, zombies, dirs, etc
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
// set up a simulation to compare against
|
|
lfs_size_t *sim = malloc(N*sizeof(lfs_size_t));
|
|
uint32_t *sim_prngs = malloc(N*sizeof(uint32_t));
|
|
bool *sim_isdirs = malloc(N*sizeof(bool));
|
|
lfs_size_t sim_size = 0;
|
|
|
|
typedef struct sim_file {
|
|
lfs_size_t x;
|
|
bool orphan;
|
|
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;
|
|
|
|
uint32_t prng = SEED;
|
|
for (lfs_size_t i = 0; i < OPS; i++) {
|
|
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 orphan = true;
|
|
uint32_t wprng = 0;
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
if (sim[j] == x) {
|
|
if (sim_isdirs[j]) {
|
|
goto nonsense;
|
|
}
|
|
orphan = false;
|
|
wprng = sim_prngs[j];
|
|
break;
|
|
}
|
|
}
|
|
// choose a random seed if we don't exist
|
|
if (orphan) {
|
|
wprng = TEST_PRNG(&prng);
|
|
}
|
|
|
|
// open in our sim
|
|
lfs_size_t j = sim_file_count;
|
|
sim_files[j] = malloc(sizeof(sim_file_t));
|
|
sim_files[j]->x = x;
|
|
sim_files[j]->orphan = orphan;
|
|
sim_files[j]->zombie = false;
|
|
sim_files[j]->prng = wprng;
|
|
sim_file_count++;
|
|
|
|
// open the actual file
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
lfsr_file_open(&lfs, &sim_files[j]->file, name,
|
|
LFS_O_RDWR | LFS_O_CREAT) => 0;
|
|
|
|
// write some initial data if we don't exist
|
|
if (orphan) {
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t k = 0; k < SIZE; k++) {
|
|
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE;
|
|
}
|
|
|
|
// write/rewrite a file?
|
|
} else if (op == 1) {
|
|
if (sim_file_count == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file handle
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
|
|
lfs_size_t x = sim_files[j]->x;
|
|
// choose a random seed
|
|
uint32_t wprng = TEST_PRNG(&prng);
|
|
|
|
// update sim
|
|
sim_files[j]->prng = wprng;
|
|
if (!sim_files[j]->zombie) {
|
|
// insert into our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= x) {
|
|
// already seen?
|
|
if (k < sim_size && sim[k] == x) {
|
|
// new prng
|
|
sim_prngs[k] = wprng;
|
|
} else {
|
|
// insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim_size += 1;
|
|
sim[k] = x;
|
|
sim_prngs[k] = wprng;
|
|
sim_isdirs[k] = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update related sim files
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
|
|
sim_files[k]->orphan = false;
|
|
sim_files[k]->prng = wprng;
|
|
}
|
|
}
|
|
}
|
|
|
|
// write to the file
|
|
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t k = 0; k < SIZE; k++) {
|
|
wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE;
|
|
lfsr_file_sync(&lfs, &sim_files[j]->file)
|
|
=> (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT;
|
|
|
|
// close a file?
|
|
} else if (op == 2) {
|
|
if (sim_file_count == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file handle
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
|
|
|
|
// this doesn't really test anything, but if we don't close
|
|
// files eventually everything will end up zombies
|
|
|
|
// close the file without affected disk
|
|
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
|
|
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
|
|
// 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;
|
|
|
|
// remove a file?
|
|
} else if (op == 3) {
|
|
if (sim_size == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file to delete
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
|
|
// delete from our sim
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
sim_size -= 1;
|
|
|
|
// mark any related sim files as zombied
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
|
|
// delete this file
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
lfsr_remove(&lfs, name) => 0;
|
|
|
|
// rename a file?
|
|
} else if (op == 4) {
|
|
if (sim_size == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file to rename, and a random number to
|
|
// rename to
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
lfs_size_t y = TEST_PRNG(&prng) % N;
|
|
uint32_t wprng = sim_prngs[j];
|
|
bool isdir = sim_isdirs[j];
|
|
|
|
// update our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= y) {
|
|
// renaming and replacing
|
|
if (k < sim_size && sim[k] == y && x != y) {
|
|
// type mismatch?
|
|
if (sim_isdirs[k] != isdir) {
|
|
goto nonsense;
|
|
}
|
|
|
|
// delete the original entry
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
sim_size -= 1;
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// update the prng
|
|
sim_prngs[k] = wprng;
|
|
// just renaming
|
|
} else {
|
|
// first delete
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// then insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim[k] = y;
|
|
sim_prngs[k] = wprng;
|
|
sim_isdirs[k] = isdir;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update any related sim files
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
// move source files
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->x = y;
|
|
|
|
// mark target files as zombied
|
|
} else if (sim_files[k]->x == y) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
|
|
// rename this file
|
|
char old_name[256];
|
|
sprintf(old_name, "batman%03x", x);
|
|
char new_name[256];
|
|
sprintf(new_name, "batman%03x", y);
|
|
lfsr_rename(&lfs, old_name, new_name) => 0;
|
|
|
|
// toss a directory into the mix
|
|
} else if (op == 5) {
|
|
// choose a pseudo-random number
|
|
lfs_size_t x = TEST_PRNG(&prng) % N;
|
|
|
|
// insert into our sim, use negative numbers for dirs
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= x) {
|
|
// already seen?
|
|
if (k < sim_size && sim[k] == x) {
|
|
goto nonsense;
|
|
} else {
|
|
// insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim_size += 1;
|
|
sim[k] = x;
|
|
sim_prngs[k] = 0;
|
|
sim_isdirs[k] = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// mark any related sim files as zombied
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
|
|
// make the directory
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
lfsr_mkdir(&lfs, name) => 0;
|
|
}
|
|
|
|
// gc!
|
|
lfsr_fs_gc(&lfs, GC_STEPS,
|
|
((MKCONSISTENT) ? LFS_GC_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
|
}
|
|
|
|
// check that disk matches our simulation
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
struct lfs_info info;
|
|
lfsr_stat(&lfs, name, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
if (sim_isdirs[j]) {
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
} else {
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
}
|
|
}
|
|
|
|
lfsr_dir_t dir;
|
|
lfsr_dir_open(&lfs, &dir, "/") => 0;
|
|
struct lfs_info info;
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, ".") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, "..") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
if (sim_isdirs[j]) {
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
} else {
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
}
|
|
}
|
|
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
|
lfsr_dir_close(&lfs, &dir) => 0;
|
|
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
if (sim_isdirs[j]) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => LFS_ERR_ISDIR;
|
|
|
|
} else {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
|
|
|
uint32_t wprng = sim_prngs[j];
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
|
|
uint8_t rbuf[SIZE];
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
|
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];
|
|
lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE;
|
|
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
|
}
|
|
|
|
// clean up sim/lfs
|
|
free(sim);
|
|
free(sim_prngs);
|
|
for (lfs_size_t j = 0; j < sim_file_count; j++) {
|
|
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
|
|
free(sim_files[j]);
|
|
}
|
|
free(sim_files);
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|