e2c238c30d
These are basically the same as lfsr_fs_ckmeta/ckdata but limited to a
single file. They may be useful when you need to validate a file but
don't want to bother validating the entire filesystem:
// Check a file for metadata errors
int lfsr_file_ckmeta(lfs_t *lfs, lfsr_file_t *file);
// Check a file for metadata + data errors
int lfsr_file_ckdata(lfs_t *lfs, lfsr_file_t *file);
I've also added test_ck to test these and added some more
lfsr_fs_ckmeta/ckdata tests there. These currently just test simple
full-block clobbering, but we should eventually test more interesting
error patterns.
Unfortunately lfsr_file_ckmeta/ckdata can't reuse the internal
lfsr_mtree_traverse in quite the same way lfsr_fs_ckmeta/ckdata can, so
they're actually a bit more expensive. Though keep in mind with
link-time gc you won't pay the cost unless you call these functions:
code stack
before: 36024 2696
after: 36368 (+1.0%) 2664 (-1.2%)
Oh, and the multiple calls to lfsr_btree/bshrub_traverse apparently
uninlined it out of lfsr_mtree_traverse, saving the stack cost in the
stack hot-path... Yay?
426 lines
13 KiB
TOML
426 lines
13 KiB
TOML
# Advanced mount tests
|
|
after = ['test_mtree', 'test_traversal']
|
|
|
|
# test we can mount
|
|
[cases.test_mount_simple]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that various mount flags are returned by lfsr_fs_stat
|
|
[cases.test_mount_flags]
|
|
defines.RDONLY = [false, true]
|
|
defines.CKPROGS = [false, true]
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, CFG) => 0;
|
|
lfsr_mount(&lfs,
|
|
((RDONLY) ? LFS_M_RDONLY : LFS_M_RDWR)
|
|
| ((CKPROGS) ? LFS_M_CKPROGS : 0),
|
|
CFG) => 0;
|
|
|
|
struct lfs_fsinfo fsinfo;
|
|
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
|
assert(fsinfo.flags == (
|
|
((RDONLY) ? LFS_I_RDONLY : 0)
|
|
| ((CKPROGS) ? LFS_I_CKPROGS : 0)
|
|
| LFS_I_CANLOOKAHEAD
|
|
| LFS_I_UNCOMPACTED));
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
|
|
# 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
|
|
#
|
|
# these are tested more thoroughly in test_ck
|
|
[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?
|