diff --git a/src/libstd/sys/unix/ext/process.rs b/src/libstd/sys/unix/ext/process.rs index cf72cfd7e507e5bfbd6a813aa2e337e200a1eeb0..fa19a2620bacffcd83a0a9fd4460a099cbf54551 100644 --- a/src/libstd/sys/unix/ext/process.rs +++ b/src/libstd/sys/unix/ext/process.rs @@ -75,6 +75,28 @@ pub trait CommandExt { #[unstable(feature = "process_exec", issue = "31398")] fn before_exec(&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 @@ fn before_exec(&mut self, f: F) -> &mut 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` diff --git a/src/libstd/sys/unix/process.rs b/src/libstd/sys/unix/process.rs index 4716d25c0b28ff4ccc0503221e5456c69531ff80..60785f376423f7750d2c9cacd83d26080bb88269 100644 --- a/src/libstd/sys/unix/process.rs +++ b/src/libstd/sys/unix/process.rs @@ -230,7 +230,7 @@ pub fn spawn(&mut self, default: Stdio) 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 @@ fn combine(arr: &[u8]) -> 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) { + 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 @@ fn combine(arr: &[u8]) -> i32 { // 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, diff --git a/src/test/run-pass/command-exec.rs b/src/test/run-pass/command-exec.rs new file mode 100644 index 0000000000000000000000000000000000000000..039245bfd08ba9eceb748a625d4298901f9802fe --- /dev/null +++ b/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 or the MIT license +// , 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"); +}