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.
This commit is contained in:
parent
39b463f153
commit
4175f1ce2f
16 changed files with 2051 additions and 36 deletions
|
|
@ -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<S> for Wtf8 {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsciiExt<Wtf8Buf> 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::*;
|
||||
|
|
|
|||
|
|
@ -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<u8>) -> 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<i32>;
|
||||
}
|
||||
|
||||
impl ExitStatusExt for process::ExitStatus {
|
||||
fn signal(&self) -> Option<i32> {
|
||||
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};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ impl Drop for ReadDir {
|
|||
|
||||
impl DirEntry {
|
||||
pub fn path(&self) -> PathBuf {
|
||||
self.root.join(<OsStr as OsStrExt>::from_byte_slice(self.name_bytes()))
|
||||
self.root.join(<OsStr as OsStrExt>::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<()> {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<I, T>(paths: I) -> Result<OsString, JoinPathsError>
|
|||
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<OsString> {
|
||||
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<OsString> {
|
|||
|
||||
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());
|
||||
}
|
||||
|
|
|
|||
49
src/libstd/sys/unix/pipe2.rs
Normal file
49
src/libstd/sys/unix/pipe2.rs
Normal file
|
|
@ -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 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
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<usize> {
|
||||
self.0.read(buf)
|
||||
}
|
||||
|
||||
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.0.write(buf)
|
||||
}
|
||||
|
||||
pub fn raw(&self) -> libc::c_int {
|
||||
self.0.raw()
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,12 @@ pub use sys_common::ProcessConfig;
|
|||
|
||||
helper_init! { static HELPER: Helper<Req> }
|
||||
|
||||
/// Unix-specific extensions to the Command builder
|
||||
pub struct CommandExt {
|
||||
uid: Option<u32>,
|
||||
gid: Option<u32>,
|
||||
}
|
||||
|
||||
/// The unique id of the process (this should never be negative).
|
||||
pub struct Process {
|
||||
pub pid: pid_t
|
||||
|
|
|
|||
446
src/libstd/sys/unix/process2.rs
Normal file
446
src/libstd/sys/unix/process2.rs
Normal file
|
|
@ -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 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
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<CString>,
|
||||
pub env: Option<HashMap<OsString, OsString>>,
|
||||
pub cwd: Option<CString>,
|
||||
pub uid: Option<uid_t>,
|
||||
pub gid: Option<gid_t>,
|
||||
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<Item = &'a OsStr>>(&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<i32> {
|
||||
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<AnonPipe>, out_fd: Option<AnonPipe>, err_fd: Option<AnonPipe>)
|
||||
-> io::Result<Process>
|
||||
{
|
||||
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<AnonPipe>, 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<ExitStatus> {
|
||||
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<ExitStatus> {
|
||||
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<T,F>(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<T, F>(env: Option<&HashMap<OsString, OsString>>, 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))
|
||||
}
|
||||
}
|
||||
|
|
@ -225,27 +225,11 @@ impl File {
|
|||
}
|
||||
|
||||
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
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<usize> {
|
||||
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(()) }
|
||||
|
|
|
|||
|
|
@ -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<usize> {
|
||||
read(self.0, buf)
|
||||
}
|
||||
|
||||
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
77
src/libstd/sys/windows/pipe2.rs
Normal file
77
src/libstd/sys/windows/pipe2.rs
Normal file
|
|
@ -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 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
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<usize> {
|
||||
handle::read(self.raw(), buf)
|
||||
}
|
||||
|
||||
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
479
src/libstd/sys/windows/process2.rs
Normal file
479
src/libstd/sys/windows/process2.rs
Normal file
|
|
@ -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 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
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<OsString>,
|
||||
pub env: Option<HashMap<OsString, OsString>>,
|
||||
pub cwd: Option<OsString>,
|
||||
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<Item = &'a OsStr>>(&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<AnonPipe>, out_fd: Option<AnonPipe>, err_fd: Option<AnonPipe>)
|
||||
-> io::Result<Process>
|
||||
{
|
||||
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::<STARTUPINFO>() 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<AnonPipe>, 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::<libc::SECURITY_ATTRIBUTES>();
|
||||
let mut sa = libc::SECURITY_ATTRIBUTES {
|
||||
nLength: size as libc::DWORD,
|
||||
lpSecurityDescriptor: ptr::null_mut(),
|
||||
bInheritHandle: 1,
|
||||
};
|
||||
let mut filename: Vec<u16> = "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<ExitStatus> {
|
||||
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<i32> {
|
||||
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<u16> {
|
||||
let mut cmd: Vec<u16> = 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<u16>, 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<F, T>(env: Option<&collections::HashMap<OsString, OsString>>, 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<T, F>(d: Option<&OsString>, cb: F) -> T where
|
||||
F: FnOnce(*const u16) -> T,
|
||||
{
|
||||
match d {
|
||||
Some(dir) => {
|
||||
let mut dir_str: Vec<u16> = 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::<Vec<OsString>>()
|
||||
.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}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue