reverie-process/src/stdio.rs (104 lines of code) (raw):
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
use super::fd::{pipe, AsyncFd, Fd};
use core::pin::Pin;
use core::task::{Context, Poll};
use std::io;
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
use syscalls::Errno;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
/// Describes what to do with a standard I/O stream for a child process when
/// passed to the [`stdin`], [`stdout`], and [`stderr`] methods of [`Command`].
///
/// [`stdin`]: super::Command::stdin
/// [`stdout`]: super::Command::stdout
/// [`stderr`]: super::Command::stderr
/// [`Command`]: super::Command
#[derive(Debug)]
pub struct Stdio(InnerStdio);
/// A handle to a child process's standard input (stdin).
///
/// This struct is used in the [`stdin`] field on [`Child`].
///
/// When an instance of `ChildStdin` is [dropped], the `ChildStdin`'s underlying
/// file handle will be closed. If the child process was blocked on input prior
/// to being dropped, it will become unblocked after dropping.
///
/// [`stdin`]: super::Child::stdin
/// [`Child`]: super::Child
/// [dropped]: Drop
#[derive(Debug)]
pub struct ChildStdin(AsyncFd);
/// A handle to a child process's standard output (stdout).
///
/// This struct is used in the [`stdout`] field on [`Child`].
///
/// When an instance of `ChildStdout` is [dropped], the `ChildStdout`'s
/// underlying file handle will be closed.
///
/// [`stdout`]: super::Child::stdout
/// [`Child`]: super::Child
/// [dropped]: Drop
#[derive(Debug)]
pub struct ChildStdout(AsyncFd);
/// A handle to a child process's stderr.
///
/// This struct is used in the [`stderr`] field on [`Child`].
///
/// When an instance of `ChildStderr` is [dropped], the `ChildStderr`'s
/// underlying file handle will be closed.
///
/// [`stderr`]: super::Child::stderr
/// [`Child`]: super::Child
/// [dropped]: Drop
#[derive(Debug)]
pub struct ChildStderr(AsyncFd);
#[derive(Debug)]
enum InnerStdio {
Inherit,
Null,
Piped,
File(Fd),
}
impl Default for Stdio {
fn default() -> Self {
Self(InnerStdio::Inherit)
}
}
impl Stdio {
/// A new pipe should be arranged to connect the parent and child processes.
pub fn piped() -> Self {
Self(InnerStdio::Piped)
}
/// The child inherits from the corresponding parent descriptor. This is the default mode.
pub fn inherit() -> Self {
Self(InnerStdio::Inherit)
}
/// This stream will be ignored. This is the equivalent of attaching the
/// stream to `/dev/null`.
pub fn null() -> Self {
Self(InnerStdio::Null)
}
/// Returns a pair of file descriptors, one for the parent and one for the
/// child. If the child's file descriptor is `None`, then it shall be
/// inherited from the parent. If the parent's file descriptor is `None`,
/// then there is no link to the child and the child owns the other half of
/// the file descriptor (if any). Both file descriptors will be `None` if
/// stdio is being inherited.
pub(super) fn pipes(&self, readable: bool) -> Result<(Option<Fd>, Option<Fd>), Errno> {
match &self.0 {
InnerStdio::Inherit => Ok((None, None)),
InnerStdio::Null => Ok((None, Some(Fd::null(readable)?))),
InnerStdio::Piped => {
let (reader, writer) = pipe()?;
let (parent, child) = if readable {
(writer, reader)
} else {
(reader, writer)
};
Ok((Some(parent), Some(child)))
}
InnerStdio::File(file) => Ok((None, Some(file.dup()?))),
}
}
}
impl<T: IntoRawFd> From<T> for Stdio {
fn from(f: T) -> Self {
Self(InnerStdio::File(Fd::new(f.into_raw_fd())))
}
}
impl From<Stdio> for std::process::Stdio {
fn from(stdio: Stdio) -> Self {
match stdio.0 {
InnerStdio::Inherit => Self::inherit(),
InnerStdio::Null => Self::null(),
InnerStdio::Piped => Self::piped(),
InnerStdio::File(fd) => Self::from(std::fs::File::from(fd)),
}
}
}
impl ChildStdin {
pub(super) fn new(fd: Fd) -> Result<Self, Errno> {
AsyncFd::writable(fd).map(Self)
}
}
impl ChildStdout {
pub(super) fn new(fd: Fd) -> Result<Self, Errno> {
AsyncFd::readable(fd).map(Self)
}
}
impl ChildStderr {
pub(super) fn new(fd: Fd) -> Result<Self, Errno> {
AsyncFd::readable(fd).map(Self)
}
}
impl AsyncWrite for ChildStdin {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<tokio::io::Result<usize>> {
Pin::new(&mut self.0).poll_write(cx, buf)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.0).poll_flush(cx)
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.0).poll_shutdown(cx)
}
}
impl AsyncRead for ChildStdout {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context,
buf: &mut ReadBuf,
) -> Poll<tokio::io::Result<()>> {
Pin::new(&mut self.0).poll_read(cx, buf)
}
}
impl AsyncRead for ChildStderr {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context,
buf: &mut ReadBuf,
) -> Poll<tokio::io::Result<()>> {
Pin::new(&mut self.0).poll_read(cx, buf)
}
}
impl FromRawFd for ChildStdin {
unsafe fn from_raw_fd(fd: i32) -> Self {
Self::new(Fd::new(fd)).unwrap()
}
}
impl FromRawFd for ChildStdout {
unsafe fn from_raw_fd(fd: i32) -> Self {
Self::new(Fd::new(fd)).unwrap()
}
}
impl FromRawFd for ChildStderr {
unsafe fn from_raw_fd(fd: i32) -> Self {
Self::new(Fd::new(fd)).unwrap()
}
}
impl From<tokio::process::ChildStdin> for ChildStdin {
fn from(io: tokio::process::ChildStdin) -> Self {
let fd = io.as_raw_fd();
let fd = unsafe { libc::dup(fd) };
drop(io);
unsafe { Self::from_raw_fd(fd) }
}
}
impl From<tokio::process::ChildStdout> for ChildStdout {
fn from(io: tokio::process::ChildStdout) -> Self {
let fd = io.as_raw_fd();
let fd = unsafe { libc::dup(fd) };
drop(io);
unsafe { Self::from_raw_fd(fd) }
}
}
impl From<tokio::process::ChildStderr> for ChildStderr {
fn from(io: tokio::process::ChildStderr) -> Self {
let fd = io.as_raw_fd();
let fd = unsafe { libc::dup(fd) };
drop(io);
unsafe { Self::from_raw_fd(fd) }
}
}