1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
//! 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, Result};
//! use std::io::Write;
//!
//! fn get_stdout() -> Result<FileDescriptor> {
//!   let stdout = std::io::stdout();
//!   let handle = stdout.lock();
//!   FileDescriptor::dup(&handle)
//! }
//!
//! fn print_something() -> 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, Error};
//! use std::io::{Read, Write};
//!
//! 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 filedescriptor::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 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::*;

use thiserror::Error;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum Error {
    #[error("failed to create a pipe")]
    Pipe(#[source] std::io::Error),
    #[error("failed to create a socketpair")]
    Socketpair(#[source] std::io::Error),
    #[error("failed to create a socket")]
    Socket(#[source] std::io::Error),
    #[error("failed to bind a socket")]
    Bind(#[source] std::io::Error),
    #[error("failed to fetch socket name")]
    Getsockname(#[source] std::io::Error),
    #[error("failed to set socket to listen mode")]
    Listen(#[source] std::io::Error),
    #[error("failed to connect socket")]
    Connect(#[source] std::io::Error),
    #[error("failed to accept socket")]
    Accept(#[source] std::io::Error),
    #[error("fcntl read failed")]
    Fcntl(#[source] std::io::Error),
    #[error("failed to set cloexec")]
    Cloexec(#[source] std::io::Error),
    #[error("failed to change non-blocking mode")]
    FionBio(#[source] std::io::Error),
    #[error("poll failed")]
    Poll(#[source] std::io::Error),
    #[error("dup of fd {fd} failed")]
    Dup { fd: i64, source: std::io::Error },
    #[error("dup of fd {src_fd} to fd {dest_fd} failed")]
    Dup2 {
        src_fd: i64,
        dest_fd: i64,
        source: std::io::Error,
    },
    #[error("Illegal fd value {0}")]
    IllegalFdValue(i64),
    #[error("fd value {0} too large to use with select(2)")]
    FdValueOutsideFdSetSize(i64),
    #[error("Only socket descriptors can change their non-blocking mode on Windows")]
    OnlySocketsNonBlocking,
    #[error("SetStdHandle failed")]
    SetStdHandle(#[source] std::io::Error),

    #[error("IoError")]
    Io(#[from] std::io::Error),
}

pub type Result<T> = std::result::Result<T, Error>;

/// `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) -> 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) -> 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, Result};
/// use std::io::Write;
///
/// fn get_stdout() -> Result<FileDescriptor> {
///   let stdout = std::io::stdout();
///   let handle = stdout.lock();
///   FileDescriptor::dup(&handle)
/// }
///
/// fn print_something() -> Result<()> {
///    get_stdout()?.write(b"hello")?;
///    Ok(())
/// }
/// ```
#[derive(Debug)]
pub struct FileDescriptor {
    handle: OwnedHandle,
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum StdioDescriptor {
    Stdin,
    Stdout,
    Stderr,
}

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) -> 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) -> 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) -> 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) -> Result<()> {
        self.set_non_blocking_impl(non_blocking)
    }

    /// Attempt to redirect stdio to the underlying handle and return
    /// a `FileDescriptor` wrapped around the original stdio source.
    /// Since the redirection requires kernel resources that may not be
    /// available, this is a potentially fallible operation.
    /// Supports stdin, stdout, and stderr redirections.
    pub fn redirect_stdio<F: AsRawFileDescriptor>(f: &F, stdio: StdioDescriptor) -> Result<Self> {
        Self::redirect_stdio_impl(f, stdio)
    }
}

/// Represents the readable and writable ends of a pair of descriptors
/// connected via a kernel pipe.
///
/// ```
/// use filedescriptor::{Pipe, Error};
/// use std::io::{Read,Write};
///
/// 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>) -> Result<usize> {
    poll_impl(pfd, duration)
}

/// Create a pair of connected sockets
///
/// This implementation creates a pair of SOCK_STREAM sockets.
pub fn socketpair() -> Result<(FileDescriptor, FileDescriptor)> {
    socketpair_impl()
}