use std::{
io,
os::fd::{FromRawFd, OwnedFd},
};
#[derive(Debug)]
pub struct Pipe {
reader: OwnedFd,
writer: OwnedFd,
}
impl Pipe {
#[must_use]
pub const fn reader(&self) -> &OwnedFd {
&self.reader
}
#[must_use]
pub const fn writer(&self) -> &OwnedFd {
&self.writer
}
#[must_use]
pub fn into_parts(self) -> (OwnedFd, OwnedFd) {
(self.reader, self.writer)
}
}
#[derive(Debug)]
pub struct SocketPair {
first: OwnedFd,
second: OwnedFd,
}
impl SocketPair {
#[must_use]
pub const fn first(&self) -> &OwnedFd {
&self.first
}
#[must_use]
pub const fn second(&self) -> &OwnedFd {
&self.second
}
#[must_use]
pub fn into_parts(self) -> (OwnedFd, OwnedFd) {
(self.first, self.second)
}
}
pub fn pipe_cloexec() -> io::Result<Pipe> {
let (reader, writer) = create_pipe()?;
Ok(Pipe { reader, writer })
}
pub fn socket_pair_cloexec() -> io::Result<SocketPair> {
let mut raw = [-1; 2];
let socket_type = socket_type_cloexec();
retry_syscall(|| {
unsafe { libc::socketpair(libc::AF_UNIX, socket_type, 0, raw.as_mut_ptr()) }
})?;
let first = unsafe { OwnedFd::from_raw_fd(raw[0]) };
let second = unsafe { OwnedFd::from_raw_fd(raw[1]) };
#[cfg(target_vendor = "apple")]
{
set_cloexec(&first)?;
set_cloexec(&second)?;
}
Ok(SocketPair { first, second })
}
#[cfg(any(
target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd"
))]
fn create_pipe() -> io::Result<(OwnedFd, OwnedFd)> {
let mut raw = [-1; 2];
retry_syscall(|| {
unsafe { libc::pipe2(raw.as_mut_ptr(), libc::O_CLOEXEC) }
})?;
let reader = unsafe { OwnedFd::from_raw_fd(raw[0]) };
let writer = unsafe { OwnedFd::from_raw_fd(raw[1]) };
Ok((reader, writer))
}
#[cfg(not(any(
target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd"
)))]
fn create_pipe() -> io::Result<(OwnedFd, OwnedFd)> {
let mut raw = [-1; 2];
retry_syscall(|| {
unsafe { libc::pipe(raw.as_mut_ptr()) }
})?;
let reader = unsafe { OwnedFd::from_raw_fd(raw[0]) };
let writer = unsafe { OwnedFd::from_raw_fd(raw[1]) };
set_cloexec(&reader)?;
set_cloexec(&writer)?;
Ok((reader, writer))
}
#[cfg(not(target_vendor = "apple"))]
const fn socket_type_cloexec() -> libc::c_int {
libc::SOCK_STREAM | libc::SOCK_CLOEXEC
}
#[cfg(target_vendor = "apple")]
const fn socket_type_cloexec() -> libc::c_int {
libc::SOCK_STREAM
}
#[cfg(not(any(
target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd"
)))]
fn set_cloexec(fd: &OwnedFd) -> io::Result<()> {
use std::os::fd::AsRawFd;
let raw = fd.as_raw_fd();
let current = retry_fcntl(raw, libc::F_GETFD, 0)?;
retry_fcntl(raw, libc::F_SETFD, current | libc::FD_CLOEXEC).map(|_| ())
}
#[cfg(not(any(
target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd"
)))]
fn retry_fcntl(
fd: std::os::fd::RawFd,
command: libc::c_int,
argument: libc::c_int,
) -> io::Result<libc::c_int> {
loop {
let result = unsafe { libc::fcntl(fd, command, argument) };
if result != -1 {
return Ok(result);
}
let error = io::Error::last_os_error();
if error.kind() != io::ErrorKind::Interrupted {
return Err(error);
}
}
}
fn retry_syscall(mut operation: impl FnMut() -> libc::c_int) -> io::Result<()> {
loop {
if operation() == 0 {
return Ok(());
}
let error = io::Error::last_os_error();
if error.kind() != io::ErrorKind::Interrupted {
return Err(error);
}
}
}