Fix second audit findings and restore crash detection dialog
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user