pub fn write_atomic(path: &Path, bytes: &[u8], mode: u32) -> Result<()>Expand description
Writes bytes to path atomically, at mode.
Two properties fs::write does not have. Atomic: the content lands via a
temporary file and a rename, so a crash part-way through leaves the previous
version intact rather than a truncated one. That matters for the local CA’s
revocation ledger, which is the authoritative input to CRL generation — a
half-written ledger read at the next startup is a set of revocations
silently forgotten. Owner-controlled: the mode is passed to open
rather than applied afterwards, the same reasoning as
write_private_key, so the file is never briefly world-readable.
The temporary file sits beside the target, since rename is only atomic
within a filesystem.
The scratch name belongs to one writer and one target. It used to be
path.with_extension("tmp"), shared two ways, and both hurt:
- Across targets, the local CA writes
ca.crlandca.jsonback to back and both mapped toca.tmp, so a startup rebuilding the CRL while a revocation persisted the ledger could rename one file’s bytes over the other’s name. The observed symptom was a sidecar full of CRL PEM, which the next startup refuses to parse — every revocation this CA had ever recorded, unreadable, because two writes shared a scratch name. Appending the suffix rather than substituting the extension separates those. - Across writers, two processes truncating and filling one temp file interleave, and then each renames the mixture into place: atomic, and atomically wrong. The pid separates those.
The cost is that a crash leaves litter rather than a file the next run reuses, which is the right way round — a leftover temporary is inert, and the alternative was a shared mutable one.