Compare commits

..

5 Commits

Author SHA1 Message Date
Christopher Haster
c1c0386bda Added test_new.toml with failure found by AFL
Found by pjsg
2020-03-26 15:27:30 -05:00
Christopher Haster
4677421aba Added "evil" tests and detecion/recovery from bad pointers and infinite loops
These two features have been much requested by users, and have even had
several PRs proposed to fix these in several cases. Before this, these
error conditions usually were caught by internal asserts, however
asserts prevented users from implementing their own workarounds.

It's taken me a while to provide/accept a useful recovery mechanism
(returning LFS_ERR_CORRUPT instead of asserting) because my original thinking
was that these error conditions only occur due to bugs in the filesystem, and
these bugs should be fixed properly.

While I still think this is mostly true, the point has been made clear
that being able to recover from these conditions is definitely worth the
code cost. Hopefully this new behaviour helps the longevity of devices
even if the storage code fails.

Another, less important, reason I didn't want to accept fixes for these
situations was the lack of tests that prove the code's value. This has
been fixed with the new testing framework thanks to the additional of
"internal tests" which can call C static functions and really take
advantage of the internal information of the filesystem.
2020-03-20 09:26:07 -05:00
Chris Desjardins
cb26157880 Change assert to runtime check.
I had a system that was constantly hitting this assert, after making
this change it recovered immediately.
2020-02-23 22:18:08 -06:00
Christopher Haster
a7dfae4526 Minor tweaks to debugging scripts, fixed explode_asserts.py off-by-1
- Changed readmdir.py to print the metadata pair and revision count,
  which is useful when debugging commit issues.
- Added truncated data view to readtree.py by default. This does mean
  readtree.py must read all files on the filesystem to show the
  truncated data, hopefully this does not end up being a problem.
- Made overall representation hopefully more readable, including moving
  superblock under the root dir, userattrs under files, fixing a gstate
  rendering issue.
- Added rendering of soft-tails as dotted-arrows, hopefully this isn't
  too noisy.
- Fixed explode_asserts.py off-by-1 in #line mapping caused by a strip
  call in the assert generation eating newlines. The script matches
  line numbers between the original+modified files by emitting assert
  statements that use the same number of lines. An off-by-1 here causes
  the entire file to map lines incorrectly, which can be very annoying.
2020-02-22 23:50:03 -06:00
Christopher Haster
50fe8ae258 Renamed test_format -> test_superblocks, tweaked superblock tests
With the superblock expansion stuff, the test_format tests have grown
to test more advanced superblock-related features. This is fine but
deserves a rename so it's more clear.

Also fixed a typo that meant tests never ran with block cycles.
2020-02-22 23:35:28 -06:00
11 changed files with 661 additions and 327 deletions

71
lfs.c
View File

@@ -29,8 +29,8 @@ static int lfs_bd_read(lfs_t *lfs,
lfs_block_t block, lfs_off_t off,
void *buffer, lfs_size_t size) {
uint8_t *data = buffer;
LFS_ASSERT(block != LFS_BLOCK_NULL);
if (off+size > lfs->cfg->block_size) {
if (block >= lfs->cfg->block_count ||
off+size > lfs->cfg->block_size) {
return LFS_ERR_CORRUPT;
}
@@ -173,7 +173,7 @@ static int lfs_bd_prog(lfs_t *lfs,
lfs_block_t block, lfs_off_t off,
const void *buffer, lfs_size_t size) {
const uint8_t *data = buffer;
LFS_ASSERT(block != LFS_BLOCK_NULL);
LFS_ASSERT(block == LFS_BLOCK_INLINE || block < lfs->cfg->block_count);
LFS_ASSERT(off + size <= lfs->cfg->block_size);
while (size > 0) {
@@ -418,7 +418,6 @@ int lfs_fs_traverseraw(lfs_t *lfs,
int (*cb)(void *data, lfs_block_t block), void *data,
bool includeorphans);
static int lfs_fs_forceconsistency(lfs_t *lfs);
static int lfs_fs_deorphan(lfs_t *lfs);
static int lfs_deinit(lfs_t *lfs);
#ifdef LFS_MIGRATE
static int lfs1_traverse(lfs_t *lfs,
@@ -749,6 +748,12 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
// scanning the entire directory
lfs_stag_t besttag = -1;
// if either block address is invalid we return LFS_ERR_CORRUPT here,
// otherwise later writes to the pair could fail
if (pair[0] >= lfs->cfg->block_count || pair[1] >= lfs->cfg->block_count) {
return LFS_ERR_CORRUPT;
}
// find the block with the most recent revision
uint32_t revs[2] = {0, 0};
int r = 0;
@@ -797,7 +802,6 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
if (err == LFS_ERR_CORRUPT) {
// can't continue?
dir->erased = false;
dir->first = false;
break;
}
return err;
@@ -810,11 +814,9 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
if (!lfs_tag_isvalid(tag)) {
dir->erased = (lfs_tag_type1(ptag) == LFS_TYPE_CRC &&
dir->off % lfs->cfg->prog_size == 0);
dir->first = false;
break;
} else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) {
dir->erased = false;
dir->first = false;
break;
}
@@ -829,7 +831,6 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
if (err) {
if (err == LFS_ERR_CORRUPT) {
dir->erased = false;
dir->first = false;
break;
}
return err;
@@ -838,7 +839,6 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
if (crc != dcrc) {
dir->erased = false;
dir->first = false;
break;
}
@@ -872,7 +872,6 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
if (err) {
if (err == LFS_ERR_CORRUPT) {
dir->erased = false;
dir->first = false;
break;
}
return err;
@@ -906,7 +905,6 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
if (err) {
if (err == LFS_ERR_CORRUPT) {
dir->erased = false;
dir->first = false;
break;
}
}
@@ -920,7 +918,6 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
if (res < 0) {
if (res == LFS_ERR_CORRUPT) {
dir->erased = false;
dir->first = false;
break;
}
return res;
@@ -1364,7 +1361,6 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) {
dir->tail[0] = LFS_BLOCK_NULL;
dir->tail[1] = LFS_BLOCK_NULL;
dir->erased = false;
dir->first = true;
dir->split = false;
// don't write out yet, let caller take care of that
@@ -1501,7 +1497,7 @@ static int lfs_dir_compact(lfs_t *lfs,
// 2. block_cycles = 2n, which, due to aliasing, would only ever relocate
// one metadata block in the pair, effectively making this useless
if (lfs->cfg->block_cycles > 0 &&
(dir->rev % ((lfs->cfg->block_cycles+4)/*|1*/) == 0)) { // TODO what
(dir->rev % ((lfs->cfg->block_cycles+1)|1) == 0)) {
if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) {
// oh no! we're writing too much to the superblock,
// should we expand?
@@ -1682,11 +1678,6 @@ relocate:
}
if (relocated) {
if (!dir->first) {
// TODO something funky!
dir->pair[0] = dir->pair[0];
dir->pair[1] = oldpair[1];
}
// update references if we relocated
LFS_DEBUG("Relocating %"PRIx32" %"PRIx32" -> %"PRIx32" %"PRIx32,
oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]);
@@ -2212,7 +2203,6 @@ static int lfs_ctz_find(lfs_t *lfs,
return err;
}
LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count);
current -= 1 << skip;
}
@@ -2232,7 +2222,6 @@ static int lfs_ctz_extend(lfs_t *lfs,
if (err) {
return err;
}
LFS_ASSERT(nblock >= 2 && nblock <= lfs->cfg->block_count);
{
err = lfs_bd_erase(lfs, nblock);
@@ -2305,8 +2294,6 @@ static int lfs_ctz_extend(lfs_t *lfs,
return err;
}
}
LFS_ASSERT(nhead >= 2 && nhead <= lfs->cfg->block_count);
}
*block = nblock;
@@ -3195,7 +3182,7 @@ int lfs_remove(lfs_t *lfs, const char *path) {
}
lfs->mlist = dir.next;
if (lfs_tag_type3(tag) == LFS_TYPE_DIR/* && lfs_tag_size(lfs->gstate.tag) > 0*/) {
if (lfs_tag_type3(tag) == LFS_TYPE_DIR) {
// fix orphan
lfs_fs_preporphans(lfs, -1);
@@ -3677,7 +3664,15 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
// scan directory blocks for superblock and any global updates
lfs_mdir_t dir = {.tail = {0, 1}};
lfs_block_t cycle = 0;
while (!lfs_pair_isnull(dir.tail)) {
if (cycle >= lfs->cfg->block_count/2) {
// loop detected
err = LFS_ERR_CORRUPT;
goto cleanup;
}
cycle += 1;
// fetch next block in tail list
lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail,
LFS_MKTAG(0x7ff, 0x3ff, 0),
@@ -3819,7 +3814,14 @@ int lfs_fs_traverseraw(lfs_t *lfs,
}
#endif
lfs_block_t cycle = 0;
while (!lfs_pair_isnull(dir.tail)) {
if (cycle >= lfs->cfg->block_count/2) {
// loop detected
return LFS_ERR_CORRUPT;
}
cycle += 1;
for (int i = 0; i < 2; i++) {
int err = cb(data, dir.tail[i]);
if (err) {
@@ -3903,7 +3905,14 @@ static int lfs_fs_pred(lfs_t *lfs,
// iterate over all directory directory entries
pdir->tail[0] = 0;
pdir->tail[1] = 1;
lfs_block_t cycle = 0;
while (!lfs_pair_isnull(pdir->tail)) {
if (cycle >= lfs->cfg->block_count/2) {
// loop detected
return LFS_ERR_CORRUPT;
}
cycle += 1;
if (lfs_pair_cmp(pdir->tail, pair) == 0) {
return 0;
}
@@ -3946,7 +3955,14 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
// use fetchmatch with callback to find pairs
parent->tail[0] = 0;
parent->tail[1] = 1;
lfs_block_t cycle = 0;
while (!lfs_pair_isnull(parent->tail)) {
if (cycle >= lfs->cfg->block_count/2) {
// loop detected
return LFS_ERR_CORRUPT;
}
cycle += 1;
lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail,
LFS_MKTAG(0x7ff, 0, 0x3ff),
LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, 8),
@@ -4024,12 +4040,6 @@ static int lfs_fs_relocate(lfs_t *lfs,
lfs_fs_preporphans(lfs, -1);
}
#if 0
int err = lfs_fs_deorphan(lfs);
if (err) {
return err;
}
#else
// find pred
int err = lfs_fs_pred(lfs, oldpair, &parent);
if (err && err != LFS_ERR_NOENT) {
@@ -4060,7 +4070,6 @@ static int lfs_fs_relocate(lfs_t *lfs,
return err;
}
}
#endif
return 0;
}

1
lfs.h
View File

@@ -311,7 +311,6 @@ typedef struct lfs_mdir {
uint16_t count;
bool erased;
bool split;
bool first;
lfs_block_t tail[2];
} lfs_mdir_t;

View File

@@ -166,8 +166,8 @@ def mkassert(type, comp, lh, rh, size=None):
'type': type.lower(), 'TYPE': type.upper(),
'comp': comp.lower(), 'COMP': comp.upper(),
'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(),
'lh': lh.strip(),
'rh': rh.strip(),
'lh': lh.strip(' '),
'rh': rh.strip(' '),
'size': size,
}
if size:

View File

@@ -318,6 +318,14 @@ def main(args):
# find most recent pair
mdir = MetadataPair(blocks)
print("mdir {%s} rev %d%s%s" % (
', '.join('%#x' % b
for b in [args.block1, args.block2]
if b is not None),
mdir.rev,
' (was %s)' % ', '.join('%d' % m.rev for m in mdir.pair[1:])
if len(mdir.pair) > 1 else '',
' (corrupted)' if not mdir else ''))
if args.all:
mdir.dump_all(truncate=not args.no_truncate)
elif args.log:

View File

@@ -18,26 +18,22 @@ def dumpentries(args, mdir, f):
name = mdir[Tag('name', id_, 0)]
struct_ = mdir[Tag('struct', id_, 0)]
f.write("id %d %s %s" % (
desc = "id %d %s %s" % (
id_, name.typerepr(),
json.dumps(name.data.decode('utf8'))))
json.dumps(name.data.decode('utf8')))
if struct_.is_('dirstruct'):
f.write(" dir {%#x, %#x}" % struct.unpack(
'<II', struct_.data[:8].ljust(8, b'\xff')))
desc += " dir {%#x, %#x}" % struct.unpack(
'<II', struct_.data[:8].ljust(8, b'\xff'))
if struct_.is_('ctzstruct'):
f.write(" ctz {%#x} size %d" % struct.unpack(
'<II', struct_.data[:8].ljust(8, b'\xff')))
desc += " ctz {%#x} size %d" % struct.unpack(
'<II', struct_.data[:8].ljust(8, b'\xff'))
if struct_.is_('inlinestruct'):
f.write(" inline size %d" % struct_.size)
f.write("\n")
desc += " inline size %d" % struct_.size
if args.data and struct_.is_('inlinestruct'):
for i in range(0, len(struct_.data), 16):
f.write(" %08x: %-47s %-16s\n" % (
i, ' '.join('%02x' % c for c in struct_.data[i:i+16]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, struct_.data[i:i+16]))))
elif args.data and struct_.is_('ctzstruct'):
data = None
if struct_.is_('inlinestruct'):
data = struct_.data
elif struct_.is_('ctzstruct'):
block, size = struct.unpack(
'<II', struct_.data[:8].ljust(8, b'\xff'))
data = []
@@ -51,28 +47,50 @@ def dumpentries(args, mdir, f):
data.append(dat[4*(ctz(i)+1) if i != 0 else 0:])
block, = struct.unpack('<I', dat[:4].ljust(4, b'\xff'))
i -= 1
data = bytes(it.islice(
it.chain.from_iterable(reversed(data)), size))
for i in range(0, min(len(data), 256)
if not args.no_truncate else len(data), 16):
f.write("%-45s%s\n" % (desc,
"%-23s %-8s" % (
' '.join('%02x' % c for c in data[:8]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, data[:8])))
if not args.no_truncate and len(desc) < 45
and data is not None else ""))
if name.is_('superblock') and struct_.is_('inlinestruct'):
f.write(
" block_size %d\n"
" block_count %d\n"
" name_max %d\n"
" file_max %d\n"
" attr_max %d\n" % struct.unpack(
'<IIIII', struct_.data[4:4+20].ljust(20, b'\xff')))
for tag in mdir.tags:
if tag.id==id_ and tag.is_('userattr'):
desc = "%s size %d" % (tag.typerepr(), tag.size)
f.write(" %-43s%s\n" % (desc,
"%-23s %-8s" % (
' '.join('%02x' % c for c in tag.data[:8]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, tag.data[:8])))
if not args.no_truncate and len(desc) < 43 else ""))
if args.no_truncate:
for i in range(0, len(tag.data), 16):
f.write(" %08x: %-47s %-16s\n" % (
i, ' '.join('%02x' % c for c in tag.data[i:i+16]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, tag.data[i:i+16]))))
if args.no_truncate and data is not None:
for i in range(0, len(data), 16):
f.write(" %08x: %-47s %-16s\n" % (
i, ' '.join('%02x' % c for c in data[i:i+16]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, data[i:i+16]))))
for tag in mdir.tags:
if tag.id==id_ and tag.is_('userattr'):
f.write("id %d %s size %d\n" % (
id_, tag.typerepr(), tag.size))
if args.data:
for i in range(0, len(tag.data), 16):
f.write(" %-47s %-16s\n" % (
' '.join('%02x' % c for c in tag.data[i:i+16]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, tag.data[i:i+16]))))
def main(args):
with open(args.disk, 'rb') as f:
dirs = []
@@ -161,61 +179,51 @@ def main(args):
dir[0].path = path.replace('//', '/')
# dump tree
if not args.superblock and not args.gstate and not args.mdirs:
args.superblock = True
args.gstate = True
args.mdirs = True
version = ('?', '?')
if superblock:
version = tuple(reversed(
struct.unpack('<HH', superblock[1].data[0:4].ljust(4, b'\xff'))))
print("%-47s%s" % ("littlefs v%s.%s" % version,
"data (truncated, if it fits)"
if not any([args.no_truncate, args.tags, args.log, args.all]) else ""))
if args.superblock and superblock:
print("superblock %s v%d.%d" % (
json.dumps(superblock[0].data.decode('utf8')),
struct.unpack('<H', superblock[1].data[2:2+2])[0],
struct.unpack('<H', superblock[1].data[0:0+2])[0]))
print(
" block_size %d\n"
" block_count %d\n"
" name_max %d\n"
" file_max %d\n"
" attr_max %d" % struct.unpack(
'<IIIII', superblock[1].data[4:4+20].ljust(20, b'\xff')))
if args.gstate and gstate:
if gstate:
print("gstate 0x%s" % ''.join('%02x' % c for c in gstate))
tag = Tag(struct.unpack('<I', gstate[0:4].ljust(4, b'\xff'))[0])
blocks = struct.unpack('<II', gstate[4:4+8].ljust(8, b'\xff'))
if tag.size:
print(" orphans %d" % tag.size)
if tag.size or not tag.isvalid:
print(" orphans >=%d" % max(tag.size, 1))
if tag.type:
print(" move dir {%#x, %#x} id %d" % (
blocks[0], blocks[1], tag.id))
if args.mdirs:
for i, dir in enumerate(dirs):
print("dir %s" % (json.dumps(dir[0].path)
if hasattr(dir[0], 'path') else '(orphan)'))
for i, dir in enumerate(dirs):
print("dir %s" % (json.dumps(dir[0].path)
if hasattr(dir[0], 'path') else '(orphan)'))
for j, mdir in enumerate(dir):
print("mdir {%#x, %#x} rev %d%s" % (
mdir.blocks[0], mdir.blocks[1], mdir.rev,
' (corrupted)' if not mdir else ''))
for j, mdir in enumerate(dir):
print("mdir {%#x, %#x} rev %d%s" % (
mdir.blocks[0], mdir.blocks[1], mdir.rev,
' (corrupted)' if not mdir else ''))
f = io.StringIO()
if args.tags:
mdir.dump_tags(f, truncate=not args.no_truncate)
elif args.log:
mdir.dump_log(f, truncate=not args.no_truncate)
elif args.all:
mdir.dump_all(f, truncate=not args.no_truncate)
else:
dumpentries(args, mdir, f)
f = io.StringIO()
if args.tags:
mdir.dump_tags(f, truncate=not args.no_truncate)
elif args.log:
mdir.dump_log(f, truncate=not args.no_truncate)
elif args.all:
mdir.dump_all(f, truncate=not args.no_truncate)
else:
dumpentries(args, mdir, f)
lines = list(filter(None, f.getvalue().split('\n')))
for k, line in enumerate(lines):
print("%s %s" % (
' ' if j == len(dir)-1 else
'v' if k == len(lines)-1 else
'|',
line))
lines = list(filter(None, f.getvalue().split('\n')))
for k, line in enumerate(lines):
print("%s %s" % (
' ' if i == len(dirs)-1 and j == len(dir)-1 else
'v' if k == len(lines)-1 else
'.' if j == len(dir)-1 else
'|',
line))
if cycle:
print("*** cycle detected! -> {%#x, %#x} ***" % (cycle[0], cycle[1]))
@@ -242,20 +250,12 @@ if __name__ == "__main__":
parser.add_argument('block2', nargs='?', default=1,
type=lambda x: int(x, 0),
help="Optional second block address for finding the root.")
parser.add_argument('-s', '--superblock', action='store_true',
help="Show contents of the superblock.")
parser.add_argument('-g', '--gstate', action='store_true',
help="Show contents of global-state.")
parser.add_argument('-m', '--mdirs', action='store_true',
help="Show contents of metadata-pairs/directories.")
parser.add_argument('-t', '--tags', action='store_true',
help="Show metadata tags instead of reconstructing entries.")
parser.add_argument('-l', '--log', action='store_true',
help="Show tags in log.")
parser.add_argument('-a', '--all', action='store_true',
help="Show all tags in log, included tags in corrupted commits.")
parser.add_argument('-d', '--data', action='store_true',
help="Also show the raw contents of files/attrs/tags.")
parser.add_argument('-T', '--no-truncate', action='store_true',
help="Don't truncate large amounts of data.")
help="Show the full contents of files/attrs/tags.")
sys.exit(main(parser.parse_args()))

288
tests/test_evil.toml Normal file
View File

@@ -0,0 +1,288 @@
# Tests for recovering from conditions which shouldn't normally
# happen during normal operation of littlefs
# invalid pointer tests (outside of block_count)
[[case]] # invalid tail-pointer test
define.TAIL_TYPE = ['LFS_TYPE_HARDTAIL', 'LFS_TYPE_SOFTTAIL']
define.INVALSET = [0x3, 0x1, 0x2]
in = "lfs.c"
code = '''
// create littlefs
lfs_format(&lfs, &cfg) => 0;
// change tail-pointer to invalid pointers
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
(lfs_block_t[2]){
(INVALSET & 0x1) ? 0xcccccccc : 0,
(INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0;
lfs_deinit(&lfs) => 0;
// test that mount fails gracefully
lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
'''
[[case]] # invalid dir pointer test
define.INVALSET = [0x3, 0x1, 0x2]
in = "lfs.c"
code = '''
// create littlefs
lfs_format(&lfs, &cfg) => 0;
// make a dir
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "dir_here") => 0;
lfs_unmount(&lfs) => 0;
// change the dir pointer to be invalid
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
// make sure id 1 == our directory
lfs_dir_get(&lfs, &mdir,
LFS_MKTAG(0x700, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("dir_here")), buffer)
=> LFS_MKTAG(LFS_TYPE_DIR, 1, strlen("dir_here"));
assert(memcmp((char*)buffer, "dir_here", strlen("dir_here")) == 0);
// change dir pointer
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, 8),
(lfs_block_t[2]){
(INVALSET & 0x1) ? 0xcccccccc : 0,
(INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0;
lfs_deinit(&lfs) => 0;
// test that accessing our bad dir fails, note there's a number
// of ways to access the dir, some can fail, but some don't
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "dir_here", &info) => 0;
assert(strcmp(info.name, "dir_here") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_open(&lfs, &dir, "dir_here") => LFS_ERR_CORRUPT;
lfs_stat(&lfs, "dir_here/file_here", &info) => LFS_ERR_CORRUPT;
lfs_dir_open(&lfs, &dir, "dir_here/dir_here") => LFS_ERR_CORRUPT;
lfs_file_open(&lfs, &file, "dir_here/file_here",
LFS_O_RDONLY) => LFS_ERR_CORRUPT;
lfs_file_open(&lfs, &file, "dir_here/file_here",
LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_CORRUPT;
lfs_unmount(&lfs) => 0;
'''
[[case]] # invalid file pointer test
in = "lfs.c"
define.SIZE = [10, 1000, 100000] # faked file size
code = '''
// create littlefs
lfs_format(&lfs, &cfg) => 0;
// make a file
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file, "file_here",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
// change the file pointer to be invalid
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
// make sure id 1 == our file
lfs_dir_get(&lfs, &mdir,
LFS_MKTAG(0x700, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer)
=> LFS_MKTAG(LFS_TYPE_REG, 1, strlen("file_here"));
assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0);
// change file pointer
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz)),
&(struct lfs_ctz){0xcccccccc, lfs_tole32(SIZE)}})) => 0;
lfs_deinit(&lfs) => 0;
// test that accessing our bad file fails, note there's a number
// of ways to access the dir, some can fail, but some don't
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "file_here", &info) => 0;
assert(strcmp(info.name, "file_here") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
lfs_file_open(&lfs, &file, "file_here", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, SIZE) => LFS_ERR_CORRUPT;
lfs_file_close(&lfs, &file) => 0;
// any allocs that traverse CTZ must unfortunately must fail
if (SIZE > 2*LFS_BLOCK_SIZE) {
lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT;
}
lfs_unmount(&lfs) => 0;
'''
[[case]] # invalid pointer in CTZ skip-list test
define.SIZE = ['2*LFS_BLOCK_SIZE', '3*LFS_BLOCK_SIZE', '4*LFS_BLOCK_SIZE']
in = "lfs.c"
code = '''
// create littlefs
lfs_format(&lfs, &cfg) => 0;
// make a file
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file, "file_here",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
for (int i = 0; i < SIZE; i++) {
char c = 'c';
lfs_file_write(&lfs, &file, &c, 1) => 1;
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
// change pointer in CTZ skip-list to be invalid
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
// make sure id 1 == our file and get our CTZ structure
lfs_dir_get(&lfs, &mdir,
LFS_MKTAG(0x700, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer)
=> LFS_MKTAG(LFS_TYPE_REG, 1, strlen("file_here"));
assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0);
struct lfs_ctz ctz;
lfs_dir_get(&lfs, &mdir,
LFS_MKTAG(0x700, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_STRUCT, 1, sizeof(struct lfs_ctz)), &ctz)
=> LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz));
lfs_ctz_fromle32(&ctz);
// rewrite block to contain bad pointer
uint8_t bbuffer[LFS_BLOCK_SIZE];
cfg.read(&cfg, ctz.head, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
uint32_t bad = lfs_tole32(0xcccccccc);
memcpy(&bbuffer[0], &bad, sizeof(bad));
memcpy(&bbuffer[4], &bad, sizeof(bad));
cfg.erase(&cfg, ctz.head) => 0;
cfg.prog(&cfg, ctz.head, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
lfs_deinit(&lfs) => 0;
// test that accessing our bad file fails, note there's a number
// of ways to access the dir, some can fail, but some don't
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "file_here", &info) => 0;
assert(strcmp(info.name, "file_here") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
lfs_file_open(&lfs, &file, "file_here", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, SIZE) => LFS_ERR_CORRUPT;
lfs_file_close(&lfs, &file) => 0;
// any allocs that traverse CTZ must unfortunately must fail
if (SIZE > 2*LFS_BLOCK_SIZE) {
lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT;
}
lfs_unmount(&lfs) => 0;
'''
[[case]] # invalid gstate pointer
define.INVALSET = [0x3, 0x1, 0x2]
in = "lfs.c"
code = '''
// create littlefs
lfs_format(&lfs, &cfg) => 0;
// create an invalid gstate
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
lfs_fs_prepmove(&lfs, 1, (lfs_block_t [2]){
(INVALSET & 0x1) ? 0xcccccccc : 0,
(INVALSET & 0x2) ? 0xcccccccc : 0});
lfs_dir_commit(&lfs, &mdir, NULL, 0) => 0;
lfs_deinit(&lfs) => 0;
// test that mount fails gracefully
// mount may not fail, but our first alloc should fail when
// we try to fix the gstate
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "should_fail") => LFS_ERR_CORRUPT;
lfs_unmount(&lfs) => 0;
'''
# cycle detection/recovery tests
[[case]] # metadata-pair threaded-list loop test
in = "lfs.c"
code = '''
// create littlefs
lfs_format(&lfs, &cfg) => 0;
// change tail-pointer to point to ourself
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
(lfs_block_t[2]){0, 1}})) => 0;
lfs_deinit(&lfs) => 0;
// test that mount fails gracefully
lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
'''
[[case]] # metadata-pair threaded-list 2-length loop test
in = "lfs.c"
code = '''
// create littlefs with child dir
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "child") => 0;
lfs_unmount(&lfs) => 0;
// find child
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_block_t pair[2];
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
lfs_dir_get(&lfs, &mdir,
LFS_MKTAG(0x7ff, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair)
=> LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair));
lfs_pair_fromle32(pair);
// change tail-pointer to point to root
lfs_dir_fetch(&lfs, &mdir, pair) => 0;
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
(lfs_block_t[2]){0, 1}})) => 0;
lfs_deinit(&lfs) => 0;
// test that mount fails gracefully
lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
'''
[[case]] # metadata-pair threaded-list 1-length child loop test
in = "lfs.c"
code = '''
// create littlefs with child dir
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "child") => 0;
lfs_unmount(&lfs) => 0;
// find child
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_block_t pair[2];
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
lfs_dir_get(&lfs, &mdir,
LFS_MKTAG(0x7ff, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair)
=> LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair));
lfs_pair_fromle32(pair);
// change tail-pointer to point to ourself
lfs_dir_fetch(&lfs, &mdir, pair) => 0;
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8), pair})) => 0;
lfs_deinit(&lfs) => 0;
// test that mount fails gracefully
lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
'''

View File

@@ -350,117 +350,116 @@ exhausted:
LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
'''
# TODO fixme
#[[case]] # test that we wear blocks roughly evenly
#define.LFS_ERASE_CYCLES = 0xffffffff
#define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
#define.LFS_BLOCK_CYCLES = [5, 4, 3, 2, 1]
#define.CYCLES = 100
#define.FILES = 10
#if = 'LFS_BLOCK_CYCLES < CYCLES/10'
#code = '''
# lfs_format(&lfs, &cfg) => 0;
# lfs_mount(&lfs, &cfg) => 0;
# lfs_mkdir(&lfs, "roadrunner") => 0;
# lfs_unmount(&lfs) => 0;
#
# uint32_t cycle = 0;
# while (cycle < CYCLES) {
# lfs_mount(&lfs, &cfg) => 0;
# for (uint32_t i = 0; i < FILES; i++) {
# // chose name, roughly random seed, and random 2^n size
# sprintf(path, "roadrunner/test%d", i);
# srand(cycle * i);
# size = 1 << 4; //((rand() % 10)+2);
#
# lfs_file_open(&lfs, &file, path,
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
#
# for (lfs_size_t j = 0; j < size; j++) {
# char c = 'a' + (rand() % 26);
# lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
# assert(res == 1 || res == LFS_ERR_NOSPC);
# if (res == LFS_ERR_NOSPC) {
# err = lfs_file_close(&lfs, &file);
# assert(err == 0 || err == LFS_ERR_NOSPC);
# lfs_unmount(&lfs) => 0;
# goto exhausted;
# }
# }
#
# err = lfs_file_close(&lfs, &file);
# assert(err == 0 || err == LFS_ERR_NOSPC);
# if (err == LFS_ERR_NOSPC) {
# lfs_unmount(&lfs) => 0;
# goto exhausted;
# }
# }
#
# for (uint32_t i = 0; i < FILES; i++) {
# // check for errors
# sprintf(path, "roadrunner/test%d", i);
# srand(cycle * i);
# size = 1 << 4; //((rand() % 10)+2);
#
# lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
# for (lfs_size_t j = 0; j < size; j++) {
# char c = 'a' + (rand() % 26);
# char r;
# lfs_file_read(&lfs, &file, &r, 1) => 1;
# assert(r == c);
# }
#
# lfs_file_close(&lfs, &file) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# cycle += 1;
# }
#
#exhausted:
# // should still be readable
# lfs_mount(&lfs, &cfg) => 0;
# for (uint32_t i = 0; i < FILES; i++) {
# // check for errors
# sprintf(path, "roadrunner/test%d", i);
# lfs_stat(&lfs, path, &info) => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# LFS_WARN("completed %d cycles", cycle);
#
# // check the wear on our block device
# lfs_testbd_wear_t minwear = -1;
# lfs_testbd_wear_t totalwear = 0;
# lfs_testbd_wear_t maxwear = 0;
# // skip 0 and 1 as superblock movement is intentionally avoided
# for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) {
# lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b);
# printf("%08x: wear %d\n", b, wear);
# assert(wear >= 0);
# if (wear < minwear) {
# minwear = wear;
# }
# if (wear > maxwear) {
# maxwear = wear;
# }
# totalwear += wear;
# }
# lfs_testbd_wear_t avgwear = totalwear / LFS_BLOCK_COUNT;
# LFS_WARN("max wear: %d cycles", maxwear);
# LFS_WARN("avg wear: %d cycles", totalwear / LFS_BLOCK_COUNT);
# LFS_WARN("min wear: %d cycles", minwear);
#
# // find standard deviation^2
# lfs_testbd_wear_t dev2 = 0;
# for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) {
# lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b);
# assert(wear >= 0);
# lfs_testbd_swear_t diff = wear - avgwear;
# dev2 += diff*diff;
# }
# dev2 /= totalwear;
# LFS_WARN("std dev^2: %d", dev2);
# assert(dev2 < 8);
#'''
[[case]] # test that we wear blocks roughly evenly
define.LFS_ERASE_CYCLES = 0xffffffff
define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
define.LFS_BLOCK_CYCLES = [5, 4, 3, 2, 1]
define.CYCLES = 100
define.FILES = 10
if = 'LFS_BLOCK_CYCLES < CYCLES/10'
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "roadrunner") => 0;
lfs_unmount(&lfs) => 0;
uint32_t cycle = 0;
while (cycle < CYCLES) {
lfs_mount(&lfs, &cfg) => 0;
for (uint32_t i = 0; i < FILES; i++) {
// chose name, roughly random seed, and random 2^n size
sprintf(path, "roadrunner/test%d", i);
srand(cycle * i);
size = 1 << 4; //((rand() % 10)+2);
lfs_file_open(&lfs, &file, path,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
for (lfs_size_t j = 0; j < size; j++) {
char c = 'a' + (rand() % 26);
lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
assert(res == 1 || res == LFS_ERR_NOSPC);
if (res == LFS_ERR_NOSPC) {
err = lfs_file_close(&lfs, &file);
assert(err == 0 || err == LFS_ERR_NOSPC);
lfs_unmount(&lfs) => 0;
goto exhausted;
}
}
err = lfs_file_close(&lfs, &file);
assert(err == 0 || err == LFS_ERR_NOSPC);
if (err == LFS_ERR_NOSPC) {
lfs_unmount(&lfs) => 0;
goto exhausted;
}
}
for (uint32_t i = 0; i < FILES; i++) {
// check for errors
sprintf(path, "roadrunner/test%d", i);
srand(cycle * i);
size = 1 << 4; //((rand() % 10)+2);
lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
for (lfs_size_t j = 0; j < size; j++) {
char c = 'a' + (rand() % 26);
char r;
lfs_file_read(&lfs, &file, &r, 1) => 1;
assert(r == c);
}
lfs_file_close(&lfs, &file) => 0;
}
lfs_unmount(&lfs) => 0;
cycle += 1;
}
exhausted:
// should still be readable
lfs_mount(&lfs, &cfg) => 0;
for (uint32_t i = 0; i < FILES; i++) {
// check for errors
sprintf(path, "roadrunner/test%d", i);
lfs_stat(&lfs, path, &info) => 0;
}
lfs_unmount(&lfs) => 0;
LFS_WARN("completed %d cycles", cycle);
// check the wear on our block device
lfs_testbd_wear_t minwear = -1;
lfs_testbd_wear_t totalwear = 0;
lfs_testbd_wear_t maxwear = 0;
// skip 0 and 1 as superblock movement is intentionally avoided
for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) {
lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b);
printf("%08x: wear %d\n", b, wear);
assert(wear >= 0);
if (wear < minwear) {
minwear = wear;
}
if (wear > maxwear) {
maxwear = wear;
}
totalwear += wear;
}
lfs_testbd_wear_t avgwear = totalwear / LFS_BLOCK_COUNT;
LFS_WARN("max wear: %d cycles", maxwear);
LFS_WARN("avg wear: %d cycles", totalwear / LFS_BLOCK_COUNT);
LFS_WARN("min wear: %d cycles", minwear);
// find standard deviation^2
lfs_testbd_wear_t dev2 = 0;
for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) {
lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b);
assert(wear >= 0);
lfs_testbd_swear_t diff = wear - avgwear;
dev2 += diff*diff;
}
dev2 /= totalwear;
LFS_WARN("std dev^2: %d", dev2);
assert(dev2 < 8);
'''

85
tests/test_new.toml Normal file
View File

@@ -0,0 +1,85 @@
#open(1, "5file5.xxxxxxxxxxxx", 0x503) -> 0
# write(1, , 2007)[^ 1499 us] -> 2007
# write(1, , 2007)[^ 1411 us] -> 2007
# write(1, , 2007)[^ 1390 us] -> 2007
# write(1, , 2007)[^ 1401 us] -> 2007
# close(1) -> 0
# open(1, "1file1.xxxx", 0x503) -> 0
# mount
# open(0, "5file5.xxxxxxxxxxxx", 0x3) -> 0
# open(1, "5file5.xxxxxxxxxxxx", 0x503) -> 0
# close(1) -> 0
# open(1, "1file1.xxxx", 0x2) -> 0
# write(0, , 63) -> 63
#a.out: lfs.c:2169: lfs_ctz_find: Assertion `head >= 2 && head <= lfs->cfg->block_count' failed.
# close(0)Aborted
[[case]]
define.FILESIZE5 = '4*CHUNKSIZE5'
define.FILESIZE1 = '4*CHUNKSIZE1'
define.CHUNKSIZE5 = 2007
define.CHUNKSIZE1 = 63
code = '''
lfs_file_t files[2];
uint8_t chunk5[CHUNKSIZE5];
memset(chunk5, 'a', CHUNKSIZE5);
uint8_t chunk1[CHUNKSIZE1];
memset(chunk1, 'b', CHUNKSIZE1);
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &files[1], "5file5.xxxxxxxxxxxx",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_TRUNC) => 0;
for (int i = 0; i < FILESIZE5/CHUNKSIZE5; i++) {
lfs_file_write(&lfs, &files[1], chunk5, CHUNKSIZE5) => CHUNKSIZE5;
}
lfs_file_close(&lfs, &files[1]) => 0;
lfs_file_open(&lfs, &files[1], "1file1.xxxx",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_TRUNC) => 0;
// these should not change the result
// lfs_file_close(&lfs, &files[1]) => 0;
// lfs_unmount(&lfs) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &files[0], "5file5.xxxxxxxxxxxx",
LFS_O_RDWR) => 0;
lfs_file_open(&lfs, &files[1], "5file5.xxxxxxxxxxxx",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_TRUNC) => 0;
lfs_file_close(&lfs, &files[1]) => 0;
lfs_file_open(&lfs, &files[1], "1file1.xxxx",
LFS_O_WRONLY) => 0;
for (int i = 0; i < FILESIZE1/CHUNKSIZE1; i++) {
lfs_file_write(&lfs, &files[1], chunk1, CHUNKSIZE1) => CHUNKSIZE1;
}
lfs_file_close(&lfs, &files[1]) => 0;
memset(chunk5, 'c', CHUNKSIZE5);
for (int i = 0; i < FILESIZE5/CHUNKSIZE5; i++) {
lfs_file_write(&lfs, &files[0], chunk5, CHUNKSIZE5) => CHUNKSIZE5;
}
lfs_file_close(&lfs, &files[0]) => 0;
lfs_unmount(&lfs) => 0;
// check results
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &files[0], "5file5.xxxxxxxxxxxx",
LFS_O_RDONLY) => 0;
for (int i = 0; i < FILESIZE5/CHUNKSIZE5; i++) {
uint8_t rchunk[CHUNKSIZE5];
lfs_file_read(&lfs, &files[0], rchunk, CHUNKSIZE5) => CHUNKSIZE5;
assert(memcmp(rchunk, chunk5, CHUNKSIZE5) == 0);
}
lfs_file_close(&lfs, &files[0]) => 0;
lfs_file_open(&lfs, &files[0], "1file1.xxxx",
LFS_O_RDONLY) => 0;
for (int i = 0; i < FILESIZE1/CHUNKSIZE1; i++) {
uint8_t rchunk[CHUNKSIZE1];
lfs_file_read(&lfs, &files[0], rchunk, CHUNKSIZE1) => CHUNKSIZE1;
assert(memcmp(rchunk, chunk1, CHUNKSIZE1) == 0);
}
lfs_file_close(&lfs, &files[0]) => 0;
lfs_unmount(&lfs) => 0;
'''

View File

@@ -59,7 +59,7 @@ code = '''
[[case]] # reentrant testing for orphans, basically just spam mkdir/remove
reentrant = true
# TODO fix this case, caused by non-DAG trees
#if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
define = [
{FILES=6, DEPTH=1, CYCLES=20},
{FILES=26, DEPTH=1, CYCLES=20},

View File

@@ -31,7 +31,7 @@ code = '''
for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
strcmp(info.name, path) => 0;
}
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
@@ -54,7 +54,7 @@ code = '''
for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
strcmp(info.name, path) => 0;
}
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
@@ -97,7 +97,7 @@ code = '''
for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
strcmp(info.name, path) => 0;
info.size => 0;
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
@@ -113,7 +113,7 @@ code = '''
for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
strcmp(info.name, path) => 0;
info.size => 2;
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
@@ -129,7 +129,7 @@ code = '''
for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0);
strcmp(info.name, path) => 0;
info.size => 2;
}
lfs_dir_read(&lfs, &dir, &info) => 0;
@@ -143,90 +143,12 @@ code = '''
lfs_unmount(&lfs) => 0;
'''
[[case]] # non-DAG tree test
define.LFS_BLOCK_CYCLES = [8, 1]
define.N = [10, 100, 1000]
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
// first create directories
lfs_mkdir(&lfs, "child_1") => 0;
lfs_mkdir(&lfs, "child_2") => 0;
// then move the second child under the first,
// this creates a cycle since the second child should have been
// inserted before the first
lfs_rename(&lfs, "child_2", "child_1/child_2") => 0;
// now try to force second child to relocate
lfs_file_open(&lfs, &file, "child_1/child_2/grandchild",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
size = 0;
for (int i = 0; i < N; i++) {
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
sprintf((char*)buffer, "%d", i);
size = strlen((char*)buffer);
lfs_file_write(&lfs, &file, buffer, size) => size;
lfs_file_sync(&lfs, &file) => 0;
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs);
// check that nothing broke
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir, "/") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "child_1") == 0);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_dir_open(&lfs, &dir, "/child_1") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "child_2") == 0);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_dir_open(&lfs, &dir, "/child_1/child_2") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_REG);
assert(strcmp(info.name, "grandchild") == 0);
assert(info.size == size);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_file_open(&lfs, &file, "child_1/child_2/grandchild",
LFS_O_RDONLY) => 0;
uint8_t rbuffer[1024];
lfs_file_read(&lfs, &file, rbuffer, sizeof(rbuffer)) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
[[case]] # reentrant testing for relocations, this is the same as the
# orphan testing, except here we also set block_cycles so that
# almost every tree operation needs a relocation
reentrant = true
# TODO fix this case, caused by non-DAG trees
#if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
define = [
{FILES=6, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
{FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
@@ -288,7 +210,7 @@ code = '''
[[case]] # reentrant testing for relocations, but now with random renames!
reentrant = true
# TODO fix this case, caused by non-DAG trees
#if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
define = [
{FILES=6, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
{FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},

View File

@@ -27,41 +27,55 @@ code = '''
'''
[[case]] # expanding superblock
define.BLOCK_CYCLES = [32, 33, 1]
define.LFS_BLOCK_CYCLES = [32, 33, 1]
define.N = [10, 100, 1000]
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
for (int i = 0; i < N; i++) {
lfs_mkdir(&lfs, "dummy") => 0;
lfs_file_open(&lfs, &file, "dummy",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_remove(&lfs, "dummy") => 0;
}
lfs_unmount(&lfs) => 0;
// one last check after power-cycle
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "dummy") => 0;
lfs_file_open(&lfs, &file, "dummy",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_unmount(&lfs) => 0;
'''
[[case]] # expanding superblock with power cycle
define.BLOCK_CYCLES = [32, 33, 1]
define.LFS_BLOCK_CYCLES = [32, 33, 1]
define.N = [10, 100, 1000]
code = '''
lfs_format(&lfs, &cfg) => 0;
for (int i = 0; i < N; i++) {
lfs_mount(&lfs, &cfg) => 0;
// remove lingering dummy?
err = lfs_remove(&lfs, "dummy");
err = lfs_stat(&lfs, "dummy", &info);
assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
lfs_mkdir(&lfs, "dummy") => 0;
if (!err) {
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_remove(&lfs, "dummy") => 0;
}
lfs_file_open(&lfs, &file, "dummy",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_unmount(&lfs) => 0;
}
@@ -69,11 +83,12 @@ code = '''
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_unmount(&lfs) => 0;
'''
[[case]] # reentrant expanding superblock
define.BLOCK_CYCLES = [2, 1]
define.LFS_BLOCK_CYCLES = [2, 1]
define.N = 24
reentrant = true
code = '''
@@ -85,12 +100,20 @@ code = '''
for (int i = 0; i < N; i++) {
// remove lingering dummy?
err = lfs_remove(&lfs, "dummy");
err = lfs_stat(&lfs, "dummy", &info);
assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
lfs_mkdir(&lfs, "dummy") => 0;
if (!err) {
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_remove(&lfs, "dummy") => 0;
}
lfs_file_open(&lfs, &file, "dummy",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
}
lfs_unmount(&lfs) => 0;
@@ -99,5 +122,6 @@ code = '''
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_unmount(&lfs) => 0;
'''