Skip to content

LittleFS(mount=True) on a fresh buffer produces a different on-disk image than explicit format()+mount() — fails on some embedded targets #167

@Jason2866

Description

@Jason2866

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):

  1. 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.
  2. 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.
  3. 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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions