Skip to main content

miri/shims/unix/linux_like/
eventfd.rs

1//! Linux `eventfd` implementation.
2use std::cell::{Cell, RefCell};
3use std::io;
4use std::io::ErrorKind;
5
6use crate::concurrency::VClock;
7use crate::shims::files::{FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef};
8use crate::shims::unix::UnixFileDescription;
9use crate::*;
10
11/// Maximum value that the eventfd counter can hold.
12const MAX_COUNTER: u64 = u64::MAX - 1;
13
14/// A kind of file descriptor created by `eventfd`.
15/// The `Event` type isn't currently written to by `eventfd`.
16/// The interface is meant to keep track of objects associated
17/// with a file descriptor. For more information see the man
18/// page below:
19///
20/// <https://man.netbsd.org/eventfd.2>
21#[derive(Debug)]
22struct EventFd {
23    /// The object contains an unsigned 64-bit integer (uint64_t) counter that is maintained by the
24    /// kernel. This counter is initialized with the value specified in the argument initval.
25    counter: Cell<u64>,
26    is_nonblock: bool,
27    clock: RefCell<VClock>,
28    /// A list of thread ids blocked on eventfd::read.
29    blocked_read_tid: RefCell<Vec<ThreadId>>,
30    /// A list of thread ids blocked on eventfd::write.
31    blocked_write_tid: RefCell<Vec<ThreadId>>,
32}
33
34impl FileDescription for EventFd {
35    fn name(&self) -> &'static str {
36        "event"
37    }
38
39    fn metadata<'tcx>(
40        &self,
41    ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
42        // On Linux, eventfd is an "anonymous inode" reported as S_IFREG.
43        interp_ok(Either::Right("S_IFREG"))
44    }
45
46    fn destroy<'tcx>(
47        self,
48        _self_id: FdId,
49        _communicate_allowed: bool,
50        _ecx: &mut MiriInterpCx<'tcx>,
51    ) -> InterpResult<'tcx, io::Result<()>> {
52        interp_ok(Ok(()))
53    }
54
55    /// Read the counter in the buffer and return the counter if succeeded.
56    fn read<'tcx>(
57        self: FileDescriptionRef<Self>,
58        _communicate_allowed: bool,
59        ptr: Pointer,
60        len: usize,
61        ecx: &mut MiriInterpCx<'tcx>,
62        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
63    ) -> InterpResult<'tcx> {
64        // We're treating the buffer as a `u64`.
65        let ty = ecx.machine.layouts.u64;
66        // Check the size of slice, and return error only if the size of the slice < 8.
67        if len < ty.size.bytes_usize() {
68            return finish.call(ecx, Err(ErrorKind::InvalidInput.into()));
69        }
70
71        // Turn the pointer into a place at the right type.
72        let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty);
73
74        eventfd_read(buf_place, self, ecx, finish)
75    }
76
77    /// A write call adds the 8-byte integer value supplied in
78    /// its buffer (in native endianness) to the counter.  The maximum value that may be
79    /// stored in the counter is the largest unsigned 64-bit value
80    /// minus 1 (i.e., 0xfffffffffffffffe).  If the addition would
81    /// cause the counter's value to exceed the maximum, then the
82    /// write either blocks until a read is performed on the
83    /// file descriptor, or fails with the error EAGAIN if the
84    /// file descriptor has been made nonblocking.
85    ///
86    /// A write fails with the error EINVAL if the size of the
87    /// supplied buffer is less than 8 bytes, or if an attempt is
88    /// made to write the value 0xffffffffffffffff.
89    fn write<'tcx>(
90        self: FileDescriptionRef<Self>,
91        _communicate_allowed: bool,
92        ptr: Pointer,
93        len: usize,
94        ecx: &mut MiriInterpCx<'tcx>,
95        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
96    ) -> InterpResult<'tcx> {
97        // We're treating the buffer as a `u64`.
98        let ty = ecx.machine.layouts.u64;
99        // Check the size of slice, and return error if the size is wrong. The docs say we only
100        // error when the size is too small, but Linux seems to also error when the size is too big.
101        if len != ty.layout.size.bytes_usize() {
102            return finish.call(ecx, Err(ErrorKind::InvalidInput.into()));
103        }
104
105        // Turn the pointer into a place at the right type.
106        let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty);
107
108        eventfd_write(buf_place, self, ecx, finish)
109    }
110
111    fn readiness<'tcx>(&self) -> InterpResult<'tcx, Readiness> {
112        // We only check the "readable" and "writable" readiness for eventfd. If other event flags
113        // need to be supported in the future, the check should be added here.
114
115        interp_ok(Readiness {
116            readable: self.counter.get() != 0,
117            writable: self.counter.get() != MAX_COUNTER,
118            ..Readiness::EMPTY
119        })
120    }
121
122    fn as_unix<'tcx>(
123        self: FileDescriptionRef<Self>,
124        _ecx: &MiriInterpCx<'tcx>,
125    ) -> FileDescriptionRef<dyn UnixFileDescription> {
126        self
127    }
128}
129
130impl UnixFileDescription for EventFd {}
131
132impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
133pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
134    /// This function creates an `Event` that is used as an event wait/notify mechanism by
135    /// user-space applications, and by the kernel to notify user-space applications of events.
136    /// The `Event` contains an `u64` counter maintained by the kernel. The counter is initialized
137    /// with the value specified in the `initval` argument.
138    ///
139    /// A new file descriptor referring to the `Event` is returned. The `read`, `write`, `poll`,
140    /// `select`, and `close` operations can be performed on the file descriptor. For more
141    /// information on these operations, see the man page linked below.
142    ///
143    /// The `flags` are not currently implemented for eventfd.
144    /// The `flags` may be bitwise ORed to change the behavior of `eventfd`:
145    /// `EFD_CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor.
146    /// `EFD_NONBLOCK` - Set the `O_NONBLOCK` file status flag on the new open file description.
147    /// `EFD_SEMAPHORE` - miri does not support semaphore-like semantics.
148    ///
149    /// <https://linux.die.net/man/2/eventfd>
150    fn eventfd(&mut self, val: &OpTy<'tcx>, flags: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
151        let this = self.eval_context_mut();
152
153        let val = this.read_scalar(val)?.to_u32()?;
154        let mut flags = this.read_scalar(flags)?.to_i32()?;
155
156        let efd_cloexec = this.eval_libc_i32("EFD_CLOEXEC");
157        let efd_nonblock = this.eval_libc_i32("EFD_NONBLOCK");
158        let efd_semaphore = this.eval_libc_i32("EFD_SEMAPHORE");
159
160        if flags & efd_semaphore == efd_semaphore {
161            throw_unsup_format!("eventfd: EFD_SEMAPHORE is unsupported");
162        }
163
164        let mut is_nonblock = false;
165        // Unset the flag that we support.
166        // After unloading, flags != 0 means other flags are used.
167        if flags & efd_cloexec == efd_cloexec {
168            // cloexec is ignored because Miri does not support exec.
169            flags &= !efd_cloexec;
170        }
171        if flags & efd_nonblock == efd_nonblock {
172            flags &= !efd_nonblock;
173            is_nonblock = true;
174        }
175        if flags != 0 {
176            throw_unsup_format!("eventfd: encountered unknown unsupported flags {:#x}", flags);
177        }
178
179        let fds = &mut this.machine.fds;
180
181        let fd_value = fds.insert_new(EventFd {
182            counter: Cell::new(val.into()),
183            is_nonblock,
184            clock: RefCell::new(VClock::default()),
185            blocked_read_tid: RefCell::new(Vec::new()),
186            blocked_write_tid: RefCell::new(Vec::new()),
187        });
188
189        interp_ok(Scalar::from_i32(fd_value))
190    }
191}
192
193/// Block thread if the value addition will exceed u64::MAX -1,
194/// else just add the user-supplied value to current counter.
195fn eventfd_write<'tcx>(
196    buf_place: MPlaceTy<'tcx>,
197    eventfd: FileDescriptionRef<EventFd>,
198    ecx: &mut MiriInterpCx<'tcx>,
199    finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
200) -> InterpResult<'tcx> {
201    // Figure out which value we should add.
202    let num = ecx.read_scalar(&buf_place)?.to_u64()?;
203    // u64::MAX as input is invalid because the maximum value of counter is u64::MAX - 1.
204    if num == u64::MAX {
205        return finish.call(ecx, Err(ErrorKind::InvalidInput.into()));
206    }
207
208    match eventfd.counter.get().checked_add(num) {
209        Some(new_count @ 0..=MAX_COUNTER) => {
210            // Future `read` calls will synchronize with this write, so update the FD clock.
211            ecx.release_clock(|clock| {
212                eventfd.clock.borrow_mut().join(clock);
213            })?;
214
215            // Store new counter value.
216            eventfd.counter.set(new_count);
217
218            // Unblock *all* threads previously blocked on `read`.
219            // We need to take out the blocked thread ids and unblock them together,
220            // because `unblock_threads` may block them again and end up re-adding the
221            // thread to the blocked list.
222            let waiting_threads = std::mem::take(&mut *eventfd.blocked_read_tid.borrow_mut());
223            // FIXME: We can randomize the order of unblocking.
224            for thread_id in waiting_threads {
225                ecx.unblock_thread(thread_id, BlockReason::Eventfd)?;
226            }
227
228            // The state changed; we check and update the status of all supported event
229            // types for current file description.
230            // Linux seems to cause spurious wakeups here, and Tokio seems to rely on that
231            // (see <https://github.com/rust-lang/miri/pull/4676#discussion_r2510528994>
232            // and also <https://www.illumos.org/issues/16700>).
233            ecx.update_fd_readiness(eventfd, /* force_edge */ true)?;
234
235            // Return how many bytes we consumed from the user-provided buffer.
236            return finish.call(ecx, Ok(buf_place.layout.size.bytes_usize()));
237        }
238        None | Some(u64::MAX) => {
239            // We can't update the state, so we have to block.
240            if eventfd.is_nonblock {
241                return finish.call(ecx, Err(ErrorKind::WouldBlock.into()));
242            }
243
244            eventfd.blocked_write_tid.borrow_mut().push(ecx.active_thread());
245
246            let weak_eventfd = FileDescriptionRef::downgrade(&eventfd);
247            ecx.block_thread(
248                BlockReason::Eventfd,
249                None,
250                callback!(
251                    @capture<'tcx> {
252                        num: u64,
253                        buf_place: MPlaceTy<'tcx>,
254                        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
255                        weak_eventfd: WeakFileDescriptionRef<EventFd>,
256                    }
257                    |this, unblock: UnblockKind| {
258                        assert_eq!(unblock, UnblockKind::Ready);
259                        // When we get unblocked, try again. We know the ref is still valid,
260                        // otherwise there couldn't be a `write` that unblocks us.
261                        let eventfd_ref = weak_eventfd.upgrade().unwrap();
262                        eventfd_write(buf_place, eventfd_ref, this, finish)
263                    }
264                ),
265            );
266        }
267    };
268    interp_ok(())
269}
270
271/// Block thread if the current counter is 0,
272/// else just return the current counter value to the caller and set the counter to 0.
273fn eventfd_read<'tcx>(
274    buf_place: MPlaceTy<'tcx>,
275    eventfd: FileDescriptionRef<EventFd>,
276    ecx: &mut MiriInterpCx<'tcx>,
277    finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
278) -> InterpResult<'tcx> {
279    // Set counter to 0, get old value.
280    let counter = eventfd.counter.replace(0);
281
282    // Block when counter == 0.
283    if counter == 0 {
284        if eventfd.is_nonblock {
285            return finish.call(ecx, Err(ErrorKind::WouldBlock.into()));
286        }
287
288        eventfd.blocked_read_tid.borrow_mut().push(ecx.active_thread());
289
290        let weak_eventfd = FileDescriptionRef::downgrade(&eventfd);
291        ecx.block_thread(
292            BlockReason::Eventfd,
293            None,
294            callback!(
295                @capture<'tcx> {
296                    buf_place: MPlaceTy<'tcx>,
297                    finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
298                    weak_eventfd: WeakFileDescriptionRef<EventFd>,
299                }
300                |this, unblock: UnblockKind| {
301                    assert_eq!(unblock, UnblockKind::Ready);
302                    // When we get unblocked, try again. We know the ref is still valid,
303                    // otherwise there couldn't be a `write` that unblocks us.
304                    let eventfd_ref = weak_eventfd.upgrade().unwrap();
305                    eventfd_read(buf_place, eventfd_ref, this, finish)
306                }
307            ),
308        );
309    } else {
310        // Synchronize with all prior `write` calls to this FD.
311        ecx.acquire_clock(&eventfd.clock.borrow())?;
312
313        // Return old counter value into user-space buffer.
314        ecx.write_int(counter, &buf_place)?;
315
316        // Unblock *all* threads previously blocked on `write`.
317        // We need to take out the blocked thread ids and unblock them together,
318        // because `unblock_threads` may block them again and end up re-adding the
319        // thread to the blocked list.
320        let waiting_threads = std::mem::take(&mut *eventfd.blocked_write_tid.borrow_mut());
321        // FIXME: We can randomize the order of unblocking.
322        for thread_id in waiting_threads {
323            ecx.unblock_thread(thread_id, BlockReason::Eventfd)?;
324        }
325
326        // The state changed; we check and update the status of all supported event
327        // types for current file description.
328        // Linux seems to always emit do notifications here, even if we were already writable.
329        ecx.update_fd_readiness(eventfd, /* force_edge */ true)?;
330
331        // Tell userspace how many bytes we put into the buffer.
332        return finish.call(ecx, Ok(buf_place.layout.size.bytes_usize()));
333    }
334    interp_ok(())
335}