Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow redirecting subprocess stdout to our stderr etc. #88561

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ jobs:
- name: x86_64-gnu-llvm-14
os: ubuntu-20.04-16core-64gb
env: {}
- name: x86_64-msvc-1
env:
RUST_CONFIGURE_ARGS: "--build=x86_64-pc-windows-msvc --enable-profiler"
SCRIPT: make ci-msvc
tidy: false
os: windows-2019-8core-32gb
- name: x86_64-gnu-tools
os: ubuntu-20.04-16core-64gb
env: {}
Expand Down
60 changes: 60 additions & 0 deletions library/std/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1491,6 +1491,66 @@ impl From<fs::File> for Stdio {
}
}

#[stable(feature = "stdio_from_stdio", since = "CURRENT_RUSTC_VERSION")]
impl From<io::Stdout> for Stdio {
/// Redirect command stdout/stderr to our stdout
///
/// # Examples
///
/// ```rust
/// #![feature(exit_status_error)]
/// use std::io;
/// use std::process::Command;
///
/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
/// let output = Command::new("whoami")
// "whoami" is a command which exists on both Unix and Windows,
// and which succeeds, producing some stdout output but no stderr.
/// .stdout(io::stdout())
/// .output()?;
/// output.status.exit_ok()?;
/// assert!(output.stdout.is_empty());
/// # Ok(())
/// # }
/// #
/// # if cfg!(unix) {
/// # test().unwrap();
/// # }
/// ```
fn from(inherit: io::Stdout) -> Stdio {
Stdio::from_inner(inherit.into())
}
}

#[stable(feature = "stdio_from_stdio", since = "CURRENT_RUSTC_VERSION")]
impl From<io::Stderr> for Stdio {
/// Redirect command stdout/stderr to our stderr
///
/// # Examples
///
/// ```rust
/// #![feature(exit_status_error)]
/// use std::io;
/// use std::process::Command;
///
/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
/// let output = Command::new("whoami")
/// .stdout(io::stderr())
/// .output()?;
/// output.status.exit_ok()?;
/// assert!(output.stdout.is_empty());
/// # Ok(())
/// # }
/// #
/// # if cfg!(unix) {
/// # test().unwrap();
/// # }
/// ```
fn from(inherit: io::Stderr) -> Stdio {
Stdio::from_inner(inherit.into())
}
}

/// Describes the result of a process after it has terminated.
///
/// This `struct` is used to represent the exit status or other termination of a child process.
Expand Down
30 changes: 29 additions & 1 deletion library/std/src/sys/unix/process/process_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::sys::fd::FileDesc;
use crate::sys::fs::File;
use crate::sys::pipe::{self, AnonPipe};
use crate::sys_common::process::{CommandEnv, CommandEnvs};
use crate::sys_common::IntoInner;
use crate::sys_common::{FromInner, IntoInner};

#[cfg(not(target_os = "fuchsia"))]
use crate::sys::fs::OpenOptions;
Expand Down Expand Up @@ -150,6 +150,7 @@ pub enum Stdio {
Null,
MakePipe,
Fd(FileDesc),
StaticFd(BorrowedFd<'static>),
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -463,6 +464,11 @@ impl Stdio {
}
}

Stdio::StaticFd(fd) => {
let fd = FileDesc::from_inner(fd.try_clone_to_owned()?);
Ok((ChildStdio::Owned(fd), None))
}

Stdio::MakePipe => {
let (reader, writer) = pipe::anon_pipe()?;
let (ours, theirs) = if readable { (writer, reader) } else { (reader, writer) };
Expand Down Expand Up @@ -497,6 +503,28 @@ impl From<File> for Stdio {
}
}

impl From<io::Stdout> for Stdio {
fn from(_: io::Stdout) -> Stdio {
// This ought really to be is Stdio::StaticFd(input_argument.as_fd()).
// But AsFd::as_fd takes its argument by reference, and yields
// a bounded lifetime, so it's no use here. There is no AsStaticFd.
//
// Additionally AsFd is only implemented for the *locked* versions.
// We don't want to lock them here. (The implications of not locking
// are the same as those for process::Stdio::inherit().)
//
// Arguably the hypothetical AsStaticFd and AsFd<'static>
// should be implemented for io::Stdout, not just for StdoutLocked.
Stdio::StaticFd(unsafe { BorrowedFd::borrow_raw(libc::STDOUT_FILENO) })
}
}

impl From<io::Stderr> for Stdio {
fn from(_: io::Stderr) -> Stdio {
Stdio::StaticFd(unsafe { BorrowedFd::borrow_raw(libc::STDERR_FILENO) })
}
}

impl ChildStdio {
pub fn fd(&self) -> Option<c_int> {
match *self {
Expand Down
35 changes: 25 additions & 10 deletions library/std/src/sys/windows/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ pub struct Command {

pub enum Stdio {
Inherit,
InheritSpecific { from_stdio_id: c::DWORD },
Null,
MakePipe,
Pipe(AnonPipe),
Expand Down Expand Up @@ -521,17 +522,19 @@ fn program_exists(path: &Path) -> Option<Vec<u16>> {

impl Stdio {
fn to_handle(&self, stdio_id: c::DWORD, pipe: &mut Option<AnonPipe>) -> io::Result<Handle> {
match *self {
Stdio::Inherit => match stdio::get_handle(stdio_id) {
Ok(io) => unsafe {
let io = Handle::from_raw_handle(io);
let ret = io.duplicate(0, true, c::DUPLICATE_SAME_ACCESS);
io.into_raw_handle();
ret
},
// If no stdio handle is available, then propagate the null value.
Err(..) => unsafe { Ok(Handle::from_raw_handle(ptr::null_mut())) },
let use_stdio_id = |stdio_id| match stdio::get_handle(stdio_id) {
Ok(io) => unsafe {
let io = Handle::from_raw_handle(io);
let ret = io.duplicate(0, true, c::DUPLICATE_SAME_ACCESS);
io.into_raw_handle();
ret
},
// If no stdio handle is available, then propagate the null value.
Err(..) => unsafe { Ok(Handle::from_raw_handle(ptr::null_mut())) },
};
match *self {
Stdio::Inherit => use_stdio_id(stdio_id),
Stdio::InheritSpecific { from_stdio_id } => use_stdio_id(from_stdio_id),

Stdio::MakePipe => {
let ours_readable = stdio_id != c::STD_INPUT_HANDLE;
Expand Down Expand Up @@ -579,6 +582,18 @@ impl From<File> for Stdio {
}
}

impl From<io::Stdout> for Stdio {
fn from(_: io::Stdout) -> Stdio {
Stdio::InheritSpecific { from_stdio_id: c::STD_OUTPUT_HANDLE }
}
}

impl From<io::Stderr> for Stdio {
fn from(_: io::Stderr) -> Stdio {
Stdio::InheritSpecific { from_stdio_id: c::STD_ERROR_HANDLE }
}
}

////////////////////////////////////////////////////////////////////////////////
// Processes
////////////////////////////////////////////////////////////////////////////////
Expand Down
7 changes: 7 additions & 0 deletions src/ci/github-actions/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,13 @@ jobs:
- name: x86_64-gnu-llvm-14
<<: *job-linux-16c

- name: x86_64-msvc-1
env:
RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-profiler
SCRIPT: make ci-msvc
<<: *job-windows-8c
tidy: false

- name: x86_64-gnu-tools
<<: *job-linux-16c

Expand Down