diff options
| author | Brian Anderson <banderson@mozilla.com> | 2015-01-27 09:38:30 -0800 |
|---|---|---|
| committer | Brian Anderson <banderson@mozilla.com> | 2015-01-27 15:05:04 -0800 |
| commit | 71223050538939ed758fcd3b9114f71abff20bb2 (patch) | |
| tree | 43ddd18223904fa86601f1a0e16ebcbaddead270 /src/libstd/old_io/process.rs | |
| parent | 3c172392cf0c86ffd1d7b39d3f44de98f77afc44 (diff) | |
| parent | 777435990e0e91df6b72ce80c9b6fa485eeb5daa (diff) | |
| download | rust-71223050538939ed758fcd3b9114f71abff20bb2.tar.gz rust-71223050538939ed758fcd3b9114f71abff20bb2.zip | |
Merge remote-tracking branch 'rust-lang/master'
Conflicts: src/libcore/cell.rs src/librustc_driver/test.rs src/libstd/old_io/net/tcp.rs src/libstd/old_io/process.rs
Diffstat (limited to 'src/libstd/old_io/process.rs')
| -rw-r--r-- | src/libstd/old_io/process.rs | 1230 |
1 files changed, 1230 insertions, 0 deletions
diff --git a/src/libstd/old_io/process.rs b/src/libstd/old_io/process.rs new file mode 100644 index 00000000000..78910882467 --- /dev/null +++ b/src/libstd/old_io/process.rs @@ -0,0 +1,1230 @@ +// 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. + +//! Bindings for executing child processes + +#![allow(non_upper_case_globals)] + +pub use self::StdioContainer::*; +pub use self::ProcessExit::*; + +use prelude::v1::*; + +use collections::HashMap; +use ffi::CString; +use fmt; +use old_io::pipe::{PipeStream, PipePair}; +use old_io::{IoResult, IoError}; +use old_io; +use libc; +use os; +use path::BytesContainer; +use sync::mpsc::{channel, Receiver}; +use sys::fs::FileDesc; +use sys::process::Process as ProcessImp; +use sys; +use thread::Thread; + +#[cfg(windows)] use hash; +#[cfg(windows)] use str; + +/// Signal a process to exit, without forcibly killing it. Corresponds to +/// SIGTERM on unix platforms. +#[cfg(windows)] pub const PleaseExitSignal: int = 15; +/// Signal a process to exit immediately, forcibly killing it. Corresponds to +/// SIGKILL on unix platforms. +#[cfg(windows)] pub const MustDieSignal: int = 9; +/// Signal a process to exit, without forcibly killing it. Corresponds to +/// SIGTERM on unix platforms. +#[cfg(not(windows))] pub const 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 const MustDieSignal: int = libc::SIGKILL as int; + +/// Representation of a running or exited child process. +/// +/// This structure is used to represent and manage child processes. A child +/// process is created via the `Command` struct, which configures the spawning +/// process and can itself be constructed using a builder-style interface. +/// +/// # Example +/// +/// ```should_fail +/// use std::old_io::Command; +/// +/// let mut child = match Command::new("/bin/cat").arg("file.txt").spawn() { +/// Ok(child) => child, +/// Err(e) => panic!("failed to execute child: {}", e), +/// }; +/// +/// let contents = child.stdout.as_mut().unwrap().read_to_end(); +/// assert!(child.wait().unwrap().success()); +/// ``` +pub struct Process { + handle: ProcessImp, + forget: bool, + + /// None until wait() is called. + exit_code: Option<ProcessExit>, + + /// Manually delivered signal + exit_signal: Option<int>, + + /// Deadline after which wait() will return + deadline: u64, + + /// Handle to the child's stdin, if the `stdin` field of this process's + /// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`. + pub stdin: Option<PipeStream>, + + /// Handle to the child's stdout, if the `stdout` field of this process's + /// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`. + pub stdout: Option<PipeStream>, + + /// Handle to the child's stderr, if the `stderr` field of this process's + /// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`. + pub stderr: Option<PipeStream>, +} + +/// A representation of environment variable name +/// It compares case-insensitive on Windows and case-sensitive everywhere else. +#[cfg(not(windows))] +#[derive(Hash, PartialEq, Eq, Clone, Show)] +struct EnvKey(CString); + +#[doc(hidden)] +#[cfg(windows)] +#[derive(Eq, Clone, Show)] +struct EnvKey(CString); + +#[cfg(windows)] +impl<H: hash::Writer + hash::Hasher> hash::Hash<H> for EnvKey { + fn hash(&self, state: &mut H) { + let &EnvKey(ref x) = self; + match str::from_utf8(x.as_bytes()) { + Ok(s) => for ch in s.chars() { + (ch as u8 as char).to_lowercase().hash(state); + }, + Err(..) => x.hash(state) + } + } +} + +#[cfg(windows)] +impl PartialEq for EnvKey { + fn eq(&self, other: &EnvKey) -> bool { + let &EnvKey(ref x) = self; + let &EnvKey(ref y) = other; + match (str::from_utf8(x.as_bytes()), str::from_utf8(y.as_bytes())) { + (Ok(xs), Ok(ys)) => { + if xs.len() != ys.len() { + return false + } else { + for (xch, ych) in xs.chars().zip(ys.chars()) { + if xch.to_lowercase() != ych.to_lowercase() { + return false; + } + } + return true; + } + }, + // If either is not a valid utf8 string, just compare them byte-wise + _ => return x.eq(y) + } + } +} + +impl BytesContainer for EnvKey { + fn container_as_bytes<'a>(&'a self) -> &'a [u8] { + let &EnvKey(ref k) = self; + k.container_as_bytes() + } +} + +/// A HashMap representation of environment variables. +pub type EnvMap = HashMap<EnvKey, CString>; + +/// The `Command` type acts as a process builder, providing fine-grained control +/// over how a new process should be spawned. A default configuration can be +/// generated using `Command::new(program)`, where `program` gives a path to the +/// program to be executed. Additional builder methods allow the configuration +/// to be changed (for example, by adding arguments) prior to spawning: +/// +/// ``` +/// use std::old_io::Command; +/// +/// let mut process = match Command::new("sh").arg("-c").arg("echo hello").spawn() { +/// Ok(p) => p, +/// Err(e) => panic!("failed to execute process: {}", e), +/// }; +/// +/// let output = process.stdout.as_mut().unwrap().read_to_end(); +/// ``` +#[derive(Clone)] +pub struct Command { + // The internal data for the builder. Documented by the builder + // methods below, and serialized into rt::rtio::ProcessConfig. + program: CString, + args: Vec<CString>, + env: Option<EnvMap>, + cwd: Option<CString>, + stdin: StdioContainer, + stdout: StdioContainer, + stderr: StdioContainer, + uid: Option<uint>, + gid: Option<uint>, + detach: bool, +} + +// FIXME (#12938): Until DST lands, we cannot decompose &str into & and str, so +// we cannot usefully take BytesContainer arguments by reference (without forcing an +// additional & around &str). So we are instead temporarily adding an instance +// for &Path, so that we can take BytesContainer as owned. When DST lands, the &Path +// instance should be removed, and arguments bound by BytesContainer should be passed by +// reference. (Here: {new, arg, args, env}.) + +impl Command { + /// Constructs a new `Command` for launching the program at + /// path `program`, with the following default configuration: + /// + /// * No arguments to the program + /// * Inherit the current process's environment + /// * Inherit the current process's working directory + /// * A readable pipe for stdin (file descriptor 0) + /// * A writeable pipe for stdout and stderr (file descriptors 1 and 2) + /// + /// Builder methods are provided to change these defaults and + /// otherwise configure the process. + pub fn new<T: BytesContainer>(program: T) -> Command { + Command { + program: CString::from_slice(program.container_as_bytes()), + args: Vec::new(), + env: None, + cwd: None, + stdin: CreatePipe(true, false), + stdout: CreatePipe(false, true), + stderr: CreatePipe(false, true), + uid: None, + gid: None, + detach: false, + } + } + + /// Add an argument to pass to the program. + pub fn arg<'a, T: BytesContainer>(&'a mut self, arg: T) -> &'a mut Command { + self.args.push(CString::from_slice(arg.container_as_bytes())); + self + } + + /// Add multiple arguments to pass to the program. + pub fn args<'a, T: BytesContainer>(&'a mut self, args: &[T]) -> &'a mut Command { + self.args.extend(args.iter().map(|arg| { + CString::from_slice(arg.container_as_bytes()) + })); + self + } + // Get a mutable borrow of the environment variable map for this `Command`. + fn get_env_map<'a>(&'a mut self) -> &'a mut EnvMap { + match self.env { + Some(ref mut map) => map, + None => { + // if the env is currently just inheriting from the parent's, + // materialize the parent's env into a hashtable. + self.env = Some(os::env_as_bytes().into_iter().map(|(k, v)| { + (EnvKey(CString::from_slice(k.as_slice())), + CString::from_slice(v.as_slice())) + }).collect()); + self.env.as_mut().unwrap() + } + } + } + + /// Inserts or updates an environment variable mapping. + /// + /// Note that environment variable names are case-insensitive (but case-preserving) on Windows, + /// and case-sensitive on all other platforms. + pub fn env<'a, T, U>(&'a mut self, key: T, val: U) + -> &'a mut Command + where T: BytesContainer, U: BytesContainer { + let key = EnvKey(CString::from_slice(key.container_as_bytes())); + let val = CString::from_slice(val.container_as_bytes()); + self.get_env_map().insert(key, val); + self + } + + /// Removes an environment variable mapping. + pub fn env_remove<'a, T>(&'a mut self, key: T) -> &'a mut Command + where T: BytesContainer { + let key = EnvKey(CString::from_slice(key.container_as_bytes())); + self.get_env_map().remove(&key); + self + } + + /// Sets the entire environment map for the child process. + /// + /// If the given slice contains multiple instances of an environment + /// variable, the *rightmost* instance will determine the value. + pub fn env_set_all<'a, T, U>(&'a mut self, env: &[(T,U)]) + -> &'a mut Command + where T: BytesContainer, U: BytesContainer { + self.env = Some(env.iter().map(|&(ref k, ref v)| { + (EnvKey(CString::from_slice(k.container_as_bytes())), + CString::from_slice(v.container_as_bytes())) + }).collect()); + self + } + + /// Set the working directory for the child process. + pub fn cwd<'a>(&'a mut self, dir: &Path) -> &'a mut Command { + self.cwd = Some(CString::from_slice(dir.as_vec())); + self + } + + /// Configuration for the child process's stdin handle (file descriptor 0). + /// Defaults to `CreatePipe(true, false)` so the input can be written to. + pub fn stdin<'a>(&'a mut self, cfg: StdioContainer) -> &'a mut Command { + self.stdin = cfg; + self + } + + /// Configuration for the child process's stdout handle (file descriptor 1). + /// Defaults to `CreatePipe(false, true)` so the output can be collected. + pub fn stdout<'a>(&'a mut self, cfg: StdioContainer) -> &'a mut Command { + self.stdout = cfg; + self + } + + /// Configuration for the child process's stderr handle (file descriptor 2). + /// Defaults to `CreatePipe(false, true)` so the output can be collected. + pub fn stderr<'a>(&'a mut self, cfg: StdioContainer) -> &'a mut Command { + self.stderr = cfg; + self + } + + /// 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. + pub fn uid<'a>(&'a mut self, id: uint) -> &'a mut Command { + self.uid = Some(id); + self + } + + /// Similar to `uid`, but sets the group id of the child process. This has + /// the same semantics as the `uid` field. + pub fn gid<'a>(&'a mut self, id: uint) -> &'a mut Command { + self.gid = Some(id); + self + } + + /// Sets the child process to be spawned in a detached state. On unix, this + /// means that the child is the leader of a new process group. + pub fn detached<'a>(&'a mut self) -> &'a mut Command { + self.detach = true; + self + } + + /// Executes the command as a child process, which is returned. + pub fn spawn(&self) -> IoResult<Process> { + let (their_stdin, our_stdin) = try!(setup_io(self.stdin)); + let (their_stdout, our_stdout) = try!(setup_io(self.stdout)); + let (their_stderr, our_stderr) = try!(setup_io(self.stderr)); + + match ProcessImp::spawn(self, their_stdin, their_stdout, their_stderr) { + Err(e) => Err(e), + Ok(handle) => Ok(Process { + handle: handle, + forget: false, + exit_code: None, + exit_signal: None, + deadline: 0, + stdin: our_stdin, + stdout: our_stdout, + stderr: our_stderr, + }) + } + } + + /// Executes the command as a child process, waiting for it to finish and + /// collecting all of its output. + /// + /// # Example + /// + /// ``` + /// use std::old_io::Command; + /// + /// let output = match Command::new("cat").arg("foot.txt").output() { + /// Ok(output) => output, + /// Err(e) => panic!("failed to execute process: {}", e), + /// }; + /// + /// println!("status: {}", output.status); + /// println!("stdout: {}", String::from_utf8_lossy(output.output.as_slice())); + /// println!("stderr: {}", String::from_utf8_lossy(output.error.as_slice())); + /// ``` + pub fn output(&self) -> IoResult<ProcessOutput> { + self.spawn().and_then(|p| p.wait_with_output()) + } + + /// Executes a command as a child process, waiting for it to finish and + /// collecting its exit status. + /// + /// # Example + /// + /// ``` + /// use std::old_io::Command; + /// + /// let status = match Command::new("ls").status() { + /// Ok(status) => status, + /// Err(e) => panic!("failed to execute process: {}", e), + /// }; + /// + /// println!("process exited with: {}", status); + /// ``` + pub fn status(&self) -> IoResult<ProcessExit> { + self.spawn().and_then(|mut p| p.wait()) + } +} + +impl fmt::Debug for Command { + /// Format the program and arguments of a Command for display. Any + /// non-utf8 data is lossily converted using the utf8 replacement + /// character. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(write!(f, "{}", String::from_utf8_lossy(self.program.as_bytes()))); + for arg in self.args.iter() { + try!(write!(f, " '{}'", String::from_utf8_lossy(arg.as_bytes()))); + } + Ok(()) + } +} + +fn setup_io(io: StdioContainer) -> IoResult<(Option<PipeStream>, Option<PipeStream>)> { + let ours; + let theirs; + match io { + Ignored => { + theirs = None; + ours = None; + } + InheritFd(fd) => { + theirs = Some(PipeStream::from_filedesc(FileDesc::new(fd, false))); + ours = None; + } + CreatePipe(readable, _writable) => { + let PipePair { reader, writer } = try!(PipeStream::pair()); + if readable { + theirs = Some(reader); + ours = Some(writer); + } else { + theirs = Some(writer); + ours = Some(reader); + } + } + } + Ok((theirs, ours)) +} + +// Allow the sys module to get access to the Command state +impl sys::process::ProcessConfig<EnvKey, CString> for Command { + fn program(&self) -> &CString { + &self.program + } + fn args(&self) -> &[CString] { + self.args.as_slice() + } + fn env(&self) -> Option<&EnvMap> { + self.env.as_ref() + } + fn cwd(&self) -> Option<&CString> { + self.cwd.as_ref() + } + fn uid(&self) -> Option<uint> { + self.uid.clone() + } + fn gid(&self) -> Option<uint> { + self.gid.clone() + } + fn detach(&self) -> bool { + self.detach + } + +} + +/// The output of a finished process. +#[derive(PartialEq, Eq, Clone)] +pub struct ProcessOutput { + /// The status (exit code) of the process. + pub status: ProcessExit, + /// The data that the process wrote to stdout. + pub output: Vec<u8>, + /// The data that the process wrote to stderr. + pub error: Vec<u8>, +} + +/// Describes what to do with a standard io stream for a child process. +#[derive(Clone, Copy)] +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. Ownership of the file descriptor is *not* taken, so the + /// caller must clean it up. + 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. +#[derive(PartialEq, Eq, Clone, Copy, Show)] +pub enum ProcessExit { + /// Normal termination with an exit status. + ExitStatus(int), + + /// Termination by signal, with the signal number. + ExitSignal(int), +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl fmt::Display 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, "exit code: {}", code), + ExitSignal(code) => write!(f, "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 Process { + /// 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. Note, though, that on some platforms signals will continue to + /// be successfully delivered if the child has exited, but not yet been + /// reaped. + pub fn kill(id: libc::pid_t, signal: int) -> IoResult<()> { + unsafe { ProcessImp::killpid(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 signal 0 is interpreted as a poll to check whether the child + /// process is still alive or not. If an error is returned, then the child + /// process has exited. + /// + /// On some unix platforms signals will continue to be received after a + /// child has exited but not yet been reaped. In order to report the status + /// of signal delivery correctly, unix implementations may invoke + /// `waitpid()` with `WNOHANG` in order to reap the child as necessary. + /// + /// # Errors + /// + /// If the signal delivery fails, the corresponding error is returned. + pub fn signal(&mut self, signal: int) -> IoResult<()> { + #[cfg(unix)] fn collect_status(p: &mut Process) { + // On Linux (and possibly other unices), a process that has exited will + // continue to accept signals because it is "defunct". The delivery of + // signals will only fail once the child has been reaped. For this + // reason, if the process hasn't exited yet, then we attempt to collect + // their status with WNOHANG. + if p.exit_code.is_none() { + match p.handle.try_wait() { + Some(code) => { p.exit_code = Some(code); } + None => {} + } + } + } + #[cfg(windows)] fn collect_status(_p: &mut Process) {} + + collect_status(self); + + // if the process has finished, and therefore had waitpid called, + // and we kill it, then on unix we might ending up killing a + // newer process that happens to have the same (re-used) id + if self.exit_code.is_some() { + return Err(IoError { + kind: old_io::InvalidInput, + desc: "invalid argument: can't kill an exited process", + detail: None, + }) + } + + // A successfully delivered signal that isn't 0 (just a poll for being + // alive) is recorded for windows (see wait()) + match unsafe { self.handle.kill(signal) } { + Ok(()) if signal == 0 => Ok(()), + Ok(()) => { self.exit_signal = Some(signal); Ok(()) } + Err(e) => Err(e), + } + + } + + /// 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. + /// + /// # Errors + /// + /// This function can fail if a timeout was previously specified via + /// `set_timeout` and the timeout expires before the child exits. + pub fn wait(&mut self) -> IoResult<ProcessExit> { + drop(self.stdin.take()); + match self.exit_code { + Some(code) => Ok(code), + None => { + let code = try!(self.handle.wait(self.deadline)); + // On windows, waitpid will never return a signal. If a signal + // was successfully delivered to the process, however, we can + // consider it as having died via a signal. + let code = match self.exit_signal { + None => code, + Some(signal) if cfg!(windows) => ExitSignal(signal), + Some(..) => code, + }; + self.exit_code = Some(code); + Ok(code) + } + } + } + + /// Sets a timeout, in milliseconds, for future calls to wait(). + /// + /// The argument specified is a relative distance into the future, in + /// milliseconds, after which any call to wait() will return immediately + /// with a timeout error, and all future calls to wait() will not block. + /// + /// A value of `None` will clear any previous timeout, and a value of `Some` + /// will override any previously set timeout. + /// + /// # Example + /// + /// ```no_run + /// use std::old_io::{Command, IoResult}; + /// use std::old_io::process::ProcessExit; + /// + /// fn run_gracefully(prog: &str) -> IoResult<ProcessExit> { + /// let mut p = try!(Command::new("long-running-process").spawn()); + /// + /// // give the process 10 seconds to finish completely + /// p.set_timeout(Some(10_000)); + /// match p.wait() { + /// Ok(status) => return Ok(status), + /// Err(..) => {} + /// } + /// + /// // Attempt to exit gracefully, but don't wait for it too long + /// try!(p.signal_exit()); + /// p.set_timeout(Some(1_000)); + /// match p.wait() { + /// Ok(status) => return Ok(status), + /// Err(..) => {} + /// } + /// + /// // Well, we did our best, forcefully kill the process + /// try!(p.signal_kill()); + /// p.set_timeout(None); + /// p.wait() + /// } + /// ``` + #[unstable(feature = "io", + reason = "the type of the timeout is likely to change")] + pub fn set_timeout(&mut self, timeout_ms: Option<u64>) { + self.deadline = timeout_ms.map(|i| i + sys::timer::now()).unwrap_or(0); + } + + /// 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. + /// + /// # Errors + /// + /// This function can fail for any of the same reasons that `wait()` can + /// fail. + pub fn wait_with_output(mut self) -> IoResult<ProcessOutput> { + drop(self.stdin.take()); + fn read(stream: Option<old_io::PipeStream>) -> Receiver<IoResult<Vec<u8>>> { + let (tx, rx) = channel(); + match stream { + Some(stream) => { + Thread::spawn(move |:| { + let mut stream = stream; + tx.send(stream.read_to_end()).unwrap(); + }); + } + None => tx.send(Ok(Vec::new())).unwrap() + } + rx + } + let stdout = read(self.stdout.take()); + let stderr = read(self.stderr.take()); + + let status = try!(self.wait()); + + Ok(ProcessOutput { + status: status, + output: stdout.recv().unwrap().unwrap_or(Vec::new()), + error: stderr.recv().unwrap().unwrap_or(Vec::new()), + }) + } + + /// Forgets this process, allowing it to outlive the parent + /// + /// This function will forcefully prevent calling `wait()` on the child + /// process in the destructor, allowing the child to outlive the + /// parent. Note that this operation can easily lead to leaking the + /// resources of the child process, so care must be taken when + /// invoking this method. + pub fn forget(mut self) { + self.forget = true; + } +} + +impl Drop for Process { + fn drop(&mut self) { + if self.forget { return } + + // 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()); + + self.set_timeout(None); + let _ = self.wait().unwrap(); + } +} + +#[cfg(test)] +mod tests { + use old_io::{Truncate, Write, TimedOut, timer, process, FileNotFound}; + use prelude::v1::{Ok, Err, range, drop, Some, None, Vec}; + use prelude::v1::{Path, String, Reader, Writer, Clone}; + use prelude::v1::{SliceExt, Str, StrExt, AsSlice, ToString, GenericPath}; + use old_io::fs::PathExtensions; + use old_io::timer::*; + use rt::running_on_valgrind; + use str; + use super::{CreatePipe}; + use super::{InheritFd, Process, PleaseExitSignal, Command, ProcessOutput}; + use sync::mpsc::channel; + use thread::Thread; + use time::Duration; + + // FIXME(#10380) these tests should not all be ignored on android. + + #[cfg(not(target_os="android"))] + #[test] + fn smoke() { + let p = Command::new("true").spawn(); + assert!(p.is_ok()); + let mut p = p.unwrap(); + assert!(p.wait().unwrap().success()); + } + + #[cfg(not(target_os="android"))] + #[test] + fn smoke_failure() { + match Command::new("if-this-is-a-binary-then-the-world-has-ended").spawn() { + Ok(..) => panic!(), + Err(..) => {} + } + } + + #[cfg(not(target_os="android"))] + #[test] + fn exit_reported_right() { + let p = Command::new("false").spawn(); + assert!(p.is_ok()); + let mut p = p.unwrap(); + assert!(p.wait().unwrap().matches_exit_status(1)); + drop(p.wait().clone()); + } + + #[cfg(all(unix, not(target_os="android")))] + #[test] + fn signal_reported_right() { + let p = Command::new("/bin/sh").arg("-c").arg("kill -1 $$").spawn(); + assert!(p.is_ok()); + let mut p = p.unwrap(); + match p.wait().unwrap() { + process::ExitSignal(1) => {}, + result => panic!("not terminated by signal 1 (instead, {})", result), + } + } + + pub fn read_all(input: &mut Reader) -> String { + input.read_to_string().unwrap() + } + + pub fn run_output(cmd: Command) -> String { + let p = cmd.spawn(); + assert!(p.is_ok()); + let mut p = p.unwrap(); + assert!(p.stdout.is_some()); + let ret = read_all(p.stdout.as_mut().unwrap() as &mut Reader); + assert!(p.wait().unwrap().success()); + return ret; + } + + #[cfg(not(target_os="android"))] + #[test] + fn stdout_works() { + let mut cmd = Command::new("echo"); + cmd.arg("foobar").stdout(CreatePipe(false, true)); + assert_eq!(run_output(cmd), "foobar\n"); + } + + #[cfg(all(unix, not(target_os="android")))] + #[test] + fn set_cwd_works() { + let mut cmd = Command::new("/bin/sh"); + cmd.arg("-c").arg("pwd") + .cwd(&Path::new("/")) + .stdout(CreatePipe(false, true)); + assert_eq!(run_output(cmd), "/\n"); + } + + #[cfg(all(unix, not(target_os="android")))] + #[test] + fn stdin_works() { + let mut p = Command::new("/bin/sh") + .arg("-c").arg("read line; echo $line") + .stdin(CreatePipe(true, false)) + .stdout(CreatePipe(false, true)) + .spawn().unwrap(); + p.stdin.as_mut().unwrap().write("foobar".as_bytes()).unwrap(); + drop(p.stdin.take()); + let out = read_all(p.stdout.as_mut().unwrap() as &mut Reader); + assert!(p.wait().unwrap().success()); + assert_eq!(out, "foobar\n"); + } + + #[cfg(not(target_os="android"))] + #[test] + fn detach_works() { + let mut p = Command::new("true").detached().spawn().unwrap(); + assert!(p.wait().unwrap().success()); + } + + #[cfg(windows)] + #[test] + fn uid_fails_on_windows() { + assert!(Command::new("test").uid(10).spawn().is_err()); + } + + #[cfg(all(unix, not(target_os="android")))] + #[test] + fn uid_works() { + use libc; + let mut p = Command::new("/bin/sh") + .arg("-c").arg("true") + .uid(unsafe { libc::getuid() as uint }) + .gid(unsafe { libc::getgid() as uint }) + .spawn().unwrap(); + assert!(p.wait().unwrap().success()); + } + + #[cfg(all(unix, not(target_os="android")))] + #[test] + 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 } + assert!(Command::new("/bin/ls").uid(0).gid(0).spawn().is_err()); + } + + #[cfg(not(target_os="android"))] + #[test] + fn test_process_status() { + let mut status = Command::new("false").status().unwrap(); + assert!(status.matches_exit_status(1)); + + status = Command::new("true").status().unwrap(); + assert!(status.success()); + } + + #[test] + fn test_process_output_fail_to_start() { + match Command::new("/no-binary-by-this-name-should-exist").output() { + Err(e) => assert_eq!(e.kind, FileNotFound), + Ok(..) => panic!() + } + } + + #[cfg(not(target_os="android"))] + #[test] + fn test_process_output_output() { + let ProcessOutput {status, output, error} + = Command::new("echo").arg("hello").output().unwrap(); + let output_str = str::from_utf8(output.as_slice()).unwrap(); + + assert!(status.success()); + assert_eq!(output_str.trim().to_string(), "hello"); + // FIXME #7224 + if !running_on_valgrind() { + assert_eq!(error, Vec::new()); + } + } + + #[cfg(not(target_os="android"))] + #[test] + fn test_process_output_error() { + let ProcessOutput {status, output, error} + = Command::new("mkdir").arg(".").output().unwrap(); + + assert!(status.matches_exit_status(1)); + assert_eq!(output, Vec::new()); + assert!(!error.is_empty()); + } + + #[cfg(not(target_os="android"))] + #[test] + fn test_finish_once() { + let mut prog = Command::new("false").spawn().unwrap(); + assert!(prog.wait().unwrap().matches_exit_status(1)); + } + + #[cfg(not(target_os="android"))] + #[test] + fn test_finish_twice() { + let mut prog = Command::new("false").spawn().unwrap(); + assert!(prog.wait().unwrap().matches_exit_status(1)); + assert!(prog.wait().unwrap().matches_exit_status(1)); + } + + #[cfg(not(target_os="android"))] + #[test] + fn test_wait_with_output_once() { + let prog = Command::new("echo").arg("hello").spawn().unwrap(); + let ProcessOutput {status, output, error} = prog.wait_with_output().unwrap(); + let output_str = str::from_utf8(output.as_slice()).unwrap(); + + assert!(status.success()); + assert_eq!(output_str.trim().to_string(), "hello"); + // FIXME #7224 + if !running_on_valgrind() { + assert_eq!(error, Vec::new()); + } + } + + #[cfg(all(unix, not(target_os="android")))] + pub fn pwd_cmd() -> Command { + Command::new("pwd") + } + #[cfg(target_os="android")] + pub fn pwd_cmd() -> Command { + let mut cmd = Command::new("/system/bin/sh"); + cmd.arg("-c").arg("pwd"); + cmd + } + + #[cfg(windows)] + pub fn pwd_cmd() -> Command { + let mut cmd = Command::new("cmd"); + cmd.arg("/c").arg("cd"); + cmd + } + + #[test] + fn test_keep_current_working_dir() { + use os; + let prog = pwd_cmd().spawn().unwrap(); + + let output = String::from_utf8(prog.wait_with_output().unwrap().output).unwrap(); + let parent_dir = os::getcwd().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); + } + + #[test] + 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().unwrap().dir_path(); + let prog = pwd_cmd().cwd(&parent_dir).spawn().unwrap(); + + let output = String::from_utf8(prog.wait_with_output().unwrap().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(all(unix, not(target_os="android")))] + pub fn env_cmd() -> Command { + Command::new("env") + } + #[cfg(target_os="android")] + pub fn env_cmd() -> Command { + let mut cmd = Command::new("/system/bin/sh"); + cmd.arg("-c").arg("set"); + cmd + } + + #[cfg(windows)] + pub fn env_cmd() -> Command { + let mut cmd = Command::new("cmd"); + cmd.arg("/c").arg("set"); + cmd + } + + #[cfg(not(target_os="android"))] + #[test] + fn test_inherit_env() { + use os; + if running_on_valgrind() { return; } + + let prog = env_cmd().spawn().unwrap(); + let output = String::from_utf8(prog.wait_with_output().unwrap().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).as_slice()), + "output doesn't contain `{}={}`\n{}", + k, v, output); + } + } + #[cfg(target_os="android")] + #[test] + fn test_inherit_env() { + use os; + if running_on_valgrind() { return; } + + let mut prog = env_cmd().spawn().unwrap(); + let output = String::from_utf8(prog.wait_with_output().unwrap().output).unwrap(); + + let r = os::env(); + for &(ref k, ref v) in r.iter() { + // don't check android RANDOM variables + if *k != "RANDOM".to_string() { + assert!(output.contains(format!("{}={}", + *k, + *v).as_slice()) || + output.contains(format!("{}=\'{}\'", + *k, + *v).as_slice())); + } + } + } + + #[test] + fn test_override_env() { + use os; + + // In some build environments (such as chrooted Nix builds), `env` can + // only be found in the explicitly-provided PATH env variable, not in + // default places such as /bin or /usr/bin. So we need to pass through + // PATH to our sub-process. + let path_val: String; + let mut new_env = vec![("RUN_TEST_NEW_ENV", "123")]; + match os::getenv("PATH") { + None => {} + Some(val) => { + path_val = val; + new_env.push(("PATH", path_val.as_slice())) + } + } + + let prog = env_cmd().env_set_all(new_env.as_slice()).spawn().unwrap(); + let result = prog.wait_with_output().unwrap(); + let output = String::from_utf8_lossy(result.output.as_slice()).to_string(); + + assert!(output.contains("RUN_TEST_NEW_ENV=123"), + "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output); + } + + #[test] + fn test_add_to_env() { + let prog = env_cmd().env("RUN_TEST_NEW_ENV", "123").spawn().unwrap(); + let result = prog.wait_with_output().unwrap(); + let output = String::from_utf8_lossy(result.output.as_slice()).to_string(); + + 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 { + Command::new("sleep").arg("1000").spawn().unwrap() + } + #[cfg(windows)] + pub fn sleeper() -> Process { + // There's a `timeout` command on windows, but it doesn't like having + // its output piped, so instead just ping ourselves a few times with + // gaps in between so we're sure this process is alive for awhile + Command::new("ping").arg("127.0.0.1").arg("-n").arg("1000").spawn().unwrap() + } + + #[test] + fn test_kill() { + let mut p = sleeper(); + Process::kill(p.id(), PleaseExitSignal).unwrap(); + assert!(!p.wait().unwrap().success()); + } + + #[test] + fn test_exists() { + let mut p = sleeper(); + assert!(Process::kill(p.id(), 0).is_ok()); + p.signal_kill().unwrap(); + assert!(!p.wait().unwrap().success()); + } + + #[test] + fn test_zero() { + let mut p = sleeper(); + p.signal_kill().unwrap(); + for _ in range(0i, 20) { + if p.signal(0).is_err() { + assert!(!p.wait().unwrap().success()); + return + } + timer::sleep(Duration::milliseconds(100)); + } + panic!("never saw the child go away"); + } + + #[test] + fn wait_timeout() { + let mut p = sleeper(); + p.set_timeout(Some(10)); + assert_eq!(p.wait().err().unwrap().kind, TimedOut); + assert_eq!(p.wait().err().unwrap().kind, TimedOut); + p.signal_kill().unwrap(); + p.set_timeout(None); + assert!(p.wait().is_ok()); + } + + #[test] + fn wait_timeout2() { + let (tx, rx) = channel(); + let tx2 = tx.clone(); + let _t = Thread::spawn(move|| { + let mut p = sleeper(); + p.set_timeout(Some(10)); + assert_eq!(p.wait().err().unwrap().kind, TimedOut); + p.signal_kill().unwrap(); + tx.send(()).unwrap(); + }); + let _t = Thread::spawn(move|| { + let mut p = sleeper(); + p.set_timeout(Some(10)); + assert_eq!(p.wait().err().unwrap().kind, TimedOut); + p.signal_kill().unwrap(); + tx2.send(()).unwrap(); + }); + rx.recv().unwrap(); + rx.recv().unwrap(); + } + + #[test] + fn forget() { + let p = sleeper(); + let id = p.id(); + p.forget(); + assert!(Process::kill(id, 0).is_ok()); + assert!(Process::kill(id, PleaseExitSignal).is_ok()); + } + + #[test] + fn dont_close_fd_on_command_spawn() { + use sys::fs; + + let path = if cfg!(windows) { + Path::new("NUL") + } else { + Path::new("/dev/null") + }; + + let fdes = match fs::open(&path, Truncate, Write) { + Ok(f) => f, + Err(_) => panic!("failed to open file descriptor"), + }; + + let mut cmd = pwd_cmd(); + let _ = cmd.stdout(InheritFd(fdes.fd())); + assert!(cmd.status().unwrap().success()); + assert!(fdes.write("extra write\n".as_bytes()).is_ok()); + } + + #[test] + #[cfg(windows)] + fn env_map_keys_ci() { + use ffi::CString; + use super::EnvKey; + let mut cmd = Command::new(""); + cmd.env("path", "foo"); + cmd.env("Path", "bar"); + let env = &cmd.env.unwrap(); + let val = env.get(&EnvKey(CString::from_slice(b"PATH"))); + assert!(val.unwrap() == &CString::from_slice(b"bar")); + } +} |
