use std::sync::Arc;
use atomic_refcell::AtomicRefCell;
use linux_api::errno::Errno;
use linux_api::fcntl::DescriptorFlags;
use linux_api::time::{itimerspec, ClockId};
use nix::sys::timerfd::{TimerFlags, TimerSetTimeFlags};
use shadow_shim_helper_rs::{
emulated_time::EmulatedTime, simulation_time::SimulationTime, syscall_types::ForeignPtr,
};
use crate::core::worker::Worker;
use crate::host::descriptor::descriptor_table::DescriptorHandle;
use crate::host::descriptor::{
timerfd::TimerFd, CompatFile, Descriptor, File, FileStatus, OpenFile,
};
use crate::host::{
syscall::handler::{SyscallContext, SyscallHandler},
syscall::types::SyscallError,
};
use crate::utility::callback_queue::CallbackQueue;
impl SyscallHandler {
log_syscall!(
timerfd_create,
std::ffi::c_int,
linux_api::time::ClockId,
std::ffi::c_int,
);
pub fn timerfd_create(
ctx: &mut SyscallContext,
clockid: std::ffi::c_int,
flags: std::ffi::c_int,
) -> Result<DescriptorHandle, SyscallError> {
let Ok(clockid) = ClockId::try_from(clockid) else {
log::debug!("Invalid clockid: {clockid}");
return Err(Errno::EINVAL.into());
};
check_clockid(clockid)?;
let Some(flags) = TimerFlags::from_bits(flags) else {
log::debug!("Invalid timerfd_create flags: {flags}");
return Err(Errno::EINVAL.into());
};
let mut file_flags = FileStatus::empty();
let mut desc_flags = DescriptorFlags::empty();
if flags.contains(TimerFlags::TFD_NONBLOCK) {
file_flags.insert(FileStatus::NONBLOCK);
}
if flags.contains(TimerFlags::TFD_CLOEXEC) {
desc_flags.insert(DescriptorFlags::FD_CLOEXEC);
}
let file = TimerFd::new(file_flags);
let mut desc = Descriptor::new(CompatFile::New(OpenFile::new(File::TimerFd(file))));
desc.set_flags(desc_flags);
let fd = ctx
.objs
.thread
.descriptor_table_borrow_mut(ctx.objs.host)
.register_descriptor(desc)
.or(Err(Errno::ENFILE))?;
log::trace!("timerfd_create() returning fd {fd}");
Ok(fd)
}
log_syscall!(
timerfd_gettime,
std::ffi::c_int,
std::ffi::c_int,
*const std::ffi::c_void,
);
pub fn timerfd_gettime(
ctx: &mut SyscallContext,
fd: std::ffi::c_int,
curr_value_ptr: ForeignPtr<linux_api::time::itimerspec>,
) -> Result<(), SyscallError> {
let file = get_cloned_file(ctx, fd)?;
let File::TimerFd(ref timerfd) = file else {
return Err(Errno::EINVAL.into());
};
Self::timerfd_gettime_helper(ctx, timerfd, curr_value_ptr)?;
Ok(())
}
fn timerfd_gettime_helper(
ctx: &mut SyscallContext,
timerfd: &Arc<AtomicRefCell<TimerFd>>,
value_ptr: ForeignPtr<linux_api::time::itimerspec>,
) -> Result<(), Errno> {
let (remaining, interval) = {
let borrowed = timerfd.borrow();
let remaining = borrowed
.get_timer_remaining()
.unwrap_or(SimulationTime::ZERO);
let interval = borrowed
.get_timer_interval()
.unwrap_or(SimulationTime::ZERO);
(remaining, interval)
};
let result = itimerspec {
it_value: remaining.try_into().unwrap(),
it_interval: interval.try_into().unwrap(),
};
ctx.objs
.process
.memory_borrow_mut()
.write(value_ptr, &result)?;
Ok(())
}
log_syscall!(
timerfd_settime,
std::ffi::c_int,
std::ffi::c_int,
std::ffi::c_int,
*const std::ffi::c_void,
*const std::ffi::c_void,
);
pub fn timerfd_settime(
ctx: &mut SyscallContext,
fd: std::ffi::c_int,
flags: std::ffi::c_int,
new_value_ptr: ForeignPtr<linux_api::time::itimerspec>,
old_value_ptr: ForeignPtr<linux_api::time::itimerspec>,
) -> Result<(), SyscallError> {
let Some(flags) = TimerSetTimeFlags::from_bits(flags) else {
log::debug!("Invalid timerfd_settime flags: {flags}");
return Err(Errno::EINVAL.into());
};
let file = get_cloned_file(ctx, fd)?;
let File::TimerFd(ref timerfd) = file else {
return Err(Errno::EINVAL.into());
};
let new_value = ctx.objs.process.memory_borrow().read(new_value_ptr)?;
let value = SimulationTime::try_from(new_value.it_value).or(Err(Errno::EINVAL))?;
let interval = SimulationTime::try_from(new_value.it_interval).or(Err(Errno::EINVAL))?;
if !old_value_ptr.is_null() {
Self::timerfd_gettime_helper(ctx, timerfd, old_value_ptr)?;
}
if value.is_zero() {
CallbackQueue::queue_and_run_with_legacy(|cb_queue| {
timerfd.borrow_mut().disarm_timer(cb_queue);
});
log::trace!("TimerFd {fd} disarmed");
} else {
let now = Worker::current_time().unwrap();
let expire_time = {
let base = match flags.contains(TimerSetTimeFlags::TFD_TIMER_ABSTIME) {
true => EmulatedTime::UNIX_EPOCH,
false => now,
};
EmulatedTime::max(base + value, now)
};
CallbackQueue::queue_and_run_with_legacy(|cb_queue| {
timerfd.borrow_mut().arm_timer(
ctx.objs.host,
expire_time,
interval.is_positive().then_some(interval),
cb_queue,
);
});
log::trace!(
"TimerFd {fd} armed to expire at {expire_time:?} (in {:?})",
expire_time.duration_since(&now)
);
}
Ok(())
}
}
fn check_clockid(clockid: ClockId) -> Result<(), Errno> {
if clockid == ClockId::CLOCK_MONOTONIC || clockid == ClockId::CLOCK_REALTIME {
return Ok(());
}
warn_once_then_debug!("Unsupported clockid {clockid:?}");
Err(Errno::EINVAL)
}
fn get_cloned_file(ctx: &mut SyscallContext, fd: std::ffi::c_int) -> Result<File, Errno> {
let desc_table = ctx.objs.thread.descriptor_table_borrow(ctx.objs.host);
let desc = SyscallHandler::get_descriptor(&desc_table, fd)?;
let CompatFile::New(file) = desc.file() else {
return Err(Errno::EINVAL);
};
Ok(file.inner_file().clone())
}