Skip to main content

relibc/header/poll/
mod.rs

1//! `poll.h` implementation.
2//!
3//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/poll.h.html>.
4
5use core::{mem, ptr, slice};
6
7use crate::{
8    error::Errno,
9    fs::File,
10    header::{
11        bits_sigset_t::sigset_t,
12        errno::{EBADF, EINTR},
13        sys_epoll::{
14            EPOLL_CLOEXEC, EPOLL_CTL_ADD, EPOLLERR, EPOLLHUP, EPOLLIN, EPOLLNVAL, EPOLLOUT,
15            EPOLLPRI, EPOLLRDBAND, EPOLLRDNORM, EPOLLWRBAND, EPOLLWRNORM, epoll_data, epoll_event,
16        },
17        time::timespec,
18    },
19    platform::{
20        ERRNO, PalEpoll, Sys,
21        types::{c_int, c_short, c_ulong},
22    },
23};
24
25/// Data other than high-priority data may be read without blocking.
26pub const POLLIN: c_short = 0x001;
27/// High-priority data may be read without blocking.
28pub const POLLPRI: c_short = 0x002;
29/// Normal data may be written without blocking.
30pub const POLLOUT: c_short = 0x004;
31/// An error has occurred (revents only).
32pub const POLLERR: c_short = 0x008;
33/// Device has been disconnected (revents only).
34pub const POLLHUP: c_short = 0x010;
35/// Invalid fd member (revents only).
36pub const POLLNVAL: c_short = 0x020;
37/// Normal data may be read without blocking.
38pub const POLLRDNORM: c_short = 0x040;
39/// Priority data may be read without blocking.
40pub const POLLRDBAND: c_short = 0x080;
41/// Equivalent to POLLOUT.
42pub const POLLWRNORM: c_short = 0x100;
43/// Priority data may be written.
44pub const POLLWRBAND: c_short = 0x200;
45
46/// An unsigned integer type used for the number of file descriptors.
47#[allow(non_camel_case_types)]
48pub type nfds_t = c_ulong;
49
50/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/poll.h.html>.
51///
52/// A structure storing a file descriptor along with input and output flags for poll operations.
53#[allow(non_camel_case_types)]
54#[repr(C)]
55pub struct pollfd {
56    /// The following descriptor being polled.
57    pub fd: c_int,
58    /// The input event flags.
59    pub events: c_short,
60    /// The output event flags.
61    pub revents: c_short,
62}
63
64#[allow(clippy::needless_update)] // epoll_event _pad field on redox
65pub unsafe fn poll_epoll(fds: &mut [pollfd], timeout: c_int, sigmask: *const sigset_t) -> c_int {
66    let event_map = [
67        (POLLIN, EPOLLIN),
68        (POLLPRI, EPOLLPRI),
69        (POLLOUT, EPOLLOUT),
70        (POLLERR, EPOLLERR),
71        (POLLHUP, EPOLLHUP),
72        (POLLNVAL, EPOLLNVAL),
73        (POLLRDNORM, EPOLLRDNORM),
74        (POLLWRNORM, EPOLLWRNORM),
75        (POLLRDBAND, EPOLLRDBAND),
76        (POLLWRBAND, EPOLLWRBAND),
77    ];
78
79    let ep = {
80        let epfd = match Sys::epoll_create1(EPOLL_CLOEXEC) {
81            Ok(epfd) => epfd,
82            Err(Errno(err)) => {
83                ERRNO.set(err);
84                return -1;
85            }
86        };
87        File::new(epfd)
88    };
89
90    let mut closed = 0;
91    for (i, fd) in fds.iter_mut().enumerate() {
92        let pfd = fd;
93
94        pfd.revents = 0;
95
96        // Ignore the entry with negative fd
97        if pfd.fd < 0 {
98            continue;
99        }
100
101        #[expect(clippy::needless_update)]
102        let mut event = epoll_event {
103            events: 0,
104            data: epoll_data { u64: i as u64 },
105            ..Default::default() // needed only on redox for _pad field
106        };
107
108        for (p, ep) in event_map.iter() {
109            if pfd.events & p > 0 {
110                event.events |= ep;
111            }
112        }
113
114        match unsafe { Sys::epoll_ctl(*ep, EPOLL_CTL_ADD, pfd.fd, &raw mut event) } {
115            Ok(()) => {}
116            Err(Errno(EBADF)) => {
117                pfd.revents |= POLLNVAL;
118                closed += 1;
119            }
120            Err(Errno(err)) => {
121                ERRNO.set(err);
122                return -1;
123            }
124        }
125    }
126
127    // Early exit if there are fds, and all are closed (revents = POLLNVAL)
128    if closed > 0 && closed == fds.len() {
129        return closed as i32;
130    }
131
132    let mut events: [epoll_event; 32] = unsafe { mem::zeroed() };
133    match unsafe {
134        Sys::epoll_pwait(
135            *ep,
136            events.as_mut_ptr(),
137            events.len() as c_int,
138            timeout,
139            sigmask,
140        )
141    } {
142        Ok(res) => {
143            for event in events.iter().take(res) {
144                let pi = unsafe { event.data.u64 as usize };
145                // TODO: Error status when fd does not match?
146                if let Some(pfd) = fds.get_mut(pi) {
147                    for (p, ep) in event_map.iter() {
148                        if event.events & ep > 0 {
149                            pfd.revents |= p;
150                        }
151                    }
152                }
153            }
154        }
155        Err(Errno(err)) => {
156            if err == EINTR && closed > 0 {
157                // some fds are closed by signal
158            } else {
159                ERRNO.set(err);
160                return -1;
161            }
162        }
163    }
164
165    let mut count = 0;
166    for pfd in fds.iter() {
167        if pfd.revents > 0 {
168            count += 1;
169        }
170    }
171    count
172}
173
174/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/poll.html>.
175///
176/// Provides applications with a mechanism for multiplexing input/output
177/// over a set of file descriptors.
178///
179/// - The `timeout` parameter represents milliseconds.
180/// - A `timeout` of `-1` is equivalent to passing a null pointer for `tmo_p` to `ppoll`.
181/// - `poll` should behave equivalent to `ppoll` with a null pointer for `sigmask`.
182///
183/// Note: Uses epoll internally.
184#[unsafe(no_mangle)]
185pub unsafe extern "C" fn poll(fds: *mut pollfd, nfds: nfds_t, timeout: c_int) -> c_int {
186    trace_expr!(
187        unsafe {
188            poll_epoll(
189                slice::from_raw_parts_mut(fds, nfds as usize),
190                timeout,
191                ptr::null_mut(),
192            )
193        },
194        "poll({:p}, {}, {})",
195        fds,
196        nfds,
197        timeout,
198    )
199}
200
201/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/ppoll.html>.
202///
203/// Provides applications with a mechanism for multiplexing input/output
204/// over a set of file descriptors.
205///
206/// - The `tmo_p` parameter is the timeout as represented by a `timespec` struct.
207/// - Passing a null pointer for timeout is equivalent to `-1` for `timeout` to `poll`.
208///
209/// Note: Uses epoll internally.
210#[unsafe(no_mangle)]
211pub unsafe extern "C" fn ppoll(
212    fds: *mut pollfd,
213    nfds: nfds_t,
214    tmo_p: *const timespec,
215    sigmask: *const sigset_t,
216) -> c_int {
217    let timeout = if tmo_p.is_null() {
218        -1
219    } else {
220        let tmo = unsafe { &*tmo_p };
221        if tmo.tv_sec > (c_int::MAX / 1000).into() {
222            c_int::MAX
223        } else {
224            ((tmo.tv_sec as c_int) * 1000) + ((tmo.tv_nsec as c_int) / 1000000)
225        }
226    };
227    trace_expr!(
228        unsafe {
229            poll_epoll(
230                slice::from_raw_parts_mut(fds, nfds as usize),
231                timeout,
232                sigmask,
233            )
234        },
235        "ppoll({:p}, {}, {:p}, {:p})",
236        fds,
237        nfds,
238        tmo_p,
239        sigmask
240    )
241}