nix/sys/
select.rs

1//! Portably monitor a group of file descriptors for readiness.
2use std::convert::TryFrom;
3use std::iter::FusedIterator;
4use std::mem;
5use std::ops::Range;
6use std::os::unix::io::RawFd;
7use std::ptr::{null, null_mut};
8use libc::{self, c_int};
9use crate::Result;
10use crate::errno::Errno;
11use crate::sys::signal::SigSet;
12use crate::sys::time::{TimeSpec, TimeVal};
13
14pub use libc::FD_SETSIZE;
15
16/// Contains a set of file descriptors used by [`select`]
17#[repr(transparent)]
18#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
19pub struct FdSet(libc::fd_set);
20
21fn assert_fd_valid(fd: RawFd) {
22    assert!(
23        usize::try_from(fd).map_or(false, |fd| fd < FD_SETSIZE),
24        "fd must be in the range 0..FD_SETSIZE",
25    );
26}
27
28impl FdSet {
29    /// Create an empty `FdSet`
30    pub fn new() -> FdSet {
31        let mut fdset = mem::MaybeUninit::uninit();
32        unsafe {
33            libc::FD_ZERO(fdset.as_mut_ptr());
34            FdSet(fdset.assume_init())
35        }
36    }
37
38    /// Add a file descriptor to an `FdSet`
39    pub fn insert(&mut self, fd: RawFd) {
40        assert_fd_valid(fd);
41        unsafe { libc::FD_SET(fd, &mut self.0) };
42    }
43
44    /// Remove a file descriptor from an `FdSet`
45    pub fn remove(&mut self, fd: RawFd) {
46        assert_fd_valid(fd);
47        unsafe { libc::FD_CLR(fd, &mut self.0) };
48    }
49
50    /// Test an `FdSet` for the presence of a certain file descriptor.
51    pub fn contains(&self, fd: RawFd) -> bool {
52        assert_fd_valid(fd);
53        unsafe { libc::FD_ISSET(fd, &self.0) }
54    }
55
56    /// Remove all file descriptors from this `FdSet`.
57    pub fn clear(&mut self) {
58        unsafe { libc::FD_ZERO(&mut self.0) };
59    }
60
61    /// Finds the highest file descriptor in the set.
62    ///
63    /// Returns `None` if the set is empty.
64    ///
65    /// This can be used to calculate the `nfds` parameter of the [`select`] function.
66    ///
67    /// # Example
68    ///
69    /// ```
70    /// # use nix::sys::select::FdSet;
71    /// let mut set = FdSet::new();
72    /// set.insert(4);
73    /// set.insert(9);
74    /// assert_eq!(set.highest(), Some(9));
75    /// ```
76    ///
77    /// [`select`]: fn.select.html
78    pub fn highest(&self) -> Option<RawFd> {
79        self.fds(None).next_back()
80    }
81
82    /// Returns an iterator over the file descriptors in the set.
83    ///
84    /// For performance, it takes an optional higher bound: the iterator will
85    /// not return any elements of the set greater than the given file
86    /// descriptor.
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// # use nix::sys::select::FdSet;
92    /// # use std::os::unix::io::RawFd;
93    /// let mut set = FdSet::new();
94    /// set.insert(4);
95    /// set.insert(9);
96    /// let fds: Vec<RawFd> = set.fds(None).collect();
97    /// assert_eq!(fds, vec![4, 9]);
98    /// ```
99    #[inline]
100    pub fn fds(&self, highest: Option<RawFd>) -> Fds {
101        Fds {
102            set: self,
103            range: 0..highest.map(|h| h as usize + 1).unwrap_or(FD_SETSIZE),
104        }
105    }
106}
107
108impl Default for FdSet {
109    fn default() -> Self {
110        Self::new()
111    }
112}
113
114/// Iterator over `FdSet`.
115#[derive(Debug)]
116pub struct Fds<'a> {
117    set: &'a FdSet,
118    range: Range<usize>,
119}
120
121impl<'a> Iterator for Fds<'a> {
122    type Item = RawFd;
123
124    fn next(&mut self) -> Option<RawFd> {
125        for i in &mut self.range {
126            if self.set.contains(i as RawFd) {
127                return Some(i as RawFd);
128            }
129        }
130        None
131    }
132
133    #[inline]
134    fn size_hint(&self) -> (usize, Option<usize>) {
135        let (_, upper) = self.range.size_hint();
136        (0, upper)
137    }
138}
139
140impl<'a> DoubleEndedIterator for Fds<'a> {
141    #[inline]
142    fn next_back(&mut self) -> Option<RawFd> {
143        while let Some(i) = self.range.next_back() {
144            if self.set.contains(i as RawFd) {
145                return Some(i as RawFd);
146            }
147        }
148        None
149    }
150}
151
152impl<'a> FusedIterator for Fds<'a> {}
153
154/// Monitors file descriptors for readiness
155///
156/// Returns the total number of ready file descriptors in all sets. The sets are changed so that all
157/// file descriptors that are ready for the given operation are set.
158///
159/// When this function returns, `timeout` has an implementation-defined value.
160///
161/// # Parameters
162///
163/// * `nfds`: The highest file descriptor set in any of the passed `FdSet`s, plus 1. If `None`, this
164///   is calculated automatically by calling [`FdSet::highest`] on all descriptor sets and adding 1
165///   to the maximum of that.
166/// * `readfds`: File descriptors to check for being ready to read.
167/// * `writefds`: File descriptors to check for being ready to write.
168/// * `errorfds`: File descriptors to check for pending error conditions.
169/// * `timeout`: Maximum time to wait for descriptors to become ready (`None` to block
170///   indefinitely).
171///
172/// # References
173///
174/// [select(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/select.html)
175///
176/// [`FdSet::highest`]: struct.FdSet.html#method.highest
177pub fn select<'a, N, R, W, E, T>(nfds: N,
178    readfds: R,
179    writefds: W,
180    errorfds: E,
181                                 timeout: T) -> Result<c_int>
182where
183    N: Into<Option<c_int>>,
184    R: Into<Option<&'a mut FdSet>>,
185    W: Into<Option<&'a mut FdSet>>,
186    E: Into<Option<&'a mut FdSet>>,
187    T: Into<Option<&'a mut TimeVal>>,
188{
189    let mut readfds = readfds.into();
190    let mut writefds = writefds.into();
191    let mut errorfds = errorfds.into();
192    let timeout = timeout.into();
193
194    let nfds = nfds.into().unwrap_or_else(|| {
195        readfds.iter_mut()
196            .chain(writefds.iter_mut())
197            .chain(errorfds.iter_mut())
198            .map(|set| set.highest().unwrap_or(-1))
199            .max()
200            .unwrap_or(-1) + 1
201    });
202
203    let readfds = readfds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut());
204    let writefds = writefds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut());
205    let errorfds = errorfds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut());
206    let timeout = timeout.map(|tv| tv as *mut _ as *mut libc::timeval)
207        .unwrap_or(null_mut());
208
209    let res = unsafe {
210        libc::select(nfds, readfds, writefds, errorfds, timeout)
211    };
212
213    Errno::result(res)
214}
215
216/// Monitors file descriptors for readiness with an altered signal mask.
217///
218/// Returns the total number of ready file descriptors in all sets. The sets are changed so that all
219/// file descriptors that are ready for the given operation are set.
220///
221/// When this function returns, the original signal mask is restored.
222///
223/// Unlike [`select`](#fn.select), `pselect` does not mutate the `timeout` value.
224///
225/// # Parameters
226///
227/// * `nfds`: The highest file descriptor set in any of the passed `FdSet`s, plus 1. If `None`, this
228///   is calculated automatically by calling [`FdSet::highest`] on all descriptor sets and adding 1
229///   to the maximum of that.
230/// * `readfds`: File descriptors to check for read readiness
231/// * `writefds`: File descriptors to check for write readiness
232/// * `errorfds`: File descriptors to check for pending error conditions.
233/// * `timeout`: Maximum time to wait for descriptors to become ready (`None` to block
234///   indefinitely).
235/// * `sigmask`: Signal mask to activate while waiting for file descriptors to turn
236///    ready (`None` to set no alternative signal mask).
237///
238/// # References
239///
240/// [pselect(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html)
241///
242/// [The new pselect() system call](https://lwn.net/Articles/176911/)
243///
244/// [`FdSet::highest`]: struct.FdSet.html#method.highest
245pub fn pselect<'a, N, R, W, E, T, S>(nfds: N,
246    readfds: R,
247    writefds: W,
248    errorfds: E,
249    timeout: T,
250                                     sigmask: S) -> Result<c_int>
251where
252    N: Into<Option<c_int>>,
253    R: Into<Option<&'a mut FdSet>>,
254    W: Into<Option<&'a mut FdSet>>,
255    E: Into<Option<&'a mut FdSet>>,
256    T: Into<Option<&'a TimeSpec>>,
257    S: Into<Option<&'a SigSet>>,
258{
259    let mut readfds = readfds.into();
260    let mut writefds = writefds.into();
261    let mut errorfds = errorfds.into();
262    let sigmask = sigmask.into();
263    let timeout = timeout.into();
264
265    let nfds = nfds.into().unwrap_or_else(|| {
266        readfds.iter_mut()
267            .chain(writefds.iter_mut())
268            .chain(errorfds.iter_mut())
269            .map(|set| set.highest().unwrap_or(-1))
270            .max()
271            .unwrap_or(-1) + 1
272    });
273
274    let readfds = readfds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut());
275    let writefds = writefds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut());
276    let errorfds = errorfds.map(|set| set as *mut _ as *mut libc::fd_set).unwrap_or(null_mut());
277    let timeout = timeout.map(|ts| ts.as_ref() as *const libc::timespec).unwrap_or(null());
278    let sigmask = sigmask.map(|sm| sm.as_ref() as *const libc::sigset_t).unwrap_or(null());
279
280    let res = unsafe {
281        libc::pselect(nfds, readfds, writefds, errorfds, timeout, sigmask)
282    };
283
284    Errno::result(res)
285}
286
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use std::os::unix::io::RawFd;
292    use crate::sys::time::{TimeVal, TimeValLike};
293    use crate::unistd::{write, pipe};
294
295    #[test]
296    fn fdset_insert() {
297        let mut fd_set = FdSet::new();
298
299        for i in 0..FD_SETSIZE {
300            assert!(!fd_set.contains(i as RawFd));
301        }
302
303        fd_set.insert(7);
304
305        assert!(fd_set.contains(7));
306    }
307
308    #[test]
309    fn fdset_remove() {
310        let mut fd_set = FdSet::new();
311
312        for i in 0..FD_SETSIZE {
313            assert!(!fd_set.contains(i as RawFd));
314        }
315
316        fd_set.insert(7);
317        fd_set.remove(7);
318
319        for i in 0..FD_SETSIZE {
320            assert!(!fd_set.contains(i as RawFd));
321        }
322    }
323
324    #[test]
325    fn fdset_clear() {
326        let mut fd_set = FdSet::new();
327        fd_set.insert(1);
328        fd_set.insert((FD_SETSIZE / 2) as RawFd);
329        fd_set.insert((FD_SETSIZE - 1) as RawFd);
330
331        fd_set.clear();
332
333        for i in 0..FD_SETSIZE {
334            assert!(!fd_set.contains(i as RawFd));
335        }
336    }
337
338    #[test]
339    fn fdset_highest() {
340        let mut set = FdSet::new();
341        assert_eq!(set.highest(), None);
342        set.insert(0);
343        assert_eq!(set.highest(), Some(0));
344        set.insert(90);
345        assert_eq!(set.highest(), Some(90));
346        set.remove(0);
347        assert_eq!(set.highest(), Some(90));
348        set.remove(90);
349        assert_eq!(set.highest(), None);
350
351        set.insert(4);
352        set.insert(5);
353        set.insert(7);
354        assert_eq!(set.highest(), Some(7));
355    }
356
357    #[test]
358    fn fdset_fds() {
359        let mut set = FdSet::new();
360        assert_eq!(set.fds(None).collect::<Vec<_>>(), vec![]);
361        set.insert(0);
362        assert_eq!(set.fds(None).collect::<Vec<_>>(), vec![0]);
363        set.insert(90);
364        assert_eq!(set.fds(None).collect::<Vec<_>>(), vec![0, 90]);
365
366        // highest limit
367        assert_eq!(set.fds(Some(89)).collect::<Vec<_>>(), vec![0]);
368        assert_eq!(set.fds(Some(90)).collect::<Vec<_>>(), vec![0, 90]);
369    }
370
371    #[test]
372    fn test_select() {
373        let (r1, w1) = pipe().unwrap();
374        write(w1, b"hi!").unwrap();
375        let (r2, _w2) = pipe().unwrap();
376
377        let mut fd_set = FdSet::new();
378        fd_set.insert(r1);
379        fd_set.insert(r2);
380
381        let mut timeout = TimeVal::seconds(10);
382        assert_eq!(1, select(None,
383                             &mut fd_set,
384                             None,
385                             None,
386                             &mut timeout).unwrap());
387        assert!(fd_set.contains(r1));
388        assert!(!fd_set.contains(r2));
389    }
390
391    #[test]
392    fn test_select_nfds() {
393        let (r1, w1) = pipe().unwrap();
394        write(w1, b"hi!").unwrap();
395        let (r2, _w2) = pipe().unwrap();
396
397        let mut fd_set = FdSet::new();
398        fd_set.insert(r1);
399        fd_set.insert(r2);
400
401        let mut timeout = TimeVal::seconds(10);
402        assert_eq!(1, select(Some(fd_set.highest().unwrap() + 1),
403                &mut fd_set,
404                None,
405                None,
406                             &mut timeout).unwrap());
407        assert!(fd_set.contains(r1));
408        assert!(!fd_set.contains(r2));
409    }
410
411    #[test]
412    fn test_select_nfds2() {
413        let (r1, w1) = pipe().unwrap();
414        write(w1, b"hi!").unwrap();
415        let (r2, _w2) = pipe().unwrap();
416
417        let mut fd_set = FdSet::new();
418        fd_set.insert(r1);
419        fd_set.insert(r2);
420
421        let mut timeout = TimeVal::seconds(10);
422        assert_eq!(1, select(::std::cmp::max(r1, r2) + 1,
423                &mut fd_set,
424                None,
425                None,
426                             &mut timeout).unwrap());
427        assert!(fd_set.contains(r1));
428        assert!(!fd_set.contains(r2));
429    }
430}