Skip to main content

shadow_rs/host/syscall/handler/
epoll.rs

1use std::ops::DerefMut;
2use std::sync::Arc;
3
4use linux_api::epoll::{EpollCreateFlags, EpollCtlOp, EpollEvents};
5use linux_api::errno::Errno;
6use linux_api::fcntl::DescriptorFlags;
7use shadow_shim_helper_rs::simulation_time::SimulationTime;
8use shadow_shim_helper_rs::syscall_types::ForeignPtr;
9
10use crate::core::worker::Worker;
11use crate::cshadow;
12use crate::host::descriptor::descriptor_table::DescriptorHandle;
13use crate::host::descriptor::epoll::Epoll;
14use crate::host::descriptor::{CompatFile, Descriptor, File, FileState, OpenFile};
15use crate::host::memory_manager::MemoryManager;
16use crate::host::syscall::handler::{SyscallContext, SyscallHandler};
17use crate::host::syscall::type_formatting::SyscallArrayArg;
18use crate::host::syscall::types::{ForeignArrayPtr, SyscallError};
19use crate::utility::callback_queue::CallbackQueue;
20
21impl SyscallHandler {
22    log_syscall!(
23        epoll_create,
24        /* rv */ std::ffi::c_int,
25        /* size */ std::ffi::c_int,
26    );
27    pub fn epoll_create(
28        ctx: &mut SyscallContext,
29        size: std::ffi::c_int,
30    ) -> Result<DescriptorHandle, Errno> {
31        // epoll_create(2): "Since Linux 2.6.8, the size argument is ignored, but must be greater
32        // than zero"
33        if size <= 0 {
34            return Err(Errno::EINVAL);
35        }
36
37        Self::epoll_create_helper(ctx, 0)
38    }
39
40    log_syscall!(
41        epoll_create1,
42        /* rv */ std::ffi::c_int,
43        /* flags */ std::ffi::c_int,
44    );
45    pub fn epoll_create1(
46        ctx: &mut SyscallContext,
47        flags: std::ffi::c_int,
48    ) -> Result<DescriptorHandle, Errno> {
49        Self::epoll_create_helper(ctx, flags)
50    }
51
52    fn epoll_create_helper(
53        ctx: &mut SyscallContext,
54        flags: std::ffi::c_int,
55    ) -> Result<DescriptorHandle, Errno> {
56        // See here for the order that the input args are checked in Linux:
57        // https://github.com/torvalds/linux/blob/2cf0f715623872823a72e451243bbf555d10d032/fs/eventpoll.c#L2030
58        let Some(flags) = EpollCreateFlags::from_bits(flags) else {
59            log::debug!("Invalid epoll_create flags: {flags}");
60            return Err(Errno::EINVAL);
61        };
62
63        let mut desc_flags = DescriptorFlags::empty();
64
65        if flags.contains(EpollCreateFlags::EPOLL_CLOEXEC) {
66            desc_flags.insert(DescriptorFlags::FD_CLOEXEC);
67        }
68
69        let epoll = Epoll::new();
70        let mut desc = Descriptor::new(CompatFile::New(OpenFile::new(File::Epoll(epoll))));
71        desc.set_flags(desc_flags);
72
73        let fd = ctx
74            .objs
75            .thread
76            .descriptor_table_borrow_mut(ctx.objs.host)
77            .register_descriptor(desc)
78            .or(Err(Errno::ENFILE))?;
79
80        log::trace!("Created epoll fd {fd}");
81
82        Ok(fd)
83    }
84
85    log_syscall!(
86        epoll_ctl,
87        /* rv */ std::ffi::c_int,
88        /* epfd */ std::ffi::c_int,
89        /* op */ std::ffi::c_int,
90        /* fd */ std::ffi::c_int,
91        /* event */ *const std::ffi::c_void,
92    );
93    pub fn epoll_ctl(
94        ctx: &mut SyscallContext,
95        epfd: std::ffi::c_int,
96        op: std::ffi::c_int,
97        fd: std::ffi::c_int,
98        event_ptr: ForeignPtr<linux_api::epoll::epoll_event>,
99    ) -> Result<(), Errno> {
100        // See here for the order that the input args are checked in Linux:
101        // https://github.com/torvalds/linux/blob/2cf0f715623872823a72e451243bbf555d10d032/fs/eventpoll.c#L2111
102
103        // We'll need to look up descriptors.
104        let desc_table = ctx.objs.thread.descriptor_table_borrow(ctx.objs.host);
105
106        // Get the epoll descriptor, or return early if it doesn't exist.
107        let (epoll, epoll_canon_handle) = {
108            let desc = Self::get_descriptor(&desc_table, epfd)?;
109
110            let CompatFile::New(epoll) = desc.file() else {
111                return Err(Errno::EINVAL);
112            };
113
114            let epoll_canon_handle = epoll.inner_file().canonical_handle();
115
116            let File::Epoll(epoll) = epoll.inner_file() else {
117                return Err(Errno::EINVAL);
118            };
119
120            (epoll, epoll_canon_handle)
121        };
122
123        // Get the target descriptor, or return errors as appropriate.
124        let target = {
125            let desc = Self::get_descriptor(&desc_table, fd)?;
126
127            // Our epoll implementation only supports adding new Rust descriptor types.
128            // However, the only legacy type remaining in Shadow is a regular file, and
129            // epoll_ctl(2) states that EPERM should be returned for regular files and
130            // other files that don't support epolling.
131            match desc.file() {
132                CompatFile::New(file) => file.inner_file().clone(),
133                CompatFile::Legacy(file) => {
134                    let file_type = unsafe { cshadow::legacyfile_getType(file.ptr()) };
135                    if file_type == cshadow::_LegacyFileType_DT_FILE {
136                        // Epoll doesn't support regular files.
137                        return Err(Errno::EPERM);
138                    } else {
139                        // Our implementation doesn't support other legacy types.
140                        // We don't think we have such types remaining, but warn anyway.
141                        warn_once_then_trace!(
142                            "Attempted to add a legacy file to an epoll file, which \
143                            shadow doesn't support"
144                        );
145                        return Err(Errno::EINVAL);
146                    }
147                }
148            }
149        };
150
151        // An epoll instance is not allowed to monitor itself.
152        if epoll_canon_handle == target.canonical_handle() {
153            return Err(Errno::EINVAL);
154        }
155
156        // Extract the operation.
157        let Ok(op) = EpollCtlOp::try_from(op) else {
158            log::debug!("Invalid epoll op: {op}");
159            return Err(Errno::EINVAL);
160        };
161
162        // Extract the events and data.
163        let (events, data) = if op == EpollCtlOp::EPOLL_CTL_DEL {
164            // epoll_ctl(2): Since Linux 2.6.9, the event pointer is ignored and can be specified as
165            // NULL when using EPOLL_CTL_DEL.
166            (EpollEvents::empty(), 0)
167        } else {
168            let mem = ctx.objs.process.memory_borrow();
169            let ev = mem.read(event_ptr)?;
170
171            let Some(mut events) = EpollEvents::from_bits(ev.events) else {
172                // Braces are needed around `ev.events` for alignment (see rustc --explain E0793).
173                log::debug!("Invalid epoll_ctl events: {}", { ev.events });
174                return Err(Errno::EINVAL);
175            };
176
177            // epoll_ctl(2): epoll always reports for EPOLLERR and EPOLLHUP
178            events.insert(EpollEvents::EPOLLERR | EpollEvents::EPOLLHUP);
179
180            (events, ev.data)
181        };
182
183        log::trace!("Calling epoll_ctl on epoll {epfd} with child {fd}");
184
185        CallbackQueue::queue_and_run_with_legacy(|cb_queue| {
186            let weak_epoll = Arc::downgrade(epoll);
187            epoll
188                .borrow_mut()
189                .ctl(op, fd, target, events, data, weak_epoll, cb_queue)
190        })?;
191        Ok(())
192    }
193
194    log_syscall!(
195        epoll_wait,
196        /* rv */ std::ffi::c_int,
197        /* epfd */ std::ffi::c_int,
198        /* events */ SyscallArrayArg</* max_events */ 2, linux_api::epoll::epoll_event>,
199        /* max_events */ std::ffi::c_int,
200        /* timeout */ std::ffi::c_int,
201    );
202    pub fn epoll_wait(
203        ctx: &mut SyscallContext,
204        epfd: std::ffi::c_int,
205        events_ptr: ForeignPtr<linux_api::epoll::epoll_event>,
206        max_events: std::ffi::c_int,
207        timeout: std::ffi::c_int,
208    ) -> Result<std::ffi::c_int, SyscallError> {
209        // Note that timeout is given in milliseconds.
210        let timeout = timeout_arg_to_maybe_simtime(timeout)?;
211        Self::epoll_wait_helper(ctx, epfd, events_ptr, max_events, timeout, None)
212    }
213
214    log_syscall!(
215        epoll_pwait,
216        /* rv */ std::ffi::c_int,
217        /* epfd */ std::ffi::c_int,
218        /* events */ SyscallArrayArg</* max_events */ 2, linux_api::epoll::epoll_event>,
219        /* max_events */ std::ffi::c_int,
220        /* timeout */ std::ffi::c_int,
221        /* sigmask */ *const std::ffi::c_void,
222        /* sigsetsize */ linux_api::posix_types::kernel_size_t,
223    );
224    pub fn epoll_pwait(
225        ctx: &mut SyscallContext,
226        epfd: std::ffi::c_int,
227        events_ptr: ForeignPtr<linux_api::epoll::epoll_event>,
228        max_events: std::ffi::c_int,
229        timeout: std::ffi::c_int,
230        sigmask_ptr: ForeignPtr<linux_api::signal::sigset_t>,
231        _sigsetsize: linux_api::posix_types::kernel_size_t,
232    ) -> Result<std::ffi::c_int, SyscallError> {
233        // epoll_wait(2): "The sigmask argument may be specified as NULL, in which case
234        // epoll_pwait() is equivalent to epoll_wait()"
235        let sigmask = if sigmask_ptr.is_null() {
236            None
237        } else {
238            Some(ctx.objs.process.memory_borrow().read(sigmask_ptr)?)
239        };
240
241        // Note that timeout is given in milliseconds.
242        let timeout = timeout_arg_to_maybe_simtime(timeout)?;
243        Self::epoll_wait_helper(ctx, epfd, events_ptr, max_events, timeout, sigmask)
244    }
245
246    log_syscall!(
247        epoll_pwait2,
248        /* rv */ std::ffi::c_int,
249        /* epfd */ std::ffi::c_int,
250        /* events */ SyscallArrayArg</* max_events */ 2, linux_api::epoll::epoll_event>,
251        /* max_events */ std::ffi::c_int,
252        /* timeout */ *const std::ffi::c_void,
253        /* sigmask */ *const std::ffi::c_void,
254        /* sigsetsize */ linux_api::posix_types::kernel_size_t,
255    );
256    pub fn epoll_pwait2(
257        ctx: &mut SyscallContext,
258        epfd: std::ffi::c_int,
259        events_ptr: ForeignPtr<linux_api::epoll::epoll_event>,
260        max_events: std::ffi::c_int,
261        timeout_ptr: ForeignPtr<linux_api::time::timespec>,
262        sigmask_ptr: ForeignPtr<linux_api::signal::sigset_t>,
263        _sigsetsize: linux_api::posix_types::kernel_size_t,
264    ) -> Result<std::ffi::c_int, SyscallError> {
265        let (sigmask, timeout) = {
266            let mem = ctx.objs.process.memory_borrow();
267
268            // epoll_wait(2): "The sigmask argument may be specified as NULL, in which case
269            // epoll_pwait() is equivalent to epoll_wait()"
270            let sigmask = if sigmask_ptr.is_null() {
271                None
272            } else {
273                Some(mem.read(sigmask_ptr)?)
274            };
275
276            // epoll_wait(2): "If timeout is NULL, then epoll_pwait2() can block indefinitely"
277            let timeout = if timeout_ptr.is_null() {
278                None
279            } else {
280                let tspec = mem.read(timeout_ptr)?;
281                let sim_time = SimulationTime::try_from(tspec).map_err(|_| Errno::EINVAL)?;
282                Some(sim_time)
283            };
284
285            (sigmask, timeout)
286        };
287
288        Self::epoll_wait_helper(ctx, epfd, events_ptr, max_events, timeout, sigmask)
289    }
290
291    fn epoll_wait_helper(
292        ctx: &mut SyscallContext,
293        epfd: std::ffi::c_int,
294        events_ptr: ForeignPtr<linux_api::epoll::epoll_event>,
295        max_events: std::ffi::c_int,
296        timeout: Option<SimulationTime>,
297        sigmask: Option<linux_api::signal::sigset_t>,
298    ) -> Result<std::ffi::c_int, SyscallError> {
299        // Linux enforces a range for max_events.
300        let max_events = {
301            let upper_bound = epoll_max_events_upper_bound();
302
303            if max_events <= 0 || max_events > upper_bound {
304                log::trace!(
305                    "Epoll maxevents {max_events} is not greater than 0 \
306                            and less than {upper_bound}"
307                );
308                return Err(Errno::EINVAL.into());
309            }
310
311            u32::try_from(max_events).unwrap()
312        };
313
314        // TODO: support the signal mask
315        if sigmask.is_some() {
316            warn_once_then_trace!(
317                "Epoll pwait called with non-null sigmask, \
318                which is not yet supported by shadow; returning EINVAL"
319            );
320            return Err(Errno::EINVAL.into());
321        }
322
323        // Get the descriptor, or return early if it doesn't exist.
324        let desc_table = ctx.objs.thread.descriptor_table_borrow(ctx.objs.host);
325        let epoll = {
326            let desc = Self::get_descriptor(&desc_table, epfd)?;
327
328            let CompatFile::New(epoll) = desc.file() else {
329                return Err(Errno::EINVAL.into());
330            };
331
332            let File::Epoll(epoll) = epoll.inner_file() else {
333                return Err(Errno::EINVAL.into());
334            };
335
336            epoll
337        };
338
339        if epoll.borrow().has_ready_events() {
340            log::trace!("Epoll {epfd} has ready events");
341
342            // We must not return an error after collecting events from epoll, otherwise the epoll
343            // state will become inconsitent with the view of events from the managed process.
344            // Thus, we explicitly check that we have a valid location to return the events before
345            // we collect them from epoll.
346            if events_ptr.is_null() {
347                return Err(Errno::EFAULT.into());
348            }
349
350            // After we collect the events here, failing to write them out to the events_ptr
351            // ForeignPointer below will leave our event state inconsistent with the managed
352            // process's understanding of the available events.
353            let ready = CallbackQueue::queue_and_run_with_legacy(|cb_queue| {
354                epoll
355                    .borrow_mut()
356                    .collect_ready_events(cb_queue, max_events)
357            });
358            let n_ready = ready.len();
359            if n_ready > max_events as usize {
360                panic!("Epoll should not return more than {max_events} events");
361            }
362
363            // Write the events out to the managed process memory.
364            let mut mem = ctx.objs.process.memory_borrow_mut();
365            write_events_to_ptr(&mut mem, ready, events_ptr)?;
366
367            // Return the number of events we are reporting.
368            log::trace!("Epoll {epfd} returning {n_ready} events");
369            return Ok(n_ready.try_into().unwrap());
370        }
371
372        // Our behavior depends on the value of timeout.
373        // Return immediately if timeout is 0.
374        if let Some(timeout) = timeout
375            && timeout.is_zero()
376        {
377            log::trace!("No events are ready on epoll {epfd} and the timeout is 0");
378            return Ok(0);
379        }
380
381        // Return immediately if we were already blocked for a while and still have no events.
382        // Condition will only exist after a wakeup.
383        if let Some(cond) = ctx.objs.thread.syscall_condition()
384            && let Some(abs_timeout) = cond.timeout()
385            && Worker::current_time().unwrap() >= abs_timeout
386        {
387            log::trace!("No events are ready on epoll {epfd} and the timeout expired");
388            return Ok(0);
389        }
390
391        // If there's a signal pending, this syscall will be interrupted.
392        if ctx.objs.thread.unblocked_signal_pending(
393            ctx.objs.process,
394            &ctx.objs.host.shim_shmem_lock_borrow().unwrap(),
395        ) {
396            return Err(SyscallError::new_interrupted(false));
397        }
398
399        // Convert timeout to an EmulatedTime.
400        let Ok(abs_timeout_opt) = timeout
401            .map(|x| Worker::current_time().unwrap().checked_add(x).ok_or(()))
402            .transpose()
403        else {
404            log::trace!("Epoll wait with invalid timeout {timeout:?} (too large)");
405            return Err(Errno::EINVAL.into());
406        };
407
408        log::trace!("No events are ready on epoll {epfd} and we need to block");
409
410        // Block on epoll state; an epoll descriptor is readable when it has events.
411        let mut rv = SyscallError::new_blocked_on_file(
412            File::Epoll(Arc::clone(epoll)),
413            FileState::READABLE,
414            /* restartable= */ false,
415        );
416
417        // Set timeout, if provided.
418        if abs_timeout_opt.is_some() {
419            rv.blocked_condition().unwrap().set_timeout(abs_timeout_opt);
420        }
421
422        Err(rv)
423    }
424}
425
426fn timeout_arg_to_maybe_simtime(
427    timeout_ms: std::ffi::c_int,
428) -> Result<Option<SimulationTime>, Errno> {
429    // epoll_wait(2): "Specifying a timeout of -1 causes epoll_wait() to block indefinitely"
430    let timeout_ms = (timeout_ms >= 0).then_some(timeout_ms);
431
432    if let Some(timeout_ms) = timeout_ms {
433        // a non-negative c_int should always convert to a u64
434        let timeout_ms = timeout_ms.try_into().unwrap();
435        let timeout = SimulationTime::try_from_millis(timeout_ms).ok_or(Errno::EINVAL)?;
436        Ok(Some(timeout))
437    } else {
438        Ok(None)
439    }
440}
441
442/// There is a maximum number of events that can be specified in Linux:
443/// https://github.com/torvalds/linux/blob/2cf0f715623872823a72e451243bbf555d10d032/fs/eventpoll.c#L2291
444///
445/// The maximum is defined as:
446///   `#define EP_MAX_EVENTS (INT_MAX / sizeof(struct epoll_event))`
447/// https://github.com/torvalds/linux/blob/2cf0f715623872823a72e451243bbf555d10d032/fs/eventpoll.c#L95
448///
449/// This function performs the above computation as Linux does.
450fn epoll_max_events_upper_bound() -> i32 {
451    let ep_max_events = i32::MAX;
452    let ep_ev_size: i32 = std::mem::size_of::<linux_api::epoll::epoll_event>()
453        .try_into()
454        .unwrap_or(i32::MAX);
455    ep_max_events.saturating_div(ep_ev_size)
456}
457
458fn write_events_to_ptr(
459    mem: &mut MemoryManager,
460    ready: Vec<(EpollEvents, u64)>,
461    events_ptr: ForeignPtr<linux_api::epoll::epoll_event>,
462) -> Result<(), Errno> {
463    let events_ptr = ForeignArrayPtr::new(events_ptr, ready.len());
464    let mut mem_ref = mem.memory_ref_mut(events_ptr)?;
465
466    for ((ev, data), plugin_ev) in ready.iter().zip(mem_ref.deref_mut().iter_mut()) {
467        plugin_ev.events = ev.bits();
468        plugin_ev.data = *data;
469    }
470
471    mem_ref.flush()?;
472
473    Ok(())
474}