// 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 #[deny(missing_doc)]; use prelude::*; use fmt; use io::IoResult; use io; use libc; use rt::rtio::{RtioProcess, IoFactory, LocalIo}; /// Signal a process to exit, without forcibly killing it. Corresponds to /// SIGTERM on unix platforms. #[cfg(windows)] pub static PleaseExitSignal: int = 15; /// Signal a process to exit immediately, forcibly killing it. Corresponds to /// SIGKILL on unix platforms. #[cfg(windows)] pub static MustDieSignal: int = 9; /// Signal a process to exit, without forcibly killing it. Corresponds to /// SIGTERM on unix platforms. #[cfg(not(windows))] pub static PleaseExitSignal: int = libc::SIGTERM as int; /// Signal a process to exit immediately, forcibly killing it. Corresponds to /// SIGKILL on unix platforms. #[cfg(not(windows))] pub static MustDieSignal: int = libc::SIGKILL as int; /// Representation of a running or exited child process. /// /// This structure is used to create, run, and manage child processes. A process /// is configured with the `ProcessConfig` struct which contains specific /// options for dictating how the child is spawned. /// /// # Example /// /// ```should_fail /// use std::io::Process; /// /// let mut child = match Process::new("/bin/cat", [~"file.txt"]) { /// Ok(child) => child, /// Err(e) => fail!("failed to execute child: {}", e), /// }; /// /// let contents = child.stdout.get_mut_ref().read_to_end(); /// assert!(child.wait().success()); /// ``` pub struct Process { priv handle: ~RtioProcess, /// Handle to the child's stdin, if the `stdin` field of this process's /// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`. stdin: Option, /// Handle to the child's stdout, if the `stdout` field of this process's /// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`. stdout: Option, /// Handle to the child's stderr, if the `stderr` field of this process's /// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`. stderr: Option, /// Extra I/O handles as configured by the original `ProcessConfig` when /// this process was created. This is by default empty. extra_io: ~[Option], } /// This configuration describes how a new process should be spawned. A blank /// configuration can be created with `ProcessConfig::new()`. It is also /// recommented to use a functional struct update pattern when creating process /// configuration: /// /// ``` /// use std::io::ProcessConfig; /// /// let config = ProcessConfig { /// program: "/bin/sh", /// args: &[~"-c", ~"true"], /// .. ProcessConfig::new() /// }; /// ``` pub struct ProcessConfig<'a> { /// Path to the program to run program: &'a str, /// Arguments to pass to the program (doesn't include the program itself) args: &'a [~str], /// Optional environment to specify for the program. If this is None, then /// it will inherit the current process's environment. env: Option<&'a [(~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<&'a Path>, /// Configuration for the child process's stdin handle (file descriptor 0). /// This field defaults to `CreatePipe(true, false)` so the input can be /// written to. stdin: StdioContainer, /// Configuration for the child process's stdout handle (file descriptor 1). /// This field defaults to `CreatePipe(false, true)` so the output can be /// collected. stdout: StdioContainer, /// Configuration for the child process's stdout handle (file descriptor 2). /// This field defaults to `CreatePipe(false, true)` so the output can be /// collected. stderr: StdioContainer, /// 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 /// 3 and go to the length of this array. The first three file descriptors /// (stdin/stdout/stderr) are configured with the `stdin`, `stdout`, and /// `stderr` fields. extra_io: &'a [StdioContainer], /// Sets the child process's user id. This translates to a `setuid` call in /// the child process. Setting this value on windows will cause the spawn to /// fail. Failure in the `setuid` call on unix will also cause the spawn to /// fail. uid: Option, /// Similar to `uid`, but sets the group id of the child process. This has /// the same semantics as the `uid` field. gid: Option, /// If true, the child process is spawned in a detached state. On unix, this /// means that the child is the leader of a new process group. detach: bool, } /// The output of a finished process. pub struct ProcessOutput { /// The status (exit code) of the process. status: ProcessExit, /// The data that the process wrote to stdout. output: ~[u8], /// The data that the process wrote to stderr. error: ~[u8], } /// 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), /// Creates a pipe for the specified file descriptor which will be created /// when the process is spawned. /// /// 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(bool /* readable */, bool /* writable */), } /// Describes the result of a process after it has terminated. /// Note that Windows have no signals, so the result is usually ExitStatus. #[deriving(Eq)] pub enum ProcessExit { /// Normal termination with an exit status. ExitStatus(int), /// Termination by signal, with the signal number. ExitSignal(int), } impl fmt::Show for ProcessExit { /// Format a ProcessExit enum, to nicely present the information. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ExitStatus(code) => write!(f.buf, "exit code: {}", code), ExitSignal(code) => write!(f.buf, "signal: {}", code), } } } impl ProcessExit { /// Was termination successful? Signal termination not considered a success, /// and success is defined as a zero exit status. pub fn success(&self) -> bool { return self.matches_exit_status(0); } /// Checks whether this ProcessExit matches the given exit status. /// Termination by signal will never match an exit code. pub fn matches_exit_status(&self, wanted: int) -> bool { *self == ExitStatus(wanted) } } impl<'a> ProcessConfig<'a> { /// Creates a new configuration with blanks as all of the defaults. This is /// useful when using functional struct updates: /// /// ```rust /// use std::io::process::{ProcessConfig, Process}; /// /// let config = ProcessConfig { /// program: "/bin/sh", /// args: &[~"-c", ~"echo hello"], /// .. ProcessConfig::new() /// }; /// /// let p = Process::configure(config); /// ``` /// pub fn new<'a>() -> ProcessConfig<'a> { ProcessConfig { program: "", args: &[], env: None, cwd: None, stdin: CreatePipe(true, false), stdout: CreatePipe(false, true), stderr: CreatePipe(false, true), extra_io: &[], uid: None, gid: None, detach: false, } } } impl Process { /// Creates a new process for the specified program/arguments, using /// otherwise default configuration. /// /// By default, new processes have their stdin/stdout/stderr handles created /// as pipes the can be manipulated through the respective fields of the /// returned `Process`. /// /// # Example /// /// ``` /// use std::io::Process; /// /// let mut process = match Process::new("sh", &[~"c", ~"echo hello"]) { /// Ok(p) => p, /// Err(e) => fail!("failed to execute process: {}", e), /// }; /// /// let output = process.stdout.get_mut_ref().read_to_end(); /// ``` pub fn new(prog: &str, args: &[~str]) -> IoResult { Process::configure(ProcessConfig { program: prog, args: args, .. ProcessConfig::new() }) } /// Executes the specified program with arguments, waiting for it to finish /// and collecting all of its output. /// /// # Example /// /// ``` /// use std::io::Process; /// use std::str; /// /// let output = match Process::output("cat", [~"foo.txt"]) { /// Ok(output) => output, /// Err(e) => fail!("failed to execute process: {}", e), /// }; /// /// println!("status: {}", output.status); /// println!("stdout: {}", str::from_utf8_lossy(output.output)); /// println!("stderr: {}", str::from_utf8_lossy(output.error)); /// ``` pub fn output(prog: &str, args: &[~str]) -> IoResult { Process::new(prog, args).map(|mut p| p.wait_with_output()) } /// Executes a child process and collects its exit status. This will block /// waiting for the child to exit. /// /// # Example /// /// ``` /// use std::io::Process; /// /// let status = match Process::status("ls", []) { /// Ok(status) => status, /// Err(e) => fail!("failed to execute process: {}", e), /// }; /// /// println!("process exited with: {}", status); /// ``` pub fn status(prog: &str, args: &[~str]) -> IoResult { Process::new(prog, args).map(|mut p| p.wait()) } /// Creates a new process with the specified configuration. pub fn configure(config: ProcessConfig) -> IoResult { let mut config = Some(config); LocalIo::maybe_raise(|io| { io.spawn(config.take_unwrap()).map(|(p, io)| { let mut io = io.move_iter().map(|p| { p.map(|p| io::PipeStream::new(p)) }); Process { handle: p, stdin: io.next().unwrap(), stdout: io.next().unwrap(), stderr: io.next().unwrap(), extra_io: io.collect(), } }) }) } /// Sends `signal` to another process in the system identified by `id`. /// /// Note that windows doesn't quite have the same model as unix, so some /// unix signals are mapped to windows signals. Notably, unix termination /// signals (SIGTERM/SIGKILL/SIGINT) are translated to `TerminateProcess`. /// /// Additionally, a signal number of 0 can check for existence of the target /// process. pub fn kill(id: libc::pid_t, signal: int) -> IoResult<()> { LocalIo::maybe_raise(|io| io.kill(id, signal)) } /// 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, the corresponding error is returned. pub fn signal(&mut self, signal: int) -> IoResult<()> { self.handle.kill(signal) } /// Sends a signal to this child requesting that it exits. This is /// equivalent to sending a SIGTERM on unix platforms. pub fn signal_exit(&mut self) -> IoResult<()> { self.signal(PleaseExitSignal) } /// Sends a signal to this child forcing it to exit. This is equivalent to /// sending a SIGKILL on unix platforms. pub fn signal_kill(&mut self) -> IoResult<()> { self.signal(MustDieSignal) } /// 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. /// /// The stdin handle to the child process will be closed before waiting. pub fn wait(&mut self) -> ProcessExit { drop(self.stdin.take()); self.handle.wait() } /// Simultaneously wait for the child to exit and collect all remaining /// output on the stdout/stderr handles, returning a `ProcessOutput` /// instance. /// /// The stdin handle to the child is closed before waiting. pub fn wait_with_output(&mut self) -> ProcessOutput { drop(self.stdin.take()); fn read(stream: Option) -> Port> { let (p, c) = Chan::new(); match stream { Some(stream) => spawn(proc() { let mut stream = stream; c.send(stream.read_to_end()) }), None => c.send(Ok(~[])) } p } let stdout = read(self.stdout.take()); let stderr = read(self.stderr.take()); let status = self.wait(); ProcessOutput { status: status, output: stdout.recv().ok().unwrap_or(~[]), error: stderr.recv().ok().unwrap_or(~[]) } } } 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. drop(self.stdin.take()); drop(self.stdout.take()); drop(self.stderr.take()); loop { match self.extra_io.pop() { Some(_) => (), None => break, } } self.wait(); } } #[cfg(test)] mod tests { use io::process::{ProcessConfig, Process}; use prelude::*; // FIXME(#10380) these tests should not all be ignored on android. #[cfg(not(target_os="android"))] iotest!(fn smoke() { let args = ProcessConfig { program: "true", .. ProcessConfig::new() }; let p = Process::configure(args); assert!(p.is_ok()); let mut p = p.unwrap(); assert!(p.wait().success()); }) #[cfg(not(target_os="android"))] iotest!(fn smoke_failure() { let args = ProcessConfig { program: "if-this-is-a-binary-then-the-world-has-ended", .. ProcessConfig::new() }; match Process::configure(args) { Ok(..) => fail!(), Err(..) => {} } }) #[cfg(not(target_os="android"))] iotest!(fn exit_reported_right() { let args = ProcessConfig { program: "false", .. ProcessConfig::new() }; let p = Process::configure(args); assert!(p.is_ok()); let mut p = p.unwrap(); assert!(p.wait().matches_exit_status(1)); }) #[cfg(unix, not(target_os="android"))] iotest!(fn signal_reported_right() { let args = ProcessConfig { program: "/bin/sh", args: &[~"-c", ~"kill -1 $$"], .. ProcessConfig::new() }; let p = Process::configure(args); assert!(p.is_ok()); let mut p = p.unwrap(); match p.wait() { process::ExitSignal(1) => {}, result => fail!("not terminated by signal 1 (instead, {})", result), } }) pub fn read_all(input: &mut Reader) -> ~str { input.read_to_str().unwrap() } pub fn run_output(args: ProcessConfig) -> ~str { let p = Process::configure(args); assert!(p.is_ok()); let mut p = p.unwrap(); assert!(p.stdout.is_some()); let ret = read_all(p.stdout.get_mut_ref() as &mut Reader); assert!(p.wait().success()); return ret; } #[cfg(not(target_os="android"))] iotest!(fn stdout_works() { let args = ProcessConfig { program: "echo", args: &[~"foobar"], stdout: CreatePipe(false, true), .. ProcessConfig::new() }; assert_eq!(run_output(args), ~"foobar\n"); }) #[cfg(unix, not(target_os="android"))] iotest!(fn set_cwd_works() { let cwd = Path::new("/"); let args = ProcessConfig { program: "/bin/sh", args: &[~"-c", ~"pwd"], cwd: Some(&cwd), stdout: CreatePipe(false, true), .. ProcessConfig::new() }; assert_eq!(run_output(args), ~"/\n"); }) #[cfg(unix, not(target_os="android"))] iotest!(fn stdin_works() { let args = ProcessConfig { program: "/bin/sh", args: &[~"-c", ~"read line; echo $line"], stdin: CreatePipe(true, false), stdout: CreatePipe(false, true), .. ProcessConfig::new() }; let mut p = Process::configure(args).unwrap(); p.stdin.get_mut_ref().write("foobar".as_bytes()).unwrap(); drop(p.stdin.take()); let out = read_all(p.stdout.get_mut_ref() as &mut Reader); assert!(p.wait().success()); assert_eq!(out, ~"foobar\n"); }) #[cfg(not(target_os="android"))] iotest!(fn detach_works() { let args = ProcessConfig { program: "true", detach: true, .. ProcessConfig::new() }; let mut p = Process::configure(args).unwrap(); assert!(p.wait().success()); }) #[cfg(windows)] iotest!(fn uid_fails_on_windows() { let args = ProcessConfig { program: "test", uid: Some(10), .. ProcessConfig::new() }; assert!(Process::configure(args).is_err()); }) #[cfg(unix, not(target_os="android"))] iotest!(fn uid_works() { use libc; let args = ProcessConfig { program: "/bin/sh", args: &[~"-c", ~"true"], uid: Some(unsafe { libc::getuid() as uint }), gid: Some(unsafe { libc::getgid() as uint }), .. ProcessConfig::new() }; let mut p = Process::configure(args).unwrap(); assert!(p.wait().success()); }) #[cfg(unix, not(target_os="android"))] iotest!(fn uid_to_root_fails() { use libc; // if we're already root, this isn't a valid test. Most of the bots run // as non-root though (android is an exception). if unsafe { libc::getuid() == 0 } { return } let args = ProcessConfig { program: "/bin/ls", uid: Some(0), gid: Some(0), .. ProcessConfig::new() }; assert!(Process::configure(args).is_err()); }) #[cfg(not(target_os="android"))] iotest!(fn test_process_status() { let mut status = Process::status("false", []).unwrap(); assert!(status.matches_exit_status(1)); status = Process::status("true", []).unwrap(); assert!(status.success()); }) iotest!(fn test_process_output_fail_to_start() { match Process::output("/no-binary-by-this-name-should-exist", []) { Err(e) => assert_eq!(e.kind, FileNotFound), Ok(..) => fail!() } }) #[cfg(not(target_os="android"))] iotest!(fn test_process_output_output() { let ProcessOutput {status, output, error} = Process::output("echo", [~"hello"]).unwrap(); let output_str = str::from_utf8_owned(output).unwrap(); assert!(status.success()); assert_eq!(output_str.trim().to_owned(), ~"hello"); // FIXME #7224 if !running_on_valgrind() { assert_eq!(error, ~[]); } }) #[cfg(not(target_os="android"))] iotest!(fn test_process_output_error() { let ProcessOutput {status, output, error} = Process::output("mkdir", [~"."]).unwrap(); assert!(status.matches_exit_status(1)); assert_eq!(output, ~[]); assert!(!error.is_empty()); }) #[cfg(not(target_os="android"))] iotest!(fn test_finish_once() { let mut prog = Process::new("false", []).unwrap(); assert!(prog.wait().matches_exit_status(1)); }) #[cfg(not(target_os="android"))] iotest!(fn test_finish_twice() { let mut prog = Process::new("false", []).unwrap(); assert!(prog.wait().matches_exit_status(1)); assert!(prog.wait().matches_exit_status(1)); }) #[cfg(not(target_os="android"))] iotest!(fn test_wait_with_output_once() { let mut prog = Process::new("echo", [~"hello"]).unwrap(); let ProcessOutput {status, output, error} = prog.wait_with_output(); let output_str = str::from_utf8_owned(output).unwrap(); assert!(status.success()); assert_eq!(output_str.trim().to_owned(), ~"hello"); // FIXME #7224 if !running_on_valgrind() { assert_eq!(error, ~[]); } }) #[cfg(not(target_os="android"))] iotest!(fn test_wait_with_output_twice() { let mut prog = Process::new("echo", [~"hello"]).unwrap(); let ProcessOutput {status, output, error} = prog.wait_with_output(); let output_str = str::from_utf8_owned(output).unwrap(); assert!(status.success()); assert_eq!(output_str.trim().to_owned(), ~"hello"); // FIXME #7224 if !running_on_valgrind() { assert_eq!(error, ~[]); } let ProcessOutput {status, output, error} = prog.wait_with_output(); assert!(status.success()); assert_eq!(output, ~[]); // FIXME #7224 if !running_on_valgrind() { assert_eq!(error, ~[]); } }) #[cfg(unix,not(target_os="android"))] pub fn run_pwd(dir: Option<&Path>) -> Process { Process::configure(ProcessConfig { program: "pwd", cwd: dir, .. ProcessConfig::new() }).unwrap() } #[cfg(target_os="android")] pub fn run_pwd(dir: Option<&Path>) -> Process { Process::configure(ProcessConfig { program: "/system/bin/sh", args: &[~"-c",~"pwd"], cwd: dir.map(|a| &*a), .. ProcessConfig::new() }).unwrap() } #[cfg(windows)] pub fn run_pwd(dir: Option<&Path>) -> Process { Process::configure(ProcessConfig { program: "cmd", args: &[~"/c", ~"cd"], cwd: dir.map(|a| &*a), .. ProcessConfig::new() }).unwrap() } iotest!(fn test_keep_current_working_dir() { use os; let mut prog = run_pwd(None); let output = str::from_utf8_owned(prog.wait_with_output().output).unwrap(); let parent_dir = os::getcwd(); let child_dir = Path::new(output.trim()); let parent_stat = parent_dir.stat().unwrap(); let child_stat = child_dir.stat().unwrap(); assert_eq!(parent_stat.unstable.device, child_stat.unstable.device); assert_eq!(parent_stat.unstable.inode, child_stat.unstable.inode); }) iotest!(fn test_change_working_directory() { use os; // test changing to the parent of os::getcwd() because we know // the path exists (and os::getcwd() is not expected to be root) let parent_dir = os::getcwd().dir_path(); let mut prog = run_pwd(Some(&parent_dir)); let output = str::from_utf8_owned(prog.wait_with_output().output).unwrap(); let child_dir = Path::new(output.trim()); let parent_stat = parent_dir.stat().unwrap(); let child_stat = child_dir.stat().unwrap(); assert_eq!(parent_stat.unstable.device, child_stat.unstable.device); assert_eq!(parent_stat.unstable.inode, child_stat.unstable.inode); }) #[cfg(unix,not(target_os="android"))] pub fn run_env(env: Option<~[(~str, ~str)]>) -> Process { Process::configure(ProcessConfig { program: "env", env: env.as_ref().map(|e| e.as_slice()), .. ProcessConfig::new() }).unwrap() } #[cfg(target_os="android")] pub fn run_env(env: Option<~[(~str, ~str)]>) -> Process { Process::configure(ProcessConfig { program: "/system/bin/sh", args: &[~"-c",~"set"], env: env.as_ref().map(|e| e.as_slice()), .. ProcessConfig::new() }).unwrap() } #[cfg(windows)] pub fn run_env(env: Option<~[(~str, ~str)]>) -> Process { Process::configure(ProcessConfig { program: "cmd", args: &[~"/c", ~"set"], env: env.as_ref().map(|e| e.as_slice()), .. ProcessConfig::new() }).unwrap() } #[cfg(not(target_os="android"))] iotest!(fn test_inherit_env() { use os; if running_on_valgrind() { return; } let mut prog = run_env(None); let output = str::from_utf8_owned(prog.wait_with_output().output).unwrap(); let r = os::env(); for &(ref k, ref v) in r.iter() { // don't check windows magical empty-named variables assert!(k.is_empty() || output.contains(format!("{}={}", *k, *v))); } }) #[cfg(target_os="android")] iotest!(fn test_inherit_env() { use os; if running_on_valgrind() { return; } let mut prog = run_env(None); let output = str::from_utf8_owned(prog.wait_with_output().output).unwrap(); let r = os::env(); for &(ref k, ref v) in r.iter() { // don't check android RANDOM variables if *k != ~"RANDOM" { assert!(output.contains(format!("{}={}", *k, *v)) || output.contains(format!("{}=\'{}\'", *k, *v))); } } }) iotest!(fn test_add_to_env() { let new_env = ~[(~"RUN_TEST_NEW_ENV", ~"123")]; let mut prog = run_env(Some(new_env)); let result = prog.wait_with_output(); let output = str::from_utf8_lossy(result.output).into_owned(); assert!(output.contains("RUN_TEST_NEW_ENV=123"), "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output); }) #[cfg(unix)] pub fn sleeper() -> Process { Process::new("sleep", [~"1000"]).unwrap() } #[cfg(windows)] pub fn sleeper() -> Process { Process::new("timeout", [~"1000"]).unwrap() } iotest!(fn test_kill() { let mut p = sleeper(); Process::kill(p.id(), PleaseExitSignal).unwrap(); assert!(!p.wait().success()); }) iotest!(fn test_exists() { let mut p = sleeper(); assert!(Process::kill(p.id(), 0).is_ok()); p.signal_kill().unwrap(); assert!(!p.wait().success()); } #[ignore(cfg(windows))]) // FIXME(#12516) }