Files
littlefs/scripts/dbgflags.py
Christopher Haster 726bf86d21 Added dbgflags.py for easier flag debugging
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
2025-01-28 14:41:45 -06:00

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}))