diff --git a/fish-rust/build.rs b/fish-rust/build.rs index 02d289433..74f4b8263 100644 --- a/fish-rust/build.rs +++ b/fish-rust/build.rs @@ -42,6 +42,7 @@ fn main() -> miette::Result<()> { "src/parse_tree.rs", "src/parse_util.rs", "src/redirection.rs", + "src/signal.rs", "src/smoke.rs", "src/termsize.rs", "src/timer.rs", diff --git a/fish-rust/src/event.rs b/fish-rust/src/event.rs index d4d0534d0..1d9d87bb7 100644 --- a/fish-rust/src/event.rs +++ b/fish-rust/src/event.rs @@ -15,10 +15,10 @@ use widestring_suffix::widestrs; use crate::builtins::shared::io_streams_t; use crate::common::{escape_string, scoped_push, EscapeFlags, EscapeStringStyle, ScopeGuard}; -use crate::ffi::{self, block_t, parser_t, signal_check_cancel, signal_handle, Repin}; +use crate::ffi::{self, block_t, parser_t, Repin}; use crate::flog::FLOG; use crate::job_group::{JobId, MaybeJobId}; -use crate::signal::Signal; +use crate::signal::{signal_check_cancel, signal_handle, Signal}; use crate::termsize; use crate::wchar::{wstr, WString, L}; use crate::wchar_ext::ToWString; @@ -617,7 +617,7 @@ fn event_get_desc_ffi(parser: &parser_t, evt: &Event) -> UniquePtr { /// Add an event handler. pub fn add_handler(eh: EventHandler) { if let EventType::Signal { signal } = eh.desc.typ { - signal_handle(ffi::c_int(signal.code())); + signal_handle(signal); inc_signal_observed(signal); } @@ -772,7 +772,7 @@ pub fn fire_delayed(parser: &mut parser_t) { return; }; // Do not invoke new event handlers if we are unwinding (#6649). - if signal_check_cancel().0 != 0 { + if signal_check_cancel() != 0 { return; }; diff --git a/fish-rust/src/ffi.rs b/fish-rust/src/ffi.rs index afc65e682..b664e4617 100644 --- a/fish-rust/src/ffi.rs +++ b/fish-rust/src/ffi.rs @@ -93,15 +93,9 @@ include_cpp! { generate!("pretty_printer_t") generate!("escape_string") - generate!("sig2wcs") - generate!("wcs2sig") - generate!("signal_get_desc") generate!("fd_event_signaller_t") - generate!("signal_handle") - generate!("signal_check_cancel") - generate!("block_t") generate!("block_type_t") generate!("statuses_t") diff --git a/fish-rust/src/signal.rs b/fish-rust/src/signal.rs index a499d467d..236b22eea 100644 --- a/fish-rust/src/signal.rs +++ b/fish-rust/src/signal.rs @@ -5,12 +5,64 @@ use crate::event::{enqueue_signal, is_signal_observed}; use crate::termsize::termsize_handle_winch; use crate::topic_monitor::{generation_t, invalid_generations, topic_monitor_principal, topic_t}; use crate::wchar::{wstr, WExt, L}; -use crate::wutil::fish_wcstoi; -use crate::wutil::{wgettext, wgettext_str, wperror}; +use crate::wchar_ffi::{AsWstr, WCharToFFI}; +use crate::wutil::{fish_wcstoi, wgettext, wgettext_str, wperror}; +use cxx::{CxxWString, UniquePtr}; use errno::{errno, set_errno}; use std::sync::atomic::{AtomicI32, Ordering}; use widestring_suffix::widestrs; +#[cxx::bridge] +mod signal_ffi { + extern "Rust" { + fn signal_set_handlers(interactive: bool); + fn signal_set_handlers_once(interactive: bool); + #[cxx_name = "signal_handle"] + fn signal_handle_ffi(sig: i32); + fn signal_unblock_all(); + + #[cxx_name = "sig2wcs"] + fn sig2wcs_ffi(sig: i32) -> UniquePtr; + + #[cxx_name = "wcs2sig"] + fn wcs2sig_ffi(sig: &CxxWString) -> i32; + + #[cxx_name = "signal_get_desc"] + fn signal_get_desc_ffi(sig: i32) -> UniquePtr; + + fn signal_check_cancel() -> i32; + fn signal_clear_cancel(); + fn signal_reset_handlers(); + + } +} + +fn sig2wcs_ffi(sig: i32) -> UniquePtr { + Signal::new(sig).name().to_ffi() +} + +fn wcs2sig_ffi(sig: &CxxWString) -> i32 { + if let Some(sig) = Signal::parse(sig.as_wstr()) { + sig.code() + } else { + -1 + } +} + +fn signal_get_desc_ffi(sig: i32) -> UniquePtr { + Signal::new(sig).desc().to_ffi() +} + +fn signal_handle_ffi(sig: i32) { + signal_handle(Signal::new(sig)); +} + +// This is extern "C" for FFI purposes, as this is used after fork(). +#[no_mangle] +pub extern "C" fn get_signals_with_handlers_ffi(set: *mut libc::sigset_t) { + get_signals_with_handlers(unsafe { &mut *set }); +} + /// Store the "main" pid. This allows us to reliably determine if we are in a forked child. static MAIN_PID: AtomicI32 = AtomicI32::new(0); @@ -38,10 +90,15 @@ fn reraise_if_forked_child(sig: i32) -> bool { /// Of course this is modified from a signal handler. static CANCELLATION_SIGNAL: AtomicI32 = AtomicI32::new(0); +/// Set the cancellation signal to zero. +/// In generally this should only be done in interactive sessions. pub fn signal_clear_cancel() { CANCELLATION_SIGNAL.store(0, Ordering::Relaxed); } +/// \return the most recent cancellation signal received by the fish process. +/// Currently only SIGINT is considered a cancellation signal. +/// This is thread safe. pub fn signal_check_cancel() -> i32 { CANCELLATION_SIGNAL.load(Ordering::Relaxed) } @@ -121,7 +178,8 @@ extern "C" fn fish_signal_handler( set_errno(saved_errno); } -fn signal_reset_handlers() { +/// Set all signal handlers to SIG_DFL. +pub fn signal_reset_handlers() { let mut act: libc::sigaction = unsafe { std::mem::zeroed() }; unsafe { libc::sigemptyset(&mut act.sa_mask) }; act.sa_flags = 0; @@ -190,8 +248,11 @@ fn set_interactive_handlers() { sigaction(libc::SIGWINCH, &act, nullptr); } -/// Sets up appropriate signal handlers. -fn signal_set_handlers(interactive: bool) { +/// Set signal handlers to fish default handlers. +pub fn signal_set_handlers(interactive: bool) { + // Mark our main pid. + MAIN_PID.store(unsafe { libc::getpid() }, Ordering::Relaxed); + use libc::SIG_IGN; let nullptr = std::ptr::null_mut(); let mut act: libc::sigaction = unsafe { std::mem::zeroed() }; @@ -238,7 +299,19 @@ fn signal_set_handlers(interactive: bool) { } } -pub fn signal_handle(sig: libc::c_int) { +pub fn signal_set_handlers_once(interactive: bool) { + static NONINTER_ONCE: std::sync::Once = std::sync::Once::new(); + NONINTER_ONCE.call_once(|| signal_set_handlers(false)); + + static INTER_ONCE: std::sync::Once = std::sync::Once::new(); + if interactive { + INTER_ONCE.call_once(set_interactive_handlers); + } +} + +/// Mark that a signal is being handled. +pub fn signal_handle(sig: Signal) { + let sig = sig.code(); let mut act: libc::sigaction = unsafe { std::mem::zeroed() }; // These should always be handled. @@ -443,7 +516,8 @@ impl Signal { .find(|entry| entry.signal == self.code()) } - // Previously sig2wcs(). + /// Get string representation of a signal. + /// Previously sig2wcs(). pub fn name(&self) -> &'static wstr { match self.get_lookup_entry() { Some(entry) => entry.name, @@ -451,7 +525,8 @@ impl Signal { } } - // Previously signal_get_desc(). + /// Returns a description of the specified signal. + /// Previously signal_get_desc(). pub fn desc(&self) -> &'static wstr { match self.get_lookup_entry() { Some(entry) => wgettext_str(entry.desc), diff --git a/src/function.cpp b/src/function.cpp index 388542e73..a647f6462 100644 --- a/src/function.cpp +++ b/src/function.cpp @@ -360,7 +360,7 @@ wcstring function_properties_t::annotated_definition(const wcstring &name) const for (const auto &d : handlers) { switch (d.typ) { case event_type_t::signal: { - append_format(out, L" --on-signal %ls", sig2wcs(d.signal)); + append_format(out, L" --on-signal %ls", sig2wcs(d.signal)->c_str()); break; } case event_type_t::variable: { diff --git a/src/proc.cpp b/src/proc.cpp index 1178c28e6..e4ce149fc 100644 --- a/src/proc.cpp +++ b/src/proc.cpp @@ -581,10 +581,10 @@ wcstring summary_command(const job_ref_t &j, const process_ptr_t &p = nullptr) { // Arguments are the signal name and description. int sig = p->status.signal_code(); buffer.push_back(L' '); - buffer.append(escape_string(sig2wcs(sig))); + buffer.append(escape_string(std::move(*sig2wcs(sig)))); buffer.push_back(L' '); - buffer.append(escape_string(signal_get_desc(sig))); + buffer.append(escape_string(std::move(*signal_get_desc(sig)))); // If we have multiple processes, we also append the pid and argv. if (j->processes.size() > 1) { diff --git a/src/signals.cpp b/src/signals.cpp index 59288a30e..3e1f2c4cf 100644 --- a/src/signals.cpp +++ b/src/signals.cpp @@ -21,407 +21,10 @@ #include "topic_monitor.h" #include "wutil.h" // IWYU pragma: keep -/// Struct describing an entry for the lookup table used to convert between signal names and signal -/// ids, etc. -struct lookup_entry { - /// Signal id. - int signal; - /// Signal name. - const wchar_t *name; - /// Signal description. - const wchar_t *desc; -}; - -/// Lookup table used to convert between signal names and signal ids, etc. -static const struct lookup_entry signal_table[] = { -#ifdef SIGHUP - {SIGHUP, L"SIGHUP", N_(L"Terminal hung up")}, -#endif -#ifdef SIGINT - {SIGINT, L"SIGINT", N_(L"Quit request from job control (^C)")}, -#endif -#ifdef SIGQUIT - {SIGQUIT, L"SIGQUIT", N_(L"Quit request from job control with core dump (^\\)")}, -#endif -#ifdef SIGILL - {SIGILL, L"SIGILL", N_(L"Illegal instruction")}, -#endif -#ifdef SIGTRAP - {SIGTRAP, L"SIGTRAP", N_(L"Trace or breakpoint trap")}, -#endif -#ifdef SIGABRT - {SIGABRT, L"SIGABRT", N_(L"Abort")}, -#endif -#ifdef SIGBUS - {SIGBUS, L"SIGBUS", N_(L"Misaligned address error")}, -#endif -#ifdef SIGFPE - {SIGFPE, L"SIGFPE", N_(L"Floating point exception")}, -#endif -#ifdef SIGKILL - {SIGKILL, L"SIGKILL", N_(L"Forced quit")}, -#endif -#ifdef SIGUSR1 - {SIGUSR1, L"SIGUSR1", N_(L"User defined signal 1")}, -#endif -#ifdef SIGUSR2 - {SIGUSR2, L"SIGUSR2", N_(L"User defined signal 2")}, -#endif -#ifdef SIGSEGV - {SIGSEGV, L"SIGSEGV", N_(L"Address boundary error")}, -#endif -#ifdef SIGPIPE - {SIGPIPE, L"SIGPIPE", N_(L"Broken pipe")}, -#endif -#ifdef SIGALRM - {SIGALRM, L"SIGALRM", N_(L"Timer expired")}, -#endif -#ifdef SIGTERM - {SIGTERM, L"SIGTERM", N_(L"Polite quit request")}, -#endif -#ifdef SIGCHLD - {SIGCHLD, L"SIGCHLD", N_(L"Child process status changed")}, -#endif -#ifdef SIGCONT - {SIGCONT, L"SIGCONT", N_(L"Continue previously stopped process")}, -#endif -#ifdef SIGSTOP - {SIGSTOP, L"SIGSTOP", N_(L"Forced stop")}, -#endif -#ifdef SIGTSTP - {SIGTSTP, L"SIGTSTP", N_(L"Stop request from job control (^Z)")}, -#endif -#ifdef SIGTTIN - {SIGTTIN, L"SIGTTIN", N_(L"Stop from terminal input")}, -#endif -#ifdef SIGTTOU - {SIGTTOU, L"SIGTTOU", N_(L"Stop from terminal output")}, -#endif -#ifdef SIGURG - {SIGURG, L"SIGURG", N_(L"Urgent socket condition")}, -#endif -#ifdef SIGXCPU - {SIGXCPU, L"SIGXCPU", N_(L"CPU time limit exceeded")}, -#endif -#ifdef SIGXFSZ - {SIGXFSZ, L"SIGXFSZ", N_(L"File size limit exceeded")}, -#endif -#ifdef SIGVTALRM - {SIGVTALRM, L"SIGVTALRM", N_(L"Virtual timer expired")}, -#endif -#ifdef SIGPROF - {SIGPROF, L"SIGPROF", N_(L"Profiling timer expired")}, -#endif -#ifdef SIGWINCH - {SIGWINCH, L"SIGWINCH", N_(L"Window size change")}, -#endif -#ifdef SIGWIND - {SIGWIND, L"SIGWIND", N_(L"Window size change")}, -#endif -#ifdef SIGIO - {SIGIO, L"SIGIO", N_(L"I/O on asynchronous file descriptor is possible")}, -#endif -#ifdef SIGPWR - {SIGPWR, L"SIGPWR", N_(L"Power failure")}, -#endif -#ifdef SIGSYS - {SIGSYS, L"SIGSYS", N_(L"Bad system call")}, -#endif -#ifdef SIGINFO - {SIGINFO, L"SIGINFO", N_(L"Information request")}, -#endif -#ifdef SIGSTKFLT - {SIGSTKFLT, L"SISTKFLT", N_(L"Stack fault")}, -#endif -#ifdef SIGEMT - {SIGEMT, L"SIGEMT", N_(L"Emulator trap")}, -#endif -#ifdef SIGIOT - {SIGIOT, L"SIGIOT", N_(L"Abort (Alias for SIGABRT)")}, -#endif -#ifdef SIGUNUSED - {SIGUNUSED, L"SIGUNUSED", N_(L"Unused signal")}, -#endif -}; - -/// Test if \c name is a string describing the signal named \c canonical. -static int match_signal_name(const wchar_t *canonical, const wchar_t *name) { - if (wcsncasecmp(name, L"sig", const_strlen("sig")) == 0) name += 3; - - return wcscasecmp(canonical + const_strlen("sig"), name) == 0; -} - -int wcs2sig(const wchar_t *str) { - for (const auto &data : signal_table) { - if (match_signal_name(data.name, str)) { - return data.signal; - } - } - - int res = fish_wcstoi(str); - if (errno || res < 0) return -1; - return res; -} - -const wchar_t *sig2wcs(int sig) { - for (const auto &data : signal_table) { - if (data.signal == sig) { - return data.name; - } - } - - return _(L"Unknown"); -} - -const wchar_t *signal_get_desc(int sig) { - for (const auto &data : signal_table) { - if (data.signal == sig) { - return _(data.desc); - } - } - - return _(L"Unknown"); -} - -/// Store the "main" pid. This allows us to reliably determine if we are in a forked child. -static const pid_t s_main_pid = getpid(); - -/// It's possible that we receive a signal after we have forked, but before we have reset the signal -/// handlers (or even run the pthread_atfork calls). In that event we will do something dumb like -/// swallow SIGINT. Ensure that doesn't happen. Check if we are the main fish process; if not, reset -/// and re-raise the signal. \return whether we re-raised the signal. -static bool reraise_if_forked_child(int sig) { - // Don't use is_forked_child: it relies on atfork handlers which may have not yet run. - if (getpid() == s_main_pid) { - return false; - } - signal(sig, SIG_DFL); - raise(sig); - return true; -} - -/// The cancellation signal we have received. -/// Of course this is modified from a signal handler. -static volatile relaxed_atomic_t s_cancellation_signal{0}; - -void signal_clear_cancel() { s_cancellation_signal = 0; } - -int signal_check_cancel() { return s_cancellation_signal; } - -/// The single signal handler. By centralizing signal handling we ensure that we can never install -/// the "wrong" signal handler (see #5969). -static void fish_signal_handler(int sig, siginfo_t *info, void *context) { - UNUSED(info); - UNUSED(context); - - // Ensure we preserve errno. - const int saved_errno = errno; - - // Check if we are a forked child. - if (reraise_if_forked_child(sig)) { - errno = saved_errno; - return; - } - - // Check if fish script cares about this. - const bool observed = event_is_signal_observed(sig); - if (observed) { - event_enqueue_signal(sig); - } - - // Do some signal-specific stuff. - switch (sig) { -#ifdef SIGWINCH - case SIGWINCH: - // Respond to a winch signal by telling the termsize container. - termsize_handle_winch(); - break; -#endif - - case SIGHUP: - // Exit unless the signal was trapped. - if (!observed) { - reader_sighup(); - } - topic_monitor_principal().post(topic_t::sighupint); - break; - - case 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(); - signal(SIGTERM, SIG_DFL); - raise(SIGTERM); - } - break; - - case SIGINT: - // Cancel unless the signal was trapped. - if (!observed) { - s_cancellation_signal = SIGINT; - } - reader_handle_sigint(); - topic_monitor_principal().post(topic_t::sighupint); - break; - - case SIGCHLD: - // A child process stopped or exited. - topic_monitor_principal().post(topic_t::sigchld); - break; - - case SIGALRM: - // We have a sigalarm handler that does nothing. This is used in the signal torture - // test, to verify that we behave correctly when receiving lots of irrelevant signals. - break; - } - errno = saved_errno; -} - -void signal_reset_handlers() { - struct sigaction act; - sigemptyset(&act.sa_mask); - act.sa_flags = 0; - act.sa_handler = SIG_DFL; - - for (const auto &data : signal_table) { - if (data.signal == SIGHUP) { - struct sigaction oact; - sigaction(SIGHUP, nullptr, &oact); - if (oact.sa_handler == SIG_IGN) continue; - } - sigaction(data.signal, &act, nullptr); - } -} - -static void set_interactive_handlers() { - struct sigaction act, oact; - act.sa_flags = 0; - oact.sa_flags = 0; - sigemptyset(&act.sa_mask); - - // Interactive mode. Ignore interactive signals. We are a shell, we know what is best for - // the user. - act.sa_handler = SIG_IGN; - sigaction(SIGTSTP, &act, nullptr); - sigaction(SIGTTOU, &act, nullptr); - - // We don't ignore SIGTTIN because we might send it to ourself. - act.sa_sigaction = &fish_signal_handler; - act.sa_flags = SA_SIGINFO; - sigaction(SIGTTIN, &act, nullptr); - - // SIGTERM restores the terminal controlling process before dying. - act.sa_sigaction = &fish_signal_handler; - act.sa_flags = SA_SIGINFO; - sigaction(SIGTERM, &act, nullptr); - - sigaction(SIGHUP, nullptr, &oact); - if (oact.sa_handler == SIG_DFL) { - act.sa_sigaction = &fish_signal_handler; - act.sa_flags = SA_SIGINFO; - sigaction(SIGHUP, &act, nullptr); - } - - // SIGALARM as part of our signal torture test - act.sa_sigaction = &fish_signal_handler; - act.sa_flags = SA_SIGINFO; - sigaction(SIGALRM, &act, nullptr); - -#ifdef SIGWINCH - act.sa_sigaction = &fish_signal_handler; - act.sa_flags = SA_SIGINFO; - sigaction(SIGWINCH, &act, nullptr); -#endif -} - -/// Sets up appropriate signal handlers. -void signal_set_handlers(bool interactive) { - struct sigaction act; - act.sa_flags = 0; - sigemptyset(&act.sa_mask); - - // Ignore SIGPIPE. We'll detect failed writes and deal with them appropriately. We don't want - // this signal interrupting other syscalls or terminating us. - act.sa_sigaction = nullptr; - act.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &act, nullptr); - - // Ignore SIGQUIT. - act.sa_handler = SIG_IGN; - sigaction(SIGQUIT, &act, nullptr); - - // Apply our SIGINT handler. - act.sa_sigaction = &fish_signal_handler; - act.sa_flags = SA_SIGINFO; - sigaction(SIGINT, &act, nullptr); - - // Whether or not we're interactive we want SIGCHLD to not interrupt restartable syscalls. - act.sa_sigaction = &fish_signal_handler; - act.sa_flags = SA_SIGINFO | SA_RESTART; - if (sigaction(SIGCHLD, &act, nullptr)) { - wperror(L"sigaction"); - FATAL_EXIT(); - } - - if (interactive) { - set_interactive_handlers(); - } - -#ifdef FISH_TSAN_WORKAROUNDS - // Work around the following TSAN bug: - // The structure containing signal information for a thread is lazily allocated by TSAN. - // It is possible for the same thread to receive two allocations, if the signal handler - // races with other allocation paths (e.g. a blocking call). This results in the first signal - // being potentially dropped. - // The workaround is to send ourselves a SIGCHLD signal now, to force the allocation to happen. - // As no child is associated with this signal, it is OK if it is dropped, so long as the - // allocation happens. - (void)kill(getpid(), SIGCHLD); -#endif -} - -void signal_set_handlers_once(bool interactive) { - static std::once_flag s_noninter_once; - std::call_once(s_noninter_once, signal_set_handlers, false); - - static std::once_flag s_inter_once; - if (interactive) std::call_once(s_inter_once, set_interactive_handlers); -} - -void signal_handle(int sig) { - struct sigaction act; - - // These should always be handled. - if ((sig == SIGINT) || (sig == SIGQUIT) || (sig == SIGTSTP) || (sig == SIGTTIN) || - (sig == SIGTTOU) || (sig == SIGCHLD)) - return; - - act.sa_flags = 0; - sigemptyset(&act.sa_mask); - act.sa_flags = SA_SIGINFO; - act.sa_sigaction = &fish_signal_handler; - sigaction(sig, &act, nullptr); -} - -void get_signals_with_handlers(sigset_t *set) { - sigemptyset(set); - for (const auto &data : signal_table) { - struct sigaction act = {}; - sigaction(data.signal, nullptr, &act); - // If SIGHUP is being ignored (e.g., because were were run via `nohup`) don't reset it. - // We don't special case other signals because if they're being ignored that shouldn't - // affect processes we spawn. They should get the default behavior for those signals. - if (data.signal == SIGHUP && act.sa_handler == SIG_IGN) continue; - if (act.sa_handler != SIG_DFL) sigaddset(set, data.signal); - } -} - -/// Ensure we did not inherit any blocked signals. See issue #3964. -void signal_unblock_all() { - sigset_t iset; - sigemptyset(&iset); - sigprocmask(SIG_SETMASK, &iset, nullptr); +extern "C" { +void get_signals_with_handlers_ffi(sigset_t *set); } +void get_signals_with_handlers(sigset_t *set) { get_signals_with_handlers_ffi(set); } sigchecker_t::sigchecker_t(topic_t signal) : topic_(signal) { // Call check() to update our generation. diff --git a/src/signals.h b/src/signals.h index 1becc11e6..7c160d763 100644 --- a/src/signals.h +++ b/src/signals.h @@ -5,44 +5,13 @@ #include #include -/// Get the integer signal value representing the specified signal, or -1 of no signal was found. -int wcs2sig(const wchar_t *str); - -/// Get string representation of a signal. -const wchar_t *sig2wcs(int sig); - -/// Returns a description of the specified signal. -const wchar_t *signal_get_desc(int sig); - -/// Set all signal handlers to SIG_DFL. -void signal_reset_handlers(); - -/// Set signal handlers to fish default handlers. -void signal_set_handlers(bool interactive); - -/// Latch function. This sets signal handlers, but only the first time it is called. -void signal_set_handlers_once(bool interactive); - -/// Tell fish what to do on the specified signal. -/// -/// \param sig The signal to specify the action of -void signal_handle(int sig); - -/// Ensure we did not inherit any blocked signals. See issue #3964. -void signal_unblock_all(); +#if INCLUDE_RUST_HEADERS +#include "signal.rs.h" +#endif /// Returns signals with non-default handlers. void get_signals_with_handlers(sigset_t *set); -/// \return the most recent cancellation signal received by the fish process. -/// Currently only SIGINT is considered a cancellation signal. -/// This is thread safe. -int signal_check_cancel(); - -/// Set the cancellation signal to zero. -/// In generally this should only be done in interactive sessions. -void signal_clear_cancel(); - enum class topic_t : uint8_t; /// A sigint_detector_t can be used to check if a SIGINT (or SIGHUP) has been delivered. class sigchecker_t {