Skip to content

Allow redirecting subprocess stdout to our stderr etc. #88561

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

Closed
wants to merge 15 commits into from
Closed
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
@@ -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: {}
60 changes: 60 additions & 0 deletions library/std/src/process.rs
Original file line number Diff line number Diff line change
@@ -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.
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
@@ -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;
@@ -150,6 +150,7 @@ pub enum Stdio {
Null,
MakePipe,
Fd(FileDesc),
StaticFd(BorrowedFd<'static>),
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -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) };
@@ -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 {
35 changes: 25 additions & 10 deletions library/std/src/sys/windows/process.rs
Original file line number Diff line number Diff line change
@@ -170,6 +170,7 @@ pub struct Command {

pub enum Stdio {
Inherit,
InheritSpecific { from_stdio_id: c::DWORD },
Null,
MakePipe,
Pipe(AnonPipe),
@@ -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;
@@ -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
////////////////////////////////////////////////////////////////////////////////
7 changes: 7 additions & 0 deletions src/ci/github-actions/ci.yml
Original file line number Diff line number Diff line change
@@ -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