Summary
LittleFS(..., mount=True) does not produce the same on-disk image as LittleFS(..., mount=False) followed by explicit fs.format() + fs.mount() when the backing buffer is uninitialized. The two paths produce different superblock revision counts in the first 4 bytes of the image, and on some embedded targets the constructor-built image fails to mount.
Affected code
src/littlefs/__init__.py lines 61–70:
def __init__(self, context: Optional["UserContext"] = None, mount=True, **kwargs) -> None:
self.cfg = lfs.LFSConfig(context=context, **kwargs)
self.fs = lfs.LFSFilesystem()
if mount:
try:
self.mount() # ① reads garbage, fails
except errors.LittleFSError:
self.format() # ② runs on a now-dirty lfs_t
self.mount() # ③
Root cause
When the backing buffer is fresh (no valid superblock):
self.mount() is called on a pristine lfs_t. Internally lfs_mount() reads block 0/1, scans the lookahead buffer, and mutates the lfs_t C struct (allocator state, seed, cached revision counters) before raising LittleFSError.
self.format() then runs on that dirty lfs_t. The superblock it writes is influenced by the leftover state from the failed mount — most visibly, the revision counter in the first 4 bytes of the superblock is not the expected 0x00000001.
- The resulting image still mounts successfully inside the same Python process (because the in-memory state is consistent), but a fresh LittleFS driver — e.g. on an embedded target reading the same bytes from flash — sees a superblock that does not match what a clean
format() would have produced and refuses to mount.
The contrasting code path, LittleFS(mount=False) followed by explicit fs.format() + fs.mount(), runs lfs_format() on a pristine lfs_t and produces a superblock that mounts on-device.
Reproduction / evidence
Reported and reproduced on ESP8684 (XH-C2X) in pioarduino/platform-espressif32:
- Image built with
LittleFS(mount=True) (first 32 bytes):
01000000 F00FFFF7 6C6974746C656673 2FE0001000000200 0010000060010000
→ fails to mount on device.
- Image built with
LittleFS(mount=False) + explicit format() + mount() (first 32 bytes):
F6010000 F00FFFF7 6C6974746C656673 2FE0001000000200 0010000060010000
→ mounts successfully on device.
The "littlefs" magic at offset 8 and everything after is identical; only the revision count in bytes 0–3 differs, and only the second image is accepted by the device-side LittleFS driver.
Original downstream issue and discussion:
Jason2866/platform-espressif32#237 (see comment 4391264360).
Workaround (already applied downstream)
fs = LittleFS(..., mount=False)
fs.format()
fs.mount()
Suggested fix in littlefs-python
Re-create the lfs_t between the failed mount and the subsequent format so lfs_format() always sees a pristine struct:
if mount:
try:
self.mount()
except errors.LittleFSError:
self.fs = lfs.LFSFilesystem() # reset C struct
self.format()
self.mount()
This would make mount=True produce the same on-disk image as the explicit-format workaround, and avoid surprising downstream users who build images for embedded targets.
Environment
littlefs-python: latest from PyPI / main
- Python: 3.11
- Downstream consumer: pioarduino/platform-espressif32 image builder
- Affected device: ESP8684 / XH-C2X (LittleFS on ESP-IDF), reported by @msantos2007
Summary
LittleFS(..., mount=True)does not produce the same on-disk image asLittleFS(..., mount=False)followed by explicitfs.format()+fs.mount()when the backing buffer is uninitialized. The two paths produce different superblock revision counts in the first 4 bytes of the image, and on some embedded targets the constructor-built image fails to mount.Affected code
src/littlefs/__init__.pylines 61–70:Root cause
When the backing buffer is fresh (no valid superblock):
self.mount()is called on a pristinelfs_t. Internallylfs_mount()reads block 0/1, scans the lookahead buffer, and mutates thelfs_tC struct (allocator state, seed, cached revision counters) before raisingLittleFSError.self.format()then runs on that dirtylfs_t. The superblock it writes is influenced by the leftover state from the failed mount — most visibly, the revision counter in the first 4 bytes of the superblock is not the expected0x00000001.format()would have produced and refuses to mount.The contrasting code path,
LittleFS(mount=False)followed by explicitfs.format()+fs.mount(), runslfs_format()on a pristinelfs_tand produces a superblock that mounts on-device.Reproduction / evidence
Reported and reproduced on ESP8684 (XH-C2X) in pioarduino/platform-espressif32:
LittleFS(mount=True)(first 32 bytes):01000000 F00FFFF7 6C6974746C656673 2FE0001000000200 0010000060010000→ fails to mount on device.
LittleFS(mount=False)+ explicitformat()+mount()(first 32 bytes):F6010000 F00FFFF7 6C6974746C656673 2FE0001000000200 0010000060010000→ mounts successfully on device.
The "littlefs" magic at offset 8 and everything after is identical; only the revision count in bytes 0–3 differs, and only the second image is accepted by the device-side LittleFS driver.
Original downstream issue and discussion:
Jason2866/platform-espressif32#237 (see comment 4391264360).
Workaround (already applied downstream)
Suggested fix in
littlefs-pythonRe-create the
lfs_tbetween the failed mount and the subsequent format solfs_format()always sees a pristine struct:This would make
mount=Trueproduce the same on-disk image as the explicit-format workaround, and avoid surprising downstream users who build images for embedded targets.Environment
littlefs-python: latest from PyPI /main