1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(test), warn(unused_crate_dependencies))]
3
4use std::fmt;
5
6#[cfg(feature = "fromstr")]
7use std::str::FromStr;
8
9#[cfg(unix)]
10use nix::sys::signal::Signal as NixSignal;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
24#[non_exhaustive]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26#[cfg_attr(
27 feature = "serde",
28 serde(
29 from = "serde_support::SerdeSignal",
30 into = "serde_support::SerdeSignal"
31 )
32)]
33pub enum Signal {
34 Hangup,
41
42 ForceStop,
49
50 Interrupt,
58
59 Quit,
65
66 Terminate,
74
75 User1,
81
82 User2,
88
89 Custom(i32),
123}
124
125impl Signal {
126 #[cfg(unix)]
131 #[must_use]
132 pub fn to_nix(self) -> Option<NixSignal> {
133 match self {
134 Self::Hangup => Some(NixSignal::SIGHUP),
135 Self::ForceStop => Some(NixSignal::SIGKILL),
136 Self::Interrupt => Some(NixSignal::SIGINT),
137 Self::Quit => Some(NixSignal::SIGQUIT),
138 Self::Terminate => Some(NixSignal::SIGTERM),
139 Self::User1 => Some(NixSignal::SIGUSR1),
140 Self::User2 => Some(NixSignal::SIGUSR2),
141 Self::Custom(sig) => NixSignal::try_from(sig).ok(),
142 }
143 }
144
145 #[cfg(unix)]
147 #[allow(clippy::missing_const_for_fn)]
148 #[must_use]
149 pub fn from_nix(sig: NixSignal) -> Self {
150 match sig {
151 NixSignal::SIGHUP => Self::Hangup,
152 NixSignal::SIGKILL => Self::ForceStop,
153 NixSignal::SIGINT => Self::Interrupt,
154 NixSignal::SIGQUIT => Self::Quit,
155 NixSignal::SIGTERM => Self::Terminate,
156 NixSignal::SIGUSR1 => Self::User1,
157 NixSignal::SIGUSR2 => Self::User2,
158 sig => Self::Custom(sig as _),
159 }
160 }
161}
162
163impl From<i32> for Signal {
164 fn from(raw: i32) -> Self {
168 match raw {
169 1 => Self::Hangup,
170 2 => Self::Interrupt,
171 3 => Self::Quit,
172 9 => Self::ForceStop,
173 10 => Self::User1,
174 12 => Self::User2,
175 15 => Self::Terminate,
176 _ => Self::Custom(raw),
177 }
178 }
179}
180
181#[cfg(feature = "fromstr")]
182impl Signal {
183 pub fn from_unix_str(s: &str) -> Result<Self, SignalParseError> {
202 Self::from_unix_str_impl(s)
203 }
204
205 #[cfg(unix)]
206 fn from_unix_str_impl(s: &str) -> Result<Self, SignalParseError> {
207 if let Ok(sig) = i32::from_str(s) {
208 if let Ok(sig) = NixSignal::try_from(sig) {
209 return Ok(Self::from_nix(sig));
210 }
211 }
212
213 if let Ok(sig) = NixSignal::from_str(&s.to_ascii_uppercase())
214 .or_else(|_| NixSignal::from_str(&format!("SIG{}", s.to_ascii_uppercase())))
215 {
216 return Ok(Self::from_nix(sig));
217 }
218
219 Err(SignalParseError::new(s, "unsupported signal"))
220 }
221
222 #[cfg(not(unix))]
223 fn from_unix_str_impl(s: &str) -> Result<Self, SignalParseError> {
224 match s.to_ascii_uppercase().as_str() {
225 "KILL" | "SIGKILL" | "9" => Ok(Self::ForceStop),
226 "HUP" | "SIGHUP" | "1" => Ok(Self::Hangup),
227 "INT" | "SIGINT" | "2" => Ok(Self::Interrupt),
228 "QUIT" | "SIGQUIT" | "3" => Ok(Self::Quit),
229 "TERM" | "SIGTERM" | "15" => Ok(Self::Terminate),
230 "USR1" | "SIGUSR1" | "10" => Ok(Self::User1),
231 "USR2" | "SIGUSR2" | "12" => Ok(Self::User2),
232 number => match i32::from_str(number) {
233 Ok(int) => Ok(Self::Custom(int)),
234 Err(_) => Err(SignalParseError::new(s, "unsupported signal")),
235 },
236 }
237 }
238
239 pub fn from_windows_str(s: &str) -> Result<Self, SignalParseError> {
261 match s.to_ascii_uppercase().as_str() {
262 "CTRL-CLOSE" | "CTRL+CLOSE" | "CLOSE" => Ok(Self::Hangup),
263 "CTRL-BREAK" | "CTRL+BREAK" | "BREAK" => Ok(Self::Terminate),
264 "CTRL-C" | "CTRL+C" | "C" => Ok(Self::Interrupt),
265 "KILL" | "SIGKILL" | "FORCE-STOP" | "STOP" => Ok(Self::ForceStop),
266 _ => Err(SignalParseError::new(s, "unknown control name")),
267 }
268 }
269}
270
271#[cfg(feature = "fromstr")]
272impl FromStr for Signal {
273 type Err = SignalParseError;
274
275 fn from_str(s: &str) -> Result<Self, Self::Err> {
276 Self::from_windows_str(s).or_else(|err| Self::from_unix_str(s).map_err(|_| err))
277 }
278}
279
280#[cfg(feature = "fromstr")]
282#[cfg_attr(feature = "miette", derive(miette::Diagnostic))]
283#[derive(Debug, thiserror::Error)]
284#[error("invalid signal `{src}`: {err}")]
285pub struct SignalParseError {
286 #[cfg_attr(feature = "miette", source_code)]
288 src: String,
289
290 err: String,
292
293 #[cfg_attr(feature = "miette", label = "invalid signal")]
295 span: (usize, usize),
296}
297
298#[cfg(feature = "fromstr")]
299impl SignalParseError {
300 #[must_use]
301 pub fn new(src: &str, err: &str) -> Self {
302 Self {
303 src: src.to_owned(),
304 err: err.to_owned(),
305 span: (0, src.len()),
306 }
307 }
308}
309
310impl fmt::Display for Signal {
311 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312 write!(
313 f,
314 "{}",
315 match (self, cfg!(windows)) {
316 (Self::Hangup, false) => "SIGHUP",
317 (Self::Hangup, true) => "CTRL-CLOSE",
318 (Self::ForceStop, false) => "SIGKILL",
319 (Self::ForceStop, true) => "STOP",
320 (Self::Interrupt, false) => "SIGINT",
321 (Self::Interrupt, true) => "CTRL-C",
322 (Self::Quit, _) => "SIGQUIT",
323 (Self::Terminate, false) => "SIGTERM",
324 (Self::Terminate, true) => "CTRL-BREAK",
325 (Self::User1, _) => "SIGUSR1",
326 (Self::User2, _) => "SIGUSR2",
327 (Self::Custom(n), _) => {
328 return write!(f, "{n}");
329 }
330 }
331 )
332 }
333}
334
335#[cfg(feature = "serde")]
336mod serde_support {
337 use super::Signal;
338
339 #[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
340 #[serde(untagged)]
341 pub enum SerdeSignal {
342 Named(NamedSignal),
343 Number(i32),
344 }
345
346 #[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
347 #[serde(rename_all = "kebab-case")]
348 pub enum NamedSignal {
349 #[serde(rename = "SIGHUP")]
350 Hangup,
351 #[serde(rename = "SIGKILL")]
352 ForceStop,
353 #[serde(rename = "SIGINT")]
354 Interrupt,
355 #[serde(rename = "SIGQUIT")]
356 Quit,
357 #[serde(rename = "SIGTERM")]
358 Terminate,
359 #[serde(rename = "SIGUSR1")]
360 User1,
361 #[serde(rename = "SIGUSR2")]
362 User2,
363 }
364
365 impl From<Signal> for SerdeSignal {
366 fn from(signal: Signal) -> Self {
367 match signal {
368 Signal::Hangup => Self::Named(NamedSignal::Hangup),
369 Signal::Interrupt => Self::Named(NamedSignal::Interrupt),
370 Signal::Quit => Self::Named(NamedSignal::Quit),
371 Signal::Terminate => Self::Named(NamedSignal::Terminate),
372 Signal::User1 => Self::Named(NamedSignal::User1),
373 Signal::User2 => Self::Named(NamedSignal::User2),
374 Signal::ForceStop => Self::Named(NamedSignal::ForceStop),
375 Signal::Custom(number) => Self::Number(number),
376 }
377 }
378 }
379
380 impl From<SerdeSignal> for Signal {
381 fn from(signal: SerdeSignal) -> Self {
382 match signal {
383 SerdeSignal::Named(NamedSignal::Hangup) => Self::Hangup,
384 SerdeSignal::Named(NamedSignal::ForceStop) => Self::ForceStop,
385 SerdeSignal::Named(NamedSignal::Interrupt) => Self::Interrupt,
386 SerdeSignal::Named(NamedSignal::Quit) => Self::Quit,
387 SerdeSignal::Named(NamedSignal::Terminate) => Self::Terminate,
388 SerdeSignal::Named(NamedSignal::User1) => Self::User1,
389 SerdeSignal::Named(NamedSignal::User2) => Self::User2,
390 SerdeSignal::Number(number) => Self::Custom(number),
391 }
392 }
393 }
394}