use std::io::Write;
use std::sync::{Arc, Weak};
use atomic_refcell::AtomicRefCell;
use linux_api::errno::Errno;
use linux_api::ioctls::IoctlRequest;
use linux_api::posix_types::kernel_off_t;
use shadow_shim_helper_rs::{
emulated_time::EmulatedTime, simulation_time::SimulationTime, syscall_types::ForeignPtr,
};
use crate::cshadow as c;
use crate::host::descriptor::listener::{StateEventSource, StateListenHandle, StateListenerFilter};
use crate::host::descriptor::{FileMode, FileSignals, FileState, FileStatus};
use crate::host::host::Host;
use crate::host::memory_manager::MemoryManager;
use crate::host::syscall::io::{IoVec, IoVecWriter};
use crate::host::syscall::types::{SyscallError, SyscallResult};
use crate::host::timer::Timer;
use crate::utility::callback_queue::CallbackQueue;
use crate::utility::HostTreePointer;
pub struct TimerFd {
timer: Timer,
event_source: StateEventSource,
status: FileStatus,
state: FileState,
has_open_file: bool,
}
impl TimerFd {
pub fn new(status: FileStatus) -> Arc<AtomicRefCell<Self>> {
Arc::new_cyclic(|weak| {
let weak_cloned = weak.clone();
AtomicRefCell::new(Self {
timer: Timer::new(move |_host| Self::timer_expired(&weak_cloned)),
event_source: StateEventSource::new(),
state: FileState::ACTIVE,
status,
has_open_file: false,
})
})
}
fn timer_expired(timerfd_weak: &Weak<AtomicRefCell<TimerFd>>) {
let Some(timerfd) = timerfd_weak.upgrade() else {
log::trace!("Expired TimerFd no longer exists.");
return;
};
CallbackQueue::queue_and_run_with_legacy(|cb_queue| {
timerfd.borrow_mut().refresh_state(cb_queue);
});
}
fn get_timer_count(&self) -> u64 {
self.timer.expiration_count()
}
pub fn get_timer_remaining(&self) -> Option<SimulationTime> {
self.timer.remaining_time()
}
pub fn get_timer_interval(&self) -> Option<SimulationTime> {
self.timer.expire_interval()
}
pub fn arm_timer(
&mut self,
host: &Host,
expire_time: EmulatedTime,
interval: Option<SimulationTime>,
cb_queue: &mut CallbackQueue,
) {
self.timer.arm(host, expire_time, interval);
self.refresh_state(cb_queue);
}
pub fn disarm_timer(&mut self, cb_queue: &mut CallbackQueue) {
self.timer.disarm();
self.refresh_state(cb_queue);
}
pub fn status(&self) -> FileStatus {
self.status
}
pub fn set_status(&mut self, status: FileStatus) {
self.status = status;
}
pub fn mode(&self) -> FileMode {
FileMode::READ
}
pub fn has_open_file(&self) -> bool {
self.has_open_file
}
pub fn supports_sa_restart(&self) -> bool {
false
}
pub fn set_has_open_file(&mut self, val: bool) {
self.has_open_file = val;
}
pub fn readv(
&mut self,
iovs: &[IoVec],
offset: Option<kernel_off_t>,
_flags: std::ffi::c_int,
mem: &mut MemoryManager,
cb_queue: &mut CallbackQueue,
) -> Result<isize, SyscallError> {
if offset.is_some() {
return Err(Errno::ESPIPE.into());
}
const NUM_BYTES: usize = 8;
let len: usize = iovs.iter().map(|x| x.len).sum();
if len < NUM_BYTES {
log::trace!("Reading from TimerFd requires a buffer of at least {NUM_BYTES} bytes",);
return Err(Errno::EINVAL.into());
}
let expiration_count = self.timer.consume_expiration_count();
if expiration_count == 0 {
log::trace!("TimerFd expiration count is 0 and cannot be read right now");
return Err(Errno::EWOULDBLOCK.into());
}
let mut writer = IoVecWriter::new(iovs, mem);
let to_write: [u8; NUM_BYTES] = expiration_count.to_ne_bytes();
writer.write_all(&to_write)?;
self.refresh_state(cb_queue);
Ok(NUM_BYTES.try_into().unwrap())
}
pub fn writev(
&mut self,
_iovs: &[IoVec],
_offset: Option<kernel_off_t>,
_flags: std::ffi::c_int,
_mem: &mut MemoryManager,
_cb_queue: &mut CallbackQueue,
) -> Result<isize, SyscallError> {
Err(Errno::EINVAL.into())
}
pub fn close(&mut self, cb_queue: &mut CallbackQueue) -> Result<(), SyscallError> {
self.update_state(
FileState::CLOSED | FileState::ACTIVE | FileState::READABLE,
FileState::CLOSED,
FileSignals::empty(),
cb_queue,
);
Ok(())
}
pub fn ioctl(
&mut self,
request: IoctlRequest,
_arg_ptr: ForeignPtr<()>,
_memory_manager: &mut MemoryManager,
) -> SyscallResult {
warn_once_then_debug!("We do not yet handle ioctl request {request:?} on TimerFds");
Err(Errno::EINVAL.into())
}
pub fn stat(&self) -> Result<linux_api::stat::stat, SyscallError> {
warn_once_then_debug!("We do not yet handle stat calls on timerfds");
Err(Errno::EINVAL.into())
}
pub fn add_listener(
&mut self,
monitoring_state: FileState,
monitoring_signals: FileSignals,
filter: StateListenerFilter,
notify_fn: impl Fn(FileState, FileState, FileSignals, &mut CallbackQueue)
+ Send
+ Sync
+ 'static,
) -> StateListenHandle {
self.event_source
.add_listener(monitoring_state, monitoring_signals, filter, notify_fn)
}
pub fn add_legacy_listener(&mut self, ptr: HostTreePointer<c::StatusListener>) {
self.event_source.add_legacy_listener(ptr);
}
pub fn remove_legacy_listener(&mut self, ptr: *mut c::StatusListener) {
self.event_source.remove_legacy_listener(ptr);
}
pub fn state(&self) -> FileState {
self.state
}
fn refresh_state(&mut self, cb_queue: &mut CallbackQueue) {
if self.state.contains(FileState::CLOSED) {
return;
}
let mut new_state = FileState::empty();
new_state.set(FileState::READABLE, self.get_timer_count() > 0);
self.update_state(
FileState::READABLE,
new_state,
FileSignals::empty(),
cb_queue,
);
}
fn update_state(
&mut self,
mask: FileState,
state: FileState,
signals: FileSignals,
cb_queue: &mut CallbackQueue,
) {
let old_state = self.state;
self.state.remove(mask);
self.state.insert(state & mask);
self.handle_state_change(old_state, signals, cb_queue);
}
fn handle_state_change(
&mut self,
old_state: FileState,
signals: FileSignals,
cb_queue: &mut CallbackQueue,
) {
let states_changed = self.state ^ old_state;
if states_changed.is_empty() && signals.is_empty() {
return;
}
self.event_source
.notify_listeners(self.state, states_changed, signals, cb_queue);
}
}