mirror of
https://github.com/littlefs-project/littlefs.git
synced 2025-12-01 12:20:02 +00:00
dbgerr.py and dbgtag.py have proven to be incredibly useful for quick debugging/introspection, so I figured why not have more of that. My favorite part is being able to quickly see all flags set on an open file handle: (gdb) p file.o.o.flags $2 = 24117517 (gdb) !./scripts/dbgflags.py o 24117517 LFS_O_WRONLY 0x00000001 Open a file as write only LFS_O_CREAT 0x00000004 Create a file if it does not exist LFS_O_EXCL 0x00000008 Fail if a file already exists LFS_O_DESYNC 0x00000100 Do not sync or recieve file updates LFS_o_REG 0x01000000 Type = regular-file LFS_o_UNFLUSH 0x00100000 File's data does not match disk LFS_o_UNSYNC 0x00200000 File's metadata does not match disk LFS_o_UNCREAT 0x00400000 File does not exist yet The only concern is if dbgflags.py falls out-of-sync often, I suspect flag encoding will have quite a bit more churn than flags/tags. But we can always drop this script in the future if this turns into a problem. --- While poking around this also ended up with a bunch of other small changes: - Added LFS_*_MODE masks for consistency with other "type<->flag embeddings" - Added compat flag comments - Adopted lowercase prefix for internal flags (LFS_o_ZOMBIE), though not sure if I'll keep this yet... - Tweaked dbgerr.py to also match ERR_ prefixes and to ignore case
314 lines
14 KiB
Python
Executable File
314 lines
14 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# prevent local imports
|
|
if __name__ == "__main__":
|
|
__import__('sys').path.pop(0)
|
|
|
|
import collections as co
|
|
|
|
|
|
ALIASES = [
|
|
{'O', 'OPEN'},
|
|
{'A', 'ATTR'},
|
|
{'F', 'FORMAT'},
|
|
{'M', 'MOUNT'},
|
|
{'GC'},
|
|
{'I', 'INFO'},
|
|
{'T', 'TRAVERSAL'},
|
|
{'RC', 'RCOMPAT'},
|
|
{'WC', 'WCOMPAT'},
|
|
{'OC', 'OCOMPAT'}
|
|
]
|
|
|
|
FLAGS = [
|
|
# File open flags
|
|
('O', 'MODE', 3, "The file's access mode" ),
|
|
('^', 'RDONLY', 0, "Open a file as read only" ),
|
|
('^', 'WRONLY', 1, "Open a file as write only" ),
|
|
('^', 'RDWR', 2, "Open a file as read and write" ),
|
|
('O', 'CREAT', 0x00000004, "Create a file if it does not exist" ),
|
|
('O', 'EXCL', 0x00000008, "Fail if a file already exists" ),
|
|
('O', 'TRUNC', 0x00000010, "Truncate the existing file to zero size" ),
|
|
('O', 'APPEND', 0x00000020, "Move to end of file on every write" ),
|
|
('O', 'FLUSH', 0x00000040, "Flush data on every write" ),
|
|
('O', 'SYNC', 0x00000080, "Sync metadata on every write" ),
|
|
('O', 'DESYNC', 0x00000100, "Do not sync or recieve file updates" ),
|
|
('O', 'CKMETA', 0x00010000, "Check metadata checksums" ),
|
|
('O', 'CKDATA', 0x00020000, "Check metadata + data checksums" ),
|
|
|
|
('o', 'TYPE', 0xff000000, "The file's type" ),
|
|
('^', 'REG', 0x01000000, "Type = regular-file" ),
|
|
('^', 'DIR', 0x02000000, "Type = directory" ),
|
|
('^', 'BOOKMARK', 0x04000000, "Type = bookmark" ),
|
|
('^', 'STICKYNOTE',0x05000000, "Type = stickynote" ),
|
|
('^', 'TRAVERSAL', 0x09000000, "Type = traversal" ),
|
|
('o', 'UNFLUSH', 0x00100000, "File's data does not match disk" ),
|
|
('o', 'UNSYNC', 0x00200000, "File's metadata does not match disk" ),
|
|
('o', 'UNCREAT', 0x00400000, "File does not exist yet" ),
|
|
('o', 'ZOMBIE', 0x00800000, "File has been removed" ),
|
|
|
|
# Custom attribute flags
|
|
('A', 'MODE', 3, "The attr's access mode" ),
|
|
('^', 'RDONLY', 0, "Open an attr as read only" ),
|
|
('^', 'WRONLY', 1, "Open an attr as write only" ),
|
|
('^', 'RDWR', 2, "Open an attr as read and write" ),
|
|
('A', 'LAZY', 0x04, "Only write attr if file changed" ),
|
|
|
|
# Filesystem format flags
|
|
('F', 'MODE', 1, "Format's access mode" ),
|
|
('^', 'RDWR', 0, "Format the filesystem as read and write" ),
|
|
('F', 'CKPROGS', 0x00100000, "Check progs by reading back progged data" ),
|
|
('F', 'CKFETCHES', 0x00200000, "Check block checksums before first use" ),
|
|
('F', 'CKPARITY', 0x00400000, "Check metadata tag parity bits" ),
|
|
('F', 'CKDATACKSUMS',
|
|
0x08000000, "Check data checksums on reads" ),
|
|
|
|
('F', 'CKMETA', 0x00010000, "Check metadata checksums" ),
|
|
('F', 'CKDATA', 0x00020000, "Check metadata + data checksums" ),
|
|
|
|
# Filesystem mount flags
|
|
('M', 'MODE', 1, "Mount's access mode" ),
|
|
('^', 'RDWR', 0, "Mount the filesystem as read and write" ),
|
|
('^', 'RDONLY', 1, "Mount the filesystem as read only" ),
|
|
('M', 'FLUSH', 0x00000040, "Open all files with LFS_O_FLUSH" ),
|
|
('M', 'SYNC', 0x00000080, "Open all files with LFS_O_SYNC" ),
|
|
('M', 'CKPROGS', 0x00100000, "Check progs by reading back progged data" ),
|
|
('M', 'CKFETCHES', 0x00200000, "Check block checksums before first use" ),
|
|
('M', 'CKPARITY', 0x00400000, "Check metadata tag parity bits" ),
|
|
('M', 'CKDATACKSUMS',
|
|
0x08000000, "Check data checksums on reads" ),
|
|
|
|
('M', 'MKCONSISTENT',
|
|
0x00001000, "Make the filesystem consistent" ),
|
|
('M', 'LOOKAHEAD', 0x00002000, "Populate lookahead buffer" ),
|
|
('M', 'COMPACT', 0x00008000, "Compact metadata logs" ),
|
|
('M', 'CKMETA', 0x00010000, "Check metadata checksums" ),
|
|
('M', 'CKDATA', 0x00020000, "Check metadata + data checksums" ),
|
|
|
|
('m', 'UNTIDY', 0x00001000, "Filesystem may have orphaned stickynotes" ),
|
|
|
|
# GC flags
|
|
('GC', 'MKCONSISTENT',
|
|
0x00001000, "Make the filesystem consistent" ),
|
|
('GC', 'LOOKAHEAD',0x00002000, "Populate lookahead buffer" ),
|
|
('GC', 'COMPACT', 0x00008000, "Compact metadata logs" ),
|
|
('GC', 'CKMETA', 0x00010000, "Check metadata checksums" ),
|
|
('GC', 'CKDATA', 0x00020000, "Check metadata + data checksums" ),
|
|
|
|
# Filesystem info flags
|
|
('I', 'RDONLY', 0x00000001, "Mounted read only" ),
|
|
('I', 'FLUSH', 0x00000040, "Mounted with LFS_M_FLUSH" ),
|
|
('I', 'SYNC', 0x00000080, "Mounted with LFS_M_SYNC" ),
|
|
('I', 'CKPROGS', 0x00100000, "Mounted with LFS_M_CKPROGS" ),
|
|
('I', 'CKFETCHES', 0x00200000, "Mounted with LFS_M_CKFETCHES" ),
|
|
('I', 'CKPARITY', 0x00400000, "Mounted with LFS_M_CKPARITY" ),
|
|
('I', 'CKDATACKSUMS',
|
|
0x08000000, "Mounted with LFS_M_CKDATACKSUMS" ),
|
|
|
|
('I', 'MKCONSISTENT',
|
|
0x00001000, "Filesystem needs mkconsistent to write" ),
|
|
('I', 'LOOKAHEAD', 0x00002000, "Lookahead buffer is not full" ),
|
|
('I', 'COMPACT', 0x00008000, "Filesystem may have uncompacted metadata" ),
|
|
('I', 'CKMETA', 0x00010000, "Metadata checksums not checked recently" ),
|
|
('I', 'CKDATA', 0x00020000, "Data checksums not checked recently" ),
|
|
|
|
# Traversal flags
|
|
('T', 'MTREEONLY', 0x00000800, "Only traverse the mtree" ),
|
|
('T', 'MKCONSISTENT',
|
|
0x00001000, "Make the filesystem consistent" ),
|
|
('T', 'LOOKAHEAD', 0x00002000, "Populate lookahead buffer" ),
|
|
('T', 'COMPACT', 0x00008000, "Compact metadata logs" ),
|
|
('T', 'CKMETA', 0x00010000, "Check metadata checksums" ),
|
|
('T', 'CKDATA', 0x00020000, "Check metadata + data checksums" ),
|
|
|
|
('t', 'TSTATE', 0x0000000f, "The traversal's current tstate" ),
|
|
('^', 'MROOTANCHOR',
|
|
0x00000000, "Tstate = mroot-anchor" ),
|
|
('^', 'MROOTCHAIN',0x00000001, "Tstate = mroot-chain" ),
|
|
('^', 'MTREE', 0x00000002, "Tstate = mtree" ),
|
|
('^', 'MDIRS', 0x00000003, "Tstate = mtree-mdirs" ),
|
|
('^', 'MDIR', 0x00000004, "Tstate = mdir" ),
|
|
('^', 'BTREE', 0x00000005, "Tstate = btree" ),
|
|
('^', 'OMDIRS', 0x00000006, "Tstate = open-mdirs" ),
|
|
('^', 'OBTREE', 0x00000007, "Tstate = open-btree" ),
|
|
('^', 'DONE', 0x00000008, "Tstate = done" ),
|
|
('t', 'BTYPE', 0x000000f0, "The traversal's current btype" ),
|
|
('^', 'MDIR', 0x00000010, "Btype = mdir" ),
|
|
('^', 'BTREE', 0x00000020, "Btype = btree" ),
|
|
('^', 'DATA', 0x00000030, "Btype = data" ),
|
|
('t', 'DIRTY', 0x00000100, "Filesystem modified during traversal" ),
|
|
('t', 'MUTATED', 0x00000200, "Filesystem modified by traversal" ),
|
|
|
|
# Read-compat flags
|
|
('RCOMPAT', 'NONSTANDARD',
|
|
0x0001, "Non-standard filesystem format" ),
|
|
('RCOMPAT', 'MLEAF', 0x0002, "May use a single mdir pointer" ),
|
|
('RCOMPAT', 'MTREE', 0x0008, "May use an mdir btree" ),
|
|
('RCOMPAT', 'BSPROUT', 0x0010, "Files may use inlined data" ),
|
|
('RCOMPAT', 'BLEAF', 0x0020, "Files may use single block pointers" ),
|
|
('RCOMPAT', 'BSHRUB', 0x0040, "Files may use inlined btrees" ),
|
|
('RCOMPAT', 'BTREE', 0x0080, "Files may use btrees" ),
|
|
('RCOMPAT', 'GRM', 0x0100, "May use a global-remove" ),
|
|
|
|
('rcompat', 'OVERFLOW',0x8000, "Can't represent all flags" ),
|
|
|
|
# Write-compat flags
|
|
('WCOMPAT', 'NONSTANDARD',
|
|
0x0001, "Non-standard filesystem format" ),
|
|
|
|
('wcompat', 'OVERFLOW',0x8000, "Can't represent all flags" ),
|
|
|
|
# Optional-compat flags
|
|
('OCOMPAT', 'NONSTANDARD',
|
|
0x0001, "Non-standard filesystem format" ),
|
|
|
|
('ocompat', 'OVERFLOW',0x8000, "Can't represent all flags" ),
|
|
]
|
|
|
|
|
|
def main(flags, *,
|
|
list=False,
|
|
all=False):
|
|
import builtins
|
|
list_, list = list, builtins.list
|
|
all_, all = all, builtins.all
|
|
|
|
# first compile prefixes
|
|
prefixes = {}
|
|
for a in ALIASES:
|
|
for k in a:
|
|
prefixes[k] = a
|
|
for p, n, f, h in FLAGS:
|
|
if p not in prefixes:
|
|
prefixes[p] = {p}
|
|
|
|
# only look at specific prefix?
|
|
flags_ = []
|
|
prefix = set()
|
|
for f in flags:
|
|
if f.upper() in prefixes:
|
|
prefix.update(prefixes[f.upper()])
|
|
else:
|
|
flags_.append(f)
|
|
|
|
# filter by prefix
|
|
flags__ = []
|
|
types__ = co.defaultdict(lambda: set())
|
|
for p, n, f, h in FLAGS:
|
|
if p == '^':
|
|
p = last_p
|
|
t = last_t
|
|
types__[p].add(t)
|
|
else:
|
|
t = None
|
|
last_p = p
|
|
last_t = f
|
|
if not prefix or p.upper() in prefix:
|
|
flags__.append((p, t, n, f, h))
|
|
|
|
lines = []
|
|
# list all known flags
|
|
if list_:
|
|
for p, t, n, f, h in flags__:
|
|
if not all_ and (t is not None or p[0].islower()):
|
|
continue
|
|
lines.append(('LFS_%s_%s' % (p, n), '0x%08x' % f, h))
|
|
|
|
# find flags by name or value
|
|
else:
|
|
for f_ in flags_:
|
|
found = False
|
|
# find by LFS_+prefix+_+name
|
|
for p, t, n, f, h in flags__:
|
|
if 'LFS_%s_%s' % (p, n) == f_.upper():
|
|
lines.append(('LFS_%s_%s' % (p, n), '0x%08x' % f, h))
|
|
found = True
|
|
if found:
|
|
continue
|
|
# find by prefix+_+name
|
|
for p, t, n, f, h in flags__:
|
|
if '%s_%s' % (p, n) == f_.upper():
|
|
lines.append(('LFS_%s_%s' % (p, n), '0x%08x' % f, h))
|
|
found = True
|
|
if found:
|
|
continue
|
|
# find by name
|
|
for p, t, n, f, h in flags__:
|
|
if n == f_.upper():
|
|
lines.append(('LFS_%s_%s' % (p, n), '0x%08x' % f, h))
|
|
found = True
|
|
if found:
|
|
continue
|
|
# find by value
|
|
try:
|
|
f__ = int(f_, 0)
|
|
f___ = f__
|
|
for p, t, n, f, h in flags__:
|
|
# ignore type masks here
|
|
if t is None and f in types__[p]:
|
|
continue
|
|
# matches flag?
|
|
if t is None and (f__ & f) == f:
|
|
lines.append(('LFS_%s_%s' % (p, n), '0x%08x' % f, h))
|
|
f___ &= ~f
|
|
# matches type?
|
|
elif t is not None and (f__ & t) == f:
|
|
lines.append(('LFS_%s_%s' % (p, n), '0x%08x' % f, h))
|
|
f___ &= ~t
|
|
if f___:
|
|
lines.append(('?', '0x%08x' % f___, 'Unknown flags'))
|
|
except ValueError:
|
|
lines.append(('?', f_, 'Unknown flag'))
|
|
|
|
# first find widths
|
|
w = [0, 0]
|
|
for l in lines:
|
|
w[0] = max(w[0], len(l[0]))
|
|
w[1] = max(w[1], len(l[1]))
|
|
|
|
# then print results
|
|
for l in lines:
|
|
print('%-*s %-*s %s' % (
|
|
w[0], l[0],
|
|
w[1], l[1],
|
|
l[2]))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import argparse
|
|
import sys
|
|
parser = argparse.ArgumentParser(
|
|
description="Decode littlefs error codes.",
|
|
allow_abbrev=False)
|
|
class AppendFlags(argparse.Action):
|
|
def __call__(self, parser, namespace, value, option):
|
|
if getattr(namespace, 'flags', None) is None:
|
|
namespace.flags = []
|
|
if value is None:
|
|
pass
|
|
elif isinstance(value, str):
|
|
namespace.flags.append(value)
|
|
else:
|
|
namespace.flags.extend(value)
|
|
parser.add_argument(
|
|
'prefix',
|
|
nargs='?',
|
|
action=AppendFlags,
|
|
help="Flag prefix to consider, defaults to all flags.")
|
|
parser.add_argument(
|
|
'flags',
|
|
nargs='*',
|
|
action=AppendFlags,
|
|
help="Flags or names of flags to decode.")
|
|
parser.add_argument(
|
|
'-l', '--list',
|
|
action='store_true',
|
|
help="List all known flags.")
|
|
parser.add_argument(
|
|
'-a', '--all',
|
|
action='store_true',
|
|
help="Also show internal flags and types.")
|
|
sys.exit(main(**{k: v
|
|
for k, v in vars(parser.parse_intermixed_args()).items()
|
|
if v is not None}))
|