air_sys_types/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//! Signal Linux et types associés.
6//!
7//! Couvre la famille `signal` de la couche 0 (cf.
8//! `docs/specs/layer-0/family-signal.md`). Périmètre :
9//!
10//! - Constantes [`Signal`] : 10 / ~31 signaux POSIX standards
11//! référencés par le code et les tests Air. **Reste stub partiel** —
12//! les ~21 restants (et les RT signals 32-64) seront ajoutés au fil
13//! des PRs qui les utiliseront. Le type [`Signal`] **borne**
14//! structurellement ses valeurs à `[1, 64]` (= `_NSIG` Linux) via
15//! [`Signal::try_from_raw`] : aucun signal hors de cette plage ne
16//! peut être construit publiquement.
17//! - [`SignalInfo`] : payload `siginfo_t` opaque (128 octets) construit
18//! via des constructeurs Air (`new_queue`) pour le pattern SI_QUEUE
19//! utilisé par `rt_sigqueueinfo` et acceptable pour `pidfd_send_signal`.
20//! - [`SignalValue`] : valeur associée à un signal-envoyé (`Integer(i32)`
21//! ou `Pointer(u64)`).
22//! - [`SignalMask`] : ensemble de signaux comme bitmask `u64`.
23//! - [`SignalFdInfo`] : structure retournée par la lecture d'un
24//! `signalfd`.
25//! - [`SignalFdFlags`] : drapeaux de `signalfd4`.
26//! - Sous-module [`synchronous_handler`] : `FatalSignal`, `SignalInfo`,
27//! `PreviousHandler` (cf. ADR-020 sigaction restreint).
28
29use core::num::NonZeroI32;
30
31use bitflags::bitflags;
32
33use crate::process::Pid;
34
35// ─────────────────────────────────────────────────────────────────────────
36// Signal — newtype + constantes (stub partiel 10/~31).
37// ─────────────────────────────────────────────────────────────────────────
38
39/// Nombre maximum de signaux Linux (`_NSIG` dans `asm-generic/signal.h`).
40///
41/// Signal 1 = `SIGHUP`, signal 64 = `SIGRTMAX`. Aucun signal légitime au
42/// dessus. Toute construction publique de [`Signal`] avec une valeur
43/// hors `[1, 64]` est refusée à la borne — voir [`Signal::try_from_raw`].
44const NSIG: i32 = 64;
45
46/// Numéro de signal Unix/Linux.
47///
48/// Toujours dans `[1, 64]` par construction : le signal 0 n'existe pas
49/// (sentinelle « pas de signal » exprimée par `Option<Signal>` cf.
50/// convention 1 ADR-021), et Linux n'admet aucun signal > 64
51/// (`_NSIG = 64`). La borne est validée à la construction
52/// ([`Self::try_from_raw`]) — pas de runtime check ailleurs.
53#[repr(transparent)]
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
55pub struct Signal(NonZeroI32);
56
57impl Signal {
58 /// `SIGHUP` — Hangup (1) : la fin d'un terminal de contrôle. Envoyé au shell de
59 /// login d'`air-sshd` quand le client ferme le canal interactif (E.3).
60 pub const SIGHUP: Self = Self(unwrap_nz(1));
61
62 /// `SIGILL` — Illegal instruction (4).
63 ///
64 /// Référencé par cette PR : `FatalSignal::Ill` (sous-module
65 /// `synchronous_handler`), tests d'install_fatal_handler.
66 pub const SIGILL: Self = Self(unwrap_nz(4));
67
68 /// `SIGTRAP` — Trace/breakpoint trap (5).
69 pub const SIGTRAP: Self = Self(unwrap_nz(5));
70
71 /// `SIGABRT` — Abort (6).
72 pub const SIGABRT: Self = Self(unwrap_nz(6));
73
74 /// `SIGBUS` — Bus error (7).
75 ///
76 /// Référencé par cette PR : `FatalSignal::Bus`.
77 pub const SIGBUS: Self = Self(unwrap_nz(7));
78
79 /// `SIGFPE` — Floating-point exception (8).
80 ///
81 /// Référencé par cette PR : `FatalSignal::Fpe`.
82 pub const SIGFPE: Self = Self(unwrap_nz(8));
83
84 /// `SIGKILL` — Kill (cannot be caught or ignored, 9).
85 pub const SIGKILL: Self = Self(unwrap_nz(9));
86
87 /// `SIGUSR1` — User-defined signal 1 (10, x86_64/aarch64).
88 ///
89 /// Référencé par cette PR : signal canonique pour les tests
90 /// signalfd / block / tgkill / rt_sigqueueinfo.
91 pub const SIGUSR1: Self = Self(unwrap_nz(10));
92
93 /// `SIGPIPE` — Écriture sur un tube ou une socket dont le lecteur est parti (13).
94 ///
95 /// **Le PAL de la `std` `linux-air` le pose à `SIG_IGN` au démarrage**, comme le fait le
96 /// PAL `unix` de l'amont — sans quoi tout binaire Air meurt sur signal 13 là où un
97 /// programme Rust reçoit `Err(EPIPE)` et peut le traiter.
98 ///
99 /// Son absence de cette liste était l'anomalie : onze signaux y figuraient, et celui qui
100 /// décide du sort de toute écriture sur un pair disparu n'y était pas.
101 pub const SIGPIPE: Self = Self(unwrap_nz(13));
102
103 /// `SIGSEGV` — Segmentation fault (11).
104 ///
105 /// Référencé par cette PR : `FatalSignal::Segv`.
106 pub const SIGSEGV: Self = Self(unwrap_nz(11));
107
108 /// `SIGCHLD` — Child status changed (17, x86_64/aarch64).
109 pub const SIGCHLD: Self = Self(unwrap_nz(17));
110
111 /// `SIGSTOP` — Stop process (cannot be caught or ignored, 19,
112 /// x86_64/aarch64).
113 pub const SIGSTOP: Self = Self(unwrap_nz(19));
114
115 /// Tente de construire un `Signal` à partir d'un entier brut.
116 ///
117 /// **Borné à `[1, NSIG]` (= `[1, 64]` sur Linux).** `NSIG = 64` est
118 /// le nombre maximum de signaux Unix POSIX standard + temps-réel
119 /// supportés par Linux (cf. `_NSIG` dans `asm-generic/signal.h`).
120 ///
121 /// Retourne `None` si `raw <= 0` ou `raw > 64`. Aucune valeur hors
122 /// de cette plage ne peut désigner un signal Linux valide ; les
123 /// rejeter à la construction transforme l'invariant en garantie de
124 /// type (Principe 4 — validation amont), plutôt qu'en `EINVAL`
125 /// kernel observé au runtime sur `kill`/`tgkill`/etc.
126 #[must_use]
127 #[inline]
128 pub const fn try_from_raw(raw: i32) -> Option<Self> {
129 match NonZeroI32::new(raw) {
130 Some(nz) if nz.get() > 0 && nz.get() <= NSIG => Some(Self(nz)),
131 _ => None,
132 }
133 }
134
135 /// Retourne la représentation brute (positive) du numéro de signal.
136 #[must_use]
137 #[inline]
138 pub const fn as_raw(self) -> i32 {
139 self.0.get()
140 }
141}
142
143/// Helper const : construit un `NonZeroI32` dans la plage `[1, NSIG]`
144/// depuis un littéral. Panique au compile-time si l'invariant est
145/// violé — défense en profondeur pour que tout ajout futur de constante
146/// `Signal::SIGXXX` avec une valeur hors plage soit rejeté à la
147/// compilation, en plus de la borne runtime de [`Signal::try_from_raw`].
148const fn unwrap_nz(n: i32) -> NonZeroI32 {
149 if n <= 0 || n > NSIG {
150 panic!("Signal constants must be in [1, 64]");
151 }
152 match NonZeroI32::new(n) {
153 Some(v) => v,
154 // Inatteignable : la garde au-dessus exige `n > 0`. Présent
155 // pour satisfaire le contrôle exhaustif const.
156 None => panic!("unreachable: n > 0 guaranteed by the check above"),
157 }
158}
159
160// ─────────────────────────────────────────────────────────────────────────
161// SignalValue — valeur attachée à un signal envoyé via SI_QUEUE.
162// ─────────────────────────────────────────────────────────────────────────
163
164/// Valeur attachée à un signal envoyé via le pattern SI_QUEUE
165/// (`rt_sigqueueinfo`, ou `pidfd_send_signal` avec `info` non-null).
166///
167/// Côté kernel `siginfo_t`, c'est l'union `sigval_t { int si_int;
168/// void *si_ptr; }` — discrimination via `si_code` (mais ici la
169/// discrimination est portée par la variante Rust).
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
171pub enum SignalValue {
172 /// Variante `si_int`.
173 Integer(i32),
174 /// Variante `si_ptr`.
175 Pointer(u64),
176}
177
178// ─────────────────────────────────────────────────────────────────────────
179// SignalInfo — payload siginfo_t opaque.
180// ─────────────────────────────────────────────────────────────────────────
181
182// Constantes `si_code` extraites de `include/uapi/asm-generic/siginfo.h`.
183// Identiques x86_64 / aarch64.
184const SI_QUEUE: i32 = -1;
185
186// Offsets dans la struct `siginfo_t` Linux générique (vérifiés sur
187// `arch/x86/include/uapi/asm/siginfo.h` et `asm-generic/siginfo.h`).
188//
189// Layout des 16 premiers octets (toutes archs supportées) :
190// offset 0 : si_signo (i32)
191// offset 4 : si_errno (i32)
192// offset 8 : si_code (i32)
193// offset 12 : padding (4 octets, requis sur 64-bit pour aligner l'union)
194//
195// La variante union `_sigqueue` commence à l'offset 16 :
196// offset 16 : si_pid (i32)
197// offset 20 : si_uid (u32)
198// offset 24 : sigval_t (union { si_int, si_ptr }) — 8 octets
199//
200// La variante union `_sigchld` partage le début (si_pid, si_uid) mais
201// utilise les octets suivants pour si_status / si_utime / si_stime.
202const SIGINFO_OFFSET_SI_CODE: usize = 8;
203const SIGINFO_OFFSET_SI_VALUE: usize = 24;
204const SIGINFO_SIZE: usize = 128;
205
206/// Payload `siginfo_t` opaque associé à un syscall d'envoi de signal
207/// (`pidfd_send_signal`, `rt_sigqueueinfo`).
208///
209/// **Représentation interne.** Buffer brut de 128 octets, alignement 4,
210/// layout `siginfo_t` Linux côté x86_64 et aarch64 (identique sur ces
211/// deux architectures). Opaque côté API publique : on n'expose pas les
212/// champs individuels — le kernel valide ses propres invariants
213/// (notamment `si_code`) et un usage à champs ouverts serait un footgun
214/// de sécurité (cf. la note dans `kernel/signal.c::do_rt_sigqueueinfo` :
215/// « Not even root can pretend to send signals from the kernel »).
216///
217/// **Construction.** Pas de champ public. Seuls des constructeurs Air
218/// disciplinés ([`Self::new_queue`]) fabriquent des `SignalInfo`
219/// dont le kernel accepte le `si_code`. Cela ferme la dette d'ambiguïté
220/// avec `SignalQueueInfo` consignée à la PR pidfd_* (le type
221/// `SignalQueueInfo` a été retiré ; le concept est porté par
222/// [`Self::new_queue`]).
223#[repr(C)]
224#[derive(Debug)]
225pub struct SignalInfo {
226 raw: [u8; SIGINFO_SIZE],
227}
228
229impl SignalInfo {
230 /// Construit un `SignalInfo` pour le pattern **SI_QUEUE**
231 /// (`rt_sigqueueinfo`, équivalent userspace de `sigqueue(3)`).
232 ///
233 /// Le `si_code` est fixé à `SI_QUEUE` (-1), seule valeur que le
234 /// kernel accepte pour un envoi cross-process userspace
235 /// (cf. `do_rt_sigqueueinfo` dans `kernel/signal.c`). Les champs
236 /// `si_signo`, `si_pid`, `si_uid` sont laissés à zéro — le kernel
237 /// les **écrase** avec les valeurs correctes au moment de la
238 /// délivrance (`sig` du syscall, `pid`/`uid` de l'appelant).
239 ///
240 /// Le champ `si_value` (offset 24, 8 octets) reçoit la valeur
241 /// fournie : variante `Integer(i32)` écrit les 4 premiers octets,
242 /// variante `Pointer(u64)` écrit les 8 octets.
243 #[must_use]
244 pub fn new_queue(value: SignalValue) -> Self {
245 let mut raw = [0_u8; SIGINFO_SIZE];
246 // si_code = SI_QUEUE à l'offset 8.
247 raw[SIGINFO_OFFSET_SI_CODE..SIGINFO_OFFSET_SI_CODE.saturating_add(4)]
248 .copy_from_slice(&SI_QUEUE.to_ne_bytes());
249 match value {
250 SignalValue::Integer(i) => {
251 raw[SIGINFO_OFFSET_SI_VALUE..SIGINFO_OFFSET_SI_VALUE.saturating_add(4)]
252 .copy_from_slice(&i.to_ne_bytes());
253 }
254 SignalValue::Pointer(p) => {
255 raw[SIGINFO_OFFSET_SI_VALUE..SIGINFO_OFFSET_SI_VALUE.saturating_add(8)]
256 .copy_from_slice(&p.to_ne_bytes());
257 }
258 }
259 Self { raw }
260 }
261
262 /// Accès lecture seule à la représentation brute, **uniquement** pour
263 /// les wrappers de syscalls qui doivent passer un pointeur au kernel.
264 /// Pas destiné à l'inspection des champs côté utilisateur (les
265 /// constructeurs Air fixent les champs corrects ; le kernel les
266 /// écrase à la délivrance).
267 #[doc(hidden)]
268 #[must_use]
269 #[inline]
270 pub fn as_bytes(&self) -> &[u8; SIGINFO_SIZE] {
271 &self.raw
272 }
273
274 /// `siginfo_t` **zéro-initialisé** — tampon de sortie destiné à être
275 /// **rempli par le kernel** (ex. `IORING_OP_WAITID`, où le kernel y écrit
276 /// l'état du processus). Lire ensuite via [`Self::as_bytes`].
277 #[must_use]
278 #[inline]
279 pub const fn zeroed() -> Self {
280 Self {
281 raw: [0_u8; SIGINFO_SIZE],
282 }
283 }
284}
285
286// ─────────────────────────────────────────────────────────────────────────
287// AltStack — pile de signal alternative (`stack_t`).
288// ─────────────────────────────────────────────────────────────────────────
289
290/// Pile de signal **alternative** (`stack_t` du kernel) — descellement additif ADR-085
291/// pour la face libc `sigaltstack`. Disposition `#[repr(C)]` **exacte** de `stack_t`
292/// (LP64 : `void *ss_sp` @0, `int ss_flags` @8, `size_t ss_size` @16 ; 24 octets), pour
293/// être passée telle quelle au syscall.
294///
295/// Une pile alternative permet à un gestionnaire de signal de s'exécuter sur une pile
296/// **dédiée** (indispensable pour traiter un `SIGSEGV` de **débordement de pile** — c'est
297/// l'usage de `std`).
298#[repr(C)]
299#[derive(Debug, Clone, Copy)]
300pub struct AltStack {
301 /// `ss_sp` — base de la pile alternative (ignoré si `SS_DISABLE`).
302 stack_pointer: *mut u8,
303 /// `ss_flags` — `SS_DISABLE`/`SS_ONSTACK`/`SS_AUTODISARM`.
304 flags: i32,
305 /// `ss_size` — taille en octets de la pile alternative.
306 size: usize,
307}
308
309/// `SS_ONSTACK` (1) — le thread s'exécute **actuellement** sur la pile alternative
310/// (lecture seule, rendu par la requête).
311pub const SS_ONSTACK: i32 = 1;
312/// `SS_DISABLE` (2) — désactive la pile alternative.
313pub const SS_DISABLE: i32 = 2;
314
315impl AltStack {
316 /// Construit une pile alternative **active** de base `stack_pointer` et `size` octets.
317 #[must_use]
318 pub fn new(stack_pointer: *mut u8, size: usize) -> Self {
319 Self {
320 stack_pointer,
321 flags: 0,
322 size,
323 }
324 }
325
326 /// Construit une pile **désactivée** (`SS_DISABLE`) — pour retirer une pile alternative.
327 #[must_use]
328 pub fn disabled() -> Self {
329 Self {
330 stack_pointer: core::ptr::null_mut(),
331 flags: SS_DISABLE,
332 size: 0,
333 }
334 }
335
336 /// Base de la pile alternative (`ss_sp`).
337 #[must_use]
338 pub fn stack_pointer(&self) -> *mut u8 {
339 self.stack_pointer
340 }
341
342 /// Taille de la pile alternative (`ss_size`).
343 #[must_use]
344 pub fn size(&self) -> usize {
345 self.size
346 }
347
348 /// Drapeaux bruts (`ss_flags`).
349 #[must_use]
350 pub fn flags(&self) -> i32 {
351 self.flags
352 }
353
354 /// `true` si la pile est **désactivée** (`SS_DISABLE`).
355 #[must_use]
356 pub fn is_disabled(&self) -> bool {
357 self.flags & SS_DISABLE != 0
358 }
359
360 /// `true` si le thread s'exécute **actuellement** sur la pile alternative
361 /// (`SS_ONSTACK`) — pertinent sur la valeur **rendue** par une requête.
362 #[must_use]
363 pub fn is_on_stack(&self) -> bool {
364 self.flags & SS_ONSTACK != 0
365 }
366}
367
368// ─────────────────────────────────────────────────────────────────────────
369// SignalMask — bitmask u64 (signaux 1-64).
370// ─────────────────────────────────────────────────────────────────────────
371
372/// Ensemble de signaux représenté comme bitmask `u64`.
373///
374/// Le bit `N` du mask correspond au signal `N + 1` (convention Linux
375/// `sigset_t` : signal 1 = bit 0, signal 64 = bit 63). Les signaux
376/// au-delà de 64 (RT signals étendus, rares) ne sont pas représentables
377/// par ce stub — ils seront supportés via une représentation plus large
378/// si nécessaire dans une PR ultérieure.
379#[repr(transparent)]
380#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
381pub struct SignalMask(u64);
382
383impl SignalMask {
384 /// Masque vide (aucun signal).
385 #[must_use]
386 #[inline]
387 pub const fn empty() -> Self {
388 Self(0)
389 }
390
391 /// Construit depuis sa représentation brute.
392 #[must_use]
393 #[inline]
394 pub const fn from_bits(bits: u64) -> Self {
395 Self(bits)
396 }
397
398 /// Représentation brute (bits 0-63 = signaux 1-64).
399 #[must_use]
400 #[inline]
401 pub const fn bits(self) -> u64 {
402 self.0
403 }
404
405 /// Construit un mask à partir d'une liste de signaux. Les signaux
406 /// au-delà de 64 (rares) sont silencieusement ignorés.
407 #[must_use]
408 pub fn from_signals(signals: &[Signal]) -> Self {
409 let mut bits = 0_u64;
410 for sig in signals {
411 // signal N → bit N-1 ; signal positif par construction.
412 let bit_index = u32::try_from(sig.as_raw())
413 .expect("Signal positive")
414 .saturating_sub(1);
415 // COVERAGE-UNREACHABLE: le `None` de `checked_shl` demanderait `bit_index >= 64`,
416 // donc un signal hors de [1, 64] — que `Signal::try_from_raw` refuse déjà à la
417 // construction. La branche est structurellement inatteignable ; elle est
418 // conservée comme garde no-op au titre du Principe 2 (jamais l'opération nue),
419 // et pour qu'un futur constructeur de `Signal` plus permissif ne devienne pas
420 // un débordement silencieux. Elle tombera le jour où ce constructeur existera.
421 if let Some(bit) = 1_u64.checked_shl(bit_index) {
422 bits |= bit;
423 }
424 }
425 Self(bits)
426 }
427
428 /// Indique si `sig` est dans le mask.
429 ///
430 /// **Défense en profondeur.** Le type `Signal` borne déjà ses valeurs
431 /// à `[1, 64]` (cf. [`Signal::try_from_raw`]), donc `bit_index < 64`
432 /// par construction et la branche `None` de `checked_shl` est
433 /// structurellement inatteignable. Elle est conservée comme garde
434 /// no-op (retourne `false`) au cas où un futur constructeur de
435 /// `Signal` oublierait d'appliquer la borne — Principe 5 :
436 /// sur-sécuriser, ne jamais corrompre.
437 #[must_use]
438 pub fn contains(self, sig: Signal) -> bool {
439 let bit_index = u32::try_from(sig.as_raw())
440 .expect("Signal positive")
441 .saturating_sub(1);
442 match 1_u64.checked_shl(bit_index) {
443 Some(bit) => (self.0 & bit) != 0,
444 None => false,
445 }
446 }
447
448 /// Retourne un nouveau mask avec `sig` ajouté.
449 ///
450 /// Même garde no-op défensive que [`Self::contains`] : si jamais
451 /// `bit_index >= 64`, retourne le mask inchangé (sans paniquer ni
452 /// corrompre l'état).
453 #[must_use]
454 pub fn with(self, sig: Signal) -> Self {
455 let bit_index = u32::try_from(sig.as_raw())
456 .expect("Signal positive")
457 .saturating_sub(1);
458 match 1_u64.checked_shl(bit_index) {
459 Some(bit) => Self(self.0 | bit),
460 None => self,
461 }
462 }
463
464 /// Retourne un nouveau mask avec `sig` retiré.
465 ///
466 /// Même garde no-op défensive que [`Self::contains`].
467 #[must_use]
468 pub fn without(self, sig: Signal) -> Self {
469 let bit_index = u32::try_from(sig.as_raw())
470 .expect("Signal positive")
471 .saturating_sub(1);
472 match 1_u64.checked_shl(bit_index) {
473 Some(bit) => Self(self.0 & !bit),
474 None => self,
475 }
476 }
477}
478
479// ─────────────────────────────────────────────────────────────────────────
480// SignalFdFlags + SignalFdInfo.
481// ─────────────────────────────────────────────────────────────────────────
482
483bitflags! {
484 /// Drapeaux pour [`signalfd_create`] (`signalfd4(2)`).
485 ///
486 /// **Note.** Le wrapper Air ajoute toujours `CLOEXEC` (cf.
487 /// `family-signal.md` : « Tous les FDs créés sont CLOEXEC par
488 /// défaut »). Les autres bits sont à la discrétion de l'appelant.
489 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
490 pub struct SignalFdFlags: i32 {
491 /// `SFD_NONBLOCK` : le FD est créé en mode non-bloquant.
492 const NONBLOCK = 0o4000;
493 /// `SFD_CLOEXEC` : automatiquement fermé à `execve`.
494 const CLOEXEC = 0o2_000_000;
495 }
496}
497
498/// Information lue depuis un `signalfd` (un `signalfd_siginfo`).
499///
500/// Layout C parsé par le wrapper depuis le buffer kernel ; les champs
501/// non-pertinents pour le signal reçu sont à 0/None côté kernel.
502#[derive(Debug, Clone, Copy)]
503pub struct SignalFdInfo {
504 /// Signal reçu.
505 pub signal: Signal,
506 /// `si_errno` (rarement utilisé).
507 pub errno: i32,
508 /// `si_code` (origine du signal : SI_USER, SI_KERNEL, CLD_EXITED, …).
509 pub code: i32,
510 /// `si_pid` : PID expéditeur. `None` si l'origine est le kernel (0).
511 pub pid: Option<Pid>,
512 /// `si_uid` : UID expéditeur.
513 pub uid: u32,
514 /// `ssi_fd` : FD source pour les signaux liés à un I/O (`SIGIO`).
515 pub fd: i32,
516 /// `ssi_tid` : ID du timer (spec Air : `timer_id`).
517 pub timer_id: u32,
518 /// `ssi_band` : pour `SIGIO`.
519 pub band: u32,
520 /// `ssi_overrun` : nombre d'overruns timer.
521 pub overrun: u32,
522 /// `ssi_trapno` : pour `SIGTRAP`.
523 pub trap_no: u32,
524 /// `ssi_status` : pour `SIGCHLD` (code de sortie ou signal fatal).
525 pub status: i32,
526 /// `ssi_int` : entier attaché pour SI_QUEUE-style.
527 pub int: i32,
528 /// `ssi_ptr` : pointeur attaché pour SI_QUEUE-style.
529 pub ptr: u64,
530 /// `ssi_utime` : pour `SIGCHLD`.
531 pub utime: u64,
532 /// `ssi_stime` : pour `SIGCHLD`.
533 pub stime: u64,
534 /// `ssi_addr` : pour `SIGSEGV`/`SIGBUS`/`SIGFPE`/`SIGILL`.
535 pub addr: u64,
536}
537
538// ─────────────────────────────────────────────────────────────────────────
539// Sous-module synchronous_handler (cf. ADR-020).
540// ─────────────────────────────────────────────────────────────────────────
541
542/// Sous-module restreint aux 4 signaux synchrones fatals
543/// (cf. ADR-020). Les seuls types et fonctions Air permettant
544/// d'installer un handler `sigaction` vivent ici ; la barrière est par
545/// construction.
546pub mod synchronous_handler {
547 use super::Signal;
548
549 /// Les quatre signaux synchrones fatals couverts par l'ADR-020.
550 /// L'enum est restreint à ces 4 variantes ; aucune API Air n'expose
551 /// d'autre signal pour `sigaction`.
552 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
553 pub enum FatalSignal {
554 /// `SIGSEGV` — segmentation fault.
555 Segv,
556 /// `SIGBUS` — bus error.
557 Bus,
558 /// `SIGFPE` — floating-point exception.
559 Fpe,
560 /// `SIGILL` — illegal instruction.
561 Ill,
562 }
563
564 impl FatalSignal {
565 /// Convertit en [`Signal`] correspondant.
566 #[must_use]
567 pub const fn as_signal(self) -> Signal {
568 match self {
569 Self::Segv => Signal::SIGSEGV,
570 Self::Bus => Signal::SIGBUS,
571 Self::Fpe => Signal::SIGFPE,
572 Self::Ill => Signal::SIGILL,
573 }
574 }
575 }
576
577 /// `siginfo_t` parsé pour un handler fatal. **Layout C** miroir du
578 /// `siginfo_t` Linux côté receiver — différent de
579 /// [`super::SignalInfo`] qui est le builder côté sender.
580 ///
581 /// Champs exposés : les plus utiles à un crash reporter. Le reste
582 /// du `siginfo_t` est dans `_padding`.
583 #[repr(C)]
584 pub struct SignalInfo {
585 /// `si_signo` : signal reçu (devrait correspondre à
586 /// [`FatalSignal::as_signal`] sur le handler installé).
587 pub signal: core::ffi::c_int,
588 /// `si_errno`.
589 pub errno: core::ffi::c_int,
590 /// `si_code` : type de fault (SEGV_MAPERR, SEGV_ACCERR, …).
591 pub code: core::ffi::c_int,
592 /// Padding à l'alignement union (4 octets sur 64-bit).
593 pub _pad0: u32,
594 /// `si_addr` : adresse mémoire fautive (pour SIGSEGV/SIGBUS).
595 pub addr: u64,
596 /// Reste de la struct (~104 octets restants pour atteindre 128).
597 pub _trailing: [u8; 104],
598 }
599
600 /// Handler installé via `install_fatal_handler`
601 /// (`air-sys-syscall::signal::synchronous_handler`). **Contrainte
602 /// async-signal-safe** : le handler ne doit appeler que des fonctions
603 /// listées dans `man 7 signal-safety` (typiquement `write`,
604 /// `_exit`, opérations sur des `core::sync::atomic`).
605 pub type FatalHandler = unsafe extern "C" fn(
606 signum: core::ffi::c_int,
607 info: *mut SignalInfo,
608 context: *mut core::ffi::c_void,
609 );
610
611 /// Handler précédemment installé sur un `FatalSignal`, à passer à
612 /// [`restore_handler`](crate::process::Pid) (dans
613 /// `air-sys-syscall`). Représentation opaque : contient
614 /// l'ancien `struct sigaction` kernel.
615 #[repr(C)]
616 pub struct PreviousHandler {
617 /// Buffer brut du `struct sigaction` kernel précédent.
618 /// Taille fixée à 32 octets — borne supérieure pour x86_64
619 /// (32 octets) et aarch64 (24 octets, padded à 32 pour
620 /// uniformité de représentation).
621 pub(crate) raw: [u8; 32],
622 }
623
624 impl PreviousHandler {
625 #[doc(hidden)]
626 #[must_use]
627 pub fn zeroed() -> Self {
628 Self { raw: [0_u8; 32] }
629 }
630
631 #[doc(hidden)]
632 #[must_use]
633 pub fn as_bytes(&self) -> &[u8; 32] {
634 &self.raw
635 }
636
637 #[doc(hidden)]
638 pub fn as_bytes_mut(&mut self) -> &mut [u8; 32] {
639 &mut self.raw
640 }
641 }
642
643 /// Vérifications à la compilation des invariants ABI.
644 const _: () = {
645 assert!(core::mem::size_of::<SignalInfo>() == 128);
646 assert!(core::mem::size_of::<PreviousHandler>() == 32);
647 };
648}
649
650// ─────────────────────────────────────────────────────────────────────────
651// Sous-module async_handler (cf. ADR-066) — types du descellement additif
652// « rt_sigaction non-faute ».
653// ─────────────────────────────────────────────────────────────────────────
654
655/// Types du **handler asynchrone** des signaux **non-faute** (ADR-066).
656///
657/// Contrairement à [`synchronous_handler`] (restreint aux 4 fautes, ADR-020) qui
658/// installe un handler **inerte** (le noyau force l'action par défaut sur les
659/// fautes), ce sous-module décrit les attributs d'un `rt_sigaction` **réellement
660/// installé** : le noyau détourne le thread vers le handler C à la délivrance
661/// d'un signal **gérable** (`SIGINT`/`SIGTERM`/`SIGCHLD`/`SIGWINCH`/`SIGUSR1-2`/
662/// temps-réel…). C'est la **fondation dual-face** : la libc C-ABI y bâtit
663/// `sigaction`/`signal`, le PAL Rust y bâtit ses handlers.
664///
665/// Les **fonctions** qui consomment ces types (`install`/`restore`) vivent en
666/// couche 0 dans `air-sys-syscall::signal::async_handler` — même partage
667/// types/wrappers que `synchronous_handler`.
668pub mod async_handler {
669 use bitflags::bitflags;
670
671 bitflags! {
672 /// Drapeaux `sa_flags` d'un `rt_sigaction` (identiques x86_64/aarch64,
673 /// `asm-generic`).
674 ///
675 /// **Exposés** : les drapeaux utiles à un handler applicatif. Le drapeau
676 /// interne `SA_RESTORER` (`0x0400_0000`) n'apparaît **pas** ici : il est
677 /// posé par le wrapper couche 0 sur x86_64 (trampoline `rt_sigreturn`
678 /// fourni par Air) et n'a **pas** de sens sur aarch64 (retour via VDSO).
679 /// C'est un attribut **typé** (ADR-021 §3), jamais un entier magique côté
680 /// appelant.
681 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
682 pub struct SigActionFlags: u64 {
683 /// `SA_NOCLDSTOP` : pas de `SIGCHLD` quand un enfant s'arrête/reprend.
684 const NOCLDSTOP = 0x0000_0001;
685 /// `SA_NOCLDWAIT` : les enfants ne deviennent pas zombies (`SIGCHLD`).
686 const NOCLDWAIT = 0x0000_0002;
687 /// `SA_SIGINFO` : handler à 3 arguments (`siginfo_t`/contexte).
688 const SIGINFO = 0x0000_0004;
689 /// `SA_ONSTACK` : exécute le handler sur la pile alternative
690 /// (`sigaltstack`).
691 const ONSTACK = 0x0800_0000;
692 /// `SA_RESTART` : redémarre certains syscalls interrompus. **Air ne
693 /// le force jamais** (ADR-021 conv.2 / ADR-064 §4) — mais un appelant
694 /// C peut le demander explicitement.
695 const RESTART = 0x1000_0000;
696 /// `SA_NODEFER` : ne bloque pas le signal courant pendant le handler.
697 const NODEFER = 0x4000_0000;
698 /// `SA_RESETHAND` : restaure `SIG_DFL` après une première délivrance.
699 const RESETHAND = 0x8000_0000;
700 }
701 }
702
703 /// Disposition **précédente** d'un signal, capturée par `install` et rendue à
704 /// `restore` (ADR-066). Représentation **opaque** : contient l'ancien
705 /// `struct sigaction` kernel (borne 32 octets — x86_64 = 32, aarch64 = 24
706 /// paddé à 32), même buffer que
707 /// [`synchronous_handler::PreviousHandler`](super::synchronous_handler::PreviousHandler)
708 /// mais **type distinct** pour garder étanches les deux faces (faute vs
709 /// non-faute).
710 #[repr(C)]
711 #[derive(Debug)]
712 pub struct PreviousDisposition {
713 /// Buffer brut du `struct sigaction` kernel précédent.
714 pub(crate) raw: [u8; 32],
715 }
716
717 impl PreviousDisposition {
718 /// Buffer **zéro-initialisé** — tampon de sortie rempli par le kernel
719 /// (`oldact` de `rt_sigaction`).
720 #[doc(hidden)]
721 #[must_use]
722 pub fn zeroed() -> Self {
723 Self { raw: [0_u8; 32] }
724 }
725
726 /// Vue lecture seule du buffer (pour le wrapper couche 0 qui le repasse au
727 /// kernel lors du `restore`).
728 #[doc(hidden)]
729 #[must_use]
730 pub fn as_bytes(&self) -> &[u8; 32] {
731 &self.raw
732 }
733
734 /// Vue mutable du buffer (rempli par le kernel lors de l'`install`).
735 #[doc(hidden)]
736 pub fn as_bytes_mut(&mut self) -> &mut [u8; 32] {
737 &mut self.raw
738 }
739 }
740
741 /// Invariant ABI : la disposition précédente tient dans 32 octets.
742 const _: () = {
743 assert!(core::mem::size_of::<PreviousDisposition>() == 32);
744 };
745}
746
747#[cfg(test)]
748mod tests;