Added on-mount traversal flags: LFS_M_MKCONSISTENT/CKMETA/CKDATA/etc
These tell littlefs to do the relevant gc work during mount, which may
be more convenient than calling lfsr_mount and then lfsr_fs_gc.
It also implicitly tears down the filesystem on error, which you can
imagine would be quite useful for LFS_M_CKMETA/LFS_M_CKDATA.
Some flags are more useful here than other (is LFS_M_LOOKAHEAD/COMPACT
really useful?), but since we just pass these directly to our traversal
APIs, we might as well support all of them for consistency.
Also note that since these only change mount's behavior, and have no
effect on the rest of the filesystem, these LFS_M_* flags don't have
related LFS_I_* flags and are not returned by lfsr_fs_stat.
---
This added quite a chunk of code, considering that this is entirely for
convenience:
code stack
before: 35932 2680
after: 36280 (+1.0%) 2680 (+0.0%)
But I think this is mostly because our low-level traversal state is
relatively costly to manage. It may be possible to deduplicate this a
bit better...
This commit is contained in:
@@ -5924,6 +5924,19 @@ static inline bool lfsr_f_hasorphans(uint32_t flags) {
|
||||
return flags & LFS_F_ORPHANS;
|
||||
}
|
||||
|
||||
// on-demand flags
|
||||
|
||||
// needed in lfsr_fs_isinconsistent
|
||||
static inline uint8_t lfsr_grm_count(const lfs_t *lfs);
|
||||
|
||||
static bool lfsr_fs_isinconsistent(const lfs_t *lfs) {
|
||||
return lfsr_grm_count(lfs) > 0 || lfsr_f_hasorphans(lfs->flags);
|
||||
}
|
||||
|
||||
static bool lfsr_fs_canlookahead(const lfs_t *lfs) {
|
||||
return lfs->lookahead.next > 0 || lfs->lookahead.size == 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// opened mdir things ///
|
||||
@@ -6044,7 +6057,7 @@ static inline uint8_t lfsr_grm_count_(const lfsr_grm_t *grm) {
|
||||
return (grm->mids[0] >= 0) + (grm->mids[1] >= 0);
|
||||
}
|
||||
|
||||
static inline uint8_t lfsr_grm_count(lfs_t *lfs) {
|
||||
static inline uint8_t lfsr_grm_count(const lfs_t *lfs) {
|
||||
return lfsr_grm_count_(&lfs->grm);
|
||||
}
|
||||
|
||||
@@ -6061,7 +6074,7 @@ static inline lfsr_smid_t lfsr_grm_pop(lfs_t *lfs) {
|
||||
return mid;
|
||||
}
|
||||
|
||||
static inline bool lfsr_grm_ismidrm(lfs_t *lfs, lfsr_smid_t mid) {
|
||||
static inline bool lfsr_grm_ismidrm(const lfs_t *lfs, lfsr_smid_t mid) {
|
||||
return lfs->grm.mids[0] == mid || lfs->grm.mids[1] == mid;
|
||||
}
|
||||
|
||||
@@ -12572,16 +12585,38 @@ static int lfsr_mountinited(lfs_t *lfs) {
|
||||
|
||||
int lfsr_mount(lfs_t *lfs, uint32_t flags,
|
||||
const struct lfs_config *cfg) {
|
||||
int err = lfs_init(lfs, flags, cfg);
|
||||
// some flags don't make sense when only traversing the mtree
|
||||
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_islookahead(flags));
|
||||
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_isckdata(flags));
|
||||
// unknown flags?
|
||||
LFS_ASSERT((flags
|
||||
& ~LFS_M_RDWR
|
||||
& ~LFS_M_RDONLY
|
||||
& ~LFS_M_CKPROGS
|
||||
& ~LFS_M_MTREEONLY
|
||||
& ~LFS_M_MKCONSISTENT
|
||||
& ~LFS_M_LOOKAHEAD
|
||||
& ~LFS_M_COMPACT
|
||||
& ~LFS_M_CKMETA
|
||||
& ~LFS_M_CKDATA) == 0);
|
||||
|
||||
int err = lfs_init(lfs,
|
||||
// strip out the one-time traversal flags
|
||||
flags
|
||||
& ~LFS_M_MTREEONLY
|
||||
& ~LFS_M_MKCONSISTENT
|
||||
& ~LFS_M_LOOKAHEAD
|
||||
& ~LFS_M_COMPACT
|
||||
& ~LFS_M_CKMETA
|
||||
& ~LFS_M_CKDATA,
|
||||
cfg);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = lfsr_mountinited(lfs);
|
||||
if (err) {
|
||||
// make sure we clean up on error
|
||||
lfs_deinit(lfs);
|
||||
return err;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// TODO this should use any configured values
|
||||
@@ -12599,7 +12634,84 @@ int lfsr_mount(lfs_t *lfs, uint32_t flags,
|
||||
lfsr_mtree_weight_(&lfs->mtree) >> lfs->mdir_bits,
|
||||
1 << lfs->mdir_bits);
|
||||
|
||||
// fix pending grms if requested
|
||||
if (lfsr_t_ismkconsistent(flags)
|
||||
&& 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]));
|
||||
}
|
||||
|
||||
err = lfsr_fs_fixgrm(lfs);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
// run gc until all requested mount work is done
|
||||
bool mutated = true;
|
||||
while ((lfsr_t_ismkconsistent(flags)
|
||||
&& lfsr_f_hasorphans(lfs->flags))
|
||||
|| (lfsr_t_islookahead(flags)
|
||||
&& lfsr_fs_canlookahead(lfs))
|
||||
|| (lfsr_t_iscompact(flags)
|
||||
&& lfsr_i_isuncompacted(lfs->flags))
|
||||
|| (lfsr_t_isckmeta(flags)
|
||||
&& mutated)
|
||||
|| (lfsr_t_isckdata(flags)
|
||||
&& mutated)) {
|
||||
|
||||
// do we really need a full traversal?
|
||||
uint32_t flags_ = flags;
|
||||
if (!((lfsr_t_islookahead(flags)
|
||||
&& lfsr_fs_canlookahead(lfs))
|
||||
|| (lfsr_t_iscompact(flags)
|
||||
&& lfsr_i_isuncompacted(lfs->flags))
|
||||
|| (lfsr_t_isckmeta(flags)
|
||||
&& mutated)
|
||||
|| (lfsr_t_isckdata(flags)
|
||||
&& mutated))) {
|
||||
flags_ |= LFS_GC_MTREEONLY;
|
||||
}
|
||||
|
||||
lfsr_traversal_t t = LFSR_TRAVERSAL(flags_);
|
||||
lfsr_omdir_open(lfs, &t.o.o);
|
||||
|
||||
// shift the lookahead buffer if requested
|
||||
if (lfsr_t_islookahead(t.o.o.flags)) {
|
||||
lfs_alloc_shift(lfs);
|
||||
lfs_alloc_ckpoint(lfs);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
err = lfsr_mtree_gc(lfs, &t,
|
||||
NULL, NULL);
|
||||
if (err) {
|
||||
lfsr_omdir_close(lfs, &t.o.o);
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
// mutated? we need another pass for ckmeta/ckdata
|
||||
mutated = lfsr_f_ismutated(t.o.o.flags);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
failed:;
|
||||
// make sure we clean up on error
|
||||
lfs_deinit(lfs);
|
||||
return err;
|
||||
}
|
||||
|
||||
int lfsr_unmount(lfs_t *lfs) {
|
||||
@@ -12723,15 +12835,9 @@ int lfsr_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) {
|
||||
LFS_I_RDONLY
|
||||
| LFS_I_CKPROGS
|
||||
| LFS_I_UNCOMPACTED);
|
||||
|
||||
// some flags we calculate on demand
|
||||
if (lfsr_grm_count(lfs) > 0 || lfsr_f_hasorphans(lfs->flags)) {
|
||||
fsinfo->flags |= LFS_I_INCONSISTENT;
|
||||
}
|
||||
|
||||
if (lfs->lookahead.next > 0 || lfs->lookahead.size == 0) {
|
||||
fsinfo->flags |= LFS_I_CANLOOKAHEAD;
|
||||
}
|
||||
fsinfo->flags |= (lfsr_fs_isinconsistent(lfs)) ? LFS_I_INCONSISTENT : 0;
|
||||
fsinfo->flags |= (lfsr_fs_canlookahead(lfs)) ? LFS_I_CANLOOKAHEAD : 0;
|
||||
|
||||
// return filesystem config, this may come from disk
|
||||
fsinfo->block_size = lfs->cfg->block_size;
|
||||
@@ -12950,9 +13056,10 @@ int lfsr_fs_gc(lfs_t *lfs, uint32_t flags) {
|
||||
}
|
||||
|
||||
// do we need to do anything?
|
||||
if (!((lfsr_t_ismkconsistent(flags) && lfsr_f_hasorphans(lfs->flags))
|
||||
if (!((lfsr_t_ismkconsistent(flags)
|
||||
&& lfsr_f_hasorphans(lfs->flags))
|
||||
|| (lfsr_t_islookahead(flags)
|
||||
&& (lfs->lookahead.next > 0 || lfs->lookahead.size == 0))
|
||||
&& lfsr_fs_canlookahead(lfs))
|
||||
|| (lfsr_t_iscompact(flags)
|
||||
&& lfsr_i_isuncompacted(lfs->flags))
|
||||
|| lfsr_t_isckmeta(flags)
|
||||
@@ -12961,8 +13068,10 @@ int lfsr_fs_gc(lfs_t *lfs, uint32_t flags) {
|
||||
}
|
||||
|
||||
// do we really need a full traversal?
|
||||
if (!(lfsr_t_islookahead(flags)
|
||||
|| lfsr_t_iscompact(flags)
|
||||
if (!((lfsr_t_islookahead(flags)
|
||||
&& lfsr_fs_canlookahead(lfs))
|
||||
|| (lfsr_t_iscompact(flags)
|
||||
&& lfsr_i_isuncompacted(lfs->flags))
|
||||
|| lfsr_t_isckmeta(flags)
|
||||
|| lfsr_t_isckdata(flags))) {
|
||||
flags |= LFS_GC_MTREEONLY;
|
||||
|
||||
@@ -117,14 +117,21 @@ enum lfs_error {
|
||||
enum lfs_mount_flags {
|
||||
LFS_M_RDWR = 0x0000, // Mount the filesystem as read and write
|
||||
LFS_M_RDONLY = 0x0001, // Mount the filesystem as read only
|
||||
LFS_M_CKPROGS = 0x0010, // Check progs by reading back progged data
|
||||
LFS_M_CKPROGS = 0x0008, // Check progs by reading back progged data
|
||||
|
||||
LFS_M_MTREEONLY = 0x0010, // Only traverse the mtree
|
||||
LFS_M_MKCONSISTENT = 0x0020, // Make the filesystem consistent
|
||||
LFS_M_LOOKAHEAD = 0x0040, // Populate lookahead buffer
|
||||
LFS_M_COMPACT = 0x0080, // Compact metadata logs
|
||||
LFS_M_CKMETA = 0x0100, // Check metadata checksums
|
||||
LFS_M_CKDATA = 0x0200, // Check metadata + data checksums
|
||||
};
|
||||
|
||||
// Filesystem info flags
|
||||
enum lfs_fsinfo_flags {
|
||||
// mount flags
|
||||
LFS_I_RDONLY = 0x0001, // Filesystem mounted read only
|
||||
LFS_I_CKPROGS = 0x0010, // Check progs by reading back progged data
|
||||
LFS_I_CKPROGS = 0x0008, // Check progs by reading back progged data
|
||||
|
||||
// state flags
|
||||
LFS_I_INCONSISTENT = 0x0100, // Filesystem needs mkconsistent to write
|
||||
|
||||
@@ -1127,6 +1127,209 @@ code = '''
|
||||
'''
|
||||
|
||||
|
||||
# test we can detect at least fully clobbered blocks
|
||||
[cases.test_gc_ckmeta]
|
||||
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
||||
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
||||
defines.SIZE = [
|
||||
'0',
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'8*BLOCK_SIZE',
|
||||
]
|
||||
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
||||
code = '''
|
||||
lfs_block_t i = 0;
|
||||
while (true) {
|
||||
// a bit hacky, but this catches infinite loops
|
||||
assert(i < 2*BLOCK_COUNT);
|
||||
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, 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
|
||||
while (true) {
|
||||
int err = lfsr_fs_gc(&lfs, LFS_GC_CKMETA);
|
||||
assert(!err || err == LFS_ERR_CORRUPT);
|
||||
// found it
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
break;
|
||||
}
|
||||
|
||||
// we should find the clobbered block before finishing the
|
||||
// traversal
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
}
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
}
|
||||
done:;
|
||||
'''
|
||||
|
||||
[cases.test_gc_ckdata]
|
||||
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
||||
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
||||
defines.SIZE = [
|
||||
'0',
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'8*BLOCK_SIZE',
|
||||
]
|
||||
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
||||
code = '''
|
||||
lfs_block_t i = 0;
|
||||
while (true) {
|
||||
// a bit hacky, but this catches infinite loops
|
||||
assert(i < 2*BLOCK_COUNT);
|
||||
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, 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
|
||||
while (true) {
|
||||
int err = lfsr_fs_gc(&lfs, LFS_GC_CKDATA);
|
||||
assert(!err || err == LFS_ERR_CORRUPT);
|
||||
// found it
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
break;
|
||||
}
|
||||
|
||||
// we should find the clobbered block before finishing the
|
||||
// traversal
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
}
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
}
|
||||
done:;
|
||||
'''
|
||||
|
||||
|
||||
# pseudo-fuzz test that dirtying still works with the GC API
|
||||
[cases.test_gc_mutation]
|
||||
defines.GC_STEPS = [-1, 1, 2, 10, 100, 1000]
|
||||
|
||||
@@ -34,4 +34,390 @@ code = '''
|
||||
'''
|
||||
|
||||
|
||||
# test that on-mount traversals do what they say they do
|
||||
|
||||
[cases.test_mount_t_lookahead]
|
||||
defines.CKMETA = [false, true]
|
||||
defines.CKDATA = [false, true]
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
|
||||
// by default we need a lookahead scan
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags == (
|
||||
LFS_I_CANLOOKAHEAD
|
||||
| LFS_I_UNCOMPACTED));
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
// with LFS_M_LOOKAHEAD, mount performs a lookahead scan
|
||||
lfsr_mount(&lfs,
|
||||
LFS_M_RDWR
|
||||
| LFS_M_LOOKAHEAD
|
||||
| ((CKMETA) ? LFS_M_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_M_CKDATA : 0),
|
||||
CFG) => 0;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags == LFS_I_UNCOMPACTED);
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
[cases.test_mount_t_compact]
|
||||
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;
|
||||
|
||||
uint32_t prng = 42;
|
||||
|
||||
// first lets create a compactable filesystem
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
|
||||
// write to our mdir until >gc_compact_thresh full
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "jellyfish",
|
||||
LFS_O_WRONLY | 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;
|
||||
}
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
// by default mount does not compact
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags == (
|
||||
LFS_I_CANLOOKAHEAD
|
||||
| LFS_I_UNCOMPACTED));
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
// with LFS_M_COMPACT, mount compact any uncompacted blocks
|
||||
lfsr_mount(&lfs,
|
||||
LFS_M_RDWR
|
||||
| LFS_M_COMPACT
|
||||
| ((LOOKAHEAD) ? LFS_M_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_M_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_M_CKDATA : 0),
|
||||
CFG) => 0;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags == ((!LOOKAHEAD) ? LFS_I_CANLOOKAHEAD : 0));
|
||||
|
||||
// mdir should have been compacted
|
||||
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
|
||||
assert((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH);
|
||||
|
||||
// check we can still read the file
|
||||
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;
|
||||
'''
|
||||
|
||||
[cases.test_mount_t_mkconsistent]
|
||||
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 = [0, 1, 2, 3, 100]
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
|
||||
uint32_t prng = 42;
|
||||
|
||||
// first lets create some orphans
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
|
||||
// create two files
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "cuttlefish",
|
||||
LFS_O_WRONLY | 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, &file, wbuf1, SIZE) => SIZE;
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
lfsr_file_open(&lfs, &file, "octopus",
|
||||
LFS_O_WRONLY | 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, &file, wbuf2, SIZE) => SIZE;
|
||||
lfsr_file_close(&lfs, &file) => 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;
|
||||
}
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
// by default we clean up orphans lazily
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags == (
|
||||
((ORPHANS > 0) ? LFS_I_INCONSISTENT : 0)
|
||||
| LFS_I_CANLOOKAHEAD
|
||||
| LFS_I_UNCOMPACTED));
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
// with LFS_M_MKCONSISTENT, mount cleans up orphans eagerly
|
||||
lfsr_mount(&lfs,
|
||||
LFS_M_RDWR
|
||||
| LFS_M_MKCONSISTENT
|
||||
| ((LOOKAHEAD) ? LFS_M_LOOKAHEAD : 0)
|
||||
| ((COMPACT) ? LFS_M_COMPACT : 0)
|
||||
| ((CKMETA) ? LFS_M_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_M_CKDATA : 0),
|
||||
CFG) => 0;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags == (
|
||||
((!LOOKAHEAD) ? LFS_I_CANLOOKAHEAD : 0)
|
||||
| ((!COMPACT) ? LFS_I_UNCOMPACTED : 0)));
|
||||
|
||||
// check we can still read the files
|
||||
lfsr_file_open(&lfs, &file, "cuttlefish", LFS_O_RDONLY) => 0;
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf1, SIZE) == 0);
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
lfsr_file_open(&lfs, &file, "octopus", LFS_O_RDONLY) => 0;
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf2, SIZE) == 0);
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# test we can detect at least fully clobbered blocks
|
||||
[cases.test_mount_t_ckmeta]
|
||||
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
||||
defines.SIZE = [
|
||||
'0',
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'8*BLOCK_SIZE',
|
||||
]
|
||||
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
||||
code = '''
|
||||
lfs_block_t i = 0;
|
||||
while (true) {
|
||||
// a bit hacky, but this catches infinite loops
|
||||
assert(i < 2*BLOCK_COUNT);
|
||||
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, 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;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
goto clobbered;
|
||||
}
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
|
||||
clobbered:;
|
||||
// mount with LFS_M_CKMETA, we should detect clobbered blocks
|
||||
lfsr_mount(&lfs,
|
||||
LFS_M_RDWR
|
||||
| LFS_M_CKMETA,
|
||||
CFG) => LFS_ERR_CORRUPT;
|
||||
}
|
||||
done:;
|
||||
'''
|
||||
|
||||
[cases.test_mount_t_ckdata]
|
||||
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
||||
defines.SIZE = [
|
||||
'0',
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'8*BLOCK_SIZE',
|
||||
]
|
||||
if = '(SIZE*N)/BLOCK_SIZE <= 32'
|
||||
code = '''
|
||||
lfs_block_t i = 0;
|
||||
while (true) {
|
||||
// a bit hacky, but this catches infinite loops
|
||||
assert(i < 2*BLOCK_COUNT);
|
||||
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, 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;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
goto clobbered;
|
||||
}
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
|
||||
clobbered:;
|
||||
// mount with LFS_M_CKDATA, we should detect clobbered blocks
|
||||
//
|
||||
// note LFS_M_CKDATA implies LFS_M_CKMETA
|
||||
lfsr_mount(&lfs,
|
||||
LFS_M_RDWR
|
||||
| LFS_M_CKDATA,
|
||||
CFG) => LFS_ERR_CORRUPT;
|
||||
}
|
||||
done:;
|
||||
'''
|
||||
|
||||
|
||||
|
||||
# TODO should we move test_incompat here?
|
||||
|
||||
@@ -1442,9 +1442,8 @@ code = '''
|
||||
clobber_buf, BLOCK_SIZE) => 0;
|
||||
lfsr_traversal_close(&lfs, &t) => 0;
|
||||
goto clobbered;
|
||||
} else {
|
||||
k += 1;
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1537,9 +1536,8 @@ code = '''
|
||||
clobber_buf, BLOCK_SIZE) => 0;
|
||||
lfsr_traversal_close(&lfs, &t) => 0;
|
||||
goto clobbered;
|
||||
} else {
|
||||
k += 1;
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1634,9 +1632,8 @@ code = '''
|
||||
clobber_buf, BLOCK_SIZE) => 0;
|
||||
lfsr_traversal_close(&lfs, &t) => 0;
|
||||
goto clobbered;
|
||||
} else {
|
||||
k += 1;
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user