diff --git a/util/src/lib.rs b/util/src/lib.rs index ca65b47..66d2387 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -22,7 +22,7 @@ pub mod io; pub mod length_prefix_encoding; /// Memory manipulation and allocation utilities. pub mod mem; -/// MIO integration utilities. +/// [MIO (Metal I/O)](https://docs.rs/crate/mio/) integration utilities. pub mod mio; /// Extended Option type functionality. pub mod option; diff --git a/util/src/mio/mio.rs b/util/src/mio/mio.rs index 1df9310..97c724b 100644 --- a/util/src/mio/mio.rs +++ b/util/src/mio/mio.rs @@ -6,7 +6,7 @@ use crate::{ result::OkExt, }; -/// Module containing I/O interest flags for Unix operations +/// Module containing I/O interest flags for Unix operations (see also: [mio::Interest]) pub mod interest { use mio::Interest; @@ -20,9 +20,48 @@ pub mod interest { pub const RW: Interest = R.add(W); } -/// Extension trait providing additional functionality for Unix listener +/// Extension trait providing additional functionality for a Unix listener +/// +/// # Example +/// +/// ```rust +/// use mio::net::{UnixListener, UnixStream}; +/// use rosenpass_util::mio::{UnixListenerExt, UnixStreamExt}; +/// +/// use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; +/// use std::path::Path; +/// +/// // This would be the UDS created by an external source +/// let socket_path = "/tmp/rp_mio_uds_test_socket"; +/// if Path::new(socket_path).exists() { +/// std::fs::remove_file(socket_path).expect("Failed to remove existing socket"); +/// } +/// +/// // An extended MIO listener can then be created by claiming the existing socket +/// // Note that the original descriptor is not reused, but copied before claiming it here +/// let listener = UnixListener::bind(socket_path).unwrap(); +/// let listener_fd: RawFd = listener.as_raw_fd(); +/// let ext_listener = +/// ::claim_fd(listener_fd).expect("Failed to claim_fd for ext_listener socket"); +/// +/// // Similarly, "client" connections can be established by claiming existing sockets +/// // Note that in this case, the file descriptor will be reused (safety implications!) +/// let stream = UnixStream::connect(socket_path).unwrap(); +/// let stream_fd = stream.into_raw_fd(); +/// let ext_stream = +/// ::claim_fd_inplace(stream_fd).expect("Failed to claim_fd_inplace for ext_stream socket"); +/// +/// // Handle accepted connections... +/// ext_listener.accept().expect("Failed to accept incoming connection"); +/// +/// // Send or receive messages ... +/// +/// // Cleanup, shutdown etc. goes here ... +/// std::fs::remove_file(socket_path).unwrap(); +/// ``` pub trait UnixListenerExt: Sized { /// Creates a new Unix listener by claiming ownership of a raw file descriptor + /// (see [fd::claim_fd](crate::fd::claim_fd)) fn claim_fd(fd: RawFd) -> anyhow::Result; } @@ -36,15 +75,17 @@ impl UnixListenerExt for UnixListener { } } -/// Extension trait providing additional functionality for Unix streams +/// Extension trait providing additional functionality for a Unix stream pub trait UnixStreamExt: Sized { /// Creates a new Unix stream from an owned file descriptor fn from_fd(fd: OwnedFd) -> anyhow::Result; /// Claims ownership of a raw file descriptor and creates a new Unix stream + /// (see [fd::claim_fd](crate::fd::claim_fd)) fn claim_fd(fd: RawFd) -> anyhow::Result; /// Claims ownership of a raw file descriptor in place and creates a new Unix stream + /// (see [fd::claim_fd_inplace](crate::fd::claim_fd_inplace)) fn claim_fd_inplace(fd: RawFd) -> anyhow::Result; } diff --git a/util/src/mio/uds_recv_fd.rs b/util/src/mio/uds_recv_fd.rs index 834b10c..8cba2a3 100644 --- a/util/src/mio/uds_recv_fd.rs +++ b/util/src/mio/uds_recv_fd.rs @@ -12,6 +12,58 @@ use crate::fd::{claim_fd_inplace, IntoStdioErr}; /// A wrapper around a socket that combines reading from the socket with tracking /// received file descriptors. Limits the maximum number of file descriptors that /// can be received in a single read operation via the `MAX_FDS` parameter. +/// +/// # Example +/// +/// ```rust +/// use std::collections::VecDeque; +/// use std::io::Cursor; +/// use std::io::Read; +/// use std::os::fd::AsRawFd; +/// use std::os::fd::OwnedFd; +/// +/// use mio::net::UnixStream; +/// use rosenpass_util::mio::ReadWithFileDescriptors; +/// use rosenpass_util::io::TryIoResultKindHintExt; +/// +/// const MAX_REQUEST_FDS : usize = 2; // Limit to 2 descriptors per read operation +/// let mut read_fd_buffer = VecDeque::::new(); // File descriptor queue +/// +/// // In this case, the unused writable end of the connection can be ignored +/// let (io_stream, _) = UnixStream::pair().expect("failed to create socket pair"); +/// +/// // Wait until the output stream is writable... +/// +/// // Wrap the socket to start tracking received file descriptors +/// let mut fd_passing_sock = ReadWithFileDescriptors::::new( +/// &io_stream, +/// &mut read_fd_buffer, +/// ); +//// +/// // Simulated reads; the actual operations will depend on the protocol (implementation details) +/// let mut recv_buffer = Vec::::new(); +/// let bytes_read = fd_passing_sock.read(&mut recv_buffer[..]).expect("error reading from socket"); +/// assert_eq!(bytes_read, 0); +/// assert_eq!(&recv_buffer[..bytes_read], []); +/// +/// // Alternatively, it's possible to use the try_io_err_kind_hint utility provided by this crate +/// match fd_passing_sock.read(&mut recv_buffer).try_io_err_kind_hint() { +/// Err(_) => { +/// // Handle errors here ... +/// } +/// Ok(result) => { +/// // Process messages here ... +/// assert_eq!(0, result); // Nothing to read in this example +/// } +/// }; +/// +/// // The wrapped components can still be accessed +/// assert_eq!(fd_passing_sock.socket().as_raw_fd(), io_stream.as_raw_fd()); +/// let (socket, fd_queue) = fd_passing_sock.into_parts(); +/// assert_eq!(socket.as_raw_fd(), io_stream.as_raw_fd()); +/// +/// // Shutdown, cleanup, etc. goes here ... +/// ``` pub struct ReadWithFileDescriptors where Sock: FdPassingExt,