shadow_rs/host/syscall/handler/
timerfd.rs

1use std::sync::Arc;
2
3use atomic_refcell::AtomicRefCell;
4use linux_api::errno::Errno;
5use linux_api::fcntl::DescriptorFlags;
6use linux_api::time::{ClockId, itimerspec};
7use nix::sys::timerfd::{TimerFlags, TimerSetTimeFlags};
8use shadow_shim_helper_rs::{
9    emulated_time::EmulatedTime, simulation_time::SimulationTime, syscall_types::ForeignPtr,
10};
11
12use crate::core::worker::Worker;
13use crate::host::descriptor::descriptor_table::DescriptorHandle;
14use crate::host::descriptor::{
15    CompatFile, Descriptor, File, FileStatus, OpenFile, timerfd::TimerFd,
16};
17use crate::host::{
18    syscall::handler::{SyscallContext, SyscallHandler},
19    syscall::types::SyscallError,
20};
21use crate::utility::callback_queue::CallbackQueue;
22
23impl SyscallHandler {
24    log_syscall!(
25        timerfd_create,
26        /* rv */ std::ffi::c_int,
27        /* clockid */ linux_api::time::ClockId,
28        /* flags */ std::ffi::c_int,
29    );
30    pub fn timerfd_create(
31        ctx: &mut SyscallContext,
32        clockid: std::ffi::c_int,
33        flags: std::ffi::c_int,
34    ) -> Result<DescriptorHandle, SyscallError> {
35        let Ok(clockid) = ClockId::try_from(clockid) else {
36            log::debug!("Invalid clockid: {clockid}");
37            return Err(Errno::EINVAL.into());
38        };
39
40        // Continue only if we support the clockid.
41        check_clockid(clockid)?;
42
43        let Some(flags) = TimerFlags::from_bits(flags) else {
44            log::debug!("Invalid timerfd_create flags: {flags}");
45            return Err(Errno::EINVAL.into());
46        };
47
48        let mut file_flags = FileStatus::empty();
49        let mut desc_flags = DescriptorFlags::empty();
50
51        if flags.contains(TimerFlags::TFD_NONBLOCK) {
52            file_flags.insert(FileStatus::NONBLOCK);
53        }
54
55        if flags.contains(TimerFlags::TFD_CLOEXEC) {
56            desc_flags.insert(DescriptorFlags::FD_CLOEXEC);
57        }
58
59        let file = TimerFd::new(file_flags);
60        let mut desc = Descriptor::new(CompatFile::New(OpenFile::new(File::TimerFd(file))));
61        desc.set_flags(desc_flags);
62
63        let fd = ctx
64            .objs
65            .thread
66            .descriptor_table_borrow_mut(ctx.objs.host)
67            .register_descriptor(desc)
68            .or(Err(Errno::ENFILE))?;
69
70        log::trace!("timerfd_create() returning fd {fd}");
71
72        Ok(fd)
73    }
74
75    log_syscall!(
76        timerfd_gettime,
77        /* rv */ std::ffi::c_int,
78        /* fd */ std::ffi::c_int,
79        /*curr_value*/ *const std::ffi::c_void,
80    );
81    pub fn timerfd_gettime(
82        ctx: &mut SyscallContext,
83        fd: std::ffi::c_int,
84        curr_value_ptr: ForeignPtr<linux_api::time::itimerspec>,
85    ) -> Result<(), SyscallError> {
86        // Get the TimerFd object.
87        let file = get_cloned_file(ctx, fd)?;
88        let File::TimerFd(ref timerfd) = file else {
89            return Err(Errno::EINVAL.into());
90        };
91
92        Self::timerfd_gettime_helper(ctx, timerfd, curr_value_ptr)?;
93
94        Ok(())
95    }
96
97    fn timerfd_gettime_helper(
98        ctx: &mut SyscallContext,
99        timerfd: &Arc<AtomicRefCell<TimerFd>>,
100        value_ptr: ForeignPtr<linux_api::time::itimerspec>,
101    ) -> Result<(), Errno> {
102        // Lookup the timer state.
103        let (remaining, interval) = {
104            let borrowed = timerfd.borrow();
105
106            // We return a zero duration if the timer is disarmed.
107            let remaining = borrowed
108                .get_timer_remaining()
109                .unwrap_or(SimulationTime::ZERO);
110
111            // We return a zero duration if the timer is not configured with an interval, which
112            // indicates that it is non-periodic (i.e., set to expire only once).
113            let interval = borrowed
114                .get_timer_interval()
115                .unwrap_or(SimulationTime::ZERO);
116
117            (remaining, interval)
118        };
119
120        // Set up the result values.
121        let result = itimerspec {
122            it_value: remaining.try_into().unwrap(),
123            it_interval: interval.try_into().unwrap(),
124        };
125
126        // Write the result to the plugin.
127        ctx.objs
128            .process
129            .memory_borrow_mut()
130            .write(value_ptr, &result)?;
131
132        Ok(())
133    }
134
135    log_syscall!(
136        timerfd_settime,
137        /* rv */ std::ffi::c_int,
138        /* fd */ std::ffi::c_int,
139        /* flags */ std::ffi::c_int,
140        /* new_value */ *const std::ffi::c_void,
141        /* old_value */ *const std::ffi::c_void,
142    );
143    pub fn timerfd_settime(
144        ctx: &mut SyscallContext,
145        fd: std::ffi::c_int,
146        flags: std::ffi::c_int,
147        new_value_ptr: ForeignPtr<linux_api::time::itimerspec>,
148        old_value_ptr: ForeignPtr<linux_api::time::itimerspec>,
149    ) -> Result<(), SyscallError> {
150        let Some(flags) = TimerSetTimeFlags::from_bits(flags) else {
151            log::debug!("Invalid timerfd_settime flags: {flags}");
152            return Err(Errno::EINVAL.into());
153        };
154
155        // Get the TimerFd object.
156        let file = get_cloned_file(ctx, fd)?;
157        let File::TimerFd(ref timerfd) = file else {
158            return Err(Errno::EINVAL.into());
159        };
160
161        // Read in the new value from the plugin.
162        let new_value = ctx.objs.process.memory_borrow().read(new_value_ptr)?;
163
164        // Verify a valid range for new_time nanosecond vals.
165        let value = SimulationTime::try_from(new_value.it_value).or(Err(Errno::EINVAL))?;
166        let interval = SimulationTime::try_from(new_value.it_interval).or(Err(Errno::EINVAL))?;
167
168        // First, write out the old time if requested.
169        if !old_value_ptr.is_null() {
170            // Old value is always relative, even if TFD_TIMER_ABSTIME is set.
171            Self::timerfd_gettime_helper(ctx, timerfd, old_value_ptr)?;
172        }
173
174        // Now we can adjust the timer with the new_value.
175        if value.is_zero() {
176            // A value of 0 disarms the timer; it_interval is ignored.
177            CallbackQueue::queue_and_run_with_legacy(|cb_queue| {
178                timerfd.borrow_mut().disarm_timer(cb_queue);
179            });
180            log::trace!("TimerFd {fd} disarmed");
181        } else {
182            // Need to arm the timer, value may be absolute or relative.
183            let now = Worker::current_time().unwrap();
184
185            let expire_time = {
186                let base = match flags.contains(TimerSetTimeFlags::TFD_TIMER_ABSTIME) {
187                    true => EmulatedTime::UNIX_EPOCH,
188                    false => now,
189                };
190                // The man page does not specify what happens if the configured time is in the past.
191                // On Linux, the result is an immediate timer expiration.
192                EmulatedTime::max(base + value, now)
193            };
194
195            CallbackQueue::queue_and_run_with_legacy(|cb_queue| {
196                timerfd.borrow_mut().arm_timer(
197                    ctx.objs.host,
198                    expire_time,
199                    interval.is_positive().then_some(interval),
200                    cb_queue,
201                );
202            });
203
204            log::trace!(
205                "TimerFd {fd} armed to expire at {expire_time:?} (in {:?})",
206                expire_time.duration_since(&now)
207            );
208        }
209
210        Ok(())
211    }
212}
213
214/// Checks the clockid; returns `Ok(())` if the clockid is `CLOCK_REALTIME` or
215/// `CLOCK_MONOTONIC`, or the appropriate errno if the clockid is unknown or
216/// unsupported.
217fn check_clockid(clockid: ClockId) -> Result<(), Errno> {
218    if clockid == ClockId::CLOCK_MONOTONIC || clockid == ClockId::CLOCK_REALTIME {
219        return Ok(());
220    }
221
222    warn_once_then_debug!("Unsupported clockid {clockid:?}");
223    Err(Errno::EINVAL)
224}
225
226fn get_cloned_file(ctx: &mut SyscallContext, fd: std::ffi::c_int) -> Result<File, Errno> {
227    // get the descriptor, or return error if it doesn't exist
228    let desc_table = ctx.objs.thread.descriptor_table_borrow(ctx.objs.host);
229    let desc = SyscallHandler::get_descriptor(&desc_table, fd)?;
230
231    // Our TimerFd is a New Rust type, if we get a Legacy C type it must not be a TimerFd.
232    let CompatFile::New(file) = desc.file() else {
233        return Err(Errno::EINVAL);
234    };
235
236    Ok(file.inner_file().clone())
237}