Skip to main content

relibc/header/sys_select/
mod.rs

1//! `sys/select.h` implementation.
2//!
3//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_select.h.html>.
4
5use core::mem;
6
7use cbitset::BitSet;
8
9use crate::{
10    fs::File,
11    header::{
12        bits_sigset_t::sigset_t,
13        errno,
14        sys_epoll::{
15            EPOLL_CLOEXEC, EPOLL_CTL_ADD, EPOLLERR, EPOLLIN, EPOLLOUT, epoll_create1, epoll_ctl,
16            epoll_data, epoll_event, epoll_pwait,
17        },
18        time::timespec,
19    },
20    platform::types::{c_int, suseconds_t},
21};
22
23pub use crate::header::bits_timeval::timeval;
24
25// FD_SETSIZE and fd_set is also defined in C because cbindgen is incompatible with mem::size_of
26
27/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_select.h.html>.
28///
29/// Maximum number of file descriptors in an `fd_set` structure.
30/// cbindgen:ignore
31pub const FD_SETSIZE: usize = 1024;
32type FdBitSet = BitSet<[u64; FD_SETSIZE / (8 * mem::size_of::<u64>())]>;
33
34/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_select.h.html>.
35/// cbindgen:ignore
36#[allow(non_camel_case_types)]
37#[repr(C)]
38pub struct fd_set {
39    pub fds_bits: FdBitSet,
40}
41
42#[allow(clippy::needless_update)]
43pub unsafe fn select_epoll(
44    nfds: c_int,
45    readfds: Option<&mut fd_set>,
46    writefds: Option<&mut fd_set>,
47    exceptfds: Option<&mut fd_set>,
48    timeout: Option<&mut timeval>,
49    sigmask: *const sigset_t,
50) -> c_int {
51    if nfds < 0 || nfds > FD_SETSIZE as i32 {
52        crate::platform::ERRNO.set(errno::EINVAL);
53        return -1;
54    };
55
56    let ep = {
57        let epfd = epoll_create1(EPOLL_CLOEXEC);
58        if epfd < 0 {
59            return -1;
60        }
61        File::new(epfd)
62    };
63    let mut read_bitset: Option<&mut FdBitSet> = readfds.map(|fd_set| &mut fd_set.fds_bits);
64    let mut write_bitset: Option<&mut FdBitSet> = writefds.map(|fd_set| &mut fd_set.fds_bits);
65    let mut except_bitset: Option<&mut FdBitSet> = exceptfds.map(|fd_set| &mut fd_set.fds_bits);
66
67    // Keep track of the number of file descriptors that do not support epoll
68    let mut not_epoll = 0;
69    for fd in 0..nfds {
70        let mut events = 0;
71
72        if let Some(ref fd_set) = read_bitset
73            && fd_set.contains(fd as usize)
74        {
75            events |= EPOLLIN;
76        }
77
78        if let Some(ref fd_set) = write_bitset
79            && fd_set.contains(fd as usize)
80        {
81            events |= EPOLLOUT;
82        }
83
84        if let Some(ref fd_set) = except_bitset
85            && fd_set.contains(fd as usize)
86        {
87            events |= EPOLLERR;
88        }
89
90        if events > 0 {
91            let mut event = epoll_event {
92                events,
93                data: epoll_data { fd },
94                ..Default::default() // clippy lint, _pad field on redox but not linux
95            };
96            if unsafe { epoll_ctl(*ep, EPOLL_CTL_ADD, fd, &raw mut event) } < 0 {
97                if crate::platform::ERRNO.get() == errno::EPERM {
98                    not_epoll += 1;
99                } else {
100                    return -1;
101                }
102            } else {
103                if let Some(ref mut fd_set) = read_bitset
104                    && fd_set.contains(fd as usize)
105                {
106                    fd_set.remove(fd as usize);
107                }
108
109                if let Some(ref mut fd_set) = write_bitset
110                    && fd_set.contains(fd as usize)
111                {
112                    fd_set.remove(fd as usize);
113                }
114
115                if let Some(ref mut fd_set) = except_bitset
116                    && fd_set.contains(fd as usize)
117                {
118                    fd_set.remove(fd as usize);
119                }
120            }
121        }
122    }
123
124    let mut events: [epoll_event; 32] = unsafe { mem::zeroed() };
125    let epoll_timeout = if not_epoll > 0 {
126        // Do not wait if any non-epoll file descriptors were found
127        0
128    } else {
129        match timeout {
130            Some(timeout) => {
131                let sec_ms = (timeout.tv_sec as c_int).checked_mul(1000);
132                let usec_ms = (timeout.tv_usec as c_int) / 1000;
133                match sec_ms.and_then(|s| s.checked_add(usec_ms)) {
134                    Some(s) => s as c_int,
135                    None => c_int::MAX,
136                }
137            }
138            None => -1,
139        }
140    };
141
142    let res = unsafe {
143        epoll_pwait(
144            *ep,
145            events.as_mut_ptr(),
146            events.len() as c_int,
147            epoll_timeout,
148            sigmask,
149        )
150    };
151    if res < 0 {
152        return -1;
153    }
154
155    let mut count = not_epoll;
156    for event in events.iter().take(res as usize) {
157        let fd = unsafe { event.data.fd };
158        // TODO: Error status when fd does not match?
159        if fd >= 0 && fd < FD_SETSIZE as c_int {
160            if event.events & EPOLLIN > 0
161                && let Some(ref mut fd_set) = read_bitset
162            {
163                fd_set.insert(fd as usize);
164                count += 1;
165            }
166            if event.events & EPOLLOUT > 0
167                && let Some(ref mut fd_set) = write_bitset
168            {
169                fd_set.insert(fd as usize);
170                count += 1;
171            }
172            if event.events & EPOLLERR > 0
173                && let Some(ref mut fd_set) = except_bitset
174            {
175                fd_set.insert(fd as usize);
176                count += 1;
177            }
178        }
179    }
180    count
181}
182
183/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/select.html>.
184///
185/// Examines the file descriptor sets whose addresses are passed in the
186/// `readfds`, `writefds` and `errorfds` parameters to see whether some of
187/// their descriptors are ready for reading, ready for writing, or have an
188/// exceptional condition pending, respectively.
189///
190/// `timeout` is given in seconds and microseconds as represented by `timeval`.
191/// Behaves as `pselect()` does when `sigmask` is a null pointer. When
192/// successful, may modify the object pointed to by `timeout`.
193///
194/// Upon success, returns the total number of bits set in the bit masks. Upon
195/// failure, returns `-1` and sets errno to indicate the error.
196#[unsafe(no_mangle)]
197pub unsafe extern "C" fn select(
198    nfds: c_int,
199    readfds: *mut fd_set,
200    writefds: *mut fd_set,
201    exceptfds: *mut fd_set,
202    timeout: *mut timeval,
203) -> c_int {
204    trace_expr!(
205        unsafe {
206            select_epoll(
207                nfds,
208                if readfds.is_null() {
209                    None
210                } else {
211                    Some(&mut *readfds)
212                },
213                if writefds.is_null() {
214                    None
215                } else {
216                    Some(&mut *writefds)
217                },
218                if exceptfds.is_null() {
219                    None
220                } else {
221                    Some(&mut *exceptfds)
222                },
223                if timeout.is_null() {
224                    None
225                } else {
226                    Some(&mut *timeout)
227                },
228                core::ptr::null(),
229            )
230        },
231        "select({}, {:p}, {:p}, {:p}, {:p})",
232        nfds,
233        readfds,
234        writefds,
235        exceptfds,
236        timeout
237    )
238}
239
240/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/pselect.html>.
241///
242/// Examines the file descriptor sets whose addresses are passed in the
243/// `readfds`, `writefds` and `errorfds` parameters to see whether some of
244/// their descriptors are ready for reading, ready for writing, or have an
245/// exceptional condition pending, respectively.
246///
247/// `timeout` is given in seconds and nanoseconds as represented by `timespec`.
248///
249/// Upon success, returns the total number of bits set in the bit masks. Upon
250/// failure, returns `-1` and sets errno to indicate the error.
251#[unsafe(no_mangle)]
252pub unsafe extern "C" fn pselect(
253    nfds: c_int,
254    readfds: *mut fd_set,
255    writefds: *mut fd_set,
256    exceptfds: *mut fd_set,
257    timeout: *const timespec,
258    sigmask: *const sigset_t,
259) -> c_int {
260    let mut micro_timeout = if timeout.is_null() {
261        None
262    } else {
263        unsafe {
264            Some(timeval {
265                tv_sec: (*timeout).tv_sec,
266                tv_usec: ((*timeout).tv_nsec / 1000) as suseconds_t,
267            })
268        }
269    };
270    trace_expr!(
271        unsafe {
272            select_epoll(
273                nfds,
274                if readfds.is_null() {
275                    None
276                } else {
277                    Some(&mut *readfds)
278                },
279                if writefds.is_null() {
280                    None
281                } else {
282                    Some(&mut *writefds)
283                },
284                if exceptfds.is_null() {
285                    None
286                } else {
287                    Some(&mut *exceptfds)
288                },
289                micro_timeout.as_mut(),
290                sigmask,
291            )
292        },
293        "pselect({}, {:p}, {:p}, {:p}, {:p}, {:p})",
294        nfds,
295        readfds,
296        writefds,
297        exceptfds,
298        timeout,
299        sigmask,
300    )
301}