Skip to content

RFC: Use pipe2 and O_CLOEXEC to check if exec succeeds after fork #8126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/libstd/libc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2770,6 +2770,7 @@ pub mod funcs {
pub unsafe fn pathconf(path: *c_char, name: c_int) -> c_long;
pub unsafe fn pause() -> c_int;
pub unsafe fn pipe(fds: *mut c_int) -> c_int;
pub unsafe fn pipe2(fds: *mut c_int, flags: c_int) -> c_int;
#[fast_ffi]
pub unsafe fn read(fd: c_int, buf: *mut c_void, count: size_t)
-> ssize_t;
Expand Down
45 changes: 44 additions & 1 deletion src/libstd/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,9 @@ fn spawn_process_os(prog: &str, args: &[~str],

use libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp};
use libc::funcs::bsd44::getdtablesize;
use libc::funcs::c95::string::strerror;
use int;
use str::raw::from_c_str;

mod rustrt {
use libc::c_void;
Expand All @@ -649,11 +651,44 @@ fn spawn_process_os(prog: &str, args: &[~str],
}

unsafe {
// TODO: Only use functions defined in libc.rs (if possible?). This
// means that there's no pipe2 function. The function fcntl() is
// defined in POSIX, but Rust does not provide a way to *set* value
// using fcntl(). At the moment it is only possible to get a value.

// See also https://lkml.org/lkml/2006/7/10/300 for more info about
// the use of O_CLOEXEC combined with exec().

let O_CLOEXEC = 524288;
let mut fds = os::Pipe {in: 0 as c_int,
out: 0 as c_int };
let result = libc::pipe2(&mut fds.in, O_CLOEXEC) as int;
assert_eq!(result, 0);

let pid = fork();
if pid < 0 {
fail!("failure in fork: %s", os::last_os_error());
} else if pid > 0 {
close(fds.out);

// Read the errno value from the child, if the exec failed, or get
// 0 if the exec succeeded because the pipe fd was set as
// close-on-exec.
let reader = io::FILE_reader(os::fdopen(fds.in), false);

let mut buf = [255 as u8];
let n = reader.read(buf, 1);

if !reader.eof() {
if n == 0 {
fail!("failure in read: %s", os::last_os_error());
} else if buf[0] != 0 {
// exec failed
let msg = from_c_str(strerror(buf[0] as i32));
fail!("failure in execvp: errno=%s", msg);
}
}

return SpawnProcessResult {pid: pid, handle: ptr::null()};
}

Expand All @@ -670,7 +705,11 @@ fn spawn_process_os(prog: &str, args: &[~str],
}
// close all other fds
for int::range_rev(getdtablesize() as int, 3) |fd| {
close(fd as c_int);
// Do not close the close-on-exec fd otherwise there's no way of
// knowing if the exec failed or succeeded.
if fd != (fds.out as int) {
close(fd as c_int);
}
}

do with_dirp(dir) |dirp| {
Expand All @@ -686,7 +725,11 @@ fn spawn_process_os(prog: &str, args: &[~str],
do with_argv(prog, args) |argv| {
execvp(*argv, argv);
// execvp only returns if an error occurred
let errno = os::errno();
let writer = io::fd_writer(fds.out, false); // false?
writer.write_u8(errno as u8);
fail!("failure in execvp: %s", os::last_os_error());

}
}
}
Expand Down