← Slim64FS

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:

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:

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:

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.

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:

  1. 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.

  2. Inode table restore: Load all inodes from the inode table region identified by the selected superblock. Inodes with mode=0 are free slots.

  3. Directory reconstruction: For each directory inode, read the dirent block at din.first_block. Build the in-memory child list.

  4. 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.

  5. 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.

  6. slim64fs-check validation (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