//@ 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 = 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]) }