miri/shims/unix/linux_like/
epoll.rs1use std::cell::RefCell;
2use std::collections::{BTreeMap, BTreeSet, VecDeque};
3use std::io;
4use std::time::Duration;
5
6use rustc_abi::FieldIdx;
7
8use crate::concurrency::VClock;
9use crate::shims::files::{
10 DynFileDescriptionRef, FdId, FdNum, FileDescription, FileDescriptionRef, WeakFileDescriptionRef,
11};
12use crate::shims::unix::UnixFileDescription;
13use crate::*;
14
15type EpollEventKey = (FdId, FdNum);
16
17#[derive(Debug, Default)]
19struct Epoll {
20 interest_list: RefCell<BTreeMap<EpollEventKey, EpollEventInterest>>,
23 ready_set: RefCell<BTreeSet<EpollEventKey>>,
26 queue: RefCell<VecDeque<ThreadId>>,
28}
29
30impl VisitProvenance for Epoll {
31 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
32 }
34}
35
36fn range_for_id(id: FdId) -> std::ops::RangeInclusive<EpollEventKey> {
38 (id, 0)..=(id, i32::MAX)
39}
40
41#[derive(Debug)]
43pub struct EpollEventInterest {
44 relevant_events: u32,
46 active_events: u32,
48 clock: VClock,
50 data: u64,
55}
56
57#[derive(Debug)]
59pub struct EpollEvents {
60 pub epollin: bool,
63 pub epollout: bool,
65 pub epollrdhup: bool,
68 pub epollhup: bool,
74 pub epollerr: bool,
76}
77
78impl EpollEvents {
79 pub fn new() -> Self {
80 EpollEvents {
81 epollin: false,
82 epollout: false,
83 epollrdhup: false,
84 epollhup: false,
85 epollerr: false,
86 }
87 }
88
89 pub fn get_event_bitmask<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> u32 {
90 let epollin = ecx.eval_libc_u32("EPOLLIN");
91 let epollout = ecx.eval_libc_u32("EPOLLOUT");
92 let epollrdhup = ecx.eval_libc_u32("EPOLLRDHUP");
93 let epollhup = ecx.eval_libc_u32("EPOLLHUP");
94 let epollerr = ecx.eval_libc_u32("EPOLLERR");
95
96 let mut bitmask = 0;
97 if self.epollin {
98 bitmask |= epollin;
99 }
100 if self.epollout {
101 bitmask |= epollout;
102 }
103 if self.epollrdhup {
104 bitmask |= epollrdhup;
105 }
106 if self.epollhup {
107 bitmask |= epollhup;
108 }
109 if self.epollerr {
110 bitmask |= epollerr;
111 }
112 bitmask
113 }
114}
115
116impl FileDescription for Epoll {
117 fn name(&self) -> &'static str {
118 "epoll"
119 }
120
121 fn destroy<'tcx>(
122 mut self,
123 self_id: FdId,
124 _communicate_allowed: bool,
125 ecx: &mut MiriInterpCx<'tcx>,
126 ) -> InterpResult<'tcx, io::Result<()>> {
127 let mut ids = self.interest_list.get_mut().keys().map(|(id, _num)| *id).collect::<Vec<_>>();
129 ids.dedup(); for id in ids {
131 ecx.machine.epoll_interests.remove(id, self_id);
132 }
133 interp_ok(Ok(()))
134 }
135
136 fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
137 self
138 }
139}
140
141impl UnixFileDescription for Epoll {}
142
143pub struct EpollInterestTable(BTreeMap<FdId, Vec<(FdId, WeakFileDescriptionRef<Epoll>)>>);
148
149impl EpollInterestTable {
150 pub(crate) fn new() -> Self {
151 EpollInterestTable(BTreeMap::new())
152 }
153
154 fn insert(&mut self, id: FdId, epoll: &FileDescriptionRef<Epoll>) {
155 let epolls = self.0.entry(id).or_default();
156 let idx = epolls
157 .binary_search_by_key(&epoll.id(), |&(id, _)| id)
158 .expect_err("trying to add an epoll that's already in the list");
159 epolls.insert(idx, (epoll.id(), FileDescriptionRef::downgrade(epoll)));
160 }
161
162 fn remove(&mut self, id: FdId, epoll_id: FdId) {
163 let epolls = self.0.entry(id).or_default();
164 let idx = epolls
165 .binary_search_by_key(&epoll_id, |&(id, _)| id)
166 .expect("trying to remove an epoll that's not in the list");
167 epolls.remove(idx);
168 }
169
170 fn get_epolls(&self, id: FdId) -> Option<impl Iterator<Item = &WeakFileDescriptionRef<Epoll>>> {
171 self.0.get(&id).map(|epolls| epolls.iter().map(|(_id, epoll)| epoll))
172 }
173
174 pub fn remove_epolls(&mut self, id: FdId) {
175 if let Some(epolls) = self.0.remove(&id) {
176 for epoll in epolls.iter().filter_map(|(_id, epoll)| epoll.upgrade()) {
177 epoll
180 .interest_list
181 .borrow_mut()
182 .extract_if(range_for_id(id), |_, _| true)
183 .for_each(drop);
185 epoll
186 .ready_set
187 .borrow_mut()
188 .extract_if(range_for_id(id), |_| true)
189 .for_each(drop);
191 }
192 }
193 }
194}
195
196impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
197pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
198 fn epoll_create1(&mut self, flags: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
204 let this = self.eval_context_mut();
205
206 let flags = this.read_scalar(flags)?.to_i32()?;
207
208 let epoll_cloexec = this.eval_libc_i32("EPOLL_CLOEXEC");
209
210 if flags != epoll_cloexec && flags != 0 {
212 throw_unsup_format!(
213 "epoll_create1: flag {:#x} is unsupported, only 0 or EPOLL_CLOEXEC are allowed",
214 flags
215 );
216 }
217
218 let fd = this.machine.fds.insert_new(Epoll::default());
219 interp_ok(Scalar::from_i32(fd))
220 }
221
222 fn epoll_ctl(
236 &mut self,
237 epfd: &OpTy<'tcx>,
238 op: &OpTy<'tcx>,
239 fd: &OpTy<'tcx>,
240 event: &OpTy<'tcx>,
241 ) -> InterpResult<'tcx, Scalar> {
242 let this = self.eval_context_mut();
243
244 let epfd_value = this.read_scalar(epfd)?.to_i32()?;
245 let op = this.read_scalar(op)?.to_i32()?;
246 let fd = this.read_scalar(fd)?.to_i32()?;
247 let event = this.deref_pointer_as(event, this.libc_ty_layout("epoll_event"))?;
248
249 let epoll_ctl_add = this.eval_libc_i32("EPOLL_CTL_ADD");
250 let epoll_ctl_mod = this.eval_libc_i32("EPOLL_CTL_MOD");
251 let epoll_ctl_del = this.eval_libc_i32("EPOLL_CTL_DEL");
252 let epollin = this.eval_libc_u32("EPOLLIN");
253 let epollout = this.eval_libc_u32("EPOLLOUT");
254 let epollrdhup = this.eval_libc_u32("EPOLLRDHUP");
255 let epollet = this.eval_libc_u32("EPOLLET");
256 let epollhup = this.eval_libc_u32("EPOLLHUP");
257 let epollerr = this.eval_libc_u32("EPOLLERR");
258
259 if epfd_value == fd {
261 return this.set_last_error_and_return_i32(LibcError("EFAULT"));
262 }
263
264 let Some(epfd) = this.machine.fds.get(epfd_value) else {
266 return this.set_last_error_and_return_i32(LibcError("EBADF"));
267 };
268 let epfd = epfd
269 .downcast::<Epoll>()
270 .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
271
272 let mut interest_list = epfd.interest_list.borrow_mut();
273
274 let Some(fd_ref) = this.machine.fds.get(fd) else {
275 return this.set_last_error_and_return_i32(LibcError("EBADF"));
276 };
277 let id = fd_ref.id();
278
279 if op == epoll_ctl_add || op == epoll_ctl_mod {
280 let mut events =
282 this.read_scalar(&this.project_field(&event, FieldIdx::ZERO)?)?.to_u32()?;
283 let data = this.read_scalar(&this.project_field(&event, FieldIdx::ONE)?)?.to_u64()?;
284
285 let mut flags = events;
287 events |= epollhup;
291 events |= epollerr;
292
293 if events & epollet != epollet {
294 throw_unsup_format!("epoll_ctl: epollet flag must be included.");
296 } else {
297 flags &= !epollet;
298 }
299 if flags & epollin == epollin {
300 flags &= !epollin;
301 }
302 if flags & epollout == epollout {
303 flags &= !epollout;
304 }
305 if flags & epollrdhup == epollrdhup {
306 flags &= !epollrdhup;
307 }
308 if flags & epollhup == epollhup {
309 flags &= !epollhup;
310 }
311 if flags & epollerr == epollerr {
312 flags &= !epollerr;
313 }
314 if flags != 0 {
315 throw_unsup_format!(
316 "epoll_ctl: encountered unknown unsupported flags {:#x}",
317 flags
318 );
319 }
320
321 let epoll_key = (id, fd);
324 if op == epoll_ctl_add {
325 if interest_list.range(range_for_id(id)).next().is_none() {
326 this.machine.epoll_interests.insert(id, &epfd);
329 }
330 let new_interest = EpollEventInterest {
331 relevant_events: events,
332 data,
333 active_events: 0,
334 clock: VClock::default(),
335 };
336 if interest_list.try_insert(epoll_key, new_interest).is_err() {
337 return this.set_last_error_and_return_i32(LibcError("EEXIST"));
339 }
340 } else {
341 let Some(interest) = interest_list.get_mut(&epoll_key) else {
343 return this.set_last_error_and_return_i32(LibcError("ENOENT"));
344 };
345 interest.relevant_events = events;
346 interest.data = data;
347 }
348
349 update_readiness(
351 this,
352 &epfd,
353 fd_ref.as_unix(this).epoll_active_events()?.get_event_bitmask(this),
354 true,
355 move |callback| {
356 callback(epoll_key, interest_list.get_mut(&epoll_key).unwrap())
359 },
360 )?;
361
362 interp_ok(Scalar::from_i32(0))
363 } else if op == epoll_ctl_del {
364 let epoll_key = (id, fd);
365
366 if interest_list.remove(&epoll_key).is_none() {
368 return this.set_last_error_and_return_i32(LibcError("ENOENT"));
370 };
371 epfd.ready_set.borrow_mut().remove(&epoll_key);
372 if interest_list.range(range_for_id(id)).next().is_none() {
375 this.machine.epoll_interests.remove(id, epfd.id());
376 }
377
378 interp_ok(Scalar::from_i32(0))
379 } else {
380 throw_unsup_format!("unsupported epoll_ctl operation: {op}");
381 }
382 }
383
384 fn epoll_wait(
417 &mut self,
418 epfd: &OpTy<'tcx>,
419 events_op: &OpTy<'tcx>,
420 maxevents: &OpTy<'tcx>,
421 timeout: &OpTy<'tcx>,
422 dest: &MPlaceTy<'tcx>,
423 ) -> InterpResult<'tcx> {
424 let this = self.eval_context_mut();
425
426 let epfd_value = this.read_scalar(epfd)?.to_i32()?;
427 let events = this.read_immediate(events_op)?;
428 let maxevents = this.read_scalar(maxevents)?.to_i32()?;
429 let timeout = this.read_scalar(timeout)?.to_i32()?;
430
431 if epfd_value <= 0 || maxevents <= 0 {
432 return this.set_last_error_and_return(LibcError("EINVAL"), dest);
433 }
434
435 let event = this.deref_pointer_as(
438 &events,
439 this.libc_array_ty_layout("epoll_event", maxevents.try_into().unwrap()),
440 )?;
441
442 let Some(epfd) = this.machine.fds.get(epfd_value) else {
443 return this.set_last_error_and_return(LibcError("EBADF"), dest);
444 };
445 let Some(epfd) = epfd.downcast::<Epoll>() else {
446 return this.set_last_error_and_return(LibcError("EBADF"), dest);
447 };
448
449 if timeout == 0 || !epfd.ready_set.borrow().is_empty() {
450 return_ready_list(&epfd, dest, &event, this)?;
452 } else {
453 let timeout = match timeout {
455 0.. => {
456 let duration = Duration::from_millis(timeout.try_into().unwrap());
457 Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration))
458 }
459 -1 => None,
460 ..-1 => {
461 throw_unsup_format!(
462 "epoll_wait: Only timeout values greater than or equal to -1 are supported."
463 );
464 }
465 };
466 epfd.queue.borrow_mut().push_back(this.active_thread());
468 let dest = dest.clone();
470 this.block_thread(
474 BlockReason::Epoll,
475 timeout,
476 callback!(
477 @capture<'tcx> {
478 epfd: FileDescriptionRef<Epoll>,
479 dest: MPlaceTy<'tcx>,
480 event: MPlaceTy<'tcx>,
481 }
482 |this, unblock: UnblockKind| {
483 match unblock {
484 UnblockKind::Ready => {
485 let events = return_ready_list(&epfd, &dest, &event, this)?;
486 assert!(events > 0, "we got woken up with no events to deliver");
487 interp_ok(())
488 },
489 UnblockKind::TimedOut => {
490 epfd
492 .queue.borrow_mut()
493 .retain(|&id| id != this.active_thread());
494 this.write_int(0, &dest)?;
495 interp_ok(())
496 },
497 }
498 }
499 ),
500 );
501 }
502 interp_ok(())
503 }
504
505 fn update_epoll_active_events(
512 &mut self,
513 fd_ref: DynFileDescriptionRef,
514 force_edge: bool,
515 ) -> InterpResult<'tcx> {
516 let this = self.eval_context_mut();
517 let id = fd_ref.id();
518 let Some(epolls) = this.machine.epoll_interests.get_epolls(id) else {
521 return interp_ok(());
522 };
523 let epolls = epolls
524 .map(|weak| {
525 weak.upgrade()
526 .expect("someone forgot to remove the garbage from `machine.epoll_interests`")
527 })
528 .collect::<Vec<_>>();
529 let active_events = fd_ref.as_unix(this).epoll_active_events()?.get_event_bitmask(this);
530 for epoll in epolls {
531 update_readiness(this, &epoll, active_events, force_edge, |callback| {
532 for (&key, interest) in epoll.interest_list.borrow_mut().range_mut(range_for_id(id))
533 {
534 callback(key, interest)?;
535 }
536 interp_ok(())
537 })?;
538 }
539
540 interp_ok(())
541 }
542}
543
544fn update_readiness<'tcx>(
551 ecx: &mut MiriInterpCx<'tcx>,
552 epoll: &Epoll,
553 active_events: u32,
554 force_edge: bool,
555 for_each_interest: impl FnOnce(
556 &mut dyn FnMut(EpollEventKey, &mut EpollEventInterest) -> InterpResult<'tcx>,
557 ) -> InterpResult<'tcx>,
558) -> InterpResult<'tcx> {
559 let mut ready_set = epoll.ready_set.borrow_mut();
560 for_each_interest(&mut |key, interest| {
561 let new_readiness = interest.relevant_events & active_events;
563 let prev_readiness = std::mem::replace(&mut interest.active_events, new_readiness);
564 if new_readiness == 0 {
565 ready_set.remove(&key);
567 } else if force_edge || new_readiness != prev_readiness & new_readiness {
568 ready_set.insert(key);
571 ecx.release_clock(|clock| {
572 interest.clock.join(clock);
573 })?;
574 }
575 interp_ok(())
576 })?;
577 while !ready_set.is_empty()
579 && let Some(thread_id) = epoll.queue.borrow_mut().pop_front()
580 {
581 drop(ready_set); ecx.unblock_thread(thread_id, BlockReason::Epoll)?;
583 ready_set = epoll.ready_set.borrow_mut();
584 }
585
586 interp_ok(())
587}
588
589fn return_ready_list<'tcx>(
592 epfd: &FileDescriptionRef<Epoll>,
593 dest: &MPlaceTy<'tcx>,
594 events: &MPlaceTy<'tcx>,
595 ecx: &mut MiriInterpCx<'tcx>,
596) -> InterpResult<'tcx, i32> {
597 let mut interest_list = epfd.interest_list.borrow_mut();
598 let mut ready_set = epfd.ready_set.borrow_mut();
599 let mut num_of_events: i32 = 0;
600 let mut array_iter = ecx.project_array_fields(events)?;
601
602 if cfg!(debug_assertions) {
604 for (key, interest) in interest_list.iter() {
605 let fd = &ecx.machine.fds.fds.values().find(|fd| fd.id() == key.0).unwrap();
608 let current_active = fd.as_unix(ecx).epoll_active_events()?.get_event_bitmask(ecx);
609 assert_eq!(interest.active_events, current_active & interest.relevant_events);
610 }
611 }
612
613 while let Some(slot) = array_iter.next(ecx)?
615 && let Some(&key) = ready_set.first()
616 {
617 let interest = interest_list.get_mut(&key).expect("non-existent event in ready set");
618 ecx.write_int_fields_named(
620 &[("events", interest.active_events.into()), ("u64", interest.data.into())],
621 &slot.1,
622 )?;
623 num_of_events = num_of_events.strict_add(1);
624 ecx.acquire_clock(&interest.clock)?;
626 ready_set.remove(&key);
629 }
630 ecx.write_int(num_of_events, dest)?;
631 interp_ok(num_of_events)
632}