Fix second audit findings and restore crash detection dialog

This commit is contained in:
2026-02-27 22:48:43 +02:00
parent 804ba35a70
commit 011c07820d
21 changed files with 228 additions and 181 deletions

View File

@@ -58,18 +58,18 @@ impl LaunchMethod {
/// Result of a launch attempt.
#[derive(Debug)]
pub enum LaunchResult {
/// Successfully spawned the process and it's still running.
/// Process spawned and survived the startup crash-check window.
Started {
child: Child,
pid: u32,
method: LaunchMethod,
},
/// Process spawned but crashed immediately (within ~1 second).
/// Process spawned but exited during the startup crash-check window.
Crashed {
exit_code: Option<i32>,
stderr: String,
method: LaunchMethod,
},
/// Failed to launch.
/// Failed to launch (binary not found, permission denied, etc.).
Failed(String),
}
@@ -183,34 +183,21 @@ fn execute_appimage(
match cmd.spawn() {
Ok(mut child) => {
// Give the process a brief moment to fail on immediate errors
// (missing libs, exec format errors, Qt plugin failures, etc.)
std::thread::sleep(std::time::Duration::from_millis(150));
match child.try_wait() {
Ok(Some(status)) => {
// Already exited - immediate crash. Read stderr for details.
let stderr_text = child.stderr.take().map(|mut pipe| {
let mut buf = String::new();
use std::io::Read;
// Read with a size cap to avoid huge allocations
let mut limited = (&mut pipe).take(64 * 1024);
let _ = limited.read_to_string(&mut buf);
buf
}).unwrap_or_default();
let pid = child.id();
// Monitor for early crash (2s window). This blocks the current
// thread, so callers should run this inside gio::spawn_blocking.
match check_early_crash(&mut child, std::time::Duration::from_secs(2)) {
Some((exit_code, stderr)) => {
LaunchResult::Crashed {
exit_code: status.code(),
stderr: stderr_text,
exit_code,
stderr,
method: method.clone(),
}
}
_ => {
// Still running after 150ms - drop the stderr pipe so the
// child process won't block if it fills the pipe buffer.
drop(child.stderr.take());
None => {
LaunchResult::Started {
child,
pid,
method: method.clone(),
}
}
@@ -220,7 +207,44 @@ fn execute_appimage(
}
}
/// Parse a launch_args string from the database into a Vec of individual arguments.
/// Check if a recently-launched child process crashed during startup.
/// Waits up to `timeout` for the process to exit. If it exits within that window,
/// reads stderr and returns a Crashed result. If still running, drops the stderr
/// pipe (to prevent pipe buffer deadlock) and returns None.
///
/// Call this from a background thread after spawning the process.
pub fn check_early_crash(
child: &mut Child,
timeout: std::time::Duration,
) -> Option<(Option<i32>, String)> {
let start = std::time::Instant::now();
loop {
match child.try_wait() {
Ok(Some(status)) => {
// Process exited - read stderr for crash details
let stderr_text = child.stderr.take().map(|mut pipe| {
let mut buf = String::new();
use std::io::Read;
let mut limited = (&mut pipe).take(64 * 1024);
let _ = limited.read_to_string(&mut buf);
buf
}).unwrap_or_default();
return Some((status.code(), stderr_text));
}
Ok(None) => {
if start.elapsed() >= timeout {
// Still running - drop stderr pipe to avoid deadlock
drop(child.stderr.take());
return None;
}
std::thread::sleep(std::time::Duration::from_millis(50));
}
Err(_) => return None,
}
}
}
/// Parse launch arguments with basic quote support.
/// Splits on whitespace, respecting double-quoted strings.
/// Returns an empty Vec if the input is None or empty.