filedescriptor/
lib.rs

1//! The purpose of this crate is to make it a bit more ergonomic for portable
2//! applications that need to work with the platform level `RawFd` and
3//! `RawHandle` types.
4//!
5//! Rather than conditionally using `RawFd` and `RawHandle`, the `FileDescriptor`
6//! type can be used to manage ownership, duplicate, read and write.
7//!
8//! ## FileDescriptor
9//!
10//! This is a bit of a contrived example, but demonstrates how to avoid
11//! the conditional code that would otherwise be required to deal with
12//! calling `as_raw_fd` and `as_raw_handle`:
13//!
14//! ```
15//! use filedescriptor::{FileDescriptor, FromRawFileDescriptor, Result};
16//! use std::io::Write;
17//!
18//! fn get_stdout() -> Result<FileDescriptor> {
19//!   let stdout = std::io::stdout();
20//!   let handle = stdout.lock();
21//!   FileDescriptor::dup(&handle)
22//! }
23//!
24//! fn print_something() -> Result<()> {
25//!    get_stdout()?.write(b"hello")?;
26//!    Ok(())
27//! }
28//! ```
29//!
30//! ## Pipe
31//! The `Pipe` type makes it more convenient to create a pipe and manage
32//! the lifetime of both the read and write ends of that pipe.
33//!
34//! ```
35//! use filedescriptor::{Pipe, Error};
36//! use std::io::{Read, Write};
37//!
38//! let mut pipe = Pipe::new()?;
39//! pipe.write.write(b"hello")?;
40//! drop(pipe.write);
41//!
42//! let mut s = String::new();
43//! pipe.read.read_to_string(&mut s)?;
44//! assert_eq!(s, "hello");
45//! # Ok::<(), Error>(())
46//! ```
47//!
48//! ## Socketpair
49//! The `socketpair` function returns a pair of connected `SOCK_STREAM`
50//! sockets and functions both on posix and windows systems.
51//!
52//! ```
53//! use std::io::{Read, Write};
54//! use filedescriptor::Error;
55//!
56//! let (mut a, mut b) = filedescriptor::socketpair()?;
57//! a.write(b"hello")?;
58//! drop(a);
59//!
60//! let mut s = String::new();
61//! b.read_to_string(&mut s)?;
62//! assert_eq!(s, "hello");
63//! # Ok::<(), Error>(())
64//! ```
65//!
66//! ## Polling
67//! The `mio` crate offers powerful and scalable IO multiplexing, but there
68//! are some situations where `mio` doesn't fit.  The `filedescriptor` crate
69//! offers a `poll(2)` compatible interface suitable for testing the readiness
70//! of a set of file descriptors.  On unix systems this is a very thin wrapper
71//! around `poll(2)`, except on macOS where it is actually a wrapper around
72//! the `select(2)` interface.  On Windows systems the winsock `WSAPoll`
73//! function is used instead.
74//!
75//! ```
76//! use filedescriptor::*;
77//! use std::time::Duration;
78//! use std::io::{Read, Write};
79//!
80//! let (mut a, mut b) = filedescriptor::socketpair()?;
81//! let mut poll_array = [pollfd {
82//!    fd: a.as_socket_descriptor(),
83//!    events: POLLIN,
84//!    revents: 0
85//! }];
86//! // sleeps for 20 milliseconds because `a` is not yet ready
87//! assert_eq!(poll(&mut poll_array, Some(Duration::from_millis(20)))?, 0);
88//!
89//! b.write(b"hello")?;
90//!
91//! // Now a is ready for read
92//! assert_eq!(poll(&mut poll_array, Some(Duration::from_millis(20)))?, 1);
93//!
94//! # Ok::<(), Error>(())
95//! ```
96
97#[cfg(unix)]
98mod unix;
99#[cfg(unix)]
100pub use crate::unix::*;
101
102#[cfg(windows)]
103mod windows;
104#[cfg(windows)]
105pub use crate::windows::*;
106
107use thiserror::Error;
108#[derive(Error, Debug)]
109#[non_exhaustive]
110pub enum Error {
111    #[error("failed to create a pipe")]
112    Pipe(#[source] std::io::Error),
113    #[error("failed to create a socketpair")]
114    Socketpair(#[source] std::io::Error),
115    #[error("failed to create a socket")]
116    Socket(#[source] std::io::Error),
117    #[error("failed to bind a socket")]
118    Bind(#[source] std::io::Error),
119    #[error("failed to fetch socket name")]
120    Getsockname(#[source] std::io::Error),
121    #[error("failed to set socket to listen mode")]
122    Listen(#[source] std::io::Error),
123    #[error("failed to connect socket")]
124    Connect(#[source] std::io::Error),
125    #[error("failed to accept socket")]
126    Accept(#[source] std::io::Error),
127    #[error("fcntl read failed")]
128    Fcntl(#[source] std::io::Error),
129    #[error("failed to set cloexec")]
130    Cloexec(#[source] std::io::Error),
131    #[error("failed to change non-blocking mode")]
132    FionBio(#[source] std::io::Error),
133    #[error("poll failed")]
134    Poll(#[source] std::io::Error),
135    #[error("dup of fd {fd} failed")]
136    Dup { fd: i64, source: std::io::Error },
137    #[error("dup of fd {src_fd} to fd {dest_fd} failed")]
138    Dup2 {
139        src_fd: i64,
140        dest_fd: i64,
141        source: std::io::Error,
142    },
143    #[error("Illegal fd value {0}")]
144    IllegalFdValue(i64),
145    #[error("fd value {0} too large to use with select(2)")]
146    FdValueOutsideFdSetSize(i64),
147    #[error("Only socket descriptors can change their non-blocking mode on Windows")]
148    OnlySocketsNonBlocking,
149    #[error("SetStdHandle failed")]
150    SetStdHandle(#[source] std::io::Error),
151
152    #[error("IoError")]
153    Io(#[from] std::io::Error),
154}
155
156pub type Result<T> = std::result::Result<T, Error>;
157
158/// `AsRawFileDescriptor` is a platform independent trait for returning
159/// a non-owning reference to the underlying platform file descriptor
160/// type.
161pub trait AsRawFileDescriptor {
162    fn as_raw_file_descriptor(&self) -> RawFileDescriptor;
163}
164
165/// `IntoRawFileDescriptor` is a platform independent trait for converting
166/// an instance into the underlying platform file descriptor type.
167pub trait IntoRawFileDescriptor {
168    fn into_raw_file_descriptor(self) -> RawFileDescriptor;
169}
170
171/// `FromRawFileDescriptor` is a platform independent trait for creating
172/// an instance from the underlying platform file descriptor type.
173/// Because the platform file descriptor type has no inherent ownership
174/// management, the `from_raw_file_descriptor` function is marked as unsafe
175/// to indicate that care must be taken by the caller to ensure that it
176/// is used appropriately.
177pub trait FromRawFileDescriptor {
178    unsafe fn from_raw_file_descriptor(fd: RawFileDescriptor) -> Self;
179}
180
181pub trait AsRawSocketDescriptor {
182    fn as_socket_descriptor(&self) -> SocketDescriptor;
183}
184pub trait IntoRawSocketDescriptor {
185    fn into_socket_descriptor(self) -> SocketDescriptor;
186}
187pub trait FromRawSocketDescriptor {
188    unsafe fn from_socket_descriptor(fd: SocketDescriptor) -> Self;
189}
190
191/// `OwnedHandle` allows managing the lifetime of the platform `RawFileDescriptor`
192/// type.  It is exposed in the interface of this crate primarily for convenience
193/// on Windows where the system handle type is used for a variety of objects
194/// that don't support reading and writing.
195#[derive(Debug)]
196pub struct OwnedHandle {
197    handle: RawFileDescriptor,
198    handle_type: HandleType,
199}
200
201impl OwnedHandle {
202    /// Create a new handle from some object that is convertible into
203    /// the system `RawFileDescriptor` type.  This consumes the parameter
204    /// and replaces it with an `OwnedHandle` instance.
205    pub fn new<F: IntoRawFileDescriptor>(f: F) -> Self {
206        let handle = f.into_raw_file_descriptor();
207        Self {
208            handle,
209            handle_type: Self::probe_handle_type(handle),
210        }
211    }
212
213    /// Attempt to duplicate the underlying handle and return an
214    /// `OwnedHandle` wrapped around the duplicate.  Since the duplication
215    /// requires kernel resources that may not be available, this is a
216    /// potentially fallible operation.
217    /// The returned handle has a separate lifetime from the source, but
218    /// references the same object at the kernel level.
219    pub fn try_clone(&self) -> Result<Self> {
220        Self::dup_impl(self, self.handle_type)
221    }
222
223    /// Attempt to duplicate the underlying handle from an object that is
224    /// representable as the system `RawFileDescriptor` type and return an
225    /// `OwnedHandle` wrapped around the duplicate.  Since the duplication
226    /// requires kernel resources that may not be available, this is a
227    /// potentially fallible operation.
228    /// The returned handle has a separate lifetime from the source, but
229    /// references the same object at the kernel level.
230    pub fn dup<F: AsRawFileDescriptor>(f: &F) -> Result<Self> {
231        Self::dup_impl(f, Default::default())
232    }
233}
234
235/// `FileDescriptor` is a thin wrapper on top of the `OwnedHandle` type that
236/// exposes the ability to Read and Write to the platform `RawFileDescriptor`.
237///
238/// This is a bit of a contrived example, but demonstrates how to avoid
239/// the conditional code that would otherwise be required to deal with
240/// calling `as_raw_fd` and `as_raw_handle`:
241///
242/// ```
243/// use filedescriptor::{FileDescriptor, FromRawFileDescriptor, Result};
244/// use std::io::Write;
245///
246/// fn get_stdout() -> Result<FileDescriptor> {
247///   let stdout = std::io::stdout();
248///   let handle = stdout.lock();
249///   FileDescriptor::dup(&handle)
250/// }
251///
252/// fn print_something() -> Result<()> {
253///    get_stdout()?.write(b"hello")?;
254///    Ok(())
255/// }
256/// ```
257#[derive(Debug)]
258pub struct FileDescriptor {
259    handle: OwnedHandle,
260}
261
262#[derive(Debug, Clone, Copy, Eq, PartialEq)]
263pub enum StdioDescriptor {
264    Stdin,
265    Stdout,
266    Stderr,
267}
268
269impl FileDescriptor {
270    /// Create a new descriptor from some object that is convertible into
271    /// the system `RawFileDescriptor` type.  This consumes the parameter
272    /// and replaces it with a `FileDescriptor` instance.
273    pub fn new<F: IntoRawFileDescriptor>(f: F) -> Self {
274        let handle = OwnedHandle::new(f);
275        Self { handle }
276    }
277
278    /// Attempt to duplicate the underlying handle from an object that is
279    /// representable as the system `RawFileDescriptor` type and return a
280    /// `FileDescriptor` wrapped around the duplicate.  Since the duplication
281    /// requires kernel resources that may not be available, this is a
282    /// potentially fallible operation.
283    /// The returned handle has a separate lifetime from the source, but
284    /// references the same object at the kernel level.
285    pub fn dup<F: AsRawFileDescriptor>(f: &F) -> Result<Self> {
286        OwnedHandle::dup(f).map(|handle| Self { handle })
287    }
288
289    /// Attempt to duplicate the underlying handle and return a
290    /// `FileDescriptor` wrapped around the duplicate.  Since the duplication
291    /// requires kernel resources that may not be available, this is a
292    /// potentially fallible operation.
293    /// The returned handle has a separate lifetime from the source, but
294    /// references the same object at the kernel level.
295    pub fn try_clone(&self) -> Result<Self> {
296        self.handle.try_clone().map(|handle| Self { handle })
297    }
298
299    /// A convenience method for creating a `std::process::Stdio` object
300    /// to be used for eg: redirecting the stdio streams of a child
301    /// process.  The `Stdio` is created using a duplicated handle so
302    /// that the source handle remains alive.
303    pub fn as_stdio(&self) -> Result<std::process::Stdio> {
304        self.as_stdio_impl()
305    }
306
307    /// A convenience method for creating a `std::fs::File` object.
308    /// The `File` is created using a duplicated handle so
309    /// that the source handle remains alive.
310    pub fn as_file(&self) -> Result<std::fs::File> {
311        self.as_file_impl()
312    }
313
314    /// Attempt to change the non-blocking IO mode of the file descriptor.
315    /// Not all kinds of file descriptor can be placed in non-blocking mode
316    /// on all systems, and some file descriptors will claim to be in
317    /// non-blocking mode but it will have no effect.
318    /// File descriptors based on sockets are the most portable type
319    /// that can be successfully made non-blocking.
320    pub fn set_non_blocking(&mut self, non_blocking: bool) -> Result<()> {
321        self.set_non_blocking_impl(non_blocking)
322    }
323
324    /// Attempt to redirect stdio to the underlying handle and return
325    /// a `FileDescriptor` wrapped around the original stdio source.
326    /// Since the redirection requires kernel resources that may not be
327    /// available, this is a potentially fallible operation.
328    /// Supports stdin, stdout, and stderr redirections.
329    pub fn redirect_stdio<F: AsRawFileDescriptor>(f: &F, stdio: StdioDescriptor) -> Result<Self> {
330        Self::redirect_stdio_impl(f, stdio)
331    }
332}
333
334/// Represents the readable and writable ends of a pair of descriptors
335/// connected via a kernel pipe.
336///
337/// ```
338/// use filedescriptor::{Pipe, Error};
339/// use std::io::{Read,Write};
340///
341/// let mut pipe = Pipe::new()?;
342/// pipe.write.write(b"hello")?;
343/// drop(pipe.write);
344///
345/// let mut s = String::new();
346/// pipe.read.read_to_string(&mut s)?;
347/// assert_eq!(s, "hello");
348/// # Ok::<(), Error>(())
349/// ```
350pub struct Pipe {
351    /// The readable end of the pipe
352    pub read: FileDescriptor,
353    /// The writable end of the pipe
354    pub write: FileDescriptor,
355}
356
357use std::time::Duration;
358
359/// Examines a set of FileDescriptors to see if some of them are ready for I/O,
360/// or if certain events have occurred on them.
361///
362/// This uses the system native readiness checking mechanism, which on Windows
363/// means that it does NOT use IOCP and that this only works with sockets on
364/// Windows.  If you need IOCP then the `mio` crate is recommended for a much
365/// more scalable solution.
366///
367/// On macOS, the `poll(2)` implementation has problems when used with eg: pty
368/// descriptors, so this implementation of poll uses the `select(2)` interface
369/// under the covers.  That places a limit on the maximum file descriptor value
370/// that can be passed to poll.  If a file descriptor is out of range then an
371/// error will returned.  This limitation could potentially be lifted in the
372/// future.
373///
374/// On Windows, `WSAPoll` is used to implement readiness checking, which has
375/// the consequence that it can only be used with sockets.
376///
377/// If `duration` is `None`, then `poll` will block until any of the requested
378/// events are ready.  Otherwise, `duration` specifies how long to wait for
379/// readiness before giving up.
380///
381/// The return value is the number of entries that were satisfied; `0` means
382/// that none were ready after waiting for the specified duration.
383///
384/// The `pfd` array is mutated and the `revents` field is updated to indicate
385/// which of the events were received.
386pub fn poll(pfd: &mut [pollfd], duration: Option<Duration>) -> Result<usize> {
387    poll_impl(pfd, duration)
388}
389
390/// Create a pair of connected sockets
391///
392/// This implementation creates a pair of SOCK_STREAM sockets.
393pub fn socketpair() -> Result<(FileDescriptor, FileDescriptor)> {
394    socketpair_impl()
395}