1use std::fs::{self, File}; 2use std::io::Write; 3use std::path::{Path, PathBuf}; 4use std::time::{SystemTime, UNIX_EPOCH}; 5 6use ::log::{debug, error, warn}; 7 8/// Replace `path` with `bytes` so that a power cut leaves either the old file or the new one, 9/// never an empty or half-written one: the bytes reach the disk before the rename does. 10pub(crate) fn write_atomic(path: &Path, bytes: &[u8]) -> std::io::Result<()> { 11 let tmp = path.with_extension("tmp"); 12 debug!("atomic write of {} bytes to {}", bytes.len(), path.display()); 13 let result = (|| { 14 let mut f = File::create(&tmp)?; 15 f.write_all(bytes)?; 16 f.sync_all()?; 17 drop(f); 18 fs::rename(&tmp, path) 19 })(); 20 if let Err(e) = &result { 21 error!("atomic write of {} bytes to {} failed: {e}", bytes.len(), path.display()); 22 } 23 result 24} 25 26/// Move a file that cannot be read out of the way, keeping it for a human to look at, so the 27/// app can start clean rather than refuse to start. Returns where it went. 28pub fn set_aside(path: &Path) -> std::io::Result<PathBuf> { 29 let ms = SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_millis()).unwrap_or(0); 30 let name = path.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default(); 31 let aside = path.with_file_name(format!("{name}.damaged-{ms}")); 32 warn!("setting aside unreadable {} as {}", path.display(), aside.display()); 33 fs::rename(path, &aside).inspect_err(|e| error!("could not set {} aside: {e}", path.display()))?; 34 Ok(aside) 35}