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:
commit
872d350ac5
26
filedescriptor/Cargo.toml
Normal file
26
filedescriptor/Cargo.toml
Normal 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
98
filedescriptor/README.md
Normal 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
324
filedescriptor/src/lib.rs
Normal 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
481
filedescriptor/src/unix.rs
Normal 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;
|
531
filedescriptor/src/windows.rs
Normal file
531
filedescriptor/src/windows.rs
Normal 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");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user