fix(figcraft): write the recovery copy's online note owner-only

The .online note beside a recovery copy holds the secret the client resumes
its identity with, but it was written with the default mode while the copy
itself was created 0600. Both now go through one owner-only writer, which
also narrows a note that already existed.
This commit is contained in:
Dylan M. Taylor committed 2026-11-03 04:26:03 +00:00
1 parent 02c38a6d95
commit 1ff0da1841
1 file changed
+17 -11
+17 -11
View File
@@ -217,7 +217,8 @@ impl Recovery {
return false;
}
let _ = match online {
Some(text) => std::fs::write(&note, text),
// The note holds the secret the client resumes with: owner-only, as the copy.
Some(text) => write_private(&note, text.as_bytes()),
None => std::fs::remove_file(&note),
};
true
@@ -326,24 +327,29 @@ fn rejoin_online(file: &Path, s: &mut Session) -> Option<u64> {
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