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