Files
littlefs/tests/test_mount.toml
Christopher Haster de7564e448 Added phase bits to cksum tags
This carves out two more bits in cksum tags to store the "phase" of the
rbyd block (maybe the name is too fancy, this is just the lowest 2 bits
of the block address):

  LFSR_TAG_CKSUM        0x300p  v-11 ---- ---- -pqq
                                                ^ ^
                                                | '-- phase bits
                                                '---- perturb bit

The intention here is to catch mrootanchors that are "out-of-phase",
i.e. they've been shifted by a small number of blocks.

This can happen if we find the wrong mrootanchor (after, say, a magic
scan), and risks filesystem corruption:

                formatted
  .-----------------'-----------------.
                          mounted
           .-----------------'-----------------.
  .--------+--------+--------+--------+ ...
  |(erased)| mroot  |
  |        | anchor |                   ...
  |        |        |
  '--------+--------+--------+--------+ ...

Including the lower 2 bits of the block address in cksum tags avoids
this, for up to a 3 block shift (the maximum number of redund
mrootanchors).

---

Note that cksum tags really are the only place we could put these bits.
Anywhere else and they would interfere with the canonical cksum, which
would break error correction. By definition these need to be different
per block.

We include these phase bits in every cksum tag (because it's easier),
but these don't really say much about mdirs that are not the
mrootanchor. Non-anchor mdirs can have arbitrary block addresses,
therefore arbitrary phase bits.

You _might_ be able to do something interesting if you sort the rbyd
addresses and use the index as the phase bits, but that would add quite
a bit of code for questionable benefit...

You could argue this adds noise to our cksums, but:

1. 2 bits seems like a really small amount of noise
2. our cksums are just crc32cs
3. the phase bits humorously never change when you rewrite a block

---

As with any feature this adds code, but only a small amount. I think
it's worth the extra protection:

           code          stack          ctx
  before: 35792           2368          636
  after:  35824 (+0.1%)   2368 (+0.0%)  636 (+0.0%)

Also added test_mount_incompat_out_of_phase to test this.

The dbg scripts _don't_ error (block mismatch seems likely when
debugging), but dbgrbyd.py at least adds phase mismatch notes in
-l/--log mode.
2025-04-30 00:57:17 -05:00

2018 lines
67 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.REVDBG = [false, true]
defines.REVNOISE = [false, true]
defines.CKPROGS = [false, true]
defines.CKFETCHES = [false, true]
defines.CKPARITY = [false, true]
defines.CKDATACKSUMS = [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_REVDBG(true, !REVDBG)',
'LFS_IFDEF_REVNOISE(true, !REVNOISE)',
'!REVDBG || !REVNOISE',
'LFS_IFDEF_CKPROGS(true, !CKPROGS)',
'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)',
'LFS_IFDEF_CKPARITY(true, !CKPARITY)',
'LFS_IFDEF_CKDATACKSUMS(true, !CKDATACKSUMS)',
'!RDONLY || !MKCONSISTENT',
'!RDONLY || !LOOKAHEAD',
'!RDONLY || !COMPACT',
]
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)
| ((REVDBG) ? LFS_IFDEF_REVDBG(LFS_M_REVDBG, -1) : 0)
| ((REVNOISE) ? LFS_IFDEF_REVNOISE(LFS_M_REVNOISE, -1) : 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)
| ((CKDATACKSUMS)
? LFS_IFDEF_CKDATACKSUMS(LFS_M_CKDATACKSUMS, -1)
: 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)
| ((REVDBG) ? LFS_IFDEF_REVDBG(LFS_M_REVDBG, -1) : 0)
| ((REVNOISE) ? LFS_IFDEF_REVNOISE(LFS_M_REVNOISE, -1) : 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)
| ((CKDATACKSUMS)
? LFS_IFDEF_CKDATACKSUMS(LFS_I_CKDATACKSUMS, -1)
: 0)
| ((!MKCONSISTENT) ? LFS_I_MKCONSISTENT : 0)
| ((!LOOKAHEAD) ? LFS_I_LOOKAHEAD : 0)
| ((!COMPACT) ? LFS_I_COMPACT : 0)
// note ckdata implies ckmeta
| ((!CKMETA && !CKDATA) ? LFS_I_CKMETA : 0)
| ((!CKDATA) ? LFS_I_CKDATA : 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.REVDBG = [false, true]
defines.REVNOISE = [false, true]
defines.CKPROGS = [false, true]
defines.CKFETCHES = [false, true]
defines.CKPARITY = [false, true]
defines.CKDATACKSUMS = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
if = [
'LFS_IFDEF_REVDBG(true, !REVDBG)',
'LFS_IFDEF_REVNOISE(true, !REVNOISE)',
'!REVDBG || !REVNOISE',
'LFS_IFDEF_CKPROGS(true, !CKPROGS)',
'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)',
'LFS_IFDEF_CKPARITY(true, !CKPARITY)',
'LFS_IFDEF_CKDATACKSUMS(true, !CKDATACKSUMS)',
]
code = '''
lfs_t lfs;
lfsr_format(&lfs,
LFS_F_RDWR
| ((REVDBG) ? LFS_IFDEF_REVDBG(LFS_F_REVDBG, -1) : 0)
| ((REVNOISE) ? LFS_IFDEF_REVNOISE(LFS_F_REVNOISE, -1) : 0)
| ((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)
| ((CKDATACKSUMS)
? LFS_IFDEF_CKDATACKSUMS(LFS_F_CKDATACKSUMS, -1)
: 0)
| ((CKMETA) ? LFS_F_CKMETA : 0)
| ((CKDATA) ? LFS_F_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_MKCONSISTENT
| LFS_I_LOOKAHEAD
| LFS_I_COMPACT
| LFS_I_CKMETA
| LFS_I_CKDATA));
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_MKCONSISTENT
| LFS_I_COMPACT
// note ckdata implies ckmeta
| ((!CKMETA && !CKDATA) ? LFS_I_CKMETA : 0)
| ((!CKDATA) ? LFS_I_CKDATA : 0)));
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mount_t_compact]
defines.LOOKAHEAD = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.SIZE = [
'FILE_CACHE_SIZE/2',
'2*FILE_CACHE_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.b.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_MKCONSISTENT
| LFS_I_LOOKAHEAD
| LFS_I_COMPACT
| LFS_I_CKMETA
| LFS_I_CKDATA));
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_MKCONSISTENT
| ((!LOOKAHEAD) ? LFS_I_LOOKAHEAD : 0)
// note ckdata implies ckmeta
| ((!CKMETA && !CKDATA) ? LFS_I_CKMETA : 0)
| ((!CKDATA) ? LFS_I_CKDATA : 0)));
// mdir should have been compacted
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
assert((file.b.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_CACHE_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_MKCONSISTENT
| LFS_I_LOOKAHEAD
| LFS_I_COMPACT
| LFS_I_CKMETA
| LFS_I_CKDATA));
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_LOOKAHEAD : 0)
| ((!COMPACT) ? LFS_I_COMPACT : 0)
// note ckdata implies ckmeta
| ((!CKMETA && !CKDATA) ? LFS_I_CKMETA : 0)
| ((!CKDATA) ? LFS_I_CKDATA : 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_CACHE_SIZE/2',
'2*FILE_CACHE_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_CACHE_SIZE/2',
'2*FILE_CACHE_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_RATTRS(
LFSR_RATTR(
LFSR_TAG_RM | LFSR_TAG_MAGIC, 0))) => 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_RATTRS(
LFSR_RATTR_BUF(
LFSR_TAG_MAGIC, 0,
"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_RATTRS(
LFSR_RATTR_BUF(
LFSR_TAG_VERSION, 0,
((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_RATTRS(
LFSR_RATTR_BUF(
LFSR_TAG_VERSION, 0,
((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_RATTRS(
LFSR_RATTR_LE32(
LFSR_TAG_RCOMPAT, 0,
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_RATTRS(
LFSR_RATTR_LE32(
LFSR_TAG_WCOMPAT, 0,
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_RATTRS(
LFSR_RATTR_LE32(
LFSR_TAG_OCOMPAT, 0,
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;
'''
# test that we fail to mount rdonly images
[cases.test_mount_incompat_rdonly]
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
// set the rdonly flag, this prevents writing from a littlefs image
//
// 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_RATTRS(
LFSR_RATTR_LE32(
LFSR_TAG_WCOMPAT, 0,
LFSR_WCOMPAT_COMPAT
| LFSR_WCOMPAT_RDONLY))) => 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 we fail to mount wronly images
[cases.test_mount_incompat_wronly]
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
// set the wronly flag, this prevents reading from a littlefs image
//
// 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_RATTRS(
LFSR_RATTR_LE32(
LFSR_TAG_RCOMPAT, 0,
LFSR_RCOMPAT_COMPAT
| LFSR_RCOMPAT_WRONLY))) => 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;
'''
# these are just a bit harder to detect
[cases.test_mount_incompat_rcompat_overflow]
defines.OVERFLOW = 72
defines.FLAG = 'range(72)'
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
uint8_t overflow[OVERFLOW / 8];
memset(overflow, 0, sizeof(overflow));
overflow[FLAG / 8] |= 1 << (FLAG % 8);
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint8_t rcompat_buf[LFSR_LE32_DSIZE];
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_RCOMPAT, 0,
lfsr_data_fromle32(LFSR_RCOMPAT_COMPAT, rcompat_buf),
LFSR_DATA_BUF(overflow, sizeof(overflow))))) => 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]
defines.OVERFLOW = 72
defines.FLAG = 'range(72)'
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
uint8_t flags[9] = {0};
flags[FLAG / 8] |= 1 << (FLAG % 8);
// set a really far wcompat flag
//
// note we're messing around with internals to do this! this
// is not a user API
uint8_t overflow[OVERFLOW / 8];
memset(overflow, 0, sizeof(overflow));
overflow[FLAG / 8] |= 1 << (FLAG % 8);
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint8_t wcompat_buf[LFSR_LE32_DSIZE];
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_WCOMPAT, 0,
lfsr_data_fromle32(LFSR_WCOMPAT_COMPAT, wcompat_buf),
LFSR_DATA_BUF(overflow, sizeof(overflow))))) => 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]
defines.OVERFLOW = 72
defines.FLAG = 'range(72)'
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
uint8_t overflow[OVERFLOW / 8];
memset(overflow, 0, sizeof(overflow));
overflow[FLAG / 8] |= 1 << (FLAG % 8);
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint8_t ocompat_buf[LFSR_LE32_DSIZE];
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_OCOMPAT, 0,
lfsr_data_fromle32(LFSR_OCOMPAT_COMPAT, ocompat_buf),
LFSR_DATA_BUF(overflow, sizeof(overflow))))) => 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;
'''
# but just appending zeros is _not_ an error
[cases.test_mount_incompat_rcompat_padding]
defines.OVERFLOW = 72
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
uint8_t overflow[OVERFLOW / 8];
memset(overflow, 0, sizeof(overflow));
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint8_t rcompat_buf[LFSR_LE32_DSIZE];
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_RCOMPAT, 0,
lfsr_data_fromle32(LFSR_RCOMPAT_COMPAT, rcompat_buf),
LFSR_DATA_BUF(overflow, sizeof(overflow))))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should _not_ fail, extra zeros should 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;
'''
[cases.test_mount_incompat_wcompat_padding]
defines.OVERFLOW = 72
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
uint8_t flags[9] = {0};
flags[FLAG / 8] |= 1 << (FLAG % 8);
// set a really far wcompat flag
//
// note we're messing around with internals to do this! this
// is not a user API
uint8_t overflow[OVERFLOW / 8];
memset(overflow, 0, sizeof(overflow));
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint8_t wcompat_buf[LFSR_LE32_DSIZE];
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_WCOMPAT, 0,
lfsr_data_fromle32(LFSR_WCOMPAT_COMPAT, wcompat_buf),
LFSR_DATA_BUF(overflow, sizeof(overflow))))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should _not_ fail, extra zeros should 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;
'''
[cases.test_mount_incompat_ocompat_padding]
defines.OVERFLOW = 72
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
uint8_t overflow[OVERFLOW / 8];
memset(overflow, 0, sizeof(overflow));
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
uint8_t ocompat_buf[LFSR_LE32_DSIZE];
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_OCOMPAT, 0,
lfsr_data_fromle32(LFSR_OCOMPAT_COMPAT, ocompat_buf),
LFSR_DATA_BUF(overflow, sizeof(overflow))))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should _not_ fail, extra zeros should 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_RATTRS(
LFSR_RATTR_GEOMETRY(
LFSR_TAG_GEOMETRY, 0,
(&(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_RATTRS(
LFSR_RATTR_GEOMETRY(
LFSR_TAG_GEOMETRY, 0,
(&(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_RATTRS(
LFSR_RATTR_LEB128(
LFSR_TAG_NAMELIMIT, 0,
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;
uint8_t file_limit_buf[LFSR_LEB128_DSIZE];
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS(
LFSR_RATTR_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_fromleb128(LFS_FILE_MAX, file_limit_buf)))) => 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_RATTRS(
LFSR_RATTR_BUF(
LFSR_TAG_CONFIG + 0x42, 0,
"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;
const char *path = "b";
lfsr_mdir_t mdir;
lfsr_did_t did;
lfsr_mtree_pathlookup(&lfs, &path,
&mdir, NULL, &did) => 0;
lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0,
lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}),
LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0;
lfsr_unmount(&lfs) => 0;
// mount
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs_info info;
lfsr_stat(&lfs, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// open/mkdir should error
lfsr_file_open(&lfs, &file, "b",
LFS_O_RDONLY) => LFS_ERR_NOTSUP;
lfsr_file_open(&lfs, &file, "b",
LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTSUP;
lfsr_mkdir(&lfs, "b") => LFS_ERR_EXIST;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mount_incompat_unknown_type_rm]
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;
const char *path = "b";
lfsr_mdir_t mdir;
lfsr_did_t did;
lfsr_mtree_pathlookup(&lfs, &path,
&mdir, NULL, &did) => 0;
lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0,
lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}),
LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0;
lfsr_unmount(&lfs) => 0;
// mount
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs_info info;
lfsr_stat(&lfs, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// removing unknown files should still work, if this would leak
// resources the new type should set a wcompat flag
lfsr_remove(&lfs, "b") => 0;
// check that things look reasonable
lfsr_stat(&lfs, "b", &info) => LFS_ERR_NOENT;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mount_incompat_unknown_type_mv_src]
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;
const char *path = "b";
lfsr_mdir_t mdir;
lfsr_did_t did;
lfsr_mtree_pathlookup(&lfs, &path,
&mdir, NULL, &did) => 0;
lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0,
lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}),
LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0;
lfsr_unmount(&lfs) => 0;
// mount
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs_info info;
lfsr_stat(&lfs, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// renaming unknown files should still work, if this would leak
// resources the new type should set a wcompat flag
lfsr_rename(&lfs, "b", "c") => 0;
// check that things look reasonable after renaming/removing
lfsr_stat(&lfs, "c", &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mount_incompat_unknown_type_mv_dst]
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;
const char *path = "b";
lfsr_mdir_t mdir;
lfsr_did_t did;
lfsr_mtree_pathlookup(&lfs, &path,
&mdir, NULL, &did) => 0;
lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0,
lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}),
LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0;
lfsr_unmount(&lfs) => 0;
// mount
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs_info info;
lfsr_stat(&lfs, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// renaming unknown files should still work, if this would leak
// resources the new type should set a wcompat flag
lfsr_rename(&lfs, "c", "b") => 0;
// check that things look reasonable after renaming/removing
lfsr_stat(&lfs, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mount_incompat_unknown_type_mv_src_dst]
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,
"oh hi!", strlen("oh hi!")) => strlen("oh hi!");
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;
const char *path = "b";
lfsr_mdir_t mdir;
lfsr_did_t did;
lfsr_mtree_pathlookup(&lfs, &path,
&mdir, NULL, &did) => 0;
lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0,
lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}),
LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0;
path = "c";
lfsr_mtree_pathlookup(&lfs, &path,
&mdir, NULL, &did) => 0;
lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0,
lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}),
LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0;
lfsr_unmount(&lfs) => 0;
// mount
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs_info info;
lfsr_stat(&lfs, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// renaming unknown files should still work, if this would leak
// resources the new type should set a wcompat flag
lfsr_rename(&lfs, "b", "c") => 0;
// check that things look reasonable after renaming/removing
lfsr_stat(&lfs, "c", &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mount_incompat_unknown_type_mv_noop]
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;
const char *path = "b";
lfsr_mdir_t mdir;
lfsr_did_t did;
lfsr_mtree_pathlookup(&lfs, &path,
&mdir, NULL, &did) => 0;
lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0,
lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}),
LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0;
lfsr_unmount(&lfs) => 0;
// mount
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs_info info;
lfsr_stat(&lfs, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// renaming unknown files should still work, if this would leak
// resources the new type should set a wcompat flag
lfsr_rename(&lfs, "b", "b") => 0;
// check that things look reasonable after renaming/removing
lfsr_stat(&lfs, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mount_incompat_unknown_type_mv_notdir]
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_mkdir(&lfs, "c") => 0;
lfsr_unmount(&lfs) => 0;
// change a file's type to something unknown
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
const char *path = "b";
lfsr_mdir_t mdir;
lfsr_did_t did;
lfsr_mtree_pathlookup(&lfs, &path,
&mdir, NULL, &did) => 0;
lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0,
lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}),
LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0;
lfsr_unmount(&lfs) => 0;
// mount
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs_info info;
lfsr_stat(&lfs, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// renaming unknown files should still work, if this would leak
// resources the new type should set a wcompat flag
lfsr_rename(&lfs, "c", "b") => LFS_ERR_NOTDIR;
// check that things look reasonable after renaming/removing
lfsr_stat(&lfs, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
lfsr_unmount(&lfs) => 0;
'''
[cases.test_mount_incompat_unknown_type_mv_isdir]
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_mkdir(&lfs, "c") => 0;
lfsr_unmount(&lfs) => 0;
// change a file's type to something unknown
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
const char *path = "b";
lfsr_mdir_t mdir;
lfsr_did_t did;
lfsr_mtree_pathlookup(&lfs, &path,
&mdir, NULL, &did) => 0;
lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS(
LFSR_RATTR_CAT(
LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0,
lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}),
LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0;
lfsr_unmount(&lfs) => 0;
// mount
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs_info info;
lfsr_stat(&lfs, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_t dir;
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
// renaming unknown files should still work, if this would leak
// resources the new type should set a wcompat flag
lfsr_rename(&lfs, "b", "c") => LFS_ERR_ISDIR;
// check that things look reasonable after renaming/removing
lfsr_stat(&lfs, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_open(&lfs, &dir, "/") => 0;
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS_TYPE_UNKNOWN);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS_TYPE_DIR);
assert(info.size == 0);
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
lfsr_dir_close(&lfs, &dir) => 0;
lfsr_unmount(&lfs) => 0;
'''
# Ok, here's an interesting one, test that we fail if we're
# "out-of-phase", i.e. the mrootanchor has been shifted by a small
# number of blocks.
#
# This can happen if we find the wrong mrootanchor (after, say, a magic
# scan), and risks filesystem corruption. To prevent this, we include 2
# phase bits in cksum tags to detect up to a 3 block shift (the maximum
# number of redund mrootanchors)
#
[cases.test_mount_incompat_out_of_phase]
defines.PHASE = [1, 2, 3, 4]
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
// with some files
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "r2d2",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
char wbuf[256];
strcpy(wbuf, "beep boop");
lfsr_file_write(&lfs, &file, wbuf, strlen(wbuf)) => strlen(wbuf);
lfsr_file_close(&lfs, &file) => 0;
lfsr_file_open(&lfs, &file, "c3po",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
strcpy(wbuf, "we seem to be made to suffer");
lfsr_file_write(&lfs, &file, wbuf, strlen(wbuf)) => strlen(wbuf);
lfsr_file_close(&lfs, &file) => 0;
// shift the filesystem out-of-phase
uint8_t shift_buf[BLOCK_SIZE];
for (lfs_size_t i = 0; i < 4; i++) {
CFG->read(CFG, 4-1-i, 0, shift_buf, BLOCK_SIZE) => 0;
CFG->erase(CFG, 4-1-i + PHASE) => 0;
CFG->prog(CFG, 4-1-i + PHASE, 0, shift_buf, BLOCK_SIZE) => 0;
memset(shift_buf, 0, BLOCK_SIZE);
strcpy((char*)shift_buf,
"these aren't the files you're looking for ;)");
CFG->erase(CFG, 4-1-i) => 0;
CFG->prog(CFG, 4-1-i, 0, shift_buf, BLOCK_SIZE) => 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;
'''