mirror of
https://github.com/storytold/soundcraft.git
synced 2026-11-12 08:53:41 -06:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d47f48e65 | ||
|
|
1f96843a24 |
@@ -454,6 +454,50 @@ pub fn create_folder(dir: &Path) -> std::io::Result<()> {
|
||||
builder.create(dir)
|
||||
}
|
||||
|
||||
/// A folder at a predictable place in a shared folder (the system's temporary folder), for work only
|
||||
/// this account may see. It is created exclusively, owner-only; one that already exists must be a
|
||||
/// real folder (not a link) owned by this account and open to no one else, since another account
|
||||
/// could have made it first and could read, or swap, what is written into it.
|
||||
pub fn create_private_folder(dir: &Path) -> std::io::Result<()> {
|
||||
if let Some(parent) = dir.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
let mut builder = std::fs::DirBuilder::new();
|
||||
#[cfg(unix)]
|
||||
std::os::unix::fs::DirBuilderExt::mode(&mut builder, 0o700);
|
||||
match builder.create(dir) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => check_own_private_folder(dir),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn check_own_private_folder(dir: &Path) -> std::io::Result<()> {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
let refuse = |why: &str| Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, why.to_string()));
|
||||
let meta = std::fs::symlink_metadata(dir)?;
|
||||
if !meta.is_dir() {
|
||||
return refuse("it exists and is not a folder");
|
||||
}
|
||||
if meta.mode() & 0o077 != 0 {
|
||||
return refuse("it exists and is open to other accounts");
|
||||
}
|
||||
// This account's id, from a file it creates (std has no direct call for it).
|
||||
let probe = dir.parent().unwrap_or(dir).join(format!(".soundcraft-owner-{}", std::process::id()));
|
||||
let own = std::fs::OpenOptions::new().write(true).create_new(true).open(&probe).and_then(|f| f.metadata());
|
||||
let _ = std::fs::remove_file(&probe);
|
||||
if own?.uid() != meta.uid() {
|
||||
return refuse("it exists and belongs to another account");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn check_own_private_folder(_dir: &Path) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_synced(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
|
||||
let mut f = std::fs::File::create(path)?;
|
||||
f.write_all(bytes)?;
|
||||
@@ -663,6 +707,24 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn a_private_folder_is_refused_when_it_already_exists_open() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let base = folder("private-folder");
|
||||
let dir = base.join("soundcraft-recordings-1");
|
||||
create_private_folder(&dir).unwrap();
|
||||
create_private_folder(&dir).unwrap();
|
||||
assert_eq!(std::fs::metadata(&dir).unwrap().permissions().mode() & 0o777, 0o700);
|
||||
std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
assert!(create_private_folder(&dir).is_err(), "open to others");
|
||||
std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o700)).unwrap();
|
||||
let link = base.join("soundcraft-recordings-2");
|
||||
std::os::unix::fs::symlink(&dir, &link).unwrap();
|
||||
assert!(create_private_folder(&link).is_err(), "a link is not our folder");
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_take_still_recording_at_a_crash_is_placed_on_recovery() {
|
||||
use crate::io::Take;
|
||||
|
||||
@@ -155,7 +155,13 @@ impl SoundApp {
|
||||
/// autosave folder. Returns the takes it records.
|
||||
fn arm_recorder(&mut self) -> Result<Vec<soundcraft_engine::io::Take>, String> {
|
||||
let name = soundcraft_engine::io::sanitize_name(&self.engine.session().name);
|
||||
let unsaved_dir = self.autosave_dir.as_ref().map_or_else(std::env::temp_dir, |d| d.join(name));
|
||||
// Without autosave storage the takes of a session never saved go to a folder of their own under
|
||||
// the shared temporary folder, made private below, not into the shared folder itself.
|
||||
let shared_temp = self.autosave_dir.is_none();
|
||||
let unsaved_dir = self
|
||||
.autosave_dir
|
||||
.as_ref()
|
||||
.map_or_else(|| std::env::temp_dir().join(format!("soundcraft-recordings-{}", std::process::id())), |d| d.join(name));
|
||||
let Some(r) = &mut self.recorder else { return Err("no audio input device".into()) };
|
||||
let plan = soundcraft_engine::io::plan_takes(&self.engine, &unsaved_dir, r.channels);
|
||||
if plan.is_empty() {
|
||||
@@ -163,9 +169,17 @@ impl SoundApp {
|
||||
}
|
||||
// The takes are unsaved work: the folder they go into under the autosave root is
|
||||
// owner-only, like the autosaves, before the recorder makes anything in it.
|
||||
if self.engine.path.is_none() && self.autosave_dir.is_some() {
|
||||
if self.engine.path.is_none() {
|
||||
if shared_temp {
|
||||
soundcraft_engine::autosave::create_private_folder(&unsaved_dir).map_err(|e| format!("{}: {e}", unsaved_dir.display()))?;
|
||||
}
|
||||
for dir in plan.iter().filter_map(|(_, _, path)| path.parent()) {
|
||||
soundcraft_engine::autosave::create_folder(dir).map_err(|e| format!("{}: {e}", dir.display()))?;
|
||||
let made = if shared_temp {
|
||||
soundcraft_engine::autosave::create_private_folder(dir)
|
||||
} else {
|
||||
soundcraft_engine::autosave::create_folder(dir)
|
||||
};
|
||||
made.map_err(|e| format!("{}: {e}", dir.display()))?;
|
||||
}
|
||||
}
|
||||
let takes = plan
|
||||
|
||||
Reference in New Issue
Block a user