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            // Convert from `Timespec` to `c::timeval`.
251            timeout_data = c::timeval {
252                tv_sec: timeout.tv_sec.try_into().map_err(|_| io::Errno::INVAL)?,
253                tv_usec: ((timeout.tv_nsec + 999) / 1000) as _,
254            };
255            &timeout_data
256        }
257        None => null(),
258    };
259
260    // On Apple platforms, use the specially mangled `select` which doesn't
261    // have an `FD_SETSIZE` limitation.
262    #[cfg(apple)]
263    {
264        extern "C" {
265            #[link_name = "select$DARWIN_EXTSN$NOCANCEL"]
266            fn select(
267                nfds: c::c_int,
268                readfds: *mut FdSetElement,
269                writefds: *mut FdSetElement,
270                errorfds: *mut FdSetElement,
271                timeout: *const c::timeval,
272            ) -> c::c_int;
273        }
274
275        ret_c_int(select(nfds, readfds, writefds, exceptfds, timeout_ptr))
276    }
277
278    // Otherwise just use the normal `select`.
279    #[cfg(not(apple))]
280    {
281        ret_c_int(c::select(
282            nfds,
283            readfds.cast(),
284            writefds.cast(),
285            exceptfds.cast(),
286            timeout_ptr as *mut c::timeval,
287        ))
288    }
289}
290
291// WASI uses a count + array instead of a bitvector.
292#[cfg(target_os = "wasi")]
293pub(crate) unsafe fn select(
294    nfds: i32,
295    readfds: Option<&mut [FdSetElement]>,
296    writefds: Option<&mut [FdSetElement]>,
297    exceptfds: Option<&mut [FdSetElement]>,
298    timeout: Option<&Timespec>,
299) -> io::Result<i32> {
300    let len = crate::event::fd_set_num_elements_for_fd_array(nfds as usize);
301
302    let readfds = match readfds {
303        Some(readfds) => {
304            assert!(readfds.len() >= len);
305            readfds.as_mut_ptr()
306        }
307        None => null_mut(),
308    };
309    let writefds = match writefds {
310        Some(writefds) => {
311            assert!(writefds.len() >= len);
312            writefds.as_mut_ptr()
313        }
314        None => null_mut(),
315    };
316    let exceptfds = match exceptfds {
317        Some(exceptfds) => {
318            assert!(exceptfds.len() >= len);
319            exceptfds.as_mut_ptr()
320        }
321        None => null_mut(),
322    };
323
324    let timeout_data;
325    let timeout_ptr = match timeout {
326        Some(timeout) => {
327            // Convert from `Timespec` to `c::timeval`.
328            timeout_data = c::timeval {
329                tv_sec: timeout.tv_sec.try_into().map_err(|_| io::Errno::INVAL)?,
330                tv_usec: ((timeout.tv_nsec + 999) / 1000) as _,
331            };
332            &timeout_data
333        }
334        None => null(),
335    };
336
337    ret_c_int(c::select(
338        nfds,
339        readfds.cast(),
340        writefds.cast(),
341        exceptfds.cast(),
342        timeout_ptr as *mut c::timeval,
343    ))
344}
345
346#[cfg(solarish)]
347pub(crate) fn port_create() -> io::Result<OwnedFd> {
348    unsafe { ret_owned_fd(c::port_create()) }
349}
350
351#[cfg(solarish)]
352pub(crate) unsafe fn port_associate(
353    port: BorrowedFd<'_>,
354    source: c::c_int,
355    object: c::uintptr_t,
356    events: c::c_int,
357    user: *mut c::c_void,
358) -> io::Result<()> {
359    ret(c::port_associate(
360        borrowed_fd(port),
361        source,
362        object,
363        events,
364        user,
365    ))
366}
367
368#[cfg(solarish)]
369pub(crate) unsafe fn port_dissociate(
370    port: BorrowedFd<'_>,
371    source: c::c_int,
372    object: c::uintptr_t,
373) -> io::Result<()> {
374    ret(c::port_dissociate(borrowed_fd(port), source, object))
375}
376
377#[cfg(solarish)]
378pub(crate) fn port_get(port: BorrowedFd<'_>, timeout: Option<&Timespec>) -> io::Result<Event> {
379    // If we don't have to fix y2038 on this platform, `Timespec` is
380    // the same as `c::timespec` and it's easy.
381    #[cfg(not(fix_y2038))]
382    let timeout = crate::timespec::option_as_libc_timespec_ptr(timeout);
383
384    // If we do have to fix y2038 on this platform, convert to
385    // `c::timespec`.
386    #[cfg(fix_y2038)]
387    let converted_timeout;
388    #[cfg(fix_y2038)]
389    let timeout = match timeout {
390        None => null(),
391        Some(timeout) => {
392            converted_timeout = c::timespec {
393                tv_sec: timeout.tv_sec.try_into().map_err(|_| io::Errno::OVERFLOW)?,
394                tv_nsec: timeout.tv_nsec as _,
395            };
396            &converted_timeout
397        }
398    };
399
400    let mut event = MaybeUninit::<c::port_event>::uninit();
401
402    // In Rust ≥ 1.65, the `as _` can be `.cast_mut()`.
403    unsafe {
404        ret(c::port_get(
405            borrowed_fd(port),
406            event.as_mut_ptr(),
407            timeout as _,
408        ))?;
409    }
410
411    // If we're done, initialize the event and return it.
412    Ok(Event(unsafe { event.assume_init() }))
413}
414
415#[cfg(solarish)]
416pub(crate) unsafe fn port_getn(
417    port: BorrowedFd<'_>,
418    events: (*mut Event, usize),
419    mut nget: u32,
420    timeout: Option<&Timespec>,
421) -> io::Result<usize> {
422    // If we don't have to fix y2038 on this platform, `Timespec` is
423    // the same as `c::timespec` and it's easy.
424    #[cfg(not(fix_y2038))]
425    let timeout = crate::timespec::option_as_libc_timespec_ptr(timeout);
426
427    // If we do have to fix y2038 on this platform, convert to
428    // `c::timespec`.
429    #[cfg(fix_y2038)]
430    let converted_timeout;
431    #[cfg(fix_y2038)]
432    let timeout = match timeout {
433        None => null(),
434        Some(timeout) => {
435            converted_timeout = c::timespec {
436                tv_sec: timeout.tv_sec.try_into().map_err(|_| io::Errno::OVERFLOW)?,
437                tv_nsec: timeout.tv_nsec as _,
438            };
439            &converted_timeout
440        }
441    };
442
443    // `port_getn` special-cases a max value of 0 to be a query that returns
444    // the number of events, so bail out early if needed.
445    if events.1 == 0 {
446        return Ok(0);
447    }
448
449    // In Rust ≥ 1.65, the `as _` can be `.cast_mut()`.
450    ret(c::port_getn(
451        borrowed_fd(port),
452        events.0.cast(),
453        events.1.try_into().unwrap_or(u32::MAX),
454        &mut nget,
455        timeout as _,
456    ))?;
457
458    Ok(nget as usize)
459}
460
461#[cfg(solarish)]
462pub(crate) fn port_getn_query(port: BorrowedFd<'_>) -> io::Result<u32> {
463    let mut nget: u32 = 0;
464
465    // Pass a `max` of 0 to query the number of available events.
466    unsafe {
467        ret(c::port_getn(
468            borrowed_fd(port),
469            null_mut(),
470            0,
471            &mut nget,
472            null_mut(),
473        ))?;
474    }
475
476    Ok(nget)
477}
478
479#[cfg(solarish)]
480pub(crate) fn port_send(
481    port: BorrowedFd<'_>,
482    events: c::c_int,
483    userdata: *mut c::c_void,
484) -> io::Result<()> {
485    unsafe { ret(c::port_send(borrowed_fd(port), events, userdata)) }
486}
487
488#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
489pub(crate) fn pause() {
490    let r = unsafe { c::pause() };
491    let errno = libc_errno::errno().0;
492    debug_assert_eq!(r, -1);
493    debug_assert_eq!(errno, c::EINTR);
494}
495
496#[inline]
497#[cfg(any(linux_kernel, target_os = "illumos", target_os = "redox"))]
498pub(crate) fn epoll_create(flags: super::epoll::CreateFlags) -> io::Result<OwnedFd> {
499    unsafe { ret_owned_fd(c::epoll_create1(bitflags_bits!(flags))) }
500}
501
502#[inline]
503#[cfg(any(linux_kernel, target_os = "illumos", target_os = "redox"))]
504pub(crate) fn epoll_add(
505    epoll: BorrowedFd<'_>,
506    source: BorrowedFd<'_>,
507    event: &crate::event::epoll::Event,
508) -> io::Result<()> {
509    // We use our own `Event` struct instead of libc's because
510    // ours preserves pointer provenance instead of just using a `u64`,
511    // and we have tests elsewhere for layout equivalence.
512    unsafe {
513        ret(c::epoll_ctl(
514            borrowed_fd(epoll),
515            c::EPOLL_CTL_ADD,
516            borrowed_fd(source),
517            // The event is read-only even though libc has a non-const pointer.
518            as_ptr(event) as *mut c::epoll_event,
519        ))
520    }
521}
522
523#[inline]
524#[cfg(any(linux_kernel, target_os = "illumos", target_os = "redox"))]
525pub(crate) fn epoll_mod(
526    epoll: BorrowedFd<'_>,
527    source: BorrowedFd<'_>,
528    event: &crate::event::epoll::Event,
529) -> io::Result<()> {
530    unsafe {
531        ret(c::epoll_ctl(
532            borrowed_fd(epoll),
533            c::EPOLL_CTL_MOD,
534            borrowed_fd(source),
535            // The event is read-only even though libc has a non-const pointer.
536            as_ptr(event) as *mut c::epoll_event,
537        ))
538    }
539}
540
541#[inline]
542#[cfg(any(linux_kernel, target_os = "illumos", target_os = "redox"))]
543pub(crate) fn epoll_del(epoll: BorrowedFd<'_>, source: BorrowedFd<'_>) -> io::Result<()> {
544    unsafe {
545        ret(c::epoll_ctl(
546            borrowed_fd(epoll),
547            c::EPOLL_CTL_DEL,
548            borrowed_fd(source),
549            null_mut(),
550        ))
551    }
552}
553
554#[inline]
555#[cfg(any(linux_kernel, target_os = "illumos", target_os = "redox"))]
556pub(crate) unsafe fn epoll_wait(
557    epoll: BorrowedFd<'_>,
558    events: (*mut crate::event::epoll::Event, usize),
559    timeout: Option<&Timespec>,
560) -> io::Result<usize> {
561    // If we're on Linux ≥ 5.11 and a libc that has an `epoll_pwait2`
562    // function, and it's y2038-safe, use it.
563    #[cfg(all(
564        linux_kernel,
565        feature = "linux_5_11",
566        target_env = "gnu",
567        not(fix_y2038)
568    ))]
569    {
570        weak! {
571            fn epoll_pwait2(
572                c::c_int,
573                *mut c::epoll_event,
574                c::c_int,
575                *const c::timespec,
576                *const c::sigset_t
577            ) -> c::c_int
578        }
579
580        if let Some(epoll_pwait2_func) = epoll_pwait2.get() {
581            return ret_u32(epoll_pwait2_func(
582                borrowed_fd(epoll),
583                events.0.cast::<c::epoll_event>(),
584                events.1.try_into().unwrap_or(i32::MAX),
585                crate::utils::option_as_ptr(timeout).cast(),
586                null(),
587            ))
588            .map(|i| i as usize);
589        }
590    }
591
592    // If we're on Linux ≥ 5.11, use `epoll_pwait2` via `libc::syscall`.
593    #[cfg(all(linux_kernel, feature = "linux_5_11"))]
594    {
595        syscall! {
596            fn epoll_pwait2(
597                epfd: c::c_int,
598                events: *mut c::epoll_event,
599                maxevents: c::c_int,
600                timeout: *const Timespec,
601                sigmask: *const c::sigset_t
602            ) via SYS_epoll_pwait2 -> c::c_int
603        }
604
605        ret_u32(epoll_pwait2(
606            borrowed_fd(epoll),
607            events.0.cast::<c::epoll_event>(),
608            events.1.try_into().unwrap_or(i32::MAX),
609            crate::utils::option_as_ptr(timeout).cast(),
610            null(),
611        ))
612        .map(|i| i as usize)
613    }
614
615    // Otherwise just use `epoll_wait`.
616    #[cfg(not(all(linux_kernel, feature = "linux_5_11")))]
617    {
618        let timeout = match timeout {
619            None => -1,
620            Some(timeout) => timeout.as_c_int_millis().ok_or(io::Errno::INVAL)?,
621        };
622
623        ret_u32(c::epoll_wait(
624            borrowed_fd(epoll),
625            events.0.cast::<c::epoll_event>(),
626            events.1.try_into().unwrap_or(i32::MAX),
627            timeout,
628        ))
629        .map(|i| i as usize)
630    }
631}