Files
littlefs/tests/test_mount.toml
T
Christopher Haster 673fa7876f Reduced the scope of LFS3_REVDBG/REVNOISE
LFS3_REVDBG introduced a lot of overhead for something I'm not sure
anyone will actually use (I have enough tooling that the state of an
rbyd is rarely a mystery, see dbgbmap.py). That, and we're running out
of flags!

So this reduces LFS3_REVDBG to just store one of "himb" in the first
(lowest) byte of the revision count; information that is easily
available:

  vvvv---- -------- -------- --------
  vvvvrrrr rrrrrr-- -------- --------
  vvvvrrrr rrrrrrnn nnnnnnnn nnnnnnnn
  vvvvrrrr rrrrrrnn nnnnnnnn dddddddd
  '-.''----.----''----.- - - '---.--'
    '------|----------|----------|---- 4-bit relocation revision
           '----------|----------|---- recycle-bits recycle counter
                      '----------|---- pseudorandom noise (if revnoise)
                                 '---- h, i, m, or b (if revdbg)
                             -11-1---  - h = mroot anchor
                             -11-1--1  - i = mroot
                             -11-11-1  - m = mdir
                             -11---1-  - b = btree node

Some other notes:

- Enabled LFS3_REVDBG and LFS3_REVNOISE to work together, now that
  LFS3_REVDBG doesn't consume all unused rev bits.

  Note that LFS3_REVDBG has priority over LFS3_REVNOISE, but _not_
  recycle-bits, etc. Otherwise problems would happen for recycle-bits
  >2^20 (though do we care?).

- Fixed an issue where using the gcksum as a noise source results in
  noise=0 when there is only an mroot. This is due to how we xor out
  the current mdir cksum during an mdir commit.

  Fixed by using gcksum_p instead of gcksum.

- Added missing LFS3_I_REVDBG/REVNOISE flags in the tests, so now you
  can actually run the tests with LFS3_REVDBG/REVNOISE (this probably
  just fell out-of-date at some point).

---

Curiously, despite LFS3_REVDBG/REVNOISE being disabled by default, this
did save some code. I'm guessing the non-tail-call mtree/gbmap commit
functions prevented some level of inlining?:

                 code          stack          ctx
  before:       35964           2280          660
  after:        35964 (+0.0%)   2280 (+0.0%)  660 (+0.0%)

                 code          stack          ctx
  gbmap before: 38940           2296          772
  gbmap after:  38828 (-0.3%)   2296 (+0.0%)  772 (+0.0%)
2025-11-13 01:44:37 -06:00

2157 lines
74 KiB
TOML

# Advanced mount tests
after = ['test_mtree', 'test_trvs']
# test we can mount
[cases.test_mount_simple]
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_unmount(&lfs3) => 0;
'''
# test that various mount flags don't assert and are returned by
# lfs3_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.CKMETAPARITY = [false, true]
defines.CKDATACKSUMS = [false, true]
defines.MKCONSISTENT = [false, true]
defines.RELOOKAHEAD = [false, true]
defines.REGBMAP = [false, true]
defines.COMPACTMETA = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
if = [
'LFS3_IFDEF_YES_REVDBG(REVDBG, LFS3_IFDEF_REVDBG(true, !REVDBG))',
'LFS3_IFDEF_YES_REVNOISE(REVNOISE, LFS3_IFDEF_REVNOISE(true, !REVNOISE))',
'LFS3_IFDEF_CKPROGS(true, !CKPROGS)',
'LFS3_IFDEF_CKFETCHES(true, !CKFETCHES)',
'LFS3_IFDEF_CKMETAPARITY(true, !CKMETAPARITY)',
'LFS3_IFDEF_CKDATACKSUMS(true, !CKDATACKSUMS)',
'!RDONLY || !MKCONSISTENT',
'!RDONLY || !RELOOKAHEAD',
'LFS3_IFDEF_YES_GBMAP(true, !REGBMAP)',
'!RDONLY || !REGBMAP',
'!RDONLY || !COMPACTMETA',
]
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
lfs3_mount(&lfs3,
((RDONLY) ? LFS3_M_RDONLY : LFS3_M_RDWR)
| ((FLUSH) ? LFS3_M_FLUSH : 0)
| ((SYNC) ? LFS3_M_SYNC : 0)
| ((REVDBG) ? LFS3_IFDEF_REVDBG(LFS3_M_REVDBG, -1) : 0)
| ((REVNOISE) ? LFS3_IFDEF_REVNOISE(LFS3_M_REVNOISE, -1) : 0)
| ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0)
| ((CKFETCHES) ? LFS3_IFDEF_CKFETCHES(LFS3_M_CKFETCHES, -1) : 0)
| ((CKMETAPARITY)
? LFS3_IFDEF_CKMETAPARITY(LFS3_M_CKMETAPARITY, -1)
: 0)
| ((CKDATACKSUMS)
? LFS3_IFDEF_CKDATACKSUMS(LFS3_M_CKDATACKSUMS, -1)
: 0)
| ((MKCONSISTENT) ? LFS3_M_MKCONSISTENT : 0)
| ((RELOOKAHEAD) ? LFS3_M_RELOOKAHEAD : 0)
| ((REGBMAP)
? LFS3_IFDEF_GBMAP(LFS3_M_REGBMAP, -1)
: 0)
| ((COMPACTMETA) ? LFS3_M_COMPACTMETA : 0)
| ((CKMETA) ? LFS3_M_CKMETA : 0)
| ((CKDATA) ? LFS3_M_CKDATA : 0),
CFG) => 0;
// lfs3_fs_stat only returns some flags
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
((RDONLY) ? LFS3_I_RDONLY : 0)
| ((FLUSH) ? LFS3_I_FLUSH : 0)
| ((SYNC) ? LFS3_I_SYNC : 0)
| ((REVDBG) ? LFS3_IFDEF_REVDBG(LFS3_I_REVDBG, -1) : 0)
| ((REVNOISE) ? LFS3_IFDEF_REVNOISE(LFS3_I_REVNOISE, -1) : 0)
| ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_I_CKPROGS, -1) : 0)
| ((CKFETCHES) ? LFS3_IFDEF_CKFETCHES(LFS3_I_CKFETCHES, -1) : 0)
| ((CKMETAPARITY)
? LFS3_IFDEF_CKMETAPARITY(LFS3_I_CKMETAPARITY, -1)
: 0)
| ((CKDATACKSUMS)
? LFS3_IFDEF_CKDATACKSUMS(LFS3_I_CKDATACKSUMS, -1)
: 0)
| ((!MKCONSISTENT) ? LFS3_I_MKCONSISTENT : 0)
| ((!RELOOKAHEAD) ? LFS3_I_RELOOKAHEAD : 0)
| ((!COMPACTMETA) ? LFS3_I_COMPACTMETA : 0)
// note ckdata implies ckmeta
| ((!CKMETA && !CKDATA) ? LFS3_I_CKMETA : 0)
| ((!CKDATA) ? LFS3_I_CKDATA : 0)
| LFS3_IFDEF_YES_GBMAP(LFS3_I_GBMAP, 0)));
lfs3_unmount(&lfs3) => 0;
'''
# test that various format flags don't 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.CKMETAPARITY = [false, true]
defines.CKDATACKSUMS = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.GBMAP = [false, true]
if = [
'LFS3_IFDEF_YES_REVDBG(REVDBG, LFS3_IFDEF_REVDBG(true, !REVDBG))',
'LFS3_IFDEF_YES_REVNOISE(REVNOISE, LFS3_IFDEF_REVNOISE(true, !REVNOISE))',
'LFS3_IFDEF_CKPROGS(true, !CKPROGS)',
'LFS3_IFDEF_CKFETCHES(true, !CKFETCHES)',
'LFS3_IFDEF_CKMETAPARITY(true, !CKMETAPARITY)',
'LFS3_IFDEF_CKDATACKSUMS(true, !CKDATACKSUMS)',
'LFS3_IFDEF_GBMAP(true, !GBMAP)',
]
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((REVDBG) ? LFS3_IFDEF_REVDBG(LFS3_F_REVDBG, -1) : 0)
| ((REVNOISE) ? LFS3_IFDEF_REVNOISE(LFS3_F_REVNOISE, -1) : 0)
| ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0)
| ((CKFETCHES) ? LFS3_IFDEF_CKFETCHES(LFS3_F_CKFETCHES, -1) : 0)
| ((CKMETAPARITY)
? LFS3_IFDEF_CKMETAPARITY(LFS3_F_CKMETAPARITY, -1)
: 0)
| ((CKDATACKSUMS)
? LFS3_IFDEF_CKDATACKSUMS(LFS3_F_CKDATACKSUMS, -1)
: 0)
| ((CKMETA) ? LFS3_F_CKMETA : 0)
| ((CKDATA) ? LFS3_F_CKDATA : 0)
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
CFG) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// test that format-only flags are read correctly
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
LFS3_IFDEF_YES_REVDBG(LFS3_I_REVDBG, 0)
| LFS3_IFDEF_YES_REVNOISE(LFS3_I_REVNOISE, 0)
| LFS3_I_MKCONSISTENT
| LFS3_I_RELOOKAHEAD
| LFS3_I_COMPACTMETA
| LFS3_I_CKMETA
| LFS3_I_CKDATA
| LFS3_IFDEF_YES_GBMAP(
LFS3_I_GBMAP,
(GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_I_GBMAP, -1) : 0)));
lfs3_unmount(&lfs3) => 0;
'''
# test that on-mount traversals do what they say they do
[cases.test_mount_t_repoplookahead]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// by default we need a lookahead scan
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
LFS3_IFDEF_YES_REVDBG(LFS3_I_REVDBG, 0)
| LFS3_IFDEF_YES_REVNOISE(LFS3_I_REVNOISE, 0)
| LFS3_I_MKCONSISTENT
| LFS3_I_RELOOKAHEAD
| LFS3_I_COMPACTMETA
| LFS3_I_CKMETA
| LFS3_I_CKDATA
| LFS3_IFDEF_YES_GBMAP(LFS3_I_GBMAP, 0)));
lfs3_unmount(&lfs3) => 0;
// with LFS3_M_RELOOKAHEAD, mount performs a lookahead scan
lfs3_mount(&lfs3,
LFS3_M_RDWR
| LFS3_M_RELOOKAHEAD
| ((CKMETA) ? LFS3_M_CKMETA : 0)
| ((CKDATA) ? LFS3_M_CKDATA : 0),
CFG) => 0;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
LFS3_IFDEF_YES_REVDBG(LFS3_I_REVDBG, 0)
| LFS3_IFDEF_YES_REVNOISE(LFS3_I_REVNOISE, 0)
| LFS3_I_MKCONSISTENT
| LFS3_I_COMPACTMETA
// note ckdata implies ckmeta
| ((!CKMETA && !CKDATA) ? LFS3_I_CKMETA : 0)
| ((!CKDATA) ? LFS3_I_CKDATA : 0)
| LFS3_IFDEF_YES_GBMAP(LFS3_I_GBMAP, 0)));
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_t_repopgbmap]
ifdef = 'LFS3_GBMAP'
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.SIZE = [
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR | LFS3_F_GBMAP, CFG) => 0;
uint32_t prng = 42;
// gbmap is persistant, so by default we _don't_ need a gbmap scan
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
LFS3_IFDEF_YES_REVDBG(LFS3_I_REVDBG, 0)
| LFS3_IFDEF_YES_REVNOISE(LFS3_I_REVNOISE, 0)
| LFS3_I_MKCONSISTENT
| LFS3_I_RELOOKAHEAD
| LFS3_I_COMPACTMETA
| LFS3_I_CKMETA
| LFS3_I_CKDATA
| LFS3_IFDEF_GBMAP(LFS3_I_GBMAP, 0)));
// write to a file
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "jellyfish",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
lfs3_unmount(&lfs3) => 0;
// but if we allocated any blocks our gbmap will be inexhaustive
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
LFS3_IFDEF_YES_REVDBG(LFS3_I_REVDBG, 0)
| LFS3_IFDEF_YES_REVNOISE(LFS3_I_REVNOISE, 0)
| LFS3_I_MKCONSISTENT
| LFS3_I_RELOOKAHEAD
| LFS3_I_REGBMAP
| LFS3_I_COMPACTMETA
| LFS3_I_CKMETA
| LFS3_I_CKDATA
| LFS3_IFDEF_GBMAP(LFS3_I_GBMAP, 0)));
lfs3_unmount(&lfs3) => 0;
// with LFS3_M_REGBMAP, mount performs a gbmap repop
lfs3_mount(&lfs3,
LFS3_M_RDWR
| LFS3_M_RELOOKAHEAD
| LFS3_M_REGBMAP
| ((CKMETA) ? LFS3_M_CKMETA : 0)
| ((CKDATA) ? LFS3_M_CKDATA : 0),
CFG) => 0;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
LFS3_IFDEF_YES_REVDBG(LFS3_I_REVDBG, 0)
| LFS3_IFDEF_YES_REVNOISE(LFS3_I_REVNOISE, 0)
| LFS3_I_MKCONSISTENT
| LFS3_I_COMPACTMETA
// note ckdata implies ckmeta
| ((!CKMETA && !CKDATA) ? LFS3_I_CKMETA : 0)
| ((!CKDATA) ? LFS3_I_CKDATA : 0)
| LFS3_IFDEF_GBMAP(LFS3_I_GBMAP, 0)));
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_t_compactmeta]
defines.RELOOKAHEAD = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.SIZE = [
'FCACHE_SIZE/2',
'2*FCACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
# set compactmeta thresh to minimum
defines.GC_COMPACTMETA_THRESH = 'BLOCK_SIZE/2'
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
uint32_t prng = 42;
// first lets create a compactable filesystem
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// write to our mdir until >gc_compactmeta_thresh full
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "jellyfish",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
// hack, don't use the internals like this
uint8_t wbuf[SIZE];
while ((file.b.h.mdir.r.eoff & 0x7fffffff) <= GC_COMPACTMETA_THRESH) {
lfs3_file_rewind(&lfs3, &file) => 0;
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_sync(&lfs3, &file) => 0;
}
lfs3_file_close(&lfs3, &file) => 0;
lfs3_unmount(&lfs3) => 0;
// by default mount does not compact
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
LFS3_IFDEF_YES_REVDBG(LFS3_I_REVDBG, 0)
| LFS3_IFDEF_YES_REVNOISE(LFS3_I_REVNOISE, 0)
| LFS3_I_MKCONSISTENT
| LFS3_I_RELOOKAHEAD
| LFS3_IFDEF_YES_GBMAP(
(SIZE >= BLOCK_SIZE/4) ? LFS3_I_REGBMAP : 0,
0)
| LFS3_I_COMPACTMETA
| LFS3_I_CKMETA
| LFS3_I_CKDATA
| LFS3_IFDEF_YES_GBMAP(LFS3_I_GBMAP, 0)));
lfs3_unmount(&lfs3) => 0;
// with LFS3_M_COMPACTMETA, mount compacts any uncompacted blocks
lfs3_mount(&lfs3,
LFS3_M_RDWR
| LFS3_M_COMPACTMETA
| ((RELOOKAHEAD) ? LFS3_M_RELOOKAHEAD : 0)
| ((CKMETA) ? LFS3_M_CKMETA : 0)
| ((CKDATA) ? LFS3_M_CKDATA : 0),
CFG) => 0;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
LFS3_IFDEF_YES_REVDBG(LFS3_I_REVDBG, 0)
| LFS3_IFDEF_YES_REVNOISE(LFS3_I_REVNOISE, 0)
| LFS3_I_MKCONSISTENT
| ((!RELOOKAHEAD) ? LFS3_I_RELOOKAHEAD : 0)
| LFS3_IFDEF_YES_GBMAP(
(SIZE >= BLOCK_SIZE/4) ? LFS3_I_REGBMAP : 0,
0)
// note ckdata implies ckmeta
| ((!CKMETA && !CKDATA) ? LFS3_I_CKMETA : 0)
| ((!CKDATA) ? LFS3_I_CKDATA : 0)
| LFS3_IFDEF_YES_GBMAP(LFS3_I_GBMAP, 0)));
// mdir should have been compacted
lfs3_file_open(&lfs3, &file, "jellyfish", LFS3_O_RDONLY) => 0;
assert((file.b.h.mdir.r.eoff & 0x7fffffff) <= GC_COMPACTMETA_THRESH);
// check we can still read the file
uint8_t rbuf[SIZE];
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
lfs3_file_close(&lfs3, &file) => 0;
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_t_mkconsistent]
defines.RELOOKAHEAD = [false, true]
defines.COMPACTMETA = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.SIZE = 'FCACHE_SIZE/2'
# <=2 => grm-able
# >2 => requires orphans
defines.ORPHANS = [0, 1, 2, 3, 100]
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
uint32_t prng = 42;
// first lets create some orphans
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// create two files
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "cuttlefish",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
uint8_t wbuf1[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfs3_file_write(&lfs3, &file, wbuf1, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "octopus",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
uint8_t wbuf2[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfs3_file_write(&lfs3, &file, wbuf2, SIZE) => SIZE;
lfs3_file_close(&lfs3, &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)
//
lfs3_file_t orphans[ORPHANS];
for (lfs3_size_t i = 0; i < ORPHANS; i++) {
char name[256];
sprintf(name, "jellyfish%03x", i);
lfs3_file_open(&lfs3, &orphans[i], name,
LFS3_O_WRONLY
| LFS3_O_CREAT
| LFS3_O_EXCL
| LFS3_O_DESYNC) => 0;
}
for (lfs3_size_t i = 0; i < ORPHANS; i++) {
lfs3_file_close(&lfs3, &orphans[i]) => 0;
}
lfs3_unmount(&lfs3) => 0;
// by default we clean up orphans lazily
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
struct lfs3_fsinfo fsinfo;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
LFS3_IFDEF_YES_REVDBG(LFS3_I_REVDBG, 0)
| LFS3_IFDEF_YES_REVNOISE(LFS3_I_REVNOISE, 0)
| LFS3_I_MKCONSISTENT
| LFS3_I_RELOOKAHEAD
| LFS3_IFDEF_YES_GBMAP(
(ORPHANS >= 100) ? LFS3_I_REGBMAP : 0,
0)
| LFS3_I_COMPACTMETA
| LFS3_I_CKMETA
| LFS3_I_CKDATA
| LFS3_IFDEF_YES_GBMAP(LFS3_I_GBMAP, 0)));
lfs3_unmount(&lfs3) => 0;
// with LFS3_M_MKCONSISTENT, mount cleans up orphans eagerly
lfs3_mount(&lfs3,
LFS3_M_RDWR
| LFS3_M_MKCONSISTENT
| ((RELOOKAHEAD) ? LFS3_M_RELOOKAHEAD : 0)
| ((COMPACTMETA) ? LFS3_M_COMPACTMETA : 0)
| ((CKMETA) ? LFS3_M_CKMETA : 0)
| ((CKDATA) ? LFS3_M_CKDATA : 0),
CFG) => 0;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
LFS3_IFDEF_YES_REVDBG(LFS3_I_REVDBG, 0)
| LFS3_IFDEF_YES_REVNOISE(LFS3_I_REVNOISE, 0)
| ((!RELOOKAHEAD) ? LFS3_I_RELOOKAHEAD : 0)
| LFS3_IFDEF_YES_GBMAP(
(ORPHANS >= 100) ? LFS3_I_REGBMAP : 0,
0)
| ((!COMPACTMETA) ? LFS3_I_COMPACTMETA : 0)
// note ckdata implies ckmeta
| ((!CKMETA && !CKDATA) ? LFS3_I_CKMETA : 0)
| ((!CKDATA) ? LFS3_I_CKDATA : 0)
| LFS3_IFDEF_YES_GBMAP(LFS3_I_GBMAP, 0)));
// check we can still read the files
lfs3_file_open(&lfs3, &file, "cuttlefish", LFS3_O_RDONLY) => 0;
uint8_t rbuf[SIZE];
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf1, SIZE) == 0);
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "octopus", LFS3_O_RDONLY) => 0;
lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE;
assert(memcmp(rbuf, wbuf2, SIZE) == 0);
lfs3_file_close(&lfs3, &file) => 0;
lfs3_unmount(&lfs3) => 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',
'FCACHE_SIZE/2',
'2*FCACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
lfs3_block_t i = 0;
while (true) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// create an interesting filesystem
uint32_t prng = 42;
for (lfs3_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "squid%03x", i);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
}
// traverse to find blocks
lfs3_trv_t trv;
lfs3_trv_open(&lfs3, &trv, LFS3_T_RDONLY) => 0;
lfs3_block_t k = 0;
for (lfs3_block_t j = 0;; j++) {
assert(j < 2*BLOCK_COUNT);
struct lfs3_tinfo tinfo;
int err = lfs3_trv_read(&lfs3, &trv, &tinfo);
assert(!err || err == LFS3_ERR_NOENT);
if (err == LFS3_ERR_NOENT) {
lfs3_trv_close(&lfs3, &trv) => 0;
lfs3_unmount(&lfs3) => 0;
goto done;
}
// this gets a bit tricky be cause we need to clobber both
// blocks in mdir pairs
if (tinfo.btype == LFS3_BTYPE_MDIR
|| tinfo.btype == LFS3_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 != LFS3_BTYPE_MDIR || k == i+1) {
i += (tinfo.btype == LFS3_BTYPE_MDIR) ? 2 : 1;
lfs3_trv_close(&lfs3, &trv) => 0;
lfs3_unmount(&lfs3) => 0;
goto clobbered;
}
}
k += 1;
}
}
clobbered:;
// mount with LFS3_M_CKMETA, we should detect clobbered blocks
lfs3_mount(&lfs3,
LFS3_M_RDWR
| LFS3_M_CKMETA,
CFG) => LFS3_ERR_CORRUPT;
}
done:;
'''
[cases.test_mount_t_ckdata]
defines.N = [1, 2, 4, 8, 16, 32, 64]
defines.SIZE = [
'0',
'FCACHE_SIZE/2',
'2*FCACHE_SIZE',
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
lfs3_block_t i = 0;
while (true) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// create an interesting filesystem
uint32_t prng = 42;
for (lfs3_size_t i = 0; i < N; i++) {
char name[256];
sprintf(name, "squid%03x", i);
uint8_t wbuf[SIZE];
for (lfs3_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, name,
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE;
lfs3_file_close(&lfs3, &file) => 0;
}
// traverse to find blocks
lfs3_trv_t trv;
lfs3_trv_open(&lfs3, &trv, LFS3_T_RDONLY) => 0;
lfs3_block_t k = 0;
for (lfs3_block_t j = 0;; j++) {
assert(j < 2*BLOCK_COUNT);
struct lfs3_tinfo tinfo;
int err = lfs3_trv_read(&lfs3, &trv, &tinfo);
assert(!err || err == LFS3_ERR_NOENT);
if (err == LFS3_ERR_NOENT) {
lfs3_trv_close(&lfs3, &trv) => 0;
lfs3_unmount(&lfs3) => 0;
goto done;
}
// this gets a bit tricky be cause we need to clobber both
// blocks in mdir pairs
if (tinfo.btype == LFS3_BTYPE_MDIR
|| tinfo.btype == LFS3_BTYPE_BTREE
|| tinfo.btype == LFS3_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 != LFS3_BTYPE_MDIR || k == i+1) {
i += (tinfo.btype == LFS3_BTYPE_MDIR) ? 2 : 1;
lfs3_trv_close(&lfs3, &trv) => 0;
lfs3_unmount(&lfs3) => 0;
goto clobbered;
}
}
k += 1;
}
}
clobbered:;
// mount with LFS3_M_CKDATA, we should detect clobbered blocks
//
// note LFS3_M_CKDATA implies LFS3_M_CKMETA
lfs3_mount(&lfs3,
LFS3_M_RDWR
| LFS3_M_CKDATA,
CFG) => LFS3_ERR_CORRUPT;
}
done:;
'''
## incompatiblity tests ##
# test that we fail if we find no magic
[cases.test_mount_incompat_no_magic]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// delete the magic string
//
// note we're messing around with internals to do this! this
// is not a user API
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR(
LFS3_TAG_RM | LFS3_TAG_MAGIC, 0))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_CORRUPT;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_CORRUPT;
'''
# test that we fail if we find bad magic
[cases.test_mount_incompat_bad_magic]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// tweak the magic string
//
// note we're messing around with internals to do this! this
// is not a user API
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_BUF(
LFS3_TAG_MAGIC, 0,
"lottlefs", 8))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_CORRUPT;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_CORRUPT;
'''
# test that we fail to mount after a major version bump
[cases.test_mount_incompat_major]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// bump the major version
//
// note we're messing around with internals to do this! this
// is not a user API
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_BUF(
LFS3_TAG_VERSION, 0,
((const uint8_t[2]){
LFS3_DISK_VERSION_MAJOR+1,
0}), 2))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP;
'''
# test that we fail to mount after a minor version bump
[cases.test_mount_incompat_minor]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// bump the minor version
//
// note we're messing around with internals to do this! this
// is not a user API
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_BUF(
LFS3_TAG_VERSION, 0,
((const uint8_t[2]){
LFS3_DISK_VERSION_MAJOR,
LFS3_DISK_VERSION_MINOR+1}), 2))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP;
'''
# test that we fail to mount incompatible rcompat flags
[cases.test_mount_incompat_rcompat]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_LE32(
LFS3_TAG_RCOMPAT, 0,
lfs3_rcompat(&lfs3)
| LFS3_RCOMPAT_NONSTANDARD))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP;
'''
# test that we fail to mount incompatible wcompat flags
[cases.test_mount_incompat_wcompat]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_LE32(
LFS3_TAG_WCOMPAT, 0,
lfs3_wcompat(&lfs3)
| LFS3_WCOMPAT_NONSTANDARD))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP;
// but we _can_ mount readonly
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0;
lfs3_unmount(&lfs3) => 0;
'''
# test that an incompatible ocompat flag is a noop
[cases.test_mount_incompat_ocompat]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_LE32(
LFS3_TAG_OCOMPAT, 0,
lfs3_ocompat(&lfs3)
| LFS3_OCOMPAT_NONSTANDARD))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should _not_ fail, ocompat should always be ignored
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0;
lfs3_unmount(&lfs3) => 0;
'''
# test that we fail to mount rdonly images
[cases.test_mount_incompat_rdonly]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_LE32(
LFS3_TAG_WCOMPAT, 0,
lfs3_wcompat(&lfs3)
| LFS3_WCOMPAT_RDONLY))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP;
// but we _can_ mount readonly
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0;
lfs3_unmount(&lfs3) => 0;
'''
# test that we fail to mount wronly images
[cases.test_mount_incompat_wronly]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_LE32(
LFS3_TAG_RCOMPAT, 0,
lfs3_rcompat(&lfs3)
| LFS3_RCOMPAT_WRONLY))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP;
'''
# these are just a bit harder to detect
[cases.test_mount_incompat_rcompat_overflow]
defines.OVERFLOW = 72
defines.FLAG = 'range(72)'
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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);
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
uint8_t rcompat_buf[LFS3_LE32_DSIZE];
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_RCOMPAT, 0,
lfs3_data_fromle32(lfs3_rcompat(&lfs3), rcompat_buf),
LFS3_DATA_BUF(overflow, sizeof(overflow))))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP;
'''
[cases.test_mount_incompat_wcompat_overflow]
defines.OVERFLOW = 72
defines.FLAG = 'range(72)'
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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);
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
uint8_t wcompat_buf[LFS3_LE32_DSIZE];
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_WCOMPAT, 0,
lfs3_data_fromle32(lfs3_wcompat(&lfs3), wcompat_buf),
LFS3_DATA_BUF(overflow, sizeof(overflow))))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP;
// but we _can_ mount readonly
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0;
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_incompat_ocompat_overflow]
defines.OVERFLOW = 72
defines.FLAG = 'range(72)'
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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);
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
uint8_t ocompat_buf[LFS3_LE32_DSIZE];
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_OCOMPAT, 0,
lfs3_data_fromle32(lfs3_ocompat(&lfs3), ocompat_buf),
LFS3_DATA_BUF(overflow, sizeof(overflow))))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should _not_ fail, ocompat should always be ignored
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0;
lfs3_unmount(&lfs3) => 0;
'''
# but just appending zeros is _not_ an error
[cases.test_mount_incompat_rcompat_padding]
defines.OVERFLOW = 72
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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));
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
uint8_t rcompat_buf[LFS3_LE32_DSIZE];
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_RCOMPAT, 0,
lfs3_data_fromle32(lfs3_rcompat(&lfs3), rcompat_buf),
LFS3_DATA_BUF(overflow, sizeof(overflow))))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should _not_ fail, extra zeros should be ignored
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0;
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_incompat_wcompat_padding]
defines.OVERFLOW = 72
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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));
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
uint8_t wcompat_buf[LFS3_LE32_DSIZE];
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_WCOMPAT, 0,
lfs3_data_fromle32(lfs3_wcompat(&lfs3), wcompat_buf),
LFS3_DATA_BUF(overflow, sizeof(overflow))))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should _not_ fail, extra zeros should be ignored
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0;
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_incompat_ocompat_padding]
defines.OVERFLOW = 72
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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));
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
uint8_t ocompat_buf[LFS3_LE32_DSIZE];
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_OCOMPAT, 0,
lfs3_data_fromle32(lfs3_ocompat(&lfs3), ocompat_buf),
LFS3_DATA_BUF(overflow, sizeof(overflow))))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should _not_ fail, extra zeros should be ignored
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_unmount(&lfs3) => 0;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0;
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_GEOMETRY(
LFS3_TAG_GEOMETRY, 0,
(&(lfs3_geometry_t){
INC_BLOCK_SIZE,
BLOCK_COUNT})))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_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 = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_GEOMETRY(
LFS3_TAG_GEOMETRY, 0,
(&(lfs3_geometry_t){
BLOCK_SIZE,
INC_BLOCK_COUNT})))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP;
'''
# test that we fail to mount after incompatible name limit
[cases.test_mount_incompat_name_limit]
defines.INC_NAME_LIMIT = ['LFS3_NAME_MAX*2']
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_LEB128(
LFS3_TAG_NAMELIMIT, 0,
INC_NAME_LIMIT))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP;
'''
# test that we fail to mount after incompatible file limit
[cases.test_mount_incompat_file_limit]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_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
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
uint8_t file_limit_buf[LFS3_LEB128_DSIZE];
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_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
LFS3_DATA_BUF("\xff", 1),
lfs3_data_fromleb128(LFS3_FILE_MAX, file_limit_buf)))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP;
'''
# test what happens if we find an unknown config
[cases.test_mount_incompat_unknown_config]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// create an unknown config
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR_BUF(
LFS3_TAG_CONFIG + 0x42, 0,
"oh no!", strlen("oh no!")))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount should now fail
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP;
'''
# test what happens if we find an unknown file type
[cases.test_mount_incompat_unknown_type]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// create some files
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "a",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"hi a!", strlen("hi a!")) => strlen("hi a!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "b",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"oh no!", strlen("oh no!")) => strlen("oh no!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "c",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"hi c!", strlen("hi c!")) => strlen("hi c!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_unmount(&lfs3) => 0;
// change a file's type to something unknown
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
const char *path = "b";
lfs3_mdir_t mdir;
lfs3_did_t did;
lfs3_mtree_pathlookup(&lfs3, &path,
&mdir, &did) => LFS3_TAG_REG;
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}),
LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs3_info info;
lfs3_stat(&lfs3, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_t dir;
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
// open/mkdir should error
lfs3_file_open(&lfs3, &file, "b",
LFS3_O_RDONLY) => LFS3_ERR_NOTSUP;
lfs3_file_open(&lfs3, &file, "b",
LFS3_O_WRONLY | LFS3_O_CREAT) => LFS3_ERR_NOTSUP;
lfs3_mkdir(&lfs3, "b") => LFS3_ERR_EXIST;
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_incompat_unknown_type_rm]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// create some files
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "a",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"hi a!", strlen("hi a!")) => strlen("hi a!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "b",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"oh no!", strlen("oh no!")) => strlen("oh no!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "c",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"hi c!", strlen("hi c!")) => strlen("hi c!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_unmount(&lfs3) => 0;
// change a file's type to something unknown
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
const char *path = "b";
lfs3_mdir_t mdir;
lfs3_did_t did;
lfs3_mtree_pathlookup(&lfs3, &path,
&mdir, &did) => LFS3_TAG_REG;
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}),
LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs3_info info;
lfs3_stat(&lfs3, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_t dir;
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
// removing unknown files should still work, if this would leak
// resources the new type should set a wcompat flag
lfs3_remove(&lfs3, "b") => 0;
// check that things look reasonable
lfs3_stat(&lfs3, "b", &info) => LFS3_ERR_NOENT;
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_incompat_unknown_type_mv_src]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// create some files
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "a",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"hi a!", strlen("hi a!")) => strlen("hi a!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "b",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"oh no!", strlen("oh no!")) => strlen("oh no!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "c",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"hi c!", strlen("hi c!")) => strlen("hi c!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_unmount(&lfs3) => 0;
// change a file's type to something unknown
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
const char *path = "b";
lfs3_mdir_t mdir;
lfs3_did_t did;
lfs3_mtree_pathlookup(&lfs3, &path,
&mdir, &did) => LFS3_TAG_REG;
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}),
LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs3_info info;
lfs3_stat(&lfs3, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_t dir;
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
// renaming unknown files should still work, if this would leak
// resources the new type should set a wcompat flag
lfs3_rename(&lfs3, "b", "c") => 0;
// check that things look reasonable after renaming/removing
lfs3_stat(&lfs3, "c", &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_incompat_unknown_type_mv_dst]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// create some files
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "a",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"hi a!", strlen("hi a!")) => strlen("hi a!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "b",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"oh no!", strlen("oh no!")) => strlen("oh no!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "c",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"hi c!", strlen("hi c!")) => strlen("hi c!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_unmount(&lfs3) => 0;
// change a file's type to something unknown
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
const char *path = "b";
lfs3_mdir_t mdir;
lfs3_did_t did;
lfs3_mtree_pathlookup(&lfs3, &path,
&mdir, &did) => LFS3_TAG_REG;
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}),
LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs3_info info;
lfs3_stat(&lfs3, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_t dir;
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
// renaming unknown files should still work, if this would leak
// resources the new type should set a wcompat flag
lfs3_rename(&lfs3, "c", "b") => 0;
// check that things look reasonable after renaming/removing
lfs3_stat(&lfs3, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_incompat_unknown_type_mv_src_dst]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// create some files
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "a",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"hi a!", strlen("hi a!")) => strlen("hi a!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "b",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"oh no!", strlen("oh no!")) => strlen("oh no!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "c",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"oh hi!", strlen("oh hi!")) => strlen("oh hi!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_unmount(&lfs3) => 0;
// change a file's type to something unknown
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
const char *path = "b";
lfs3_mdir_t mdir;
lfs3_did_t did;
lfs3_mtree_pathlookup(&lfs3, &path,
&mdir, &did) => LFS3_TAG_REG;
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}),
LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0;
path = "c";
lfs3_mtree_pathlookup(&lfs3, &path,
&mdir, &did) => LFS3_TAG_REG;
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}),
LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs3_info info;
lfs3_stat(&lfs3, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_t dir;
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
// renaming unknown files should still work, if this would leak
// resources the new type should set a wcompat flag
lfs3_rename(&lfs3, "b", "c") => 0;
// check that things look reasonable after renaming/removing
lfs3_stat(&lfs3, "c", &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_incompat_unknown_type_mv_noop]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// create some files
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "a",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"hi a!", strlen("hi a!")) => strlen("hi a!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "b",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"oh no!", strlen("oh no!")) => strlen("oh no!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "c",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"hi c!", strlen("hi c!")) => strlen("hi c!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_unmount(&lfs3) => 0;
// change a file's type to something unknown
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
const char *path = "b";
lfs3_mdir_t mdir;
lfs3_did_t did;
lfs3_mtree_pathlookup(&lfs3, &path,
&mdir, &did) => LFS3_TAG_REG;
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}),
LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs3_info info;
lfs3_stat(&lfs3, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_t dir;
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
// renaming unknown files should still work, if this would leak
// resources the new type should set a wcompat flag
lfs3_rename(&lfs3, "b", "b") => 0;
// check that things look reasonable after renaming/removing
lfs3_stat(&lfs3, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi c!"));
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_incompat_unknown_type_mv_notdir]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// create some files
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "a",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"hi a!", strlen("hi a!")) => strlen("hi a!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "b",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"oh no!", strlen("oh no!")) => strlen("oh no!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_mkdir(&lfs3, "c") => 0;
lfs3_unmount(&lfs3) => 0;
// change a file's type to something unknown
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
const char *path = "b";
lfs3_mdir_t mdir;
lfs3_did_t did;
lfs3_mtree_pathlookup(&lfs3, &path,
&mdir, &did) => LFS3_TAG_REG;
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}),
LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs3_info info;
lfs3_stat(&lfs3, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_t dir;
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
// renaming unknown files should still work, if this would leak
// resources the new type should set a wcompat flag
lfs3_rename(&lfs3, "c", "b") => LFS3_ERR_NOTDIR;
// check that things look reasonable after renaming/removing
lfs3_stat(&lfs3, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_incompat_unknown_type_mv_isdir]
in = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// create some files
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "a",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"hi a!", strlen("hi a!")) => strlen("hi a!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "b",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
lfs3_file_write(&lfs3, &file,
"oh no!", strlen("oh no!")) => strlen("oh no!");
lfs3_file_close(&lfs3, &file) => 0;
lfs3_mkdir(&lfs3, "c") => 0;
lfs3_unmount(&lfs3) => 0;
// change a file's type to something unknown
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
const char *path = "b";
lfs3_mdir_t mdir;
lfs3_did_t did;
lfs3_mtree_pathlookup(&lfs3, &path,
&mdir, &did) => LFS3_TAG_REG;
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
LFS3_RATTR_CAT(
LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}),
LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0;
lfs3_unmount(&lfs3) => 0;
// mount
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
// our file should appear as an unknown type
struct lfs3_info info;
lfs3_stat(&lfs3, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_t dir;
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
// renaming unknown files should still work, if this would leak
// resources the new type should set a wcompat flag
lfs3_rename(&lfs3, "b", "c") => LFS3_ERR_ISDIR;
// check that things look reasonable after renaming/removing
lfs3_stat(&lfs3, "b", &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_open(&lfs3, &dir, "/") => 0;
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, ".") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "..") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "a") == 0);
assert(info.type == LFS3_TYPE_REG);
assert(info.size == strlen("hi a!"));
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "b") == 0);
assert(info.type == LFS3_TYPE_UNKNOWN);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => 0;
assert(strcmp(info.name, "c") == 0);
assert(info.type == LFS3_TYPE_DIR);
assert(info.size == 0);
lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT;
lfs3_dir_close(&lfs3, &dir) => 0;
lfs3_unmount(&lfs3) => 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 = 'lfs3.c'
code = '''
// create a superblock
lfs3_t lfs3;
lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0;
// with some files
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_file_t file;
lfs3_file_open(&lfs3, &file, "r2d2",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
char wbuf[256];
strcpy(wbuf, "beep boop");
lfs3_file_write(&lfs3, &file, wbuf, strlen(wbuf)) => strlen(wbuf);
lfs3_file_close(&lfs3, &file) => 0;
lfs3_file_open(&lfs3, &file, "c3po",
LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0;
strcpy(wbuf, "we seem to be made to suffer");
lfs3_file_write(&lfs3, &file, wbuf, strlen(wbuf)) => strlen(wbuf);
lfs3_file_close(&lfs3, &file) => 0;
// shift the filesystem out-of-phase
uint8_t shift_buf[BLOCK_SIZE];
for (lfs3_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
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_CORRUPT;
lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_CORRUPT;
'''