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:
bors 2016-12-02 07:35:06 +00:00
commit af0a0719ea
8 changed files with 689 additions and 238 deletions

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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();

View File

@ -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;
}

View File

@ -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;

View File

@ -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::*;

View File

@ -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); }
}
}

View File

@ -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))
}
}