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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! Extends and wraps `std::process::Child` to make it clonable. Thus eliminating the problem that
//! `libstd` does not have a cross platfrom and simple way to kill a child while also waiting for
//! it.
//!
//! ## Getting Started
//!
//! Add the dependency to your Cargo.toml:
//!
//! ```toml
//! [dependencies]
//! clonablechild = "0.1"
//! ```
//!
//! ## Example
//!
//! Use it in your program to kill a sleep process before it terminates naturally:
//!
//! ```rust,ignore
//! extern crate clonablechild;
//!
//! use clonablechild::{ChildExt, ClonableChild};
//!
//! use std::process::Command;
//! use std::thread;
//! use std::time::Duration;
//!
//! fn main() {
//!     // This command is specific to unix systems. See tests for Windows examples.
//!     let child = Command::new("sleep").arg("10").spawn().unwrap();
//!     let clonable_child = child.into_clonable();
//!
//!     kill_async(clonable_child.clone());
//!     let exit_status = clonable_child.wait().unwrap();
//!
//!     // Assert child was killed by a signal and did not exit cleanly
//!     assert_eq!(None, exit_status.code());
//!     assert!(!exit_status.success());
//! }
//!
//! fn kill_async(child: ClonableChild) {
//!     thread::spawn(move || {
//!         thread::sleep(Duration::new(1, 0));
//!         child.kill().expect("Expected to be able to kill subprocess");
//!     });
//! }
//! ```

#![deny(missing_docs)]

extern crate libc;

use std::io;
use std::process::{Child, ExitStatus, ChildStdin, ChildStdout, ChildStderr};
use std::sync::{Arc, Mutex};

#[cfg(unix)]
#[path = "unix.rs"]
mod imp;

#[cfg(windows)]
#[path = "windows.rs"]
mod imp;


/// A trait that extends `Child` with a method to convert it into a `ClonableChild`.
pub trait ChildExt {
    /// Consumes the child and wraps it in a `ClonableChild`. Results in an object with a similar
    /// API, but that can be cloned.
    fn into_clonable(self) -> ClonableChild;
}

impl ChildExt for Child {
    fn into_clonable(self) -> ClonableChild {
        ClonableChild::new(self)
    }
}

struct ChildIo {
    pub stdin: Option<ChildStdin>,
    pub stdout: Option<ChildStdout>,
    pub stderr: Option<ChildStderr>,
}

impl ChildIo {
    pub fn new(child: &mut Child) -> Self {
        ChildIo {
            stdin: child.stdin.take(),
            stdout: child.stdout.take(),
            stderr: child.stderr.take(),
        }
    }
}

/// Representation of a clonable `std::process::Child`.
#[derive(Clone)]
pub struct ClonableChild {
    id: u32,
    child: Arc<Mutex<Child>>,
    imp_child: imp::ClonableChild,
    io: Arc<Mutex<ChildIo>>,
}

impl ClonableChild {
    /// Creates a new `ClonableChild` by consuming and wrapping the given `Child`.
    pub fn new(mut child: Child) -> Self {
        let imp_child = imp::ClonableChild::new(&child);
        let io = Arc::new(Mutex::new(ChildIo::new(&mut child)));

        ClonableChild {
            id: child.id(),
            child: Arc::new(Mutex::new(child)),
            imp_child: imp_child,
            io: io,
        }
    }

    /// Forces the child to exit. This is equivalent to sending a SIGKILL on unix platforms and
    /// calling TerminateProcess on Windows.
    ///
    /// This method first tries to use the ordinary `Child::kill()`, but if that is blocked by
    /// another thread waiting for the child it will kill it itself in the same way `Child::kill()`
    /// would have done.
    pub fn kill(&self) -> io::Result<()> {
        let child = self.child.try_lock();
        match child {
            Ok(mut child) => child.kill(),
            Err(..) => self.imp_child.kill(),
        }
    }

    /// Returns the OS-assigned process identifier associated with this child. This value is
    /// obtained from `Child::id()` in `ClonableChild::new()` and then that value is returned every
    /// time.
    pub fn id(&self) -> u32 {
        self.id
    }

    /// Behaves just like `Child::wait()`, see documentation for that method.
    pub fn wait(&self) -> io::Result<ExitStatus> {
        let mut child = self.child.lock().unwrap();
        child.wait()
    }

    /// Retrieve the stdin stream from the child if one exist. Will only return something on the
    /// first call.
    pub fn stdin(&mut self) -> Option<ChildStdin> {
        self.io.lock().unwrap().stdin.take()
    }

    /// Retrieve the stdout stream from the child if one exist. Will only return something on the
    /// first call.
    pub fn stdout(&mut self) -> Option<ChildStdout> {
        self.io.lock().unwrap().stdout.take()
    }

    /// Retrieve the stderr stream from the child if one exist. Will only return something on the
    /// first call.
    pub fn stderr(&mut self) -> Option<ChildStderr> {
        self.io.lock().unwrap().stderr.take()
    }
}