Open-source project
littlefs-project/littlefs avatar
littlefs-project/littlefs

littlefs: a fail-safe filesystem for microcontrollers, and what it asks of your block device

A little fail-safe filesystem designed for microcontrollers

6,956 stars1,028 forksCBSD-3-Clause

At a glance

What is it?
littlefs is a C99 block-based filesystem with power-loss resilience, dynamic wear leveling and bounded RAM, aimed at firmware engineers on NOR and NAND flash. The catch is that you supply the block device layer, and the README does not document rollback.
Who is it for?
littlefs fits firmware projects where the storage is raw NOR or NAND flash, RAM is measured in kilobytes, and the device can lose power at any instant. It is the wrong tool when your storage already has a flash translation layer with its own wear leveling, when you need POSIX semantics with per-write durability, or when you want a filesystem that manages its own memory.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What littlefs solves, and the firmware engineers it targets

Microcontrollers write to flash, and flash has two properties that make naive writing dangerous. Erases happen in blocks, writes happen in pages, and a power failure in the middle of either leaves storage in a state the previous code did not expect. The README describes littlefs as a little fail-safe filesystem designed for microcontrollers, and the three properties it advertises are the answer to that situation: power-loss resilience, dynamic wear leveling, and bounded RAM and ROM.

The audience is narrow and specific. You are writing C99 firmware, you have a raw block device rather than a filesystem already mounted on top of it, and you care about what happens when the battery is pulled mid-write. The README states that all file operations have strong copy-on-write guarantees and that if power is lost the filesystem falls back to the last known good state. That is a claim about the design, not a guarantee about your driver: littlefs can only fall back to a good state if the block device underneath it behaves.

Bounded RAM is the other half of the pitch. RAM usage is strictly bounded, so consumption does not change as the filesystem grows, and the README notes there is no unbounded recursion. Dynamic memory is limited to configurable buffers, which the user can provide statically. For a device with a fixed heap, that predictability matters more than throughput.

The two-layered design: metadata pairs and copy-on-write file data

The DESIGN notes in the repository describe the structure as a two-layered cake. Small logs, called metadata pairs, store metadata and provide fast updates to it anywhere on storage. Larger copy-on-write structures store file data compactly, and the README says this avoids wear amplification cost on the data path. Both structures are built out of blocks fed by a common block allocator. By limiting the number of erases allowed on a block per allocation, the allocator provides dynamic wear leveling across the whole filesystem.

The consequence is that metadata and file data have different update behaviour, and the README is explicit about where durability lands: file updates are not committed to the filesystem until sync or close is called on the file. The boot counter example ends with lfs_file_close, followed by the comment that storage is not updated until the file is closed successfully. If your application writes a record and never closes or syncs the file, a power loss can discard it, and that is the design working as intended rather than a bug.

All POSIX operations, including remove and rename, are atomic even on power loss, according to the README. The API is POSIX-like with one deviation the README calls out: the allocation of filesystem structures must be provided by the user. State lives in an lfs_t that you allocate, so several filesystems can be mounted at once.

Wiring up the block device: read, prog, erase, sync

littlefs does not talk to hardware. You fill in a struct lfs_config with four function pointers, and the README's example names them user_provided_block_device_read, prog, erase and sync, alongside dimensions such as read_size, prog_size, block_size, block_count, cache_size, lookahead_size and block_cycles. The dimensions are not decoration: they trade memory for performance, and they must match the part.

The sync callback is the one most likely to be implemented incorrectly. The README states that if your storage caches writes, sync must flush all data to memory and ensure the next read fetches from memory, otherwise data integrity cannot be guaranteed. If the write function does no caching and every read or write hits memory directly, sync can simply return 0. A driver that returns 0 while a write-back cache still holds dirty pages is the failure mode to look for during bring-up.

Error handling has a similar shape. Every littlefs call can return a negative error code, either from enum lfs_error in lfs.h or from your own block device functions. The README also notes that prog and erase may return LFS_ERR_CORRUPT if your implementation can already detect corrupt blocks, but wear leveling does not depend on that return code: all data is read back and checked for integrity. Bad blocks are detected and worked around, but the detection does not come free.

Building littlefs and running the boot counter example

The repository is a source tree, not a package. The top level contains lfs.c, lfs.h, lfs_util.c, lfs_util.h, a Makefile, and the tests, benches and runners directories. The README points at lfs.h for the detailed documentation, describing it as at least as much detail as is currently available. There is no install command in the README, so the practical path is to drop the two source files into your build and compile them as C99.

The Makefile chooses its target by what it finds in the directory. If test.c and main.c are present it builds $(BUILDDIR)/lfs, otherwise it builds $(BUILDDIR)/liblfs.a. To exercise the filesystem on a host you need a block device implementation and an entry point, which is what the example in the README supplies. The core of it is the configuration struct:

c
const struct lfs_config cfg = {
    .read  = user_provided_block_device_read,
    .prog  = user_provided_block_device_prog,
    .erase = user_provided_block_device_erase,
    .sync  = user_provided_block_device_sync,
    .read_size = 16,
    .prog_size = 16,
    .block_size = 4096,
    .block_count = 128,
    .cache_size = 16,
    .lookahead_size = 16,
    .block_cycles = 500,
};

Those four function pointers are yours to write. The mount path then tries to mount and reformats on failure, which the README says should only happen on the first boot:

c
int err = lfs_mount(&lfs, &cfg);
if (err) {
    lfs_format(&lfs, &cfg);
    lfs_mount(&lfs, &cfg);
}

The first real use is a file opened with LFS_O_RDWR | LFS_O_CREAT, read into a uint32_t, rewound with lfs_file_rewind, written back, and closed. The README's comment on close is the part to internalise: the storage is not updated until the file is closed successfully. After lfs_unmount, the example prints the boot count. If you interrupt the program at any point, the count should be either the old value or the new one, never a corrupted filesystem.

Where littlefs is the wrong choice, and what it does not do for you

The first limitation is architectural. littlefs assumes it owns a block device and that you can describe that device in terms of read, prog and erase units. If your storage sits behind a controller with its own flash translation layer and wear leveling, you are stacking two allocators with different assumptions about block lifetime, and the wear leveling littlefs provides is doing work the layer below already did.

The second is that the README does not document rollback. There is no snapshot, no versioning, no way to return to an earlier state of a file after a successful close. The fail-safe property means falling back to the last known good state on power loss, not recovering a previous revision. If you need transactional multi-file updates with an explicit rollback path, littlefs gives you atomic individual operations and leaves the transaction design to you.

The third is memory and API shape. Structures must be user-allocated, so there is no hidden malloc to rescue you, and the caches and lookahead buffers come out of your budget. The README does not state whether littlefs is thread safe, and it does not document a locking model, so concurrency is a question for your own integration rather than something the project answers.

Finally, the documentation situation is thin by the project's own account. The README points to comments in lfs.h, qualified as at least as much detail as is currently available. The repository carries DESIGN.md and SPEC.md, which is more than many embedded libraries offer, but there is no separate manual.

littlefs vs SPIFFS: different answers to the same flash problem

The comparison people reach for is SPIFFS, and the difference is structural rather than a matter of tuning. SPIFFS is a log-structured filesystem for SPI NOR flash. It appends updates to a log and garbage-collects pages when free space runs low, which means a write can trigger a collection pass whose duration depends on how fragmented the filesystem has become. littlefs splits the problem: metadata pairs are small logs for fast metadata updates, while file data lives in copy-on-write structures that the README says store data compactly without wear amplification cost.

The practical divergence is in RAM and in predictability. littlefs advertises strictly bounded RAM that does not grow with the filesystem, and a block allocator that caps erases per block per allocation to spread wear. SPIFFS has no equivalent bounded-memory claim in its own documentation, and its garbage collection makes write latency less predictable. littlefs also detects bad blocks and works around them, and it checks all data for integrity on read-back rather than trusting the return code of prog and erase.

What SPIFFS offers in exchange is a simpler mental model and a smaller API surface. If your workload is append-mostly and your RAM budget is comfortable, that simplicity has value. If you are updating small records in place on a device that loses power, littlefs's copy-on-write metadata path is the more defensible design.

Editorial conclusion

littlefs fits firmware projects where the storage is raw NOR or NAND flash, RAM is measured in kilobytes, and the device can lose power at any instant. It is the wrong tool when your storage already has a flash translation layer with its own wear leveling, when you need POSIX semantics with per-write durability, or when you want a filesystem that manages its own memory. Before adopting it, verify three things against your own hardware: that your sync callback really flushes a write-back cache, that your block_size and prog_size match the part's erase and program units, and that your RAM budget covers the caches and lookahead buffers you configure. The example in the README with read_size, prog_size and cache_size at 16 bytes and block_size at 4096 is a starting point for that budget, not a measured requirement.

Frequently asked questions

What does littlefs do?

It is a block-based filesystem written in C99 for microcontrollers, providing POSIX-like file and directory operations on top of a raw block device. The README describes it as fail-safe, with copy-on-write guarantees on all file operations and a fallback to the last known good state after power loss.

What are the key differences between SPIFFS and littlefs?

littlefs uses small metadata logs for fast metadata updates and copy-on-write structures for file data, with a block allocator that limits erases per block per allocation to provide dynamic wear leveling. It also advertises strictly bounded RAM that does not change as the filesystem grows, and it reads data back to check integrity rather than trusting the return code of prog and erase.

How do you use littlefs?

You provide a struct lfs_config with read, prog, erase and sync callbacks plus block device dimensions, allocate an lfs_t, then call lfs_mount or lfs_format. After that, files are opened with flags such as LFS_O_RDWR | LFS_O_CREAT, and updates are not committed until sync or close is called on the file.

Is littlefs thread safe?

The README does not state whether littlefs is thread safe and does not document a locking model. State lives in the lfs_t that the user allocates, so any concurrency protection is up to the integration around it.

What is lfs.h used for in littlefs?

The README points to lfs.h for detailed documentation, describing it as at least as much detail as is currently available. It also defines the error codes, since every littlefs call can return a negative value from enum lfs_error or from your own block device functions.

Official sources

  1. Issues
  2. License: BSD-3-Clause
  3. littlefs-project/littlefs on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/littlefs-project-littlefs.svg)](https://hysenlabs.com/projects/littlefs-project-littlefs)