linux_io/poll.rs
1use core::{cell::UnsafeCell, ptr::null};
2
3use crate::result::{Error, Result};
4
5/// A request for events related to a particular file descriptor in a call to
6/// [`poll`].
7#[repr(C)]
8pub struct PollRequest<'a> {
9 // Note: The layout of this struct must exactly match the kernel's
10 // struct pollfd, because we'll be passing a pointer to an array of
11 // instances of this struct directly to the kernel.
12 fd: linux_unsafe::int,
13 events: linux_unsafe::short,
14 revents: UnsafeCell<linux_unsafe::short>, // Kernel will write in here
15 _phantom: core::marker::PhantomData<&'a super::File>,
16}
17
18impl<'a> PollRequest<'a> {
19 /// Begin constructing a [`PollRequest`] that describes events to request
20 /// for the given file to use in a subsequent call to [`poll`].
21 ///
22 /// The the given file must outlive the returned poll request.
23 #[inline]
24 pub const fn new<'f: 'a>(file: &'f super::File) -> Self {
25 let fd = file.fd;
26 Self {
27 fd,
28 events: 0,
29 revents: UnsafeCell::new(0),
30 _phantom: core::marker::PhantomData,
31 }
32 }
33
34 /// Directly set the events bitmask for this request.
35 ///
36 /// Safety: The given bitmask must be a valid value for the `events`
37 /// field of `struct pollfd` in the kernel's C API.
38 #[inline(always)]
39 pub unsafe fn events_raw(mut self, bitmask: core::ffi::c_short) -> Self {
40 self.events = bitmask;
41 self
42 }
43
44 /// Merge the given events bitmask with the existing event bits in the
45 /// object using the bitwise OR operation.
46 ///
47 /// Safety: The given bitmask must be a valid value for the `events`
48 /// field of `struct pollfd` in the kernel's C API.
49 #[inline(always)]
50 pub unsafe fn or_events_raw(mut self, bitmask: core::ffi::c_short) -> Self {
51 self.events |= bitmask;
52 self
53 }
54
55 /// Returns the result written by the latest call to [`poll`] that included
56 /// this poll request object. If [`poll`] hasn't yet been called then the
57 /// response indicates that no events have occurred.
58 #[inline(always)]
59 pub fn response(&self) -> PollResponse {
60 PollResponse::new(unsafe { *self.revents.get() })
61 }
62}
63
64/// Represents the bitmask of event flags produced for a [`PollRequest`] when
65/// passed to [`poll`].
66#[derive(Clone, Copy, Debug)]
67#[repr(transparent)]
68pub struct PollResponse {
69 revents: core::ffi::c_short,
70}
71
72impl PollResponse {
73 #[inline(always)]
74 const fn new(raw: core::ffi::c_short) -> Self {
75 Self { revents: raw }
76 }
77
78 /// Returns the raw bitmask representing the events that the kernel
79 /// reported. The meaning of this bitmask may be architecture-dependent.
80 #[inline(always)]
81 pub const fn raw_result(&self) -> core::ffi::c_short {
82 self.revents
83 }
84
85 /// Returns true if the response indicates that there is data to read.
86 #[inline(always)]
87 pub const fn readable(&self) -> bool {
88 (self.revents & linux_unsafe::POLLIN) != 0
89 }
90
91 /// Returns true if the response indicates that the file is writable.
92 ///
93 /// This only indicates that _some_ amount of writing is possible, but
94 /// does not guarantee that a write of any given size will succeed.
95 #[inline(always)]
96 pub const fn writable(&self) -> bool {
97 (self.revents & linux_unsafe::POLLOUT) != 0
98 }
99
100 /// Returns true if there is an error condition on the file descriptor.
101 ///
102 /// This condition is also used for the write end of a pipe once the read
103 /// end has been closed.
104 #[inline(always)]
105 pub const fn error(&self) -> bool {
106 (self.revents & linux_unsafe::POLLERR) != 0
107 }
108
109 /// Returns true if the other end of a channel has been closed.
110 ///
111 /// There might still be data in the read buffer, which can be read until
112 /// reaching EOF.
113 #[inline(always)]
114 pub const fn hung_up(&self) -> bool {
115 (self.revents & linux_unsafe::POLLERR) != 0
116 }
117
118 /// Returns true if there is an exceptional condition on the file descriptor.
119 #[inline(always)]
120 pub const fn exception(&self) -> bool {
121 (self.revents & linux_unsafe::POLLPRI) != 0
122 }
123
124 /// Returns true if the kernel deemed the corresponding request to be invalid.
125 #[inline(always)]
126 pub const fn invalid(&self) -> bool {
127 (self.revents & linux_unsafe::POLLNVAL) != 0
128 }
129}
130
131/// `poll` wraps the Linux system call of the same name, or at least one
132/// with a similar name, passing all of the given requests to the kernel.
133///
134/// *Warning:* This abstraction doesn't really work because the lifetime
135/// bounds on `PollRequest` make the `File` objects unusable once the are
136/// used here with a mutable borrow. We'll hopefully fix this in a future
137/// release.
138///
139/// The kernel will modify the request objects in-place with updated event
140/// flags, which you can then retrieve using [`PollRequest::response`].
141/// The successful return value is the number of entries in `reqs` that now
142/// have at least one response flag set.
143///
144/// The kernel may have an upper limit on the number of entries in reqs that
145/// is smaller than the maximum value of `usize`. If the given slice is too
146/// long then this function will return the EINVAL error code.
147pub fn poll(reqs: &mut [PollRequest], timeout: linux_unsafe::int) -> Result<linux_unsafe::int> {
148 // NOTE: We're effectively transmuting our PollRequest type into
149 // the kernel's struct pollfd here. This is safe because the layout
150 // of our struct should exactly match the kernel's, and the kernel
151 // will only write into our "revents" field, and any bits written
152 // in there are valid for type "short".
153 let reqs_ptr = reqs.as_ptr() as *mut linux_unsafe::pollfd;
154 if reqs.len() > (!(0 as linux_unsafe::nfds_t)) as usize {
155 // More file descriptors than the kernel can physicall support on this
156 // platform, so we'll return a synthetic EINVAL to mimic how the
157 // kernel would behave if it had a smaller soft limit.
158 return Err(Error::new(22)); // hard-coded EINVAL value (TODO: expose this as a constant from linux-unsafe instead?)
159 }
160 let reqs_count = reqs.len() as linux_unsafe::nfds_t;
161 // We actually use ppoll rather than poll, because poll is not
162 // available on recently-added architectures like riscv64.
163 let tmo = linux_unsafe::timespec {
164 tv_sec: (timeout / 1000) as linux_unsafe::long,
165 tv_nsec: ((timeout % 1000) * 1_000_000) as linux_unsafe::long,
166 };
167 let tmo_p = &tmo as *const _;
168 let result = unsafe { linux_unsafe::ppoll(reqs_ptr, reqs_count, tmo_p, null()) };
169 result.map(|count| count as _).map_err(|e| e.into())
170}