Added simple lfsr_fs_ckmeta/ckdata functions
These functions provide an easy API for checking all metadata/data
checksums in the filesystem:
// Check the filesystem for metadata errors
int lfsr_fs_ckmeta(lfs_t *lfs);
// Check the filesystem for metadata + data errors
int lfsr_fs_ckdata(lfs_t *lfs);
These are more-or-less the same as calling lfsr_fs_gc with
LFS_GC_CKMETA/CKDATA, but don't involve the gc/traversal-invalidation
machinery, and may be a bit easier for users to pick up.
---
Unfortunately, for simple wrappers, we're again hit with a somewhat
surprising code cost:
code stack
before: 36288 2680
after: 36472 (+0.5%) 2680 (+0.0%)
But I think we can again blame the high overhead of LFS_TRAVERSAL/
lfsr_mtree_gc. We should look into reducing/deduplicating this logic...
This commit is contained in:
@@ -13024,6 +13024,43 @@ int lfsr_fs_mkconsistent(lfs_t *lfs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// perform any pending janitorial work
|
||||
int lfsr_fs_gc(lfs_t *lfs, uint32_t flags) {
|
||||
// some flags don't make sense when only traversing the mtree
|
||||
LFS_ASSERT(!lfsr_t_ismtreeonly(flags) || !lfsr_t_islookahead(flags));
|
||||
|
||||
@@ -1165,7 +1165,23 @@ int lfsr_fs_mkconsistent(lfs_t *lfs);
|
||||
#endif
|
||||
|
||||
#ifndef LFS_READONLY
|
||||
// Attempt any janitorial work that may be pending.
|
||||
// Check the filesystem for metadata errors
|
||||
//
|
||||
// Returns LFS_ERR_CORRUPT if a checksum mismatch is found, or a negative
|
||||
// error code on failure.
|
||||
int lfsr_fs_ckmeta(lfs_t *lfs);
|
||||
#endif
|
||||
|
||||
#ifndef LFS_READONLY
|
||||
// Check the filesystem for metadata + data errors
|
||||
//
|
||||
// Returns LFS_ERR_CORRUPT if a checksum mismatch is found, or a negative
|
||||
// error code on failure.
|
||||
int lfsr_fs_ckdata(lfs_t *lfs);
|
||||
#endif
|
||||
|
||||
#ifndef LFS_READONLY
|
||||
// Perform any janitorial work that may be pending.
|
||||
//
|
||||
// The exact janitorial work depends on the provided flags. Note multiple
|
||||
// calls may be required to complete all janitorial work.
|
||||
|
||||
@@ -779,6 +779,99 @@ code = '''
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# test that an explicit lfsr_fs_mkconsistent call also works, this calls
|
||||
# the same logic internally
|
||||
[cases.test_gc_mkconsistent_explicit]
|
||||
defines.SIZE = 'FILE_BUFFER_SIZE/2'
|
||||
# <=2 => grm-able
|
||||
# >2 => requires orphans
|
||||
defines.ORPHANS = [1, 2, 3, 100]
|
||||
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);
|
||||
|
||||
// call lfsr_fs_mkconsistent
|
||||
lfsr_fs_mkconsistent(&lfs) => 0;
|
||||
|
||||
// we should have made progress
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(!(fsinfo.flags & LFS_I_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 mkconsistent dirtying still works with the GC API
|
||||
[cases.test_gc_mkconsistent_mutation]
|
||||
defines.GC_STEPS = 1
|
||||
@@ -1329,6 +1422,183 @@ code = '''
|
||||
done:;
|
||||
'''
|
||||
|
||||
# test that our explicit functions (lfsr_fs_ckmeta/ckdata) work as well,
|
||||
# these call the same logic internally
|
||||
[cases.test_gc_ckmeta_explicit]
|
||||
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
||||
defines.SIZE = [
|
||||
'0',
|
||||
'FILE_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:;
|
||||
// lfsr_fs_ckmeta should find the clobbered block
|
||||
lfsr_fs_ckmeta(&lfs) => LFS_ERR_CORRUPT;
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
}
|
||||
done:;
|
||||
'''
|
||||
|
||||
[cases.test_gc_ckdata_explicit]
|
||||
defines.N = [1, 2, 4, 8, 16, 32, 64]
|
||||
defines.SIZE = [
|
||||
'0',
|
||||
'FILE_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:;
|
||||
// lfsr_fs_ckdata should find the clobbered block
|
||||
lfsr_fs_ckdata(&lfs) => LFS_ERR_CORRUPT;
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
}
|
||||
done:;
|
||||
'''
|
||||
|
||||
|
||||
# pseudo-fuzz test that dirtying still works with the GC API
|
||||
[cases.test_gc_mutation]
|
||||
|
||||
Reference in New Issue
Block a user