Files
littlefs/tests/test_mount.toml
T
Christopher Haster 6e63920338 Dropped the HASORPHAN scan in lfsr_mount
The motivation here is to simplify lfsr_mount, but there's a number of
knock-on effects.

For one, lfsr_mount should now be faster on filesystems with large
blocks:

  O(nb(log b)(log_b n)) -> O(nb(log_b n))

But we now no longer check if our filesystem contains orphaned
stickynotes or unknown filetypes:

- Orphaned stickynotes turned out to not be a big deal. If we find
  orphans we'd need to do a second traversal to remove them anyways (no
  mutation allowed in lfsr_mount), so this actually ends up a net
  improvement in the found-orphan case.

  If anything, doing a traversal on first write sets user expectations
  correctly, and can be offloaded with lfsr_fs_mkconsistent or
  lfsr_fs_gc.

- Unknown filetypes are a bit more annoying (I actually forgot about
  this check), but unknown filetypes that require special care should
  probably set WCOMPAT/RCOMPAT flags.

  Allowing unknown filetypes is a bit more flexible in cases where a
  filesystem image is being shared between drivers with different
  features (bootloader + app for example).

  Though we should probably add more checks/tests that we're handling
  these correctly now that we no longer just bail during mount...

Also renamed LFS_I_HASORPHANS -> LFS_I_UNTIDY.

Not doing something is cheaper than doing something, so this saves a bit
of code:

           code          stack          ctx
  before: 38120           2624          752
  after:  38020 (-0.3%)   2624 (+0.0%)  752 (+0.0%)
2025-01-28 14:41:45 -06:00

905 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 don't assert and are returned by
# lfsr_fs_stat
[cases.test_mount_flags]
defines.RDONLY = [false, true]
defines.FLUSH = [false, true]
defines.SYNC = [false, true]
defines.CKPROGS = [false, true]
defines.CKFETCHES = [false, true]
defines.CKPARITY = [false, true]
defines.CKCKSUMS = [false, true]
defines.MTREEONLY = [false, true]
defines.MKCONSISTENT = [false, true]
defines.LOOKAHEAD = [false, true]
defines.COMPACT = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
if = [
'LFS_IFDEF_CKPROGS(true, !CKPROGS)',
'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)',
'LFS_IFDEF_CKPARITY(true, !CKPARITY)',
'LFS_IFDEF_CKCKSUMS(true, !CKCKSUMS)',
'!RDONLY || !MKCONSISTENT',
'!RDONLY || !LOOKAHEAD',
'!RDONLY || !COMPACT',
'!MTREEONLY || !LOOKAHEAD',
'!MTREEONLY || !CKDATA',
]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs,
((RDONLY) ? LFS_M_RDONLY : LFS_M_RDWR)
| ((FLUSH) ? LFS_M_FLUSH : 0)
| ((SYNC) ? LFS_M_SYNC : 0)
| ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) : 0)
| ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) : 0)
| ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_M_CKPARITY, -1) : 0)
| ((CKCKSUMS) ? LFS_IFDEF_CKCKSUMS(LFS_M_CKCKSUMS, -1) : 0)
| ((MTREEONLY) ? LFS_M_MTREEONLY : 0)
| ((MKCONSISTENT) ? LFS_M_MKCONSISTENT : 0)
| ((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 only returns some flags
struct lfs_fsinfo fsinfo;
lfsr_fs_stat(&lfs, &fsinfo) => 0;
assert(fsinfo.flags == (
((RDONLY) ? LFS_I_RDONLY : 0)
| ((FLUSH) ? LFS_I_FLUSH : 0)
| ((SYNC) ? LFS_I_SYNC : 0)
| ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_I_CKPROGS, -1) : 0)
| ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_I_CKFETCHES, -1) : 0)
| ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_I_CKPARITY, -1) : 0)
| ((CKCKSUMS) ? LFS_IFDEF_CKCKSUMS(LFS_I_CKCKSUMS, -1) : 0)
| ((!MKCONSISTENT) ? LFS_I_INCONSISTENT : 0)
| ((!LOOKAHEAD) ? LFS_I_CANLOOKAHEAD : 0)
| ((!COMPACT) ? LFS_I_UNCOMPACTED : 0)));
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.CKFETCHES = [false, true]
defines.CKPARITY = [false, true]
defines.CKCKSUMS = [false, true]
defines.MTREEONLY = [false, true]
defines.COMPACT = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
if = [
'LFS_IFDEF_CKPROGS(true, !CKPROGS)',
'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)',
'LFS_IFDEF_CKPARITY(true, !CKPARITY)',
'LFS_IFDEF_CKCKSUMS(true, !CKCKSUMS)',
'!MTREEONLY || !CKDATA',
]
code = '''
lfs_t lfs;
lfsr_format(&lfs,
LFS_F_RDWR
| ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_F_CKPROGS, -1) : 0)
| ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_F_CKFETCHES, -1) : 0)
| ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_F_CKPARITY, -1) : 0)
| ((CKCKSUMS) ? LFS_IFDEF_CKCKSUMS(LFS_F_CKCKSUMS, -1) : 0)
| ((MTREEONLY) ? LFS_M_MTREEONLY : 0)
| ((COMPACT) ? LFS_M_COMPACT : 0)
| ((CKMETA) ? LFS_M_CKMETA : 0)
| ((CKDATA) ? LFS_M_CKDATA : 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_INCONSISTENT
| 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_INCONSISTENT
| 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_INCONSISTENT
| 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 == (
LFS_I_INCONSISTENT
| ((!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 == (
LFS_I_INCONSISTENT
| 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_RATS(
LFSR_RAT(
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_RATS(
LFSR_RAT(
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_RATS(
LFSR_RAT(
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_RATS(
LFSR_RAT(
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_RATS(
LFSR_RAT(
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_RATS(
LFSR_RAT(
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_RATS(
LFSR_RAT(
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_RATS(
LFSR_RAT_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_RATS(
LFSR_RAT_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_RATS(
LFSR_RAT_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_RATS(
LFSR_RAT(
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_RATS(
LFSR_RAT(
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_RATS(
LFSR_RAT(
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_RATS(
LFSR_RAT_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_RATS(
LFSR_RAT(
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;
'''