Files
littlefs/tests/test_mount.toml
T
Christopher Haster 35a1ac93fa preerase: Adopted PREERASE in tests, fixes, relaxed gbmap zeroing
Still needs testing with LFS3_GC=1, and tests that intentionally test
preerasing, but this should at least fix most fsinfo.flag related issues.

Despite no intentional preerase testing, this already found a number of
issues. Most importantly: our ckpoint-agnostic gbmap zeroing was never
going to work with preerasing!

Main fixes:

- Adopted conservative zeroing of gbmap during rebuilds

  This was the biggest change. Our previous lfs3_gbmap_zero impl was
  never going to work with preerasing because it unconditionally
  cleared BMERASED ranges.

  Not entirely wrong, but a big waste of any preerase work.

  It also causes the whole system to lock up when LFS3_GC_LOOKAHEAD and
  LFS3_GC_PREERASE fight to make progress. With LFS3_GC_LOOKAHEAD
  clearing BMERASED ranges, and LFS3_GC_PREERASE clearing the lookahead
  flag, nothing gets done!

  ---

  The fix was to rewrite lfs3_gbmap_zero[unknown] to only zero BMERASED
  (and BMINUSE, though this isn't strictly necessary) ranges in the
  unknown window. This keeps any known-preerased blocks around and
  avoids throwing that information away.

  This is also slightly different from BMBAD ranges, which we want to
  keep around forever, even if in the unknown window.

  Whether or not was should limit zeroing BMINUSE ranges is an
  interesting question. If we already need this logic, I think extending
  it to BMINUSE is a good idea because of how it limits gbmap commits
  during rebuilds:

  - Unfortunately, gbmap rebuilds require quite a few commits to both
    (1) zero gbmap state, and (2) set all the in-use blocks to BMINUSE.
    This is especially concerning when relying on aggressive gc, such as
    gc_lookgbmap=-1, which may trigger rebuilds when only a couple
    blocks are allocated.

    Limiting zeroing limits gbmap commits in two ways:

    1. We only need to update ranges in the unknown window, which
       shrinks with more aggressive gbmap rebuilds.

    2. By not clearing BMINUSE ranges in the known window, populating
       those blocks during the lookgbmap scan should be a noop.

    Together, this hopefully makes aggressive gbmap rebuilds relatively
    cheap, at least in terms of progs/erases.

  - It's slightly simpler if BMINUSE and BMERASED are handled the same.

- Actually increment the preeraser known window in lfs3_alloc_inc.

  Otherwise our estimated preeraser.count only ever increases! There was
  some trickiness to make sure preeraser.count is only ever decremented
  when allocating erased blocks, but fortunately lfs3->gbmap.ecksum's
  existence can tell us that information.

- Reset preeraser state during gbmap rebuilds.

  Also necessary to avoid unbounded preeraser.count. The simplest
  solution is to zero the preeraser, which forces it to rescan the gbmap
  for BMERASED ranges. The preeraser strictly avoids redundant erases.
  This does require extra gbmap lookups during LFS3_GC_PREERASE, but
  that's not the end of the world.

- Avoid erasing corrupted preerased blocks in case there's other
  preerased blocks available in our gbmap.

  This happens when the ecksum check fails, implying a prog was
  attempted, but power was lost.

  Before this change (the continue in lfs3_alloc_:11244), we were
  erasing corrupt ecksums, which is not _wrong_, but sort of defeats the
  purpose of prerasing. Skipping the block and trying another:

  1. Is better in terms of wear-leveling (try not to double erase!)
  2. Minimizes latency if we have other preerased blocks we can use

- Made lfs3_fs_gc_ preerasing actually conditional on the
  LFS3_GC_PREERASE flag.

  Before, lfs3_fs_gc_ was unconditionally preerasing, which is wrong!

---

Currently passing:

  LFS3_YES_GBMAP=1 \
          LFS3_YES_REVPERTURB=1 \
          LFS3_PREERASE=1 \
          make test-runner -j \
      & ./scripts/test.py -j -b

Other test fixes:

- Mostly just adding the necessary LFS3_I_PREERASE flags for all
  lfs3_fs_stat calls.

- LFS3_I_PREERASE and LFS3_I_LOOKAHEAD can interact in funny ways. Just
  needed testing.

- lfs3_trv_t doesn't actually do anything with LFS3_T_PREERASE, so we
  shouldn't try to test it.

- Adopted lfs3_fs_ck instead of explicit traversals where possible.

- test_badblocks_*_btree_many was still running with LFS3_YES_GBMAP, but
  it shouldn't be. The gbmap state is undefined during internal btree
  tests.

Code changes:

                    code          stack          ctx
  before:          35260           2136          660
  after:           35260 (+0.0%)   2136 (+0.0%)  660 (+0.0%)

                    code          stack          ctx
  gbmap before:    38492           2144          776
  gbmap after:     38560 (+0.2%)   2144 (+0.0%)  776 (+0.0%)

                    code          stack          ctx
  preerase before: 39036           2168          796
  preerase after:  39168 (+0.3%)   2168 (+0.0%)  796 (+0.0%)
2026-01-09 00:03:37 -06:00

2317 lines
79 KiB
TOML

# Advanced mount tests
after = ['test_mtree', 'test_trvs']
# Test both with and without the gbmap if available
defines.GBMAP = [false, true]
if = 'LFS3_IFYES_GBMAP(GBMAP, true, !GBMAP)'
# test we can mount
[cases.test_mount_simple]
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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.REVPERTURB = [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.LOOKAHEAD = [false, true]
defines.PREERASE = [false, true]
defines.COMPACT = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
if = [
'LFS3_IFYES_REVPERTURB(REVPERTURB, true, !REVPERTURB)',
'LFS3_IFYES_REVNOISE(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 || !LOOKAHEAD',
'LFS3_IFDEF_PREERASE(true, !PREERASE)',
'GBMAP || !PREERASE',
'!RDONLY || !PREERASE',
'!RDONLY || !COMPACT',
]
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
CFG) => 0;
lfs3_mount(&lfs3,
((RDONLY) ? LFS3_M_RDONLY : LFS3_M_RDWR)
| ((FLUSH) ? LFS3_M_FLUSH : 0)
| ((SYNC) ? LFS3_M_SYNC : 0)
| ((REVPERTURB)
? LFS3_IFDEF_REVPERTURB(LFS3_M_REVPERTURB, -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)
| ((LOOKAHEAD) ? LFS3_M_LOOKAHEAD : 0)
| ((PREERASE) ? LFS3_IFDEF_PREERASE(LFS3_M_PREERASE, -1) : 0)
| ((COMPACT) ? LFS3_M_COMPACT : 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)
| ((REVPERTURB)
? LFS3_IFDEF_REVPERTURB(LFS3_I_REVPERTURB, -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)
| ((!LOOKAHEAD && (!GBMAP || PREERASE))
? LFS3_I_LOOKAHEAD
: 0)
| LFS3_IFDEF_PREERASE(
(GBMAP && !PREERASE) ? LFS3_I_PREERASE : 0,
0)
| ((!COMPACT) ? LFS3_I_COMPACT : 0)
// note ckdata implies ckmeta
| ((!CKMETA && !CKDATA) ? LFS3_I_CKMETA : 0)
| ((!CKDATA) ? LFS3_I_CKDATA : 0)
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_I_GBMAP, -1) : 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.REVPERTURB = [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.LOOKAHEAD = [false, true]
defines.PREERASE = [false, true]
defines.COMPACT = [false, true]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
if = [
'LFS3_IFYES_REVPERTURB(REVPERTURB, true, !REVPERTURB)',
'LFS3_IFYES_REVNOISE(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_PREERASE(true, !PREERASE)',
'GBMAP || !PREERASE',
]
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((REVPERTURB)
? LFS3_IFDEF_REVPERTURB(LFS3_F_REVPERTURB, -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)
| ((MKCONSISTENT) ? LFS3_F_MKCONSISTENT : 0)
| ((LOOKAHEAD) ? LFS3_F_LOOKAHEAD : 0)
| ((PREERASE) ? LFS3_IFDEF_PREERASE(LFS3_F_PREERASE, -1) : 0)
| ((COMPACT) ? LFS3_F_COMPACT : 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_IFYES_REVPERTURB(LFS3_I_REVPERTURB, 0, 0)
| LFS3_IFYES_REVNOISE(LFS3_I_REVNOISE, 0, 0)
| LFS3_I_MKCONSISTENT
| ((!GBMAP || (PREERASE && !LOOKAHEAD))
? LFS3_I_LOOKAHEAD
: 0)
| LFS3_IFDEF_PREERASE((GBMAP) ? LFS3_I_PREERASE : 0, 0)
| LFS3_I_COMPACT
| LFS3_I_CKMETA
| LFS3_I_CKDATA
| ((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_lookahead]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
CFG) => 0;
// by default we need a lookahead scan
//
// unless we have a gbmap, the gbmap is persistent so usually
// _doesn't_ 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_IFYES_REVPERTURB(LFS3_I_REVPERTURB, 0, 0)
| LFS3_IFYES_REVNOISE(LFS3_I_REVNOISE, 0, 0)
| LFS3_I_MKCONSISTENT
| ((!GBMAP) ? LFS3_I_LOOKAHEAD : 0)
| LFS3_IFDEF_PREERASE((GBMAP) ? LFS3_I_PREERASE : 0, 0)
| LFS3_I_COMPACT
| LFS3_I_CKMETA
| LFS3_I_CKDATA
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_I_GBMAP, -1) : 0)));
lfs3_unmount(&lfs3) => 0;
// with LFS3_M_LOOKAHEAD, mount performs a lookahead scan
lfs3_mount(&lfs3,
LFS3_M_RDWR
| LFS3_M_LOOKAHEAD
| ((CKMETA) ? LFS3_M_CKMETA : 0)
| ((CKDATA) ? LFS3_M_CKDATA : 0),
CFG) => 0;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
LFS3_IFYES_REVPERTURB(LFS3_I_REVPERTURB, 0, 0)
| LFS3_IFYES_REVNOISE(LFS3_I_REVNOISE, 0, 0)
| LFS3_I_MKCONSISTENT
| LFS3_IFDEF_PREERASE((GBMAP) ? LFS3_I_PREERASE : 0, 0)
| LFS3_I_COMPACT
// note ckdata implies ckmeta
| ((!CKMETA && !CKDATA) ? LFS3_I_CKMETA : 0)
| ((!CKDATA) ? LFS3_I_CKDATA : 0)
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_I_GBMAP, -1) : 0)));
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_t_lookgbmap]
defines.CKMETA = [false, true]
defines.CKDATA = [false, true]
defines.SIZE = [
'BLOCK_SIZE/2',
'BLOCK_SIZE',
'2*BLOCK_SIZE',
'8*BLOCK_SIZE',
]
ifdef = 'LFS3_GBMAP'
if = 'GBMAP'
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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_IFYES_REVPERTURB(LFS3_I_REVPERTURB, 0, 0)
| LFS3_IFYES_REVNOISE(LFS3_I_REVNOISE, 0, 0)
| LFS3_I_MKCONSISTENT
| LFS3_IFDEF_PREERASE((GBMAP) ? LFS3_I_PREERASE : 0, 0)
| LFS3_I_COMPACT
| LFS3_I_CKMETA
| LFS3_I_CKDATA
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_I_GBMAP, -1) : 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_IFYES_REVPERTURB(LFS3_I_REVPERTURB, 0, 0)
| LFS3_IFYES_REVNOISE(LFS3_I_REVNOISE, 0, 0)
| LFS3_I_MKCONSISTENT
| LFS3_I_LOOKAHEAD
| LFS3_IFDEF_PREERASE((GBMAP) ? LFS3_I_PREERASE : 0, 0)
| LFS3_I_COMPACT
| LFS3_I_CKMETA
| LFS3_I_CKDATA
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_I_GBMAP, -1) : 0)));
lfs3_unmount(&lfs3) => 0;
// with LFS3_M_LOOKAHEAD, mount rebuild the gbmap
lfs3_mount(&lfs3,
LFS3_M_RDWR
| LFS3_M_LOOKAHEAD
| ((CKMETA) ? LFS3_M_CKMETA : 0)
| ((CKDATA) ? LFS3_M_CKDATA : 0),
CFG) => 0;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
LFS3_IFYES_REVPERTURB(LFS3_I_REVPERTURB, 0, 0)
| LFS3_IFYES_REVNOISE(LFS3_I_REVNOISE, 0, 0)
| LFS3_I_MKCONSISTENT
| LFS3_IFDEF_PREERASE((GBMAP) ? LFS3_I_PREERASE : 0, 0)
| LFS3_I_COMPACT
// note ckdata implies ckmeta
| ((!CKMETA && !CKDATA) ? LFS3_I_CKMETA : 0)
| ((!CKDATA) ? LFS3_I_CKDATA : 0)
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_I_GBMAP, -1) : 0)));
lfs3_unmount(&lfs3) => 0;
'''
[cases.test_mount_t_compact]
defines.LOOKAHEAD = [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 compact thresh to minimum
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
code = '''
lfs3_t lfs3;
lfs3_format(&lfs3,
LFS3_F_RDWR
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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_compact_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_COMPACT_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_IFYES_REVPERTURB(LFS3_I_REVPERTURB, 0, 0)
| LFS3_IFYES_REVNOISE(LFS3_I_REVNOISE, 0, 0)
| LFS3_I_MKCONSISTENT
| ((!GBMAP || SIZE >= BLOCK_SIZE/4)
? LFS3_I_LOOKAHEAD
: 0)
| LFS3_IFDEF_PREERASE((GBMAP) ? LFS3_I_PREERASE : 0, 0)
| LFS3_I_COMPACT
| LFS3_I_CKMETA
| LFS3_I_CKDATA
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_I_GBMAP, -1) : 0)));
lfs3_unmount(&lfs3) => 0;
// with LFS3_M_COMPACT, mount compacts any uncompacted blocks
lfs3_mount(&lfs3,
LFS3_M_RDWR
| LFS3_M_COMPACT
| ((LOOKAHEAD) ? LFS3_M_LOOKAHEAD : 0)
| ((CKMETA) ? LFS3_M_CKMETA : 0)
| ((CKDATA) ? LFS3_M_CKDATA : 0),
CFG) => 0;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
LFS3_IFYES_REVPERTURB(LFS3_I_REVPERTURB, 0, 0)
| LFS3_IFYES_REVNOISE(LFS3_I_REVNOISE, 0, 0)
| LFS3_I_MKCONSISTENT
| ((!LOOKAHEAD && (!GBMAP || SIZE >= BLOCK_SIZE/4))
? LFS3_I_LOOKAHEAD
: 0)
| LFS3_IFDEF_PREERASE((GBMAP) ? LFS3_I_PREERASE : 0, 0)
// note ckdata implies ckmeta
| ((!CKMETA && !CKDATA) ? LFS3_I_CKMETA : 0)
| ((!CKDATA) ? LFS3_I_CKDATA : 0)
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_I_GBMAP, -1) : 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_COMPACT_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.LOOKAHEAD = [false, true]
defines.COMPACT = [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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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_IFYES_REVPERTURB(LFS3_I_REVPERTURB, 0, 0)
| LFS3_IFYES_REVNOISE(LFS3_I_REVNOISE, 0, 0)
| LFS3_I_MKCONSISTENT
| ((!GBMAP || ORPHANS >= 100)
? LFS3_I_LOOKAHEAD
: 0)
| LFS3_IFDEF_PREERASE((GBMAP) ? LFS3_I_PREERASE : 0, 0)
| LFS3_I_COMPACT
| LFS3_I_CKMETA
| LFS3_I_CKDATA
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_I_GBMAP, -1) : 0)));
lfs3_unmount(&lfs3) => 0;
// with LFS3_M_MKCONSISTENT, mount cleans up orphans eagerly
lfs3_mount(&lfs3,
LFS3_M_RDWR
| LFS3_M_MKCONSISTENT
| ((LOOKAHEAD) ? LFS3_M_LOOKAHEAD : 0)
| ((COMPACT) ? LFS3_M_COMPACT : 0)
| ((CKMETA) ? LFS3_M_CKMETA : 0)
| ((CKDATA) ? LFS3_M_CKDATA : 0),
CFG) => 0;
lfs3_fs_stat(&lfs3, &fsinfo) => 0;
assert(fsinfo.flags == (
LFS3_IFYES_REVPERTURB(LFS3_I_REVPERTURB, 0, 0)
| LFS3_IFYES_REVNOISE(LFS3_I_REVNOISE, 0, 0)
| ((!LOOKAHEAD && (!GBMAP || ORPHANS >= 100))
? LFS3_I_LOOKAHEAD
: 0)
| LFS3_IFDEF_PREERASE((GBMAP) ? LFS3_I_PREERASE : 0, 0)
| ((!COMPACT) ? LFS3_I_COMPACT : 0)
// note ckdata implies ckmeta
| ((!CKMETA && !CKDATA) ? LFS3_I_CKMETA : 0)
| ((!CKDATA) ? LFS3_I_CKDATA : 0)
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_I_GBMAP, -1) : 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(1, LFS3_tag_RM | LFS3_TAG_MAGIC, 0),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(2, LFS3_TAG_MAGIC, 0, LFS3_FROM_BUF, 8),
LFS3_RATTR_ARG("lottlefs"),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(2, LFS3_TAG_VERSION, 0, LFS3_FROM_BUF, 2),
LFS3_RATTR_ARG(((const uint8_t[2]){
LFS3_DISK_VERSION_MAJOR+1,
0})),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(2, LFS3_TAG_VERSION, 0, LFS3_FROM_BUF, 2),
LFS3_RATTR_ARG(((const uint8_t[2]){
LFS3_DISK_VERSION_MAJOR,
LFS3_DISK_VERSION_MINOR+1})),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(2, LFS3_TAG_RCOMPAT, 0, LFS3_FROM_LE32),
LFS3_RATTR_ARG(
lfs3_rcompat(&lfs3)
| LFS3_RCOMPAT_NONSTANDARD),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(2, LFS3_TAG_WCOMPAT, 0, LFS3_FROM_LE32),
LFS3_RATTR_ARG(
lfs3_wcompat(&lfs3)
| LFS3_WCOMPAT_NONSTANDARD),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(2, LFS3_TAG_OCOMPAT, 0, LFS3_FROM_LE32),
LFS3_RATTR_ARG(
lfs3_ocompat(&lfs3)
| LFS3_OCOMPAT_NONSTANDARD),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(2, LFS3_TAG_WCOMPAT, 0, LFS3_FROM_LE32),
LFS3_RATTR_ARG(
lfs3_wcompat(&lfs3)
| LFS3_WCOMPAT_RDONLY),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(2, LFS3_TAG_RCOMPAT, 0, LFS3_FROM_LE32),
LFS3_RATTR_ARG(
lfs3_rcompat(&lfs3)
| LFS3_RCOMPAT_WRONLY),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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 rcompat_buf[sizeof(uint32_t) + OVERFLOW/8];
memset(rcompat_buf, 0, sizeof(rcompat_buf));
lfs3_data_fromle32(lfs3_rcompat(&lfs3), rcompat_buf);
rcompat_buf[sizeof(uint32_t) + FLAG/8] |= 1 << (FLAG%8);
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR(3, LFS3_TAG_RCOMPAT, 0, LFS3_FROM_DATA),
LFS3_RATTR_ARG(sizeof(rcompat_buf)),
LFS3_RATTR_ARG(rcompat_buf),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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 wcompat_buf[sizeof(uint32_t) + OVERFLOW/8];
memset(wcompat_buf, 0, sizeof(wcompat_buf));
lfs3_data_fromle32(lfs3_wcompat(&lfs3), wcompat_buf);
wcompat_buf[sizeof(uint32_t) + FLAG/8] |= 1 << (FLAG%8);
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR(3, LFS3_TAG_WCOMPAT, 0, LFS3_FROM_DATA),
LFS3_RATTR_ARG(sizeof(wcompat_buf)),
LFS3_RATTR_ARG(wcompat_buf),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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 ocompat_buf[sizeof(uint32_t) + OVERFLOW/8];
memset(ocompat_buf, 0, sizeof(ocompat_buf));
ocompat_buf[sizeof(uint32_t) + FLAG/8] |= 1 << (FLAG%8);
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR(3, LFS3_TAG_OCOMPAT, 0, LFS3_FROM_DATA),
LFS3_RATTR_ARG(sizeof(ocompat_buf)),
LFS3_RATTR_ARG(ocompat_buf),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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 rcompat_buf[sizeof(uint32_t) + OVERFLOW/8];
memset(rcompat_buf, 0, sizeof(rcompat_buf));
lfs3_data_fromle32(lfs3_rcompat(&lfs3), rcompat_buf);
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR(3, LFS3_TAG_RCOMPAT, 0, LFS3_FROM_DATA),
LFS3_RATTR_ARG(sizeof(rcompat_buf)),
LFS3_RATTR_ARG(rcompat_buf),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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 wcompat_buf[sizeof(uint32_t) + OVERFLOW/8];
memset(wcompat_buf, 0, sizeof(wcompat_buf));
lfs3_data_fromle32(lfs3_wcompat(&lfs3), wcompat_buf);
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR(3, LFS3_TAG_WCOMPAT, 0, LFS3_FROM_DATA),
LFS3_RATTR_ARG(sizeof(wcompat_buf)),
LFS3_RATTR_ARG(wcompat_buf),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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 ocompat_buf[sizeof(uint32_t) + OVERFLOW/8];
memset(ocompat_buf, 0, sizeof(ocompat_buf));
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR(3, LFS3_TAG_OCOMPAT, 0, LFS3_FROM_DATA),
LFS3_RATTR_ARG(sizeof(ocompat_buf)),
LFS3_RATTR_ARG(ocompat_buf),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(2, LFS3_TAG_GEOMETRY, 0, LFS3_FROM_GEOMETRY),
LFS3_RATTR_ARG((&(lfs3_geometry_t){
INC_BLOCK_SIZE,
BLOCK_COUNT})),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(2, LFS3_TAG_GEOMETRY, 0, LFS3_FROM_GEOMETRY),
LFS3_RATTR_ARG((&(lfs3_geometry_t){
BLOCK_SIZE,
INC_BLOCK_COUNT})),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(2, LFS3_TAG_NAMELIMIT, 0, LFS3_FROM_LEB128),
LFS3_RATTR_ARG(INC_NAME_LIMIT),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
CFG) => 0;
// set an incompatible file limit
//
// note we're messing around with internals to do this! this
// is not a user API
// 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
uint8_t filelimit_buf[1+LFS3_LEB128_DSIZE];
filelimit_buf[0] = 0xff;
lfs3_ssize_t d_ = lfs3_toleb128(LFS3_FILE_MAX,
filelimit_buf+1, LFS3_LEB128_DSIZE);
assert(d_ >= 0);
lfs3_size_t d = 1 + d_;
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR(3, LFS3_TAG_FILELIMIT, 0, LFS3_FROM_DATA),
LFS3_RATTR_ARG(d),
LFS3_RATTR_ARG(filelimit_buf),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
CFG) => 0;
// create an unknown config
lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0;
lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS(
LFS3_RATTR(3, LFS3_TAG_CONFIG + 0x42, 0, LFS3_FROM_DATA),
LFS3_RATTR_ARG(strlen("oh no!")),
LFS3_RATTR_ARG("oh no!"),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(3, LFS3_tag_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
LFS3_FROM_NAME),
LFS3_RATTR_ARG(did),
LFS3_RATTR_ARG(path),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(3, LFS3_tag_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
LFS3_FROM_NAME),
LFS3_RATTR_ARG(did),
LFS3_RATTR_ARG(path),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(3, LFS3_tag_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
LFS3_FROM_NAME),
LFS3_RATTR_ARG(did),
LFS3_RATTR_ARG(path),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(3, LFS3_tag_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
LFS3_FROM_NAME),
LFS3_RATTR_ARG(did),
LFS3_RATTR_ARG(path),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(3, LFS3_tag_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
LFS3_FROM_NAME),
LFS3_RATTR_ARG(did),
LFS3_RATTR_ARG(path),
LFS3_RATTR_NULL)) => 0;
path = "c";
lfs3_mtree_pathlookup(&lfs3, &path,
&mdir, &did) => LFS3_TAG_REG;
lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS(
LFS3_RATTR(3, LFS3_tag_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
LFS3_FROM_NAME),
LFS3_RATTR_ARG(did),
LFS3_RATTR_ARG(path),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(3, LFS3_tag_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
LFS3_FROM_NAME),
LFS3_RATTR_ARG(did),
LFS3_RATTR_ARG(path),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(3, LFS3_tag_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
LFS3_FROM_NAME),
LFS3_RATTR_ARG(did),
LFS3_RATTR_ARG(path),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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(3, LFS3_tag_MASK8 | (LFS3_TAG_NAME + 0x13), 0,
LFS3_FROM_NAME),
LFS3_RATTR_ARG(did),
LFS3_RATTR_ARG(path),
LFS3_RATTR_NULL)) => 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
| ((GBMAP) ? LFS3_IFDEF_GBMAP(LFS3_F_GBMAP, -1) : 0),
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;
'''