|
|
@@ -1,10 +1,10 @@
|
|
|
-## The little filesystem technical specification
|
|
|
+## littlefs technical specification
|
|
|
|
|
|
This is the technical specification of the little filesystem. This document
|
|
|
covers the technical details of how the littlefs is stored on disk for
|
|
|
-introspection and tooling development. This document assumes you are
|
|
|
-familiar with the design of the littlefs, for more info on how littlefs
|
|
|
-works check out [DESIGN.md](DESIGN.md).
|
|
|
+introspection and tooling. This document assumes you are familiar with the
|
|
|
+design of the littlefs, for more info on how littlefs works check
|
|
|
+out [DESIGN.md](DESIGN.md).
|
|
|
|
|
|
```
|
|
|
| | | .---._____
|
|
|
@@ -15,356 +15,773 @@ works check out [DESIGN.md](DESIGN.md).
|
|
|
| | |
|
|
|
```
|
|
|
|
|
|
-## Some important details
|
|
|
+## Some quick notes
|
|
|
|
|
|
-- The littlefs is a block-based filesystem. This is, the disk is divided into
|
|
|
- an array of evenly sized blocks that are used as the logical unit of storage
|
|
|
- in littlefs. Block pointers are stored in 32 bits.
|
|
|
+- littlefs is a block-based filesystem. The disk is divided into an array of
|
|
|
+ evenly sized blocks that are used as the logical unit of storage.
|
|
|
|
|
|
-- There is no explicit free-list stored on disk, the littlefs only knows what
|
|
|
- is in use in the filesystem.
|
|
|
+- Block pointers are stored in 32 bits, with the special value `0xffffffff`
|
|
|
+ representing a null block address.
|
|
|
|
|
|
-- The littlefs uses the value of 0xffffffff to represent a null block-pointer.
|
|
|
+- In addition to the logical block size (which usually matches the erase
|
|
|
+ block size), littlefs also uses a program block size and read block size.
|
|
|
+ These determine the alignment of block device operations, but don't need
|
|
|
+ to be consistent for portability.
|
|
|
|
|
|
-- All values in littlefs are stored in little-endian byte order.
|
|
|
+- By default, all values in littlefs are stored in little-endian byte order.
|
|
|
|
|
|
## Directories / Metadata pairs
|
|
|
|
|
|
-Metadata pairs form the backbone of the littlefs and provide a system for
|
|
|
-atomic updates. Even the superblock is stored in a metadata pair.
|
|
|
+Metadata pairs form the backbone of littlefs and provide a system for
|
|
|
+distributed atomic updates. Even the superblock is stored in a metadata pair.
|
|
|
|
|
|
As their name suggests, a metadata pair is stored in two blocks, with one block
|
|
|
-acting as a redundant backup in case the other is corrupted. These two blocks
|
|
|
-could be anywhere in the disk and may not be next to each other, so any
|
|
|
-pointers to directory pairs need to be stored as two block pointers.
|
|
|
-
|
|
|
-Here's the layout of metadata blocks on disk:
|
|
|
-
|
|
|
-| offset | size | description |
|
|
|
-|--------|---------------|----------------|
|
|
|
-| 0x00 | 32 bits | revision count |
|
|
|
-| 0x04 | 32 bits | dir size |
|
|
|
-| 0x08 | 64 bits | tail pointer |
|
|
|
-| 0x10 | size-16 bytes | dir entries |
|
|
|
-| 0x00+s | 32 bits | CRC |
|
|
|
-
|
|
|
-**Revision count** - Incremented every update, only the uncorrupted
|
|
|
-metadata-block with the most recent revision count contains the valid metadata.
|
|
|
-Comparison between revision counts must use sequence comparison since the
|
|
|
-revision counts may overflow.
|
|
|
-
|
|
|
-**Dir size** - Size in bytes of the contents in the current metadata block,
|
|
|
-including the metadata-pair metadata. Additionally, the highest bit of the
|
|
|
-dir size may be set to indicate that the directory's contents continue on the
|
|
|
-next metadata-pair pointed to by the tail pointer.
|
|
|
-
|
|
|
-**Tail pointer** - Pointer to the next metadata-pair in the filesystem.
|
|
|
-A null pair-pointer (0xffffffff, 0xffffffff) indicates the end of the list.
|
|
|
-If the highest bit in the dir size is set, this points to the next
|
|
|
-metadata-pair in the current directory, otherwise it points to an arbitrary
|
|
|
-metadata-pair. Starting with the superblock, the tail-pointers form a
|
|
|
-linked-list containing all metadata-pairs in the filesystem.
|
|
|
-
|
|
|
-**CRC** - 32 bit CRC used to detect corruption from power-lost, from block
|
|
|
-end-of-life, or just from noise on the storage bus. The CRC is appended to
|
|
|
-the end of each metadata-block. The littlefs uses the standard CRC-32, which
|
|
|
-uses a polynomial of 0x04c11db7, initialized with 0xffffffff.
|
|
|
-
|
|
|
-Here's an example of a simple directory stored on disk:
|
|
|
-```
|
|
|
-(32 bits) revision count = 10 (0x0000000a)
|
|
|
-(32 bits) dir size = 154 bytes, end of dir (0x0000009a)
|
|
|
-(64 bits) tail pointer = 37, 36 (0x00000025, 0x00000024)
|
|
|
-(32 bits) CRC = 0xc86e3106
|
|
|
-
|
|
|
-00000000: 0a 00 00 00 9a 00 00 00 25 00 00 00 24 00 00 00 ........%...$...
|
|
|
-00000010: 22 08 00 03 05 00 00 00 04 00 00 00 74 65 61 22 "...........tea"
|
|
|
-00000020: 08 00 06 07 00 00 00 06 00 00 00 63 6f 66 66 65 ...........coffe
|
|
|
-00000030: 65 22 08 00 04 09 00 00 00 08 00 00 00 73 6f 64 e"...........sod
|
|
|
-00000040: 61 22 08 00 05 1d 00 00 00 1c 00 00 00 6d 69 6c a"...........mil
|
|
|
-00000050: 6b 31 22 08 00 05 1f 00 00 00 1e 00 00 00 6d 69 k1"...........mi
|
|
|
-00000060: 6c 6b 32 22 08 00 05 21 00 00 00 20 00 00 00 6d lk2"...!... ...m
|
|
|
-00000070: 69 6c 6b 33 22 08 00 05 23 00 00 00 22 00 00 00 ilk3"...#..."...
|
|
|
-00000080: 6d 69 6c 6b 34 22 08 00 05 25 00 00 00 24 00 00 milk4"...%...$..
|
|
|
-00000090: 00 6d 69 6c 6b 35 06 31 6e c8 .milk5.1n.
|
|
|
-```
|
|
|
-
|
|
|
-A note about the tail pointer linked-list: Normally, this linked-list is
|
|
|
-threaded through the entire filesystem. However, after power-loss this
|
|
|
-linked-list may become out of sync with the rest of the filesystem.
|
|
|
-- The linked-list may contain a directory that has actually been removed
|
|
|
-- The linked-list may contain a metadata pair that has not been updated after
|
|
|
- a block in the pair has gone bad.
|
|
|
-
|
|
|
-The threaded linked-list must be checked for these errors before it can be
|
|
|
-used reliably. Fortunately, the threaded linked-list can simply be ignored
|
|
|
-if littlefs is mounted read-only.
|
|
|
-
|
|
|
-## Entries
|
|
|
-
|
|
|
-Each metadata block contains a series of entries that follow a standard
|
|
|
-layout. An entry contains the type of the entry, along with a section for
|
|
|
-entry-specific data, attributes, and a name.
|
|
|
-
|
|
|
-Here's the layout of entries on disk:
|
|
|
-
|
|
|
-| offset | size | description |
|
|
|
-|---------|------------------------|----------------------------|
|
|
|
-| 0x0 | 8 bits | entry type |
|
|
|
-| 0x1 | 8 bits | entry length |
|
|
|
-| 0x2 | 8 bits | attribute length |
|
|
|
-| 0x3 | 8 bits | name length |
|
|
|
-| 0x4 | entry length bytes | entry-specific data |
|
|
|
-| 0x4+e | attribute length bytes | system-specific attributes |
|
|
|
-| 0x4+e+a | name length bytes | entry name |
|
|
|
-
|
|
|
-**Entry type** - Type of the entry, currently this is limited to the following:
|
|
|
-- 0x11 - file entry
|
|
|
-- 0x22 - directory entry
|
|
|
-- 0x2e - superblock entry
|
|
|
-
|
|
|
-Additionally, the type is broken into two 4 bit nibbles, with the upper nibble
|
|
|
-specifying the type's data structure used when scanning the filesystem. The
|
|
|
-lower nibble clarifies the type further when multiple entries share the same
|
|
|
-data structure.
|
|
|
-
|
|
|
-The highest bit is reserved for marking the entry as "moved". If an entry
|
|
|
-is marked as "moved", the entry may also exist somewhere else in the
|
|
|
-filesystem. If the entry exists elsewhere, this entry must be treated as
|
|
|
-though it does not exist.
|
|
|
-
|
|
|
-**Entry length** - Length in bytes of the entry-specific data. This does
|
|
|
-not include the entry type size, attributes, or name. The full size in bytes
|
|
|
-of the entry is 4 + entry length + attribute length + name length.
|
|
|
-
|
|
|
-**Attribute length** - Length of system-specific attributes in bytes. Since
|
|
|
-attributes are system specific, there is not much guarantee on the values in
|
|
|
-this section, and systems are expected to work even when it is empty. See the
|
|
|
-[attributes](#entry-attributes) section for more details.
|
|
|
-
|
|
|
-**Name length** - Length of the entry name. Entry names are stored as UTF8,
|
|
|
-although most systems will probably only support ASCII. Entry names can not
|
|
|
-contain '/' and can not be '.' or '..' as these are a part of the syntax of
|
|
|
-filesystem paths.
|
|
|
-
|
|
|
-Here's an example of a simple entry stored on disk:
|
|
|
-```
|
|
|
-(8 bits) entry type = file (0x11)
|
|
|
-(8 bits) entry length = 8 bytes (0x08)
|
|
|
-(8 bits) attribute length = 0 bytes (0x00)
|
|
|
-(8 bits) name length = 12 bytes (0x0c)
|
|
|
-(8 bytes) entry data = 05 00 00 00 20 00 00 00
|
|
|
-(12 bytes) entry name = smallavacado
|
|
|
-
|
|
|
-00000000: 11 08 00 0c 05 00 00 00 20 00 00 00 73 6d 61 6c ........ ...smal
|
|
|
-00000010: 6c 61 76 61 63 61 64 6f lavacado
|
|
|
-```
|
|
|
-
|
|
|
-## Superblock
|
|
|
-
|
|
|
-The superblock is the anchor for the littlefs. The superblock is stored as
|
|
|
-a metadata pair containing a single superblock entry. It is through the
|
|
|
-superblock that littlefs can access the rest of the filesystem.
|
|
|
-
|
|
|
-The superblock can always be found in blocks 0 and 1, however fetching the
|
|
|
-superblock requires knowing the block size. The block size can be guessed by
|
|
|
-searching the beginning of disk for the string "littlefs", although currently
|
|
|
-the filesystems relies on the user providing the correct block size.
|
|
|
-
|
|
|
-The superblock is the most valuable block in the filesystem. It is updated
|
|
|
-very rarely, only during format or when the root directory must be moved. It
|
|
|
-is encouraged to always write out both superblock pairs even though it is not
|
|
|
-required.
|
|
|
-
|
|
|
-Here's the layout of the superblock entry:
|
|
|
-
|
|
|
-| offset | size | description |
|
|
|
-|--------|------------------------|----------------------------------------|
|
|
|
-| 0x00 | 8 bits | entry type (0x2e for superblock entry) |
|
|
|
-| 0x01 | 8 bits | entry length (20 bytes) |
|
|
|
-| 0x02 | 8 bits | attribute length |
|
|
|
-| 0x03 | 8 bits | name length (8 bytes) |
|
|
|
-| 0x04 | 64 bits | root directory |
|
|
|
-| 0x0c | 32 bits | block size |
|
|
|
-| 0x10 | 32 bits | block count |
|
|
|
-| 0x14 | 32 bits | version |
|
|
|
-| 0x18 | attribute length bytes | system-specific attributes |
|
|
|
-| 0x18+a | 8 bytes | magic string ("littlefs") |
|
|
|
-
|
|
|
-**Root directory** - Pointer to the root directory's metadata pair.
|
|
|
-
|
|
|
-**Block size** - Size of the logical block size used by the filesystem.
|
|
|
-
|
|
|
-**Block count** - Number of blocks in the filesystem.
|
|
|
-
|
|
|
-**Version** - The littlefs version encoded as a 32 bit value. The upper 16 bits
|
|
|
-encodes the major version, which is incremented when a breaking-change is
|
|
|
-introduced in the filesystem specification. The lower 16 bits encodes the
|
|
|
-minor version, which is incremented when a backwards-compatible change is
|
|
|
-introduced. Non-standard Attribute changes do not change the version. This
|
|
|
-specification describes version 1.1 (0x00010001), which is the first version
|
|
|
-of littlefs.
|
|
|
-
|
|
|
-**Magic string** - The magic string "littlefs" takes the place of an entry
|
|
|
-name.
|
|
|
-
|
|
|
-Here's an example of a complete superblock:
|
|
|
-```
|
|
|
-(32 bits) revision count = 3 (0x00000003)
|
|
|
-(32 bits) dir size = 52 bytes, end of dir (0x00000034)
|
|
|
-(64 bits) tail pointer = 3, 2 (0x00000003, 0x00000002)
|
|
|
-(8 bits) entry type = superblock (0x2e)
|
|
|
-(8 bits) entry length = 20 bytes (0x14)
|
|
|
-(8 bits) attribute length = 0 bytes (0x00)
|
|
|
-(8 bits) name length = 8 bytes (0x08)
|
|
|
-(64 bits) root directory = 3, 2 (0x00000003, 0x00000002)
|
|
|
-(32 bits) block size = 512 bytes (0x00000200)
|
|
|
-(32 bits) block count = 1024 blocks (0x00000400)
|
|
|
-(32 bits) version = 1.1 (0x00010001)
|
|
|
-(8 bytes) magic string = littlefs
|
|
|
-(32 bits) CRC = 0xc50b74fa
|
|
|
-
|
|
|
-00000000: 03 00 00 00 34 00 00 00 03 00 00 00 02 00 00 00 ....4...........
|
|
|
-00000010: 2e 14 00 08 03 00 00 00 02 00 00 00 00 02 00 00 ................
|
|
|
-00000020: 00 04 00 00 01 00 01 00 6c 69 74 74 6c 65 66 73 ........littlefs
|
|
|
-00000030: fa 74 0b c5 .t..
|
|
|
-```
|
|
|
-
|
|
|
-## Directory entries
|
|
|
-
|
|
|
-Directories are stored in entries with a pointer to the first metadata pair
|
|
|
-in the directory. Keep in mind that a directory may be composed of multiple
|
|
|
-metadata pairs connected by the tail pointer when the highest bit in the dir
|
|
|
-size is set.
|
|
|
+providing a backup during erase cycles in case power is lost. These two blocks
|
|
|
+are not necessarily sequential and may be anywhere on disk, so a "pointer" to a
|
|
|
+metadata pair is stored as two block pointers.
|
|
|
|
|
|
-Here's the layout of a directory entry:
|
|
|
-
|
|
|
-| offset | size | description |
|
|
|
-|--------|------------------------|-----------------------------------------|
|
|
|
-| 0x0 | 8 bits | entry type (0x22 for directory entries) |
|
|
|
-| 0x1 | 8 bits | entry length (8 bytes) |
|
|
|
-| 0x2 | 8 bits | attribute length |
|
|
|
-| 0x3 | 8 bits | name length |
|
|
|
-| 0x4 | 64 bits | directory pointer |
|
|
|
-| 0xc | attribute length bytes | system-specific attributes |
|
|
|
-| 0xc+a | name length bytes | directory name |
|
|
|
+On top of this, each metadata block behaves as an appendable log, containing a
|
|
|
+variable number of commits. Commits can be appended to the metadata log in
|
|
|
+order to update the metadata without requiring an erase cycles. Note that
|
|
|
+successive commits may supersede the metadata in previous commits. Only the
|
|
|
+most recent metadata should be considered valid.
|
|
|
+
|
|
|
+The high-level layout of a metadata block is fairly simple:
|
|
|
+
|
|
|
+```
|
|
|
+ .---------------------------------------.
|
|
|
+.-| revision count | entries | \
|
|
|
+| |-------------------+ | |
|
|
|
+| | | |
|
|
|
+| | | +-- 1st commit
|
|
|
+| | | |
|
|
|
+| | +-------------------| |
|
|
|
+| | | CRC | /
|
|
|
+| |-------------------+-------------------|
|
|
|
+| | entries | \
|
|
|
+| | | |
|
|
|
+| | | +-- 2nd commit
|
|
|
+| | +-------------------+--------------| |
|
|
|
+| | | CRC | padding | /
|
|
|
+| |----+-------------------+--------------|
|
|
|
+| | entries | \
|
|
|
+| | | |
|
|
|
+| | | +-- 3rd commit
|
|
|
+| | +-------------------+---------| |
|
|
|
+| | | CRC | | /
|
|
|
+| |---------+-------------------+ |
|
|
|
+| | unwritten storage | more commits
|
|
|
+| | | |
|
|
|
+| | | v
|
|
|
+| | |
|
|
|
+| | |
|
|
|
+| '---------------------------------------'
|
|
|
+'---------------------------------------'
|
|
|
+```
|
|
|
+
|
|
|
+Each metadata block contains a 32-bit revision count followed by a number of
|
|
|
+commits. Each commit contains a variable number of metadata entries followed
|
|
|
+by a 32-bit CRC.
|
|
|
+
|
|
|
+Note also that entries aren't necessarily word-aligned. This allows us to
|
|
|
+store metadata more compactly, however we can only write to addresses that are
|
|
|
+aligned to our program block size. This means each commit may have padding for
|
|
|
+alignment.
|
|
|
+
|
|
|
+Metadata block fields:
|
|
|
+
|
|
|
+1. **Revision count (32-bits)** - Incremented every erase cycle. If both blocks
|
|
|
+ contain valid commits, only the block with the most recent revision count
|
|
|
+ should be used. Sequence comparison must be used to avoid issues with
|
|
|
+ integer overflow.
|
|
|
+
|
|
|
+2. **CRC (32-bits)** - Detects corruption from power-loss or other write
|
|
|
+ issues. Uses a CRC-32 with a polynomial of `0x04c11db7` initialized
|
|
|
+ with `0xffffffff`.
|
|
|
+
|
|
|
+Entries themselves are stored as a 32-bit tag followed by a variable length
|
|
|
+blob of data. But exactly how these tags are stored is a little bit tricky.
|
|
|
+
|
|
|
+Metadata blocks support both forward and backward iteration. In order to do
|
|
|
+this without duplicating the space for each tag, neighboring entries have their
|
|
|
+tags XORed together, starting with `0xffffffff`.
|
|
|
|
|
|
-**Directory pointer** - Pointer to the first metadata pair in the directory.
|
|
|
-
|
|
|
-Here's an example of a directory entry:
|
|
|
```
|
|
|
-(8 bits) entry type = directory (0x22)
|
|
|
-(8 bits) entry length = 8 bytes (0x08)
|
|
|
-(8 bits) attribute length = 0 bytes (0x00)
|
|
|
-(8 bits) name length = 3 bytes (0x03)
|
|
|
-(64 bits) directory pointer = 5, 4 (0x00000005, 0x00000004)
|
|
|
-(3 bytes) name = tea
|
|
|
-
|
|
|
-00000000: 22 08 00 03 05 00 00 00 04 00 00 00 74 65 61 "...........tea
|
|
|
-```
|
|
|
-
|
|
|
-## File entries
|
|
|
-
|
|
|
-Files are stored in entries with a pointer to the head of the file and the
|
|
|
-size of the file. This is enough information to determine the state of the
|
|
|
-CTZ skip-list that is being referenced.
|
|
|
-
|
|
|
-How files are actually stored on disk is a bit complicated. The full
|
|
|
-explanation of CTZ skip-lists can be found in [DESIGN.md](DESIGN.md#ctz-skip-lists).
|
|
|
-
|
|
|
-A terribly quick summary: For every nth block where n is divisible by 2^x,
|
|
|
-the block contains a pointer to block n-2^x. These pointers are stored in
|
|
|
-increasing order of x in each block of the file preceding the data in the
|
|
|
+ Forward iteration Backward iteration
|
|
|
+
|
|
|
+.-------------------. 0xffffffff .-------------------.
|
|
|
+| revision count | | | revision count |
|
|
|
+|-------------------| v |-------------------|
|
|
|
+| tag ~A |---> xor -> tag A | tag ~A |---> xor -> 0xffffffff
|
|
|
+|-------------------| | |-------------------| ^
|
|
|
+| data A | | | data A | |
|
|
|
+| | | | | |
|
|
|
+| | | | | |
|
|
|
+|-------------------| v |-------------------| |
|
|
|
+| tag AxB |---> xor -> tag B | tag AxB |---> xor -> tag A
|
|
|
+|-------------------| | |-------------------| ^
|
|
|
+| data B | | | data B | |
|
|
|
+| | | | | |
|
|
|
+| | | | | |
|
|
|
+|-------------------| v |-------------------| |
|
|
|
+| tag BxC |---> xor -> tag C | tag BxC |---> xor -> tag B
|
|
|
+|-------------------| |-------------------| ^
|
|
|
+| data C | | data C | |
|
|
|
+| | | | tag C
|
|
|
+| | | |
|
|
|
+| | | |
|
|
|
+'-------------------' '-------------------'
|
|
|
+```
|
|
|
+
|
|
|
+One last thing to note before we get into the details around tag encoding. Each
|
|
|
+tag contains a valid bit used to indicate if the tag and containing commit is
|
|
|
+valid. This valid bit is the first bit found in the tag and the commit and can
|
|
|
+be used to tell if we've attempted to write to the remaining space in the
|
|
|
block.
|
|
|
|
|
|
-The maximum number of pointers in a block is bounded by the maximum file size
|
|
|
-divided by the block size. With 32 bits for file size, this results in a
|
|
|
-minimum block size of 104 bytes.
|
|
|
+Here's a more complete example of metadata block containing 4 entries:
|
|
|
|
|
|
-Here's the layout of a file entry:
|
|
|
+```
|
|
|
+ .---------------------------------------.
|
|
|
+.-| revision count | tag ~A | \
|
|
|
+| |-------------------+-------------------| |
|
|
|
+| | data A | |
|
|
|
+| | | |
|
|
|
+| |-------------------+-------------------| |
|
|
|
+| | tag AxB | data B | <--. |
|
|
|
+| |-------------------+ | | |
|
|
|
+| | | | +-- 1st commit
|
|
|
+| | +-------------------+---------| | |
|
|
|
+| | | tag BxC | | <-.| |
|
|
|
+| |---------+-------------------+ | || |
|
|
|
+| | data C | || |
|
|
|
+| | | || |
|
|
|
+| |-------------------+-------------------| || |
|
|
|
+| | tag CxCRC | CRC | || /
|
|
|
+| |-------------------+-------------------| ||
|
|
|
+| | tag CRCxA' | data A' | || \
|
|
|
+| |-------------------+ | || |
|
|
|
+| | | || |
|
|
|
+| | +-------------------+----| || +-- 2nd commit
|
|
|
+| | | tag CRCxA' | | || |
|
|
|
+| |--------------+-------------------+----| || |
|
|
|
+| | CRC | padding | || /
|
|
|
+| |--------------+----+-------------------| ||
|
|
|
+| | tag CRCxA'' | data A'' | <---. \
|
|
|
+| |-------------------+ | ||| |
|
|
|
+| | | ||| |
|
|
|
+| | +-------------------+---------| ||| |
|
|
|
+| | | tag A''xD | | < ||| |
|
|
|
+| |---------+-------------------+ | |||| +-- 3rd commit
|
|
|
+| | data D | |||| |
|
|
|
+| | +---------| |||| |
|
|
|
+| | | tag Dx| |||| |
|
|
|
+| |---------+-------------------+---------| |||| |
|
|
|
+| |CRC | CRC | | |||| /
|
|
|
+| |---------+-------------------+ | ||||
|
|
|
+| | unwritten storage | |||| more commits
|
|
|
+| | | |||| |
|
|
|
+| | | |||| v
|
|
|
+| | | ||||
|
|
|
+| | | ||||
|
|
|
+| '---------------------------------------' ||||
|
|
|
+'---------------------------------------' |||'- most recent A
|
|
|
+ ||'-- most recent B
|
|
|
+ |'--- most recent C
|
|
|
+ '---- most recent D
|
|
|
+```
|
|
|
|
|
|
-| offset | size | description |
|
|
|
-|--------|------------------------|------------------------------------|
|
|
|
-| 0x0 | 8 bits | entry type (0x11 for file entries) |
|
|
|
-| 0x1 | 8 bits | entry length (8 bytes) |
|
|
|
-| 0x2 | 8 bits | attribute length |
|
|
|
-| 0x3 | 8 bits | name length |
|
|
|
-| 0x4 | 32 bits | file head |
|
|
|
-| 0x8 | 32 bits | file size |
|
|
|
-| 0xc | attribute length bytes | system-specific attributes |
|
|
|
-| 0xc+a | name length bytes | directory name |
|
|
|
+## Metadata tags
|
|
|
|
|
|
-**File head** - Pointer to the block that is the head of the file's CTZ
|
|
|
-skip-list.
|
|
|
+So in littlefs, 32-bit tags describe every type of metadata. And this means
|
|
|
+_every_ type of metadata, including file entries, directory fields, and
|
|
|
+global state. Even the CRCs used to mark the end of commits get their own tag.
|
|
|
|
|
|
-**File size** - Size of file in bytes.
|
|
|
+Because of this, the tag format contains some densely packed information. Note
|
|
|
+that there are multiple levels of types which break down into more info:
|
|
|
|
|
|
-Here's an example of a file entry:
|
|
|
```
|
|
|
-(8 bits) entry type = file (0x11)
|
|
|
-(8 bits) entry length = 8 bytes (0x08)
|
|
|
-(8 bits) attribute length = 0 bytes (0x00)
|
|
|
-(8 bits) name length = 12 bytes (0x03)
|
|
|
-(32 bits) file head = 543 (0x0000021f)
|
|
|
-(32 bits) file size = 256 KB (0x00040000)
|
|
|
-(12 bytes) name = largeavacado
|
|
|
+[---- 32 ----]
|
|
|
+[1|-- 11 --|-- 10 --|-- 10 --]
|
|
|
+ ^. ^ . ^ ^- length
|
|
|
+ |. | . '------------ id
|
|
|
+ |. '-----.------------------ type (type3)
|
|
|
+ '.-----------.------------------ valid bit
|
|
|
+ [-3-|-- 8 --]
|
|
|
+ ^ ^- chunk
|
|
|
+ '------- type (type1)
|
|
|
+```
|
|
|
+
|
|
|
+
|
|
|
+Before we go further, there's one important thing to note. These tags are
|
|
|
+**not** stored in little-endian. Tags stored in commits are actually stored
|
|
|
+in big-endian (and is the only thing in littlefs stored in big-endian). This
|
|
|
+little bit of craziness comes from the fact that the valid bit must be the
|
|
|
+first bit in a commit, and when converted to little-endian, the valid bit finds
|
|
|
+itself in byte 4. We could restructure the tag to store the valid bit lower,
|
|
|
+but, because none of the fields are byte-aligned, this would be more
|
|
|
+complicated than just storing the tag in big-endian.
|
|
|
+
|
|
|
+Another thing to note is that both the tags `0x00000000` and `0xffffffff` are
|
|
|
+invalid and can be used for null values.
|
|
|
+
|
|
|
+Metadata tag fields:
|
|
|
+
|
|
|
+1. **Valid bit (1-bit)** - Indicates if the tag is valid.
|
|
|
+
|
|
|
+2. **Type3 (11-bits)** - Type of the tag. This field is broken down further
|
|
|
+ into a 3-bit abstract type and an 8-bit chunk field. Note that the value
|
|
|
+ `0x000` is invalid and not assigned a type.
|
|
|
|
|
|
-00000000: 11 08 00 0c 1f 02 00 00 00 00 04 00 6c 61 72 67 ............larg
|
|
|
-00000010: 65 61 76 61 63 61 64 6f eavacado
|
|
|
+3. **Type1 (3-bits)** - Abstract type of the tag. Groups the tags into
|
|
|
+ 8 categories that facilitate bitmasked lookups.
|
|
|
+
|
|
|
+4. **Chunk (8-bits)** - Chunk field used for various purposes by the different
|
|
|
+ abstract types. type1+chunk+id form a unique identifier for each tag in the
|
|
|
+ metadata block.
|
|
|
+
|
|
|
+5. **Id (10-bits)** - File id associated with the tag. Each file in a metadata
|
|
|
+ block gets a unique id which is used to associate tags with that file. The
|
|
|
+ special value `0x3ff` is used for any tags that are not associated with a
|
|
|
+ file, such as directory and global metadata.
|
|
|
+
|
|
|
+6. **Length (10-bits)** - Length of the data in bytes. The special value
|
|
|
+ `0x3ff` indicates that this tag has been deleted.
|
|
|
+
|
|
|
+## Metadata types
|
|
|
+
|
|
|
+What follows is an exhaustive list of metadata in littlefs.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x401` LFS_TYPE_CREATE
|
|
|
+
|
|
|
+Creates a new file with this id. Note that files in a metadata block
|
|
|
+don't necessarily need a create tag. All a create does is move over any
|
|
|
+files using this id. In this sense a create is similar to insertion into
|
|
|
+an imaginary array of files.
|
|
|
+
|
|
|
+The create and delete tags allow littlefs to keep files in a directory
|
|
|
+ordered alphabetically by filename.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x4ff` LFS_TYPE_DELETE
|
|
|
+
|
|
|
+Deletes the file with this id. An inverse to create, this tag moves over
|
|
|
+any files neighboring this id similar to a deletion from an imaginary
|
|
|
+array of files.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x0xx` LFS_TYPE_NAME
|
|
|
+
|
|
|
+Associates the id with a file name and file type.
|
|
|
+
|
|
|
+The data contains the file name stored as an ASCII string (may be expanded to
|
|
|
+UTF8 in the future).
|
|
|
+
|
|
|
+The chunk field in this tag indicates an 8-bit file type which can be one of
|
|
|
+the following.
|
|
|
+
|
|
|
+Currently, the name tag must precede any other tags associated with the id and
|
|
|
+can not be reassigned without deleting the file.
|
|
|
+
|
|
|
+Layout of the name tag:
|
|
|
+
|
|
|
+```
|
|
|
+ tag data
|
|
|
+[-- 32 --][--- variable length ---]
|
|
|
+[1| 3| 8 | 10 | 10 ][--- (size) ---]
|
|
|
+ ^ ^ ^ ^ ^- size ^- file name
|
|
|
+ | | | '------ id
|
|
|
+ | | '----------- file type
|
|
|
+ | '-------------- type1 (0x0)
|
|
|
+ '----------------- valid bit
|
|
|
```
|
|
|
|
|
|
-## Entry attributes
|
|
|
+Name fields:
|
|
|
+
|
|
|
+1. **file type (8-bits)** - Type of the file.
|
|
|
+
|
|
|
+2. **file name** - File name stored as an ASCII string.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x001` LFS_TYPE_REG
|
|
|
|
|
|
-Each dir entry can have up to 256 bytes of system-specific attributes. Since
|
|
|
-these attributes are system-specific, they may not be portable between
|
|
|
-different systems. For this reason, all attributes must be optional. A minimal
|
|
|
-littlefs driver must be able to get away with supporting no attributes at all.
|
|
|
+Initializes the id + name as a regular file.
|
|
|
|
|
|
-For some level of portability, littlefs has a simple scheme for attributes.
|
|
|
-Each attribute is prefixes with an 8-bit type that indicates what the attribute
|
|
|
-is. The length of attributes may also be determined from this type. Attributes
|
|
|
-in an entry should be sorted based on portability, since attribute parsing
|
|
|
-will end when it hits the first attribute it does not understand.
|
|
|
+How each file is stored depends on its struct tag, which is described below.
|
|
|
|
|
|
-Each system should choose a 4-bit value to prefix all attribute types with to
|
|
|
-avoid conflicts with other systems. Additionally, littlefs drivers that support
|
|
|
-attributes should provide a "ignore attributes" flag to users in case attribute
|
|
|
-conflicts do occur.
|
|
|
+---
|
|
|
+#### `0x002` LFS_TYPE_DIR
|
|
|
|
|
|
-Attribute types prefixes with 0x0 and 0xf are currently reserved for future
|
|
|
-standard attributes. Standard attributes will be added to this document in
|
|
|
-that case.
|
|
|
+Initializes the id + name as a directory.
|
|
|
|
|
|
-Here's an example of non-standard time attribute:
|
|
|
+Directories in littlefs are stored on disk as a linked-list of metadata pairs,
|
|
|
+each pair containing any number of files in alphabetical order. A pointer to
|
|
|
+the directory is stored in the struct tag, which is described below.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x0ff` LFS_TYPE_SUPERBLOCK
|
|
|
+
|
|
|
+Initializes the id as a superblock entry.
|
|
|
+
|
|
|
+The superblock entry is a special entry used to store format-time configuration
|
|
|
+and identify the filesystem.
|
|
|
+
|
|
|
+The name is a bit of a misnomer. While the superblock entry serves the same
|
|
|
+purpose as a superblock found in other filesystems, in littlefs the superblock
|
|
|
+does not get a dedicated block. Instead, the superblock entry is duplicated
|
|
|
+across a linked-list of metadata pairs rooted on the blocks 0 and 1. The last
|
|
|
+metadata pair doubles as the root directory of the filesystem.
|
|
|
+
|
|
|
+```
|
|
|
+ .--------. .--------. .--------. .--------. .--------.
|
|
|
+.| super |->| super |->| super |->| super |->| file B |
|
|
|
+|| block | || block | || block | || block | || file C |
|
|
|
+|| | || | || | || file A | || file D |
|
|
|
+|'--------' |'--------' |'--------' |'--------' |'--------'
|
|
|
+'--------' '--------' '--------' '--------' '--------'
|
|
|
+
|
|
|
+\----------------+----------------/ \----------+----------/
|
|
|
+ superblock pairs root directory
|
|
|
```
|
|
|
-(8 bits) attribute type = time (0xc1)
|
|
|
-(72 bits) time in seconds = 1506286115 (0x0059c81a23)
|
|
|
|
|
|
-00000000: c1 23 1a c8 59 00 .#..Y.
|
|
|
+The filesystem starts with only the root directory. The superblock metadata
|
|
|
+pairs grow every time the root pair is compacted in order to prolong the
|
|
|
+life of the device exponentially.
|
|
|
+
|
|
|
+The contents of the superblock entry are stored in a name tag with the
|
|
|
+superblock type and an inline-struct tag. The name tag contains the magic
|
|
|
+string "littlefs", while the inline-struct tag contains version and
|
|
|
+configuration information.
|
|
|
+
|
|
|
+Layout of the superblock name tag and inline-struct tag:
|
|
|
+
|
|
|
```
|
|
|
+ tag data
|
|
|
+[-- 32 --][-- 32 --|-- 32 --]
|
|
|
+[1|- 11 -| 10 | 10 ][--- 64 ---]
|
|
|
+ ^ ^ ^ ^- size (8) ^- magic string ("littlefs")
|
|
|
+ | | '------ id (0)
|
|
|
+ | '------------ type (0x0ff)
|
|
|
+ '----------------- valid bit
|
|
|
+
|
|
|
+ tag data
|
|
|
+[-- 32 --][-- 32 --|-- 32 --|-- 32 --]
|
|
|
+[1|- 11 -| 10 | 10 ][-- 32 --|-- 32 --|-- 32 --]
|
|
|
+ ^ ^ ^ ^ ^- version ^- block size ^- block count
|
|
|
+ | | | | [-- 32 --|-- 32 --|-- 32 --]
|
|
|
+ | | | | [-- 32 --|-- 32 --|-- 32 --]
|
|
|
+ | | | | ^- name max ^- file max ^- attr max
|
|
|
+ | | | '- size (24)
|
|
|
+ | | '------ id (0)
|
|
|
+ | '------------ type (0x201)
|
|
|
+ '----------------- valid bit
|
|
|
+```
|
|
|
+
|
|
|
+Superblock fields:
|
|
|
+
|
|
|
+1. **Magic string (8-bytes)** - Magic string indicating the presence of
|
|
|
+ littlefs on the device. Must be the string "littlefs".
|
|
|
+
|
|
|
+2. **Version (32-bits)** - The version of littlefs at format time. The version
|
|
|
+ is encoded in a 32-bit value with the upper 16-bits containing the major
|
|
|
+ version, and the lower 16-bits containing the minor version.
|
|
|
+
|
|
|
+ This specification describes version 2.0 (`0x00020000`).
|
|
|
+
|
|
|
+3. **Block size (32-bits)** - Size of the logical block size used by the
|
|
|
+ filesystem in bytes.
|
|
|
+
|
|
|
+4. **Block count (32-bits)** - Number of blocks in the filesystem.
|
|
|
+
|
|
|
+5. **Name max (32-bits)** - Maximum size of file names in bytes.
|
|
|
+
|
|
|
+6. **File max (32-bits)** - Maximum size of files in bytes.
|
|
|
+
|
|
|
+7. **Attr max (32-bits)** - Maximum size of file attributes in bytes.
|
|
|
+
|
|
|
+The superblock must always be the first entry (id 0) in a metadata pair as well
|
|
|
+as be the first entry written to the block. This means that the superblock
|
|
|
+entry can be read from a device using offsets alone.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x2xx` LFS_TYPE_STRUCT
|
|
|
+
|
|
|
+Associates the id with an on-disk data structure.
|
|
|
|
|
|
-Here's an example of non-standard permissions attribute:
|
|
|
+The exact layout of the data depends on the data structure type stored in the
|
|
|
+chunk field and can be one of the following.
|
|
|
+
|
|
|
+Any type of struct supersedes all other structs associated with the id. For
|
|
|
+example, appending a ctz-struct replaces an inline-struct on the same file.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x200` LFS_TYPE_DIRSTRUCT
|
|
|
+
|
|
|
+Gives the id a directory data structure.
|
|
|
+
|
|
|
+Directories in littlefs are stored on disk as a linked-list of metadata pairs,
|
|
|
+each pair containing any number of files in alphabetical order.
|
|
|
+
|
|
|
+```
|
|
|
+ |
|
|
|
+ v
|
|
|
+ .--------. .--------. .--------. .--------. .--------. .--------.
|
|
|
+.| file A |->| file D |->| file G |->| file I |->| file J |->| file M |
|
|
|
+|| file B | || file E | || file H | || | || file K | || file N |
|
|
|
+|| file C | || file F | || | || | || file L | || |
|
|
|
+|'--------' |'--------' |'--------' |'--------' |'--------' |'--------'
|
|
|
+'--------' '--------' '--------' '--------' '--------' '--------'
|
|
|
```
|
|
|
-(8 bits) attribute type = permissions (0xc2)
|
|
|
-(16 bits) permission bits = rw-rw-r-- (0x01b4)
|
|
|
|
|
|
-00000000: c2 b4 01 ...
|
|
|
+The dir-struct tag contains only the pointer to the first metadata-pair in the
|
|
|
+directory. The directory size is not known without traversing the directory.
|
|
|
+
|
|
|
+The pointer to the next metadata-pair in the directory is stored in a tail tag,
|
|
|
+which is described below.
|
|
|
+
|
|
|
+Layout of the dir-struct tag:
|
|
|
+
|
|
|
```
|
|
|
+ tag data
|
|
|
+[-- 32 --][-- 32 --|-- 32 --]
|
|
|
+[1|- 11 -| 10 | 10 ][--- 64 ---]
|
|
|
+ ^ ^ ^ ^- size (8) ^- metadata pair
|
|
|
+ | | '------ id
|
|
|
+ | '------------ type (0x200)
|
|
|
+ '----------------- valid bit
|
|
|
+```
|
|
|
+
|
|
|
+Dir-struct fields:
|
|
|
+
|
|
|
+1. **Metadata pair (8-bytes)** - Pointer to the first metadata-pair
|
|
|
+ in the directory.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x201` LFS_TYPE_INLINESTRUCT
|
|
|
+
|
|
|
+Gives the id an inline data structure.
|
|
|
+
|
|
|
+Inline structs store small files that can fit in the metadata pair. In this
|
|
|
+case, the file data is stored directly in the tag's data area.
|
|
|
+
|
|
|
+Layout of the inline-struct tag:
|
|
|
|
|
|
-Here's what a dir entry may look like with these attributes:
|
|
|
```
|
|
|
-(8 bits) entry type = file (0x11)
|
|
|
-(8 bits) entry length = 8 bytes (0x08)
|
|
|
-(8 bits) attribute length = 9 bytes (0x09)
|
|
|
-(8 bits) name length = 12 bytes (0x0c)
|
|
|
-(8 bytes) entry data = 05 00 00 00 20 00 00 00
|
|
|
-(8 bits) attribute type = time (0xc1)
|
|
|
-(72 bits) time in seconds = 1506286115 (0x0059c81a23)
|
|
|
-(8 bits) attribute type = permissions (0xc2)
|
|
|
-(16 bits) permission bits = rw-rw-r-- (0x01b4)
|
|
|
-(12 bytes) entry name = smallavacado
|
|
|
+ tag data
|
|
|
+[-- 32 --][--- variable length ---]
|
|
|
+[1|- 11 -| 10 | 10 ][--- (size) ---]
|
|
|
+ ^ ^ ^ ^- size ^- inline data
|
|
|
+ | | '------ id
|
|
|
+ | '------------ type (0x201)
|
|
|
+ '----------------- valid bit
|
|
|
+```
|
|
|
+
|
|
|
+Inline-struct fields:
|
|
|
+
|
|
|
+1. **Inline data** - File data stored directly in the metadata-pair.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x202` LFS_TYPE_CTZSTRUCT
|
|
|
+
|
|
|
+Gives the id a CTZ skip-list data structure.
|
|
|
+
|
|
|
+CTZ skip-lists store files that can not fit in the metadata pair. These files
|
|
|
+are stored in a skip-list in reverse, with a pointer to the head of the
|
|
|
+skip-list. Note that the head of the skip-list and the file size is enough
|
|
|
+information to read the file.
|
|
|
+
|
|
|
+How exactly CTZ skip-lists work is a bit complicated. A full explanation can be
|
|
|
+found in the [DESIGN.md](DESIGN.md#ctz-skip-lists).
|
|
|
+
|
|
|
+A quick summary: For every _n_‍th block where _n_ is divisible by
|
|
|
+2‍_ˣ_, that block contains a pointer to block _n_-2‍_ˣ_.
|
|
|
+These pointers are stored in increasing order of _x_ in each block of the file
|
|
|
+before the actual data.
|
|
|
+
|
|
|
+```
|
|
|
+ |
|
|
|
+ v
|
|
|
+.--------. .--------. .--------. .--------. .--------. .--------.
|
|
|
+| A |<-| D |<-| G |<-| J |<-| M |<-| P |
|
|
|
+| B |<-| E |--| H |<-| K |--| N | | Q |
|
|
|
+| C |<-| F |--| I |--| L |--| O | | |
|
|
|
+'--------' '--------' '--------' '--------' '--------' '--------'
|
|
|
+ block 0 block 1 block 2 block 3 block 4 block 5
|
|
|
+ 1 skip 2 skips 1 skip 3 skips 1 skip
|
|
|
+```
|
|
|
+
|
|
|
+Note that the maximum number of pointers in a block is bounded by the maximum
|
|
|
+file size divided by the block size. With 32 bits for file size, this results
|
|
|
+in a minimum block size of 104 bytes.
|
|
|
+
|
|
|
+Layout of the CTZ-struct tag:
|
|
|
+
|
|
|
+```
|
|
|
+ tag data
|
|
|
+[-- 32 --][-- 32 --|-- 32 --]
|
|
|
+[1|- 11 -| 10 | 10 ][-- 32 --|-- 32 --]
|
|
|
+ ^ ^ ^ ^ ^ ^- file size
|
|
|
+ | | | | '-------------------- file head
|
|
|
+ | | | '- size (8)
|
|
|
+ | | '------ id
|
|
|
+ | '------------ type (0x202)
|
|
|
+ '----------------- valid bit
|
|
|
+```
|
|
|
+
|
|
|
+CTZ-struct fields:
|
|
|
+
|
|
|
+1. **File head (32-bits)** - Pointer to the block that is the head of the
|
|
|
+ file's CTZ skip-list.
|
|
|
+
|
|
|
+2. **File size (32-bits)** - Size of the file in bytes.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x3xx` LFS_TYPE_USERATTR
|
|
|
+
|
|
|
+Attaches a user attribute to an id.
|
|
|
+
|
|
|
+littlefs has a concept of "user attributes". These are small user-provided
|
|
|
+attributes that can be used to store things like timestamps, hashes,
|
|
|
+permissions, etc.
|
|
|
+
|
|
|
+Each user attribute is uniquely identified by an 8-bit type which is stored in
|
|
|
+the chunk field, and the user attribute itself can be found in the tag's data.
|
|
|
+
|
|
|
+There are currently no standard user attributes and a portable littlefs
|
|
|
+implementation should work with any user attributes missing.
|
|
|
+
|
|
|
+Layout of the user-attr tag:
|
|
|
|
|
|
-00000000: 11 08 09 0c 05 00 00 00 20 00 00 00 c1 23 1a c8 ........ ....#..
|
|
|
-00000010: 59 00 c2 b4 01 73 6d 61 6c 6c 61 76 61 63 61 64 Y....smallavacad
|
|
|
-00000020: 6f o
|
|
|
```
|
|
|
+ tag data
|
|
|
+[-- 32 --][--- variable length ---]
|
|
|
+[1| 3| 8 | 10 | 10 ][--- (size) ---]
|
|
|
+ ^ ^ ^ ^ ^- size ^- attr data
|
|
|
+ | | | '------ id
|
|
|
+ | | '----------- attr type
|
|
|
+ | '-------------- type1 (0x3)
|
|
|
+ '----------------- valid bit
|
|
|
+```
|
|
|
+
|
|
|
+User-attr fields:
|
|
|
+
|
|
|
+1. **Attr type (8-bits)** - Type of the user attributes.
|
|
|
+
|
|
|
+2. **Attr data** - The data associated with the user attribute.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x6xx` LFS_TYPE_TAIL
|
|
|
+
|
|
|
+Provides the tail pointer for the metadata pair itself.
|
|
|
+
|
|
|
+The metadata pair's tail pointer is used in littlefs for a linked-list
|
|
|
+containing all metadata pairs. The chunk field contains the type of the tail,
|
|
|
+which indicates if the following metadata pair is a part of the directory
|
|
|
+(hard-tail) or only used to traverse the filesystem (soft-tail).
|
|
|
+
|
|
|
+```
|
|
|
+ .--------.
|
|
|
+ .| dir A |-.
|
|
|
+ ||softtail| |
|
|
|
+.--------| |-'
|
|
|
+| |'--------'
|
|
|
+| '---|--|-'
|
|
|
+| .-' '-------------.
|
|
|
+| v v
|
|
|
+| .--------. .--------. .--------.
|
|
|
+'->| dir B |->| dir B |->| dir C |
|
|
|
+ ||hardtail| ||softtail| || |
|
|
|
+ || | || | || |
|
|
|
+ |'--------' |'--------' |'--------'
|
|
|
+ '--------' '--------' '--------'
|
|
|
+```
|
|
|
+
|
|
|
+Currently any type supersedes any other preceding tails in the metadata pair,
|
|
|
+but this may change if additional metadata pair state is added.
|
|
|
+
|
|
|
+A note about the metadata pair linked-list: Normally, this linked-list contains
|
|
|
+every metadata pair in the filesystem. However, there are some operations that
|
|
|
+can cause this linked-list to become out of sync if a power-loss were to occur.
|
|
|
+When this happens, littlefs sets the "sync" flag in the global state. How
|
|
|
+exactly this flag is stored is described below.
|
|
|
+
|
|
|
+When the sync flag is set:
|
|
|
+
|
|
|
+1. The linked-list may contain an orphaned directory that has been removed in
|
|
|
+ the filesystem.
|
|
|
+2. The linked-list may contain a metadata pair with a bad block that has been
|
|
|
+ replaced in the filesystem.
|
|
|
+
|
|
|
+If the sync flag is set, the threaded linked-list must be checked for these
|
|
|
+errors before it can be used reliably. Note that the threaded linked-list can
|
|
|
+be ignored if littlefs is mounted read-only.
|
|
|
+
|
|
|
+Layout of the tail tag:
|
|
|
+
|
|
|
+```
|
|
|
+ tag data
|
|
|
+[-- 32 --][-- 32 --|-- 32 --]
|
|
|
+[1| 3| 8 | 10 | 10 ][--- 64 ---]
|
|
|
+ ^ ^ ^ ^ ^- size (8) ^- metadata pair
|
|
|
+ | | | '------ id
|
|
|
+ | | '---------- tail type
|
|
|
+ | '------------- type1 (0x6)
|
|
|
+ '---------------- valid bit
|
|
|
+```
|
|
|
+
|
|
|
+Tail fields:
|
|
|
+
|
|
|
+1. **Tail type (8-bits)** - Type of the tail pointer.
|
|
|
+
|
|
|
+2. **Metadata pair (8-bytes)** - Pointer to the next metadata-pair.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x600` LFS_TYPE_SOFTTAIL
|
|
|
+
|
|
|
+Provides a tail pointer that points to the next metadata pair in the
|
|
|
+filesystem.
|
|
|
+
|
|
|
+In this case, the next metadata pair is not a part of our current directory
|
|
|
+and should only be followed when traversing the entire filesystem.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x601` LFS_TYPE_HARDTAIL
|
|
|
+
|
|
|
+Provides a tail pointer that points to the next metadata pair in the
|
|
|
+directory.
|
|
|
+
|
|
|
+In this case, the next metadata pair belongs to the current directory. Note
|
|
|
+that because directories in littlefs are sorted alphabetically, the next
|
|
|
+metadata pair should only contain filenames greater than any filename in the
|
|
|
+current pair.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x7xx` LFS_TYPE_GSTATE
|
|
|
+
|
|
|
+Provides delta bits for global state entries.
|
|
|
+
|
|
|
+littlefs has a concept of "global state". This is a small set of state that
|
|
|
+can be updated by a commit to _any_ metadata pair in the filesystem.
|
|
|
+
|
|
|
+The way this works is that the global state is stored as a set of deltas
|
|
|
+distributed across the filesystem such that the global state can be found by
|
|
|
+the xor-sum of these deltas.
|
|
|
+
|
|
|
+```
|
|
|
+ .--------. .--------. .--------. .--------. .--------.
|
|
|
+.| |->| gdelta |->| |->| gdelta |->| gdelta |
|
|
|
+|| | || 0x23 | || | || 0xff | || 0xce |
|
|
|
+|| | || | || | || | || |
|
|
|
+|'--------' |'--------' |'--------' |'--------' |'--------'
|
|
|
+'--------' '----|---' '--------' '----|---' '----|---'
|
|
|
+ v v v
|
|
|
+ 0x00 --> xor ------------------> xor ------> xor --> gstate = 0x12
|
|
|
+```
|
|
|
+
|
|
|
+Note that storing globals this way is very expensive in terms of storage usage,
|
|
|
+so any global state should be kept very small.
|
|
|
+
|
|
|
+The size and format of each piece of global state depends on the type, which
|
|
|
+is stored in the chunk field. Currently, the only global state is move state,
|
|
|
+which is outlined below.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x7ff` LFS_TYPE_MOVESTATE
|
|
|
+
|
|
|
+Provides delta bits for the global move state.
|
|
|
+
|
|
|
+The move state in littlefs is used to store info about operations that could
|
|
|
+cause to filesystem to go out of sync if the power is lost. The operations
|
|
|
+where this could occur is moves of files between metadata pairs and any
|
|
|
+operation that changes metadata pairs on the threaded linked-list.
|
|
|
+
|
|
|
+In the case of moves, the move state contains a tag + metadata pair describing
|
|
|
+the source of the ongoing move. If this tag is non-zero, that means that power
|
|
|
+was lost during a move, and the file exists in two different locations. If this
|
|
|
+happens, the source of the move should be considered deleted, and the move
|
|
|
+should be completed (the source should be deleted) before any other write
|
|
|
+operations to the filesystem.
|
|
|
+
|
|
|
+In the case of operations to the threaded linked-list, a single "sync" bit is
|
|
|
+used to indicate that a modification is ongoing. If this sync flag is set, the
|
|
|
+threaded linked-list will need to be checked for errors before it can be used
|
|
|
+reliably. The exact cases to check for are described above in the tail tag.
|
|
|
+
|
|
|
+Layout of the move state:
|
|
|
+
|
|
|
+```
|
|
|
+ tag data
|
|
|
+[-- 32 --][-- 32 --|-- 32 --|-- 32 --]
|
|
|
+[1|- 11 -| 10 | 10 ][1|- 11 -| 10 | 10 |--- 64 ---]
|
|
|
+ ^ ^ ^ ^ ^ ^ ^ ^- padding (0) ^- metadata pair
|
|
|
+ | | | | | | '------ move id
|
|
|
+ | | | | | '------------ move type
|
|
|
+ | | | | '----------------- sync bit
|
|
|
+ | | | |
|
|
|
+ | | | '- size (12)
|
|
|
+ | | '------ id (0x3ff)
|
|
|
+ | '------------ type (0x7ff)
|
|
|
+ '----------------- valid bit
|
|
|
+```
|
|
|
+
|
|
|
+Move state fields:
|
|
|
+
|
|
|
+1. **Sync bit (1-bit)** - Indicates if the metadata pair threaded linked-list
|
|
|
+ is in-sync. If set, the threaded linked-list should be checked for errors.
|
|
|
+
|
|
|
+2. **Move type (11-bits)** - Type of move being performed. Must be either
|
|
|
+ `0x000`, indicating no move, or `0x4ff` indicating the source file should
|
|
|
+ be deleted.
|
|
|
+
|
|
|
+3. **Move id (10-bits)** - The file id being moved.
|
|
|
+
|
|
|
+4. **Metadata pair (8-bytes)** - Pointer to the metadata-pair containing
|
|
|
+ the move.
|
|
|
+
|
|
|
+---
|
|
|
+#### `0x5xx` LFS_TYPE_CRC
|
|
|
+
|
|
|
+Last but not least, the CRC tag marks the end of a commit and provides a
|
|
|
+checksum for any commits to the metadata block.
|
|
|
+
|
|
|
+The first 32-bits of the data contain a CRC-32 with a polynomial of
|
|
|
+`0x04c11db7` initialized with `0xffffffff`. This CRC provides a checksum for
|
|
|
+all metadata since the previous CRC tag, including the CRC tag itself. For
|
|
|
+the first commit, this includes the revision count for the metadata block.
|
|
|
+
|
|
|
+However, the size of the data is not limited to 32-bits. The data field may
|
|
|
+larger to pad the commit to the next program-aligned boundary.
|
|
|
+
|
|
|
+In addition, the CRC tag's chunk field contains a set of flags which can
|
|
|
+change the behaviour of commits. Currently the only flag in use is the lowest
|
|
|
+bit, which determines the expected state of the valid bit for any following
|
|
|
+tags. This is used to guarantee that unwritten storage in a metadata block
|
|
|
+will be detected as invalid.
|
|
|
+
|
|
|
+Layout of the CRC tag:
|
|
|
+
|
|
|
+```
|
|
|
+ tag data
|
|
|
+[-- 32 --][-- 32 --|--- variable length ---]
|
|
|
+[1| 3| 8 | 10 | 10 ][-- 32 --|--- (size) ---]
|
|
|
+ ^ ^ ^ ^ ^ ^- crc ^- padding
|
|
|
+ | | | | '- size (12)
|
|
|
+ | | | '------ id (0x3ff)
|
|
|
+ | | '----------- valid state
|
|
|
+ | '-------------- type1 (0x5)
|
|
|
+ '----------------- valid bit
|
|
|
+```
|
|
|
+
|
|
|
+CRC fields:
|
|
|
+
|
|
|
+1. **Valid state (1-bit)** - Indicates the expected value of the valid bit for
|
|
|
+ any tags in the next commit.
|
|
|
+
|
|
|
+2. **CRC (32-bits)** - CRC-32 with a polynomial of `0x04c11db7` initialized
|
|
|
+ with `0xffffffff`.
|
|
|
+
|
|
|
+3. **Padding** - Padding to the next program-aligned boundary. No guarantees
|
|
|
+ are made about the contents.
|
|
|
+
|
|
|
+---
|