// 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 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Bindings for executing child processes use prelude::*; use libc; use rt::io; use rt::io::io_error; use rt::local::Local; use rt::rtio::{RtioProcess, RtioProcessObject, IoFactoryObject, IoFactory}; pub struct Process { priv handle: ~RtioProcessObject, io: ~[Option], } /// This configuration describes how a new process should be spawned. This is /// translated to libuv's own configuration pub struct ProcessConfig<'self> { /// Path to the program to run program: &'self str, /// Arguments to pass to the program (doesn't include the program itself) args: &'self [~str], /// Optional environment to specify for the program. If this is None, then /// it will inherit the current process's environment. env: Option<&'self [(~str, ~str)]>, /// Optional working directory for the new process. If this is None, then /// the current directory of the running process is inherited. cwd: Option<&'self str>, /// Any number of streams/file descriptors/pipes may be attached to this /// process. This list enumerates the file descriptors and such for the /// process to be spawned, and the file descriptors inherited will start at /// 0 and go to the length of this array. /// /// Standard file descriptors are: /// /// 0 - stdin /// 1 - stdout /// 2 - stderr io: ~[StdioContainer] } /// Describes what to do with a standard io stream for a child process. pub enum StdioContainer { /// This stream will be ignored. This is the equivalent of attaching the /// stream to `/dev/null` Ignored, /// The specified file descriptor is inherited for the stream which it is /// specified for. InheritFd(libc::c_int), // XXX: these two shouldn't have libuv-specific implementation details /// The specified libuv stream is inherited for the corresponding file /// descriptor it is assigned to. // XXX: this needs to be thought out more. //InheritStream(uv::net::StreamWatcher), /// Creates a pipe for the specified file descriptor which will be directed /// into the previously-initialized pipe passed in. /// /// The first boolean argument is whether the pipe is readable, and the /// second is whether it is writable. These properties are from the view of /// the *child* process, not the parent process. CreatePipe(io::UnboundPipeStream, bool /* readable */, bool /* writable */), } impl Process { /// Creates a new pipe initialized, but not bound to any particular /// source/destination pub fn new(config: ProcessConfig) -> Option { let process = unsafe { let io: *mut IoFactoryObject = Local::unsafe_borrow(); (*io).spawn(config) }; match process { Ok((p, io)) => Some(Process{ handle: p, io: io.move_iter().map(|p| p.map_move(|p| io::PipeStream::bind(p)) ).collect() }), Err(ioerr) => { io_error::cond.raise(ioerr); None } } } /// Returns the process id of this child process pub fn id(&self) -> libc::pid_t { self.handle.id() } /// Sends the specified signal to the child process, returning whether the /// signal could be delivered or not. /// /// Note that this is purely a wrapper around libuv's `uv_process_kill` /// function. /// /// If the signal delivery fails, then the `io_error` condition is raised on pub fn signal(&mut self, signal: int) { match self.handle.kill(signal) { Ok(()) => {} Err(err) => { io_error::cond.raise(err) } } } /// Wait for the child to exit completely, returning the status that it /// exited with. This function will continue to have the same return value /// after it has been called at least once. pub fn wait(&mut self) -> int { self.handle.wait() } } impl Drop for Process { fn drop(&mut self) { // Close all I/O before exiting to ensure that the child doesn't wait // forever to print some text or something similar. for _ in range(0, self.io.len()) { self.io.pop(); } self.wait(); } } #[cfg(test)] mod tests { use prelude::*; use super::*; use rt::io::{Reader, Writer}; use rt::io::pipe::*; use str; #[test] #[cfg(unix, not(android))] #[ignore] // FIXME(#9341) fn smoke() { let io = ~[]; let args = ProcessConfig { program: "/bin/sh", args: [~"-c", ~"true"], env: None, cwd: None, io: io, }; let p = Process::new(args); assert!(p.is_some()); let mut p = p.unwrap(); assert_eq!(p.wait(), 0); } #[test] #[cfg(unix, not(android))] #[ignore] // FIXME(#9341) fn smoke_failure() { let io = ~[]; let args = ProcessConfig { program: "if-this-is-a-binary-then-the-world-has-ended", args: [], env: None, cwd: None, io: io, }; let p = Process::new(args); assert!(p.is_some()); let mut p = p.unwrap(); assert!(p.wait() != 0); } #[test] #[cfg(unix, not(android))] #[ignore] // FIXME(#9341) fn exit_reported_right() { let io = ~[]; let args = ProcessConfig { program: "/bin/sh", args: [~"-c", ~"exit 1"], env: None, cwd: None, io: io, }; let p = Process::new(args); assert!(p.is_some()); let mut p = p.unwrap(); assert_eq!(p.wait(), 1); } fn read_all(input: &mut Reader) -> ~str { let mut ret = ~""; let mut buf = [0, ..1024]; loop { match input.read(buf) { None | Some(0) => { break } Some(n) => { ret = ret + str::from_utf8(buf.slice_to(n)); } } } return ret; } fn run_output(args: ProcessConfig) -> ~str { let p = Process::new(args); assert!(p.is_some()); let mut p = p.unwrap(); assert!(p.io[0].is_none()); assert!(p.io[1].is_some()); let ret = read_all(p.io[1].get_mut_ref() as &mut Reader); assert_eq!(p.wait(), 0); return ret; } #[test] #[cfg(unix, not(android))] #[ignore] // FIXME(#9341) fn stdout_works() { let pipe = PipeStream::new().unwrap(); let io = ~[Ignored, CreatePipe(pipe, false, true)]; let args = ProcessConfig { program: "/bin/sh", args: [~"-c", ~"echo foobar"], env: None, cwd: None, io: io, }; assert_eq!(run_output(args), ~"foobar\n"); } #[test] #[cfg(unix, not(android))] #[ignore] // FIXME(#9341) fn set_cwd_works() { let pipe = PipeStream::new().unwrap(); let io = ~[Ignored, CreatePipe(pipe, false, true)]; let cwd = Some("/"); let args = ProcessConfig { program: "/bin/sh", args: [~"-c", ~"pwd"], env: None, cwd: cwd, io: io, }; assert_eq!(run_output(args), ~"/\n"); } #[test] #[cfg(unix, not(android))] #[ignore] // FIXME(#9341) fn stdin_works() { let input = PipeStream::new().unwrap(); let output = PipeStream::new().unwrap(); let io = ~[CreatePipe(input, true, false), CreatePipe(output, false, true)]; let args = ProcessConfig { program: "/bin/sh", args: [~"-c", ~"read line; echo $line"], env: None, cwd: None, io: io, }; let mut p = Process::new(args).expect("didn't create a proces?!"); p.io[0].get_mut_ref().write("foobar".as_bytes()); p.io[0] = None; // close stdin; let out = read_all(p.io[1].get_mut_ref() as &mut Reader); assert_eq!(p.wait(), 0); assert_eq!(out, ~"foobar\n"); } }