| use std::iter::FusedIterator; |
| use std::mem; |
| use std::ops::Range; |
| use std::os::unix::io::RawFd; |
| use std::ptr::{null, null_mut}; |
| use libc::{self, c_int}; |
| use crate::Result; |
| use crate::errno::Errno; |
| use crate::sys::signal::SigSet; |
| use crate::sys::time::{TimeSpec, TimeVal}; |
| |
| pub use libc::FD_SETSIZE; |
| |
| #[repr(transparent)] |
| #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] |
| pub struct FdSet(libc::fd_set); |
| |
| impl FdSet { |
| pub fn new() -> FdSet { |
| let mut fdset = mem::MaybeUninit::uninit(); |
| unsafe { |
| libc::FD_ZERO(fdset.as_mut_ptr()); |
| FdSet(fdset.assume_init()) |
| } |
| } |
| |
| pub fn insert(&mut self, fd: RawFd) { |
| unsafe { libc::FD_SET(fd, &mut self.0) }; |
| } |
| |
| pub fn remove(&mut self, fd: RawFd) { |
| unsafe { libc::FD_CLR(fd, &mut self.0) }; |
| } |
| |
| pub fn contains(&mut self, fd: RawFd) -> bool { |
| unsafe { libc::FD_ISSET(fd, &mut self.0) } |
| } |
| |
| pub fn clear(&mut self) { |
| unsafe { libc::FD_ZERO(&mut self.0) }; |
| } |
| |
| /// 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 nix::sys::select::FdSet; |
| /// let mut set = FdSet::new(); |
| /// set.insert(4); |
| /// set.insert(9); |
| /// assert_eq!(set.highest(), Some(9)); |
| /// ``` |
| /// |
| /// [`select`]: fn.select.html |
| pub fn highest(&mut self) -> Option<RawFd> { |
| 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::RawFd; |
| /// let mut set = FdSet::new(); |
| /// set.insert(4); |
| /// set.insert(9); |
| /// let fds: Vec<RawFd> = set.fds(None).collect(); |
| /// assert_eq!(fds, vec![4, 9]); |
| /// ``` |
| #[inline] |
| pub fn fds(&mut self, highest: Option<RawFd>) -> Fds { |
| Fds { |
| set: self, |
| range: 0..highest.map(|h| h as usize + 1).unwrap_or(FD_SETSIZE), |
| } |
| } |
| } |
| |
| impl Default for FdSet { |
| fn default() -> Self { |
| Self::new() |
| } |
| } |
| |
| /// Iterator over `FdSet`. |
| #[derive(Debug)] |
| pub struct Fds<'a> { |
| set: &'a mut FdSet, |
| range: Range<usize>, |
| } |
| |
| impl<'a> Iterator for Fds<'a> { |
| type Item = RawFd; |
| |
| fn next(&mut self) -> Option<RawFd> { |
| while let Some(i) = self.range.next() { |
| if self.set.contains(i as RawFd) { |
| return Some(i as RawFd); |
| } |
| } |
| None |
| } |
| |
| #[inline] |
| fn size_hint(&self) -> (usize, Option<usize>) { |
| let (_, upper) = self.range.size_hint(); |
| (0, upper) |
| } |
| } |
| |
| impl<'a> DoubleEndedIterator for Fds<'a> { |
| #[inline] |
| fn next_back(&mut self) -> Option<RawFd> { |
| while let Some(i) = self.range.next_back() { |
| if self.set.contains(i as RawFd) { |
| return Some(i as RawFd); |
| } |
| } |
| None |
| } |
| } |
| |
| impl<'a> FusedIterator for Fds<'a> {} |
| |
| /// 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, N, R, W, E, T>(nfds: N, |
| readfds: R, |
| writefds: W, |
| errorfds: E, |
| timeout: T) -> Result<c_int> |
| where |
| N: Into<Option<c_int>>, |
| R: Into<Option<&'a mut FdSet>>, |
| W: Into<Option<&'a mut FdSet>>, |
| E: Into<Option<&'a mut FdSet>>, |
| T: Into<Option<&'a mut TimeVal>>, |
| { |
| 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().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) |
| } |
| |
| /// 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, N, R, W, E, T, S>(nfds: N, |
| readfds: R, |
| writefds: W, |
| errorfds: E, |
| timeout: T, |
| sigmask: S) -> Result<c_int> |
| where |
| N: Into<Option<c_int>>, |
| R: Into<Option<&'a mut FdSet>>, |
| W: Into<Option<&'a mut FdSet>>, |
| E: Into<Option<&'a mut FdSet>>, |
| T: Into<Option<&'a TimeSpec>>, |
| S: Into<Option<&'a SigSet>>, |
| { |
| 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().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 std::os::unix::io::RawFd; |
| use crate::sys::time::{TimeVal, TimeValLike}; |
| use crate::unistd::{write, pipe}; |
| |
| #[test] |
| fn fdset_insert() { |
| let mut fd_set = FdSet::new(); |
| |
| for i in 0..FD_SETSIZE { |
| assert!(!fd_set.contains(i as RawFd)); |
| } |
| |
| fd_set.insert(7); |
| |
| assert!(fd_set.contains(7)); |
| } |
| |
| #[test] |
| fn fdset_remove() { |
| let mut fd_set = FdSet::new(); |
| |
| for i in 0..FD_SETSIZE { |
| assert!(!fd_set.contains(i as RawFd)); |
| } |
| |
| fd_set.insert(7); |
| fd_set.remove(7); |
| |
| for i in 0..FD_SETSIZE { |
| assert!(!fd_set.contains(i as RawFd)); |
| } |
| } |
| |
| #[test] |
| fn fdset_clear() { |
| let mut fd_set = FdSet::new(); |
| fd_set.insert(1); |
| fd_set.insert((FD_SETSIZE / 2) as RawFd); |
| fd_set.insert((FD_SETSIZE - 1) as RawFd); |
| |
| fd_set.clear(); |
| |
| for i in 0..FD_SETSIZE { |
| assert!(!fd_set.contains(i as RawFd)); |
| } |
| } |
| |
| #[test] |
| fn fdset_highest() { |
| let mut set = FdSet::new(); |
| assert_eq!(set.highest(), None); |
| set.insert(0); |
| assert_eq!(set.highest(), Some(0)); |
| set.insert(90); |
| assert_eq!(set.highest(), Some(90)); |
| set.remove(0); |
| assert_eq!(set.highest(), Some(90)); |
| set.remove(90); |
| assert_eq!(set.highest(), None); |
| |
| set.insert(4); |
| set.insert(5); |
| set.insert(7); |
| assert_eq!(set.highest(), Some(7)); |
| } |
| |
| #[test] |
| fn fdset_fds() { |
| let mut set = FdSet::new(); |
| assert_eq!(set.fds(None).collect::<Vec<_>>(), vec![]); |
| set.insert(0); |
| assert_eq!(set.fds(None).collect::<Vec<_>>(), vec![0]); |
| set.insert(90); |
| assert_eq!(set.fds(None).collect::<Vec<_>>(), vec![0, 90]); |
| |
| // highest limit |
| assert_eq!(set.fds(Some(89)).collect::<Vec<_>>(), vec![0]); |
| assert_eq!(set.fds(Some(90)).collect::<Vec<_>>(), vec![0, 90]); |
| } |
| |
| #[test] |
| fn test_select() { |
| let (r1, w1) = pipe().unwrap(); |
| write(w1, b"hi!").unwrap(); |
| let (r2, _w2) = pipe().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)); |
| } |
| |
| #[test] |
| fn test_select_nfds() { |
| let (r1, w1) = pipe().unwrap(); |
| write(w1, b"hi!").unwrap(); |
| let (r2, _w2) = pipe().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().unwrap() + 1), |
| &mut fd_set, |
| None, |
| None, |
| &mut timeout).unwrap()); |
| assert!(fd_set.contains(r1)); |
| assert!(!fd_set.contains(r2)); |
| } |
| |
| #[test] |
| fn test_select_nfds2() { |
| let (r1, w1) = pipe().unwrap(); |
| write(w1, b"hi!").unwrap(); |
| let (r2, _w2) = pipe().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(::std::cmp::max(r1, r2) + 1, |
| &mut fd_set, |
| None, |
| None, |
| &mut timeout).unwrap()); |
| assert!(fd_set.contains(r1)); |
| assert!(!fd_set.contains(r2)); |
| } |
| } |