Compare commits

..

9 Commits

Author SHA1 Message Date
Christopher Haster
20b46ded5a WIP making good progress, found a solution for the cycle issue
Found solution for cycle issue for tail+branch solution, though it does
require creating an extra metadata-pair on relocate.

Need to get passing the advanced relocation tests, this is currently
failing. Also need to figure out why I added the assert on tail end, it
may be related.
2020-03-10 08:43:28 -05:00
Christopher Haster
eecb06a9dc WIP crazy new idea work in progress
passing non-reentrant tests already!
2020-03-06 20:14:27 -06:00
Christopher Haster
3ee291de59 WIP so close 2020-02-28 06:03:59 -06:00
Christopher Haster
f7bc22937a WIP branches in dir 2020-02-28 04:51:25 -06:00
Christopher Haster
26ed6dee7d WIP initial commit of branch idea to repair non-DAG trees 2020-02-26 15:50:38 -06:00
Christopher Haster
e33bef55d3 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-02-24 08:18:28 -06: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
12 changed files with 1664 additions and 546 deletions

1208
lfs.c

File diff suppressed because it is too large Load Diff

11
lfs.h
View File

@@ -111,12 +111,14 @@ enum lfs_type {
LFS_TYPE_INLINESTRUCT = 0x201, LFS_TYPE_INLINESTRUCT = 0x201,
LFS_TYPE_SOFTTAIL = 0x600, LFS_TYPE_SOFTTAIL = 0x600,
LFS_TYPE_HARDTAIL = 0x601, LFS_TYPE_HARDTAIL = 0x601,
LFS_TYPE_BRANCH = 0x681,
LFS_TYPE_MOVESTATE = 0x7ff, LFS_TYPE_MOVESTATE = 0x7ff,
// internal chip sources // internal chip sources
LFS_FROM_NOOP = 0x000, LFS_FROM_NOOP = 0x000,
LFS_FROM_MOVE = 0x101, LFS_FROM_MOVE = 0x101,
LFS_FROM_USERATTRS = 0x102, LFS_FROM_DROP = 0x102,
LFS_FROM_USERATTRS = 0x103,
}; };
// File open flags // File open flags
@@ -310,9 +312,11 @@ typedef struct lfs_mdir {
uint32_t etag; uint32_t etag;
uint16_t count; uint16_t count;
bool erased; bool erased;
bool first; // TODO come on
bool split; bool split;
bool first; bool mustrelocate; // TODO not great either
lfs_block_t tail[2]; lfs_block_t tail[2];
lfs_block_t branch[2];
} lfs_mdir_t; } lfs_mdir_t;
// littlefs directory type // littlefs directory type
@@ -367,6 +371,9 @@ typedef struct lfs {
lfs_cache_t pcache; lfs_cache_t pcache;
lfs_block_t root[2]; lfs_block_t root[2];
lfs_block_t relocate_tail[2];
lfs_block_t relocate_end[2];
bool relocate_do_hack; // TODO fixme
struct lfs_mlist { struct lfs_mlist {
struct lfs_mlist *next; struct lfs_mlist *next;
uint16_t id; uint16_t id;

View File

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

View File

@@ -18,9 +18,10 @@ TAG_TYPES = {
'ctzstruct': (0x7ff, 0x202), 'ctzstruct': (0x7ff, 0x202),
'inlinestruct': (0x7ff, 0x201), 'inlinestruct': (0x7ff, 0x201),
'userattr': (0x700, 0x300), 'userattr': (0x700, 0x300),
'tail': (0x700, 0x600), 'tail': (0x700, 0x600), # TODO rename these?
'softtail': (0x7ff, 0x600), 'softtail': (0x7ff, 0x600),
'hardtail': (0x7ff, 0x601), 'hardtail': (0x7ff, 0x601),
'branch': (0x7ff, 0x681),
'gstate': (0x700, 0x700), 'gstate': (0x700, 0x700),
'movestate': (0x7ff, 0x7ff), 'movestate': (0x7ff, 0x7ff),
'crc': (0x700, 0x500), 'crc': (0x700, 0x500),
@@ -103,7 +104,7 @@ class Tag:
def mkmask(self): def mkmask(self):
return Tag( return Tag(
0x700 if self.isunique else 0x7ff, 0x780 if self.is_('tail') else 0x700 if self.isunique else 0x7ff, # TODO best way?
0x3ff if self.isattr else 0, 0x3ff if self.isattr else 0,
0) 0)
@@ -233,8 +234,8 @@ class MetadataPair:
def __lt__(self, other): def __lt__(self, other):
# corrupt blocks don't count # corrupt blocks don't count
if not self and other: if not self or not other:
return True return bool(other)
# use sequence arithmetic to avoid overflow # use sequence arithmetic to avoid overflow
return not ((other.rev - self.rev) & 0x80000000) return not ((other.rev - self.rev) & 0x80000000)
@@ -318,6 +319,14 @@ def main(args):
# find most recent pair # find most recent pair
mdir = MetadataPair(blocks) 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: if args.all:
mdir.dump_all(truncate=not args.no_truncate) mdir.dump_all(truncate=not args.no_truncate)
elif args.log: elif args.log:

View File

@@ -5,6 +5,7 @@ import sys
import json import json
import io import io
import itertools as it import itertools as it
import collections as c
from readmdir import Tag, MetadataPair from readmdir import Tag, MetadataPair
def popc(x): def popc(x):
@@ -13,31 +14,29 @@ def popc(x):
def ctz(x): def ctz(x):
return len(bin(x)) - len(bin(x).rstrip('0')) return len(bin(x)) - len(bin(x).rstrip('0'))
def dumpentries(args, mdir, f): def dumpentries(args, mdir, mdirs, f):
for k, id_ in enumerate(mdir.ids): for k, id_ in enumerate(mdir.ids):
name = mdir[Tag('name', id_, 0)] name = mdir[Tag('name', id_, 0)]
struct_ = mdir[Tag('struct', id_, 0)] struct_ = mdir[Tag('struct', id_, 0)]
f.write("id %d %s %s" % ( desc = "id %d %s %s" % (
id_, name.typerepr(), id_, name.typerepr(),
json.dumps(name.data.decode('utf8')))) json.dumps(name.data.decode('utf8')))
if struct_.is_('dirstruct'): if struct_.is_('dirstruct'):
f.write(" dir {%#x, %#x}" % struct.unpack( pair = struct.unpack('<II', struct_.data[:8].ljust(8, b'\xff'))
'<II', struct_.data[:8].ljust(8, b'\xff'))) desc += " dir {%#x, %#x}%s" % (
pair[0], pair[1],
'?' if frozenset(pair) not in mdirs else '')
if struct_.is_('ctzstruct'): if struct_.is_('ctzstruct'):
f.write(" ctz {%#x} size %d" % struct.unpack( desc += " ctz {%#x} size %d" % struct.unpack(
'<II', struct_.data[:8].ljust(8, b'\xff'))) '<II', struct_.data[:8].ljust(8, b'\xff'))
if struct_.is_('inlinestruct'): if struct_.is_('inlinestruct'):
f.write(" inline size %d" % struct_.size) desc += " inline size %d" % struct_.size
f.write("\n")
if args.data and struct_.is_('inlinestruct'): data = None
for i in range(0, len(struct_.data), 16): if struct_.is_('inlinestruct'):
f.write(" %08x: %-47s %-16s\n" % ( data = struct_.data
i, ' '.join('%02x' % c for c in struct_.data[i:i+16]), elif struct_.is_('ctzstruct'):
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, struct_.data[i:i+16]))))
elif args.data and struct_.is_('ctzstruct'):
block, size = struct.unpack( block, size = struct.unpack(
'<II', struct_.data[:8].ljust(8, b'\xff')) '<II', struct_.data[:8].ljust(8, b'\xff'))
data = [] data = []
@@ -51,43 +50,62 @@ def dumpentries(args, mdir, f):
data.append(dat[4*(ctz(i)+1) if i != 0 else 0:]) data.append(dat[4*(ctz(i)+1) if i != 0 else 0:])
block, = struct.unpack('<I', dat[:4].ljust(4, b'\xff')) block, = struct.unpack('<I', dat[:4].ljust(4, b'\xff'))
i -= 1 i -= 1
data = bytes(it.islice( data = bytes(it.islice(
it.chain.from_iterable(reversed(data)), size)) 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" % ( f.write(" %08x: %-47s %-16s\n" % (
i, ' '.join('%02x' % c for c in data[i:i+16]), i, ' '.join('%02x' % c for c in data[i:i+16]),
''.join(c if c >= ' ' and c <= '~' else '.' ''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, data[i:i+16])))) 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): def main(args):
superblock = None
gstate = b'\0\0\0\0\0\0\0\0\0\0\0\0'
mdirs = c.OrderedDict()
corrupted = []
cycle = False
with open(args.disk, 'rb') as f: with open(args.disk, 'rb') as f:
dirs = []
superblock = None
gstate = b''
mdirs = []
cycle = False
tail = (args.block1, args.block2) tail = (args.block1, args.block2)
hard = False while tail:
while True: if frozenset(tail) in mdirs:
for m in it.chain((m for d in dirs for m in d), mdirs): # cycle detected
if set(m.blocks) == set(tail): cycle = tail
# cycle detected
cycle = m.blocks
if cycle:
break break
# load mdir # load mdir
@@ -110,6 +128,13 @@ def main(args):
except KeyError: except KeyError:
mdir.tail = None mdir.tail = None
try:
mdir.branch = mdir[Tag('branch', 0, 0)]
if mdir.branch.size != 8 or mdir.branch.data == 8*b'\xff':
mdir.branch = None
except KeyError:
mdir.branch = None
# have superblock? # have superblock?
try: try:
nsuperblock = mdir[ nsuperblock = mdir[
@@ -126,106 +151,135 @@ def main(args):
except KeyError: except KeyError:
pass pass
# add to directories # corrupted?
mdirs.append(mdir) if not mdir:
if mdir.tail is None or not mdir.tail.is_('hardtail'): corrupted.append(mdir)
dirs.append(mdirs)
mdirs = []
if mdir.tail is None: # add to metadata-pairs
break mdirs[frozenset(mdir.blocks)] = mdir
tail = (struct.unpack('<II', mdir.tail.data)
if mdir.tail else None)
tail = struct.unpack('<II', mdir.tail.data) # derive paths and build directories
hard = mdir.tail.is_('hardtail') dirs = {}
rogue = {}
# find paths pending = [('/', (args.block1, args.block2))]
dirtable = {}
for dir in dirs:
dirtable[frozenset(dir[0].blocks)] = dir
pending = [("/", dirs[0])]
while pending: while pending:
path, dir = pending.pop(0) path, branch = pending.pop(0)
for mdir in dir: dir = []
while branch and frozenset(branch) in mdirs:
mdir = mdirs[frozenset(branch)]
dir.append(mdir)
for tag in mdir.tags: for tag in mdir.tags:
if tag.is_('dir'): if tag.is_('dir'):
try: try:
npath = tag.data.decode('utf8') npath = path + '/' + tag.data.decode('utf8')
npath = npath.replace('//', '/')
dirstruct = mdir[Tag('dirstruct', tag.id, 0)] dirstruct = mdir[Tag('dirstruct', tag.id, 0)]
nblocks = struct.unpack('<II', dirstruct.data) npair = struct.unpack('<II', dirstruct.data)
nmdir = dirtable[frozenset(nblocks)] pending.append((npath, npair))
pending.append(((path + '/' + npath), nmdir))
except KeyError: except KeyError:
pass pass
dir[0].path = path.replace('//', '/') branch = (struct.unpack('<II', mdir.branch.data)
if mdir.branch else None)
# dump tree if not dir:
if not args.superblock and not args.gstate and not args.mdirs: rogue[path] = branch
args.superblock = True else:
args.gstate = True dirs[path] = dir
args.mdirs = True
if args.superblock and superblock: # also find orphans
print("superblock %s v%d.%d" % ( not_orphans = {frozenset(mdir.blocks)
json.dumps(superblock[0].data.decode('utf8')), for dir in dirs.values()
struct.unpack('<H', superblock[1].data[2:2+2])[0], for mdir in dir}
struct.unpack('<H', superblock[1].data[0:0+2])[0])) orphans = []
print( for pair, mdir in mdirs.items():
" block_size %d\n" if pair not in not_orphans:
" block_count %d\n" if len(orphans) > 0 and (pair == frozenset(
" name_max %d\n" struct.unpack('<II', orphans[-1][-1].tail.data))):
" file_max %d\n" orphans[-1].append(mdir)
" attr_max %d" % struct.unpack( else:
'<IIIII', superblock[1].data[4:4+20].ljust(20, b'\xff'))) orphans.append([mdir])
if args.gstate and gstate: # print littlefs + version info
print("gstate 0x%s" % ''.join('%02x' % c for c in gstate)) version = ('?', '?')
tag = Tag(struct.unpack('<I', gstate[0:4].ljust(4, b'\xff'))[0]) if superblock:
blocks = struct.unpack('<II', gstate[4:4+8].ljust(8, b'\xff')) version = tuple(reversed(
if tag.size: struct.unpack('<HH', superblock[1].data[0:4].ljust(4, b'\xff'))))
print(" orphans %d" % tag.size) print("%-47s%s" % ("littlefs v%s.%s" % version,
if tag.type: "data (truncated, if it fits)"
print(" move dir {%#x, %#x} id %d" % ( if not any([args.no_truncate, args.tags, args.log, args.all]) else ""))
blocks[0], blocks[1], tag.id))
if args.mdirs: # print gstate
for i, dir in enumerate(dirs): badgstate = None
print("dir %s" % (json.dumps(dir[0].path) print("gstate 0x%s" % ''.join('%02x' % c for c in gstate))
if hasattr(dir[0], 'path') else '(orphan)')) 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 or not tag.isvalid:
print(" orphans >=%d" % max(tag.size, 1))
if tag.type:
if frozenset(blocks) not in mdirs:
badgstate = gstate
print(" move dir {%#x, %#x}%s id %d" % (
blocks[0], blocks[1],
'?' if frozenset(blocks) not in mdirs else '',
tag.id))
for j, mdir in enumerate(dir): # print dir info
print("mdir {%#x, %#x} rev %d%s" % ( for path, dir in it.chain(
mdir.blocks[0], mdir.blocks[1], mdir.rev, sorted(dirs.items()),
' (corrupted)' if not mdir else '')) zip(it.repeat(None), orphans)):
print("dir %s" % json.dumps(path) if path else "orphaned")
f = io.StringIO() for j, mdir in enumerate(dir):
if args.tags: print("mdir {%#x, %#x} rev %d (was %d)%s%s" % (
mdir.dump_tags(f, truncate=not args.no_truncate) mdir.blocks[0], mdir.blocks[1], mdir.rev, mdir.pair[1].rev,
elif args.log: ' (corrupted!)' if not mdir else '',
mdir.dump_log(f, truncate=not args.no_truncate) ' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
elif args.all: if mdir.tail else ''))
mdir.dump_all(f, truncate=not args.no_truncate)
else:
dumpentries(args, mdir, f)
lines = list(filter(None, f.getvalue().split('\n'))) f = io.StringIO()
for k, line in enumerate(lines): if args.tags:
print("%s %s" % ( mdir.dump_tags(f, truncate=not args.no_truncate)
' ' if j == len(dir)-1 else elif args.log:
'v' if k == len(lines)-1 else mdir.dump_log(f, truncate=not args.no_truncate)
'|', elif args.all:
line)) mdir.dump_all(f, truncate=not args.no_truncate)
else:
dumpentries(args, mdir, mdirs, 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
'|' if path else '.',
line))
errcode = 0
for mdir in corrupted:
errcode = errcode or 1
print("*** corrupted mdir {%#x, %#x}! ***" % (
mdir.blocks[0], mdir.blocks[1]))
for path, pair in rogue.items():
errcode = errcode or 2
print("*** couldn't find dir %s {%#x, %#x}! ***" % (
json.dumps(path), pair[0], pair[1]))
if badgstate:
errcode = errcode or 3
print("*** bad gstate 0x%s! ***" %
''.join('%02x' % c for c in gstate))
if cycle: if cycle:
print("*** cycle detected! -> {%#x, %#x} ***" % (cycle[0], cycle[1])) errcode = errcode or 4
print("*** cycle detected {%#x, %#x}! ***" % (
cycle[0], cycle[1]))
if cycle: return errcode
return 2
elif not all(mdir for dir in dirs for mdir in dir):
return 1
else:
return 0;
if __name__ == "__main__": if __name__ == "__main__":
import argparse import argparse
@@ -242,20 +296,12 @@ if __name__ == "__main__":
parser.add_argument('block2', nargs='?', default=1, parser.add_argument('block2', nargs='?', default=1,
type=lambda x: int(x, 0), type=lambda x: int(x, 0),
help="Optional second block address for finding the root.") 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', parser.add_argument('-t', '--tags', action='store_true',
help="Show metadata tags instead of reconstructing entries.") help="Show metadata tags instead of reconstructing entries.")
parser.add_argument('-l', '--log', action='store_true', parser.add_argument('-l', '--log', action='store_true',
help="Show tags in log.") help="Show tags in log.")
parser.add_argument('-a', '--all', action='store_true', parser.add_argument('-a', '--all', action='store_true',
help="Show all tags in log, included tags in corrupted commits.") 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', 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())) sys.exit(main(parser.parse_args()))

View File

@@ -231,7 +231,7 @@ class TestCase:
ncmd.extend(['-ex', 'r']) ncmd.extend(['-ex', 'r'])
if failure.assert_: if failure.assert_:
ncmd.extend(['-ex', 'up 2']) ncmd.extend(['-ex', 'up 2'])
elif gdb == 'start': elif gdb == 'start' or isinstance(gdb, int):
ncmd.extend([ ncmd.extend([
'-ex', 'b %s:%d' % (self.suite.path, self.code_lineno), '-ex', 'b %s:%d' % (self.suite.path, self.code_lineno),
'-ex', 'r']) '-ex', 'r'])
@@ -329,7 +329,9 @@ class ReentrantTestCase(TestCase):
persist = 'noerase' persist = 'noerase'
# exact cycle we should drop into debugger? # exact cycle we should drop into debugger?
if gdb and failure and failure.cycleno == cycles: if gdb and failure and (
failure.cycleno == cycles or
(isinstance(gdb, int) and gdb == cycles)):
return super().test(gdb=gdb, persist=persist, cycles=cycles, return super().test(gdb=gdb, persist=persist, cycles=cycles,
failure=failure, **args) failure=failure, **args)
@@ -760,7 +762,8 @@ if __name__ == "__main__":
help="Store disk image in a file.") help="Store disk image in a file.")
parser.add_argument('-b', '--build', action='store_true', parser.add_argument('-b', '--build', action='store_true',
help="Only build the tests, do not execute.") help="Only build the tests, do not execute.")
parser.add_argument('-g', '--gdb', choices=['init', 'start', 'assert'], parser.add_argument('-g', '--gdb', metavar='{init,start,assert},CYCLE',
type=lambda n: n if n in {'init', 'start', 'assert'} else int(n, 0),
nargs='?', const='assert', nargs='?', const='assert',
help="Drop into gdb on test failure.") help="Drop into gdb on test failure.")
parser.add_argument('--no-internal', action='store_true', parser.add_argument('--no-internal', action='store_true',

View File

@@ -246,6 +246,8 @@ code = '''
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0; lfs_file_close(&lfs, &file) => 0;
} }
// TODO rm me
lfs_mkdir(&lfs, "a") => 0;
lfs_unmount(&lfs) => 0; lfs_unmount(&lfs) => 0;
lfs_mount(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0;
@@ -256,6 +258,9 @@ code = '''
lfs_dir_read(&lfs, &dir, &info) => 1; lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR); assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "..") == 0); assert(strcmp(info.name, "..") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "a") == 0);
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
sprintf(path, "file%03d", i); sprintf(path, "file%03d", i);
lfs_dir_read(&lfs, &dir, &info) => 1; lfs_dir_read(&lfs, &dir, &info) => 1;

285
tests/test_evil.toml Normal file
View File

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

View File

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

View File

@@ -31,7 +31,7 @@ code = '''
for (int i = 0; i < COUNT; i++) { for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i); sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1; 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_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0; lfs_dir_close(&lfs, &dir) => 0;
@@ -54,7 +54,7 @@ code = '''
for (int i = 0; i < COUNT; i++) { for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i); sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1; 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_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0; lfs_dir_close(&lfs, &dir) => 0;
@@ -97,7 +97,7 @@ code = '''
for (int i = 0; i < COUNT; i++) { for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i); sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1; lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0); strcmp(info.name, path) => 0;
info.size => 0; info.size => 0;
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
@@ -113,7 +113,7 @@ code = '''
for (int i = 0; i < COUNT; i++) { for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i); sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1; lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0); strcmp(info.name, path) => 0;
info.size => 2; info.size => 2;
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
@@ -129,7 +129,7 @@ code = '''
for (int i = 0; i < COUNT; i++) { for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i); sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1; lfs_dir_read(&lfs, &dir, &info) => 1;
assert(strcmp(info.name, path) == 0); strcmp(info.name, path) => 0;
info.size => 2; info.size => 2;
} }
lfs_dir_read(&lfs, &dir, &info) => 0; lfs_dir_read(&lfs, &dir, &info) => 0;
@@ -143,84 +143,6 @@ code = '''
lfs_unmount(&lfs) => 0; 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 [[case]] # reentrant testing for relocations, this is the same as the
# orphan testing, except here we also set block_cycles so that # orphan testing, except here we also set block_cycles so that
# almost every tree operation needs a relocation # almost every tree operation needs a relocation

View File

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