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