|
| 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