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