Data Recovery: the recovery folders are owner-only on Unix

Recovery copies are unsaved documents. FolderStore made its folders with
create_dir_all, so on Unix they got the default mode and other accounts on
the machine could read the copies. Folders the store creates are now 0o700.
A store folder that already exists (it can be one picked in Preferences)
keeps its mode; the area made inside it is owner-only.

The same change as storytold/pdfcraft#3320.
This commit is contained in:
oracola91
2026-10-21 23:14:27 +07:00
committed by bflatastic
parent 04bf2dcab9
commit d8e876b721
2 changed files with 41 additions and 2 deletions
+14 -2
View File
@@ -252,6 +252,18 @@ impl FolderStore {
}
}
/// Create area folder `dir`, and the store's folder when it is missing. On Unix every folder
/// made here is owner-only (`0o700`): the copies are unsaved documents, and with the default
/// mode other accounts on the machine could read them. A store folder that already exists
/// keeps its mode, since it can be one the user picked in Preferences.
fn create_area(dir: &std::path::Path) -> std::result::Result<(), String> {
let mut builder = std::fs::DirBuilder::new();
builder.recursive(true);
#[cfg(unix)]
std::os::unix::fs::DirBuilderExt::mode(&mut builder, 0o700);
builder.create(dir).map_err(|e| format!("{}: {e}", dir.display()))
}
/// The plain names of the files (or, `dirs`, folders) in `dir`; none when it doesn't exist.
fn names(dir: &std::path::Path, dirs: bool) -> std::result::Result<Vec<String>, String> {
let entries = match std::fs::read_dir(dir) {
@@ -285,7 +297,7 @@ impl RecoveryStore for FolderStore {
fn write(&self, name: &str, bytes: &[u8]) -> std::result::Result<(), String> {
let f = self.file(name)?;
if let Some(dir) = f.parent() {
std::fs::create_dir_all(dir).map_err(|e| format!("{}: {e}", dir.display()))?;
Self::create_area(dir)?;
}
vectorcraft_format::write_atomic(&f, bytes).map_err(|e| format!("{}: {e}", f.display()))
}
@@ -304,7 +316,7 @@ impl RecoveryStore for FolderStore {
}
fn lock(&self, area: &str) -> std::result::Result<Lock, String> {
let dir = self.area(area)?;
std::fs::create_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
Self::create_area(&dir)?;
let path = dir.join(LOCK_FILE);
let file = std::fs::OpenOptions::new()
.read(true)
+27
View File
@@ -363,6 +363,33 @@ fn folder_areas_are_locked_by_running_apps() {
let _ = std::fs::remove_dir_all(&dir);
}
/// The copies are unsaved documents: on Unix the folders the store makes are owner-only, and a
/// store folder that already exists (one picked in Preferences) keeps its mode.
#[cfg(unix)]
#[test]
fn the_folder_store_makes_owner_only_folders() {
use std::os::unix::fs::PermissionsExt as _;
let mode = |p: &std::path::Path| std::fs::metadata(p).unwrap().permissions().mode() & 0o777;
let base = std::env::temp_dir().join(format!("vc-recovery-modes-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
// A store folder that doesn't exist yet is made with the first copy, owner-only like its area.
let new = base.join("new");
let store = FolderStore::new(&new);
store.write("1-1/Untitled-1-1.vectorcraft", b"copy").unwrap();
assert_eq!(mode(&new), 0o700);
assert_eq!(mode(&new.join("1-1")), 0o700);
assert!(matches!(store.lock("1-2"), Ok(cmd::recovery::Lock::Held(_))));
assert_eq!(mode(&new.join("1-2")), 0o700);
// A folder that already exists keeps its mode; the area made inside it is owner-only.
let picked = base.join("picked");
std::fs::create_dir_all(&picked).unwrap();
std::fs::set_permissions(&picked, std::fs::Permissions::from_mode(0o755)).unwrap();
FolderStore::new(&picked).write("2-1/Untitled-1-1.vectorcraft", b"copy").unwrap();
assert_eq!(mode(&picked), 0o755);
assert_eq!(mode(&picked.join("2-1")), 0o700);
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn saving_reverting_or_closing_removes_the_copy() {
let dir = std::env::temp_dir().join(format!("vc-recovery-save-{}", std::process::id()));