lio/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! # Lio - Platform-Independent Async I/O Library
4//!
5//! Lio is a high-performance, platform-independent async I/O library that uses
6//! the most efficient IO for each platform.
7//!
8//! ## Features
9//! - **Zero-copy operations** where possible.
10//! - **Automatic fallback** to blocking operations when async isn't supported.
11//! - **Manual control** with high level async API.
12//!
13//! *Note:* This is a quite low-level library. This library creates os resources
14//! (fd's) which it doesn't cleanup automatically.
15//!
16//! ## Platform support
17//!
18//! | Platform   | I/O Mechanism            | Status                  |
19//! |------------|--------------------------|-------------------------|
20//! | Linux      | io_uring                 | Yes                     |
21//! | Windows    | IOCP                     | Not supported (planned) |
22//! | macOS      | kqueue                   | Yes                     |
23//! | Other Unix | poll/epoll/event ports   | Yes                     |
24//!
25//!
26//! ## Quick Start
27//!
28//! ```rust
29//! # #![cfg(feature = "high")]
30//! use std::os::fd::RawFd;
31//!
32//! fn handle_result(result: std::io::Result<i32>, buf: Vec<u8>) {
33//!   println!("result: {result:?}, buf: {buf:?}");
34//! }
35//!
36//! async fn example() -> std::io::Result<()> {
37//!     let fd: RawFd = 1; // stdout
38//!     let data = b"Hello, World!\n".to_vec();
39//!
40//!     // Async API (on "high" feature flag).
41//!     let (result, buf) = lio::write(fd, data.clone(), 0).await;
42//!     handle_result(result, buf);
43//!
44//!     // Channel API (on "high" feature flag).
45//!     let receiver: oneshot::Receiver<(std::io::Result<i32>, Vec<u8>)> = lio::write(fd, data.clone(), 0).get_receiver();
46//!     let (result, buf) = receiver.recv().unwrap();
47//!     handle_result(result, buf);
48//!
49//!     // Callback API.
50//!     lio::write(fd, data.clone(), 0).when_done(|(result, buf)| {
51//!       handle_result(result, buf);
52//!     });
53//!
54//!     Ok(())
55//! }
56//! ```
57//!
58//! **Note**: Only one of these API's can be used for one operation.
59//!
60//! ## Safety and Threading
61//!
62//! - The library handles thread management for background I/O processing
63//! - Operations can be safely used across different threads
64//!
65//! ## Error Handling
66//!
67//! All operations return [`std::io::Result`] or [`BufResult`] for operations
68//! that return buffers. Errors are automatically converted from platform-specific
69//! error codes to Rust's standard I/O error types.
70
71#[cfg(feature = "unstable_ffi")]
72pub mod ffi;
73use std::{
74  ffi::{CString, NulError},
75  net::SocketAddr,
76  os::fd::RawFd,
77};
78
79/// Result type for operations that return both a result and a buffer.
80///
81/// This is commonly used for read/write operations where the buffer
82/// is returned along with the operation result.
83///
84/// Example: See [`lio::read`](crate::read).
85pub type BufResult<T, B> = (std::io::Result<T>, B);
86
87#[macro_use]
88mod macros;
89
90mod driver;
91
92pub mod op;
93use op::*;
94
95mod op_progress;
96mod op_registration;
97
98pub use op_progress::OperationProgress;
99
100use crate::driver::Driver;
101use std::path::Path;
102
103macro_rules! impl_op {
104  // Internal helper: Generate function with common documentation
105  (@impl_fn
106    { $desc:expr, $operation:ident, $name:ident, [$($arg:ident : $arg_ty:ty),*], $ret:ty }
107    $( #[$($doc:tt)*] )*
108    { $($body:tt)* }
109  ) => {
110    #[doc = $desc]
111    #[doc = "# Returns"]
112    #[doc = concat!("This function returns `OperationProgress<", stringify!($operation), ">`.")]
113    #[doc = "This function signature is equivalent to:"]
114    #[doc = concat!("```ignore\nasync fn ",stringify!($name), "(", stringify!($($arg_ty),*), ") -> ", stringify!($ret), "\n```")]
115    #[doc = "# Behavior"]
116    #[doc = "As soon as this function is called, the operation is submitted into the io-driver used by the current platform (for example io-uring). If the user then chooses to drop [`OperationProgress`] before the [`Future`] is ready, the operation will **NOT** tried be cancelled, but instead \"detached\"."]
117    #[doc = "\n\nSee more [what methods are available to the return type](crate::OperationProgress#impl-OperationProgress<T>)."]
118    $( #[$($doc)*] )*
119    $($body)*
120  };
121
122  // !detach variant with error type
123  (
124    !detach $desc:tt,
125    $(#[$($doc:tt)*])*
126    $operation:ident, fn $name:ident ( $($arg:ident: $arg_ty:ty),* ) -> $ret:ty ; $err:ty
127  ) => {
128    impl_op!(
129      concat!($desc, "\n\n### Detach safe\n This method is not [`detach safe`](crate::DetachSafe), which means that resources _**will**_ leak if not handled carefully."),
130      $(#[$($doc)*])*
131      $operation,
132      fn $name($($arg: $arg_ty),*) -> $ret:ty ; $err:ty
133    );
134  };
135
136  // !detach variant without error type
137  (
138    !detach $desc:tt,
139    $(#[$($doc:tt)*])*
140    $operation:ident, fn $name:ident ( $($arg:ident: $arg_ty:ty),* ) -> $ret:ty
141  ) => {
142    impl_op!(
143      concat!($desc, "\n\n### Detach safe\n This method is not [`detach safe`](crate::DetachSafe), which means that resources _**will**_ leak if not handled carefully."),
144      $(#[$($doc)*])*
145      $operation,
146      fn $name($($arg: $arg_ty),*) -> $ret
147    );
148  };
149
150  // With error type - fallible constructor
151  (
152    $desc:expr,
153    $(#[$($doc:tt)*])*
154    $operation:ident, fn $name:ident ( $($arg:ident: $arg_ty:ty),* ) -> $ret:ty ; $err:ty
155  ) => {
156    use op::$operation;
157
158    impl_op!(@impl_fn
159      { $desc, $operation, $name, [$($arg : $arg_ty),*], $ret }
160      $( #[$($doc)*] )*
161      {
162        pub fn $name($($arg: $arg_ty),*) -> Result<OperationProgress<$operation>, $err> {
163          Ok(Driver::submit($operation::new($($arg),*)?))
164        }
165      }
166    );
167  };
168
169  // Without error type - infallible constructor
170  (
171    $desc:expr,
172    $(#[$($doc:tt)*])*
173    $operation:ident, fn $name:ident ( $($arg:ident: $arg_ty:ty),* ) -> $ret:ty
174  ) => {
175    impl_op!(@impl_fn
176      { $desc, $operation, $name, [$($arg : $arg_ty),*], $ret }
177      $( #[$($doc)*] )*
178      {
179        pub fn $name($($arg: $arg_ty),*) -> OperationProgress<$operation> {
180          Driver::submit($operation::new($($arg),*))
181        }
182      }
183    );
184  };
185
186  // Convenience: no description provided
187  (
188    $(#[$($doc:tt)*])*
189    $operation:ty, fn $name:ident ( $($arg:ident: $arg_ty:ty),* ) -> $ret:ty
190  ) => {
191    impl_op!("", $(#[$($doc)*])* $operation, fn $name($($arg: $arg_ty),*) -> $ret);
192  };
193}
194
195#[cfg(linux)]
196use std::time::Duration;
197
198impl_op!(
199 "Shuts socket down.",
200 /// # Examples
201 ///
202 /// ```rust
203 /// async fn write_example() -> std::io::Result<()> {
204 ///     let socket = lio::socket(socket2::Domain::IPV4, socket2::Type::STREAM, None).await?;
205 ///     let how = 0;
206 ///     lio::shutdown(socket, how).await?;
207 ///     Ok(())
208 /// }
209 /// ```
210 Shutdown, fn shutdown(fd: RawFd, how: i32) -> io::Result<()>
211);
212
213#[cfg(linux)]
214#[cfg_attr(docsrs, doc(cfg(linux)))]
215impl_op!(
216  "Times out something",
217  /// # Examples
218  ///
219  /// ```rust
220  /// use lio::timeout;
221  /// use std::{time::Duration, os::fd::RawFd};
222  ///
223  /// async fn write_example() -> std::io::Result<()> {
224  ///     timeout(Duration::from_millis(10)).await?;
225  ///     Ok(())
226  /// }
227  /// ```
228  Timeout, fn timeout(duration: Duration) -> io::Result<()>
229);
230
231impl_op!(
232  "Create a soft-link",
233  /// # Examples
234  ///
235  /// ```rust
236  /// async fn write_example() -> std::io::Result<()> {
237  ///     # let fd = 0;
238  ///     // todo
239  // ///     let (bytes_written, _buf) = lio::symlinkat(fd).await?;
240  ///     Ok(())
241  /// }
242  /// ```
243  SymlinkAt, fn symlinkat(new_dir_fd: RawFd, target: impl AsRef<Path>, linkpath: impl AsRef<Path>) -> io::Result<()> ; NulError
244);
245
246impl_op!(
247  "Create a hard-link",
248  /// # Examples
249  ///
250  /// ```rust
251  /// async fn write_example() -> std::io::Result<()> {
252  ///     # let fd = 0;
253  ///       // todo
254  // ///     let (bytes_written, _buf) = lio::linkat(fd)?.await?;
255  ///     Ok(())
256  /// }
257  /// ```
258  LinkAt, fn linkat(old_dir_fd: RawFd, old_path: impl AsRef<Path>, new_dir_fd: RawFd, new_path: impl AsRef<Path>) -> io::Result<()> ; NulError
259);
260
261impl_op!(
262  "Sync to fd.",
263  /// # Examples
264  ///
265  /// ```rust
266  /// async fn write_example() -> std::io::Result<()> {
267  ///     # let fd = 0;
268  ///     lio::fsync(fd).await?;
269  ///     Ok(())
270  /// }
271  /// ```
272  Fsync, fn fsync(fd: RawFd) -> io::Result<()>
273);
274
275impl_op!(
276  "Performs a write operation on a file descriptor. Equivalent to the `pwrite` syscall.",
277  /// # Examples
278  ///
279  /// ```rust
280  /// async fn write_example() -> std::io::Result<()> {
281  ///     # let fd = 0;
282  ///     let data = b"Hello, World!".to_vec();
283  ///     let (result_bytes_written, _buf) = lio::write(fd, data, 0).await;
284  ///     println!("Wrote {} bytes", result_bytes_written?);
285  ///     Ok(())
286  /// }
287  /// ```
288  Write, fn write(fd: RawFd, buf: Vec<u8>, offset: i64) -> BufResult<i32, Vec<u8>>
289);
290
291impl_op!(
292  "Performs a read operation on a file descriptor. Equivalent of the `pread` syscall.",
293  /// # Examples
294  ///
295  /// ```rust
296  /// async fn read_example() -> std::io::Result<()> {
297  ///     # let fd = 0;
298  ///     let mut buffer = vec![0u8; 1024];
299  ///     let (res_bytes_read, buf) = lio::read(fd, buffer, 0).await;
300  ///     let bytes_read = res_bytes_read?;
301  ///     println!("Read {} bytes: {:?}", bytes_read, &buf[..bytes_read as usize]);
302  ///     Ok(())
303  /// }
304  /// ```
305  Read, fn read(fd: RawFd, mem: Vec<u8>, offset: i64) -> BufResult<i32, Vec<u8>>
306);
307
308impl_op!(
309  "Truncates a file to a specified length.",
310  /// # Examples
311  ///
312  /// ```rust
313  /// async fn truncate_example() -> std::io::Result<()> {
314  ///     # let fd = 0;
315  ///     lio::truncate(fd, 1024).await?; // Truncate to 1KB
316  ///     Ok(())
317  /// }
318  /// ```
319  Truncate, fn truncate(fd: RawFd, len: u64) -> std::io::Result<()>
320);
321
322impl_op!(
323  !detach
324  "Creates a new socket with the specified domain, type, and protocol.",
325  /// # Examples
326  ///
327  /// ```rust
328  /// use socket2::{Domain, Type, Protocol};
329  ///
330  /// async fn socket_example() -> std::io::Result<()> {
331  ///     let sock = lio::socket(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).await?;
332  ///     println!("Created socket with fd: {}", sock);
333  ///     Ok(())
334  /// }
335  /// ```
336  Socket, fn socket(domain: socket2::Domain, ty: socket2::Type, proto: Option<socket2::Protocol>) -> std::io::Result<i32>
337);
338
339impl_op!(
340  "Binds a socket to a specific address.",
341  /// # Examples
342  ///
343  /// ```rust
344  /// use socket2::SockAddr;
345  ///
346  /// async fn bind_example() -> std::io::Result<()> {
347  ///     # let fd = 0;
348  ///     let addr = "127.0.0.1:8080".parse::<std::net::SocketAddr>().unwrap();
349  ///     lio::bind(fd, addr).await?;
350  ///     Ok(())
351  /// }
352  ///
353  /// ```
354  Bind, fn bind(fd: RawFd, addr: SocketAddr) -> std::io::Result<()>
355);
356
357impl_op!(
358  !detach
359  "Accepts a connection on a listening socket.",
360  /// # Examples
361  ///
362  /// ```rust
363  /// use std::mem::MaybeUninit;
364  ///
365  /// async fn accept_example() -> std::io::Result<()> {
366  ///     # let fd = 0;
367  ///
368  ///     let (client_fd, addr) = lio::accept(fd).await?;
369  ///     println!("Accepted connection on fd: {}", client_fd);
370  ///     Ok(())
371  /// }
372  /// ```
373  Accept, fn accept(fd: RawFd) -> std::io::Result<(RawFd, SocketAddr)>
374);
375
376impl_op!(
377  "Marks a socket as listening for incoming connections.",
378  /// # Examples
379  ///
380  /// ```rust
381  /// use std::os::fd::RawFd;
382  ///
383  /// async fn listen_example() -> std::io::Result<()> {
384  ///     # let fd = 0;
385  ///     lio::listen(fd, 128).await?;
386  ///     println!("Socket is now listening for connections");
387  ///     Ok(())
388  /// }
389  /// ```
390  Listen, fn listen(fd: RawFd, backlog: i32) -> std::io::Result<()>
391);
392
393impl_op!(
394  "Connects a socket to a remote address.",
395  /// # Examples
396  ///
397  /// ```rust
398  /// use std::net::SocketAddr;
399  ///
400  /// async fn connect_example() -> std::io::Result<()> {
401  ///     # let fd = 0;
402  ///     let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
403  ///     lio::connect(fd, addr).await?;
404  ///     println!("Connected to remote address");
405  ///     Ok(())
406  /// }
407  /// ```
408  Connect, fn connect(fd: RawFd, addr: SocketAddr) -> std::io::Result<()>
409);
410
411impl_op!(
412  "Sends data on a connected socket.",
413  /// # Examples
414  ///
415  /// ```rust
416  /// async fn send_example() -> std::io::Result<()> {
417  ///     # let fd = 0;
418  ///     let data = b"Hello, server!".to_vec();
419  ///     let (bytes_sent, _buf) = lio::send(fd, data, None).await;
420  ///     println!("Sent {} bytes", bytes_sent?);
421  ///     Ok(())
422  /// }
423  /// ```
424  Send, fn send(fd: RawFd, buf: Vec<u8>, flags: Option<i32>) -> BufResult<i32, Vec<u8>>
425);
426
427impl_op!(
428  "Receives data from a connected socket.",
429  /// # Examples
430  ///
431  /// ```rust
432  /// async fn recv_example() -> std::io::Result<()> {
433  ///     # let fd = 0;
434  ///     let mut buffer = vec![0u8; 1024];
435  ///     let (res_bytes_received, buf) = lio::recv(fd, buffer, None).await;
436  ///     let bytes_received = res_bytes_received?;
437  ///     println!("Received {} bytes: {:?}", bytes_received, &buf[..bytes_received as usize]);
438  ///     Ok(())
439  /// }
440  /// ```
441  Recv, fn recv(fd: RawFd, buf: Vec<u8>, flags: Option<i32>) -> BufResult<i32, Vec<u8>>
442);
443
444impl_op!(
445  "Closes a file descriptor.",
446  /// # Examples
447  ///
448  /// ```rust
449  /// async fn close_example() -> std::io::Result<()> {
450  ///     # let fd = 0;
451  ///     lio::close(fd).await?;
452  ///     println!("File descriptor closed successfully");
453  ///     Ok(())
454  /// }
455  /// ```
456  Close, fn close(fd: RawFd) -> io::Result<()>
457);
458
459impl_op!(
460  "Opens a file relative to a directory file descriptor.",
461  /// # Examples
462  ///
463  /// ```rust
464  /// use std::ffi::CString;
465  ///
466  /// async fn openat_example() -> std::io::Result<()> {
467  ///     let path = CString::new("/tmp/test.txt").unwrap();
468  ///     let fd = lio::openat(libc::AT_FDCWD, path, libc::O_RDONLY).await?;
469  ///     println!("Opened file with fd: {}", fd);
470  ///     Ok(())
471  /// }
472  /// ```
473  OpenAt, fn openat(fd: RawFd, path: CString, flags: i32) -> std::io::Result<i32>
474);
475
476#[cfg(linux)]
477#[cfg_attr(docsrs, doc(cfg(linux)))]
478impl_op!(
479  "Copies data between file descriptors without copying to userspace (Linux only).",
480  /// This operation is only available on Linux systems with io_uring support.
481  /// It's equivalent to the `tee(2)` system call.
482  ///
483  /// # Examples
484  ///
485  /// ```rust
486  /// #[cfg(linux)]
487  /// async fn tee_example() -> std::io::Result<()> {
488  ///     # let fd_in = 0;
489  ///     # let fd_out = 0;
490  ///     let bytes_copied = lio::tee(fd_in, fd_out, 1024).await?;
491  ///     println!("Copied {} bytes", bytes_copied);
492  ///     Ok(())
493  /// }
494  /// ```
495  Tee, fn tee(fd_in: RawFd, fd_out: RawFd, size: u32) -> std::io::Result<()>
496);
497
498/// Shut down the lio I/O driver background thread(s) and release OS resources.
499///
500/// After calling this, further I/O operations in this process are unsupported.
501/// Calling shutdown more than once will panic.
502pub fn exit() {
503  Driver::shutdown()
504}
505
506// #[cfg(any(loom, test))]
507#[doc(hidden)]
508pub fn init() {
509  let _ = Driver::get();
510}