Skip to content

Commit b37477c

Browse files
committed
std: Implement CommandExt::exec
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
1 parent d15db1d commit b37477c

File tree

3 files changed

+112
-2
lines changed

3 files changed

+112
-2
lines changed

src/libstd/sys/unix/ext/process.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,28 @@ pub trait CommandExt {
7575
#[unstable(feature = "process_exec", issue = "31398")]
7676
fn before_exec<F>(&mut self, f: F) -> &mut process::Command
7777
where F: FnMut() -> io::Result<()> + Send + Sync + 'static;
78+
79+
/// Performs all the required setup by this `Command`, followed by calling
80+
/// the `execvp` syscall.
81+
///
82+
/// On success this function will not return, and otherwise it will return
83+
/// an error indicating why the exec (or another part of the setup of the
84+
/// `Command`) failed.
85+
///
86+
/// This function, unlike `spawn`, will **not** `fork` the process to create
87+
/// a new child. Like spawn, however, the default behavior for the stdio
88+
/// descriptors will be to inherited from the current process.
89+
///
90+
/// # Notes
91+
///
92+
/// The process may be in a "broken state" if this function returns in
93+
/// error. For example the working directory, environment variables, signal
94+
/// handling settings, various user/group information, or aspects of stdio
95+
/// file descriptors may have changed. If a "transactional spawn" is
96+
/// required to gracefully handle errors it is recommended to use the
97+
/// cross-platform `spawn` instead.
98+
#[unstable(feature = "process_exec", issue = "31398")]
99+
fn exec(&mut self) -> io::Error;
78100
}
79101

80102
#[stable(feature = "rust1", since = "1.0.0")]
@@ -100,6 +122,10 @@ impl CommandExt for process::Command {
100122
self.as_inner_mut().before_exec(Box::new(f));
101123
self
102124
}
125+
126+
fn exec(&mut self) -> io::Error {
127+
self.as_inner_mut().exec(sys::process::Stdio::Inherit)
128+
}
103129
}
104130

105131
/// Unix-specific extensions to `std::process::ExitStatus`

src/libstd/sys/unix/process.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ impl Command {
230230
match try!(cvt(libc::fork())) {
231231
0 => {
232232
drop(input);
233-
let err = self.exec(theirs);
233+
let err = self.do_exec(theirs);
234234
let errno = err.raw_os_error().unwrap_or(libc::EINVAL) as u32;
235235
let bytes = [
236236
(errno >> 24) as u8,
@@ -290,6 +290,18 @@ impl Command {
290290
}
291291
}
292292

293+
pub fn exec(&mut self, default: Stdio) -> io::Error {
294+
if self.saw_nul {
295+
return io::Error::new(ErrorKind::InvalidInput,
296+
"nul byte found in provided data")
297+
}
298+
299+
match self.setup_io(default) {
300+
Ok((_, theirs)) => unsafe { self.do_exec(theirs) },
301+
Err(e) => e,
302+
}
303+
}
304+
293305
// And at this point we've reached a special time in the life of the
294306
// child. The child must now be considered hamstrung and unable to
295307
// do anything other than syscalls really. Consider the following
@@ -320,7 +332,7 @@ impl Command {
320332
// allocation). Instead we just close it manually. This will never
321333
// have the drop glue anyway because this code never returns (the
322334
// child will either exec() or invoke libc::exit)
323-
unsafe fn exec(&mut self, stdio: ChildPipes) -> io::Error {
335+
unsafe fn do_exec(&mut self, stdio: ChildPipes) -> io::Error {
324336
macro_rules! try {
325337
($e:expr) => (match $e {
326338
Ok(e) => e,

src/test/run-pass/command-exec.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// ignore-windows - this is a unix-specific test
12+
// ignore-pretty
13+
14+
#![feature(process_exec)]
15+
16+
use std::env;
17+
use std::os::unix::process::CommandExt;
18+
use std::process::Command;
19+
20+
fn main() {
21+
let mut args = env::args();
22+
let me = args.next().unwrap();
23+
24+
if let Some(arg) = args.next() {
25+
match &arg[..] {
26+
"test1" => println!("passed"),
27+
28+
"exec-test1" => {
29+
let err = Command::new(&me).arg("test1").exec();
30+
panic!("failed to spawn: {}", err);
31+
}
32+
33+
"exec-test2" => {
34+
Command::new("/path/to/nowhere").exec();
35+
println!("passed");
36+
}
37+
38+
"exec-test3" => {
39+
Command::new(&me).arg("bad\0").exec();
40+
println!("passed");
41+
}
42+
43+
"exec-test4" => {
44+
Command::new(&me).current_dir("/path/to/nowhere").exec();
45+
println!("passed");
46+
}
47+
48+
_ => panic!("unknown argument: {}", arg),
49+
}
50+
return
51+
}
52+
53+
let output = Command::new(&me).arg("exec-test1").output().unwrap();
54+
assert!(output.status.success());
55+
assert!(output.stderr.is_empty());
56+
assert_eq!(output.stdout, b"passed\n");
57+
58+
let output = Command::new(&me).arg("exec-test2").output().unwrap();
59+
assert!(output.status.success());
60+
assert!(output.stderr.is_empty());
61+
assert_eq!(output.stdout, b"passed\n");
62+
63+
let output = Command::new(&me).arg("exec-test3").output().unwrap();
64+
assert!(output.status.success());
65+
assert!(output.stderr.is_empty());
66+
assert_eq!(output.stdout, b"passed\n");
67+
68+
let output = Command::new(&me).arg("exec-test4").output().unwrap();
69+
assert!(output.status.success());
70+
assert!(output.stderr.is_empty());
71+
assert_eq!(output.stdout, b"passed\n");
72+
}

0 commit comments

Comments
 (0)