reverie-process 0.4.1

Deterministic async process spawning and management for the Reverie framework.
Documentation
/*
 * Copyright (c) Meta Platforms, Inc. and 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 core::pin::Pin;
use core::task::Context;
use core::task::Poll;
use std::io;
use std::os::fd::OwnedFd;
use std::os::unix::io::AsRawFd;
use std::os::unix::io::FromRawFd;
use std::os::unix::io::IntoRawFd;

use syscalls::Errno;
use tokio::io::AsyncRead;
use tokio::io::AsyncWrite;
use tokio::io::ReadBuf;

use super::fd::AsyncFd;
use super::fd::Fd;
use super::fd::pipe;

/// 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 From<OwnedFd> for ChildStdin {
    fn from(fd: OwnedFd) -> Self {
        Self::new(fd.into()).unwrap()
    }
}

impl FromRawFd for ChildStdout {
    unsafe fn from_raw_fd(fd: i32) -> Self {
        Self::new(Fd::new(fd)).unwrap()
    }
}

impl From<OwnedFd> for ChildStdout {
    fn from(fd: OwnedFd) -> Self {
        Self::new(fd.into()).unwrap()
    }
}

impl FromRawFd for ChildStderr {
    unsafe fn from_raw_fd(fd: i32) -> Self {
        Self::new(Fd::new(fd)).unwrap()
    }
}

impl From<OwnedFd> for ChildStderr {
    fn from(fd: OwnedFd) -> Self {
        Self::new(fd.into()).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) }
    }
}