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}