tags: [slim64fs, v8, durability, spec, crash-recovery] aliases: [Durability Contract, Option A Contract]
Slim64FS Durability Contract
Version: V8.6
Status: AUTHORITATIVE — final, all V8 gates complete
Last updated: 2026-05-20
This document defines what Slim64FS guarantees, what it explicitly does not guarantee, and the exact crash windows where state may be partially persisted.
The crash matrix (docs/crash-matrix.html) is the attempt to falsify these claims. If a matrix cell contradicts a claim here, the claim loses.
1. The Model: Option A
Slim64FS follows an explicit Option A durability model.
write()returning success means the data reached the filesystem layer.
It does NOT mean the data is durable against power loss.
fsync()is the explicit durability boundary.
This is not a bug. It is a documented design decision. Applications
that require durability must call fsync(). Applications
that do not call fsync() accept that data written since the
last fsync() may be lost on power loss.
This mirrors the behavior of many production filesystems under default mount options. The difference is that Slim64FS states it explicitly rather than implying guarantees it cannot prove.
1.1 Archaeology vs Visibility
Crash archaeology may show dirty primary metadata bytes (for example bitmap or inode table drift relative to the authoritative shadow/commit pair) before recovery replay/promotion completes. Under Option A, this is acceptable if:
- authoritative shadow/commit state is valid,
- remount/recovery succeeds deterministically, and
- user-visible namespace/data truth after remount matches the durability model.
Durability truth is defined by post-recovery visibility semantics, not by literal absence of dirty bytes in pre-remount raw image state.
2. What IS Guaranteed
2.1 After fsync(fd)
The following operations are durable after the fsync()
that covers them returns successfully:
write()— file data and size are persistentmkdir()— directory exists and is traversablerename()— new name is visible, old name is goneunlink()— file is removed from directoryrmdir()— directory is removedtruncate()— file size reflects the truncation
Mechanism: fsync() triggers
flush_all_dirty_dirs() followed by
s64_fs_checkpoint(). The checkpoint writes a new superblock
slot with a monotonically increasing generation number. On remount, the
slot with the highest valid generation is selected. All operations
committed before the checkpoint are visible after remount.
2.2 After Clean Unmount
All fsynced operations are durable after a clean unmount
(fusermount3 -u).
Mechanism: s64_destroy() calls
flush_all_dirty_dirs() followed by
s64_fs_checkpoint(). This is equivalent to an implicit
fsync() on the entire filesystem at unmount time.
Note: clean unmount is not power-loss-safe. If power is lost during unmount, the checkpoint may not complete. This is acceptable — clean unmount is not a crash scenario.
2.3 Per-Block CRC Integrity
Every data block carries a CRC32c checksum. On read, the checksum is verified. Corruption is detected and reported as EIO. Corrupted data is never silently served to the application.
Scope: data blocks only. Inode table blocks and bitmap blocks do not carry per-block CRCs in V8. This is a known limitation documented in Section 4.
2.4 Metadata Consistency After Crash
After a crash (power loss, SIGKILL), the filesystem will remount to a consistent state. "Consistent" means:
- No orphaned inodes that are referenced by directory entries
- No directory entries that reference nonexistent inodes
- Bitmap accurately reflects allocated blocks
- Dirlog, if present, can be replayed to restore in-flight namespace mutations
Metadata consistency does NOT imply that all recently committed operations survived. It means the on-disk state is internally coherent.
2.5 Superblock Dual-Slot Atomicity
The superblock uses a dual-slot design with generation numbers. A checkpoint writes the new slot before invalidating the old slot. On remount, if the new slot's CRC is invalid, the old slot is used. This prevents a partial checkpoint write from corrupting the filesystem.
3. What is NOT Guaranteed
3.1 Durability Without
fsync()
Operations performed without a subsequent fsync() may be
lost on power loss. This includes all mutations — writes, creates,
renames, unlinks — that have not been covered by a successful
fsync() call.
This is intentional. See Section 2.
3.2 mmap Durability
mmap() write durability semantics are currently
undefined. Slim64FS does not implement msync().
Applications using mmap() for writes should not assume
durability without an explicit fsync() on the underlying
file descriptor.
3.3 Atomic Multi-File Operations
There is no cross-file atomicity. An operation that modifies two files (e.g., writing to a journal file then updating a data file) does not have atomic crash semantics. If power is lost between the two writes, the filesystem will reflect a partial state.
3.4 Sub-Block Write Atomicity
A write that spans multiple 4KiB blocks is not atomic. If power is lost mid-write, some blocks may be committed and others not. The data in committed blocks is intact; the data in uncommitted blocks reflects the pre-write state. The file size may or may not reflect the partial write — see Section 5.
3.5 Inode Table and Bitmap CRC Protection
The inode table blocks and allocation bitmap do not carry per-block
CRCs in V8. Silent corruption of these structures is not detected at
read time. slim64fs-check can detect structural
inconsistencies but not silent bit flips within otherwise valid-looking
inode or bitmap records.
3.6 Sparse File Semantics
Slim64FS does not support sparse files. SEEK_HOLE /
SEEK_DATA are not implemented. Truncate-to-larger fills the
new region with zeros — this is correct POSIX behavior but requires
block allocation for the entire extended region.
3.7 Hardlinks
Hardlinks (link(2)) return EOPNOTSUPP. Each
filename maps to exactly one inode. This is a design constraint, not a
crash-safety concern.
4. Crash Windows
The following windows exist where a crash can leave state in a partially persisted condition. Each window is named. The crash matrix tests each one.
W1: Pre-fsync Write Window
When: After write() returns but before
fsync() is called.
What is on disk: Previous file state.
What is in memory: New file data and updated inode
size.
On crash: Memory is lost. Disk reflects previous state.
File appears unchanged after remount. No corruption, no orphans.
Expected behavior: Clean rollback to pre-write
state.
W2: Mid-Checkpoint Window
When: During s64_fs_checkpoint() —
after the new superblock slot is being written but before it
completes.
What is on disk: Previous checkpoint slot (valid) plus
partial new slot.
On crash: The partial new slot will fail CRC validation
on remount. The previous slot is selected. All operations since the
previous checkpoint are lost.
Expected behavior: Rollback to previous checkpoint. No
corruption.
W3: Dirty Directory Pre-Flush Window
When: After a namespace mutation (mkdir, unlink,
rename) but before flush_all_dirty_dirs() runs.
What is on disk: Previous directory state (old dirent
block).
What is in the dirlog: The mutation is recorded (dirlog
is fsynced per-append).
On crash: Dirlog replay on remount reconstructs the
mutation. The directory reflects the correct post-mutation state.
Expected behavior: Mutation survives via dirlog replay.
This is the primary crash recovery path.
W4: Post-Flush Pre-Checkpoint Window
When: After flush_all_dirty_dirs()
succeeds but before s64_fs_checkpoint() completes.
What is on disk: New dirent blocks are written.
Superblock still points to previous checkpoint generation.
On crash: On remount, the previous checkpoint is
selected. The new dirent blocks are on disk but the inode pointers to
them may not be in the checkpoint. Dirlog replay compensates.
Expected behavior: Consistent state recovered via
checkpoint + dirlog.
W5: Dirlog Truncation Window
When: After a successful checkpoint,
s64_vfs_truncate_all_dirlogs() frees dirlog blocks for all
live directories.
What is at risk: If power is lost during dirlog
truncation, some dirlog blocks may be freed in the bitmap but still
referenced by the directory inode.
On crash: The dirlog head pointer in the inode points
to a freed block. Dirlog replay on remount reads a block that may have
been reallocated.
Severity: HIGH — this window can produce corrupted
dirlog replay.
Mitigation in V8: Zero the dirlog head pointer in the
inode BEFORE freeing the block, not after. If power is lost after
zeroing but before freeing, the block is leaked (recoverable by fsck)
but not corrupted.
Status: Fixed in V8.1.
s64_dirlog_free_chain() (src/s64_dirlog.c)
zeros the dirlog head in the inode before freeing the block. Validated:
crash matrix 310/0 (V8.2), fsstress 100/100 (V8.4) — no W5-class
corruption observed in any gate.
W6: Rename Cross-Directory Window
When: During cross-directory rename — after the
destination entry is written but before the source entry is
removed.
What is on disk: File appears in both source and
destination.
On crash: Both entries exist in their respective
directories. The inode link count is 1. One of the two directory entries
references a valid inode; the other is an orphan.
Expected behavior: slim64fs-check detects
the duplicate reference. Salvage removes the orphan entry.
Status: Tested in V8.2 crash matrix (rename cross-dir /
mid-write cell).
W7: txn_block PENDING on Mount
When: After
s64_txn_write_state(PENDING) at the start of a file-append
operation, but before s64_txn_write_state(COMMITTED) or the
next s64_fs_checkpoint().
What is on disk: txn_block carries a valid PENDING
header. The primary bitmap may contain newly allocated extent blocks;
the primary inode may or may not reflect the new size.
What is on disk (shadow): Shadow bitmap and inode table
match the last committed checkpoint CRCs.
On crash: s64_recover_from_commit()
detects primary/shadow CRC mismatch and restores shadow → primary,
reverting the partial extend. s64_open_txn_classify()
clears the PENDING txn_block. Mount succeeds.
Expected behavior: Extent rolled back; inode reverts to
pre-append size; txn_block zeroed; slim64fs-check exits
0.
Status: Activated V10.1. Tested by
s64_w7_txn_pending_test and
baby_xfstests 095.
5. Crash Recovery Path
On mount after a crash, Slim64FS performs the following recovery sequence:
Superblock slot selection: Read both superblock slots. Select the slot with the highest generation number whose CRC is valid. If neither slot is valid, mount fails.
Inode table restore: Load all inodes from the inode table region identified by the selected superblock. Inodes with mode=0 are free slots.
Directory reconstruction: For each directory inode, read the dirent block at
din.first_block. Build the in-memory child list.Dirlog replay: For each directory inode with a non-zero dirlog head, walk the dirlog chain and replay all records in sequence order. CREATE records add entries; DELETE records remove entries; RENAME_FROM/RENAME_TO pairs update names.
Bitmap reconciliation: The bitmap is loaded as-is from disk. It is not recomputed from inode table during normal recovery — the bitmap is kept consistent as a first-class structure.
slim64fs-checkvalidation (optional but recommended after crash): Verify structural consistency before accepting any new writes.
6. What
slim64fs-check Catches
| Condition | Detected |
|---|---|
| Orphaned inodes (allocated but unreachable) | Yes |
| Directory entries referencing freed inodes | Yes |
| CRC mismatch on data blocks | Yes |
| Bitmap inconsistency (block marked free but referenced) | Yes |
| Dirlog chain corruption (bad magic or CRC) | Yes |
| Duplicate inode references across directories | Yes |
7. What
slim64fs-check Does NOT Catch
| Condition | Not Detected |
|---|---|
| Silent bit flips in inode table blocks | No — no per-block CRC on inodes |
| Silent bit flips in bitmap blocks | No — no per-block CRC on bitmap |
| Stale data within a valid block (CRC matches pre-write content) | No |
| Data loss within Option A window (no fsync before crash) | No — by design |
| W5 dirlog truncation corruption (see Section 4) | Partial |
8. Jay and Chris
Jay (wildlife photographer, large RAW bursts on SD cards):
After fsync() — or after the camera's write-cache flush
— photo #7421 is durable. If the battery dies before the write-cache
flushes, the photo may be lost. Slim64FS does not claim to protect
against that. The camera firmware's flush behavior determines
durability, not the filesystem alone.
Chris (8K videographer, sustained writes, drop-safe footage):
Footage written in a continuous stream without per-frame
fsync() is NOT guaranteed durable on power loss. The last
checkpoint before the crash determines what survives. For critical
footage, the recording application must call fsync() at
meaningful boundaries (scene cuts, buffer fills). Slim64FS will honor
those boundaries correctly.
9. Known Limitations (V8 Scope)
| Limitation | Severity | V8 Action |
|---|---|---|
| W5 dirlog truncation window (Section 4) | HIGH | ✅ Fixed in V8.1 — zero-before-free in
s64_dirlog_free_chain() |
| No per-block CRC on inode table | MEDIUM | Documented; V9 candidate |
| No per-block CRC on bitmap | MEDIUM | Documented; V9 candidate |
| mmap durability undefined | LOW | NOTRUN guard in xfstests |
| No hardlinks | LOW | EOPNOTSUPP; documented |
| No sparse file support | LOW | NOTRUN guard in xfstests |
10. Version History
| Version | Date | Change |
|---|---|---|
| V8.0 | 2026-05-14 | Initial contract — written before crash tests |
| V8.2 | 2026-05-16 | Updated after crash matrix: 310/0, W5 fix confirmed, warning taxonomy finalized |
| V8.5 | 2026-05-16 | Recovery audit: W5 closed, generic/035 deferred, orphan policy documented |
| V8.6 | 2026-05-20 | Final — all gates complete (crash matrix 310/0, fsx 25-seed, fsstress 100/100) |
This document is updated after each V8 milestone. The crash matrix (docs/crash-matrix.html) is the evidentiary record. Where they conflict, the matrix wins.
See Also
- [[crash_matrix_results|Crash Matrix]] — the matrix that falsifies these claims
- [[recovery_model|Recovery Model]] — how the contract is implemented on remount
- [[known_limitations|Known Limitations]] — what the contract explicitly does not cover
- [[corruption_taxonomy|Corruption Taxonomy]] — classification of crash outcomes
- [[invariants|Five Invariants]] — the invariants this contract is grounded in