Skip to content

Commit

Permalink
Add fd_set and other convenience functions.
Browse files Browse the repository at this point in the history
  • Loading branch information
sunfishcode committed Sep 14, 2024
1 parent c6b92bd commit f249fc3
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 36 deletions.
2 changes: 1 addition & 1 deletion src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
41 changes: 39 additions & 2 deletions src/event/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<FdSetElement>())` 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::<FdSetElement>() * 8)` has the bit
/// `1 << (fd % (size_of::<FdSetElement>() * 8))` set.
/// `1 << (fd % (size_of::<FdSetElement>() * 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.
Expand Down Expand Up @@ -63,3 +65,38 @@ pub unsafe fn select(
) -> io::Result<i32> {
backend::event::syscalls::select(nfds, readfds, writefds, exceptfds, timeout)
}

const BITS: usize = size_of::<FdSetElement>() * 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
}
54 changes: 21 additions & 33 deletions tests/event/select.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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::<FdSetElement>() * 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,
Expand All @@ -39,31 +34,30 @@ 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'];
assert_eq!(retry_on_intr(|| read(&reader, &mut buf)).unwrap(), 1);
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,
Expand All @@ -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::<FdSetElement>() * 8;

// Create a pipe.
let (reader, writer) = pipe().unwrap();

Expand All @@ -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,
Expand All @@ -133,31 +122,30 @@ 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'];
assert_eq!(retry_on_intr(|| read(&reader, &mut buf)).unwrap(), 1);
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,
Expand All @@ -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();
Expand Down

0 comments on commit f249fc3

Please sign in to comment.