Skip to content

Commit 7324888

Browse files
committed
Merge #701
701: Calculate `nfds` parameter for `select` r=asomers Doing this behind the scenes makes the API less error-prone and easier to use. It should also fix my issue in #679 (comment)
2 parents 4409962 + 885a1f7 commit 7324888

File tree

4 files changed

+222
-65
lines changed

4 files changed

+222
-65
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2424
has changed type from `c_int` to `SockProtocol`.
2525
It accepts a `None` value for default protocol that was specified with zero using `c_int`.
2626
([#647](https://github.com/nix-rust/nix/pull/647))
27+
- Made `select` easier to use, adding the ability to automatically calculate the `nfds` parameter using the new
28+
`FdSet::highest` ([#701](https://github.com/nix-rust/nix/pull/701))
2729
- Exposed `unistd::setresuid` and `unistd::setresgid` on FreeBSD and OpenBSD
2830
([#721](https://github.com/nix-rust/nix/pull/721))
2931
- Refactored the `statvfs` module removing extraneous API functions and the

src/sys/select.rs

+220-5
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,42 @@ impl FdSet {
5353
*bits = 0
5454
}
5555
}
56+
57+
/// Finds the highest file descriptor in the set.
58+
///
59+
/// Returns `None` if the set is empty.
60+
///
61+
/// This can be used to calculate the `nfds` parameter of the [`select`] function.
62+
///
63+
/// # Example
64+
///
65+
/// ```
66+
/// # extern crate nix;
67+
/// # use nix::sys::select::FdSet;
68+
/// # fn main() {
69+
/// let mut set = FdSet::new();
70+
/// set.insert(4);
71+
/// set.insert(9);
72+
/// assert_eq!(set.highest(), Some(9));
73+
/// # }
74+
/// ```
75+
///
76+
/// [`select`]: fn.select.html
77+
pub fn highest(&self) -> Option<RawFd> {
78+
for (i, &block) in self.bits.iter().enumerate().rev() {
79+
if block != 0 {
80+
// Highest bit is located at `BITS - 1 - n.leading_zeros()`. Examples:
81+
// 0b00000001
82+
// 7 leading zeros, result should be 0 (bit at index 0 is 1)
83+
// 0b001xxxxx
84+
// 2 leading zeros, result should be 5 (bit at index 5 is 1) - x may be 0 or 1
85+
86+
return Some((i * BITS + BITS - 1 - block.leading_zeros() as usize) as RawFd);
87+
}
88+
}
89+
90+
None
91+
}
5692
}
5793

5894
mod ffi {
@@ -68,11 +104,52 @@ mod ffi {
68104
}
69105
}
70106

71-
pub fn select(nfds: c_int,
72-
readfds: Option<&mut FdSet>,
73-
writefds: Option<&mut FdSet>,
74-
errorfds: Option<&mut FdSet>,
75-
timeout: Option<&mut TimeVal>) -> Result<c_int> {
107+
/// Monitors file descriptors for readiness (see [select(2)]).
108+
///
109+
/// Returns the total number of ready file descriptors in all sets. The sets are changed so that all
110+
/// file descriptors that are ready for the given operation are set.
111+
///
112+
/// When this function returns, `timeout` has an implementation-defined value.
113+
///
114+
/// # Parameters
115+
///
116+
/// * `nfds`: The highest file descriptor set in any of the passed `FdSet`s, plus 1. If `None`, this
117+
/// is calculated automatically by calling [`FdSet::highest`] on all descriptor sets and adding 1
118+
/// to the maximum of that.
119+
/// * `readfds`: File descriptors to check for being ready to read.
120+
/// * `writefds`: File descriptors to check for being ready to write.
121+
/// * `errorfds`: File descriptors to check for pending error conditions.
122+
/// * `timeout`: Maximum time to wait for descriptors to become ready (`None` to block
123+
/// indefinitely).
124+
///
125+
/// [select(2)]: http://man7.org/linux/man-pages/man2/select.2.html
126+
/// [`FdSet::highest`]: struct.FdSet.html#method.highest
127+
pub fn select<'a, N, R, W, E, T>(nfds: N,
128+
readfds: R,
129+
writefds: W,
130+
errorfds: E,
131+
timeout: T) -> Result<c_int>
132+
where
133+
N: Into<Option<c_int>>,
134+
R: Into<Option<&'a mut FdSet>>,
135+
W: Into<Option<&'a mut FdSet>>,
136+
E: Into<Option<&'a mut FdSet>>,
137+
T: Into<Option<&'a mut TimeVal>>,
138+
{
139+
let readfds = readfds.into();
140+
let writefds = writefds.into();
141+
let errorfds = errorfds.into();
142+
let timeout = timeout.into();
143+
144+
let nfds = nfds.into().unwrap_or_else(|| {
145+
readfds.iter()
146+
.chain(writefds.iter())
147+
.chain(errorfds.iter())
148+
.map(|set| set.highest().unwrap_or(-1))
149+
.max()
150+
.unwrap_or(-1) + 1
151+
});
152+
76153
let readfds = readfds.map(|set| set as *mut FdSet).unwrap_or(null_mut());
77154
let writefds = writefds.map(|set| set as *mut FdSet).unwrap_or(null_mut());
78155
let errorfds = errorfds.map(|set| set as *mut FdSet).unwrap_or(null_mut());
@@ -85,3 +162,141 @@ pub fn select(nfds: c_int,
85162

86163
Errno::result(res)
87164
}
165+
166+
#[cfg(test)]
167+
mod tests {
168+
use super::*;
169+
use sys::time::{TimeVal, TimeValLike};
170+
use unistd::{write, pipe};
171+
172+
#[test]
173+
fn fdset_insert() {
174+
let mut fd_set = FdSet::new();
175+
176+
for i in 0..FD_SETSIZE {
177+
assert!(!fd_set.contains(i));
178+
}
179+
180+
fd_set.insert(7);
181+
182+
assert!(fd_set.contains(7));
183+
}
184+
185+
#[test]
186+
fn fdset_remove() {
187+
let mut fd_set = FdSet::new();
188+
189+
for i in 0..FD_SETSIZE {
190+
assert!(!fd_set.contains(i));
191+
}
192+
193+
fd_set.insert(7);
194+
fd_set.remove(7);
195+
196+
for i in 0..FD_SETSIZE {
197+
assert!(!fd_set.contains(i));
198+
}
199+
}
200+
201+
#[test]
202+
fn fdset_clear() {
203+
let mut fd_set = FdSet::new();
204+
fd_set.insert(1);
205+
fd_set.insert(FD_SETSIZE / 2);
206+
fd_set.insert(FD_SETSIZE - 1);
207+
208+
fd_set.clear();
209+
210+
for i in 0..FD_SETSIZE {
211+
assert!(!fd_set.contains(i));
212+
}
213+
}
214+
215+
#[test]
216+
fn fdset_highest() {
217+
let mut set = FdSet::new();
218+
assert_eq!(set.highest(), None);
219+
set.insert(0);
220+
assert_eq!(set.highest(), Some(0));
221+
set.insert(90);
222+
assert_eq!(set.highest(), Some(90));
223+
set.remove(0);
224+
assert_eq!(set.highest(), Some(90));
225+
set.remove(90);
226+
assert_eq!(set.highest(), None);
227+
228+
set.insert(4);
229+
set.insert(5);
230+
set.insert(7);
231+
assert_eq!(set.highest(), Some(7));
232+
}
233+
234+
// powerpc-unknown-linux-gnu currently fails on the first `assert_eq` because
235+
// `select()` returns a 0 instead of a 1. Since this test has only been run on
236+
// qemu, it's unclear if this is a OS or qemu bug. Just disable it on that arch
237+
// for now.
238+
// FIXME: Fix tests for powerpc and mips
239+
// FIXME: Add a link to an upstream qemu bug if there is one
240+
#[test]
241+
#[cfg_attr(any(target_arch = "powerpc", target_arch = "mips"), ignore)]
242+
fn test_select() {
243+
let (r1, w1) = pipe().unwrap();
244+
write(w1, b"hi!").unwrap();
245+
let (r2, _w2) = pipe().unwrap();
246+
247+
let mut fd_set = FdSet::new();
248+
fd_set.insert(r1);
249+
fd_set.insert(r2);
250+
251+
let mut timeout = TimeVal::seconds(10);
252+
assert_eq!(1, select(None,
253+
&mut fd_set,
254+
None,
255+
None,
256+
&mut timeout).unwrap());
257+
assert!(fd_set.contains(r1));
258+
assert!(!fd_set.contains(r2));
259+
}
260+
261+
#[test]
262+
#[cfg_attr(any(target_arch = "powerpc", target_arch = "mips"), ignore)]
263+
fn test_select_nfds() {
264+
let (r1, w1) = pipe().unwrap();
265+
write(w1, b"hi!").unwrap();
266+
let (r2, _w2) = pipe().unwrap();
267+
268+
let mut fd_set = FdSet::new();
269+
fd_set.insert(r1);
270+
fd_set.insert(r2);
271+
272+
let mut timeout = TimeVal::seconds(10);
273+
assert_eq!(1, select(Some(fd_set.highest().unwrap() + 1),
274+
&mut fd_set,
275+
None,
276+
None,
277+
&mut timeout).unwrap());
278+
assert!(fd_set.contains(r1));
279+
assert!(!fd_set.contains(r2));
280+
}
281+
282+
#[test]
283+
#[cfg_attr(any(target_arch = "powerpc", target_arch = "mips"), ignore)]
284+
fn test_select_nfds2() {
285+
let (r1, w1) = pipe().unwrap();
286+
write(w1, b"hi!").unwrap();
287+
let (r2, _w2) = pipe().unwrap();
288+
289+
let mut fd_set = FdSet::new();
290+
fd_set.insert(r1);
291+
fd_set.insert(r2);
292+
293+
let mut timeout = TimeVal::seconds(10);
294+
assert_eq!(1, select(::std::cmp::max(r1, r2) + 1,
295+
&mut fd_set,
296+
None,
297+
None,
298+
&mut timeout).unwrap());
299+
assert!(fd_set.contains(r1));
300+
assert!(!fd_set.contains(r2));
301+
}
302+
}

test/sys/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ mod test_sockopt;
99
mod test_termios;
1010
mod test_ioctl;
1111
mod test_wait;
12-
mod test_select;
1312
mod test_uio;
1413

1514
#[cfg(target_os = "linux")]

test/sys/test_select.rs

-59
This file was deleted.

0 commit comments

Comments
 (0)