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" ))] |
9 | use crate::backend::c; |
10 | use crate::fd::RawFd; |
11 | use crate::{backend, io}; |
12 | #[cfg (any(windows, target_os = "wasi" ))] |
13 | use core::mem::{align_of, size_of}; |
14 | #[cfg (any(windows, target_os = "wasi" ))] |
15 | use core::slice; |
16 | |
17 | pub 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)] |
24 | struct 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)] |
32 | use 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)] |
44 | pub 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)] |
50 | pub 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)] |
64 | pub 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)] |
70 | pub 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§ion=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 |
121 | pub 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" )))] |
132 | const 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 ] |
137 | pub fn fd_set_insert(fds: &mut [FdSetElement], fd: RawFd) { |
138 | #[cfg (not(any(windows, target_os = "wasi" )))] |
139 | { |
140 | let fd: usize = 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 ] |
163 | pub fn fd_set_remove(fds: &mut [FdSetElement], fd: RawFd) { |
164 | #[cfg (not(any(windows, target_os = "wasi" )))] |
165 | { |
166 | let fd: usize = 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 ] |
186 | pub 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 ] |
215 | pub 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 ] |
235 | pub(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 ] |
248 | pub(crate) fn fd_set_num_elements_for_bitvector(nfds: RawFd) -> usize { |
249 | // Allocate space for a dense bitvector for `nfds` bits. |
250 | let nfds: usize = nfds as usize; |
251 | div_ceil(lhs:nfds, BITS) |
252 | } |
253 | |
254 | fn div_ceil(lhs: usize, rhs: usize) -> usize { |
255 | let d: usize = lhs / rhs; |
256 | let r: usize = 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" )))] |
267 | pub 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" ))] |
275 | pub struct FdSetIter<'a> { |
276 | current: usize, |
277 | fds: &'a [FdSetElement], |
278 | } |
279 | |
280 | impl<'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" )))] |
288 | impl<'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" ))] |
318 | impl<'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)] |
338 | mod 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 | } |
374 | |