103 lines
3.6 KiB
Rust
103 lines
3.6 KiB
Rust
//@ run-pass
|
|
//@ ignore-emscripten no processes
|
|
//@ ignore-sgx no processes
|
|
//@ ignore-fuchsia Child I/O swaps not privileged
|
|
|
|
// Previously libstd would set stdio descriptors of a child process
|
|
// by `dup`ing the requested descriptors to inherit directly into the
|
|
// stdio descriptors. This, however, would incorrectly handle cases
|
|
// where the descriptors to inherit were already stdio descriptors.
|
|
// This test checks to avoid that regression.
|
|
|
|
#![cfg_attr(unix, feature(rustc_private))]
|
|
#![cfg_attr(not(unix), allow(unused_imports))]
|
|
|
|
#[cfg(unix)]
|
|
extern crate libc;
|
|
|
|
use std::fs::File;
|
|
use std::io::{Read, Write};
|
|
use std::io::{stdout, stderr};
|
|
use std::process::{Command, Stdio};
|
|
|
|
#[cfg(unix)]
|
|
use std::os::unix::io::FromRawFd;
|
|
|
|
#[cfg(not(unix))]
|
|
fn main() {
|
|
// Bug not present in Windows
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn main() {
|
|
let mut args = std::env::args();
|
|
let name = args.next().unwrap();
|
|
let args: Vec<String> = args.collect();
|
|
if let Some("--child") = args.get(0).map(|s| &**s) {
|
|
return child();
|
|
} else if !args.is_empty() {
|
|
panic!("unknown options");
|
|
}
|
|
|
|
let stdout_backup = unsafe { libc::dup(libc::STDOUT_FILENO) };
|
|
let stderr_backup = unsafe { libc::dup(libc::STDERR_FILENO) };
|
|
assert!(stdout_backup > -1);
|
|
assert!(stderr_backup > -1);
|
|
|
|
let (stdout_reader, stdout_writer) = pipe();
|
|
let (stderr_reader, stderr_writer) = pipe();
|
|
assert!(unsafe { libc::dup2(stdout_writer, libc::STDOUT_FILENO) } > -1);
|
|
assert!(unsafe { libc::dup2(stderr_writer, libc::STDERR_FILENO) } > -1);
|
|
|
|
// Make sure we close any duplicates of the writer end of the pipe,
|
|
// otherwise we can get stuck reading from the pipe which has open
|
|
// writers but no one supplying any input
|
|
assert_eq!(unsafe { libc::close(stdout_writer) }, 0);
|
|
assert_eq!(unsafe { libc::close(stderr_writer) }, 0);
|
|
|
|
stdout().write_all("parent stdout\n".as_bytes()).expect("failed to write to stdout");
|
|
stderr().write_all("parent stderr\n".as_bytes()).expect("failed to write to stderr");
|
|
|
|
let child = {
|
|
Command::new(name)
|
|
.arg("--child")
|
|
.stdin(Stdio::inherit())
|
|
.stdout(unsafe { Stdio::from_raw_fd(libc::STDERR_FILENO) })
|
|
.stderr(unsafe { Stdio::from_raw_fd(libc::STDOUT_FILENO) })
|
|
.spawn()
|
|
};
|
|
|
|
// The Stdio passed into the Command took over (and closed) std{out, err}
|
|
// so we should restore them as they were.
|
|
assert!(unsafe { libc::dup2(stdout_backup, libc::STDOUT_FILENO) } > -1);
|
|
assert!(unsafe { libc::dup2(stderr_backup, libc::STDERR_FILENO) } > -1);
|
|
|
|
// Using File as a shim around the descriptor
|
|
let mut read = String::new();
|
|
let mut f: File = unsafe { FromRawFd::from_raw_fd(stdout_reader) };
|
|
f.read_to_string(&mut read).expect("failed to read from stdout file");
|
|
assert_eq!(read, "parent stdout\nchild stderr\n");
|
|
|
|
// Using File as a shim around the descriptor
|
|
read.clear();
|
|
let mut f: File = unsafe { FromRawFd::from_raw_fd(stderr_reader) };
|
|
f.read_to_string(&mut read).expect("failed to read from stderr file");
|
|
assert_eq!(read, "parent stderr\nchild stdout\n");
|
|
|
|
assert!(child.expect("failed to execute child process").wait().unwrap().success());
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn child() {
|
|
stdout().write_all("child stdout\n".as_bytes()).expect("child failed to write to stdout");
|
|
stderr().write_all("child stderr\n".as_bytes()).expect("child failed to write to stderr");
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
/// Returns a pipe (reader, writer combo)
|
|
fn pipe() -> (i32, i32) {
|
|
let mut fds = [0; 2];
|
|
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0);
|
|
(fds[0], fds[1])
|
|
}
|