std: Push process stdio setup in std::sys

Most of this is platform-specific anyway, and we generally have to jump through
fewer hoops to do the equivalent operation on Windows. One benefit for Windows
today is that this new structure avoids an extra `DuplicateHandle` when creating
pipes. For Unix, however, the behavior should be the same.

Note that this is just a pure refactoring, no functionality was added or
removed.
This commit is contained in:
Alex Crichton 2016-02-04 11:10:37 -08:00
parent 18f9a79c23
commit d15db1d392
7 changed files with 441 additions and 368 deletions

View File

@ -20,7 +20,7 @@ use fmt;
use io;
use path::Path;
use str;
use sys::pipe::{self, AnonPipe};
use sys::pipe::AnonPipe;
use sys::process as imp;
use sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
use thread::{self, JoinHandle};
@ -77,6 +77,17 @@ impl AsInner<imp::Process> for Child {
fn as_inner(&self) -> &imp::Process { &self.handle }
}
impl FromInner<(imp::Process, imp::StdioPipes)> for Child {
fn from_inner((handle, io): (imp::Process, imp::StdioPipes)) -> Child {
Child {
handle: handle,
stdin: io.stdin.map(ChildStdin::from_inner),
stdout: io.stdout.map(ChildStdout::from_inner),
stderr: io.stderr.map(ChildStderr::from_inner),
}
}
}
impl IntoInner<imp::Process> for Child {
fn into_inner(self) -> imp::Process { self.handle }
}
@ -106,6 +117,12 @@ impl IntoInner<AnonPipe> for ChildStdin {
fn into_inner(self) -> AnonPipe { self.inner }
}
impl FromInner<AnonPipe> for ChildStdin {
fn from_inner(pipe: AnonPipe) -> ChildStdin {
ChildStdin { inner: pipe }
}
}
/// A handle to a child process's stdout
#[stable(feature = "process", since = "1.0.0")]
pub struct ChildStdout {
@ -127,6 +144,12 @@ impl IntoInner<AnonPipe> for ChildStdout {
fn into_inner(self) -> AnonPipe { self.inner }
}
impl FromInner<AnonPipe> for ChildStdout {
fn from_inner(pipe: AnonPipe) -> ChildStdout {
ChildStdout { inner: pipe }
}
}
/// A handle to a child process's stderr
#[stable(feature = "process", since = "1.0.0")]
pub struct ChildStderr {
@ -148,6 +171,12 @@ impl IntoInner<AnonPipe> for ChildStderr {
fn into_inner(self) -> AnonPipe { self.inner }
}
impl FromInner<AnonPipe> for ChildStderr {
fn from_inner(pipe: AnonPipe) -> ChildStderr {
ChildStderr { inner: pipe }
}
}
/// 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
@ -167,11 +196,6 @@ impl IntoInner<AnonPipe> for ChildStderr {
#[stable(feature = "process", since = "1.0.0")]
pub struct Command {
inner: imp::Command,
// Details explained in the builder methods
stdin: Option<Stdio>,
stdout: Option<Stdio>,
stderr: Option<Stdio>,
}
impl Command {
@ -187,12 +211,7 @@ impl Command {
/// otherwise configure the process.
#[stable(feature = "process", since = "1.0.0")]
pub fn new<S: AsRef<OsStr>>(program: S) -> Command {
Command {
inner: imp::Command::new(program.as_ref()),
stdin: None,
stdout: None,
stderr: None,
}
Command { inner: imp::Command::new(program.as_ref()) }
}
/// Add an argument to pass to the program.
@ -247,56 +266,30 @@ impl Command {
/// Configuration for the child process's stdin handle (file descriptor 0).
#[stable(feature = "process", since = "1.0.0")]
pub fn stdin(&mut self, cfg: Stdio) -> &mut Command {
self.stdin = Some(cfg);
self.inner.stdin(cfg.0);
self
}
/// Configuration for the child process's stdout handle (file descriptor 1).
#[stable(feature = "process", since = "1.0.0")]
pub fn stdout(&mut self, cfg: Stdio) -> &mut Command {
self.stdout = Some(cfg);
self.inner.stdout(cfg.0);
self
}
/// Configuration for the child process's stderr handle (file descriptor 2).
#[stable(feature = "process", since = "1.0.0")]
pub fn stderr(&mut self, cfg: Stdio) -> &mut Command {
self.stderr = Some(cfg);
self.inner.stderr(cfg.0);
self
}
fn spawn_inner(&mut self, default_io: StdioImp) -> io::Result<Child> {
let default_io = Stdio(default_io);
// See comment on `setup_io` for what `_drop_later` is.
let (their_stdin, our_stdin, _drop_later) = try!(
setup_io(self.stdin.as_ref().unwrap_or(&default_io), true)
);
let (their_stdout, our_stdout, _drop_later) = try!(
setup_io(self.stdout.as_ref().unwrap_or(&default_io), false)
);
let (their_stderr, our_stderr, _drop_later) = try!(
setup_io(self.stderr.as_ref().unwrap_or(&default_io), false)
);
match imp::Process::spawn(&mut self.inner, their_stdin, their_stdout,
their_stderr) {
Err(e) => Err(e),
Ok(handle) => Ok(Child {
handle: handle,
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 from the parent.
#[stable(feature = "process", since = "1.0.0")]
pub fn spawn(&mut self) -> io::Result<Child> {
self.spawn_inner(StdioImp::Inherit)
self.inner.spawn(imp::Stdio::Inherit).map(Child::from_inner)
}
/// Executes the command as a child process, waiting for it to finish and
@ -319,7 +312,8 @@ impl Command {
/// ```
#[stable(feature = "process", since = "1.0.0")]
pub fn output(&mut self) -> io::Result<Output> {
self.spawn_inner(StdioImp::MakePipe).and_then(|p| p.wait_with_output())
self.inner.spawn(imp::Stdio::MakePipe).map(Child::from_inner)
.and_then(|p| p.wait_with_output())
}
/// Executes a command as a child process, waiting for it to finish and
@ -362,33 +356,6 @@ impl AsInnerMut<imp::Command> for Command {
fn as_inner_mut(&mut self) -> &mut imp::Command { &mut self.inner }
}
// Takes a `Stdio` configuration (this module) and whether the to-be-owned
// handle will be readable.
//
// Returns a triple of (stdio to spawn with, stdio to store, stdio to drop). The
// stdio to spawn with is passed down to the `sys` module and indicates how the
// stdio stream should be set up. The "stdio to store" is an object which
// should be returned in the `Child` that makes its way out. The "stdio to drop"
// represents the raw value of "stdio to spawn with", but is the owned variant
// for it. This needs to be dropped after the child spawns
fn setup_io(io: &Stdio, readable: bool)
-> io::Result<(imp::Stdio, Option<AnonPipe>, Option<AnonPipe>)>
{
Ok(match io.0 {
StdioImp::MakePipe => {
let (reader, writer) = try!(pipe::anon_pipe());
if readable {
(imp::Stdio::Raw(reader.raw()), Some(writer), Some(reader))
} else {
(imp::Stdio::Raw(writer.raw()), Some(reader), Some(writer))
}
}
StdioImp::Raw(ref owned) => (imp::Stdio::Raw(owned.raw()), None, None),
StdioImp::Inherit => (imp::Stdio::Inherit, None, None),
StdioImp::Null => (imp::Stdio::Null, None, None),
})
}
/// The output of a finished process.
#[derive(PartialEq, Eq, Clone)]
#[stable(feature = "process", since = "1.0.0")]
@ -432,34 +399,26 @@ impl fmt::Debug for Output {
/// Describes what to do with a standard I/O stream for a child process.
#[stable(feature = "process", since = "1.0.0")]
pub struct Stdio(StdioImp);
// The internal enum for stdio setup; see below for descriptions.
enum StdioImp {
MakePipe,
Raw(imp::RawStdio),
Inherit,
Null,
}
pub struct Stdio(imp::Stdio);
impl Stdio {
/// A new pipe should be arranged to connect the parent and child processes.
#[stable(feature = "process", since = "1.0.0")]
pub fn piped() -> Stdio { Stdio(StdioImp::MakePipe) }
pub fn piped() -> Stdio { Stdio(imp::Stdio::MakePipe) }
/// The child inherits from the corresponding parent descriptor.
#[stable(feature = "process", since = "1.0.0")]
pub fn inherit() -> Stdio { Stdio(StdioImp::Inherit) }
pub fn inherit() -> Stdio { Stdio(imp::Stdio::Inherit) }
/// This stream will be ignored. This is the equivalent of attaching the
/// stream to `/dev/null`
#[stable(feature = "process", since = "1.0.0")]
pub fn null() -> Stdio { Stdio(StdioImp::Null) }
pub fn null() -> Stdio { Stdio(imp::Stdio::Null) }
}
impl FromInner<imp::RawStdio> for Stdio {
fn from_inner(inner: imp::RawStdio) -> Stdio {
Stdio(StdioImp::Raw(inner))
impl FromInner<imp::Stdio> for Stdio {
fn from_inner(inner: imp::Stdio) -> Stdio {
Stdio(inner)
}
}

View File

@ -120,7 +120,9 @@ impl ExitStatusExt for process::ExitStatus {
#[stable(feature = "process_extensions", since = "1.2.0")]
impl FromRawFd for process::Stdio {
unsafe fn from_raw_fd(fd: RawFd) -> process::Stdio {
process::Stdio::from_inner(sys::fd::FileDesc::new(fd))
let fd = sys::fd::FileDesc::new(fd);
let io = sys::process::Stdio::Fd(fd);
process::Stdio::from_inner(io)
}
}

View File

@ -61,7 +61,6 @@ impl AnonPipe {
self.0.write(buf)
}
pub fn raw(&self) -> libc::c_int { self.0.raw() }
pub fn fd(&self) -> &FileDesc { &self.0 }
pub fn into_fd(self) -> FileDesc { self.0 }
}

View File

@ -21,6 +21,7 @@ use mem;
use ptr;
use sys::fd::FileDesc;
use sys::fs::{File, OpenOptions};
use sys::pipe::{self, AnonPipe};
use sys::{self, cvt, cvt_r};
////////////////////////////////////////////////////////////////////////////////
@ -57,6 +58,38 @@ pub struct Command {
session_leader: bool,
saw_nul: bool,
closures: Vec<Box<FnMut() -> io::Result<()> + Send + Sync>>,
stdin: Option<Stdio>,
stdout: Option<Stdio>,
stderr: Option<Stdio>,
}
// passed back to std::process with the pipes connected to the child, if any
// were requested
pub struct StdioPipes {
pub stdin: Option<AnonPipe>,
pub stdout: Option<AnonPipe>,
pub stderr: Option<AnonPipe>,
}
// passed to do_exec() with configuration of what the child stdio should look
// like
struct ChildPipes {
stdin: ChildStdio,
stdout: ChildStdio,
stderr: ChildStdio,
}
enum ChildStdio {
Inherit,
Explicit(c_int),
Owned(FileDesc),
}
pub enum Stdio {
Inherit,
Null,
MakePipe,
Fd(FileDesc),
}
impl Command {
@ -75,6 +108,9 @@ impl Command {
session_leader: false,
saw_nul: saw_nul,
closures: Vec::new(),
stdin: None,
stdout: None,
stderr: None,
}
}
@ -169,136 +205,32 @@ impl Command {
f: Box<FnMut() -> io::Result<()> + Send + Sync>) {
self.closures.push(f);
}
}
fn os2c(s: &OsStr, saw_nul: &mut bool) -> CString {
CString::new(s.as_bytes()).unwrap_or_else(|_e| {
*saw_nul = true;
CString::new("<string-with-nul>").unwrap()
})
}
fn pair_to_key(key: &OsStr, value: &OsStr, saw_nul: &mut bool) -> CString {
let (key, value) = (key.as_bytes(), value.as_bytes());
let mut v = Vec::with_capacity(key.len() + value.len() + 1);
v.extend(key);
v.push(b'=');
v.extend(value);
CString::new(v).unwrap_or_else(|_e| {
*saw_nul = true;
CString::new("foo=bar").unwrap()
})
}
impl fmt::Debug for Command {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
try!(write!(f, "{:?}", self.program));
for arg in &self.args {
try!(write!(f, " {:?}", arg));
}
Ok(())
pub fn stdin(&mut self, stdin: Stdio) {
self.stdin = Some(stdin);
}
}
////////////////////////////////////////////////////////////////////////////////
// Processes
////////////////////////////////////////////////////////////////////////////////
/// Unix exit statuses
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct ExitStatus(c_int);
#[cfg(any(target_os = "linux", target_os = "android",
target_os = "nacl", target_os = "solaris",
target_os = "emscripten"))]
mod status_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 = "bitrig",
target_os = "netbsd",
target_os = "openbsd"))]
mod status_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 }
}
impl ExitStatus {
fn exited(&self) -> bool {
status_imp::WIFEXITED(self.0)
pub fn stdout(&mut self, stdout: Stdio) {
self.stdout = Some(stdout);
}
pub fn stderr(&mut self, stderr: Stdio) {
self.stderr = Some(stderr);
}
pub fn success(&self) -> bool {
self.code() == Some(0)
}
pub fn code(&self) -> Option<i32> {
if self.exited() {
Some(status_imp::WEXITSTATUS(self.0))
} else {
None
}
}
pub fn signal(&self) -> Option<i32> {
if !self.exited() {
Some(status_imp::WTERMSIG(self.0))
} else {
None
}
}
}
impl fmt::Display for ExitStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(code) = self.code() {
write!(f, "exit code: {}", code)
} else {
let signal = self.signal().unwrap();
write!(f, "signal: {}", signal)
}
}
}
/// The unique id of the process (this should never be negative).
pub struct Process {
pid: pid_t,
status: Option<ExitStatus>,
}
pub enum Stdio {
Inherit,
Null,
Raw(c_int),
}
pub type RawStdio = FileDesc;
const CLOEXEC_MSG_FOOTER: &'static [u8] = b"NOEX";
impl Process {
pub fn spawn(cfg: &mut Command,
in_fd: Stdio,
out_fd: Stdio,
err_fd: Stdio) -> io::Result<Process> {
if cfg.saw_nul {
return Err(io::Error::new(ErrorKind::InvalidInput, "nul byte found in provided data"));
pub fn spawn(&mut self, default: Stdio)
-> io::Result<(Process, StdioPipes)> {
if self.saw_nul {
return Err(io::Error::new(ErrorKind::InvalidInput,
"nul byte found in provided data"));
}
let (ours, theirs) = try!(self.setup_io(default));
let (input, output) = try!(sys::pipe::anon_pipe());
let pid = unsafe {
match try!(cvt(libc::fork())) {
0 => {
drop(input);
let err = Process::exec(cfg, in_fd, out_fd, err_fd);
let err = self.exec(theirs);
let errno = err.raw_os_error().unwrap_or(libc::EINVAL) as u32;
let bytes = [
(errno >> 24) as u8,
@ -325,7 +257,7 @@ impl Process {
// loop to handle EINTR
loop {
match input.read(&mut bytes) {
Ok(0) => return Ok(p),
Ok(0) => return Ok((p, ours)),
Ok(8) => {
assert!(combine(CLOEXEC_MSG_FOOTER) == combine(&bytes[4.. 8]),
"Validation on the CLOEXEC pipe failed: {:?}", bytes);
@ -388,10 +320,7 @@ impl Process {
// 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)
unsafe fn exec(cfg: &mut Command,
in_fd: Stdio,
out_fd: Stdio,
err_fd: Stdio) -> io::Error {
unsafe fn exec(&mut self, stdio: ChildPipes) -> io::Error {
macro_rules! try {
($e:expr) => (match $e {
Ok(e) => e,
@ -399,60 +328,20 @@ impl Process {
})
}
// Make sure that the source descriptors are not an stdio descriptor,
// otherwise the order which we set the child's descriptors may blow
// away a descriptor which we are hoping to save. For example,
// suppose we want the child's stderr to be the parent's stdout, and
// the child's stdout to be the parent's stderr. No matter which we
// dup first, the second will get overwritten prematurely.
let maybe_migrate = |src: Stdio| {
match src {
Stdio::Raw(fd @ libc::STDIN_FILENO) |
Stdio::Raw(fd @ libc::STDOUT_FILENO) |
Stdio::Raw(fd @ libc::STDERR_FILENO) => {
cvt_r(|| libc::dup(fd)).map(|fd| {
let fd = FileDesc::new(fd);
fd.set_cloexec();
Stdio::Raw(fd.into_raw())
})
}
s @ Stdio::Null |
s @ Stdio::Inherit |
s @ Stdio::Raw(_) => Ok(s),
}
};
let in_fd = try!(maybe_migrate(in_fd));
let out_fd = try!(maybe_migrate(out_fd));
let err_fd = try!(maybe_migrate(err_fd));
if let Some(fd) = stdio.stdin.fd() {
try!(cvt_r(|| libc::dup2(fd, libc::STDIN_FILENO)));
}
if let Some(fd) = stdio.stdout.fd() {
try!(cvt_r(|| libc::dup2(fd, libc::STDOUT_FILENO)));
}
if let Some(fd) = stdio.stderr.fd() {
try!(cvt_r(|| libc::dup2(fd, libc::STDERR_FILENO)));
}
let setup = |src: Stdio, dst: c_int| {
match src {
Stdio::Inherit => Ok(()),
Stdio::Raw(fd) => cvt_r(|| libc::dup2(fd, dst)).map(|_| ()),
// Open up a reference to /dev/null with appropriate read/write
// permissions and then move it into the correct location via
// `dup2`.
Stdio::Null => {
let mut opts = OpenOptions::new();
opts.read(dst == libc::STDIN_FILENO);
opts.write(dst != libc::STDIN_FILENO);
let devnull = CStr::from_ptr(b"/dev/null\0".as_ptr()
as *const _);
File::open_c(devnull, &opts).and_then(|f| {
cvt_r(|| libc::dup2(f.fd().raw(), dst)).map(|_| ())
})
}
}
};
try!(setup(in_fd, libc::STDIN_FILENO));
try!(setup(out_fd, libc::STDOUT_FILENO));
try!(setup(err_fd, libc::STDERR_FILENO));
if let Some(u) = cfg.gid {
if let Some(u) = self.gid {
try!(cvt(libc::setgid(u as gid_t)));
}
if let Some(u) = cfg.uid {
if let Some(u) = self.uid {
// 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
@ -464,16 +353,16 @@ impl Process {
try!(cvt(libc::setuid(u as uid_t)));
}
if cfg.session_leader {
if self.session_leader {
// 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 let Some(ref cwd) = cfg.cwd {
if let Some(ref cwd) = self.cwd {
try!(cvt(libc::chdir(cwd.as_ptr())));
}
if let Some(ref envp) = cfg.envp {
if let Some(ref envp) = self.envp {
*sys::os::environ() = envp.as_ptr();
}
@ -496,14 +385,195 @@ impl Process {
}
}
for callback in cfg.closures.iter_mut() {
for callback in self.closures.iter_mut() {
try!(callback());
}
libc::execvp(cfg.argv[0], cfg.argv.as_ptr());
libc::execvp(self.argv[0], self.argv.as_ptr());
io::Error::last_os_error()
}
fn setup_io(&self, default: Stdio) -> io::Result<(StdioPipes, ChildPipes)> {
let stdin = self.stdin.as_ref().unwrap_or(&default);
let stdout = self.stdout.as_ref().unwrap_or(&default);
let stderr = self.stderr.as_ref().unwrap_or(&default);
let (their_stdin, our_stdin) = try!(stdin.to_child_stdio(true));
let (their_stdout, our_stdout) = try!(stdout.to_child_stdio(false));
let (their_stderr, our_stderr) = try!(stderr.to_child_stdio(false));
let ours = StdioPipes {
stdin: our_stdin,
stdout: our_stdout,
stderr: our_stderr,
};
let theirs = ChildPipes {
stdin: their_stdin,
stdout: their_stdout,
stderr: their_stderr,
};
Ok((ours, theirs))
}
}
fn os2c(s: &OsStr, saw_nul: &mut bool) -> CString {
CString::new(s.as_bytes()).unwrap_or_else(|_e| {
*saw_nul = true;
CString::new("<string-with-nul>").unwrap()
})
}
impl Stdio {
fn to_child_stdio(&self, readable: bool)
-> io::Result<(ChildStdio, Option<AnonPipe>)> {
match *self {
Stdio::Inherit => Ok((ChildStdio::Inherit, None)),
// Make sure that the source descriptors are not an stdio
// descriptor, otherwise the order which we set the child's
// descriptors may blow away a descriptor which we are hoping to
// save. For example, suppose we want the child's stderr to be the
// parent's stdout, and the child's stdout to be the parent's
// stderr. No matter which we dup first, the second will get
// overwritten prematurely.
Stdio::Fd(ref fd) => {
if fd.raw() >= 0 && fd.raw() <= libc::STDERR_FILENO {
Ok((ChildStdio::Owned(try!(fd.duplicate())), None))
} else {
Ok((ChildStdio::Explicit(fd.raw()), None))
}
}
Stdio::MakePipe => {
let (reader, writer) = try!(pipe::anon_pipe());
let (ours, theirs) = if readable {
(writer, reader)
} else {
(reader, writer)
};
Ok((ChildStdio::Owned(theirs.into_fd()), Some(ours)))
}
Stdio::Null => {
let mut opts = OpenOptions::new();
opts.read(readable);
opts.write(!readable);
let path = unsafe {
CStr::from_ptr("/dev/null\0".as_ptr() as *const _)
};
let fd = try!(File::open_c(&path, &opts));
Ok((ChildStdio::Owned(fd.into_fd()), None))
}
}
}
}
impl ChildStdio {
fn fd(&self) -> Option<c_int> {
match *self {
ChildStdio::Inherit => None,
ChildStdio::Explicit(fd) => Some(fd),
ChildStdio::Owned(ref fd) => Some(fd.raw()),
}
}
}
fn pair_to_key(key: &OsStr, value: &OsStr, saw_nul: &mut bool) -> CString {
let (key, value) = (key.as_bytes(), value.as_bytes());
let mut v = Vec::with_capacity(key.len() + value.len() + 1);
v.extend(key);
v.push(b'=');
v.extend(value);
CString::new(v).unwrap_or_else(|_e| {
*saw_nul = true;
CString::new("foo=bar").unwrap()
})
}
impl fmt::Debug for Command {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
try!(write!(f, "{:?}", self.program));
for arg in &self.args {
try!(write!(f, " {:?}", arg));
}
Ok(())
}
}
////////////////////////////////////////////////////////////////////////////////
// Processes
////////////////////////////////////////////////////////////////////////////////
/// Unix exit statuses
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct ExitStatus(c_int);
#[cfg(any(target_os = "linux", target_os = "android",
target_os = "nacl"))]
mod status_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 = "bitrig",
target_os = "netbsd",
target_os = "openbsd"))]
mod status_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 }
}
impl ExitStatus {
fn exited(&self) -> bool {
status_imp::WIFEXITED(self.0)
}
pub fn success(&self) -> bool {
self.code() == Some(0)
}
pub fn code(&self) -> Option<i32> {
if self.exited() {
Some(status_imp::WEXITSTATUS(self.0))
} else {
None
}
}
pub fn signal(&self) -> Option<i32> {
if !self.exited() {
Some(status_imp::WTERMSIG(self.0))
} else {
None
}
}
}
impl fmt::Display for ExitStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(code) = self.code() {
write!(f, "exit code: {}", code)
} else {
let signal = self.signal().unwrap();
write!(f, "signal: {}", signal)
}
}
}
/// The unique id of the process (this should never be negative).
pub struct Process {
pid: pid_t,
status: Option<ExitStatus>,
}
const CLOEXEC_MSG_FOOTER: &'static [u8] = b"NOEX";
impl Process {
pub fn id(&self) -> u32 {
self.pid as u32
}
@ -540,7 +610,7 @@ mod tests {
use mem;
use ptr;
use libc;
use sys::{self, cvt};
use sys::cvt;
macro_rules! t {
($e:expr) => {
@ -576,9 +646,7 @@ mod tests {
fn test_process_mask() {
unsafe {
// Test to make sure that a signal mask does not get inherited.
let cmd = Command::new(OsStr::new("cat"));
let (stdin_read, stdin_write) = t!(sys::pipe::anon_pipe());
let (stdout_read, stdout_write) = t!(sys::pipe::anon_pipe());
let mut cmd = Command::new(OsStr::new("cat"));
let mut set: libc::sigset_t = mem::uninitialized();
let mut old_set: libc::sigset_t = mem::uninitialized();
@ -586,11 +654,12 @@ mod tests {
t!(cvt(sigaddset(&mut set, libc::SIGINT)));
t!(cvt(libc::pthread_sigmask(libc::SIG_SETMASK, &set, &mut old_set)));
let cat = t!(Process::spawn(&cmd, Stdio::Raw(stdin_read.raw()),
Stdio::Raw(stdout_write.raw()),
Stdio::Null));
drop(stdin_read);
drop(stdout_write);
cmd.stdin(Stdio::MakePipe);
cmd.stdout(Stdio::MakePipe);
let (mut cat, mut pipes) = t!(cmd.spawn(Stdio::Null));
let stdin_write = pipes.stdin.take().unwrap();
let stdout_read = pipes.stdout.take().unwrap();
t!(cvt(libc::pthread_sigmask(libc::SIG_SETMASK, &old_set,
ptr::null_mut())));

View File

@ -21,7 +21,8 @@ use sys_common::{AsInner, FromInner, IntoInner};
impl FromRawHandle for process::Stdio {
unsafe fn from_raw_handle(handle: RawHandle) -> process::Stdio {
let handle = sys::handle::Handle::new(handle as *mut _);
process::Stdio::from_inner(handle)
let io = sys::process::Stdio::Handle(handle);
process::Stdio::from_inner(io)
}
}

View File

@ -37,8 +37,6 @@ impl AnonPipe {
pub fn handle(&self) -> &Handle { &self.inner }
pub fn into_handle(self) -> Handle { self.inner }
pub fn raw(&self) -> c::HANDLE { self.inner.raw() }
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}

View File

@ -26,9 +26,9 @@ use path::Path;
use ptr;
use sync::StaticMutex;
use sys::c;
use sys::fs::{OpenOptions, File};
use sys::handle::{Handle, RawHandle};
use sys::handle::Handle;
use sys::pipe::{self, AnonPipe};
use sys::stdio;
use sys::{self, cvt};
use sys_common::{AsInner, FromInner};
@ -51,13 +51,28 @@ fn ensure_no_nuls<T: AsRef<OsStr>>(str: T) -> io::Result<T> {
}
}
#[derive(Clone)]
pub struct Command {
program: OsString,
args: Vec<OsString>,
env: Option<HashMap<OsString, OsString>>,
cwd: Option<OsString>,
detach: bool, // not currently exposed in std::process
stdin: Option<Stdio>,
stdout: Option<Stdio>,
stderr: Option<Stdio>,
}
pub enum Stdio {
Inherit,
Null,
MakePipe,
Handle(Handle),
}
pub struct StdioPipes {
pub stdin: Option<AnonPipe>,
pub stdout: Option<AnonPipe>,
pub stderr: Option<AnonPipe>,
}
impl Command {
@ -68,6 +83,9 @@ impl Command {
env: None,
cwd: None,
detach: false,
stdin: None,
stdout: None,
stderr: None,
}
}
@ -95,56 +113,29 @@ impl Command {
pub fn cwd(&mut self, dir: &OsStr) {
self.cwd = Some(dir.to_os_string())
}
}
impl fmt::Debug for Command {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
try!(write!(f, "{:?}", self.program));
for arg in &self.args {
try!(write!(f, " {:?}", arg));
}
Ok(())
pub fn stdin(&mut self, stdin: Stdio) {
self.stdin = Some(stdin);
}
pub fn stdout(&mut self, stdout: Stdio) {
self.stdout = Some(stdout);
}
pub fn stderr(&mut self, stderr: Stdio) {
self.stderr = Some(stderr);
}
}
////////////////////////////////////////////////////////////////////////////////
// Processes
////////////////////////////////////////////////////////////////////////////////
/// 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 {
handle: Handle,
}
pub enum Stdio {
Inherit,
Null,
Raw(c::HANDLE),
}
pub type RawStdio = Handle;
impl Process {
pub fn spawn(cfg: &Command,
in_handle: Stdio,
out_handle: Stdio,
err_handle: Stdio) -> io::Result<Process>
{
pub fn spawn(&mut self, default: Stdio)
-> io::Result<(Process, StdioPipes)> {
// 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| {
let program = self.env.as_ref().and_then(|env| {
for (key, v) in env {
if OsStr::new("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())
let path = path.join(self.program.to_str().unwrap())
.with_extension(env::consts::EXE_EXTENSION);
if fs::metadata(&path).is_ok() {
return Some(path.into_os_string())
@ -159,18 +150,18 @@ impl Process {
si.cb = mem::size_of::<c::STARTUPINFO>() as c::DWORD;
si.dwFlags = c::STARTF_USESTDHANDLES;
let program = program.as_ref().unwrap_or(&cfg.program);
let mut cmd_str = try!(make_command_line(program, &cfg.args));
let program = program.as_ref().unwrap_or(&self.program);
let mut cmd_str = try!(make_command_line(program, &self.args));
cmd_str.push(0); // add null terminator
// stolen from the libuv code.
let mut flags = c::CREATE_UNICODE_ENVIRONMENT;
if cfg.detach {
if self.detach {
flags |= c::DETACHED_PROCESS | c::CREATE_NEW_PROCESS_GROUP;
}
let (envp, _data) = try!(make_envp(cfg.env.as_ref()));
let (dirp, _data) = try!(make_dirp(cfg.cwd.as_ref()));
let (envp, _data) = try!(make_envp(self.env.as_ref()));
let (dirp, _data) = try!(make_dirp(self.cwd.as_ref()));
let mut pi = zeroed_process_information();
// Prepare all stdio handles to be inherited by the child. This
@ -185,9 +176,19 @@ impl Process {
static CREATE_PROCESS_LOCK: StaticMutex = StaticMutex::new();
let _lock = CREATE_PROCESS_LOCK.lock();
let stdin = try!(in_handle.to_handle(c::STD_INPUT_HANDLE));
let stdout = try!(out_handle.to_handle(c::STD_OUTPUT_HANDLE));
let stderr = try!(err_handle.to_handle(c::STD_ERROR_HANDLE));
let mut pipes = StdioPipes {
stdin: None,
stdout: None,
stderr: None,
};
let stdin = self.stdin.as_ref().unwrap_or(&default);
let stdout = self.stdout.as_ref().unwrap_or(&default);
let stderr = self.stderr.as_ref().unwrap_or(&default);
let stdin = try!(stdin.to_handle(c::STD_INPUT_HANDLE, &mut pipes.stdin));
let stdout = try!(stdout.to_handle(c::STD_OUTPUT_HANDLE,
&mut pipes.stdout));
let stderr = try!(stderr.to_handle(c::STD_ERROR_HANDLE,
&mut pipes.stderr));
si.hStdInput = stdin.raw();
si.hStdOutput = stdout.raw();
si.hStdError = stderr.raw();
@ -206,9 +207,92 @@ impl Process {
// around to be able to close it later.
drop(Handle::new(pi.hThread));
Ok(Process { handle: Handle::new(pi.hProcess) })
Ok((Process { handle: Handle::new(pi.hProcess) }, pipes))
}
}
impl fmt::Debug for Command {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
try!(write!(f, "{:?}", self.program));
for arg in &self.args {
try!(write!(f, " {:?}", arg));
}
Ok(())
}
}
impl Stdio {
fn to_handle(&self, stdio_id: c::DWORD, pipe: &mut Option<AnonPipe>)
-> io::Result<Handle> {
match *self {
// If no stdio handle is available, then inherit means that it
// should still be unavailable so propagate the
// INVALID_HANDLE_VALUE.
Stdio::Inherit => {
match stdio::get(stdio_id) {
Ok(io) => io.handle().duplicate(0, true,
c::DUPLICATE_SAME_ACCESS),
Err(..) => Ok(Handle::new(c::INVALID_HANDLE_VALUE)),
}
}
Stdio::MakePipe => {
let (reader, writer) = try!(pipe::anon_pipe());
let (ours, theirs) = if stdio_id == c::STD_INPUT_HANDLE {
(writer, reader)
} else {
(reader, writer)
};
*pipe = Some(ours);
try!(cvt(unsafe {
c::SetHandleInformation(theirs.handle().raw(),
c::HANDLE_FLAG_INHERIT,
c::HANDLE_FLAG_INHERIT)
}));
Ok(theirs.into_handle())
}
Stdio::Handle(ref handle) => {
handle.duplicate(0, true, c::DUPLICATE_SAME_ACCESS)
}
// Open up a reference to NUL with appropriate read/write
// permissions as well as the ability to be inherited to child
// processes (as this is about to be inherited).
Stdio::Null => {
let size = mem::size_of::<c::SECURITY_ATTRIBUTES>();
let mut sa = c::SECURITY_ATTRIBUTES {
nLength: size as c::DWORD,
lpSecurityDescriptor: ptr::null_mut(),
bInheritHandle: 1,
};
let mut opts = OpenOptions::new();
opts.read(stdio_id == c::STD_INPUT_HANDLE);
opts.write(stdio_id != c::STD_INPUT_HANDLE);
opts.security_attributes(&mut sa);
File::open(Path::new("NUL"), &opts).map(|file| {
file.into_handle()
})
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
// Processes
////////////////////////////////////////////////////////////////////////////////
/// 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 {
handle: Handle,
}
impl Process {
pub fn kill(&mut self) -> io::Result<()> {
try!(cvt(unsafe {
c::TerminateProcess(self.handle.raw(), 1)
@ -376,45 +460,6 @@ fn make_dirp(d: Option<&OsString>) -> io::Result<(*const u16, Vec<u16>)> {
}
}
impl Stdio {
fn to_handle(&self, stdio_id: c::DWORD) -> io::Result<Handle> {
match *self {
// If no stdio handle is available, then inherit means that it
// should still be unavailable so propagate the
// INVALID_HANDLE_VALUE.
Stdio::Inherit => {
match stdio::get(stdio_id) {
Ok(io) => io.handle().duplicate(0, true,
c::DUPLICATE_SAME_ACCESS),
Err(..) => Ok(Handle::new(c::INVALID_HANDLE_VALUE)),
}
}
Stdio::Raw(handle) => {
RawHandle::new(handle).duplicate(0, true, c::DUPLICATE_SAME_ACCESS)
}
// Open up a reference to NUL with appropriate read/write
// permissions as well as the ability to be inherited to child
// processes (as this is about to be inherited).
Stdio::Null => {
let size = mem::size_of::<c::SECURITY_ATTRIBUTES>();
let mut sa = c::SECURITY_ATTRIBUTES {
nLength: size as c::DWORD,
lpSecurityDescriptor: ptr::null_mut(),
bInheritHandle: 1,
};
let mut opts = OpenOptions::new();
opts.read(stdio_id == c::STD_INPUT_HANDLE);
opts.write(stdio_id != c::STD_INPUT_HANDLE);
opts.security_attributes(&mut sa);
File::open(Path::new("NUL"), &opts).map(|file| {
file.into_handle()
})
}
}
}
}
#[cfg(test)]
mod tests {
use prelude::v1::*;