@@ -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
5894mod 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+ }
0 commit comments