diff options
Diffstat (limited to 'vendor/rustix/src/event/select.rs')
| -rw-r--r-- | vendor/rustix/src/event/select.rs | 391 |
1 files changed, 391 insertions, 0 deletions
diff --git a/vendor/rustix/src/event/select.rs b/vendor/rustix/src/event/select.rs new file mode 100644 index 00000000..6bb93b53 --- /dev/null +++ b/vendor/rustix/src/event/select.rs @@ -0,0 +1,391 @@ +//! The `select` function. +//! +//! # Safety +//! +//! `select` is unsafe due to I/O safety. +#![allow(unsafe_code)] + +#[cfg(any(linux_like, target_os = "wasi"))] +use crate::backend::c; +use crate::event::Timespec; +use crate::fd::RawFd; +use crate::{backend, io}; +#[cfg(any(windows, target_os = "wasi"))] +use core::mem::align_of; +use core::mem::size_of; + +/// wasi-libc's `fd_set` type. The libc bindings for it have private fields, so +/// we redeclare it for ourselves so that we can access the fields. They're +/// publicly exposed in wasi-libc. +#[cfg(target_os = "wasi")] +#[repr(C)] +struct FD_SET { + /// The wasi-libc headers call this `__nfds`. + fd_count: usize, + /// The wasi-libc headers call this `__fds`. + fd_array: [i32; c::FD_SETSIZE], +} + +#[cfg(windows)] +use windows_sys::Win32::Networking::WinSock::FD_SET; + +/// Storage element type for use with [`select`]. +#[cfg(all( + target_pointer_width = "64", + any(windows, target_os = "freebsd", target_os = "dragonfly") +))] +#[repr(transparent)] +#[derive(Copy, Clone, Default)] +pub struct FdSetElement(pub(crate) u64); + +/// Storage element type for use with [`select`]. +#[cfg(linux_like)] +#[repr(transparent)] +#[derive(Copy, Clone, Default)] +pub struct FdSetElement(pub(crate) c::c_ulong); + +/// Storage element type for use with [`select`]. +#[cfg(not(any( + linux_like, + target_os = "wasi", + all( + target_pointer_width = "64", + any(windows, target_os = "freebsd", target_os = "dragonfly") + ) +)))] +#[repr(transparent)] +#[derive(Copy, Clone, Default)] +pub struct FdSetElement(pub(crate) u32); + +/// Storage element type for use with [`select`]. +#[cfg(target_os = "wasi")] +#[repr(transparent)] +#[derive(Copy, Clone, Default)] +pub struct FdSetElement(pub(crate) usize); + +/// `select(nfds, readfds, writefds, exceptfds, timeout)`—Wait for events on +/// sets of file descriptors. +/// +/// `readfds`, `writefds`, `exceptfds` must point to arrays of `FdSetElement` +/// containing at least `nfds.div_ceil(size_of::<FdSetElement>())` elements. +/// +/// If an unsupported timeout is passed, this function fails with +/// [`io::Errno::INVAL`]. +/// +/// This `select` wrapper differs from POSIX in that `nfds` is not limited to +/// `FD_SETSIZE`. Instead of using the fixed-sized `fd_set` type, this function +/// takes raw pointers to arrays of `fd_set_num_elements(max_fd + 1, num_fds)`, +/// where `max_fd` is the maximum value of any fd that will be inserted into +/// the set, and `num_fds` is the maximum number of fds that will be inserted +/// into the set. +/// +/// In particular, on Apple platforms, this function behaves as if +/// `_DARWIN_UNLIMITED_SELECT` were predefined. +/// +/// On illumos, this function is not defined because the `select` function on +/// this platform always has an `FD_SETSIZE` limitation, following POSIX. This +/// platform's documentation recommends using [`poll`] instead. +/// +/// [`fd_set_insert`], [`fd_set_remove`], and [`FdSetIter`] are provided for +/// setting, clearing, and iterating with sets. +/// +/// [`poll`]: crate::event::poll() +/// +/// # Safety +/// +/// All fds in all the sets must correspond to open file descriptors. +/// +/// # References +/// - [POSIX] +/// - [Linux] +/// - [Apple] +/// - [FreeBSD] +/// - [NetBSD] +/// - [OpenBSD] +/// - [DragonFly BSD] +/// - [Winsock] +/// - [glibc] +/// +/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/select.html +/// [Linux]: https://man7.org/linux/man-pages/man2/select.2.html +/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/select.2.html +/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=select&sektion=2 +/// [NetBSD]: https://man.netbsd.org/select.2 +/// [OpenBSD]: https://man.openbsd.org/select.2 +/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=select§ion=2 +/// [Winsock]: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-select +/// [glibc]: https://sourceware.org/glibc/manual/latest/html_node/Waiting-for-I_002fO.html#index-select +pub unsafe fn select( + nfds: i32, + readfds: Option<&mut [FdSetElement]>, + writefds: Option<&mut [FdSetElement]>, + exceptfds: Option<&mut [FdSetElement]>, + timeout: Option<&Timespec>, +) -> io::Result<i32> { + backend::event::syscalls::select(nfds, readfds, writefds, exceptfds, timeout) +} + +#[cfg(not(any(windows, target_os = "wasi")))] +const BITS: usize = size_of::<FdSetElement>() * 8; + +/// Set `fd` in the set pointed to by `fds`. +#[doc(alias = "FD_SET")] +#[inline] +pub fn fd_set_insert(fds: &mut [FdSetElement], fd: RawFd) { + #[cfg(not(any(windows, target_os = "wasi")))] + { + let fd = fd as usize; + fds[fd / BITS].0 |= 1 << (fd % BITS); + } + + #[cfg(any(windows, target_os = "wasi"))] + { + let set = unsafe { &mut *fds.as_mut_ptr().cast::<FD_SET>() }; + let fd_count = set.fd_count; + let fd_array = &set.fd_array[..fd_count as usize]; + + if !fd_array.contains(&(fd as _)) { + let fd_array = &mut set.fd_array[..fd_count as usize + 1]; + set.fd_count = fd_count + 1; + fd_array[fd_count as usize] = fd as _; + } + } +} + +/// Clear `fd` in the set pointed to by `fds`. +#[doc(alias = "FD_CLR")] +#[inline] +pub fn fd_set_remove(fds: &mut [FdSetElement], fd: RawFd) { + #[cfg(not(any(windows, target_os = "wasi")))] + { + let fd = fd as usize; + fds[fd / BITS].0 &= !(1 << (fd % BITS)); + } + + #[cfg(any(windows, target_os = "wasi"))] + { + let set = unsafe { &mut *fds.as_mut_ptr().cast::<FD_SET>() }; + let fd_count = set.fd_count; + let fd_array = &set.fd_array[..fd_count as usize]; + + if let Some(pos) = fd_array.iter().position(|p| *p as RawFd == fd) { + set.fd_count = fd_count - 1; + set.fd_array[pos] = *set.fd_array.last().unwrap(); + } + } +} + +/// Compute the minimum `nfds` value needed for the set pointed to by `fds`. +#[inline] +pub fn fd_set_bound(fds: &[FdSetElement]) -> RawFd { + #[cfg(not(any(windows, target_os = "wasi")))] + { + if let Some(position) = fds.iter().rposition(|element| element.0 != 0) { + let element = fds[position].0; + (position * BITS + (BITS - element.leading_zeros() as usize)) as RawFd + } else { + 0 + } + } + + #[cfg(any(windows, target_os = "wasi"))] + { + let set = unsafe { &*fds.as_ptr().cast::<FD_SET>() }; + let fd_count = set.fd_count; + let fd_array = &set.fd_array[..fd_count as usize]; + let mut max = 0; + for fd in fd_array { + if *fd >= max { + max = *fd + 1; + } + } + max as RawFd + } +} + +/// Compute the number of `FdSetElement`s needed to hold a set which can +/// contain up to `set_count` file descriptors with values less than `nfds`. +#[inline] +pub fn fd_set_num_elements(set_count: usize, nfds: RawFd) -> usize { + #[cfg(any(windows, target_os = "wasi"))] + { + let _ = nfds; + + fd_set_num_elements_for_fd_array(set_count) + } + + #[cfg(not(any(windows, target_os = "wasi")))] + { + let _ = set_count; + + fd_set_num_elements_for_bitvector(nfds) + } +} + +/// `fd_set_num_elements` implementation on platforms with fd array +/// implementations. +#[cfg(any(windows, target_os = "wasi"))] +#[inline] +pub(crate) fn fd_set_num_elements_for_fd_array(set_count: usize) -> usize { + // Ensure that we always have a big enough set to dereference an `FD_SET`. + core::cmp::max( + fd_set_num_elements_for_fd_array_raw(set_count), + div_ceil(size_of::<FD_SET>(), size_of::<FdSetElement>()), + ) +} + +/// Compute the raw `fd_set_num_elements` value, before ensuring the value is +/// big enough to dereference an `FD_SET`. +#[cfg(any(windows, target_os = "wasi"))] +#[inline] +fn fd_set_num_elements_for_fd_array_raw(set_count: usize) -> usize { + // Allocate space for an `fd_count` field, plus `set_count` elements + // for the `fd_array` field. + div_ceil( + core::cmp::max(align_of::<FD_SET>(), align_of::<RawFd>()) + set_count * size_of::<RawFd>(), + size_of::<FdSetElement>(), + ) +} + +/// `fd_set_num_elements` implementation on platforms with bitvector +/// implementations. +#[cfg(not(any(windows, target_os = "wasi")))] +#[inline] +pub(crate) fn fd_set_num_elements_for_bitvector(nfds: RawFd) -> usize { + // Allocate space for a dense bitvector for `nfds` bits. + let nfds = nfds as usize; + div_ceil(nfds, BITS) +} + +fn div_ceil(lhs: usize, rhs: usize) -> usize { + let d = lhs / rhs; + let r = lhs % rhs; + if r > 0 { + d + 1 + } else { + d + } +} + +/// An iterator over the fds in a set. +#[doc(alias = "FD_ISSET")] +#[cfg(not(any(windows, target_os = "wasi")))] +pub struct FdSetIter<'a> { + current: RawFd, + fds: &'a [FdSetElement], +} + +/// An iterator over the fds in a set. +#[doc(alias = "FD_ISSET")] +#[cfg(any(windows, target_os = "wasi"))] +pub struct FdSetIter<'a> { + current: usize, + fds: &'a [FdSetElement], +} + +impl<'a> FdSetIter<'a> { + /// Construct a `FdSetIter` for the given set. + pub fn new(fds: &'a [FdSetElement]) -> Self { + Self { current: 0, fds } + } +} + +#[cfg(not(any(windows, target_os = "wasi")))] +impl<'a> Iterator for FdSetIter<'a> { + type Item = RawFd; + + fn next(&mut self) -> Option<Self::Item> { + if let Some(element) = self.fds.get(self.current as usize / BITS) { + // Test whether the current element has more bits set. + let shifted = element.0 >> ((self.current as usize % BITS) as u32); + if shifted != 0 { + let fd = self.current + shifted.trailing_zeros() as RawFd; + self.current = fd + 1; + return Some(fd); + } + + // Search through the array for the next element with bits set. + if let Some(index) = self.fds[(self.current as usize / BITS) + 1..] + .iter() + .position(|element| element.0 != 0) + { + let index = index + (self.current as usize / BITS) + 1; + let element = self.fds[index].0; + let fd = (index * BITS) as RawFd + element.trailing_zeros() as RawFd; + self.current = fd + 1; + return Some(fd); + } + } + None + } +} + +#[cfg(any(windows, target_os = "wasi"))] +impl<'a> Iterator for FdSetIter<'a> { + type Item = RawFd; + + fn next(&mut self) -> Option<Self::Item> { + let current = self.current; + + let set = unsafe { &*self.fds.as_ptr().cast::<FD_SET>() }; + let fd_count = set.fd_count; + let fd_array = &set.fd_array[..fd_count as usize]; + + if current == fd_count as usize { + return None; + } + let fd = fd_array[current as usize]; + self.current = current + 1; + Some(fd as RawFd) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::mem::{align_of, size_of}; + + #[test] + #[cfg(any(windows, target_os = "wasi"))] + fn layouts() { + // The `FdSetElement` array should be suitably aligned. + assert_eq!(align_of::<FdSetElement>(), align_of::<FD_SET>()); + + // The layout of `FD_SET` should match our layout of a set of the same + // size. + assert_eq!( + fd_set_num_elements_for_fd_array_raw( + memoffset::span_of!(FD_SET, fd_array).len() / size_of::<RawFd>() + ) * size_of::<FdSetElement>(), + size_of::<FD_SET>() + ); + assert_eq!( + fd_set_num_elements_for_fd_array( + memoffset::span_of!(FD_SET, fd_array).len() / size_of::<RawFd>() + ) * size_of::<FdSetElement>(), + size_of::<FD_SET>() + ); + + // Don't create fd sets smaller than `FD_SET`. + assert_eq!( + fd_set_num_elements_for_fd_array(0) * size_of::<FdSetElement>(), + size_of::<FD_SET>() + ); + } + + #[test] + #[cfg(any(bsd, linux_kernel))] + fn layouts() { + use crate::backend::c; + + // The `FdSetElement` array should be suitably aligned. + assert_eq!(align_of::<FdSetElement>(), align_of::<c::fd_set>()); + + // The layout of `fd_set` should match our layout of a set of the same + // size. + assert_eq!( + fd_set_num_elements_for_bitvector(c::FD_SETSIZE as RawFd) * size_of::<FdSetElement>(), + size_of::<c::fd_set>() + ); + } +} |
