miri/shims/unix/linux_like/
epoll.rs1use std::cell::RefCell;
2use std::collections::{BTreeMap, BTreeSet, VecDeque};
3use std::io;
4use std::ops::Bound;
5use std::time::Duration;
6
7use rustc_abi::FieldIdx;
8
9use crate::concurrency::VClock;
10use crate::shims::files::{
11 DynFileDescriptionRef, FdId, FdNum, FileDescription, FileDescriptionRef, WeakFileDescriptionRef,
12};
13use crate::shims::unix::UnixFileDescription;
14use crate::*;
15
16type EpollEventKey = (FdId, FdNum);
17
18#[derive(Debug, Default)]
20pub struct Epoll {
21 interest_list: RefCell<BTreeMap<EpollEventKey, EpollEventInterest>>,
24 ready_set: RefCell<BTreeSet<EpollEventKey>>,
27 queue: RefCell<VecDeque<ThreadId>>,
29}
30
31impl VisitProvenance for Epoll {
32 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
33 }
35}
36
37fn range_for_id(id: FdId) -> std::ops::RangeInclusive<EpollEventKey> {
39 (id, 0)..=(id, i32::MAX)
40}
41
42#[derive(Debug)]
44pub struct EpollEventInterest {
45 relevant_events: u32,
47 active_events: u32,
49 clock: VClock,
51 data: u64,
56}
57
58#[derive(Debug)]
60pub struct EpollReadiness {
61 pub epollin: bool,
64 pub epollout: bool,
66 pub epollrdhup: bool,
69 pub epollhup: bool,
75 pub epollerr: bool,
77}
78
79impl EpollReadiness {
80 pub fn empty() -> Self {
81 EpollReadiness {
82 epollin: false,
83 epollout: false,
84 epollrdhup: false,
85 epollhup: false,
86 epollerr: false,
87 }
88 }
89
90 pub fn get_event_bitmask<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> u32 {
91 let epollin = ecx.eval_libc_u32("EPOLLIN");
92 let epollout = ecx.eval_libc_u32("EPOLLOUT");
93 let epollrdhup = ecx.eval_libc_u32("EPOLLRDHUP");
94 let epollhup = ecx.eval_libc_u32("EPOLLHUP");
95 let epollerr = ecx.eval_libc_u32("EPOLLERR");
96
97 let mut bitmask = 0;
98 if self.epollin {
99 bitmask |= epollin;
100 }
101 if self.epollout {
102 bitmask |= epollout;
103 }
104 if self.epollrdhup {
105 bitmask |= epollrdhup;
106 }
107 if self.epollhup {
108 bitmask |= epollhup;
109 }
110 if self.epollerr {
111 bitmask |= epollerr;
112 }
113 bitmask
114 }
115}
116
117impl From<&BlockingIoSourceReadiness> for EpollReadiness {
119 fn from(readiness: &BlockingIoSourceReadiness) -> Self {
120 Self {
121 epollin: readiness.readable,
122 epollout: readiness.writable,
123 epollrdhup: readiness.read_closed,
124 epollhup: readiness.write_closed,
125 epollerr: readiness.error,
126 }
127 }
128}
129
130impl FileDescription for Epoll {
131 fn name(&self) -> &'static str {
132 "epoll"
133 }
134
135 fn metadata<'tcx>(
136 &self,
137 ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
138 interp_ok(Either::Right("S_IFREG"))
140 }
141
142 fn destroy<'tcx>(
143 mut self,
144 self_id: FdId,
145 _communicate_allowed: bool,
146 ecx: &mut MiriInterpCx<'tcx>,
147 ) -> InterpResult<'tcx, io::Result<()>> {
148 let mut ids = self.interest_list.get_mut().keys().map(|(id, _num)| *id).collect::<Vec<_>>();
150 ids.dedup(); for id in ids {
152 ecx.machine.epoll_interests.remove(id, self_id);
153 }
154 interp_ok(Ok(()))
155 }
156
157 fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
158 self
159 }
160}
161
162impl UnixFileDescription for Epoll {}
163
164pub struct EpollInterestTable(BTreeMap<FdId, Vec<(FdId, WeakFileDescriptionRef<Epoll>)>>);
169
170impl EpollInterestTable {
171 pub(crate) fn new() -> Self {
172 EpollInterestTable(BTreeMap::new())
173 }
174
175 fn insert(&mut self, id: FdId, epoll: &FileDescriptionRef<Epoll>) {
176 let epolls = self.0.entry(id).or_default();
177 let idx = epolls
178 .binary_search_by_key(&epoll.id(), |&(id, _)| id)
179 .expect_err("trying to add an epoll that's already in the list");
180 epolls.insert(idx, (epoll.id(), FileDescriptionRef::downgrade(epoll)));
181 }
182
183 fn remove(&mut self, id: FdId, epoll_id: FdId) {
184 let epolls = self.0.entry(id).or_default();
185 let idx = epolls
186 .binary_search_by_key(&epoll_id, |&(id, _)| id)
187 .expect("trying to remove an epoll that's not in the list");
188 epolls.remove(idx);
189 }
190
191 fn get_epolls(&self, id: FdId) -> Option<impl Iterator<Item = &WeakFileDescriptionRef<Epoll>>> {
192 self.0.get(&id).map(|epolls| epolls.iter().map(|(_id, epoll)| epoll))
193 }
194
195 pub fn remove_epolls(&mut self, id: FdId) {
196 if let Some(epolls) = self.0.remove(&id) {
197 for epoll in epolls.iter().filter_map(|(_id, epoll)| epoll.upgrade()) {
198 epoll
201 .interest_list
202 .borrow_mut()
203 .extract_if(range_for_id(id), |_, _| true)
204 .for_each(drop);
206 epoll
207 .ready_set
208 .borrow_mut()
209 .extract_if(range_for_id(id), |_| true)
210 .for_each(drop);
212 }
213 }
214 }
215}
216
217impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
218pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
219 fn epoll_create1(&mut self, flags: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
225 let this = self.eval_context_mut();
226
227 let flags = this.read_scalar(flags)?.to_i32()?;
228
229 let epoll_cloexec = this.eval_libc_i32("EPOLL_CLOEXEC");
230
231 if flags != epoll_cloexec && flags != 0 {
233 throw_unsup_format!(
234 "epoll_create1: flag {:#x} is unsupported, only 0 or EPOLL_CLOEXEC are allowed",
235 flags
236 );
237 }
238
239 let fd = this.machine.fds.insert_new(Epoll::default());
240 interp_ok(Scalar::from_i32(fd))
241 }
242
243 fn epoll_ctl(
257 &mut self,
258 epfd: &OpTy<'tcx>,
259 op: &OpTy<'tcx>,
260 fd: &OpTy<'tcx>,
261 event: &OpTy<'tcx>,
262 ) -> InterpResult<'tcx, Scalar> {
263 let this = self.eval_context_mut();
264
265 let epfd_value = this.read_scalar(epfd)?.to_i32()?;
266 let op = this.read_scalar(op)?.to_i32()?;
267 let fd = this.read_scalar(fd)?.to_i32()?;
268 let event = this.deref_pointer_as(event, this.libc_ty_layout("epoll_event"))?;
269
270 let epoll_ctl_add = this.eval_libc_i32("EPOLL_CTL_ADD");
271 let epoll_ctl_mod = this.eval_libc_i32("EPOLL_CTL_MOD");
272 let epoll_ctl_del = this.eval_libc_i32("EPOLL_CTL_DEL");
273 let epollin = this.eval_libc_u32("EPOLLIN");
274 let epollout = this.eval_libc_u32("EPOLLOUT");
275 let epollrdhup = this.eval_libc_u32("EPOLLRDHUP");
276 let epollet = this.eval_libc_u32("EPOLLET");
277 let epollhup = this.eval_libc_u32("EPOLLHUP");
278 let epollerr = this.eval_libc_u32("EPOLLERR");
279
280 if epfd_value == fd {
282 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
283 }
284
285 let Some(epfd) = this.machine.fds.get(epfd_value) else {
287 return this.set_last_error_and_return_i32(LibcError("EBADF"));
288 };
289 let epfd = epfd
290 .downcast::<Epoll>()
291 .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
292
293 let mut interest_list = epfd.interest_list.borrow_mut();
294
295 let Some(fd_ref) = this.machine.fds.get(fd) else {
296 return this.set_last_error_and_return_i32(LibcError("EBADF"));
297 };
298 let id = fd_ref.id();
299
300 if op == epoll_ctl_add || op == epoll_ctl_mod {
301 let mut events =
303 this.read_scalar(&this.project_field(&event, FieldIdx::ZERO)?)?.to_u32()?;
304 let data = this.read_scalar(&this.project_field(&event, FieldIdx::ONE)?)?.to_u64()?;
305
306 let mut flags = events;
308 events |= epollhup;
312 events |= epollerr;
313
314 if events & epollet != epollet {
315 throw_unsup_format!("epoll_ctl: epollet flag must be included.");
317 } else {
318 flags &= !epollet;
319 }
320 if flags & epollin == epollin {
321 flags &= !epollin;
322 }
323 if flags & epollout == epollout {
324 flags &= !epollout;
325 }
326 if flags & epollrdhup == epollrdhup {
327 flags &= !epollrdhup;
328 }
329 if flags & epollhup == epollhup {
330 flags &= !epollhup;
331 }
332 if flags & epollerr == epollerr {
333 flags &= !epollerr;
334 }
335 if flags != 0 {
336 throw_unsup_format!(
337 "epoll_ctl: encountered unknown unsupported flags {:#x}",
338 flags
339 );
340 }
341
342 let epoll_key = (id, fd);
345 if op == epoll_ctl_add {
346 if interest_list.range(range_for_id(id)).next().is_none() {
347 this.machine.epoll_interests.insert(id, &epfd);
350 }
351 let new_interest = EpollEventInterest {
352 relevant_events: events,
353 data,
354 active_events: 0,
355 clock: VClock::default(),
356 };
357 if interest_list.try_insert(epoll_key, new_interest).is_err() {
358 return this.set_last_error_and_return_i32(LibcError("EEXIST"));
360 }
361 } else {
362 let Some(interest) = interest_list.get_mut(&epoll_key) else {
364 return this.set_last_error_and_return_i32(LibcError("ENOENT"));
365 };
366 interest.relevant_events = events;
367 interest.data = data;
368 }
369
370 let active_events = fd_ref.as_unix(this).epoll_active_events()?.get_event_bitmask(this);
371
372 update_readiness(
374 this,
375 &epfd,
376 active_events,
377 true,
378 move |callback| {
379 callback(epoll_key, interest_list.get_mut(&epoll_key).unwrap())
382 },
383 )?;
384
385 interp_ok(Scalar::from_i32(0))
386 } else if op == epoll_ctl_del {
387 let epoll_key = (id, fd);
388
389 if interest_list.remove(&epoll_key).is_none() {
391 return this.set_last_error_and_return_i32(LibcError("ENOENT"));
393 };
394 epfd.ready_set.borrow_mut().remove(&epoll_key);
395 if interest_list.range(range_for_id(id)).next().is_none() {
398 this.machine.epoll_interests.remove(id, epfd.id());
399 }
400
401 interp_ok(Scalar::from_i32(0))
402 } else {
403 throw_unsup_format!("unsupported epoll_ctl operation: {op}");
404 }
405 }
406
407 fn epoll_wait(
440 &mut self,
441 epfd: &OpTy<'tcx>,
442 events_op: &OpTy<'tcx>,
443 maxevents: &OpTy<'tcx>,
444 timeout: &OpTy<'tcx>,
445 dest: &MPlaceTy<'tcx>,
446 ) -> InterpResult<'tcx> {
447 let this = self.eval_context_mut();
448
449 let epfd_value = this.read_scalar(epfd)?.to_i32()?;
450 let events = this.read_immediate(events_op)?;
451 let maxevents = this.read_scalar(maxevents)?.to_i32()?;
452 let timeout = this.read_scalar(timeout)?.to_i32()?;
453
454 if epfd_value <= 0 || maxevents <= 0 {
455 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
456 }
457
458 let event = this.deref_pointer_as(
461 &events,
462 this.libc_array_ty_layout("epoll_event", maxevents.try_into().unwrap()),
463 )?;
464
465 let Some(epfd) = this.machine.fds.get(epfd_value) else {
466 return this.set_last_error_and_return(LibcError("EBADF"), dest);
467 };
468 let Some(epfd) = epfd.downcast::<Epoll>() else {
469 return this.set_last_error_and_return(LibcError("EBADF"), dest);
470 };
471
472 if timeout == 0 || !epfd.ready_set.borrow().is_empty() {
473 return_ready_list(&epfd, dest, &event, this)?;
475 } else {
476 let timeout = match timeout {
478 0.. => {
479 let duration = Duration::from_millis(timeout.try_into().unwrap());
480 Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration))
481 }
482 -1 => None,
483 ..-1 => {
484 throw_unsup_format!(
485 "epoll_wait: Only timeout values greater than or equal to -1 are supported."
486 );
487 }
488 };
489 epfd.queue.borrow_mut().push_back(this.active_thread());
491 let dest = dest.clone();
493 this.block_thread(
497 BlockReason::Epoll { epfd: epfd.clone() },
498 timeout,
499 callback!(
500 @capture<'tcx> {
501 epfd: FileDescriptionRef<Epoll>,
502 dest: MPlaceTy<'tcx>,
503 event: MPlaceTy<'tcx>,
504 }
505 |this, unblock: UnblockKind| {
506 match unblock {
507 UnblockKind::Ready => {
508 let events = return_ready_list(&epfd, &dest, &event, this)?;
509 assert!(events > 0, "we got woken up with no events to deliver");
510 interp_ok(())
511 },
512 UnblockKind::TimedOut => {
513 epfd
515 .queue.borrow_mut()
516 .retain(|&id| id != this.active_thread());
517 this.write_int(0, &dest)?;
518 interp_ok(())
519 },
520 }
521 }
522 ),
523 );
524 }
525 interp_ok(())
526 }
527
528 fn update_epoll_active_events(
535 &mut self,
536 fd_ref: DynFileDescriptionRef,
537 force_edge: bool,
538 ) -> InterpResult<'tcx> {
539 let this = self.eval_context_mut();
540 let id = fd_ref.id();
541 let Some(epolls) = this.machine.epoll_interests.get_epolls(id) else {
544 return interp_ok(());
545 };
546 let epolls = epolls
547 .map(|weak| {
548 weak.upgrade()
549 .expect("someone forgot to remove the garbage from `machine.epoll_interests`")
550 })
551 .collect::<Vec<_>>();
552 let active_events = fd_ref.as_unix(this).epoll_active_events()?.get_event_bitmask(this);
553 for epoll in epolls {
554 update_readiness(this, &epoll, active_events, force_edge, |callback| {
555 for (&key, interest) in epoll.interest_list.borrow_mut().range_mut(range_for_id(id))
556 {
557 callback(key, interest)?;
558 }
559 interp_ok(())
560 })?;
561 }
562
563 interp_ok(())
564 }
565
566 fn has_epoll_host_interests(&self, epfd: &FileDescriptionRef<Epoll>) -> bool {
569 let this = self.eval_context_ref();
570 epfd.interest_list.borrow().iter().any(|((fd_id, _fd_num), _)| {
571 this.machine.blocking_io.contains_source(fd_id)
574 })
575 }
576}
577
578fn update_readiness<'tcx>(
585 ecx: &mut MiriInterpCx<'tcx>,
586 epoll: &FileDescriptionRef<Epoll>,
587 active_events: u32,
588 force_edge: bool,
589 for_each_interest: impl FnOnce(
590 &mut dyn FnMut(EpollEventKey, &mut EpollEventInterest) -> InterpResult<'tcx>,
591 ) -> InterpResult<'tcx>,
592) -> InterpResult<'tcx> {
593 let mut ready_set = epoll.ready_set.borrow_mut();
594 for_each_interest(&mut |key, interest| {
595 let new_readiness = interest.relevant_events & active_events;
597 let prev_readiness = std::mem::replace(&mut interest.active_events, new_readiness);
598 if new_readiness == 0 {
599 ready_set.remove(&key);
601 } else if force_edge || new_readiness != prev_readiness & new_readiness {
602 ready_set.insert(key);
605 ecx.release_clock(|clock| {
606 interest.clock.join(clock);
607 })?;
608 }
609 interp_ok(())
610 })?;
611 while !ready_set.is_empty()
613 && let Some(thread_id) = epoll.queue.borrow_mut().pop_front()
614 {
615 drop(ready_set); ecx.unblock_thread(thread_id, BlockReason::Epoll { epfd: epoll.clone() })?;
617 ready_set = epoll.ready_set.borrow_mut();
618 }
619
620 interp_ok(())
621}
622
623fn return_ready_list<'tcx>(
626 epfd: &FileDescriptionRef<Epoll>,
627 dest: &MPlaceTy<'tcx>,
628 events: &MPlaceTy<'tcx>,
629 ecx: &mut MiriInterpCx<'tcx>,
630) -> InterpResult<'tcx, i32> {
631 let mut interest_list = epfd.interest_list.borrow_mut();
632 let mut ready_set = epfd.ready_set.borrow_mut();
633 let mut num_of_events: i32 = 0;
634 let mut array_iter = ecx.project_array_fields(events)?;
635
636 if cfg!(debug_assertions) {
638 for (key, interest) in interest_list.iter() {
639 let fd = ecx.machine.fds.fds.values().find(|fd| fd.id() == key.0).unwrap();
642 let current_active = fd.as_unix(ecx).epoll_active_events()?.get_event_bitmask(ecx);
643 assert_eq!(interest.active_events, current_active & interest.relevant_events);
644 }
645 }
646
647 let mut event_lower_bound = Bound::Unbounded;
652 while let Some(slot) = array_iter.next(ecx)?
653 && let Some(&key) = ready_set.range((event_lower_bound, Bound::Unbounded)).next()
654 {
655 let interest = interest_list.get_mut(&key).expect("non-existent event in ready set");
656 ecx.write_int_fields_named(
658 &[("events", interest.active_events.into()), ("u64", interest.data.into())],
659 &slot.1,
660 )?;
661 num_of_events = num_of_events.strict_add(1);
662 ecx.acquire_clock(&interest.clock)?;
664 ready_set.remove(&key);
666 event_lower_bound = Bound::Excluded(key);
668 }
669 ecx.write_int(num_of_events, dest)?;
670 interp_ok(num_of_events)
671}