fish-shell/fish-rust/src/signal.rs
Johannes Altmanninger 141dcde498 signal.rs: crash a bit earlier when signal number is negative
The conversion to usize is used for array accesses, so negative values
would cause crashes either way. Let's do it earlier so we can get rid of
the suspect C-style cast.
2023-04-16 17:21:54 +02:00

363 lines
14 KiB
Rust

use std::borrow::Cow;
use std::num::NonZeroI32;
use crate::ffi;
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 widestring_suffix::widestrs;
/// A sigint_detector_t can be used to check if a SIGINT (or SIGHUP) has been delivered.
pub struct sigchecker_t {
topic: topic_t,
gen: generation_t,
}
impl sigchecker_t {
/// Create a new checker for the given topic.
pub fn new(topic: topic_t) -> sigchecker_t {
let mut res = sigchecker_t { topic, gen: 0 };
// Call check() to update our generation.
res.check();
res
}
/// Create a new checker for SIGHUP and SIGINT.
pub fn new_sighupint() -> sigchecker_t {
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 mut gens = invalid_generations();
*gens.at_mut(self.topic) = self.gen;
tm.check(&mut gens, true /* wait */);
}
}
#[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()
}
#[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")
}
#[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) };
wstr::from_ucstr(s).expect("signal description should be valid utf-32")
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
/// A wrapper around the system signal code.
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 {
match NonZeroI32::new(sig) {
None => panic!("Invalid zero signal value!"),
Some(result) => Signal(result),
}
}
#[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,
}
}
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,
}
}
}
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
}
}
#[test]
fn signal_name() {
let sig = Signal::SIGINT;
assert_eq!(sig.name(), "SIGINT");
}
#[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);
}
#[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::SIGEMT.code(), libc::SIGEMT);
assert_eq!(Signal::SIGINFO.code(), libc::SIGINFO);
}