miri/shims/unix/linux_like/
epoll.rs1use 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#[derive(Debug)]
13pub struct Epoll {
14 watcher: Rc<ReadinessWatcher>,
17}
18
19impl VisitProvenance for Epoll {
20 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
21 }
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 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 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 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 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 if epfd_value == fd {
127 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
128 }
129
130 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 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 let mut flags = relevant_bitflag;
159 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 let result =
192 epfd.watcher.add_interest(fd, relevant, is_edge_triggered, data, this)?;
193 if result.is_err() {
194 return this.set_errno_and_return_neg1_i32(LibcError("EEXIST"));
196 }
197 } else {
198 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 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 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 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 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 this.return_ready_list(&epfd, dest, &event)?;
289 } else {
290 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 epfd.watcher.add_blocked_thread(this.active_thread());
306 let dest = dest.clone();
308 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 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 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 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 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 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 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 this.acquire_clock(interest.clock())?;
423 }
424 this.write_int(num_of_events, dest)?;
425 interp_ok(num_of_events)
426 }
427}