acad3a3143
This is mainly to solve the weird check-hole where passing CKPROGS/
CKREADS as mount flags has no effect on lfsr_format (I mean, it'd be a
bit silly if it did somehow):
LFS_F_RDWR 0 // Format the filesystem as read and write
LFS_F_CKPROGS 0x00000010 // Check progs by reading back progged data
LFS_F_CKREADS 0x00000020 // Check reads via parity bits/checksums
This makes lfsr_format a more cumbersome interface, but I don't know if
this is necessarily a bad thing. There's always risk of data loss when
calling lfsr_format, so maybe it should be a pain to call.
At the very least, format flags may be useful in the future for
enabling/disabling format-time things such as the planned block-map,
parity-tree, etc. Though it's unclear if such significant settings
should be format flags or somehow encoded as fields in our config
struct.
---
The LFS_F_* format flags of course ended up conflicting with our
internal LFS_F_* flags, so I renamed most of the internal flags to match
the closest flag set they participate in:
- LFS_F_TYPE -> LFS_O_TYPE
- LFS_F_UNFLUSH -> LFS_O_UNFLUSH
- LFS_F_UNSYNC -> LFS_O_UNSYNC
- LFS_F_ORPHAN -> LFS_O_ORPHAN
- LFS_F_ZOMBIE -> LFS_O_ZOMBIE
- LFS_F_ORPHANS -> LFS_I_ORPHANS
- LFS_F_UNCOMPACTED -> LFS_I_UNCOMPACTED
- LFS_F_TSTATE -> LFS_T_TSTATE
- LFS_F_BTYPE -> LFS_T_BTYPE
- LFS_F_DIRTY -> LFS_T_DIRTY
- LFS_F_MUTATED -> LFS_T_MUTATED
This may make it a bit less clear which flags are a part of the public
API, vs intended only for internal use, but at the very least our asserts
in format/mount/open/etc should catch most of these mistakes.
---
Code cost ended up being pretty minimal. Actually negative. This is the
second time we're _adding_ a feature that somehow saves code, though the
reality for this one is we're really just pushing constants up into the
user's stack frame. Still, it's a good indication the cost of format
flags is small:
code stack
before: 36452 2680
after: 36448 (-0.0%) 2680 (+0.0%)
900 lines
28 KiB
TOML
900 lines
28 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, LFS_F_RDWR, 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]
|
|
defines.CKREADS = [false, true]
|
|
defines.FLUSH = [false, true]
|
|
defines.SYNC = [false, true]
|
|
if = 'LFS_IFDEF_CKREADS(true, !CKREADS)'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
lfsr_mount(&lfs,
|
|
((RDONLY) ? LFS_M_RDONLY : LFS_M_RDWR)
|
|
| ((CKPROGS) ? LFS_M_CKPROGS : 0)
|
|
| ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_M_CKREADS, 0) : 0)
|
|
| ((FLUSH) ? LFS_M_FLUSH : 0)
|
|
| ((SYNC) ? LFS_M_SYNC : 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)
|
|
| ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_I_CKREADS, 0) : 0)
|
|
| ((FLUSH) ? LFS_I_FLUSH : 0)
|
|
| ((SYNC) ? LFS_I_SYNC : 0)
|
|
| LFS_I_CANLOOKAHEAD
|
|
| LFS_I_UNCOMPACTED));
|
|
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that various format flags don't, uh, assert or anything
|
|
#
|
|
# these end up passed to mount internally
|
|
[cases.test_mount_format_flags]
|
|
defines.CKPROGS = [false, true]
|
|
defines.CKREADS = [false, true]
|
|
if = 'LFS_IFDEF_CKREADS(true, !CKREADS)'
|
|
code = '''
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs,
|
|
LFS_F_RDWR
|
|
| ((CKPROGS) ? LFS_F_CKPROGS : 0)
|
|
| ((CKREADS) ? LFS_IFDEF_CKREADS(LFS_F_CKREADS, 0) : 0),
|
|
CFG) => 0;
|
|
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
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, LFS_F_RDWR, 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, LFS_F_RDWR, 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, LFS_F_RDWR, 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, LFS_F_RDWR, 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, LFS_F_RDWR, 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:;
|
|
'''
|
|
|
|
|
|
|
|
## incompatiblity tests ##
|
|
|
|
# test that we fail if we find no magic
|
|
[cases.test_mount_incompat_no_magic]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// delete the magic string
|
|
//
|
|
// note we're messing around with internals to do this! this
|
|
// is not a user API
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_RM | LFSR_TAG_MAGIC, 0,
|
|
LFSR_DATA_NULL()))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should now fail
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_CORRUPT;
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_CORRUPT;
|
|
'''
|
|
|
|
# test that we fail if we find bad magic
|
|
[cases.test_mount_incompat_bad_magic]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// tweak the magic string
|
|
//
|
|
// note we're messing around with internals to do this! this
|
|
// is not a user API
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_MAGIC, 0,
|
|
LFSR_DATA_BUF("lottlefs", 8)))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should now fail
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_CORRUPT;
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_CORRUPT;
|
|
'''
|
|
|
|
# test that we fail to mount after a major version bump
|
|
[cases.test_mount_incompat_major]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// bump the major version
|
|
//
|
|
// note we're messing around with internals to do this! this
|
|
// is not a user API
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_VERSION, 0,
|
|
LFSR_DATA_BUF(((const uint8_t[2]){
|
|
LFS_DISK_VERSION_MAJOR+1,
|
|
0}), 2)))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should now fail
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
|
|
'''
|
|
|
|
# test that we fail to mount after a minor version bump
|
|
[cases.test_mount_incompat_minor]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// bump the minor version
|
|
//
|
|
// note we're messing around with internals to do this! this
|
|
// is not a user API
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_VERSION, 0,
|
|
LFSR_DATA_BUF(((const uint8_t[2]){
|
|
LFS_DISK_VERSION_MAJOR,
|
|
LFS_DISK_VERSION_MINOR+1}), 2)))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should now fail
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
|
|
'''
|
|
|
|
# test that we fail to mount incompatible rcompat flags
|
|
[cases.test_mount_incompat_rcompat]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// set the nonstandard rcompat flag, this will always be incompatible
|
|
// with standard littlefs
|
|
//
|
|
// note we're messing around with internals to do this! this
|
|
// is not a user API
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_RCOMPAT, 0,
|
|
LFSR_DATA_RCOMPAT(
|
|
LFSR_RCOMPAT_COMPAT
|
|
| LFSR_RCOMPAT_NONSTANDARD)))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should now fail
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
|
|
'''
|
|
|
|
# test that we fail to mount incompatible wcompat flags
|
|
[cases.test_mount_incompat_wcompat]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// set the nonstandard rcompat flag, this will always be incompatible
|
|
// with standard littlefs
|
|
//
|
|
// note we're messing around with internals to do this! this
|
|
// is not a user API
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_WCOMPAT, 0,
|
|
LFSR_DATA_WCOMPAT(
|
|
LFSR_WCOMPAT_COMPAT
|
|
| LFSR_WCOMPAT_NONSTANDARD)))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should now fail
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
|
|
|
|
// but we _can_ mount readonly
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that an incompatible ocompat flag is a noop
|
|
[cases.test_mount_incompat_ocompat]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// set the nonstandard ocompat flag, this will always be incompatible
|
|
// with standard littlefs
|
|
//
|
|
// note we're messing around with internals to do this! this
|
|
// is not a user API
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_OCOMPAT, 0,
|
|
LFSR_DATA_OCOMPAT(
|
|
LFSR_OCOMPAT_COMPAT
|
|
| LFSR_OCOMPAT_NONSTANDARD)))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should _not_ fail, ocompat should always be ignored
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# these are just a bit harder to detect
|
|
[cases.test_mount_incompat_rcompat_overflow]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// set a really far rcompat flag
|
|
//
|
|
// note we're messing around with internals to do this! this
|
|
// is not a user API
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR_CAT(
|
|
LFSR_TAG_RCOMPAT, 0,
|
|
LFSR_DATA_RCOMPAT(LFSR_RCOMPAT_COMPAT),
|
|
LFSR_DATA_BUF("\x00\x00\x00\x00\x80", 5)))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should now fail
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
|
|
'''
|
|
|
|
[cases.test_mount_incompat_wcompat_overflow]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// set a really far wcompat flag
|
|
//
|
|
// note we're messing around with internals to do this! this
|
|
// is not a user API
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR_CAT(
|
|
LFSR_TAG_WCOMPAT, 0,
|
|
LFSR_DATA_WCOMPAT(LFSR_WCOMPAT_COMPAT),
|
|
LFSR_DATA_BUF("\x00\x00\x00\x00\x80", 5)))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should now fail
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
|
|
|
|
// but we _can_ mount readonly
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
[cases.test_mount_incompat_ocompat_overflow]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// set a really far ocompat flag
|
|
//
|
|
// note we're messing around with internals to do this! this
|
|
// is not a user API
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR_CAT(
|
|
LFSR_TAG_OCOMPAT, 0,
|
|
LFSR_DATA_OCOMPAT(LFSR_OCOMPAT_COMPAT),
|
|
LFSR_DATA_BUF("\x00\x00\x00\x00\x80", 5)))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should _not_ fail, ocompat should always be ignored
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|
|
|
|
# test that we fail to mount incompatible block sizes
|
|
[cases.test_mount_incompat_block_size]
|
|
defines.INC_BLOCK_SIZE = ['BLOCK_SIZE/2', 'BLOCK_SIZE*2']
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// set an incompatible block size
|
|
//
|
|
// note we're messing around with internals to do this! this
|
|
// is not a user API
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_GEOMETRY, 0,
|
|
LFSR_DATA_GEOMETRY((&(lfsr_geometry_t){
|
|
INC_BLOCK_SIZE,
|
|
BLOCK_COUNT}))))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should now fail
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
|
|
'''
|
|
|
|
# test that we fail to mount after incompatible block counts
|
|
[cases.test_mount_incompat_block_count]
|
|
defines.INC_BLOCK_COUNT = ['BLOCK_COUNT*2']
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// set an incompatible block count
|
|
//
|
|
// note we're messing around with internals to do this! this
|
|
// is not a user API
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_GEOMETRY, 0,
|
|
LFSR_DATA_GEOMETRY((&(lfsr_geometry_t){
|
|
BLOCK_SIZE,
|
|
INC_BLOCK_COUNT}))))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should now fail
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
|
|
'''
|
|
|
|
# test that we fail to mount after incompatible name limit
|
|
[cases.test_mount_incompat_name_limit]
|
|
defines.INC_NAME_LIMIT = ['LFS_NAME_MAX*2']
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// set an incompatible block size
|
|
//
|
|
// note we're messing around with internals to do this! this
|
|
// is not a user API
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_NAMELIMIT, 0,
|
|
LFSR_DATA_LLEB128(INC_NAME_LIMIT)))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should now fail
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
|
|
'''
|
|
|
|
# test that we fail to mount after incompatible file limit
|
|
[cases.test_mount_incompat_file_limit]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// set an incompatible file limit
|
|
//
|
|
// note we're messing around with internals to do this! this
|
|
// is not a user API
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR_CAT(
|
|
LFSR_TAG_FILELIMIT, 0,
|
|
// it's a bit difficult to test this since file limit
|
|
// is usually our integer limit, but we can force a
|
|
// larger value by inserting an extra byte into our
|
|
// leb128 encoding
|
|
LFSR_DATA_BUF("\xff", 1),
|
|
LFSR_DATA_LEB128(LFS_FILE_MAX)))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should now fail
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
|
|
'''
|
|
|
|
# test what happens if we find an unknown config
|
|
[cases.test_mount_incompat_unknown_config]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// create an unknown config
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
|
LFSR_ATTR(
|
|
LFSR_TAG_CONFIG + 0x13, 0,
|
|
LFSR_DATA_BUF("oh no!", strlen("oh no!"))))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should now fail
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP;
|
|
'''
|
|
|
|
# test what happens if we find an unknown file type
|
|
[cases.test_mount_incompat_unknown_type]
|
|
in = 'lfs.c'
|
|
code = '''
|
|
// create a superblock
|
|
lfs_t lfs;
|
|
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
|
|
|
|
// create some files
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_file_t file;
|
|
lfsr_file_open(&lfs, &file, "a",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfsr_file_write(&lfs, &file,
|
|
"hi a!", strlen("hi a!")) => strlen("hi a!");
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
lfsr_file_open(&lfs, &file, "b",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfsr_file_write(&lfs, &file,
|
|
"oh no!", strlen("oh no!")) => strlen("oh no!");
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
lfsr_file_open(&lfs, &file, "c",
|
|
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
|
lfsr_file_write(&lfs, &file,
|
|
"hi c!", strlen("hi c!")) => strlen("hi c!");
|
|
lfsr_file_close(&lfs, &file) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// change a file's type to something unknown
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
|
lfsr_mdir_t mdir;
|
|
lfsr_did_t did;
|
|
const char *name;
|
|
lfs_size_t name_size;
|
|
lfsr_mtree_pathlookup(&lfs, "b",
|
|
&mdir, NULL,
|
|
&did, &name, &name_size) => LFS_ERR_EXIST;
|
|
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
|
LFSR_ATTR_NAME(
|
|
LFSR_TAG_SUB | (LFSR_TAG_NAME + 0x13), 0,
|
|
did, name, name_size))) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
|
|
// mount should now fail
|
|
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP;
|
|
|
|
// but we _can_ mount readonly
|
|
lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0;
|
|
lfsr_unmount(&lfs) => 0;
|
|
'''
|