Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/job_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use std::os::unix::process::CommandExt;

use libc;

use crate::tty;

static FG_PGID: Lazy<AtomicI32> = Lazy::new(|| AtomicI32::new(0));

pub fn init_signal_handlers() {
Expand All @@ -21,6 +23,9 @@ pub fn init_signal_handlers() {
libc::SIGCHLD,
libc::SIGINT,
libc::SIGTSTP,
libc::SIGTERM,
libc::SIGQUIT,
libc::SIGHUP,
]) {
Ok(s) => s,
Err(e) => {
Expand Down Expand Up @@ -53,6 +58,13 @@ pub fn init_signal_handlers() {
unsafe { libc::kill(-(pgid as libc::pid_t), libc::SIGTSTP) };
}
}
libc::SIGTERM | libc::SIGQUIT | libc::SIGHUP => {
// Best-effort: restore terminal state before exiting
crate::tty::restore_original_termios();
// Exit with a code indicating signal termination.
// Use 128 + signo as conventional shell exit code for signals.
std::process::exit(128 + signal);
}
_ => {}
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod parser;
mod job_control;
mod terminal;
mod wasm_host;
mod tty;

use crate::dotfiles::import_dotfiles;
use crate::wasm_host::{load_plugin_manifest, WasmPlugin};
Expand All @@ -16,6 +17,21 @@ fn main() {

// Initialize job control and signal handlers
job_control::init_signal_handlers();

// Save the current terminal attributes so we can restore them on panic/abnormal exit.
if let Err(e) = tty::save_original_termios() {
eprintln!("warning: failed to save terminal settings: {}", e);
}

// Install a panic hook that restores the terminal before printing panic info / exiting.
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
// best-effort restore
crate::tty::restore_original_termios();
// call the default hook so panic info still appears
default_hook(info);
}));

if let Ok(home) = std::env::var("HOME") {
match import_dotfiles(std::path::Path::new(&home)) {
Ok(imports) => {
Expand Down
35 changes: 35 additions & 0 deletions src/tty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use once_cell::sync::Lazy;
use std::sync::Mutex;
use std::io;

/// Stores the original terminal attributes so we can restore them on panic/signals.
///
/// This is intentionally best-effort: saving/restoring termios can fail (e.g. stdin closed),
/// but restoring when possible prevents leaving the user's terminal in an unusable state.
static ORIGINAL_TERMIOS: Lazy<Mutex<Option<libc::termios>>> = Lazy::new(|| Mutex::new(None));

/// Save the current terminal attributes once. Safe to call multiple times.
pub fn save_original_termios() -> io::Result<()> {
let mut guard = ORIGINAL_TERMIOS.lock().unwrap();
if guard.is_some() {
return Ok(());
}

let mut t: libc::termios = unsafe { std::mem::zeroed() };
let r = unsafe { libc::tcgetattr(libc::STDIN_FILENO, &mut t as *mut libc::termios) };
if r != 0 {
return Err(io::Error::last_os_error());
}

*guard = Some(t);
Ok(())
}

/// Attempt to restore the saved termios. Best-effort; ignore errors.
pub fn restore_original_termios() {
let guard = ORIGINAL_TERMIOS.lock().unwrap();
if let Some(orig) = guard.as_ref() {
// use TCSANOW to apply immediately; ignore return value (best-effort)
let _ = unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, orig as *const libc::termios) };
}
}
Loading