littlefs-project / littlefs

A little fail-safe filesystem designed for microcontrollers
BSD 3-Clause "New" or "Revised" License
4.9k stars 770 forks source link

Duplicate the superblock entry during superblock expansion, fix missing magic #959

Closed geky closed 2 months ago

geky commented 3 months ago

The documentation does not match the implementation here. The intended behavior of superblock expansion was to duplicate the current superblock entry into the new superblock:

 .--------.  .--------.
.|littlefs|->|littlefs|
||bs=4096 | ||bs=4096 |
||bc=256  | ||bc=256  |
||crc32   | ||root dir|
||        | ||crc32   |
|'--------' |'--------'
'--------'  '--------'

The main benefit is that we can rely on the magic string "littlefs" always residing in blocks 0x{0,1}, even if the superblock chain has multiple superblocks.

The downside is that earlier superblocks in the superblock chain may contain out-of-date configuration. This is a bit annoying, and risks hard-to-reach bugs, but in theory shouldn't break anything as long as the filesystem is aware of this.

Unfortunately this was lost at some point during refactoring in the early v2-alpha work. A lot of code was moving around in this stage, so it's a bit hard to track down the change and if it was intentional. The result is superblock expansion creates a valid linked-list of superblocks, but only the last superblock contains a valid superblock entry:

 .--------.  .--------.
.|crc32   |->|littlefs|
||        | ||bs=4096 |
||        | ||bc=256  |
||        | ||root dir|
||        | ||crc32   |
|'--------' |'--------'
'--------'  '--------'

What's interesting is this isn't invalid as far as lfs_mount is concerned. lfs_mount is happy as long as a superblock entry exists anywhere in the superblock chain. This is good for compat flexibility, but is the main reason this has gone unnoticed for so long.


With the benefit of more time to think about the problem, it may have been more preferable to copy only the "littlefs" magic string and NOT the superblock entry:

 .--------.  .--------.
.|littlefs|->|littlefs|
||crc32c  | ||bs=4096 |
||        | ||bc=256  |
||        | ||root dir|
||        | ||crc32   |
|'--------' |'--------'
'--------'  '--------'

This would allow for simple "littlefs" magic string checks without the risks associated with out-of-date superblock entries.

Unfortunately the current implementation errors if it finds a "littlefs" magic string without an associated superblock entry, so such a change would not be compatible with old drivers.


This PR tweaks superblock expansion to duplicate the superblock entry instead of simply moving it to the new superblock. And adds tests over the magic string "littlefs" both before and after superblock expansion.

It also cleans up some documentation around what can be found at specific offsets in the superblock in SPEC.md, which had fallen out-of-date.

Found by @rojer and Nikola Kosturski Related https://github.com/littlefs-project/littlefs/issues/953

geky-bot commented 3 months ago
Tests passed ✓, Code: 17064 B (+0.2%), Stack: 1440 B (+0.6%), Structs: 812 B (+0.0%) | | Code | Stack | Structs | | Coverage | |:--|-----:|------:|--------:|:--|---------:| | Default | 17064 B (+0.2%) | 1440 B (+0.6%) | 812 B (+0.0%) | Lines | 2394/2574 lines (+0.0%) | | Readonly | 6194 B (+0.1%) | 448 B (+0.0%) | 812 B (+0.0%) | Branches | 1245/1584 branches (+0.0%) | | Threadsafe | 17924 B (+0.2%) | 1440 B (+0.6%) | 820 B (+0.0%) | | **Benchmarks** | | Multiversion | 17124 B (+0.2%) | 1440 B (+0.6%) | 816 B (+0.0%) | Readed | 29369693876 B (+0.0%) | | Migrate | 18760 B (+0.2%) | 1744 B (+0.5%) | 816 B (+0.0%) | Proged | 1482874766 B (+0.0%) | | Error-asserts | 17748 B (+0.2%) | 1432 B (+0.6%) | 812 B (+0.0%) | Erased | 1568888832 B (+0.0%) |