signal_child/
lib.rs

1//! A little library to easily signal other process with no dependencies.
2//!
3//! This is essentially a wrapper around `kill(3)` on Unix-like systems. As such
4//! this crate only supports Unix-like systems.
5//!
6//! ## Example
7//!
8//! ```rust
9//! use std::process::Command;
10//! use signal_child::Signalable;
11//!
12//! // Spawn child process
13//! let mut child = Command::new("sleep")
14//!     .arg("1000")
15//!     .spawn()
16//!     .expect("Error spawning sleep process");
17//! // Send SIGINT to the child.
18//! child.interrupt().expect("Error interrupting child");
19//! child.wait().ok();
20//! ```
21
22mod error;
23pub mod signal;
24
25use std::io;
26use std::process::Child;
27
28pub use error::InvalidSignal;
29use signal::Signal;
30
31extern "C" {
32    fn kill(pid: i32, sig: Signal) -> i32;
33}
34
35/// Signal the process `pid`
36pub fn signal(pid: i32, signal: Signal) -> io::Result<()> {
37    let ret = unsafe { kill(pid, signal) };
38    if ret == 0 {
39        Ok(())
40    } else {
41        Err(io::Error::last_os_error())
42    }
43}
44
45/// Trait for things that can be signaled, mainly [`Child`].
46///
47/// ## Example
48///
49/// ```no_run
50/// use std::process::Command;
51/// use signal_child::{Signalable, signal};
52///
53/// // Spawn child process
54/// let mut child = Command::new("sleep")
55///     .arg("1000")
56///     .spawn()
57///     .expect("Error spawning sleep process");
58/// // Send SIGUSR1 to the child.
59/// child.signal(signal::SIGUSR1).expect("Error signaling child");
60/// ```
61pub trait Signalable {
62    /// Signal the thing
63    fn signal(&mut self, signal: Signal) -> io::Result<()>;
64
65    /// Send SIGTERM
66    fn term(&mut self) -> io::Result<()> {
67        self.signal(Signal::SIGTERM)
68    }
69
70    /// Send SIGINT
71    fn interrupt(&mut self) -> io::Result<()> {
72        self.signal(Signal::SIGINT)
73    }
74
75    /// Send SIGHUP
76    fn hangup(&mut self) -> io::Result<()> {
77        self.signal(Signal::SIGHUP)
78    }
79}
80
81impl Signalable for Child {
82    fn signal(&mut self, signal: Signal) -> io::Result<()> {
83        if self.try_wait()?.is_some() {
84            Err(io::Error::new(
85                io::ErrorKind::InvalidInput,
86                "invalid argument: can't signal an exited process",
87            ))
88        } else {
89            crate::signal(self.id() as i32, signal)
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use std::{
97        process::Command,
98        time::{Duration, Instant},
99    };
100
101    use crate::error::InvalidSignal;
102
103    use super::*;
104
105    #[test]
106    fn test_from_str_round_trips() {
107        for signal in Signal::iterator() {
108            assert_eq!(signal.as_ref().parse::<Signal>().unwrap(), signal);
109            assert_eq!(signal.to_string().parse::<Signal>().unwrap(), signal);
110        }
111    }
112
113    #[test]
114    fn test_from_str_invalid_value() {
115        let err_val = Err(InvalidSignal);
116        assert_eq!("NOSIGNAL".parse::<Signal>(), err_val);
117        assert_eq!("kill".parse::<Signal>(), err_val);
118        assert_eq!("9".parse::<Signal>(), err_val);
119    }
120
121    #[test]
122    fn test_termite_child() {
123        let mut child = Command::new("sleep")
124            .arg("1000")
125            .spawn()
126            .expect("Error spawning sleep process");
127        if let Ok(Some(_)) | Err(_) = child.try_wait() {
128            panic!("Child exited too early");
129        }
130        assert!(child.interrupt().is_ok());
131        let to_wait = Duration::from_millis(500);
132        let start = Instant::now();
133        while child.try_wait().unwrap().is_none() {
134            std::thread::sleep(Duration::from_millis(10));
135            if start.elapsed() > to_wait {
136                panic!("Sleep process didn't exit");
137            }
138        }
139        assert!(!child.try_wait().unwrap().unwrap().success());
140    }
141}