This is a re-landing of #8645, except that the bindings are *not* being used to power std::run just yet. Instead, this adds the bindings as standalone bindings inside the rt::io::process module. I made one major change from before, having to do with how pipes are created/bound. It's much clearer now when you can read/write to a pipe, as there's an explicit difference (different types) between an unbound and a bound pipe. The process configuration now takes unbound pipes (and consumes ownership of them), and will return corresponding pipe structures back if spawning is successful (otherwise everything is destroyed normally).
219 lines
7.6 KiB
Rust
219 lines
7.6 KiB
Rust
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
|
// file at the top-level directory of this distribution and at
|
|
// http://rust-lang.org/COPYRIGHT.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
// option. This file may not be copied, modified, or distributed
|
|
// except according to those terms.
|
|
|
|
use prelude::*;
|
|
use cell::Cell;
|
|
use libc;
|
|
use ptr;
|
|
use util;
|
|
use vec;
|
|
|
|
use rt::io::process::*;
|
|
use rt::uv;
|
|
use rt::uv::uvio::UvPipeStream;
|
|
use rt::uv::uvll;
|
|
|
|
/// A process wraps the handle of the underlying uv_process_t.
|
|
pub struct Process(*uvll::uv_process_t);
|
|
|
|
impl uv::Watcher for Process {}
|
|
|
|
impl Process {
|
|
/// Creates a new process, ready to spawn inside an event loop
|
|
pub fn new() -> Process {
|
|
let handle = unsafe { uvll::malloc_handle(uvll::UV_PROCESS) };
|
|
assert!(handle.is_not_null());
|
|
let mut ret: Process = uv::NativeHandle::from_native_handle(handle);
|
|
ret.install_watcher_data();
|
|
return ret;
|
|
}
|
|
|
|
/// Spawn a new process inside the specified event loop.
|
|
///
|
|
/// The `config` variable will be passed down to libuv, and the `exit_cb`
|
|
/// will be run only once, when the process exits.
|
|
///
|
|
/// Returns either the corresponding process object or an error which
|
|
/// occurred.
|
|
pub fn spawn(&mut self, loop_: &uv::Loop, mut config: ProcessConfig,
|
|
exit_cb: uv::ExitCallback)
|
|
-> Result<~[Option<UvPipeStream>], uv::UvError>
|
|
{
|
|
let cwd = config.cwd.map_move(|s| s.to_c_str());
|
|
|
|
extern fn on_exit(p: *uvll::uv_process_t,
|
|
exit_status: libc::c_int,
|
|
term_signal: libc::c_int) {
|
|
let mut p: Process = uv::NativeHandle::from_native_handle(p);
|
|
let err = match exit_status {
|
|
0 => None,
|
|
_ => uv::status_to_maybe_uv_error(-1)
|
|
};
|
|
p.get_watcher_data().exit_cb.take_unwrap()(p,
|
|
exit_status as int,
|
|
term_signal as int,
|
|
err);
|
|
}
|
|
|
|
let io = util::replace(&mut config.io, ~[]);
|
|
let mut stdio = vec::with_capacity::<uvll::uv_stdio_container_t>(io.len());
|
|
let mut ret_io = vec::with_capacity(io.len());
|
|
unsafe {
|
|
vec::raw::set_len(&mut stdio, io.len());
|
|
for (slot, other) in stdio.iter().zip(io.move_iter()) {
|
|
let io = set_stdio(slot as *uvll::uv_stdio_container_t, other);
|
|
ret_io.push(io);
|
|
}
|
|
}
|
|
|
|
let exit_cb = Cell::new(exit_cb);
|
|
let ret_io = Cell::new(ret_io);
|
|
do with_argv(config.program, config.args) |argv| {
|
|
do with_env(config.env) |envp| {
|
|
let options = uvll::uv_process_options_t {
|
|
exit_cb: on_exit,
|
|
file: unsafe { *argv },
|
|
args: argv,
|
|
env: envp,
|
|
cwd: match cwd {
|
|
Some(ref cwd) => cwd.with_ref(|p| p),
|
|
None => ptr::null(),
|
|
},
|
|
flags: 0,
|
|
stdio_count: stdio.len() as libc::c_int,
|
|
stdio: stdio.as_imm_buf(|p, _| p),
|
|
uid: 0,
|
|
gid: 0,
|
|
};
|
|
|
|
match unsafe {
|
|
uvll::spawn(loop_.native_handle(), **self, options)
|
|
} {
|
|
0 => {
|
|
(*self).get_watcher_data().exit_cb = Some(exit_cb.take());
|
|
Ok(ret_io.take())
|
|
}
|
|
err => Err(uv::UvError(err))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Sends a signal to this process.
|
|
///
|
|
/// This is a wrapper around `uv_process_kill`
|
|
pub fn kill(&self, signum: int) -> Result<(), uv::UvError> {
|
|
match unsafe {
|
|
uvll::process_kill(self.native_handle(), signum as libc::c_int)
|
|
} {
|
|
0 => Ok(()),
|
|
err => Err(uv::UvError(err))
|
|
}
|
|
}
|
|
|
|
/// Returns the process id of a spawned process
|
|
pub fn pid(&self) -> libc::pid_t {
|
|
unsafe { uvll::process_pid(**self) as libc::pid_t }
|
|
}
|
|
|
|
/// Closes this handle, invoking the specified callback once closed
|
|
pub fn close(self, cb: uv::NullCallback) {
|
|
{
|
|
let mut this = self;
|
|
let data = this.get_watcher_data();
|
|
assert!(data.close_cb.is_none());
|
|
data.close_cb = Some(cb);
|
|
}
|
|
|
|
unsafe { uvll::close(self.native_handle(), close_cb); }
|
|
|
|
extern fn close_cb(handle: *uvll::uv_process_t) {
|
|
let mut process: Process = uv::NativeHandle::from_native_handle(handle);
|
|
process.get_watcher_data().close_cb.take_unwrap()();
|
|
process.drop_watcher_data();
|
|
unsafe { uvll::free_handle(handle as *libc::c_void) }
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe fn set_stdio(dst: *uvll::uv_stdio_container_t,
|
|
io: StdioContainer) -> Option<UvPipeStream> {
|
|
match io {
|
|
Ignored => {
|
|
uvll::set_stdio_container_flags(dst, uvll::STDIO_IGNORE);
|
|
None
|
|
}
|
|
InheritFd(fd) => {
|
|
uvll::set_stdio_container_flags(dst, uvll::STDIO_INHERIT_FD);
|
|
uvll::set_stdio_container_fd(dst, fd);
|
|
None
|
|
}
|
|
CreatePipe(pipe, readable, writable) => {
|
|
let mut flags = uvll::STDIO_CREATE_PIPE as libc::c_int;
|
|
if readable {
|
|
flags |= uvll::STDIO_READABLE_PIPE as libc::c_int;
|
|
}
|
|
if writable {
|
|
flags |= uvll::STDIO_WRITABLE_PIPE as libc::c_int;
|
|
}
|
|
let handle = pipe.pipe.as_stream().native_handle();
|
|
uvll::set_stdio_container_flags(dst, flags);
|
|
uvll::set_stdio_container_stream(dst, handle);
|
|
Some(pipe.bind())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Converts the program and arguments to the argv array expected by libuv
|
|
fn with_argv<T>(prog: &str, args: &[~str], f: &fn(**libc::c_char) -> T) -> T {
|
|
// First, allocation space to put all the C-strings (we need to have
|
|
// ownership of them somewhere
|
|
let mut c_strs = vec::with_capacity(args.len() + 1);
|
|
c_strs.push(prog.to_c_str());
|
|
for arg in args.iter() {
|
|
c_strs.push(arg.to_c_str());
|
|
}
|
|
|
|
// Next, create the char** array
|
|
let mut c_args = vec::with_capacity(c_strs.len() + 1);
|
|
for s in c_strs.iter() {
|
|
c_args.push(s.with_ref(|p| p));
|
|
}
|
|
c_args.push(ptr::null());
|
|
c_args.as_imm_buf(|buf, _| f(buf))
|
|
}
|
|
|
|
/// Converts the environment to the env array expected by libuv
|
|
fn with_env<T>(env: Option<&[(~str, ~str)]>, f: &fn(**libc::c_char) -> T) -> T {
|
|
let env = match env {
|
|
Some(s) => s,
|
|
None => { return f(ptr::null()); }
|
|
};
|
|
// As with argv, create some temporary storage and then the actual array
|
|
let mut envp = vec::with_capacity(env.len());
|
|
for &(ref key, ref value) in env.iter() {
|
|
envp.push(fmt!("%s=%s", *key, *value).to_c_str());
|
|
}
|
|
let mut c_envp = vec::with_capacity(envp.len() + 1);
|
|
for s in envp.iter() {
|
|
c_envp.push(s.with_ref(|p| p));
|
|
}
|
|
c_envp.push(ptr::null());
|
|
c_envp.as_imm_buf(|buf, _| f(buf))
|
|
}
|
|
|
|
impl uv::NativeHandle<*uvll::uv_process_t> for Process {
|
|
fn from_native_handle(handle: *uvll::uv_process_t) -> Process {
|
|
Process(handle)
|
|
}
|
|
fn native_handle(&self) -> *uvll::uv_process_t {
|
|
match self { &Process(ptr) => ptr }
|
|
}
|
|
}
|