Skip to main content

write_atomic

Function write_atomic 

Source
pub fn write_atomic(path: &Path, bytes: &[u8]) -> Result<()>
Expand description

Durably write bytes to path via create-tmp + fsync + rename + parent fsync.

The temporary file uses a UUIDv7-derived suffix to avoid collision with any concurrent writer targeting the same final path, and is opened with create_new so a stale suffix collision fails safely instead of clobbering. Callers that already hold the parent directory pass any Path; missing parents are created before the temp file is opened.

If any step between temp creation and rename fails, the temp file is unlinked via an RAII guard so we never leak zero-byte .tmp files into the spool. A repeatedly-failing writer would otherwise fill the inode table on ext4/xfs long before the disk is full — an operational silent death.

Semantics of Ok(()) vs Err(...) after rename: once the tmp file has been atomically renamed onto path, the caller can consider the data durably visible. A post-rename sync_directory failure means the dirent may not survive an immediate power loss on POSIX-conformant filesystems (xfs, btrfs, ext4 with data=ordered), but the file is present and readable for every observer running now. Historically this function still returned Err in that case (round-12 F5), which misled callers whose retry logic assumes “Err → not present”: they would either double-write (harmless but wasted IO) or, worse, treat the write as failed and skip session-state advancement while the file was in fact readable — producing a hard split between on-disk state and in-registry accounting.

Fix: post-rename sync_directory failure now becomes a tracing::warn! (best-effort) and Ok(()) is returned. Callers that need a stronger guarantee should call sync_directory again after their own operation completes. A dedicated counter is not registered here because fsutil cannot depend on av-core’s metrics registry without a cycle; harness-level callers can wrap this with their own counter if needed.