Files
littlefs/scripts/dbgflags.py
Christopher Haster f5508a1b6c gbmap: Added LFS3_T_REBUILDGBMAP and friends
This adds LFS3_T_REBUILDGBMAP and friends, and enables incremental gbmap
rebuilds as a part of gc/traversal work:

  LFS3_M_REBUILDGBMAP   0x00000400  Rebuild the gbmap
  LFS3_GC_REBUILDGBMAP  0x00000400  Rebuild the gbmap
  LFS3_I_REBUILDGBMAP   0x00000400  The gbmap is not full
  LFS3_T_REBUILDGBMAP   0x00000400  Rebuild the gbmap

On paper, this is more or less identical to repopulating the lookahead
buffer -- traverse the filesystem, mark blocks as in-use, adopt the new
gbmap/lookahead buffer on success -- but a couple nuances make
rebuilding the gbmap a bit trickier:

- Unlike the lookahead buffer, which eagerly zeros in allocation, we
  need an explicit zeroing pass before we start marking blocks as
  in-use. This means multiple traversals can potentially conflict with
  each other, risking the adoption of a clobbered gbmap.

- The gbmap, which stores information on disk, relies on block
  allocation and the temporary "in-flight window" defined by allocator
  ckpoints to avoid circular block states during gbmap rebuilds. This
  makes gbmap rebuilds sensitive to allocator ckpoints, which we
  consider more-or-less a noop in other parts of the system.

  Though now that I'm writing this, it might have been possible to
  instead include gbmap rebuild snapshots in fs traversals... but that
  would probably have been much more complicated.

- Rebuilding the gbmap requires writing to disk and is generally much
  more expensive/destructive. We want to avoid trying to rebuild the
  gbmap when it's not possible to actually make progress.

On top of this, the current trv-clobber system is a delicate,
error-prone mess.

---

To simplify everything related to gbmap rebuilds, I added a new
internal traversal flag: LFS3_t_CKPOINTED:

  LFS3_t_CKPOINTED  0x04000000  Filesystem ckpointed during traversal

LFS3_t_CKPOINTED is set, unconditionally, on all open traversals in
lfs3_alloc_ckpoint, and provides a simple, robust mechanism for checking
if _any_ allocator checkpoints have occured since a traversal was
started. Since lfs3_alloc_ckpoint is required before any block
allocation, this provides a strong guarantee that nothing funny happened
to any allocator state during a traversal.

This makes lfs3_alloc_ckpoint a bit less cheap, but the strong
guarantees that allocator state is unmodified during traversal are well
worth it.

This makes both lookahead and gbmap passes simpler, safer, and easier to
reason about.

I'd like to adopt something similar+stronger for LFs3_t_MUTATED, and
reduce this back to two flags, but that can be a future commit.

---

Unfortunately due to the potential for recursion, this ended up reusing
less logic between lfs3_alloc_rebuildgbmap and lfs3_mtree_gc than I had
hoped, but at like the main chunks (lfs3_alloc_remap,
lfs3_gbmap_setbptr, lfs3_alloc_adoptgbmap) could be split out into
common functions.

The result is a decent chunk of code and stack, but the value is high as
incremental gbmap rebuilds are the only option to reduce the latency
spikes introduced by the gbmap allocator (it's not significantly worse
than the lookahead buffer, but both do require traversing the entire
filesystem):

                 code          stack          ctx
  before:       37164           2352          684
  after:        37208 (+0.1%)   2360 (+0.3%)  684 (+0.0%)

                 code          stack          ctx
  gbmap before: 39708           2376          848
  gbmap after:  40100 (+1.0%)   2432 (+2.4%)  848 (+0.0%)

Note the gbmap build is now measured with LFS3_GBMAP=1, instead of
LFS3_YES_GBMAP=1 (maybe-gbmap) as before. This includes the cost of
mkgbmap, lfs3_f_isgbmap, etc.
2025-10-23 23:39:55 -05:00

339 lines
17 KiB
Python
Executable File

#!/usr/bin/env python3
# prevent local imports
if __name__ == "__main__":
__import__('sys').path.pop(0)
import collections as co
FILTERS = [
(['--o', '--open'], 'O', "Filter by LFS3_O_* flags."),
(['--a', '--attr'], 'A', "Filter by LFS3_A_* flags."),
(['--f', '--format'], 'F', "Filter by LFS3_F_* flags."),
(['--m', '--mount'], 'M', "Filter by LFS3_M_* flags."),
('--gc', 'GC', "Filter by LFS3_GC_* flags."),
(['--i', '--info'], 'I', "Filter by LFS3_I_* flags."),
(['--t', '--traversal'], 'T', "Filter by LFS3_T_* flags."),
('--alloc', 'ALLOC', "Filter by LFS3_ALLOC_* flags."),
(['--rc', '--rcompat'], 'RCOMPAT', "Filter by LFS3_RCOMPAT_* flags."),
(['--wc', '--wcompat'], 'WCOMPAT', "Filter by LFS3_WCOMPAT_* flags."),
(['--oc', '--ocompat'], 'OCOMPAT', "Filter by LFS3_OCOMPAT_* flags."),
]
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_WRSET', 3, "Open a file as an atomic write" ),
('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 disk" ),
('o_UNFLUSH', 0x00200000, "File's cache 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_CKDATACKSUMS', 0x00800000, "Check data checksums on reads" ),
('F_CKMETA', 0x00001000, "Check metadata checksums" ),
('F_CKDATA', 0x00002000, "Check metadata + data checksums" ),
('F_GBMAP', 0x01000000, "Use the global on-disk block-map" ),
# 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 LFS3_O_FLUSH" ),
('M_SYNC', 0x00000080, "Open all files with LFS3_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_CKDATACKSUMS', 0x00800000, "Check data checksums on reads" ),
('M_MKCONSISTENT', 0x00000100, "Make the filesystem consistent" ),
('M_LOOKAHEAD', 0x00000200, "Populate lookahead buffer" ),
('M_REBUILDGBMAP', 0x00000400, "Rebuild the gbmap" ),
('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_REBUILDGBMAP',0x00000400, "Rebuild the gbmap" ),
('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 LFS3_M_FLUSH" ),
('I_SYNC', 0x00000080, "Mounted with LFS3_M_SYNC" ),
('I_REVDBG', 0x00000010, "Mounted with LFS3_M_REVDBG" ),
('I_REVNOISE', 0x00000020, "Mounted with LFS3_M_REVNOISE" ),
('I_CKPROGS', 0x00080000, "Mounted with LFS3_M_CKPROGS" ),
('I_CKFETCHES', 0x00100000, "Mounted with LFS3_M_CKFETCHES" ),
('I_CKMETAPARITY', 0x00200000, "Mounted with LFS3_M_CKMETAPARITY" ),
('I_CKDATACKSUMS', 0x00800000, "Mounted with LFS3_M_CKDATACKSUMS" ),
('I_MKCONSISTENT', 0x00000100, "Filesystem needs mkconsistent to write" ),
('I_LOOKAHEAD', 0x00000200, "Lookahead buffer is not full" ),
('I_REBUILDGBMAP', 0x00000400, "The gbmap 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_GBMAP', 0x01000000, "Global on-disk block-map in use" ),
('i_INMODE', 0x00030000, "Btree commit mode" ),
('^_INMTREE', 0x00010000, "Committing to mtree" ),
('^_INGBMAP', 0x00020000, "Committing to gbmap" ),
# 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_REBUILDGBMAP', 0x00000400, "Rebuild the gbmap" ),
('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" ),
('^_HANDLES', 0x00060000, "Tstate = open-mdirs" ),
('^_HBTREE', 0x00070000, "Tstate = open-btree" ),
('^_GBMAP', 0x00080000, "Tstate = gbmap" ),
('^_GBMAP_P', 0x00090000, "Tstate = gbmap_p" ),
('^_DONE', 0x000a0000, "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_CKPOINTED', 0x04000000, "Filesystem ckpointed during traversal" ),
('t_DIRTY', 0x02000000, "Filesystem modified during traversal" ),
('t_MUTATED', 0x01000000, "Filesystem modified by traversal" ),
# Block allocator flags
('alloc_ERASE', 0x00000001, "Please erase the block" ),
# 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_GBMAP', 0x00002000, "Global on-disk block-map 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,
filter=[]):
import builtins
list_, list = list, builtins.list
all_, all = all, builtins.all
filter_, filter = filter, builtins.filter
# filter by prefix if there are any filters
filter__ = set(filter_)
flags__ = []
types__ = co.defaultdict(lambda: set())
for n, f, h in FLAGS:
p, n = n.split('_', 1)
if p == '^':
p = last_p
t = last_t
types__[p].add(t)
else:
t = None
last_p = p
last_t = f
if not filter__ or p.upper() in filter__:
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(('LFS3_%s_%s' % (p, n), '0x%08x' % f, h))
# find flags by name or value
else:
for f_ in flags:
found = False
# find by LFS3_+prefix+_+name
for p, t, n, f, h in flags__:
if 'LFS3_%s_%s' % (p, n) == f_.upper():
lines.append(('LFS3_%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(('LFS3_%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(('LFS3_%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(('LFS3_%s_%s' % (p, n), '0x%08x' % f, h))
f___ &= ~f
# matches type?
elif t is not None and (f__ & t) == f:
lines.append(('LFS3_%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)
parser.add_argument(
'flags',
nargs='*',
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.")
class AppendFilter(argparse.Action):
def __init__(self, nargs=None, **kwargs):
super().__init__(nargs=0, **kwargs)
def __call__(self, parser, namespace, value, option):
if getattr(namespace, 'filter', None) is None:
namespace.filter = []
namespace.filter.append(self.const)
for flag, prefix, help in FILTERS:
parser.add_argument(
*([flag] if isinstance(flag, str) else flag),
action=AppendFilter,
const=prefix,
help=help)
sys.exit(main(**{k: v
for k, v in vars(parser.parse_intermixed_args()).items()
if v is not None}))