rustix/event/
select.rs

1//! The `select` function.
2//!
3//! # Safety
4//!
5//! `select` is unsafe due to I/O safety.
6#![allow(unsafe_code)]
7
8#[cfg(any(linux_like, target_os = "wasi"))]
9use crate::backend::c;
10use crate::fd::RawFd;
11use crate::{backend, io};
12#[cfg(any(windows, target_os = "wasi"))]
13use core::mem::{align_of, size_of};
14#[cfg(any(windows, target_os = "wasi"))]
15use core::slice;
16
17pub use crate::timespec::{Nsecs, Secs, Timespec};
18
19/// wasi-libc's `fd_set` type. The libc bindings for it have private fields,
20/// so we redeclare it for ourselves so that we can access the fields. They're
21/// publicly exposed in wasi-libc.
22#[cfg(target_os = "wasi")]
23#[repr(C)]
24struct FD_SET {
25    /// The wasi-libc headers call this `__nfds`.
26    fd_count: usize,
27    /// The wasi-libc headers call this `__fds`.
28    fd_array: [i32; c::FD_SETSIZE],
29}
30
31#[cfg(windows)]
32use windows_sys::Win32::Networking::WinSock::FD_SET;
33
34/// Storage element type for use with [`select`].
35#[cfg(any(
36    windows,
37    all(
38        target_pointer_width = "64",
39        any(target_os = "freebsd", target_os = "dragonfly")
40    )
41))]
42#[repr(transparent)]
43#[derive(Copy, Clone, Default)]
44pub struct FdSetElement(pub(crate) u64);
45
46/// Storage element type for use with [`select`].
47#[cfg(linux_like)]
48#[repr(transparent)]
49#[derive(Copy, Clone, Default)]
50pub struct FdSetElement(pub(crate) c::c_ulong);
51
52/// Storage element type for use with [`select`].
53#[cfg(not(any(
54    linux_like,
55    windows,
56    target_os = "wasi",
57    all(
58        target_pointer_width = "64",
59        any(target_os = "freebsd", target_os = "dragonfly")
60    )
61)))]
62#[repr(transparent)]
63#[derive(Copy, Clone, Default)]
64pub struct FdSetElement(pub(crate) u32);
65
66/// Storage element type for use with [`select`].
67#[cfg(target_os = "wasi")]
68#[repr(transparent)]
69#[derive(Copy, Clone, Default)]
70pub struct FdSetElement(pub(crate) usize);
71
72/// `select(nfds, readfds, writefds, exceptfds, timeout)`—Wait for events on
73/// sets of file descriptors.
74///
75/// `readfds`, `writefds`, `exceptfds` must point to arrays of `FdSetElement`
76/// containing at least `nfds.div_ceil(size_of::<FdSetElement>())` elements.
77///
78/// This `select` wrapper differs from POSIX in that `nfds` is not limited to
79/// `FD_SETSIZE`. Instead of using the fixed-sized `fd_set` type, this function
80/// takes raw pointers to arrays of `fd_set_num_elements(max_fd + 1, num_fds)`,
81/// where `max_fd` is the maximum value of any fd that will be inserted into
82/// the set, and `num_fds` is the maximum number of fds that will be inserted
83/// into the set.
84///
85/// In particular, on Apple platforms, this function behaves as if
86/// `_DARWIN_UNLIMITED_SELECT` were predefined.
87///
88/// On illumos, this function is not defined because the `select` function on
89/// this platform always has an `FD_SETSIZE` limitation, following POSIX. This
90/// platform's documentation recommends using [`poll`] instead.
91///
92/// [`fd_set_insert`], [`fd_set_remove`], and [`FdSetIter`] are provided for
93/// setting, clearing, and iterating with sets.
94///
95/// [`poll`]: crate::event::poll()
96///
97/// # Safety
98///
99/// All fds in in all the sets must correspond to open file descriptors.
100///
101/// # References
102///  - [POSIX]
103///  - [Linux]
104///  - [Apple]
105///  - [FreeBSD]
106///  - [NetBSD]
107///  - [OpenBSD]
108///  - [DragonFly BSD]
109///  - [Winsock]
110///  - [glibc]
111///
112///  [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/select.html
113///  [Linux]: https://man7.org/linux/man-pages/man2/select.2.html
114///  [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/select.2.html
115///  [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=select&sektion=2
116///  [NetBSD]: https://man.netbsd.org/select.2
117///  [OpenBSD]: https://man.openbsd.org/select.2
118///  [DragonFly BSD]: https://man.dragonflybsd.org/?command=select&section=2
119///  [Winsock]: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-select
120///  [glibc]: https://sourceware.org/glibc/manual/latest/html_node/Waiting-for-I_002fO.html#index-select
121pub unsafe fn select(
122    nfds: i32,
123    readfds: Option<&mut [FdSetElement]>,
124    writefds: Option<&mut [FdSetElement]>,
125    exceptfds: Option<&mut [FdSetElement]>,
126    timeout: Option<&Timespec>,
127) -> io::Result<i32> {
128    backend::event::syscalls::select(nfds, readfds, writefds, exceptfds, timeout)
129}
130
131#[cfg(not(any(windows, target_os = "wasi")))]
132const BITS: usize = core::mem::size_of::<FdSetElement>() * 8;
133
134/// Set `fd` in the set pointed to by `fds`.
135#[doc(alias = "FD_SET")]
136#[inline]
137pub fn fd_set_insert(fds: &mut [FdSetElement], fd: RawFd) {
138    #[cfg(not(any(windows, target_os = "wasi")))]
139    {
140        let fd = fd as usize;
141        fds[fd / BITS].0 |= 1 << (fd % BITS);
142    }
143
144    #[cfg(any(windows, target_os = "wasi"))]
145    {
146        let set = unsafe { &mut *fds.as_mut_ptr().cast::<FD_SET>() };
147        let fd_count = set.fd_count;
148        let fd_array = unsafe { slice::from_raw_parts(set.fd_array.as_ptr(), fd_count as usize) };
149
150        if !fd_array.iter().any(|p| *p as RawFd == fd) {
151            let fd_array = unsafe {
152                slice::from_raw_parts_mut(set.fd_array.as_mut_ptr(), fd_count as usize + 1)
153            };
154            set.fd_count = fd_count + 1;
155            fd_array[fd_count as usize] = fd as _;
156        }
157    }
158}
159
160/// Clear `fd` in the set pointed to by `fds`.
161#[doc(alias = "FD_CLR")]
162#[inline]
163pub fn fd_set_remove(fds: &mut [FdSetElement], fd: RawFd) {
164    #[cfg(not(any(windows, target_os = "wasi")))]
165    {
166        let fd = fd as usize;
167        fds[fd / BITS].0 &= !(1 << (fd % BITS));
168    }
169
170    #[cfg(any(windows, target_os = "wasi"))]
171    {
172        let set = unsafe { &mut *fds.as_mut_ptr().cast::<FD_SET>() };
173        let fd_count = set.fd_count;
174        let fd_array = unsafe { slice::from_raw_parts(set.fd_array.as_ptr(), fd_count as usize) };
175
176        if let Some(pos) = fd_array.iter().position(|p| *p as RawFd == fd) {
177            set.fd_count = fd_count - 1;
178            set.fd_array[pos] = *set.fd_array.last().unwrap();
179        }
180    }
181}
182
183/// Compute the minimum `nfds` value needed for the set pointed to by
184/// `fds`.
185#[inline]
186pub fn fd_set_bound(fds: &[FdSetElement]) -> RawFd {
187    #[cfg(not(any(windows, target_os = "wasi")))]
188    {
189        if let Some(position) = fds.iter().rposition(|element| element.0 != 0) {
190            let element = fds[position].0;
191            (position * BITS + (BITS - element.leading_zeros() as usize)) as RawFd
192        } else {
193            0
194        }
195    }
196
197    #[cfg(any(windows, target_os = "wasi"))]
198    {
199        let set = unsafe { &*fds.as_ptr().cast::<FD_SET>() };
200        let fd_count = set.fd_count;
201        let fd_array = unsafe { slice::from_raw_parts(set.fd_array.as_ptr(), fd_count as usize) };
202        let mut max = 0;
203        for fd in fd_array {
204            if *fd >= max {
205                max = *fd + 1;
206            }
207        }
208        max as RawFd
209    }
210}
211
212/// Compute the number of `FdSetElement`s needed to hold a set which can
213/// contain up to `set_count` file descriptors with values less than `nfds`.
214#[inline]
215pub fn fd_set_num_elements(set_count: usize, nfds: RawFd) -> usize {
216    #[cfg(any(windows, target_os = "wasi"))]
217    {
218        let _ = nfds;
219
220        fd_set_num_elements_for_fd_array(set_count)
221    }
222
223    #[cfg(not(any(windows, target_os = "wasi")))]
224    {
225        let _ = set_count;
226
227        fd_set_num_elements_for_bitvector(nfds)
228    }
229}
230
231/// `fd_set_num_elements` implementation on platforms with fd array
232/// implementations.
233#[cfg(any(windows, target_os = "wasi"))]
234#[inline]
235pub(crate) fn fd_set_num_elements_for_fd_array(set_count: usize) -> usize {
236    // Allocate space for an `fd_count` field, plus `set_count` elements
237    // for the `fd_array` field.
238    div_ceil(
239        align_of::<FD_SET>() + set_count * size_of::<RawFd>(),
240        size_of::<FdSetElement>(),
241    )
242}
243
244/// `fd_set_num_elements` implementation on platforms with bitvector
245/// implementations.
246#[cfg(not(any(windows, target_os = "wasi")))]
247#[inline]
248pub(crate) fn fd_set_num_elements_for_bitvector(nfds: RawFd) -> usize {
249    // Allocate space for a dense bitvector for `nfds` bits.
250    let nfds = nfds as usize;
251    div_ceil(nfds, BITS)
252}
253
254fn div_ceil(lhs: usize, rhs: usize) -> usize {
255    let d = lhs / rhs;
256    let r = lhs % rhs;
257    if r > 0 {
258        d + 1
259    } else {
260        d
261    }
262}
263
264/// An iterator over the fds in a set.
265#[doc(alias = "FD_ISSET")]
266#[cfg(not(any(windows, target_os = "wasi")))]
267pub struct FdSetIter<'a> {
268    current: RawFd,
269    fds: &'a [FdSetElement],
270}
271
272/// An iterator over the fds in a set.
273#[doc(alias = "FD_ISSET")]
274#[cfg(any(windows, target_os = "wasi"))]
275pub struct FdSetIter<'a> {
276    current: usize,
277    fds: &'a [FdSetElement],
278}
279
280impl<'a> FdSetIter<'a> {
281    /// Construct a `FdSetIter` for the given set.
282    pub fn new(fds: &'a [FdSetElement]) -> Self {
283        Self { current: 0, fds }
284    }
285}
286
287#[cfg(not(any(windows, target_os = "wasi")))]
288impl<'a> Iterator for FdSetIter<'a> {
289    type Item = RawFd;
290
291    fn next(&mut self) -> Option<Self::Item> {
292        if let Some(element) = self.fds.get(self.current as usize / BITS) {
293            // Test whether the current element has more bits set.
294            let shifted = element.0 >> ((self.current as usize % BITS) as u32);
295            if shifted != 0 {
296                let fd = self.current + shifted.trailing_zeros() as RawFd;
297                self.current = fd + 1;
298                return Some(fd);
299            }
300
301            // Search through the array for the next element with bits set.
302            if let Some(index) = self.fds[(self.current as usize / BITS) + 1..]
303                .iter()
304                .position(|element| element.0 != 0)
305            {
306                let index = index + (self.current as usize / BITS) + 1;
307                let element = self.fds[index].0;
308                let fd = (index * BITS) as RawFd + element.trailing_zeros() as RawFd;
309                self.current = fd + 1;
310                return Some(fd);
311            }
312        }
313        None
314    }
315}
316
317#[cfg(any(windows, target_os = "wasi"))]
318impl<'a> Iterator for FdSetIter<'a> {
319    type Item = RawFd;
320
321    fn next(&mut self) -> Option<Self::Item> {
322        let current = self.current;
323
324        let set = unsafe { &*self.fds.as_ptr().cast::<FD_SET>() };
325        let fd_count = set.fd_count;
326        let fd_array = unsafe { slice::from_raw_parts(set.fd_array.as_ptr(), fd_count as usize) };
327
328        if current == fd_count as usize {
329            return None;
330        }
331        let fd = fd_array[current as usize];
332        self.current = current + 1;
333        Some(fd as RawFd)
334    }
335}
336
337#[cfg(test)]
338mod test {
339    use super::*;
340    use core::mem::{align_of, size_of};
341
342    #[test]
343    #[cfg(any(windows, target_os = "wasi"))]
344    fn layouts() {
345        // The `FdSetElement` array should be suitably aligned.
346        assert_eq!(align_of::<FdSetElement>(), align_of::<FD_SET>());
347
348        // The layout of `FD_SET` should match our layout of a set of the same
349        // size.
350        assert_eq!(
351            fd_set_num_elements_for_fd_array(
352                memoffset::span_of!(FD_SET, fd_array).len() / size_of::<RawFd>()
353            ) * size_of::<FdSetElement>(),
354            size_of::<FD_SET>()
355        );
356    }
357
358    #[test]
359    #[cfg(any(bsd, linux_kernel))]
360    fn layouts() {
361        use crate::backend::c;
362
363        // The `FdSetElement` array should be suitably aligned.
364        assert_eq!(align_of::<FdSetElement>(), align_of::<c::fd_set>());
365
366        // The layout of `fd_set` should match our layout of a set of the same
367        // size.
368        assert_eq!(
369            fd_set_num_elements_for_bitvector(c::FD_SETSIZE as RawFd) * size_of::<FdSetElement>(),
370            size_of::<c::fd_set>()
371        );
372    }
373}