Compare commits

..
7 changed files with 15 additions and 72 deletions
-37
View File
@@ -425,27 +425,6 @@ pub fn create_folder(dir: &Path) -> std::io::Result<()> {
builder.create(dir)
}
/// A new folder directly in `parent`, named `{prefix}-` and 32 random hex digits. It is made
/// exclusively (it is created by this call or the call fails), so a folder or link another account
/// put there first is never used, and the name cannot be guessed ahead. On Unix it is owner-only
/// (`0o700`); on other systems it has the mode `parent` gives it.
pub fn create_unique_folder(parent: &Path, prefix: &str) -> std::io::Result<PathBuf> {
use std::hash::{BuildHasher, Hasher};
for _ in 0..16 {
let random = |_| std::collections::hash_map::RandomState::new().build_hasher().finish();
let dir = parent.join(format!("{prefix}-{:016x}{:016x}", random(0), random(1)));
let mut builder = std::fs::DirBuilder::new();
#[cfg(unix)]
std::os::unix::fs::DirBuilderExt::mode(&mut builder, 0o700);
match builder.create(&dir) {
Ok(()) => return Ok(dir),
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {}
Err(err) => return Err(err),
}
}
Err(std::io::Error::new(std::io::ErrorKind::AlreadyExists, "no unused folder name"))
}
fn write_synced(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
let mut f = std::fs::File::create(path)?;
f.write_all(bytes)?;
@@ -468,22 +447,6 @@ fn io_error(path: &Path, err: std::io::Error) -> EngineError {
mod tests {
use super::*;
use crate::io::open_session;
#[test]
fn unique_folders_are_new_and_owner_only() {
let parent = std::env::temp_dir().join(format!("soundcraft-unique-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&parent);
std::fs::create_dir_all(&parent).unwrap();
let (a, b) = (create_unique_folder(&parent, "T").unwrap(), create_unique_folder(&parent, "T").unwrap());
assert_ne!(a, b);
assert!(a.is_dir() && a.parent() == Some(parent.as_path()));
assert_eq!(a.file_name().unwrap().len(), "T-".len() + 32);
#[cfg(unix)]
assert_eq!(std::os::unix::fs::PermissionsExt::mode(&std::fs::metadata(&a).unwrap().permissions()) & 0o777, 0o700);
assert!(create_unique_folder(&parent.join("missing"), "T").is_err(), "no parents are made");
let _ = std::fs::remove_dir_all(&parent);
}
use serde_json::json;
use soundcraft_audio_io::AudioBuffer;
use soundcraft_model::{ChannelFormat, Clip, TrackKind};
-1
View File
@@ -46,7 +46,6 @@ impl SoundApp {
services,
native_menu_bar: false,
autosave_dir: None,
fallback_take_dir: None,
autosaver: None,
autosaved_rev: 0,
leftovers: Vec::new(),
+4 -23
View File
@@ -155,21 +155,15 @@ 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);
// Without a settings folder the takes go under the temporary folder, in a folder of this
// run's own that nobody else could have made first.
let unsaved_dir = match &self.autosave_dir {
Some(d) => d.join(name),
None => self.fallback_take_dir()?,
};
let unsaved_dir = self.autosave_dir.as_ref().map_or_else(std::env::temp_dir, |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() {
return Err("no audio track is record-enabled".into());
}
// The takes are unsaved work: on Unix the folder they go into is owner-only, like the
// autosaves, before the recorder makes anything in it (other systems get the folder's
// inherited access: no ACLs are set, so the temporary folder's own per-user access applies).
if self.engine.path.is_none() {
// 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() {
for dir in plan.iter().filter_map(|(_, _, path)| path.parent()) {
soundcraft_engine::autosave::create_folder(dir).map_err(|e| format!("{}: {e}", dir.display()))?;
}
@@ -182,19 +176,6 @@ impl SoundApp {
Ok(takes)
}
/// The folder for the takes of a session never saved when there is no autosave folder, made on
/// first use: exclusively and under a name nobody can guess, so another account cannot have
/// made it first, with open permissions or as a link.
fn fallback_take_dir(&mut self) -> Result<std::path::PathBuf, String> {
if let Some(dir) = &self.fallback_take_dir {
return Ok(dir.clone());
}
let dir = soundcraft_engine::autosave::create_unique_folder(&std::env::temp_dir(), "SoundCraft")
.map_err(|e| format!("temporary take folder: {e}"))?;
self.fallback_take_dir = Some(dir.clone());
Ok(dir)
}
fn finish_recording(&mut self) {
self.engine.transport.recording = false;
if let Some(p) = &self.player {
-2
View File
@@ -278,8 +278,6 @@ pub struct SoundApp {
pub native_menu_bar: bool,
/// Folder for crash-recovery autosaves (native apps set it).
pub autosave_dir: Option<std::path::PathBuf>,
/// Where the takes of a session never saved go without an autosave folder: made once per run, under the temporary folder.
fallback_take_dir: Option<std::path::PathBuf>,
/// Writes the open session's autosaves in the background, once there is something to write.
autosaver: Option<soundcraft_engine::autosave::Autosaver>,
/// The document revision last handed to the autosaver (0 once that autosave is stale).
-7
View File
@@ -90,13 +90,6 @@ impl Worker {
}
fn open_movie(path: &str) -> Result<Movie, String> {
// The last check before the file system: the path came from a session file, so the read
// roots apply, and only a regular file is opened (a pipe would hold the video worker
// forever).
soundcraft_engine::file_access::check_read(path)?;
if !std::fs::metadata(path).map_err(|e| format!("{path}: {e}"))?.is_file() {
return Err(format!("{path}: not a regular file"));
}
#[cfg(not(target_arch = "wasm32"))]
{
let f = std::fs::File::open(path).map_err(|e| format!("{path}: {e}"))?;
+6
View File
@@ -47,6 +47,12 @@ done
echo "==> SoundCraft $VERSION Flatpak bundle for $ARCH from $(basename "$TARBALL")"
# The packaged app unpacks to tens of MB; an archive that lists far more would fill the build
# host's disk before anything here could check it.
MAX_UNPACKED_BYTES=$((2 * 1024 * 1024 * 1024))
UNPACKED="$(tar -tzvf "$TARBALL" | awk '{ s += $3 } END { print s + 0 }')"
[ "$UNPACKED" -le "$MAX_UNPACKED_BYTES" ] || { echo "error: $TARBALL unpacks to $UNPACKED bytes, more than $MAX_UNPACKED_BYTES" >&2; exit 1; }
WORK="$CARGO_TARGET_DIR/flatpak-bundle"
rm -rf "$WORK"
mkdir -p "$WORK/stage"
+5 -2
View File
@@ -19,10 +19,13 @@ fi
TMP="${RUNNER_TEMP:-${TMPDIR:-/tmp}}"
KEYCHAIN="$TMP/soundcraft-signing.keychain-db"
CERT="$TMP/soundcraft-signing.p12"
# The decoded certificate goes in a folder of this run's own (mktemp creates it, owner-only):
# a name another account could plant in the shared folder must not receive or redirect it.
CERT_DIR="$(mktemp -d "$TMP/soundcraft-signing.XXXXXX")"
CERT="$CERT_DIR/soundcraft-signing.p12"
KC_PASS="${KEYCHAIN_PASSWORD:-$(openssl rand -hex 24)}"
trap 'rm -f "$CERT"' EXIT
trap 'rm -rf "$CERT_DIR"' EXIT
printf '%s' "$APPLE_CERTIFICATE" | base64 --decode >"$CERT"
security create-keychain -p "$KC_PASS" "$KEYCHAIN"
security set-keychain-settings -lut 21600 "$KEYCHAIN"