Skip to main content

air_sys_syscall/
time.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Wrappers de la famille `time`.
6//!
7//! Cf. `docs/specs/layer-0/family-time.md`.
8
9use 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/// FD `timerfd` (cf. `timerfd_create(2)`).
39#[derive(Debug)]
40pub struct TimerFd(OwnedFd);
41
42impl TimerFd {
43    /// Construit depuis un `OwnedFd` déjà ouvert sur un timerfd.
44    #[must_use]
45    #[inline]
46    pub const fn from_owned_fd(fd: OwnedFd) -> Self {
47        Self(fd)
48    }
49
50    /// Vue empruntée du FD sous-jacent.
51    #[must_use]
52    pub fn as_fd(&self) -> BorrowedFd<'_> {
53        use air_sys_types::fd::AsFd;
54        self.0.as_fd()
55    }
56
57    /// Consomme et restitue le `OwnedFd` sous-jacent.
58    #[must_use]
59    pub fn into_fd(self) -> OwnedFd {
60        self.0
61    }
62
63    /// Arme le timer et retourne l'ancienne spécification.
64    ///
65    /// # Errors
66    ///
67    /// - `EINVAL` : durée hors-plage (dépassement `i64` lors de la conversion
68    ///   en `KernelTimespec`), ou combinaison de `flags` refusée par le kernel.
69    /// - `EBADF` : le FD sous-jacent n'est pas un timerfd valide.
70    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        // SAFETY: `new_value` et `old_value` sont des `KernelItimerspec` locaux
78        // vivants ; on passe leurs adresses (lecture pour `new_value`, écriture pour
79        // `old_value`) — préconditions de `raw_syscall_timerfd_settime` satisfaites.
80        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    /// Désarme le timer et retourne l'ancienne spécification.
95    ///
96    /// # Errors
97    ///
98    /// Délègue à [`TimerFd::arm`] avec une spécification nulle — seul `EBADF`
99    /// peut survenir. `EINVAL` ne peut pas survenir ici car la spécification
100    /// `Duration::ZERO` est toujours valide.
101    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    /// Lit l'état courant du timer.
112    ///
113    /// # Errors
114    ///
115    /// - `EBADF` : le FD sous-jacent n'est pas un timerfd valide.
116    /// - `EINVAL` : valeur renvoyée par le kernel hors-plage (ne devrait pas
117    ///   survenir sur un timerfd bien formé).
118    pub fn current(&self) -> Result<TimerFdSpecification, Errno> {
119        let mut current = KernelItimerspec::default();
120        // SAFETY: `current` est un `KernelItimerspec` local vivant ; on passe son
121        // adresse au kernel qui y écrit l'échéance courante — précondition de
122        // `raw_syscall_timerfd_gettime` satisfaite.
123        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    /// Lit le nombre d'expirations accumulées.
136    ///
137    /// # Errors
138    ///
139    /// - `EAGAIN` : aucune expiration disponible et le FD a été créé avec
140    ///   `TFD_NONBLOCK` (cf. test `timerfd_nonblocking_read_without_expiration_returns_eagain`).
141    /// - `EINTR` : interrompu par un signal avant toute expiration. Conforme
142    ///   à la politique EINTR de la couche 0 : l'appelant doit retry
143    ///   explicitement s'il le souhaite.
144    /// - `EBADF` : le FD sous-jacent n'est pas un timerfd valide.
145    /// - `ECANCELED` : l'horloge `CLOCK_REALTIME` (ou `CLOCK_REALTIME_ALARM`) a
146    ///   subi un changement discontinu alors que le timer absolu avait été armé
147    ///   avec `TFD_TIMER_CANCEL_ON_SET` (cf. `man 2 timerfd_create`).
148    pub fn read(&self) -> Result<u64, Errno> {
149        let mut count = 0_u64;
150        // SAFETY: `count` est un `u64` local vivant ; on passe son adresse et la
151        // taille exacte (`size_of::<u64>()`) — le kernel y écrit au plus 8 octets,
152        // précondition de `raw_syscall_read` satisfaite.
153        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
168/// Lit le temps courant pour l'horloge ciblée.
169///
170/// # Errors
171///
172/// - `EINVAL` : identifiant d'horloge inconnu du kernel, ou valeur renvoyée
173///   hors-plage (nanoseconde négative ou ≥ 10⁹).
174pub 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
179/// Modifie le temps courant d'une horloge ajustable.
180///
181/// # Errors
182///
183/// - `EINVAL` : `instant.clock() != clock` (vérifié avant l'appel syscall),
184///   ou identifiant d'horloge refusé par le kernel.
185/// - `EPERM` : `CAP_SYS_TIME` manquante. Échoue systématiquement en
186///   environnement non privilégié (cf. test `clock_settime_realtime_current_time_is_ok_or_eperm`).
187pub 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    // Note coverage:
193    // - Le chemin `Ok(())` de `clock_settime` n'est généralement pas couvrable
194    //   en CI ni en développement courant, car `clock_settime(2)` exige des
195    //   privilèges élevés (`CAP_SYS_TIME`) pour réussir sur les horloges
196    //   ajustables.
197    // - Nous savons donc que la branche succès peut rester non couverte sur un
198    //   environnement non privilégié, même si le wrapper est correct.
199    // - Une manière d'atteindre artificiellement 100 % ici serait
200    //   d'introduire un point d'injection / seam test-only pour simuler un
201    //   succès du syscall. Ce choix est refusé dans Air : on n'ouvre pas de
202    //   brèche structurelle dans le code de production uniquement pour flatter
203    //   la couverture. Le projet vise une couverture maximale, mais reste
204    //   pragmatique plutôt que paranoïaque.
205    // SAFETY: `ts` est un `KernelTimespec` local vivant ; on passe son adresse en
206    // lecture seule — précondition de `raw_syscall_clock_settime` satisfaite.
207    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
215/// Résolution de l'horloge ciblée.
216///
217/// # Errors
218///
219/// - `EINVAL` : identifiant d'horloge inconnu du kernel.
220pub 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
225/// Endort le thread appelant jusqu'à l'échéance demandée.
226pub 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    // SAFETY: `req` est un `KernelTimespec` local vivant (lecture) ; `rem_ptr` est
245    // soit nul (sommeil absolu), soit l'adresse du `rem` local vivant (écriture du
246    // reste) — préconditions de `raw_syscall_clock_nanosleep` satisfaites.
247    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
258/// Crée un `timerfd` pour l'horloge ciblée.
259///
260/// `TFD_CLOEXEC` est toujours positionné en interne, quelle que soit la valeur
261/// de `flags` (sécurité par défaut).
262///
263/// # Errors
264///
265/// - `EINVAL` : identifiant d'horloge non supporté par `timerfd`, ou
266///   combinaison de `flags` invalide.
267/// - `EMFILE` : quota de FD du processus atteint.
268/// - `ENFILE` : quota de FD système atteint.
269/// - `ENOMEM` : mémoire kernel insuffisante.
270/// - `ENODEV` : impossible de monter le pseudo-périphérique anonyme interne.
271pub 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    // SAFETY: `ts` est un `KernelTimespec` local vivant ; on passe son adresse en
280    // écriture — précondition de `raw_syscall_clock_gettime` satisfaite.
281    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    // SAFETY: `ts` est un `KernelTimespec` local vivant ; on passe son adresse en
291    // écriture — précondition de `raw_syscall_clock_getres` satisfaite.
292    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    // SAFETY: `raw_syscall_timerfd_create` ne prend que des scalaires et ne touche
301    // à aucune mémoire utilisateur — aucune précondition de pointeur à honorer.
302    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    // SAFETY: `ret >= 0` ici, donc `fd` est un descripteur fraîchement créé par
309    // `timerfd_create`, non possédé ailleurs : on en prend la propriété exclusive.
310    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    // SAFETY: SYS_clock_gettime (x86_64 = 228). Le kernel écrit l'instant courant
391    // à l'adresse `ts`, qui doit pointer un `KernelTimespec` valide et inscriptible
392    // pour la durée de l'appel (précondition de cette fn unsafe, tenue par l'appelant).
393    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    // SAFETY: SYS_clock_settime (x86_64 = 227). Le kernel lit l'instant à poser
413    // à l'adresse `ts`, qui doit pointer un `KernelTimespec` valide pour la durée
414    // de l'appel. `readonly` : aucune mémoire utilisateur n'est écrite.
415    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    // SAFETY: SYS_clock_getres (x86_64 = 229). Le kernel écrit la résolution de
435    // l'horloge à l'adresse `ts`, qui doit pointer un `KernelTimespec` valide et
436    // inscriptible pour la durée de l'appel (précondition de cette fn unsafe).
437    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    // SAFETY: SYS_clock_nanosleep (x86_64 = 230). Le kernel lit la durée demandée
457    // à l'adresse `req` et, en cas d'interruption d'un sommeil relatif, écrit le
458    // reste à l'adresse `rem`. `req` doit pointer un `KernelTimespec` valide ; `rem`
459    // doit être nul ou pointer un `KernelTimespec` inscriptible (préconditions de
460    // cette fn unsafe, tenues par l'appelant).
461    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    // SAFETY: SYS_timerfd_create (x86_64 = 283). Ne prend que des scalaires (clock,
483    // flags) et ne touche à aucune mémoire utilisateur ; retourne un FD ≥ 0 ou un
484    // errno négatif. `readonly` : aucune écriture mémoire.
485    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    // SAFETY: SYS_timerfd_settime (x86_64 = 286). Le kernel lit la nouvelle
505    // échéance à l'adresse `new_value` et, si `old_value` est non nul, y écrit
506    // l'ancienne. `new_value` doit pointer un `KernelItimerspec` valide ; `old_value`
507    // doit être nul ou pointer un `KernelItimerspec` inscriptible (préconditions de
508    // cette fn unsafe).
509    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    // SAFETY: SYS_timerfd_gettime (x86_64 = 287). Le kernel écrit l'échéance
531    // courante à l'adresse `curr_value`, qui doit pointer un `KernelItimerspec`
532    // valide et inscriptible pour la durée de l'appel (précondition de cette fn unsafe).
533    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    // SAFETY: SYS_read (x86_64 = 0). Le kernel écrit jusqu'à `count` octets à
553    // l'adresse `buffer`, qui doit pointer une zone valide et inscriptible d'au
554    // moins `count` octets (précondition de cette fn unsafe, tenue par l'appelant).
555    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    // SAFETY: SYS_clock_gettime (aarch64 = 113). Le kernel écrit l'instant courant
576    // à l'adresse `ts`, qui doit pointer un `KernelTimespec` valide et inscriptible
577    // pour la durée de l'appel (précondition de cette fn unsafe, tenue par l'appelant).
578    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    // SAFETY: SYS_clock_settime (aarch64 = 112). Le kernel lit l'instant à poser
595    // à l'adresse `ts`, qui doit pointer un `KernelTimespec` valide pour la durée
596    // de l'appel. `readonly` : aucune mémoire utilisateur n'est écrite.
597    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    // SAFETY: SYS_clock_getres (aarch64 = 114). Le kernel écrit la résolution de
614    // l'horloge à l'adresse `ts`, qui doit pointer un `KernelTimespec` valide et
615    // inscriptible pour la durée de l'appel (précondition de cette fn unsafe).
616    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    // SAFETY: SYS_clock_nanosleep (aarch64 = 115). Le kernel lit la durée demandée
633    // à l'adresse `req` et, en cas d'interruption d'un sommeil relatif, écrit le
634    // reste à l'adresse `rem`. `req` doit pointer un `KernelTimespec` valide ; `rem`
635    // doit être nul ou pointer un `KernelTimespec` inscriptible (préconditions de
636    // cette fn unsafe, tenues par l'appelant).
637    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    // SAFETY: SYS_timerfd_create (aarch64 = 85). Ne prend que des scalaires (clock,
656    // flags) et ne touche à aucune mémoire utilisateur ; retourne un FD ≥ 0 ou un
657    // errno négatif. `readonly` : aucune écriture mémoire.
658    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    // SAFETY: SYS_timerfd_settime (aarch64 = 86). Le kernel lit la nouvelle
675    // échéance à l'adresse `new_value` et, si `old_value` est non nul, y écrit
676    // l'ancienne. `new_value` doit pointer un `KernelItimerspec` valide ; `old_value`
677    // doit être nul ou pointer un `KernelItimerspec` inscriptible (préconditions de
678    // cette fn unsafe).
679    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    // SAFETY: SYS_timerfd_gettime (aarch64 = 87). Le kernel écrit l'échéance
698    // courante à l'adresse `curr_value`, qui doit pointer un `KernelItimerspec`
699    // valide et inscriptible pour la durée de l'appel (précondition de cette fn unsafe).
700    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    // SAFETY: SYS_read (aarch64 = 63). Le kernel écrit jusqu'à `count` octets à
717    // l'adresse `buffer`, qui doit pointer une zone valide et inscriptible d'au
718    // moins `count` octets (précondition de cette fn unsafe, tenue par l'appelant).
719    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        // Unix epoch > 0 (valeur post-2000).
754        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        // SAFETY: `ts` est un `KernelTimespec` local vivant ; on passe son adresse
937        // en lecture seule (`clock = -1` invalide ⇒ EINVAL avant toute lecture
938        // effective) — précondition de `raw_syscall_clock_settime` satisfaite.
939        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        // errno non-EINTR : couvre le côté faux de la condition `errno == EINTR`
977        // (la branche tombe directement sur `SleepError::Other`).
978        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    // Couvre les deux côtés faux du `debug_assert!(ret < 0 && ret > -4096)` de
988    // `errno_from_negative_syscall_ret` (garde de défense en profondeur).
989    #[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        // `tv_nsec` négatif : opérande médian de la disjonction de
1036        // `kernel_timespec_to_duration` (jamais pris autrement).
1037        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        // `tv_nsec` négatif : premier opérande de la disjonction de
1055        // `kernel_timespec_to_instant`.
1056        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        // On prouve seulement la variante : un `if let` laisserait une
1127        // branche « else » morte. La valeur exacte de `remaining` (et ses
1128        // bornes) est vérifiée de façon déterministe dans
1129        // `decode_clock_nanosleep_result_covers_eintr_paths`.
1130        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            // SAFETY:
1163            // - `new_action` et `previous` pointent sur des buffers locaux valides.
1164            // - `signum` est soit un signal valide du test, soit une valeur invalide
1165            //   exercée explicitement pour couvrir le chemin d'erreur.
1166            // - `sigsetsize = 8` correspond au `kernel_sigset_t` 64-bit visé par Air.
1167            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            // SAFETY: on restaure la disposition sauvegardée `self.previous` (buffer
1186            // local vivant, lecture seule) ; `old_sa = 0` (aucune ancienne à écrire),
1187            // `sigsetsize = 8` pour le `kernel_sigset_t` 64-bit visé par Air —
1188            // préconditions de `raw_syscall_rt_sigaction` satisfaites.
1189            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        // `.err().expect(...)` au lieu d'un `match { Ok => panic }` : ce
1225        // dernier laisserait le bras `Ok` comme branche morte. `.err()` évite
1226        // d'exiger `Debug` sur `TestSignalHandler` (que `expect_err` réclame).
1227        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        // SAFETY: SYS_rt_sigaction (x86_64 = 13). Le kernel lit la nouvelle
1247        // disposition à l'adresse `new_sa` (si non nulle) et écrit l'ancienne à
1248        // `old_sa` (si non nulle) ; `sigsetsize` borne la taille du masque. Chaque
1249        // pointeur non nul doit viser un `KernelSigaction` valide (resp. inscriptible
1250        // pour `old_sa`) — préconditions de cette fn unsafe, tenues par l'appelant.
1251        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        // SAFETY: SYS_rt_sigaction (aarch64 = 134). Le kernel lit la nouvelle
1277        // disposition à l'adresse `new_sa` (si non nulle) et écrit l'ancienne à
1278        // `old_sa` (si non nulle) ; `sigsetsize` borne la taille du masque. Chaque
1279        // pointeur non nul doit viser un `KernelSigaction` valide (resp. inscriptible
1280        // pour `old_sa`) — préconditions de cette fn unsafe, tenues par l'appelant.
1281        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}