diff --git a/fish-rust/src/signal.rs b/fish-rust/src/signal.rs index 89bb68ade..d6f0f6a1a 100644 --- a/fish-rust/src/signal.rs +++ b/fish-rust/src/signal.rs @@ -1,3 +1,6 @@ +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; @@ -43,6 +46,7 @@ pub fn wait(&self) { } } +#[deprecated(note = "Use [`Signal::parse()`] instead.")] /// Get the integer signal value representing the specified signal. pub fn wcs2sig(s: &wstr) -> Option { let sig = ffi::wcs2sig(c_str!(s)); @@ -50,6 +54,7 @@ pub fn wcs2sig(s: &wstr) -> Option { 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)); @@ -58,6 +63,7 @@ pub fn sig2wcs(sig: i32) -> &'static wstr { 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)); @@ -65,3 +71,296 @@ pub fn signal_get_desc(sig: i32) -> &'static wstr { wstr::from_ucstr(s).expect("signal description should be valid utf-32") } + +#[derive(Clone, Copy, 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(target_os = "freebsd", 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(target_os = "freebsd", 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 { + const UNKNOWN_SIG_NAME: &'static str = "SIG???"; + + /// 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), + } + } + + pub const fn name(&self) -> &'static str { + match *self { + Signal::SIGHUP => "SIGHUP", + Signal::SIGINT => "SIGINT", + Signal::SIGQUIT => "SIGQUIT", + Signal::SIGILL => "SIGILL", + Signal::SIGTRAP => "SIGTRAP", + Signal::SIGABRT => "SIGABRT", + #[cfg(any(target_os = "freebsd", target_os = "macos"))] + Signal::SIGEMT => "SIGEMT", + Signal::SIGFPE => "SIGFPE", + Signal::SIGKILL => "SIGKILL", + Signal::SIGBUS => "SIGBUS", + Signal::SIGSEGV => "SIGSEGV", + Signal::SIGSYS => "SIGSYS", + Signal::SIGPIPE => "SIGPIPE", + Signal::SIGALRM => "SIGALRM", + Signal::SIGTERM => "SIGTERM", + Signal::SIGURG => "SIGURG", + Signal::SIGSTOP => "SIGSTOP", + Signal::SIGTSTP => "SIGTSTP", + Signal::SIGCONT => "SIGCONT", + Signal::SIGCHLD => "SIGCHLD", + Signal::SIGTTIN => "SIGTTIN", + Signal::SIGTTOU => "SIGTTOU", + Signal::SIGIO => "SIGIO", + Signal::SIGXCPU => "SIGXCPU", + Signal::SIGXFSZ => "SIGXFSZ", + Signal::SIGVTALRM => "SIGVTALRM", + Signal::SIGPROF => "SIGPROF", + Signal::SIGWINCH => "SIGWINCH", + #[cfg(any(target_os = "freebsd", target_os = "macos"))] + Signal::SIGINFO => "SIGINFO", + Signal::SIGUSR1 => "SIGUSR1", + Signal::SIGUSR2 => "SIGUSR2", + #[cfg(any(target_os = "freebsd"))] + Signal::SIGTHR => "SIGTHR", + #[cfg(any(target_os = "freebsd"))] + Signal::SIGLIBRT => "SIGLIBRT", + #[cfg(target_os = "linux")] + Signal::SIGSTKFLT => "SIGSTKFLT", + #[cfg(target_os = "linux")] + Signal::SIGPWR => "SIGPWR", + Signal(_) => Self::UNKNOWN_SIG_NAME, + } + } + + pub fn code(&self) -> i32 { + self.0.into() + } + + pub const fn desc(&self) -> &'static str { + match *self { + Signal::SIGHUP => "Terminal hung up", + Signal::SIGINT => "Quit request from job control (^C)", + Signal::SIGQUIT => "Quit request from job control with core dump (^\\)", + Signal::SIGILL => "Illegal instruction", + Signal::SIGTRAP => "Trace or breakpoint trap", + Signal::SIGABRT => "Abort", + #[cfg(any(target_os = "freebsd", target_os = "macos"))] + Signal::SIGEMT => "Emulator trap", + Signal::SIGFPE => "Floating point exception", + Signal::SIGKILL => "Forced quit", + Signal::SIGBUS => "Misaligned address error", + Signal::SIGSEGV => "Address boundary error", + Signal::SIGSYS => "Bad system call", + Signal::SIGPIPE => "Broken pipe", + Signal::SIGALRM => "Timer expired", + Signal::SIGTERM => "Polite quit request", + Signal::SIGURG => "Urgent socket condition", + Signal::SIGSTOP => "Forced stop", + Signal::SIGTSTP => "Stop request from job control (^Z)", + Signal::SIGCONT => "Continue previously stopped process", + Signal::SIGCHLD => "Child process status changed", + Signal::SIGTTIN => "Stop from terminal input", + Signal::SIGTTOU => "Stop from terminal output", + Signal::SIGIO => "I/O on asynchronous file descriptior is possible", + Signal::SIGXCPU => "CPU time limit exceeded", + Signal::SIGXFSZ => "File size limit exceeded", + Signal::SIGVTALRM => "Virtual timer expired", + Signal::SIGPROF => "Profiling timer expired", + Signal::SIGWINCH => "Window size change", + #[cfg(any(target_os = "freebsd", target_os = "macos"))] + Signal::SIGINFO => "Information request", + Signal::SIGUSR1 => "User-defined signal 1", + Signal::SIGUSR2 => "User-defined signal 2", + #[cfg(any(target_os = "freebsd"))] + Signal::SIGTHR => "Thread interrupt", + #[cfg(any(target_os = "freebsd"))] + Signal::SIGLIBRT => "Real-time library interrupt", + #[cfg(target_os = "linux")] + Signal::SIGSTKFLT => "Stack fault", + #[cfg(target_os = "linux")] + Signal::SIGPWR => "Power failure", + Signal(_) => "Unknown", + } + } + + /// 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 { + 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(target_os = "freebsd", 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(target_os = "freebsd", 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 std::fmt::Debug for Signal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}({})", self.name(), self.code())) + } +} + +impl std::fmt::Display for Signal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = self.name(); + if name != Self::UNKNOWN_SIG_NAME { + f.write_str(name) + } else { + f.write_fmt(format_args!("Unrecognized Signal {}", self.code())) + } + } +} + +impl From for i32 { + fn from(value: Signal) -> Self { + value.code() + } +} + +impl From for usize { + fn from(value: Signal) -> Self { + value.code() as usize + } +} + +impl From 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); +}