mirror of
https://github.com/littlefs-project/littlefs.git
synced 2025-12-01 12:20:02 +00:00
The idea behind separate ctrled+unctrled airspaces was to try to avoid multiple interpretations of the on-disk bmap, but I'm starting to think this adds more complexity than it solves. The main conflict is the meaning of "in-flight" blocks. When using the "uncontrolled" bmap algorithm, in-flight blocks need to be double-checked by traversing the filesystem. But in the "controlled" bmap algorithm, blocks are only marked as "in-flight" while they are truly in-flight (in-use in RAM, but not yet in use on disk). Representing these both with the same "in-flight" state risks incompatible algorithms misinterpreting the bmap across different mounts. In theory the separate airspaces solve this, but now all the algorithms need to know how to convert the bmap from different modes, adding complexity and code cost. Well, in theory at least. I'm unsure separate airspaces actually solves this due to subtleties between what "in-flight" means in the different algorithms (note both in-use and free blocks are "in-flight" in the unknown airspace!). It really depends on how the "controlled" algorithm actually works, which isn't implemented/fully designed yet. --- Long story short, due to a time crunch, I'm ripping this out for now and just storing the current algorithm in the wcompat flags: LFS3_WCOMPAT_GBMAP 0x00006000 Global block-map in use LFS3_WCOMPAT_GBMAPNONE 0x00000000 Gbmap not in use LFS3_WCOMPAT_GBMAPCACHE 0x00002000 Gbmap in cache mode LFS3_WCOMPAT_GBMAPVFR 0x00004000 Gbmap in VFR mode LFS3_WCOMPAT_GBMAPIFR 0x00006000 Gbmap in IFR mode Note GBMAPVFR/IFR != BMAPSLOW/FAST! At least BMAPSLOW/FAST can share bmap representations: - GBMAPVFR => Uncontrolled airspace, i.e. in-flight blocks may or may not be in use, need to traverse open files. - GBMAPIFR => Controlled airspace, i.e. in-flight blocks are in use, at least until powerloss, no traversal needed, but requires more bmap writes. - BMAPSLOW => Treediff by checking what blocks are in B but not in A, and what blocks are in A but not in B, O(n^2), but minimizes bmap updates. Can be optimized with a bloom filter. - BMAPFAST => Treediff by clearing all blocks in A, and then setting all blocks in B, O(n), but also writes all blocks to the bmap twice even on small changes. Can be optimized with a sliding bitmap window (or a block hashtable, though a bitmap converges to the same thing in both algorithms when >=disk_size). It will probably be worth unifying the bmap representation later (the more algorithm-specific flags there are, the harder interop becomes for users, but for now this opens a path to implementing/experimenting with bmap algorithms without dealing with this headache.
350 lines
18 KiB
Python
Executable File
350 lines
18 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_UNFLUSH', 0x00400000, "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_BMAPMODE', 0x03000000, "On-disk block-map mode" ),
|
|
('^_BMAPNONE', 0x00000000, "Don't use the bmap" ),
|
|
('^_BMAPCACHE', 0x01000000, "Use the bmap to cache lookahead scans" ),
|
|
('^_BMAPVFR', 0x02000000, "Use the bmap in VFR mode" ),
|
|
('^_BMAPIFR', 0x03000000, "Use the bmap in IFR mode" ),
|
|
|
|
# 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_COMPACT', 0x00000800, "Compact metadata logs" ),
|
|
('M_CKMETA', 0x00001000, "Check metadata checksums" ),
|
|
('M_CKDATA', 0x00002000, "Check metadata + data checksums" ),
|
|
|
|
('M_BMAPMODE', 0x03000000, "On-disk block-map mode" ),
|
|
('^_BMAPNONE', 0x00000000, "Don't use the bmap" ),
|
|
('^_BMAPCACHE', 0x01000000, "Use the bmap to cache lookahead scans" ),
|
|
('^_BMAPVFR', 0x02000000, "Use the bmap in VFR mode" ),
|
|
('^_BMAPIFR', 0x03000000, "Use the bmap in IFR mode" ),
|
|
|
|
# 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 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_COMPACT', 0x00000800, "Filesystem may have uncompacted metadata" ),
|
|
('I_CKMETA', 0x00001000, "Metadata checksums not checked recently" ),
|
|
('I_CKDATA', 0x00002000, "Data checksums not checked recently" ),
|
|
|
|
('I_BMAPMODE', 0x03000000, "On-disk block-map mode" ),
|
|
('^_BMAPNONE', 0x00000000, "Mounted with LFS3_M_BMAPNONE" ),
|
|
('^_BMAPCACHE', 0x01000000, "Mounted with LFS3_M_BMAPCACHE" ),
|
|
('^_BMAPVFR', 0x02000000, "Mounted with LFS3_M_BMAPVFR" ),
|
|
('^_BMAPIFR', 0x03000000, "Mounted with LFS3_M_BMAPIFR" ),
|
|
|
|
('i_INMTREE', 0x00030000, "Committing to mtree" ),
|
|
('^_INMTREE', 0x00010000, "Committing to mtree" ),
|
|
('^_INBMAP', 0x00020000, "Committing to bmap" ),
|
|
|
|
# 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" ),
|
|
('^_BMAP', 0x00030000, "Tstate = bmap" ),
|
|
('^_MDIRS', 0x00040000, "Tstate = mtree-mdirs" ),
|
|
('^_MDIR', 0x00050000, "Tstate = mdir" ),
|
|
('^_BTREE', 0x00060000, "Tstate = btree" ),
|
|
('^_HANDLES', 0x00070000, "Tstate = open-mdirs" ),
|
|
('^_HBTREE', 0x00080000, "Tstate = open-btree" ),
|
|
('^_DONE', 0x00090000, "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" ),
|
|
|
|
# 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', 0x00006000, "Global block-map in use" ),
|
|
('^_GBMAPNONE', 0x00000000, "Gbmap not in use" ),
|
|
('^_GBMAPCACHE', 0x00002000, "Gbmap in cache mode" ),
|
|
('^_GBMAPVFR', 0x00004000, "Gbmap in VFR mode" ),
|
|
('^_GBMAPIFR', 0x00006000, "Gbmap in IFR mode" ),
|
|
('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}))
|