Skip to content

Commit b1898db

Browse files
committed
std: Implement CommandExt::before_exec
This is a Unix-specific function which adds the ability to register a closure to run pre-exec to configure the child process as required (note that these closures are run post-fork). cc #31398
1 parent 6c41984 commit b1898db

File tree

4 files changed

+143
-4
lines changed

4 files changed

+143
-4
lines changed

src/libstd/process.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ impl Command {
269269
self
270270
}
271271

272-
fn spawn_inner(&self, default_io: StdioImp) -> io::Result<Child> {
272+
fn spawn_inner(&mut self, default_io: StdioImp) -> io::Result<Child> {
273273
let default_io = Stdio(default_io);
274274

275275
// See comment on `setup_io` for what `_drop_later` is.
@@ -283,7 +283,7 @@ impl Command {
283283
setup_io(self.stderr.as_ref().unwrap_or(&default_io), false)
284284
);
285285

286-
match imp::Process::spawn(&self.inner, their_stdin, their_stdout,
286+
match imp::Process::spawn(&mut self.inner, their_stdin, their_stdout,
287287
their_stderr) {
288288
Err(e) => Err(e),
289289
Ok(handle) => Ok(Child {

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
1313
#![stable(feature = "rust1", since = "1.0.0")]
1414

15+
use prelude::v1::*;
16+
17+
use io;
1518
use os::unix::io::{FromRawFd, RawFd, AsRawFd, IntoRawFd};
1619
use os::unix::raw::{uid_t, gid_t};
1720
use process;
@@ -44,6 +47,34 @@ pub trait CommandExt {
4447
#[unstable(feature = "process_session_leader", reason = "recently added",
4548
issue = "27811")]
4649
fn session_leader(&mut self, on: bool) -> &mut process::Command;
50+
51+
/// Schedules a closure to be run just before the `exec` function is
52+
/// invoked.
53+
///
54+
/// The closure is allowed to return an I/O error whose OS error code will
55+
/// be communicated back to the parent and returned as an error from when
56+
/// the spawn was requested.
57+
///
58+
/// Multiple closures can be registered and they will be called in order of
59+
/// their registration. If a closure returns `Err` then no further closures
60+
/// will be called and the spawn operation will immediately return with a
61+
/// failure.
62+
///
63+
/// # Notes
64+
///
65+
/// This closure will be run in the context of the child process after a
66+
/// `fork`. This primarily means that any modificatons made to memory on
67+
/// behalf of this closure will **not** be visible to the parent process.
68+
/// This is often a very constrained environment where normal operations
69+
/// like `malloc` or acquiring a mutex are not guaranteed to work (due to
70+
/// other threads perhaps still running when the `fork` was run).
71+
///
72+
/// When this closure is run, aspects such as the stdio file descriptors and
73+
/// working directory have successfully been changed, so output to these
74+
/// locations may not appear where intended.
75+
#[unstable(feature = "process_exec", issue = "31398")]
76+
fn before_exec<F>(&mut self, f: F) -> &mut process::Command
77+
where F: FnMut() -> io::Result<()> + Send + Sync + 'static;
4778
}
4879

4980
#[stable(feature = "rust1", since = "1.0.0")]
@@ -62,6 +93,13 @@ impl CommandExt for process::Command {
6293
self.as_inner_mut().session_leader(on);
6394
self
6495
}
96+
97+
fn before_exec<F>(&mut self, f: F) -> &mut process::Command
98+
where F: FnMut() -> io::Result<()> + Send + Sync + 'static
99+
{
100+
self.as_inner_mut().before_exec(Box::new(f));
101+
self
102+
}
65103
}
66104

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

src/libstd/sys/unix/process.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ pub struct Command {
5858
gid: Option<gid_t>,
5959
session_leader: bool,
6060
saw_nul: bool,
61+
closures: Vec<Box<FnMut() -> io::Result<()> + Send + Sync>>,
6162
}
6263

6364
impl Command {
@@ -75,6 +76,7 @@ impl Command {
7576
gid: None,
7677
session_leader: false,
7778
saw_nul: saw_nul,
79+
closures: Vec::new(),
7880
}
7981
}
8082

@@ -164,6 +166,11 @@ impl Command {
164166
pub fn session_leader(&mut self, session_leader: bool) {
165167
self.session_leader = session_leader;
166168
}
169+
170+
pub fn before_exec(&mut self,
171+
f: Box<FnMut() -> io::Result<()> + Send + Sync>) {
172+
self.closures.push(f);
173+
}
167174
}
168175

169176
fn os2c(s: &OsStr, saw_nul: &mut bool) -> CString {
@@ -283,7 +290,7 @@ impl Process {
283290
Ok(())
284291
}
285292

286-
pub fn spawn(cfg: &Command,
293+
pub fn spawn(cfg: &mut Command,
287294
in_fd: Stdio,
288295
out_fd: Stdio,
289296
err_fd: Stdio) -> io::Result<Process> {
@@ -387,7 +394,7 @@ impl Process {
387394
// allocation). Instead we just close it manually. This will never
388395
// have the drop glue anyway because this code never returns (the
389396
// child will either exec() or invoke libc::exit)
390-
unsafe fn exec(cfg: &Command,
397+
unsafe fn exec(cfg: &mut Command,
391398
in_fd: Stdio,
392399
out_fd: Stdio,
393400
err_fd: Stdio) -> io::Error {
@@ -497,6 +504,10 @@ impl Process {
497504
}
498505
}
499506

507+
for callback in cfg.closures.iter_mut() {
508+
try!(callback());
509+
}
510+
500511
libc::execvp(cfg.argv[0], cfg.argv.as_ptr());
501512
io::Error::last_os_error()
502513
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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+
13+
#![feature(process_exec, libc)]
14+
15+
extern crate libc;
16+
17+
use std::env;
18+
use std::io::Error;
19+
use std::os::unix::process::CommandExt;
20+
use std::process::Command;
21+
use std::sync::Arc;
22+
use std::sync::atomic::{AtomicUsize, Ordering};
23+
24+
fn main() {
25+
if let Some(arg) = env::args().skip(1).next() {
26+
match &arg[..] {
27+
"test1" => println!("hello2"),
28+
"test2" => assert_eq!(env::var("FOO").unwrap(), "BAR"),
29+
"test3" => assert_eq!(env::current_dir().unwrap()
30+
.to_str().unwrap(), "/"),
31+
"empty" => {}
32+
_ => panic!("unknown argument: {}", arg),
33+
}
34+
return
35+
}
36+
37+
let me = env::current_exe().unwrap();
38+
39+
let output = Command::new(&me).arg("test1").before_exec(|| {
40+
println!("hello");
41+
Ok(())
42+
}).output().unwrap();
43+
assert!(output.status.success());
44+
assert!(output.stderr.is_empty());
45+
assert_eq!(output.stdout, b"hello\nhello2\n");
46+
47+
let output = Command::new(&me).arg("test2").before_exec(|| {
48+
env::set_var("FOO", "BAR");
49+
Ok(())
50+
}).output().unwrap();
51+
assert!(output.status.success());
52+
assert!(output.stderr.is_empty());
53+
assert!(output.stdout.is_empty());
54+
55+
let output = Command::new(&me).arg("test3").before_exec(|| {
56+
env::set_current_dir("/").unwrap();
57+
Ok(())
58+
}).output().unwrap();
59+
assert!(output.status.success());
60+
assert!(output.stderr.is_empty());
61+
assert!(output.stdout.is_empty());
62+
63+
let output = Command::new(&me).arg("bad").before_exec(|| {
64+
Err(Error::from_raw_os_error(102))
65+
}).output().err().unwrap();
66+
assert_eq!(output.raw_os_error(), Some(102));
67+
68+
let pid = unsafe { libc::getpid() };
69+
assert!(pid >= 0);
70+
let output = Command::new(&me).arg("empty").before_exec(move || {
71+
let child = unsafe { libc::getpid() };
72+
assert!(child >= 0);
73+
assert!(pid != child);
74+
Ok(())
75+
}).output().unwrap();
76+
assert!(output.status.success());
77+
assert!(output.stderr.is_empty());
78+
assert!(output.stdout.is_empty());
79+
80+
let mem = Arc::new(AtomicUsize::new(0));
81+
let mem2 = mem.clone();
82+
let output = Command::new(&me).arg("empty").before_exec(move || {
83+
assert_eq!(mem2.fetch_add(1, Ordering::SeqCst), 0);
84+
Ok(())
85+
}).output().unwrap();
86+
assert!(output.status.success());
87+
assert!(output.stderr.is_empty());
88+
assert!(output.stdout.is_empty());
89+
assert_eq!(mem.load(Ordering::SeqCst), 0);
90+
}

0 commit comments

Comments
 (0)