Auto merge of #37936 - tedsta:fuchsia_std_process, r=alexcrichton
Fuchsia support for std::process via liblaunchpad. Now we can launch processes on Fuchsia via the Rust standard library! ... Mostly. Right now, ~5% of the time, reading the stdout/stderr off the pipes will fail. Some Magenta kernel people think it's probably a bug in Magenta's pipes. I wrote a unit test that demonstrates the issue in C, which I was told will expedite a fix. https://fuchsia-review.googlesource.com/#/c/15628/ Hopefully this can get merged once the issue is fixed :) @raphlinus
This commit is contained in:
commit
af0a0719ea
|
@ -60,6 +60,8 @@ fn main() {
|
|||
println!("cargo:rustc-link-lib=shell32");
|
||||
} else if target.contains("fuchsia") {
|
||||
println!("cargo:rustc-link-lib=magenta");
|
||||
println!("cargo:rustc-link-lib=mxio");
|
||||
println!("cargo:rustc-link-lib=launchpad"); // for std::process
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -110,6 +110,7 @@ impl FileDesc {
|
|||
#[cfg(not(any(target_env = "newlib",
|
||||
target_os = "solaris",
|
||||
target_os = "emscripten",
|
||||
target_os = "fuchsia",
|
||||
target_os = "haiku")))]
|
||||
pub fn set_cloexec(&self) -> io::Result<()> {
|
||||
unsafe {
|
||||
|
@ -120,6 +121,7 @@ impl FileDesc {
|
|||
#[cfg(any(target_env = "newlib",
|
||||
target_os = "solaris",
|
||||
target_os = "emscripten",
|
||||
target_os = "fuchsia",
|
||||
target_os = "haiku"))]
|
||||
pub fn set_cloexec(&self) -> io::Result<()> {
|
||||
unsafe {
|
||||
|
|
|
@ -77,6 +77,7 @@ pub fn read2(p1: AnonPipe,
|
|||
v1: &mut Vec<u8>,
|
||||
p2: AnonPipe,
|
||||
v2: &mut Vec<u8>) -> io::Result<()> {
|
||||
|
||||
// Set both pipes into nonblocking mode as we're gonna be reading from both
|
||||
// in the `select` loop below, and we wouldn't want one to block the other!
|
||||
let p1 = p1.into_fd();
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
// Copyright 2016 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.
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
use convert::TryInto;
|
||||
use io;
|
||||
use os::raw::c_char;
|
||||
use u64;
|
||||
|
||||
use libc::{c_int, c_void};
|
||||
|
||||
pub type mx_handle_t = i32;
|
||||
pub type mx_vaddr_t = usize;
|
||||
pub type mx_rights_t = u32;
|
||||
pub type mx_status_t = i32;
|
||||
|
||||
pub type mx_size_t = usize;
|
||||
pub type mx_ssize_t = isize;
|
||||
|
||||
pub const MX_HANDLE_INVALID: mx_handle_t = 0;
|
||||
|
||||
pub type mx_time_t = u64;
|
||||
pub const MX_TIME_INFINITE : mx_time_t = u64::MAX;
|
||||
|
||||
pub type mx_signals_t = u32;
|
||||
|
||||
pub const MX_OBJECT_SIGNAL_3 : mx_signals_t = 1 << 3;
|
||||
|
||||
pub const MX_TASK_TERMINATED : mx_signals_t = MX_OBJECT_SIGNAL_3;
|
||||
|
||||
pub const MX_RIGHT_SAME_RIGHTS : mx_rights_t = 1 << 31;
|
||||
|
||||
pub type mx_object_info_topic_t = u32;
|
||||
|
||||
pub const MX_INFO_PROCESS : mx_object_info_topic_t = 3;
|
||||
|
||||
pub const MX_HND_TYPE_JOB: u32 = 6;
|
||||
|
||||
pub fn mx_cvt<T>(t: T) -> io::Result<T> where T: TryInto<mx_status_t>+Copy {
|
||||
if let Ok(status) = TryInto::try_into(t) {
|
||||
if status < 0 {
|
||||
Err(io::Error::from_raw_os_error(status))
|
||||
} else {
|
||||
Ok(t)
|
||||
}
|
||||
} else {
|
||||
Err(io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
// Safe wrapper around mx_handle_t
|
||||
pub struct Handle {
|
||||
raw: mx_handle_t,
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
pub fn new(raw: mx_handle_t) -> Handle {
|
||||
Handle {
|
||||
raw: raw,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raw(&self) -> mx_handle_t {
|
||||
self.raw
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Handle {
|
||||
fn drop(&mut self) {
|
||||
unsafe { mx_cvt(mx_handle_close(self.raw)).expect("Failed to close mx_handle_t"); }
|
||||
}
|
||||
}
|
||||
|
||||
// Common MX_INFO header
|
||||
#[derive(Default)]
|
||||
#[repr(C)]
|
||||
pub struct mx_info_header_t {
|
||||
pub topic: u32, // identifies the info struct
|
||||
pub avail_topic_size: u16, // “native” size of the struct
|
||||
pub topic_size: u16, // size of the returned struct (<=topic_size)
|
||||
pub avail_count: u32, // number of records the kernel has
|
||||
pub count: u32, // number of records returned (limited by buffer size)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[repr(C)]
|
||||
pub struct mx_record_process_t {
|
||||
pub return_code: c_int,
|
||||
}
|
||||
|
||||
// Returned for topic MX_INFO_PROCESS
|
||||
#[derive(Default)]
|
||||
#[repr(C)]
|
||||
pub struct mx_info_process_t {
|
||||
pub hdr: mx_info_header_t,
|
||||
pub rec: mx_record_process_t,
|
||||
}
|
||||
|
||||
extern {
|
||||
pub fn mx_task_kill(handle: mx_handle_t) -> mx_status_t;
|
||||
|
||||
pub fn mx_handle_close(handle: mx_handle_t) -> mx_status_t;
|
||||
|
||||
pub fn mx_handle_duplicate(handle: mx_handle_t, rights: mx_rights_t,
|
||||
out: *const mx_handle_t) -> mx_handle_t;
|
||||
|
||||
pub fn mx_handle_wait_one(handle: mx_handle_t, signals: mx_signals_t, timeout: mx_time_t,
|
||||
pending: *mut mx_signals_t) -> mx_status_t;
|
||||
|
||||
pub fn mx_object_get_info(handle: mx_handle_t, topic: u32, buffer: *mut c_void,
|
||||
buffer_size: mx_size_t, actual_size: *mut mx_size_t,
|
||||
avail: *mut mx_size_t) -> mx_status_t;
|
||||
}
|
||||
|
||||
// Handle Info entries associate a type and optional
|
||||
// argument with each handle included in the process
|
||||
// arguments message.
|
||||
pub fn mx_hnd_info(hnd_type: u32, arg: u32) -> u32 {
|
||||
(hnd_type & 0xFFFF) | ((arg & 0xFFFF) << 16)
|
||||
}
|
||||
|
||||
extern {
|
||||
pub fn mxio_get_startup_handle(id: u32) -> mx_handle_t;
|
||||
}
|
||||
|
||||
// From `enum special_handles` in system/ulib/launchpad/launchpad.c
|
||||
#[allow(unused)] pub const HND_LOADER_SVC: usize = 0;
|
||||
// HND_EXEC_VMO = 1
|
||||
#[allow(unused)] pub const HND_SPECIAL_COUNT: usize = 2;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct launchpad_t {
|
||||
argc: u32,
|
||||
envc: u32,
|
||||
args: *const c_char,
|
||||
args_len: usize,
|
||||
env: *const c_char,
|
||||
env_len: usize,
|
||||
|
||||
handles: *mut mx_handle_t,
|
||||
handles_info: *mut u32,
|
||||
handle_count: usize,
|
||||
handle_alloc: usize,
|
||||
|
||||
entry: mx_vaddr_t,
|
||||
base: mx_vaddr_t,
|
||||
vdso_base: mx_vaddr_t,
|
||||
|
||||
stack_size: usize,
|
||||
|
||||
special_handles: [mx_handle_t; HND_SPECIAL_COUNT],
|
||||
loader_message: bool,
|
||||
}
|
||||
|
||||
extern {
|
||||
pub fn launchpad_create(job: mx_handle_t, name: *const c_char,
|
||||
lp: *mut *mut launchpad_t) -> mx_status_t;
|
||||
|
||||
pub fn launchpad_start(lp: *mut launchpad_t) -> mx_status_t;
|
||||
|
||||
pub fn launchpad_destroy(lp: *mut launchpad_t);
|
||||
|
||||
pub fn launchpad_arguments(lp: *mut launchpad_t, argc: c_int,
|
||||
argv: *const *const c_char) -> mx_status_t;
|
||||
|
||||
pub fn launchpad_environ(lp: *mut launchpad_t, envp: *const *const c_char) -> mx_status_t;
|
||||
|
||||
pub fn launchpad_clone_mxio_root(lp: *mut launchpad_t) -> mx_status_t;
|
||||
|
||||
pub fn launchpad_clone_mxio_cwd(lp: *mut launchpad_t) -> mx_status_t;
|
||||
|
||||
pub fn launchpad_clone_fd(lp: *mut launchpad_t, fd: c_int, target_fd: c_int) -> mx_status_t;
|
||||
|
||||
pub fn launchpad_transfer_fd(lp: *mut launchpad_t, fd: c_int, target_fd: c_int) -> mx_status_t;
|
||||
|
||||
pub fn launchpad_elf_load(lp: *mut launchpad_t, vmo: mx_handle_t) -> mx_status_t;
|
||||
|
||||
pub fn launchpad_add_vdso_vmo(lp: *mut launchpad_t) -> mx_status_t;
|
||||
|
||||
pub fn launchpad_load_vdso(lp: *mut launchpad_t, vmo: mx_handle_t) -> mx_status_t;
|
||||
|
||||
pub fn launchpad_vmo_from_file(filename: *const c_char) -> mx_handle_t;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2016 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.
|
||||
|
||||
pub use self::process_common::{Command, ExitStatus, Stdio, StdioPipes};
|
||||
pub use self::process_inner::Process;
|
||||
|
||||
mod process_common;
|
||||
#[cfg(not(target_os = "fuchsia"))]
|
||||
#[path = "process_unix.rs"]
|
||||
mod process_inner;
|
||||
#[cfg(target_os = "fuchsia")]
|
||||
#[path = "process_fuchsia.rs"]
|
||||
mod process_inner;
|
||||
#[cfg(target_os = "fuchsia")]
|
||||
mod magenta;
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
|
||||
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
|
@ -14,14 +14,12 @@ use collections::hash_map::{HashMap, Entry};
|
|||
use env;
|
||||
use ffi::{OsString, OsStr, CString, CStr};
|
||||
use fmt;
|
||||
use io::{self, Error, ErrorKind};
|
||||
use libc::{self, pid_t, c_int, gid_t, uid_t, c_char};
|
||||
use mem;
|
||||
use io;
|
||||
use libc::{self, c_int, gid_t, uid_t, c_char};
|
||||
use ptr;
|
||||
use sys::fd::FileDesc;
|
||||
use sys::fs::{File, OpenOptions};
|
||||
use sys::pipe::{self, AnonPipe};
|
||||
use sys::{self, cvt, cvt_r};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Command
|
||||
|
@ -71,13 +69,13 @@ pub struct StdioPipes {
|
|||
|
||||
// passed to do_exec() with configuration of what the child stdio should look
|
||||
// like
|
||||
struct ChildPipes {
|
||||
stdin: ChildStdio,
|
||||
stdout: ChildStdio,
|
||||
stderr: ChildStdio,
|
||||
pub struct ChildPipes {
|
||||
pub stdin: ChildStdio,
|
||||
pub stdout: ChildStdio,
|
||||
pub stderr: ChildStdio,
|
||||
}
|
||||
|
||||
enum ChildStdio {
|
||||
pub enum ChildStdio {
|
||||
Inherit,
|
||||
Explicit(c_int),
|
||||
Owned(FileDesc),
|
||||
|
@ -195,6 +193,33 @@ impl Command {
|
|||
self.gid = Some(id);
|
||||
}
|
||||
|
||||
pub fn saw_nul(&self) -> bool {
|
||||
self.saw_nul
|
||||
}
|
||||
pub fn get_envp(&self) -> &Option<Vec<*const c_char>> {
|
||||
&self.envp
|
||||
}
|
||||
pub fn get_argv(&self) -> &Vec<*const c_char> {
|
||||
&self.argv
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_cwd(&self) -> &Option<CString> {
|
||||
&self.cwd
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn get_uid(&self) -> Option<uid_t> {
|
||||
self.uid
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn get_gid(&self) -> Option<gid_t> {
|
||||
self.gid
|
||||
}
|
||||
|
||||
pub fn get_closures(&mut self) -> &mut Vec<Box<FnMut() -> io::Result<()> + Send + Sync>> {
|
||||
&mut self.closures
|
||||
}
|
||||
|
||||
pub fn before_exec(&mut self,
|
||||
f: Box<FnMut() -> io::Result<()> + Send + Sync>) {
|
||||
self.closures.push(f);
|
||||
|
@ -203,200 +228,16 @@ impl Command {
|
|||
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);
|
||||
}
|
||||
|
||||
pub fn spawn(&mut self, default: Stdio, needs_stdin: bool)
|
||||
-> io::Result<(Process, StdioPipes)> {
|
||||
const CLOEXEC_MSG_FOOTER: &'static [u8] = b"NOEX";
|
||||
|
||||
if self.saw_nul {
|
||||
return Err(io::Error::new(ErrorKind::InvalidInput,
|
||||
"nul byte found in provided data"));
|
||||
}
|
||||
|
||||
let (ours, theirs) = self.setup_io(default, needs_stdin)?;
|
||||
let (input, output) = sys::pipe::anon_pipe()?;
|
||||
|
||||
let pid = unsafe {
|
||||
match cvt(libc::fork())? {
|
||||
0 => {
|
||||
drop(input);
|
||||
let err = self.do_exec(theirs);
|
||||
let errno = err.raw_os_error().unwrap_or(libc::EINVAL) 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, and then
|
||||
// we want to be sure we *don't* run at_exit destructors as
|
||||
// we're being torn down regardless
|
||||
assert!(output.write(&bytes).is_ok());
|
||||
libc::_exit(1)
|
||||
}
|
||||
n => n,
|
||||
}
|
||||
};
|
||||
|
||||
let mut p = Process { pid: pid, status: None };
|
||||
drop(output);
|
||||
let mut bytes = [0; 8];
|
||||
|
||||
// loop to handle EINTR
|
||||
loop {
|
||||
match input.read(&mut bytes) {
|
||||
Ok(0) => return Ok((p, ours)),
|
||||
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_raw_os_error(errno))
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exec(&mut self, default: Stdio) -> io::Error {
|
||||
if self.saw_nul {
|
||||
return io::Error::new(ErrorKind::InvalidInput,
|
||||
"nul byte found in provided data")
|
||||
}
|
||||
|
||||
match self.setup_io(default, true) {
|
||||
Ok((_, theirs)) => unsafe { self.do_exec(theirs) },
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
unsafe fn do_exec(&mut self, stdio: ChildPipes) -> io::Error {
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => return e,
|
||||
})
|
||||
}
|
||||
|
||||
if let Some(fd) = stdio.stdin.fd() {
|
||||
t!(cvt_r(|| libc::dup2(fd, libc::STDIN_FILENO)));
|
||||
}
|
||||
if let Some(fd) = stdio.stdout.fd() {
|
||||
t!(cvt_r(|| libc::dup2(fd, libc::STDOUT_FILENO)));
|
||||
}
|
||||
if let Some(fd) = stdio.stderr.fd() {
|
||||
t!(cvt_r(|| libc::dup2(fd, libc::STDERR_FILENO)));
|
||||
}
|
||||
|
||||
if let Some(u) = self.gid {
|
||||
t!(cvt(libc::setgid(u as gid_t)));
|
||||
}
|
||||
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
|
||||
// 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.
|
||||
let _ = libc::setgroups(0, ptr::null());
|
||||
|
||||
t!(cvt(libc::setuid(u as uid_t)));
|
||||
}
|
||||
if let Some(ref cwd) = self.cwd {
|
||||
t!(cvt(libc::chdir(cwd.as_ptr())));
|
||||
}
|
||||
if let Some(ref envp) = self.envp {
|
||||
*sys::os::environ() = envp.as_ptr();
|
||||
}
|
||||
|
||||
// NaCl has no signal support.
|
||||
if cfg!(not(any(target_os = "nacl", target_os = "emscripten"))) {
|
||||
// Reset signal handling so the child process starts in a
|
||||
// standardized state. libstd ignores SIGPIPE, and signal-handling
|
||||
// libraries often set a mask. Child processes inherit ignored
|
||||
// signals and the signal mask from their parent, but most
|
||||
// UNIX programs do not reset these things on their own, so we
|
||||
// need to clean things up now to avoid confusing the program
|
||||
// we're about to run.
|
||||
let mut set: libc::sigset_t = mem::uninitialized();
|
||||
t!(cvt(libc::sigemptyset(&mut set)));
|
||||
t!(cvt(libc::pthread_sigmask(libc::SIG_SETMASK, &set,
|
||||
ptr::null_mut())));
|
||||
let ret = super::signal(libc::SIGPIPE, libc::SIG_DFL);
|
||||
if ret == libc::SIG_ERR {
|
||||
return io::Error::last_os_error()
|
||||
}
|
||||
}
|
||||
|
||||
for callback in self.closures.iter_mut() {
|
||||
t!(callback());
|
||||
}
|
||||
|
||||
libc::execvp(self.argv[0], self.argv.as_ptr());
|
||||
io::Error::last_os_error()
|
||||
}
|
||||
|
||||
|
||||
fn setup_io(&self, default: Stdio, needs_stdin: bool)
|
||||
pub fn setup_io(&self, default: Stdio, needs_stdin: bool)
|
||||
-> io::Result<(StdioPipes, ChildPipes)> {
|
||||
let null = Stdio::Null;
|
||||
let default_stdin = if needs_stdin {&default} else {&null};
|
||||
|
@ -428,10 +269,12 @@ fn os2c(s: &OsStr, saw_nul: &mut bool) -> CString {
|
|||
}
|
||||
|
||||
impl Stdio {
|
||||
fn to_child_stdio(&self, readable: bool)
|
||||
pub fn to_child_stdio(&self, readable: bool)
|
||||
-> io::Result<(ChildStdio, Option<AnonPipe>)> {
|
||||
match *self {
|
||||
Stdio::Inherit => Ok((ChildStdio::Inherit, None)),
|
||||
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
|
||||
|
@ -473,7 +316,7 @@ impl Stdio {
|
|||
}
|
||||
|
||||
impl ChildStdio {
|
||||
fn fd(&self) -> Option<c_int> {
|
||||
pub fn fd(&self) -> Option<c_int> {
|
||||
match *self {
|
||||
ChildStdio::Inherit => None,
|
||||
ChildStdio::Explicit(fd) => Some(fd),
|
||||
|
@ -504,15 +347,15 @@ impl fmt::Debug for Command {
|
|||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Processes
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Unix exit statuses
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub struct ExitStatus(c_int);
|
||||
|
||||
impl ExitStatus {
|
||||
pub fn new(status: c_int) -> ExitStatus {
|
||||
ExitStatus(status)
|
||||
}
|
||||
|
||||
fn exited(&self) -> bool {
|
||||
unsafe { libc::WIFEXITED(self.0) }
|
||||
}
|
||||
|
@ -555,40 +398,6 @@ impl fmt::Display for ExitStatus {
|
|||
}
|
||||
}
|
||||
|
||||
/// The unique id of the process (this should never be negative).
|
||||
pub struct Process {
|
||||
pid: pid_t,
|
||||
status: Option<ExitStatus>,
|
||||
}
|
||||
|
||||
impl Process {
|
||||
pub fn id(&self) -> u32 {
|
||||
self.pid as u32
|
||||
}
|
||||
|
||||
pub fn kill(&mut self) -> io::Result<()> {
|
||||
// If we've already waited on this process then the pid can be recycled
|
||||
// and used for another process, and we probably shouldn't be killing
|
||||
// random processes, so just return an error.
|
||||
if self.status.is_some() {
|
||||
Err(Error::new(ErrorKind::InvalidInput,
|
||||
"invalid argument: can't kill an exited process"))
|
||||
} else {
|
||||
cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait(&mut self) -> io::Result<ExitStatus> {
|
||||
if let Some(status) = self.status {
|
||||
return Ok(status)
|
||||
}
|
||||
let mut status = 0 as c_int;
|
||||
cvt_r(|| unsafe { libc::waitpid(self.pid, &mut status, 0) })?;
|
||||
self.status = Some(ExitStatus(status));
|
||||
Ok(ExitStatus(status))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(target_os = "emscripten")))]
|
||||
mod tests {
|
||||
use super::*;
|
|
@ -0,0 +1,174 @@
|
|||
// Copyright 2016 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 io;
|
||||
use libc;
|
||||
use mem;
|
||||
use ptr;
|
||||
|
||||
use sys::process::magenta::{Handle, launchpad_t, mx_handle_t};
|
||||
use sys::process::process_common::*;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Command
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
impl Command {
|
||||
pub fn spawn(&mut self, default: Stdio, needs_stdin: bool)
|
||||
-> io::Result<(Process, StdioPipes)> {
|
||||
if self.saw_nul() {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidInput,
|
||||
"nul byte found in provided data"));
|
||||
}
|
||||
|
||||
let (ours, theirs) = self.setup_io(default, needs_stdin)?;
|
||||
|
||||
let (launchpad, process_handle) = unsafe { self.do_exec(theirs)? };
|
||||
|
||||
Ok((Process { launchpad: launchpad, handle: Handle::new(process_handle) }, ours))
|
||||
}
|
||||
|
||||
pub fn exec(&mut self, default: Stdio) -> io::Error {
|
||||
if self.saw_nul() {
|
||||
return io::Error::new(io::ErrorKind::InvalidInput,
|
||||
"nul byte found in provided data")
|
||||
}
|
||||
|
||||
match self.setup_io(default, true) {
|
||||
Ok((_, _)) => {
|
||||
// FIXME: This is tough because we don't support the exec syscalls
|
||||
unimplemented!();
|
||||
},
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn do_exec(&mut self, stdio: ChildPipes)
|
||||
-> io::Result<(*mut launchpad_t, mx_handle_t)> {
|
||||
use sys::process::magenta::*;
|
||||
|
||||
let job_handle = mxio_get_startup_handle(mx_hnd_info(MX_HND_TYPE_JOB, 0));
|
||||
let envp = match *self.get_envp() {
|
||||
Some(ref envp) => envp.as_ptr(),
|
||||
None => ptr::null(),
|
||||
};
|
||||
|
||||
// To make sure launchpad_destroy gets called on the launchpad if this function fails
|
||||
struct LaunchpadDestructor(*mut launchpad_t);
|
||||
impl Drop for LaunchpadDestructor {
|
||||
fn drop(&mut self) { unsafe { launchpad_destroy(self.0); } }
|
||||
}
|
||||
|
||||
let mut launchpad: *mut launchpad_t = ptr::null_mut();
|
||||
let launchpad_destructor = LaunchpadDestructor(launchpad);
|
||||
|
||||
// Duplicate the job handle
|
||||
let mut job_copy: mx_handle_t = MX_HANDLE_INVALID;
|
||||
mx_cvt(mx_handle_duplicate(job_handle, MX_RIGHT_SAME_RIGHTS, &mut job_copy))?;
|
||||
// Create a launchpad
|
||||
mx_cvt(launchpad_create(job_copy, self.get_argv()[0], &mut launchpad))?;
|
||||
// Set the process argv
|
||||
mx_cvt(launchpad_arguments(launchpad, self.get_argv().len() as i32 - 1,
|
||||
self.get_argv().as_ptr()))?;
|
||||
// Setup the environment vars
|
||||
mx_cvt(launchpad_environ(launchpad, envp))?;
|
||||
mx_cvt(launchpad_add_vdso_vmo(launchpad))?;
|
||||
mx_cvt(launchpad_clone_mxio_root(launchpad))?;
|
||||
// Load the executable
|
||||
mx_cvt(launchpad_elf_load(launchpad, launchpad_vmo_from_file(self.get_argv()[0])))?;
|
||||
mx_cvt(launchpad_load_vdso(launchpad, MX_HANDLE_INVALID))?;
|
||||
mx_cvt(launchpad_clone_mxio_cwd(launchpad))?;
|
||||
|
||||
// Clone stdin, stdout, and stderr
|
||||
if let Some(fd) = stdio.stdin.fd() {
|
||||
launchpad_transfer_fd(launchpad, fd, 0);
|
||||
} else {
|
||||
launchpad_clone_fd(launchpad, 0, 0);
|
||||
}
|
||||
if let Some(fd) = stdio.stdout.fd() {
|
||||
launchpad_transfer_fd(launchpad, fd, 1);
|
||||
} else {
|
||||
launchpad_clone_fd(launchpad, 1, 1);
|
||||
}
|
||||
if let Some(fd) = stdio.stderr.fd() {
|
||||
launchpad_transfer_fd(launchpad, fd, 2);
|
||||
} else {
|
||||
launchpad_clone_fd(launchpad, 2, 2);
|
||||
}
|
||||
|
||||
// We don't want FileDesc::drop to be called on any stdio. It would close their fds. The
|
||||
// fds will be closed once the child process finishes.
|
||||
mem::forget(stdio);
|
||||
|
||||
for callback in self.get_closures().iter_mut() {
|
||||
callback()?;
|
||||
}
|
||||
|
||||
let process_handle = mx_cvt(launchpad_start(launchpad))?;
|
||||
|
||||
// Successfully started the launchpad
|
||||
mem::forget(launchpad_destructor);
|
||||
|
||||
Ok((launchpad, process_handle))
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Processes
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub struct Process {
|
||||
launchpad: *mut launchpad_t,
|
||||
handle: Handle,
|
||||
}
|
||||
|
||||
impl Process {
|
||||
pub fn id(&self) -> u32 {
|
||||
self.handle.raw() as u32
|
||||
}
|
||||
|
||||
pub fn kill(&mut self) -> io::Result<()> {
|
||||
use sys::process::magenta::*;
|
||||
|
||||
unsafe { mx_cvt(mx_task_kill(self.handle.raw()))?; }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn wait(&mut self) -> io::Result<ExitStatus> {
|
||||
use default::Default;
|
||||
use sys::process::magenta::*;
|
||||
|
||||
let mut proc_info: mx_info_process_t = Default::default();
|
||||
let mut actual: mx_size_t = 0;
|
||||
let mut avail: mx_size_t = 0;
|
||||
|
||||
unsafe {
|
||||
mx_cvt(mx_handle_wait_one(self.handle.raw(), MX_TASK_TERMINATED,
|
||||
MX_TIME_INFINITE, ptr::null_mut()))?;
|
||||
mx_cvt(mx_object_get_info(self.handle.raw(), MX_INFO_PROCESS,
|
||||
&mut proc_info as *mut _ as *mut libc::c_void,
|
||||
mem::size_of::<mx_info_process_t>(), &mut actual,
|
||||
&mut avail))?;
|
||||
}
|
||||
if actual != 1 {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData,
|
||||
"Failed to get exit status of process"));
|
||||
}
|
||||
Ok(ExitStatus::new(proc_info.rec.return_code))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Process {
|
||||
fn drop(&mut self) {
|
||||
use sys::process::magenta::launchpad_destroy;
|
||||
unsafe { launchpad_destroy(self.launchpad); }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
// 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 io::{self, Error, ErrorKind};
|
||||
use libc::{self, c_int, gid_t, pid_t, uid_t};
|
||||
use mem;
|
||||
use ptr;
|
||||
|
||||
use sys::cvt;
|
||||
use sys::process::process_common::*;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Command
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
impl Command {
|
||||
pub fn spawn(&mut self, default: Stdio, needs_stdin: bool)
|
||||
-> io::Result<(Process, StdioPipes)> {
|
||||
use sys;
|
||||
|
||||
const CLOEXEC_MSG_FOOTER: &'static [u8] = b"NOEX";
|
||||
|
||||
if self.saw_nul() {
|
||||
return Err(io::Error::new(ErrorKind::InvalidInput,
|
||||
"nul byte found in provided data"));
|
||||
}
|
||||
|
||||
let (ours, theirs) = self.setup_io(default, needs_stdin)?;
|
||||
let (input, output) = sys::pipe::anon_pipe()?;
|
||||
|
||||
let pid = unsafe {
|
||||
match cvt(libc::fork())? {
|
||||
0 => {
|
||||
drop(input);
|
||||
let err = self.do_exec(theirs);
|
||||
let errno = err.raw_os_error().unwrap_or(libc::EINVAL) 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, and then
|
||||
// we want to be sure we *don't* run at_exit destructors as
|
||||
// we're being torn down regardless
|
||||
assert!(output.write(&bytes).is_ok());
|
||||
libc::_exit(1)
|
||||
}
|
||||
n => n,
|
||||
}
|
||||
};
|
||||
|
||||
let mut p = Process { pid: pid, status: None };
|
||||
drop(output);
|
||||
let mut bytes = [0; 8];
|
||||
|
||||
// loop to handle EINTR
|
||||
loop {
|
||||
match input.read(&mut bytes) {
|
||||
Ok(0) => return Ok((p, ours)),
|
||||
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_raw_os_error(errno))
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exec(&mut self, default: Stdio) -> io::Error {
|
||||
if self.saw_nul() {
|
||||
return io::Error::new(ErrorKind::InvalidInput,
|
||||
"nul byte found in provided data")
|
||||
}
|
||||
|
||||
match self.setup_io(default, true) {
|
||||
Ok((_, theirs)) => unsafe { self.do_exec(theirs) },
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
unsafe fn do_exec(&mut self, stdio: ChildPipes) -> io::Error {
|
||||
use sys::{self, cvt_r};
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => return e,
|
||||
})
|
||||
}
|
||||
|
||||
if let Some(fd) = stdio.stdin.fd() {
|
||||
t!(cvt_r(|| libc::dup2(fd, libc::STDIN_FILENO)));
|
||||
}
|
||||
if let Some(fd) = stdio.stdout.fd() {
|
||||
t!(cvt_r(|| libc::dup2(fd, libc::STDOUT_FILENO)));
|
||||
}
|
||||
if let Some(fd) = stdio.stderr.fd() {
|
||||
t!(cvt_r(|| libc::dup2(fd, libc::STDERR_FILENO)));
|
||||
}
|
||||
|
||||
if let Some(u) = self.get_gid() {
|
||||
t!(cvt(libc::setgid(u as gid_t)));
|
||||
}
|
||||
if let Some(u) = self.get_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
|
||||
// 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.
|
||||
let _ = libc::setgroups(0, ptr::null());
|
||||
|
||||
t!(cvt(libc::setuid(u as uid_t)));
|
||||
}
|
||||
if let Some(ref cwd) = *self.get_cwd() {
|
||||
t!(cvt(libc::chdir(cwd.as_ptr())));
|
||||
}
|
||||
if let Some(ref envp) = *self.get_envp() {
|
||||
*sys::os::environ() = envp.as_ptr();
|
||||
}
|
||||
|
||||
// NaCl has no signal support.
|
||||
if cfg!(not(any(target_os = "nacl", target_os = "emscripten"))) {
|
||||
// Reset signal handling so the child process starts in a
|
||||
// standardized state. libstd ignores SIGPIPE, and signal-handling
|
||||
// libraries often set a mask. Child processes inherit ignored
|
||||
// signals and the signal mask from their parent, but most
|
||||
// UNIX programs do not reset these things on their own, so we
|
||||
// need to clean things up now to avoid confusing the program
|
||||
// we're about to run.
|
||||
let mut set: libc::sigset_t = mem::uninitialized();
|
||||
t!(cvt(libc::sigemptyset(&mut set)));
|
||||
t!(cvt(libc::pthread_sigmask(libc::SIG_SETMASK, &set,
|
||||
ptr::null_mut())));
|
||||
let ret = sys::signal(libc::SIGPIPE, libc::SIG_DFL);
|
||||
if ret == libc::SIG_ERR {
|
||||
return io::Error::last_os_error()
|
||||
}
|
||||
}
|
||||
|
||||
for callback in self.get_closures().iter_mut() {
|
||||
t!(callback());
|
||||
}
|
||||
|
||||
libc::execvp(self.get_argv()[0], self.get_argv().as_ptr());
|
||||
io::Error::last_os_error()
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Processes
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// The unique id of the process (this should never be negative).
|
||||
pub struct Process {
|
||||
pid: pid_t,
|
||||
status: Option<ExitStatus>,
|
||||
}
|
||||
|
||||
impl Process {
|
||||
pub fn id(&self) -> u32 {
|
||||
self.pid as u32
|
||||
}
|
||||
|
||||
pub fn kill(&mut self) -> io::Result<()> {
|
||||
// If we've already waited on this process then the pid can be recycled
|
||||
// and used for another process, and we probably shouldn't be killing
|
||||
// random processes, so just return an error.
|
||||
if self.status.is_some() {
|
||||
Err(Error::new(ErrorKind::InvalidInput,
|
||||
"invalid argument: can't kill an exited process"))
|
||||
} else {
|
||||
cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(|_| ())
|
||||
}
|
||||
}
|
||||
pub fn wait(&mut self) -> io::Result<ExitStatus> {
|
||||
use sys::cvt_r;
|
||||
if let Some(status) = self.status {
|
||||
return Ok(status)
|
||||
}
|
||||
let mut status = 0 as c_int;
|
||||
cvt_r(|| unsafe { libc::waitpid(self.pid, &mut status, 0) })?;
|
||||
self.status = Some(ExitStatus::new(status));
|
||||
Ok(ExitStatus::new(status))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue