mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-28 13:53:10 +00:00
Adopt the new Rust signal implementation
This switches the signals implementation from C++ to Rust.
This commit is contained in:
parent
1ecf9d013d
commit
4771f25102
8 changed files with 97 additions and 455 deletions
|
@ -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",
|
||||
|
|
|
@ -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<CxxWString> {
|
|||
/// 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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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<CxxWString>;
|
||||
|
||||
#[cxx_name = "wcs2sig"]
|
||||
fn wcs2sig_ffi(sig: &CxxWString) -> i32;
|
||||
|
||||
#[cxx_name = "signal_get_desc"]
|
||||
fn signal_get_desc_ffi(sig: i32) -> UniquePtr<CxxWString>;
|
||||
|
||||
fn signal_check_cancel() -> i32;
|
||||
fn signal_clear_cancel();
|
||||
fn signal_reset_handlers();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn sig2wcs_ffi(sig: i32) -> UniquePtr<CxxWString> {
|
||||
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<CxxWString> {
|
||||
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),
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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) {
|
||||
|
|
403
src/signals.cpp
403
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<sig_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.
|
||||
|
|
|
@ -5,44 +5,13 @@
|
|||
#include <csignal>
|
||||
#include <cstdint>
|
||||
|
||||
/// 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 {
|
||||
|
|
Loading…
Reference in a new issue