rust/library/std/src/sys/unix/pipe.rs
bors e2d6aa77ed Auto merge of #98704 - vthib:impl-from-raw-for-childstd-structs, r=dtolnay
Implement From<OwnedFd/Handle> for ChildStdin/out/err object

## Summary

Comments in `library/std/src/process.rs` ( ab08639e59 ) indicates that `ChildStdin`, `ChildStdout`, `ChildStderr` implements some traits that are not actually implemented: `FromRawFd`, `FromRawHandle`, and the `From<OwnedFd>/From<OwnedHandle>` from the io_safety feature.

In this PR I implement `FromRawHandle` and `FromRawFd` for those 3 objects.

## Usecase

I have a usecase where those implementations are basically needed. I want to customize
in the `Command::spawn` API how the pipes for the parent/child communications are created (mainly to strengthen the security attributes on them). I can properly setup the pipes,
and the "child" handles can be provided to `Child::spawn` easily using `Stdio::from_raw_handle`. However, there is no way to generate the `ChildStd*` objects from the raw handle of the created name pipe, which would be very useful to still expose the same API
than in other OS (basically a `spawn(...) -> (Child, ChildStdin, ChildStdout, ChildSterr)`, where on windows this is customized), and to for example use `tokio::ChildStdin::from_std` afterwards.

## Questions

* Are those impls OK to add? I have searched to see if those impls were missing on purpose, or if it was just never implemented because never needed. I haven't found any indication on why they couldn't be added, although the user clearly has to be very careful that the handle provided makes sense (i think, mainly that it is in overlapped mode for windows).
* If this change is ok, adding the impls for the io_safety feature would probably be best, or should it be done in another PR?
* I just copy-pasted the `#[stable(...)]` attributes, but the `since` value has to be updated, I'm not sure to which value.
2023-09-28 09:59:03 +00:00

167 lines
4.9 KiB
Rust

use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut};
use crate::mem;
use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
use crate::sys::fd::FileDesc;
use crate::sys::{cvt, cvt_r};
use crate::sys_common::{FromInner, IntoInner};
////////////////////////////////////////////////////////////////////////////////
// Anonymous pipes
////////////////////////////////////////////////////////////////////////////////
pub struct AnonPipe(FileDesc);
pub fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> {
let mut fds = [0; 2];
// The only known way right now to create atomically set the CLOEXEC flag is
// to use the `pipe2` syscall. This was added to Linux in 2.6.27, glibc 2.9
// and musl 0.9.3, and some other targets also have it.
cfg_if::cfg_if! {
if #[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "hurd",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd",
target_os = "redox"
))] {
unsafe {
cvt(libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC))?;
Ok((AnonPipe(FileDesc::from_raw_fd(fds[0])), AnonPipe(FileDesc::from_raw_fd(fds[1]))))
}
} else {
unsafe {
cvt(libc::pipe(fds.as_mut_ptr()))?;
let fd0 = FileDesc::from_raw_fd(fds[0]);
let fd1 = FileDesc::from_raw_fd(fds[1]);
fd0.set_cloexec()?;
fd1.set_cloexec()?;
Ok((AnonPipe(fd0), AnonPipe(fd1)))
}
}
}
}
impl AnonPipe {
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
pub fn read_buf(&self, buf: BorrowedCursor<'_>) -> io::Result<()> {
self.0.read_buf(buf)
}
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
self.0.read_vectored(bufs)
}
#[inline]
pub fn is_read_vectored(&self) -> bool {
self.0.is_read_vectored()
}
pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
self.0.read_to_end(buf)
}
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
self.0.write_vectored(bufs)
}
#[inline]
pub fn is_write_vectored(&self) -> bool {
self.0.is_write_vectored()
}
}
impl IntoInner<FileDesc> for AnonPipe {
fn into_inner(self) -> FileDesc {
self.0
}
}
pub fn read2(p1: AnonPipe, v1: &mut Vec<u8>, p2: AnonPipe, v2: &mut Vec<u8>) -> io::Result<()> {
// Set both pipes into nonblocking mode as we're gonna be reading from both
// in the `select` loop below, and we wouldn't want one to block the other!
let p1 = p1.into_inner();
let p2 = p2.into_inner();
p1.set_nonblocking(true)?;
p2.set_nonblocking(true)?;
let mut fds: [libc::pollfd; 2] = unsafe { mem::zeroed() };
fds[0].fd = p1.as_raw_fd();
fds[0].events = libc::POLLIN;
fds[1].fd = p2.as_raw_fd();
fds[1].events = libc::POLLIN;
loop {
// wait for either pipe to become readable using `poll`
cvt_r(|| unsafe { libc::poll(fds.as_mut_ptr(), 2, -1) })?;
if fds[0].revents != 0 && read(&p1, v1)? {
p2.set_nonblocking(false)?;
return p2.read_to_end(v2).map(drop);
}
if fds[1].revents != 0 && read(&p2, v2)? {
p1.set_nonblocking(false)?;
return p1.read_to_end(v1).map(drop);
}
}
// Read as much as we can from each pipe, ignoring EWOULDBLOCK or
// EAGAIN. If we hit EOF, then this will happen because the underlying
// reader will return Ok(0), in which case we'll see `Ok` ourselves. In
// this case we flip the other fd back into blocking mode and read
// whatever's leftover on that file descriptor.
fn read(fd: &FileDesc, dst: &mut Vec<u8>) -> Result<bool, io::Error> {
match fd.read_to_end(dst) {
Ok(_) => Ok(true),
Err(e) => {
if e.raw_os_error() == Some(libc::EWOULDBLOCK)
|| e.raw_os_error() == Some(libc::EAGAIN)
{
Ok(false)
} else {
Err(e)
}
}
}
}
}
impl AsRawFd for AnonPipe {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl AsFd for AnonPipe {
fn as_fd(&self) -> BorrowedFd<'_> {
self.0.as_fd()
}
}
impl IntoRawFd for AnonPipe {
fn into_raw_fd(self) -> RawFd {
self.0.into_raw_fd()
}
}
impl FromRawFd for AnonPipe {
unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
Self(FromRawFd::from_raw_fd(raw_fd))
}
}
impl FromInner<FileDesc> for AnonPipe {
fn from_inner(fd: FileDesc) -> Self {
Self(fd)
}
}