From 4175f1ce2fa57ca466e94aa59de9b9383f3c05a4 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 6 Feb 2015 09:42:57 -0800 Subject: [PATCH] Add std::process Per [RFC 579](https://github.com/rust-lang/rfcs/pull/579), this commit adds a new `std::process` module. This module is largely based on the existing `std::old_io::process` module, but refactors the API to use `OsStr` and other new standards set out by IO reform. The existing module is not yet deprecated, to allow for the new API to get a bit of testing before a mass migration to it. --- src/libstd/lib.rs | 1 + src/libstd/path.rs | 5 + src/libstd/process.rs | 834 +++++++++++++++++++++++++++++ src/libstd/sys/common/wtf8.rs | 21 + src/libstd/sys/unix/ext.rs | 83 ++- src/libstd/sys/unix/fs2.rs | 4 +- src/libstd/sys/unix/mod.rs | 2 + src/libstd/sys/unix/os.rs | 14 +- src/libstd/sys/unix/pipe2.rs | 49 ++ src/libstd/sys/unix/process.rs | 6 + src/libstd/sys/unix/process2.rs | 446 +++++++++++++++ src/libstd/sys/windows/fs2.rs | 20 +- src/libstd/sys/windows/handle.rs | 44 ++ src/libstd/sys/windows/mod.rs | 2 + src/libstd/sys/windows/pipe2.rs | 77 +++ src/libstd/sys/windows/process2.rs | 479 +++++++++++++++++ 16 files changed, 2051 insertions(+), 36 deletions(-) create mode 100644 src/libstd/process.rs create mode 100644 src/libstd/sys/unix/pipe2.rs create mode 100644 src/libstd/sys/unix/process2.rs create mode 100644 src/libstd/sys/windows/pipe2.rs create mode 100644 src/libstd/sys/windows/process2.rs diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index 83f0b7bc0e9..e6ca1bfdb81 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -258,6 +258,7 @@ pub mod os; pub mod env; pub mod path; pub mod old_path; +pub mod process; pub mod rand; pub mod time; diff --git a/src/libstd/path.rs b/src/libstd/path.rs index 4984b4f9aba..271a4cdb629 100755 --- a/src/libstd/path.rs +++ b/src/libstd/path.rs @@ -942,6 +942,11 @@ impl PathBuf { true } + + /// Consume the `PathBuf`, yielding its internal `OsString` storage + pub fn into_os_string(self) -> OsString { + self.inner + } } impl<'a, P: ?Sized + 'a> iter::FromIterator<&'a P> for PathBuf where P: AsPath { diff --git a/src/libstd/process.rs b/src/libstd/process.rs new file mode 100644 index 00000000000..d2b98ec8939 --- /dev/null +++ b/src/libstd/process.rs @@ -0,0 +1,834 @@ +// Copyright 2015 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. + +//! Working with processes. + +#![unstable(feature = "process", reason = "recently added via RFC 579")] +#![allow(non_upper_case_globals)] + +use prelude::v1::*; +use io::prelude::*; + +use ffi::AsOsStr; +use fmt; +use io::{self, Error, ErrorKind}; +use path::AsPath; +use libc; +use sync::mpsc::{channel, Receiver}; +use sys::pipe2::{self, AnonPipe}; +use sys::process2::Process as ProcessImp; +use sys::process2::Command as CommandImp; +use sys::process2::ExitStatus as ExitStatusImp; +use sys_common::{AsInner, AsInnerMut}; +use thread::Thread; + +/// 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 +/// # #![feature(process)] +/// +/// use std::process::Command; +/// +/// let output = Command::new("/bin/cat").arg("file.txt").output().unwrap_or_else(|e| { +/// panic!("failed to execute child: {}", e) +/// }); +/// let contents = output.stdout; +/// assert!(output.status.success()); +/// ``` +pub struct Child { + handle: ProcessImp, + + /// None until wait() or wait_with_output() is called. + status: Option, + + /// The handle for writing to the child's stdin, if it has been captured + pub stdin: Option, + + /// The handle for reading from the child's stdout, if it has been captured + pub stdout: Option, + + /// The handle for reading from the child's stderr, if it has been captured + pub stderr: Option, +} + +/// A handle to a child procesess's stdin +pub struct ChildStdin { + inner: AnonPipe +} + +impl Write for ChildStdin { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// A handle to a child procesess's stdout +pub struct ChildStdout { + inner: AnonPipe +} + +impl Read for ChildStdout { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.inner.read(buf) + } +} + +/// A handle to a child procesess's stderr +pub struct ChildStderr { + inner: AnonPipe +} + +impl Read for ChildStderr { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.inner.read(buf) + } +} + +/// 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: +/// +/// ``` +/// # #![feature(process)] +/// +/// use std::process::Command; +/// +/// let output = Command::new("sh").arg("-c").arg("echo hello").output().unwrap_or_else(|e| { +/// panic!("failed to execute process: {}", e) +/// }); +/// let hello = output.stdout; +/// ``` +pub struct Command { + inner: CommandImp, + + // Details explained in the builder methods + stdin: Option, + stdout: Option, + stderr: Option, +} + +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 + /// * Inherit stdin/stdout/stderr for `run` or `status`, but create pipes for `output` + /// + /// Builder methods are provided to change these defaults and + /// otherwise configure the process. + pub fn new(program: &S) -> Command { + Command { + inner: CommandImp::new(program.as_os_str()), + stdin: None, + stdout: None, + stderr: None, + } + } + + /// Add an argument to pass to the program. + pub fn arg(&mut self, arg: &S) -> &mut Command { + self.inner.arg(arg.as_os_str()); + self + } + + /// Add multiple arguments to pass to the program. + pub fn args(&mut self, args: &[S]) -> &mut Command { + self.inner.args(args.iter().map(AsOsStr::as_os_str)); + self + } + + /// 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(&mut self, key: &S, val: &T) -> &mut Command where + S: AsOsStr, T: AsOsStr + { + self.inner.env(key.as_os_str(), val.as_os_str()); + self + } + + /// Removes an environment variable mapping. + pub fn env_remove(&mut self, key: &S) -> &mut Command { + self.inner.env_remove(key.as_os_str()); + self + } + + /// Clears the entire environment map for the child process. + pub fn env_clear(&mut self) -> &mut Command { + self.inner.env_clear(); + self + } + + /// Set the working directory for the child process. + pub fn current_dir(&mut self, dir: &P) -> &mut Command { + self.inner.cwd(dir.as_path().as_os_str()); + 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(&mut self, cfg: Stdio) -> &mut Command { + self.stdin = Some(cfg.0); + 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(&mut self, cfg: Stdio) -> &mut Command { + self.stdout = Some(cfg.0); + 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(&mut self, cfg: Stdio) -> &mut Command { + self.stderr = Some(cfg.0); + self + } + + fn spawn_inner(&self, default_io: StdioImp) -> io::Result { + let (their_stdin, our_stdin) = try!( + setup_io(self.stdin.as_ref().unwrap_or(&default_io), 0, true) + ); + let (their_stdout, our_stdout) = try!( + setup_io(self.stdout.as_ref().unwrap_or(&default_io), 1, false) + ); + let (their_stderr, our_stderr) = try!( + setup_io(self.stderr.as_ref().unwrap_or(&default_io), 2, false) + ); + + match ProcessImp::spawn(&self.inner, their_stdin, their_stdout, their_stderr) { + Err(e) => Err(e), + Ok(handle) => Ok(Child { + handle: handle, + status: None, + stdin: our_stdin.map(|fd| ChildStdin { inner: fd }), + stdout: our_stdout.map(|fd| ChildStdout { inner: fd }), + stderr: our_stderr.map(|fd| ChildStderr { inner: fd }), + }) + } + } + + /// Executes the command as a child process, returning a handle to it. + /// + /// By default, stdin, stdout and stderr are inherited by the parent. + pub fn spawn(&mut self) -> io::Result { + self.spawn_inner(StdioImp::Inherit) + } + + /// Executes the command as a child process, waiting for it to finish and + /// collecting all of its output. + /// + /// By default, stdin, stdout and stderr are captured (and used to + /// provide the resulting output). + /// + /// # Example + /// + /// ``` + /// # #![feature(process)] + /// use std::process::Command; + /// + /// let output = Command::new("cat").arg("foot.txt").output().unwrap_or_else(|e| { + /// panic!("failed to execute process: {}", e) + /// }); + /// + /// println!("status: {}", output.status); + /// println!("stdout: {}", String::from_utf8_lossy(output.stdout.as_slice())); + /// println!("stderr: {}", String::from_utf8_lossy(output.stderr.as_slice())); + /// ``` + pub fn output(&mut self) -> io::Result { + self.spawn_inner(StdioImp::Capture).and_then(|p| p.wait_with_output()) + } + + /// Executes a command as a child process, waiting for it to finish and + /// collecting its exit status. + /// + /// By default, stdin, stdout and stderr are inherited by the parent. + /// + /// # Example + /// + /// ``` + /// # #![feature(process)] + /// use std::process::Command; + /// + /// let status = Command::new("ls").status().unwrap_or_else(|e| { + /// panic!("failed to execute process: {}", e) + /// }); + /// + /// println!("process exited with: {}", status); + /// ``` + pub fn status(&mut self) -> io::Result { + self.spawn().and_then(|mut p| p.wait()) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +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, "{:?}", self.inner.program)); + for arg in &self.inner.args { + try!(write!(f, " {:?}", arg)); + } + Ok(()) + } +} + +impl AsInner for Command { + fn as_inner(&self) -> &CommandImp { &self.inner } +} + +impl AsInnerMut for Command { + fn as_inner_mut(&mut self) -> &mut CommandImp { &mut self.inner } +} + +fn setup_io(io: &StdioImp, fd: libc::c_int, readable: bool) + -> io::Result<(Option, Option)> +{ + use self::StdioImp::*; + Ok(match *io { + Null => { + (None, None) + } + Inherit => { + (Some(AnonPipe::from_fd(fd)), None) + } + Capture => { + let (reader, writer) = try!(unsafe { pipe2::anon_pipe() }); + if readable { + (Some(reader), Some(writer)) + } else { + (Some(writer), Some(reader)) + } + } + }) +} + +/// The output of a finished process. +#[derive(PartialEq, Eq, Clone)] +pub struct Output { + /// The status (exit code) of the process. + pub status: ExitStatus, + /// The data that the process wrote to stdout. + pub stdout: Vec, + /// The data that the process wrote to stderr. + pub stderr: Vec, +} + +/// Describes what to do with a standard io stream for a child process. +pub struct Stdio(StdioImp); + +// The internal enum for stdio setup; see below for descriptions. +#[derive(Clone)] +enum StdioImp { + Capture, + Inherit, + Null, +} + +impl Stdio { + /// A new pipe should be arranged to connect the parent and child processes. + pub fn capture() -> Stdio { Stdio(StdioImp::Capture) } + + /// The child inherits from the corresponding parent descriptor. + pub fn inherit() -> Stdio { Stdio(StdioImp::Capture) } + + /// This stream will be ignored. This is the equivalent of attaching the + /// stream to `/dev/null` + pub fn null() -> Stdio { Stdio(StdioImp::Capture) } +} + +/// Describes the result of a process after it has terminated. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ExitStatus(ExitStatusImp); + +impl ExitStatus { + /// Was termination successful? Signal termination not considered a success, + /// and success is defined as a zero exit status. + pub fn success(&self) -> bool { + self.0.success() + } + + /// Return the exit code of the process, if any. + /// + /// On Unix, this will return `None` if the process was terminated + /// by a signal; `std::os::unix` provides an extension trait for + /// extracting the signal and other details from the `ExitStatus`. + pub fn code(&self) -> Option { + self.0.code() + } +} + +impl AsInner for ExitStatus { + fn as_inner(&self) -> &ExitStatusImp { &self.0 } +} + +impl fmt::Display for ExitStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Child { + /// Forces the child to exit. This is equivalent to sending a + /// SIGKILL on unix platforms. + pub fn kill(&mut self) -> io::Result<()> { + #[cfg(unix)] fn collect_status(p: &mut Child) { + // 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.status.is_none() { + match p.handle.try_wait() { + Some(status) => { p.status = Some(status); } + None => {} + } + } + } + #[cfg(windows)] fn collect_status(_p: &mut Child) {} + + 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.status.is_some() { + return Err(Error::new( + ErrorKind::InvalidInput, + "invalid argument: can't kill an exited process", + None + )) + } + + unsafe { self.handle.kill() } + } + + /// 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, if any, will be closed + /// before waiting. This helps avoid deadlock: it ensures that the + /// child does not block waiting for input from the parent, while + /// the parent waits for the child to exit. + pub fn wait(&mut self) -> io::Result { + drop(self.stdin.take()); + match self.status { + Some(code) => Ok(ExitStatus(code)), + None => { + let status = try!(self.handle.wait()); + self.status = Some(status); + Ok(ExitStatus(status)) + } + } + } + + /// Simultaneously wait for the child to exit and collect all remaining + /// output on the stdout/stderr handles, returning a `Output` + /// instance. + /// + /// The stdin handle to the child process, if any, will be closed + /// before waiting. This helps avoid deadlock: it ensures that the + /// child does not block waiting for input from the parent, while + /// the parent waits for the child to exit. + pub fn wait_with_output(mut self) -> io::Result { + drop(self.stdin.take()); + fn read(stream: Option) -> Receiver>> { + let (tx, rx) = channel(); + match stream { + Some(stream) => { + Thread::spawn(move || { + let mut stream = stream; + let mut ret = Vec::new(); + let res = stream.read_to_end(&mut ret); + tx.send(res.map(|_| ret)).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(Output { + status: status, + stdout: stdout.recv().unwrap().unwrap_or(Vec::new()), + stderr: stderr.recv().unwrap().unwrap_or(Vec::new()), + }) + } +} + +#[cfg(test)] +mod tests { + use io::ErrorKind; + use io::prelude::*; + use prelude::v1::{Ok, Err, range, drop, Some, None, Vec}; + use prelude::v1::{String, Clone}; + use prelude::v1::{SliceExt, Str, StrExt, AsSlice, ToString, GenericPath}; + use path::Path; + use old_path; + use old_io::fs::PathExtensions; + use rt::running_on_valgrind; + use str; + use super::{Child, Command, Output, ExitStatus, Stdio}; + 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().code() == Some(1)); + drop(p.wait().clone()); + } + + #[cfg(all(unix, not(target_os="android")))] + #[test] + fn signal_reported_right() { + use os::unix::ExitStatusExt; + + 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().signal() { + Some(1) => {}, + result => panic!("not terminated by signal 1 (instead, {:?})", result), + } + } + + pub fn run_output(mut cmd: Command) -> String { + let p = cmd.spawn(); + assert!(p.is_ok()); + let mut p = p.unwrap(); + assert!(p.stdout.is_some()); + let mut ret = String::new(); + p.stdout.as_mut().unwrap().read_to_string(&mut ret).unwrap(); + 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(Stdio::capture()); + assert_eq!(run_output(cmd), "foobar\n"); + } + + #[cfg(all(unix, not(target_os="android")))] + #[test] + fn set_current_dir_works() { + let mut cmd = Command::new("/bin/sh"); + cmd.arg("-c").arg("pwd") + .current_dir("/") + .stdout(Stdio::capture()); + 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(Stdio::capture()) + .stdout(Stdio::capture()) + .spawn().unwrap(); + p.stdin.as_mut().unwrap().write("foobar".as_bytes()).unwrap(); + drop(p.stdin.take()); + let mut out = String::new(); + p.stdout.as_mut().unwrap().read_to_string(&mut out).unwrap(); + assert!(p.wait().unwrap().success()); + assert_eq!(out, "foobar\n"); + } + + + #[cfg(all(unix, not(target_os="android")))] + #[test] + fn uid_works() { + use os::unix::*; + use libc; + let mut p = Command::new("/bin/sh") + .arg("-c").arg("true") + .uid(unsafe { libc::getuid() }) + .gid(unsafe { libc::getgid() }) + .spawn().unwrap(); + assert!(p.wait().unwrap().success()); + } + + #[cfg(all(unix, not(target_os="android")))] + #[test] + fn uid_to_root_fails() { + use os::unix::*; + 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.code() == Some(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(), ErrorKind::FileNotFound), + Ok(..) => panic!() + } + } + + #[cfg(not(target_os="android"))] + #[test] + fn test_process_output_output() { + let Output {status, stdout, stderr} + = Command::new("echo").arg("hello").output().unwrap(); + let output_str = str::from_utf8(stdout.as_slice()).unwrap(); + + assert!(status.success()); + assert_eq!(output_str.trim().to_string(), "hello"); + // FIXME #7224 + if !running_on_valgrind() { + assert_eq!(stderr, Vec::new()); + } + } + + #[cfg(not(target_os="android"))] + #[test] + fn test_process_output_error() { + let Output {status, stdout, stderr} + = Command::new("mkdir").arg(".").output().unwrap(); + + assert!(status.code() == Some(1)); + assert_eq!(stdout, Vec::new()); + assert!(!stderr.is_empty()); + } + + #[cfg(not(target_os="android"))] + #[test] + fn test_finish_once() { + let mut prog = Command::new("false").spawn().unwrap(); + assert!(prog.wait().unwrap().code() == Some(1)); + } + + #[cfg(not(target_os="android"))] + #[test] + fn test_finish_twice() { + let mut prog = Command::new("false").spawn().unwrap(); + assert!(prog.wait().unwrap().code() == Some(1)); + assert!(prog.wait().unwrap().code() == Some(1)); + } + + #[cfg(not(target_os="android"))] + #[test] + fn test_wait_with_output_once() { + let prog = Command::new("echo").arg("hello").stdout(Stdio::capture()) + .spawn().unwrap(); + let Output {status, stdout, stderr} = prog.wait_with_output().unwrap(); + let output_str = str::from_utf8(stdout.as_slice()).unwrap(); + + assert!(status.success()); + assert_eq!(output_str.trim().to_string(), "hello"); + // FIXME #7224 + if !running_on_valgrind() { + assert_eq!(stderr, 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().stdout).unwrap(); + let parent_dir = os::getcwd().unwrap(); + let child_dir = old_path::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 result = pwd_cmd().current_dir(&parent_dir).output().unwrap(); + + let output = String::from_utf8(result.stdout).unwrap(); + let child_dir = old_path::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 result = env_cmd().output().unwrap(); + let output = String::from_utf8(result.stdout).unwrap(); + + let r = os::env(); + for &(ref k, ref v) in &r { + // 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 result = env_cmd().output().unwrap(); + let output = String::from_utf8(result.stdout).unwrap(); + + let r = os::env(); + for &(ref k, ref v) in &r { + // 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 env; + + // 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 mut cmd = env_cmd(); + cmd.env_clear().env("RUN_TEST_NEW_ENV", "123"); + if let Some(p) = env::var_os("PATH") { + cmd.env("PATH", &p); + } + let result = cmd.output().unwrap(); + let output = String::from_utf8_lossy(result.stdout.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 result = env_cmd().env("RUN_TEST_NEW_ENV", "123").output().unwrap(); + let output = String::from_utf8_lossy(result.stdout.as_slice()).to_string(); + + assert!(output.contains("RUN_TEST_NEW_ENV=123"), + "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output); + } +} diff --git a/src/libstd/sys/common/wtf8.rs b/src/libstd/sys/common/wtf8.rs index 89200471465..6047f94b3b4 100644 --- a/src/libstd/sys/common/wtf8.rs +++ b/src/libstd/sys/common/wtf8.rs @@ -27,6 +27,7 @@ use core::char::{encode_utf8_raw, encode_utf16_raw}; use core::str::{char_range_at_raw, next_code_point}; use core::raw::Slice as RawSlice; +use ascii::*; use borrow::Cow; use cmp; use fmt; @@ -38,6 +39,7 @@ use ops; use slice; use str; use string::{String, CowString}; +use sys_common::AsInner; use unicode::str::{Utf16Item, utf16_items}; use vec::Vec; @@ -384,6 +386,10 @@ pub struct Wtf8 { bytes: [u8] } +impl AsInner<[u8]> for Wtf8 { + fn as_inner(&self) -> &[u8] { &self.bytes } +} + // FIXME: https://github.com/rust-lang/rust/issues/18805 impl PartialEq for Wtf8 { fn eq(&self, other: &Wtf8) -> bool { self.bytes.eq(&other.bytes) } @@ -811,6 +817,21 @@ impl<'a, S: Writer + Hasher> Hash for Wtf8 { } } +impl AsciiExt for Wtf8 { + fn is_ascii(&self) -> bool { + self.bytes.is_ascii() + } + fn to_ascii_uppercase(&self) -> Wtf8Buf { + Wtf8Buf { bytes: self.bytes.to_ascii_uppercase() } + } + fn to_ascii_lowercase(&self) -> Wtf8Buf { + Wtf8Buf { bytes: self.bytes.to_ascii_lowercase() } + } + fn eq_ignore_ascii_case(&self, other: &Wtf8) -> bool { + self.bytes.eq_ignore_ascii_case(&other.bytes) + } +} + #[cfg(test)] mod tests { use prelude::v1::*; diff --git a/src/libstd/sys/unix/ext.rs b/src/libstd/sys/unix/ext.rs index 1d95f1cce7e..bbbe022fbaf 100644 --- a/src/libstd/sys/unix/ext.rs +++ b/src/libstd/sys/unix/ext.rs @@ -31,14 +31,17 @@ #![unstable(feature = "std_misc")] -use ffi::{OsStr, OsString}; +use prelude::v1::*; + +use ffi::{CString, OsStr, OsString}; use fs::{self, Permissions, OpenOptions}; use net; -use libc; use mem; +use process; +use sys; use sys::os_str::Buf; use sys_common::{AsInner, AsInnerMut, IntoInner, FromInner}; -use vec::Vec; +use libc::{self, gid_t, uid_t}; use old_io; @@ -121,7 +124,11 @@ impl AsRawFd for net::UdpSocket { fn as_raw_fd(&self) -> Fd { *self.as_inner().socket().as_inner() } } -// Unix-specific extensions to `OsString`. +//////////////////////////////////////////////////////////////////////////////// +// OsString and OsStr +//////////////////////////////////////////////////////////////////////////////// + +/// Unix-specific extensions to `OsString`. pub trait OsStringExt { /// Create an `OsString` from a byte vector. fn from_vec(vec: Vec) -> Self; @@ -140,19 +147,28 @@ impl OsStringExt for OsString { } } -// Unix-specific extensions to `OsStr`. +/// Unix-specific extensions to `OsStr`. pub trait OsStrExt { - fn from_byte_slice(slice: &[u8]) -> &OsStr; - fn as_byte_slice(&self) -> &[u8]; + fn from_bytes(slice: &[u8]) -> &OsStr; + + /// Get the underlying byte view of the `OsStr` slice. + fn as_bytes(&self) -> &[u8]; + + /// Convert the `OsStr` slice into a `CString`. + fn to_cstring(&self) -> CString; } impl OsStrExt for OsStr { - fn from_byte_slice(slice: &[u8]) -> &OsStr { + fn from_bytes(slice: &[u8]) -> &OsStr { unsafe { mem::transmute(slice) } } - fn as_byte_slice(&self) -> &[u8] { + fn as_bytes(&self) -> &[u8] { &self.as_inner().inner } + + fn to_cstring(&self) -> CString { + CString::from_slice(self.as_bytes()) + } } // Unix-specific extensions to `Permissions` @@ -181,10 +197,57 @@ impl OpenOptionsExt for OpenOptions { } } +//////////////////////////////////////////////////////////////////////////////// +// Process and Command +//////////////////////////////////////////////////////////////////////////////// + +/// Unix-specific extensions to the `std::process::Command` builder +pub trait CommandExt { + /// Sets the child process's user id. This translates to a + /// `setuid` call in the child process. Failure in the `setuid` + /// call will cause the spawn to fail. + fn uid(&mut self, id: uid_t) -> &mut process::Command; + + /// Similar to `uid`, but sets the group id of the child process. This has + /// the same semantics as the `uid` field. + fn gid(&mut self, id: gid_t) -> &mut process::Command; +} + +impl CommandExt for process::Command { + fn uid(&mut self, id: uid_t) -> &mut process::Command { + self.as_inner_mut().uid = Some(id); + self + } + + fn gid(&mut self, id: gid_t) -> &mut process::Command { + self.as_inner_mut().gid = Some(id); + self + } +} + +/// Unix-specific extensions to `std::process::ExitStatus` +pub trait ExitStatusExt { + /// If the process was terminated by a signal, returns that signal. + fn signal(&self) -> Option; +} + +impl ExitStatusExt for process::ExitStatus { + fn signal(&self) -> Option { + match *self.as_inner() { + sys::process2::ExitStatus::Signal(s) => Some(s), + _ => None + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Prelude +//////////////////////////////////////////////////////////////////////////////// + /// A prelude for conveniently writing platform-specific code. /// /// Includes all extension traits, and some important type definitions. pub mod prelude { #[doc(no_inline)] - pub use super::{Fd, AsRawFd, OsStrExt, OsStringExt, PermissionsExt}; + pub use super::{Fd, AsRawFd, OsStrExt, OsStringExt, PermissionsExt, CommandExt, ExitStatusExt}; } diff --git a/src/libstd/sys/unix/fs2.rs b/src/libstd/sys/unix/fs2.rs index 77d5503b683..e5904b074bc 100644 --- a/src/libstd/sys/unix/fs2.rs +++ b/src/libstd/sys/unix/fs2.rs @@ -139,7 +139,7 @@ impl Drop for ReadDir { impl DirEntry { pub fn path(&self) -> PathBuf { - self.root.join(::from_byte_slice(self.name_bytes())) + self.root.join(::from_bytes(self.name_bytes())) } fn name_bytes(&self) -> &[u8] { @@ -269,7 +269,7 @@ impl File { } fn cstr(path: &Path) -> CString { - CString::from_slice(path.as_os_str().as_byte_slice()) + CString::from_slice(path.as_os_str().as_bytes()) } pub fn mkdir(p: &Path) -> io::Result<()> { diff --git a/src/libstd/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs index 96a18a956c6..850189140d1 100644 --- a/src/libstd/sys/unix/mod.rs +++ b/src/libstd/sys/unix/mod.rs @@ -50,7 +50,9 @@ pub mod net; pub mod os; pub mod os_str; pub mod pipe; +pub mod pipe2; pub mod process; +pub mod process2; pub mod rwlock; pub mod stack_overflow; pub mod sync; diff --git a/src/libstd/sys/unix/os.rs b/src/libstd/sys/unix/os.rs index b191eda583c..8a6ef17818a 100644 --- a/src/libstd/sys/unix/os.rs +++ b/src/libstd/sys/unix/os.rs @@ -19,11 +19,13 @@ use fmt; use iter; use libc::{self, c_int, c_char, c_void}; use mem; +use io; use old_io::{IoResult, IoError, fs}; use ptr; use slice; use str; use sys::c; +use sys::fd; use sys::fs::FileDesc; use vec; @@ -118,7 +120,7 @@ pub struct SplitPaths<'a> { pub fn split_paths<'a>(unparsed: &'a OsStr) -> SplitPaths<'a> { fn is_colon(b: &u8) -> bool { *b == b':' } - let unparsed = unparsed.as_byte_slice(); + let unparsed = unparsed.as_bytes(); SplitPaths { iter: unparsed.split(is_colon as fn(&u8) -> bool) .map(Path::new as fn(&'a [u8]) -> Path) @@ -141,7 +143,7 @@ pub fn join_paths(paths: I) -> Result let sep = b':'; for (i, path) in paths.enumerate() { - let path = path.as_os_str().as_byte_slice(); + let path = path.as_os_str().as_bytes(); if i > 0 { joined.push(sep) } if path.contains(&sep) { return Err(JoinPathsError) @@ -391,7 +393,7 @@ pub fn env() -> Env { pub fn getenv(k: &OsStr) -> Option { unsafe { - let s = CString::from_slice(k.as_byte_slice()); + let s = CString::from_slice(k.as_bytes()); let s = libc::getenv(s.as_ptr()) as *const _; if s.is_null() { None @@ -403,8 +405,8 @@ pub fn getenv(k: &OsStr) -> Option { pub fn setenv(k: &OsStr, v: &OsStr) { unsafe { - let k = CString::from_slice(k.as_byte_slice()); - let v = CString::from_slice(v.as_byte_slice()); + let k = CString::from_slice(k.as_bytes()); + let v = CString::from_slice(v.as_bytes()); if libc::funcs::posix01::unistd::setenv(k.as_ptr(), v.as_ptr(), 1) != 0 { panic!("failed setenv: {}", IoError::last_error()); } @@ -413,7 +415,7 @@ pub fn setenv(k: &OsStr, v: &OsStr) { pub fn unsetenv(n: &OsStr) { unsafe { - let nbuf = CString::from_slice(n.as_byte_slice()); + let nbuf = CString::from_slice(n.as_bytes()); if libc::funcs::posix01::unistd::unsetenv(nbuf.as_ptr()) != 0 { panic!("failed unsetenv: {}", IoError::last_error()); } diff --git a/src/libstd/sys/unix/pipe2.rs b/src/libstd/sys/unix/pipe2.rs new file mode 100644 index 00000000000..7af2c0f0b2a --- /dev/null +++ b/src/libstd/sys/unix/pipe2.rs @@ -0,0 +1,49 @@ +// Copyright 2015 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. + +use prelude::v1::*; + +use sys::fd::FileDesc; +use io; +use libc; + +//////////////////////////////////////////////////////////////////////////////// +// Anonymous pipes +//////////////////////////////////////////////////////////////////////////////// + +pub struct AnonPipe(FileDesc); + +pub unsafe fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> { + let mut fds = [0; 2]; + if libc::pipe(fds.as_mut_ptr()) == 0 { + Ok((AnonPipe::from_fd(fds[0]), + AnonPipe::from_fd(fds[1]))) + } else { + Err(io::Error::last_os_error()) + } +} + +impl AnonPipe { + pub fn from_fd(fd: libc::c_int) -> AnonPipe { + AnonPipe(FileDesc::new(fd)) + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + + pub fn raw(&self) -> libc::c_int { + self.0.raw() + } +} diff --git a/src/libstd/sys/unix/process.rs b/src/libstd/sys/unix/process.rs index 52a8ac9c338..ab06109076e 100644 --- a/src/libstd/sys/unix/process.rs +++ b/src/libstd/sys/unix/process.rs @@ -32,6 +32,12 @@ pub use sys_common::ProcessConfig; helper_init! { static HELPER: Helper } +/// Unix-specific extensions to the Command builder +pub struct CommandExt { + uid: Option, + gid: Option, +} + /// The unique id of the process (this should never be negative). pub struct Process { pub pid: pid_t diff --git a/src/libstd/sys/unix/process2.rs b/src/libstd/sys/unix/process2.rs new file mode 100644 index 00000000000..5e2c207f375 --- /dev/null +++ b/src/libstd/sys/unix/process2.rs @@ -0,0 +1,446 @@ +// Copyright 2014-2015 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. + +use prelude::v1::*; + +use collections::HashMap; +use collections::hash_map::Hasher; +use env; +use ffi::{OsString, OsStr, CString}; +use fmt; +use hash::Hash; +use io::{self, Error, ErrorKind}; +use libc::{self, pid_t, c_void, c_int, gid_t, uid_t}; +use mem; +use old_io; +use os; +use os::unix::OsStrExt; +use ptr; +use sync::mpsc::{channel, Sender, Receiver}; +use sys::pipe2::AnonPipe; +use sys::{self, retry, c, wouldblock, set_nonblocking, ms_to_timeval, cvt}; +use sys_common::AsInner; + +//////////////////////////////////////////////////////////////////////////////// +// Command +//////////////////////////////////////////////////////////////////////////////// + +#[derive(Clone)] +pub struct Command { + pub program: CString, + pub args: Vec, + pub env: Option>, + pub cwd: Option, + pub uid: Option, + pub gid: Option, + pub detach: bool, // not currently exposed in std::process +} + +impl Command { + pub fn new(program: &OsStr) -> Command { + Command { + program: program.to_cstring(), + args: Vec::new(), + env: None, + cwd: None, + uid: None, + gid: None, + detach: false, + } + } + + pub fn arg(&mut self, arg: &OsStr) { + self.args.push(arg.to_cstring()) + } + pub fn args<'a, I: Iterator>(&mut self, args: I) { + self.args.extend(args.map(OsStrExt::to_cstring)) + } + fn init_env_map(&mut self) { + if self.env.is_none() { + self.env = Some(env::vars_os().collect()); + } + } + pub fn env(&mut self, key: &OsStr, val: &OsStr) { + self.init_env_map(); + self.env.as_mut().unwrap().insert(key.to_os_string(), val.to_os_string()); + } + pub fn env_remove(&mut self, key: &OsStr) { + self.init_env_map(); + self.env.as_mut().unwrap().remove(&key.to_os_string()); + } + pub fn env_clear(&mut self) { + self.env = Some(HashMap::new()) + } + pub fn cwd(&mut self, dir: &OsStr) { + self.cwd = Some(dir.to_cstring()) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Processes +//////////////////////////////////////////////////////////////////////////////// + +/// Unix exit statuses +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum ExitStatus { + /// Normal termination with an exit code. + Code(i32), + + /// Termination by signal, with the signal number. + /// + /// Never generated on Windows. + Signal(i32), +} + +impl ExitStatus { + pub fn success(&self) -> bool { + *self == ExitStatus::Code(0) + } + pub fn code(&self) -> Option { + match *self { + ExitStatus::Code(c) => Some(c), + _ => None + } + } +} + +impl fmt::Display for ExitStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ExitStatus::Code(code) => write!(f, "exit code: {}", code), + ExitStatus::Signal(code) => write!(f, "signal: {}", code), + } + } +} + +/// The unique id of the process (this should never be negative). +pub struct Process { + pid: pid_t +} + +const CLOEXEC_MSG_FOOTER: &'static [u8] = b"NOEX"; + +impl Process { + pub fn id(&self) -> pid_t { + self.pid + } + + pub unsafe fn kill(&self) -> io::Result<()> { + try!(cvt(libc::funcs::posix88::signal::kill(self.pid, libc::SIGKILL))); + Ok(()) + } + + pub fn spawn(cfg: &Command, + in_fd: Option, out_fd: Option, err_fd: Option) + -> io::Result + { + use libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp}; + use libc::funcs::bsd44::getdtablesize; + + mod rustrt { + extern { + pub fn rust_unset_sigprocmask(); + } + } + + unsafe fn set_cloexec(fd: c_int) { + let ret = c::ioctl(fd, c::FIOCLEX); + assert_eq!(ret, 0); + } + + let dirp = cfg.cwd.as_ref().map(|c| c.as_ptr()).unwrap_or(ptr::null()); + + with_envp(cfg.env.as_ref(), |envp: *const c_void| { + with_argv(&cfg.program, &cfg.args, |argv: *const *const libc::c_char| unsafe { + let (input, mut output) = try!(sys::pipe2::anon_pipe()); + + // We may use this in the child, so perform allocations before the + // fork + let devnull = b"/dev/null\0"; + + set_cloexec(output.raw()); + + let pid = fork(); + if pid < 0 { + return Err(Error::last_os_error()) + } else if pid > 0 { + #[inline] + fn combine(arr: &[u8]) -> i32 { + let a = arr[0] as u32; + let b = arr[1] as u32; + let c = arr[2] as u32; + let d = arr[3] as u32; + + ((a << 24) | (b << 16) | (c << 8) | (d << 0)) as i32 + } + + let p = Process{ pid: pid }; + drop(output); + let mut bytes = [0; 8]; + + // loop to handle EINTER + loop { + match input.read(&mut bytes) { + Ok(8) => { + assert!(combine(CLOEXEC_MSG_FOOTER) == combine(&bytes[4.. 8]), + "Validation on the CLOEXEC pipe failed: {:?}", bytes); + let errno = combine(&bytes[0.. 4]); + assert!(p.wait().is_ok(), + "wait() should either return Ok or panic"); + return Err(Error::from_os_error(errno)) + } + Ok(0) => return Ok(p), + Err(ref e) if e.kind() == ErrorKind::Interrupted => {} + Err(e) => { + assert!(p.wait().is_ok(), + "wait() should either return Ok or panic"); + panic!("the CLOEXEC pipe failed: {:?}", e) + }, + Ok(..) => { // pipe I/O up to PIPE_BUF bytes should be atomic + assert!(p.wait().is_ok(), + "wait() should either return Ok or panic"); + panic!("short read on the CLOEXEC pipe") + } + } + } + } + + // And at this point we've reached a special time in the life of the + // child. The child must now be considered hamstrung and unable to + // do anything other than syscalls really. Consider the following + // scenario: + // + // 1. Thread A of process 1 grabs the malloc() mutex + // 2. Thread B of process 1 forks(), creating thread C + // 3. Thread C of process 2 then attempts to malloc() + // 4. The memory of process 2 is the same as the memory of + // process 1, so the mutex is locked. + // + // This situation looks a lot like deadlock, right? It turns out + // that this is what pthread_atfork() takes care of, which is + // presumably implemented across platforms. The first thing that + // threads to *before* forking is to do things like grab the malloc + // mutex, and then after the fork they unlock it. + // + // Despite this information, libnative's spawn has been witnessed to + // deadlock on both OSX and FreeBSD. I'm not entirely sure why, but + // all collected backtraces point at malloc/free traffic in the + // child spawned process. + // + // For this reason, the block of code below should contain 0 + // invocations of either malloc of free (or their related friends). + // + // As an example of not having malloc/free traffic, we don't close + // this file descriptor by dropping the FileDesc (which contains an + // allocation). Instead we just close it manually. This will never + // have the drop glue anyway because this code never returns (the + // child will either exec() or invoke libc::exit) + let _ = libc::close(input.raw()); + + fn fail(output: &mut AnonPipe) -> ! { + let errno = sys::os::errno() as u32; + let bytes = [ + (errno >> 24) as u8, + (errno >> 16) as u8, + (errno >> 8) as u8, + (errno >> 0) as u8, + CLOEXEC_MSG_FOOTER[0], CLOEXEC_MSG_FOOTER[1], + CLOEXEC_MSG_FOOTER[2], CLOEXEC_MSG_FOOTER[3] + ]; + // pipe I/O up to PIPE_BUF bytes should be atomic + assert!(output.write(&bytes).is_ok()); + unsafe { libc::_exit(1) } + } + + rustrt::rust_unset_sigprocmask(); + + // If a stdio file descriptor is set to be ignored, we don't + // actually close it, but rather open up /dev/null into that + // file descriptor. Otherwise, the first file descriptor opened + // up in the child would be numbered as one of the stdio file + // descriptors, which is likely to wreak havoc. + let setup = |&: src: Option, dst: c_int| { + let src = match src { + None => { + let flags = if dst == libc::STDIN_FILENO { + libc::O_RDONLY + } else { + libc::O_RDWR + }; + libc::open(devnull.as_ptr() as *const _, flags, 0) + } + Some(obj) => { + let fd = obj.raw(); + // Leak the memory and the file descriptor. We're in the + // child now an all our resources are going to be + // cleaned up very soon + mem::forget(obj); + fd + } + }; + src != -1 && retry(|| dup2(src, dst)) != -1 + }; + + if !setup(in_fd, libc::STDIN_FILENO) { fail(&mut output) } + if !setup(out_fd, libc::STDOUT_FILENO) { fail(&mut output) } + if !setup(err_fd, libc::STDERR_FILENO) { fail(&mut output) } + + // close all other fds + for fd in (3..getdtablesize()).rev() { + if fd != output.raw() { + let _ = close(fd as c_int); + } + } + + match cfg.gid { + Some(u) => { + if libc::setgid(u as libc::gid_t) != 0 { + fail(&mut output); + } + } + None => {} + } + match cfg.uid { + Some(u) => { + // When dropping privileges from root, the `setgroups` call + // will remove any extraneous groups. If we don't call this, + // then even though our uid has dropped, we may still have + // groups that enable us to do super-user things. This will + // fail if we aren't root, so don't bother checking the + // return value, this is just done as an optimistic + // privilege dropping function. + extern { + fn setgroups(ngroups: libc::c_int, + ptr: *const libc::c_void) -> libc::c_int; + } + let _ = setgroups(0, ptr::null()); + + if libc::setuid(u as libc::uid_t) != 0 { + fail(&mut output); + } + } + None => {} + } + if cfg.detach { + // Don't check the error of setsid because it fails if we're the + // process leader already. We just forked so it shouldn't return + // error, but ignore it anyway. + let _ = libc::setsid(); + } + if !dirp.is_null() && chdir(dirp) == -1 { + fail(&mut output); + } + if !envp.is_null() { + *sys::os::environ() = envp as *const _; + } + let _ = execvp(*argv, argv as *mut _); + fail(&mut output); + }) + }) + } + + pub fn wait(&self) -> io::Result { + let mut status = 0 as c_int; + try!(cvt(retry(|| unsafe { c::waitpid(self.pid, &mut status, 0) }))); + Ok(translate_status(status)) + } + + pub fn try_wait(&self) -> Option { + let mut status = 0 as c_int; + match retry(|| unsafe { + c::waitpid(self.pid, &mut status, c::WNOHANG) + }) { + n if n == self.pid => Some(translate_status(status)), + 0 => None, + n => panic!("unknown waitpid error `{:?}`: {:?}", n, + super::last_error()), + } + } +} + +fn with_argv(prog: &CString, args: &[CString], cb: F) -> T + where F : FnOnce(*const *const libc::c_char) -> T +{ + let mut ptrs: Vec<*const libc::c_char> = Vec::with_capacity(args.len()+1); + + // Convert the CStrings into an array of pointers. Note: the + // lifetime of the various CStrings involved is guaranteed to be + // larger than the lifetime of our invocation of cb, but this is + // technically unsafe as the callback could leak these pointers + // out of our scope. + ptrs.push(prog.as_ptr()); + ptrs.extend(args.iter().map(|tmp| tmp.as_ptr())); + + // Add a terminating null pointer (required by libc). + ptrs.push(ptr::null()); + + cb(ptrs.as_ptr()) +} + +fn with_envp(env: Option<&HashMap>, cb: F) -> T + where F : FnOnce(*const c_void) -> T +{ + // On posixy systems we can pass a char** for envp, which is a + // null-terminated array of "k=v\0" strings. Since we must create + // these strings locally, yet expose a raw pointer to them, we + // create a temporary vector to own the CStrings that outlives the + // call to cb. + match env { + Some(env) => { + let mut tmps = Vec::with_capacity(env.len()); + + for pair in env { + let mut kv = Vec::new(); + kv.push_all(pair.0.as_bytes()); + kv.push('=' as u8); + kv.push_all(pair.1.as_bytes()); + kv.push(0); // terminating null + tmps.push(kv); + } + + // As with `with_argv`, this is unsafe, since cb could leak the pointers. + let mut ptrs: Vec<*const libc::c_char> = + tmps.iter() + .map(|tmp| tmp.as_ptr() as *const libc::c_char) + .collect(); + ptrs.push(ptr::null()); + + cb(ptrs.as_ptr() as *const c_void) + } + _ => cb(ptr::null()) + } +} + +fn translate_status(status: c_int) -> ExitStatus { + #![allow(non_snake_case)] + #[cfg(any(target_os = "linux", target_os = "android"))] + mod imp { + pub fn WIFEXITED(status: i32) -> bool { (status & 0xff) == 0 } + pub fn WEXITSTATUS(status: i32) -> i32 { (status >> 8) & 0xff } + pub fn WTERMSIG(status: i32) -> i32 { status & 0x7f } + } + + #[cfg(any(target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd"))] + mod imp { + pub fn WIFEXITED(status: i32) -> bool { (status & 0x7f) == 0 } + pub fn WEXITSTATUS(status: i32) -> i32 { status >> 8 } + pub fn WTERMSIG(status: i32) -> i32 { status & 0o177 } + } + + if imp::WIFEXITED(status) { + ExitStatus::Code(imp::WEXITSTATUS(status)) + } else { + ExitStatus::Signal(imp::WTERMSIG(status)) + } +} diff --git a/src/libstd/sys/windows/fs2.rs b/src/libstd/sys/windows/fs2.rs index 74bb509789b..8abcd90efe8 100644 --- a/src/libstd/sys/windows/fs2.rs +++ b/src/libstd/sys/windows/fs2.rs @@ -225,27 +225,11 @@ impl File { } pub fn read(&self, buf: &mut [u8]) -> io::Result { - let mut read = 0; - try!(cvt(unsafe { - libc::ReadFile(self.handle.raw(), - buf.as_ptr() as libc::LPVOID, - buf.len() as libc::DWORD, - &mut read, - ptr::null_mut()) - })); - Ok(read as usize) + self.handle.read(buf) } pub fn write(&self, buf: &[u8]) -> io::Result { - let mut amt = 0; - try!(cvt(unsafe { - libc::WriteFile(self.handle.raw(), - buf.as_ptr() as libc::LPVOID, - buf.len() as libc::DWORD, - &mut amt, - ptr::null_mut()) - })); - Ok(amt as usize) + self.handle.write(buf) } pub fn flush(&self) -> io::Result<()> { Ok(()) } diff --git a/src/libstd/sys/windows/handle.rs b/src/libstd/sys/windows/handle.rs index 52aa5fb036a..99de659be41 100644 --- a/src/libstd/sys/windows/handle.rs +++ b/src/libstd/sys/windows/handle.rs @@ -11,6 +11,10 @@ use prelude::v1::*; use libc::{self, HANDLE}; +use io; +use io::ErrorKind; +use ptr; +use sys::cvt; pub struct Handle(HANDLE); @@ -21,7 +25,16 @@ impl Handle { pub fn new(handle: HANDLE) -> Handle { Handle(handle) } + pub fn raw(&self) -> HANDLE { self.0 } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + read(self.0, buf) + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + write(self.0, buf) + } } impl Drop for Handle { @@ -30,3 +43,34 @@ impl Drop for Handle { } } + +pub fn read(h: HANDLE, buf: &mut [u8]) -> io::Result { + let mut read = 0; + let res = cvt(unsafe { + libc::ReadFile(h, buf.as_ptr() as libc::LPVOID, + buf.len() as libc::DWORD, &mut read, + ptr::null_mut()) + }); + + match res { + Ok(_) => Ok(read as usize), + + // The special treatment of BrokenPipe is to deal with Windows + // pipe semantics, which yields this error when *reading* from + // a pipe after the other end has closed; we interpret that as + // EOF on the pipe. + Err(ref e) if e.kind() == ErrorKind::BrokenPipe => Ok(0), + + Err(e) => Err(e) + } +} + +pub fn write(h: HANDLE, buf: &[u8]) -> io::Result { + let mut amt = 0; + try!(cvt(unsafe { + libc::WriteFile(h, buf.as_ptr() as libc::LPVOID, + buf.len() as libc::DWORD, &mut amt, + ptr::null_mut()) + })); + Ok(amt as usize) +} diff --git a/src/libstd/sys/windows/mod.rs b/src/libstd/sys/windows/mod.rs index 0fa9aaf4323..4d6d033deee 100644 --- a/src/libstd/sys/windows/mod.rs +++ b/src/libstd/sys/windows/mod.rs @@ -47,7 +47,9 @@ pub mod net; pub mod os; pub mod os_str; pub mod pipe; +pub mod pipe2; pub mod process; +pub mod process2; pub mod rwlock; pub mod stack_overflow; pub mod sync; diff --git a/src/libstd/sys/windows/pipe2.rs b/src/libstd/sys/windows/pipe2.rs new file mode 100644 index 00000000000..229481e3d57 --- /dev/null +++ b/src/libstd/sys/windows/pipe2.rs @@ -0,0 +1,77 @@ +// Copyright 2015 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. + +use prelude::v1::*; + +use sys::handle; +use io; +use libc::{self, c_int, HANDLE}; + +//////////////////////////////////////////////////////////////////////////////// +// Anonymous pipes +//////////////////////////////////////////////////////////////////////////////// + +pub struct AnonPipe { + fd: c_int +} + +pub unsafe fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> { + // Windows pipes work subtly differently than unix pipes, and their + // inheritance has to be handled in a different way that I do not + // fully understand. Here we explicitly make the pipe non-inheritable, + // which means to pass it to a subprocess they need to be duplicated + // first, as in std::run. + let mut fds = [0; 2]; + match libc::pipe(fds.as_mut_ptr(), 1024 as ::libc::c_uint, + (libc::O_BINARY | libc::O_NOINHERIT) as c_int) { + 0 => { + assert!(fds[0] != -1 && fds[0] != 0); + assert!(fds[1] != -1 && fds[1] != 0); + + Ok((AnonPipe::from_fd(fds[0]), AnonPipe::from_fd(fds[1]))) + } + _ => Err(io::Error::last_os_error()), + } +} + +impl AnonPipe { + pub fn from_fd(fd: libc::c_int) -> AnonPipe { + AnonPipe { fd: fd } + } + + pub fn raw(&self) -> HANDLE { + unsafe { libc::get_osfhandle(self.fd) as libc::HANDLE } + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + handle::read(self.raw(), buf) + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + handle::write(self.raw(), buf) + } +} + +impl Drop for AnonPipe { + fn drop(&mut self) { + // closing stdio file handles makes no sense, so never do it. Also, note + // that errors are ignored when closing a file descriptor. The reason + // for this is that if an error occurs we don't actually know if the + // file descriptor was closed or not, and if we retried (for something + // like EINTR), we might close another valid file descriptor (opened + // after we closed ours. + if self.fd > libc::STDERR_FILENO { + let n = unsafe { libc::close(self.fd) }; + if n != 0 { + println!("error {} when closing file descriptor {}", n, self.fd); + } + } + } +} diff --git a/src/libstd/sys/windows/process2.rs b/src/libstd/sys/windows/process2.rs new file mode 100644 index 00000000000..19e38196d19 --- /dev/null +++ b/src/libstd/sys/windows/process2.rs @@ -0,0 +1,479 @@ +// Copyright 2012-2014 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. + +use prelude::v1::*; + +use ascii::*; +use collections::HashMap; +use collections; +use env; +use ffi::{OsString, OsStr}; +use fmt; +use io::{self, Error}; +use libc::{self, c_void}; +use old_io::fs; +use old_path; +use os::windows::OsStrExt; +use ptr; +use sync::{StaticMutex, MUTEX_INIT}; +use sys::pipe2::AnonPipe; +use sys::{self, cvt}; +use sys::handle::Handle; +use sys_common::{AsInner, FromInner}; + +//////////////////////////////////////////////////////////////////////////////// +// Command +//////////////////////////////////////////////////////////////////////////////// + +fn mk_key(s: &OsStr) -> OsString { + FromInner::from_inner(sys::os_str::Buf { + inner: s.as_inner().inner.to_ascii_uppercase() + }) +} + +#[derive(Clone)] +pub struct Command { + pub program: OsString, + pub args: Vec, + pub env: Option>, + pub cwd: Option, + pub detach: bool, // not currently exposed in std::process +} + +impl Command { + pub fn new(program: &OsStr) -> Command { + Command { + program: program.to_os_string(), + args: Vec::new(), + env: None, + cwd: None, + detach: false, + } + } + + pub fn arg(&mut self, arg: &OsStr) { + self.args.push(arg.to_os_string()) + } + pub fn args<'a, I: Iterator>(&mut self, args: I) { + self.args.extend(args.map(OsStr::to_os_string)) + } + fn init_env_map(&mut self){ + if self.env.is_none() { + self.env = Some(env::vars_os().map(|(key, val)| { + (mk_key(&key), val) + }).collect()); + } + } + pub fn env(&mut self, key: &OsStr, val: &OsStr) { + self.init_env_map(); + self.env.as_mut().unwrap().insert(mk_key(key), val.to_os_string()); + } + pub fn env_remove(&mut self, key: &OsStr) { + self.init_env_map(); + self.env.as_mut().unwrap().remove(&mk_key(key)); + } + pub fn env_clear(&mut self) { + self.env = Some(HashMap::new()) + } + pub fn cwd(&mut self, dir: &OsStr) { + self.cwd = Some(dir.to_os_string()) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Processes +//////////////////////////////////////////////////////////////////////////////// + +// `CreateProcess` is racy! +// http://support.microsoft.com/kb/315939 +static CREATE_PROCESS_LOCK: StaticMutex = MUTEX_INIT; + +/// A value representing a child process. +/// +/// The lifetime of this value is linked to the lifetime of the actual +/// process - the Process destructor calls self.finish() which waits +/// for the process to terminate. +pub struct Process { + /// A HANDLE to the process, which will prevent the pid being + /// re-used until the handle is closed. + handle: Handle, +} + +impl Process { + #[allow(deprecated)] + pub fn spawn(cfg: &Command, + in_fd: Option, out_fd: Option, err_fd: Option) + -> io::Result + { + use libc::types::os::arch::extra::{DWORD, HANDLE, STARTUPINFO}; + use libc::consts::os::extra::{ + TRUE, FALSE, + STARTF_USESTDHANDLES, + INVALID_HANDLE_VALUE, + DUPLICATE_SAME_ACCESS + }; + use libc::funcs::extra::kernel32::{ + GetCurrentProcess, + DuplicateHandle, + CloseHandle, + CreateProcessW + }; + + use env::split_paths; + use mem; + use iter::IteratorExt; + use str::StrExt; + + // To have the spawning semantics of unix/windows stay the same, we need to + // read the *child's* PATH if one is provided. See #15149 for more details. + let program = cfg.env.as_ref().and_then(|env| { + for (key, v) in env { + if OsStr::from_str("PATH") != &**key { continue } + + // Split the value and test each path to see if the + // program exists. + for path in split_paths(&v) { + let path = path.join(cfg.program.to_str().unwrap()) + .with_extension(env::consts::EXE_EXTENSION); + // FIXME: update with new fs module once it lands + if fs::stat(&old_path::Path::new(&path)).is_ok() { + return Some(OsString::from_str(path.as_str().unwrap())) + } + } + break + } + None + }); + + unsafe { + let mut si = zeroed_startupinfo(); + si.cb = mem::size_of::() as DWORD; + si.dwFlags = STARTF_USESTDHANDLES; + + let cur_proc = GetCurrentProcess(); + + // Similarly to unix, we don't actually leave holes for the stdio file + // descriptors, but rather open up /dev/null equivalents. These + // equivalents are drawn from libuv's windows process spawning. + let set_fd = |&: fd: &Option, slot: &mut HANDLE, + is_stdin: bool| { + match *fd { + None => { + let access = if is_stdin { + libc::FILE_GENERIC_READ + } else { + libc::FILE_GENERIC_WRITE | libc::FILE_READ_ATTRIBUTES + }; + let size = mem::size_of::(); + let mut sa = libc::SECURITY_ATTRIBUTES { + nLength: size as libc::DWORD, + lpSecurityDescriptor: ptr::null_mut(), + bInheritHandle: 1, + }; + let mut filename: Vec = "NUL".utf16_units().collect(); + filename.push(0); + *slot = libc::CreateFileW(filename.as_ptr(), + access, + libc::FILE_SHARE_READ | + libc::FILE_SHARE_WRITE, + &mut sa, + libc::OPEN_EXISTING, + 0, + ptr::null_mut()); + if *slot == INVALID_HANDLE_VALUE { + return Err(Error::last_os_error()) + } + } + Some(ref pipe) => { + let orig = pipe.raw(); + if orig == INVALID_HANDLE_VALUE { + return Err(Error::last_os_error()) + } + if DuplicateHandle(cur_proc, orig, cur_proc, slot, + 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE { + return Err(Error::last_os_error()) + } + } + } + Ok(()) + }; + + try!(set_fd(&in_fd, &mut si.hStdInput, true)); + try!(set_fd(&out_fd, &mut si.hStdOutput, false)); + try!(set_fd(&err_fd, &mut si.hStdError, false)); + + let mut cmd_str = make_command_line(program.as_ref().unwrap_or(&cfg.program), + &cfg.args); + cmd_str.push(0); // add null terminator + + let mut pi = zeroed_process_information(); + let mut create_err = None; + + // stolen from the libuv code. + let mut flags = libc::CREATE_UNICODE_ENVIRONMENT; + if cfg.detach { + flags |= libc::DETACHED_PROCESS | libc::CREATE_NEW_PROCESS_GROUP; + } + + with_envp(cfg.env.as_ref(), |envp| { + with_dirp(cfg.cwd.as_ref(), |dirp| { + let _lock = CREATE_PROCESS_LOCK.lock().unwrap(); + let created = CreateProcessW(ptr::null(), + cmd_str.as_mut_ptr(), + ptr::null_mut(), + ptr::null_mut(), + TRUE, + flags, envp, dirp, + &mut si, &mut pi); + if created == FALSE { + create_err = Some(Error::last_os_error()); + } + }) + }); + + assert!(CloseHandle(si.hStdInput) != 0); + assert!(CloseHandle(si.hStdOutput) != 0); + assert!(CloseHandle(si.hStdError) != 0); + + match create_err { + Some(err) => return Err(err), + None => {} + } + + // We close the thread handle because we don't care about keeping the + // thread id valid, and we aren't keeping the thread handle around to be + // able to close it later. We don't close the process handle however + // because std::we want the process id to stay valid at least until the + // calling code closes the process handle. + assert!(CloseHandle(pi.hThread) != 0); + + Ok(Process { + handle: Handle::new(pi.hProcess) + }) + } + } + + pub unsafe fn kill(&self) -> io::Result<()> { + try!(cvt(libc::TerminateProcess(self.handle.raw(), 1))); + Ok(()) + } + + pub fn wait(&self) -> io::Result { + use libc::consts::os::extra::{ + FALSE, + STILL_ACTIVE, + INFINITE, + WAIT_OBJECT_0, + }; + use libc::funcs::extra::kernel32::{ + GetExitCodeProcess, + WaitForSingleObject, + }; + + unsafe { + loop { + let mut status = 0; + if GetExitCodeProcess(self.handle.raw(), &mut status) == FALSE { + let err = Err(Error::last_os_error()); + return err; + } + if status != STILL_ACTIVE { + return Ok(ExitStatus(status as i32)); + } + match WaitForSingleObject(self.handle.raw(), INFINITE) { + WAIT_OBJECT_0 => {} + _ => { + let err = Err(Error::last_os_error()); + return err + } + } + } + } + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ExitStatus(i32); + +impl ExitStatus { + pub fn success(&self) -> bool { + self.0 == 0 + } + pub fn code(&self) -> Option { + Some(self.0) + } +} + +impl fmt::Display for ExitStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "exit code: {}", self.0) + } +} + +fn zeroed_startupinfo() -> libc::types::os::arch::extra::STARTUPINFO { + libc::types::os::arch::extra::STARTUPINFO { + cb: 0, + lpReserved: ptr::null_mut(), + lpDesktop: ptr::null_mut(), + lpTitle: ptr::null_mut(), + dwX: 0, + dwY: 0, + dwXSize: 0, + dwYSize: 0, + dwXCountChars: 0, + dwYCountCharts: 0, + dwFillAttribute: 0, + dwFlags: 0, + wShowWindow: 0, + cbReserved2: 0, + lpReserved2: ptr::null_mut(), + hStdInput: libc::INVALID_HANDLE_VALUE, + hStdOutput: libc::INVALID_HANDLE_VALUE, + hStdError: libc::INVALID_HANDLE_VALUE, + } +} + +fn zeroed_process_information() -> libc::types::os::arch::extra::PROCESS_INFORMATION { + libc::types::os::arch::extra::PROCESS_INFORMATION { + hProcess: ptr::null_mut(), + hThread: ptr::null_mut(), + dwProcessId: 0, + dwThreadId: 0 + } +} + +// Produces a wide string *without terminating null* +fn make_command_line(prog: &OsStr, args: &[OsString]) -> Vec { + let mut cmd: Vec = Vec::new(); + append_arg(&mut cmd, prog); + for arg in args { + cmd.push(' ' as u16); + append_arg(&mut cmd, arg); + } + return cmd; + + fn append_arg(cmd: &mut Vec, arg: &OsStr) { + // If an argument has 0 characters then we need to quote it to ensure + // that it actually gets passed through on the command line or otherwise + // it will be dropped entirely when parsed on the other end. + let arg_bytes = &arg.as_inner().inner.as_inner(); + let quote = arg_bytes.iter().any(|c| *c == b' ' || *c == b'\t') + || arg_bytes.len() == 0; + if quote { + cmd.push('"' as u16); + } + + let mut iter = arg.encode_wide(); + while let Some(x) = iter.next() { + if x == '"' as u16 { + // escape quotes + cmd.push('\\' as u16); + cmd.push('"' as u16); + } else if x == '\\' as u16 { + // is this a run of backslashes followed by a " ? + if iter.clone().skip_while(|y| *y == '\\' as u16).next() == Some('"' as u16) { + // Double it ... NOTE: this behavior is being + // preserved as it's been part of Rust for a long + // time, but no one seems to know exactly why this + // is the right thing to do. + cmd.push('\\' as u16); + cmd.push('\\' as u16); + } else { + // Push it through unescaped + cmd.push('\\' as u16); + } + } else { + cmd.push(x) + } + } + + if quote { + cmd.push('"' as u16); + } + } +} + +fn with_envp(env: Option<&collections::HashMap>, cb: F) -> T + where F: FnOnce(*mut c_void) -> T, +{ + // On Windows we pass an "environment block" which is not a char**, but + // rather a concatenation of null-terminated k=v\0 sequences, with a final + // \0 to terminate. + match env { + Some(env) => { + let mut blk = Vec::new(); + + for pair in env { + blk.extend(pair.0.encode_wide()); + blk.push('=' as u16); + blk.extend(pair.1.encode_wide()); + blk.push(0); + } + blk.push(0); + cb(blk.as_mut_ptr() as *mut c_void) + } + _ => cb(ptr::null_mut()) + } +} + +fn with_dirp(d: Option<&OsString>, cb: F) -> T where + F: FnOnce(*const u16) -> T, +{ + match d { + Some(dir) => { + let mut dir_str: Vec = dir.encode_wide().collect(); + dir_str.push(0); + cb(dir_str.as_ptr()) + }, + None => cb(ptr::null()) + } +} + +#[cfg(test)] +mod tests { + use prelude::v1::*; + use str; + use ffi::{OsStr, OsString}; + use super::make_command_line; + + #[test] + fn test_make_command_line() { + fn test_wrapper(prog: &str, args: &[&str]) -> String { + String::from_utf16( + &make_command_line(OsStr::from_str(prog), + args.iter() + .map(|a| OsString::from_str(a)) + .collect::>() + .as_slice())).unwrap() + } + + assert_eq!( + test_wrapper("prog", &["aaa", "bbb", "ccc"]), + "prog aaa bbb ccc" + ); + + assert_eq!( + test_wrapper("C:\\Program Files\\blah\\blah.exe", &["aaa"]), + "\"C:\\Program Files\\blah\\blah.exe\" aaa" + ); + assert_eq!( + test_wrapper("C:\\Program Files\\test", &["aa\"bb"]), + "\"C:\\Program Files\\test\" aa\\\"bb" + ); + assert_eq!( + test_wrapper("echo", &["a b c"]), + "echo \"a b c\"" + ); + assert_eq!( + test_wrapper("\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}", &[]), + "\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}" + ); + } +}