Added filesystem-level info flags to lfsr_fs_stat
Thinking again of use cases, lfsr_fs_gc provides the perfect API to call
in the background to perform any pending filesystem work. But what if
there's no work to be done? Sure we could just spin forever, but that's
a waste. Especially on devices that can turn on sleep modes to save
power.
To help with this, this commit adds a set of flags to struct lfs_fsinfo
that signals when lfsr_fs_gc can accomplish work:
LFS_I_INCONSISTENT = 0x01, // Filesystem needs mkconsistent to write
LFS_I_NEEDSUPGRADE* = 0x02, // Filesystem needs an upgrade to write
LFS_I_CANLOOKAHEAD = 0x04, // Lookahead buffer is not full
LFS_I_CANPREERASE+ = 0x08, // Pre-erase buffer is not full
LFS_I_UNCOMPACTED = 0x10, // Filesystem may have uncompacted metadata
LFS_I_NEEDSREPAIRMETA+ = 0x20, // Filesystem contains damaged metadata
LFS_I_NEEDSREPAIRDATA+ = 0x40, // Filesystem contains damaged data
*Hypothetical
+Planned
This flags field also provides a useful place internally to store other
filesystem-related flags, currently LFS_F_ORPHANS, though this may be
expanded in the future.
These flags allow users to know exactly what work can/needs to be done
for the filesystem to make progress:
- LFS_I_INCONSISTENT => LFS_GC_MKCONSISTENT or lfsr_fs_mkconsistent
- LFS_I_CANLOOKAHEAD => LFS_GC_LOOKAHEAD
- LFS_I_UNCOMPACTED => LFS_GC_COMPACT
The one is new!
If we complete a compaction-traversal without any mutation, we know
all mdirs/btree nodes have been compacted and future traversals won't
accomplish anything. Of course, we need to clear this bit on
filesystem mutation.
Right now we just pessimistically assume the filesystem is uncompacted
during mount, but in theory we can also figure this out during our
initial mount traversal.
- LFS_GC_CKMETA/CKDATA?
LFS_GC_CKMETA and LFS_GC_CKDATA are a bit trickier. In theory,
LFS_GC_CKMETA/CKDATA will always accomplish something, since time is
the only ingredient necessary to introduce bit errors.
So there isn't really a reasonable flag here. It's entirely up to the
user to decide when to do an LFS_GC_CKMETA/CKDATA traversal.
Code changes:
code stack
before: 35740 2672
after: 35880 (+0.4%) 2672 (+0.0%)
This commit is contained in:
@@ -4701,7 +4701,11 @@ code = '''
|
||||
// we should have cleaned up all grms/orphans
|
||||
assert(lfs.grm.mids[0] == -1);
|
||||
assert(lfs.grm.mids[1] == -1);
|
||||
assert(lfs.hasorphans == false);
|
||||
assert(!(lfs.flags & LFS_F_ORPHANS));
|
||||
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(!(fsinfo.flags & LFS_I_INCONSISTENT));
|
||||
|
||||
// double check the actual disk state, it's easy for littlefs to
|
||||
// lie here
|
||||
@@ -4831,7 +4835,11 @@ code = '''
|
||||
// we should have cleaned up all grms/orphans
|
||||
assert(lfs.grm.mids[0] == -1);
|
||||
assert(lfs.grm.mids[1] == -1);
|
||||
assert(lfs.hasorphans == false);
|
||||
assert(!(lfs.flags & LFS_F_ORPHANS));
|
||||
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(!(fsinfo.flags & LFS_I_INCONSISTENT));
|
||||
|
||||
// double check the actual disk state, it's easy for littlefs to
|
||||
// lie here
|
||||
@@ -4975,7 +4983,11 @@ code = '''
|
||||
// we should have cleaned up all grms/orphans
|
||||
assert(lfs.grm.mids[0] == -1);
|
||||
assert(lfs.grm.mids[1] == -1);
|
||||
assert(lfs.hasorphans == false);
|
||||
assert(!(lfs.flags & LFS_F_ORPHANS));
|
||||
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(!(fsinfo.flags & LFS_I_INCONSISTENT));
|
||||
|
||||
// double check the actual disk state, it's easy for littlefs to
|
||||
// lie here
|
||||
|
||||
+459
-50
@@ -17,7 +17,6 @@ defines.SIZE = [
|
||||
'2*BLOCK_SIZE',
|
||||
'8*BLOCK_SIZE',
|
||||
]
|
||||
in = 'lfs.c'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
@@ -37,8 +36,10 @@ code = '''
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
assert(!lfsr_omdir_isopen(&lfs, &lfs.gc.o.o));
|
||||
assert(lfs.lookahead.next > 0 || lfs.lookahead.size == 0);
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC until our traversal is done
|
||||
while (true) {
|
||||
@@ -48,13 +49,14 @@ code = '''
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
|
||||
// internal traversal done?
|
||||
if (!lfsr_omdir_isopen(&lfs, &lfs.gc.o.o)) {
|
||||
if (lfs.omdirs != &lfs.gc.o.o) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// we should have made progress
|
||||
assert(!(lfs.lookahead.next > 0 || lfs.lookahead.size == 0));
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(!(fsinfo.flags & LFS_I_CANLOOKAHEAD));
|
||||
|
||||
// check the file contents
|
||||
lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0;
|
||||
@@ -66,7 +68,7 @@ code = '''
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# test that lookahead clobbering still works with the GC API
|
||||
# test that lookahead dirtying still works with the GC API
|
||||
[cases.test_gc_lookahead_mutation]
|
||||
defines.GC_STEPS = 1
|
||||
defines.CKMETA = [false, true]
|
||||
@@ -79,7 +81,6 @@ defines.SIZE = [
|
||||
'2*BLOCK_SIZE',
|
||||
'8*BLOCK_SIZE',
|
||||
]
|
||||
in = 'lfs.c'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
@@ -99,15 +100,17 @@ code = '''
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
assert(!lfsr_omdir_isopen(&lfs, &lfs.gc.o.o));
|
||||
assert(lfs.lookahead.next > 0 || lfs.lookahead.size == 0);
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC one step
|
||||
lfsr_fs_gc(&lfs,
|
||||
LFS_GC_LOOKAHEAD
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfsr_omdir_isopen(&lfs, &lfs.gc.o.o));
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// mutate the filesystem
|
||||
lfsr_file_open(&lfs, &file, "spider",
|
||||
@@ -119,7 +122,7 @@ code = '''
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
// run GC until our traversal is done
|
||||
while (lfsr_omdir_isopen(&lfs, &lfs.gc.o.o)) {
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs,
|
||||
LFS_GC_LOOKAHEAD
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
@@ -127,7 +130,8 @@ code = '''
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
assert(lfs.lookahead.next > 0 || lfs.lookahead.size == 0);
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
||||
|
||||
// check the file contents
|
||||
lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0;
|
||||
@@ -154,7 +158,6 @@ defines.SIZE = [
|
||||
]
|
||||
# we need something to keep the traversal running
|
||||
if = 'CKMETA || CKDATA'
|
||||
in = 'lfs.c'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
@@ -174,17 +177,19 @@ code = '''
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
assert(!lfsr_omdir_isopen(&lfs, &lfs.gc.o.o));
|
||||
assert(lfs.lookahead.next > 0 || lfs.lookahead.size == 0);
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC one step
|
||||
lfsr_fs_gc(&lfs,
|
||||
((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfsr_omdir_isopen(&lfs, &lfs.gc.o.o));
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// change flags and run GC until our traversal is done
|
||||
while (lfsr_omdir_isopen(&lfs, &lfs.gc.o.o)) {
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs,
|
||||
LFS_GC_LOOKAHEAD
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
@@ -192,7 +197,8 @@ code = '''
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
assert(lfs.lookahead.next > 0 || lfs.lookahead.size == 0);
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
||||
|
||||
// check the file contents
|
||||
lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0;
|
||||
@@ -219,7 +225,6 @@ defines.SIZE = [
|
||||
]
|
||||
# we need something to keep the traversal running
|
||||
if = 'CKMETA || CKDATA'
|
||||
in = 'lfs.c'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
@@ -239,25 +244,28 @@ code = '''
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
assert(!lfsr_omdir_isopen(&lfs, &lfs.gc.o.o));
|
||||
assert(lfs.lookahead.next > 0 || lfs.lookahead.size == 0);
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC one step
|
||||
lfsr_fs_gc(&lfs,
|
||||
LFS_GC_LOOKAHEAD
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfsr_omdir_isopen(&lfs, &lfs.gc.o.o));
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// change flags and run GC until our traversal is done
|
||||
while (lfsr_omdir_isopen(&lfs, &lfs.gc.o.o)) {
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs,
|
||||
((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
assert(lfs.lookahead.next > 0 || lfs.lookahead.size == 0);
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
||||
|
||||
// check the file contents
|
||||
lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0;
|
||||
@@ -270,6 +278,398 @@ code = '''
|
||||
'''
|
||||
|
||||
|
||||
# test that compact can make progress in isolation
|
||||
[cases.test_gc_compact_progress]
|
||||
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
||||
defines.LOOKAHEAD = [false, true]
|
||||
defines.CKMETA = [false, true]
|
||||
defines.CKDATA = [false, true]
|
||||
defines.SIZE = [
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'8*BLOCK_SIZE',
|
||||
]
|
||||
# set compact thresh to minimum
|
||||
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
|
||||
uint32_t prng = 42;
|
||||
|
||||
// write to our mdir until >gc_compact_thresh full
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "jellyfish",
|
||||
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
|
||||
// hack, don't use the internals like this
|
||||
uint8_t wbuf[SIZE];
|
||||
while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) {
|
||||
lfsr_file_rewind(&lfs, &file) => 0;
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_sync(&lfs, &file) => 0;
|
||||
}
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC until our traversal is done (twice for compact)
|
||||
for (int i = 0; i < 2; i++) {
|
||||
while (true) {
|
||||
lfsr_fs_gc(&lfs,
|
||||
LFS_GC_COMPACT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
|
||||
// internal traversal done?
|
||||
if (lfs.omdirs != &lfs.gc.o.o) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mdir should have been compacted
|
||||
assert((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH);
|
||||
|
||||
// we should have made progress
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(!(fsinfo.flags & LFS_I_UNCOMPACTED));
|
||||
|
||||
// check we can still read the file
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
|
||||
}
|
||||
|
||||
lfsr_file_rewind(&lfs, &file) => 0;
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
}
|
||||
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# test that compact dirtying still works with the GC API
|
||||
[cases.test_gc_compact_mutation]
|
||||
defines.GC_STEPS = 1
|
||||
defines.LOOKAHEAD = [false, true]
|
||||
defines.CKMETA = [false, true]
|
||||
defines.CKDATA = [false, true]
|
||||
defines.SIZE = [
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'8*BLOCK_SIZE',
|
||||
]
|
||||
# set compact thresh to minimum
|
||||
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
|
||||
uint32_t prng = 42;
|
||||
|
||||
// write to our mdir until >gc_compact_thresh full
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "jellyfish",
|
||||
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
|
||||
// hack, don't use the internals like this
|
||||
uint8_t wbuf[SIZE];
|
||||
while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) {
|
||||
lfsr_file_rewind(&lfs, &file) => 0;
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_sync(&lfs, &file) => 0;
|
||||
}
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC one traversal + one step
|
||||
while (true) {
|
||||
lfsr_fs_gc(&lfs,
|
||||
LFS_GC_COMPACT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
|
||||
// internal traversal done?
|
||||
if (lfs.omdirs != &lfs.gc.o.o) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
lfsr_fs_gc(&lfs,
|
||||
LFS_GC_COMPACT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// mutate the filesystem
|
||||
lfsr_file_rewind(&lfs, &file) => 0;
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_sync(&lfs, &file) => 0;
|
||||
|
||||
// run GC until our traversal is done (twice for compact)
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs,
|
||||
LFS_GC_COMPACT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
||||
|
||||
// check we can still read the file
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
|
||||
}
|
||||
|
||||
lfsr_file_rewind(&lfs, &file) => 0;
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
}
|
||||
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# test that adding flags doesn't break compact
|
||||
[cases.test_gc_compact_add_flags]
|
||||
defines.GC_STEPS = 1
|
||||
defines.LOOKAHEAD = [false, true]
|
||||
defines.CKMETA = [false, true]
|
||||
defines.CKDATA = [false, true]
|
||||
defines.SIZE = [
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'8*BLOCK_SIZE',
|
||||
]
|
||||
# set compact thresh to minimum
|
||||
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
||||
# we need something to keep the traversal running
|
||||
if = 'CKMETA || CKDATA'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
|
||||
uint32_t prng = 42;
|
||||
|
||||
// write to our mdir until >gc_compact_thresh full
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "jellyfish",
|
||||
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
|
||||
// hack, don't use the internals like this
|
||||
uint8_t wbuf[SIZE];
|
||||
while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) {
|
||||
lfsr_file_rewind(&lfs, &file) => 0;
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_sync(&lfs, &file) => 0;
|
||||
}
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC one traversal + one step
|
||||
while (true) {
|
||||
lfsr_fs_gc(&lfs,
|
||||
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
|
||||
// internal traversal done?
|
||||
if (lfs.omdirs != &lfs.gc.o.o) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
lfsr_fs_gc(&lfs,
|
||||
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// change flags and run GC until our traversal is done (twice for compact)
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs,
|
||||
LFS_GC_COMPACT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
||||
|
||||
// check we can still read the file
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
|
||||
}
|
||||
|
||||
lfsr_file_rewind(&lfs, &file) => 0;
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
}
|
||||
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# test that removing flags invalidates compact
|
||||
[cases.test_gc_compact_remove_flags]
|
||||
defines.GC_STEPS = 1
|
||||
defines.LOOKAHEAD = [false, true]
|
||||
defines.CKMETA = [false, true]
|
||||
defines.CKDATA = [false, true]
|
||||
defines.SIZE = [
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'8*BLOCK_SIZE',
|
||||
]
|
||||
# set compact thresh to minimum
|
||||
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
||||
# we need something to keep the traversal running
|
||||
if = 'CKMETA || CKDATA'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
|
||||
uint32_t prng = 42;
|
||||
|
||||
// write to our mdir until >gc_compact_thresh full
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "jellyfish",
|
||||
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
|
||||
// hack, don't use the internals like this
|
||||
uint8_t wbuf[SIZE];
|
||||
while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) {
|
||||
lfsr_file_rewind(&lfs, &file) => 0;
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_sync(&lfs, &file) => 0;
|
||||
}
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC one traversal + one step
|
||||
while (true) {
|
||||
lfsr_fs_gc(&lfs,
|
||||
LFS_GC_COMPACT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
|
||||
// internal traversal done?
|
||||
if (lfs.omdirs != &lfs.gc.o.o) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
lfsr_fs_gc(&lfs,
|
||||
LFS_GC_COMPACT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// change flags and run GC until our traversal is done (twice for compact)
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs,
|
||||
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
||||
|
||||
// check we can still read the file
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
|
||||
}
|
||||
|
||||
lfsr_file_rewind(&lfs, &file) => 0;
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
}
|
||||
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
|
||||
# test that mkconsistent can make progress in isolation
|
||||
[cases.test_gc_mkconsistent_progress]
|
||||
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
||||
@@ -281,7 +681,6 @@ defines.SIZE = 'FILE_BUFFER_SIZE/2'
|
||||
# <=2 => grm-able
|
||||
# >2 => requires orphans
|
||||
defines.ORPHANS = [1, 2, 3, 100]
|
||||
in = 'lfs.c'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
@@ -329,8 +728,10 @@ code = '''
|
||||
}
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
assert(!lfsr_omdir_isopen(&lfs, &lfs.gc.o.o));
|
||||
assert(lfsr_grm_count(&lfs) > 0 || lfs.hasorphans);
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC until our traversal is done
|
||||
while (true) {
|
||||
@@ -342,13 +743,14 @@ code = '''
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
|
||||
// internal traversal done?
|
||||
if (!lfsr_omdir_isopen(&lfs, &lfs.gc.o.o)) {
|
||||
if (lfs.omdirs != &lfs.gc.o.o) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// we should have made progress
|
||||
assert(!(lfsr_grm_count(&lfs) > 0 || lfs.hasorphans));
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(!(fsinfo.flags & LFS_I_INCONSISTENT));
|
||||
|
||||
// check we can still read the files
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
@@ -377,7 +779,7 @@ code = '''
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# test that mkconsistent clobbering still works with the GC API
|
||||
# test that mkconsistent dirtying still works with the GC API
|
||||
[cases.test_gc_mkconsistent_mutation]
|
||||
defines.GC_STEPS = 1
|
||||
defines.LOOKAHEAD = [false, true]
|
||||
@@ -388,7 +790,6 @@ defines.SIZE = 'FILE_BUFFER_SIZE/2'
|
||||
# <=2 => grm-able
|
||||
# >2 => requires orphans
|
||||
defines.ORPHANS = [3, 100]
|
||||
in = 'lfs.c'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
@@ -430,14 +831,14 @@ code = '''
|
||||
}
|
||||
|
||||
// run GC one step
|
||||
assert(!lfsr_omdir_isopen(&lfs, &lfs.gc.o.o));
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
lfsr_fs_gc(&lfs,
|
||||
LFS_GC_MKCONSISTENT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfsr_omdir_isopen(&lfs, &lfs.gc.o.o));
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// create the rest of the orphans after GC has started
|
||||
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
||||
@@ -451,10 +852,12 @@ code = '''
|
||||
}
|
||||
|
||||
// we should now have dirty state
|
||||
assert(lfsr_grm_count(&lfs) > 0 || lfs.hasorphans);
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
||||
|
||||
// run GC until our traversal is done
|
||||
while (lfsr_omdir_isopen(&lfs, &lfs.gc.o.o)) {
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs,
|
||||
LFS_GC_MKCONSISTENT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
@@ -464,7 +867,8 @@ code = '''
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
assert(lfsr_grm_count(&lfs) > 0 || lfs.hasorphans);
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
||||
|
||||
// check we can still read the files
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
@@ -505,7 +909,7 @@ defines.SIZE = 'FILE_BUFFER_SIZE/2'
|
||||
# >2 => requires orphans
|
||||
defines.ORPHANS = [3, 100]
|
||||
# we need something to keep the traversal running
|
||||
if = 'COMPACT || CKMETA || CKDATA'
|
||||
if = 'CKMETA || CKDATA'
|
||||
in = 'lfs.c'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
@@ -554,8 +958,10 @@ code = '''
|
||||
}
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
assert(!lfsr_omdir_isopen(&lfs, &lfs.gc.o.o));
|
||||
assert(lfsr_grm_count(&lfs) > 0 || lfs.hasorphans);
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC one step
|
||||
lfsr_fs_gc(&lfs,
|
||||
@@ -563,10 +969,10 @@ code = '''
|
||||
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfsr_omdir_isopen(&lfs, &lfs.gc.o.o));
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// change flags and run GC until our traversal is done
|
||||
while (lfsr_omdir_isopen(&lfs, &lfs.gc.o.o)) {
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs,
|
||||
LFS_GC_MKCONSISTENT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
@@ -576,7 +982,8 @@ code = '''
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
assert(lfsr_grm_count(&lfs) > 0 || lfs.hasorphans);
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
||||
|
||||
// check we can still read the files
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
@@ -617,8 +1024,7 @@ defines.SIZE = 'FILE_BUFFER_SIZE/2'
|
||||
# >2 => requires orphans
|
||||
defines.ORPHANS = [3, 100]
|
||||
# we need something to keep the traversal running
|
||||
if = 'COMPACT || CKMETA || CKDATA'
|
||||
in = 'lfs.c'
|
||||
if = 'CKMETA || CKDATA'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
@@ -666,8 +1072,10 @@ code = '''
|
||||
}
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
assert(!lfsr_omdir_isopen(&lfs, &lfs.gc.o.o));
|
||||
assert(lfsr_grm_count(&lfs) > 0 || lfs.hasorphans);
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC one step
|
||||
lfsr_fs_gc(&lfs,
|
||||
@@ -676,10 +1084,10 @@ code = '''
|
||||
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfsr_omdir_isopen(&lfs, &lfs.gc.o.o));
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// change flags and run GC until our traversal is done
|
||||
while (lfsr_omdir_isopen(&lfs, &lfs.gc.o.o)) {
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs,
|
||||
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
||||
@@ -688,7 +1096,8 @@ code = '''
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
assert(lfsr_grm_count(&lfs) > 0 || lfs.hasorphans);
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
||||
|
||||
// check we can still read the files
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
@@ -718,7 +1127,7 @@ code = '''
|
||||
'''
|
||||
|
||||
|
||||
# pseudo-fuzz test that clobbering still works with the GC API
|
||||
# pseudo-fuzz test that dirtying still works with the GC API
|
||||
[cases.test_gc_mutation]
|
||||
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
||||
defines.STEPS = 100
|
||||
|
||||
+1038
-22
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user