| 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 | |