| //! epoll support. |
| //! |
| //! This is an experiment, and it isn't yet clear whether epoll is the right |
| //! level of abstraction at which to introduce safety. But it works fairly well |
| //! in simple examples 🙂. |
| //! |
| //! # Examples |
| //! |
| //! ```rust,no_run |
| //! # #![cfg_attr(io_lifetimes_use_std, feature(io_safety))] |
| //! # #[cfg(feature = "net")] |
| //! # fn main() -> std::io::Result<()> { |
| //! use io_lifetimes::AsFd; |
| //! use rustix::io::epoll::{self, Epoll}; |
| //! use rustix::io::{ioctl_fionbio, read, write}; |
| //! use rustix::net::{ |
| //! accept, bind_v4, listen, socket, AddressFamily, Ipv4Addr, Protocol, SocketAddrV4, |
| //! SocketType, |
| //! }; |
| //! use std::os::unix::io::AsRawFd; |
| //! |
| //! // Create a socket and listen on it. |
| //! let listen_sock = socket(AddressFamily::INET, SocketType::STREAM, Protocol::default())?; |
| //! bind_v4(&listen_sock, &SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0))?; |
| //! listen(&listen_sock, 1)?; |
| //! |
| //! // Create an epoll object. Using `Owning` here means the epoll object will |
| //! // take ownership of the file descriptors registered with it. |
| //! let epoll = Epoll::new(epoll::CreateFlags::CLOEXEC, epoll::Owning::new())?; |
| //! |
| //! // Remember the socket raw fd, which we use for comparisons only. |
| //! let raw_listen_sock = listen_sock.as_fd().as_raw_fd(); |
| //! |
| //! // Register the socket with the epoll object. |
| //! epoll.add(listen_sock, epoll::EventFlags::IN)?; |
| //! |
| //! // Process events. |
| //! let mut event_list = epoll::EventVec::with_capacity(4); |
| //! loop { |
| //! epoll.wait(&mut event_list, -1)?; |
| //! for (_event_flags, target) in &event_list { |
| //! if target.as_raw_fd() == raw_listen_sock { |
| //! // Accept a new connection, set it to non-blocking, and |
| //! // register to be notified when it's ready to write to. |
| //! let conn_sock = accept(&*target)?; |
| //! ioctl_fionbio(&conn_sock, true)?; |
| //! epoll.add(conn_sock, epoll::EventFlags::OUT | epoll::EventFlags::ET)?; |
| //! } else { |
| //! // Write a message to the stream and then unregister it. |
| //! write(&*target, b"hello\n")?; |
| //! let _ = epoll.del(target)?; |
| //! } |
| //! } |
| //! } |
| //! # } |
| //! # #[cfg(not(feature = "net"))] |
| //! # fn main() {} |
| //! ``` |
| |
| use super::super::c; |
| use super::super::conv::{ret, ret_owned_fd, ret_u32}; |
| use crate::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd}; |
| #[cfg(not(feature = "rustc-dep-of-std"))] |
| use crate::fd::{FromRawFd, IntoRawFd}; |
| use crate::io; |
| use alloc::vec::Vec; |
| use bitflags::bitflags; |
| use core::convert::TryInto; |
| use core::fmt; |
| use core::marker::PhantomData; |
| use core::ops::Deref; |
| use core::ptr::{null, null_mut}; |
| |
| bitflags! { |
| /// `EPOLL_*` for use with [`Epoll::new`]. |
| pub struct CreateFlags: c::c_int { |
| /// `EPOLL_CLOEXEC` |
| const CLOEXEC = c::EPOLL_CLOEXEC; |
| } |
| } |
| |
| bitflags! { |
| /// `EPOLL*` for use with [`Epoll::add`]. |
| #[derive(Default)] |
| pub struct EventFlags: u32 { |
| /// `EPOLLIN` |
| const IN = c::EPOLLIN as u32; |
| |
| /// `EPOLLOUT` |
| const OUT = c::EPOLLOUT as u32; |
| |
| /// `EPOLLPRI` |
| const PRI = c::EPOLLPRI as u32; |
| |
| /// `EPOLLERR` |
| const ERR = c::EPOLLERR as u32; |
| |
| /// `EPOLLHUP` |
| const HUP = c::EPOLLHUP as u32; |
| |
| /// `EPOLLET` |
| const ET = c::EPOLLET as u32; |
| |
| /// `EPOLLONESHOT` |
| const ONESHOT = c::EPOLLONESHOT as u32; |
| |
| /// `EPOLLWAKEUP` |
| const WAKEUP = c::EPOLLWAKEUP as u32; |
| |
| /// `EPOLLEXCLUSIVE` |
| #[cfg(not(target_os = "android"))] |
| const EXCLUSIVE = c::EPOLLEXCLUSIVE as u32; |
| } |
| } |
| |
| /// A reference to a `T`. |
| pub struct Ref<'a, T> { |
| t: T, |
| _phantom: PhantomData<&'a T>, |
| } |
| |
| impl<'a, T> Ref<'a, T> { |
| #[inline] |
| fn new(t: T) -> Self { |
| Self { |
| t, |
| _phantom: PhantomData, |
| } |
| } |
| |
| #[inline] |
| fn consume(self) -> T { |
| self.t |
| } |
| } |
| |
| impl<'a, T> Deref for Ref<'a, T> { |
| type Target = T; |
| |
| #[inline] |
| fn deref(&self) -> &T { |
| &self.t |
| } |
| } |
| |
| impl<'a, T: fmt::Debug> fmt::Debug for Ref<'a, T> { |
| fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { |
| self.t.fmt(fmt) |
| } |
| } |
| |
| /// A trait for data stored within an [`Epoll`] instance. |
| pub trait Context { |
| /// The type of an element owned by this context. |
| type Data; |
| |
| /// The type of a value used to refer to an element owned by this context. |
| type Target: AsFd; |
| |
| /// Assume ownership of `data`, and returning a `Target`. |
| fn acquire<'call>(&self, data: Self::Data) -> Ref<'call, Self::Target>; |
| |
| /// Encode `target` as a `u64`. The only requirement on this value is that |
| /// it be decodable by `decode`. |
| fn encode(&self, target: Ref<'_, Self::Target>) -> u64; |
| |
| /// Decode `raw`, which is a value encoded by `encode`, into a `Target`. |
| /// |
| /// # Safety |
| /// |
| /// `raw` must be a `u64` value returned from `encode`, from the same |
| /// context, and within the context's lifetime. |
| unsafe fn decode<'call>(&self, raw: u64) -> Ref<'call, Self::Target>; |
| |
| /// Release ownership of the value referred to by `target` and return it. |
| fn release(&self, target: Ref<'_, Self::Target>) -> Self::Data; |
| } |
| |
| /// A type implementing [`Context`] where the `Data` type is `BorrowedFd<'a>`. |
| pub struct Borrowing<'a> { |
| _phantom: PhantomData<BorrowedFd<'a>>, |
| } |
| |
| impl<'a> Context for Borrowing<'a> { |
| type Data = BorrowedFd<'a>; |
| type Target = BorrowedFd<'a>; |
| |
| #[inline] |
| fn acquire<'call>(&self, data: Self::Data) -> Ref<'call, Self::Target> { |
| Ref::new(data) |
| } |
| |
| #[inline] |
| fn encode(&self, target: Ref<'_, Self::Target>) -> u64 { |
| target.as_raw_fd() as u64 |
| } |
| |
| #[inline] |
| unsafe fn decode<'call>(&self, raw: u64) -> Ref<'call, Self::Target> { |
| Ref::new(BorrowedFd::<'a>::borrow_raw(raw as RawFd)) |
| } |
| |
| #[inline] |
| fn release(&self, target: Ref<'_, Self::Target>) -> Self::Data { |
| target.consume() |
| } |
| } |
| |
| /// A type implementing [`Context`] where the `Data` type is `T`, a type |
| /// implementing `From<OwnedFd>` and `From<T> for OwnedFd`. |
| /// |
| /// This may be used with [`OwnedFd`], or higher-level types like |
| /// [`std::fs::File`] or [`std::net::TcpStream`]. |
| #[cfg(not(feature = "rustc-dep-of-std"))] |
| pub struct Owning<'context, T: Into<OwnedFd> + From<OwnedFd>> { |
| _phantom: PhantomData<&'context T>, |
| } |
| |
| #[cfg(not(feature = "rustc-dep-of-std"))] |
| impl<'context, T: Into<OwnedFd> + From<OwnedFd>> Owning<'context, T> { |
| /// Creates a new empty `Owning`. |
| #[allow(clippy::new_without_default)] // This is a specialized type that doesn't need to be generically constructible. |
| #[inline] |
| pub fn new() -> Self { |
| Self { |
| _phantom: PhantomData, |
| } |
| } |
| } |
| |
| #[cfg(not(feature = "rustc-dep-of-std"))] |
| impl<'context, T: AsFd + Into<OwnedFd> + From<OwnedFd>> Context for Owning<'context, T> { |
| type Data = T; |
| type Target = BorrowedFd<'context>; |
| |
| #[inline] |
| fn acquire<'call>(&self, data: Self::Data) -> Ref<'call, Self::Target> { |
| let fd: OwnedFd = data.into(); |
| let raw_fd = fd.into_raw_fd(); |
| // Safety: `epoll` will assign ownership of the file descriptor to the |
| // kernel epoll object. We use `Into<OwnedFd>`+`IntoRawFd` to consume |
| // the `Data` and extract the raw file descriptor and then "borrow" it |
| // with `borrow_raw` knowing that the borrow won't outlive the |
| // kernel epoll object. |
| unsafe { Ref::new(BorrowedFd::<'context>::borrow_raw(raw_fd)) } |
| } |
| |
| #[inline] |
| fn encode(&self, target: Ref<'_, Self::Target>) -> u64 { |
| target.as_fd().as_raw_fd() as u64 |
| } |
| |
| #[inline] |
| unsafe fn decode<'call>(&self, raw: u64) -> Ref<'call, Self::Target> { |
| Ref::new(BorrowedFd::<'context>::borrow_raw(raw as RawFd)) |
| } |
| |
| #[inline] |
| fn release(&self, target: Ref<'_, Self::Target>) -> Self::Data { |
| // The file descriptor was held by the kernel epoll object and is now |
| // being released, so we can create a new `OwnedFd` that assumes |
| // ownership. |
| let raw_fd = target.consume().as_raw_fd(); |
| unsafe { T::from(OwnedFd::from_raw_fd(raw_fd).into()) } |
| } |
| } |
| |
| /// An "epoll", an interface to an OS object allowing one to repeatedly wait |
| /// for events from a set of file descriptors efficiently. |
| pub struct Epoll<Context: self::Context> { |
| epoll_fd: OwnedFd, |
| context: Context, |
| } |
| |
| impl<Context: self::Context> Epoll<Context> { |
| /// `epoll_create1(flags)`—Creates a new `Epoll`. |
| /// |
| /// Use the [`CreateFlags::CLOEXEC`] flag to prevent the resulting file |
| /// descriptor from being implicitly passed across `exec` boundaries. |
| #[inline] |
| #[doc(alias = "epoll_create1")] |
| pub fn new(flags: CreateFlags, context: Context) -> io::Result<Self> { |
| // Safety: We're calling `epoll_create1` via FFI and we know how it |
| // behaves. |
| unsafe { |
| Ok(Self { |
| epoll_fd: ret_owned_fd(c::epoll_create1(flags.bits()))?, |
| context, |
| }) |
| } |
| } |
| |
| /// `epoll_ctl(self, EPOLL_CTL_ADD, data, event)`—Adds an element to an |
| /// `Epoll`. |
| /// |
| /// This registers interest in any of the events set in `events` occurring |
| /// on the file descriptor associated with `data`. |
| #[doc(alias = "epoll_ctl")] |
| pub fn add( |
| &self, |
| data: Context::Data, |
| event_flags: EventFlags, |
| ) -> io::Result<Ref<'_, Context::Target>> { |
| // Safety: We're calling `epoll_ctl` via FFI and we know how it |
| // behaves. |
| unsafe { |
| let target = self.context.acquire(data); |
| let raw_fd = target.as_fd().as_raw_fd(); |
| let encoded = self.context.encode(target); |
| ret(c::epoll_ctl( |
| self.epoll_fd.as_fd().as_raw_fd(), |
| c::EPOLL_CTL_ADD, |
| raw_fd, |
| &mut c::epoll_event { |
| events: event_flags.bits(), |
| r#u64: encoded, |
| }, |
| ))?; |
| Ok(self.context.decode(encoded)) |
| } |
| } |
| |
| /// `epoll_ctl(self, EPOLL_CTL_MOD, target, event)`—Modifies an element in |
| /// this `Epoll`. |
| /// |
| /// This sets the events of interest with `target` to `events`. |
| #[doc(alias = "epoll_ctl")] |
| pub fn mod_( |
| &self, |
| target: Ref<'_, Context::Target>, |
| event_flags: EventFlags, |
| ) -> io::Result<()> { |
| let raw_fd = target.as_fd().as_raw_fd(); |
| let encoded = self.context.encode(target); |
| // Safety: We're calling `epoll_ctl` via FFI and we know how it |
| // behaves. |
| unsafe { |
| ret(c::epoll_ctl( |
| self.epoll_fd.as_fd().as_raw_fd(), |
| c::EPOLL_CTL_MOD, |
| raw_fd, |
| &mut c::epoll_event { |
| events: event_flags.bits(), |
| r#u64: encoded, |
| }, |
| )) |
| } |
| } |
| |
| /// `epoll_ctl(self, EPOLL_CTL_DEL, target, NULL)`—Removes an element in |
| /// this `Epoll`. |
| /// |
| /// This also returns the owning `Data`. |
| #[doc(alias = "epoll_ctl")] |
| pub fn del(&self, target: Ref<'_, Context::Target>) -> io::Result<Context::Data> { |
| // Safety: We're calling `epoll_ctl` via FFI and we know how it |
| // behaves. |
| unsafe { |
| let raw_fd = target.as_fd().as_raw_fd(); |
| ret(c::epoll_ctl( |
| self.epoll_fd.as_fd().as_raw_fd(), |
| c::EPOLL_CTL_DEL, |
| raw_fd, |
| null_mut(), |
| ))?; |
| } |
| Ok(self.context.release(target)) |
| } |
| |
| /// `epoll_wait(self, events, timeout)`—Waits for registered events of |
| /// interest. |
| /// |
| /// For each event of interest, an element is written to `events`. On |
| /// success, this returns the number of written elements. |
| #[doc(alias = "epoll_wait")] |
| pub fn wait<'context>( |
| &'context self, |
| event_list: &mut EventVec<'context, Context>, |
| timeout: c::c_int, |
| ) -> io::Result<()> { |
| // Safety: We're calling `epoll_wait` via FFI and we know how it |
| // behaves. |
| unsafe { |
| event_list.events.set_len(0); |
| let nfds = ret_u32(c::epoll_wait( |
| self.epoll_fd.as_fd().as_raw_fd(), |
| event_list.events.as_mut_ptr().cast::<c::epoll_event>(), |
| event_list.events.capacity().try_into().unwrap_or(i32::MAX), |
| timeout, |
| ))?; |
| event_list.events.set_len(nfds as usize); |
| event_list.context = &self.context; |
| } |
| |
| Ok(()) |
| } |
| } |
| |
| #[cfg(not(feature = "rustc-dep-of-std"))] |
| impl<'context, T: AsFd + Into<OwnedFd> + From<OwnedFd>> AsRawFd for Epoll<Owning<'context, T>> { |
| fn as_raw_fd(&self) -> RawFd { |
| self.epoll_fd.as_raw_fd() |
| } |
| } |
| |
| #[cfg(not(feature = "rustc-dep-of-std"))] |
| impl<'context, T: AsFd + Into<OwnedFd> + From<OwnedFd>> IntoRawFd for Epoll<Owning<'context, T>> { |
| fn into_raw_fd(self) -> RawFd { |
| self.epoll_fd.into_raw_fd() |
| } |
| } |
| |
| #[cfg(not(feature = "rustc-dep-of-std"))] |
| impl<'context, T: AsFd + Into<OwnedFd> + From<OwnedFd>> FromRawFd for Epoll<Owning<'context, T>> { |
| unsafe fn from_raw_fd(fd: RawFd) -> Self { |
| Self { |
| epoll_fd: OwnedFd::from_raw_fd(fd), |
| context: Owning::new(), |
| } |
| } |
| } |
| |
| #[cfg(not(feature = "rustc-dep-of-std"))] |
| impl<'context, T: AsFd + Into<OwnedFd> + From<OwnedFd>> AsFd for Epoll<Owning<'context, T>> { |
| fn as_fd(&self) -> BorrowedFd<'_> { |
| self.epoll_fd.as_fd() |
| } |
| } |
| |
| #[cfg(not(feature = "rustc-dep-of-std"))] |
| impl<'context, T: AsFd + Into<OwnedFd> + From<OwnedFd>> From<Epoll<Owning<'context, T>>> |
| for OwnedFd |
| { |
| fn from(epoll: Epoll<Owning<'context, T>>) -> Self { |
| epoll.epoll_fd |
| } |
| } |
| |
| #[cfg(not(feature = "rustc-dep-of-std"))] |
| impl<'context, T: AsFd + Into<OwnedFd> + From<OwnedFd>> From<OwnedFd> |
| for Epoll<Owning<'context, T>> |
| { |
| fn from(fd: OwnedFd) -> Self { |
| Self { |
| epoll_fd: fd, |
| context: Owning::new(), |
| } |
| } |
| } |
| |
| /// An iterator over the `Event`s in an `EventVec`. |
| pub struct Iter<'context, Context: self::Context> { |
| iter: core::slice::Iter<'context, Event>, |
| context: *const Context, |
| _phantom: PhantomData<&'context Context>, |
| } |
| |
| impl<'context, Context: self::Context> Iterator for Iter<'context, Context> { |
| type Item = (EventFlags, Ref<'context, Context::Target>); |
| |
| fn next(&mut self) -> Option<Self::Item> { |
| // Safety: `self.context` is guaranteed to be valid because we hold |
| // `'context` for it. And we know this event is associated with this |
| // context because `wait` sets both. |
| self.iter.next().map(|event| { |
| (event.event_flags, unsafe { |
| (*self.context).decode(event.encoded) |
| }) |
| }) |
| } |
| } |
| |
| /// A record of an event that occurred. |
| #[repr(C)] |
| #[cfg_attr( |
| any( |
| all( |
| target_arch = "x86", |
| not(target_env = "musl"), |
| not(target_os = "android"), |
| ), |
| target_arch = "x86_64", |
| ), |
| repr(packed) |
| )] |
| struct Event { |
| // Match the layout of `c::epoll_event`. We just use a `u64` instead of |
| // the full union; `Context` implementations will simply need to deal with |
| // casting the value into and out of the `u64` themselves. |
| event_flags: EventFlags, |
| encoded: u64, |
| } |
| |
| /// A vector of `Event`s, plus context for interpreting them. |
| pub struct EventVec<'context, Context: self::Context> { |
| events: Vec<Event>, |
| context: *const Context, |
| _phantom: PhantomData<&'context Context>, |
| } |
| |
| impl<'context, Context: self::Context> EventVec<'context, Context> { |
| /// Constructs an `EventVec` with memory for `capacity` `Event`s. |
| #[inline] |
| pub fn with_capacity(capacity: usize) -> Self { |
| Self { |
| events: Vec::with_capacity(capacity), |
| context: null(), |
| _phantom: PhantomData, |
| } |
| } |
| |
| /// Returns the current `Event` capacity of this `EventVec`. |
| #[inline] |
| pub fn capacity(&self) -> usize { |
| self.events.capacity() |
| } |
| |
| /// Reserves enough memory for at least `additional` more `Event`s. |
| #[inline] |
| pub fn reserve(&mut self, additional: usize) { |
| self.events.reserve(additional); |
| } |
| |
| /// Reserves enough memory for exactly `additional` more `Event`s. |
| #[inline] |
| pub fn reserve_exact(&mut self, additional: usize) { |
| self.events.reserve_exact(additional); |
| } |
| |
| /// Clears all the `Events` out of this `EventVec`. |
| #[inline] |
| pub fn clear(&mut self) { |
| self.events.clear(); |
| } |
| |
| /// Shrinks the capacity of this `EventVec` as much as possible. |
| #[inline] |
| pub fn shrink_to_fit(&mut self) { |
| self.events.shrink_to_fit(); |
| } |
| |
| /// Returns an iterator over the `Event`s in this `EventVec`. |
| #[inline] |
| pub fn iter(&self) -> Iter<'_, Context> { |
| Iter { |
| iter: self.events.iter(), |
| context: self.context, |
| _phantom: PhantomData, |
| } |
| } |
| |
| /// Returns the number of `Event`s logically contained in this `EventVec`. |
| #[inline] |
| pub fn len(&mut self) -> usize { |
| self.events.len() |
| } |
| |
| /// Tests whether this `EventVec` is logically empty. |
| #[inline] |
| pub fn is_empty(&mut self) -> bool { |
| self.events.is_empty() |
| } |
| } |
| |
| impl<'context, Context: self::Context> IntoIterator for &'context EventVec<'context, Context> { |
| type IntoIter = Iter<'context, Context>; |
| type Item = (EventFlags, Ref<'context, Context::Target>); |
| |
| #[inline] |
| fn into_iter(self) -> Self::IntoIter { |
| self.iter() |
| } |
| } |