shadow_rs/host/descriptor/
timerfd.rs

1use std::io::Write;
2use std::sync::{Arc, Weak};
3
4use atomic_refcell::AtomicRefCell;
5use linux_api::errno::Errno;
6use linux_api::ioctls::IoctlRequest;
7use linux_api::posix_types::kernel_off_t;
8use shadow_shim_helper_rs::{
9    emulated_time::EmulatedTime, simulation_time::SimulationTime, syscall_types::ForeignPtr,
10};
11
12use crate::cshadow as c;
13use crate::host::descriptor::listener::{StateEventSource, StateListenHandle, StateListenerFilter};
14use crate::host::descriptor::{FileMode, FileSignals, FileState, FileStatus};
15use crate::host::host::Host;
16use crate::host::memory_manager::MemoryManager;
17use crate::host::syscall::io::{IoVec, IoVecWriter};
18use crate::host::syscall::types::{SyscallError, SyscallResult};
19use crate::host::timer::Timer;
20use crate::utility::HostTreePointer;
21use crate::utility::callback_queue::CallbackQueue;
22
23pub struct TimerFd {
24    timer: Timer,
25    event_source: StateEventSource,
26    status: FileStatus,
27    state: FileState,
28    // Should only be used by `OpenFile` to make sure there is only ever one `OpenFile` instance for
29    // this file,
30    has_open_file: bool,
31}
32
33impl TimerFd {
34    /// Creates a new [`TimerFd`] object that internally sets up a [`Timer`] that can be waited on
35    /// with poll, select, and epoll, enabling support for timerfd_create(2).
36    ///
37    /// We wrap the new [`TimerFd`] in an [`Arc<AtomicRefCell>`] because we need to use a weak
38    /// reference to internally support setting up callback functions that reference the [`TimerFd`]
39    /// on timer expiration.
40    pub fn new(status: FileStatus) -> Arc<AtomicRefCell<Self>> {
41        // We need a circular reference here, so that the inner Timer can refer back to the outer
42        // TimerFd when executing a callback that will mutate the TimerFd when the timer expires.
43        Arc::new_cyclic(|weak| {
44            let weak_cloned = weak.clone();
45            AtomicRefCell::new(Self {
46                timer: Timer::new(move |_host| Self::timer_expired(&weak_cloned)),
47                event_source: StateEventSource::new(),
48                state: FileState::ACTIVE,
49                status,
50                has_open_file: false,
51            })
52        })
53    }
54
55    /// Called by the inner [`Timer`] when a timer expiration occurs.
56    fn timer_expired(timerfd_weak: &Weak<AtomicRefCell<TimerFd>>) {
57        let Some(timerfd) = timerfd_weak.upgrade() else {
58            log::trace!("Expired TimerFd no longer exists.");
59            return;
60        };
61
62        // The TimerFd may have become readable now that a timer expired. We use the CallbackQueue
63        // here to make sure that any listeners that need to wake up and handle a readable TimerFd
64        // are not invoked until after we release the borrow.
65        CallbackQueue::queue_and_run_with_legacy(|cb_queue| {
66            timerfd.borrow_mut().refresh_state(cb_queue);
67        });
68    }
69
70    /// Returns the number of expirations that have occured since the timer was last armed.
71    fn get_timer_count(&self) -> u64 {
72        self.timer.expiration_count()
73    }
74
75    /// Returns the relative duration until the next expiration event occurs if the timer is armed,
76    /// and `None` if the timer is disarmed.
77    pub fn get_timer_remaining(&self) -> Option<SimulationTime> {
78        self.timer.remaining_time()
79    }
80
81    /// Returns the relative duration over which the timer has been configured to periodically
82    /// expire, or `None` if the timer is configured to expire only once.
83    pub fn get_timer_interval(&self) -> Option<SimulationTime> {
84        self.timer.expire_interval()
85    }
86
87    /// Arm the timer by setting its expiration time and interval, enabling support for
88    /// timerfd_settime(2). The readable state of the [`TimerFd`] is updated as appropriate.
89    pub fn arm_timer(
90        &mut self,
91        host: &Host,
92        expire_time: EmulatedTime,
93        interval: Option<SimulationTime>,
94        cb_queue: &mut CallbackQueue,
95    ) {
96        // Make sure to update our READABLE status.
97        self.timer.arm(host, expire_time, interval);
98        self.refresh_state(cb_queue);
99    }
100
101    /// Disarm the timer so that it no longer fires expiration events, enabling support for
102    /// timerfd_settime(2). The readable state of the [`TimerFd`] is updated as appropriate.
103    pub fn disarm_timer(&mut self, cb_queue: &mut CallbackQueue) {
104        // Make sure to update our READABLE status.
105        self.timer.disarm();
106        self.refresh_state(cb_queue);
107    }
108
109    pub fn status(&self) -> FileStatus {
110        self.status
111    }
112
113    pub fn set_status(&mut self, status: FileStatus) {
114        self.status = status;
115    }
116
117    pub fn mode(&self) -> FileMode {
118        FileMode::READ
119    }
120
121    pub fn has_open_file(&self) -> bool {
122        self.has_open_file
123    }
124
125    pub fn supports_sa_restart(&self) -> bool {
126        false
127    }
128
129    pub fn set_has_open_file(&mut self, val: bool) {
130        self.has_open_file = val;
131    }
132
133    pub fn readv(
134        &mut self,
135        iovs: &[IoVec],
136        offset: Option<kernel_off_t>,
137        _flags: std::ffi::c_int,
138        mem: &mut MemoryManager,
139        cb_queue: &mut CallbackQueue,
140    ) -> Result<isize, SyscallError> {
141        // TimerFds don't support seeking
142        if offset.is_some() {
143            return Err(Errno::ESPIPE.into());
144        }
145
146        // timerfd_create(2): "read(2) returns an unsigned 8-byte integer containing the number of
147        // expirations that have occurred."
148        const NUM_BYTES: usize = 8;
149
150        let len: usize = iovs.iter().map(|x| x.len).sum();
151
152        // This check doesn't guarantee that we can write all bytes since the stream length is only
153        // a hint.
154        if len < NUM_BYTES {
155            log::trace!("Reading from TimerFd requires a buffer of at least {NUM_BYTES} bytes",);
156            return Err(Errno::EINVAL.into());
157        }
158
159        let expiration_count = self.timer.consume_expiration_count();
160
161        if expiration_count == 0 {
162            log::trace!("TimerFd expiration count is 0 and cannot be read right now");
163            return Err(Errno::EWOULDBLOCK.into());
164        }
165
166        let mut writer = IoVecWriter::new(iovs, mem);
167        let to_write: [u8; NUM_BYTES] = expiration_count.to_ne_bytes();
168        writer.write_all(&to_write)?;
169
170        // We just read the expiration counter and so are not readable anymore.
171        self.refresh_state(cb_queue);
172
173        Ok(NUM_BYTES.try_into().unwrap())
174    }
175
176    pub fn writev(
177        &mut self,
178        _iovs: &[IoVec],
179        _offset: Option<kernel_off_t>,
180        _flags: std::ffi::c_int,
181        _mem: &mut MemoryManager,
182        _cb_queue: &mut CallbackQueue,
183    ) -> Result<isize, SyscallError> {
184        // TimerFds don't support writing.
185        Err(Errno::EINVAL.into())
186    }
187
188    pub fn close(&mut self, cb_queue: &mut CallbackQueue) -> Result<(), SyscallError> {
189        // Set the closed flag and remove the active and readable flags.
190        self.update_state(
191            FileState::CLOSED | FileState::ACTIVE | FileState::READABLE,
192            FileState::CLOSED,
193            FileSignals::empty(),
194            cb_queue,
195        );
196
197        Ok(())
198    }
199
200    pub fn ioctl(
201        &mut self,
202        request: IoctlRequest,
203        _arg_ptr: ForeignPtr<()>,
204        _memory_manager: &mut MemoryManager,
205    ) -> SyscallResult {
206        // The only timerfd-specific ioctl request is for `TFD_IOC_SET_TICKS`, which is available
207        // since Linux 3.17 but only if the kernel was configured with `CONFIG_CHECKPOINT_RESTORE`.
208        // See timerfd_create(2) for more details.
209        warn_once_then_debug!("We do not yet handle ioctl request {request:?} on TimerFds");
210        Err(Errno::EINVAL.into())
211    }
212
213    pub fn stat(&self) -> Result<linux_api::stat::stat, SyscallError> {
214        warn_once_then_debug!("We do not yet handle stat calls on timerfds");
215        Err(Errno::EINVAL.into())
216    }
217
218    pub fn add_listener(
219        &mut self,
220        monitoring_state: FileState,
221        monitoring_signals: FileSignals,
222        filter: StateListenerFilter,
223        notify_fn: impl Fn(FileState, FileState, FileSignals, &mut CallbackQueue)
224        + Send
225        + Sync
226        + 'static,
227    ) -> StateListenHandle {
228        self.event_source
229            .add_listener(monitoring_state, monitoring_signals, filter, notify_fn)
230    }
231
232    pub fn add_legacy_listener(&mut self, ptr: HostTreePointer<c::StatusListener>) {
233        self.event_source.add_legacy_listener(ptr);
234    }
235
236    pub fn remove_legacy_listener(&mut self, ptr: *mut c::StatusListener) {
237        self.event_source.remove_legacy_listener(ptr);
238    }
239
240    pub fn state(&self) -> FileState {
241        self.state
242    }
243
244    fn refresh_state(&mut self, cb_queue: &mut CallbackQueue) {
245        if self.state.contains(FileState::CLOSED) {
246            return;
247        }
248
249        let mut new_state = FileState::empty();
250
251        // Set the descriptor as readable if we have a non-zero expiration count.
252        new_state.set(FileState::READABLE, self.get_timer_count() > 0);
253
254        self.update_state(
255            FileState::READABLE,
256            new_state,
257            FileSignals::empty(),
258            cb_queue,
259        );
260    }
261
262    fn update_state(
263        &mut self,
264        mask: FileState,
265        state: FileState,
266        signals: FileSignals,
267        cb_queue: &mut CallbackQueue,
268    ) {
269        let old_state = self.state;
270
271        // Remove the mask, then copy the masked flags.
272        self.state.remove(mask);
273        self.state.insert(state & mask);
274
275        self.handle_state_change(old_state, signals, cb_queue);
276    }
277
278    fn handle_state_change(
279        &mut self,
280        old_state: FileState,
281        signals: FileSignals,
282        cb_queue: &mut CallbackQueue,
283    ) {
284        let states_changed = self.state ^ old_state;
285
286        // Just return if nothing changed.
287        if states_changed.is_empty() && signals.is_empty() {
288            return;
289        }
290
291        self.event_source
292            .notify_listeners(self.state, states_changed, signals, cb_queue);
293    }
294}