Skip to main content

rustix/backend/libc/event/
syscalls.rs

1//! libc syscalls supporting `rustix::event`.
2
3use crate::backend::c;
4#[cfg(any(linux_kernel, solarish, target_os = "redox"))]
5use crate::backend::conv::ret;
6use crate::backend::conv::ret_c_int;
7#[cfg(any(linux_kernel, target_os = "illumos", target_os = "redox"))]
8use crate::backend::conv::ret_u32;
9#[cfg(bsd)]
10use crate::event::kqueue::Event;
11#[cfg(solarish)]
12use crate::event::port::Event;
13#[cfg(any(
14    linux_kernel,
15    target_os = "freebsd",
16    target_os = "illumos",
17    target_os = "espidf"
18))]
19use crate::event::EventfdFlags;
20#[cfg(any(bsd, linux_kernel, target_os = "wasi"))]
21use crate::event::FdSetElement;
22use crate::event::{PollFd, Timespec};
23use crate::io;
24#[cfg(any(linux_kernel, target_os = "illumos", target_os = "redox"))]
25use crate::utils::as_ptr;
26#[cfg(solarish)]
27use core::mem::MaybeUninit;
28#[cfg(any(
29    bsd,
30    linux_kernel,
31    target_os = "fuchsia",
32    target_os = "haiku",
33    target_os = "hurd",
34    target_os = "netbsd",
35    target_os = "wasi"
36))]
37use core::ptr::null;
38#[cfg(any(bsd, linux_kernel, solarish, target_os = "redox", target_os = "wasi"))]
39use core::ptr::null_mut;
40#[cfg(any(bsd, linux_kernel, solarish, target_os = "redox"))]
41use {crate::backend::conv::borrowed_fd, crate::fd::BorrowedFd};
42#[cfg(any(
43    bsd,
44    linux_kernel,
45    solarish,
46    target_os = "freebsd",
47    target_os = "illumos",
48    target_os = "espidf",
49    target_os = "redox"
50))]
51use {crate::backend::conv::ret_owned_fd, crate::fd::OwnedFd};
52
53#[cfg(any(
54    linux_kernel,
55    target_os = "freebsd",
56    target_os = "illumos",
57    target_os = "espidf"
58))]
59pub(crate) fn eventfd(initval: u32, flags: EventfdFlags) -> io::Result<OwnedFd> {
60    #[cfg(linux_kernel)]
61    unsafe {
62        syscall! {
63            fn eventfd2(
64                initval: c::c_uint,
65                flags: c::c_int
66            ) via SYS_eventfd2 -> c::c_int
67        }
68        ret_owned_fd(eventfd2(initval, bitflags_bits!(flags)))
69    }
70
71    // `eventfd` was added in FreeBSD 13, so it isn't available on FreeBSD 12.
72    #[cfg(target_os = "freebsd")]
73    unsafe {
74        weakcall! {
75            fn eventfd(
76                initval: c::c_uint,
77                flags: c::c_int
78            ) -> c::c_int
79        }
80        ret_owned_fd(eventfd(initval, bitflags_bits!(flags)))
81    }
82
83    #[cfg(any(target_os = "illumos", target_os = "espidf"))]
84    unsafe {
85        ret_owned_fd(c::eventfd(initval, bitflags_bits!(flags)))
86    }
87}
88
89#[cfg(bsd)]
90pub(crate) fn kqueue() -> io::Result<OwnedFd> {
91    unsafe { ret_owned_fd(c::kqueue()) }
92}
93
94#[cfg(bsd)]
95pub(crate) unsafe fn kevent(
96    kq: BorrowedFd<'_>,
97    changelist: &[Event],
98    eventlist: (*mut Event, usize),
99    timeout: Option<&Timespec>,
100) -> io::Result<c::c_int> {
101    // If we don't have to fix y2038 on this platform, `Timespec` is the same
102    // as `c::timespec` and it's easy.
103    #[cfg(not(fix_y2038))]
104    let timeout = crate::timespec::option_as_libc_timespec_ptr(timeout);
105
106    // If we do have to fix y2038 on this platform, convert to `c::timespec`.
107    #[cfg(fix_y2038)]
108    let converted_timeout;
109    #[cfg(fix_y2038)]
110    let timeout = match timeout {
111        None => null(),
112        Some(timeout) => {
113            converted_timeout = c::timespec {
114                tv_sec: timeout.tv_sec.try_into().map_err(|_| io::Errno::OVERFLOW)?,
115                tv_nsec: timeout.tv_nsec as _,
116            };
117            &converted_timeout
118        }
119    };
120
121    ret_c_int(c::kevent(
122        borrowed_fd(kq),
123        changelist.as_ptr().cast(),
124        changelist
125            .len()
126            .try_into()
127            .map_err(|_| io::Errno::OVERFLOW)?,
128        eventlist.0.cast(),
129        eventlist.1.try_into().map_err(|_| io::Errno::OVERFLOW)?,
130        timeout,
131    ))
132}
133
134#[inline]
135pub(crate) fn poll(fds: &mut [PollFd<'_>], timeout: Option<&Timespec>) -> io::Result<usize> {
136    let nfds = fds
137        .len()
138        .try_into()
139        .map_err(|_convert_err| io::Errno::INVAL)?;
140
141    // If we have `ppoll`, it supports a `timespec` timeout, so use it.
142    #[cfg(any(
143        linux_kernel,
144        freebsdlike,
145        target_os = "fuchsia",
146        target_os = "haiku",
147        target_os = "hurd",
148        target_os = "netbsd"
149    ))]
150    {
151        // If we don't have to fix y2038 on this platform, `Timespec` is
152        // the same as `c::timespec` and it's easy.
153        #[cfg(not(fix_y2038))]
154        let timeout = crate::timespec::option_as_libc_timespec_ptr(timeout);
155
156        // If we do have to fix y2038 on this platform, convert to
157        // `c::timespec`.
158        #[cfg(fix_y2038)]
159        let converted_timeout;
160        #[cfg(fix_y2038)]
161        let timeout = match timeout {
162            None => null(),
163            Some(timeout) => {
164                converted_timeout = c::timespec {
165                    tv_sec: timeout.tv_sec.try_into().map_err(|_| io::Errno::OVERFLOW)?,
166                    tv_nsec: timeout.tv_nsec as _,
167                };
168                &converted_timeout
169            }
170        };
171
172        #[cfg(not(target_os = "netbsd"))]
173        {
174            ret_c_int(unsafe { c::ppoll(fds.as_mut_ptr().cast(), nfds, timeout, null()) })
175                .map(|nready| nready as usize)
176        }
177
178        // NetBSD 9.x lacks `ppoll`, so use a weak symbol and fall back to
179        // plain `poll` if needed.
180        #[cfg(target_os = "netbsd")]
181        {
182            weak! {
183                fn ppoll(
184                    *mut c::pollfd,
185                    c::nfds_t,
186                    *const c::timespec,
187                    *const c::sigset_t
188                ) -> c::c_int
189            }
190            if let Some(func) = ppoll.get() {
191                return ret_c_int(unsafe { func(fds.as_mut_ptr().cast(), nfds, timeout, null()) })
192                    .map(|nready| nready as usize);
193            }
194        }
195    }
196
197    // If we don't have `ppoll`, convert the timeout to `c_int` and use `poll`.
198    #[cfg(not(any(
199        linux_kernel,
200        freebsdlike,
201        target_os = "fuchsia",
202        target_os = "haiku",
203        target_os = "hurd"
204    )))]
205    {
206        let timeout = match timeout {
207            None => -1,
208            Some(timeout) => timeout.as_c_int_millis().ok_or(io::Errno::INVAL)?,
209        };
210        ret_c_int(unsafe { c::poll(fds.as_mut_ptr().cast(), nfds, timeout) })
211            .map(|nready| nready as usize)
212    }
213}
214
215#[cfg(any(bsd, linux_kernel))]
216pub(crate) unsafe fn select(
217    nfds: i32,
218    readfds: Option<&mut [FdSetElement]>,
219    writefds: Option<&mut [FdSetElement]>,
220    exceptfds: Option<&mut [FdSetElement]>,
221    timeout: Option<&Timespec>,
222) -> io::Result<i32> {
223    let len = crate::event::fd_set_num_elements_for_bitvector(nfds);
224
225    let readfds = match readfds {
226        Some(readfds) => {
227            assert!(readfds.len() >= len);
228            readfds.as_mut_ptr()
229        }
230        None => null_mut(),
231    };
232    let writefds = match writefds {
233        Some(writefds) => {
234            assert!(writefds.len() >= len);
235            writefds.as_mut_ptr()
236        }
237        None => null_mut(),
238    };
239    let exceptfds = match exceptfds {
240        Some(exceptfds) => {
241            assert!(exceptfds.len() >= len);
242            exceptfds.as_mut_ptr()
243        }
244        None => null_mut(),
245    };
246
247    let timeout_data;
248    let timeout_ptr = match timeout {
249        Some(timeout) => {
250            timeout_data = timeout.to_timeval()?;
251            &timeout_data
252        }
253        None => null(),
254    };
255
256    // On Apple platforms, use the specially mangled `select` which doesn't
257    // have an `FD_SETSIZE` limitation.
258    #[cfg(apple)]
259    {
260        extern "C" {
261            #[link_name = "select$DARWIN_EXTSN$NOCANCEL"]
262            fn select(
263                nfds: c::c_int,
264                readfds: *mut FdSetElement,
265                writefds: *mut FdSetElement,
266                errorfds: *mut FdSetElement,
267                timeout: *const c::timeval,
268            ) -> c::c_int;
269        }
270
271        ret_c_int(select(nfds, readfds, writefds, exceptfds, timeout_ptr))
272    }
273
274    // Otherwise just use the normal `select`.
275    #[cfg(not(apple))]
276    {
277        ret_c_int(c::select(
278            nfds,
279            readfds.cast(),
280            writefds.cast(),
281            exceptfds.cast(),
282            timeout_ptr as *mut c::timeval,
283        ))
284    }
285}
286
287// WASI uses a count + array instead of a bitvector.
288#[cfg(target_os = "wasi")]
289pub(crate) unsafe fn select(
290    nfds: i32,
291    readfds: Option<&mut [FdSetElement]>,
292    writefds: Option<&mut [FdSetElement]>,
293    exceptfds: Option<&mut [FdSetElement]>,
294    timeout: Option<&Timespec>,
295) -> io::Result<i32> {
296    let len = crate::event::fd_set_num_elements_for_fd_array(nfds as usize);
297
298    let readfds = match readfds {
299        Some(readfds) => {
300            assert!(readfds.len() >= len);
301            readfds.as_mut_ptr()
302        }
303        None => null_mut(),
304    };
305    let writefds = match writefds {
306        Some(writefds) => {
307            assert!(writefds.len() >= len);
308            writefds.as_mut_ptr()
309        }
310        None => null_mut(),
311    };
312    let exceptfds = match exceptfds {
313        Some(exceptfds) => {
314            assert!(exceptfds.len() >= len);
315            exceptfds.as_mut_ptr()
316        }
317        None => null_mut(),
318    };
319
320    let timeout_data;
321    let timeout_ptr = match timeout {
322        Some(timeout) => {
323            timeout_data = timeout.to_timeval()?;
324            &timeout_data
325        }
326        None => null(),
327    };
328
329    ret_c_int(c::select(
330        nfds,
331        readfds.cast(),
332        writefds.cast(),
333        exceptfds.cast(),
334        timeout_ptr as *mut c::timeval,
335    ))
336}
337
338#[cfg(solarish)]
339pub(crate) fn port_create() -> io::Result<OwnedFd> {
340    unsafe { ret_owned_fd(c::port_create()) }
341}
342
343#[cfg(solarish)]
344pub(crate) unsafe fn port_associate(
345    port: BorrowedFd<'_>,
346    source: c::c_int,
347    object: c::uintptr_t,
348    events: c::c_int,
349    user: *mut c::c_void,
350) -> io::Result<()> {
351    ret(c::port_associate(
352        borrowed_fd(port),
353        source,
354        object,
355        events,
356        user,
357    ))
358}
359
360#[cfg(solarish)]
361pub(crate) unsafe fn port_dissociate(
362    port: BorrowedFd<'_>,
363    source: c::c_int,
364    object: c::uintptr_t,
365) -> io::Result<()> {
366    ret(c::port_dissociate(borrowed_fd(port), source, object))
367}
368
369#[cfg(solarish)]
370pub(crate) fn port_get(port: BorrowedFd<'_>, timeout: Option<&Timespec>) -> io::Result<Event> {
371    // If we don't have to fix y2038 on this platform, `Timespec` is
372    // the same as `c::timespec` and it's easy.
373    #[cfg(not(fix_y2038))]
374    let timeout = crate::timespec::option_as_libc_timespec_ptr(timeout);
375
376    // If we do have to fix y2038 on this platform, convert to
377    // `c::timespec`.
378    #[cfg(fix_y2038)]
379    let converted_timeout;
380    #[cfg(fix_y2038)]
381    let timeout = match timeout {
382        None => null(),
383        Some(timeout) => {
384            converted_timeout = c::timespec {
385                tv_sec: timeout.tv_sec.try_into().map_err(|_| io::Errno::OVERFLOW)?,
386                tv_nsec: timeout.tv_nsec as _,
387            };
388            &converted_timeout
389        }
390    };
391
392    let mut event = MaybeUninit::<c::port_event>::uninit();
393
394    // In Rust ≥ 1.65, the `as _` can be `.cast_mut()`.
395    unsafe {
396        ret(c::port_get(
397            borrowed_fd(port),
398            event.as_mut_ptr(),
399            timeout as _,
400        ))?;
401    }
402
403    // If we're done, initialize the event and return it.
404    Ok(Event(unsafe { event.assume_init() }))
405}
406
407#[cfg(solarish)]
408pub(crate) unsafe fn port_getn(
409    port: BorrowedFd<'_>,
410    events: (*mut Event, usize),
411    mut nget: u32,
412    timeout: Option<&Timespec>,
413) -> io::Result<usize> {
414    // If we don't have to fix y2038 on this platform, `Timespec` is
415    // the same as `c::timespec` and it's easy.
416    #[cfg(not(fix_y2038))]
417    let timeout = crate::timespec::option_as_libc_timespec_ptr(timeout);
418
419    // If we do have to fix y2038 on this platform, convert to
420    // `c::timespec`.
421    #[cfg(fix_y2038)]
422    let converted_timeout;
423    #[cfg(fix_y2038)]
424    let timeout = match timeout {
425        None => null(),
426        Some(timeout) => {
427            converted_timeout = c::timespec {
428                tv_sec: timeout.tv_sec.try_into().map_err(|_| io::Errno::OVERFLOW)?,
429                tv_nsec: timeout.tv_nsec as _,
430            };
431            &converted_timeout
432        }
433    };
434
435    // `port_getn` special-cases a max value of 0 to be a query that returns
436    // the number of events, so bail out early if needed.
437    if events.1 == 0 {
438        return Ok(0);
439    }
440
441    // In Rust ≥ 1.65, the `as _` can be `.cast_mut()`.
442    ret(c::port_getn(
443        borrowed_fd(port),
444        events.0.cast(),
445        events.1.try_into().unwrap_or(u32::MAX),
446        &mut nget,
447        timeout as _,
448    ))?;
449
450    Ok(nget as usize)
451}
452
453#[cfg(solarish)]
454pub(crate) fn port_getn_query(port: BorrowedFd<'_>) -> io::Result<u32> {
455    let mut nget: u32 = 0;
456
457    // Pass a `max` of 0 to query the number of available events.
458    unsafe {
459        ret(c::port_getn(
460            borrowed_fd(port),
461            null_mut(),
462            0,
463            &mut nget,
464            null_mut(),
465        ))?;
466    }
467
468    Ok(nget)
469}
470
471#[cfg(solarish)]
472pub(crate) fn port_send(
473    port: BorrowedFd<'_>,
474    events: c::c_int,
475    userdata: *mut c::c_void,
476) -> io::Result<()> {
477    unsafe { ret(c::port_send(borrowed_fd(port), events, userdata)) }
478}
479
480#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
481pub(crate) fn pause() {
482    let r = unsafe { c::pause() };
483    let errno = libc_errno::errno().0;
484    debug_assert_eq!(r, -1);
485    debug_assert_eq!(errno, c::EINTR);
486}
487
488#[inline]
489#[cfg(any(linux_kernel, target_os = "illumos", target_os = "redox"))]
490pub(crate) fn epoll_create(flags: super::epoll::CreateFlags) -> io::Result<OwnedFd> {
491    unsafe { ret_owned_fd(c::epoll_create1(bitflags_bits!(flags))) }
492}
493
494#[inline]
495#[cfg(any(linux_kernel, target_os = "illumos", target_os = "redox"))]
496pub(crate) fn epoll_add(
497    epoll: BorrowedFd<'_>,
498    source: BorrowedFd<'_>,
499    event: &crate::event::epoll::Event,
500) -> io::Result<()> {
501    // We use our own `Event` struct instead of libc's because
502    // ours preserves pointer provenance instead of just using a `u64`,
503    // and we have tests elsewhere for layout equivalence.
504    unsafe {
505        ret(c::epoll_ctl(
506            borrowed_fd(epoll),
507            c::EPOLL_CTL_ADD,
508            borrowed_fd(source),
509            // The event is read-only even though libc has a non-const pointer.
510            as_ptr(event) as *mut c::epoll_event,
511        ))
512    }
513}
514
515#[inline]
516#[cfg(any(linux_kernel, target_os = "illumos", target_os = "redox"))]
517pub(crate) fn epoll_mod(
518    epoll: BorrowedFd<'_>,
519    source: BorrowedFd<'_>,
520    event: &crate::event::epoll::Event,
521) -> io::Result<()> {
522    unsafe {
523        ret(c::epoll_ctl(
524            borrowed_fd(epoll),
525            c::EPOLL_CTL_MOD,
526            borrowed_fd(source),
527            // The event is read-only even though libc has a non-const pointer.
528            as_ptr(event) as *mut c::epoll_event,
529        ))
530    }
531}
532
533#[inline]
534#[cfg(any(linux_kernel, target_os = "illumos", target_os = "redox"))]
535pub(crate) fn epoll_del(epoll: BorrowedFd<'_>, source: BorrowedFd<'_>) -> io::Result<()> {
536    unsafe {
537        ret(c::epoll_ctl(
538            borrowed_fd(epoll),
539            c::EPOLL_CTL_DEL,
540            borrowed_fd(source),
541            null_mut(),
542        ))
543    }
544}
545
546#[inline]
547#[cfg(any(linux_kernel, target_os = "illumos", target_os = "redox"))]
548pub(crate) unsafe fn epoll_wait(
549    epoll: BorrowedFd<'_>,
550    events: (*mut crate::event::epoll::Event, usize),
551    timeout: Option<&Timespec>,
552) -> io::Result<usize> {
553    // If we're on Linux ≥ 5.11 and a libc that has an `epoll_pwait2`
554    // function, and it's y2038-safe, use it.
555    #[cfg(all(
556        linux_kernel,
557        feature = "linux_5_11",
558        target_env = "gnu",
559        not(fix_y2038)
560    ))]
561    {
562        weak! {
563            fn epoll_pwait2(
564                c::c_int,
565                *mut c::epoll_event,
566                c::c_int,
567                *const c::timespec,
568                *const c::sigset_t
569            ) -> c::c_int
570        }
571
572        if let Some(epoll_pwait2_func) = epoll_pwait2.get() {
573            return ret_u32(epoll_pwait2_func(
574                borrowed_fd(epoll),
575                events.0.cast::<c::epoll_event>(),
576                events.1.try_into().unwrap_or(i32::MAX),
577                crate::utils::option_as_ptr(timeout).cast(),
578                null(),
579            ))
580            .map(|i| i as usize);
581        }
582    }
583
584    // If we're on Linux ≥ 5.11, use `epoll_pwait2` via `libc::syscall`.
585    #[cfg(all(linux_kernel, feature = "linux_5_11"))]
586    {
587        syscall! {
588            fn epoll_pwait2(
589                epfd: c::c_int,
590                events: *mut c::epoll_event,
591                maxevents: c::c_int,
592                timeout: *const Timespec,
593                sigmask: *const c::sigset_t
594            ) via SYS_epoll_pwait2 -> c::c_int
595        }
596
597        ret_u32(epoll_pwait2(
598            borrowed_fd(epoll),
599            events.0.cast::<c::epoll_event>(),
600            events.1.try_into().unwrap_or(i32::MAX),
601            crate::utils::option_as_ptr(timeout).cast(),
602            null(),
603        ))
604        .map(|i| i as usize)
605    }
606
607    // Otherwise just use `epoll_wait`.
608    #[cfg(not(all(linux_kernel, feature = "linux_5_11")))]
609    {
610        let timeout = match timeout {
611            None => -1,
612            Some(timeout) => timeout.as_c_int_millis().ok_or(io::Errno::INVAL)?,
613        };
614
615        ret_u32(c::epoll_wait(
616            borrowed_fd(epoll),
617            events.0.cast::<c::epoll_event>(),
618            events.1.try_into().unwrap_or(i32::MAX),
619            timeout,
620        ))
621        .map(|i| i as usize)
622    }
623}