Port (but do not adopt) signal handling bits in Rust

This ports some signal setup and handling bits to Rust.

The signal handling machinery requires walking over the list of known signals;
that's not supported by the Signal type. Rather than duplicate the list of
signals yet again, switch back to a table, as we had in C++.

This also adds two further pieces which were neglected by the Signal struct:

1. Localize signal descriptions
2. Support for integers as the signal name
This commit is contained in:
ridiculousfish 2023-04-30 12:38:06 -07:00
parent 603a2d6973
commit 1ecf9d013d
8 changed files with 466 additions and 262 deletions

View file

@ -947,10 +947,8 @@ pub const fn char_offset(base: char, offset: u32) -> char {
}
/// Exits without invoking destructors (via _exit), useful for code after fork.
fn exit_without_destructors(code: i32) -> ! {
unsafe {
libc::_exit(code);
}
pub fn exit_without_destructors(code: i32) -> ! {
unsafe { libc::_exit(code) };
}
/// Save the shell mode on startup so we can restore them on exit.
@ -1594,6 +1592,7 @@ pub fn restore_term_foreground_process_group_for_exit() {
// Note initial_fg_process_group == 0 is possible with Linux pid namespaces.
// This is called during shutdown and from a signal handler. We don't bother to complain on
// failure because doing so is unlikely to be noticed.
// Safety: All of getpgrp, signal, and tcsetpgrp are async-signal-safe.
let initial_fg_process_group = INITIAL_FG_PROCESS_GROUP.load(Ordering::Relaxed);
if initial_fg_process_group > 0 && initial_fg_process_group != unsafe { libc::getpgrp() } {
unsafe {

View file

@ -791,7 +791,7 @@ pub fn fire_delayed(parser: &mut parser_t) {
// HACK: The only variables we change in response to a *signal* are $COLUMNS and $LINES.
// Do that now.
if sig == Signal::SIGWINCH {
if sig == libc::SIGWINCH {
termsize::SHARED_CONTAINER.updating(parser);
}
let event = Event {

View file

@ -1,13 +1,290 @@
use std::borrow::Cow;
use std::num::NonZeroI32;
use crate::ffi;
use crate::common::{exit_without_destructors, restore_term_foreground_process_group_for_exit};
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;
use crate::wchar_ffi::c_str;
use widestring::U32CStr;
use crate::wchar::{wstr, WExt, L};
use crate::wutil::fish_wcstoi;
use crate::wutil::{wgettext, wgettext_str, wperror};
use errno::{errno, set_errno};
use std::sync::atomic::{AtomicI32, Ordering};
use widestring_suffix::widestrs;
/// Store the "main" pid. This allows us to reliably determine if we are in a forked child.
static MAIN_PID: AtomicI32 = AtomicI32::new(0);
/// 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.
fn reraise_if_forked_child(sig: i32) -> bool {
// Don't use is_forked_child: it relies on atfork handlers which may have not yet run.
// Safety: getpid() is async-signal-safe.
let pid = unsafe { libc::getpid() };
if pid == MAIN_PID.load(Ordering::Relaxed) {
return false;
}
// Safety: signal() and raise() are async-signal-safe.
unsafe {
libc::signal(sig, libc::SIG_DFL);
libc::raise(sig);
}
true
}
/// The cancellation signal we have received.
/// Of course this is modified from a signal handler.
static CANCELLATION_SIGNAL: AtomicI32 = AtomicI32::new(0);
pub fn signal_clear_cancel() {
CANCELLATION_SIGNAL.store(0, Ordering::Relaxed);
}
pub fn signal_check_cancel() -> i32 {
CANCELLATION_SIGNAL.load(Ordering::Relaxed)
}
// Declare these as an extern C functions and call them directly,
// in case the autocxx ffi allocates or does something else signal-unfriendly.
extern "C" {
fn reader_sighup();
fn reader_handle_sigint();
}
/// The single signal handler. By centralizing signal handling we ensure that we can never install
/// the "wrong" signal handler (see #5969).
extern "C" fn fish_signal_handler(
sig: i32,
_info: *mut libc::siginfo_t,
_context: *mut libc::c_void,
) {
// Ensure we preserve errno.
let saved_errno = errno();
// Check if we are a forked child.
if reraise_if_forked_child(sig) {
set_errno(saved_errno);
return;
}
// Check if fish script cares about this.
let observed = is_signal_observed(sig);
if observed {
enqueue_signal(sig);
}
// Do some signal-specific stuff.
match sig {
libc::SIGWINCH => {
// Respond to a winch signal by telling the termsize container.
termsize_handle_winch();
}
libc::SIGHUP => {
// Exit unless the signal was trapped.
if !observed {
unsafe { reader_sighup() };
}
topic_monitor_principal().post(topic_t::sighupint);
}
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();
// Safety: signal() and raise() are async-signal-safe.
unsafe {
libc::signal(libc::SIGTERM, libc::SIG_DFL);
libc::raise(libc::SIGTERM);
}
}
}
libc::SIGINT => {
// Cancel unless the signal was trapped.
if !observed {
CANCELLATION_SIGNAL.store(libc::SIGINT, Ordering::Relaxed);
}
unsafe { reader_handle_sigint() };
topic_monitor_principal().post(topic_t::sighupint);
}
libc::SIGCHLD => {
// A child process stopped or exited.
topic_monitor_principal().post(topic_t::sigchld);
}
libc::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.
}
_ => {}
}
set_errno(saved_errno);
}
fn signal_reset_handlers() {
let mut act: libc::sigaction = unsafe { std::mem::zeroed() };
unsafe { libc::sigemptyset(&mut act.sa_mask) };
act.sa_flags = 0;
act.sa_sigaction = libc::SIG_DFL;
for data in SIGNAL_TABLE.iter() {
if data.signal == libc::SIGHUP {
let mut oact: libc::sigaction = unsafe { std::mem::zeroed() };
unsafe { libc::sigaction(libc::SIGHUP, std::ptr::null(), &mut oact) };
if oact.sa_sigaction == libc::SIG_IGN {
continue;
}
}
unsafe {
libc::sigaction(data.signal.code(), &act, std::ptr::null_mut());
};
}
}
// Wrapper around sigaction.
fn sigaction(sig: i32, act: &libc::sigaction, oact: *mut libc::sigaction) -> libc::c_int {
// Note: historically many call sites have ignored return value of sigaction here.
unsafe { libc::sigaction(sig, act, oact) }
}
fn set_interactive_handlers() {
let signal_handler: usize = fish_signal_handler as usize;
let mut act: libc::sigaction = unsafe { std::mem::zeroed() };
let mut oact: libc::sigaction = unsafe { std::mem::zeroed() };
act.sa_flags = 0;
oact.sa_flags = 0;
unsafe { libc::sigemptyset(&mut act.sa_mask) };
let nullptr = std::ptr::null_mut();
// Interactive mode. Ignore interactive signals. We are a shell, we know what is best for
// the user.
act.sa_sigaction = libc::SIG_IGN;
sigaction(libc::SIGTSTP, &act, nullptr);
sigaction(libc::SIGTTOU, &act, nullptr);
// We don't ignore SIGTTIN because we might send it to ourself.
act.sa_sigaction = signal_handler;
act.sa_flags = libc::SA_SIGINFO;
sigaction(libc::SIGTTIN, &act, nullptr);
// SIGTERM restores the terminal controlling process before dying.
act.sa_sigaction = signal_handler;
act.sa_flags = libc::SA_SIGINFO;
sigaction(libc::SIGTERM, &act, nullptr);
unsafe { libc::sigaction(libc::SIGHUP, nullptr, &mut oact) };
if oact.sa_sigaction == libc::SIG_DFL {
act.sa_sigaction = signal_handler;
act.sa_flags = libc::SA_SIGINFO;
sigaction(libc::SIGHUP, &act, nullptr);
}
// SIGALARM as part of our signal torture test
act.sa_sigaction = signal_handler;
act.sa_flags = libc::SA_SIGINFO;
sigaction(libc::SIGALRM, &act, nullptr);
act.sa_sigaction = signal_handler;
act.sa_flags = libc::SA_SIGINFO;
sigaction(libc::SIGWINCH, &act, nullptr);
}
/// Sets up appropriate signal handlers.
fn signal_set_handlers(interactive: bool) {
use libc::SIG_IGN;
let nullptr = std::ptr::null_mut();
let mut act: libc::sigaction = unsafe { std::mem::zeroed() };
act.sa_flags = 0;
unsafe { libc::sigemptyset(&mut 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 = SIG_IGN;
sigaction(libc::SIGPIPE, &act, nullptr);
// Ignore SIGQUIT.
act.sa_sigaction = SIG_IGN;
sigaction(libc::SIGQUIT, &act, nullptr);
// Apply our SIGINT handler.
act.sa_sigaction = fish_signal_handler as usize;
act.sa_flags = libc::SA_SIGINFO;
sigaction(libc::SIGINT, &act, nullptr);
// Whether or not we're interactive we want SIGCHLD to not interrupt restartable syscalls.
act.sa_sigaction = fish_signal_handler as usize;
act.sa_flags = libc::SA_SIGINFO | libc::SA_RESTART;
if sigaction(libc::SIGCHLD, &act, nullptr) != 0 {
wperror(L!("sigaction"));
exit_without_destructors(1);
}
if interactive {
set_interactive_handlers();
}
if cfg!(feature = "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.
unsafe { libc::kill(libc::getpid(), libc::SIGCHLD) };
}
}
pub fn signal_handle(sig: libc::c_int) {
let mut act: libc::sigaction = unsafe { std::mem::zeroed() };
// These should always be handled.
if sig == libc::SIGINT
|| sig == libc::SIGQUIT
|| sig == libc::SIGTSTP
|| sig == libc::SIGTTIN
|| sig == libc::SIGTTOU
|| sig == libc::SIGCHLD
{
return;
}
act.sa_flags = 0;
unsafe { libc::sigemptyset(&mut act.sa_mask) };
act.sa_flags = libc::SA_SIGINFO;
act.sa_sigaction = fish_signal_handler as usize;
sigaction(sig, &act, std::ptr::null_mut());
}
pub fn get_signals_with_handlers(set: &mut libc::sigset_t) {
unsafe { libc::sigemptyset(set) };
for data in SIGNAL_TABLE.iter() {
let mut act: libc::sigaction = unsafe { std::mem::zeroed() };
unsafe { libc::sigaction(data.signal.code(), std::ptr::null(), &mut 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 == libc::SIGHUP && act.sa_sigaction == libc::SIG_IGN {
continue;
}
if act.sa_sigaction != libc::SIG_DFL {
unsafe { libc::sigaddset(set, data.signal.code()) };
}
}
}
/// Ensure we did not inherit any blocked signals. See issue #3964.
pub fn signal_unblock_all() {
unsafe {
let mut iset: libc::sigset_t = std::mem::zeroed();
libc::sigemptyset(&mut iset);
libc::sigprocmask(libc::SIG_SETMASK, &iset, std::ptr::null_mut());
}
}
/// A Sigchecker can be used to check if a SIGINT (or SIGHUP) has been delivered.
pub struct Sigchecker {
topic: topic_t,
@ -47,30 +324,102 @@ impl Sigchecker {
}
}
#[deprecated(note = "Use [`Signal::parse()`] instead.")]
/// Get the integer signal value representing the specified signal.
pub fn wcs2sig(s: &wstr) -> Option<usize> {
let sig = ffi::wcs2sig(c_str!(s));
sig.0.try_into().ok()
/// Struct describing an entry for the lookup table used to convert between signal names and signal
/// ids, etc.
struct LookupEntry {
signal: Signal,
name: &'static wstr,
desc: &'static wstr, // Note: this needs to be translated via gettext before presenting it to the user.
}
#[deprecated(note = "Use [`Signal::name()`] instead.")]
/// Get string representation of a signal.
pub fn sig2wcs(sig: i32) -> &'static wstr {
let s = ffi::sig2wcs(ffi::c_int(sig));
let s = unsafe { U32CStr::from_ptr_str(s) };
wstr::from_ucstr(s).expect("signal name should be valid utf-32")
impl LookupEntry {
const fn new(signal: i32, name: &'static wstr, desc: &'static wstr) -> Self {
Self {
signal: Signal::new(signal),
name,
desc,
}
}
}
#[deprecated(note = "Use [`Signal::desc()`] instead.")]
/// Returns a description of the specified signal.
pub fn signal_get_desc(sig: i32) -> &'static wstr {
let s = ffi::signal_get_desc(ffi::c_int(sig));
let s = unsafe { U32CStr::from_ptr_str(s) };
// Lookup table used to convert between signal names and signal ids, etc.
#[rustfmt::skip]
#[widestrs]
const SIGNAL_TABLE : &[LookupEntry] = &[
LookupEntry::new(libc::SIGHUP, "SIGHUP"L, "Terminal hung up"L),
LookupEntry::new(libc::SIGINT, "SIGINT"L, "Quit request from job control (^C)"L),
LookupEntry::new(libc::SIGQUIT, "SIGQUIT"L, "Quit request from job control with core dump (^\\)"L),
LookupEntry::new(libc::SIGILL, "SIGILL"L, "Illegal instruction"L),
LookupEntry::new(libc::SIGTRAP, "SIGTRAP"L, "Trace or breakpoint trap"L),
LookupEntry::new(libc::SIGABRT, "SIGABRT"L, "Abort"L),
LookupEntry::new(libc::SIGBUS, "SIGBUS"L, "Misaligned address error"L),
LookupEntry::new(libc::SIGFPE, "SIGFPE"L, "Floating point exception"L),
LookupEntry::new(libc::SIGKILL, "SIGKILL"L, "Forced quit"L),
LookupEntry::new(libc::SIGUSR1, "SIGUSR1"L, "User defined signal 1"L),
LookupEntry::new(libc::SIGUSR2, "SIGUSR2"L, "User defined signal 2"L),
LookupEntry::new(libc::SIGSEGV, "SIGSEGV"L, "Address boundary error"L),
LookupEntry::new(libc::SIGPIPE, "SIGPIPE"L, "Broken pipe"L),
LookupEntry::new(libc::SIGALRM, "SIGALRM"L, "Timer expired"L),
LookupEntry::new(libc::SIGTERM, "SIGTERM"L, "Polite quit request"L),
LookupEntry::new(libc::SIGCHLD, "SIGCHLD"L, "Child process status changed"L),
LookupEntry::new(libc::SIGCONT, "SIGCONT"L, "Continue previously stopped process"L),
LookupEntry::new(libc::SIGSTOP, "SIGSTOP"L, "Forced stop"L),
LookupEntry::new(libc::SIGTSTP, "SIGTSTP"L, "Stop request from job control (^Z)"L),
LookupEntry::new(libc::SIGTTIN, "SIGTTIN"L, "Stop from terminal input"L),
LookupEntry::new(libc::SIGTTOU, "SIGTTOU"L, "Stop from terminal output"L),
LookupEntry::new(libc::SIGURG, "SIGURG"L, "Urgent socket condition"L),
LookupEntry::new(libc::SIGXCPU, "SIGXCPU"L, "CPU time limit exceeded"L),
LookupEntry::new(libc::SIGXFSZ, "SIGXFSZ"L, "File size limit exceeded"L),
LookupEntry::new(libc::SIGVTALRM, "SIGVTALRM"L, "Virtual timefr expired"L),
LookupEntry::new(libc::SIGPROF, "SIGPROF"L, "Profiling timer expired"L),
LookupEntry::new(libc::SIGWINCH, "SIGWINCH"L, "Window size change"L),
LookupEntry::new(libc::SIGIO, "SIGIO"L, "I/O on asynchronous file descriptor is possible"L),
LookupEntry::new(libc::SIGSYS, "SIGSYS"L, "Bad system call"L),
LookupEntry::new(libc::SIGIOT, "SIGIOT"L, "Abort (Alias for SIGABRT)"L),
wstr::from_ucstr(s).expect("signal description should be valid utf-32")
#[cfg(any(feature = "bsd", target_os = "macos"))]
LookupEntry::new(libc::SIGEMT, "SIGEMT"L, "Unused signal"L),
#[cfg(any(feature = "bsd", target_os = "macos"))]
LookupEntry::new(libc::SIGINFO, "SIGINFO"L, "Information request"L),
#[cfg(target_os = "linux")]
LookupEntry::new(libc::SIGSTKFLT, "SISTKFLT"L, "Stack fault"L),
#[cfg(target_os = "linux")]
LookupEntry::new(libc::SIGIOT, "SIGIOT"L, "Abort (Alias for SIGABRT)"L),
#[cfg(target_os = "linux")]
#[allow(deprecated)]
LookupEntry::new(libc::SIGUNUSED, "SIGUNUSED"L, "Unused signal"L),
#[cfg(target_os = "linux")]
LookupEntry::new(libc::SIGPWR, "SIGPWR"L, "Power failure"L),
// TODO: determine whether SIGWIND is defined on any platform.
//LookupEntry::new(libc::SIGWIND, "SIGWIND"L, "Window size change"L),
];
// Return true if two strings are equal, ignoring ASCII case.
fn equals_ascii_icase(left: &wstr, right: &wstr) -> bool {
if left.len() != right.len() {
return false;
}
for (lc, rc) in left.chars().zip(right.chars()) {
if lc.to_ascii_lowercase() != rc.to_ascii_lowercase() {
return false;
}
}
true
}
/// Test if \c name is a string describing the signal named \c canonical.
fn match_signal_name(canonical: &wstr, mut name: &wstr) -> bool {
// Skip the "SIG" prefix if it exists.
if name.char_count() >= 3 && equals_ascii_icase(name.slice_to(3), L!("sig")) {
name = name.slice_from(3)
}
equals_ascii_icase(canonical.slice_from(3), name)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
@ -78,71 +427,6 @@ pub fn signal_get_desc(sig: i32) -> &'static wstr {
pub struct Signal(NonZeroI32);
impl Signal {
pub const SIGHUP: Signal = Signal::new(libc::SIGHUP);
pub const SIGINT: Signal = Signal::new(libc::SIGINT);
pub const SIGQUIT: Signal = Signal::new(libc::SIGQUIT);
pub const SIGILL: Signal = Signal::new(libc::SIGILL);
pub const SIGTRAP: Signal = Signal::new(libc::SIGTRAP);
pub const SIGABRT: Signal = Signal::new(libc::SIGABRT);
/// Available on BSD and macOS only.
#[cfg(any(feature = "bsd", target_os = "macos"))]
pub const SIGEMT: Signal = Signal::new(libc::SIGEMT);
pub const SIGFPE: Signal = Signal::new(libc::SIGFPE);
pub const SIGKILL: Signal = Signal::new(libc::SIGKILL);
pub const SIGBUS: Signal = Signal::new(libc::SIGBUS);
pub const SIGSEGV: Signal = Signal::new(libc::SIGSEGV);
pub const SIGSYS: Signal = Signal::new(libc::SIGSYS);
pub const SIGPIPE: Signal = Signal::new(libc::SIGPIPE);
pub const SIGALRM: Signal = Signal::new(libc::SIGALRM);
pub const SIGTERM: Signal = Signal::new(libc::SIGTERM);
pub const SIGURG: Signal = Signal::new(libc::SIGURG);
pub const SIGSTOP: Signal = Signal::new(libc::SIGSTOP);
pub const SIGTSTP: Signal = Signal::new(libc::SIGTSTP);
pub const SIGCONT: Signal = Signal::new(libc::SIGCONT);
pub const SIGCHLD: Signal = Signal::new(libc::SIGCHLD);
pub const SIGTTIN: Signal = Signal::new(libc::SIGTTIN);
pub const SIGTTOU: Signal = Signal::new(libc::SIGTTOU);
pub const SIGIO: Signal = Signal::new(libc::SIGIO);
pub const SIGXCPU: Signal = Signal::new(libc::SIGXCPU);
pub const SIGXFSZ: Signal = Signal::new(libc::SIGXFSZ);
pub const SIGVTALRM: Signal = Signal::new(libc::SIGVTALRM);
pub const SIGPROF: Signal = Signal::new(libc::SIGPROF);
pub const SIGWINCH: Signal = Signal::new(libc::SIGWINCH);
/// Available on BSD and macOS only.
#[cfg(any(feature = "bsd", target_os = "macos"))]
pub const SIGINFO: Signal = Signal::new(libc::SIGINFO);
pub const SIGUSR1: Signal = Signal::new(libc::SIGUSR1);
pub const SIGUSR2: Signal = Signal::new(libc::SIGUSR2);
/// Available on BSD only.
#[cfg(any(target_os = "freebsd"))]
pub const SIGTHR: Signal = Signal::new(32); // Not exposed by libc crate
/// Available on BSD only.
#[cfg(any(target_os = "freebsd"))]
pub const SIGLIBRT: Signal = Signal::new(33); // Not exposed by libc crate
#[cfg(target_os = "linux")]
/// Available on Linux only.
pub const SIGSTKFLT: Signal = Signal::new(libc::SIGSTKFLT);
#[cfg(target_os = "linux")]
/// Available on Linux only.
pub const SIGPWR: Signal = Signal::new(libc::SIGPWR);
// Signals aliased to other signals
/// Available on Linux only. Use [`Signal::SIGIO`] instead.
#[cfg(target_os = "linux")]
pub const SIGPOLL: Signal = Signal::SIGIO;
/// Available on Linux only. Use [`Signal::SIGSYS`] instead.
#[cfg(target_os = "linux")]
#[deprecated(note = "Use SIGSYS instead")]
pub const SIGUNUSED: Signal = Signal::SIGSYS;
/// Available on Linux only. Alias for [`Signal::SIGABRT`].
#[cfg(target_os = "linux")]
pub const SIGIOT: Signal = Signal::SIGABRT;
}
impl Signal {
#[widestrs]
const UNKNOWN_SIG_NAME: &'static wstr = "SIG???"L;
/// Creates a new `Signal` to represent the passed system signal code `sig`.
/// Panics if `sig` is zero.
pub const fn new(sig: i32) -> Self {
@ -152,166 +436,57 @@ impl Signal {
}
}
#[widestrs]
pub const fn name(&self) -> &'static wstr {
match *self {
Signal::SIGHUP => "SIGHUP"L,
Signal::SIGINT => "SIGINT"L,
Signal::SIGQUIT => "SIGQUIT"L,
Signal::SIGILL => "SIGILL"L,
Signal::SIGTRAP => "SIGTRAP"L,
Signal::SIGABRT => "SIGABRT"L,
#[cfg(any(feature = "bsd", target_os = "macos"))]
Signal::SIGEMT => "SIGEMT"L,
Signal::SIGFPE => "SIGFPE"L,
Signal::SIGKILL => "SIGKILL"L,
Signal::SIGBUS => "SIGBUS"L,
Signal::SIGSEGV => "SIGSEGV"L,
Signal::SIGSYS => "SIGSYS"L,
Signal::SIGPIPE => "SIGPIPE"L,
Signal::SIGALRM => "SIGALRM"L,
Signal::SIGTERM => "SIGTERM"L,
Signal::SIGURG => "SIGURG"L,
Signal::SIGSTOP => "SIGSTOP"L,
Signal::SIGTSTP => "SIGTSTP"L,
Signal::SIGCONT => "SIGCONT"L,
Signal::SIGCHLD => "SIGCHLD"L,
Signal::SIGTTIN => "SIGTTIN"L,
Signal::SIGTTOU => "SIGTTOU"L,
Signal::SIGIO => "SIGIO"L,
Signal::SIGXCPU => "SIGXCPU"L,
Signal::SIGXFSZ => "SIGXFSZ"L,
Signal::SIGVTALRM => "SIGVTALRM"L,
Signal::SIGPROF => "SIGPROF"L,
Signal::SIGWINCH => "SIGWINCH"L,
#[cfg(any(feature = "bsd", target_os = "macos"))]
Signal::SIGINFO => "SIGINFO"L,
Signal::SIGUSR1 => "SIGUSR1"L,
Signal::SIGUSR2 => "SIGUSR2"L,
#[cfg(any(target_os = "freebsd"))]
Signal::SIGTHR => "SIGTHR"L,
#[cfg(any(target_os = "freebsd"))]
Signal::SIGLIBRT => "SIGLIBRT"L,
#[cfg(target_os = "linux")]
Signal::SIGSTKFLT => "SIGSTKFLT"L,
#[cfg(target_os = "linux")]
Signal::SIGPWR => "SIGPWR"L,
Signal(_) => Self::UNKNOWN_SIG_NAME,
/// Return the LookupEntry for ourself.
fn get_lookup_entry(&self) -> Option<&'static LookupEntry> {
SIGNAL_TABLE
.iter()
.find(|entry| entry.signal == self.code())
}
// Previously sig2wcs().
pub fn name(&self) -> &'static wstr {
match self.get_lookup_entry() {
Some(entry) => entry.name,
None => wgettext!("Unknown"),
}
}
// Previously signal_get_desc().
pub fn desc(&self) -> &'static wstr {
match self.get_lookup_entry() {
Some(entry) => wgettext_str(entry.desc),
None => wgettext!("Unknown"),
}
}
pub fn code(&self) -> i32 {
self.0.into()
}
#[widestrs]
pub const fn desc(&self) -> &'static wstr {
match *self {
Signal::SIGHUP => "Terminal hung up"L,
Signal::SIGINT => "Quit request from job control (^C)"L,
Signal::SIGQUIT => "Quit request from job control with core dump (^\\)"L,
Signal::SIGILL => "Illegal instruction"L,
Signal::SIGTRAP => "Trace or breakpoint trap"L,
Signal::SIGABRT => "Abort"L,
#[cfg(any(feature = "bsd", target_os = "macos"))]
Signal::SIGEMT => "Emulator trap"L,
Signal::SIGFPE => "Floating point exception"L,
Signal::SIGKILL => "Forced quit"L,
Signal::SIGBUS => "Misaligned address error"L,
Signal::SIGSEGV => "Address boundary error"L,
Signal::SIGSYS => "Bad system call"L,
Signal::SIGPIPE => "Broken pipe"L,
Signal::SIGALRM => "Timer expired"L,
Signal::SIGTERM => "Polite quit request"L,
Signal::SIGURG => "Urgent socket condition"L,
Signal::SIGSTOP => "Forced stop"L,
Signal::SIGTSTP => "Stop request from job control (^Z)"L,
Signal::SIGCONT => "Continue previously stopped process"L,
Signal::SIGCHLD => "Child process status changed"L,
Signal::SIGTTIN => "Stop from terminal input"L,
Signal::SIGTTOU => "Stop from terminal output"L,
Signal::SIGIO => "I/O on asynchronous file descriptior is possible"L,
Signal::SIGXCPU => "CPU time limit exceeded"L,
Signal::SIGXFSZ => "File size limit exceeded"L,
Signal::SIGVTALRM => "Virtual timer expired"L,
Signal::SIGPROF => "Profiling timer expired"L,
Signal::SIGWINCH => "Window size change"L,
#[cfg(any(feature = "bsd", target_os = "macos"))]
Signal::SIGINFO => "Information request"L,
Signal::SIGUSR1 => "User-defined signal 1"L,
Signal::SIGUSR2 => "User-defined signal 2"L,
#[cfg(any(target_os = "freebsd"))]
Signal::SIGTHR => "Thread interrupt"L,
#[cfg(any(target_os = "freebsd"))]
Signal::SIGLIBRT => "Real-time library interrupt"L,
#[cfg(target_os = "linux")]
Signal::SIGSTKFLT => "Stack fault"L,
#[cfg(target_os = "linux")]
Signal::SIGPWR => "Power failure"L,
Signal(_) => "Unknown"L,
}
}
/// Parses a string into the equivalent [`Signal`] sharing the same name.
///
/// Accepts both `SIGABC` and `ABC` to match against `Signal::SIGABC`. If the signal name is not
/// recognized, `None` is returned.
pub fn parse(name: &str) -> Option<Signal> {
let mut chars = name.chars();
let name = loop {
match chars.next() {
None => break Cow::Borrowed(name),
Some(c) if !c.is_ascii() => return None,
Some(c) if !c.is_ascii_uppercase() => break Cow::Owned(name.to_ascii_uppercase()),
_ => (),
};
};
let name = name.strip_prefix("SIG").unwrap_or(name.as_ref());
match name {
"HUP" => Some(Signal::SIGHUP),
"INT" => Some(Signal::SIGINT),
"QUIT" => Some(Signal::SIGQUIT),
"ILL" => Some(Signal::SIGILL),
"TRAP" => Some(Signal::SIGTRAP),
"ABRT" => Some(Signal::SIGABRT),
#[cfg(any(feature = "bsd", target_os = "macos"))]
"EMT" => Some(Signal::SIGEMT),
"FPE" => Some(Signal::SIGFPE),
"KILL" => Some(Signal::SIGKILL),
"BUS" => Some(Signal::SIGBUS),
"SEGV" => Some(Signal::SIGSEGV),
"SYS" => Some(Signal::SIGSYS),
"PIPE" => Some(Signal::SIGPIPE),
"ALRM" => Some(Signal::SIGALRM),
"TERM" => Some(Signal::SIGTERM),
"URG" => Some(Signal::SIGURG),
"STOP" => Some(Signal::SIGSTOP),
"TSTP" => Some(Signal::SIGTSTP),
"CONT" => Some(Signal::SIGCONT),
"CHLD" => Some(Signal::SIGCHLD),
"TTIN" => Some(Signal::SIGTTIN),
"TTOU" => Some(Signal::SIGTTOU),
"IO" => Some(Signal::SIGIO),
"XCPU" => Some(Signal::SIGXCPU),
"XFSZ" => Some(Signal::SIGXFSZ),
"VTALRM" => Some(Signal::SIGVTALRM),
"PROF" => Some(Signal::SIGPROF),
"WINCH" => Some(Signal::SIGWINCH),
#[cfg(any(feature = "bsd", target_os = "macos"))]
"INFO" => Some(Signal::SIGINFO),
"USR1" => Some(Signal::SIGUSR1),
"USR2" => Some(Signal::SIGUSR2),
#[cfg(any(target_os = "freebsd"))]
"THR" => Some(Signal::SIGTHR),
#[cfg(any(target_os = "freebsd"))]
"LIBRT" => Some(Signal::SIGLIBRT),
#[cfg(target_os = "linux")]
"STKFLT" => Some(Signal::SIGSTKFLT),
#[cfg(target_os = "linux")]
"PWR" => Some(Signal::SIGPWR),
_ => None,
/// This also accepts integer codes via fish_wcstoi().
/// Previously sig2wcs().
pub fn parse(name: &wstr) -> Option<Signal> {
for entry in SIGNAL_TABLE.iter() {
if match_signal_name(entry.name, name) {
return Some(entry.signal);
}
}
if let Ok(num) = fish_wcstoi(name) {
if num > 0 {
return Some(Signal::new(num));
}
}
None
}
}
// Allow signals to be compared against i32.
impl PartialEq<i32> for Signal {
fn eq(&self, other: &i32) -> bool {
self.code() == *other
}
}
@ -333,24 +508,32 @@ impl From<Signal> for NonZeroI32 {
}
}
#[test]
fn signal_name() {
let sig = Signal::SIGINT;
assert_eq!(sig.name(), "SIGINT");
}
// Need to use add_test for wgettext support.
use crate::ffi_tests::add_test;
#[test]
fn parse_signal() {
assert_eq!(Signal::parse("SIGHUP"), Some(Signal::SIGHUP));
assert_eq!(Signal::parse("sigwinch"), Some(Signal::SIGWINCH));
assert_eq!(Signal::parse("TSTP"), Some(Signal::SIGTSTP));
assert_eq!(Signal::parse("TstP"), Some(Signal::SIGTSTP));
assert_eq!(Signal::parse("sigCONT"), Some(Signal::SIGCONT));
assert_eq!(Signal::parse("SIGFOO"), None);
assert_eq!(Signal::parse(""), None);
assert_eq!(Signal::parse("SIG"), None);
assert_eq!(Signal::parse("سلام"), None);
}
add_test!("test_signal_name", || {
let sig = Signal::new(libc::SIGINT);
assert_eq!(sig.name(), "SIGINT");
});
#[rustfmt::skip]
add_test!("test_signal_parse", || {
use crate::wchar_ext::ToWString;
assert_eq!(Signal::parse(L!("SIGHUP")), Some(Signal::new(libc::SIGHUP)));
assert_eq!(Signal::parse(L!("sigwinch")), Some(Signal::new(libc::SIGWINCH)));
assert_eq!(Signal::parse(L!("TSTP")), Some(Signal::new(libc::SIGTSTP)));
assert_eq!(Signal::parse(L!("TstP")), Some(Signal::new(libc::SIGTSTP)));
assert_eq!(Signal::parse(L!("sigCONT")), Some(Signal::new(libc::SIGCONT)));
assert_eq!(Signal::parse(L!("SIGFOO")), None);
assert_eq!(Signal::parse(L!("")), None);
assert_eq!(Signal::parse(L!("SIG")), None);
assert_eq!(Signal::parse(L!("سلام")), None);
assert_eq!(Signal::parse(&libc::SIGINT.to_wstring()), Some(Signal::new(libc::SIGINT)));
assert_eq!(Signal::parse(L!("0")), None);
assert_eq!(Signal::parse(L!("-0")), None);
assert_eq!(Signal::parse(L!("-1")), None);
});
#[test]
#[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]

View file

@ -192,6 +192,13 @@ pub trait WExt {
wstr::from_char_slice(&chars[start..])
}
/// Return a char slice up to a *char index*.
/// This is different from Rust string slicing, which takes a byte index.
fn slice_to(&self, end: usize) -> &wstr {
let chars = self.as_char_slice();
wstr::from_char_slice(&chars[..end])
}
/// Return the number of chars.
/// This is different from Rust string len, which returns the number of bytes.
fn char_count(&self) -> usize {

View file

@ -1,6 +1,7 @@
use crate::ffi;
use crate::wchar::wstr;
use crate::wchar_ffi::{wchar_t, wcslen};
use widestring::U32CString;
/// Support for wgettext.
@ -12,6 +13,12 @@ pub fn wgettext_impl_do_not_use_directly(text: &[wchar_t]) -> &'static wstr {
wstr::from_slice(slice).expect("Invalid UTF-32")
}
/// Get a (possibly translated) string from a non-literal.
pub fn wgettext_str(s: &wstr) -> &'static wstr {
let cstr: U32CString = U32CString::from_chars_truncate(s.as_char_slice());
wgettext_impl_do_not_use_directly(cstr.as_slice_with_nul())
}
/// Get a (possibly translated) string from a string literal.
/// This returns a &'static wstr.
macro_rules! wgettext {

View file

@ -14,7 +14,7 @@ use crate::flog::FLOGF;
use crate::wchar::{wstr, WString, L};
use crate::wchar_ext::WExt;
use crate::wcstringutil::{join_strings, split_string, wcs2string_callback};
pub(crate) use gettext::{wgettext, wgettext_fmt};
pub(crate) use gettext::{wgettext, wgettext_fmt, wgettext_str};
use libc::{
DT_BLK, DT_CHR, DT_DIR, DT_FIFO, DT_LNK, DT_REG, DT_SOCK, EACCES, EIO, ELOOP, ENAMETOOLONG,
ENODEV, ENOENT, ENOTDIR, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG,

View file

@ -1002,10 +1002,12 @@ static relaxed_atomic_t<exit_state_t> s_exit_state{exit_state_t::none};
/// This is set from a signal handler.
static volatile sig_atomic_t s_sighup_received{false};
extern "C" {
void reader_sighup() {
// Beware, we may be in a signal handler.
s_sighup_received = true;
}
}
static void redirect_tty_after_sighup() {
// If we have received SIGHUP, redirect the tty to avoid a user script triggering SIGTTIN or
@ -1256,8 +1258,10 @@ void reader_data_t::kill(editable_line_t *el, size_t begin_idx, size_t length, i
erase_substring(el, begin_idx, length);
}
extern "C" {
// This is called from a signal handler!
void reader_handle_sigint() { interrupted = SIGINT; }
}
/// Make sure buffers are large enough to hold the current string length.
void reader_data_t::command_line_changed(const editable_line_t *el) {

View file

@ -144,7 +144,9 @@ class editable_line_t {
int reader_read(parser_t &parser, int fd, const io_chain_t &io);
/// Mark that we encountered SIGHUP and must (soon) exit. This is invoked from a signal handler.
extern "C" {
void reader_sighup();
}
/// Initialize the reader.
void reader_init();
@ -254,7 +256,9 @@ void reader_push(parser_t &parser, const wcstring &history_name, reader_config_t
void reader_pop();
/// The readers interrupt signal handler. Cancels all currently running blocks.
extern "C" {
void reader_handle_sigint();
}
/// \return whether fish is currently unwinding the stack in preparation to exit.
bool fish_is_unwinding_for_exit();