Commit Graph

9 Commits

Author SHA1 Message Date
Christopher Haster e196be53df Adopted LFS3_ERR_BUSY for root-related errors
Now that we use LFS3_ERR_BUSY for traversals, we no longer have an
excuse for not returning LFS3_ERR_BUSY on root-related errors:

- lfs3_remove(&lfs3, "/") => LFS3_ERR_BUSY
- lfs3_rename(&lfs3, "/", *) => LFS3_ERR_BUSY
- lfs3_rename(&lfs3, *, "/") => LFS3_ERR_BUSY

This better aligns with POSIX. Arguably we should have defined
LFS3_ERR_BUSY for this case anyways, it's not like additional error
codes cost much.

No code changes.
2025-11-12 13:40:59 -06:00
Christopher Haster 3ab7ecb2b0 Renamed file_cache -> fcache and gbmap_re -> regbmap
This walks back some of the attempt at strict object namespacing in
struct lfs3_cfg:

- cfg.file_cache_size  -> cfg.fcache_size
- filecfg.cache_size   -> filecfg.fcache_size
- filecfg.cache_buffer -> filecfg.fcache_buffer
- cfg.gbmap_re_thresh  -> cfg.regbmap_thresh

Motivation:

- cfg.regbmap_thresh now matches cfg.gc_regbmap_thresh, instead of using
  awkwardly different namespacing patterns.

- Giving fcache a more unique name is useful for discussion. Having
  pcache, rcache, and then file_cache was a bit awkward.

  Hopefully it's also more clear that cfg.fcache_size and
  filecfg.fcache_size are related.

- Config in struct lfs3_cfg is named a bit more consistently, well, if
  you ignore gc_*_* options.

- Less typing.

Though this gets into pretty subjective naming territory. May revert
this if the new terms are uncomfortable after use.
2025-10-24 00:18:54 -05:00
Christopher Haster 5d70e47708 trv: Reverted LFS3_t_NOSPC, forward gbmap repop errors
Note: This affects the blocking lfs3_alloc_repopgbmap as well as
incremental gc/traversal repopulations. Now all repop attempts return
LFS3_ERR_NOSPC when we don't have space for the gbmap, motivation below.

This reverts the previous LFS3_t_NOSPC soft error, in which traversals
were allowed to continue some gc/traversal work when encountering
LFS3_ERR_NOSPC. This results in a simpler implementation and fewer error
cases to worry about.

Observation/motivation:

- The main motivation is noticing that when we're in low-space
  conditions, we just start spamming gbmap repops even if they all fail.

  That's really not great! We might as well just mark the flash as dead
  if we're going to start spamming erases!

  At least with an error the user can call rmgbmap to try to make
  progress.

- If we're in a low-space condition, something else will probably return
  LFS3_ERR_NOSPC anyways. Might as well report this early and simplify
  our system.

- It's a simpler model, and littlefs3 is already much more complicated
  than littlefs2. Maybe we should lean more towards a simpler system
  at the cost of some niche optimizations.

---

This had the side-effect of causing more lfs3_alloc_ckpoints to return
errors during testing, which revealed a bug in our uz/uzd_fuzz tests:

- We weren't flushing after writes to the opened RDWR files, which could
  cause delayed errors to occur during the later read checks in the
  test.

  Fortunately LFS3_O_FLUSH provides a quick and easy fix!

  Note we _don't_ adopt this in all uz/uzd_fuzz tests, only those that
  error. It's good to test both with and without LFS3_O_FLUSH to test
  that read-flushing also works under stress.

Saves a bit of code:

                 code          stack          ctx
  before:       37260           2352          688
  after:        37220 (-0.1%)   2352 (+0.0%)  688 (+0.0%)

                 code          stack          ctx
  gbmap before: 40220           2368          856
  gbmap after:  40184 (-0.1%)   2368 (+0.0%)  856 (+0.0%)
2025-10-24 00:03:14 -05:00
Christopher Haster 0bed3867d8 Adopted more single-char field names
Limited to nested struct fields where the names don't really matter:

- bptr.data -> bptr.d
- mdir.rbyd -> mdir.r

Ok it actually just ended up those two.

This is on the tail end of some optimization work that ended up
abandoned because of maintainability concerns. But it did highlight that
struct nesting gets a bit out-of-control when trying to both optimize
stack allocations and respect C99's strict aliasing.

Consider further fragmenting lfs3_rbyd_t for fine-grain stack
allocations:

  typedef struct lfs3_rbyd {
      struct lfs3_rtrunkcksum {
          struct lfs3_rtrunk {
              lfs3_rid_t weight;
              struct lfs3_rtrunktrunk {
                  lfs3_block_t blocks[2];
                  lfs3_size_t trunk;
              } rtrunktrunk;
          } rtrunk;
          uint32_t cksum;
      } rtrunkcksum;
      lfs3_size_t eoff;
  } lfs3_rbyd_t;

Accessing fields just starts to get silly:

  rbyd.rtrunkcksum.rtrunk.trunktrunk.trunk

At least single-char field names keeps a little bit of readability:

  rbyd.ck.t.t.trunk

Or for some real examples:

- file->b.o.mdir.rbyd.weight -> file->b.o.mdir.r.weight
- bptr->data.u.disk.block -> bptr->d.u.disk.block
2025-07-15 16:50:06 -05:00
Christopher Haster 6eba1180c8 Big rename! Renamed lfs -> lfs3 and lfsr -> lfs3 2025-05-28 15:00:04 -05:00
Christopher Haster f2e6b60f36 Reworked grm encoding a bit
This drops the leading count/mode byte, and instead uses mid=0 to
terminate grms. This shaves off 1 bytes from grmdeltas.

Previously, we needed the count/mode byte for a couple reasons:

- We needed to know the number of grm entries somehow, and there wasn't
  always an obvious sentinel value. mid=-1, for example, is
  unrepresentable with our unsigned leb128 encoding.

  But now that development has settled, we can use mid=0.0 to figure out
  the end-of-queue. mid=0.0 should always map to the root bookmark,
  which doesn't make sense to delete, so it makes for a reasonable null
  terminator here.

- It provided a route for future grm extensions, which could use the >2
  count/mode encodings.

  But I think we can use additional grm tag encodings for this.

  There's only one gdelta tag so far, but the current plan for future
  gdelta tags is to carve out the bottom 2 bits for redund like we do
  with the struct tags:

    LFSR_TAG_GDELTA        0x01tt  v--- ---1 -ttt ttrr
    LFSR_TAG_GRMDELTA      0x0100  v--- ---1 ---- ----
    LFSR_TAG_GBMAPDELTA    0x0104  v--- ---1 ---- -1rr
    LFSR_TAG_GDDTREEDELTA  0x0108  v--- ---1 ---- 1-rr
    LFSR_TAG_GPTREEDELTA   0x010c  v--- ---1 ---- 11rr
    ...

  Decoding is a bit more complicated for gstate, since we will need to
  xor those bits if mutable, but this avoids needing a full byte just
  for redund in every auxiliary tree.

  Long story short, we can leverage the lower 2 bits of the grm tag for
  future extensions using the same mechanism.

This may seem like a lot of effort for only a handful of bytes, but keep
in mind each gdelta lives in more-or-less every mdir in the filesystem.

Also saves a bit of code/ctx:

           code          stack          ctx
  before: 35772           2368          640
  after:  35768 (-0.0%)   2368 (+0.0%)  636 (-0.6%)
2025-04-30 00:53:33 -05:00
Christopher Haster 6c8fa28ae4 Reverted lfsr_mtree_*lookupleaf -> lfsr_mtree_lookup
Why?

- lfsr_mtree_lookupleaf vs lfsr_mtree_commit is inconsistent. Should
  lfsr_mdir_commit be called lfsr_mtree_commitleaf? That'd be weird.

  It's reasonable to call mdirs entries of the mtree, but it'd be weird
  to call rbyds entries of btrees, so the inconsistency there is
  expected.

- lfsr_mtree_lookup/lfsr_mtree_lookupnext (going mtree -> mdir) aren't
  actually useful.

- The lfsr_mtree_namelookup/lfsr_mtree_namelookupleaf split is just more
  of a headache than it's worth.

Saves a tiny bit of code:

           code          stack          ctx
  before: 35768           2392          640
  after:  35764 (-0.0%)   2392 (+0.0%)  640 (+0.0%)
2025-04-30 00:40:53 -05:00
Christopher Haster b5e503ca85 Made lfsr_file_sync a noop if zombied
So now calling lfsr_file_sync on zombied files is a noop:

  // create a file
  lfsr_file_t a;
  lfsr_file_open(&lfs, &a, "a",
          LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;

  // remove, creating a zombie
  lfsr_remove(&lfs, "a") => 0;

  // sync, this is now a noop (previously LFS_ERR_NOENT)
  lfsr_file_sync(&lfs, &a) => 0;

  // close is also a noop
  lfsr_file_close(&lfs, &a) => 0;

I've been on the fence on this for a while, on one hand erroring
provides more information to the user, on the other hand a noop is less
surprising if the user comes from other systems.

Ended up making this a noop. I figured minimizing surprises is good API
design, and the user can always use lfsr_stat to check if the file still
exists.

This also matches POSIX, and, perhaps more importantly, the current
version of littlefs.

---

Note that lfsr_file_resync still errors with LFS_ERR_NOENT. It's hard to
argue the file "matches the state of disk" otherwise.

Code changes minimal:

           code          stack          ctx
  before: 35784           2440          640
  after:  35780 (-0.0%)   2440 (+0.0%)  640 (+0.0%)
2025-04-25 17:29:34 -05:00
Christopher Haster 3b1526ace9 Renamed test_forphans -> test_stickynotes
Seems like a better name now that LFS_TYPE_STICKYNOTE is its own file
type.

Though this does contain some tests that I think don't even use
stickynotes...
2025-04-23 23:22:18 -05:00