Skip to content

Commit a6a1c90

Browse files
committed
auto merge of #14477 : alexcrichton/rust/issue-14456, r=brson
When spawning a process, stdio file descriptors can be configured to be ignored, which basically means that they'll be closed. Currently this is done by literally closing the file descriptors in the child, but this can have adverse side effects if the child process then opens a new file descriptor, assigning it to a stdio number. To work around the problems of the child, this commit alters the process spawning code to map stdio fds to /dev/null on unix (and a similar equivalent on windows) when they are specified as being ignored. This should allow spawned programs to have more expected behavior when opening new files. Closes #14456
2 parents 1489374 + 034eb4e commit a6a1c90

File tree

3 files changed

+140
-54
lines changed

3 files changed

+140
-54
lines changed

src/liblibc/lib.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ pub use funcs::bsd43::{shutdown};
235235
#[cfg(windows)] pub use types::os::arch::extra::{LARGE_INTEGER, LPVOID, LONG};
236236
#[cfg(windows)] pub use types::os::arch::extra::{time64_t, OVERLAPPED, LPCWSTR};
237237
#[cfg(windows)] pub use types::os::arch::extra::{LPOVERLAPPED, SIZE_T, LPDWORD};
238+
#[cfg(windows)] pub use types::os::arch::extra::{SECURITY_ATTRIBUTES};
238239
#[cfg(windows)] pub use funcs::c95::string::{wcslen};
239240
#[cfg(windows)] pub use funcs::posix88::stat_::{wstat, wutime, wchmod, wrmdir};
240241
#[cfg(windows)] pub use funcs::bsd43::{closesocket};
@@ -1140,8 +1141,12 @@ pub mod types {
11401141
pub type LPWCH = *mut WCHAR;
11411142
pub type LPCH = *mut CHAR;
11421143

1143-
// Not really, but opaque to us.
1144-
pub type LPSECURITY_ATTRIBUTES = LPVOID;
1144+
pub struct SECURITY_ATTRIBUTES {
1145+
pub nLength: DWORD,
1146+
pub lpSecurityDescriptor: LPVOID,
1147+
pub bInheritHandle: BOOL,
1148+
}
1149+
pub type LPSECURITY_ATTRIBUTES = *mut SECURITY_ATTRIBUTES;
11451150

11461151
pub type LPVOID = *mut c_void;
11471152
pub type LPCVOID = *c_void;

src/libnative/io/process.rs

+78-52
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,8 @@ struct SpawnProcessResult {
244244
}
245245

246246
#[cfg(windows)]
247-
fn spawn_process_os(cfg: ProcessConfig, in_fd: c_int, out_fd: c_int, err_fd: c_int)
247+
fn spawn_process_os(cfg: ProcessConfig,
248+
in_fd: c_int, out_fd: c_int, err_fd: c_int)
248249
-> IoResult<SpawnProcessResult> {
249250
use libc::types::os::arch::extra::{DWORD, HANDLE, STARTUPINFO};
250251
use libc::consts::os::extra::{
@@ -278,38 +279,51 @@ fn spawn_process_os(cfg: ProcessConfig, in_fd: c_int, out_fd: c_int, err_fd: c_i
278279

279280
let cur_proc = GetCurrentProcess();
280281

281-
if in_fd != -1 {
282-
let orig_std_in = get_osfhandle(in_fd) as HANDLE;
283-
if orig_std_in == INVALID_HANDLE_VALUE as HANDLE {
284-
fail!("failure in get_osfhandle: {}", os::last_os_error());
285-
}
286-
if DuplicateHandle(cur_proc, orig_std_in, cur_proc, &mut si.hStdInput,
287-
0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE {
288-
fail!("failure in DuplicateHandle: {}", os::last_os_error());
289-
}
290-
}
291-
292-
if out_fd != -1 {
293-
let orig_std_out = get_osfhandle(out_fd) as HANDLE;
294-
if orig_std_out == INVALID_HANDLE_VALUE as HANDLE {
295-
fail!("failure in get_osfhandle: {}", os::last_os_error());
296-
}
297-
if DuplicateHandle(cur_proc, orig_std_out, cur_proc, &mut si.hStdOutput,
298-
0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE {
299-
fail!("failure in DuplicateHandle: {}", os::last_os_error());
282+
// Similarly to unix, we don't actually leave holes for the stdio file
283+
// descriptors, but rather open up /dev/null equivalents. These
284+
// equivalents are drawn from libuv's windows process spawning.
285+
let set_fd = |fd: c_int, slot: &mut HANDLE, is_stdin: bool| {
286+
if fd == -1 {
287+
let access = if is_stdin {
288+
libc::FILE_GENERIC_READ
289+
} else {
290+
libc::FILE_GENERIC_WRITE | libc::FILE_READ_ATTRIBUTES
291+
};
292+
let size = mem::size_of::<libc::SECURITY_ATTRIBUTES>();
293+
let mut sa = libc::SECURITY_ATTRIBUTES {
294+
nLength: size as libc::DWORD,
295+
lpSecurityDescriptor: ptr::mut_null(),
296+
bInheritHandle: 1,
297+
};
298+
*slot = os::win32::as_utf16_p("NUL", |filename| {
299+
libc::CreateFileW(filename,
300+
access,
301+
libc::FILE_SHARE_READ |
302+
libc::FILE_SHARE_WRITE,
303+
&mut sa,
304+
libc::OPEN_EXISTING,
305+
0,
306+
ptr::mut_null())
307+
});
308+
if *slot == INVALID_HANDLE_VALUE as libc::HANDLE {
309+
return Err(super::last_error())
310+
}
311+
} else {
312+
let orig = get_osfhandle(fd) as HANDLE;
313+
if orig == INVALID_HANDLE_VALUE as HANDLE {
314+
return Err(super::last_error())
315+
}
316+
if DuplicateHandle(cur_proc, orig, cur_proc, slot,
317+
0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE {
318+
return Err(super::last_error())
319+
}
300320
}
301-
}
321+
Ok(())
322+
};
302323

303-
if err_fd != -1 {
304-
let orig_std_err = get_osfhandle(err_fd) as HANDLE;
305-
if orig_std_err == INVALID_HANDLE_VALUE as HANDLE {
306-
fail!("failure in get_osfhandle: {}", os::last_os_error());
307-
}
308-
if DuplicateHandle(cur_proc, orig_std_err, cur_proc, &mut si.hStdError,
309-
0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE {
310-
fail!("failure in DuplicateHandle: {}", os::last_os_error());
311-
}
312-
}
324+
try!(set_fd(in_fd, &mut si.hStdInput, true));
325+
try!(set_fd(out_fd, &mut si.hStdOutput, false));
326+
try!(set_fd(err_fd, &mut si.hStdError, false));
313327

314328
let cmd_str = make_command_line(cfg.program, cfg.args);
315329
let mut pi = zeroed_process_information();
@@ -338,9 +352,9 @@ fn spawn_process_os(cfg: ProcessConfig, in_fd: c_int, out_fd: c_int, err_fd: c_i
338352
})
339353
});
340354

341-
if in_fd != -1 { assert!(CloseHandle(si.hStdInput) != 0); }
342-
if out_fd != -1 { assert!(CloseHandle(si.hStdOutput) != 0); }
343-
if err_fd != -1 { assert!(CloseHandle(si.hStdError) != 0); }
355+
assert!(CloseHandle(si.hStdInput) != 0);
356+
assert!(CloseHandle(si.hStdOutput) != 0);
357+
assert!(CloseHandle(si.hStdError) != 0);
344358

345359
match create_err {
346360
Some(err) => return Err(err),
@@ -379,9 +393,9 @@ fn zeroed_startupinfo() -> libc::types::os::arch::extra::STARTUPINFO {
379393
wShowWindow: 0,
380394
cbReserved2: 0,
381395
lpReserved2: ptr::mut_null(),
382-
hStdInput: ptr::mut_null(),
383-
hStdOutput: ptr::mut_null(),
384-
hStdError: ptr::mut_null()
396+
hStdInput: libc::INVALID_HANDLE_VALUE as libc::HANDLE,
397+
hStdOutput: libc::INVALID_HANDLE_VALUE as libc::HANDLE,
398+
hStdError: libc::INVALID_HANDLE_VALUE as libc::HANDLE,
385399
}
386400
}
387401

@@ -489,6 +503,10 @@ fn spawn_process_os(cfg: ProcessConfig, in_fd: c_int, out_fd: c_int, err_fd: c_i
489503
let mut input = file::FileDesc::new(pipe.input, true);
490504
let mut output = file::FileDesc::new(pipe.out, true);
491505

506+
// We may use this in the child, so perform allocations before the
507+
// fork
508+
let devnull = "/dev/null".to_c_str();
509+
492510
set_cloexec(output.fd());
493511

494512
let pid = fork();
@@ -563,21 +581,29 @@ fn spawn_process_os(cfg: ProcessConfig, in_fd: c_int, out_fd: c_int, err_fd: c_i
563581

564582
rustrt::rust_unset_sigprocmask();
565583

566-
if in_fd == -1 {
567-
let _ = libc::close(libc::STDIN_FILENO);
568-
} else if retry(|| dup2(in_fd, 0)) == -1 {
569-
fail(&mut output);
570-
}
571-
if out_fd == -1 {
572-
let _ = libc::close(libc::STDOUT_FILENO);
573-
} else if retry(|| dup2(out_fd, 1)) == -1 {
574-
fail(&mut output);
575-
}
576-
if err_fd == -1 {
577-
let _ = libc::close(libc::STDERR_FILENO);
578-
} else if retry(|| dup2(err_fd, 2)) == -1 {
579-
fail(&mut output);
580-
}
584+
// If a stdio file descriptor is set to be ignored (via a -1 file
585+
// descriptor), then we don't actually close it, but rather open
586+
// up /dev/null into that file descriptor. Otherwise, the first file
587+
// descriptor opened up in the child would be numbered as one of the
588+
// stdio file descriptors, which is likely to wreak havoc.
589+
let setup = |src: c_int, dst: c_int| {
590+
let src = if src == -1 {
591+
let flags = if dst == libc::STDIN_FILENO {
592+
libc::O_RDONLY
593+
} else {
594+
libc::O_RDWR
595+
};
596+
devnull.with_ref(|p| libc::open(p, flags, 0))
597+
} else {
598+
src
599+
};
600+
src != -1 && retry(|| dup2(src, dst)) != -1
601+
};
602+
603+
if !setup(in_fd, libc::STDIN_FILENO) { fail(&mut output) }
604+
if !setup(out_fd, libc::STDOUT_FILENO) { fail(&mut output) }
605+
if !setup(err_fd, libc::STDERR_FILENO) { fail(&mut output) }
606+
581607
// close all other fds
582608
for fd in range(3, getdtablesize()).rev() {
583609
if fd != output.fd() {

src/test/run-pass/issue-14456.rs

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2014 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+
#![feature(phase)]
12+
13+
#[phase(syntax, link)]
14+
extern crate green;
15+
extern crate native;
16+
17+
use std::io::process;
18+
use std::io::Command;
19+
use std::io;
20+
use std::os;
21+
22+
green_start!(main)
23+
24+
fn main() {
25+
let args = os::args();
26+
if args.len() > 1 && args.get(1).as_slice() == "child" {
27+
return child()
28+
}
29+
30+
test();
31+
32+
let (tx, rx) = channel();
33+
native::task::spawn(proc() {
34+
tx.send(test());
35+
});
36+
rx.recv();
37+
38+
}
39+
40+
fn child() {
41+
io::stdout().write_line("foo").unwrap();
42+
io::stderr().write_line("bar").unwrap();
43+
assert_eq!(io::stdin().read_line().err().unwrap().kind, io::EndOfFile);
44+
}
45+
46+
fn test() {
47+
let args = os::args();
48+
let mut p = Command::new(args.get(0).as_slice()).arg("child")
49+
.stdin(process::Ignored)
50+
.stdout(process::Ignored)
51+
.stderr(process::Ignored)
52+
.spawn().unwrap();
53+
assert!(p.wait().unwrap().success());
54+
}
55+

0 commit comments

Comments
 (0)