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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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