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}