1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-11 14:25:57 +03:00

Import filedescriptor crate from the wzsh repo

I'm moving that crate into this repo
This commit is contained in:
Wez Furlong 2020-01-26 09:11:57 -08:00
commit 872d350ac5
5 changed files with 1460 additions and 0 deletions

26
filedescriptor/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "filedescriptor"
version = "0.7.1"
authors = ["Wez Furlong"]
edition = "2018"
repository = "https://github.com/wez/wzsh"
description = "More ergonomic wrappers around RawFd and RawHandle"
license = "MIT"
documentation = "https://docs.rs/filedescriptor"
readme = "README.md"
keywords = ["socketpair", "pipe", "poll", "filedescriptor"]
[dependencies]
anyhow = "1.0"
thiserror = "1.0"
libc = "0.2"
[target."cfg(windows)".dependencies]
winapi = { version = "0.3", features = [
"winuser",
"handleapi",
"fileapi",
"namedpipeapi",
"processthreadsapi",
"winsock2"
]}

98
filedescriptor/README.md Normal file
View File

@ -0,0 +1,98 @@
<!-- cargo-sync-readme start -->
The purpose of this crate is to make it a bit more ergonomic for portable
applications that need to work with the platform level `RawFd` and
`RawHandle` types.
Rather than conditionally using `RawFd` and `RawHandle`, the `FileDescriptor`
type can be used to manage ownership, duplicate, read and write.
## FileDescriptor
This is a bit of a contrived example, but demonstrates how to avoid
the conditional code that would otherwise be required to deal with
calling `as_raw_fd` and `as_raw_handle`:
```
use filedescriptor::{FileDescriptor, FromRawFileDescriptor};
use std::io::Write;
fn get_stdout() -> anyhow::Result<FileDescriptor> {
let stdout = std::io::stdout();
let handle = stdout.lock();
FileDescriptor::dup(&handle)
}
fn print_something() -> anyhow::Result<()> {
get_stdout()?.write(b"hello")?;
Ok(())
}
```
## Pipe
The `Pipe` type makes it more convenient to create a pipe and manage
the lifetime of both the read and write ends of that pipe.
```
use filedescriptor::Pipe;
use std::io::{Read, Write};
use anyhow::Error;
let mut pipe = Pipe::new()?;
pipe.write.write(b"hello")?;
drop(pipe.write);
let mut s = String::new();
pipe.read.read_to_string(&mut s)?;
assert_eq!(s, "hello");
```
## Socketpair
The `socketpair` function returns a pair of connected `SOCK_STREAM`
sockets and functions both on posix and windows systems.
```
use std::io::{Read, Write};
use anyhow::Error;
let (mut a, mut b) = filedescriptor::socketpair()?;
a.write(b"hello")?;
drop(a);
let mut s = String::new();
b.read_to_string(&mut s)?;
assert_eq!(s, "hello");
```
## Polling
The `mio` crate offers powerful and scalable IO multiplexing, but there
are some situations where `mio` doesn't fit. The `filedescriptor` crate
offers a `poll(2)` compatible interface suitable for testing the readiness
of a set of file descriptors. On unix systems this is a very thin wrapper
around `poll(2)`, except on macOS where it is actually a wrapper around
the `select(2)` interface. On Windows systems the winsock `WSAPoll`
function is used instead.
```
use filedescriptor::*;
use anyhow::Error;
use std::time::Duration;
use std::io::{Read, Write};
let (mut a, mut b) = filedescriptor::socketpair()?;
let mut poll_array = [pollfd {
fd: a.as_socket_descriptor(),
events: POLLIN,
revents: 0
}];
// sleeps for 20 milliseconds because `a` is not yet ready
assert_eq!(poll(&mut poll_array, Some(Duration::from_millis(20)))?, 0);
b.write(b"hello")?;
// Now a is ready for read
assert_eq!(poll(&mut poll_array, Some(Duration::from_millis(20)))?, 1);
```
<!-- cargo-sync-readme end -->

324
filedescriptor/src/lib.rs Normal file
View File

@ -0,0 +1,324 @@
//! The purpose of this crate is to make it a bit more ergonomic for portable
//! applications that need to work with the platform level `RawFd` and
//! `RawHandle` types.
//!
//! Rather than conditionally using `RawFd` and `RawHandle`, the `FileDescriptor`
//! type can be used to manage ownership, duplicate, read and write.
//!
//! ## FileDescriptor
//!
//! This is a bit of a contrived example, but demonstrates how to avoid
//! the conditional code that would otherwise be required to deal with
//! calling `as_raw_fd` and `as_raw_handle`:
//!
//! ```
//! use filedescriptor::{FileDescriptor, FromRawFileDescriptor};
//! use std::io::Write;
//!
//! fn get_stdout() -> anyhow::Result<FileDescriptor> {
//! let stdout = std::io::stdout();
//! let handle = stdout.lock();
//! FileDescriptor::dup(&handle)
//! }
//!
//! fn print_something() -> anyhow::Result<()> {
//! get_stdout()?.write(b"hello")?;
//! Ok(())
//! }
//! ```
//!
//! ## Pipe
//! The `Pipe` type makes it more convenient to create a pipe and manage
//! the lifetime of both the read and write ends of that pipe.
//!
//! ```
//! use filedescriptor::Pipe;
//! use std::io::{Read, Write};
//! use anyhow::Error;
//!
//! let mut pipe = Pipe::new()?;
//! pipe.write.write(b"hello")?;
//! drop(pipe.write);
//!
//! let mut s = String::new();
//! pipe.read.read_to_string(&mut s)?;
//! assert_eq!(s, "hello");
//! # Ok::<(), Error>(())
//! ```
//!
//! ## Socketpair
//! The `socketpair` function returns a pair of connected `SOCK_STREAM`
//! sockets and functions both on posix and windows systems.
//!
//! ```
//! use std::io::{Read, Write};
//! use anyhow::Error;
//!
//! let (mut a, mut b) = filedescriptor::socketpair()?;
//! a.write(b"hello")?;
//! drop(a);
//!
//! let mut s = String::new();
//! b.read_to_string(&mut s)?;
//! assert_eq!(s, "hello");
//! # Ok::<(), Error>(())
//! ```
//!
//! ## Polling
//! The `mio` crate offers powerful and scalable IO multiplexing, but there
//! are some situations where `mio` doesn't fit. The `filedescriptor` crate
//! offers a `poll(2)` compatible interface suitable for testing the readiness
//! of a set of file descriptors. On unix systems this is a very thin wrapper
//! around `poll(2)`, except on macOS where it is actually a wrapper around
//! the `select(2)` interface. On Windows systems the winsock `WSAPoll`
//! function is used instead.
//!
//! ```
//! use filedescriptor::*;
//! use anyhow::Error;
//! use std::time::Duration;
//! use std::io::{Read, Write};
//!
//! let (mut a, mut b) = filedescriptor::socketpair()?;
//! let mut poll_array = [pollfd {
//! fd: a.as_socket_descriptor(),
//! events: POLLIN,
//! revents: 0
//! }];
//! // sleeps for 20 milliseconds because `a` is not yet ready
//! assert_eq!(poll(&mut poll_array, Some(Duration::from_millis(20)))?, 0);
//!
//! b.write(b"hello")?;
//!
//! // Now a is ready for read
//! assert_eq!(poll(&mut poll_array, Some(Duration::from_millis(20)))?, 1);
//!
//! # Ok::<(), Error>(())
//! ```
#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub use crate::unix::*;
#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub use crate::windows::*;
/// `AsRawFileDescriptor` is a platform independent trait for returning
/// a non-owning reference to the underlying platform file descriptor
/// type.
pub trait AsRawFileDescriptor {
fn as_raw_file_descriptor(&self) -> RawFileDescriptor;
}
/// `IntoRawFileDescriptor` is a platform independent trait for converting
/// an instance into the underlying platform file descriptor type.
pub trait IntoRawFileDescriptor {
fn into_raw_file_descriptor(self) -> RawFileDescriptor;
}
/// `FromRawFileDescriptor` is a platform independent trait for creating
/// an instance from the underlying platform file descriptor type.
/// Because the platform file descriptor type has no inherent ownership
/// management, the `from_raw_file_descriptor` function is marked as unsafe
/// to indicate that care must be taken by the caller to ensure that it
/// is used appropriately.
pub trait FromRawFileDescriptor {
unsafe fn from_raw_file_descriptor(fd: RawFileDescriptor) -> Self;
}
pub trait AsRawSocketDescriptor {
fn as_socket_descriptor(&self) -> SocketDescriptor;
}
pub trait IntoRawSocketDescriptor {
fn into_socket_descriptor(self) -> SocketDescriptor;
}
pub trait FromRawSocketDescriptor {
unsafe fn from_socket_descriptor(fd: SocketDescriptor) -> Self;
}
/// `OwnedHandle` allows managing the lifetime of the platform `RawFileDescriptor`
/// type. It is exposed in the interface of this crate primarily for convenience
/// on Windows where the system handle type is used for a variety of objects
/// that don't support reading and writing.
#[derive(Debug)]
pub struct OwnedHandle {
handle: RawFileDescriptor,
handle_type: HandleType,
}
impl OwnedHandle {
/// Create a new handle from some object that is convertible into
/// the system `RawFileDescriptor` type. This consumes the parameter
/// and replaces it with an `OwnedHandle` instance.
pub fn new<F: IntoRawFileDescriptor>(f: F) -> Self {
let handle = f.into_raw_file_descriptor();
Self {
handle,
handle_type: Self::probe_handle_type(handle),
}
}
/// Attempt to duplicate the underlying handle and return an
/// `OwnedHandle` wrapped around the duplicate. Since the duplication
/// requires kernel resources that may not be available, this is a
/// potentially fallible operation.
/// The returned handle has a separate lifetime from the source, but
/// references the same object at the kernel level.
pub fn try_clone(&self) -> anyhow::Result<Self> {
Self::dup_impl(self, self.handle_type)
}
/// Attempt to duplicate the underlying handle from an object that is
/// representable as the system `RawFileDescriptor` type and return an
/// `OwnedHandle` wrapped around the duplicate. Since the duplication
/// requires kernel resources that may not be available, this is a
/// potentially fallible operation.
/// The returned handle has a separate lifetime from the source, but
/// references the same object at the kernel level.
pub fn dup<F: AsRawFileDescriptor>(f: &F) -> anyhow::Result<Self> {
Self::dup_impl(f, Default::default())
}
}
/// `FileDescriptor` is a thin wrapper on top of the `OwnedHandle` type that
/// exposes the ability to Read and Write to the platform `RawFileDescriptor`.
///
/// This is a bit of a contrived example, but demonstrates how to avoid
/// the conditional code that would otherwise be required to deal with
/// calling `as_raw_fd` and `as_raw_handle`:
///
/// ```
/// use filedescriptor::{FileDescriptor, FromRawFileDescriptor};
/// use std::io::Write;
///
/// fn get_stdout() -> anyhow::Result<FileDescriptor> {
/// let stdout = std::io::stdout();
/// let handle = stdout.lock();
/// FileDescriptor::dup(&handle)
/// }
///
/// fn print_something() -> anyhow::Result<()> {
/// get_stdout()?.write(b"hello")?;
/// Ok(())
/// }
/// ```
#[derive(Debug)]
pub struct FileDescriptor {
handle: OwnedHandle,
}
impl FileDescriptor {
/// Create a new descriptor from some object that is convertible into
/// the system `RawFileDescriptor` type. This consumes the parameter
/// and replaces it with a `FileDescriptor` instance.
pub fn new<F: IntoRawFileDescriptor>(f: F) -> Self {
let handle = OwnedHandle::new(f);
Self { handle }
}
/// Attempt to duplicate the underlying handle from an object that is
/// representable as the system `RawFileDescriptor` type and return a
/// `FileDescriptor` wrapped around the duplicate. Since the duplication
/// requires kernel resources that may not be available, this is a
/// potentially fallible operation.
/// The returned handle has a separate lifetime from the source, but
/// references the same object at the kernel level.
pub fn dup<F: AsRawFileDescriptor>(f: &F) -> anyhow::Result<Self> {
OwnedHandle::dup(f).map(|handle| Self { handle })
}
/// Attempt to duplicate the underlying handle and return a
/// `FileDescriptor` wrapped around the duplicate. Since the duplication
/// requires kernel resources that may not be available, this is a
/// potentially fallible operation.
/// The returned handle has a separate lifetime from the source, but
/// references the same object at the kernel level.
pub fn try_clone(&self) -> anyhow::Result<Self> {
self.handle.try_clone().map(|handle| Self { handle })
}
/// A convenience method for creating a `std::process::Stdio` object
/// to be used for eg: redirecting the stdio streams of a child
/// process. The `Stdio` is created using a duplicated handle so
/// that the source handle remains alive.
pub fn as_stdio(&self) -> anyhow::Result<std::process::Stdio> {
self.as_stdio_impl()
}
/// Attempt to change the non-blocking IO mode of the file descriptor.
/// Not all kinds of file descriptor can be placed in non-blocking mode
/// on all systems, and some file descriptors will claim to be in
/// non-blocking mode but it will have no effect.
/// File descriptors based on sockets are the most portable type
/// that can be successfully made non-blocking.
pub fn set_non_blocking(&mut self, non_blocking: bool) -> anyhow::Result<()> {
self.set_non_blocking_impl(non_blocking)
}
}
/// Represents the readable and writable ends of a pair of descriptors
/// connected via a kernel pipe.
///
/// ```
/// use filedescriptor::Pipe;
/// use std::io::{Read,Write};
/// use anyhow::Error;
///
/// let mut pipe = Pipe::new()?;
/// pipe.write.write(b"hello")?;
/// drop(pipe.write);
///
/// let mut s = String::new();
/// pipe.read.read_to_string(&mut s)?;
/// assert_eq!(s, "hello");
/// # Ok::<(), Error>(())
/// ```
pub struct Pipe {
/// The readable end of the pipe
pub read: FileDescriptor,
/// The writable end of the pipe
pub write: FileDescriptor,
}
use std::time::Duration;
/// Examines a set of FileDescriptors to see if some of them are ready for I/O,
/// or if certain events have occurred on them.
///
/// This uses the system native readiness checking mechanism, which on Windows
/// means that it does NOT use IOCP and that this only works with sockets on
/// Windows. If you need IOCP then the `mio` crate is recommended for a much
/// more scalable solution.
///
/// On macOS, the `poll(2)` implementation has problems when used with eg: pty
/// descriptors, so this implementation of poll uses the `select(2)` interface
/// under the covers. That places a limit on the maximum file descriptor value
/// that can be passed to poll. If a file descriptor is out of range then an
/// error will returned. This limitation could potentially be lifted in the
/// future.
///
/// On Windows, `WSAPoll` is used to implement readiness checking, which has
/// the consequence that it can only be used with sockets.
///
/// If `duration` is `None`, then `poll` will block until any of the requested
/// events are ready. Otherwise, `duration` specifies how long to wait for
/// readiness before giving up.
///
/// The return value is the number of entries that were satisfied; `0` means
/// that none were ready after waiting for the specified duration.
///
/// The `pfd` array is mutated and the `revents` field is updated to indicate
/// which of the events were received.
pub fn poll(pfd: &mut [pollfd], duration: Option<Duration>) -> anyhow::Result<usize> {
poll_impl(pfd, duration)
}
/// Create a pair of connected sockets
///
/// This implementation creates a pair of SOCK_STREAM sockets.
pub fn socketpair() -> anyhow::Result<(FileDescriptor, FileDescriptor)> {
socketpair_impl()
}

481
filedescriptor/src/unix.rs Normal file
View File

@ -0,0 +1,481 @@
use crate::{
AsRawFileDescriptor, AsRawSocketDescriptor, FileDescriptor, FromRawFileDescriptor,
FromRawSocketDescriptor, IntoRawFileDescriptor, IntoRawSocketDescriptor, OwnedHandle, Pipe,
};
use anyhow::bail;
use std::os::unix::prelude::*;
pub(crate) type HandleType = ();
/// `RawFileDescriptor` is a platform independent type alias for the
/// underlying platform file descriptor type. It is primarily useful
/// for avoiding using `cfg` blocks in platform independent code.
pub type RawFileDescriptor = RawFd;
/// `SocketDescriptor` is a platform independent type alias for the
/// underlying platform socket descriptor type. It is primarily useful
/// for avoiding using `cfg` blocks in platform independent code.
pub type SocketDescriptor = RawFd;
impl<T: AsRawFd> AsRawFileDescriptor for T {
fn as_raw_file_descriptor(&self) -> RawFileDescriptor {
self.as_raw_fd()
}
}
impl<T: IntoRawFd> IntoRawFileDescriptor for T {
fn into_raw_file_descriptor(self) -> RawFileDescriptor {
self.into_raw_fd()
}
}
impl<T: FromRawFd> FromRawFileDescriptor for T {
unsafe fn from_raw_file_descriptor(fd: RawFileDescriptor) -> Self {
Self::from_raw_fd(fd)
}
}
impl<T: AsRawFd> AsRawSocketDescriptor for T {
fn as_socket_descriptor(&self) -> SocketDescriptor {
self.as_raw_fd()
}
}
impl<T: IntoRawFd> IntoRawSocketDescriptor for T {
fn into_socket_descriptor(self) -> SocketDescriptor {
self.into_raw_fd()
}
}
impl<T: FromRawFd> FromRawSocketDescriptor for T {
unsafe fn from_socket_descriptor(fd: SocketDescriptor) -> Self {
Self::from_raw_fd(fd)
}
}
impl Drop for OwnedHandle {
fn drop(&mut self) {
unsafe {
libc::close(self.handle);
}
}
}
impl AsRawFd for OwnedHandle {
fn as_raw_fd(&self) -> RawFd {
self.handle
}
}
impl IntoRawFd for OwnedHandle {
fn into_raw_fd(self) -> RawFd {
let fd = self.handle;
std::mem::forget(self);
fd
}
}
impl FromRawFd for OwnedHandle {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
Self {
handle: fd,
handle_type: (),
}
}
}
impl OwnedHandle {
/// Helper function to set the close-on-exec flag for a raw descriptor
fn cloexec(&mut self) -> anyhow::Result<()> {
let flags = unsafe { libc::fcntl(self.handle, libc::F_GETFD) };
if flags == -1 {
bail!(
"fcntl to read flags failed: {:?}",
std::io::Error::last_os_error()
);
}
let result = unsafe { libc::fcntl(self.handle, libc::F_SETFD, flags | libc::FD_CLOEXEC) };
if result == -1 {
bail!(
"fcntl to set CLOEXEC failed: {:?}",
std::io::Error::last_os_error()
);
}
Ok(())
}
fn non_atomic_dup(fd: RawFd) -> anyhow::Result<Self> {
let duped = unsafe { libc::dup(fd) };
if duped == -1 {
bail!(
"dup of fd {} failed: {:?}",
fd,
std::io::Error::last_os_error()
)
} else {
let mut owned = OwnedHandle {
handle: duped,
handle_type: (),
};
owned.cloexec()?;
Ok(owned)
}
}
#[inline]
pub(crate) fn dup_impl<F: AsRawFileDescriptor>(
fd: &F,
handle_type: HandleType,
) -> anyhow::Result<Self> {
let fd = fd.as_raw_file_descriptor();
let duped = unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0) };
if duped == -1 {
let err = std::io::Error::last_os_error();
if let Some(libc::EINVAL) = err.raw_os_error() {
// We may be running on eg: WSL or an old kernel that
// doesn't support F_DUPFD_CLOEXEC; fall back.
return Self::non_atomic_dup(fd);
} else {
bail!("dup of fd {} failed: {:?}", fd, err)
}
} else {
Ok(OwnedHandle {
handle: duped,
handle_type,
})
}
}
pub(crate) fn probe_handle_type(_handle: RawFileDescriptor) -> HandleType {
()
}
}
impl std::io::Read for FileDescriptor {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
let size = unsafe { libc::read(self.handle.handle, buf.as_mut_ptr() as *mut _, buf.len()) };
if size == -1 {
Err(std::io::Error::last_os_error())
} else {
Ok(size as usize)
}
}
}
impl std::io::Write for FileDescriptor {
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
let size = unsafe { libc::write(self.handle.handle, buf.as_ptr() as *const _, buf.len()) };
if size == -1 {
Err(std::io::Error::last_os_error())
} else {
Ok(size as usize)
}
}
fn flush(&mut self) -> Result<(), std::io::Error> {
Ok(())
}
}
impl AsRawFd for FileDescriptor {
fn as_raw_fd(&self) -> RawFd {
self.handle.as_raw_fd()
}
}
impl IntoRawFd for FileDescriptor {
fn into_raw_fd(self) -> RawFd {
self.handle.into_raw_fd()
}
}
impl FromRawFd for FileDescriptor {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
Self {
handle: OwnedHandle::from_raw_fd(fd),
}
}
}
impl FileDescriptor {
#[inline]
pub(crate) fn as_stdio_impl(&self) -> anyhow::Result<std::process::Stdio> {
let duped = OwnedHandle::dup(self)?;
let fd = duped.into_raw_fd();
let stdio = unsafe { std::process::Stdio::from_raw_fd(fd) };
Ok(stdio)
}
#[inline]
pub(crate) fn set_non_blocking_impl(&mut self, non_blocking: bool) -> anyhow::Result<()> {
let on = if non_blocking { 1 } else { 0 };
let res = unsafe { libc::ioctl(self.handle.as_raw_file_descriptor(), libc::FIONBIO, &on) };
if res != 0 {
bail!(
"failed to change non-blocking mode: {:?}",
std::io::Error::last_os_error()
);
}
Ok(())
}
}
impl Pipe {
#[cfg(target_os = "linux")]
pub fn new() -> anyhow::Result<Pipe> {
let mut fds = [-1i32; 2];
let res = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) };
if res == -1 {
bail!(
"failed to create a pipe: {:?}",
std::io::Error::last_os_error()
)
} else {
let read = FileDescriptor {
handle: OwnedHandle {
handle: fds[0],
handle_type: (),
},
};
let write = FileDescriptor {
handle: OwnedHandle {
handle: fds[1],
handle_type: (),
},
};
Ok(Pipe { read, write })
}
}
#[cfg(not(target_os = "linux"))]
pub fn new() -> anyhow::Result<Pipe> {
let mut fds = [-1i32; 2];
let res = unsafe { libc::pipe(fds.as_mut_ptr()) };
if res == -1 {
bail!(
"failed to create a pipe: {:?}",
std::io::Error::last_os_error()
)
} else {
let mut read = FileDescriptor {
handle: OwnedHandle {
handle: fds[0],
handle_type: (),
},
};
let mut write = FileDescriptor {
handle: OwnedHandle {
handle: fds[1],
handle_type: (),
},
};
read.handle.cloexec()?;
write.handle.cloexec()?;
Ok(Pipe { read, write })
}
}
}
#[cfg(target_os = "linux")]
#[doc(hidden)]
pub fn socketpair_impl() -> anyhow::Result<(FileDescriptor, FileDescriptor)> {
let mut fds = [-1i32; 2];
let res = unsafe {
libc::socketpair(
libc::PF_LOCAL,
libc::SOCK_STREAM | libc::SOCK_CLOEXEC,
0,
fds.as_mut_ptr(),
)
};
if res == -1 {
bail!(
"failed to create a socketpair: {:?}",
std::io::Error::last_os_error()
)
} else {
let read = FileDescriptor {
handle: OwnedHandle {
handle: fds[0],
handle_type: (),
},
};
let write = FileDescriptor {
handle: OwnedHandle {
handle: fds[1],
handle_type: (),
},
};
Ok((read, write))
}
}
#[cfg(not(target_os = "linux"))]
#[doc(hidden)]
pub fn socketpair_impl() -> anyhow::Result<(FileDescriptor, FileDescriptor)> {
let mut fds = [-1i32; 2];
let res = unsafe { libc::socketpair(libc::PF_LOCAL, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
if res == -1 {
bail!(
"failed to create a socketpair: {:?}",
std::io::Error::last_os_error()
)
} else {
let mut read = FileDescriptor {
handle: OwnedHandle {
handle: fds[0],
handle_type: (),
},
};
let mut write = FileDescriptor {
handle: OwnedHandle {
handle: fds[1],
handle_type: (),
},
};
read.handle.cloexec()?;
write.handle.cloexec()?;
Ok((read, write))
}
}
pub use libc::{pollfd, POLLERR, POLLHUP, POLLIN, POLLOUT};
use std::time::Duration;
#[cfg(not(target_os = "macos"))]
#[doc(hidden)]
pub fn poll_impl(pfd: &mut [pollfd], duration: Option<Duration>) -> anyhow::Result<usize> {
let poll_result = unsafe {
libc::poll(
pfd.as_mut_ptr(),
pfd.len() as _,
duration
.map(|wait| wait.as_millis() as libc::c_int)
.unwrap_or(-1),
)
};
if poll_result < 0 {
Err(std::io::Error::last_os_error().into())
} else {
Ok(poll_result as usize)
}
}
// macOS has a broken poll(2) implementation, so we introduce a layer to deal with that here
#[cfg(target_os = "macos")]
mod macos {
use super::*;
use libc::{fd_set, timeval, FD_ISSET, FD_SET, FD_SETSIZE, FD_ZERO, POLLERR, POLLIN, POLLOUT};
use std::os::unix::io::RawFd;
struct FdSet {
set: fd_set,
}
#[inline]
fn check_fd(fd: RawFd) -> anyhow::Result<()> {
anyhow::ensure!(fd >= 0, "illegal fd value");
anyhow::ensure!(
(fd as usize) < FD_SETSIZE,
"fd value is too large to use with select(2) on macos"
);
Ok(())
}
impl FdSet {
pub fn new() -> Self {
unsafe {
let mut set = std::mem::uninitialized();
FD_ZERO(&mut set);
Self { set }
}
}
pub fn add(&mut self, fd: RawFd) -> anyhow::Result<()> {
check_fd(fd)?;
unsafe {
FD_SET(fd, &mut self.set);
}
Ok(())
}
pub fn contains(&mut self, fd: RawFd) -> bool {
check_fd(fd).unwrap();
unsafe { FD_ISSET(fd, &mut self.set) }
}
}
fn materialize(set: &mut Option<FdSet>) -> &mut FdSet {
set.get_or_insert_with(FdSet::new)
}
fn set_ptr(set: &mut Option<FdSet>) -> *mut fd_set {
set.as_mut()
.map(|s| &mut s.set as *mut _)
.unwrap_or_else(std::ptr::null_mut)
}
fn is_set(set: &mut Option<FdSet>, fd: RawFd) -> bool {
set.as_mut().map(|s| s.contains(fd)).unwrap_or(false)
}
pub fn poll_impl(pfd: &mut [pollfd], duration: Option<Duration>) -> anyhow::Result<usize> {
let mut read_set = None;
let mut write_set = None;
let mut exception_set = None;
let mut nfds = 0;
for item in pfd.iter_mut() {
item.revents = 0;
nfds = nfds.max(item.fd);
if item.events & POLLIN != 0 {
materialize(&mut read_set).add(item.fd)?;
}
if item.events & POLLOUT != 0 {
materialize(&mut write_set).add(item.fd)?;
}
materialize(&mut exception_set).add(item.fd)?;
}
let mut timeout = duration.map(|d| timeval {
tv_sec: d.as_secs() as _,
tv_usec: d.as_micros() as _,
});
let res = unsafe {
libc::select(
nfds + 1,
set_ptr(&mut read_set),
set_ptr(&mut write_set),
set_ptr(&mut exception_set),
timeout
.as_mut()
.map(|t| t as *mut _)
.unwrap_or_else(std::ptr::null_mut),
)
};
if res < 0 {
Err(std::io::Error::last_os_error().into())
} else {
for item in pfd.iter_mut() {
if is_set(&mut read_set, item.fd) {
item.revents |= POLLIN;
}
if is_set(&mut write_set, item.fd) {
item.revents |= POLLOUT;
}
if is_set(&mut exception_set, item.fd) {
item.revents |= POLLERR;
}
}
Ok(res as usize)
}
}
}
#[cfg(target_os = "macos")]
#[doc(hidden)]
pub use macos::poll_impl;

View File

@ -0,0 +1,531 @@
use crate::{
AsRawFileDescriptor, AsRawSocketDescriptor, FileDescriptor, FromRawFileDescriptor,
FromRawSocketDescriptor, IntoRawFileDescriptor, IntoRawSocketDescriptor, OwnedHandle, Pipe,
};
use anyhow::bail;
use std::io::{self, Error as IoError};
use std::os::windows::prelude::*;
use std::ptr;
use std::sync::Once;
use std::time::Duration;
use winapi::shared::ws2def::AF_INET;
use winapi::shared::ws2def::INADDR_LOOPBACK;
use winapi::shared::ws2def::SOCKADDR_IN;
use winapi::um::fileapi::*;
use winapi::um::handleapi::*;
use winapi::um::minwinbase::SECURITY_ATTRIBUTES;
use winapi::um::namedpipeapi::{CreatePipe, GetNamedPipeInfo};
use winapi::um::processthreadsapi::*;
use winapi::um::winbase::{FILE_TYPE_CHAR, FILE_TYPE_DISK, FILE_TYPE_PIPE};
use winapi::um::winnt::HANDLE;
use winapi::um::winsock2::{
accept, bind, closesocket, connect, getsockname, htonl, ioctlsocket, listen, recv, send,
WSAPoll, WSASocketW, WSAStartup, INVALID_SOCKET, SOCKET, SOCK_STREAM, WSADATA,
WSA_FLAG_NO_HANDLE_INHERIT,
};
pub use winapi::um::winsock2::{POLLERR, POLLHUP, POLLIN, POLLOUT, WSAPOLLFD as pollfd};
/// `RawFileDescriptor` is a platform independent type alias for the
/// underlying platform file descriptor type. It is primarily useful
/// for avoiding using `cfg` blocks in platform independent code.
pub type RawFileDescriptor = RawHandle;
/// `SocketDescriptor` is a platform independent type alias for the
/// underlying platform socket descriptor type. It is primarily useful
/// for avoiding using `cfg` blocks in platform independent code.
pub type SocketDescriptor = SOCKET;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum HandleType {
Char,
Disk,
Pipe,
Socket,
Unknown,
}
impl Default for HandleType {
fn default() -> Self {
HandleType::Unknown
}
}
impl<T: AsRawHandle> AsRawFileDescriptor for T {
fn as_raw_file_descriptor(&self) -> RawFileDescriptor {
self.as_raw_handle()
}
}
impl<T: IntoRawHandle> IntoRawFileDescriptor for T {
fn into_raw_file_descriptor(self) -> RawFileDescriptor {
self.into_raw_handle()
}
}
impl<T: FromRawHandle> FromRawFileDescriptor for T {
unsafe fn from_raw_file_descriptor(handle: RawHandle) -> Self {
Self::from_raw_handle(handle)
}
}
impl<T: AsRawSocket> AsRawSocketDescriptor for T {
fn as_socket_descriptor(&self) -> SocketDescriptor {
self.as_raw_socket() as SocketDescriptor
}
}
impl<T: IntoRawSocket> IntoRawSocketDescriptor for T {
fn into_socket_descriptor(self) -> SocketDescriptor {
self.into_raw_socket() as SocketDescriptor
}
}
impl<T: FromRawSocket> FromRawSocketDescriptor for T {
unsafe fn from_socket_descriptor(handle: SocketDescriptor) -> Self {
Self::from_raw_socket(handle as _)
}
}
unsafe impl Send for OwnedHandle {}
impl OwnedHandle {
fn probe_handle_type_if_unknown(handle: RawHandle, handle_type: HandleType) -> HandleType {
match handle_type {
HandleType::Unknown => Self::probe_handle_type(handle),
t => t,
}
}
pub(crate) fn probe_handle_type(handle: RawHandle) -> HandleType {
let handle = handle as HANDLE;
match unsafe { GetFileType(handle) } {
FILE_TYPE_CHAR => HandleType::Char,
FILE_TYPE_DISK => HandleType::Disk,
FILE_TYPE_PIPE => {
// Could be a pipe or a socket. Test if for pipeness
let mut flags = 0;
let mut out_buf = 0;
let mut in_buf = 0;
let mut inst = 0;
if unsafe {
GetNamedPipeInfo(handle, &mut flags, &mut out_buf, &mut in_buf, &mut inst)
} != 0
{
HandleType::Pipe
} else {
HandleType::Socket
}
}
_ => HandleType::Unknown,
}
}
fn is_socket_handle(&self) -> bool {
match self.handle_type {
HandleType::Socket => true,
HandleType::Unknown => Self::probe_handle_type(self.handle) == HandleType::Socket,
_ => false,
}
}
}
impl Drop for OwnedHandle {
fn drop(&mut self) {
if self.handle != INVALID_HANDLE_VALUE as _ && !self.handle.is_null() {
unsafe {
if self.is_socket_handle() {
closesocket(self.handle as _);
} else {
CloseHandle(self.handle as _);
}
};
}
}
}
impl FromRawHandle for OwnedHandle {
unsafe fn from_raw_handle(handle: RawHandle) -> Self {
OwnedHandle {
handle,
handle_type: Self::probe_handle_type(handle),
}
}
}
impl OwnedHandle {
#[inline]
pub(crate) fn dup_impl<F: AsRawFileDescriptor>(
f: &F,
handle_type: HandleType,
) -> anyhow::Result<Self> {
let handle = f.as_raw_file_descriptor();
if handle == INVALID_HANDLE_VALUE as _ || handle.is_null() {
return Ok(OwnedHandle {
handle,
handle_type,
});
}
let handle_type = Self::probe_handle_type_if_unknown(handle, handle_type);
let proc = unsafe { GetCurrentProcess() };
let mut duped = INVALID_HANDLE_VALUE;
let ok = unsafe {
DuplicateHandle(
proc,
handle as *mut _,
proc,
&mut duped,
0,
0, // not inheritable
winapi::um::winnt::DUPLICATE_SAME_ACCESS,
)
};
if ok == 0 {
Err(IoError::last_os_error().into())
} else {
Ok(OwnedHandle {
handle: duped as *mut _,
handle_type,
})
}
}
}
impl AsRawHandle for OwnedHandle {
fn as_raw_handle(&self) -> RawHandle {
self.handle
}
}
impl IntoRawHandle for OwnedHandle {
fn into_raw_handle(self) -> RawHandle {
let handle = self.handle;
std::mem::forget(self);
handle
}
}
impl FileDescriptor {
#[inline]
pub(crate) fn as_stdio_impl(&self) -> anyhow::Result<std::process::Stdio> {
let duped = self.handle.try_clone()?;
let handle = duped.into_raw_handle();
let stdio = unsafe { std::process::Stdio::from_raw_handle(handle) };
Ok(stdio)
}
#[inline]
pub(crate) fn set_non_blocking_impl(&mut self, non_blocking: bool) -> anyhow::Result<()> {
if !self.handle.is_socket_handle() {
bail!("only socket descriptors can change their non-blocking mode on windows");
}
let mut on = if non_blocking { 1 } else { 0 };
let res = unsafe {
ioctlsocket(
self.as_raw_socket() as SOCKET,
winapi::um::winsock2::FIONBIO,
&mut on,
)
};
if res != 0 {
bail!(
"failed to change non-blocking mode: {:?}",
std::io::Error::last_os_error()
);
}
Ok(())
}
}
impl IntoRawHandle for FileDescriptor {
fn into_raw_handle(self) -> RawHandle {
self.handle.into_raw_handle()
}
}
impl AsRawHandle for FileDescriptor {
fn as_raw_handle(&self) -> RawHandle {
self.handle.as_raw_handle()
}
}
impl FromRawHandle for FileDescriptor {
unsafe fn from_raw_handle(handle: RawHandle) -> FileDescriptor {
Self {
handle: OwnedHandle::from_raw_handle(handle),
}
}
}
impl IntoRawSocket for FileDescriptor {
fn into_raw_socket(self) -> RawSocket {
// FIXME: this isn't a guaranteed conversion!
debug_assert!(self.handle.is_socket_handle());
self.handle.into_raw_handle() as RawSocket
}
}
impl AsRawSocket for FileDescriptor {
fn as_raw_socket(&self) -> RawSocket {
// FIXME: this isn't a guaranteed conversion!
debug_assert!(self.handle.is_socket_handle());
self.handle.as_raw_handle() as RawSocket
}
}
impl FromRawSocket for FileDescriptor {
unsafe fn from_raw_socket(handle: RawSocket) -> FileDescriptor {
Self {
handle: OwnedHandle::from_raw_handle(handle as RawHandle),
}
}
}
impl io::Read for FileDescriptor {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
if self.handle.is_socket_handle() {
// It's important to use the winsock functions to read/write
// even though ReadFile and WriteFile technically work; only
// the winsock functions respect non-blocking mode.
let num_read = unsafe {
recv(
self.as_socket_descriptor(),
buf.as_mut_ptr() as *mut _,
buf.len() as _,
0,
)
};
if num_read < 0 {
Err(IoError::last_os_error())
} else {
Ok(num_read as usize)
}
} else {
let mut num_read = 0;
let ok = unsafe {
ReadFile(
self.handle.as_raw_handle() as *mut _,
buf.as_mut_ptr() as *mut _,
buf.len() as _,
&mut num_read,
ptr::null_mut(),
)
};
if ok == 0 {
let err = IoError::last_os_error();
if err.kind() == std::io::ErrorKind::BrokenPipe {
Ok(0)
} else {
Err(err)
}
} else {
Ok(num_read as usize)
}
}
}
}
impl io::Write for FileDescriptor {
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
if self.handle.is_socket_handle() {
let num_wrote = unsafe {
send(
self.as_socket_descriptor(),
buf.as_ptr() as *const _,
buf.len() as _,
0,
)
};
if num_wrote < 0 {
Err(IoError::last_os_error())
} else {
Ok(num_wrote as usize)
}
} else {
let mut num_wrote = 0;
let ok = unsafe {
WriteFile(
self.handle.as_raw_handle() as *mut _,
buf.as_ptr() as *const _,
buf.len() as u32,
&mut num_wrote,
ptr::null_mut(),
)
};
if ok == 0 {
Err(IoError::last_os_error())
} else {
Ok(num_wrote as usize)
}
}
}
fn flush(&mut self) -> Result<(), io::Error> {
Ok(())
}
}
impl Pipe {
pub fn new() -> anyhow::Result<Pipe> {
let mut sa = SECURITY_ATTRIBUTES {
nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: ptr::null_mut(),
bInheritHandle: 0,
};
let mut read: HANDLE = INVALID_HANDLE_VALUE as _;
let mut write: HANDLE = INVALID_HANDLE_VALUE as _;
if unsafe { CreatePipe(&mut read, &mut write, &mut sa, 0) } == 0 {
bail!("CreatePipe failed: {}", IoError::last_os_error());
}
Ok(Pipe {
read: FileDescriptor {
handle: OwnedHandle {
handle: read as _,
handle_type: HandleType::Pipe,
},
},
write: FileDescriptor {
handle: OwnedHandle {
handle: write as _,
handle_type: HandleType::Pipe,
},
},
})
}
}
fn init_winsock() {
static START: Once = Once::new();
START.call_once(|| unsafe {
let mut data: WSADATA = std::mem::zeroed();
let ret = WSAStartup(
0x202, // version 2.2
&mut data,
);
assert_eq!(ret, 0, "failed to initialize winsock");
});
}
fn socket(af: i32, sock_type: i32, proto: i32) -> anyhow::Result<FileDescriptor> {
let s = unsafe {
WSASocketW(
af,
sock_type,
proto,
ptr::null_mut(),
0,
WSA_FLAG_NO_HANDLE_INHERIT,
)
};
if s == INVALID_SOCKET {
bail!("socket failed: {}", IoError::last_os_error());
}
Ok(FileDescriptor {
handle: OwnedHandle {
handle: s as _,
handle_type: HandleType::Socket,
},
})
}
#[doc(hidden)]
pub fn socketpair_impl() -> anyhow::Result<(FileDescriptor, FileDescriptor)> {
init_winsock();
let s = socket(AF_INET, SOCK_STREAM, 0)?;
let mut in_addr: SOCKADDR_IN = unsafe { std::mem::zeroed() };
in_addr.sin_family = AF_INET as _;
unsafe {
*in_addr.sin_addr.S_un.S_addr_mut() = htonl(INADDR_LOOPBACK);
}
unsafe {
if bind(
s.as_raw_handle() as _,
std::mem::transmute(&in_addr),
std::mem::size_of_val(&in_addr) as _,
) != 0
{
bail!("bind failed: {}", IoError::last_os_error());
}
}
let mut addr_len = std::mem::size_of_val(&in_addr) as i32;
unsafe {
if getsockname(
s.as_raw_handle() as _,
std::mem::transmute(&mut in_addr),
&mut addr_len,
) != 0
{
bail!("getsockname failed: {}", IoError::last_os_error());
}
}
unsafe {
if listen(s.as_raw_handle() as _, 1) != 0 {
bail!("listen failed: {}", IoError::last_os_error());
}
}
let client = socket(AF_INET, SOCK_STREAM, 0)?;
unsafe {
if connect(
client.as_raw_handle() as _,
std::mem::transmute(&in_addr),
addr_len,
) != 0
{
bail!("connect failed: {}", IoError::last_os_error());
}
}
let server = unsafe { accept(s.as_raw_handle() as _, ptr::null_mut(), ptr::null_mut()) };
if server == INVALID_SOCKET {
bail!("socket failed: {}", IoError::last_os_error());
}
let server = FileDescriptor {
handle: OwnedHandle {
handle: server as _,
handle_type: HandleType::Socket,
},
};
Ok((server, client))
}
#[doc(hidden)]
pub fn poll_impl(pfd: &mut [pollfd], duration: Option<Duration>) -> anyhow::Result<usize> {
let poll_result = unsafe {
WSAPoll(
pfd.as_mut_ptr(),
pfd.len() as _,
duration
.map(|wait| wait.as_millis() as libc::c_int)
.unwrap_or(-1),
)
};
if poll_result < 0 {
Err(std::io::Error::last_os_error().into())
} else {
Ok(poll_result as usize)
}
}
#[cfg(test)]
mod test {
use std::io::{Read, Write};
#[test]
fn socketpair() {
let (mut a, mut b) = super::socketpair_impl().unwrap();
a.write(b"hello").unwrap();
let mut buf = [0u8; 5];
assert_eq!(b.read(&mut buf).unwrap(), 5);
assert_eq!(&buf, b"hello");
}
}