Files
littlefs/scripts/dbgflags.py
Christopher Haster f7e17c8aad Added LFS_T_RDONLY, LFS_T_RDWR, etc
These mimic the relevant LFS_O_* flags, and allow users to assert
whether or not a traversal will mutate the filesystem:

  LFS_T_MODE          0x00000001  The traversal's access mode
  LFS_T_RDWR          0x00000000  Open traversal as read and write
  LFS_T_RDONLY        0x00000001  Open traversal as read only

In theory, these could also change internal allocations, but littlefs
doesn't really work that way.

Note we _don't_ add related LFS_GC_RDONLY, LFS_GC_RDWR, etc flags. These
are sort of implied by the relevant LFS_M_* flags.

Adds a bit more code, probably because of the slightly more complicated
internal constants for the internal traversals. But I think the
self-documentingness is worth it:

           code          stack          ctx
  before: 37200           2288          636
  after:  37220 (+0.1%)   2288 (+0.0%)  636 (+0.0%)
2025-05-24 23:27:10 -05:00

363 lines
16 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', 0x04000000, "Do not sync or recieve file updates" ),
('O', 'CKMETA', 0x00001000, "Check metadata checksums" ),
('O', 'CKDATA', 0x00002000, "Check metadata + data checksums" ),
('o', 'TYPE', 0xf0000000, "The file's type" ),
('^', 'REG', 0x10000000, "Type = regular-file" ),
('^', 'DIR', 0x20000000, "Type = directory" ),
('^', 'STICKYNOTE',0x30000000, "Type = stickynote" ),
('^', 'BOOKMARK', 0x40000000, "Type = bookmark" ),
('^', 'ORPHAN', 0x50000000, "Type = orphan" ),
('^', 'TRAVERSAL', 0x60000000, "Type = traversal" ),
('^', 'UNKNOWN', 0x70000000, "Type = unknown" ),
('o', 'ZOMBIE', 0x08000000, "File has been removed" ),
('o', 'UNCREAT', 0x02000000, "File does not exist yet" ),
('o', 'UNSYNC', 0x01000000, "File's metadata does not match disk" ),
('o', 'UNCRYST', 0x00800000, "File's leaf not fully crystallized" ),
('o', 'UNGRAFT', 0x00400000, "File's leaf does not match bshrub/btree" ),
('o', 'UNFLUSH', 0x00200000, "File's data does not match disk" ),
# 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', 'REVDBG', 0x00000010, "Add debug info to revision counts" ),
('F', 'REVNOISE', 0x00000020, "Add noise to revision counts" ),
('F', 'CKPROGS', 0x00080000, "Check progs by reading back progged data" ),
('F', 'CKFETCHES', 0x00100000, "Check block checksums before first use" ),
('F', 'CKMETAPARITY',
0x00200000, "Check metadata tag parity bits" ),
('F', 'CKDATACKSUMREADS',
0x00800000, "Check data checksums on reads" ),
('F', 'CKMETA', 0x00001000, "Check metadata checksums" ),
('F', 'CKDATA', 0x00002000, "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', 'REVDBG', 0x00000010, "Add debug info to revision counts" ),
('M', 'REVNOISE', 0x00000020, "Add noise to revision counts" ),
('M', 'CKPROGS', 0x00080000, "Check progs by reading back progged data" ),
('M', 'CKFETCHES', 0x00100000, "Check block checksums before first use" ),
('M', 'CKMETAPARITY',
0x00200000, "Check metadata tag parity bits" ),
('M', 'CKDATACKSUMREADS',
0x00800000, "Check data checksums on reads" ),
('M', 'MKCONSISTENT',
0x00000100, "Make the filesystem consistent" ),
('M', 'LOOKAHEAD', 0x00000200, "Populate lookahead buffer" ),
('M', 'COMPACT', 0x00000800, "Compact metadata logs" ),
('M', 'CKMETA', 0x00001000, "Check metadata checksums" ),
('M', 'CKDATA', 0x00002000, "Check metadata + data checksums" ),
# GC flags
('GC', 'MKCONSISTENT',
0x00000100, "Make the filesystem consistent" ),
('GC', 'LOOKAHEAD',0x00000200, "Populate lookahead buffer" ),
('GC', 'COMPACT', 0x00000800, "Compact metadata logs" ),
('GC', 'CKMETA', 0x00001000, "Check metadata checksums" ),
('GC', 'CKDATA', 0x00002000, "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', 'REVDBG', 0x00000010, "Mounted with LFS_M_REVDBG" ),
('I', 'REVNOISE', 0x00000020, "Mounted with LFS_M_REVNOISE" ),
('I', 'CKPROGS', 0x00080000, "Mounted with LFS_M_CKPROGS" ),
('I', 'CKFETCHES', 0x00100000, "Mounted with LFS_M_CKFETCHES" ),
('I', 'CKMETAPARITY',
0x00200000, "Mounted with LFS_M_CKMETAPARITY" ),
('I', 'CKDATACKSUMREADS',
0x00800000, "Mounted with LFS_M_CKDATACKSUMREADS" ),
('I', 'MKCONSISTENT',
0x00000100, "Filesystem needs mkconsistent to write" ),
('I', 'LOOKAHEAD', 0x00000200, "Lookahead buffer is not full" ),
('I', 'COMPACT', 0x00000800, "Filesystem may have uncompacted metadata" ),
('I', 'CKMETA', 0x00001000, "Metadata checksums not checked recently" ),
('I', 'CKDATA', 0x00002000, "Data checksums not checked recently" ),
('i', 'INMTREE', 0x08000000, "Committing to mtree" ),
# Traversal flags
('T', 'MODE', 1, "The traversal's access mode" ),
('^', 'RDWR', 0, "Open traversal as read and write" ),
('^', 'RDONLY', 1, "Open traversal as read only" ),
('T', 'MTREEONLY', 0x00000002, "Only traverse the mtree" ),
('T', 'MKCONSISTENT',
0x00000100, "Make the filesystem consistent" ),
('T', 'LOOKAHEAD', 0x00000200, "Populate lookahead buffer" ),
('T', 'COMPACT', 0x00000800, "Compact metadata logs" ),
('T', 'CKMETA', 0x00001000, "Check metadata checksums" ),
('T', 'CKDATA', 0x00002000, "Check metadata + data checksums" ),
('t', 'TYPE', 0xf0000000, "The traversal's type" ),
('^', 'REG', 0x10000000, "Type = regular-file" ),
('^', 'DIR', 0x20000000, "Type = directory" ),
('^', 'STICKYNOTE',0x30000000, "Type = stickynote" ),
('^', 'BOOKMARK', 0x40000000, "Type = bookmark" ),
('^', 'ORPHAN', 0x50000000, "Type = orphan" ),
('^', 'TRAVERSAL', 0x60000000, "Type = traversal" ),
('^', 'UNKNOWN', 0x70000000, "Type = unknown" ),
('t', 'TSTATE', 0x000f0000, "The current traversal state" ),
('^', 'MROOTANCHOR',
0x00000000, "Tstate = mroot-anchor" ),
('^', 'MROOTCHAIN',0x00010000, "Tstate = mroot-chain" ),
('^', 'MTREE', 0x00020000, "Tstate = mtree" ),
('^', 'MDIRS', 0x00030000, "Tstate = mtree-mdirs" ),
('^', 'MDIR', 0x00040000, "Tstate = mdir" ),
('^', 'BTREE', 0x00050000, "Tstate = btree" ),
('^', 'OMDIRS', 0x00060000, "Tstate = open-mdirs" ),
('^', 'OBTREE', 0x00070000, "Tstate = open-btree" ),
('^', 'DONE', 0x00080000, "Tstate = done" ),
('t', 'BTYPE', 0x00f00000, "The current block type" ),
('^', 'MDIR', 0x00100000, "Btype = mdir" ),
('^', 'BTREE', 0x00200000, "Btype = btree" ),
('^', 'DATA', 0x00300000, "Btype = data" ),
('t', 'ZOMBIE', 0x08000000, "File has been removed" ),
('t', 'DIRTY', 0x02000000, "Filesystem modified during traversal" ),
('t', 'MUTATED', 0x01000000, "Filesystem modified by traversal" ),
# Read-compat flags
('RCOMPAT', 'NONSTANDARD',
0x00000001, "Non-standard filesystem format" ),
('RCOMPAT', 'WRONLY',
0x00000002, "Reading is disallowed" ),
('RCOMPAT', 'BMOSS',
0x00000010, "Files may use inlined data" ),
('RCOMPAT', 'BSPROUT',
0x00000020, "Files may use block pointers" ),
('RCOMPAT', 'BSHRUB',
0x00000040, "Files may use inlined btrees" ),
('RCOMPAT', 'BTREE',
0x00000080, "Files may use btrees" ),
('RCOMPAT', 'MMOSS',
0x00000100, "May use an inlined mdir" ),
('RCOMPAT', 'MSPROUT',
0x00000200, "May use an mdir pointer" ),
('RCOMPAT', 'MSHRUB',
0x00000400, "May use an inlined mtree" ),
('RCOMPAT', 'MTREE',
0x00000800, "May use an mdir btree" ),
('RCOMPAT', 'GRM',
0x00001000, "Global-remove in use" ),
('rcompat', 'OVERFLOW',
0x80000000, "Can't represent all flags" ),
# Write-compat flags
('WCOMPAT', 'NONSTANDARD',
0x00000001, "Non-standard filesystem format" ),
('WCOMPAT', 'RDONLY',
0x00000002, "Writing is disallowed" ),
('WCOMPAT', 'DIR',
0x00000010, "Directory file types in use" ),
('WCOMPAT', 'GCKSUM',
0x00001000, "Global-checksum in use" ),
('wcompat', 'OVERFLOW',
0x80000000, "Can't represent all flags" ),
# Optional-compat flags
('OCOMPAT', 'NONSTANDARD',
0x00000001, "Non-standard filesystem format" ),
('ocompat', 'OVERFLOW',
0x80000000, "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:
# accept prefix prefix
if f.upper() in prefixes:
prefix.update(prefixes[f.upper()])
# accept LFS_+prefix prefix
elif (f.upper().startswith('LFS_')
and f.upper()[len('LFS_'):] in prefixes):
prefix.update(prefixes[f.upper()[len('LFS_'):]])
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 flags.",
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}))