~zethra/signal-child

bf2fa0883ed9d0d4dfe2cee771d755d8710d9547 — Ben Aaron Goldberg 5 months ago
Initial commit
7 files changed, 500 insertions(+), 0 deletions(-)

A .gitignore
A Cargo.toml
A README.md
A rustfmt.toml
A src/error.rs
A src/lib.rs
A src/signal.rs
A  => .gitignore +2 -0
@@ 1,2 @@
/target
Cargo.lock

A  => Cargo.toml +9 -0
@@ 1,9 @@
[package]
name = "signal-child"
version = "0.1.0"
authors = ["Ben Aaron Goldberg <ben@benaaron.dev>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

A  => README.md +18 -0
@@ 1,18 @@
# signal-child

A little library to easily signal other process with no dependencies.

## 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");
// Sing SIGINT to the child.
child.interrupt().expect("Error interrupting child");
```

A  => rustfmt.toml +1 -0
@@ 1,1 @@
max_width = 80
\ No newline at end of file

A  => src/error.rs +13 -0
@@ 1,13 @@
use std::error::Error;
use std::fmt::{self, Display, Formatter};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidSignal;

impl Display for InvalidSignal {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "Invalid signal value")
    }
}

impl Error for InvalidSignal {}

A  => src/lib.rs +115 -0
@@ 1,115 @@
//! A little library to easily signal other process with no dependencies.
//!
//! ## 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");
//! // Sing SIGINT to the child.
//! child.interrupt().expect("Error interrupting child");
//! ```

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`].
pub trait Signalable {
    /// Signal the thing
    fn signal(&self, signal: Signal) -> io::Result<()>;

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

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

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

impl Signalable for Child {
    fn signal(&self, signal: Signal) -> io::Result<()> {
        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 errval = Err(InvalidSignal);
        assert_eq!("NOSIGNAL".parse::<Signal>(), errval);
        assert_eq!("kill".parse::<Signal>(), errval);
        assert_eq!("9".parse::<Signal>(), errval);
    }

    #[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);
    }
}

A  => src/signal.rs +342 -0
@@ 1,342 @@
//! This signal module is mostly take from the
//! [nix](https://crates.io/crates/nix) crate.
use std::str::FromStr;
use std::{convert::TryFrom, fmt, mem};

#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Signal {
    SIGHUP,
    SIGINT,
    SIGQUIT,
    SIGILL,
    SIGTRAP,
    SIGABRT,
    SIGBUS,
    SIGFPE,
    SIGKILL,
    SIGUSR1,
    SIGSEGV,
    SIGUSR2,
    SIGPIPE,
    SIGALRM,
    SIGTERM,
    #[cfg(all(
        any(
            target_os = "android",
            target_os = "emscripten",
            target_os = "fuchsia",
            target_os = "linux"
        ),
        not(any(
            target_arch = "mips",
            target_arch = "mips64",
            target_arch = "sparc64"
        ))
    ))]
    SIGSTKFLT,
    SIGCHLD,
    SIGCONT,
    SIGSTOP,
    SIGTSTP,
    SIGTTIN,
    SIGTTOU,
    SIGURG,
    SIGXCPU,
    SIGXFSZ,
    SIGVTALRM,
    SIGPROF,
    SIGWINCH,
    SIGIO,
    #[cfg(any(
        target_os = "android",
        target_os = "emscripten",
        target_os = "fuchsia",
        target_os = "linux"
    ))]
    SIGPWR,
    SIGSYS,
    #[cfg(not(any(
        target_os = "android",
        target_os = "emscripten",
        target_os = "fuchsia",
        target_os = "linux",
        target_os = "redox"
    )))]
    SIGEMT,
    #[cfg(not(any(
        target_os = "android",
        target_os = "emscripten",
        target_os = "fuchsia",
        target_os = "linux",
        target_os = "redox"
    )))]
    SIGINFO,
}

impl FromStr for Signal {
    type Err = InvalidSignal;
    fn from_str(s: &str) -> Result<Signal, InvalidSignal> {
        Ok(match s {
            "SIGHUP" => Signal::SIGHUP,
            "SIGINT" => Signal::SIGINT,
            "SIGQUIT" => Signal::SIGQUIT,
            "SIGILL" => Signal::SIGILL,
            "SIGTRAP" => Signal::SIGTRAP,
            "SIGABRT" => Signal::SIGABRT,
            "SIGBUS" => Signal::SIGBUS,
            "SIGFPE" => Signal::SIGFPE,
            "SIGKILL" => Signal::SIGKILL,
            "SIGUSR1" => Signal::SIGUSR1,
            "SIGSEGV" => Signal::SIGSEGV,
            "SIGUSR2" => Signal::SIGUSR2,
            "SIGPIPE" => Signal::SIGPIPE,
            "SIGALRM" => Signal::SIGALRM,
            "SIGTERM" => Signal::SIGTERM,
            #[cfg(all(
                any(
                    target_os = "android",
                    target_os = "emscripten",
                    target_os = "fuchsia",
                    target_os = "linux"
                ),
                not(any(
                    target_arch = "mips",
                    target_arch = "mips64",
                    target_arch = "sparc64"
                ))
            ))]
            "SIGSTKFLT" => Signal::SIGSTKFLT,
            "SIGCHLD" => Signal::SIGCHLD,
            "SIGCONT" => Signal::SIGCONT,
            "SIGSTOP" => Signal::SIGSTOP,
            "SIGTSTP" => Signal::SIGTSTP,
            "SIGTTIN" => Signal::SIGTTIN,
            "SIGTTOU" => Signal::SIGTTOU,
            "SIGURG" => Signal::SIGURG,
            "SIGXCPU" => Signal::SIGXCPU,
            "SIGXFSZ" => Signal::SIGXFSZ,
            "SIGVTALRM" => Signal::SIGVTALRM,
            "SIGPROF" => Signal::SIGPROF,
            "SIGWINCH" => Signal::SIGWINCH,
            "SIGIO" => Signal::SIGIO,
            #[cfg(any(
                target_os = "android",
                target_os = "emscripten",
                target_os = "fuchsia",
                target_os = "linux"
            ))]
            "SIGPWR" => Signal::SIGPWR,
            "SIGSYS" => Signal::SIGSYS,
            #[cfg(not(any(
                target_os = "android",
                target_os = "emscripten",
                target_os = "fuchsia",
                target_os = "linux",
                target_os = "redox"
            )))]
            "SIGEMT" => Signal::SIGEMT,
            #[cfg(not(any(
                target_os = "android",
                target_os = "emscripten",
                target_os = "fuchsia",
                target_os = "linux",
                target_os = "redox"
            )))]
            "SIGINFO" => Signal::SIGINFO,
            _ => return Err(InvalidSignal),
        })
    }
}

impl Signal {
    /// Returns name of signal.
    ///
    /// This function is equivalent to `<Signal as AsRef<str>>::as_ref()`,
    /// with difference that returned string is `'static`
    /// and not bound to `self`'s lifetime.
    pub fn as_str(self) -> &'static str {
        match self {
            Signal::SIGHUP => "SIGHUP",
            Signal::SIGINT => "SIGINT",
            Signal::SIGQUIT => "SIGQUIT",
            Signal::SIGILL => "SIGILL",
            Signal::SIGTRAP => "SIGTRAP",
            Signal::SIGABRT => "SIGABRT",
            Signal::SIGBUS => "SIGBUS",
            Signal::SIGFPE => "SIGFPE",
            Signal::SIGKILL => "SIGKILL",
            Signal::SIGUSR1 => "SIGUSR1",
            Signal::SIGSEGV => "SIGSEGV",
            Signal::SIGUSR2 => "SIGUSR2",
            Signal::SIGPIPE => "SIGPIPE",
            Signal::SIGALRM => "SIGALRM",
            Signal::SIGTERM => "SIGTERM",
            #[cfg(all(
                any(
                    target_os = "android",
                    target_os = "emscripten",
                    target_os = "fuchsia",
                    target_os = "linux"
                ),
                not(any(
                    target_arch = "mips",
                    target_arch = "mips64",
                    target_arch = "sparc64"
                ))
            ))]
            Signal::SIGSTKFLT => "SIGSTKFLT",
            Signal::SIGCHLD => "SIGCHLD",
            Signal::SIGCONT => "SIGCONT",
            Signal::SIGSTOP => "SIGSTOP",
            Signal::SIGTSTP => "SIGTSTP",
            Signal::SIGTTIN => "SIGTTIN",
            Signal::SIGTTOU => "SIGTTOU",
            Signal::SIGURG => "SIGURG",
            Signal::SIGXCPU => "SIGXCPU",
            Signal::SIGXFSZ => "SIGXFSZ",
            Signal::SIGVTALRM => "SIGVTALRM",
            Signal::SIGPROF => "SIGPROF",
            Signal::SIGWINCH => "SIGWINCH",
            Signal::SIGIO => "SIGIO",
            #[cfg(any(
                target_os = "android",
                target_os = "emscripten",
                target_os = "fuchsia",
                target_os = "linux"
            ))]
            Signal::SIGPWR => "SIGPWR",
            Signal::SIGSYS => "SIGSYS",
            #[cfg(not(any(
                target_os = "android",
                target_os = "emscripten",
                target_os = "fuchsia",
                target_os = "linux",
                target_os = "redox"
            )))]
            Signal::SIGEMT => "SIGEMT",
            #[cfg(not(any(
                target_os = "android",
                target_os = "emscripten",
                target_os = "fuchsia",
                target_os = "linux",
                target_os = "redox"
            )))]
            Signal::SIGINFO => "SIGINFO",
        }
    }
}

impl AsRef<str> for Signal {
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}

impl fmt::Display for Signal {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(self.as_ref())
    }
}

use crate::error::InvalidSignal;

pub use self::Signal::*;

#[cfg(target_os = "redox")]
const SIGNALS: [Signal; 29] = [
    SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL,
    SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT,
    SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM,
    SIGPROF, SIGWINCH, SIGIO, SIGSYS,
];
#[cfg(all(
    any(
        target_os = "linux",
        target_os = "android",
        target_os = "emscripten",
        target_os = "fuchsia"
    ),
    not(any(
        target_arch = "mips",
        target_arch = "mips64",
        target_arch = "sparc64"
    ))
))]
const SIGNALS: [Signal; 31] = [
    SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL,
    SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGSTKFLT, SIGCHLD,
    SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ,
    SIGVTALRM, SIGPROF, SIGWINCH, SIGIO, SIGPWR, SIGSYS,
];
#[cfg(all(
    any(
        target_os = "linux",
        target_os = "android",
        target_os = "emscripten",
        target_os = "fuchsia"
    ),
    any(target_arch = "mips", target_arch = "mips64", target_arch = "sparc64")
))]
const SIGNALS: [Signal; 30] = [
    SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL,
    SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT,
    SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM,
    SIGPROF, SIGWINCH, SIGIO, SIGPWR, SIGSYS,
];
#[cfg(not(any(
    target_os = "linux",
    target_os = "android",
    target_os = "fuchsia",
    target_os = "emscripten",
    target_os = "redox"
)))]
const SIGNALS: [Signal; 31] = [
    SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL,
    SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT,
    SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM,
    SIGPROF, SIGWINCH, SIGIO, SIGSYS, SIGEMT, SIGINFO,
];

pub const NSIG: i32 = 32;

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct SignalIterator {
    next: usize,
}

impl Iterator for SignalIterator {
    type Item = Signal;

    fn next(&mut self) -> Option<Signal> {
        if self.next < SIGNALS.len() {
            let next_signal = SIGNALS[self.next];
            self.next += 1;
            Some(next_signal)
        } else {
            None
        }
    }
}

impl Signal {
    pub fn iterator() -> SignalIterator {
        SignalIterator { next: 0 }
    }
}

impl TryFrom<i32> for Signal {
    type Error = InvalidSignal;

    fn try_from(signum: i32) -> Result<Signal, InvalidSignal> {
        if 0 < signum && signum < NSIG {
            Ok(unsafe { mem::transmute(signum) })
        } else {
            Err(InvalidSignal)
        }
    }
}

pub const SIGIOT: Signal = SIGABRT;
pub const SIGPOLL: Signal = SIGIO;
pub const SIGUNUSED: Signal = SIGSYS;