//! Portably monitor a group of file descriptors for readiness. use crate::errno::Errno; use crate::sys::time::{TimeSpec, TimeVal}; use crate::Result; use libc::{self, c_int}; use std::convert::TryFrom; use std::iter::FusedIterator; use std::mem; use std::ops::Range; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::ptr::{null, null_mut}; pub use libc::FD_SETSIZE; /// Contains a set of file descriptors used by [`select`] #[repr(transparent)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct FdSet<'fd> { set: libc::fd_set, _fd: std::marker::PhantomData>, } fn assert_fd_valid(fd: RawFd) { assert!( usize::try_from(fd).map_or(false, |fd| fd < FD_SETSIZE), "fd must be in the range 0..FD_SETSIZE", ); } impl<'fd> FdSet<'fd> { /// Create an empty `FdSet` pub fn new() -> FdSet<'fd> { let mut fdset = mem::MaybeUninit::uninit(); unsafe { libc::FD_ZERO(fdset.as_mut_ptr()); Self { set: fdset.assume_init(), _fd: std::marker::PhantomData, } } } /// Add a file descriptor to an `FdSet` pub fn insert(&mut self, fd: &'fd Fd) { assert_fd_valid(fd.as_fd().as_raw_fd()); unsafe { libc::FD_SET(fd.as_fd().as_raw_fd(), &mut self.set) }; } /// Remove a file descriptor from an `FdSet` pub fn remove(&mut self, fd: &'fd Fd) { assert_fd_valid(fd.as_fd().as_raw_fd()); unsafe { libc::FD_CLR(fd.as_fd().as_raw_fd(), &mut self.set) }; } /// Test an `FdSet` for the presence of a certain file descriptor. pub fn contains(&self, fd: &'fd Fd) -> bool { assert_fd_valid(fd.as_fd().as_raw_fd()); unsafe { libc::FD_ISSET(fd.as_fd().as_raw_fd(), &self.set) } } /// Remove all file descriptors from this `FdSet`. pub fn clear(&mut self) { unsafe { libc::FD_ZERO(&mut self.set) }; } /// Finds the highest file descriptor in the set. /// /// Returns `None` if the set is empty. /// /// This can be used to calculate the `nfds` parameter of the [`select`] function. /// /// # Example /// /// ``` /// # use std::os::unix::io::{AsRawFd, BorrowedFd}; /// # use nix::sys::select::FdSet; /// let fd_four = unsafe {BorrowedFd::borrow_raw(4)}; /// let fd_nine = unsafe {BorrowedFd::borrow_raw(9)}; /// let mut set = FdSet::new(); /// set.insert(&fd_four); /// set.insert(&fd_nine); /// assert_eq!(set.highest().map(|borrowed_fd|borrowed_fd.as_raw_fd()), Some(9)); /// ``` /// /// [`select`]: fn.select.html pub fn highest(&self) -> Option> { self.fds(None).next_back() } /// Returns an iterator over the file descriptors in the set. /// /// For performance, it takes an optional higher bound: the iterator will /// not return any elements of the set greater than the given file /// descriptor. /// /// # Examples /// /// ``` /// # use nix::sys::select::FdSet; /// # use std::os::unix::io::{AsRawFd, BorrowedFd, RawFd}; /// let mut set = FdSet::new(); /// let fd_four = unsafe {BorrowedFd::borrow_raw(4)}; /// let fd_nine = unsafe {BorrowedFd::borrow_raw(9)}; /// set.insert(&fd_four); /// set.insert(&fd_nine); /// let fds: Vec = set.fds(None).map(|borrowed_fd|borrowed_fd.as_raw_fd()).collect(); /// assert_eq!(fds, vec![4, 9]); /// ``` #[inline] pub fn fds(&self, highest: Option) -> Fds { Fds { set: self, range: 0..highest.map(|h| h as usize + 1).unwrap_or(FD_SETSIZE), } } } impl<'fd> Default for FdSet<'fd> { fn default() -> Self { Self::new() } } /// Iterator over `FdSet`. #[derive(Debug)] pub struct Fds<'a, 'fd> { set: &'a FdSet<'fd>, range: Range, } impl<'a, 'fd> Iterator for Fds<'a, 'fd> { type Item = BorrowedFd<'fd>; fn next(&mut self) -> Option { for i in &mut self.range { let borrowed_i = unsafe { BorrowedFd::borrow_raw(i as RawFd) }; if self.set.contains(&borrowed_i) { return Some(borrowed_i); } } None } #[inline] fn size_hint(&self) -> (usize, Option) { let (_, upper) = self.range.size_hint(); (0, upper) } } impl<'a, 'fd> DoubleEndedIterator for Fds<'a, 'fd> { #[inline] fn next_back(&mut self) -> Option> { while let Some(i) = self.range.next_back() { let borrowed_i = unsafe { BorrowedFd::borrow_raw(i as RawFd) }; if self.set.contains(&borrowed_i) { return Some(borrowed_i); } } None } } impl<'a, 'fd> FusedIterator for Fds<'a, 'fd> {} /// Monitors file descriptors for readiness /// /// Returns the total number of ready file descriptors in all sets. The sets are changed so that all /// file descriptors that are ready for the given operation are set. /// /// When this function returns, `timeout` has an implementation-defined value. /// /// # Parameters /// /// * `nfds`: The highest file descriptor set in any of the passed `FdSet`s, plus 1. If `None`, this /// is calculated automatically by calling [`FdSet::highest`] on all descriptor sets and adding 1 /// to the maximum of that. /// * `readfds`: File descriptors to check for being ready to read. /// * `writefds`: File descriptors to check for being ready to write. /// * `errorfds`: File descriptors to check for pending error conditions. /// * `timeout`: Maximum time to wait for descriptors to become ready (`None` to block /// indefinitely). /// /// # References /// /// [select(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/select.html) /// /// [`FdSet::highest`]: struct.FdSet.html#method.highest pub fn select<'a, 'fd, N, R, W, E, T>( nfds: N, readfds: R, writefds: W, errorfds: E, timeout: T, ) -> Result where 'fd: 'a, N: Into>, R: Into>>, W: Into>>, E: Into>>, T: Into>, { let mut readfds = readfds.into(); let mut writefds = writefds.into(); let mut errorfds = errorfds.into(); let timeout = timeout.into(); let nfds = nfds.into().unwrap_or_else(|| { readfds .iter_mut() .chain(writefds.iter_mut()) .chain(errorfds.iter_mut()) .map(|set| { set.highest() .map(|borrowed_fd| borrowed_fd.as_raw_fd()) .unwrap_or(-1) }) .max() .unwrap_or(-1) + 1 }); let readfds = readfds .map(|set| set as *mut _ as *mut libc::fd_set) .unwrap_or(null_mut()); let writefds = writefds .map(|set| set as *mut _ as *mut libc::fd_set) .unwrap_or(null_mut()); let errorfds = errorfds .map(|set| set as *mut _ as *mut libc::fd_set) .unwrap_or(null_mut()); let timeout = timeout .map(|tv| tv as *mut _ as *mut libc::timeval) .unwrap_or(null_mut()); let res = unsafe { libc::select(nfds, readfds, writefds, errorfds, timeout) }; Errno::result(res) } feature! { #![feature = "signal"] use crate::sys::signal::SigSet; /// Monitors file descriptors for readiness with an altered signal mask. /// /// Returns the total number of ready file descriptors in all sets. The sets are changed so that all /// file descriptors that are ready for the given operation are set. /// /// When this function returns, the original signal mask is restored. /// /// Unlike [`select`](#fn.select), `pselect` does not mutate the `timeout` value. /// /// # Parameters /// /// * `nfds`: The highest file descriptor set in any of the passed `FdSet`s, plus 1. If `None`, this /// is calculated automatically by calling [`FdSet::highest`] on all descriptor sets and adding 1 /// to the maximum of that. /// * `readfds`: File descriptors to check for read readiness /// * `writefds`: File descriptors to check for write readiness /// * `errorfds`: File descriptors to check for pending error conditions. /// * `timeout`: Maximum time to wait for descriptors to become ready (`None` to block /// indefinitely). /// * `sigmask`: Signal mask to activate while waiting for file descriptors to turn /// ready (`None` to set no alternative signal mask). /// /// # References /// /// [pselect(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html) /// /// [The new pselect() system call](https://lwn.net/Articles/176911/) /// /// [`FdSet::highest`]: struct.FdSet.html#method.highest pub fn pselect<'a, 'fd, N, R, W, E, T, S>(nfds: N, readfds: R, writefds: W, errorfds: E, timeout: T, sigmask: S) -> Result where 'fd: 'a, N: Into>, R: Into>>, W: Into>>, E: Into>>, T: Into>, S: Into>, { let mut readfds = readfds.into(); let mut writefds = writefds.into(); let mut errorfds = errorfds.into(); let sigmask = sigmask.into(); let timeout = timeout.into(); let nfds = nfds.into().unwrap_or_else(|| { readfds.iter_mut() .chain(writefds.iter_mut()) .chain(errorfds.iter_mut()) .map(|set| set.highest().map(|borrowed_fd|borrowed_fd.as_raw_fd()).unwrap_or(-1)) .max() .unwrap_or(-1) + 1 }); let readfds = readfds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut()); let writefds = writefds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut()); let errorfds = errorfds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut()); let timeout = timeout.map(|ts| ts.as_ref() as *const libc::timespec).unwrap_or(null()); let sigmask = sigmask.map(|sm| sm.as_ref() as *const libc::sigset_t).unwrap_or(null()); let res = unsafe { libc::pselect(nfds, readfds, writefds, errorfds, timeout, sigmask) }; Errno::result(res) } } #[cfg(test)] mod tests { use super::*; use crate::sys::time::{TimeVal, TimeValLike}; use crate::unistd::{close, pipe, write}; use std::os::unix::io::{FromRawFd, OwnedFd, RawFd}; #[test] fn fdset_insert() { let mut fd_set = FdSet::new(); for i in 0..FD_SETSIZE { let borrowed_i = unsafe { BorrowedFd::borrow_raw(i as RawFd) }; assert!(!fd_set.contains(&borrowed_i)); } let fd_seven = unsafe { BorrowedFd::borrow_raw(7) }; fd_set.insert(&fd_seven); assert!(fd_set.contains(&fd_seven)); } #[test] fn fdset_remove() { let mut fd_set = FdSet::new(); for i in 0..FD_SETSIZE { let borrowed_i = unsafe { BorrowedFd::borrow_raw(i as RawFd) }; assert!(!fd_set.contains(&borrowed_i)); } let fd_seven = unsafe { BorrowedFd::borrow_raw(7) }; fd_set.insert(&fd_seven); fd_set.remove(&fd_seven); for i in 0..FD_SETSIZE { let borrowed_i = unsafe { BorrowedFd::borrow_raw(i as RawFd) }; assert!(!fd_set.contains(&borrowed_i)); } } #[test] #[allow(non_snake_case)] fn fdset_clear() { let mut fd_set = FdSet::new(); let fd_one = unsafe { BorrowedFd::borrow_raw(1) }; let fd_FD_SETSIZE_devided_by_two = unsafe { BorrowedFd::borrow_raw((FD_SETSIZE / 2) as RawFd) }; let fd_FD_SETSIZE_minus_one = unsafe { BorrowedFd::borrow_raw((FD_SETSIZE - 1) as RawFd) }; fd_set.insert(&fd_one); fd_set.insert(&fd_FD_SETSIZE_devided_by_two); fd_set.insert(&fd_FD_SETSIZE_minus_one); fd_set.clear(); for i in 0..FD_SETSIZE { let borrowed_i = unsafe { BorrowedFd::borrow_raw(i as RawFd) }; assert!(!fd_set.contains(&borrowed_i)); } } #[test] fn fdset_highest() { let mut set = FdSet::new(); assert_eq!( set.highest().map(|borrowed_fd| borrowed_fd.as_raw_fd()), None ); let fd_zero = unsafe { BorrowedFd::borrow_raw(0) }; let fd_ninety = unsafe { BorrowedFd::borrow_raw(90) }; set.insert(&fd_zero); assert_eq!( set.highest().map(|borrowed_fd| borrowed_fd.as_raw_fd()), Some(0) ); set.insert(&fd_ninety); assert_eq!( set.highest().map(|borrowed_fd| borrowed_fd.as_raw_fd()), Some(90) ); set.remove(&fd_zero); assert_eq!( set.highest().map(|borrowed_fd| borrowed_fd.as_raw_fd()), Some(90) ); set.remove(&fd_ninety); assert_eq!( set.highest().map(|borrowed_fd| borrowed_fd.as_raw_fd()), None ); let fd_four = unsafe { BorrowedFd::borrow_raw(4) }; let fd_five = unsafe { BorrowedFd::borrow_raw(5) }; let fd_seven = unsafe { BorrowedFd::borrow_raw(7) }; set.insert(&fd_four); set.insert(&fd_five); set.insert(&fd_seven); assert_eq!( set.highest().map(|borrowed_fd| borrowed_fd.as_raw_fd()), Some(7) ); } #[test] fn fdset_fds() { let mut set = FdSet::new(); let fd_zero = unsafe { BorrowedFd::borrow_raw(0) }; let fd_ninety = unsafe { BorrowedFd::borrow_raw(90) }; assert_eq!( set.fds(None) .map(|borrowed_fd| borrowed_fd.as_raw_fd()) .collect::>(), vec![] ); set.insert(&fd_zero); assert_eq!( set.fds(None) .map(|borrowed_fd| borrowed_fd.as_raw_fd()) .collect::>(), vec![0] ); set.insert(&fd_ninety); assert_eq!( set.fds(None) .map(|borrowed_fd| borrowed_fd.as_raw_fd()) .collect::>(), vec![0, 90] ); // highest limit assert_eq!( set.fds(Some(89)) .map(|borrowed_fd| borrowed_fd.as_raw_fd()) .collect::>(), vec![0] ); assert_eq!( set.fds(Some(90)) .map(|borrowed_fd| borrowed_fd.as_raw_fd()) .collect::>(), vec![0, 90] ); } #[test] fn test_select() { let (r1, w1) = pipe().unwrap(); let r1 = unsafe { OwnedFd::from_raw_fd(r1) }; let w1 = unsafe { OwnedFd::from_raw_fd(w1) }; let (r2, _w2) = pipe().unwrap(); let r2 = unsafe { OwnedFd::from_raw_fd(r2) }; write(w1.as_raw_fd(), b"hi!").unwrap(); let mut fd_set = FdSet::new(); fd_set.insert(&r1); fd_set.insert(&r2); let mut timeout = TimeVal::seconds(10); assert_eq!( 1, select(None, &mut fd_set, None, None, &mut timeout).unwrap() ); assert!(fd_set.contains(&r1)); assert!(!fd_set.contains(&r2)); close(_w2).unwrap(); } #[test] fn test_select_nfds() { let (r1, w1) = pipe().unwrap(); let (r2, _w2) = pipe().unwrap(); let r1 = unsafe { OwnedFd::from_raw_fd(r1) }; let w1 = unsafe { OwnedFd::from_raw_fd(w1) }; let r2 = unsafe { OwnedFd::from_raw_fd(r2) }; write(w1.as_raw_fd(), b"hi!").unwrap(); let mut fd_set = FdSet::new(); fd_set.insert(&r1); fd_set.insert(&r2); let mut timeout = TimeVal::seconds(10); { assert_eq!( 1, select( Some( fd_set .highest() .map(|borrowed_fd| borrowed_fd.as_raw_fd()) .unwrap() + 1 ), &mut fd_set, None, None, &mut timeout ) .unwrap() ); } assert!(fd_set.contains(&r1)); assert!(!fd_set.contains(&r2)); close(_w2).unwrap(); } #[test] fn test_select_nfds2() { let (r1, w1) = pipe().unwrap(); write(w1, b"hi!").unwrap(); let (r2, _w2) = pipe().unwrap(); let r1 = unsafe { OwnedFd::from_raw_fd(r1) }; let r2 = unsafe { OwnedFd::from_raw_fd(r2) }; let mut fd_set = FdSet::new(); fd_set.insert(&r1); fd_set.insert(&r2); let mut timeout = TimeVal::seconds(10); assert_eq!( 1, select( std::cmp::max(r1.as_raw_fd(), r2.as_raw_fd()) + 1, &mut fd_set, None, None, &mut timeout ) .unwrap() ); assert!(fd_set.contains(&r1)); assert!(!fd_set.contains(&r2)); close(_w2).unwrap(); } }