1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! A little library to easily signal other process with no dependencies.
//!
//! This is essentially a wrapper around `kill(3)` on Unix-like systems. As such
//! this crate only supports Unix-like systems.
//!
//! ## Example
//!
//! ```rust
//! use std::process::Command;
//! use signal_child::Signalable;
//!
//! // Spawn child process
//! let mut child = Command::new("sleep")
//!     .arg("1000")
//!     .spawn()
//!     .expect("Error spawning sleep process");
//! // Send SIGINT to the child.
//! child.interrupt().expect("Error interrupting child");
//! child.wait().ok();
//! ```

mod error;
pub mod signal;

use std::io;
use std::process::Child;

pub use error::InvalidSignal;
use signal::Signal;

extern "C" {
    fn kill(pid: i32, sig: Signal) -> i32;
}

/// Signal the process `pid`
pub fn signal(pid: i32, signal: Signal) -> io::Result<()> {
    let ret = unsafe { kill(pid, signal) };
    if ret == 0 {
        Ok(())
    } else {
        Err(io::Error::last_os_error())
    }
}

/// Trait for things that can be signaled, mainly [`Child`].
///
/// ## Example
///
/// ```no_run
/// use std::process::Command;
/// use signal_child::{Signalable, signal};
///
/// // Spawn child process
/// let mut child = Command::new("sleep")
///     .arg("1000")
///     .spawn()
///     .expect("Error spawning sleep process");
/// // Send SIGUSR1 to the child.
/// child.signal(signal::SIGUSR1).expect("Error signaling child");
/// ```
pub trait Signalable {
    /// Signal the thing
    fn signal(&mut self, signal: Signal) -> io::Result<()>;

    /// Send SIGTERM
    fn term(&mut self) -> io::Result<()> {
        self.signal(Signal::SIGTERM)
    }

    /// Send SIGINT
    fn interrupt(&mut self) -> io::Result<()> {
        self.signal(Signal::SIGINT)
    }

    /// Send SIGHUP
    fn hangup(&mut self) -> io::Result<()> {
        self.signal(Signal::SIGHUP)
    }
}

impl Signalable for Child {
    fn signal(&mut self, signal: Signal) -> io::Result<()> {
        if self.try_wait()?.is_some() {
            Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "invalid argument: can't signal an exited process",
            ))
        } else {
            crate::signal(self.id() as i32, signal)
        }
    }
}

#[cfg(test)]
mod tests {
    use std::{
        process::Command,
        time::{Duration, Instant},
    };

    use crate::error::InvalidSignal;

    use super::*;

    #[test]
    fn test_from_str_round_trips() {
        for signal in Signal::iterator() {
            assert_eq!(signal.as_ref().parse::<Signal>().unwrap(), signal);
            assert_eq!(signal.to_string().parse::<Signal>().unwrap(), signal);
        }
    }

    #[test]
    fn test_from_str_invalid_value() {
        let err_val = Err(InvalidSignal);
        assert_eq!("NOSIGNAL".parse::<Signal>(), err_val);
        assert_eq!("kill".parse::<Signal>(), err_val);
        assert_eq!("9".parse::<Signal>(), err_val);
    }

    #[test]
    fn test_termite_child() {
        let mut child = Command::new("sleep")
            .arg("1000")
            .spawn()
            .expect("Error spawning sleep process");
        if let Ok(Some(_)) | Err(_) = child.try_wait() {
            panic!("Child exited too early");
        }
        assert!(child.interrupt().is_ok());
        let to_wait = Duration::from_millis(500);
        let start = Instant::now();
        while child.try_wait().unwrap().is_none() {
            std::thread::sleep(Duration::from_millis(10));
            if start.elapsed() > to_wait {
                panic!("Sleep process didn't exit");
            }
        }
        assert_eq!(child.try_wait().unwrap().unwrap().success(), false);
    }
}