watchexec/crates/lib/src/signal/process.rs

216 lines
6.8 KiB
Rust

//! Types for cross-platform and cross-purpose handling of subprocess signals.
use std::str::FromStr;
#[cfg(unix)]
use command_group::Signal as NixSignal;
use crate::error::SignalParseError;
use super::source::MainSignal;
/// A notification sent to a subprocess.
///
/// On Windows, only some signals are supported, as described. Others will be ignored.
///
/// On Unix, there are several "first-class" signals which have their own variants, and a generic
/// [`Custom`][SubSignal::Custom] variant which can be used to send arbitrary signals.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SubSignal {
/// Indicate that the terminal is disconnected.
///
/// On Unix, this is `SIGHUP`. On Windows, this is ignored for now but may be supported in the
/// future (see [#219](https://github.com/watchexec/watchexec/issues/219)).
///
/// Despite its nominal purpose, on Unix this signal is often used to reload configuration files.
Hangup,
/// Indicate to the kernel that the process should stop.
///
/// On Unix, this is `SIGKILL`. On Windows, this is `TerminateProcess`.
///
/// This signal is not handled by the process, but directly by the kernel, and thus cannot be
/// intercepted. Subprocesses may exit in inconsistent states.
ForceStop,
/// Indicate that the process should stop.
///
/// On Unix, this is `SIGINT`. On Windows, this is ignored for now but may be supported in the
/// future (see [#219](https://github.com/watchexec/watchexec/issues/219)).
///
/// This signal generally indicates an action taken by the user, so it may be handled
/// differently than a termination.
Interrupt,
/// Indicate that the process is to stop, the kernel will then dump its core.
///
/// On Unix, this is `SIGQUIT`. On Windows, it is ignored.
///
/// This is rarely used.
Quit,
/// Indicate that the process should stop.
///
/// On Unix, this is `SIGTERM`. On Windows, this is ignored for now but may be supported in the
/// future (see [#219](https://github.com/watchexec/watchexec/issues/219)).
///
/// On Unix, this signal generally indicates an action taken by the system, so it may be handled
/// differently than an interruption.
Terminate,
/// Indicate an application-defined behaviour should happen.
///
/// On Unix, this is `SIGUSR1`. On Windows, it is ignored.
///
/// This signal is generally used to start debugging.
User1,
/// Indicate an application-defined behaviour should happen.
///
/// On Unix, this is `SIGUSR2`. On Windows, it is ignored.
///
/// This signal is generally used to reload configuration.
User2,
/// Indicate using a custom signal.
///
/// Internally, this is converted to a [`nix::Signal`](https://docs.rs/nix/*/nix/sys/signal/enum.Signal.html)
/// but for portability this variant is a raw `i32`.
///
/// Invalid signals on the current platform will be ignored. Does nothing on Windows.
///
/// # Examples
///
/// ```
/// # // we don't have a direct nix dependency, so we fake it... rather horribly
/// # mod nix { pub mod sys { pub mod signal {
/// # #[cfg(unix)] pub use command_group::Signal;
/// # #[cfg(not(unix))] #[repr(i32)] pub enum Signal { SIGABRT = 6 }
/// # } } }
/// use watchexec::signal::process::SubSignal;
/// use nix::sys::signal::Signal;
/// assert_eq!(SubSignal::Custom(6), SubSignal::from(Signal::SIGABRT as i32));
/// ```
///
/// On Unix the [`from_nix`][SubSignal::from_nix] method should be preferred if converting from
/// Nix's `Signal` type:
///
/// ```
/// # #[cfg(unix)]
/// # {
/// # // we don't have a direct nix dependency, so we fake it... rather horribly
/// # mod nix { pub mod sys { pub mod signal { pub use command_group::Signal; } } }
/// use watchexec::signal::process::SubSignal;
/// use nix::sys::signal::Signal;
/// assert_eq!(SubSignal::Custom(6), SubSignal::from_nix(Signal::SIGABRT));
/// # }
/// ```
Custom(i32),
}
impl SubSignal {
/// Converts to a [`nix::Signal`][command_group::Signal] if possible.
///
/// This will return `None` if the signal is not supported on the current platform (only for
/// [`Custom`][SubSignal::Custom], as the first-class ones are always supported).
#[cfg(unix)]
#[must_use]
pub fn to_nix(self) -> Option<NixSignal> {
match self {
Self::Hangup => Some(NixSignal::SIGHUP),
Self::ForceStop => Some(NixSignal::SIGKILL),
Self::Interrupt => Some(NixSignal::SIGINT),
Self::Quit => Some(NixSignal::SIGQUIT),
Self::Terminate => Some(NixSignal::SIGTERM),
Self::User1 => Some(NixSignal::SIGUSR1),
Self::User2 => Some(NixSignal::SIGUSR2),
Self::Custom(sig) => NixSignal::try_from(sig).ok(),
}
}
/// Converts from a [`nix::Signal`][command_group::Signal].
#[cfg(unix)]
#[allow(clippy::missing_const_for_fn)]
#[must_use]
pub fn from_nix(sig: NixSignal) -> Self {
match sig {
NixSignal::SIGHUP => Self::Hangup,
NixSignal::SIGKILL => Self::ForceStop,
NixSignal::SIGINT => Self::Interrupt,
NixSignal::SIGQUIT => Self::Quit,
NixSignal::SIGTERM => Self::Terminate,
NixSignal::SIGUSR1 => Self::User1,
NixSignal::SIGUSR2 => Self::User2,
sig => Self::Custom(sig as _),
}
}
}
impl From<MainSignal> for SubSignal {
fn from(main: MainSignal) -> Self {
match main {
MainSignal::Hangup => Self::Hangup,
MainSignal::Interrupt => Self::Interrupt,
MainSignal::Quit => Self::Quit,
MainSignal::Terminate => Self::Terminate,
MainSignal::User1 => Self::User1,
MainSignal::User2 => Self::User2,
}
}
}
impl From<i32> for SubSignal {
/// Converts from a raw signal number.
///
/// This uses hardcoded numbers for the first-class signals.
fn from(raw: i32) -> Self {
match raw {
1 => Self::Hangup,
2 => Self::Interrupt,
3 => Self::Quit,
9 => Self::ForceStop,
10 => Self::User1,
12 => Self::User2,
15 => Self::Terminate,
_ => Self::Custom(raw),
}
}
}
impl FromStr for SubSignal {
type Err = SignalParseError;
#[cfg(unix)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(sig) = i32::from_str(s) {
if let Ok(sig) = NixSignal::try_from(sig) {
return Ok(Self::from_nix(sig));
}
}
if let Ok(sig) = NixSignal::from_str(&s.to_ascii_uppercase())
.or_else(|_| NixSignal::from_str(&format!("SIG{}", s.to_ascii_uppercase())))
{
return Ok(Self::from_nix(sig));
}
Err(SignalParseError::new(s, "unsupported signal"))
}
#[cfg(windows)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_uppercase().as_str() {
"CTRL-CLOSE" | "CTRL+CLOSE" | "CLOSE" => Ok(Self::Hangup),
"CTRL-BREAK" | "CTRL+BREAK" | "BREAK" => Ok(Self::Terminate),
"CTRL-C" | "CTRL+C" | "C" => Ok(Self::Interrupt),
"KILL" | "SIGKILL" | "FORCE-STOP" | "STOP" => Ok(Self::ForceStop),
_ => Err(SignalParseError::new(s, "unknown control name")),
}
}
#[cfg(not(any(unix, windows)))]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Err(SignalParseError::new(s, "no signals supported"))
}
}