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]: https://github.com/rust-lang/rfcs/pull/1359 cc #31398
This commit is contained in:
parent
d15db1d392
commit
b37477c03e
|
@ -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")]
|
||||
|
@ -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`
|
||||
|
|
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
|
|
@ -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");
|
||||
}
|
Loading…
Reference in New Issue