air_sys_syscall/signal.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 `signal`.
6//!
7//! Cf. `docs/specs/layer-0/family-signal.md` et `ADR-020-strategie-signaux`.
8//!
9//! Périmètre :
10//! - Sous-section 1 (signalfd + masques) : [`signalfd_create`],
11//! [`signalfd_create_blocking`], [`SignalFd::read`],
12//! [`SignalFd::update_mask`], [`block_signals`], [`unblock_signals`],
13//! [`set_signal_mask`], [`current_signal_mask`], [`wait_for_signal`].
14//! - Sous-section 2 (envoi) : [`kill`], [`tgkill`], [`rt_sigqueueinfo`].
15//! - Sous-section 3 (sigaction restreint FAUTES) : sous-module
16//! [`synchronous_handler`] avec [`synchronous_handler::install_fatal_handler`]
17//! et [`synchronous_handler::restore_handler`] (handler **inerte** ; le noyau
18//! force l'action par défaut sur les 4 fautes, ADR-020).
19//! - Sous-section 4 (sigaction async NON-faute, ADR-066) : sous-module
20//! [`async_handler`] avec [`async_handler::install`]/[`async_handler::restore`]
21//! (handler **réellement installé** — le noyau appelle le handler C à la
22//! délivrance d'un signal gérable ; trampoline `rt_sigreturn` fourni sur
23//! x86_64), et [`rt_sigpending`] (ensemble des signaux pendants).
24
25use air_sys_types::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd};
26use core::num::NonZeroI32;
27use core::time::Duration;
28
29use air_sys_types::{
30 AltStack, Errno, Pid, Signal, SignalFdFlags, SignalFdInfo, SignalInfo, SignalMask, Tid,
31};
32
33#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
34compile_error!("air-sys-syscall::signal supporte uniquement x86_64 et aarch64 (ADR-014).");
35
36// ─────────────────────────────────────────────────────────────────────────
37// SignalFd — wrapper RAII opaque autour d'un signalfd kernel.
38// ─────────────────────────────────────────────────────────────────────────
39
40/// FD `signalfd` (cf. `signalfd4(2)`). Drop ferme le FD via [`OwnedFd`].
41#[derive(Debug)]
42pub struct SignalFd(OwnedFd);
43
44impl SignalFd {
45 /// Construit depuis un `OwnedFd` déjà ouvert sur un signalfd.
46 #[must_use]
47 #[inline]
48 pub const fn from_owned_fd(fd: OwnedFd) -> Self {
49 Self(fd)
50 }
51
52 /// Vue empruntée du FD sous-jacent.
53 #[must_use]
54 pub fn as_fd(&self) -> BorrowedFd<'_> {
55 use air_sys_types::fd::AsFd;
56 self.0.as_fd()
57 }
58
59 /// Consomme et restitue le `OwnedFd`.
60 #[must_use]
61 pub fn into_fd(self) -> OwnedFd {
62 self.0
63 }
64
65 /// Lit le prochain événement signal disponible sur le `signalfd`.
66 ///
67 /// Bloque si le FD est en mode bloquant (par défaut) et qu'aucun
68 /// signal n'est pendant ; retourne immédiatement avec
69 /// [`Errno::EAGAIN`] si le FD est en mode `NONBLOCK` et aucun
70 /// signal n'est disponible.
71 ///
72 /// # Errors
73 ///
74 /// - [`Errno::EAGAIN`] : mode non-bloquant, aucun signal pendant.
75 /// - [`Errno::EINTR`] : interrompu par un signal (remonté tel quel
76 /// per convention 2 ADR-021).
77 pub fn read(&self) -> Result<SignalFdInfo, Errno> {
78 let mut buffer = [0_u8; SIGNALFD_SIGINFO_SIZE];
79 let ptr: *mut u8 = buffer.as_mut_ptr();
80 let length = u64::try_from(SIGNALFD_SIGINFO_SIZE)
81 .expect("buffer fixe SIGNALFD_SIGINFO_SIZE tient en u64");
82 // SAFETY:
83 // - SYS_read sur un signalfd valide écrit exactement 128 octets
84 // dans `buffer` (taille du `signalfd_siginfo`) ou retourne une
85 // erreur. `buffer` est local valide pour la durée du syscall.
86 let ret = unsafe { raw_syscall_read(self.0.as_raw_fd(), ptr as u64, length) };
87 if ret < 0 {
88 return Err(errno_from_negative_syscall_ret(ret));
89 }
90 // Lecture partielle d'un signalfd impossible (atomique côté kernel) ;
91 // ret == 128 ou erreur.
92 debug_assert_eq!(ret, i64::from(SIGNALFD_SIGINFO_SIZE_I32));
93 Ok(parse_signalfd_siginfo(&buffer))
94 }
95
96 /// Met à jour le mask de signaux capturés par ce `signalfd`.
97 ///
98 /// Équivalent à un nouvel appel `signalfd4(self, &mask, ...)` qui
99 /// remplace atomiquement le mask interne du FD.
100 ///
101 /// # Errors
102 ///
103 /// - [`Errno::EINVAL`] : mask invalide.
104 pub fn update_mask(&mut self, mask: &SignalMask) -> Result<(), Errno> {
105 let bits = mask.bits();
106 let mask_ptr: *const u64 = &bits;
107 // SAFETY: signalfd4(fd_existant, &mask, sizeof(u64), 0). Le
108 // kernel lit 8 octets à mask_ptr (sigset_t kernel = 1 mot sur
109 // archs où _NSIG = 64). Flags=0 préserve les flags existants
110 // (NONBLOCK/CLOEXEC) du FD.
111 let ret = unsafe {
112 raw_syscall_signalfd4(self.0.as_raw_fd(), mask_ptr as u64, SIGSET_SIZE_U64, 0_i32)
113 };
114 if ret < 0 {
115 return Err(errno_from_negative_syscall_ret(ret));
116 }
117 Ok(())
118 }
119}
120
121// ─────────────────────────────────────────────────────────────────────────
122// signalfd_create / signalfd_create_blocking.
123// ─────────────────────────────────────────────────────────────────────────
124
125/// Crée un `signalfd` pour les signaux du `mask`.
126///
127/// Le drapeau `CLOEXEC` est **toujours** ajouté par le wrapper (cf.
128/// discipline universelle couche 0 : tous les FDs créés par Air sont
129/// `O_CLOEXEC`).
130///
131/// **Important.** Créer un signalfd ne suffit pas à empêcher la
132/// délivrance normale des signaux du `mask` (handler ou comportement
133/// par défaut). Il faut **aussi** bloquer ces signaux dans le mask du
134/// thread via [`block_signals`]. Le helper [`signalfd_create_blocking`]
135/// combine les deux étapes.
136///
137/// # Errors
138///
139/// - [`Errno::EINVAL`] : mask invalide.
140/// - `EMFILE`/`ENFILE` (stub Errno) : limites de FDs atteintes.
141pub fn signalfd_create(mask: &SignalMask, flags: SignalFdFlags) -> Result<SignalFd, Errno> {
142 let bits = mask.bits();
143 let mask_ptr: *const u64 = &bits;
144 let kernel_flags = flags.bits() | SignalFdFlags::CLOEXEC.bits();
145 // SAFETY: signalfd4(-1, &mask, sizeof(u64), flags) crée un nouveau
146 // FD ; le kernel lit 8 octets à mask_ptr. Pas d'écriture en mémoire
147 // utilisateur. -1 = créer un nouveau (vs réutiliser un existant).
148 let ret =
149 unsafe { raw_syscall_signalfd4(-1_i32, mask_ptr as u64, SIGSET_SIZE_U64, kernel_flags) };
150 if ret < 0 {
151 return Err(errno_from_negative_syscall_ret(ret));
152 }
153 #[allow(clippy::cast_possible_truncation)]
154 let new_fd = ret as i32;
155 // SAFETY: kernel vient de transférer la propriété d'un fd valide.
156 let owned = unsafe { OwnedFd::from_raw_fd(new_fd) };
157 Ok(SignalFd::from_owned_fd(owned))
158}
159
160/// Helper canonique : bloque les signaux du `mask` dans le thread
161/// appelant **puis** crée un `signalfd` pour ces mêmes signaux.
162///
163/// Pattern recommandé par `family-signal.md` et ADR-020.
164///
165/// **Note.** Le blocage est PER-THREAD ; il s'applique au thread qui
166/// appelle cette fonction. À combiner avec un `MaskGuard` RAII dans le
167/// code de test pour ne pas contaminer les threads/tests suivants.
168///
169/// # Errors
170///
171/// - Toutes les erreurs de [`block_signals`] et [`signalfd_create`].
172pub fn signalfd_create_blocking(
173 mask: &SignalMask,
174 flags: SignalFdFlags,
175) -> Result<SignalFd, Errno> {
176 let _ = block_signals(mask)?;
177 signalfd_create(mask, flags)
178}
179
180// ─────────────────────────────────────────────────────────────────────────
181// Masques de signaux : rt_sigprocmask wrappers.
182// ─────────────────────────────────────────────────────────────────────────
183
184const SIG_BLOCK: i32 = 0;
185const SIG_UNBLOCK: i32 = 1;
186const SIG_SETMASK: i32 = 2;
187const SIGSET_SIZE_U64: u64 = 8;
188
189/// Bloque les signaux du `mask` dans le mask courant du thread (union).
190/// Retourne l'ancien mask (avant modification).
191///
192/// `SIGKILL` et `SIGSTOP` sont silencieusement ignorés par le kernel
193/// (non bloquables).
194///
195/// # Errors
196///
197/// - [`Errno::EINVAL`] : sigsetsize invalide.
198pub fn block_signals(mask: &SignalMask) -> Result<SignalMask, Errno> {
199 rt_sigprocmask(SIG_BLOCK, Some(mask))
200}
201
202/// Déblocque les signaux du `mask` dans le mask courant du thread
203/// (différence). Retourne l'ancien mask.
204///
205/// # Errors
206///
207/// - [`Errno::EINVAL`] : sigsetsize invalide.
208pub fn unblock_signals(mask: &SignalMask) -> Result<SignalMask, Errno> {
209 rt_sigprocmask(SIG_UNBLOCK, Some(mask))
210}
211
212/// Remplace **totalement** le mask de signaux du thread par `mask`.
213/// Retourne l'ancien mask.
214///
215/// Usage typique : restauration du mask original via un guard RAII.
216///
217/// # Errors
218///
219/// - [`Errno::EINVAL`] : sigsetsize invalide.
220pub fn set_signal_mask(mask: &SignalMask) -> Result<SignalMask, Errno> {
221 rt_sigprocmask(SIG_SETMASK, Some(mask))
222}
223
224/// Lit le mask de signaux courant sans modification.
225///
226/// # Errors
227///
228/// - [`Errno::EINVAL`] : ne se produit pas en pratique.
229pub fn current_signal_mask() -> Result<SignalMask, Errno> {
230 rt_sigprocmask(SIG_SETMASK, None)
231}
232
233/// Helper interne : encapsule l'appel `rt_sigprocmask` quelle que soit
234/// l'opération (SIG_BLOCK/UNBLOCK/SETMASK). `set = None` signifie
235/// « lecture seule ».
236fn rt_sigprocmask(how: i32, set: Option<&SignalMask>) -> Result<SignalMask, Errno> {
237 let set_bits: u64 = set.map_or(0, |m| m.bits());
238 let set_ptr: u64 = match set {
239 Some(_) => {
240 let p: *const u64 = &set_bits;
241 p as u64
242 }
243 None => 0,
244 };
245 let mut old_bits: u64 = 0;
246 let old_ptr: *mut u64 = &mut old_bits;
247 // SAFETY: rt_sigprocmask lit `set` si non-null (8 octets), écrit
248 // `oldset` (8 octets). Les deux pointeurs sont locaux ; sigsetsize=8
249 // correspond à la taille kernel `sigset_t` sur x86_64/aarch64.
250 let ret = unsafe { raw_syscall_rt_sigprocmask(how, set_ptr, old_ptr as u64, SIGSET_SIZE_U64) };
251 if ret < 0 {
252 return Err(errno_from_negative_syscall_ret(ret));
253 }
254 Ok(SignalMask::from_bits(old_bits))
255}
256
257/// Lit l'ensemble des signaux **pendants** (`rt_sigpending(2)`).
258///
259/// Retourne le [`SignalMask`] des signaux **en attente** de délivrance pour le
260/// thread appelant (union des pendants par-thread et par-process), c.-à-d.
261/// générés mais pas encore délivrés — typiquement parce qu'ils sont **bloqués**
262/// dans le masque courant. Brique de `sigpending(3)` de la libc (via la couche 1
263/// `air-signal`).
264///
265/// **Kernel = bible** : cette fonction **expose** l'état pendant du noyau, elle
266/// n'en maintient aucun (ADR-064 §6).
267///
268/// # Errors
269///
270/// - [`Errno::EFAULT`] : inatteignable via cette API (le buffer est un local
271/// vivant). En pratique `rt_sigpending` ne peut pas échouer avec un
272/// `sigsetsize` correct (fixé à 8 par le wrapper).
273pub fn rt_sigpending() -> Result<SignalMask, Errno> {
274 let mut pending_bits: u64 = 0;
275 let set_ptr: *mut u64 = &mut pending_bits;
276 // SAFETY: rt_sigpending écrit 8 octets (sigset_t kernel = 1 mot sur
277 // x86_64/aarch64) à `set_ptr`, local vivant pour la durée de l'appel.
278 // sigsetsize=8 correspond à la taille kernel `sigset_t`.
279 let ret = unsafe { raw_syscall_rt_sigpending(set_ptr as u64, SIGSET_SIZE_U64) };
280 if ret < 0 {
281 return Err(errno_from_negative_syscall_ret(ret));
282 }
283 Ok(SignalMask::from_bits(pending_bits))
284}
285
286// ─────────────────────────────────────────────────────────────────────────
287// wait_for_signal — helper signalfd temporaire.
288// ─────────────────────────────────────────────────────────────────────────
289
290/// Attend l'arrivée d'un signal du `mask`, avec timeout optionnel.
291///
292/// Implémentation : crée un `signalfd` temporaire pour `mask`, bloque
293/// ces signaux dans le thread, lit l'événement (bloquant ou avec
294/// timeout via `ppoll`).
295///
296/// **Note périmètre.** Cette PR n'implémente que la variante
297/// `timeout = None` (lecture bloquante via `read` sur le signalfd) ;
298/// la variante `Some(duration)` nécessite `ppoll` qui sera ajouté dans
299/// la PR `family-time` (timers). En `Some(_)`, retourne actuellement
300/// [`Errno::EINVAL`] — l'API publique reste conforme spec mais la
301/// branche timeout est documentée comme TODO.
302///
303/// # Errors
304///
305/// - [`Errno::EINVAL`] : `timeout.is_some()` (non encore implémenté
306/// sans wrapper `ppoll`).
307/// - Toutes les erreurs de [`signalfd_create_blocking`] et de
308/// [`SignalFd::read`].
309pub fn wait_for_signal(
310 mask: &SignalMask,
311 timeout: Option<Duration>,
312) -> Result<SignalFdInfo, Errno> {
313 if timeout.is_some() {
314 return Err(Errno::EINVAL);
315 }
316 let sfd = signalfd_create_blocking(mask, SignalFdFlags::empty())?;
317 sfd.read()
318}
319
320// ─────────────────────────────────────────────────────────────────────────
321// Envoi de signaux : kill, tgkill, rt_sigqueueinfo.
322// ─────────────────────────────────────────────────────────────────────────
323
324/// Envoie `signal` au processus `pid`.
325///
326/// `signal = None` correspond au "signal 0" Linux : test d'existence du
327/// processus sans envoyer de signal effectif (cf. convention 1
328/// ADR-021).
329///
330/// **Recommandation Air.** Pour un processus enfant dont on possède un
331/// `PidFd`, préférer [`pidfd_send_signal`](crate::process::pidfd_send_signal)
332/// — pas de race sur le PID recyclé.
333///
334/// # Errors
335///
336/// - [`Errno::EINVAL`] : signal invalide.
337/// - [`Errno::EPERM`] : permissions insuffisantes.
338/// - [`Errno::ESRCH`] : processus inexistant.
339pub fn kill(pid: Pid, signal: Option<Signal>) -> Result<(), Errno> {
340 let sig_arg = signal.map_or(0_i32, |s| s.as_raw());
341 // SAFETY: kill(2) ne touche pas la mémoire utilisateur.
342 let ret = unsafe { raw_syscall_kill(pid.as_raw(), sig_arg) };
343 if ret < 0 {
344 return Err(errno_from_negative_syscall_ret(ret));
345 }
346 Ok(())
347}
348
349/// Envoie `signal` à **tout un groupe de processus** (`kill(2)` avec un `pid` négatif) —
350/// **descellement additif [ADR-085](../../../docs/adrs/ADR-085-descellement-couche0-cumule-libc-std-fr.md)**
351/// pour la face libc `killpg` (le type positif-seul [`Pid`] interdit d'exprimer le `pid`
352/// négatif « groupe » via [`kill`]). `group = None` cible le **groupe de l'appelant**
353/// (sentinelle kernel `0` typée en `Option`, ADR-021 conv. 1) ; `Some(g)` cible le groupe
354/// `g` (le kernel reçoit `-g`). `signal = None` = test d'existence.
355pub fn kill_process_group(group: Option<Pid>, signal: Option<Signal>) -> Result<(), Errno> {
356 let sig_arg = signal.map_or(0_i32, |s| s.as_raw());
357 // `None` ⇒ `0` (groupe de l'appelant) ; `Some(g)` ⇒ `-g` (g > 0 ⇒ pas de débordement).
358 let target = group.map_or(0_i32, |g| g.as_raw().wrapping_neg());
359 // SAFETY: kill(2) ne touche pas la mémoire utilisateur.
360 let ret = unsafe { raw_syscall_kill(target, sig_arg) };
361 if ret < 0 {
362 return Err(errno_from_negative_syscall_ret(ret));
363 }
364 Ok(())
365}
366
367/// Installe (`new`) et/ou interroge (`old`) la **pile de signal alternative** du thread
368/// (`sigaltstack(2)`) — **descellement additif [ADR-085](../../../docs/adrs/ADR-085-descellement-couche0-cumule-libc-std-fr.md)**
369/// pour la face libc. `new = None` ne change rien ; `old = None` ne rapporte rien.
370/// Requis par `std` (traitement d'un `SIGSEGV` de débordement de pile sur pile dédiée).
371///
372/// # Errors
373///
374/// - [`Errno::EINVAL`] : `new.flags` invalide, ou `new.size < MINSIGSTKSZ`.
375/// - [`Errno::EPERM`] : tentative de désactivation alors que le thread est **sur** la pile
376/// alternative (`SS_ONSTACK`).
377/// - [`Errno::EFAULT`] : `new`/`old` hors de l'espace adressable (impossible via `&`).
378pub fn sigaltstack(new: Option<&AltStack>, old: Option<&mut AltStack>) -> Result<(), Errno> {
379 let new_ptr = new.map_or(0_u64, |stack| core::ptr::from_ref(stack) as u64);
380 let old_ptr = old.map_or(0_u64, |stack| core::ptr::from_mut(stack) as u64);
381 // SAFETY: le kernel **lit** `*new` (24 octets, `stack_t`) si `new_ptr != 0`, et
382 // **écrit** `*old` (24 octets) si `old_ptr != 0` ; les deux proviennent de références
383 // Rust valides et correctement dimensionnées (`AltStack` est `#[repr(C)]` = `stack_t`).
384 let ret = unsafe { raw_syscall_sigaltstack(new_ptr, old_ptr) };
385 if ret < 0 {
386 return Err(errno_from_negative_syscall_ret(ret));
387 }
388 Ok(())
389}
390
391/// Envoie `signal` au thread `tid` du process `tgid`.
392///
393/// Plus précis que [`kill`] : cible un thread spécifique d'un processus
394/// multi-thread. Indispensable pour les patterns de signaling
395/// intra-process déterministes (notamment dans les tests qui veulent
396/// délivrer un signal à un thread précis sans contaminer les autres
397/// workers).
398///
399/// `signal = None` : test d'existence (convention 1 ADR-021).
400///
401/// # Errors
402///
403/// - Mêmes que [`kill`].
404pub fn tgkill(tgid: Pid, tid: Tid, signal: Option<Signal>) -> Result<(), Errno> {
405 let sig_arg = signal.map_or(0_i32, |s| s.as_raw());
406 // SAFETY: tgkill(2) ne touche pas la mémoire utilisateur.
407 let ret = unsafe { raw_syscall_tgkill(tgid.as_raw(), tid.as_raw(), sig_arg) };
408 if ret < 0 {
409 return Err(errno_from_negative_syscall_ret(ret));
410 }
411 Ok(())
412}
413
414/// Envoie `signal` au processus `pid` avec un payload `siginfo_t`
415/// (pattern SI_QUEUE — `sigqueue(3)` côté userspace).
416///
417/// `info` est construit côté appelant via
418/// [`SignalInfo::new_queue`](air_sys_types::SignalInfo::new_queue). Le
419/// kernel écrase `si_signo` avec `signal` à la délivrance.
420///
421/// # Errors
422///
423/// - [`Errno::EINVAL`] : signal invalide.
424/// - [`Errno::EPERM`] : tentative d'envoi cross-process avec un
425/// `si_code` non autorisé (impossible via notre constructeur qui
426/// fixe `si_code = SI_QUEUE`).
427/// - [`Errno::ESRCH`] : processus inexistant.
428pub fn rt_sigqueueinfo(pid: Pid, signal: Signal, info: &SignalInfo) -> Result<(), Errno> {
429 let info_ptr: *const u8 = info.as_bytes().as_ptr();
430 // SAFETY: rt_sigqueueinfo(2) lit 128 octets à `info_ptr` (taille
431 // siginfo_t Linux côté x86_64/aarch64). Pas d'écriture user.
432 let ret =
433 unsafe { raw_syscall_rt_sigqueueinfo(pid.as_raw(), signal.as_raw(), info_ptr as u64) };
434 if ret < 0 {
435 return Err(errno_from_negative_syscall_ret(ret));
436 }
437 Ok(())
438}
439
440// ─────────────────────────────────────────────────────────────────────────
441// signalfd_siginfo : parsing du buffer kernel vers SignalFdInfo.
442// ─────────────────────────────────────────────────────────────────────────
443
444const SIGNALFD_SIGINFO_SIZE: usize = 128;
445const SIGNALFD_SIGINFO_SIZE_I32: i32 = 128;
446
447// Offsets dans `struct signalfd_siginfo` (uapi/linux/signalfd.h).
448// Identiques x86_64/aarch64.
449const SFI_SSI_SIGNO: usize = 0;
450const SFI_SSI_ERRNO: usize = 4;
451const SFI_SSI_CODE: usize = 8;
452const SFI_SSI_PID: usize = 12;
453const SFI_SSI_UID: usize = 16;
454const SFI_SSI_FD: usize = 20;
455const SFI_SSI_TID: usize = 24;
456const SFI_SSI_BAND: usize = 28;
457const SFI_SSI_OVERRUN: usize = 32;
458const SFI_SSI_TRAPNO: usize = 36;
459const SFI_SSI_STATUS: usize = 40;
460const SFI_SSI_INT: usize = 44;
461const SFI_SSI_PTR: usize = 48;
462const SFI_SSI_UTIME: usize = 56;
463const SFI_SSI_STIME: usize = 64;
464const SFI_SSI_ADDR: usize = 72;
465
466fn read_u32(buffer: &[u8], offset: usize) -> u32 {
467 u32::from_ne_bytes(
468 buffer[offset..offset.saturating_add(4)]
469 .try_into()
470 .expect("4 octets"),
471 )
472}
473
474fn read_i32(buffer: &[u8], offset: usize) -> i32 {
475 i32::from_ne_bytes(
476 buffer[offset..offset.saturating_add(4)]
477 .try_into()
478 .expect("4 octets"),
479 )
480}
481
482fn read_u64(buffer: &[u8], offset: usize) -> u64 {
483 u64::from_ne_bytes(
484 buffer[offset..offset.saturating_add(8)]
485 .try_into()
486 .expect("8 octets"),
487 )
488}
489
490fn parse_signalfd_siginfo(buffer: &[u8; SIGNALFD_SIGINFO_SIZE]) -> SignalFdInfo {
491 let signo = read_u32(buffer, SFI_SSI_SIGNO);
492 // Le kernel garantit `signo` > 0 pour tout événement signalfd réel.
493 let signal = Signal::try_from_raw(i32::try_from(signo).expect("ssi_signo fit en i32"))
494 .expect("kernel : signalfd événement avec signo > 0");
495
496 let pid_raw = read_u32(buffer, SFI_SSI_PID);
497 // `ssi_pid == 0` quand le signal vient du kernel (SI_KERNEL) ; on
498 // applique la convention 1 ADR-021 et retourne `None` dans ce cas.
499 let pid = if pid_raw == 0 {
500 None
501 } else {
502 Some(
503 Pid::try_from_raw(i32::try_from(pid_raw).expect("ssi_pid fit en i32"))
504 .expect("ssi_pid > 0 ici"),
505 )
506 };
507
508 SignalFdInfo {
509 signal,
510 errno: read_i32(buffer, SFI_SSI_ERRNO),
511 code: read_i32(buffer, SFI_SSI_CODE),
512 pid,
513 uid: read_u32(buffer, SFI_SSI_UID),
514 fd: read_i32(buffer, SFI_SSI_FD),
515 timer_id: read_u32(buffer, SFI_SSI_TID),
516 band: read_u32(buffer, SFI_SSI_BAND),
517 overrun: read_u32(buffer, SFI_SSI_OVERRUN),
518 trap_no: read_u32(buffer, SFI_SSI_TRAPNO),
519 status: read_i32(buffer, SFI_SSI_STATUS),
520 int: read_i32(buffer, SFI_SSI_INT),
521 ptr: read_u64(buffer, SFI_SSI_PTR),
522 utime: read_u64(buffer, SFI_SSI_UTIME),
523 stime: read_u64(buffer, SFI_SSI_STIME),
524 addr: read_u64(buffer, SFI_SSI_ADDR),
525 }
526}
527
528// ─────────────────────────────────────────────────────────────────────────
529// Sous-module synchronous_handler : sigaction restreint (cf. ADR-020).
530// ─────────────────────────────────────────────────────────────────────────
531
532pub mod synchronous_handler {
533 //! Wrappers `sigaction` **restreints aux 4 signaux synchrones fatals**
534 //! (cf. ADR-020).
535 //!
536 //! L'API entière est `unsafe` : l'appelant doit garantir que le
537 //! handler installé respecte la contrainte async-signal-safe
538 //! (cf. `man 7 signal-safety`).
539
540 use air_sys_types::Errno;
541 use air_sys_types::signal::synchronous_handler::{FatalHandler, FatalSignal, PreviousHandler};
542
543 use super::{SIGSET_SIZE_U64, errno_from_negative_syscall_ret};
544
545 // SA_SIGINFO = 4 (constant ABI Linux, identique x86_64/aarch64).
546 const SA_SIGINFO: u64 = 4;
547
548 /// Installe `handler` comme handler du signal fatal `signal`.
549 /// Retourne l'ancien handler dans `PreviousHandler` pour pouvoir
550 /// restaurer via [`restore_handler`].
551 ///
552 /// # Safety
553 ///
554 /// L'appelant doit garantir que `handler` est **async-signal-safe**
555 /// (cf. `man 7 signal-safety`). Concrètement : pas de `malloc`, pas
556 /// de `printf`, pas de mutex, pas de `Mutex` Rust, pas d'allocations
557 /// indirectes (incluant les `format!`). Les appels typiques admis :
558 /// `write(STDERR_FILENO, ...)`, opérations `core::sync::atomic`,
559 /// `_exit(N)`. Voir l'ADR-020 pour les patterns de crash reporter.
560 pub unsafe fn install_fatal_handler(
561 signal: FatalSignal,
562 handler: FatalHandler,
563 ) -> Result<PreviousHandler, Errno> {
564 let mut new_sa = KernelSigaction::zeroed();
565 new_sa.sa_handler = handler as usize as u64;
566 new_sa.sa_flags = SA_SIGINFO;
567 // sa_mask = 0 (aucun signal additionnel bloqué pendant le
568 // handler). sa_restorer = 0 sur x86_64 → kernel utilise le
569 // trampoline VDSO ; absent sur aarch64.
570
571 let mut previous = PreviousHandler::zeroed();
572 let new_ptr: *const KernelSigaction = &new_sa;
573 let old_ptr: *mut [u8; 32] = previous.as_bytes_mut();
574
575 // SAFETY: rt_sigaction(2). Le kernel lit `new_sa`
576 // (KernelSigaction valide, locale) et écrit jusqu'à 32 octets à
577 // `old_ptr` (capacité de `PreviousHandler`). sigsetsize=8
578 // correspond à la taille kernel `sigset_t`.
579 let ret = unsafe {
580 raw_syscall_rt_sigaction(
581 signal.as_signal().as_raw(),
582 new_ptr as u64,
583 old_ptr as u64,
584 SIGSET_SIZE_U64,
585 )
586 };
587 if ret < 0 {
588 return Err(errno_from_negative_syscall_ret(ret));
589 }
590 Ok(previous)
591 }
592
593 /// Restaure le handler `previous` pour le signal `signal`.
594 ///
595 /// # Safety
596 ///
597 /// `previous` doit avoir été obtenu par un appel précédent à
598 /// [`install_fatal_handler`] sur le **même** `signal`. Restaurer un
599 /// `PreviousHandler` qui ne provient pas du même signal est
600 /// undefined behavior côté kernel.
601 pub unsafe fn restore_handler(
602 signal: FatalSignal,
603 previous: PreviousHandler,
604 ) -> Result<(), Errno> {
605 let prev_ptr: *const [u8; 32] = previous.as_bytes();
606 // SAFETY: rt_sigaction(2). Le kernel lit jusqu'à 32 octets à
607 // `prev_ptr` (KernelSigaction est ≤ 32 octets sur x86_64/aarch64).
608 // Pas d'écriture (oldset=NULL).
609 let ret = unsafe {
610 raw_syscall_rt_sigaction(
611 signal.as_signal().as_raw(),
612 prev_ptr as u64,
613 0_u64,
614 SIGSET_SIZE_U64,
615 )
616 };
617 if ret < 0 {
618 return Err(errno_from_negative_syscall_ret(ret));
619 }
620 Ok(())
621 }
622
623 /// Représentation interne du `struct sigaction` kernel.
624 ///
625 /// Layout :
626 /// - offset 0 : `sa_handler` (pointer, 8 octets)
627 /// - offset 8 : `sa_flags` (unsigned long, 8 octets)
628 /// - offset 16 : `sa_restorer` (x86_64 uniquement, 8 octets)
629 /// - offset 24 (x86_64) ou 16 (aarch64) : `sa_mask` (sigset_t, 8 octets)
630 ///
631 /// Sur aarch64, `sa_restorer` est absent (le kernel utilise VDSO
632 /// pour le retour). On utilise donc deux layouts compatibles avec
633 /// le syscall :
634 /// - x86_64 : 32 octets.
635 /// - aarch64 : 24 octets, paddé à 32 pour uniformiser le buffer
636 /// `PreviousHandler` (les 8 derniers octets sont ignorés par le
637 /// kernel sur aarch64).
638 #[repr(C)]
639 #[cfg(target_arch = "x86_64")]
640 struct KernelSigaction {
641 sa_handler: u64,
642 sa_flags: u64,
643 sa_restorer: u64,
644 sa_mask: u64,
645 }
646
647 #[cfg(target_arch = "x86_64")]
648 impl KernelSigaction {
649 const fn zeroed() -> Self {
650 Self {
651 sa_handler: 0,
652 sa_flags: 0,
653 sa_restorer: 0,
654 sa_mask: 0,
655 }
656 }
657 }
658
659 #[repr(C)]
660 #[cfg(target_arch = "aarch64")]
661 struct KernelSigaction {
662 sa_handler: u64,
663 sa_flags: u64,
664 sa_mask: u64,
665 }
666
667 #[cfg(target_arch = "aarch64")]
668 impl KernelSigaction {
669 const fn zeroed() -> Self {
670 Self {
671 sa_handler: 0,
672 sa_flags: 0,
673 sa_mask: 0,
674 }
675 }
676 }
677
678 #[cfg(target_arch = "x86_64")]
679 #[inline]
680 unsafe fn raw_syscall_rt_sigaction(
681 signum: i32,
682 new_sa: u64,
683 old_sa: u64,
684 sigsetsize: u64,
685 ) -> i64 {
686 let ret: i64;
687 // SAFETY: SYS_rt_sigaction (x86_64 = 13). Le kernel lit `new_sa`
688 // (KernelSigaction, 32 octets), écrit `old_sa` (32 octets) si
689 // non-null. ABI standard ; pas de `readonly` (écriture sur
690 // `*old_sa`).
691 unsafe {
692 core::arch::asm!(
693 "syscall",
694 in("rax") 13_i64,
695 in("rdi") i64::from(signum),
696 in("rsi") new_sa,
697 in("rdx") old_sa,
698 in("r10") sigsetsize,
699 lateout("rax") ret,
700 lateout("rcx") _,
701 lateout("r11") _,
702 options(nostack, preserves_flags),
703 );
704 }
705 ret
706 }
707
708 #[cfg(target_arch = "aarch64")]
709 #[inline]
710 unsafe fn raw_syscall_rt_sigaction(
711 signum: i32,
712 new_sa: u64,
713 old_sa: u64,
714 sigsetsize: u64,
715 ) -> i64 {
716 let ret: i64;
717 // SAFETY: SYS_rt_sigaction (aarch64 = 134).
718 unsafe {
719 core::arch::asm!(
720 "svc 0",
721 in("x8") 134_i64,
722 inout("x0") i64::from(signum) => ret,
723 in("x1") new_sa,
724 in("x2") old_sa,
725 in("x3") sigsetsize,
726 options(nostack, preserves_flags),
727 );
728 }
729 ret
730 }
731
732 /// Test : KernelSigaction tient dans le buffer 32 octets de
733 /// PreviousHandler.
734 const _: () = {
735 assert!(core::mem::size_of::<KernelSigaction>() <= 32);
736 };
737
738 /// Fonction `c_int` utilitaire pour les tests : ne fait rien (handler
739 /// vide), mais respecte la signature `FatalHandler`.
740 ///
741 /// Used by tests. Marked async-signal-safe (no allocation, no I/O,
742 /// just returns).
743 ///
744 /// # Safety
745 ///
746 /// Trivially safe : no operations.
747 #[cfg(test)]
748 pub(super) unsafe extern "C" fn noop_handler(
749 _signum: core::ffi::c_int,
750 _info: *mut air_sys_types::signal::synchronous_handler::SignalInfo,
751 _context: *mut core::ffi::c_void,
752 ) {
753 // Vide. Trivially async-signal-safe.
754 }
755}
756
757// ─────────────────────────────────────────────────────────────────────────
758// Sous-module async_handler : rt_sigaction NON-faute réel (cf. ADR-066).
759// ─────────────────────────────────────────────────────────────────────────
760
761// Trampoline `rt_sigreturn` pour x86_64. À la fin d'un handler **non-faute**, le
762// noyau ne revient PAS directement au code interrompu : il saute à l'adresse
763// `sa_restorer`, dont la seule mission est d'invoquer `rt_sigreturn` (NR 15) pour
764// restaurer le contexte pré-signal empilé par le noyau (sigframe). Sur x86_64 la
765// libc/`std` fournit ce trampoline ; sans libc (Air), on le fournit nous-mêmes.
766// Sur aarch64, le noyau route le retour par le VDSO — **aucun** `sa_restorer`.
767//
768// SAFETY: le corps asm n'est **jamais** appelé depuis Rust ; c'est le **noyau**
769// qui y saute à la fin du handler, avec `%rsp` pointant sur le sigframe. Il ne
770// fait qu'exécuter `rt_sigreturn` (`mov $15, %rax; syscall`), syscall qui ne
771// retourne pas (il restaure le contexte). Aucun prologue/épilogue (pas de cadre
772// de pile), aucun accès mémoire propre : conforme au contrat ABI de `sa_restorer`.
773#[cfg(target_arch = "x86_64")]
774core::arch::global_asm!(
775 ".p2align 4",
776 ".globl __air_rt_sigreturn_trampoline",
777 ".hidden __air_rt_sigreturn_trampoline",
778 "__air_rt_sigreturn_trampoline:",
779 "mov rax, 15", // __NR_rt_sigreturn (x86_64)
780 "syscall",
781);
782
783#[cfg(target_arch = "x86_64")]
784unsafe extern "C" {
785 /// Symbole du trampoline `rt_sigreturn` défini par le `global_asm!`
786 /// ci-dessus. Jamais appelé depuis Rust — seule son **adresse** est posée
787 /// dans `sa_restorer`.
788 fn __air_rt_sigreturn_trampoline();
789}
790
791pub mod async_handler {
792 //! Wrappers `rt_sigaction` pour les signaux **non-faute** — handler
793 //! **réellement installé** (cf. ADR-064 : délivrance async réelle ; ADR-066 :
794 //! descellement additif couche 0).
795 //!
796 //! Contrairement à [`synchronous_handler`](super::synchronous_handler)
797 //! (restreint aux 4 fautes, handler **inerte** — le noyau force l'action par
798 //! défaut), ce sous-module installe un `rt_sigaction` **effectif** : le noyau
799 //! détourne le thread vers le handler C à la délivrance d'un signal
800 //! **gérable**. C'est la **fondation dual-face** que la libc (`sigaction`) et
801 //! le PAL consomment via la couche 1 `air-signal`.
802 //!
803 //! **API entièrement `unsafe`** : l'appelant garantit que le handler est
804 //! **async-signal-safe** (`man 7 signal-safety`) — précondition invérifiable
805 //! par le compilateur, portée par l'appelant (identique à
806 //! `synchronous_handler`).
807
808 use air_sys_types::Errno;
809 use air_sys_types::signal::async_handler::{PreviousDisposition, SigActionFlags};
810 use air_sys_types::{Signal, SignalMask};
811
812 use super::{SIGSET_SIZE_U64, errno_from_negative_syscall_ret};
813
814 /// `SA_RESTORER` (`0x0400_0000`) — **x86_64 uniquement**. Signale au noyau que
815 /// `sa_restorer` porte l'adresse du trampoline de retour de handler.
816 #[cfg(target_arch = "x86_64")]
817 const SA_RESTORER: u64 = 0x0400_0000;
818
819 /// Valeur `sa_handler` = `SIG_DFL` (action par défaut du noyau).
820 pub const SIG_DFL: usize = 0;
821 /// Valeur `sa_handler` = `SIG_IGN` (signal ignoré). **Sûr** (constante noyau,
822 /// aucun code utilisateur) — cf. ADR-064 §2.
823 pub const SIG_IGN: usize = 1;
824
825 /// Installe une disposition pour le signal **non-faute** `signal` via
826 /// `rt_sigaction(2)`, et rend la disposition **précédente** (pour
827 /// [`restore`]).
828 ///
829 /// - `handler_sa` : valeur brute de `sa_handler` — [`SIG_DFL`] (`0`),
830 /// [`SIG_IGN`] (`1`), ou l'**adresse** d'une fonction handler
831 /// `extern "C"` (cast `usize`). C'est la couche 1 (`air-signal`) qui
832 /// fournit le type de handler ; la couche 0 reste agnostique (adresse
833 /// brute).
834 /// - `flags` : [`SigActionFlags`] (`SA_SIGINFO`/`SA_RESTART`/…). Sur x86_64,
835 /// le wrapper **ajoute** `SA_RESTORER` et pose `sa_restorer` sur le
836 /// trampoline `rt_sigreturn` d'Air (obligatoire sans libc). Sur aarch64, le
837 /// retour passe par le VDSO — aucun `sa_restorer`.
838 /// - `mask` : signaux **additionnels** bloqués pendant l'exécution du handler.
839 ///
840 /// # Safety
841 ///
842 /// Si `handler_sa` désigne une fonction (≠ [`SIG_DFL`]/[`SIG_IGN`]), l'appelant
843 /// doit garantir qu'elle est **async-signal-safe** (`man 7 signal-safety`) :
844 /// pas de `malloc`, pas de verrou, pas d'I/O bufferisée, etc. Le noyau peut
845 /// l'invoquer à un point **arbitraire** du programme. Poser [`SIG_DFL`] ou
846 /// [`SIG_IGN`] est en soi sûr (aucun code utilisateur), mais l'API reste
847 /// `unsafe` pour une frontière uniforme.
848 ///
849 /// Ce wrapper est réservé aux signaux **non-faute** : installer un handler
850 /// pour `SIGSEGV`/`SIGBUS`/`SIGFPE`/`SIGILL` est le domaine (inerte) de
851 /// [`synchronous_handler`](super::synchronous_handler) (ADR-020/ADR-064).
852 ///
853 /// # Errors
854 ///
855 /// - [`Errno::EINVAL`] : `signal` non-catchable (`SIGKILL`/`SIGSTOP`).
856 pub unsafe fn install(
857 signal: Signal,
858 handler_sa: usize,
859 flags: SigActionFlags,
860 mask: SignalMask,
861 ) -> Result<PreviousDisposition, Errno> {
862 let mut new_sa = KernelSigaction::zeroed();
863 // `usize → u64` : Air ne cible que des arches 64 bits (ADR-014) — même
864 // idiome que `synchronous_handler::install_fatal_handler`
865 // (`handler as usize as u64`), lossless, sans conversion faillible.
866 new_sa.sa_handler = handler_sa as u64;
867 new_sa.sa_mask = mask.bits();
868
869 // Drapeaux : ceux de l'appelant, plus `SA_RESTORER` sur x86_64 (le noyau
870 // a besoin d'un trampoline de retour ; sans libc, Air le fournit).
871 let kernel_flags = flags.bits();
872 #[cfg(target_arch = "x86_64")]
873 {
874 new_sa.sa_flags = kernel_flags | SA_RESTORER;
875 // Adresse du trampoline `rt_sigreturn` (fn item → usize → u64,
876 // 64 bits : lossless).
877 new_sa.sa_restorer = super::__air_rt_sigreturn_trampoline as *const () as usize as u64;
878 }
879 #[cfg(target_arch = "aarch64")]
880 {
881 new_sa.sa_flags = kernel_flags;
882 }
883
884 let mut previous = PreviousDisposition::zeroed();
885 let new_ptr: *const KernelSigaction = &new_sa;
886 let old_ptr: *mut u8 = previous.as_bytes_mut().as_mut_ptr();
887
888 // SAFETY: rt_sigaction(2). Le kernel lit `new_sa` (KernelSigaction valide,
889 // locale) et écrit jusqu'à 32 octets à `old_ptr` (capacité de
890 // `PreviousDisposition`). sigsetsize=8 = taille kernel `sigset_t`. Le
891 // handler éventuel respecte l'async-signal-safety par contrat `unsafe`.
892 let ret = unsafe {
893 raw_syscall_rt_sigaction(
894 signal.as_raw(),
895 new_ptr as u64,
896 old_ptr as u64,
897 SIGSET_SIZE_U64,
898 )
899 };
900 if ret < 0 {
901 return Err(errno_from_negative_syscall_ret(ret));
902 }
903 Ok(previous)
904 }
905
906 /// Restaure la disposition `previous` pour le signal `signal`.
907 ///
908 /// # Safety
909 ///
910 /// `previous` doit provenir d'un [`install`] précédent sur le **même**
911 /// `signal`. Restaurer une disposition d'un autre signal est un comportement
912 /// indéfini côté noyau.
913 ///
914 /// # Errors
915 ///
916 /// - [`Errno::EINVAL`] : `signal` non-catchable (ne se produit pas si
917 /// `previous` vient d'un `install` réussi sur ce signal).
918 pub unsafe fn restore(signal: Signal, previous: PreviousDisposition) -> Result<(), Errno> {
919 let prev_ptr: *const u8 = previous.as_bytes().as_ptr();
920 // SAFETY: rt_sigaction(2). Le kernel lit jusqu'à 32 octets à `prev_ptr`
921 // (KernelSigaction ≤ 32 octets). Pas d'écriture (oldset=NULL).
922 let ret = unsafe {
923 raw_syscall_rt_sigaction(signal.as_raw(), prev_ptr as u64, 0_u64, SIGSET_SIZE_U64)
924 };
925 if ret < 0 {
926 return Err(errno_from_negative_syscall_ret(ret));
927 }
928 Ok(())
929 }
930
931 /// Représentation interne du `struct sigaction` kernel (cf.
932 /// [`synchronous_handler`](super::synchronous_handler) — même layout ABI,
933 /// dupliqué pour ne PAS toucher au sous-module des fautes).
934 ///
935 /// - offset 0 : `sa_handler` (8) ; offset 8 : `sa_flags` (8) ;
936 /// - x86_64 : offset 16 `sa_restorer` (8), offset 24 `sa_mask` (8) — 32 octets ;
937 /// - aarch64 : offset 16 `sa_mask` (8), **pas** de `sa_restorer` — 24 octets.
938 #[repr(C)]
939 #[cfg(target_arch = "x86_64")]
940 struct KernelSigaction {
941 sa_handler: u64,
942 sa_flags: u64,
943 sa_restorer: u64,
944 sa_mask: u64,
945 }
946
947 #[cfg(target_arch = "x86_64")]
948 impl KernelSigaction {
949 const fn zeroed() -> Self {
950 Self {
951 sa_handler: 0,
952 sa_flags: 0,
953 sa_restorer: 0,
954 sa_mask: 0,
955 }
956 }
957 }
958
959 #[repr(C)]
960 #[cfg(target_arch = "aarch64")]
961 struct KernelSigaction {
962 sa_handler: u64,
963 sa_flags: u64,
964 sa_mask: u64,
965 }
966
967 #[cfg(target_arch = "aarch64")]
968 impl KernelSigaction {
969 const fn zeroed() -> Self {
970 Self {
971 sa_handler: 0,
972 sa_flags: 0,
973 sa_mask: 0,
974 }
975 }
976 }
977
978 /// `KernelSigaction` tient dans le buffer 32 octets de `PreviousDisposition`.
979 const _: () = {
980 assert!(core::mem::size_of::<KernelSigaction>() <= 32);
981 };
982
983 #[cfg(target_arch = "x86_64")]
984 #[inline]
985 unsafe fn raw_syscall_rt_sigaction(
986 signum: i32,
987 new_sa: u64,
988 old_sa: u64,
989 sigsetsize: u64,
990 ) -> i64 {
991 let ret: i64;
992 // SAFETY: SYS_rt_sigaction (x86_64 = 13). Le kernel lit `new_sa`
993 // (KernelSigaction, 32 octets), écrit `old_sa` (32 octets) si non-null.
994 unsafe {
995 core::arch::asm!(
996 "syscall",
997 in("rax") 13_i64,
998 in("rdi") i64::from(signum),
999 in("rsi") new_sa,
1000 in("rdx") old_sa,
1001 in("r10") sigsetsize,
1002 lateout("rax") ret,
1003 lateout("rcx") _,
1004 lateout("r11") _,
1005 options(nostack, preserves_flags),
1006 );
1007 }
1008 ret
1009 }
1010
1011 #[cfg(target_arch = "aarch64")]
1012 #[inline]
1013 unsafe fn raw_syscall_rt_sigaction(
1014 signum: i32,
1015 new_sa: u64,
1016 old_sa: u64,
1017 sigsetsize: u64,
1018 ) -> i64 {
1019 let ret: i64;
1020 // SAFETY: SYS_rt_sigaction (aarch64 = 134).
1021 unsafe {
1022 core::arch::asm!(
1023 "svc 0",
1024 in("x8") 134_i64,
1025 inout("x0") i64::from(signum) => ret,
1026 in("x1") new_sa,
1027 in("x2") old_sa,
1028 in("x3") sigsetsize,
1029 options(nostack, preserves_flags),
1030 );
1031 }
1032 ret
1033 }
1034}
1035
1036// ─────────────────────────────────────────────────────────────────────────
1037// asm! wrappers x86_64 / aarch64.
1038// ─────────────────────────────────────────────────────────────────────────
1039
1040#[cfg(target_arch = "x86_64")]
1041#[inline]
1042unsafe fn raw_syscall_signalfd4(fd: i32, mask: u64, sigsetsize: u64, flags: i32) -> i64 {
1043 let ret: i64;
1044 // SAFETY: SYS_signalfd4 (x86_64 = 289). Le kernel lit `*mask` (8
1045 // octets) ; pas d'écriture en mémoire utilisateur (`readonly` OK).
1046 unsafe {
1047 core::arch::asm!(
1048 "syscall",
1049 in("rax") 289_i64,
1050 in("rdi") i64::from(fd),
1051 in("rsi") mask,
1052 in("rdx") sigsetsize,
1053 in("r10") i64::from(flags),
1054 lateout("rax") ret,
1055 lateout("rcx") _,
1056 lateout("r11") _,
1057 options(nostack, preserves_flags, readonly),
1058 );
1059 }
1060 ret
1061}
1062
1063#[cfg(target_arch = "aarch64")]
1064#[inline]
1065unsafe fn raw_syscall_signalfd4(fd: i32, mask: u64, sigsetsize: u64, flags: i32) -> i64 {
1066 let ret: i64;
1067 // SAFETY: SYS_signalfd4 (aarch64 = 74).
1068 unsafe {
1069 core::arch::asm!(
1070 "svc 0",
1071 in("x8") 74_i64,
1072 inout("x0") i64::from(fd) => ret,
1073 in("x1") mask,
1074 in("x2") sigsetsize,
1075 in("x3") i64::from(flags),
1076 options(nostack, preserves_flags, readonly),
1077 );
1078 }
1079 ret
1080}
1081
1082#[cfg(target_arch = "x86_64")]
1083#[inline]
1084unsafe fn raw_syscall_rt_sigprocmask(how: i32, set: u64, oldset: u64, sigsetsize: u64) -> i64 {
1085 let ret: i64;
1086 // SAFETY: SYS_rt_sigprocmask (x86_64 = 14). Le kernel lit `*set` si
1087 // non-null (8 octets) ; écrit `*oldset` si non-null (8 octets). Pas
1088 // de `readonly` (écriture sur oldset).
1089 unsafe {
1090 core::arch::asm!(
1091 "syscall",
1092 in("rax") 14_i64,
1093 in("rdi") i64::from(how),
1094 in("rsi") set,
1095 in("rdx") oldset,
1096 in("r10") sigsetsize,
1097 lateout("rax") ret,
1098 lateout("rcx") _,
1099 lateout("r11") _,
1100 options(nostack, preserves_flags),
1101 );
1102 }
1103 ret
1104}
1105
1106#[cfg(target_arch = "aarch64")]
1107#[inline]
1108unsafe fn raw_syscall_rt_sigprocmask(how: i32, set: u64, oldset: u64, sigsetsize: u64) -> i64 {
1109 let ret: i64;
1110 // SAFETY: SYS_rt_sigprocmask (aarch64 = 135).
1111 unsafe {
1112 core::arch::asm!(
1113 "svc 0",
1114 in("x8") 135_i64,
1115 inout("x0") i64::from(how) => ret,
1116 in("x1") set,
1117 in("x2") oldset,
1118 in("x3") sigsetsize,
1119 options(nostack, preserves_flags),
1120 );
1121 }
1122 ret
1123}
1124
1125#[cfg(target_arch = "x86_64")]
1126#[inline]
1127unsafe fn raw_syscall_sigaltstack(new: u64, old: u64) -> i64 {
1128 let ret: i64;
1129 // SAFETY: SYS_sigaltstack (x86_64 = 131). Le kernel **lit** `*new` (24 octets) si
1130 // `new != 0` et **écrit** `*old` (24 octets) si `old != 0` — pas de `readonly`.
1131 unsafe {
1132 core::arch::asm!(
1133 "syscall",
1134 in("rax") 131_i64,
1135 in("rdi") new,
1136 in("rsi") old,
1137 lateout("rax") ret,
1138 lateout("rcx") _,
1139 lateout("r11") _,
1140 options(nostack, preserves_flags),
1141 );
1142 }
1143 ret
1144}
1145
1146#[cfg(target_arch = "aarch64")]
1147#[inline]
1148unsafe fn raw_syscall_sigaltstack(new: u64, old: u64) -> i64 {
1149 let ret: i64;
1150 // SAFETY: SYS_sigaltstack (aarch64 = 132). Mêmes contrats mémoire que x86_64.
1151 unsafe {
1152 core::arch::asm!(
1153 "svc 0",
1154 in("x8") 132_i64,
1155 inout("x0") new => ret,
1156 in("x1") old,
1157 options(nostack, preserves_flags),
1158 );
1159 }
1160 ret
1161}
1162
1163#[cfg(target_arch = "x86_64")]
1164#[inline]
1165unsafe fn raw_syscall_rt_sigpending(set: u64, sigsetsize: u64) -> i64 {
1166 let ret: i64;
1167 // SAFETY: SYS_rt_sigpending (x86_64 = 127). Le kernel **écrit** 8 octets à
1168 // `set` (sigset_t) ; pas de `readonly`.
1169 unsafe {
1170 core::arch::asm!(
1171 "syscall",
1172 in("rax") 127_i64,
1173 in("rdi") set,
1174 in("rsi") sigsetsize,
1175 lateout("rax") ret,
1176 lateout("rcx") _,
1177 lateout("r11") _,
1178 options(nostack, preserves_flags),
1179 );
1180 }
1181 ret
1182}
1183
1184#[cfg(target_arch = "aarch64")]
1185#[inline]
1186unsafe fn raw_syscall_rt_sigpending(set: u64, sigsetsize: u64) -> i64 {
1187 let ret: i64;
1188 // SAFETY: SYS_rt_sigpending (aarch64 = 136).
1189 unsafe {
1190 core::arch::asm!(
1191 "svc 0",
1192 in("x8") 136_i64,
1193 inout("x0") set => ret,
1194 in("x1") sigsetsize,
1195 options(nostack, preserves_flags),
1196 );
1197 }
1198 ret
1199}
1200
1201#[cfg(target_arch = "x86_64")]
1202#[inline]
1203unsafe fn raw_syscall_kill(pid: i32, sig: i32) -> i64 {
1204 let ret: i64;
1205 // SAFETY: SYS_kill (x86_64 = 62). Pas d'accès mémoire utilisateur.
1206 unsafe {
1207 core::arch::asm!(
1208 "syscall",
1209 in("rax") 62_i64,
1210 in("rdi") i64::from(pid),
1211 in("rsi") i64::from(sig),
1212 lateout("rax") ret,
1213 lateout("rcx") _,
1214 lateout("r11") _,
1215 options(nostack, preserves_flags, readonly),
1216 );
1217 }
1218 ret
1219}
1220
1221#[cfg(target_arch = "aarch64")]
1222#[inline]
1223unsafe fn raw_syscall_kill(pid: i32, sig: i32) -> i64 {
1224 let ret: i64;
1225 // SAFETY: SYS_kill (aarch64 = 129).
1226 unsafe {
1227 core::arch::asm!(
1228 "svc 0",
1229 in("x8") 129_i64,
1230 inout("x0") i64::from(pid) => ret,
1231 in("x1") i64::from(sig),
1232 options(nostack, preserves_flags, readonly),
1233 );
1234 }
1235 ret
1236}
1237
1238#[cfg(target_arch = "x86_64")]
1239#[inline]
1240unsafe fn raw_syscall_tgkill(tgid: i32, tid: i32, sig: i32) -> i64 {
1241 let ret: i64;
1242 // SAFETY: SYS_tgkill (x86_64 = 234). Pas d'accès mémoire utilisateur.
1243 unsafe {
1244 core::arch::asm!(
1245 "syscall",
1246 in("rax") 234_i64,
1247 in("rdi") i64::from(tgid),
1248 in("rsi") i64::from(tid),
1249 in("rdx") i64::from(sig),
1250 lateout("rax") ret,
1251 lateout("rcx") _,
1252 lateout("r11") _,
1253 options(nostack, preserves_flags, readonly),
1254 );
1255 }
1256 ret
1257}
1258
1259#[cfg(target_arch = "aarch64")]
1260#[inline]
1261unsafe fn raw_syscall_tgkill(tgid: i32, tid: i32, sig: i32) -> i64 {
1262 let ret: i64;
1263 // SAFETY: SYS_tgkill (aarch64 = 131).
1264 unsafe {
1265 core::arch::asm!(
1266 "svc 0",
1267 in("x8") 131_i64,
1268 inout("x0") i64::from(tgid) => ret,
1269 in("x1") i64::from(tid),
1270 in("x2") i64::from(sig),
1271 options(nostack, preserves_flags, readonly),
1272 );
1273 }
1274 ret
1275}
1276
1277#[cfg(target_arch = "x86_64")]
1278#[inline]
1279unsafe fn raw_syscall_rt_sigqueueinfo(pid: i32, sig: i32, info: u64) -> i64 {
1280 let ret: i64;
1281 // SAFETY: SYS_rt_sigqueueinfo (x86_64 = 129). Le kernel lit 128
1282 // octets à `info` (siginfo_t). Pas d'écriture user (`readonly`).
1283 unsafe {
1284 core::arch::asm!(
1285 "syscall",
1286 in("rax") 129_i64,
1287 in("rdi") i64::from(pid),
1288 in("rsi") i64::from(sig),
1289 in("rdx") info,
1290 lateout("rax") ret,
1291 lateout("rcx") _,
1292 lateout("r11") _,
1293 options(nostack, preserves_flags, readonly),
1294 );
1295 }
1296 ret
1297}
1298
1299#[cfg(target_arch = "aarch64")]
1300#[inline]
1301unsafe fn raw_syscall_rt_sigqueueinfo(pid: i32, sig: i32, info: u64) -> i64 {
1302 let ret: i64;
1303 // SAFETY: SYS_rt_sigqueueinfo (aarch64 = 138).
1304 unsafe {
1305 core::arch::asm!(
1306 "svc 0",
1307 in("x8") 138_i64,
1308 inout("x0") i64::from(pid) => ret,
1309 in("x1") i64::from(sig),
1310 in("x2") info,
1311 options(nostack, preserves_flags, readonly),
1312 );
1313 }
1314 ret
1315}
1316
1317#[cfg(target_arch = "x86_64")]
1318#[inline]
1319unsafe fn raw_syscall_read(fd: i32, buffer: u64, count: u64) -> i64 {
1320 let ret: i64;
1321 // SAFETY: SYS_read (x86_64 = 0). Le kernel écrit jusqu'à `count`
1322 // octets à `buffer`. Pour signalfd, exactement 128 octets pour un
1323 // événement complet.
1324 unsafe {
1325 core::arch::asm!(
1326 "syscall",
1327 in("rax") 0_i64,
1328 in("rdi") i64::from(fd),
1329 in("rsi") buffer,
1330 in("rdx") count,
1331 lateout("rax") ret,
1332 lateout("rcx") _,
1333 lateout("r11") _,
1334 options(nostack, preserves_flags),
1335 );
1336 }
1337 ret
1338}
1339
1340#[cfg(target_arch = "aarch64")]
1341#[inline]
1342unsafe fn raw_syscall_read(fd: i32, buffer: u64, count: u64) -> i64 {
1343 let ret: i64;
1344 // SAFETY: SYS_read (aarch64 = 63).
1345 unsafe {
1346 core::arch::asm!(
1347 "svc 0",
1348 in("x8") 63_i64,
1349 inout("x0") i64::from(fd) => ret,
1350 in("x1") buffer,
1351 in("x2") count,
1352 options(nostack, preserves_flags),
1353 );
1354 }
1355 ret
1356}
1357
1358// ─────────────────────────────────────────────────────────────────────────
1359// Helper de conversion errno (réplique de celui de process.rs ; un
1360// futur refactor pourra centraliser).
1361// ─────────────────────────────────────────────────────────────────────────
1362
1363fn errno_from_negative_syscall_ret(ret: i64) -> Errno {
1364 debug_assert!(ret < 0 && ret > -4096);
1365 #[allow(clippy::cast_possible_truncation)]
1366 let raw = ret.wrapping_neg() as i32;
1367 let nz = NonZeroI32::new(raw).expect("errno strictement positif par construction");
1368 Errno::from_nonzero(nz)
1369}
1370
1371// ─────────────────────────────────────────────────────────────────────────
1372// Tests.
1373// ─────────────────────────────────────────────────────────────────────────
1374
1375#[cfg(test)]
1376mod tests {
1377 use super::*;
1378
1379 use crate::process::{clone3, getpid, gettid, waitid};
1380 use air_sys_types::signal::async_handler::SigActionFlags;
1381 use air_sys_types::{
1382 CloneArgs, CloneFlags, CloneResult, SignalValue, WaitEvent, WaitOptions, WaitTarget,
1383 };
1384 use core::ffi::{c_int, c_void};
1385 use core::sync::atomic::{AtomicBool, Ordering};
1386 use std::sync::Mutex;
1387
1388 /// Sérialise les tests qui installent un handler **process-wide** : sans ce
1389 /// verrou, deux tests parallèles s'écraseraient mutuellement la disposition
1390 /// du signal (le handler est un état par-process). Empoisonnement toléré (on
1391 /// récupère l'inner) : un panic d'un test précédent ne doit pas bloquer les
1392 /// suivants.
1393 static ASYNC_INSTALL_LOCK: Mutex<()> = Mutex::new(());
1394
1395 /// Drapeau armé par [`async_test_handler`] : prouve que le noyau a **appelé**
1396 /// le handler async (délivrance réelle, ADR-066).
1397 static ASYNC_HANDLER_FIRED: AtomicBool = AtomicBool::new(false);
1398
1399 /// Handler async de test — **async-signal-safe** (un unique store atomique,
1400 /// aucune allocation, aucune I/O). Installé pour `SIGUSR1` et déclenché par
1401 /// auto-`tgkill` (chemin déterministe).
1402 extern "C" fn async_test_handler(_signum: c_int, _info: *mut c_void, _context: *mut c_void) {
1403 ASYNC_HANDLER_FIRED.store(true, Ordering::SeqCst);
1404 }
1405
1406 /// Flush du profil de couverture LLVM de l'enfant forké **avant**
1407 /// `exit_group` (qui court-circuite l'atexit LLVM). Sans ce flush, le
1408 /// corps des wrappers exercés dans l'enfant (install/restore handler,
1409 /// signalfd, kill, rt_sigqueueinfo…) apparaît non couvert.
1410 /// `LLVM_PROFILE_FILE` contient `%p` → un `.profraw` par enfant, fusionné.
1411 /// Aucun verrou I/O dans cette famille → le flush en fin d'enfant suffit.
1412 // Intentionnellement VIDE — **async-signal-safety**. Un enfant forké via
1413 // `clone3` hérite d'un espace d'adressage COPIÉ : si un thread frère tenait le
1414 // lock interne de `malloc` au moment du `clone3`, ce lock est copié VERROUILLÉ
1415 // (son détenteur n'existe pas dans l'enfant) ; `__llvm_profile_write_file`
1416 // **alloue** → le premier `malloc` de l'enfant deadlocke sur ce futex. On NE
1417 // flushe donc PAS dans l'enfant : ses lignes sont des exceptions **CHILD-EXIT**
1418 // (ADR-035), prouvées par le code de sortie observé via `waitid`. NE PAS
1419 // réintroduire de flush ici (flaky deadlock couche 0).
1420 fn flush_child_coverage() {}
1421
1422 /// Sortie d'un enfant forké de test : termine via `exit_group`. NE FLUSHE PLUS
1423 /// le profil (cf. [`flush_child_coverage`], désormais vide pour rester
1424 /// async-signal-safe). Les lignes de l'enfant sont des exceptions CHILD-EXIT.
1425 fn child_exit(status: i32) -> ! {
1426 flush_child_coverage();
1427 crate::process::exit_group(status)
1428 }
1429
1430 /// Guard RAII : capture le mask courant du thread à la construction,
1431 /// le restaure intégralement à la destruction via `SIG_SETMASK`.
1432 ///
1433 /// **Utilisation systématique en TOUTE PREMIÈRE LIGNE** de chaque
1434 /// test qui touche au mask de signaux (sinon contamination du
1435 /// worker thread cargo pour les tests suivants).
1436 struct MaskGuard {
1437 original: SignalMask,
1438 }
1439 impl MaskGuard {
1440 fn new() -> Self {
1441 Self {
1442 original: current_signal_mask().expect("current_signal_mask doit réussir"),
1443 }
1444 }
1445 }
1446 impl Drop for MaskGuard {
1447 fn drop(&mut self) {
1448 // SIG_SETMASK = remplacement TOTAL (jamais SIG_BLOCK/UNBLOCK
1449 // relatif). C'est le seul moyen de restaurer fidèlement le
1450 // mask initial.
1451 let _ = set_signal_mask(&self.original);
1452 }
1453 }
1454
1455 // ── current_signal_mask / set_signal_mask ─────────────────────────
1456
1457 #[test]
1458 fn current_signal_mask_returns_ok() {
1459 let _guard = MaskGuard::new();
1460 let _mask = current_signal_mask().expect("current_signal_mask doit réussir");
1461 }
1462
1463 #[test]
1464 fn mask_block_unblock_round_trip() {
1465 let _guard = MaskGuard::new();
1466 let target = SignalMask::from_signals(&[Signal::SIGUSR1]);
1467
1468 let before = current_signal_mask().expect("read");
1469 assert!(
1470 !before.contains(Signal::SIGUSR1),
1471 "SIGUSR1 ne doit pas être bloqué initialement"
1472 );
1473
1474 let _previous = block_signals(&target).expect("block");
1475 let after_block = current_signal_mask().expect("read");
1476 assert!(
1477 after_block.contains(Signal::SIGUSR1),
1478 "SIGUSR1 doit être bloqué après block_signals"
1479 );
1480
1481 let _previous = unblock_signals(&target).expect("unblock");
1482 let after_unblock = current_signal_mask().expect("read");
1483 assert!(!after_unblock.contains(Signal::SIGUSR1));
1484 }
1485
1486 #[test]
1487 fn set_signal_mask_replaces_total() {
1488 let _guard = MaskGuard::new();
1489 let mask = SignalMask::from_signals(&[Signal::SIGUSR1]);
1490 let _old = set_signal_mask(&mask).expect("set");
1491 let read_back = current_signal_mask().expect("read");
1492 assert_eq!(read_back.bits(), mask.bits());
1493 }
1494
1495 // ── signalfd + delivery via tgkill (per-thread isolation) ─────────
1496
1497 #[test]
1498 fn signalfd_blocking_create_and_read_via_tgkill_self_tid() {
1499 let _guard = MaskGuard::new();
1500 let mask = SignalMask::from_signals(&[Signal::SIGUSR1]);
1501 let sfd = signalfd_create_blocking(&mask, SignalFdFlags::empty())
1502 .expect("signalfd_create_blocking");
1503
1504 // tgkill cible CE thread précisément ; pas de routage à un autre
1505 // thread = déterminisme + isolation des workers cargo.
1506 tgkill(getpid(), gettid(), Some(Signal::SIGUSR1)).expect("tgkill");
1507 let info = sfd.read().expect("read signalfd");
1508 assert_eq!(info.signal, Signal::SIGUSR1);
1509 }
1510
1511 #[test]
1512 fn signalfd_update_mask_after_creation() {
1513 let _guard = MaskGuard::new();
1514 let initial = SignalMask::from_signals(&[Signal::SIGUSR1]);
1515 let mut sfd = signalfd_create_blocking(&initial, SignalFdFlags::empty()).expect("create");
1516
1517 let updated = SignalMask::from_signals(&[Signal::SIGUSR1, Signal::SIGCHLD]);
1518 block_signals(&updated).expect("block SIGCHLD too");
1519 sfd.update_mask(&updated).expect("update_mask");
1520 // Pas de delivery test ici — on vérifie juste que le syscall
1521 // update_mask réussit sans erreur. Le read suivant exercerait
1522 // une délivrance, déjà couvert par le test précédent.
1523 }
1524
1525 #[test]
1526 fn signalfd_into_fd_returns_owned_fd() {
1527 let _guard = MaskGuard::new();
1528 let mask = SignalMask::from_signals(&[Signal::SIGUSR1]);
1529 let sfd = signalfd_create(&mask, SignalFdFlags::empty()).expect("create");
1530 let _owned: OwnedFd = sfd.into_fd();
1531 }
1532
1533 #[test]
1534 fn signalfd_as_fd_borrows() {
1535 let _guard = MaskGuard::new();
1536 let mask = SignalMask::from_signals(&[Signal::SIGUSR1]);
1537 let sfd = signalfd_create(&mask, SignalFdFlags::empty()).expect("create");
1538 let _borrowed = sfd.as_fd();
1539 }
1540
1541 // ── wait_for_signal ───────────────────────────────────────────────
1542
1543 #[test]
1544 fn wait_for_signal_bloquant_recoit_sigusr1() {
1545 let _guard = MaskGuard::new();
1546 let mask = SignalMask::from_signals(&[Signal::SIGUSR1]);
1547 block_signals(&mask).expect("block");
1548 tgkill(getpid(), gettid(), Some(Signal::SIGUSR1)).expect("tgkill");
1549 let info = wait_for_signal(&mask, None).expect("wait_for_signal");
1550 assert_eq!(info.signal, Signal::SIGUSR1);
1551 }
1552
1553 #[test]
1554 fn wait_for_signal_with_timeout_returns_einval_not_yet_implemented() {
1555 let _guard = MaskGuard::new();
1556 let mask = SignalMask::from_signals(&[Signal::SIGUSR1]);
1557 let err = wait_for_signal(&mask, Some(Duration::from_millis(10)))
1558 .expect_err("timeout pas encore supporté");
1559 assert_eq!(err, Errno::EINVAL);
1560 }
1561
1562 // ── kill / tgkill / signal 0 (Option<Signal> None) ────────────────
1563
1564 #[test]
1565 fn kill_signal_none_tests_process_existence() {
1566 // signal=None ⇔ "signal 0" = test d'existence sans envoi.
1567 // Sur self, doit réussir.
1568 kill(getpid(), None).expect("kill self avec signal 0 doit réussir");
1569 }
1570
1571 #[test]
1572 fn kill_on_nonexistent_pid_returns_esrch() {
1573 let nope = Pid::try_from_raw(0x7FFF_FFFE).expect("pid littéral");
1574 let err = kill(nope, None).expect_err("kill PID inexistant");
1575 assert_eq!(err, Errno::ESRCH);
1576 }
1577
1578 #[test]
1579 fn kill_process_group_self_and_nonexistent() {
1580 // `group = None` + `signal = None` : test d'existence du **groupe de l'appelant**
1581 // (aucun signal envoyé) ⇒ succès.
1582 kill_process_group(None, None).expect("groupe de l'appelant existe");
1583 // Groupe **inexistant** (id positif jamais alloué) ⇒ `ESRCH`.
1584 let nope = Pid::try_from_raw(0x7FFF_FFFE).expect("pgid littéral");
1585 assert_eq!(
1586 kill_process_group(Some(nope), None).expect_err("groupe inexistant"),
1587 Errno::ESRCH
1588 );
1589 }
1590
1591 #[test]
1592 #[cfg_attr(miri, ignore = "syscalls non supportés par Miri")]
1593 fn sigaltstack_query_install_disable_and_reject_small() {
1594 // Interroge la pile alternative **actuelle** (`old` seul) — succès (chemin `old`).
1595 let mut current = AltStack::disabled();
1596 sigaltstack(None, Some(&mut current)).expect("interroge la pile alternative");
1597
1598 // Installe une pile alternative de **16 Kio** (> `MINSIGSTKSZ`) et récupère l'ancienne.
1599 let mut buffer = vec![0_u8; 16384];
1600 let stack = AltStack::new(buffer.as_mut_ptr(), buffer.len());
1601 let mut previous = AltStack::disabled();
1602 sigaltstack(Some(&stack), Some(&mut previous)).expect("installe la pile alternative");
1603
1604 // **Désactive** (restaure l'état du thread) AVANT que `buffer` ne soit libéré.
1605 sigaltstack(Some(&AltStack::disabled()), None).expect("désactive la pile alternative");
1606
1607 // Taille **trop petite** (8 octets < `MINSIGSTKSZ`) ⇒ erreur (chemin `Err`) :
1608 // `ENOMEM` sur les kernels récents, `EINVAL` sur les anciens.
1609 let mut tiny = [0_u8; 8];
1610 let small = AltStack::new(tiny.as_mut_ptr(), tiny.len());
1611 let err = sigaltstack(Some(&small), None).expect_err("pile trop petite");
1612 assert!(
1613 err == Errno::ENOMEM || err == Errno::EINVAL,
1614 "attendu ENOMEM/EINVAL, obtenu : {err:?}"
1615 );
1616 }
1617
1618 #[test]
1619 fn tgkill_signal_none_to_self_tid_succeeds() {
1620 // signal=None sur self_tid = test d'existence.
1621 tgkill(getpid(), gettid(), None).expect("tgkill self avec signal 0");
1622 }
1623
1624 #[test]
1625 fn tgkill_on_nonexistent_tid_returns_esrch() {
1626 // Couvre la branche d'erreur (`ret < 0`) de `tgkill` : un tid
1627 // au-delà de `PID_MAX_LIMIT` (~2^22) n'existe pas → ESRCH.
1628 let nope = Tid::try_from_raw(0x7FFF_FFFE).expect("tid littéral");
1629 let err = tgkill(getpid(), nope, None).expect_err("tgkill TID inexistant");
1630 assert_eq!(err, Errno::ESRCH);
1631 }
1632
1633 #[test]
1634 fn signalfd_nonblocking_read_with_no_pending_signal_returns_eagain() {
1635 // Couvre la branche d'erreur (`ret < 0`) de `SignalFd::read` : un
1636 // signalfd NONBLOCK sans signal pendant retourne EAGAIN
1637 // immédiatement (aucun signal n'est bloqué/envoyé ici).
1638 let _guard = MaskGuard::new();
1639 let mask = SignalMask::from_signals(&[Signal::SIGUSR1]);
1640 let sfd = signalfd_create(&mask, SignalFdFlags::NONBLOCK).expect("create nonblock");
1641 let err = sfd.read().expect_err("aucun signal pendant → EAGAIN");
1642 assert_eq!(err, Errno::EAGAIN);
1643 }
1644
1645 #[test]
1646 fn tgkill_delivers_sigusr1_to_self_tid() {
1647 let _guard = MaskGuard::new();
1648 let mask = SignalMask::from_signals(&[Signal::SIGUSR1]);
1649 let sfd = signalfd_create_blocking(&mask, SignalFdFlags::empty()).expect("create");
1650 tgkill(getpid(), gettid(), Some(Signal::SIGUSR1)).expect("tgkill");
1651 let info = sfd.read().expect("read");
1652 assert_eq!(info.signal, Signal::SIGUSR1);
1653 }
1654
1655 #[test]
1656 fn kill_in_forked_child_delivers_signal_to_self() {
1657 // `kill(getpid(), sig)` est dangereux en multi-thread (routing
1658 // non contrôlé). En enfant fork'é mono-thread, c'est sûr.
1659 let args = CloneArgs {
1660 flags: CloneFlags::empty(),
1661 exit_signal: Some(Signal::SIGCHLD),
1662 stack: None,
1663 child_tid: None,
1664 parent_tid: None,
1665 tls: None,
1666 };
1667 // SAFETY: fork classique.
1668 let result = unsafe { clone3(&args) }.expect("clone3");
1669 match result {
1670 CloneResult::Child => {
1671 // Enfant : block SIGUSR1, create signalfd, kill(self),
1672 // read, exit avec code 0 si OK.
1673 let mask = SignalMask::from_signals(&[Signal::SIGUSR1]);
1674 if block_signals(&mask).is_err() {
1675 child_exit(50);
1676 }
1677 let sfd = match signalfd_create(&mask, SignalFdFlags::empty()) {
1678 Ok(s) => s,
1679 Err(_) => child_exit(51),
1680 };
1681 if kill(getpid(), Some(Signal::SIGUSR1)).is_err() {
1682 child_exit(52);
1683 }
1684 match sfd.read() {
1685 Ok(info) if info.signal == Signal::SIGUSR1 => child_exit(0),
1686 Ok(_) => child_exit(53),
1687 Err(_) => child_exit(54),
1688 }
1689 }
1690 CloneResult::Parent { child_pid, .. } => {
1691 let status =
1692 waitid(WaitTarget::Pid(child_pid), WaitOptions::EXITED).expect("waitid");
1693 let s = status.expect("event");
1694 assert!(
1695 matches!(s.event, WaitEvent::Exited { code: 0 }),
1696 "kill in forked child should succeed, event={:?}",
1697 s.event
1698 );
1699 }
1700 }
1701 }
1702
1703 // ── rt_sigqueueinfo ────────────────────────────────────────────────
1704
1705 #[test]
1706 fn rt_sigqueueinfo_with_integer_value_in_forked_child() {
1707 // `rt_sigqueueinfo` cible un PROCESS (`pid`), pas un thread.
1708 // En multi-thread (cargo test workers parallèles), le kernel
1709 // route le signal vers un thread qui ne le bloque pas → un
1710 // worker collatéral pourrait recevoir SIGUSR1 et terminer le
1711 // process de test. Isolation par fork-enfant mono-thread.
1712 let args = CloneArgs {
1713 flags: CloneFlags::empty(),
1714 exit_signal: Some(Signal::SIGCHLD),
1715 stack: None,
1716 child_tid: None,
1717 parent_tid: None,
1718 tls: None,
1719 };
1720 // SAFETY: fork classique.
1721 let result = unsafe { clone3(&args) }.expect("clone3");
1722 match result {
1723 CloneResult::Child => {
1724 let mask = SignalMask::from_signals(&[Signal::SIGUSR1]);
1725 if block_signals(&mask).is_err() {
1726 child_exit(70);
1727 }
1728 let sfd = match signalfd_create(&mask, SignalFdFlags::empty()) {
1729 Ok(s) => s,
1730 Err(_) => child_exit(71),
1731 };
1732 let info = SignalInfo::new_queue(SignalValue::Integer(0xABCD));
1733 if rt_sigqueueinfo(getpid(), Signal::SIGUSR1, &info).is_err() {
1734 child_exit(72);
1735 }
1736 match sfd.read() {
1737 Ok(e) if e.signal == Signal::SIGUSR1 && e.int == 0xABCD && e.code == -1 => {
1738 child_exit(0);
1739 }
1740 Ok(_) => child_exit(73),
1741 Err(_) => child_exit(74),
1742 }
1743 }
1744 CloneResult::Parent { child_pid, .. } => {
1745 let status =
1746 waitid(WaitTarget::Pid(child_pid), WaitOptions::EXITED).expect("waitid");
1747 let s = status.expect("event");
1748 assert!(
1749 matches!(s.event, WaitEvent::Exited { code: 0 }),
1750 "rt_sigqueueinfo Integer doit aboutir, event={:?}",
1751 s.event
1752 );
1753 }
1754 }
1755 }
1756
1757 #[test]
1758 fn rt_sigqueueinfo_with_pointer_value_in_forked_child() {
1759 let args = CloneArgs {
1760 flags: CloneFlags::empty(),
1761 exit_signal: Some(Signal::SIGCHLD),
1762 stack: None,
1763 child_tid: None,
1764 parent_tid: None,
1765 tls: None,
1766 };
1767 // SAFETY: fork classique.
1768 let result = unsafe { clone3(&args) }.expect("clone3");
1769 match result {
1770 CloneResult::Child => {
1771 let mask = SignalMask::from_signals(&[Signal::SIGUSR1]);
1772 if block_signals(&mask).is_err() {
1773 child_exit(80);
1774 }
1775 let sfd = match signalfd_create(&mask, SignalFdFlags::empty()) {
1776 Ok(s) => s,
1777 Err(_) => child_exit(81),
1778 };
1779 let payload: u64 = 0xCAFE_BABE_DEAD_BEEF;
1780 let info = SignalInfo::new_queue(SignalValue::Pointer(payload));
1781 if rt_sigqueueinfo(getpid(), Signal::SIGUSR1, &info).is_err() {
1782 child_exit(82);
1783 }
1784 match sfd.read() {
1785 Ok(e) if e.signal == Signal::SIGUSR1 && e.ptr == payload && e.code == -1 => {
1786 child_exit(0);
1787 }
1788 Ok(_) => child_exit(83),
1789 Err(_) => child_exit(84),
1790 }
1791 }
1792 CloneResult::Parent { child_pid, .. } => {
1793 let status =
1794 waitid(WaitTarget::Pid(child_pid), WaitOptions::EXITED).expect("waitid");
1795 let s = status.expect("event");
1796 assert!(
1797 matches!(s.event, WaitEvent::Exited { code: 0 }),
1798 "rt_sigqueueinfo Pointer doit aboutir, event={:?}",
1799 s.event
1800 );
1801 }
1802 }
1803 }
1804
1805 #[test]
1806 fn rt_sigqueueinfo_on_nonexistent_pid_returns_esrch() {
1807 let nope = Pid::try_from_raw(0x7FFF_FFFE).expect("pid littéral");
1808 let info = SignalInfo::new_queue(SignalValue::Integer(0));
1809 let err = rt_sigqueueinfo(nope, Signal::SIGUSR1, &info)
1810 .expect_err("rt_sigqueueinfo PID inexistant");
1811 assert_eq!(err, Errno::ESRCH);
1812 }
1813
1814 // ── synchronous_handler ────────────────────────────────────────────
1815
1816 #[test]
1817 fn install_and_restore_fatal_handler_in_forked_child() {
1818 // Les handlers sont PROCESS-WIDE. Pour ne pas contaminer le
1819 // process de test, on installe/restaure dans un enfant fork'é.
1820 use air_sys_types::signal::synchronous_handler::FatalSignal;
1821 use synchronous_handler::noop_handler;
1822
1823 let args = CloneArgs {
1824 flags: CloneFlags::empty(),
1825 exit_signal: Some(Signal::SIGCHLD),
1826 stack: None,
1827 child_tid: None,
1828 parent_tid: None,
1829 tls: None,
1830 };
1831 // SAFETY: fork classique.
1832 let result = unsafe { clone3(&args) }.expect("clone3");
1833 match result {
1834 CloneResult::Child => {
1835 // SAFETY: noop_handler est trivialement async-signal-safe
1836 // (ne fait rien).
1837 let previous = unsafe {
1838 synchronous_handler::install_fatal_handler(FatalSignal::Segv, noop_handler)
1839 };
1840 let Ok(previous) = previous else {
1841 child_exit(60);
1842 };
1843 // Restaurer immédiatement (pas de signal déclenché, on
1844 // valide juste la roundtrip d'install/restore).
1845 // SAFETY: previous a été obtenu par install_fatal_handler
1846 // sur le même FatalSignal::Segv.
1847 if unsafe { synchronous_handler::restore_handler(FatalSignal::Segv, previous) }
1848 .is_err()
1849 {
1850 child_exit(61);
1851 }
1852 child_exit(0);
1853 }
1854 CloneResult::Parent { child_pid, .. } => {
1855 let status =
1856 waitid(WaitTarget::Pid(child_pid), WaitOptions::EXITED).expect("waitid");
1857 let s = status.expect("event");
1858 assert!(
1859 matches!(s.event, WaitEvent::Exited { code: 0 }),
1860 "install + restore fatal handler doit réussir, event={:?}",
1861 s.event
1862 );
1863 }
1864 }
1865 }
1866
1867 #[test]
1868 fn noop_handler_is_callable_directly() {
1869 // `noop_handler` n'est jamais invoqué par le kernel dans les tests
1870 // (aucun signal fatal n'est déclenché — hors périmètre). Son corps
1871 // (vide, trivialement async-signal-safe) est couvert en l'appelant
1872 // directement avec des pointeurs nuls qu'il ne déréférence pas.
1873 use synchronous_handler::noop_handler;
1874 // SAFETY: `noop_handler` a un corps vide ; il ne lit ni n'écrit
1875 // aucun des pointeurs passés. Appel trivialement sûr.
1876 unsafe {
1877 noop_handler(0, core::ptr::null_mut(), core::ptr::null_mut());
1878 }
1879 }
1880
1881 // ── errno_from_negative_syscall_ret (réplique locale) ─────────────
1882
1883 #[test]
1884 fn errno_from_negative_syscall_ret_maps_known_codes() {
1885 assert_eq!(errno_from_negative_syscall_ret(-1), Errno::EPERM);
1886 assert_eq!(errno_from_negative_syscall_ret(-3), Errno::ESRCH);
1887 assert_eq!(errno_from_negative_syscall_ret(-4), Errno::EINTR);
1888 assert_eq!(errno_from_negative_syscall_ret(-22), Errno::EINVAL);
1889 }
1890
1891 // Les deux tests suivants couvrent les deux opérandes du
1892 // `debug_assert!(ret < 0 && ret > -4096)` : chacun force un côté faux
1893 // (`ret == 0` viole `ret < 0` ; `ret == -5000` viole `ret > -4096`).
1894 #[test]
1895 #[should_panic(expected = "ret < 0 && ret > -4096")]
1896 fn errno_from_negative_syscall_ret_panics_on_non_negative() {
1897 let _ = errno_from_negative_syscall_ret(0);
1898 }
1899
1900 #[test]
1901 #[should_panic(expected = "ret < 0 && ret > -4096")]
1902 fn errno_from_negative_syscall_ret_panics_below_errno_range() {
1903 let _ = errno_from_negative_syscall_ret(-5000);
1904 }
1905
1906 // ── parse_signalfd_siginfo : unit-tests sur buffer synthétique ────
1907
1908 #[test]
1909 fn parse_signalfd_siginfo_decodes_sigusr1_event() {
1910 let mut buffer = [0_u8; 128];
1911 // ssi_signo = SIGUSR1 = 10 à offset 0.
1912 buffer[0..4].copy_from_slice(&10_u32.to_ne_bytes());
1913 // ssi_pid = 1234 à offset 12.
1914 buffer[12..16].copy_from_slice(&1234_u32.to_ne_bytes());
1915 // ssi_uid = 1000 à offset 16.
1916 buffer[16..20].copy_from_slice(&1000_u32.to_ne_bytes());
1917 // ssi_int = 0xABCD à offset 44.
1918 buffer[44..48].copy_from_slice(&0xABCD_i32.to_ne_bytes());
1919
1920 let info = parse_signalfd_siginfo(&buffer);
1921 assert_eq!(info.signal, Signal::SIGUSR1);
1922 assert_eq!(info.pid.map(|p| p.as_raw()), Some(1234));
1923 assert_eq!(info.uid, 1000);
1924 assert_eq!(info.int, 0xABCD);
1925 }
1926
1927 #[test]
1928 fn parse_signalfd_siginfo_kernel_origin_pid_is_none() {
1929 let mut buffer = [0_u8; 128];
1930 buffer[0..4].copy_from_slice(&10_u32.to_ne_bytes()); // SIGUSR1
1931 // ssi_pid = 0 (origine kernel) → pid = None (convention 1 ADR-021).
1932 let info = parse_signalfd_siginfo(&buffer);
1933 assert!(info.pid.is_none());
1934 }
1935
1936 // ── async_handler (ADR-066) : install/restore + délivrance async réelle ───
1937
1938 #[test]
1939 fn async_handler_install_fires_and_restores_in_thread() {
1940 // Sérialisé (disposition process-wide) ; chemin 100 % déterministe et
1941 // host-couvert : install → auto-`tgkill` → handler exécuté → restore.
1942 let _serialize = ASYNC_INSTALL_LOCK
1943 .lock()
1944 .unwrap_or_else(std::sync::PoisonError::into_inner);
1945 let _mask_guard = MaskGuard::new();
1946 // SIGUSR1 doit être **débloqué** dans ce thread : sinon le signal reste
1947 // pendant au lieu d'invoquer le handler.
1948 unblock_signals(&SignalMask::from_signals(&[Signal::SIGUSR1])).expect("unblock SIGUSR1");
1949 ASYNC_HANDLER_FIRED.store(false, Ordering::SeqCst);
1950
1951 let handler: extern "C" fn(c_int, *mut c_void, *mut c_void) = async_test_handler;
1952 let handler_sa = handler as usize;
1953 // SAFETY: `async_test_handler` est async-signal-safe (un seul store
1954 // atomique). SIGUSR1 est un signal non-faute gérable.
1955 let previous = unsafe {
1956 async_handler::install(
1957 Signal::SIGUSR1,
1958 handler_sa,
1959 SigActionFlags::SIGINFO,
1960 SignalMask::empty(),
1961 )
1962 }
1963 .expect("install async handler");
1964
1965 // Auto-délivrance ciblée sur CE thread (déterministe, pas de contamination
1966 // d'un worker frère). Au retour de `tgkill`, le signal débloqué a déjà été
1967 // délivré → le handler a tourné **et** est revenu (sur x86_64, via le
1968 // trampoline `rt_sigreturn` d'Air : atteindre l'assert prouve le retour).
1969 tgkill(getpid(), gettid(), Some(Signal::SIGUSR1)).expect("tgkill self");
1970 assert!(
1971 ASYNC_HANDLER_FIRED.load(Ordering::SeqCst),
1972 "le handler async non-faute doit être exécuté par le noyau"
1973 );
1974
1975 // SAFETY: `previous` provient de l'`install` ci-dessus, sur le même signal.
1976 unsafe { async_handler::restore(Signal::SIGUSR1, previous) }.expect("restore");
1977 }
1978
1979 #[test]
1980 fn async_handler_install_ign_then_dfl_round_trip() {
1981 // Couvre les valeurs `sa_handler` SIG_IGN(1)/SIG_DFL(0) et la restauration.
1982 let _serialize = ASYNC_INSTALL_LOCK
1983 .lock()
1984 .unwrap_or_else(std::sync::PoisonError::into_inner);
1985 // SAFETY: SIG_IGN/SIG_DFL sont des constantes noyau — aucun code
1986 // utilisateur, trivialement sûres.
1987 let prev_original = unsafe {
1988 async_handler::install(
1989 Signal::SIGUSR1,
1990 async_handler::SIG_IGN,
1991 SigActionFlags::empty(),
1992 SignalMask::empty(),
1993 )
1994 }
1995 .expect("install SIG_IGN");
1996 // SAFETY: idem — SIG_DFL.
1997 let prev_ign = unsafe {
1998 async_handler::install(
1999 Signal::SIGUSR1,
2000 async_handler::SIG_DFL,
2001 SigActionFlags::empty(),
2002 SignalMask::empty(),
2003 )
2004 }
2005 .expect("install SIG_DFL");
2006 // Restaure en pile inverse : SIG_IGN puis la disposition d'origine.
2007 // SAFETY: chaque `previous` vient d'un `install` sur SIGUSR1.
2008 unsafe { async_handler::restore(Signal::SIGUSR1, prev_ign) }.expect("restore SIG_IGN");
2009 unsafe { async_handler::restore(Signal::SIGUSR1, prev_original) }.expect("restore origin");
2010 }
2011
2012 #[test]
2013 fn async_handler_install_on_sigkill_returns_einval() {
2014 // Couvre le bras d'erreur (`ret < 0`) d'`install` : `SIGKILL` n'est pas
2015 // catchable → le noyau rejette `rt_sigaction` avec EINVAL, sans changer
2016 // aucune disposition (sûr sans sérialisation).
2017 // SAFETY: SIG_DFL — aucun handler utilisateur.
2018 let err = unsafe {
2019 async_handler::install(
2020 Signal::SIGKILL,
2021 async_handler::SIG_DFL,
2022 SigActionFlags::empty(),
2023 SignalMask::empty(),
2024 )
2025 }
2026 .expect_err("SIGKILL non catchable");
2027 assert_eq!(err, Errno::EINVAL);
2028 }
2029
2030 // ── rt_sigpending (ADR-066) ────────────────────────────────────────
2031
2032 #[test]
2033 fn rt_sigpending_reports_blocked_pending_signal() {
2034 let _mask_guard = MaskGuard::new();
2035 let mask = SignalMask::from_signals(&[Signal::SIGUSR1]);
2036 // Bloque SIGUSR1 + crée un signalfd (pour drainer proprement à la fin).
2037 let sfd = signalfd_create_blocking(&mask, SignalFdFlags::empty()).expect("signalfd");
2038
2039 let before = rt_sigpending().expect("rt_sigpending initial");
2040 assert!(
2041 !before.contains(Signal::SIGUSR1),
2042 "aucun SIGUSR1 pendant au départ"
2043 );
2044
2045 tgkill(getpid(), gettid(), Some(Signal::SIGUSR1)).expect("tgkill self");
2046 let after = rt_sigpending().expect("rt_sigpending après tgkill");
2047 assert!(
2048 after.contains(Signal::SIGUSR1),
2049 "SIGUSR1 bloqué et généré doit apparaître pendant"
2050 );
2051
2052 // Draine le signal pendant AVANT que `MaskGuard` ne restaure le masque :
2053 // sinon débloquer SIGUSR1 le délivrerait avec l'action par défaut
2054 // (terminate), tuant le process de test.
2055 let info = sfd.read().expect("drain signalfd");
2056 assert_eq!(info.signal, Signal::SIGUSR1);
2057 }
2058}