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
//! 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() }