41. Files that survive crashes: fsync and atomic replacement
std::fs::write(path, data) truncates the file and then writes it. If the
machine crashes between the two steps, the file is empty or partly written.
Data you have written sits in the page cache until the kernel flushes it
(OS Ch 14).
The crash-safe sequence is the one from
OS Ch 77:
write a temporary file in the same directory, fsync it, rename it over
the target, then fsync the directory.
use std::fs::{self, File};
use std::io::{self, Write};
use std::path::Path;
/// Replace `path` with `data` atomically: readers see the old file or the new one, never a mix.
pub fn atomic_write(path: &Path, data: &[u8]) -> io::Result<()> {
let dir = path.parent().filter(|p| !p.as_os_str().is_empty()).unwrap_or(Path::new("."));
let file_name = path.file_name().ok_or_else(|| io::Error::other("path has no file name"))?;
// Same directory = same filesystem, so rename(2) is atomic.
let tmp = dir.join(format!(".{}.tmp.{}", file_name.to_string_lossy(), std::process::id()));
let result = (|| {
let mut f = File::create(&tmp)?;
f.write_all(data)?;
f.sync_all()?; // fsync: the DATA is on stable storage
drop(f);
fs::rename(&tmp, path)?; // atomic swap of the directory entry
File::open(dir)?.sync_all() // fsync the DIRECTORY: the rename itself is durable
})();
if result.is_err() {
let _ = fs::remove_file(&tmp); // do not leave temporary files behind on failure
}
result
}
fn main() -> io::Result<()> {
let path = std::env::temp_dir().join("rust-guide-state.json");
atomic_write(&path, br#"{"version": 1}"#)?;
atomic_write(&path, br#"{"version": 2}"#)?;
println!("{}", fs::read_to_string(&path)?);
fs::remove_file(path)
}
On macOS, fsync does not force the drive's own write cache to flush. Full
durability there needs fcntl(F_FULLFSYNC). Databases such as SQLite issue
it, and Rust's File::sync_all uses it on Apple platforms.
The Go version of this lab is in
OS Ch 77.
The Rust version adds one thing: the closure plus is_err cleanup makes
sure the temporary file is removed on every failure path. Ch 63's key-value
store uses this function for snapshots.