1. About
YAFFS (Yet Another Flash File System) is a log-structured filesystem for NAND flash hardware.
This document describes the YAFFS2 on-media format in enough detail to implement an independent reader or image manipulation tool. It describes the format, not the runtime behavior (garbage collection, caching, wear handling) of the reference implementation. YAFFS1 tags and the checkpoint payload are deliberately out of scope.
YAFFS1 was the first version of the format, designed for the small-page NAND chips of the time.
YAFFS2 (the format this document describes) was an evolution of the format to relax some hard coded assumptions about NAND geometries, and to become truly log-structured. The main differences:
-
YAFFS1 assumes 512 byte chunks with 16 bytes of spare data. YAFFS2 supports arbitrary geometries, in particular the 2 KiB and larger pages of newer NAND chips.
-
YAFFS1 marks stale chunks by going back and rewriting their spare data to set a "deleted" marker. Newer NAND chips do not tolerate rewriting a page in place, so YAFFS2 is strongly "write-once": a chunk is never modified between being written and its block being erased.
-
Without a deletion marker, YAFFS2 must determine which chunks are current purely from write order. It introduces the sequence number to recover that order, and shrink headers to keep truncated-away data from being resurrected.
-
The tag formats differ. YAFFS1 tags are bit-packed (together with their ECC) into the 16 byte spare area, which limits chunk ids to 20 bits and therefore file sizes to about 512 MiB. YAFFS2 tags use full 32 bit fields and require a larger spare area (or inband tags).
-
YAFFS1 tags include a 2 bit serial number to disambiguate the two copies of a chunk that can coexist while it is being moved (e.g. by garbage collection). YAFFS2 does not need one, since write order already determines which copy is newer.
This document attempts to be precise in its usage of YAFFS/YAFFS1/YAFFS2. When referring to something that is true of both YAFFS1 and 2, it will use simply YAFFS, otherwise it will use YAFFS1/YAFFS2 to indicate something that only applies to that version.
2. Concepts
- Chunk
-
A chunk is the unit of allocation. Typically, a chunk will be the same as a NAND page, but this is not required.
There are two types of chunks:
- Data Chunks
-
A chunk holding regular data file contents
- Object Header Chunks
-
A descriptor for an object. This holds details such as the identifier for the parent directory, object name, etc
- Spare Data
-
Chunks have a small associated "Spare Data" or "Out of Band Data" area. When stored in an image, the spare data is stored immediately after the data section of the chunk.
Spare data is used to store tags associated with the chunk (usually, see Inband Tags which can be used in case spare data is too small).
- Block
-
A block is a collection of chunks, typically 32 to 128 but as many as a few hundred. A block is the unit of erasure.
- Object
-
An object is anything that is stored on the filesystem:
-
Regular Data Files
-
Directories
-
Hard Links
-
Symbolic Links
-
Special Objects (Pipes, Devices, Sockets)
All objects are identified by a unique integer object id (
obj_id) -
- Tags
-
Metadata stored alongside each chunk: the
obj_idof the object the chunk belongs to, the chunk id within the object, and (for data chunks) the populated size of the data. The exact data stored is different between YAFFS1 and YAFFS2.Tags are stored in spare data, unless configured to be stored inband (because spare data is too small).
- Sequence Number
-
Every block is assigned a sequence number when it is allocated for writing. Sequence numbers increase monotonically over the life of the filesystem, so they record the order in which blocks were written. All chunks in a block store the block’s sequence number in their tags. Since YAFFS2 never rewrites chunks in place, the sequence number is what allows a reader to reconstruct the order of events in the log (see Reading a YAFFS2 Image).
Chunks within a block are written in order, from the first chunk to the last. This totally orders all chunks in the filesystem: a chunk is newer than another if its block has a higher sequence number, or if it is later within the same block. Whenever this document refers to the "latest" or "newest" version of something, it means the latest by this order.
Normal sequence numbers are at least
0x1000and strictly below0xEFFFFF00. Values outside this range are special:0x21The block holds checkpoint data, not regular chunks.
0xFFFF0000The block is bad. When a block fails and the driver-level bad block marking also fails, the block is erased and its first chunk rewritten with this sequence number (and zeroes in the other tag fields) so the block can still be recognized as bad.
0xFFFFFFFFAll-ones is not a real sequence number: an erased chunk reads as all
0xFFbytes, so tags with an all-ones sequence number indicate an unwritten chunk.
3. Overview
A YAFFS filesystem is simply a collection of blocks. There is no overall metadata or header. Because YAFFS is a log-structured filesystem, multiple versions of the same chunks can appear. The latest version of each chunk wins, and all earlier versions are ignored (see sequence number for how chunks are ordered).
3.1. Image Layout
On NAND hardware, the data area of a chunk is stored in a page and the spare data is stored in the page’s out of band (OOB) area. When YAFFS is stored as an image file, each chunk’s data area is followed immediately by its spare data area. Chunks appear in order within a block, and blocks appear in order.
A freshly erased block reads as all 0xFF bytes.
Anything that would be all 0xFF (a whole block, a single chunk, or the tags of a chunk) is considered unwritten.
All structures described in this document are byte packed: fields are stored consecutively with no implicit alignment padding. Where padding exists on disk, it is listed explicitly as a field.
3.2. Endianness
YAFFS stores all integers in the native byte order of the system that wrote the filesystem. Images are most commonly little endian, and all layouts in this document assume little endian storage. A reader that supports both byte orders can detect a byte-swapped filesystem by sanity checking values it reads (for example, sequence numbers and object types have small valid ranges).
3.3. Error Correction
Except for the optional Tag ECC covering the tags themselves, error correction is not part of the YAFFS2 format: ECC for the data area of a chunk is the responsibility of the flash driver or MTD layer. When reading an image file rather than physical flash, no ECC processing is needed.
4. Parameters
There are several parameters which can be configured for a YAFFS filesystem. Because there is no filesystem-level metadata, the reader of a YAFFS filesystem must know these parameters inherently (or have them transmitted out of band, or guess).
- Total Bytes Per Chunk
-
The number of bytes in the data area of the chunk. If not configured with Inband Tags, then this whole area will be used to store data and the tags will be stored in the spare area. If Inband Tags is enabled then the end of this area will be used to store tags and the rest will be used to store data.
- Data Bytes Per Chunk
-
The number of bytes of the data area available to store data, called
data_bytes_per_chunkin formulas throughout this document. Not an independent parameter: it is equal to Total Bytes Per Chunk, or Total Bytes Per Chunk minus 16 when Inband Tags are enabled. - Spare Bytes Per Chunk
-
The number of bytes available for spare data out of band, associated with each chunk.
- Chunks Per Block
-
The number of chunks per block.
- Inband Tags
-
Flag indicating whether tags should be stored in the data area rather than in the spare data. See Inband Tags for details.
- Tag ECC
-
Flag indicating whether tags are followed by ECC (Error Correction Code). If Inband Tags are enabled, this is always disabled. See Tag ECC for details.
- Summaries
-
Flag indicating whether Block Summaries are written to the end of each block. A reader can also detect summaries by their reserved object id (see Special Object Ids).
5. Tags Format
|
Note
|
Only the YAFFS2 tag format is described; YAFFS1 tags are out of scope for this document. |
5.1. YAFFS2
| Type | Name | Description |
|---|---|---|
u32 |
seq_num |
Sequence Number for this chunk. Should be the same for all (written) chunks in a block. A value of |
u32 |
obj_id |
The id of the object this chunk is related to. If Extra Tag Info is present, the most significant nibble stores the object type instead of being part of the object id. |
u32 |
chunk_id |
Identifies where in the file this chunk belongs.
If the most significant bit is set, this chunk is an object header whose tags carry Extra Tag Info, and the rest of the field is interpreted differently. |
u32 |
n_bytes |
The populated size of this data chunk. If this is not a data chunk, the value is ignored, unless Extra Tag Info is present. |
5.1.1. Extra Tag Info
The tags of an object header chunk may duplicate key fields of the Object Header, so that a scanner can reconstruct the directory tree from tags alone, without reading the data area of most object header chunks (see Reading a YAFFS2 Image).
Extra tag info is present if and only if the most significant bit of the chunk_id field is set (chunk_id & 0x80000000 != 0).
Extra tag info should only be present on object header chunks, and must not be used if the object is a file whose size in bytes cannot fit in 31 bits. When present, the tag fields are reinterpreted as follows:
- chunk_id
-
The most significant nibble holds flags, and the low 28 bits store the object id of the parent directory instead of a chunk id (the chunk id of an object header is implicitly 0).
The flag bits in the most significant nibble:
0x8 Always set, to indicate extra tag information is present.
0x4 Set to indicate this object header is a shrink header.
0x2 Set to indicate this object header shadows another object.
0x1 Currently reserved, and should always be unset.
- obj_id
-
The most significant nibble stores the object type (
obj_id | (object_type << 28)). The low 28 bits store the object id as usual. - n_bytes
-
The value depends on the object type:
Hardlink Stores the object id of the object this is hardlinked to.
File Stores the size of the whole file. This field can always hold the full size, because extra tag info is not written for files too large for it: writers fall back to a plain object header, and readers get the size from the header’s 64 bit file size fields instead.
Otherwise Always stores 0
5.1.2. Tag ECC
If Tag ECC is enabled (and Inband Tags are not enabled), the tags are immediately followed by ECC information. The ECC is computed over the 16 bytes of the tags structure, and is based on the algorithm used in SmartMedia.
To compute the ECC, start with all three parity fields zeroed, then for each byte of the tags structure at index i (0 to 15):
-
XOR the byte’s six column parities (defined under
col_paritybelow) intocol_parity. -
If the byte has an odd number of bits set, XOR
iintoline_parity, and XOR~i(the bitwise NOT of the 32 bit index) intoline_parity_inv.
| Type | Name | Description | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
u8 |
col_parity |
The accumulated column parity of all 16 tag bytes. Only 6 bits are used; bits 6 and 7 are always 0. Each bit is the XOR of a group of bits of the byte value:
|
||||||||||||
u8[3] |
padding |
Unused padding bytes |
||||||||||||
u32 |
line_parity |
The XOR of the indexes of all tag bytes with an odd number of bits set. |
||||||||||||
u32 |
line_parity_inv |
The XOR of the inverted indexes ( |
5.1.3. Inband Tags
If Inband Tags is enabled, the tags are stored at the end of the chunk’s data area instead of in the spare data, shrinking the space available for data (as noted in Parameters):
data_bytes_per_chunk = total_bytes_per_chunk - 16
The 16 byte tags structure described in YAFFS2 is stored at offset data_bytes_per_chunk, and only the first data_bytes_per_chunk bytes are available to store data.
Tag ECC is never used with inband tags, and the spare data area is not used by YAFFS at all (it remains available to the driver, e.g. for ECC).
|
Warning
|
With small chunks (e.g. 512 byte chunks, giving a 496 byte data area), the inband tags overlap the last 16 bytes of the 512 byte Object Header structure, destroying the file_size_high, shadows_obj and is_shrink fields.
For this reason the object header duplicates the shadow and shrink information in the inband_shadowed_obj_id and inband_is_shrink fields, which are stored at lower offsets.
Writers should always store identical values in both copies.
Readers must take the shadow and shrink information from the inband_ fields when inband tags are in use, and from shadows_obj/is_shrink otherwise.
|
6. Special Object Ids
A few object ids are reserved:
| Value | Name | Description |
|---|---|---|
1 |
Root |
The root directory of the filesystem. |
2 |
Lost+Found |
A directory collecting chunks whose object cannot be properly reconstructed. |
3 |
Unlinked |
A pseudo-directory for open-but-unlinked files. Objects with this parent are subject to deletion (see Deletion). |
4 |
Deleted |
A pseudo-directory for deleted files. Objects with this parent are deleted (see Deletion). |
0x10 |
Summary |
Marks chunks holding a block summary. |
These built-in objects (ids 1 to 4) exist implicitly and usually have no object header of their own. However, an object header may appear for the root or lost+found directories (for example, to record permissions or extended attributes on the root directory). Object headers with ids 3, 4 or 0x10 should never describe real objects.
Object id 0 is not a valid object id.
It only appears as a "no object" marker: an object header written for the root directory stores 0 as its parent_obj_id, since the root has no parent (it is not recorded as its own parent).
Readers should ignore the parent_obj_id of headers for the root and lost+found directories.
Normal objects use ids from 256 (0x100) up to 0x3FFFF:
the entire range below 256 is reserved for special ids, and object ids are limited to 18 bits (see Object Header).
|
Note
|
New on-media features using reserved ids have been designed to degrade gracefully in readers that predate them: summary chunks, for example, carry tags that make them look like the data chunks of a small regular file for which no object header is ever written. A reader that does not understand summaries reconstructs a stray headerless object that never appears as an actual file on the filesystem, rather than misinterpreting real file data. |
7. Object Header
An object header is a chunk describing an object, marked by chunk_id == 0 in its tags.
A new object header is written every time an object is created, renamed, resized, or otherwise has its metadata changed.
Many object headers for the same object id can be present in the filesystem; only the latest one (as ordered by sequence number) is current, and all older ones are stale.
The data area of an object header chunk contains the following 512 byte structure.
The rest of the data area is filled with 0xFF bytes, except for the optional extended attributes and, when Inband Tags are enabled, the tags at the end of the data area.
Fields that do not apply to the object’s type are also stored with all bits set (0xFF bytes).
| Type | Name | Description | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
u32 |
type |
The type of this object:
|
||||||||||||||
u32 |
parent_obj_id |
The object id of the directory containing this object.
See Special Object Ids for reserved values, in particular the |
||||||||||||||
u16 |
checksum |
An obsolete checksum of the name.
No longer used, stored as |
||||||||||||||
u8[256] |
name |
The name of this object within its parent directory, null-terminated. |
||||||||||||||
u8[2] |
padding |
Unused padding bytes. |
||||||||||||||
u32 |
mode |
Unix permissions and file type bits, as in the |
||||||||||||||
u32 |
uid |
User ID of the owner. |
||||||||||||||
u32 |
gid |
Group ID of the owner. |
||||||||||||||
u32 |
atime |
Nominal access time, as a 32 bit unix timestamp. Not updated by reads; see Timestamps. |
||||||||||||||
u32 |
mtime |
Modification time, as a 32 bit unix timestamp. See Timestamps. |
||||||||||||||
u32 |
ctime |
Change time, as a 32 bit unix timestamp. See Timestamps. |
||||||||||||||
u32 |
file_size_low |
The low 32 bits of the file size. Only used for regular files. See File Size. |
||||||||||||||
s32 |
equiv_id |
Only used for hard links: the object id of the object this hard link is equivalent to. |
||||||||||||||
u8[160] |
alias |
Only used for symbolic links: the target path, null-terminated. |
||||||||||||||
u32 |
rdev |
Only used for special objects: the system specific device number, as in the |
||||||||||||||
u32[2] |
ctime_64 |
The change time as a 64 bit unix timestamp, split into two u32 values, low half first. Note that the 64 bit timestamps are stored in a different order than the 32 bit ones (change, access, modification). See Timestamps. |
||||||||||||||
u32[2] |
atime_64 |
The access time, encoded as in |
||||||||||||||
u32[2] |
mtime_64 |
The modification time, encoded as in |
||||||||||||||
u32 |
inband_shadowed_obj_id |
A second copy of |
||||||||||||||
u32 |
inband_is_shrink |
A second copy of
|
||||||||||||||
u32 |
file_size_high |
The high 32 bits of the file size.
For sizes that fit in 32 bits, storing either the actual high bits (0) or |
||||||||||||||
u32 |
reserved |
Unused. |
||||||||||||||
s32 |
shadows_obj |
If greater than 0, this object header shadows (replaces) the object with this object id. Otherwise 0 or -1. |
||||||||||||||
u32 |
is_shrink |
If non-zero, this object header is a shrink header. |
|
Note
|
Object ids are limited to 18 bits (a maximum object id of 0x3FFFF), and chunk ids to 28 bits (a maximum chunk id of 0xFFFFFFF).
The requirement that object ids leave the top nibble clear follows from the Extra Tag Info encoding, which also limits parent object ids to 28 bits.
|
7.1. Timestamps
Each of the three timestamps is stored twice: as a 32 bit unix timestamp in atime/mtime/ctime, and as a 64 bit unix timestamp split across the two u32 values of the corresponding _64 field (low half first).
To read a timestamp, look at the second (high) u32 of the _64 field:
-
If it is
0xFFFFFFFF, no 64 bit timestamp is stored; use the 32 bit value. -
Otherwise, the timestamp is
(high << 32) | low. Writers store the low 32 bits in the 32 bit field as well, so both representations agree.
The 64 bit fields were a later addition (originally holding WinCE timestamps), which is why they are stored as pairs of u32 values, apart from the 32 bit fields and in a different order.
Despite its name, the access time does not track reads.
Timestamps are only stored in object headers, and updating the access time on every read would mean writing a new object header for every read: far too much writing (and erasing) for a write-once log.
Instead, the access time is set equal to the modification time when the object is created, and changes after that only through an explicit request to set file times (e.g. utime()), which writes a new object header.
7.2. File Size
For regular files, the file size is split across file_size_low and file_size_high:
-
If
file_size_highis0xFFFFFFFF, the file size is justfile_size_low. -
Otherwise, the file size is
(file_size_high << 32) | file_size_low.
For a file smaller than 4 GiB, a writer may store either the actual high 32 bits (0) or 0xFFFFFFFF in file_size_high; both are equally valid and decode to the same size.
Current reference writers store the actual high bits; 0xFFFFFFFF is what implementations that predate the field leave behind (erased fill), which is why it means "use file_size_low only".
7.3. Extended Attributes
Extended attributes are stored in the object header chunk’s data area, immediately following the header structure, and may use the rest of the data area:
xattr_size = data_bytes_per_chunk - sizeof(object_header)
(This means the chunk data area must be larger than the object header structure for extended attributes to be usable.)
The area holds a sequence of name/value records, packed with no padding:
| Type | Name | Description |
|---|---|---|
s32 |
size |
The total size of this record in bytes, including this field. |
u8[] |
name |
The attribute name, null-terminated. |
u8[] |
value |
The attribute value, filling the rest of the record: |
The list ends at the first position where a record cannot be read:
-
Fewer than 4 bytes remain in the area, leaving no room for a
sizefield. -
sizeis less than or equal to 0 (0x00000000, or0xFFFFFFFFfrom the erased fill). -
The record would extend to or beyond the end of the area.
The last condition is strict: a record ending exactly at the last byte of the area is not readable, and also ends the list. Writers must therefore never write a record that ends exactly at the end of the area.
Because a new object header is written whenever an object changes, the extended attributes are rewritten with every header; there is no separate versioning for them.
8. Block Summaries
Scanning a YAFFS2 filesystem requires reading the tags of every chunk. On real NAND hardware, reading the many small spare data areas is slow. To speed this up, YAFFS2 can optionally sacrifice the last few chunks of each block to store a summary: a copy of the tags of all preceding chunks in the block, packed into whole chunks that can be fetched in a few reads.
The number of chunks reserved for the summary is (using truncating integer division):
chunks_used = (chunks_per_block * 12 + data_bytes_per_chunk - 1) / (data_bytes_per_chunk - 16) chunks_per_summary = chunks_per_block - chunks_used
This arithmetic is almost, but not quite, a ceiling division:
the rounding term added to the numerator is data_bytes_per_chunk - 1 even though the divisor is data_bytes_per_chunk - 16, and the numerator counts all chunks_per_block entries even though only chunks_per_summary are stored.
Both quirks can reserve one more chunk than strictly needed; over-reserved chunks at the end of the block are simply left unwritten.
Writers must reproduce this arithmetic exactly, because the reference implementation reads summaries at the positions it produces.
Readers have an easier option: summary chunks identify themselves by the reserved summary object id in their tags, so the summary can be found by reading the tags of the last few chunks of the block, and the position of the chunk tagged with chunk_id 1 is chunks_per_summary.
The summary data is an array of chunks_per_summary entries, one for each data-carrying chunk in the block (chunks 0 to chunks_per_summary - 1):
| Type | Name | Description |
|---|---|---|
u32 |
obj_id |
The obj_id field of the chunk’s tags. |
u32 |
chunk_id |
The chunk_id field of the chunk’s tags. |
u32 |
n_bytes |
The n_bytes field of the chunk’s tags. |
The values are exactly the stored tag values, i.e. the Extra Tag Info encoding applies. The sequence number is not duplicated, since it is the same for the whole block.
Only chunks that were actually written have their tags recorded.
A chunk the writer skipped over (for example, because it failed a pre-write erased check) leaves its entry all-zero.
Since 0 is not a valid object id, readers can recognize such entries by their obj_id, and must fall back to reading the chunk’s own tags from its spare area.
This array is written to the reserved chunks (starting at chunk index chunks_per_summary), split into pieces of at most data_bytes_per_chunk - 16 bytes.
The data area of each summary chunk starts with the following 16 byte header, followed by the next piece of the array:
| Type | Name | Description |
|---|---|---|
u32 |
version |
The summary format version. Must be 1. |
u32 |
block |
The block number of the block this summary describes. |
u32 |
seq |
The sequence number of the block. |
u32 |
sum |
The sum of all bytes of the complete summary tags array (across all summary chunks). |
The same header is repeated in every chunk of the summary.
The summary chunks themselves carry normal tags: obj_id is the reserved summary object id 0x10, chunk_id counts up from 1, n_bytes is the number of data area bytes used in that chunk (header plus payload), and seq_num is the block’s sequence number.
This makes the summary look like the data chunks of a small file.
However, no object header (chunk 0) is ever written for the summary object id, so this fake file has no name, parent, or metadata, and never appears as an actual file on the filesystem.
A reader unaware of summaries reconstructs a headerless, hanging object, which may end up in lost+found.
A reader should validate a summary before trusting it (the tags of each summary chunk, and the version, block, sequence number and checksum of the header), and fall back to reading the individual chunk tags if validation fails.
9. Reading a YAFFS2 Image
There is no directory tree or index stored on disk: a reader reconstructs the entire filesystem state by scanning the tags of every chunk. Because chunks are never rewritten, the tags plus the write order (recoverable from sequence numbers) fully determine the current state.
The reference implementation scans backwards, from newest chunk to oldest, and that is the model described here. The fundamental rule is:
The same rule applies to object headers, which are the (obj_id, 0) pair (headers with Extra Tag Info store their object id in the low bits of obj_id and have an implicit chunk id of 0).
This scan order visits chunks exactly newest to oldest, as ordered by sequence number.
9.1. Block Pre-Scan
First, read the tags of the first chunk of every block:
-
If the tags are unwritten (the sequence number is
0xFFFFFFFF), the block is empty (or is the block currently being written to; see below). -
If the sequence number is
0x21, the block holds checkpoint data and takes no part in scanning. -
If the sequence number is
0xFFFF0000, the block was retired as bad and takes no part in scanning. -
Otherwise the block contains data, and the first chunk’s sequence number is the block’s sequence number. Any sequence number outside the normal range (at least
0x1000and below0xEFFFFF00) that the reader does not recognize marks a block that takes no part in scanning, whether it is corruption or a special value from a newer format feature; readers should skip such blocks. (This is what allows special sequence numbers like the checkpoint marker to be introduced safely: readers that predate them skip those blocks.)
Then sort the data blocks by sequence number and scan them in decreasing order.
9.2. Scanning a Block
If Block Summaries are in use, first try to read and validate the block’s summary; on success, the tags of the data chunks come from the summary instead of their spare areas. The exception is all-zero summary entries, which record nothing about their chunk: the tags of such a chunk must still be read from its spare area (see Block Summaries).
Then, for each chunk from the last data chunk down to chunk 0, read its tags and:
-
If the tags are unwritten, the chunk is free space. (An unwritten chunk after a written one in the same block means the writer skipped it, e.g. because it failed an erased-check; an unwritten chunk with no written chunks after it means the block was the last one being written, the "allocating" block.)
-
If the tags fail sanity checks (an uncorrectable tags ECC error, an out of range object id or chunk id, a data chunk whose
n_bytesis larger than the data area, or a sequence number that doesn’t match the block’s), ignore the chunk. Chunks tagged with the reserved summary object id (0x10) are also ignored here: summary chunks are only ever read by the summary validation step above, even when that validation fails. -
If
chunk_id > 0, process it as a data chunk. -
If
chunk_id == 0(or the extra tag info flag is set), process it as an object header.
9.3. Data Chunks
A data chunk with id n holds the file’s bytes starting at offset (n - 1) * data_bytes_per_chunk, of which the first n_bytes are populated.
-
If a chunk with the same
(obj_id, chunk_id)was already seen, this chunk is stale and must be ignored. -
If the chunk’s start offset lies at or beyond the object’s current shrink size (see Shrink Headers), the chunk is stale data from before a truncation and must be ignored.
-
Until an object header for the object has been seen, the file size is estimated as the maximum of
offset + n_bytesover its data chunks. When the first (newest) header is seen, the file size becomes the larger of the estimate so far and the header’s file size; data chunks encountered after that (all older than the header) no longer affect the size. The estimate can validly exceed the header’s size: a new header is only written on events like close or flush, not on every write, so the newest data chunks of a file that was being written when power was lost extend past the size recorded in its newest header.
Missing chunks are holes: any part of a file not covered by a current data chunk reads as zero bytes. A file can be larger than its last data chunk extends; truncating a file to a larger size, or writing after seeking past the end, produces this.
A data chunk can also be partially populated (n_bytes < data_bytes_per_chunk), and not only at the end of the file: writes only rewrite the chunks they touch, so a chunk written while the file was short stays partial even after later writes grow the file past it.
Only the first n_bytes bytes of the chunk are file contents; the rest of the chunk’s range is a hole, and reads as zero bytes.
Readers should not depend on the stored bytes beyond n_bytes being zero, and should treat that range as a hole.
The kernel implementation, however, reads the stored bytes directly, so writers must fill the data area beyond n_bytes with zero bytes.
9.4. Object Headers
The first (newest) object header seen for an object provides all of its metadata: type, parent, name, permissions, timestamps and size. Older headers for the same object are ignored, with one exception: shrink information must still be extracted from stale file headers (see Shrink Headers).
If the header carries Extra Tag Info, a scanner can build the full directory tree from the tags alone (type, parent, and file size / hardlink target), deferring the read of the header’s data area until the name or other details are needed. The reference implementation calls this lazy loading.
9.4.1. Deletion
YAFFS deletes an object by writing a new object header for it whose parent_obj_id is one of the reserved pseudo-directories:
| Unlinked (3) |
The object has been unlinked while still open; it is deleted once closed. |
| Deleted (4) |
The object is deleted, and its chunks are reclaimable garbage. |
If the newest header of an object places it in either pseudo-directory, a reader reconstructing the filesystem should treat the object as nonexistent, and all of its chunks as stale. Such a header is also treated as an implicit shrink header with size 0, invalidating all older data chunks of the object.
9.4.2. Shrink Headers
When a file is truncated to a smaller size, the data chunks beyond the new size become stale, but they are older than the truncation, so a backwards scanner would otherwise resurrect them.
To prevent this, the object header written for a truncation has its is_shrink field set.
While scanning, a reader maintains a per-file shrink size, initialized to "unlimited":
-
When a shrink header is seen (even a stale one), the shrink size is lowered to the header’s file size, if smaller.
-
The newest header of a file lowers the shrink size to its file size even without the shrink flag: data chunks beyond the current file size are necessarily stale.
-
Headers placing the file in the unlinked/deleted pseudo-directories lower the shrink size to 0.
-
Data chunks starting at or beyond the current shrink size are ignored (see Data Chunks).
Shrink headers also constrain the garbage collector at runtime (blocks containing them cannot be erased as freely), which is why they are only written for actual truncations, not for every header update.
|
Note
|
When Inband Tags are enabled, the shrink flag must be read from the inband_is_shrink field of the header rather than is_shrink.
|
9.4.3. Shadowing
A rename that overwrites an existing target must atomically delete the target object.
YAFFS implements this by writing the renamed object’s new header with the shadows_obj field set to the object id of the replaced target (and the shadows flag set in the Extra Tag Info, if present).
When a scanner encounters a header that shadows object X:
-
If object
Xhas already been seen in the scan, a newer version ofXexists and the shadowing is outdated; ignore it. -
Otherwise, object
Xis deleted as of this point in the log: treatXlike a deleted object (all its older chunks are stale).
|
Note
|
When Inband Tags are enabled, the shadowed object id must be read from the inband_shadowed_obj_id field of the header rather than shadows_obj.
|
9.4.4. Hard Links
A hard link is a distinct object (with its own object id, name and parent) whose header stores the object id of the object it links to in equiv_id.
Since a hard link may be scanned before the object it points to, readers should collect hard links during the scan and resolve them once scanning completes.
9.5. Checkpoints
On a clean unmount, the reference implementation can write a checkpoint: a serialized dump of its runtime state that allows the next mount to skip scanning entirely.
Checkpoint data is written to otherwise erased blocks using the reserved sequence number 0x21, which is the only marker identifying a checkpoint block.
The tags of checkpoint chunks do not follow the normal rules described in this document; even the obj_id field is repurposed for checkpoint-internal bookkeeping.
The checkpoint format is not specified here: it is a direct serialization of implementation structures and changes between versions and configurations. The checkpoint is invalidated (its blocks erased) as soon as the filesystem is written to again, so readers can always treat checkpoint blocks as opaque, skip them, and reconstruct the filesystem by scanning as described above.
10. Worked Example
The image dissected below is a complete YAFFS2 filesystem containing a single 15 byte file, hello.txt, in the root directory.
It was generated by mkyaffs2image, the image creation tool shipped with the reference implementation.
The geometry is 2048 data bytes and 64 spare bytes per chunk, 64 chunks per block (so the whole image is a single block), with tag ECC enabled and inband tags disabled.
In the image file, each 2048 byte data area is followed immediately by its 64 byte spare data area, so chunk n starts at image offset n * 2112.
Only two chunks are written: an object header chunk for the file, followed by its single data chunk.
Every other byte of the image, including the unused remainder of each written chunk’s data and spare areas, is erased fill (0xFF).
10.1. The Object Header Chunk
The chunk’s tags, stored in the first 16 bytes of its spare data area (image offset 2048):
00 10 00 00 seq_num = 0x1000, the lowest normal sequence number 01 01 00 00 obj_id = 257 (0x101), a normal object id 00 00 00 00 chunk_id = 0: this chunk is an object header ff ff 00 00 n_bytes = 0xFFFF, ignored (not a data chunk)
This writer does not use Extra Tag Info: the most significant bit of chunk_id is clear, and the object’s type and parent are stored only in the header structure itself.
The tags are followed by the 12 byte tag ECC:
25 col_parity 00 00 00 padding 00 00 00 00 line_parity ff ff ff ff line_parity_inv
Three tag bytes have an odd number of bits set: the 0x10 at index 1 and the two 0x01 bytes at indexes 4 and 5.
line_parity is therefore 1 ^ 4 ^ 5 = 0, and line_parity_inv is ~1 ^ ~4 ^ ~5 = 0xFFFFFFFF.
The data area of the chunk (image offset 0) begins with the 512 byte Object Header structure:
0x000 01 00 00 00 type = 1, Regular File
0x004 01 00 00 00 parent_obj_id = 1, the root directory
0x008 ff ff checksum, obsolete
0x00a 68 65 6c 6c name = "hello.txt", null-terminated
6f 2e 74 78 (this writer zero fills the rest of
74 00 00 ... the 256 byte field)
0x10a ff ff padding
0x10c a4 81 00 00 mode = 0x81A4: regular file, rw-r--r--
0x110 f5 01 00 00 uid = 501
0x114 00 00 00 00 gid = 0
0x118 00 b9 55 69 atime = 0x6955B900, 2026-01-01T00:00:00Z
0x11c 00 b9 55 69 mtime = 0x6955B900
0x120 ab 40 51 6a ctime = 0x6A5140AB, when the image was generated
0x124 0f 00 00 00 file_size_low = 15
0x128 ff ff ff ff equiv_id: unused, not a hard link
0x12c ff (x 160) alias: unused, not a symbolic link
0x1cc 00 00 00 00 rdev = 0
0x1d0 ff (x 8) ctime_64: each high half is 0xFFFFFFFF, so no
0x1d8 ff (x 8) atime_64: 64 bit timestamps are stored, and
0x1e0 ff (x 8) mtime_64: readers use the 32 bit values
0x1e8 ff ff ff ff inband_shadowed_obj_id: unused, inband tags off
0x1ec ff ff ff ff inband_is_shrink: unused, inband tags off
0x1f0 00 00 00 00 file_size_high = 0 (0xFFFFFFFF would also be valid)
0x1f4 ff ff ff ff reserved
0x1f8 ff ff ff ff shadows_obj = -1: does not shadow an object
0x1fc ff ff ff ff is_shrink (see warning below)
The rest of the data area (offsets 0x200 to 0x7FF) is 0xFF: the file has no extended attributes.
|
Warning
|
This writer leaves is_shrink as erased fill instead of storing 0.
Readers take any non-zero value as marking a shrink header, which happens to be harmless here: the implied shrink size is the file’s own size.
Writers should store an explicit 0.
|
10.2. The Data Chunk
The tags of the second chunk (image offset 2112 + 2048 = 4160):
00 10 00 00 seq_num = 0x1000, same block as the header chunk 01 01 00 00 obj_id = 257: the same object 01 00 00 00 chunk_id = 1: the first data chunk, file offset 0 0f 00 00 00 n_bytes = 15
followed by its tag ECC:
30 col_parity 00 00 00 padding 08 00 00 00 line_parity 08 00 00 00 line_parity_inv
The odd-parity tag bytes are at indexes 1, 4, 5 and 8, so line_parity = 1 ^ 4 ^ 5 ^ 8 = 8.
With an even number of contributing indexes the inversions cancel out, so line_parity_inv has the same value.
The data area (image offset 2112) begins with the file contents:
48 65 6c 6c 6f 2c 20 59 Hello, Y 41 46 46 53 32 21 0a AFFS2!.
|
Note
|
The remaining 2033 bytes of the data area are 0xFF erased fill, violating the zero fill beyond n_bytes required by Data Chunks.
This is safe because mkyaffs2image never writes a file that extends past the populated bytes of its chunks, so the fill always lies beyond the file size and is never read.
|
10.3. Scanning the Image
A backwards scan of this image finds one data block, with sequence number 0x1000, and processes its two chunks from last to first:
-
The data chunk is seen first. No object header for object 257 has been seen yet, so the file size is estimated as
offset 0 + n_bytes 15 = 15bytes. -
The object header is seen next, providing the name, the metadata, and the authoritative file size.
The result is a root directory containing the single 15 byte file hello.txt.