Commit Graph

98 Commits

Author SHA1 Message Date
Christopher Haster
c4d75efa40 Added bptr checksums
Looking forward, bptr checksums provide an easy mechanism to validate
data residing in blocks. This extends the merkle-tree-like nature of the
filesystem all the way down to the data level, and is common in other
COW filesystems.

Two interesting things to note:

1. We don't actually check data-level checksums yet, but we do calculate
   data-level checksums unconditionally.

   Writing checksums is easy, but validating checksums is a bit more
   tricky. This is made a bit harder for littlefs, since we can't hold
   an entire block of data in RAM, so we have to choose between separate
   bus transactions for checksum + data reads, or extremely expensive
   overreads every read.

   Note this already exists at the metadata-level, the separate bus
   transactions for rbyd fetch + rbyd lookup means we _are_ susceptible
   to a very small window where bit errors can get through.

   But anyways, writing checksums is easy. And has basically no cost
   since we are already processing the data for our write. So we might
   as well write the data-level checksums at all times, even if we
   aren't validating at the data-level.

2. To make bptr checksums work cheaply we need an additional cksize
   field to indicate how much data is checksummed.

   This field seems redundant when we already have the bptr's data size,
   but if we didn't have this field, we would be forced to recalculate
   the checksum every time a block is sliced. This would be
   unreasonable.

   The immutable cksize field does mean we may be checksumming more data
   than we need to when validating, but we should be avoiding small
   block slices anyways for storage cost reasons.

This does add some stack cost because our bptr struct is larger now:

            code          stack
  before:  31200           2768
  after:   31272 (+0.2%)   2800 (+1.1%)
2023-12-12 12:07:55 -06:00
Christopher Haster
9f02cbb26b Tweaked mount/format/dbg littlefs info print
The info should now be ordered more-or-less by decreasing importance:

  littlefs v2.0 4096x256 0x{0,1}.36d w12.256
         ^  ^ ^    ^   ^   '-.-' ^     ^   ^
         '--|-|----|---|-----|---|-----|---|-- littlefs
            '-|----|---|-----|---|-----|---|-- on-disk major version
              '----|---|-----|---|-----|---|-- on-disk minor version
                   '---|-----|---|-----|---|-- block size
                       '-----|---|-----|---|-- block count
                             '---|-----|---|-- mroot blocks
                                 '-----|---|-- mroot trunk
                                       '---|-- mtree weight
                                           '-- mweight
2023-12-08 14:23:53 -06:00
Christopher Haster
6ccd9eb598 Adopted different strategy for hypothetical future configs
Instead of writing every possible config that has the potential to be
useful in the future, stick to just writing the configs that we know are
useful, and error if we see any configs we don't understand.

This prevents unnecessary config bloat, while still allowing configs to
be introduced in a backwards compatible way in the future.

Currently unknown configs are treated as a mount error, but in theory
you could still try to read the filesystem, just with potentially
corrupted data. Maybe this could be behind some sort of "FORCE" mount
flag. littlefs must never write to the filesystem if it finds unknown
configs.

---

This also creates a curious case for the hole in our tag encoding
previously taken up by the OCOMPATFLAGS config. We can query for any
config > SIZELIMIT with lookupnext, but the OCOMPATFLAGS flag would need
an extra lookup which just isn't worth it.

Instead I'm just adding OCOMPATFLAGS back in. To support OCOMPATFLAGS
littlefs has to do literally nothing, so this is really more of a
documentation change. And who know, maybe OCOMPATFLAGS will have some
weird use case in the future...
2023-12-08 14:03:56 -06:00
Christopher Haster
337bdf61ae Rearranged tag encodings to make space for BECKSUM, ORPHAN, etc
Also:

- Renamed GSTATE -> GDELTA for gdelta tags. GSTATE tags added as
  separate in-device flags. The GSTATE tags were already serving
  this dual purpose.

- Renamed BSHRUB* -> SHRUB when the tag is not necessarily operating
  on a file bshrub.

- Renamed TRUNK -> BSHRUB

The tag encoding space now has a couple funky holes:

- 0x0005 - Hole for aligning config tags.

  I guess this could be used for OCOMPATFLAGS in the future?

- 0x0203 - Hole so that ORPHAN can be a 1-bit difference from REG. This
  could be after BOOKMARK, but having a bit to differentiate littlefs
  specific file types (BOOKMARK, ORPHAN) from normal file types (REG,
  DIR) is nice.

  I guess this could be used for SYMLINK if we ever want symlinks in the
  future?

- 0x0314-0x0318 - Hole so that the mdir related tags (MROOT, MDIR,
  MTREE) are nicely aligned.

  This is probably a good place for file-related tags to go in the
  future (BECKSUM, CID, COMPR), but we only have two slots, so will
  probably run out pretty quickly.

- 0x3028 - Hole so that all btree related tags (BTREE, BRANCH, MTREE)
  share a common lower bit-pattern.

  I guess this could be used for MSHRUB if we ever want mshrubs in the
  future?
2023-12-08 13:28:47 -06:00
Christopher Haster
04c6b5a067 Added grm rcompat flag, dropped ocompat, tweaked compat flags a bit
I'm just not seeing a use case for optional compat flags (ocompat), so
dropping for now. It seems their *nix equivalent, feature_compat, is
used to inform fsck of things, but this doesn't really make since in
littlefs since there is no fsck. Or from a different perspective,
littlefs is always running fsck.

Ocompat flags can always be added later (since they do nothing).

Unfortunately this really ruins the alignment of the tag encoding. For
whatever reason config limits tend to come in pairs. For now the best
solution is just leave tag 0x0006 unused. I guess you can consider it
reserved for hypothetical ocompat flags in the future.

---

This adds an rcompat flag for the grm, since in theory a filesystem
doesn't need to support grms if it never renames files (or creates
directories?). But if a filesystem doesn't support grms and a grms gets
written into the filesystem, this can lead to corruption.

I think every piece of gstate will end up with its own compat flag for
this reason.

---

Also renamed r/w/oflags -> r/w/ocompatflags to make their purpose
clearer.

---

The code impact of adding the grm rcompat flag is minimal, and will
probably be less for additional rcompat flags:

            code          stack
  before:  31528           2752
  after:   31584 (+0.2%)   2752 (+0.0%)
2023-12-07 15:05:51 -06:00
Christopher Haster
4793d2f144 Fixed new bshrub roots and related bug fixing
It turned out by implicitly handling root allocation in
lfsr_btree_commit_, we were never allowing lfsr_bshrub_commit to
intercept new roots as new bshrubs. Fixing this required moving the
root allocation logic up into lfsr_btree_commit.

This resulted in quite a bit of small bug fixing because it turns out if
you can never create non-inlined bshrubs you never test non-inlined
bshrubs:

- Our previous rbyd.weight == btree.weight check for if we've reached
  the root no longer works, changed to an explicit check that the blocks
  match. Fortunately, now that new roots set trunk=0 new roots are no
  longer a problematic case.

- We need to only evict when we calculate an accurate estimate, the
  previous code had a bug where eviction occurred early based only on the
  progged-since-last-estimate.

- We need to manually set bshrub.block=mdir.block on new bshrubs,
  otherwise the lfsr_bshrub_isbshrub check fails in mdir commit staging.

Also updated btree/bshrub following code in the dbg scripts, which
mostly meant making them accept both BRANCH and SHRUBBRANCH tags as
btree/bshrub branches. Conveniently very little code needs to change
to extend btree read operations to support bshrubs.
2023-11-21 00:06:08 -06:00
Christopher Haster
6bd00caf93 Reimplemented eager shrub eviction, now with a more reliable heuristic
Unfortunately, waiting to evict shrubs until mdir compaction does not
work because we only have a single pcache. When we evict a bshrub we
need a pcache for writing the new btree root, but if we do this during
mdir compaction, our pcache is already busy handling the mdir
compaction. We can't do a separate pass for bshrub eviction, since this
would require tracking an unbounded number of new btree roots.

In the previous shrub design, we meticulously tracked the compacted
shrub estimate in RAM, determining exactly how the estimate would change
as a part of shrub carve operations.

This worked, but was fragile. It was easy for the shrub estimate to
diverge from the actual value, and required quite a bit of extra code to
maintain. Since the use cases for bshrubs is growing a bit, I didn't
want to return to this design.

So here's a new approach based on emulating btree compacts/splits inside
the shrubs:

1. When a bshrub is fetched, scan the bshrub and calculate a compaction
   estimate. Store this.

2. On every commit, find the upper bound of new data being progged, and
   keep track of estimate + progged. We can at least get this relatively
   easily from commit attr lists. We can't get the amount deleted, which
   is the problem.

3. When estimate + progged exceeds shrub_size, scan the bshrub again and
   recalculate the estimate.

4. If estimate exceeds the shrub_size/2, evict the bshrub, converting it
   into a btree.

As you may note, this is very close to how our btree compacts/splits
work, but emulated. In particular, evictions/splits occur at
(shrub_size/block_size)/2 in order to avoid runaway costs when the
bshrub/btree gets close to full.

Benefits:

- This eviction heuristic is very robust. Calculating the amount progged
  from the attr list is relatively cheap and easy, and any divergence
  should be fixed when we recalculate the estimate.

- The runtime cost is relatively small, amortized O(log n) which is
  the existing runtime to commit to rbyds.

Downsides:

- Just like btree splits, evictions force our bshrub to be ~1/2 full on
  average. This combined with the 2x cost for mdir pairs, the 2x cost
  for mdirs being ~1/2 full on average, and the need for both a synced
  and unsynced copy of file bshrubs brings our file bshrub's overhead up
  to ~16x, which is getting quite high...

Anyways, bshrubs now work, and the new file topology is passing testing.

An unfortunate surprise is the jump in stack cost. This seems to come from
moving the lfsr_btree_flush logic into the hot-path that includes bshrub
commit + mdir commit + all the mtree logic. Previously the separate of
btree/shrub commits meant that the more complex block/btree/crystal logic
was on a separate path from the mdir commit logic:

                    code           stack           lfsr_file_t
  before bshrubs:  31840            2072                   120
  after bshrubs:   30756  (-3.5%)   2448 (+15.4%)          104 (-15.4%)

I _think_ the reality is not actually as bad as measured, most of these
flush/carve/commit functions calculate some work and then commit it in
seperate steps. In theory GCC's shrinkwrapping optimizations should
limit the stack to only what we need as we finish different
calculations, but our current stack measurement scripts just add
together the whole frames, so any per-call stack optimizations get
missed...
2023-11-21 00:04:30 -06:00
Christopher Haster
6b82e9fb25 Fixed dbg scripts to allow explicit trunks without checksums
Note this is intentionally different from how lfsr_rbyd_fetch behaves
in lfs.c. We only call lfsr_rbyd_fetch when we need validated checksums,
otherwise we just don't fetch.

The dbg scripts, on the other hand, always go through fetch, but it is
useful to be able to inspect the state of incomplete trunks when
debugging.

This use to be how the dbg scripts behaved, but they broke because of
some recent script work.
2023-11-20 23:28:27 -06:00
Christopher Haster
06439f0cc4 Tried to clean up one-line file state in dbglfs.py
Before:

  littlefs v2.0 0x{0,1}.232, rev 99, weight 9.256, bd 4096x256
  {00a3,00a4}:   0.1 file0000  reg 32768, trunk 0xa3.a8 32768, btree 0x1a.846 32704
                 0.2 file0001  reg 32768, trunk 0xa3.16c 32768, btree 0xa2.be1 32704

After:

  littlefs v2.0 0x{0,1}.232, rev 99, weight 9.256, bd 4096x256
  {00a3,00a4}:   0.1 file0000  reg 32768, trunk 0xa3.a8, btree 0x1a.846
                 0.2 file0001  reg 32768, trunk 0xa3.16c, btree 0xa2.be1

Most files will have both a shrub and a btree, which makes the previous
output problematically noisy.

Unfortunately, this does lose some information: the size of the
shrub/tree, both of which may be less than the full file. But 1. this
is _technically_ redundant since you only need the block/trunk to fetch an
rbyd (though the weight is useful), and 2. The weight can still be
viewed with -s -i.
2023-10-30 15:52:33 -05:00
Christopher Haster
4ecf4cc654 Added dbgbmap.py, tweaked tracebd.py to match
dbgbmap.py parses littlefs's mtree/btrees and displays that status of
every block in use:

  $ ./scripts/dbgbmap.py disk -B4096x256 -Z -H8 -W64
  bd 4096x256,   7.8% mdir,  10.2% btree,  78.1% data
  mmddbbddddddmmddddmmdd--bbbbddddddddddddddbbdddd--ddddddmmdddddd
  mmddddbbddbbddddddddddddddddbbddddbbddddddmmddbbdddddddddddddddd
  bbdddddddddddd--ddddddddddddddddbbddddmmmmddddddddddddmmmmdddddd
  ddddddddddbbdddddddddd--ddddddddddddddmmddddddddddddddddddddmmdd
  ddddddbbddddddddbb--ddddddddddddddddddddbb--mmmmddbbdddddddddddd
  ddddddddddddddddddddbbddbbdddddddddddddddddddddddddddddddddddddd
  dddddddddd--ddddbbddddddddmmbbdd--ddddddddddddddbbmmddddbbdddddd
  ddmmddddddddddmmddddddddmmddddbbbbdddddddd--ddbbddddddmmdd--ddbb

  (ok, it looks a bit better with colors)

dbgbmap.py matches the layout and has the same options as tracebd.py,
allowing the combination of both to provide valuable insight into what
exactly littlefs is doing.

This required a bit of tweaking of tracebd.py to get right, mostly
around conflicting order-based arguments. This also reworks the internal
Bmap class to be more resilient to out-of-window ops, and adds an
optional informative header.
2023-10-30 15:52:33 -05:00
Christopher Haster
46b78de500 Tweaked tracebd.py in a couple of ways, adopted bdgeom/--off/-n
- Tried to do the rescaling a bit better with truncating divisions, so
  there shouldn't be weird cross-pixel updates when things aren't well
  aligned.

- Adopted optional -B<block_size>x<block_count> flag for explicitly
  specifying the block-device geometry in a way that is compatible with
  other scripts. Should adopt this more places.

- Adopted optional <block>.<off> argument for start of range. This
  should match dbgblock.py.

- Adopted '-' for noop/zero-wear.

- Renamed a few internal things.

- Dropped subscript chars for wear, this didn't really add anything and
  can be accomplished by specifying the --wear-chars explicitly.

Also changed dbgblock.py to match, this mostly affects the --off/-n/--size
flags. For example, these are all the same:

  ./scripts/dbgblock.py disk -B4096 --off=10 --size=5
  ./scripts/dbgblock.py disk -B4096 --off=10 -n5
  ./scripts/dbgblock.py disk -B4096 --off=10,15
  ./scripts/dbgblock.py disk -B4096 -n10,15
  ./scripts/dbgblock.py disk -B4096 0.10 -n5

Also also adopted block-device geometry argument across scripts, where
the -B flag can optionally be a full <block_size>x<block_count> geometry:

  ./scripts/tracebd.py disk -B4096x256

Though this is mostly unused outside of tracebd.py right now. It will be
useful for anything that formats littlefs (littlefs-fuse?) and allowing
the format everywhere is a bit of a nice convenience.
2023-10-30 15:52:20 -05:00
Christopher Haster
bfc8021176 Reworked config tags, adopted rflags/wflags/oflags
The biggest change here is the breaking up of the FLAGS config into
RFLAGS/WFLAGS/OFLAGS. This is directly inspired by, and honestly not
much more than a renaming, of the compat/ro_compat/incompat flags found
in Linux/Unix/POSIX filesystems.

I think these were first introduced in ext2? But I need to do a bit more
research on that.

RFLAGS/WFLAGS/OFLAGS provide a much more flexible, and extensible,
feature flag mechanism than the previous minor version bumps.

The (re)naming of these flags is intended to make their requirements
more clear. In order to do the relevant operation, you must understand
every flag set in the relevant flag:

- RFLAGS / incompat flags - All flags must be understood to read the
  filesystem, if not understood the only possible behavior is to fail.

- WFLAGS / ro-compat flags - All flags must be understood to write to the
  filesystem, if not understood the filesystem may be mounted read-only.

- OFLAGS / compat flags - Optional flags, if not understood the relevant
  flag must be cleared before the filesystem can be written to, but other
  than that these flags can mostly be ignored.

Some hypothetical littlefs examples:

- RFLAGS / incompat flags - Transparent compression

  Is this the same as a major disk-version break? Yes kinda? An
  implementation that doesn't understand compression can't read the
  filesystem.

  On the other hand, it's useful to have a filesystem that can read both
  compressed and uncompressed variants.

- WFLAGS / ro-compat flags - Closed block-map

  The idea behind a closed block-map (currently planned), is that
  littlefs maintains in global space a complete mapping of all blocks in
  use by the filesystem.

  For such a mapping to remain consistent means that if you write to the
  filesystem you must understand the closed block-map. Or in other
  words, if you don't understand the closed block-map you must not write
  to the filesystem.

  Reading, on the other hand, can ignore many such write-related
  auxiliary features, so the filesystem can still be read from.

- OFLAGS / compat flags - Global checksums

  Global checksums (currently planned) are extra checksums attached to
  each mdir that when combined self-validate the filesystem.

  But if you don't understand global checksums, you can still read and
  write the filesystem without them. The only catch is that when you write
  to the filesystem, you may end up invalidating the global checksum.

  Clearing the global checksum bit in the OFLAGS is a cheap way to
  signal that the global checksum is no longer valid, allowing you to
  still write to the filesystem without this optional feature.

Other tweaks to note:

- Renamed BLOCKLIMIT/DISKLIMIT -> BLOCKSIZE/BLOCKCOUNT

  Note these are still the _actual_ block_size/block_count minus 1. The
  subtle difference here was the original reason for the name change,
  but after working with it for a bit, I just don't think new, otherwise
  unused, names are worth it.

  The minus 1 stays, however, since it avoids overflow issues at
  extreme boundaries of powers of 2.

- Introduces STAGLIMIT/SATTRLIMIT, sys-attribute parallels to
  UTAGLIMIT/UATTRLIMIT.

  These may be useful if only uattrs are supported, or vice-versa.

- Dropped UATTRLIMIT/SATTRLIMIT to 255 bytes.

  This feels extreme, but matches NAMELIMIT. These _should_ be small,
  and limiting the uattr/sattr size to a single-byte leads to really
  nice packing of the utag+uattrsize in a single integer.

  This can always be expanded in the future if this limit proves to be a
  problem.

- Renamed MLEAFLIMIT -> MDIRLIMIT and (re?)introduced MTREELIMIT.

  These may be useful to limiting the mtree when needed, though it's not
  clear the exact use case quite yet.
2023-10-25 12:08:58 -05:00
Christopher Haster
6dcdf1ed61 Renamed BNAME -> NAME, CCKSUM -> CKSUM
It's probably better to have a separate names for a tag category and any
specific name, but I can't think of a better name for this tag, and I
hadn't noticed that I was already ignoring the C prefix for CCKSUM tags
in many places.

NAME/CKSUM now mean both the specific tag and tag category, which is a
bit of a hack since both happen to be the 0th-subtype of their
categories.
2023-10-25 01:25:39 -05:00
Christopher Haster
240fe4efe4 Changed CKSUM suptype encoding from 0x2000 -> 0x3000
I may be overthinking things, but I'm guessing of all the possible tag
modes we may want to add in the future, we will mostly like want to add
something that looks vaguely tag like. Like the shrub tags, for example.

It's beneficial, ordering wise, for these hypothetical future tags to
come before the cksum tags.

Current tag modes:

  0x0ttt  v--- tttt -ttt tttt  normal tags
  0x1ttt  v--1 tttt -ttt tttt  shrub tags
  0x3tpp  v-11 tttt ---- ---p  cksum tags
  0x4kkk  v1dc kkkk -kkk kkkk  alt tags
2023-10-24 23:46:11 -05:00
Christopher Haster
1fc2f672a2 Tweaked tag encoding a bit post-slice to make space for becksum tags 2023-10-24 22:34:21 -05:00
Christopher Haster
35434f8b54 Removed remnants of slice code, and cleaned things up a bit 2023-10-24 22:26:08 -05:00
Christopher Haster
865477d7e1 Changing coalesce strategy, reimplemented shrub/btree carve
Note this is already showing better code reuse, which is a good sign,
though maybe that's just the benefit of reimplementing similar logic
multiple times.

Now both reading and carving end up in the same lfsr_btree_readnext and
lfsr_btree_buildcarve functions for both btrees and shrubs. Both btrees
and shrubs are fundamentally rbyds, so we can share a lot of
functionality as long as we redirect to the correct commit function at
the last minute. This surprising opportunity for deduplication was
noticed while putting together the dbg scripts.

Planned logic (not actual function names):

  lfsr_file_readnext -> lfsr_shrub_readnext
            |                    |
            |                    v
            '---------> lfsr_btree_readnext

  lfsr_file_flushbuffer -> lfsr_shrub_carve ------------.
            .---------------------'                     |
            v                                           v
  lfsr_file_flushshrub  -> lfsr_btree_carve -> lfsr_btree_buildcarve

Though the btree part of the above statement is only a hypothetical at
the moment. Not even the shrubs can survive compaction now.

The reason is the new SLICE tag which needs low-level support in rbyd
compact. SLICE introduces indirect refernces to data located in the same
rbyd, which removes any copying cost associated with coalescing.
Previously, a large coalesce_size risked O(n^2) runtime when
incrementally append small amounts of data, but with SLICEs we can defer
coalescing to compaction time, where the copy is effectively free.

This compaction-time-coalescing is also hypothetical, which is why our
tests are failing. But the theory is promising.

I was originally against this idea because of how it crosses abstraction
layers, requiring some very low-level code that absolutely can not be
omitted in a simpler littlefs driver. But after working on the actual
file writing code for a while I've become convinced the tradeoff is
worth it.

Note coalesce_size will likely still need to be configurable. Data in
fragmenting/sparse btrees is still susceptible to coalescing, and it's
not clear the impacts of internal fragmentation when data sizes approach
the hard block_size/2 limit.
2023-10-17 23:21:18 -05:00
Christopher Haster
fce1612dc0 Reverted to separate BTREE/BRANCH encodings, reordered on-disk structs
My current thinking is that these are conceptually different types, with
BTREE tags representing the entire btree, and BRANCH tags representing
only the inner btree nodes. We already have multiple btree tags anyways:
btrees attached to files, the mtree, and in the future maybe a bmaptree.

Having separate tags also makes it possible to store a btree in a btree,
though I don't think we'll ever use this functionality.

This also removes the redundant weight field from branches. The
redundant weight field is only a minor cost relative to storage, but it
also takes up a bit of RAM when encoding. Though measurements show this
isn't really significant.

New encodings:

  btree encoding:        branch encoding:
  .---+- -+- -+- -+- -.  .---+- -+- -+- -+- -.
  | weight            |  | blocks            |
  +---+- -+- -+- -+- -+  '                   '
  | blocks            |  '                   '
  '                   '  +---+- -+- -+- -+- -+
  '                   '  | trunk             |
  +---+- -+- -+- -+- -+  +---+- -+- -+- -+- -'
  | trunk             |  |     cksum     |
  +---+- -+- -+- -+- -'  '---+---+---+---'
  |     cksum     |
  '---+---+---+---'

Code/RAM changes:

            code          stack
  before:  30836           2088
  after:   30944 (+0.4%)   2080 (-0.4%)

Also reordered other on-disk structs with weight/size, so such structs
always have weight/size as the first field. This may enable some
optimizations around decoding the weight/size without needing to know
the specific type in some cases.

---

This change shouldn't have affected functionality, but it revealed a bug
in a dtree test, where a did gets caught in an mdir split and the split
name makes the did unreachable.

Marking this as a TODO for now. The fix is going to be a bit involved
(fundamental changes to the opened-mdir list), and similar work is
already planned to make removed files work.
2023-10-15 14:53:07 -05:00
Christopher Haster
173de4388b Added file tags to rendering of inner tree tags in dbglfs.py
Now -i/--inner will also show the file tags that reference the
underlying data structure.

The difference is subtle but useful:

  littlefs v2.0 0x{0,1}.eee, rev 315, weight 0.256, bd 4096x262144
  {0000,0001}:  -1.1 hello  reg 8192, btree 0x5121.d50 8143
    0000.0efc:       +          0-8142 btree w8143 11             ...
    5121.0d50:       | .-+      0-4095 block w4096 6              ...
                     | | '->    0-4095 block w4096 0x5117.0 4096  ...
                     '-+-+   4096-8142 block w4047 6              ...
                         '-> 4096-8142 block w4047 0x5139.0 4047  ...
2023-10-14 04:47:25 -05:00
Christopher Haster
fbb6a27b05 Changed crystallization strategy in btrees to rely on coalescing
This is a pretty big rewrite, but is necessary to avoid "dagging".

"Dagging" (I just made this term up) is when you transform a pure tree
into a directed acyclic graph (DAG). Normally DAGs are perfectly fine in
a copy-on-write system, but in littlefs's cases, it creates havoc for
future block allocator plans, and it's interaction with parity blocks
raises some uncomfortable questions.

How does dagging happen?

Consider an innocent little btree with a single block:

  .-----.
  |btree|
  |     |
  '-----'
     |
     v
  .-----.
  |abcde|
  |     |
  '-----'

Say we wanted to write a small amount of data in the middle of our
block. Since the data is so small, the previous scheme would simply
inline the data, carving the left and right sibling (in the case the
same block) to make space:

    .-----.
    |btree|
    |     |
    '-----'
    .' v '.
    |  c' |
    '.   .'
     v   v
    .-----.
    |ab de|
    |     |
    '-----'

Oh no! A DAG!

With the potential for multiple pointers to reference the same block in
our btree, some invariants break down:

- Blocks no longer have a single reference
- If you remove a reference you can no longer assume the block is free
- Knowing when a block is free requires scanning the whole btree
- This split operation effectively creates two blocks, does that mean
  we need to rewrite parity blocks?

---

To avoid this whole situation, this commit adopts a new crystallization
algorithm.

Instead of allowing crystallization data to be arbitrarily fragmented,
we eagerly coalesce any data under our crystallization threshold, and if
we can't coalesce, we compact everything into a block.

Much like a Knuth heap, simply checking both siblings to coalesce has
the effect that any data will always coalesce up to the maximum size
where possible. And when checking for siblings, we can easily find the
block alignment.

This also has the effect of always rewriting blocks if we are writing a
small amount of data into a block. Unfortunately I think this is just
necessary in order to avoid dagging.

At the very least crystallization is still useful for files not quite
block aligned at the edges, and sparse files. This also avoids concerns
of random writes inflating a file via sparse crystallization.
2023-10-14 01:25:41 -05:00
Christopher Haster
57aa513163 Tweaked debug prints to show more information during mount
Now when you mount littlefs, the debug print shows a bit more info:

  lfs.c:7881:debug: Mounted littlefs v2.0 0x{0,1}.c63 w43.256, bd 4096x256

To dissassemble this a bit:

  littlefs v2.0 0x{0,1}.c63 w43.256, bd 4096x256
            ^ ^   '-+-'  ^   ^   ^        ^   ^
            '-|-----|----|---|---|--------|---|-- major version
              '-----|----|---|---|--------|---|-- minor version
                    '----|---|---|--------|---|-- mroot blocks
                         |   |   |        |   |   (1st is active)
                         '---|---|--------|---|-- mroot trunk
                             '---|--------|---|-- mtree weight
                                 '--------|---|-- mleaf weight
                                          '---|-- block size
                                              '-- block count

dbglfs.py also shows the block device geometry now, as read from the
mroot:

  $ ./scripts/dbglfs.py disk -B4096
  littlefs v2.0 0x{0,1}.c63, rev 1, weight 43.256, bd 4096x256
  ...

This may be over-optimizing for testing, but the reason the mount debug
is only one line is to avoid slowing down/messying test output. Both
powerloss testing and remounts completely fill the output with mount
prints that aren't actually all that useful.

Also switching to prefering parens in debug info mainly for mismatched
things.
2023-10-14 01:25:26 -05:00
Christopher Haster
5ecd6d59cd Tweaked config and gstate reprs in dbglfs.py to be more readable
Mainly aligning things, it was easy for the previous repr to become a
visual mess.

This also represents the config more like how we represent other tags,
since they've changed from a monolithic config block to separate
attributes.
2023-10-14 01:25:20 -05:00
Christopher Haster
b936e33643 Tweaked dbg scripts to resize tag repr based on weight
This a compromise between padding the tag repr correctly and parsing
speed.

If we don't have to traverse an rbyd (for, say, tree printing), we don't
want to since parsing rbyds can get quite slow when things get big
(remember this is a filesystem!). This makes tag padding a bit of a hard
sell.

Previously this was hardcoded to 22 characters, but with the new file
struct printing it quickly became apparently this would be a problematic
limit:

  12288-15711 block w3424 0x1a.0 3424  67 64 79 70 61 69 6e 71  gdypainq

It's interesting to note that this has only become an issue for large
trees, where the weight/size in the tag can be arbitrarily large.

Fortunately we already have the weight of the rbyd after fetch, so we
can use a heuristic similar to the id padding:

  tag padding = 21 + nlog10(max(weight,1)+1)

---

Also dropped extra information with the -x/--device flag. It hasn't
really been useful and was implemented inconsistently. Maybe -x/--device
should just be dropped completely...
2023-10-14 01:25:14 -05:00
Christopher Haster
c8b60f173e Extended dbglfs.py to show file data structures
You can now pass -s/--structs to dbglfs.py to show any file data
structures:

  $ ./scripts/dbglfs.py disk -B4096 -f -s -t
  littlefs v2.0 0x{0,1}.9cf, rev 3, weight 0.256
  {0000,0001}:  -1.1 hello  reg 128, trunk 0x0.993 128
    0000.0993:           .->    0-15 shrubinlined w16 16     6b 75 72 65 65 67 73 63  kureegsc
                       .-+->   16-31 shrubinlined w16 16     6b 65 6a 79 68 78 6f 77  kejyhxow
                       | .->   32-47 shrubinlined w16 16     65 6f 66 75 76 61 6a 73  eofuvajs
                     .-+-+->   48-63 shrubinlined w16 16     6e 74 73 66 67 61 74 6a  ntsfgatj
                     |   .->   64-79 shrubinlined w16 16     70 63 76 79 6c 6e 72 66  pcvylnrf
                     | .-+->   80-95 shrubinlined w16 16     70 69 73 64 76 70 6c 6f  pisdvplo
                     | | .->  96-111 shrubinlined w16 16     74 73 65 69 76 7a 69 6c  tseivzil
                     +-+-+-> 112-127 shrubinlined w16 16     7a 79 70 61 77 72 79 79  zypawryy

This supports the same -b/-t/-i options found in dbgbtree.py, with the
one exception being -z/--struct-depth which is lowercase to avoid
conflict with the -Z/--depth used to indicate the filesystem tree depth.

I think this is a surprisingly reasonable way to show the inner
structure of files without clobbering the user's console with file
contents.

Don't worry, if clobbering is desired, -T/--no-truncate still dumps all
of the file content.

Though it's still up to the user to manually apply the sprout/shrub
overlay. That step is still complex enough to not implement in this
tool yet.

I
2023-10-14 01:25:08 -05:00
Christopher Haster
4996b8419d Implemented most of file btree reading/writing
Still needs testing, though the byte-level fuzz tests were already causing
blocks to crystallize. I noticed this because of test failures which are
fixed now.

Note the block allocator currently doesn't understand file btrees. To
get the current tests passing requires -DDISK_SIZE=16777216 or greater.

It's probably also worth noting there's a lot that's not implemented
yet! Data checksums and write validation for one. Also ecksums. And we
should probably have some sort of special handling for linear writes so
linear writes (the most common) don't end up with a bunch of extra
crystallizing writes.

Also the fact that btrees can become DAGs now is an oversight and a bit
concerning. Will that work with a closed allocator? Block parity?
2023-10-14 01:12:26 -05:00
Christopher Haster
df32211bda Changed -t/--dtree to -f/--files in dbglfs.py
This flag makes more sense to me and avoids conflicts with the
-d/--delta flag used for gstate.
2023-10-14 00:54:06 -05:00
Christopher Haster
ef691d4cfe Tweaked rbyd lookup/append to use 0 lower rid bias
Previously our lower/upper bounds were initialized to -1..weight. This
made a lot of the math unintuitive and confusing, and it's not really
necessary to support -1 rids (-1 rids arise naturally in order-statistic
trees the can have weight=0).

The tweak here is to use lower/upper bounds initialized to 0..weight,
which makes the math behave as expected. -1 rids naturally arise from
rid = upper-1.
2023-10-14 00:52:00 -05:00
Christopher Haster
3fb4350ce7 Updated dbg scripts to support shrub trees
- Added shrub tags to tagrepr
- Modified dbgrbyd.py to use last non-shrub trunk by default
- Tweaked dbgrbyd's log mode to find maximum seen weight for id padding
2023-10-13 23:35:03 -05:00
Christopher Haster
9f0160556f Made significant progress around inlined-file state during mdir commits
The main improvement is moving the special inlined-file compaction logic
up into lfsr_mdir_compact__. We only need this logic for files stored in
mdirs, and thanks to its recursive nature, we weren't getting any
benefit from handling this at a lower level anyways.

This is a nice logical restructuring that probably saves a bit of code
cost in the end.

Another significant improvement is moving the staging copy of the
inlined tree's state up into the file struct itself. This solves the
problem of needed N copies of temporary inlined state when you have N
open files.

It also provides a central place to stage changes when compacting
inlined trees, which happens across several different places in the mdir
commit logic. Though some may see this as more a hack than a feature.

Also note-worthy, but minor: these changes required an additional
opened-mdir linked-list to know when the mdir is a file and may contain
an inlined tree.
2023-10-13 23:19:24 -05:00
Christopher Haster
2f38822820 Still missing quite a bit, but rudimentary inlined-trees are now working
And by working, I mean you can create inlined trees, just don't
compact/split/move/etc anything. But this does outline the path files
take when writing buffers into inlined trees.

"Inlined trees" in littlefs are entire small rbyd trees embedded as
secondary trees in an mdir's main rbyd tree. When fetching, we can
indicate if a given trunk belongs to the main tree or secondary tree by
setting one of the unused mode bits in the trunk's tag, now called the
"deferred" bit. This bit doesn't need to be included in the alt's "key"
field, so there's no issue with it conflicting with the alt's mode bits.

This requires a bit of tweaking lfsr_rbyd_fetch, since it needs to fall
back to the previous trunk if it discovers the most recent trunk belongs
to an inlined tree. But as a benefit we can leverage the full power of
rbyds in inlined files, including holes, partial updates, etc.

One downside is it looks like these inlined trees may involve more work
in maintining their state correctly, since they need to be sort of
"brought along" when mdirs are compacted, even if they don't actually
have a reference in the mdir yet. But the sheer amount of flexibility
this gives inlined files may make this overhead worth it.
2023-10-13 23:11:35 -05:00
Christopher Haster
dd6a4e6496 Dropped the header from dbg scripts
I had never noticed xxd has no header until comparing its output against
dbgblock.py. Turns out these headers aren't really all that useful, and
even sometimes wrong in dbglfs.py.
2023-09-15 17:45:17 -05:00
Christopher Haster
c1fe64314c Reworked how filesystem-level config is stored
Now, instead of storing a single contiguous block of config data, config
is stored as tagged metadata like any other attribute.

This allows more flexibility towards adding/removing config in the
future, without cluttering up the config with deprecated entries (see
ATA's "IDENTIFY DEVICE" response).

Most of the config entries are single leb128 limits on various integer
types, with the exception of the magic string and version (major/minor
pair).

---

Note this also includes some semantic changes to the config:

- Limits are stored as size-1. This avoid issues with integer overflow
  at extreme ranges.

  This was also adopted for block size (block limit) and block count
  (disk limit). This deviation between on-disk config and user-facing
  config risks confusion, but allows the potential for the full 2^31 range
  for these values.

- The default cksum type, crc32c, has been changed to 0.

  Originally this was 2 to allow the type to map to the crc width for
  crc8, crc16, crc32c, crc64, etc. But dropping this idea and numbering
  checksums as they are implemented simplifies things.

  May come back to this.

- Storing these configs as attributes opens up of the option of on-disk
  defaults when configs are missing.

  I'm being a bit conservative with this one, as it's not clear to me if
  we should prefer default configs (less code/storage, risk of untested
  config parsing) or prefer explicit on-disk configs.

  Currently the following have defaults since they seem the most obvious
  to me:

  - cksum type  => defaults to crc32c
  - redund type => defaults to parity (TODO, should this default to
    no redund?)
  - utag_limit  => defaults to 0x7f (no special tag decoding)
  - uattr_limit => defaults to block_limit (implicit)
2023-09-15 14:51:25 -05:00
Christopher Haster
5f3994c83b Renamed mbits/mlimit to mleaf_bits/mleaf_limit
- mbits -> mleaf_bits
- mlimit -> mleaf_limit
- mweight -> mleaf_weight
- lfsr_mridmask -> lfsr_midrmask
- lfsr_mbidmask -> lfsr_midbmask

This is a bit tricky to name, since we want to clarify it's not the
mtree limit and not the mdir's actual rbyd weight. But this also risks
confusing around the difference between mdirs/mleaves (mdirs are
mtree's leaves).
2023-09-15 14:09:42 -05:00
Christopher Haster
5504936b10 Added mlimit to the superconfig, dropped mtreelimit
This should be stored in the superconfig, and we should use it during
mount instead of rederiving it from the block_size (TODO).

Note that this stores the "mlimit", (1 << mbits)-1, not the mbits
directly. littlefs will probably always be limited to powers-of-two for
this, since mbits is fairly arbitrary, but storing the expanded value
allows for non-powers-of-two _just in case_.
2023-09-14 15:02:55 -05:00
Christopher Haster
900ea807ae Changed to a shifted mid=bid.rid representation for debugging
This only matters for developers, not users, but it still helps a lot to
get debug representations right.

Since the exact mid encoding depends on the block_size in an unintuitive
manner, it's tricky to render in a debug-friendly way that is useful
both with and without tools.

Previously, I avoided shifting the bid representation, since this would
be closer to the value in the device, but this hides the actual
structure of the mtree. Now the bid is shifted, showing the underlying
mtree/mdir structure, at the cost of needing to know the number of mbits
to encode the mid back into an integer.

So for example, on a device with 4KiB blocks, or 8 mbits:

  mid=1
  mid=258
  mid=515

Becomes:

  mid=0.1
  mid=1.2
  mid=2.3

This continues to make the mbits a more fundamental part of littlefs,
but that's probably just how that's going to be.
2023-09-14 00:36:13 -05:00
Christopher Haster
f7900edc1c Updated dbg scripts with changes, adopted mbid.mrid in debug prints
This format for mids is a compromise in readability vs debugability.

For example, if our mbid weight is 256 (4KiB blocks), the 19th entry
in the second mdir would be the raw integer 275. With this mid format,
we would print it as 256.19.

The idea is to make it easy to see it's the 19th entry in the mdir while
still making it relatively easy to see that 256.19 and 275 are
equivalent when debugging.

---

The scripts also took some tweaking due to the mid change. Tried to keep
the names consistent, but I don't think it's worthwhile to change too
much of the scripts while they are working.
2023-09-05 10:10:30 -05:00
Christopher Haster
256430213d Dropped separate BTREE/BRANCH encodings
There is a bit of redundancy here, as we already know the weights of
btree's inner-branches from their parents. But in theory sharing the
same encoding for both the top level btree reference and inner-branches
should offer more chance for deduplication and hopefully less code.

This also moves some members around in the btree encoding so that the
redund blocks are at the beginning. This _might_ simplify decoding of
the variable-length redund blocks at some point.

Current btree encoding:

  .----+----+----+----.
  |       blocks    ...  redund leb128s (1-20 bytes)
  :                   :
  |----+----+----+----|
  |       trunk     ...  1 leb128 (1-5 bytes)
  |----+----+----+----|
  |       weight    ...  1 leb128 (1-5 bytes)
  |----+----+----+----|
  |       cksum       |  1 le32 (4 bytes)
  '----+----+----+----'

This also partially reverts some tag name changes:

- BNAME -> BRANCH
- DMARK -> BOOKMARK
2023-08-22 13:20:37 -05:00
Christopher Haster
314c832588 Adopted new struct encoding scheme with redund tag bits
Struct tags, in littlefs, generally encode pointers to different on-disk
data structures. At this point, they've gotten a bit complex, with the
btree struct, for example, containing 1. a block address, 2. the trunk
offset, 3. the weight of the trunk, and 4. a checksum.

Also some future plans:

1. Block redundancy will make it so these pointers may have a variable
   number of block addresses to contend with.

2. Different checksum types may make the checksum field itself variable
   length, at least on larger builds of littlefs.

   This may also happen if we support truncated checksums in littlefs
   for storage saving reasons.

Having two variable sized fields becomes a bit of a pain. We can use the
encoded tag size to figure out the size of one of these fields, but not
both.

The change here makes it so the tag size now determines the checksum
size, requiring the redundancy amount to go somewhere else. This makes
it so checksums can be variably sized, and the explicit redundancy
amount avoids the need to parse the leb128s fully to know how many
blocks we're expecting.

But where to put the redundancy amount?

This commit carves out 2-bits from the struct tag to store the amount of
redundancy to allow up to 3 blocks of redundancy:

  v0000011 0TTTTTrr
  ^--^---^-^----^-^- valid bit
     '---|-|----|-|- 3-bit mode (0x0 for structs)
         '-|----|-|- 4-bit suptype (0x3 for structs)
           '----|-|- 0 bit (reserved for leb128)
                '-|- 5-bit subtype
                  '- 2-bit redund

3 blocks may sound extremely limiting, but it's a common limit for
filesystems, 1. because you have to keep in mind each redundant block
adds that much more writing/reading overhead and 2. the fact
that 2^(2^n)-1 is always divisible by 3 makes >3 parity blocks much more
complicated mathematically.

Worst case, if we ever have >3 redundant blocks, we can create new
struct subtypes. Maybe adding extended struct types that prefix the
block addresses with a leb128 encoding the redundancy amount.

---

As a part of this, reorganized the on-disk btree and ecksum encodings to
put the checksum last.

Also split out the btree and inner btree branches as separate struct
types. The btree includes the weight, whereas the weight is implicit in
inner btree branches. This came about after realizing context-specific
prefixes are relatively easy to add thanks to the composability of our
parsers.

This led to some name collisions though:

- BRANCH   -> BNAME
- BOOKMARK -> DMARK
2023-08-11 12:55:48 -05:00
Christopher Haster
d2f2b53262 Renamed fcksum -> ecksum
This checksum is used to keep track of if we have erased, and not yet
touched, the unused bytes trailing our current commit in the rbyd.

The working theory is that if any prog attempt is made, it will, most
likely, change the checksum of the contents, allowing littlefs to
determine if trailing erased-state is safe to use, even under powerloss.
littlefs can also perturb future data by a single bit, to force this
checksum to always be invalidated during normal operation.

The original name, "forward erased-state checksums (fcksum)", came from the
idea that the checksum "looks forward" into the next commit.

But after using them for a bit, I think the name is unnecessarily
confusing. It, uh, also looks a lot like a swear word. I think
shortening the name to just "erased-state checksums (ecksum)", even
though the previous name is already in use in  a release, is reasonable.

---

It's probably hard to believe but the name change from fcrc -> ecrc
really was unrelated to the crc -> cksum change. But boy is it
convenient for avoiding an awkward name. A lot of these name changes
involved sed scripts, so I didn't notice how awkward fcksum would be to
use until writing this commit message.
2023-08-07 14:34:47 -05:00
Christopher Haster
7031d6e1b3 Changed most references to crc/csum -> cksum
The reason for this is to move away from the idea that littlefs is
strictly bound to CRCs and make the code more welcoming to other
checksum types, such as SHA256, etc.

Of course, changing the name doesn't really do anything. littlefs
actually _is_ strictly bound to CRCs in a couple ways that other
filesystems aren't. These would need to have workarounds for other
checksum types:

- We leverage the parity-preserving nature of (some) CRCs to not have
  to also calculate the parity of metadata in rbyd commits.

- We leverage the linearity of CRCs to retroactively flip the
  perturb bit in the cksum tag without needing to recalculate the
  checksum. Though the fact we need to do this is because of how we
  use parity above, so this may just not be needed for non-CRC
  checksums.

- The plans for global-CRCs (not yet implemented) rely heavily on the
  mathematical properties of CRC polynomials. This doesn't mean
  global-CRCs can't work with other checksums, you would just need to
  find a different type of polynomial.
2023-08-07 14:18:37 -05:00
Christopher Haster
d77a173d5c Changed source to consistently use rid for rbyd ids
Originally it made sense to name the rbyd ids, well, ids, at least in
the internals of the rbyd functions. But this doesn't work well outside
of the rbyd code, where littlefs has to juggle several different id
types with different purposes:

- rid => rbyd-id, 31-bit index into an rbyd
- bid => btree-id, 31-bit index into a btree
- mid => mdir-id, 15-bit+15-bit index into the mtree
- did => directory-id, 31-bit unique identifier for directories

Even though context makes it clear which id the id refers to in the rbyd
internals, updating the name to rid makes it clearer that these are the
same type of id when looking at code both inside and outside the rbyd
functions.
2023-08-07 14:10:09 -05:00
Christopher Haster
64a1b46ea2 Renamed a couple directory related things
- dstart -> bookmark
- *dnamelookup -> *namelookup
2023-08-07 14:00:44 -05:00
Christopher Haster
da4e86abac Split test_dirs into test_dtree and test_dseek
- test_dtree - Pure directory creation/deletion/move functionality
  testing. This ends up testing the core of littlefs file entry
  manipulation, since directories is all we need for that.

- test_dseek - Tests more of the corner cases specific to directory
  iteration and seeking. This involves an annoying amount of
  interactions with concurrent updates to the filesystem that are
  complicated to test for.

Also generally renaming the "fstree" concept to "dtree". This only
changes dbglfs.py as far as I'm aware. It's useful to have a name for
this thing and "directory tree" fits a bit better than "filesystem tree"
which could be ambiguous when we also have the "metadata tree" as a
different concept.
2023-08-04 14:17:42 -05:00
Christopher Haster
56adc60a80 Added grms and reporting of missing/orphaned dstarts in dbglfs.py
With a bit of color, this is very useful for debugging and finding
incorrect dstart/grm situations.

This was used to find and fix the bugs in the previous commit.
2023-07-25 13:58:11 -05:00
Christopher Haster
e4ba43dd5f Extended grm to support two atomics removes
Ugh. I overlooked a weird corner case in rename's behavior that requires
changes to the grm to support.

POSIX's rename, which lfsr_rename is trying to match, supports renaming
files over existing files, effectively removing the previous file during
the rename.

This is supported, even if the files are directories, but with the
additional requirement that the previous directory is empty (matching
the behavior of lfsr_remove).

This creates a weird situation for littlefs. In order to remove
directories in littlefs, we need to atomically remove both the dstart
entry that reserves the directory's did and the directories entry in its
parent. This is made possible by using the grm to mark one entry as
pending removed while removing the other.

But in order to rename atomically, we need to use the grm to mark the
source of the rename as removed while creating/replacing the destination
of the rename.

So we end up needing two grms simultaneously.

This is extra annoying because the niche case of renaming a directory
over another empty directory is the only case where we need two grms,
but this requirement almost doubles the grm size both in-ram and
reserved in every mdir, from 11 bytes to 21 bytes, and increases the
lfs_t size by 28 bytes.

---

Anyways, this commit extends the grm to support up to two pending removes.

Fortunately the implementation was simple since we already have a type
field that can be extended, and grm operations just needed to be
changed from if statements to for loops.
2023-07-25 13:30:04 -05:00
Christopher Haster
c928ed131f Changed all dir tests to be reentrant
To help with this, added TEST_PL, which is set to true when powerloss
testing. This way tests can check for stronger conditions (no EEXIST)
when not powerloss testing.

With TEST_PL, there's really no reason every test in t5_dirs shouldn't
be reentrant, and this gives us a huge improvement of test coverage very
cheaply.

---

The increased test coverage caught a bug, which is that gstate wasn't
being consumed properly when mtree uninlining. Humorously, this went
unnoticed because the most common form of mtree uninlining, mdir splitting,
ended up incorrectly consuming the gstate twice, which canceled itself
out since the consume operation is basically just xor.

Also added support for printing dstarts to dbglfs.py, to help debugging.
2023-07-18 21:40:43 -05:00
Christopher Haster
97f867b28d Added powerloss testing over lfsr_mkdir, fixed grm bugs
The grm bugs were mostly issues with:

1. Not maintaining the on-disk grm state in RAM (lfs->grm) correctly,
   this needs to be updated correctly after every commit or littlefs
   gets a confused.

2. lfsr_fs_fixgrm got a bit confused when it was missed when changing
   the no-rm encoding from 0 to -2. Added some inline functions to help
   avoid this in the future.

3. Leaking information due to mixing fixed sized and variable sized
   encodings of the grm delta in places. This is a bit tricky to write
   an assert for as we don't parse the full grm when we see a no-rm grm.
2023-07-18 21:40:43 -05:00
Christopher Haster
b98ac119c7 Added scripts/dbglfs.py for debugging the filesystem tree
Currently this can show:

- The filesystem tree:

    $ ./scripts/dbglfs.py disk -B4096
    littlefs v2.0 0x{0,1}.bd4, rev 1, weight 41
    mdir         ids      name                        type
    {00ce,00cf}:      0.1 dir0000                     dir 0x1070c73
    {0090,0091}:     2.30 |-> child0000               dir 0x8ec7fb2
    {0042,0043}:    24.35 |   |-> grandchild0000      dir 0x32d990b
    {0009,000a}:     25.0 |   |-> grandchild0001      dir 0x1461a08
                     25.1 |   |-> grandchild0002      dir 0x216e9fc
                     25.2 |   |-> grandchild0003      dir 0x7d6aff
                     25.3 |   |-> grandchild0004      dir 0x4b70e14
                     25.4 |   |-> grandchild0005      dir 0x6dc8d17
                     25.5 |   |-> grandchild0006      dir 0x58c7ee3
                     25.6 |   '-> grandchild0007      dir 0x7e7fde0
    {0090,0091}:     2.31 |-> child0001               dir 0xa87fcb1
    {0077,0078}:     29.1 |   |-> grandchild0000      dir 0x12194f5
                     29.2 |   |-> grandchild0001      dir 0x34a17f6
    ...

- The on-disk filesystem config:

    $ ./scripts/dbglfs.py disk -B4096 -c
    littlefs v2.0 0x{0,1}.bd4, rev 1, weight 41
    mdir         ids      tag                     data (truncated)
         config: major_version 2                  02                       .
                 minor_version 0                  00                       .
                 csum_type 2                      02                       .
                 flags 0                          00                       .
                 block_size 4096                  80 20                    .
                 block_count 256                  80 02                    ..
    ...

- Any global-state on-disk:

    $ ./scripts/dbglfs.py disk -B4096 -g -d
    littlefs v2.0 0x{0,1}.bd4, rev 1, weight 41
    mdir         ids      tag                     data (truncated)
         gstate: grm none                         00 00 00 cc 05 57 ff 7f .....W..
    {0000,0001}:       -1 grm 8                   01 03 24 cc 05 57 ff 7f ..$..W..
    {00ce,00cf}:        0 grm 3                   00 2f 1b                ./.
    {00d0,00d1}:        1 grm 3                   01 04 01                ...

  Note this already reveals a bug, since grm none should be all zeros.

Also made some other minor tweaks to dbg scripts for consistency.
2023-07-18 21:40:41 -05:00