1use air_sys_types::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd};
10use core::num::NonZeroI32;
11use core::time::Duration;
12
13use air_sys_types::{
14 Clock, Errno, Instant, SleepDeadline, SleepError, TimerFdFlags, TimerFdSpecification,
15 TimerSetFlags,
16};
17
18#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
19compile_error!("air-sys-syscall::time supporte uniquement x86_64 et aarch64 (ADR-014).");
20
21const NANOS_PER_SECOND_U32: u32 = 1_000_000_000;
22const TIMER_ABSTIME: i32 = 1;
23
24#[repr(C)]
25#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
26struct KernelTimespec {
27 tv_sec: i64,
28 tv_nsec: i64,
29}
30
31#[repr(C)]
32#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
33struct KernelItimerspec {
34 it_interval: KernelTimespec,
35 it_value: KernelTimespec,
36}
37
38#[derive(Debug)]
40pub struct TimerFd(OwnedFd);
41
42impl TimerFd {
43 #[must_use]
45 #[inline]
46 pub const fn from_owned_fd(fd: OwnedFd) -> Self {
47 Self(fd)
48 }
49
50 #[must_use]
52 pub fn as_fd(&self) -> BorrowedFd<'_> {
53 use air_sys_types::fd::AsFd;
54 self.0.as_fd()
55 }
56
57 #[must_use]
59 pub fn into_fd(self) -> OwnedFd {
60 self.0
61 }
62
63 pub fn arm(
71 &self,
72 spec: &TimerFdSpecification,
73 flags: TimerSetFlags,
74 ) -> Result<TimerFdSpecification, Errno> {
75 let new_value = kernel_itimerspec_from_spec(*spec)?;
76 let mut old_value = KernelItimerspec::default();
77 let ret = unsafe {
81 raw_syscall_timerfd_settime(
82 self.0.as_raw_fd(),
83 flags.bits(),
84 (&new_value as *const KernelItimerspec) as u64,
85 (&mut old_value as *mut KernelItimerspec) as u64,
86 )
87 };
88 if ret < 0 {
89 return Err(errno_from_negative_syscall_ret(ret));
90 }
91 kernel_itimerspec_to_spec(old_value)
92 }
93
94 pub fn disarm(&self) -> Result<TimerFdSpecification, Errno> {
102 self.arm(
103 &TimerFdSpecification {
104 initial: Duration::ZERO,
105 interval: Duration::ZERO,
106 },
107 TimerSetFlags::empty(),
108 )
109 }
110
111 pub fn current(&self) -> Result<TimerFdSpecification, Errno> {
119 let mut current = KernelItimerspec::default();
120 let ret = unsafe {
124 raw_syscall_timerfd_gettime(
125 self.0.as_raw_fd(),
126 (&mut current as *mut KernelItimerspec) as u64,
127 )
128 };
129 if ret < 0 {
130 return Err(errno_from_negative_syscall_ret(ret));
131 }
132 kernel_itimerspec_to_spec(current)
133 }
134
135 pub fn read(&self) -> Result<u64, Errno> {
149 let mut count = 0_u64;
150 let ret = unsafe {
154 raw_syscall_read(
155 self.0.as_raw_fd(),
156 (&mut count as *mut u64) as u64,
157 u64::try_from(core::mem::size_of::<u64>()).expect("u64 size fits in u64"),
158 )
159 };
160 if ret < 0 {
161 return Err(errno_from_negative_syscall_ret(ret));
162 }
163 debug_assert_eq!(ret, 8);
164 Ok(count)
165 }
166}
167
168pub fn clock_gettime(clock: Clock) -> Result<Instant, Errno> {
175 let ts = syscall_clock_gettime(clock.as_raw())?;
176 kernel_timespec_to_instant(clock, ts)
177}
178
179pub fn clock_settime(clock: Clock, instant: Instant) -> Result<(), Errno> {
188 if instant.clock() != clock {
189 return Err(Errno::EINVAL);
190 }
191 let ts = kernel_timespec_from_instant(instant);
192 let ret =
208 unsafe { raw_syscall_clock_settime(clock.as_raw(), (&ts as *const KernelTimespec) as u64) };
209 if ret < 0 {
210 return Err(errno_from_negative_syscall_ret(ret));
211 }
212 Ok(())
213}
214
215pub fn clock_getres(clock: Clock) -> Result<Duration, Errno> {
221 let ts = syscall_clock_getres(clock.as_raw())?;
222 kernel_timespec_to_duration(ts)
223}
224
225pub fn clock_nanosleep(clock: Clock, deadline: SleepDeadline) -> Result<(), SleepError> {
227 let mut rem = KernelTimespec::default();
228 let (flags, req, rem_ptr) = match deadline {
229 SleepDeadline::Relative(duration) => {
230 let req = match kernel_timespec_from_duration(duration) {
231 Ok(req) => req,
232 Err(err) => return Err(SleepError::Other(err)),
233 };
234 (0_i32, req, (&mut rem as *mut KernelTimespec) as u64)
235 }
236 SleepDeadline::AbsoluteInstant(instant) => {
237 if instant.clock() != clock {
238 return Err(SleepError::Other(Errno::EINVAL));
239 }
240 (TIMER_ABSTIME, kernel_timespec_from_instant(instant), 0_u64)
241 }
242 };
243
244 let ret = unsafe {
248 raw_syscall_clock_nanosleep(
249 clock.as_raw(),
250 flags,
251 (&req as *const KernelTimespec) as u64,
252 rem_ptr,
253 )
254 };
255 decode_clock_nanosleep_result(deadline, rem, ret)
256}
257
258pub fn timerfd_create(clock: Clock, flags: TimerFdFlags) -> Result<TimerFd, Errno> {
272 let kernel_flags = flags.bits() | TimerFdFlags::CLOEXEC.bits();
273 let owned = syscall_timerfd_create(clock.as_raw(), kernel_flags)?;
274 Ok(TimerFd::from_owned_fd(owned))
275}
276
277fn syscall_clock_gettime(clock: i32) -> Result<KernelTimespec, Errno> {
278 let mut ts = KernelTimespec::default();
279 let ret = unsafe { raw_syscall_clock_gettime(clock, (&mut ts as *mut KernelTimespec) as u64) };
282 if ret < 0 {
283 return Err(errno_from_negative_syscall_ret(ret));
284 }
285 Ok(ts)
286}
287
288fn syscall_clock_getres(clock: i32) -> Result<KernelTimespec, Errno> {
289 let mut ts = KernelTimespec::default();
290 let ret = unsafe { raw_syscall_clock_getres(clock, (&mut ts as *mut KernelTimespec) as u64) };
293 if ret < 0 {
294 return Err(errno_from_negative_syscall_ret(ret));
295 }
296 Ok(ts)
297}
298
299fn syscall_timerfd_create(clock: i32, flags: i32) -> Result<OwnedFd, Errno> {
300 let ret = unsafe { raw_syscall_timerfd_create(clock, flags) };
303 if ret < 0 {
304 return Err(errno_from_negative_syscall_ret(ret));
305 }
306 #[allow(clippy::cast_possible_truncation)]
307 let fd = ret as i32;
308 let owned = unsafe { OwnedFd::from_raw_fd(fd) };
311 Ok(owned)
312}
313
314fn decode_clock_nanosleep_result(
315 deadline: SleepDeadline,
316 rem: KernelTimespec,
317 ret: i64,
318) -> Result<(), SleepError> {
319 if ret == 0 {
320 return Ok(());
321 }
322 let errno = errno_from_negative_syscall_ret(ret);
323 if errno == Errno::EINTR
324 && let SleepDeadline::Relative(_) = deadline
325 {
326 let remaining = kernel_timespec_to_duration(rem).map_err(SleepError::Other)?;
327 return Err(SleepError::Interrupted { remaining });
328 }
329 Err(SleepError::Other(errno))
330}
331
332fn kernel_timespec_from_duration(duration: Duration) -> Result<KernelTimespec, Errno> {
333 let secs = i64::try_from(duration.as_secs()).map_err(|_| Errno::EINVAL)?;
334 Ok(KernelTimespec {
335 tv_sec: secs,
336 tv_nsec: i64::from(duration.subsec_nanos()),
337 })
338}
339
340fn kernel_timespec_to_duration(ts: KernelTimespec) -> Result<Duration, Errno> {
341 if ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= i64::from(NANOS_PER_SECOND_U32) {
342 return Err(Errno::EINVAL);
343 }
344 let secs = u64::try_from(ts.tv_sec).map_err(|_| Errno::EINVAL)?;
345 let nanos = u32::try_from(ts.tv_nsec).map_err(|_| Errno::EINVAL)?;
346 Ok(Duration::new(secs, nanos))
347}
348
349fn kernel_timespec_from_instant(instant: Instant) -> KernelTimespec {
350 KernelTimespec {
351 tv_sec: instant.seconds(),
352 tv_nsec: i64::from(instant.nanoseconds()),
353 }
354}
355
356fn kernel_timespec_to_instant(clock: Clock, ts: KernelTimespec) -> Result<Instant, Errno> {
357 if ts.tv_nsec < 0 || ts.tv_nsec >= i64::from(NANOS_PER_SECOND_U32) {
358 return Err(Errno::EINVAL);
359 }
360 let nanos = u32::try_from(ts.tv_nsec).map_err(|_| Errno::EINVAL)?;
361 Instant::try_new(clock, ts.tv_sec, nanos).ok_or(Errno::EINVAL)
362}
363
364fn kernel_itimerspec_from_spec(spec: TimerFdSpecification) -> Result<KernelItimerspec, Errno> {
365 Ok(KernelItimerspec {
366 it_interval: kernel_timespec_from_duration(spec.interval)?,
367 it_value: kernel_timespec_from_duration(spec.initial)?,
368 })
369}
370
371fn kernel_itimerspec_to_spec(spec: KernelItimerspec) -> Result<TimerFdSpecification, Errno> {
372 Ok(TimerFdSpecification {
373 interval: kernel_timespec_to_duration(spec.it_interval)?,
374 initial: kernel_timespec_to_duration(spec.it_value)?,
375 })
376}
377
378fn errno_from_negative_syscall_ret(ret: i64) -> Errno {
379 debug_assert!(ret < 0 && ret > -4096);
380 #[allow(clippy::cast_possible_truncation)]
381 let raw = ret.wrapping_neg() as i32;
382 let nz = NonZeroI32::new(raw).expect("errno strictement positif par construction");
383 Errno::from_nonzero(nz)
384}
385
386#[cfg(target_arch = "x86_64")]
387#[inline]
388unsafe fn raw_syscall_clock_gettime(clock: i32, ts: u64) -> i64 {
389 let ret: i64;
390 unsafe {
394 core::arch::asm!(
395 "syscall",
396 in("rax") 228_i64,
397 in("rdi") i64::from(clock),
398 in("rsi") ts,
399 lateout("rax") ret,
400 lateout("rcx") _,
401 lateout("r11") _,
402 options(nostack, preserves_flags),
403 );
404 }
405 ret
406}
407
408#[cfg(target_arch = "x86_64")]
409#[inline]
410unsafe fn raw_syscall_clock_settime(clock: i32, ts: u64) -> i64 {
411 let ret: i64;
412 unsafe {
416 core::arch::asm!(
417 "syscall",
418 in("rax") 227_i64,
419 in("rdi") i64::from(clock),
420 in("rsi") ts,
421 lateout("rax") ret,
422 lateout("rcx") _,
423 lateout("r11") _,
424 options(nostack, preserves_flags, readonly),
425 );
426 }
427 ret
428}
429
430#[cfg(target_arch = "x86_64")]
431#[inline]
432unsafe fn raw_syscall_clock_getres(clock: i32, ts: u64) -> i64 {
433 let ret: i64;
434 unsafe {
438 core::arch::asm!(
439 "syscall",
440 in("rax") 229_i64,
441 in("rdi") i64::from(clock),
442 in("rsi") ts,
443 lateout("rax") ret,
444 lateout("rcx") _,
445 lateout("r11") _,
446 options(nostack, preserves_flags),
447 );
448 }
449 ret
450}
451
452#[cfg(target_arch = "x86_64")]
453#[inline]
454unsafe fn raw_syscall_clock_nanosleep(clock: i32, flags: i32, req: u64, rem: u64) -> i64 {
455 let ret: i64;
456 unsafe {
462 core::arch::asm!(
463 "syscall",
464 in("rax") 230_i64,
465 in("rdi") i64::from(clock),
466 in("rsi") i64::from(flags),
467 in("rdx") req,
468 in("r10") rem,
469 lateout("rax") ret,
470 lateout("rcx") _,
471 lateout("r11") _,
472 options(nostack, preserves_flags),
473 );
474 }
475 ret
476}
477
478#[cfg(target_arch = "x86_64")]
479#[inline]
480unsafe fn raw_syscall_timerfd_create(clock: i32, flags: i32) -> i64 {
481 let ret: i64;
482 unsafe {
486 core::arch::asm!(
487 "syscall",
488 in("rax") 283_i64,
489 in("rdi") i64::from(clock),
490 in("rsi") i64::from(flags),
491 lateout("rax") ret,
492 lateout("rcx") _,
493 lateout("r11") _,
494 options(nostack, preserves_flags, readonly),
495 );
496 }
497 ret
498}
499
500#[cfg(target_arch = "x86_64")]
501#[inline]
502unsafe fn raw_syscall_timerfd_settime(fd: i32, flags: i32, new_value: u64, old_value: u64) -> i64 {
503 let ret: i64;
504 unsafe {
510 core::arch::asm!(
511 "syscall",
512 in("rax") 286_i64,
513 in("rdi") i64::from(fd),
514 in("rsi") i64::from(flags),
515 in("rdx") new_value,
516 in("r10") old_value,
517 lateout("rax") ret,
518 lateout("rcx") _,
519 lateout("r11") _,
520 options(nostack, preserves_flags),
521 );
522 }
523 ret
524}
525
526#[cfg(target_arch = "x86_64")]
527#[inline]
528unsafe fn raw_syscall_timerfd_gettime(fd: i32, curr_value: u64) -> i64 {
529 let ret: i64;
530 unsafe {
534 core::arch::asm!(
535 "syscall",
536 in("rax") 287_i64,
537 in("rdi") i64::from(fd),
538 in("rsi") curr_value,
539 lateout("rax") ret,
540 lateout("rcx") _,
541 lateout("r11") _,
542 options(nostack, preserves_flags),
543 );
544 }
545 ret
546}
547
548#[cfg(target_arch = "x86_64")]
549#[inline]
550unsafe fn raw_syscall_read(fd: i32, buffer: u64, count: u64) -> i64 {
551 let ret: i64;
552 unsafe {
556 core::arch::asm!(
557 "syscall",
558 in("rax") 0_i64,
559 in("rdi") i64::from(fd),
560 in("rsi") buffer,
561 in("rdx") count,
562 lateout("rax") ret,
563 lateout("rcx") _,
564 lateout("r11") _,
565 options(nostack, preserves_flags),
566 );
567 }
568 ret
569}
570
571#[cfg(target_arch = "aarch64")]
572#[inline]
573unsafe fn raw_syscall_clock_gettime(clock: i32, ts: u64) -> i64 {
574 let ret: i64;
575 unsafe {
579 core::arch::asm!(
580 "svc 0",
581 in("x8") 113_i64,
582 inout("x0") i64::from(clock) => ret,
583 in("x1") ts,
584 options(nostack, preserves_flags),
585 );
586 }
587 ret
588}
589
590#[cfg(target_arch = "aarch64")]
591#[inline]
592unsafe fn raw_syscall_clock_settime(clock: i32, ts: u64) -> i64 {
593 let ret: i64;
594 unsafe {
598 core::arch::asm!(
599 "svc 0",
600 in("x8") 112_i64,
601 inout("x0") i64::from(clock) => ret,
602 in("x1") ts,
603 options(nostack, preserves_flags, readonly),
604 );
605 }
606 ret
607}
608
609#[cfg(target_arch = "aarch64")]
610#[inline]
611unsafe fn raw_syscall_clock_getres(clock: i32, ts: u64) -> i64 {
612 let ret: i64;
613 unsafe {
617 core::arch::asm!(
618 "svc 0",
619 in("x8") 114_i64,
620 inout("x0") i64::from(clock) => ret,
621 in("x1") ts,
622 options(nostack, preserves_flags),
623 );
624 }
625 ret
626}
627
628#[cfg(target_arch = "aarch64")]
629#[inline]
630unsafe fn raw_syscall_clock_nanosleep(clock: i32, flags: i32, req: u64, rem: u64) -> i64 {
631 let ret: i64;
632 unsafe {
638 core::arch::asm!(
639 "svc 0",
640 in("x8") 115_i64,
641 inout("x0") i64::from(clock) => ret,
642 in("x1") i64::from(flags),
643 in("x2") req,
644 in("x3") rem,
645 options(nostack, preserves_flags),
646 );
647 }
648 ret
649}
650
651#[cfg(target_arch = "aarch64")]
652#[inline]
653unsafe fn raw_syscall_timerfd_create(clock: i32, flags: i32) -> i64 {
654 let ret: i64;
655 unsafe {
659 core::arch::asm!(
660 "svc 0",
661 in("x8") 85_i64,
662 inout("x0") i64::from(clock) => ret,
663 in("x1") i64::from(flags),
664 options(nostack, preserves_flags, readonly),
665 );
666 }
667 ret
668}
669
670#[cfg(target_arch = "aarch64")]
671#[inline]
672unsafe fn raw_syscall_timerfd_settime(fd: i32, flags: i32, new_value: u64, old_value: u64) -> i64 {
673 let ret: i64;
674 unsafe {
680 core::arch::asm!(
681 "svc 0",
682 in("x8") 86_i64,
683 inout("x0") i64::from(fd) => ret,
684 in("x1") i64::from(flags),
685 in("x2") new_value,
686 in("x3") old_value,
687 options(nostack, preserves_flags),
688 );
689 }
690 ret
691}
692
693#[cfg(target_arch = "aarch64")]
694#[inline]
695unsafe fn raw_syscall_timerfd_gettime(fd: i32, curr_value: u64) -> i64 {
696 let ret: i64;
697 unsafe {
701 core::arch::asm!(
702 "svc 0",
703 in("x8") 87_i64,
704 inout("x0") i64::from(fd) => ret,
705 in("x1") curr_value,
706 options(nostack, preserves_flags),
707 );
708 }
709 ret
710}
711
712#[cfg(target_arch = "aarch64")]
713#[inline]
714unsafe fn raw_syscall_read(fd: i32, buffer: u64, count: u64) -> i64 {
715 let ret: i64;
716 unsafe {
720 core::arch::asm!(
721 "svc 0",
722 in("x8") 63_i64,
723 inout("x0") i64::from(fd) => ret,
724 in("x1") buffer,
725 in("x2") count,
726 options(nostack, preserves_flags),
727 );
728 }
729 ret
730}
731
732#[cfg(test)]
733mod tests {
734 use super::*;
735 use crate::process::{getpid, gettid};
736 use crate::signal::tgkill;
737 use crate::test_support::sown;
738 use air_sys_types::Signal;
739 use air_sys_types::fd::{AsFd, AsRawFd};
740 use std::os::unix::net::UnixStream;
741 use std::thread;
742
743 #[test]
744 fn clock_gettime_monotonic_is_non_decreasing() {
745 let t1 = clock_gettime(Clock::Monotonic).expect("clock_gettime monotonic doit réussir");
746 let t2 = clock_gettime(Clock::Monotonic).expect("clock_gettime monotonic doit réussir");
747 assert!(t2.saturating_duration_since(t1) <= Duration::from_secs(1));
748 }
749
750 #[test]
751 fn clock_gettime_realtime_is_positive() {
752 let t = clock_gettime(Clock::Realtime).expect("clock_gettime Realtime");
753 assert!(t.seconds() > 0);
755 }
756
757 #[test]
758 fn clock_gettime_process_cpu_succeeds() {
759 let _ = clock_gettime(Clock::ProcessCpuTime).expect("clock_gettime ProcessCpuTime");
760 }
761
762 #[test]
763 fn clock_getres_realtime_is_reasonable() {
764 let res = clock_getres(Clock::Realtime).expect("clock_getres Realtime");
765 assert!(res < Duration::from_secs(1));
766 }
767
768 #[test]
769 fn clock_getres_monotonic_is_reasonable() {
770 let res = clock_getres(Clock::Monotonic).expect("clock_getres doit réussir");
771 assert!(res < Duration::from_secs(1));
772 }
773
774 #[test]
775 fn clock_nanosleep_relative_waits_at_least_requested_duration() {
776 let start = std::time::Instant::now();
777 clock_nanosleep(
778 Clock::Monotonic,
779 SleepDeadline::Relative(Duration::from_millis(20)),
780 )
781 .expect("clock_nanosleep relatif doit réussir");
782 assert!(start.elapsed() >= Duration::from_millis(15));
783 }
784
785 #[test]
786 fn clock_nanosleep_absolute_rejects_cross_clock_deadline() {
787 let deadline = Instant::try_new(Clock::Realtime, 1, 0).expect("valid instant");
788 let err = clock_nanosleep(Clock::Monotonic, SleepDeadline::AbsoluteInstant(deadline))
789 .expect_err("cross-clock deadline must be rejected");
790 assert_eq!(err, SleepError::Other(Errno::EINVAL));
791 }
792
793 #[test]
794 fn clock_nanosleep_relative_rejects_duration_overflow() {
795 let err = clock_nanosleep(
796 Clock::Monotonic,
797 SleepDeadline::Relative(Duration::from_secs(u64::MAX)),
798 )
799 .expect_err("overflowing duration must be rejected");
800 assert_eq!(err, SleepError::Other(Errno::EINVAL));
801 }
802
803 #[test]
804 fn clock_nanosleep_absolute_waits_until_deadline() {
805 let start = clock_gettime(Clock::Monotonic).expect("clock_gettime monotonic");
806 let target = Duration::from_millis(15);
807 let total_nanos = u64::from(start.nanoseconds()) + u64::from(target.subsec_nanos());
808 let carry_secs = total_nanos / 1_000_000_000;
809 let nanos =
810 u32::try_from(total_nanos % 1_000_000_000).expect("normalized nanos fit in u32");
811 let deadline = Instant::try_new(
812 Clock::Monotonic,
813 start.seconds()
814 + i64::try_from(target.as_secs()).expect("short duration seconds fit in i64")
815 + i64::try_from(carry_secs).expect("carry fits in i64"),
816 nanos,
817 )
818 .expect("normalized absolute deadline");
819 let wall_start = std::time::Instant::now();
820 clock_nanosleep(Clock::Monotonic, SleepDeadline::AbsoluteInstant(deadline))
821 .expect("absolute sleep must succeed");
822 assert!(wall_start.elapsed() >= Duration::from_millis(10));
823 }
824
825 #[test]
826 fn timerfd_nonblocking_read_without_expiration_returns_eagain() {
827 let tfd = timerfd_create(Clock::Monotonic, TimerFdFlags::NONBLOCK)
828 .expect("timerfd_create doit réussir");
829 let err = tfd
830 .read()
831 .expect_err("read sans expiration doit échouer en EAGAIN");
832 assert_eq!(err, Errno::EAGAIN);
833 }
834
835 #[test]
836 fn timerfd_one_shot_arm_then_read_returns_one_expiration() {
837 let tfd = timerfd_create(Clock::Monotonic, TimerFdFlags::empty())
838 .expect("timerfd_create doit réussir");
839 let previous = tfd
840 .arm(
841 &TimerFdSpecification {
842 initial: Duration::from_millis(10),
843 interval: Duration::ZERO,
844 },
845 TimerSetFlags::empty(),
846 )
847 .expect("arm doit réussir");
848 assert_eq!(previous.initial, Duration::ZERO);
849 assert_eq!(previous.interval, Duration::ZERO);
850 let expirations = tfd.read().expect("read doit réussir après expiration");
851 assert_eq!(expirations, 1);
852 }
853
854 #[test]
855 fn timerfd_current_and_disarm_roundtrip() {
856 let tfd = timerfd_create(Clock::Monotonic, TimerFdFlags::empty())
857 .expect("timerfd_create doit réussir");
858 let spec = TimerFdSpecification {
859 initial: Duration::from_millis(50),
860 interval: Duration::from_millis(20),
861 };
862 let _ = tfd
863 .arm(&spec, TimerSetFlags::empty())
864 .expect("arm doit réussir");
865 let current = tfd.current().expect("current doit réussir");
866 assert_eq!(current.interval, Duration::from_millis(20));
867 assert!(current.initial <= Duration::from_millis(50));
868 let previous = tfd.disarm().expect("disarm doit réussir");
869 assert_eq!(previous.interval, Duration::from_millis(20));
870 let now = tfd.current().expect("current disarmed doit réussir");
871 assert_eq!(now.initial, Duration::ZERO);
872 assert_eq!(now.interval, Duration::ZERO);
873 }
874
875 #[test]
876 fn errno_from_negative_syscall_ret_maps_eagain() {
877 assert_eq!(errno_from_negative_syscall_ret(-11), Errno::EAGAIN);
878 }
879
880 #[test]
881 fn timerfd_fd_accessors_roundtrip() {
882 let tfd = timerfd_create(Clock::Monotonic, TimerFdFlags::NONBLOCK)
883 .expect("timerfd_create doit réussir");
884 let borrowed_raw = tfd.as_fd().as_raw_fd();
885 let borrowed_raw_via_inner = tfd.as_fd().as_fd().as_raw_fd();
886 assert_eq!(borrowed_raw, borrowed_raw_via_inner);
887
888 let owned = tfd.into_fd();
889 assert_eq!(borrowed_raw, owned.as_raw_fd());
890 }
891
892 #[test]
893 fn timerfd_methods_reject_non_timerfd_fd() {
894 let (stream, _peer) = UnixStream::pair().expect("unix stream pair");
895 let owned: OwnedFd = sown(stream);
896 let timer = TimerFd::from_owned_fd(owned);
897
898 assert!(timer.current().is_err());
899 assert!(
900 timer
901 .arm(
902 &TimerFdSpecification {
903 initial: Duration::from_millis(1),
904 interval: Duration::ZERO,
905 },
906 TimerSetFlags::empty(),
907 )
908 .is_err()
909 );
910 }
911
912 #[test]
913 fn clock_settime_rejects_cross_clock_instant() {
914 let instant = Instant::try_new(Clock::Realtime, 1, 0).expect("valid instant");
915 let err = clock_settime(Clock::Monotonic, instant)
916 .expect_err("cross-clock instant must be rejected");
917 assert_eq!(err, Errno::EINVAL);
918 }
919
920 #[test]
921 fn clock_settime_realtime_current_time_is_ok_or_eperm() {
922 let now = clock_gettime(Clock::Realtime).expect("clock_gettime realtime");
923 let result = clock_settime(Clock::Realtime, now);
924 assert!(matches!(result, Ok(()) | Err(Errno::EPERM)));
925 }
926
927 #[test]
928 fn raw_clock_helpers_reject_invalid_clock() {
929 let ts = KernelTimespec {
930 tv_sec: 1,
931 tv_nsec: 0,
932 };
933
934 assert_eq!(syscall_clock_gettime(-1), Err(Errno::EINVAL));
935 assert_eq!(syscall_clock_getres(-1), Err(Errno::EINVAL));
936 let settime_ret =
940 unsafe { raw_syscall_clock_settime(-1, (&ts as *const KernelTimespec) as u64) };
941 assert_eq!(errno_from_negative_syscall_ret(settime_ret), Errno::EINVAL);
942 assert!(matches!(
943 syscall_timerfd_create(-1, TimerFdFlags::CLOEXEC.bits()),
944 Err(Errno::EINVAL)
945 ));
946 }
947
948 #[test]
949 fn decode_clock_nanosleep_result_covers_eintr_paths() {
950 let interrupted = decode_clock_nanosleep_result(
951 SleepDeadline::Relative(Duration::from_secs(1)),
952 KernelTimespec {
953 tv_sec: 0,
954 tv_nsec: 500_000_000,
955 },
956 -i64::from(Errno::EINTR.as_raw()),
957 )
958 .expect_err("relative EINTR must map to Interrupted");
959 assert_eq!(
960 interrupted,
961 SleepError::Interrupted {
962 remaining: Duration::from_millis(500)
963 }
964 );
965
966 let absolute = decode_clock_nanosleep_result(
967 SleepDeadline::AbsoluteInstant(
968 Instant::try_new(Clock::Monotonic, 1, 0).expect("valid instant"),
969 ),
970 KernelTimespec::default(),
971 -i64::from(Errno::EINTR.as_raw()),
972 )
973 .expect_err("absolute EINTR stays as Other");
974 assert_eq!(absolute, SleepError::Other(Errno::EINTR));
975
976 let other = decode_clock_nanosleep_result(
979 SleepDeadline::Relative(Duration::from_secs(1)),
980 KernelTimespec::default(),
981 -i64::from(Errno::EINVAL.as_raw()),
982 )
983 .expect_err("non-EINTR error stays as Other");
984 assert_eq!(other, SleepError::Other(Errno::EINVAL));
985 }
986
987 #[test]
990 #[should_panic(expected = "ret < 0")]
991 fn errno_from_negative_syscall_ret_panics_on_non_negative() {
992 let _ = errno_from_negative_syscall_ret(0);
993 }
994
995 #[test]
996 #[should_panic(expected = "ret < 0")]
997 fn errno_from_negative_syscall_ret_panics_below_errno_range() {
998 let _ = errno_from_negative_syscall_ret(-5000);
999 }
1000
1001 #[test]
1002 fn decode_clock_nanosleep_result_rejects_invalid_remaining_timespec() {
1003 let err = decode_clock_nanosleep_result(
1004 SleepDeadline::Relative(Duration::from_secs(1)),
1005 KernelTimespec {
1006 tv_sec: -1,
1007 tv_nsec: 0,
1008 },
1009 -i64::from(Errno::EINTR.as_raw()),
1010 )
1011 .expect_err("invalid remainder must map to EINVAL");
1012 assert_eq!(err, SleepError::Other(Errno::EINVAL));
1013 }
1014
1015 #[test]
1016 fn helper_conversions_validate_bounds() {
1017 assert_eq!(
1018 kernel_timespec_from_duration(Duration::from_secs(u64::MAX)),
1019 Err(Errno::EINVAL)
1020 );
1021 assert_eq!(
1022 kernel_timespec_to_duration(KernelTimespec {
1023 tv_sec: -1,
1024 tv_nsec: 0,
1025 }),
1026 Err(Errno::EINVAL)
1027 );
1028 assert_eq!(
1029 kernel_timespec_to_duration(KernelTimespec {
1030 tv_sec: 0,
1031 tv_nsec: 1_000_000_000,
1032 }),
1033 Err(Errno::EINVAL)
1034 );
1035 assert_eq!(
1038 kernel_timespec_to_duration(KernelTimespec {
1039 tv_sec: 0,
1040 tv_nsec: -1,
1041 }),
1042 Err(Errno::EINVAL)
1043 );
1044 assert_eq!(
1045 kernel_timespec_to_instant(
1046 Clock::Monotonic,
1047 KernelTimespec {
1048 tv_sec: 0,
1049 tv_nsec: 1_000_000_000,
1050 },
1051 ),
1052 Err(Errno::EINVAL)
1053 );
1054 assert_eq!(
1057 kernel_timespec_to_instant(
1058 Clock::Monotonic,
1059 KernelTimespec {
1060 tv_sec: 0,
1061 tv_nsec: -1,
1062 },
1063 ),
1064 Err(Errno::EINVAL)
1065 );
1066 assert_eq!(
1067 kernel_itimerspec_from_spec(TimerFdSpecification {
1068 initial: Duration::ZERO,
1069 interval: Duration::from_secs(u64::MAX),
1070 }),
1071 Err(Errno::EINVAL)
1072 );
1073 assert_eq!(
1074 kernel_itimerspec_from_spec(TimerFdSpecification {
1075 initial: Duration::from_secs(u64::MAX),
1076 interval: Duration::ZERO,
1077 }),
1078 Err(Errno::EINVAL)
1079 );
1080 assert_eq!(
1081 kernel_itimerspec_to_spec(KernelItimerspec {
1082 it_interval: KernelTimespec {
1083 tv_sec: -1,
1084 tv_nsec: 0,
1085 },
1086 it_value: KernelTimespec {
1087 tv_sec: 0,
1088 tv_nsec: 0,
1089 },
1090 }),
1091 Err(Errno::EINVAL)
1092 );
1093 assert_eq!(
1094 kernel_itimerspec_to_spec(KernelItimerspec {
1095 it_interval: KernelTimespec {
1096 tv_sec: 0,
1097 tv_nsec: 0,
1098 },
1099 it_value: KernelTimespec {
1100 tv_sec: -1,
1101 tv_nsec: 0,
1102 },
1103 }),
1104 Err(Errno::EINVAL)
1105 );
1106 }
1107
1108 #[test]
1109 fn clock_nanosleep_relative_is_interruptible_by_signal() {
1110 let handler = TestSignalHandler::install(Signal::SIGUSR1).expect("install SIGUSR1 handler");
1111 let pid = getpid();
1112 let tid = gettid();
1113 let sender = thread::spawn(move || {
1114 thread::sleep(Duration::from_millis(10));
1115 tgkill(pid, tid, Some(Signal::SIGUSR1)).expect("tgkill SIGUSR1");
1116 });
1117
1118 let err = clock_nanosleep(
1119 Clock::Monotonic,
1120 SleepDeadline::Relative(Duration::from_millis(100)),
1121 )
1122 .expect_err("sleep should be interrupted");
1123 sender.join().expect("signal sender thread join");
1124 drop(handler);
1125
1126 assert!(matches!(err, SleepError::Interrupted { .. }));
1131 }
1132
1133 #[test]
1134 fn timerfd_arm_rejects_overflowing_initial_spec() {
1135 let tfd = timerfd_create(Clock::Monotonic, TimerFdFlags::NONBLOCK)
1136 .expect("timerfd_create doit réussir");
1137 let err = tfd
1138 .arm(
1139 &TimerFdSpecification {
1140 initial: Duration::from_secs(u64::MAX),
1141 interval: Duration::ZERO,
1142 },
1143 TimerSetFlags::empty(),
1144 )
1145 .expect_err("overflowing initial spec must be rejected");
1146 assert_eq!(err, Errno::EINVAL);
1147 }
1148
1149 struct TestSignalHandler {
1150 signal: Signal,
1151 previous: KernelSigaction,
1152 }
1153
1154 impl TestSignalHandler {
1155 fn install(signal: Signal) -> Result<Self, Errno> {
1156 Self::install_raw(signal.as_raw())
1157 }
1158
1159 fn install_raw(signum: i32) -> Result<Self, Errno> {
1160 let new_action = KernelSigaction::handler_only(test_noop_signal_handler);
1161 let mut previous = KernelSigaction::default();
1162 let ret = unsafe {
1168 raw_syscall_rt_sigaction(
1169 signum,
1170 (&new_action as *const KernelSigaction) as u64,
1171 (&mut previous as *mut KernelSigaction) as u64,
1172 8,
1173 )
1174 };
1175 if ret < 0 {
1176 return Err(errno_from_negative_syscall_ret(ret));
1177 }
1178 let signal = Signal::try_from_raw(signum).expect("valid signum on success");
1179 Ok(Self { signal, previous })
1180 }
1181 }
1182
1183 impl Drop for TestSignalHandler {
1184 fn drop(&mut self) {
1185 let _ = unsafe {
1190 raw_syscall_rt_sigaction(
1191 self.signal.as_raw(),
1192 (&self.previous as *const KernelSigaction) as u64,
1193 0,
1194 8,
1195 )
1196 };
1197 }
1198 }
1199
1200 #[repr(C)]
1201 #[derive(Clone, Copy, Default)]
1202 struct KernelSigaction {
1203 sa_handler: usize,
1204 sa_flags: usize,
1205 sa_restorer: usize,
1206 sa_mask: u64,
1207 }
1208
1209 impl KernelSigaction {
1210 fn handler_only(handler: extern "C" fn(i32)) -> Self {
1211 Self {
1212 sa_handler: handler as usize,
1213 sa_flags: 0,
1214 sa_restorer: 0,
1215 sa_mask: 0,
1216 }
1217 }
1218 }
1219
1220 extern "C" fn test_noop_signal_handler(_: i32) {}
1221
1222 #[test]
1223 fn test_signal_handler_install_rejects_invalid_signal_number() {
1224 let err = TestSignalHandler::install_raw(-1)
1228 .err()
1229 .expect("invalid signal number should fail");
1230 assert_eq!(err, Errno::EINVAL);
1231 }
1232
1233 #[test]
1234 fn test_noop_signal_handler_is_callable() {
1235 test_noop_signal_handler(0);
1236 }
1237
1238 #[cfg(target_arch = "x86_64")]
1239 unsafe fn raw_syscall_rt_sigaction(
1240 signum: i32,
1241 new_sa: u64,
1242 old_sa: u64,
1243 sigsetsize: u64,
1244 ) -> i64 {
1245 let ret: i64;
1246 unsafe {
1252 core::arch::asm!(
1253 "syscall",
1254 in("rax") 13_i64,
1255 in("rdi") i64::from(signum),
1256 in("rsi") new_sa,
1257 in("rdx") old_sa,
1258 in("r10") sigsetsize,
1259 lateout("rax") ret,
1260 lateout("rcx") _,
1261 lateout("r11") _,
1262 options(nostack, preserves_flags),
1263 );
1264 }
1265 ret
1266 }
1267
1268 #[cfg(target_arch = "aarch64")]
1269 unsafe fn raw_syscall_rt_sigaction(
1270 signum: i32,
1271 new_sa: u64,
1272 old_sa: u64,
1273 sigsetsize: u64,
1274 ) -> i64 {
1275 let ret: i64;
1276 unsafe {
1282 core::arch::asm!(
1283 "svc 0",
1284 in("x8") 134_i64,
1285 inout("x0") i64::from(signum) => ret,
1286 in("x1") new_sa,
1287 in("x2") old_sa,
1288 in("x3") sigsetsize,
1289 options(nostack, preserves_flags),
1290 );
1291 }
1292 ret
1293 }
1294}