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