#[cfg(unix)]
const FATAL_RESTORE_BYTES: &[u8] = concat!(
"\x1b[?2026l", "\x1b[<1u", "\x1b[?1007l", "\x1b[?1004l", "\x1b[?2004l", "\x1b[?1006l", "\x1b[?1002l", "\x1b[?1003l", "\x1b[?1000l", "\x1b[?1049l", "\x1b[?25h", )
.as_bytes();
#[cfg(unix)]
static MARKER_PATH: std::sync::OnceLock<Box<[u8; 4096]>> = std::sync::OnceLock::new();
#[cfg(unix)]
static MARKER_LEN: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
pub(crate) fn install_fatal_signal_guard() {
#[cfg(unix)]
{
if unsafe { libc::isatty(libc::STDOUT_FILENO) } == 0 {
tracing::debug!("Fatal-signal terminal guard skipped: stdout is not a TTY");
return;
}
if let Some(home) = crate::config::effective_home_dir() {
let dir = home.join(".codewhale").join("crashes");
if std::fs::create_dir_all(&dir).is_ok() {
let path = dir.join("last-fatal-signal.log");
if let Ok(bytes) = std::ffi::CString::new(path.as_os_str().as_encoded_bytes()) {
let len = bytes.as_bytes().len();
if len < 4096 {
let mut buf = [0u8; 4096];
buf[..len].copy_from_slice(bytes.as_bytes());
let _ = MARKER_PATH.set(Box::new(buf));
MARKER_LEN.store(len, std::sync::atomic::Ordering::Release);
}
}
}
}
for signal in [libc::SIGABRT, libc::SIGBUS, libc::SIGILL, libc::SIGFPE] {
unsafe { install_handler(signal) };
}
tracing::debug!("Fatal-signal terminal guard installed (ABRT/BUS/ILL/FPE)");
}
#[cfg(not(unix))]
{
}
}
#[cfg(unix)]
unsafe extern "C" fn fatal_signal_handler(signal: libc::c_int) {
unsafe {
let mut written: usize = 0;
while written < FATAL_RESTORE_BYTES.len() {
let n = libc::write(
libc::STDOUT_FILENO,
FATAL_RESTORE_BYTES.as_ptr().add(written) as *const libc::c_void,
FATAL_RESTORE_BYTES.len() - written,
);
if n <= 0 {
break;
}
written += n as usize;
}
if written == 0 {
let _ = libc::write(
libc::STDERR_FILENO,
FATAL_RESTORE_BYTES.as_ptr() as *const libc::c_void,
FATAL_RESTORE_BYTES.len(),
);
}
let len = MARKER_LEN.load(std::sync::atomic::Ordering::Acquire);
if len > 0
&& let Some(path) = MARKER_PATH.get()
{
let fd = libc::open(
path.as_ptr() as *const libc::c_char,
libc::O_WRONLY | libc::O_APPEND | libc::O_CREAT,
0o600,
);
if fd >= 0 {
let mut line = [0u8; 16];
line[0] = b's';
line[1] = b'i';
line[2] = b'g';
line[3] = b'n';
line[4] = b'a';
line[5] = b'l';
line[6] = b'=';
let mut value = if signal < 0 { 0 } else { signal } as u32;
let mut digits = [0u8; 10];
let mut count = 0;
loop {
digits[count] = b'0' + (value % 10) as u8;
value /= 10;
count += 1;
if value == 0 || count == digits.len() {
break;
}
}
for index in 0..count {
line[7 + index] = digits[count - 1 - index];
}
let total = 7 + count;
line[total] = b'\n';
let _ = libc::write(fd, line.as_ptr() as *const libc::c_void, total + 1);
let _ = libc::close(fd);
}
}
libc::signal(signal, libc::SIG_DFL);
libc::raise(signal);
}
}
#[cfg(unix)]
unsafe fn install_handler(signal: libc::c_int) {
unsafe {
let mut action: libc::sigaction = std::mem::zeroed();
action.sa_sigaction = fatal_signal_handler as *const () as libc::sighandler_t;
action.sa_flags = libc::SA_RESTART;
if libc::sigaction(signal, &action, std::ptr::null_mut()) != 0 {
tracing::warn!(signal, "fatal-signal guard install failed");
}
}
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
#[test]
fn restore_bytes_cover_every_poisoning_mode() {
let bytes = String::from_utf8_lossy(FATAL_RESTORE_BYTES).to_string();
for mode in [
"?1006l", "?1002l", "?1003l", "?1000l", "<1u", "?2004l", "?1004l", "?1007l", "?1049l",
"?2026l", "?25h",
] {
assert!(
bytes.contains(mode),
"fatal restore must reset {mode}; got: {bytes:?}"
);
}
}
#[test]
fn restore_bytes_are_one_write_friendly() {
assert!(!FATAL_RESTORE_BYTES.contains(&0));
assert!(FATAL_RESTORE_BYTES.len() < 128);
assert!(FATAL_RESTORE_BYTES.starts_with(b"\x1b[?2026l"));
}
}