# File Sync Semantics
This document records the current mounted-file sync contract. It is intentionally
written as an implementation guide for future convergence with littlefs C rather
than as a marketing-level API summary.
## Current Contract
- `FileHandle::flush` writes dirty handle contents into a remountable filesystem
state. After a successful `flush`, closing the handle is a no-op for that
version of the file unless later writes make the handle dirty again.
- `FileHandle::sync` first performs `flush`, then enters the mounted
block-device `sync` boundary once more. This gives callers an explicit fsync
style operation even when the underlying metadata commit path has already
flushed the internal program cache.
- For write-only partial overwrites of existing CTZ files, `flush` streams a
replacement CTZ chain by reading old data in block-sized chunks and overlaying
pending patches before the metadata commit exposes the new head.
- `FilesystemMut::sync` flushes the mounted block cache and calls
`BlockDevice::sync`. It does not have access to dirty file handles because
Rust's mutable borrow rules keep an open `FileHandle` as the exclusive owner
of its mounted filesystem borrow. Dirty file contents therefore need
`FileHandle::flush`, `FileHandle::sync`, or `FileHandle::close`.
- `FileBlockDevice::sync` forwards to host `File::sync_data`. The in-memory
block device treats sync as a successful boundary with no extra work.
- Successful mounted metadata operations currently return after their program
cache has crossed the block-device sync boundary. This is stricter than a
pure writeback cache and keeps the contract easy to reason about until the cache
grows a more C-like lazy policy.
## Failure Boundary
The important retry cases are covered by black-box tests, not mocks:
- If the sync inside a file flush fails after programming reached the backend,
the error is returned and the handle remains retryable. A second flush must
converge to the intended C-readable file contents. This is checked for both
newly-written buffered content and existing CTZ write-only partial overwrite.
- Existing CTZ streaming append must also keep its stream state after a metadata
sync failure. The black-box test retries the handle sync, then performs a
rename, an empty-directory removal, and a filesystem sync before asking C to
read the final file.
- If a file's contents have already been flushed and the explicit sync boundary
fails, retrying `FileHandle::sync` must not rewrite the metadata commit. The
retry should only re-enter the block-device sync boundary.
- `FilesystemMut::sync` is deliberately not a hidden dirty-handle flush. A
dropped unclosed file handle is not committed by a later filesystem sync; the
filesystem remains usable, and C sees only operations that were explicitly
closed/flushed/synced.
- When a sync failure leaves programmed bytes in the backend, the image still
has to be mountable. Tests verify the resulting image through both Rust and
upstream C littlefs.
## Open Convergence Work
- Expand mixed sync schedules inside split, relocation, pending move, and
orphan repair transactions. The public file-handle contract is now covered
for append, write-only partial overwrite, explicit retry, and dropped dirty
handles, but the lower-level metadata state machines still need broader
combinations.
- Decide whether future metadata operations should expose a lazier cache policy
that more closely matches littlefs' internal program cache. If that changes,
the public contract above must be updated before code changes land.
- Keep expanding block-device fault injection around sync failures that occur
during split, relocation, pending move, and orphan repair transactions. The
seeded mixed matrix now covers one combined path, but more seeds and
operation shrinking would make failures easier to diagnose.