feat(crash): port Ghostty's local crash reporting
Ports ZIG/src/crash/* as crates/ghostty-crash. Like Ghostty (which uses sentry-native only to write local files), crashes are written as Sentry envelopes (<uuid>.ghosttycrash) to $XDG_STATE_HOME/ghostty/crash: - envelope parse/serialize (all Zig tests ported) - panic hook plus SIGSEGV/SIGBUS/SIGILL/SIGFPE/SIGABRT handlers on an alternate stack; the signal path is allocation-free and re-raises with the default action so exit status and core dumps are unchanged - thread-local crash context (main/renderer/io/read thread, surface dimensions) set from the corresponding threads - `sentry` cargo feature on the root binary, off by default like upstream's -Dsentry; +crash-report lists exactly what is written Deviation: attachments always carry "type":"attachment" (Zig's in-memory attachments omit it and become unparseable). Test Plan: - cargo test -p ghostty-crash (19) - cargo test --test crash (child panics, SIGSEGV, stack overflow and abort each produce a valid envelope listed by +crash-report)
This commit is contained in:
1 parent
a07b224869
commit
a714fd9a65
11 files changed
+1933
-36
No files matched your search
@@ -11,52 +11,23 @@ use std::{
|
||||
};
|
||||
|
||||
use ghostty_config::types_b::Duration;
|
||||
pub use ghostty_crash::Report;
|
||||
|
||||
use crate::error::Error;
|
||||
pub use crate::options::NoOptions as Options;
|
||||
|
||||
/// A crash report file (Zig: `crash.Report`).
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Report {
|
||||
pub name: String,
|
||||
/// Modification time in nanoseconds since the unix epoch.
|
||||
pub mtime: i128,
|
||||
}
|
||||
|
||||
/// The default crash directory (Zig: `crash.defaultDir`):
|
||||
/// `$XDG_STATE_HOME/ghostty/crash`.
|
||||
pub fn default_dir() -> Result<PathBuf, Error> {
|
||||
let dir = ghostty_os::xdg::state(&ghostty_os::xdg::Options {
|
||||
subdir: Some("ghostty/crash"),
|
||||
home: None,
|
||||
})
|
||||
.map_err(|e| Error::Other(e.to_string()))?;
|
||||
Ok(PathBuf::from(dir))
|
||||
let dir = ghostty_crash::default_dir().map_err(|e| Error::Other(e.to_string()))?;
|
||||
Ok(dir.path)
|
||||
}
|
||||
|
||||
/// List the crash reports (regular files) in `dir`. A directory that cannot
|
||||
/// be opened yields no reports.
|
||||
/// List the crash reports (regular files) in `dir`, i.e. the
|
||||
/// `.ghosttycrash` envelopes written by `ghostty-crash`. A directory that
|
||||
/// cannot be opened yields no reports.
|
||||
pub fn list_reports(dir: &std::path::Path) -> Result<Vec<Report>, Error> {
|
||||
let Ok(rd) = std::fs::read_dir(dir) else {
|
||||
return Ok(vec![]);
|
||||
};
|
||||
let mut reports = vec![];
|
||||
for entry in rd {
|
||||
let entry = entry?;
|
||||
if !entry.file_type()?.is_file() {
|
||||
continue;
|
||||
}
|
||||
let mtime = entry.metadata()?.modified()?;
|
||||
let mtime = match mtime.duration_since(UNIX_EPOCH) {
|
||||
Ok(d) => i128::try_from(d.as_nanos()).unwrap_or(i128::MAX),
|
||||
Err(e) => -i128::try_from(e.duration().as_nanos()).unwrap_or(i128::MAX),
|
||||
};
|
||||
reports.push(Report {
|
||||
name: entry.file_name().to_string_lossy().into_owned(),
|
||||
mtime,
|
||||
});
|
||||
}
|
||||
Ok(reports)
|
||||
Ok(ghostty_crash::dir::list_reports(dir)?)
|
||||
}
|
||||
|
||||
/// The `crash-report` command is used to inspect and send crash reports.
|
||||
|
||||
Reference in new issue
Block a user