Christopher Haster cf34ba9aca Rearranged tag encodings, reserved suptype=0 for internal tags
This was motivated by a discussion with a gh user, in which it was noted
that not having a reserved suptype for internal tags risks potential
issues with long-term future tag compatibility.

I think the risk is low, but, without a reserved suptype, it _is_
possible for a future tag to conflict with an internal tag in an older
driver version, potentially and unintentionally breaking compatibility.
Note this is especially concerning during mdir compactions, where we
copy tags we may not understand otherwise.

In littlefs2 we reserved suptype=0x100, though this was mostly an
accident due to saturating the 3-bit suptype space. With the larger tag
space in littlefs3, the reserved suptype=0x100 was dropped.

---

Long story short, this reserves suptype=0 for internal flags (well, and
null, which is _mostly_ internal only, but does get written to disk as
unreachable tags).

Unfortunately, adding a new suptype _did_ require moving a bunch of
stuff around:

  LFS3_TAG_NULL           0x0000  v--- ---- +--- ----
  LFS3_TAG_INTERNAL       0x00tt  v--- ---- +ttt tttt

  LFS3_TAG_CONFIG         0x01tt  v--- ---1 +ttt tttt
  LFS3_TAG_MAGIC          0x0131  v--- ---1 +-11 --rr
  LFS3_TAG_VERSION        0x0134  v--- ---1 +-11 -1--
  LFS3_TAG_RCOMPAT        0x0135  v--- ---1 +-11 -1-1
  LFS3_TAG_WCOMPAT        0x0136  v--- ---1 +-11 -11-
  LFS3_TAG_OCOMPAT        0x0137  v--- ---1 +-11 -111
  LFS3_TAG_GEOMETRY       0x0138  v--- ---1 +-11 1---
  LFS3_TAG_NAMELIMIT      0x0139  v--- ---1 +-11 1--1
  LFS3_TAG_FILELIMIT      0x013a  v--- ---1 +-11 1-1-
  LFS3_TAG_ATTRLIMIT?     0x013b  v--- ---1 +-11 1-11

  LFS3_TAG_GDELTA         0x02tt  v--- --1- +ttt tttt
  LFS3_TAG_GRMDELTA       0x0230  v--- --1- +-11 ----
  LFS3_TAG_GBMAPDELTA     0x0234  v--- --1- +-11 -1rr
  LFS3_TAG_GDDTREEDELTA*  0x0238  v--- --1- +-11 1-rr
  LFS3_TAG_GPTREEDELTA*   0x023c  v--- --1- +-11 11rr

  LFS3_TAG_NAME           0x03tt  v--- --11 +ttt tttt
  LFS3_TAG_BNAME          0x0300  v--- --11 +--- ----
  LFS3_TAG_REG            0x0301  v--- --11 +--- ---1
  LFS3_TAG_DIR            0x0302  v--- --11 +--- --1-
  LFS3_TAG_STICKYNOTE     0x0303  v--- --11 +--- --11
  LFS3_TAG_BOOKMARK       0x0304  v--- --11 +--- -1--
  LFS3_TAG_SYMLINK?       0x0305  v--- --11 +--- -1-1
  LFS3_TAG_SNAPSHOT?      0x0306  v--- --11 +--- -11-
  LFS3_TAG_MNAME          0x0330  v--- --11 +-11 ----
  LFS3_TAG_DDNAME*        0x0350  v--- --11 +1-1 ----
  LFS3_TAG_DDTOMB*        0x0351  v--- --11 +1-1 ---1

  LFS3_TAG_STRUCT         0x04tt  v--- -1-- +ttt tttt
  LFS3_TAG_BRANCH         0x040r  v--- -1-- +--- --rr
  LFS3_TAG_DATA           0x0404  v--- -1-- +--- -1--
  LFS3_TAG_BLOCK          0x0408  v--- -1-- +--- 1err
  LFS3_TAG_DDKEY*         0x0410  v--- -1-- +--1 ----
  LFS3_TAG_DID            0x0420  v--- -1-- +-1- ----
  LFS3_TAG_BSHRUB         0x0428  v--- -1-- +-1- 1---
  LFS3_TAG_BTREE          0x042c  v--- -1-- +-1- 11rr
  LFS3_TAG_MROOT          0x0431  v--- -1-- +-11 --rr
  LFS3_TAG_MDIR           0x0435  v--- -1-- +-11 -1rr
  LFS3_TAG_MSHRUB+        0x0438  v--- -1-- +-11 1---
  LFS3_TAG_MTREE          0x043c  v--- -1-- +-11 11rr
  LFS3_TAG_BMRANGE        0x044u  v--- -1-- +1-- ++uu
  LFS3_TAG_BMFREE         0x0440  v--- -1-- +1-- ----
  LFS3_TAG_BMINUSE        0x0441  v--- -1-- +1-- ---1
  LFS3_TAG_BMERASED       0x0442  v--- -1-- +1-- --1-
  LFS3_TAG_BMBAD          0x0443  v--- -1-- +1-- --11
  LFS3_TAG_DDRC*          0x0450  v--- -1-- +1-1 ----
  LFS3_TAG_DDPCOEFF*      0x0451  v--- -1-- +1-1 ---1
  LFs3_TAG_PCOEFFMAP*     0x0460  v--- -1-- +11- ----

  LFS3_TAG_ATTR           0x06aa  v--- -11a +aaa aaaa
  LFS3_TAG_UATTR          0x06aa  v--- -11- +aaa aaaa
  LFS3_TAG_SATTR          0x07aa  v--- -111 +aaa aaaa

  LFS3_TAG_SHRUB          0x1kkk  v--1 kkkk +kkk kkkk
  LFS3_TAG_ALT            0x4kkk  v1cd kkkk +kkk kkkk

  LFS3_TAG_CKSUM          0x300p  v-11 ---- ++++ +pqq
  LFS3_TAG_NOTE           0x3100  v-11 ---1 ++++ ++++
  LFS3_TAG_ECKSUM         0x3200  v-11 --1- ++++ ++++
  LFS3_TAG_GCKSUMDELTA    0x3300  v-11 --11 ++++ ++++

  * Planned
  + Reserved
  ? Hypothetical

Some additional notes:

- I was on the fence on keeping the 0x30 prefix on config tags now that
  it is not longer needed to differentiate from null, but ultimately
  decided to keep it because: 1. it's fun, 2. it decreases the chance
  of false positives, 3. it keeps the redund bits readable in hexdumps,
  and 4. it reserves some tags < config, which is useful since order
  matters.

  Instead, I pushed the 0x30 prefix to _more_ tags, mainly gstate.

  As a coincidence, meta related tags (MNAME, MROOT, MRTREE) all shifted
  to also have the 0x30 prefix, which is a nice bit of unexpected
  consistency.

- I also considered reserving the redund bits across the config tags
  similarly to what we've done in struct/gstate tags, but decided
  against it as 1. it significantly reduces the config tag space
  available, and 2. makes alignment with VERSION + R/W/OCOMPAT a bit
  awkward.

  Instead I think would should relax the redund bit alignment in other
  suptypes, though in practice the intermixing of non-redund and redund
  tags makes this a bit difficult.

  Maybe we should consider including redund bits as a hint for things
  like DATA? DDKEY? BSHRUB? etc?

- I created a bit more space for file btree struct tags, allowing for
  both the future planned DDKEY, and BLOCK with optional erased-bit. We
  don't currently use this, but it may be useful for the future planned
  gddtree, which in-theory can track erased-state in partially written
  file blocks.

  Currently tracking erased-state in file blocks is difficult due to
  the potential of multiple references, and inability to prevent ecksum
  conflicts in raw data blocks.

- UATTR/SATTR bumped up to 0x600/0x700 to keep the 1-bit alignment,
  leaving the suptype 0x500 unused. Though this may be useful if we ever
  run out of struct tags (suptype=0x400), which is likely where most new
  tags will go.

---

Code changes were minimal, but with a bunch of noise:

                 code          stack          ctx
  before:       35912           2280          660
  after:        35920 (+0.0%)   2280 (+0.0%)  660 (+0.0%)

                 code          stack          ctx
  gbmap before: 38800           2296          772
  gbmap after:  38812 (+0.0%)   2296 (+0.0%)  772 (+0.0%)
2025-11-18 00:56:48 -06:00
2019-09-01 21:11:49 -07:00
2025-11-18 00:56:43 -06:00
2022-03-20 23:03:52 -05:00
2022-11-09 11:12:20 -06:00
2022-02-18 21:13:41 -06:00

littlefs

A little fail-safe filesystem designed for microcontrollers.

   | | |     .---._____
  .-----.   |          |
--|o    |---| littlefs |
--|     |---|          |
  '-----'   '----------'
   | | |

Power-loss resilience - littlefs is designed to handle random power failures. All file operations have strong copy-on-write guarantees and if power is lost the filesystem will fall back to the last known good state.

Dynamic wear leveling - littlefs is designed with flash in mind, and provides wear leveling over dynamic blocks. Additionally, littlefs can detect bad blocks and work around them.

Bounded RAM/ROM - littlefs is designed to work with a small amount of memory. RAM usage is strictly bounded, which means RAM consumption does not change as the filesystem grows. The filesystem contains no unbounded recursion and dynamic memory is limited to configurable buffers that can be provided statically.

Example

Here's a simple example that updates a file named boot_count every time main runs. The program can be interrupted at any time without losing track of how many times it has been booted and without corrupting the filesystem:

#include "lfs.h"

// variables used by the filesystem
lfs_t lfs;
lfs_file_t file;

// configuration of the filesystem is provided by this struct
const struct lfs_config cfg = {
    // block device operations
    .read  = user_provided_block_device_read,
    .prog  = user_provided_block_device_prog,
    .erase = user_provided_block_device_erase,
    .sync  = user_provided_block_device_sync,

    // block device configuration
    .read_size = 16,
    .prog_size = 16,
    .block_size = 4096,
    .block_count = 128,
    .cache_size = 16,
    .lookahead_size = 16,
    .block_cycles = 500,
};

// entry point
int main(void) {
    // mount the filesystem
    int err = lfs_mount(&lfs, &cfg);

    // reformat if we can't mount the filesystem
    // this should only happen on the first boot
    if (err) {
        lfs_format(&lfs, &cfg);
        lfs_mount(&lfs, &cfg);
    }

    // read current count
    uint32_t boot_count = 0;
    lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT);
    lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count));

    // update boot count
    boot_count += 1;
    lfs_file_rewind(&lfs, &file);
    lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count));

    // remember the storage is not updated until the file is closed successfully
    lfs_file_close(&lfs, &file);

    // release any resources we were using
    lfs_unmount(&lfs);

    // print the boot count
    printf("boot_count: %d\n", boot_count);
}

Usage

Detailed documentation (or at least as much detail as is currently available) can be found in the comments in lfs.h.

littlefs takes in a configuration structure that defines how the filesystem operates. The configuration struct provides the filesystem with the block device operations and dimensions, tweakable parameters that tradeoff memory usage for performance, and optional static buffers if the user wants to avoid dynamic memory.

The state of the littlefs is stored in the lfs_t type which is left up to the user to allocate, allowing multiple filesystems to be in use simultaneously. With the lfs_t and configuration struct, a user can format a block device or mount the filesystem.

Once mounted, the littlefs provides a full set of POSIX-like file and directory functions, with the deviation that the allocation of filesystem structures must be provided by the user.

All POSIX operations, such as remove and rename, are atomic, even in event of power-loss. Additionally, file updates are not actually committed to the filesystem until sync or close is called on the file.

Other notes

Littlefs is written in C, and specifically should compile with any compiler that conforms to the C99 standard.

All littlefs calls have the potential to return a negative error code. The errors can be either one of those found in the enum lfs_error in lfs.h, or an error returned by the user's block device operations.

In the configuration struct, the prog and erase function provided by the user may return a LFS_ERR_CORRUPT error if the implementation already can detect corrupt blocks. However, the wear leveling does not depend on the return code of these functions, instead all data is read back and checked for integrity.

If your storage caches writes, make sure that the provided sync function flushes all the data to memory and ensures that the next read fetches the data from memory, otherwise data integrity can not be guaranteed. If the write function does not perform caching, and therefore each read or write call hits the memory, the sync function can simply return 0.

Design

At a high level, littlefs is a block based filesystem that uses small logs to store metadata and larger copy-on-write (COW) structures to store file data.

In littlefs, these ingredients form a sort of two-layered cake, with the small logs (called metadata pairs) providing fast updates to metadata anywhere on storage, while the COW structures store file data compactly and without any wear amplification cost.

Both of these data structures are built out of blocks, which are fed by a common block allocator. By limiting the number of erases allowed on a block per allocation, the allocator provides dynamic wear leveling over the entire filesystem.

                    root
                   .--------.--------.
                   | A'| B'|         |
                   |   |   |->       |
                   |   |   |         |
                   '--------'--------'
                .----'   '--------------.
       A       v                 B       v
      .--------.--------.       .--------.--------.
      | C'| D'|         |       | E'|new|         |
      |   |   |->       |       |   | E'|->       |
      |   |   |         |       |   |   |         |
      '--------'--------'       '--------'--------'
      .-'   '--.                  |   '------------------.
     v          v              .-'                        v
.--------.  .--------.        v                       .--------.
|   C    |  |   D    |   .--------.       write       | new E  |
|        |  |        |   |   E    |        ==>        |        |
|        |  |        |   |        |                   |        |
'--------'  '--------'   |        |                   '--------'
                         '--------'                   .-'    |
                         .-'    '-.    .-------------|------'
                        v          v  v              v
                   .--------.  .--------.       .--------.
                   |   F    |  |   G    |       | new F  |
                   |        |  |        |       |        |
                   |        |  |        |       |        |
                   '--------'  '--------'       '--------'

More details on how littlefs works can be found in DESIGN.md and SPEC.md.

  • DESIGN.md - A fully detailed dive into how littlefs works. I would suggest reading it as the tradeoffs at work are quite interesting.

  • SPEC.md - The on-disk specification of littlefs with all the nitty-gritty details. May be useful for tooling development.

Testing

The littlefs comes with a test suite designed to run on a PC using the emulated block device found in the bd directory. The tests assume a Linux environment and can be started with make:

make test

License

The littlefs is provided under the BSD-3-Clause license. See LICENSE.md for more information. Contributions to this project are accepted under the same license.

Individual files contain the following tag instead of the full license text.

SPDX-License-Identifier:    BSD-3-Clause

This enables machine processing of license information based on the SPDX License Identifiers that are here available: http://spdx.org/licenses/

  • littlefs-fuse - A FUSE wrapper for littlefs. The project allows you to mount littlefs directly on a Linux machine. Can be useful for debugging littlefs if you have an SD card handy.

  • littlefs-js - A javascript wrapper for littlefs. I'm not sure why you would want this, but it is handy for demos. You can see it in action here.

  • littlefs-python - A Python wrapper for littlefs. The project allows you to create images of the filesystem on your PC. Check if littlefs will fit your needs, create images for a later download to the target memory or inspect the content of a binary image of the target memory.

  • mklfs - A command line tool built by the Lua RTOS guys for making littlefs images from a host PC. Supports Windows, Mac OS, and Linux.

  • Mbed OS - The easiest way to get started with littlefs is to jump into Mbed which already has block device drivers for most forms of embedded storage. littlefs is available in Mbed OS as the LittleFileSystem class.

  • SPIFFS - Another excellent embedded filesystem for NOR flash. As a more traditional logging filesystem with full static wear-leveling, SPIFFS will likely outperform littlefs on small memories such as the internal flash on microcontrollers.

  • Dhara - An interesting NAND flash translation layer designed for small MCUs. It offers static wear-leveling and power-resilience with only a fixed O(|address|) pointer structure stored on each block and in RAM.

S
Description
A little fail-safe filesystem designed for microcontrollers
https://github.com/littlefs-project/littlefs.git Readme 14 MiB
Languages
C 68.4%
Python 30.7%
Makefile 0.9%