76493142e7
This adds the LFS_TYPE_STICKYNOTE type, allowing users to interact with
stickynotes as long as they aren't orphaned.
This hopefully solves the long-standing mess that was the LFS_O_EXCL
API.
---
As for what I mean by orphaned vs non-orphaned stickynotes:
Non-orphaned stickynotes represent files that have been "created" (via
LFS_O_CREAT), but not "committed" (via sync/close). You can still close
and convert the stickynote to a reg file, so these aren't orphans. These
are also called "uncreated" files in some parts of the codebase:
- open+O_CREAT -> non-orphaned stickynote (uncreated file)
Orphaned stickynotes are possible by either removing an open file, or
desyncing a file before sync/close. These are still invisible to the
user and will be eventually cleaned up after the last file handle is
closed:
- open+remove -> orphaned stickynote (zombied file)
- open+O_CREAT+desync+close -> orphaned stickynote (orphaned file)
Desynced files are a bit special. Even though they technically aren't
orphaned, they also behave like orphaned file handles:
- open+O_CREAT+close -> orphaned stickynote (desynced file)
The idea is this mimics the state of files post-close, and allows for
some tricks like using a desync file as a temporary file with no
observable effects on the filesystem.
---
The motivation for this comes from staring at the LFS_O_EXCL API for too
long and realizing the problem is that littlefs's API contradicts itself
when it comes to whether or not uncreated files exist.
This solution is to consistently treat uncreated files as though they
exist (the alternative would make LFS_O_EXCL pretty much useless), but I
really didn't want to do this as having what appears to be normal files
disappear after powerloss risks confusion.
The compromise here is to give these files a special type, repurposing
the internal LFS_TAG_STICKYNOTE, which hopefully hints to the user these
won't behave like normal files.
If the user is more interested in POSIX compatibility, they can always
map these to either LFS_TYPE_REG or LFS_ERR_NOENT, whichever they think
is the least confusing.
As a quirk of littlefs's API, stickynotes should never actually contain
any data, and will always have size 0.
However they can have custom attributes assigned now (which is I guess
ok? also TODO should probably test this).
---
The implementation right now is a bit naive, I mostly just wanted to get
the tests working again in this new model. It may be possible to claw
back some of this code cost:
code stack ctx
before: 35740 2440 640
after: 35952 (+0.6%) 2440 (+0.0%) 640 (+0.0%)
3786 lines
122 KiB
TOML
3786 lines
122 KiB
TOML
# Test GC things
|
|
|
|
# most of the GC logic is tested in test_traversal, we just test
|
|
# GC-API specific things here
|
|
after = ['test_traversal']
|
|
|
|
# test that lookahead can make progress in isolation
|
|
[cases.test_gc_lookahead_progress]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.GC_FLAGS = '''
|
|
LFS_GC_LOOKAHEAD
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)
|
|
'''
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
ifdef = 'LFS_GC'
|
|
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_LOOKAHEAD);
|
|
assert(lfs.omdirs != &lfs.gc.t.b.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) => 0;
|
|
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
if (!(fsinfo.flags & LFS_I_LOOKAHEAD)) {
|
|
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.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.GC_FLAGS = '''
|
|
LFS_GC_LOOKAHEAD
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)
|
|
'''
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
# we need something to keep the traversal running
|
|
if = 'CKMETA || CKDATA'
|
|
ifdef = 'LFS_GC'
|
|
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_LOOKAHEAD);
|
|
assert(lfs.omdirs != &lfs.gc.t.b.o);
|
|
|
|
// run GC one step
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
assert(lfs.omdirs == &lfs.gc.t.b.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.t.b.o) {
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
}
|
|
|
|
// we should _not_ make progress
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_LOOKAHEAD);
|
|
|
|
// 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.LOOKAHEAD = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.GC_FLAGS = '''
|
|
LFS_GC_COMPACT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)
|
|
'''
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
ifdef = 'LFS_GC'
|
|
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.b.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_COMPACT);
|
|
assert(lfs.omdirs != &lfs.gc.t.b.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) => 0;
|
|
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
if (!(fsinfo.flags & LFS_I_COMPACT)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// mdir should have been compacted
|
|
assert((file.b.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.LOOKAHEAD = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.GC_FLAGS = '''
|
|
LFS_GC_COMPACT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)
|
|
'''
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
# we need something to keep the traversal running
|
|
if = 'CKMETA || CKDATA'
|
|
ifdef = 'LFS_GC'
|
|
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.b.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_COMPACT);
|
|
assert(lfs.omdirs != &lfs.gc.t.b.o);
|
|
|
|
// run GC one traversal + one step
|
|
while (true) {
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
|
|
// internal traversal done?
|
|
if (lfs.omdirs != &lfs.gc.t.b.o) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
assert(lfs.omdirs == &lfs.gc.t.b.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.t.b.o) {
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
}
|
|
|
|
// we should _not_ make progress
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_COMPACT);
|
|
|
|
// 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.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.GC_FLAGS = '''
|
|
LFS_GC_MKCONSISTENT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)
|
|
'''
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.SIZE = 'FILE_CACHE_SIZE/2'
|
|
# <=2 => grm-able
|
|
# >2 => requires orphans
|
|
defines.ORPHANS = [1, 2, 3, 100]
|
|
ifdef = 'LFS_GC'
|
|
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_MKCONSISTENT);
|
|
assert(lfs.omdirs != &lfs.gc.t.b.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) => 0;
|
|
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
if (!(fsinfo.flags & LFS_I_MKCONSISTENT)) {
|
|
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_CACHE_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_MKCONSISTENT);
|
|
#ifdef LFS_GC
|
|
assert(lfs.omdirs != &lfs.gc.t.b.o);
|
|
#endif
|
|
|
|
// 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_MKCONSISTENT));
|
|
|
|
// 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.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.GC_FLAGS = '''
|
|
LFS_GC_MKCONSISTENT
|
|
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_GC_CKDATA : 0)
|
|
'''
|
|
defines.SIZE = 'FILE_CACHE_SIZE/2'
|
|
# <=2 => grm-able
|
|
# >2 => requires orphans
|
|
defines.ORPHANS = [3, 100]
|
|
# we need something to keep the traversal running
|
|
if = 'CKMETA || CKDATA'
|
|
ifdef = 'LFS_GC'
|
|
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.t.b.o);
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
assert(lfs.omdirs == &lfs.gc.t.b.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_MKCONSISTENT);
|
|
|
|
// run GC until our traversal is done
|
|
while (lfs.omdirs == &lfs.gc.t.b.o) {
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
}
|
|
|
|
// we should _not_ make progress
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags & LFS_I_MKCONSISTENT);
|
|
|
|
// 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_FLAGS = 'LFS_GC_CKMETA'
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
ifdef = 'LFS_GC'
|
|
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);
|
|
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_FLAGS = 'LFS_GC_CKDATA'
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
ifdef = 'LFS_GC'
|
|
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);
|
|
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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_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:;
|
|
'''
|
|
|
|
# test we can detect fully clobbered blocks after a ck pass, if we call
|
|
# lfsr_fs_unck
|
|
[cases.test_gc_ckmeta_unck]
|
|
# AFTER=0 => after running lfsr_fs_gc once
|
|
# AFTER=1 => after running lfsr_fs_gc to completion
|
|
# AFTER=2 => after running lfsr_traversal_t
|
|
# AFTER=3 => after lfsr_fs_ckmeta
|
|
# AFTER=4 => after remounting with LFS_M_CKMETA
|
|
defines.AFTER = [0, 1, 2, 3, 4]
|
|
defines.GC_FLAGS = 'LFS_GC_CKMETA'
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
ifdef = 'LFS_GC'
|
|
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;
|
|
}
|
|
|
|
// run lfsr_fs_gc before clobbering, this should not find
|
|
// anything
|
|
|
|
// run lfsr_fs_gc once
|
|
if (AFTER == 0) {
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
|
|
// run lfsr_fs_gc to completion
|
|
} else if (AFTER == 1) {
|
|
while (true) {
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
if (!(fsinfo.flags & LFS_I_CKMETA)) {
|
|
break;
|
|
}
|
|
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
}
|
|
|
|
// run lfsr_traversal_t
|
|
} else if (AFTER == 2) {
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, GC_FLAGS) => 0;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(err == 0 || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
|
|
// run lfsr_fs_ckmeta
|
|
} else if (AFTER == 3) {
|
|
lfsr_fs_ckmeta(&lfs) => 0;
|
|
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(!(fsinfo.flags & LFS_I_CKMETA));
|
|
|
|
// remount with LFS_M_CKMETA
|
|
} else if (AFTER == 4) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKMETA, CFG) => 0;
|
|
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(!(fsinfo.flags & LFS_I_CKMETA));
|
|
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
// 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:;
|
|
// clear relevant ck flags
|
|
lfsr_fs_unck(&lfs, LFS_I_CKMETA) => 0;
|
|
|
|
// 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);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
// found it
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
}
|
|
done:;
|
|
'''
|
|
|
|
[cases.test_gc_ckdata_unck]
|
|
# AFTER=0 => after running lfsr_fs_gc once
|
|
# AFTER=1 => after running lfsr_fs_gc to completion
|
|
# AFTER=2 => after running lfsr_traversal_t
|
|
# AFTER=3 => after lfsr_fs_ckdata
|
|
# AFTER=4 => after remounting with LFS_M_CKDATA
|
|
defines.AFTER = [0, 1, 2, 3, 4]
|
|
defines.GC_FLAGS = 'LFS_GC_CKDATA'
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
ifdef = 'LFS_GC'
|
|
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;
|
|
}
|
|
|
|
// run lfsr_fs_gc before clobbering, this should not find
|
|
// anything
|
|
|
|
// run lfsr_fs_gc once
|
|
if (AFTER == 0) {
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
|
|
// run lfsr_fs_gc to completion
|
|
} else if (AFTER == 1) {
|
|
while (true) {
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
if (!(fsinfo.flags & LFS_I_CKDATA)) {
|
|
break;
|
|
}
|
|
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
}
|
|
|
|
// run lfsr_traversal_t
|
|
} else if (AFTER == 2) {
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, GC_FLAGS) => 0;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(err == 0 || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
|
|
// run lfsr_fs_ckdata
|
|
} else if (AFTER == 3) {
|
|
lfsr_fs_ckdata(&lfs) => 0;
|
|
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(!(fsinfo.flags & LFS_I_CKDATA));
|
|
|
|
// remount with LFS_M_CKDATA
|
|
} else if (AFTER == 4) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKDATA, CFG) => 0;
|
|
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(!(fsinfo.flags & LFS_I_CKDATA));
|
|
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
// 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:;
|
|
// clear relevant ck flags
|
|
lfsr_fs_unck(&lfs, LFS_I_CKDATA) => 0;
|
|
|
|
// 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);
|
|
assert(!err || err == LFS_ERR_CORRUPT);
|
|
// found it
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
}
|
|
done:;
|
|
'''
|
|
|
|
|
|
# test that gc work clears flags in lfsr_fs_stat
|
|
[cases.test_gc_iflags]
|
|
# AFTER=0 => after running lfsr_fs_gc
|
|
# AFTER=1 => after running lfsr_traversal_t
|
|
# AFTER=2 => after explicit operations
|
|
# AFTER=3 => after remounting
|
|
defines.AFTER = [0, 1, 2, 3]
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.GC_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)
|
|
'''
|
|
defines.GC_STEPS = -1
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
if = [
|
|
'(SIZE*N)/BLOCK_SIZE <= 32',
|
|
'LFS_IFDEF_GC(true, AFTER != 0)',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
// create an interesting filesystem
|
|
uint32_t prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "squid%03x", i);
|
|
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
// remount to reset flags
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
// check that flags were reset
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags == (
|
|
LFS_I_MKCONSISTENT
|
|
| LFS_I_LOOKAHEAD
|
|
| LFS_I_COMPACT
|
|
| LFS_I_CKMETA
|
|
| LFS_I_CKDATA));
|
|
|
|
// run gc
|
|
if (AFTER == 0) {
|
|
#ifdef LFS_GC
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
#else
|
|
assert(false);
|
|
#endif
|
|
|
|
// run lfsr_traversal_t
|
|
} else if (AFTER == 1) {
|
|
while (true) {
|
|
// it may take multiple traversals to do all pending work
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
if (!(fsinfo.flags & (
|
|
((MKCONSISTENT) ? LFS_I_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_I_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_I_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_I_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_I_CKDATA : 0)))) {
|
|
break;
|
|
}
|
|
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, GC_FLAGS) => 0;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(err == 0 || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
}
|
|
|
|
// run explicit operations
|
|
//
|
|
// yes, doing these in separate traversals is inefficient, I don't care
|
|
} else if (AFTER == 2) {
|
|
while (true) {
|
|
// it may take multiple traversals to do all pending work
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
if (!(fsinfo.flags & (
|
|
((MKCONSISTENT) ? LFS_I_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_I_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_I_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_I_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_I_CKDATA : 0)))) {
|
|
break;
|
|
}
|
|
|
|
if (MKCONSISTENT) {
|
|
lfsr_fs_mkconsistent(&lfs) => 0;
|
|
}
|
|
|
|
if (LOOKAHEAD) {
|
|
// we need an explicit traversal for this
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, LFS_T_LOOKAHEAD) => 0;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(err == 0 || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
}
|
|
|
|
if (COMPACT) {
|
|
// we need an explicit traversal for this
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT) => 0;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(err == 0 || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
}
|
|
|
|
if (CKMETA) {
|
|
lfsr_fs_ckmeta(&lfs) => 0;
|
|
}
|
|
|
|
if (CKDATA) {
|
|
lfsr_fs_ckdata(&lfs) => 0;
|
|
}
|
|
}
|
|
|
|
// remount with gc flags
|
|
} else if (AFTER == 3) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | GC_FLAGS, CFG) => 0;
|
|
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
// did these clear the right flags?
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags == (
|
|
((!MKCONSISTENT) ? LFS_I_MKCONSISTENT : 0)
|
|
| ((!LOOKAHEAD) ? LFS_I_LOOKAHEAD : 0)
|
|
| ((!COMPACT) ? LFS_I_COMPACT : 0)
|
|
// note ckdata implies ckmeta
|
|
| ((!CKMETA && !CKDATA) ? LFS_I_CKMETA : 0)
|
|
| ((!CKDATA) ? LFS_I_CKDATA : 0)));
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that gc work clears flags in lfsr_fs_stat after lfsr_fs_unck
|
|
[cases.test_gc_iflags_unck]
|
|
# AFTER=0 => after running lfsr_fs_gc
|
|
# AFTER=1 => after running lfsr_traversal_t
|
|
# AFTER=2 => after explicit operations
|
|
# AFTER=3 => after remounting
|
|
defines.AFTER = [0, 1, 2, 3]
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.GC_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)
|
|
'''
|
|
defines.GC_STEPS = -1
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
|
defines.SIZE = [
|
|
'0',
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
if = [
|
|
'(SIZE*N)/BLOCK_SIZE <= 32',
|
|
'LFS_IFDEF_GC(true, AFTER != 0)',
|
|
]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
// create an interesting filesystem
|
|
uint32_t prng = 42;
|
|
for (lfs_size_t i = 0; i < N; i++) {
|
|
char name[256];
|
|
sprintf(name, "squid%03x", i);
|
|
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
|
}
|
|
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name,
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
}
|
|
|
|
// remount to reset flags
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
|
|
// check that flags were reset
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags == (
|
|
LFS_I_MKCONSISTENT
|
|
| LFS_I_LOOKAHEAD
|
|
| LFS_I_COMPACT
|
|
| LFS_I_CKMETA
|
|
| LFS_I_CKDATA));
|
|
|
|
// run gc
|
|
if (AFTER == 0) {
|
|
#ifdef LFS_GC
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
#else
|
|
assert(false);
|
|
#endif
|
|
|
|
// run lfsr_traversal_t
|
|
} else if (AFTER == 1) {
|
|
while (true) {
|
|
// it may take multiple traversals to do all pending work
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
if (!(fsinfo.flags & (
|
|
((MKCONSISTENT) ? LFS_I_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_I_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_I_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_I_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_I_CKDATA : 0)))) {
|
|
break;
|
|
}
|
|
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, GC_FLAGS) => 0;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(err == 0 || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
}
|
|
|
|
// run explicit operations
|
|
//
|
|
// yes, doing these in separate traversals is inefficient, I don't care
|
|
} else if (AFTER == 2) {
|
|
while (true) {
|
|
// it may take multiple traversals to do all pending work
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
if (!(fsinfo.flags & (
|
|
((MKCONSISTENT) ? LFS_I_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_I_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_I_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_I_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_I_CKDATA : 0)))) {
|
|
break;
|
|
}
|
|
|
|
if (MKCONSISTENT) {
|
|
lfsr_fs_mkconsistent(&lfs) => 0;
|
|
}
|
|
|
|
if (LOOKAHEAD) {
|
|
// we need an explicit traversal for this
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, LFS_T_LOOKAHEAD) => 0;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(err == 0 || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
}
|
|
|
|
if (COMPACT) {
|
|
// we need an explicit traversal for this
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT) => 0;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(err == 0 || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
}
|
|
|
|
if (CKMETA) {
|
|
lfsr_fs_ckmeta(&lfs) => 0;
|
|
}
|
|
|
|
if (CKDATA) {
|
|
lfsr_fs_ckdata(&lfs) => 0;
|
|
}
|
|
}
|
|
|
|
// remount with gc flags
|
|
} else if (AFTER == 3) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | GC_FLAGS, CFG) => 0;
|
|
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
// did these clear the right flags?
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags == (
|
|
((!MKCONSISTENT) ? LFS_I_MKCONSISTENT : 0)
|
|
| ((!LOOKAHEAD) ? LFS_I_LOOKAHEAD : 0)
|
|
| ((!COMPACT) ? LFS_I_COMPACT : 0)
|
|
// note ckdata implies ckmeta
|
|
| ((!CKMETA && !CKDATA) ? LFS_I_CKMETA : 0)
|
|
| ((!CKDATA) ? LFS_I_CKDATA : 0)));
|
|
|
|
// test that we can reset flags with lfsr_fs_unck
|
|
lfsr_fs_unck(&lfs, GC_FLAGS) => 0;
|
|
|
|
// check that flags were reset
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags == (
|
|
LFS_I_MKCONSISTENT
|
|
| LFS_I_LOOKAHEAD
|
|
| LFS_I_COMPACT
|
|
// note ckdata implies ckmeta, but uncking ckdata does
|
|
// _not_ imply uncking ckmeta
|
|
| ((!(CKDATA && !CKMETA)) ? LFS_I_CKMETA : 0)
|
|
| LFS_I_CKDATA));
|
|
|
|
// run gc
|
|
if (AFTER == 0) {
|
|
#ifdef LFS_GC
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
#else
|
|
assert(false);
|
|
#endif
|
|
|
|
// run lfsr_traversal_t
|
|
} else if (AFTER == 1) {
|
|
while (true) {
|
|
// it may take multiple traversals to do all pending work
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
if (!(fsinfo.flags & (
|
|
((MKCONSISTENT) ? LFS_I_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_I_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_I_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_I_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_I_CKDATA : 0)))) {
|
|
break;
|
|
}
|
|
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, GC_FLAGS) => 0;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(err == 0 || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
}
|
|
|
|
// run explicit operations
|
|
//
|
|
// yes, doing these in separate traversals is inefficient, I don't care
|
|
} else if (AFTER == 2) {
|
|
while (true) {
|
|
// it may take multiple traversals to do all pending work
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
if (!(fsinfo.flags & (
|
|
((MKCONSISTENT) ? LFS_I_MKCONSISTENT : 0)
|
|
| ((LOOKAHEAD) ? LFS_I_LOOKAHEAD : 0)
|
|
| ((COMPACT) ? LFS_I_COMPACT : 0)
|
|
| ((CKMETA) ? LFS_I_CKMETA : 0)
|
|
| ((CKDATA) ? LFS_I_CKDATA : 0)))) {
|
|
break;
|
|
}
|
|
|
|
if (MKCONSISTENT) {
|
|
lfsr_fs_mkconsistent(&lfs) => 0;
|
|
}
|
|
|
|
if (LOOKAHEAD) {
|
|
// we need an explicit traversal for this
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, LFS_T_LOOKAHEAD) => 0;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(err == 0 || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
}
|
|
|
|
if (COMPACT) {
|
|
// we need an explicit traversal for this
|
|
lfsr_traversal_t t;
|
|
lfsr_traversal_open(&lfs, &t, LFS_T_COMPACT) => 0;
|
|
while (true) {
|
|
struct lfs_tinfo tinfo;
|
|
int err = lfsr_traversal_read(&lfs, &t, &tinfo);
|
|
assert(err == 0 || err == LFS_ERR_NOENT);
|
|
if (err == LFS_ERR_NOENT) {
|
|
break;
|
|
}
|
|
}
|
|
lfsr_traversal_close(&lfs, &t) => 0;
|
|
}
|
|
|
|
if (CKMETA) {
|
|
lfsr_fs_ckmeta(&lfs) => 0;
|
|
}
|
|
|
|
if (CKDATA) {
|
|
lfsr_fs_ckdata(&lfs) => 0;
|
|
}
|
|
}
|
|
|
|
// remount with gc flags
|
|
} else if (AFTER == 3) {
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR | GC_FLAGS, CFG) => 0;
|
|
|
|
} else {
|
|
assert(false);
|
|
}
|
|
|
|
// did these clear the right flags?
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags == (
|
|
((!MKCONSISTENT) ? LFS_I_MKCONSISTENT : 0)
|
|
| ((!LOOKAHEAD) ? LFS_I_LOOKAHEAD : 0)
|
|
| ((!COMPACT) ? LFS_I_COMPACT : 0)
|
|
// note ckdata implies ckmeta
|
|
| ((!CKMETA && !CKDATA) ? LFS_I_CKMETA : 0)
|
|
| ((!CKDATA) ? LFS_I_CKDATA : 0)));
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
|
|
# pseudo-fuzz test that dirtying still works with the GC API
|
|
[cases.test_gc_mutation]
|
|
defines.N = 100
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.GC_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)
|
|
'''
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
ifdef = 'LFS_GC'
|
|
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) => 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 spamming lfsr_fs_unck doesn't break anything
|
|
[cases.test_gc_mutation_unck]
|
|
defines.N = 100
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.GC_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)
|
|
'''
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'8*BLOCK_SIZE',
|
|
]
|
|
ifdef = 'LFS_GC'
|
|
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 random set of flags to unck every cycle
|
|
uint32_t flags = GC_FLAGS & TEST_PRNG(&prng);
|
|
lfsr_fs_unck(&lfs, flags) => 0;
|
|
|
|
// gc!
|
|
lfsr_fs_gc(&lfs) => 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.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.UNCK = [false, true]
|
|
defines.GC_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)
|
|
'''
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256]
|
|
ifdef = 'LFS_GC'
|
|
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) => 0;
|
|
|
|
// unck to keep things interesting?
|
|
if (UNCK) {
|
|
lfsr_fs_unck(&lfs, LFS_I_CKMETA | LFS_I_CKDATA) => 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.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.UNCK = [false, true]
|
|
defines.GC_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)
|
|
'''
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
# 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'
|
|
ifdef = 'LFS_GC'
|
|
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) => 0;
|
|
|
|
// unck to keep things interesting?
|
|
if (UNCK) {
|
|
lfsr_fs_unck(&lfs, LFS_I_CKMETA | LFS_I_CKDATA) => 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.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.UNCK = [false, true]
|
|
defines.GC_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)
|
|
'''
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
# 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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
|
ifdef = 'LFS_GC'
|
|
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) => 0;
|
|
|
|
// unck to keep things interesting?
|
|
if (UNCK) {
|
|
lfsr_fs_unck(&lfs, LFS_I_CKMETA | LFS_I_CKDATA) => 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.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.UNCK = [false, true]
|
|
defines.GC_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)
|
|
'''
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
# 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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.SEED = 42
|
|
fuzz = 'SEED'
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 16'
|
|
ifdef = 'LFS_GC'
|
|
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) => 0;
|
|
|
|
// unck to keep things interesting?
|
|
if (UNCK) {
|
|
lfsr_fs_unck(&lfs, LFS_I_CKMETA | LFS_I_CKDATA) => 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.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.UNCK = [false, true]
|
|
defines.GC_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)
|
|
'''
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
# set compact thresh to minimum
|
|
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
|
defines.OPS = 20
|
|
defines.SIZE = [
|
|
'FILE_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
# chunk is more an upper limit here
|
|
defines.CHUNK = [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',
|
|
]
|
|
ifdef = 'LFS_GC'
|
|
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) => 0;
|
|
|
|
// unck to keep things interesting?
|
|
if (UNCK) {
|
|
lfsr_fs_unck(&lfs, LFS_I_CKMETA | LFS_I_CKDATA) => 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_uz_fuzz]
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.UNCK = [false, true]
|
|
defines.GC_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)
|
|
'''
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
# 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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.SEED = 42
|
|
fuzz = 'SEED'
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 16'
|
|
ifdef = 'LFS_GC'
|
|
code = '''
|
|
// test with uncreats, 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));
|
|
bool *sim_isstickys = malloc(N*sizeof(bool));
|
|
lfs_size_t sim_size = 0;
|
|
|
|
typedef struct sim_file {
|
|
lfs_size_t x;
|
|
bool sticky;
|
|
bool zombie;
|
|
uint32_t prng;
|
|
lfsr_file_t file;
|
|
} sim_file_t;
|
|
sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*));
|
|
lfs_size_t sim_file_count = 0;
|
|
|
|
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 exist = false;
|
|
uint32_t wprng = 0;
|
|
bool sticky = true;
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
if (sim[j] == x) {
|
|
exist = true;
|
|
wprng = sim_prngs[j];
|
|
sticky = sim_isstickys[j];
|
|
break;
|
|
}
|
|
}
|
|
// choose a random seed if we don't exist
|
|
if (!exist) {
|
|
wprng = TEST_PRNG(&prng);
|
|
sticky = true;
|
|
}
|
|
|
|
lfs_size_t j = sim_file_count;
|
|
sim_files[j] = malloc(sizeof(sim_file_t));
|
|
|
|
// open the actual file
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
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 (!exist || sticky) {
|
|
uint8_t wbuf[SIZE];
|
|
uint32_t wprng_ = wprng;
|
|
for (lfs_size_t k = 0; k < SIZE; k++) {
|
|
wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE)
|
|
=> SIZE;
|
|
}
|
|
|
|
// open in our sim
|
|
sim_files[j]->x = x;
|
|
sim_files[j]->sticky = sticky;
|
|
sim_files[j]->zombie = false;
|
|
sim_files[j]->prng = wprng;
|
|
sim_file_count++;
|
|
|
|
// insert into our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= x) {
|
|
// already seen?
|
|
if (k < sim_size && sim[k] == x) {
|
|
// new prng
|
|
sim_prngs[k] = wprng;
|
|
} else {
|
|
// insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[k+1], &sim_isstickys[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim_size += 1;
|
|
sim[k] = x;
|
|
sim_prngs[k] = wprng;
|
|
sim_isstickys[k] = sticky;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// write/rewrite a file?
|
|
} else if (op == 1) {
|
|
if (sim_file_count == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file handle
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
|
|
lfs_size_t x = sim_files[j]->x;
|
|
// choose a random seed
|
|
uint32_t wprng = TEST_PRNG(&prng);
|
|
|
|
// write to the file
|
|
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
|
|
uint8_t wbuf[SIZE];
|
|
uint32_t wprng_ = wprng;
|
|
for (lfs_size_t k = 0; k < SIZE; k++) {
|
|
wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26);
|
|
}
|
|
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;
|
|
|
|
// update sim
|
|
sim_files[j]->prng = wprng;
|
|
if (!sim_files[j]->zombie) {
|
|
// update in our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (sim[k] == x) {
|
|
// new prng
|
|
sim_prngs[k] = wprng;
|
|
// no longer sticky
|
|
sim_isstickys[k] = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update related sim files
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
|
|
// new prng
|
|
sim_files[k]->prng = wprng;
|
|
// no longer sticky
|
|
sim_files[k]->sticky = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// close a file?
|
|
} else if (op == 2) {
|
|
if (sim_file_count == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file handle
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
|
|
lfs_size_t x = sim_files[j]->x;
|
|
bool sticky = sim_files[j]->sticky;
|
|
bool zombie = sim_files[j]->zombie;
|
|
|
|
// this doesn't really test anything, but if we don't close
|
|
// files eventually everything will end up zombies
|
|
|
|
// close the file without affected disk
|
|
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
|
|
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
|
|
// clobber closed files to try to catch lingering references
|
|
memset(&sim_files[j]->file, 0xcc, sizeof(lfsr_file_t));
|
|
|
|
// remove from list
|
|
free(sim_files[j]);
|
|
sim_files[j] = sim_files[sim_file_count-1];
|
|
sim_file_count -= 1;
|
|
|
|
// update our sim
|
|
if (sticky && !zombie) {
|
|
// orphaned?
|
|
bool orphan = true;
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
|
|
orphan = false;
|
|
}
|
|
}
|
|
|
|
// if we were never synced, delete from sim
|
|
if (orphan) {
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (sim[k] == x) {
|
|
memmove(&sim[k], &sim[k+1],
|
|
(sim_size-(k+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k], &sim_prngs[k+1],
|
|
(sim_size-(k+1))*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[k], &sim_isstickys[k+1],
|
|
(sim_size-(k+1))*sizeof(bool));
|
|
sim_size -= 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove a file?
|
|
} else if (op == 3) {
|
|
if (sim_size == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file to delete
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
|
|
// delete this file
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
lfsr_remove(&lfs, name) => 0;
|
|
|
|
// delete from our sim
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[j], &sim_isstickys[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
sim_size -= 1;
|
|
|
|
// mark any related sim files as zombied
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
|
|
// rename a file?
|
|
} else if (op == 4) {
|
|
if (sim_size == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file to rename, and a random number to
|
|
// rename to
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
lfs_size_t y = TEST_PRNG(&prng) % N;
|
|
uint32_t wprng = sim_prngs[j];
|
|
bool sticky = sim_isstickys[j];
|
|
|
|
// rename this file
|
|
char old_name[256];
|
|
sprintf(old_name, "batman%03x", x);
|
|
char new_name[256];
|
|
sprintf(new_name, "batman%03x", y);
|
|
lfsr_rename(&lfs, old_name, new_name) => 0;
|
|
|
|
// update our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= y) {
|
|
// renaming and replacing
|
|
if (k < sim_size && sim[k] == y && x != y) {
|
|
// delete the original entry
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[j], &sim_isstickys[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
sim_size -= 1;
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// update the prng/sticky
|
|
sim_prngs[k] = wprng;
|
|
sim_isstickys[k] = sticky;
|
|
// just renaming
|
|
} else {
|
|
// first delete
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[j], &sim_isstickys[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// then insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[k+1], &sim_isstickys[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim[k] = y;
|
|
sim_prngs[k] = wprng;
|
|
sim_isstickys[k] = sticky;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update any related sim files
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
// move source files
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->x = y;
|
|
|
|
// mark target files as zombied
|
|
} else if (sim_files[k]->x == y) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// gc!
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
|
|
// unck to keep things interesting?
|
|
if (UNCK) {
|
|
lfsr_fs_unck(&lfs, LFS_I_CKMETA | LFS_I_CKDATA) => 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_isstickys[j]) {
|
|
assert(info.type == LFS_TYPE_STICKYNOTE);
|
|
assert(info.size == 0);
|
|
} else {
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
}
|
|
}
|
|
|
|
lfsr_dir_t dir;
|
|
lfsr_dir_open(&lfs, &dir, "/") => 0;
|
|
struct lfs_info info;
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, ".") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, "..") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
if (sim_isstickys[j]) {
|
|
assert(info.type == LFS_TYPE_STICKYNOTE);
|
|
assert(info.size == 0);
|
|
} else {
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
}
|
|
}
|
|
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
|
lfsr_dir_close(&lfs, &dir) => 0;
|
|
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
|
|
|
uint32_t wprng = sim_prngs[j];
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
|
|
uint8_t rbuf[SIZE];
|
|
if (sim_isstickys[j]) {
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => 0;
|
|
} else {
|
|
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);
|
|
free(sim_isstickys);
|
|
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_uzd_fuzz]
|
|
defines.MKCONSISTENT = [false, true]
|
|
defines.LOOKAHEAD = [false, true]
|
|
defines.COMPACT = [false, true]
|
|
defines.CKMETA = [false, true]
|
|
defines.CKDATA = [false, true]
|
|
defines.UNCK = [false, true]
|
|
defines.GC_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)
|
|
'''
|
|
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
|
# 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_CACHE_SIZE/2',
|
|
'2*FILE_CACHE_SIZE',
|
|
'BLOCK_SIZE/2',
|
|
'BLOCK_SIZE',
|
|
'2*BLOCK_SIZE',
|
|
'4*BLOCK_SIZE',
|
|
]
|
|
defines.SEED = 42
|
|
fuzz = 'SEED'
|
|
if = '(SIZE*N)/BLOCK_SIZE <= 16'
|
|
ifdef = 'LFS_GC'
|
|
code = '''
|
|
// test with uncreats, 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_isstickys = malloc(N*sizeof(bool));
|
|
bool *sim_isdirs = malloc(N*sizeof(bool));
|
|
lfs_size_t sim_size = 0;
|
|
|
|
typedef struct sim_file {
|
|
lfs_size_t x;
|
|
bool sticky;
|
|
bool zombie;
|
|
uint32_t prng;
|
|
lfsr_file_t file;
|
|
} sim_file_t;
|
|
sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*));
|
|
lfs_size_t sim_file_count = 0;
|
|
|
|
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 exist = true;
|
|
uint32_t wprng = 0;
|
|
bool sticky = true;
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
if (sim[j] == x) {
|
|
if (sim_isdirs[j]) {
|
|
goto nonsense;
|
|
}
|
|
exist = true;
|
|
wprng = sim_prngs[j];
|
|
sticky = sim_isstickys[j];
|
|
break;
|
|
}
|
|
}
|
|
// choose a random seed if we don't exist
|
|
if (!exist) {
|
|
wprng = TEST_PRNG(&prng);
|
|
sticky = true;
|
|
}
|
|
|
|
lfs_size_t j = sim_file_count;
|
|
sim_files[j] = malloc(sizeof(sim_file_t));
|
|
|
|
// open the actual file
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
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 (!exist || sticky) {
|
|
uint8_t wbuf[SIZE];
|
|
uint32_t wprng_ = wprng;
|
|
for (lfs_size_t k = 0; k < SIZE; k++) {
|
|
wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26);
|
|
}
|
|
lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE)
|
|
=> SIZE;
|
|
}
|
|
|
|
// open in our sim
|
|
sim_files[j]->x = x;
|
|
sim_files[j]->sticky = sticky;
|
|
sim_files[j]->zombie = false;
|
|
sim_files[j]->prng = wprng;
|
|
sim_file_count++;
|
|
|
|
// insert into our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= x) {
|
|
// already seen?
|
|
if (k < sim_size && sim[k] == x) {
|
|
// new prng
|
|
sim_prngs[k] = wprng;
|
|
} else {
|
|
// insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[k+1], &sim_isstickys[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim_size += 1;
|
|
sim[k] = x;
|
|
sim_prngs[k] = wprng;
|
|
sim_isstickys[k] = sticky;
|
|
sim_isdirs[k] = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// write/rewrite a file?
|
|
} else if (op == 1) {
|
|
if (sim_file_count == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file handle
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
|
|
lfs_size_t x = sim_files[j]->x;
|
|
// choose a random seed
|
|
uint32_t wprng = TEST_PRNG(&prng);
|
|
|
|
// write to the file
|
|
lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0;
|
|
uint8_t wbuf[SIZE];
|
|
uint32_t wprng_ = wprng;
|
|
for (lfs_size_t k = 0; k < SIZE; k++) {
|
|
wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26);
|
|
}
|
|
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;
|
|
|
|
// update sim
|
|
sim_files[j]->prng = wprng;
|
|
if (!sim_files[j]->zombie) {
|
|
// update in our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= x) {
|
|
// new prng
|
|
sim_prngs[k] = wprng;
|
|
// no longer sticky
|
|
sim_isstickys[k] = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update related sim files
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
|
|
// new prng
|
|
sim_files[k]->prng = wprng;
|
|
// no longer sticky
|
|
sim_files[k]->sticky = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// close a file?
|
|
} else if (op == 2) {
|
|
if (sim_file_count == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file handle
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_file_count;
|
|
lfs_size_t x = sim_files[j]->x;
|
|
lfs_size_t sticky = sim_files[j]->sticky;
|
|
lfs_size_t zombie = sim_files[j]->zombie;
|
|
|
|
// this doesn't really test anything, but if we don't close
|
|
// files eventually everything will end up zombies
|
|
|
|
// close the file without affected disk
|
|
lfsr_file_desync(&lfs, &sim_files[j]->file) => 0;
|
|
lfsr_file_close(&lfs, &sim_files[j]->file) => 0;
|
|
// clobber closed files to try to catch lingering references
|
|
memset(&sim_files[j]->file, 0xcc, sizeof(lfsr_file_t));
|
|
|
|
// remove from list
|
|
free(sim_files[j]);
|
|
sim_files[j] = sim_files[sim_file_count-1];
|
|
sim_file_count -= 1;
|
|
|
|
// update our sim
|
|
if (sticky && !zombie) {
|
|
// orphaned?
|
|
bool orphan = true;
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x && !sim_files[k]->zombie) {
|
|
orphan = false;
|
|
}
|
|
}
|
|
|
|
// if we were never synced, delete from sim
|
|
if (orphan) {
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (sim[k] == x) {
|
|
memmove(&sim[k], &sim[k+1],
|
|
(sim_size-(k+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k], &sim_prngs[k+1],
|
|
(sim_size-(k+1))*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[k], &sim_isstickys[k+1],
|
|
(sim_size-(k+1))*sizeof(bool));
|
|
memmove(&sim_isdirs[k], &sim_isdirs[k+1],
|
|
(sim_size-(k+1))*sizeof(bool));
|
|
sim_size -= 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove a file?
|
|
} else if (op == 3) {
|
|
if (sim_size == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file to delete
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
|
|
// delete this file
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
lfsr_remove(&lfs, name) => 0;
|
|
|
|
// delete from our sim
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[j], &sim_isstickys[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
sim_size -= 1;
|
|
|
|
// mark any related sim files as zombied
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
|
|
// rename a file?
|
|
} else if (op == 4) {
|
|
if (sim_size == 0) {
|
|
goto nonsense;
|
|
}
|
|
// choose a random file to rename, and a random number to
|
|
// rename to
|
|
lfs_size_t j = TEST_PRNG(&prng) % sim_size;
|
|
lfs_size_t x = sim[j];
|
|
lfs_size_t y = TEST_PRNG(&prng) % N;
|
|
uint32_t wprng = sim_prngs[j];
|
|
bool sticky = sim_isstickys[j];
|
|
bool dir = sim_isdirs[j];
|
|
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= y) {
|
|
// renaming and replacing
|
|
if (k < sim_size && sim[k] == y && x != y) {
|
|
// type mismatch?
|
|
if (sim_isdirs[k] != dir) {
|
|
goto nonsense;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// rename this file
|
|
char old_name[256];
|
|
sprintf(old_name, "batman%03x", x);
|
|
char new_name[256];
|
|
sprintf(new_name, "batman%03x", y);
|
|
lfsr_rename(&lfs, old_name, new_name) => 0;
|
|
|
|
// update our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= y) {
|
|
// renaming and replacing
|
|
if (k < sim_size && sim[k] == y && x != y) {
|
|
// delete the original entry
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[j], &sim_isstickys[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
sim_size -= 1;
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// update the prng/sticky/dir
|
|
sim_prngs[k] = wprng;
|
|
sim_isstickys[k] = sticky;
|
|
sim_isdirs[k] = dir;
|
|
// just renaming
|
|
} else {
|
|
// first delete
|
|
memmove(&sim[j], &sim[j+1],
|
|
(sim_size-(j+1))*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[j], &sim_prngs[j+1],
|
|
(sim_size-(j+1))*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[j], &sim_isstickys[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
memmove(&sim_isdirs[j], &sim_isdirs[j+1],
|
|
(sim_size-(j+1))*sizeof(bool));
|
|
if (k > j) {
|
|
k -= 1;
|
|
}
|
|
// then insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[k+1], &sim_isstickys[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim[k] = y;
|
|
sim_prngs[k] = wprng;
|
|
sim_isstickys[k] = sticky;
|
|
sim_isdirs[k] = dir;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update any related sim files
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
// move source files
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->x = y;
|
|
|
|
// mark target files as zombied
|
|
} else if (sim_files[k]->x == y) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
|
|
// toss a directory into the mix
|
|
} else if (op == 5) {
|
|
// choose a pseudo-random number
|
|
lfs_size_t x = TEST_PRNG(&prng) % N;
|
|
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= x) {
|
|
// already seen?
|
|
if (k < sim_size && sim[k] == x) {
|
|
goto nonsense;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// make the directory
|
|
char name[256];
|
|
sprintf(name, "batman%03x", x);
|
|
lfsr_mkdir(&lfs, name) => 0;
|
|
|
|
// insert into our sim
|
|
for (lfs_size_t k = 0;; k++) {
|
|
if (k >= sim_size || sim[k] >= x) {
|
|
// insert
|
|
memmove(&sim[k+1], &sim[k],
|
|
(sim_size-k)*sizeof(lfs_size_t));
|
|
memmove(&sim_prngs[k+1], &sim_prngs[k],
|
|
(sim_size-k)*sizeof(uint32_t));
|
|
memmove(&sim_isstickys[k+1], &sim_isstickys[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
memmove(&sim_isdirs[k+1], &sim_isdirs[k],
|
|
(sim_size-k)*sizeof(bool));
|
|
sim_size += 1;
|
|
sim[k] = x;
|
|
sim_prngs[k] = 0;
|
|
sim_isdirs[k] = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// mark any related sim files as zombied
|
|
for (lfs_size_t k = 0; k < sim_file_count; k++) {
|
|
if (sim_files[k]->x == x) {
|
|
sim_files[k]->zombie = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// gc!
|
|
lfsr_fs_gc(&lfs) => 0;
|
|
|
|
// unck to keep things interesting?
|
|
if (UNCK) {
|
|
lfsr_fs_unck(&lfs, LFS_I_CKMETA | LFS_I_CKDATA) => 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);
|
|
assert(info.size == 0);
|
|
} else if (sim_isstickys[j]) {
|
|
assert(info.type == LFS_TYPE_STICKYNOTE);
|
|
assert(info.size == 0);
|
|
} else {
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
}
|
|
}
|
|
|
|
lfsr_dir_t dir;
|
|
lfsr_dir_open(&lfs, &dir, "/") => 0;
|
|
struct lfs_info info;
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, ".") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, "..") == 0);
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
|
assert(strcmp(info.name, name) == 0);
|
|
if (sim_isdirs[j]) {
|
|
assert(info.type == LFS_TYPE_DIR);
|
|
assert(info.size == 0);
|
|
} else if (sim_isstickys[j]) {
|
|
assert(info.type == LFS_TYPE_STICKYNOTE);
|
|
assert(info.size == 0);
|
|
} else {
|
|
assert(info.type == LFS_TYPE_REG);
|
|
assert(info.size == SIZE);
|
|
}
|
|
}
|
|
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
|
lfsr_dir_close(&lfs, &dir) => 0;
|
|
|
|
for (lfs_size_t j = 0; j < sim_size; j++) {
|
|
if (sim_isdirs[j]) {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY)
|
|
=> LFS_ERR_ISDIR;
|
|
|
|
} else {
|
|
char name[256];
|
|
sprintf(name, "batman%03x", sim[j]);
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0;
|
|
|
|
uint32_t wprng = sim_prngs[j];
|
|
uint8_t wbuf[SIZE];
|
|
for (lfs_size_t j = 0; j < SIZE; j++) {
|
|
wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26);
|
|
}
|
|
|
|
uint8_t rbuf[SIZE];
|
|
if (sim_isstickys[j]) {
|
|
lfsr_file_read(&lfs, &file, rbuf, SIZE) => 0;
|
|
} else {
|
|
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);
|
|
free(sim_isstickys);
|
|
free(sim_isdirs);
|
|
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;
|
|
'''
|
|
|