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// Liaison **`.weak`** (et non `.globl`) : sur la cible `*-unknown-linux-air`
774// (std sur PAL safe, `-Z build-std`), `air-sys-syscall` est compilé **deux fois**
775// dans un même exécutable — une instance tirée DANS `std` (le PAL couche 1 via
776// `rustc-dep-of-std`) et une instance de l'arbre applicatif (ex. `air-account` →
777// `air-filesystem` → `air-sys-syscall`). Un symbole à **nom fixe** (`#[no_mangle]`/
778// `global_asm`) serait alors **défini deux fois** ⇒ `rust-lld: duplicate symbol`.
779// `.weak` autorise la **coalescence** : les deux définitions étant **identiques**
780// (même corps asm), l'éditeur de liens en garde une, sans ambiguïté. Sur les cibles
781// mono-instance (`*-linux-gnu`, barrière/CI) il n'y a **qu'une** définition ⇒
782// comportement **inchangé** (un symbole faible référencé n'est jamais éliminé).
783#[cfg(target_arch = "x86_64")]
784core::arch::global_asm!(
785 ".p2align 4",
786 ".weak __air_rt_sigreturn_trampoline",
787 ".hidden __air_rt_sigreturn_trampoline",
788 "__air_rt_sigreturn_trampoline:",
789 "mov rax, 15", // __NR_rt_sigreturn (x86_64)
790 "syscall",
791);
792
793#[cfg(target_arch = "x86_64")]
794unsafe extern "C" {
795 /// Symbole du trampoline `rt_sigreturn` défini par le `global_asm!`
796 /// ci-dessus. Jamais appelé depuis Rust — seule son **adresse** est posée
797 /// dans `sa_restorer`.
798 fn __air_rt_sigreturn_trampoline();
799}
800
801pub mod async_handler {
802 //! Wrappers `rt_sigaction` pour les signaux **non-faute** — handler
803 //! **réellement installé** (cf. ADR-064 : délivrance async réelle ; ADR-066 :
804 //! descellement additif couche 0).
805 //!
806 //! Contrairement à [`synchronous_handler`](super::synchronous_handler)
807 //! (restreint aux 4 fautes, handler **inerte** — le noyau force l'action par
808 //! défaut), ce sous-module installe un `rt_sigaction` **effectif** : le noyau
809 //! détourne le thread vers le handler C à la délivrance d'un signal
810 //! **gérable**. C'est la **fondation dual-face** que la libc (`sigaction`) et
811 //! le PAL consomment via la couche 1 `air-signal`.
812 //!
813 //! **API entièrement `unsafe`** : l'appelant garantit que le handler est
814 //! **async-signal-safe** (`man 7 signal-safety`) — précondition invérifiable
815 //! par le compilateur, portée par l'appelant (identique à
816 //! `synchronous_handler`).
817
818 use air_sys_types::Errno;
819 use air_sys_types::signal::async_handler::{PreviousDisposition, SigActionFlags};
820 use air_sys_types::{Signal, SignalMask};
821
822 use super::{SIGSET_SIZE_U64, errno_from_negative_syscall_ret};
823
824 /// `SA_RESTORER` (`0x0400_0000`) — **x86_64 uniquement**. Signale au noyau que
825 /// `sa_restorer` porte l'adresse du trampoline de retour de handler.
826 #[cfg(target_arch = "x86_64")]
827 const SA_RESTORER: u64 = 0x0400_0000;
828
829 /// Valeur `sa_handler` = `SIG_DFL` (action par défaut du noyau).
830 pub const SIG_DFL: usize = 0;
831 /// Valeur `sa_handler` = `SIG_IGN` (signal ignoré). **Sûr** (constante noyau,
832 /// aucun code utilisateur) — cf. ADR-064 §2.
833 pub const SIG_IGN: usize = 1;
834
835 /// Installe une disposition pour le signal **non-faute** `signal` via
836 /// `rt_sigaction(2)`, et rend la disposition **précédente** (pour
837 /// [`restore`]).
838 ///
839 /// - `handler_sa` : valeur brute de `sa_handler` — [`SIG_DFL`] (`0`),
840 /// [`SIG_IGN`] (`1`), ou l'**adresse** d'une fonction handler
841 /// `extern "C"` (cast `usize`). C'est la couche 1 (`air-signal`) qui
842 /// fournit le type de handler ; la couche 0 reste agnostique (adresse
843 /// brute).
844 /// - `flags` : [`SigActionFlags`] (`SA_SIGINFO`/`SA_RESTART`/…). Sur x86_64,
845 /// le wrapper **ajoute** `SA_RESTORER` et pose `sa_restorer` sur le
846 /// trampoline `rt_sigreturn` d'Air (obligatoire sans libc). Sur aarch64, le
847 /// retour passe par le VDSO — aucun `sa_restorer`.
848 /// - `mask` : signaux **additionnels** bloqués pendant l'exécution du handler.
849 ///
850 /// # Safety
851 ///
852 /// Si `handler_sa` désigne une fonction (≠ [`SIG_DFL`]/[`SIG_IGN`]), l'appelant
853 /// doit garantir qu'elle est **async-signal-safe** (`man 7 signal-safety`) :
854 /// pas de `malloc`, pas de verrou, pas d'I/O bufferisée, etc. Le noyau peut
855 /// l'invoquer à un point **arbitraire** du programme. Poser [`SIG_DFL`] ou
856 /// [`SIG_IGN`] est en soi sûr (aucun code utilisateur), mais l'API reste
857 /// `unsafe` pour une frontière uniforme.
858 ///
859 /// Ce wrapper est réservé aux signaux **non-faute** : installer un handler
860 /// pour `SIGSEGV`/`SIGBUS`/`SIGFPE`/`SIGILL` est le domaine (inerte) de
861 /// [`synchronous_handler`](super::synchronous_handler) (ADR-020/ADR-064).
862 ///
863 /// # Errors
864 ///
865 /// - [`Errno::EINVAL`] : `signal` non-catchable (`SIGKILL`/`SIGSTOP`).
866 pub unsafe fn install(
867 signal: Signal,
868 handler_sa: usize,
869 flags: SigActionFlags,
870 mask: SignalMask,
871 ) -> Result<PreviousDisposition, Errno> {
872 let mut new_sa = KernelSigaction::zeroed();
873 // `usize → u64` : Air ne cible que des arches 64 bits (ADR-014) — même
874 // idiome que `synchronous_handler::install_fatal_handler`
875 // (`handler as usize as u64`), lossless, sans conversion faillible.
876 new_sa.sa_handler = handler_sa as u64;
877 new_sa.sa_mask = mask.bits();
878
879 // Drapeaux : ceux de l'appelant, plus `SA_RESTORER` sur x86_64 (le noyau
880 // a besoin d'un trampoline de retour ; sans libc, Air le fournit).
881 let kernel_flags = flags.bits();
882 #[cfg(target_arch = "x86_64")]
883 {
884 new_sa.sa_flags = kernel_flags | SA_RESTORER;
885 // Adresse du trampoline `rt_sigreturn` (fn item → usize → u64,
886 // 64 bits : lossless).
887 new_sa.sa_restorer = super::__air_rt_sigreturn_trampoline as *const () as usize as u64;
888 }
889 #[cfg(target_arch = "aarch64")]
890 {
891 new_sa.sa_flags = kernel_flags;
892 }
893
894 let mut previous = PreviousDisposition::zeroed();
895 let new_ptr: *const KernelSigaction = &new_sa;
896 let old_ptr: *mut u8 = previous.as_bytes_mut().as_mut_ptr();
897
898 // SAFETY: rt_sigaction(2). Le kernel lit `new_sa` (KernelSigaction valide,
899 // locale) et écrit jusqu'à 32 octets à `old_ptr` (capacité de
900 // `PreviousDisposition`). sigsetsize=8 = taille kernel `sigset_t`. Le
901 // handler éventuel respecte l'async-signal-safety par contrat `unsafe`.
902 let ret = unsafe {
903 raw_syscall_rt_sigaction(
904 signal.as_raw(),
905 new_ptr as u64,
906 old_ptr as u64,
907 SIGSET_SIZE_U64,
908 )
909 };
910 if ret < 0 {
911 return Err(errno_from_negative_syscall_ret(ret));
912 }
913 Ok(previous)
914 }
915
916 /// Restaure la disposition `previous` pour le signal `signal`.
917 ///
918 /// # Safety
919 ///
920 /// `previous` doit provenir d'un [`install`] précédent sur le **même**
921 /// `signal`. Restaurer une disposition d'un autre signal est un comportement
922 /// indéfini côté noyau.
923 ///
924 /// # Errors
925 ///
926 /// - [`Errno::EINVAL`] : `signal` non-catchable (ne se produit pas si
927 /// `previous` vient d'un `install` réussi sur ce signal).
928 pub unsafe fn restore(signal: Signal, previous: PreviousDisposition) -> Result<(), Errno> {
929 let prev_ptr: *const u8 = previous.as_bytes().as_ptr();
930 // SAFETY: rt_sigaction(2). Le kernel lit jusqu'à 32 octets à `prev_ptr`
931 // (KernelSigaction ≤ 32 octets). Pas d'écriture (oldset=NULL).
932 let ret = unsafe {
933 raw_syscall_rt_sigaction(signal.as_raw(), prev_ptr as u64, 0_u64, SIGSET_SIZE_U64)
934 };
935 if ret < 0 {
936 return Err(errno_from_negative_syscall_ret(ret));
937 }
938 Ok(())
939 }
940
941 /// Représentation interne du `struct sigaction` kernel (cf.
942 /// [`synchronous_handler`](super::synchronous_handler) — même layout ABI,
943 /// dupliqué pour ne PAS toucher au sous-module des fautes).
944 ///
945 /// - offset 0 : `sa_handler` (8) ; offset 8 : `sa_flags` (8) ;
946 /// - x86_64 : offset 16 `sa_restorer` (8), offset 24 `sa_mask` (8) — 32 octets ;
947 /// - aarch64 : offset 16 `sa_mask` (8), **pas** de `sa_restorer` — 24 octets.
948 #[repr(C)]
949 #[cfg(target_arch = "x86_64")]
950 struct KernelSigaction {
951 sa_handler: u64,
952 sa_flags: u64,
953 sa_restorer: u64,
954 sa_mask: u64,
955 }
956
957 #[cfg(target_arch = "x86_64")]
958 impl KernelSigaction {
959 const fn zeroed() -> Self {
960 Self {
961 sa_handler: 0,
962 sa_flags: 0,
963 sa_restorer: 0,
964 sa_mask: 0,
965 }
966 }
967 }
968
969 #[repr(C)]
970 #[cfg(target_arch = "aarch64")]
971 struct KernelSigaction {
972 sa_handler: u64,
973 sa_flags: u64,
974 sa_mask: u64,
975 }
976
977 #[cfg(target_arch = "aarch64")]
978 impl KernelSigaction {
979 const fn zeroed() -> Self {
980 Self {
981 sa_handler: 0,
982 sa_flags: 0,
983 sa_mask: 0,
984 }
985 }
986 }
987
988 /// `KernelSigaction` tient dans le buffer 32 octets de `PreviousDisposition`.
989 const _: () = {
990 assert!(core::mem::size_of::<KernelSigaction>() <= 32);
991 };
992
993 #[cfg(target_arch = "x86_64")]
994 #[inline]
995 unsafe fn raw_syscall_rt_sigaction(
996 signum: i32,
997 new_sa: u64,
998 old_sa: u64,
999 sigsetsize: u64,
1000 ) -> i64 {
1001 let ret: i64;
1002 // SAFETY: SYS_rt_sigaction (x86_64 = 13). Le kernel lit `new_sa`
1003 // (KernelSigaction, 32 octets), écrit `old_sa` (32 octets) si non-null.
1004 unsafe {
1005 core::arch::asm!(
1006 "syscall",
1007 in("rax") 13_i64,
1008 in("rdi") i64::from(signum),
1009 in("rsi") new_sa,
1010 in("rdx") old_sa,
1011 in("r10") sigsetsize,
1012 lateout("rax") ret,
1013 lateout("rcx") _,
1014 lateout("r11") _,
1015 options(nostack, preserves_flags),
1016 );
1017 }
1018 ret
1019 }
1020
1021 #[cfg(target_arch = "aarch64")]
1022 #[inline]
1023 unsafe fn raw_syscall_rt_sigaction(
1024 signum: i32,
1025 new_sa: u64,
1026 old_sa: u64,
1027 sigsetsize: u64,
1028 ) -> i64 {
1029 let ret: i64;
1030 // SAFETY: SYS_rt_sigaction (aarch64 = 134).
1031 unsafe {
1032 core::arch::asm!(
1033 "svc 0",
1034 in("x8") 134_i64,
1035 inout("x0") i64::from(signum) => ret,
1036 in("x1") new_sa,
1037 in("x2") old_sa,
1038 in("x3") sigsetsize,
1039 options(nostack, preserves_flags),
1040 );
1041 }
1042 ret
1043 }
1044}
1045
1046// ─────────────────────────────────────────────────────────────────────────
1047// asm! wrappers x86_64 / aarch64.
1048// ─────────────────────────────────────────────────────────────────────────
1049
1050#[cfg(target_arch = "x86_64")]
1051#[inline]
1052unsafe fn raw_syscall_signalfd4(fd: i32, mask: u64, sigsetsize: u64, flags: i32) -> i64 {
1053 let ret: i64;
1054 // SAFETY: SYS_signalfd4 (x86_64 = 289). Le kernel lit `*mask` (8
1055 // octets) ; pas d'écriture en mémoire utilisateur (`readonly` OK).
1056 unsafe {
1057 core::arch::asm!(
1058 "syscall",
1059 in("rax") 289_i64,
1060 in("rdi") i64::from(fd),
1061 in("rsi") mask,
1062 in("rdx") sigsetsize,
1063 in("r10") i64::from(flags),
1064 lateout("rax") ret,
1065 lateout("rcx") _,
1066 lateout("r11") _,
1067 options(nostack, preserves_flags, readonly),
1068 );
1069 }
1070 ret
1071}
1072
1073#[cfg(target_arch = "aarch64")]
1074#[inline]
1075unsafe fn raw_syscall_signalfd4(fd: i32, mask: u64, sigsetsize: u64, flags: i32) -> i64 {
1076 let ret: i64;
1077 // SAFETY: SYS_signalfd4 (aarch64 = 74).
1078 unsafe {
1079 core::arch::asm!(
1080 "svc 0",
1081 in("x8") 74_i64,
1082 inout("x0") i64::from(fd) => ret,
1083 in("x1") mask,
1084 in("x2") sigsetsize,
1085 in("x3") i64::from(flags),
1086 options(nostack, preserves_flags, readonly),
1087 );
1088 }
1089 ret
1090}
1091
1092#[cfg(target_arch = "x86_64")]
1093#[inline]
1094unsafe fn raw_syscall_rt_sigprocmask(how: i32, set: u64, oldset: u64, sigsetsize: u64) -> i64 {
1095 let ret: i64;
1096 // SAFETY: SYS_rt_sigprocmask (x86_64 = 14). Le kernel lit `*set` si
1097 // non-null (8 octets) ; écrit `*oldset` si non-null (8 octets). Pas
1098 // de `readonly` (écriture sur oldset).
1099 unsafe {
1100 core::arch::asm!(
1101 "syscall",
1102 in("rax") 14_i64,
1103 in("rdi") i64::from(how),
1104 in("rsi") set,
1105 in("rdx") oldset,
1106 in("r10") sigsetsize,
1107 lateout("rax") ret,
1108 lateout("rcx") _,
1109 lateout("r11") _,
1110 options(nostack, preserves_flags),
1111 );
1112 }
1113 ret
1114}
1115
1116#[cfg(target_arch = "aarch64")]
1117#[inline]
1118unsafe fn raw_syscall_rt_sigprocmask(how: i32, set: u64, oldset: u64, sigsetsize: u64) -> i64 {
1119 let ret: i64;
1120 // SAFETY: SYS_rt_sigprocmask (aarch64 = 135).
1121 unsafe {
1122 core::arch::asm!(
1123 "svc 0",
1124 in("x8") 135_i64,
1125 inout("x0") i64::from(how) => ret,
1126 in("x1") set,
1127 in("x2") oldset,
1128 in("x3") sigsetsize,
1129 options(nostack, preserves_flags),
1130 );
1131 }
1132 ret
1133}
1134
1135#[cfg(target_arch = "x86_64")]
1136#[inline]
1137unsafe fn raw_syscall_sigaltstack(new: u64, old: u64) -> i64 {
1138 let ret: i64;
1139 // SAFETY: SYS_sigaltstack (x86_64 = 131). Le kernel **lit** `*new` (24 octets) si
1140 // `new != 0` et **écrit** `*old` (24 octets) si `old != 0` — pas de `readonly`.
1141 unsafe {
1142 core::arch::asm!(
1143 "syscall",
1144 in("rax") 131_i64,
1145 in("rdi") new,
1146 in("rsi") old,
1147 lateout("rax") ret,
1148 lateout("rcx") _,
1149 lateout("r11") _,
1150 options(nostack, preserves_flags),
1151 );
1152 }
1153 ret
1154}
1155
1156#[cfg(target_arch = "aarch64")]
1157#[inline]
1158unsafe fn raw_syscall_sigaltstack(new: u64, old: u64) -> i64 {
1159 let ret: i64;
1160 // SAFETY: SYS_sigaltstack (aarch64 = 132). Mêmes contrats mémoire que x86_64.
1161 unsafe {
1162 core::arch::asm!(
1163 "svc 0",
1164 in("x8") 132_i64,
1165 inout("x0") new => ret,
1166 in("x1") old,
1167 options(nostack, preserves_flags),
1168 );
1169 }
1170 ret
1171}
1172
1173#[cfg(target_arch = "x86_64")]
1174#[inline]
1175unsafe fn raw_syscall_rt_sigpending(set: u64, sigsetsize: u64) -> i64 {
1176 let ret: i64;
1177 // SAFETY: SYS_rt_sigpending (x86_64 = 127). Le kernel **écrit** 8 octets à
1178 // `set` (sigset_t) ; pas de `readonly`.
1179 unsafe {
1180 core::arch::asm!(
1181 "syscall",
1182 in("rax") 127_i64,
1183 in("rdi") set,
1184 in("rsi") sigsetsize,
1185 lateout("rax") ret,
1186 lateout("rcx") _,
1187 lateout("r11") _,
1188 options(nostack, preserves_flags),
1189 );
1190 }
1191 ret
1192}
1193
1194#[cfg(target_arch = "aarch64")]
1195#[inline]
1196unsafe fn raw_syscall_rt_sigpending(set: u64, sigsetsize: u64) -> i64 {
1197 let ret: i64;
1198 // SAFETY: SYS_rt_sigpending (aarch64 = 136).
1199 unsafe {
1200 core::arch::asm!(
1201 "svc 0",
1202 in("x8") 136_i64,
1203 inout("x0") set => ret,
1204 in("x1") sigsetsize,
1205 options(nostack, preserves_flags),
1206 );
1207 }
1208 ret
1209}
1210
1211#[cfg(target_arch = "x86_64")]
1212#[inline]
1213unsafe fn raw_syscall_kill(pid: i32, sig: i32) -> i64 {
1214 let ret: i64;
1215 // SAFETY: SYS_kill (x86_64 = 62). Pas d'accès mémoire utilisateur.
1216 unsafe {
1217 core::arch::asm!(
1218 "syscall",
1219 in("rax") 62_i64,
1220 in("rdi") i64::from(pid),
1221 in("rsi") i64::from(sig),
1222 lateout("rax") ret,
1223 lateout("rcx") _,
1224 lateout("r11") _,
1225 options(nostack, preserves_flags, readonly),
1226 );
1227 }
1228 ret
1229}
1230
1231#[cfg(target_arch = "aarch64")]
1232#[inline]
1233unsafe fn raw_syscall_kill(pid: i32, sig: i32) -> i64 {
1234 let ret: i64;
1235 // SAFETY: SYS_kill (aarch64 = 129).
1236 unsafe {
1237 core::arch::asm!(
1238 "svc 0",
1239 in("x8") 129_i64,
1240 inout("x0") i64::from(pid) => ret,
1241 in("x1") i64::from(sig),
1242 options(nostack, preserves_flags, readonly),
1243 );
1244 }
1245 ret
1246}
1247
1248#[cfg(target_arch = "x86_64")]
1249#[inline]
1250unsafe fn raw_syscall_tgkill(tgid: i32, tid: i32, sig: i32) -> i64 {
1251 let ret: i64;
1252 // SAFETY: SYS_tgkill (x86_64 = 234). Pas d'accès mémoire utilisateur.
1253 unsafe {
1254 core::arch::asm!(
1255 "syscall",
1256 in("rax") 234_i64,
1257 in("rdi") i64::from(tgid),
1258 in("rsi") i64::from(tid),
1259 in("rdx") i64::from(sig),
1260 lateout("rax") ret,
1261 lateout("rcx") _,
1262 lateout("r11") _,
1263 options(nostack, preserves_flags, readonly),
1264 );
1265 }
1266 ret
1267}
1268
1269#[cfg(target_arch = "aarch64")]
1270#[inline]
1271unsafe fn raw_syscall_tgkill(tgid: i32, tid: i32, sig: i32) -> i64 {
1272 let ret: i64;
1273 // SAFETY: SYS_tgkill (aarch64 = 131).
1274 unsafe {
1275 core::arch::asm!(
1276 "svc 0",
1277 in("x8") 131_i64,
1278 inout("x0") i64::from(tgid) => ret,
1279 in("x1") i64::from(tid),
1280 in("x2") i64::from(sig),
1281 options(nostack, preserves_flags, readonly),
1282 );
1283 }
1284 ret
1285}
1286
1287#[cfg(target_arch = "x86_64")]
1288#[inline]
1289unsafe fn raw_syscall_rt_sigqueueinfo(pid: i32, sig: i32, info: u64) -> i64 {
1290 let ret: i64;
1291 // SAFETY: SYS_rt_sigqueueinfo (x86_64 = 129). Le kernel lit 128
1292 // octets à `info` (siginfo_t). Pas d'écriture user (`readonly`).
1293 unsafe {
1294 core::arch::asm!(
1295 "syscall",
1296 in("rax") 129_i64,
1297 in("rdi") i64::from(pid),
1298 in("rsi") i64::from(sig),
1299 in("rdx") info,
1300 lateout("rax") ret,
1301 lateout("rcx") _,
1302 lateout("r11") _,
1303 options(nostack, preserves_flags, readonly),
1304 );
1305 }
1306 ret
1307}
1308
1309#[cfg(target_arch = "aarch64")]
1310#[inline]
1311unsafe fn raw_syscall_rt_sigqueueinfo(pid: i32, sig: i32, info: u64) -> i64 {
1312 let ret: i64;
1313 // SAFETY: SYS_rt_sigqueueinfo (aarch64 = 138).
1314 unsafe {
1315 core::arch::asm!(
1316 "svc 0",
1317 in("x8") 138_i64,
1318 inout("x0") i64::from(pid) => ret,
1319 in("x1") i64::from(sig),
1320 in("x2") info,
1321 options(nostack, preserves_flags, readonly),
1322 );
1323 }
1324 ret
1325}
1326
1327#[cfg(target_arch = "x86_64")]
1328#[inline]
1329unsafe fn raw_syscall_read(fd: i32, buffer: u64, count: u64) -> i64 {
1330 let ret: i64;
1331 // SAFETY: SYS_read (x86_64 = 0). Le kernel écrit jusqu'à `count`
1332 // octets à `buffer`. Pour signalfd, exactement 128 octets pour un
1333 // événement complet.
1334 unsafe {
1335 core::arch::asm!(
1336 "syscall",
1337 in("rax") 0_i64,
1338 in("rdi") i64::from(fd),
1339 in("rsi") buffer,
1340 in("rdx") count,
1341 lateout("rax") ret,
1342 lateout("rcx") _,
1343 lateout("r11") _,
1344 options(nostack, preserves_flags),
1345 );
1346 }
1347 ret
1348}
1349
1350#[cfg(target_arch = "aarch64")]
1351#[inline]
1352unsafe fn raw_syscall_read(fd: i32, buffer: u64, count: u64) -> i64 {
1353 let ret: i64;
1354 // SAFETY: SYS_read (aarch64 = 63).
1355 unsafe {
1356 core::arch::asm!(
1357 "svc 0",
1358 in("x8") 63_i64,
1359 inout("x0") i64::from(fd) => ret,
1360 in("x1") buffer,
1361 in("x2") count,
1362 options(nostack, preserves_flags),
1363 );
1364 }
1365 ret
1366}
1367
1368// ─────────────────────────────────────────────────────────────────────────
1369// Helper de conversion errno (réplique de celui de process.rs ; un
1370// futur refactor pourra centraliser).
1371// ─────────────────────────────────────────────────────────────────────────
1372
1373fn errno_from_negative_syscall_ret(ret: i64) -> Errno {
1374 debug_assert!(ret < 0 && ret > -4096);
1375 #[allow(clippy::cast_possible_truncation)]
1376 let raw = ret.wrapping_neg() as i32;
1377 let nz = NonZeroI32::new(raw).expect("errno strictement positif par construction");
1378 Errno::from_nonzero(nz)
1379}
1380
1381// ─────────────────────────────────────────────────────────────────────────
1382// Tests.
1383// ─────────────────────────────────────────────────────────────────────────
1384
1385#[cfg(test)]
1386mod tests;