Skip to main content

miri/shims/unix/linux_like/
epoll.rs

1use std::io;
2use std::rc::Rc;
3use std::time::Duration;
4
5use rustc_abi::FieldIdx;
6
7use crate::shims::files::{FileDescription, FileDescriptionRef};
8use crate::shims::unix::UnixFileDescription;
9use crate::*;
10
11/// An `Epoll` file descriptor connects file handles and epoll events
12#[derive(Debug)]
13pub struct Epoll {
14    /// Watcher used for registering interests in the global readiness
15    /// interest table.
16    watcher: Rc<ReadinessWatcher>,
17}
18
19impl FileDescription for Epoll {
20    fn name(&self) -> &'static str {
21        "epoll"
22    }
23
24    fn metadata<'tcx>(
25        &self,
26        _communicate_allowed: bool,
27    ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
28        // On Linux, epoll is an "anonymous inode" reported as S_IFREG.
29        interp_ok(Either::Right("S_IFREG"))
30    }
31
32    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
33        self
34    }
35}
36
37impl UnixFileDescription for Epoll {}
38
39impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
40pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
41    /// This function returns a file descriptor referring to the new `Epoll` instance. This file
42    /// descriptor is used for all subsequent calls to the epoll interface. If the `flags` argument
43    /// is 0, then this function is the same as `epoll_create()`.
44    ///
45    /// <https://linux.die.net/man/2/epoll_create1>
46    fn epoll_create1(&mut self, flags: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
47        let ecx = self.eval_context_mut();
48
49        let flags = ecx.read_scalar(flags)?.to_i32()?;
50
51        let epoll_cloexec = ecx.eval_libc_i32("EPOLL_CLOEXEC");
52
53        // Miri does not support exec, so EPOLL_CLOEXEC flag has no effect.
54        if flags != epoll_cloexec && flags != 0 {
55            throw_unsup_format!(
56                "epoll_create1: flag {:#x} is unsupported, only 0 or EPOLL_CLOEXEC are allowed",
57                flags
58            );
59        }
60
61        let Some(fd) =
62            ecx.machine.fds.insert_new(Epoll { watcher: Rc::new(ReadinessWatcher::default()) })
63        else {
64            return ecx.set_errno_and_return_neg1_i32(LibcError("EMFILE"));
65        };
66        interp_ok(Scalar::from_i32(fd))
67    }
68
69    /// This function performs control operations on the `Epoll` instance referred to by the file
70    /// descriptor `epfd`. It requests that the operation `op` be performed for the target file
71    /// descriptor, `fd`.
72    ///
73    /// Valid values for the op argument are:
74    /// `EPOLL_CTL_ADD` - Register the target file descriptor `fd` on the `Epoll` instance referred
75    /// to by the file descriptor `epfd` and associate the event `event` with the internal file
76    /// linked to `fd`.
77    /// `EPOLL_CTL_MOD` - Change the event `event` associated with the target file descriptor `fd`.
78    /// `EPOLL_CTL_DEL` - Deregister the target file descriptor `fd` from the `Epoll` instance
79    /// referred to by `epfd`. The `event` is ignored and can be null.
80    ///
81    /// <https://linux.die.net/man/2/epoll_ctl>
82    fn epoll_ctl(
83        &mut self,
84        epfd: &OpTy<'tcx>,
85        op: &OpTy<'tcx>,
86        fd: &OpTy<'tcx>,
87        event: &OpTy<'tcx>,
88    ) -> InterpResult<'tcx, Scalar> {
89        let ecx = self.eval_context_mut();
90
91        let epfd_value = ecx.read_scalar(epfd)?.to_i32()?;
92        let op = ecx.read_scalar(op)?.to_i32()?;
93        let fd = ecx.read_scalar(fd)?.to_i32()?;
94        let event = ecx.deref_pointer(event)?;
95
96        let epoll_ctl_add = ecx.eval_libc_i32("EPOLL_CTL_ADD");
97        let epoll_ctl_mod = ecx.eval_libc_i32("EPOLL_CTL_MOD");
98        let epoll_ctl_del = ecx.eval_libc_i32("EPOLL_CTL_DEL");
99        let epollin = ecx.eval_libc_u32("EPOLLIN");
100        let epollout = ecx.eval_libc_u32("EPOLLOUT");
101        let epollrdhup = ecx.eval_libc_u32("EPOLLRDHUP");
102        let epollet = ecx.eval_libc_u32("EPOLLET");
103        let epollhup = ecx.eval_libc_u32("EPOLLHUP");
104        let epollerr = ecx.eval_libc_u32("EPOLLERR");
105
106        // Throw EFAULT if epfd and fd have the same value.
107        if epfd_value == fd {
108            return ecx.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
109        }
110
111        // Check if epfd is a valid epoll file descriptor.
112        let Some(epfd) = ecx.machine.fds.get(epfd_value) else {
113            return ecx.set_errno_and_return_neg1_i32(LibcError("EBADF"));
114        };
115        let epfd = epfd
116            .downcast::<Epoll>()
117            .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
118
119        let Some(fd_ref) = ecx.machine.fds.get(fd) else {
120            return ecx.set_errno_and_return_neg1_i32(LibcError("EBADF"));
121        };
122        let id = fd_ref.id();
123        let interest_key = (id, fd);
124
125        if op == epoll_ctl_add || op == epoll_ctl_mod {
126            // Read event bitmask and data from epoll_event passed by caller.
127            let mut relevant_bitflag =
128                ecx.read_scalar(&ecx.project_field(&event, FieldIdx::ZERO)?)?.to_u32()?;
129            let data = ecx.read_scalar(&ecx.project_field(&event, FieldIdx::ONE)?)?.to_u64()?;
130
131            let is_edge_triggered = if relevant_bitflag & epollet == epollet {
132                relevant_bitflag &= !epollet;
133                true
134            } else {
135                false
136            };
137
138            // Unset the flag we support to discover if any unsupported flags are used.
139            let mut flags = relevant_bitflag;
140            // epoll_wait(2) will always wait for epollhup and epollerr; it is not
141            // necessary to set it in events when calling epoll_ctl().
142            // So we will always set these two event types.
143            relevant_bitflag |= epollhup;
144            relevant_bitflag |= epollerr;
145
146            if flags & epollin == epollin {
147                flags &= !epollin;
148            }
149            if flags & epollout == epollout {
150                flags &= !epollout;
151            }
152            if flags & epollrdhup == epollrdhup {
153                flags &= !epollrdhup;
154            }
155            if flags & epollhup == epollhup {
156                flags &= !epollhup;
157            }
158            if flags & epollerr == epollerr {
159                flags &= !epollerr;
160            }
161            if flags != 0 {
162                throw_unsup_format!(
163                    "epoll_ctl: encountered unknown unsupported flags {:#x}",
164                    flags
165                );
166            }
167
168            let relevant = ecx.epoll_bitflag_to_readiness(relevant_bitflag);
169
170            if op == epoll_ctl_add {
171                // Add a new interest to the watcher.
172                let result =
173                    epfd.watcher.add_interest(fd, relevant, is_edge_triggered, data, ecx)?;
174                if result.is_err() {
175                    // We already had an interest in this.
176                    return ecx.set_errno_and_return_neg1_i32(LibcError("EEXIST"));
177                }
178            } else {
179                // Modify the existing interest.
180                let result = epfd.watcher.update_interest(interest_key, ecx, |interest| {
181                    interest.is_edge_triggered = is_edge_triggered;
182                    interest.relevant = relevant;
183                    interest.data = data;
184                })?;
185                if result.is_none() {
186                    // There is no interest registered for the specified key.
187                    return ecx.set_errno_and_return_neg1_i32(LibcError("ENOENT"));
188                }
189            }
190        } else if op == epoll_ctl_del {
191            if epfd.watcher.remove_interest(interest_key).is_none() {
192                // We did not have interest in this.
193                return ecx.set_errno_and_return_neg1_i32(LibcError("ENOENT"));
194            };
195        } else {
196            throw_unsup_format!("unsupported epoll_ctl operation: {op}");
197        }
198
199        interp_ok(Scalar::from_i32(0))
200    }
201
202    /// The `epoll_wait()` system call waits for events on the `Epoll`
203    /// instance referred to by the file descriptor `epfd`. The buffer
204    /// pointed to by `events` is used to return information from the ready
205    /// list about file descriptors in the interest list that have some
206    /// events available. Up to `maxevents` are returned by `epoll_wait()`.
207    /// The `maxevents` argument must be greater than zero.
208    ///
209    /// The `timeout` argument specifies the number of milliseconds that
210    /// `epoll_wait()` will block. Time is measured against the
211    /// CLOCK_MONOTONIC clock. If the timeout is zero, the function will not block,
212    /// while if the timeout is -1, the function will block
213    /// until at least one event has been retrieved (or an error
214    /// occurred).
215    ///
216    /// A call to `epoll_wait()` will block until either:
217    /// • a file descriptor delivers an event;
218    /// • the call is interrupted by a signal handler; or
219    /// • the timeout expires.
220    ///
221    /// Note that the timeout interval will be rounded up to the system
222    /// clock granularity, and kernel scheduling delays mean that the
223    /// blocking interval may overrun by a small amount. Specifying a
224    /// timeout of -1 causes `epoll_wait()` to block indefinitely, while
225    /// specifying a timeout equal to zero cause `epoll_wait()` to return
226    /// immediately, even if no events are available.
227    ///
228    /// On success, `epoll_wait()` returns the number of file descriptors
229    /// ready for the requested I/O, or zero if no file descriptor became
230    /// ready during the requested timeout milliseconds. On failure,
231    /// `epoll_wait()` returns -1 and errno is set to indicate the error.
232    ///
233    /// <https://man7.org/linux/man-pages/man2/epoll_wait.2.html>
234    fn epoll_wait(
235        &mut self,
236        epfd: &OpTy<'tcx>,
237        events_op: &OpTy<'tcx>,
238        maxevents: &OpTy<'tcx>,
239        timeout: &OpTy<'tcx>,
240        dest: &MPlaceTy<'tcx>,
241    ) -> InterpResult<'tcx> {
242        let ecx = self.eval_context_mut();
243
244        let epfd_value = ecx.read_scalar(epfd)?.to_i32()?;
245        let events = ecx.deref_pointer(events_op)?;
246        let maxevents = ecx.read_scalar(maxevents)?.to_i32()?;
247        let timeout = ecx.read_scalar(timeout)?.to_i32()?;
248
249        if epfd_value <= 0 || maxevents <= 0 {
250            return ecx.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
251        }
252        let maxevents = u32::try_from(maxevents).unwrap();
253
254        let Some(epfd) = ecx.machine.fds.get(epfd_value) else {
255            return ecx.set_errno_and_return_neg1(LibcError("EBADF"), dest);
256        };
257        let Some(epfd) = epfd.downcast::<Epoll>() else {
258            return ecx.set_errno_and_return_neg1(LibcError("EBADF"), dest);
259        };
260
261        if timeout == 0 || epfd.watcher.ready_count() != 0 {
262            // If the timeout is 0 or there is a ready event, we can return immediately.
263            ecx.return_ready_list(&epfd, dest, &events, maxevents)?;
264        } else {
265            // Blocking, with a relative timeout.
266            let deadline = match timeout {
267                0.. => {
268                    let duration = Duration::from_millis(timeout.try_into().unwrap());
269                    Some(ecx.machine.monotonic_clock.now().add_lossy(duration).into())
270                }
271                -1 => None,
272                ..-1 => {
273                    throw_unsup_format!(
274                        "epoll_wait: Only timeout values greater than or equal to -1 are supported."
275                    );
276                }
277            };
278
279            // Record this thread as blocked.
280            epfd.watcher.add_blocked_thread(ecx.active_thread());
281            // And block it.
282            let dest = dest.clone();
283            // We keep a strong ref to the underlying `ReadinessWatcher` to make sure it sticks around.
284            // This means there'll be a leak if we never wake up, but that anyway would imply
285            // a thread is permanently blocked so this is fine.
286            ecx.block_thread(
287                BlockReason::Readiness,
288                deadline,
289                callback!(
290                    @capture<'tcx> {
291                        epfd: FileDescriptionRef<Epoll>,
292                        dest: MPlaceTy<'tcx>,
293                        events: MPlaceTy<'tcx>,
294                        maxevents: u32,
295                    }
296                    |ecx, unblock: UnblockKind| {
297                        match unblock {
298                            UnblockKind::Ready => {
299                                let events = ecx.return_ready_list(&epfd, &dest, &events, maxevents)?;
300                                assert!(events > 0, "we got woken up with no events to deliver");
301                                interp_ok(())
302                            },
303                            UnblockKind::TimedOut => {
304                                // Remove the current active thread id from the blocked threads list.
305                                epfd.watcher.remove_blocked_thread(ecx.active_thread());
306                                ecx.write_int(0, &dest)?;
307                                interp_ok(())
308                            },
309                        }
310                    }
311                ),
312            );
313        }
314        interp_ok(())
315    }
316}
317
318impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
319trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
320    /// Convert a [`Readiness`] instance into the corresponding epoll
321    /// readiness bitflag.
322    fn readiness_to_epoll_bitflag(&self, readiness: &Readiness) -> u32 {
323        let ecx = self.eval_context_ref();
324
325        let epollin = ecx.eval_libc_u32("EPOLLIN");
326        let epollout = ecx.eval_libc_u32("EPOLLOUT");
327        let epollrdhup = ecx.eval_libc_u32("EPOLLRDHUP");
328        let epollhup = ecx.eval_libc_u32("EPOLLHUP");
329        let epollerr = ecx.eval_libc_u32("EPOLLERR");
330
331        let mut bitflag = 0;
332        if readiness.readable {
333            bitflag |= epollin;
334        }
335        if readiness.writable {
336            bitflag |= epollout;
337        }
338        if readiness.read_closed {
339            bitflag |= epollrdhup;
340        }
341        if readiness.write_closed {
342            bitflag |= epollhup;
343        }
344        if readiness.error {
345            bitflag |= epollerr;
346        }
347        bitflag
348    }
349
350    /// Convert an epoll readiness bitflag into the corresponding
351    /// [`Readiness`] instance.
352    fn epoll_bitflag_to_readiness(&self, bitflag: u32) -> Readiness {
353        let ecx = self.eval_context_ref();
354
355        let epollin = ecx.eval_libc_u32("EPOLLIN");
356        let epollout = ecx.eval_libc_u32("EPOLLOUT");
357        let epollrdhup = ecx.eval_libc_u32("EPOLLRDHUP");
358        let epollhup = ecx.eval_libc_u32("EPOLLHUP");
359        let epollerr = ecx.eval_libc_u32("EPOLLERR");
360
361        Readiness {
362            readable: bitflag & epollin == epollin,
363            writable: bitflag & epollout == epollout,
364            read_closed: bitflag & epollrdhup == epollrdhup,
365            write_closed: bitflag & epollhup == epollhup,
366            error: bitflag & epollerr == epollerr,
367        }
368    }
369
370    /// Stores the ready list of the `epfd` epoll instance into `events`,
371    /// and the number of returned events into `dest`.
372    fn return_ready_list(
373        &mut self,
374        epfd: &FileDescriptionRef<Epoll>,
375        dest: &MPlaceTy<'tcx>,
376        events: &MPlaceTy<'tcx>,
377        maxevents: u32,
378    ) -> InterpResult<'tcx, i32> {
379        let ecx = self.eval_context_mut();
380
381        let mut num_of_events = 0i32;
382
383        // We get up to the first `max_events_num` ready events from the
384        // watcher and fill them into the slots of the array.
385        let readiness_interests = epfd.watcher.get_ready_interests(maxevents.to_usize(), ecx)?;
386        for (idx, interest) in readiness_interests.into_iter().enumerate() {
387            let slot = ecx.project_c_array_field(events, idx.to_u64())?;
388            // Deliver event to caller.
389            ecx.write_int_fields_named(
390                &[
391                    ("events", ecx.readiness_to_epoll_bitflag(interest.active()).into()),
392                    ("u64", interest.data.into()),
393                ],
394                &slot,
395            )?;
396            num_of_events = num_of_events.strict_add(1);
397        }
398        ecx.write_int(num_of_events, dest)?;
399        interp_ok(num_of_events)
400    }
401}