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