Restart gc on flag change, better dedup mkconsistent/ckmeta/etc

This simplifies lfsr_fs_gc a bit, and allows lfsr_fs_mkconsistent/
ckmeta/etc to call lfsr_fs_gc directly (it would be a bit strange for
these function to finish up unrelated gc traversals).

Unfortunately, this does risk gc getting stuck constantly restarting if
there is contention between two lfsr_fs_gc calls with different flags,
but you could argue this would be a system design mistake...

The deduplication of traversal state leads to some pretty nice code
savings:

           code          stack
  before: 36316           2680
  after:  36068 (-0.7%)   2680 (+0.0%)
This commit is contained in:
Christopher Haster
2024-07-17 02:41:37 -05:00
parent eced943685
commit 1cd6a6873a
2 changed files with 35 additions and 664 deletions
+35 -104
View File
@@ -12928,97 +12928,32 @@ failed:;
return err;
}
static int lfsr_fs_fixorphans(lfs_t *lfs) {
// LFS_T_MKCONSISTENT really just removes orphans
lfsr_traversal_t t = LFSR_TRAVERSAL(
LFS_T_MTREEONLY | LFS_T_MKCONSISTENT);
while (true) {
int err = lfsr_mtree_gc(lfs, &t,
NULL, NULL);
if (err) {
if (err == LFS_ERR_NOENT) {
break;
}
return err;
}
}
return 0;
}
// prepare the filesystem for mutation
int lfsr_fs_mkconsistent(lfs_t *lfs) {
// fix pending grms
if (lfsr_grm_count(lfs) > 0) {
if (lfsr_grm_count(lfs) == 2) {
LFS_DEBUG("Fixing grm %"PRId32".%"PRId32" %"PRId32".%"PRId32,
lfsr_mid_bid(lfs, lfs->grm.mids[0]) >> lfs->mdir_bits,
lfsr_mid_rid(lfs, lfs->grm.mids[0]),
lfsr_mid_bid(lfs, lfs->grm.mids[1]) >> lfs->mdir_bits,
lfsr_mid_rid(lfs, lfs->grm.mids[1]));
} else if (lfsr_grm_count(lfs) == 1) {
LFS_DEBUG("Fixing grm %"PRId32".%"PRId32,
lfsr_mid_bid(lfs, lfs->grm.mids[0]) >> lfs->mdir_bits,
lfsr_mid_rid(lfs, lfs->grm.mids[0]));
}
int err = lfsr_fs_fixgrm(lfs);
if (err) {
return err;
}
}
// fix orphaned files
//
// this must happen after fixgrm, since removing orphaned files risks
// outdating the grm
//
if (lfsr_f_hasorphans(lfs->flags)) {
LFS_DEBUG("Fixing orphans...");
int err = lfsr_fs_fixorphans(lfs);
if (err) {
return err;
}
}
return 0;
// leave this up to lfsr_fs_gc
return lfsr_fs_gc(lfs, -1, LFS_GC_MTREEONLY | LFS_GC_MKCONSISTENT);
}
// check the filesystem for metadata errors
int lfsr_fs_ckmeta(lfs_t *lfs) {
// we leave this up to lfsr_mtree_gc
lfsr_traversal_t t = LFSR_TRAVERSAL(LFS_T_CKMETA);
while (true) {
int err = lfsr_mtree_gc(lfs, &t,
NULL, NULL);
if (err) {
if (err == LFS_ERR_NOENT) {
break;
}
return err;
}
// we want a full traversal, so make sure no gc is currently running
if (lfsr_omdir_isopen(lfs, &lfs->gc.o.o)) {
lfsr_omdir_close(lfs, &lfs->gc.o.o);
}
return 0;
// leave this up to lfsr_fs_gc
return lfsr_fs_gc(lfs, -1, LFS_GC_CKMETA);
}
// check the filesystem for metadata + data errors
int lfsr_fs_ckdata(lfs_t *lfs) {
// we leave this up to lfsr_mtree_gc
lfsr_traversal_t t = LFSR_TRAVERSAL(LFS_T_CKMETA | LFS_T_CKDATA);
while (true) {
int err = lfsr_mtree_gc(lfs, &t,
NULL, NULL);
if (err) {
if (err == LFS_ERR_NOENT) {
break;
}
return err;
}
// we want a full traversal, so make sure no gc is currently running
if (lfsr_omdir_isopen(lfs, &lfs->gc.o.o)) {
lfsr_omdir_close(lfs, &lfs->gc.o.o);
}
return 0;
// leave this up to lfsr_fs_gc
return lfsr_fs_gc(lfs, -1, LFS_GC_CKMETA | LFS_GC_CKDATA);
}
// perform any pending janitorial work
@@ -13035,6 +12970,14 @@ int lfsr_fs_gc(lfs_t *lfs, lfs_soff_t steps, uint32_t flags) {
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_islookahead(flags));
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_isckdata(flags));
// do we really need a full traversal?
if (!(lfsr_t_islookahead(flags)
|| lfsr_t_iscompact(flags)
|| lfsr_t_isckmeta(flags)
|| lfsr_t_isckdata(flags))) {
flags |= LFS_T_MTREEONLY;
}
// fix pending grms if requested
if (lfsr_t_ismkconsistent(flags)
&& lfsr_grm_count(lfs) > 0) {
@@ -13072,33 +13015,21 @@ int lfsr_fs_gc(lfs_t *lfs, lfs_soff_t steps, uint32_t flags) {
// checkpoint the allocator to maximize any lookahead scans
lfs_alloc_ckpoint(lfs);
// existing traversal?
if (lfsr_omdir_isopen(lfs, &lfs->gc.o.o)) {
// note that we mask flags (except mtreeonly)! if you change flags
// mid-traversal, the result is equivalent to the worst-case set
// of flags
lfs->gc.o.o.flags &= (
~LFS_GC_MKCONSISTENT
& ~LFS_GC_LOOKAHEAD
& ~LFS_GC_COMPACT
& ~LFS_GC_CKMETA
& ~LFS_GC_CKDATA
) | flags;
// start a new traversal
} else {
lfs->gc = LFSR_TRAVERSAL(
flags
// do we really need a full traversal?
| ((!((lfsr_t_islookahead(flags)
&& lfsr_fs_canlookahead(lfs))
|| (lfsr_t_iscompact(flags)
&& lfsr_i_isuncompacted(lfs->flags))
|| (lfsr_t_isckmeta(flags)
&& !cked)
|| (lfsr_t_isckdata(flags)
&& !cked)))
? LFS_T_MTREEONLY
: 0));
// flags mismatch? restart traversal
if (lfsr_omdir_isopen(lfs, &lfs->gc.o.o)
&& (flags != (lfs->gc.o.o.flags & (
LFS_T_MTREEONLY
| LFS_T_MKCONSISTENT
| LFS_T_LOOKAHEAD
| LFS_T_COMPACT
| LFS_T_CKMETA
| LFS_T_CKDATA)))) {
lfsr_omdir_close(lfs, &lfs->gc.o.o);
}
// start a new traversal?
if (!lfsr_omdir_isopen(lfs, &lfs->gc.o.o)) {
lfs->gc = LFSR_TRAVERSAL(flags);
lfsr_omdir_open(lfs, &lfs->gc.o.o);
}
-560
View File
@@ -142,140 +142,6 @@ code = '''
lfsr_unmount(&lfs) => 0;
'''
# test that adding flags doesn't break lookahead
[cases.test_gc_lookahead_add_flags]
defines.GC_STEPS = 1
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.SIZE = [
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
# we need something to keep the traversal running
if = 'CKMETA || CKDATA'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "spider",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// expect dirty initial state or else our test doesn't work
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
assert(lfs.omdirs != &lfs.gc.o.o);
// run GC one step
lfsr_fs_gc(&lfs, GC_STEPS,
((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
assert(lfs.omdirs == &lfs.gc.o.o);
// change flags and run GC until our traversal is done
while (lfs.omdirs == &lfs.gc.o.o) {
lfsr_fs_gc(&lfs, GC_STEPS,
LFS_GC_LOOKAHEAD
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
}
// we should _not_ make progress
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
// check the file contents
lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that removing flags invalidates lookahead
[cases.test_gc_lookahead_remove_flags]
defines.GC_STEPS = 1
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.SIZE = [
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
# we need something to keep the traversal running
if = 'CKMETA || CKDATA'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "spider",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_close(&lfs, &file) => 0;
// expect dirty initial state or else our test doesn't work
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
assert(lfs.omdirs != &lfs.gc.o.o);
// run GC one step
lfsr_fs_gc(&lfs, GC_STEPS,
LFS_GC_LOOKAHEAD
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
assert(lfs.omdirs == &lfs.gc.o.o);
// change flags and run GC until our traversal is done
while (lfs.omdirs == &lfs.gc.o.o) {
lfsr_fs_gc(&lfs, GC_STEPS,
((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
}
// we should _not_ make progress
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
// check the file contents
lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that compact can make progress in isolation
[cases.test_gc_compact_progress]
@@ -468,203 +334,6 @@ code = '''
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, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// write to our mdir until >gc_compact_thresh full
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jellyfish",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
// hack, don't use the internals like this
uint8_t wbuf[SIZE];
while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) {
lfsr_file_rewind(&lfs, &file) => 0;
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_sync(&lfs, &file) => 0;
}
// expect dirty initial state or else our test doesn't work
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
assert(lfs.omdirs != &lfs.gc.o.o);
// run GC one traversal + one step
while (true) {
lfsr_fs_gc(&lfs, GC_STEPS,
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
// internal traversal done?
if (lfs.omdirs != &lfs.gc.o.o) {
break;
}
}
lfsr_fs_gc(&lfs, GC_STEPS,
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
assert(lfs.omdirs == &lfs.gc.o.o);
// change flags and run GC until our traversal is done (twice for compact)
while (lfs.omdirs == &lfs.gc.o.o) {
lfsr_fs_gc(&lfs, GC_STEPS,
LFS_GC_COMPACT
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
}
// we should _not_ make progress
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
// check we can still read the file
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
}
lfsr_file_rewind(&lfs, &file) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that removing flags invalidates compact
[cases.test_gc_compact_remove_flags]
defines.GC_STEPS = 1
defines.LOOKAHEAD = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.SIZE = [
'FILE_BUFFER_SIZE/2',
'2*FILE_BUFFER_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
# set compact thresh to minimum
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
# we need something to keep the traversal running
if = 'CKMETA || CKDATA'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// write to our mdir until >gc_compact_thresh full
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "jellyfish",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
// hack, don't use the internals like this
uint8_t wbuf[SIZE];
while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) {
lfsr_file_rewind(&lfs, &file) => 0;
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
lfsr_file_sync(&lfs, &file) => 0;
}
// expect dirty initial state or else our test doesn't work
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
assert(lfs.omdirs != &lfs.gc.o.o);
// run GC one traversal + one step
while (true) {
lfsr_fs_gc(&lfs, GC_STEPS,
LFS_GC_COMPACT
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
// internal traversal done?
if (lfs.omdirs != &lfs.gc.o.o) {
break;
}
}
lfsr_fs_gc(&lfs, GC_STEPS,
LFS_GC_COMPACT
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
assert(lfs.omdirs == &lfs.gc.o.o);
// change flags and run GC until our traversal is done (twice for compact)
while (lfs.omdirs == &lfs.gc.o.o) {
lfsr_fs_gc(&lfs, GC_STEPS,
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
}
// we should _not_ make progress
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
// check we can still read the file
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
}
lfsr_file_rewind(&lfs, &file) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that mkconsistent can make progress in isolation
[cases.test_gc_mkconsistent_progress]
@@ -985,235 +654,6 @@ code = '''
lfsr_unmount(&lfs) => 0;
'''
# test that adding flags doesn't break mkconsistent
[cases.test_gc_mkconsistent_add_flags]
defines.GC_STEPS = 1
defines.LOOKAHEAD = [false, true]
defines.COMPACT = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.SIZE = 'FILE_BUFFER_SIZE/2'
# <=2 => grm-able
# >2 => requires orphans
defines.ORPHANS = [3, 100]
# we need something to keep the traversal running
if = 'CKMETA || CKDATA'
in = 'lfs.c'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// create two files
lfsr_file_t file1;
lfsr_file_open(&lfs, &file1, "cuttlefish",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf1[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE;
lfsr_file_sync(&lfs, &file1) => 0;
lfsr_file_t file2;
lfsr_file_open(&lfs, &file2, "octopus",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf2[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE;
lfsr_file_sync(&lfs, &file2) => 0;
// create this many orphaned files
//
// anytime we close a not-yet-created desync file, we create an
// orphan, but note we need these to be different files, and we need
// to close them after all open calls, otherwise we just end up with
// one orphan (littlefs is eager to clean up orphans)
//
lfsr_file_t orphans[ORPHANS];
for (lfs_size_t i = 0; i < ORPHANS; i++) {
char name[256];
sprintf(name, "jellyfish%03x", i);
lfsr_file_open(&lfs, &orphans[i], name,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0;
}
for (lfs_size_t i = 0; i < ORPHANS; i++) {
lfsr_file_close(&lfs, &orphans[i]) => 0;
}
// expect dirty initial state or else our test doesn't work
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_INCONSISTENT);
assert(lfs.omdirs != &lfs.gc.o.o);
// run GC one step
lfsr_fs_gc(&lfs, GC_STEPS,
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((COMPACT) ? LFS_GC_COMPACT : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
assert(lfs.omdirs == &lfs.gc.o.o);
// change flags and run GC until our traversal is done
while (lfs.omdirs == &lfs.gc.o.o) {
lfsr_fs_gc(&lfs, GC_STEPS,
LFS_GC_MKCONSISTENT
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((COMPACT) ? LFS_GC_COMPACT : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
}
// we should _not_ make progress
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_INCONSISTENT);
// check we can still read the files
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_file_close(&lfs, &file1) => 0;
lfsr_file_close(&lfs, &file2) => 0;
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0;
lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0;
}
lfsr_file_rewind(&lfs, &file1) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf1, SIZE) == 0);
lfsr_file_rewind(&lfs, &file2) => 0;
lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf2, SIZE) == 0);
}
lfsr_file_close(&lfs, &file1) => 0;
lfsr_file_close(&lfs, &file2) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that removing flags invalidates mkconsistent
[cases.test_gc_mkconsistent_remove_flags]
defines.GC_STEPS = 1
defines.LOOKAHEAD = [false, true]
defines.COMPACT = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.SIZE = 'FILE_BUFFER_SIZE/2'
# <=2 => grm-able
# >2 => requires orphans
defines.ORPHANS = [3, 100]
# we need something to keep the traversal running
if = 'CKMETA || CKDATA'
code = '''
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint32_t prng = 42;
// create two files
lfsr_file_t file1;
lfsr_file_open(&lfs, &file1, "cuttlefish",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf1[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE;
lfsr_file_sync(&lfs, &file1) => 0;
lfsr_file_t file2;
lfsr_file_open(&lfs, &file2, "octopus",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
uint8_t wbuf2[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE;
lfsr_file_sync(&lfs, &file2) => 0;
// create this many orphaned files
//
// anytime we close a not-yet-created desync file, we create an
// orphan, but note we need these to be different files, and we need
// to close them after all open calls, otherwise we just end up with
// one orphan (littlefs is eager to clean up orphans)
//
lfsr_file_t orphans[ORPHANS];
for (lfs_size_t i = 0; i < ORPHANS; i++) {
char name[256];
sprintf(name, "jellyfish%03x", i);
lfsr_file_open(&lfs, &orphans[i], name,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0;
}
for (lfs_size_t i = 0; i < ORPHANS; i++) {
lfsr_file_close(&lfs, &orphans[i]) => 0;
}
// expect dirty initial state or else our test doesn't work
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_INCONSISTENT);
assert(lfs.omdirs != &lfs.gc.o.o);
// run GC one step
lfsr_fs_gc(&lfs, GC_STEPS,
LFS_GC_MKCONSISTENT
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((COMPACT) ? LFS_GC_COMPACT : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
assert(lfs.omdirs == &lfs.gc.o.o);
// change flags and run GC until our traversal is done
while (lfs.omdirs == &lfs.gc.o.o) {
lfsr_fs_gc(&lfs, GC_STEPS,
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
| ((COMPACT) ? LFS_GC_COMPACT : 0)
| ((CKMETA) ? LFS_GC_CKMETA : 0)
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
}
// we should _not_ make progress
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags & LFS_I_INCONSISTENT);
// check we can still read the files
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_file_close(&lfs, &file1) => 0;
lfsr_file_close(&lfs, &file2) => 0;
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0;
lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0;
}
lfsr_file_rewind(&lfs, &file1) => 0;
uint8_t rbuf[SIZE];
lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf1, SIZE) == 0);
lfsr_file_rewind(&lfs, &file2) => 0;
lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf2, SIZE) == 0);
}
lfsr_file_close(&lfs, &file1) => 0;
lfsr_file_close(&lfs, &file2) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test we can detect at least fully clobbered blocks
[cases.test_gc_ckmeta]