diff --git a/apps/figcraft/src/recovery.rs b/apps/figcraft/src/recovery.rs index 750786d1c..92884e528 100644 --- a/apps/figcraft/src/recovery.rs +++ b/apps/figcraft/src/recovery.rs @@ -217,7 +217,8 @@ impl Recovery { return false; } let _ = match online { - Some(text) => std::fs::write(¬e, text), + // The note holds the secret the client resumes with: owner-only, as the copy. + Some(text) => write_private(¬e, text.as_bytes()), None => std::fs::remove_file(¬e), }; true @@ -326,24 +327,29 @@ fn rejoin_online(file: &Path, s: &mut Session) -> Option { fn write_atomic(path: &Path, doc: &figcraft_engine::doc::Document, versions: &[Version], branches: &[Branch]) -> bool { let Ok(bytes) = figcraft_format::save_bytes_with_history(doc, None, versions, branches) else { return false }; let tmp = path.with_extension("tmp"); + if write_private(&tmp, &bytes).is_err() { + let _ = std::fs::remove_file(&tmp); + return false; + } + std::fs::rename(&tmp, path).is_ok() +} + +/// Write `bytes` to `path`, created (or cut back) readable by its owner only on Unix. +fn write_private(path: &Path, bytes: &[u8]) -> std::io::Result<()> { #[cfg(unix)] { - use std::os::unix::fs::OpenOptionsExt; + use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; let mut opts = std::fs::OpenOptions::new(); opts.write(true).create(true).truncate(true).mode(0o600); - let Ok(mut f) = opts.open(&tmp) else { return false }; - if std::io::Write::write_all(&mut f, &bytes).is_err() { - let _ = std::fs::remove_file(&tmp); - return false; - } + let mut f = opts.open(path)?; + // `mode` applies to a new file only: one that was there keeps its mode unless set. + f.set_permissions(std::fs::Permissions::from_mode(0o600))?; + std::io::Write::write_all(&mut f, bytes) } #[cfg(not(unix))] { - if std::fs::write(&tmp, bytes).is_err() { - return false; - } + std::fs::write(path, bytes) } - std::fs::rename(&tmp, path).is_ok() } /// [`recover_all`], for a start that keeps nothing waiting: how many documents came back as