Commit Graph

15 Commits

Author SHA1 Message Date
Christopher Haster 4010afeafd trv: Reintroduced LFS3_T_EXCL
With the relaxation of traversal behavior under mutation, I think it
makes sense to bring back LFS3_T_EXCL. If only to allow traversals to
gaurantee termination under mutation. Now that traversals no longer
guarantee forward progress, it's possible to get stuck looping
indefinitely if the filesystem is constantly being mutated.

Non-excl traversals are probably still useful for GC work and debugging
threads, but LFS3_T_EXCL now allows traversals to terminate immediately
with LFS3_ERR_BUSY at the first sign of unrelated filesystem mutation:

  LFS3_T_EXCL  0x00000008  Error if filesystem modified

Internally, we already track unrelated mutation to avoid corrupt state
(LFS3_t_DIRTY), so this is a very low-cost feature:

                 code          stack          ctx
  before:       35944           2280          660
  after:        35964 (+0.1%)   2280 (+0.0%)  660 (+0.0%)

                 code          stack          ctx
  gbmap before: 38916           2296          772
  gbmap after:  38940 (+0.1%)   2296 (+0.0%)  772 (+0.0%)

                 code          stack          ctx
  gc before:    36016           2280          768
  gc after:     36036 (+0.1%)   2280 (+0.0%)  768 (+0.0%)
2025-11-12 13:30:11 -06:00
Christopher Haster ffc40da878 scripts: Reworked tagrepr -> Tag.repr to rely more on self-parsing
This should make tag editing less tedious/error-prone. We already used
self-parsing to generate -l/--list in dbgtag.py, but this extends the
idea to tagrepr (now Tag.repr), which is used in quite a few more
scripts.

To make this work the little tag encoding spec had to become a bit more
rigorous, fortunately the only real change was the addition of '+'
characters to mark reserved-but-expected-zero bits.

Example:

  TAG_CKSUM = 0x3000  ## v-11 ---- ++++ +pqq
                         ^--^----^----^--^-^-- valid bit, unmatched
                            '----|----|--|-|-- matches 1
                                 '----|--|-|-- matches 0
                                      '--|-|-- reserved 0, unmatched
                                         '-|-- perturb bit, unmatched
                                           '-- phase bits, unmatched

  dbgtag.py 0x3000  =>  cksumq0
  dbgtag.py 0x3007  =>  cksumq3p
  dbgtag.py 0x3017  =>  cksumq3p 0x10
  dbgtag.py 0x3417  =>  0x3417

Though Tag.repr still does a bit of manual formatting for the
differences between shrub/normal/null/alt tags.

Still, this should reduce the number of things that need to be changed
from 2 -> 1 when adding/editing most new tags.
2025-10-24 00:15:21 -05:00
Christopher Haster 0c0643d5d7 scripts: Adopted self-parsing script for dgbflags/err.py encoding
This has just proven much easier to tweak in dbgtag.py, so adopting the
same self-parsing pattern in dbgflags.py/dbgerr.py. This makes editing
easier by (1) not needing to worry about parens/quotes/commas, and
(2) allowing for non-python expressions, such as the mode flags in
dbgflags.py.

The only concern is script startup may be slightly slower, but we really
don't care.
2025-10-24 00:13:40 -05:00
Christopher Haster 6eba1180c8 Big rename! Renamed lfs -> lfs3 and lfsr -> lfs3 2025-05-28 15:00:04 -05:00
Christopher Haster 55ea13b994 scripts: Reverted del to resolve shadowed builtins
I don't know how I completely missed that this doesn't actually work!

Using del _does_ work in Python's repl, but it makes sense the repl may
differ from actual function execution in this case.

The problem is Python still thinks the relevant builtin is a local
variables after deletion, raising an UnboundLocalError instead of
performing a global lookup. In theory this would work if the variable
could be made global, but since global/nonlocal statements are lifted,
Python complains with "SyntaxError: name 'list' is parameter and
global".

And that's A-Ok! Intentionally shadowing language builtins already puts
this code deep into ugly hacks territory.
2025-05-15 14:10:42 -05:00
Christopher Haster 270230a833 scripts: Adopted del to resolve shadowed builtins
So:

  all_ = all; del all

Instead of:

  import builtins
  all_, all = all, builtins.all

The del exposes the globally scoped builtin we accidentally shadow.

This requires less megic, and no module imports, though tbh I'm
surprised it works.

It also works in the case where you change a builtin globally, but
that's a bit too crazy even for me...
2025-04-16 15:22:08 -05:00
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
Christopher Haster 0adec7f15c scripts: Replaced __builtins__ with builtins
Apparently __builtins__ is a CPython implementation detail, and behaves
differently when executed vs imported???

import builtins is the correct way to go about this.
2025-01-28 14:41:45 -06:00
Christopher Haster 62cc4dbb14 scripts: Disabled local import hack on import
Moved local import hack behind if __name__ == "__main__"

These scripts aren't really intended to be used as python libraries.
Still, it's useful to import them for debugging and to get access to
their juicy internals.
2025-01-28 14:41:30 -06:00
Christopher Haster 7cfcc1af1d scripts: Renamed summary.py -> csv.py
This seems like a more fitting name now that this script has evolved
into more of a general purpose high-level CSV tool.

Unfortunately this does conflict with the standard csv module in Python,
breaking every script that imports csv (which is most of them).
Fortunately, Python is flexible enough to let us remove the current
directory before imports with a bit of an ugly hack:

  # prevent local imports
  __import__('sys').path.pop(0)

These scripts are intended to be standalone anyways, so this is probably
a good pattern to adopt.
2024-11-09 12:31:16 -06:00
Christopher Haster 007ac97bec scripts: Adopted double-indent on multiline expressions
This matches the style used in C, which is good for consistency:

  a_really_long_function_name(
          double_indent_after_first_newline(
              single_indent_nested_newlines))

We were already doing this for multiline control-flow statements, simply
because I'm not sure how else you could indent this without making
things really confusing:

  if a_really_long_function_name(
          double_indent_after_first_newline(
              single_indent_nested_newlines)):
      do_the_thing()

This was the only real difference style-wise between the Python code and
C code, so now both should be following roughly the same style (80 cols,
double-indent multiline exprs, prefix multiline binary ops, etc).
2024-11-06 15:31:17 -06:00
Christopher Haster 23c82bd7e5 t: Replaced LFS_T_EXCL with LFS_I_DIRTY flag in lfsr_tinfo
This just forwards the internal LFS_I_DIRTY flag to the user via the
lfsr_tinfo flags field.

Benefits of this approach:

- Gives the user more flexibility on what to do if the filesystem is
  modified, maybe you want to keep traversing depending on some other
  logic.

- Can eventually add other flags to tinfo.flags, such as
  LFS_I_COMPACTED, LFS_I_REPAIRED, LFS_I_INCONSISTENT, etc.

- Avoids confusion around the very different behaviors of LFS_O_EXCL and
  LFS_T_EXCL.

  I tried to come up with a better name (maybe LFS_T_WATCH?) but it was
  a bit of a struggle... Switching to a flags approach sidesteps the
  issue.

- Can drop the LFS_ERR_BUSY error code for now.

Code changes were fairly insignificant:

           code          stack
  before: 35244           2680
  after:  35224 (-0.1%)   2680 (+0.0%)

The only concern is that the tests highlighted it's possible for our
flag scheme to miss mutation if it happens after/during the last set of
blocks... Not sure how to handle this yet...
2024-07-08 08:59:10 -05:00
Christopher Haster 635e1fe8d4 t: Added lookahead to lfsr_traversal_t, adopted in lfs_alloc
This sort of turned into a complete refactor of lfs_alloc in order to
move/reuse the lookahead buffer filling logic into lfsr_fs_traverse.

lfs_alloc now calls lfsr_fs_traverse to fill the lookahead buffer when
no more blocks are available, but also you can too with lfsr_traversal_t
+ LFS_T_LOOKAHEAD.

The one big caveat being if any mutation happens to the filesystem, any
incomplete lookahead needs to be tossed out. To help with this,
lfsr_traversal_read now returns LFS_ERR_BUSY (-16) instead of
LFS_ERR_NOENT (-2) if the filesystem has been modified since the
traversal was opened.

Note that by default lfsr_traversal_t will still try to keep traversing
blocks, but can be told to terminate immediately with LFS_T_EXCL.
Continuing the traversal is probably desired for checking checksums,
debugging, etc, as otherwise you could end up looping over only the
first couple blocks in a write-heavy system, but if you are trying to
populate the lookahead buffer you probably want to just abort and start
over.

I considered adding a flags field to lfs_tinfo for this, but decided
against it since it would be the only place in the current API where we
don't use error codes to convey behavior-changing information. Though
this may be worth reconsidering at some point...

---

In reworking lfs_alloc, a lot of the internal logic was broken up into
specific functions:

- lfs_alloc_ckpoint - checkpoint the allocator
- lfs_alloc_discard - discard any lookahead
- lfs_alloc_shift - discard/shift lookahead if progress can be made
- lfs_alloc_markinuse - mark a block as in-use
- lfs_alloc_markfree - mark any remaining blocks as free
- lfs_alloc_findnext - find the next free block in lookahead

If anything this probably makes lfs_alloc more readable, though the
original motivation was to allow lfsr_traversal_t to only shift/zero the
lookahead buffer if there's a chance we can make progress.

This was based on upstream work by opilat and myself.

Code changes:

           code          stack
  before: 34226           2560
  after:  34474 (+0.7%)   2552 (-0.3%)
2024-06-20 13:11:41 -05:00
Christopher Haster ae0e3348fe Added -l/--list to dbgtag.py
Inspired by errno's/dbgerr.py's -l/--list, this gives a quick and easy
list of the current tag encodings, which can be very useful:

  $ ./scripts/dbgtag.py -l
  LFSR_TAG_NULL       0x0000  v--- ---- ---- ----
  LFSR_TAG_CONFIG     0x00tt  v--- ---- -ttt tttt
  LFSR_TAG_MAGIC      0x0003  v--- ---- ---- --11
  LFSR_TAG_VERSION    0x0004  v--- ---- ---- -1--
  ... snip ...

We already need to keep dbgtag.py in-sync or risk a bad debugging
experience, so we might as well let it tell us all the information it
currently knows.

Also yay for self-inspecting code, I don't know if it's bad that I'm
becoming a fan of parsing information out of comments...
2024-06-20 13:02:08 -05:00
Christopher Haster 3dc597cb5f Added dbgerr.py for debugging littlefs's error codes
This is based on moreutils's errno program, but specialized for
littlefs's error codes. Everything is hardcoded in dbgerr.py, so this
should be portable to any OS really.

Hopefully this will be useful for debugging littlefs errors:

  $ ./scripts/dbgerr.py -84
  LFS_ERR_CORRUPT -84 Corrupted
2024-06-20 13:01:10 -05:00