Skip to content

Commit

Permalink
std: Implement CommandExt::exec
Browse files Browse the repository at this point in the history
This commit implements the `exec` function proposed in [RFC 1359][rfc] which is
a function on the `CommandExt` trait to execute all parts of a `Command::spawn`
without the `fork` on Unix. More details on the function itself can be found in
the comments in the commit.

[rfc]: rust-lang/rfcs#1359

cc #31398
  • Loading branch information
alexcrichton committed Feb 10, 2016
1 parent d15db1d commit b37477c
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 2 deletions.
26 changes: 26 additions & 0 deletions src/libstd/sys/unix/ext/process.rs
Expand Up @@ -75,6 +75,28 @@ pub trait CommandExt {
#[unstable(feature = "process_exec", issue = "31398")]
fn before_exec<F>(&mut self, f: F) -> &mut process::Command
where F: FnMut() -> io::Result<()> + Send + Sync + 'static;

/// Performs all the required setup by this `Command`, followed by calling
/// the `execvp` syscall.
///
/// On success this function will not return, and otherwise it will return
/// an error indicating why the exec (or another part of the setup of the
/// `Command`) failed.
///
/// This function, unlike `spawn`, will **not** `fork` the process to create
/// a new child. Like spawn, however, the default behavior for the stdio
/// descriptors will be to inherited from the current process.
///
/// # Notes
///
/// The process may be in a "broken state" if this function returns in
/// error. For example the working directory, environment variables, signal
/// handling settings, various user/group information, or aspects of stdio
/// file descriptors may have changed. If a "transactional spawn" is
/// required to gracefully handle errors it is recommended to use the
/// cross-platform `spawn` instead.
#[unstable(feature = "process_exec", issue = "31398")]
fn exec(&mut self) -> io::Error;
}

#[stable(feature = "rust1", since = "1.0.0")]
Expand All @@ -100,6 +122,10 @@ impl CommandExt for process::Command {
self.as_inner_mut().before_exec(Box::new(f));
self
}

fn exec(&mut self) -> io::Error {
self.as_inner_mut().exec(sys::process::Stdio::Inherit)
}
}

/// Unix-specific extensions to `std::process::ExitStatus`
Expand Down
16 changes: 14 additions & 2 deletions src/libstd/sys/unix/process.rs
Expand Up @@ -230,7 +230,7 @@ impl Command {
match try!(cvt(libc::fork())) {
0 => {
drop(input);
let err = self.exec(theirs);
let err = self.do_exec(theirs);
let errno = err.raw_os_error().unwrap_or(libc::EINVAL) as u32;
let bytes = [
(errno >> 24) as u8,
Expand Down Expand Up @@ -290,6 +290,18 @@ impl Command {
}
}

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) {
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
Expand Down Expand Up @@ -320,7 +332,7 @@ impl Command {
// allocation). Instead we just close it manually. This will never
// have the drop glue anyway because this code never returns (the
// child will either exec() or invoke libc::exit)
unsafe fn exec(&mut self, stdio: ChildPipes) -> io::Error {
unsafe fn do_exec(&mut self, stdio: ChildPipes) -> io::Error {
macro_rules! try {
($e:expr) => (match $e {
Ok(e) => e,
Expand Down
72 changes: 72 additions & 0 deletions src/test/run-pass/command-exec.rs
@@ -0,0 +1,72 @@
// 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.

// ignore-windows - this is a unix-specific test
// ignore-pretty

#![feature(process_exec)]

use std::env;
use std::os::unix::process::CommandExt;
use std::process::Command;

fn main() {
let mut args = env::args();
let me = args.next().unwrap();

if let Some(arg) = args.next() {
match &arg[..] {
"test1" => println!("passed"),

"exec-test1" => {
let err = Command::new(&me).arg("test1").exec();
panic!("failed to spawn: {}", err);
}

"exec-test2" => {
Command::new("/path/to/nowhere").exec();
println!("passed");
}

"exec-test3" => {
Command::new(&me).arg("bad\0").exec();
println!("passed");
}

"exec-test4" => {
Command::new(&me).current_dir("/path/to/nowhere").exec();
println!("passed");
}

_ => panic!("unknown argument: {}", arg),
}
return
}

let output = Command::new(&me).arg("exec-test1").output().unwrap();
assert!(output.status.success());
assert!(output.stderr.is_empty());
assert_eq!(output.stdout, b"passed\n");

let output = Command::new(&me).arg("exec-test2").output().unwrap();
assert!(output.status.success());
assert!(output.stderr.is_empty());
assert_eq!(output.stdout, b"passed\n");

let output = Command::new(&me).arg("exec-test3").output().unwrap();
assert!(output.status.success());
assert!(output.stderr.is_empty());
assert_eq!(output.stdout, b"passed\n");

let output = Command::new(&me).arg("exec-test4").output().unwrap();
assert!(output.status.success());
assert!(output.stderr.is_empty());
assert_eq!(output.stdout, b"passed\n");
}

0 comments on commit b37477c

Please sign in to comment.