Skip to content

Commit

Permalink
Rollup merge of rust-lang#48624 - bdrewery:freebsd-posix-spawn, r=ale…
Browse files Browse the repository at this point in the history
…xcrichton

Command: Support posix_spawn() on FreeBSD/OSX/GNU Linux
  • Loading branch information
alexcrichton committed Mar 23, 2018
2 parents 7cf4cb5 + 70559c5 commit 16eeb10
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 28 deletions.
33 changes: 5 additions & 28 deletions src/libstd/sys/unix/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,42 +383,19 @@ impl IntoInner<c_int> for Socket {
// believe it's thread-safe).
#[cfg(target_env = "gnu")]
fn on_resolver_failure() {
use sys;

// If the version fails to parse, we treat it the same as "not glibc".
if let Some(Ok(version_str)) = glibc_version_cstr().map(CStr::to_str) {
if let Some(version) = parse_glibc_version(version_str) {
if version < (2, 26) {
unsafe { libc::res_init() };
}
if let Some(version) = sys::os::glibc_version() {
if version < (2, 26) {
unsafe { libc::res_init() };
}
}
}

#[cfg(not(target_env = "gnu"))]
fn on_resolver_failure() {}

#[cfg(target_env = "gnu")]
fn glibc_version_cstr() -> Option<&'static CStr> {
weak! {
fn gnu_get_libc_version() -> *const libc::c_char
}
if let Some(f) = gnu_get_libc_version.get() {
unsafe { Some(CStr::from_ptr(f())) }
} else {
None
}
}

// Returns Some((major, minor)) if the string is a valid "x.y" version,
// ignoring any extra dot-separated parts. Otherwise return None.
#[cfg(target_env = "gnu")]
fn parse_glibc_version(version: &str) -> Option<(usize, usize)> {
let mut parsed_ints = version.split(".").map(str::parse::<usize>).fuse();
match (parsed_ints.next(), parsed_ints.next()) {
(Some(Ok(major)), Some(Ok(minor))) => Some((major, minor)),
_ => None
}
}

#[cfg(all(test, taget_env = "gnu"))]
mod test {
use super::*;
Expand Down
32 changes: 32 additions & 0 deletions src/libstd/sys/unix/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,3 +546,35 @@ pub fn getpid() -> u32 {
pub fn getppid() -> u32 {
unsafe { libc::getppid() as u32 }
}

#[cfg(target_env = "gnu")]
pub fn glibc_version() -> Option<(usize, usize)> {
if let Some(Ok(version_str)) = glibc_version_cstr().map(CStr::to_str) {
parse_glibc_version(version_str)
} else {
None
}
}

#[cfg(target_env = "gnu")]
fn glibc_version_cstr() -> Option<&'static CStr> {
weak! {
fn gnu_get_libc_version() -> *const libc::c_char
}
if let Some(f) = gnu_get_libc_version.get() {
unsafe { Some(CStr::from_ptr(f())) }
} else {
None
}
}

// Returns Some((major, minor)) if the string is a valid "x.y" version,
// ignoring any extra dot-separated parts. Otherwise return None.
#[cfg(target_env = "gnu")]
fn parse_glibc_version(version: &str) -> Option<(usize, usize)> {
let mut parsed_ints = version.split(".").map(str::parse::<usize>).fuse();
match (parsed_ints.next(), parsed_ints.next()) {
(Some(Ok(major)), Some(Ok(minor))) => Some((major, minor)),
_ => None
}
}
4 changes: 4 additions & 0 deletions src/libstd/sys/unix/process/process_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ impl Command {
let maybe_env = self.env.capture_if_changed();
maybe_env.map(|env| construct_envp(env, &mut self.saw_nul))
}
#[allow(dead_code)]
pub fn env_saw_path(&self) -> bool {
self.env.have_changed_path()
}

pub fn setup_io(&self, default: Stdio, needs_stdin: bool)
-> io::Result<(StdioPipes, ChildPipes)> {
Expand Down
118 changes: 118 additions & 0 deletions src/libstd/sys/unix/process/process_unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ impl Command {
}

let (ours, theirs) = self.setup_io(default, needs_stdin)?;

if let Some(ret) = self.posix_spawn(&theirs, envp.as_ref())? {
return Ok((ret, ours))
}

let (input, output) = sys::pipe::anon_pipe()?;

let pid = unsafe {
Expand Down Expand Up @@ -229,6 +234,119 @@ impl Command {
libc::execvp(self.get_argv()[0], self.get_argv().as_ptr());
io::Error::last_os_error()
}

#[cfg(not(any(target_os = "macos", target_os = "freebsd",
all(target_os = "linux", target_env = "gnu"))))]
fn posix_spawn(&mut self, _: &ChildPipes, _: Option<&CStringArray>)
-> io::Result<Option<Process>>
{
Ok(None)
}

// Only support platforms for which posix_spawn() can return ENOENT
// directly.
#[cfg(any(target_os = "macos", target_os = "freebsd",
all(target_os = "linux", target_env = "gnu")))]
fn posix_spawn(&mut self, stdio: &ChildPipes, envp: Option<&CStringArray>)
-> io::Result<Option<Process>>
{
use mem;
use sys;

if self.get_cwd().is_some() ||
self.get_gid().is_some() ||
self.get_uid().is_some() ||
self.env_saw_path() ||
self.get_closures().len() != 0 {
return Ok(None)
}

// Only glibc 2.24+ posix_spawn() supports returning ENOENT directly.
#[cfg(all(target_os = "linux", target_env = "gnu"))]
{
if let Some(version) = sys::os::glibc_version() {
if version < (2, 24) {
return Ok(None)
}
} else {
return Ok(None)
}
}

let mut p = Process { pid: 0, status: None };

struct PosixSpawnFileActions(libc::posix_spawn_file_actions_t);

impl Drop for PosixSpawnFileActions {
fn drop(&mut self) {
unsafe {
libc::posix_spawn_file_actions_destroy(&mut self.0);
}
}
}

struct PosixSpawnattr(libc::posix_spawnattr_t);

impl Drop for PosixSpawnattr {
fn drop(&mut self) {
unsafe {
libc::posix_spawnattr_destroy(&mut self.0);
}
}
}

unsafe {
let mut file_actions = PosixSpawnFileActions(mem::uninitialized());
let mut attrs = PosixSpawnattr(mem::uninitialized());

libc::posix_spawnattr_init(&mut attrs.0);
libc::posix_spawn_file_actions_init(&mut file_actions.0);

if let Some(fd) = stdio.stdin.fd() {
cvt(libc::posix_spawn_file_actions_adddup2(&mut file_actions.0,
fd,
libc::STDIN_FILENO))?;
}
if let Some(fd) = stdio.stdout.fd() {
cvt(libc::posix_spawn_file_actions_adddup2(&mut file_actions.0,
fd,
libc::STDOUT_FILENO))?;
}
if let Some(fd) = stdio.stderr.fd() {
cvt(libc::posix_spawn_file_actions_adddup2(&mut file_actions.0,
fd,
libc::STDERR_FILENO))?;
}

let mut set: libc::sigset_t = mem::uninitialized();
cvt(libc::sigemptyset(&mut set))?;
cvt(libc::posix_spawnattr_setsigmask(&mut attrs.0,
&set))?;
cvt(libc::sigaddset(&mut set, libc::SIGPIPE))?;
cvt(libc::posix_spawnattr_setsigdefault(&mut attrs.0,
&set))?;

let flags = libc::POSIX_SPAWN_SETSIGDEF |
libc::POSIX_SPAWN_SETSIGMASK;
cvt(libc::posix_spawnattr_setflags(&mut attrs.0, flags as _))?;

let envp = envp.map(|c| c.as_ptr())
.unwrap_or(*sys::os::environ() as *const _);
let ret = libc::posix_spawnp(
&mut p.pid,
self.get_argv()[0],
&file_actions.0,
&attrs.0,
self.get_argv().as_ptr() as *const _,
envp as *const _,
);
if ret == 0 {
Ok(Some(p))
} else {
Err(io::Error::from_raw_os_error(ret))
}
}
}
}

////////////////////////////////////////////////////////////////////////////////
Expand Down
12 changes: 12 additions & 0 deletions src/libstd/sys_common/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@ impl EnvKey for DefaultEnvKey {}
#[derive(Clone, Debug)]
pub struct CommandEnv<K> {
clear: bool,
saw_path: bool,
vars: BTreeMap<K, Option<OsString>>
}

impl<K: EnvKey> Default for CommandEnv<K> {
fn default() -> Self {
CommandEnv {
clear: false,
saw_path: false,
vars: Default::default()
}
}
Expand Down Expand Up @@ -108,9 +110,11 @@ impl<K: EnvKey> CommandEnv<K> {

// The following functions build up changes
pub fn set(&mut self, key: &OsStr, value: &OsStr) {
self.maybe_saw_path(&key);
self.vars.insert(key.to_owned().into(), Some(value.to_owned()));
}
pub fn remove(&mut self, key: &OsStr) {
self.maybe_saw_path(&key);
if self.clear {
self.vars.remove(key);
} else {
Expand All @@ -121,4 +125,12 @@ impl<K: EnvKey> CommandEnv<K> {
self.clear = true;
self.vars.clear();
}
pub fn have_changed_path(&self) -> bool {
self.saw_path || self.clear
}
fn maybe_saw_path(&mut self, key: &OsStr) {
if !self.saw_path && key == "PATH" {
self.saw_path = true;
}
}
}
23 changes: 23 additions & 0 deletions src/test/run-pass/process-spawn-nonexistent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2018 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.

// ignore-cloudabi no processes
// ignore-emscripten no processes

use std::io::ErrorKind;
use std::process::Command;

fn main() {
assert_eq!(Command::new("nonexistent")
.spawn()
.unwrap_err()
.kind(),
ErrorKind::NotFound);
}

0 comments on commit 16eeb10

Please sign in to comment.