diff --git a/src/bin/fish.rs b/src/bin/fish.rs index 06cee4155..ca80b5948 100644 --- a/src/bin/fish.rs +++ b/src/bin/fish.rs @@ -27,9 +27,8 @@ use fish::{ BUILTIN_ERR_MISSING, BUILTIN_ERR_UNKNOWN, STATUS_CMD_OK, STATUS_CMD_UNKNOWN, }, common::{ - escape, get_executable_path, restore_term_foreground_process_group_for_exit, - save_term_foreground_process_group, scoped_push_replacer, str2wcstring, wcs2string, - ScopeGuard, PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME, + escape, get_executable_path, save_term_foreground_process_group, scoped_push_replacer, + str2wcstring, wcs2string, PACKAGE_NAME, PROFILING_ACTIVE, PROGRAM_NAME, }, env::{ environment::{env_init, EnvStack, Environment}, @@ -573,9 +572,7 @@ fn throwing_main() -> i32 { features::set_from_string(opts.features.as_utfstr()); proc_init(); fish::env::misc_init(); - let _restore_term_foreground_process_group = - ScopeGuard::new((), |()| restore_term_foreground_process_group_for_exit()); - let _restore_term = reader_init(); + reader_init(true); // Construct the root parser! let env = Rc::new(EnvStack::globals().create_child(true /* dispatches_var_changes */)); diff --git a/src/bin/fish_key_reader.rs b/src/bin/fish_key_reader.rs index 65cca790b..b401621b1 100644 --- a/src/bin/fish_key_reader.rs +++ b/src/bin/fish_key_reader.rs @@ -132,7 +132,7 @@ fn setup_and_process_keys(continuous_mode: bool, verbose: bool) -> i32 { topic_monitor_init(); threads::init(); env_init(None, true, false); - let _restore_term = reader_init(); + reader_init(false); signal_set_handlers(true); // We need to set the shell-modes for ICRNL, diff --git a/src/panic.rs b/src/panic.rs index baa41b63c..cd0bdf3a5 100644 --- a/src/panic.rs +++ b/src/panic.rs @@ -1,34 +1,61 @@ -use std::panic::{set_hook, take_hook, UnwindSafe}; +use std::{ + panic::{set_hook, take_hook, UnwindSafe}, + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; use libc::STDIN_FILENO; +use once_cell::sync::OnceCell; use crate::{ common::{read_blocked, PROGRAM_NAME}, nix::isatty, - threads::asan_maybe_exit, + threads::{asan_maybe_exit, is_main_thread}, }; +pub static AT_EXIT: OnceCell> = OnceCell::new(); + pub fn panic_handler(main: impl FnOnce() -> i32 + UnwindSafe) -> ! { if isatty(STDIN_FILENO) { let standard_hook = take_hook(); set_hook(Box::new(move |panic_info| { standard_hook(panic_info); + static PANICKING: AtomicBool = AtomicBool::new(false); + if PANICKING + .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) + .is_err() + { + return; + } + if let Some(at_exit) = AT_EXIT.get() { + (at_exit)(); + } eprintf!( - "%s crashed, please report a bug. Debug PID %d or press Enter to exit\n", + "%s crashed, please report a bug.", PROGRAM_NAME.get().unwrap(), - unsafe { libc::getpid() } ); - let mut buf = [0_u8; 1]; - loop { - let n = read_blocked(STDIN_FILENO, &mut buf).unwrap_or(0); - if n == 0 || matches!(buf[0], b'q' | b'\n' | b'\r') { - eprintf!("\n"); - std::process::abort(); + if is_main_thread() { + eprintf!("\n"); + std::thread::sleep(Duration::from_secs(1)); + } else { + eprintf!(" Debug PID %d or press Enter to exit\n", unsafe { + libc::getpid() + }); + let mut buf = [0_u8; 1]; + while let Ok(n) = read_blocked(STDIN_FILENO, &mut buf) { + if n == 0 || matches!(buf[0], b'q' | b'\n' | b'\r') { + eprintf!("\n"); + break; + } } } + std::process::abort(); })); } let exit_status = main(); + if let Some(at_exit) = AT_EXIT.get() { + (at_exit)(); + } asan_maybe_exit(exit_status); std::process::exit(exit_status) } diff --git a/src/reader.rs b/src/reader.rs index 4aae0ae1e..e1d62afc3 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -45,11 +45,12 @@ use crate::abbrs::abbrs_match; use crate::ast::{self, Ast, Category, Traversal}; use crate::builtins::shared::STATUS_CMD_OK; use crate::color::RgbColor; +use crate::common::restore_term_foreground_process_group_for_exit; use crate::common::{ escape, escape_string, exit_without_destructors, get_ellipsis_char, get_obfuscation_read_char, redirect_tty_output, scoped_push_replacer, scoped_push_replacer_ctx, shell_modes, str2wcstring, - wcs2string, write_loop, EscapeFlags, EscapeStringStyle, ScopeGuard, ScopeGuarding, - PROGRAM_NAME, UTF8_BOM_WCHAR, + wcs2string, write_loop, EscapeFlags, EscapeStringStyle, ScopeGuard, PROGRAM_NAME, + UTF8_BOM_WCHAR, }; use crate::complete::{ complete, complete_load, sort_and_prioritize, CompleteFlags, Completion, CompletionList, @@ -85,6 +86,7 @@ use crate::nix::isatty; use crate::operation_context::{get_bg_context, OperationContext}; use crate::output::Outputter; use crate::pager::{PageRendering, Pager, SelectionMotion}; +use crate::panic::AT_EXIT; use crate::parse_constants::SourceRange; use crate::parse_constants::{ParseTreeFlags, ParserTestErrorBits}; use crate::parse_tree::ParsedSource; @@ -784,11 +786,15 @@ fn read_ni(parser: &Parser, fd: RawFd, io: &IoChain) -> i32 { } /// Initialize the reader. -pub fn reader_init() -> impl ScopeGuarding { +pub fn reader_init(will_restore_foreground_pgroup: bool) { // Save the initial terminal mode. let mut terminal_mode_on_startup = TERMINAL_MODE_ON_STARTUP.lock().unwrap(); unsafe { libc::tcgetattr(STDIN_FILENO, &mut *terminal_mode_on_startup) }; + #[cfg(not(test))] + assert!(AT_EXIT.get().is_none()); + AT_EXIT.get_or_init(|| Box::new(move || reader_deinit(will_restore_foreground_pgroup))); + // Set the mode used for program execution, initialized to the current mode. let mut tty_modes_for_external_cmds = TTY_MODES_FOR_EXTERNAL_CMDS.lock().unwrap(); *tty_modes_for_external_cmds = *terminal_mode_on_startup; @@ -813,10 +819,14 @@ pub fn reader_init() -> impl ScopeGuarding { term_donate(/*quiet=*/ true); } } - ScopeGuard::new((), move |()| { - restore_term_mode(); - crate::input_common::terminal_protocols_disable_ifn(); - }) +} + +pub fn reader_deinit(restore_foreground_pgroup: bool) { + restore_term_mode(); + crate::input_common::terminal_protocols_disable_ifn(); + if restore_foreground_pgroup { + restore_term_foreground_process_group_for_exit(); + } } /// Restore the term mode if we own the terminal and are interactive (#8705). diff --git a/src/signal.rs b/src/signal.rs index 7e479177b..de423ac47 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -1,9 +1,9 @@ use std::num::NonZeroI32; -use crate::common::{exit_without_destructors, restore_term_foreground_process_group_for_exit}; +use crate::common::exit_without_destructors; use crate::event::{enqueue_signal, is_signal_observed}; -use crate::input_common::terminal_protocols_disable_ifn; use crate::nix::getpid; +use crate::panic::AT_EXIT; use crate::reader::{reader_handle_sigint, reader_sighup}; use crate::termsize::TermsizeContainer; use crate::topic_monitor::{topic_monitor_principal, Generation, GenerationsList, Topic}; @@ -88,8 +88,9 @@ extern "C" fn fish_signal_handler( libc::SIGTERM => { // Handle sigterm. The only thing we do is restore the front process ID, then die. if !observed { - restore_term_foreground_process_group_for_exit(); - terminal_protocols_disable_ifn(); + if let Some(at_exit) = AT_EXIT.get() { + (at_exit)(); + } // Safety: signal() and raise() are async-signal-safe. unsafe { libc::signal(libc::SIGTERM, libc::SIG_DFL); diff --git a/src/tests/mod.rs b/src/tests/mod.rs index e3065b7e9..f2f0f73e5 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -28,10 +28,10 @@ mod topic_monitor; mod wgetopt; pub mod prelude { - use crate::common::ScopeGuarding; + use crate::common::{ScopeGuard, ScopeGuarding}; use crate::env::{env_init, misc_init}; use crate::parser::{CancelBehavior, Parser}; - use crate::reader::reader_init; + use crate::reader::{reader_deinit, reader_init}; use crate::signal::signal_reset_handlers; pub use crate::tests::env::{PwdEnvironment, TestEnvironment}; use crate::topic_monitor::topic_monitor_init; @@ -109,7 +109,10 @@ pub mod prelude { // Set PWD from getcwd - fixes #5599 EnvStack::globals().set_pwd_from_getcwd(); }); - reader_init() + reader_init(false); + ScopeGuard::new((), |()| { + reader_deinit(false); + }) } pub use serial_test::serial;