From f249fc35564716f4464e29b8e6454608c22d0138 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Sat, 14 Sep 2024 05:07:42 -0700 Subject: [PATCH] Add `fd_set` and other convenience functions. --- src/event/mod.rs | 2 +- src/event/select.rs | 41 ++++++++++++++++++++++++++++++-- tests/event/select.rs | 54 +++++++++++++++++-------------------------- 3 files changed, 61 insertions(+), 36 deletions(-) diff --git a/src/event/mod.rs b/src/event/mod.rs index be0f1bc89..d0ebee5be 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -30,4 +30,4 @@ pub use eventfd::{eventfd, EventfdFlags}; pub use pause::*; pub use poll::{poll, PollFd, PollFlags}; #[cfg(any(apple, freebsdlike, target_os = "netbsd"))] -pub use select::{select, FdSetElement, Timespec}; +pub use select::*; diff --git a/src/event/select.rs b/src/event/select.rs index 6f475e496..528c1573b 100644 --- a/src/event/select.rs +++ b/src/event/select.rs @@ -23,9 +23,11 @@ pub type FdSetElement = i32; /// `FD_SETSIZE`. Instead of using the opaque fixed-sized `fd_set` type, this /// function takes raw pointers to arrays of /// `nfds.div_ceil(size_of::())` elements of type `FdSetElement`, -/// where a fd `fd` is in the set if the element at index +/// representing bitvectors where a fd `fd` is set if the element at index /// `fd / (size_of::() * 8)` has the bit -/// `1 << (fd % (size_of::() * 8))` set. +/// `1 << (fd % (size_of::() * 8))` set. Convenience functions +/// [`fd_set`], [`fd_clr`], [`fd_isset`], and [`fd_bitvector_len`] are provided +/// for setting, clearing, testing, and sizing bitvectors. /// /// In particular, on Apple platforms, this function behaves as if /// `_DARWIN_UNLIMITED_SELECT` were predefined. @@ -63,3 +65,38 @@ pub unsafe fn select( ) -> io::Result { backend::event::syscalls::select(nfds, readfds, writefds, exceptfds, timeout) } + +const BITS: usize = size_of::() * 8; +use crate::fd::RawFd; + +/// Set `fd` in the bitvector pointed to by `fds`. +#[doc(alias = "FD_SET")] +#[inline] +pub fn fd_set(fd: RawFd, fds: &mut [FdSetElement]) { + let fd = fd as usize; + fds[fd / BITS] |= 1 << (fd % BITS); +} + +/// Clear `fd` in the bitvector pointed to by `fds`. +#[doc(alias = "FD_CLR")] +#[inline] +pub fn fd_clr(fd: RawFd, fds: &mut [FdSetElement]) { + let fd = fd as usize; + fds[fd / BITS] &= !(1 << (fd % BITS)); +} + +/// Test whether `fd` is set in the bitvector pointed to by `fds`. +#[doc(alias = "FD_ISSET")] +#[inline] +pub fn fd_isset(fd: RawFd, fds: &[FdSetElement]) -> bool { + let fd = fd as usize; + (fds[fd / BITS] & (1 << (fd % BITS))) != 0 +} + +/// Compute the number of `FdSetElement`s needed to hold a bitvector which can +/// contain file descriptors less than `nfds`. +#[inline] +pub fn fd_bitvector_len(nfds: RawFd) -> usize { + let nfds = nfds as usize; + (nfds + (BITS - 1)) / BITS +} diff --git a/tests/event/select.rs b/tests/event/select.rs index 12dfc2b86..c6f7d977e 100644 --- a/tests/event/select.rs +++ b/tests/event/select.rs @@ -1,6 +1,6 @@ #[cfg(feature = "pipe")] use { - rustix::event::{select, FdSetElement}, + rustix::event::{fd_bitvector_len, fd_clr, fd_isset, fd_set, select, FdSetElement, Timespec}, rustix::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd}, rustix::io::retry_on_intr, std::cmp::max, @@ -9,22 +9,17 @@ use { #[cfg(feature = "pipe")] #[test] fn test_select() { - use core::mem::size_of; use core::ptr::null_mut; - use rustix::event::Timespec; use rustix::io::{read, write}; use rustix::pipe::pipe; - // The number of bits in an `fd_set` element. - const BITS: usize = size_of::() * 8; - // Create a pipe. let (reader, writer) = pipe().unwrap(); let nfds = max(reader.as_raw_fd(), writer.as_raw_fd()) + 1; // `select` should say there's nothing ready to be read from the pipe. - let mut readfds = vec![0 as FdSetElement; (nfds as usize + (bits - 1)) / bits]; - readfds[reader.as_raw_fd() as usize / BITS] |= 1 << (reader.as_raw_fd() as usize % BITS); + let mut readfds = vec![0 as FdSetElement; fd_bitvector_len(nfds)]; + fd_set(reader.as_raw_fd(), &mut readfds); let num = retry_on_intr(|| unsafe { select( nfds, @@ -39,23 +34,22 @@ fn test_select() { }) .unwrap(); assert_eq!(num, 0); - assert_eq!(readfds[reader.as_raw_fd() as usize / BITS], 0); + assert!(!fd_isset(reader.as_raw_fd(), &readfds)); // Write a byte to the pipe. assert_eq!(retry_on_intr(|| write(&writer, b"a")).unwrap(), 1); // `select` should now say there's data to be read. - let mut readfds = vec![0 as FdSetElement; (nfds as usize + (bits - 1)) / bits]; - readfds[reader.as_raw_fd() as usize / BITS] |= 1 << (reader.as_raw_fd() as usize % BITS); + let mut readfds = vec![0 as FdSetElement; fd_bitvector_len(nfds)]; + fd_set(reader.as_raw_fd(), &mut readfds); let num = retry_on_intr(|| unsafe { select(nfds, readfds.as_mut_ptr(), null_mut(), null_mut(), None) }) .unwrap(); assert_eq!(num, 1); - assert_eq!( - readfds[reader.as_raw_fd() as usize / BITS], - 1 << (reader.as_raw_fd() as usize % BITS) - ); + assert!(fd_isset(reader.as_raw_fd(), &readfds)); + fd_clr(reader.as_raw_fd(), &mut readfds); + assert!(!fd_isset(reader.as_raw_fd(), &readfds)); // Read the byte from the pipe. let mut buf = [b'\0']; @@ -63,7 +57,7 @@ fn test_select() { assert_eq!(buf[0], b'a'); // Select should now say there's no more data to be read. - readfds[reader.as_raw_fd() as usize / BITS] |= 1 << (reader.as_raw_fd() as usize % BITS); + fd_set(reader.as_raw_fd(), &mut readfds); let num = retry_on_intr(|| unsafe { select( nfds, @@ -78,23 +72,18 @@ fn test_select() { }) .unwrap(); assert_eq!(num, 0); - assert_eq!(readfds[reader.as_raw_fd() as usize / BITS], 0); + assert!(!fd_isset(reader.as_raw_fd(), &readfds)); } #[cfg(feature = "pipe")] #[test] fn test_select_with_great_fds() { use core::cmp::max; - use core::mem::size_of; use core::ptr::null_mut; - use rustix::event::{select, Timespec}; use rustix::io::{read, write}; use rustix::pipe::pipe; use rustix::process::{getrlimit, setrlimit, Resource}; - // The number of bits in an `fd_set` element. - const BITS: usize = size_of::() * 8; - // Create a pipe. let (reader, writer) = pipe().unwrap(); @@ -117,8 +106,8 @@ fn test_select_with_great_fds() { let nfds = max(reader.as_raw_fd(), writer.as_raw_fd()) + 1; // `select` should say there's nothing ready to be read from the pipe. - let mut readfds = vec![0 as FdSetElement; (nfds as usize + (bits - 1)) / bits]; - readfds[reader.as_raw_fd() as usize / BITS] |= 1 << (reader.as_raw_fd() as usize % BITS); + let mut readfds = vec![0 as FdSetElement; fd_bitvector_len(nfds)]; + fd_set(reader.as_raw_fd(), &mut readfds); let num = retry_on_intr(|| unsafe { select( nfds, @@ -133,23 +122,22 @@ fn test_select_with_great_fds() { }) .unwrap(); assert_eq!(num, 0); - assert_eq!(readfds[reader.as_raw_fd() as usize / BITS], 0); + assert!(!fd_isset(reader.as_raw_fd(), &readfds)); // Write a byte to the pipe. assert_eq!(retry_on_intr(|| write(&writer, b"a")).unwrap(), 1); // `select` should now say there's data to be read. - let mut readfds = vec![0 as FdSetElement; (nfds as usize + (bits - 1)) / bits]; - readfds[reader.as_raw_fd() as usize / BITS] |= 1 << (reader.as_raw_fd() as usize % BITS); + let mut readfds = vec![0 as FdSetElement; fd_bitvector_len(nfds)]; + fd_set(reader.as_raw_fd(), &mut readfds); let num = retry_on_intr(|| unsafe { select(nfds, readfds.as_mut_ptr(), null_mut(), null_mut(), None) }) .unwrap(); assert_eq!(num, 1); - assert_eq!( - readfds[reader.as_raw_fd() as usize / BITS], - 1 << (reader.as_raw_fd() as usize % BITS) - ); + assert!(fd_isset(reader.as_raw_fd(), &readfds)); + fd_clr(reader.as_raw_fd(), &mut readfds); + assert!(!fd_isset(reader.as_raw_fd(), &readfds)); // Read the byte from the pipe. let mut buf = [b'\0']; @@ -157,7 +145,7 @@ fn test_select_with_great_fds() { assert_eq!(buf[0], b'a'); // Select should now say there's no more data to be read. - readfds[reader.as_raw_fd() as usize / BITS] |= 1 << (reader.as_raw_fd() as usize % BITS); + fd_set(reader.as_raw_fd(), &mut readfds); let num = retry_on_intr(|| unsafe { select( nfds, @@ -172,7 +160,7 @@ fn test_select_with_great_fds() { }) .unwrap(); assert_eq!(num, 0); - assert_eq!(readfds[reader.as_raw_fd() as usize / BITS], 0); + assert!(!fd_isset(reader.as_raw_fd(), &readfds)); // Reset the process limit. setrlimit(Resource::Nofile, orig_rlimit).unwrap();