Skip to content

Commit 7f7a059

Browse files
committed
libstd: unix process spawning: fix bug with setting stdio
* If the requested descriptors to inherit are stdio descriptors there are situations where they will not be set correctly * Example: parent's stdout --> child's stderr parent's stderr --> child's stdout * Solution: if the requested descriptors for the child are stdio descriptors, `dup` them before overwriting the child's stdio
1 parent 439e184 commit 7f7a059

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed

src/libstd/sys/unix/process.rs

+32
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,32 @@ impl Process {
288288
unsafe { libc::_exit(1) }
289289
}
290290

291+
// Make sure that the source descriptors are not an stdio descriptor,
292+
// otherwise the order which we set the child's descriptors may blow
293+
// away a descriptor which we are hoping to save. For example,
294+
// suppose we want the child's stderr to be the parent's stdout, and
295+
// the child's stdout to be the parent's stderr. No matter which we
296+
// dup first, the second will get overwritten prematurely.
297+
let maybe_migrate = |src: Stdio, output: &mut AnonPipe| {
298+
match src {
299+
Stdio::Raw(fd @ libc::STDIN_FILENO) |
300+
Stdio::Raw(fd @ libc::STDOUT_FILENO) |
301+
Stdio::Raw(fd @ libc::STDERR_FILENO) => {
302+
let fd = match cvt_r(|| libc::dup(fd)) {
303+
Ok(fd) => fd,
304+
Err(_) => fail(output),
305+
};
306+
let fd = FileDesc::new(fd);
307+
fd.set_cloexec();
308+
Stdio::Raw(fd.into_raw())
309+
},
310+
311+
s @ Stdio::None |
312+
s @ Stdio::Inherit |
313+
s @ Stdio::Raw(_) => s,
314+
}
315+
};
316+
291317
let setup = |src: Stdio, dst: c_int| {
292318
match src {
293319
Stdio::Inherit => true,
@@ -313,6 +339,12 @@ impl Process {
313339
}
314340
};
315341

342+
// Make sure we migrate all source descriptors before
343+
// we start overwriting them
344+
let in_fd = maybe_migrate(in_fd, &mut output);
345+
let out_fd = maybe_migrate(out_fd, &mut output);
346+
let err_fd = maybe_migrate(err_fd, &mut output);
347+
316348
if !setup(in_fd, libc::STDIN_FILENO) { fail(&mut output) }
317349
if !setup(out_fd, libc::STDOUT_FILENO) { fail(&mut output) }
318350
if !setup(err_fd, libc::STDERR_FILENO) { fail(&mut output) }

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

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright 2015 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+
// Previously libstd would set stdio descriptors of a child process
12+
// by `dup`ing the requested descriptors to inherit directly into the
13+
// stdio descriptors. This, however, would incorrectly handle cases
14+
// where the descriptors to inherit were already stdio descriptors.
15+
// This test checks to avoid that regression.
16+
17+
#![cfg_attr(unix, feature(libc))]
18+
#![cfg_attr(windows, allow(unused_imports))]
19+
20+
#[cfg(unix)]
21+
extern crate libc;
22+
23+
use std::fs::File;
24+
use std::io::{Read, Write};
25+
use std::io::{stdout, stderr};
26+
use std::process::{Command, Stdio};
27+
28+
#[cfg(unix)]
29+
use std::os::unix::io::FromRawFd;
30+
31+
#[cfg(not(unix))]
32+
fn main() {
33+
// Bug not present in Windows
34+
}
35+
36+
#[cfg(unix)]
37+
fn main() {
38+
let mut args = std::env::args();
39+
let name = args.next().unwrap();
40+
let args: Vec<String> = args.collect();
41+
if let Some("--child") = args.get(0).map(|s| &**s) {
42+
return child();
43+
} else if !args.is_empty() {
44+
panic!("unknown options");
45+
}
46+
47+
let stdout_backup = unsafe { libc::dup(libc::STDOUT_FILENO) };
48+
let stderr_backup = unsafe { libc::dup(libc::STDERR_FILENO) };
49+
assert!(stdout_backup > -1);
50+
assert!(stderr_backup > -1);
51+
52+
let (stdout_reader, stdout_writer) = pipe();
53+
let (stderr_reader, stderr_writer) = pipe();
54+
assert!(unsafe { libc::dup2(stdout_writer, libc::STDOUT_FILENO) } > -1);
55+
assert!(unsafe { libc::dup2(stderr_writer, libc::STDERR_FILENO) } > -1);
56+
57+
// Make sure we close any duplicates of the writer end of the pipe,
58+
// otherwise we can get stuck reading from the pipe which has open
59+
// writers but no one supplying any input
60+
assert_eq!(unsafe { libc::close(stdout_writer) }, 0);
61+
assert_eq!(unsafe { libc::close(stderr_writer) }, 0);
62+
63+
stdout().write_all("parent stdout\n".as_bytes()).expect("failed to write to stdout");
64+
stderr().write_all("parent stderr\n".as_bytes()).expect("failed to write to stderr");
65+
66+
let child = {
67+
Command::new(name)
68+
.arg("--child")
69+
.stdin(Stdio::inherit())
70+
.stdout(unsafe { FromRawFd::from_raw_fd(libc::STDERR_FILENO) })
71+
.stderr(unsafe { FromRawFd::from_raw_fd(libc::STDOUT_FILENO) })
72+
.spawn()
73+
};
74+
75+
// The Stdio passed into the Command took over (and closed) std{out, err}
76+
// so we should restore them as they were.
77+
assert!(unsafe { libc::dup2(stdout_backup, libc::STDOUT_FILENO) } > -1);
78+
assert!(unsafe { libc::dup2(stderr_backup, libc::STDERR_FILENO) } > -1);
79+
80+
// Using File as a shim around the descriptor
81+
let mut read = String::new();
82+
let mut f: File = unsafe { FromRawFd::from_raw_fd(stdout_reader) };
83+
f.read_to_string(&mut read).expect("failed to read from stdout file");
84+
assert_eq!(read, "parent stdout\nchild stderr\n");
85+
86+
// Using File as a shim around the descriptor
87+
read.clear();
88+
let mut f: File = unsafe { FromRawFd::from_raw_fd(stderr_reader) };
89+
f.read_to_string(&mut read).expect("failed to read from stderr file");
90+
assert_eq!(read, "parent stderr\nchild stdout\n");
91+
92+
assert!(child.expect("failed to execute child process").wait().unwrap().success());
93+
}
94+
95+
#[cfg(unix)]
96+
fn child() {
97+
stdout().write_all("child stdout\n".as_bytes()).expect("child failed to write to stdout");
98+
stderr().write_all("child stderr\n".as_bytes()).expect("child failed to write to stderr");
99+
}
100+
101+
#[cfg(unix)]
102+
/// Returns a pipe (reader, writer combo)
103+
fn pipe() -> (i32, i32) {
104+
let mut fds = [0; 2];
105+
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0);
106+
(fds[0], fds[1])
107+
}

0 commit comments

Comments
 (0)