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