mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-12-27 04:33:38 +08:00
6361362996
This removes some spurious unsafe blocks and makes usage a bit nicer
626 lines
22 KiB
Rust
626 lines
22 KiB
Rust
use std::num::NonZeroI32;
|
|
|
|
use crate::common::{exit_without_destructors, restore_term_foreground_process_group_for_exit};
|
|
use crate::event::{enqueue_signal, is_signal_observed};
|
|
use crate::nix::getpid;
|
|
use crate::reader::{reader_handle_sigint, reader_sighup};
|
|
use crate::termsize::TermsizeContainer;
|
|
use crate::topic_monitor::{generation_t, topic_monitor_principal, topic_t, GenerationsList};
|
|
use crate::wchar::prelude::*;
|
|
use crate::wchar_ffi::{AsWstr, WCharToFFI};
|
|
use crate::wutil::{fish_wcstoi, perror};
|
|
use cxx::{CxxWString, UniquePtr};
|
|
use errno::{errno, set_errno};
|
|
use std::sync::atomic::{AtomicI32, Ordering};
|
|
|
|
#[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();
|
|
|
|
}
|
|
extern "Rust" {
|
|
type SigChecker;
|
|
fn new_sighupint_checker() -> Box<SigChecker>;
|
|
fn check(&mut self) -> bool;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
/// 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.
|
|
if getpid() == 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);
|
|
|
|
/// 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)
|
|
}
|
|
|
|
/// 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.
|
|
TermsizeContainer::handle_winch();
|
|
}
|
|
libc::SIGHUP => {
|
|
// Exit unless the signal was trapped.
|
|
if !observed {
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
|
|
/// Set all signal handlers to SIG_DFL.
|
|
/// This is called after fork - it should be async signal safe.
|
|
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;
|
|
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);
|
|
}
|
|
|
|
/// Set signal handlers to fish default handlers.
|
|
pub fn signal_set_handlers(interactive: bool) {
|
|
// Mark our main pid.
|
|
MAIN_PID.store(getpid(), Ordering::Relaxed);
|
|
|
|
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 {
|
|
perror("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(getpid(), libc::SIGCHLD) };
|
|
}
|
|
}
|
|
|
|
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.
|
|
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,
|
|
gen: generation_t,
|
|
}
|
|
|
|
impl SigChecker {
|
|
/// Create a new checker for the given topic.
|
|
pub fn new(topic: topic_t) -> Self {
|
|
let mut res = SigChecker { topic, gen: 0 };
|
|
// Call check() to update our generation.
|
|
res.check();
|
|
res
|
|
}
|
|
|
|
/// Create a new checker for SIGHUP and SIGINT.
|
|
pub fn new_sighupint() -> Self {
|
|
Self::new(topic_t::sighupint)
|
|
}
|
|
|
|
/// Check if a sigint has been delivered since the last call to check(), or since the detector
|
|
/// was created.
|
|
pub fn check(&mut self) -> bool {
|
|
let tm = topic_monitor_principal();
|
|
let gen = tm.generation_for_topic(self.topic);
|
|
let changed = self.gen != gen;
|
|
self.gen = gen;
|
|
changed
|
|
}
|
|
|
|
/// Wait until a sigint is delivered.
|
|
pub fn wait(&self) {
|
|
let tm = topic_monitor_principal();
|
|
let gens = GenerationsList::invalid();
|
|
gens.set(self.topic, self.gen);
|
|
tm.check(&gens, true /* wait */);
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
}
|
|
|
|
impl LookupEntry {
|
|
const fn new(signal: i32, name: &'static wstr, desc: &'static wstr) -> Self {
|
|
Self {
|
|
signal: Signal::new(signal),
|
|
name,
|
|
desc,
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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),
|
|
|
|
#[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)]
|
|
/// A wrapper around the system signal code.
|
|
pub struct Signal(NonZeroI32);
|
|
|
|
impl Signal {
|
|
/// Creates a new `Signal` to represent the passed system signal code `sig`.
|
|
/// Panics if `sig` is zero.
|
|
pub const fn new(sig: i32) -> Self {
|
|
match NonZeroI32::new(sig) {
|
|
None => panic!("Invalid zero signal value!"),
|
|
Some(result) => Signal(result),
|
|
}
|
|
}
|
|
|
|
/// Return the LookupEntry for ourself.
|
|
fn get_lookup_entry(&self) -> Option<&'static LookupEntry> {
|
|
SIGNAL_TABLE
|
|
.iter()
|
|
.find(|entry| entry.signal == self.code())
|
|
}
|
|
|
|
/// Get string representation of a signal.
|
|
/// Previously sig2wcs().
|
|
pub fn name(&self) -> &'static wstr {
|
|
match self.get_lookup_entry() {
|
|
Some(entry) => entry.name,
|
|
None => wgettext!("Unknown"),
|
|
}
|
|
}
|
|
|
|
/// 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),
|
|
None => wgettext!("Unknown"),
|
|
}
|
|
}
|
|
|
|
pub fn code(&self) -> i32 {
|
|
self.0.into()
|
|
}
|
|
/// 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.
|
|
/// 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
|
|
}
|
|
}
|
|
|
|
impl From<Signal> for i32 {
|
|
fn from(value: Signal) -> Self {
|
|
value.code()
|
|
}
|
|
}
|
|
|
|
impl From<Signal> for usize {
|
|
fn from(value: Signal) -> Self {
|
|
usize::try_from(value.code()).unwrap()
|
|
}
|
|
}
|
|
|
|
impl From<Signal> for NonZeroI32 {
|
|
fn from(value: Signal) -> Self {
|
|
value.0
|
|
}
|
|
}
|
|
|
|
// Need to use add_test for wgettext support.
|
|
use crate::ffi_tests::add_test;
|
|
|
|
add_test!("test_signal_name", || {
|
|
let sig = Signal::new(libc::SIGINT);
|
|
assert_eq!(sig.name(), "SIGINT");
|
|
});
|
|
|
|
fn new_sighupint_checker() -> Box<SigChecker> {
|
|
Box::new(SigChecker::new_sighupint())
|
|
}
|
|
|
|
#[rustfmt::skip]
|
|
add_test!("test_signal_parse", || {
|
|
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"))]
|
|
/// Verify bsd feature is detected on the known BSDs, which gives us greater confidence it'll work
|
|
/// for the unknown ones too. We don't need to do this for Linux and macOS because we're using
|
|
/// rust's native OS targeting for those.
|
|
fn bsd_signals() {
|
|
assert_eq!(Signal::parse(L!("SIGEMT")), Some(Signal::new(libc::SIGEMT)));
|
|
assert_eq!(
|
|
Signal::parse(L!("SIGINFO")),
|
|
Some(Signal::new(libc::SIGINFO))
|
|
);
|
|
}
|