Skip to main content

air_sys_types/
process.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//! Types fondamentaux de la famille `process`.
6//!
7//! Cf. `docs/specs/layer-0/family-process.md` et `air-sys-types.md`.
8//!
9//! `Pid` et `Tid` sont deux newtypes **distincts** sur `NonZeroI32` afin
10//! d'empêcher par typage la confusion entre un identifiant de processus et un
11//! identifiant de thread (application directe du Principe 7 et de la
12//! convention 1 de l'ADR-021). Le reste du module héberge les types liés à
13//! `clone3` (création de processus / thread) et `waitid` (attente
14//! d'événement sur un enfant).
15
16use core::num::NonZeroI32;
17use core::sync::atomic::AtomicU32;
18
19use bitflags::bitflags;
20
21use crate::fd::{BorrowedFd, OwnedFd};
22use crate::signal::Signal;
23
24/// Identifiant kernel d'un processus (`pid_t`).
25///
26/// Garanti strictement positif par le kernel Linux : un PID nul ne désigne
27/// jamais un processus réel (cf. `getpid(2)`). Le wrapper `NonZeroI32` encode
28/// cet invariant dans le type, ce qui permet à `Option<Pid>` d'occuper la
29/// même taille que `Pid`. La convention 1 de l'ADR-021 exploite cette
30/// propriété pour remplacer les sentinelles kernel (`0` = « processus
31/// courant ») par `None`.
32#[repr(transparent)]
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
34pub struct Pid(NonZeroI32);
35
36impl Pid {
37    /// Construit un `Pid` à partir d'une valeur déjà validée.
38    ///
39    /// Réservé aux producteurs qui ont déjà la garantie que la valeur vient
40    /// du kernel et est strictement positive (par exemple le wrapper
41    /// `getpid` de la couche 0).
42    #[must_use]
43    #[inline]
44    pub const fn from_nonzero(raw: NonZeroI32) -> Self {
45        Self(raw)
46    }
47
48    /// Tente de construire un `Pid` à partir d'un `i32` brut.
49    ///
50    /// Retourne `None` si la valeur est négative ou nulle, deux cas qui
51    /// n'identifient pas un processus valide.
52    #[must_use]
53    #[inline]
54    pub const fn try_from_raw(raw: i32) -> Option<Self> {
55        match NonZeroI32::new(raw) {
56            Some(nz) if nz.get() > 0 => Some(Self(nz)),
57            _ => None,
58        }
59    }
60
61    /// Retourne la représentation brute (`i32`) du PID.
62    ///
63    /// L'expose pour l'interfaçage avec les wrappers de syscalls et les
64    /// affichages humains ; éviter de manipuler des `i32` nus dans le
65    /// reste du code (convention « newtypes systématiques »).
66    #[must_use]
67    #[inline]
68    pub const fn as_raw(self) -> i32 {
69        self.0.get()
70    }
71}
72
73/// Identifiant kernel d'un thread (TID, `tid_t`).
74///
75/// Sur Linux, chaque thread possède un TID distinct. Le TID du thread
76/// principal d'un processus est égal au PID du processus (invariant
77/// `gettid() == getpid()` pour le thread principal).
78///
79/// Type **distinct** de [`Pid`] : on ne peut pas confondre les deux par
80/// inadvertance.
81#[repr(transparent)]
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
83pub struct Tid(NonZeroI32);
84
85impl Tid {
86    /// Construit un `Tid` à partir d'une valeur déjà validée.
87    #[must_use]
88    #[inline]
89    pub const fn from_nonzero(raw: NonZeroI32) -> Self {
90        Self(raw)
91    }
92
93    /// Tente de construire un `Tid` à partir d'un `i32` brut.
94    ///
95    /// Retourne `None` si la valeur est négative ou nulle.
96    #[must_use]
97    #[inline]
98    pub const fn try_from_raw(raw: i32) -> Option<Self> {
99        match NonZeroI32::new(raw) {
100            Some(nz) if nz.get() > 0 => Some(Self(nz)),
101            _ => None,
102        }
103    }
104
105    /// Retourne la représentation brute (`i32`) du TID.
106    #[must_use]
107    #[inline]
108    pub const fn as_raw(self) -> i32 {
109        self.0.get()
110    }
111}
112
113// ─────────────────────────────────────────────────────────────────────────
114// Identifiants d'utilisateur / de groupe (`uid_t` / `gid_t`).
115//
116// Spec : `docs/specs/layer-0/family-process-privsep.md` §1.
117//
118// Contrairement à `Pid`/`Tid` (toujours > 0), **`0` est une valeur valide**
119// (root) : on n'utilise donc pas `NonZeroI32` mais un newtype `repr(transparent)`
120// sur `u32` (= `uid_t`/`gid_t`). Newtypes typés systématiques (ADR-029 : jamais
121// d'`u32` brut pour un identifiant ; `Uid` et `Gid` sont **distincts** pour
122// empêcher toute confusion par typage).
123// ─────────────────────────────────────────────────────────────────────────
124
125/// Identifiant d'utilisateur (`uid_t`). `0` = root (valeur valide).
126///
127/// La valeur `(uid_t)-1` (`u32::MAX`) est **réservée** par le kernel comme
128/// sentinelle « inchangé » de `setresuid` ; elle est exposée typée via
129/// `Option<Uid>` (`None`), jamais comme un `Uid` de production.
130#[repr(transparent)]
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
132pub struct Uid(u32);
133
134impl Uid {
135    /// L'utilisateur root (`uid == 0`).
136    pub const ROOT: Self = Self(0);
137
138    /// Construit un `Uid` à partir d'une valeur brute `uid_t`.
139    #[must_use]
140    #[inline]
141    pub const fn from_raw(raw: u32) -> Self {
142        Self(raw)
143    }
144
145    /// Retourne la représentation brute (`uid_t`) de l'identifiant.
146    #[must_use]
147    #[inline]
148    pub const fn as_raw(self) -> u32 {
149        self.0
150    }
151}
152
153/// Identifiant de groupe (`gid_t`). `0` = groupe root (valeur valide).
154///
155/// Voir [`Uid`] pour la sentinelle `(gid_t)-1` (`None` de `setresgid`).
156#[repr(transparent)]
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
158pub struct Gid(u32);
159
160impl Gid {
161    /// Le groupe root (`gid == 0`).
162    pub const ROOT: Self = Self(0);
163
164    /// Construit un `Gid` à partir d'une valeur brute `gid_t`.
165    #[must_use]
166    #[inline]
167    pub const fn from_raw(raw: u32) -> Self {
168        Self(raw)
169    }
170
171    /// Retourne la représentation brute (`gid_t`) de l'identifiant.
172    #[must_use]
173    #[inline]
174    pub const fn as_raw(self) -> u32 {
175        self.0
176    }
177}
178
179/// Triplet d'identité UID **réel / effectif / sauvegardé** (`getresuid`).
180///
181/// Le *saved-set-uid* est la composante qui rend une réduction de privilèges
182/// **irréversible** : si `real == effective == saved == non-root`, le processus
183/// ne peut plus regagner root (cf. `setresuid`, `family-process-privsep.md` §3).
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
185pub struct ResUid {
186    /// UID réel.
187    pub real: Uid,
188    /// UID effectif (gouverne les contrôles d'accès).
189    pub effective: Uid,
190    /// UID sauvegardé (*saved-set-uid*).
191    pub saved: Uid,
192}
193
194/// Triplet d'identité GID **réel / effectif / sauvegardé** (`getresgid`).
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub struct ResGid {
197    /// GID réel.
198    pub real: Gid,
199    /// GID effectif.
200    pub effective: Gid,
201    /// GID sauvegardé (*saved-set-gid*).
202    pub saved: Gid,
203}
204
205// ─────────────────────────────────────────────────────────────────────────
206// Création de processus : types associés à `clone3`.
207// Spec : `docs/specs/layer-0/family-process.md` sous-section 2.
208// ─────────────────────────────────────────────────────────────────────────
209
210/// File descriptor RAII qui référence un processus par pidfd.
211///
212/// Ferme automatiquement le FD sous-jacent à la destruction. Reste valide
213/// même après la mort du processus référencé (le FD ne se transfère pas à
214/// un éventuel nouveau processus qui recyclerait le PID).
215#[derive(Debug)]
216pub struct PidFd(OwnedFd);
217
218impl PidFd {
219    /// Construit un `PidFd` à partir d'un `OwnedFd` déjà ouvert sur un
220    /// processus par le kernel (typiquement via `pidfd_open` ou `clone3`
221    /// avec `CLONE_PIDFD`).
222    #[must_use]
223    #[inline]
224    pub const fn from_owned_fd(fd: OwnedFd) -> Self {
225        Self(fd)
226    }
227
228    /// Retourne une vue empruntée du FD sous-jacent.
229    #[must_use]
230    #[inline]
231    pub fn as_fd(&self) -> BorrowedFd<'_> {
232        use crate::fd::AsFd;
233        self.0.as_fd()
234    }
235
236    /// Consomme le `PidFd` et restitue le `OwnedFd` sous-jacent.
237    #[must_use]
238    #[inline]
239    pub fn into_fd(self) -> OwnedFd {
240        self.0
241    }
242}
243
244bitflags! {
245    /// Drapeaux de `clone3` (cf. `linux/sched.h`).
246    ///
247    /// Représentation `u64` pour accepter les bits Linux ≥ 5.7 qui dépassent
248    /// 32 bits (`CLEAR_SIGHAND`, `INTO_CGROUP`, `NEWTIME`).
249    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
250    pub struct CloneFlags: u64 {
251        /// Partage l'espace d'adressage avec le parent (création de thread).
252        const VM = 0x0000_0100;
253        /// Partage la table FS (`umask`, `cwd`, `root`).
254        const FS = 0x0000_0200;
255        /// Partage la table des FD ouverts.
256        const FILES = 0x0000_0400;
257        /// Partage la table des dispositions de signaux.
258        const SIGHAND = 0x0000_0800;
259        /// Demande au kernel de créer un pidfd vers l'enfant.
260        const PIDFD = 0x0000_1000;
261        /// L'enfant peut être tracé par `ptrace`.
262        const PTRACE = 0x0000_2000;
263        /// Suspend l'appelant jusqu'à `execve`/`exit` de l'enfant (vfork).
264        const VFORK = 0x0000_4000;
265        /// Le parent de l'enfant est le parent du parent (sibling).
266        const PARENT = 0x0000_8000;
267        /// L'enfant est dans le même groupe de threads que l'appelant.
268        const THREAD = 0x0001_0000;
269        /// L'enfant est dans un nouveau mount namespace.
270        const NEWNS = 0x0002_0000;
271        /// Partage les sémaphores System V.
272        const SYSVSEM = 0x0004_0000;
273        /// Initialise le `tls` de l'enfant avec la valeur fournie.
274        const SETTLS = 0x0008_0000;
275        /// Écrit le TID enfant dans `parent_tid` (mémoire du parent).
276        const PARENT_SETTID = 0x0010_0000;
277        /// `CLEAR_TID` à la mort de l'enfant.
278        const CHILD_CLEARTID = 0x0020_0000;
279        /// Enfant détaché (le parent n'est pas SIGCHLD-notifié).
280        const DETACHED = 0x0040_0000;
281        /// Désactive le traçage hérité par `ptrace`.
282        const UNTRACED = 0x0080_0000;
283        /// Écrit le TID enfant dans `child_tid` (mémoire de l'enfant).
284        const CHILD_SETTID = 0x0100_0000;
285        /// Nouveau cgroup namespace.
286        const NEWCGROUP = 0x0200_0000;
287        /// Nouveau UTS namespace (hostname).
288        const NEWUTS = 0x0400_0000;
289        /// Nouveau IPC namespace.
290        const NEWIPC = 0x0800_0000;
291        /// Nouveau user namespace.
292        const NEWUSER = 0x1000_0000;
293        /// Nouveau PID namespace.
294        const NEWPID = 0x2000_0000;
295        /// Nouveau network namespace.
296        const NEWNET = 0x4000_0000;
297        /// Partage le contexte I/O du parent.
298        const IO = 0x8000_0000;
299        /// Réinitialise toutes les dispositions de signal de l'enfant.
300        const CLEAR_SIGHAND = 0x1_0000_0000;
301        /// Place l'enfant dans le cgroup référencé par l'`fd` fourni.
302        const INTO_CGROUP = 0x2_0000_0000;
303        /// Nouveau time namespace (offsets `CLOCK_MONOTONIC`/`BOOTTIME`).
304        const NEWTIME = 0x4_0000_0000;
305    }
306}
307
308bitflags! {
309    /// Drapeaux de [`pidfd_open`](../../air_sys_syscall/process/fn.pidfd_open.html).
310    ///
311    /// La spec couche 0 (`docs/specs/layer-0/family-process.md`) ne définit
312    /// qu'une seule valeur valide (`NONBLOCK`) ; les autres bits sont
313    /// réservés et le kernel retourne `EINVAL` s'ils sont positionnés.
314    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
315    pub struct PidFdOpenFlags: u32 {
316        /// Le pidfd est créé en mode non-bloquant : `poll`/`epoll` sur
317        /// ce FD retournera immédiatement (utile pour de la surveillance
318        /// asynchrone). Valeur ABI `O_NONBLOCK = 0x800`.
319        const NONBLOCK = 0x800;
320    }
321}
322
323bitflags! {
324    /// Drapeaux de [`execveat`](../../air_sys_syscall/process/fn.execveat.html).
325    ///
326    /// Variante *at préférée d'`execve` (ADR-021 : « variantes modernes
327    /// préférées »). Correspond aux constantes `AT_*` de `<fcntl.h>`.
328    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
329    pub struct ExecveatFlags: i32 {
330        /// `AT_SYMLINK_NOFOLLOW` : si le composant final du chemin est un lien
331        /// symbolique, ne pas le suivre (échoue avec `ELOOP`).
332        const SYMLINK_NOFOLLOW = 0x100;
333        /// `AT_EMPTY_PATH` : `path` est vide (`c""`) et l'exécutable est
334        /// désigné **directement** par le `dirfd` (un fd ouvert sur le binaire,
335        /// typiquement avec `O_PATH`). Évite toute race sur le chemin
336        /// filesystem entre l'ouverture et l'exec.
337        const EMPTY_PATH = 0x1000;
338    }
339}
340
341bitflags! {
342    /// Drapeaux de [`dup3`](../../air_sys_syscall/process/fn.dup3.html).
343    ///
344    /// `dup3(2)` (et non `dup2`) impose de passer les drapeaux explicitement ;
345    /// le seul drapeau valide est `O_CLOEXEC`. Contrairement à `dup2`, `dup3`
346    /// échoue avec `EINVAL` si `old == new` — pas de no-op silencieux.
347    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
348    pub struct Dup3Flags: i32 {
349        /// `O_CLOEXEC` : le nouveau descripteur est marqué close-on-exec
350        /// (fermé automatiquement lors d'un `execve`/`execveat` ultérieur).
351        /// Valeur ABI `0o2000000`, identique sur x86_64 et aarch64.
352        const CLOEXEC = 0o2_000_000;
353    }
354}
355
356/// Spécification d'une pile pour la création d'un thread (`CLONE_VM`).
357///
358/// Le pointeur `addr` désigne l'**adresse de base** (octet le plus bas) d'une
359/// région mémoire valide d'au moins `size` octets, propriété de l'appelant
360/// (typiquement issue d'un `mmap` anonyme). Le kernel positionne le pointeur
361/// de pile de l'enfant au **sommet** de la région (`addr + size`) ; sur les
362/// deux architectures Air (x86_64, aarch64) la pile croît vers le bas.
363///
364/// **Répartition des rôles.** La création de thread se fait via
365/// [`clone_thread`](../../air_sys_syscall/process/fn.clone_thread.html), qui
366/// **exige** `Some(StackSpecification)` et valide en amont l'alignement
367/// (`addr` et `size` multiples de 16) et la taille minimale. Le wrapper de fork
368/// [`clone3`](../../air_sys_syscall/process/fn.clone3.html) — dont le modèle de
369/// retour `CloneResult::Child` est *unsound* sur une pile neuve — **rejette**
370/// au contraire `Some(StackSpecification)` avec
371/// [`Errno::EINVAL`](crate::errno::Errno::EINVAL) (cf. la doc de `clone_thread`
372/// pour la justification kernel : un retour Rust normal sur une pile neuve lit
373/// des ordures).
374#[derive(Debug, Clone, Copy)]
375pub struct StackSpecification {
376    /// Adresse de base de la pile.
377    pub addr: *mut u8,
378    /// Taille de la pile, en octets.
379    pub size: usize,
380}
381
382/// Emplacement mémoire où le kernel écrit (`CLONE_PARENT_SETTID` /
383/// `CLONE_CHILD_SETTID`) ou efface (`CLONE_CHILD_CLEARTID`) le TID d'un enfant
384/// créé par `clone3`.
385///
386/// Encapsule l'adresse d'un **mot 32 bits partagé** ([`AtomicU32`]) — un
387/// `tid_t` côté kernel — plutôt qu'un pointeur nu (convention 1 de l'ADR-021 :
388/// pas de pointeur brut non typé exposé). Le pattern de *join* de thread :
389///
390/// - `CLONE_PARENT_SETTID` + `parent_tid = &mot` → le kernel écrit le TID dans
391///   le mot, **dans le contexte du parent**, avant le retour de `clone3` (sans
392///   course avec une terminaison précoce de l'enfant) ;
393/// - `CLONE_CHILD_CLEARTID` + `child_tid = &mot` → à la mort du thread, le
394///   kernel remet le mot à `0` **et** effectue un `FUTEX_WAKE` dessus. Le parent
395///   joint donc en bouclant sur `futex_wait(&mot, tid)` jusqu'à lire `0`.
396///
397/// Le mot doit rester **vivant** (et l'`AtomicU32` non déplacé) jusqu'à la fin
398/// du *join* ; cette garantie incombe à l'appelant de la fonction `unsafe`
399/// `clone_thread`. Construire un `TidReceiver` est en soi inoffensif (on ne fait
400/// qu'enregistrer une adresse).
401#[derive(Debug, Clone, Copy)]
402pub struct TidReceiver {
403    /// Adresse du mot 32 bits (`tid_t`) partagé avec le kernel.
404    word: *mut u32,
405}
406
407impl TidReceiver {
408    /// Construit un récepteur à partir d'un mot atomique partagé vivant.
409    ///
410    /// On ne capture que l'**adresse** du mot ; sa validité au moment de l'appel
411    /// `clone3`/`clone_thread` (et jusqu'au *join*) relève du contrat `unsafe`
412    /// de ces fonctions.
413    #[must_use]
414    #[inline]
415    pub fn new(word: &AtomicU32) -> Self {
416        Self {
417            word: word.as_ptr(),
418        }
419    }
420
421    /// Retourne le pointeur brut du mot, pour le marshalling vers la structure
422    /// `clone_args` du kernel (réservé aux wrappers de la couche 0).
423    #[must_use]
424    #[inline]
425    pub fn as_ptr(self) -> *mut u32 {
426        self.word
427    }
428}
429
430/// Arguments pour `clone3`.
431///
432/// La spec couche 0 (`docs/specs/layer-0/family-process.md`) liste neuf
433/// champs (flags, pidfd, child_tid, parent_tid, exit_signal, stack, tls,
434/// set_tid, cgroup). Ce type en expose six : les trois du fork classique
435/// (`flags`, `exit_signal`, `stack`) **plus** les trois requis par la
436/// création de thread (`child_tid`, `parent_tid`, `tls`). Les restants
437/// (`pidfd` en tant que récepteur typé, `set_tid`, `cgroup`) seront ajoutés au
438/// fil des syscalls qui les exigent. L'évolution est strictement additive :
439/// aucun champ existant n'est destiné à être renommé ou retiré.
440///
441/// Pour demander la création d'un pidfd sur l'enfant, positionner le bit
442/// [`CloneFlags::PIDFD`] ; le `PidFd` résultant est retourné dans la
443/// variante [`CloneResult::Parent`].
444///
445/// **Création de thread.** Renseigner `stack`, `child_tid`/`parent_tid` (pour
446/// le *join* via `CLONE_CHILD_CLEARTID`) et éventuellement `tls`, puis appeler
447/// [`clone_thread`](../../air_sys_syscall/process/fn.clone_thread.html) (et non
448/// `clone3`, qui reste dédié au fork).
449#[derive(Debug, Clone, Default)]
450pub struct CloneArgs {
451    /// Drapeaux de comportement (`CLONE_*`).
452    pub flags: CloneFlags,
453    /// Signal à délivrer au parent à la mort de l'enfant. `Some(SIGCHLD)`
454    /// est le choix standard pour `clone3` style fork. **Doit être `None`**
455    /// pour un thread (`CLONE_THREAD` interdit tout signal de sortie).
456    pub exit_signal: Option<Signal>,
457    /// Pile pour `CLONE_VM` (création de thread). `None` pour le fork classique
458    /// (rejeté par `clone3` si renseigné) ; `Some(_)` est **requis** par
459    /// `clone_thread`.
460    pub stack: Option<StackSpecification>,
461    /// Récepteur du TID enfant côté **enfant** (`CLONE_CHILD_SETTID` /
462    /// `CLONE_CHILD_CLEARTID`). Pour le *join* de thread, le pointer sur le
463    /// même mot que [`Self::parent_tid`] et positionner `CLONE_CHILD_CLEARTID`.
464    pub child_tid: Option<TidReceiver>,
465    /// Récepteur du TID enfant côté **parent** (`CLONE_PARENT_SETTID`). Écrit
466    /// par le kernel avant le retour de `clone3`, sans course.
467    pub parent_tid: Option<TidReceiver>,
468    /// Adresse du bloc TLS de l'enfant (`CLONE_SETTLS`). `None` = pas de TLS
469    /// dédié (l'enfant hérite alors du registre TLS du parent : n'utiliser
470    /// **aucune** variable `thread_local` côté enfant dans ce cas). La spec
471    /// couche 0 type ce champ en `u64` (adresse brute) — voir
472    /// `docs/specs/layer-0/family-process.md`.
473    pub tls: Option<u64>,
474}
475
476/// Résultat de `clone3`.
477///
478/// Distingue sans ambiguïté le côté parent (avec le PID enfant et,
479/// optionnellement, un pidfd) du côté enfant (qui n'a rien d'autre à
480/// connaître que sa propre identité, accessible via `getpid`/`gettid`).
481#[derive(Debug)]
482pub enum CloneResult {
483    /// Retour côté parent.
484    Parent {
485        /// PID du nouvel enfant.
486        child_pid: Pid,
487        /// `PidFd` si [`CloneFlags::PIDFD`] était positionné, `None` sinon.
488        child_pidfd: Option<PidFd>,
489    },
490    /// Retour côté enfant.
491    ///
492    /// L'enfant doit typiquement exécuter ses traitements puis terminer
493    /// (`exit_group`, ou — dans les tests Air — `std::process::exit` qui
494    /// flush les compteurs LLVM de couverture). Voir le pattern recommandé
495    /// dans la spec.
496    Child,
497}
498
499// ─────────────────────────────────────────────────────────────────────────
500// Attente d'événements : types associés à `waitid`.
501// Spec : `docs/specs/layer-0/family-process.md` sous-section 3.
502// ─────────────────────────────────────────────────────────────────────────
503
504/// Cible d'un appel `waitid`.
505///
506/// L'enum encode à la fois `idtype` et `id` du syscall, en évitant les
507/// combinaisons invalides (`P_PIDFD` avec un id qui n'est pas un fd, etc.).
508#[derive(Debug, Clone, Copy)]
509pub enum WaitTarget<'fd> {
510    /// `P_ALL` — n'importe quel enfant.
511    AnyChild,
512    /// `P_PID` — un enfant désigné par son PID.
513    Pid(Pid),
514    /// `P_PGID` — n'importe quel processus du groupe désigné.
515    ProcessGroup(Pid),
516    /// `P_PGID` avec `id = 0` — le groupe de processus du caller.
517    AnyProcessGroup,
518    /// `P_PIDFD` — l'enfant référencé par le pidfd.
519    PidFd(BorrowedFd<'fd>),
520}
521
522bitflags! {
523    /// Options de `waitid` (`W*` flags).
524    ///
525    /// Au moins un parmi [`Self::EXITED`], [`Self::STOPPED`],
526    /// [`Self::CONTINUED`] doit être positionné, sinon le kernel n'a aucun
527    /// événement à rapporter et retourne `EINVAL`. Le wrapper laisse cette
528    /// validation au kernel.
529    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
530    pub struct WaitOptions: i32 {
531        /// `WNOHANG` — ne pas bloquer si aucun événement n'est disponible.
532        const NOHANG = 1;
533        /// `WSTOPPED` — rapporter les arrêts (`SIGSTOP`/`SIGTSTP`/…).
534        const STOPPED = 2;
535        /// `WEXITED` — rapporter les terminaisons (normal, par signal).
536        const EXITED = 4;
537        /// `WCONTINUED` — rapporter les reprises (`SIGCONT`).
538        const CONTINUED = 8;
539        /// `WNOWAIT` — ne pas consommer l'événement (peek).
540        const NOWAIT = 0x0100_0000;
541    }
542}
543
544/// État rapporté par `waitid` pour un événement enfant.
545#[derive(Debug, Clone, Copy)]
546pub struct WaitStatus {
547    /// PID de l'enfant concerné.
548    pub pid: Pid,
549    /// UID effectif de l'enfant au moment de l'événement.
550    pub uid: u32,
551    /// Type d'événement (terminaison, signal, arrêt, reprise, …).
552    pub event: WaitEvent,
553}
554
555/// Type d'événement remonté par `waitid`.
556///
557/// Le mapping kernel `si_code` → variante :
558/// - `CLD_EXITED` (1) → [`Self::Exited`]
559/// - `CLD_KILLED` (2) → [`Self::Killed { core_dumped: false }`](Self::Killed)
560/// - `CLD_DUMPED` (3) → [`Self::Killed { core_dumped: true }`](Self::Killed)
561/// - `CLD_TRAPPED` (4) → [`Self::Trapped`]
562/// - `CLD_STOPPED` (5) → [`Self::Stopped`]
563/// - `CLD_CONTINUED` (6) → [`Self::Continued`]
564#[derive(Debug, Clone, Copy, PartialEq, Eq)]
565pub enum WaitEvent {
566    /// L'enfant a terminé normalement (`exit_group` / retour de `main`).
567    Exited {
568        /// Code de sortie (low 8 bits du `exit_group(status)`).
569        code: i32,
570    },
571    /// L'enfant a été terminé par un signal.
572    Killed {
573        /// Signal fatal.
574        signal: Signal,
575        /// Vrai si le kernel a généré un fichier `core` (`CLD_DUMPED`).
576        core_dumped: bool,
577    },
578    /// L'enfant a été arrêté (`SIGSTOP`/`SIGTSTP`).
579    Stopped {
580        /// Signal d'arrêt.
581        signal: Signal,
582    },
583    /// L'enfant a été repris (`SIGCONT`).
584    Continued,
585    /// `ptrace` trap.
586    Trapped {
587        /// Signal de trap.
588        signal: Signal,
589    },
590}
591
592// ─────────────────────────────────────────────────────────────────────────
593// prctl : modes exposés (sous-section 5 de family-process.md).
594// ─────────────────────────────────────────────────────────────────────────
595
596/// Mode `dumpable` du processus (`prctl(PR_GET_DUMPABLE | PR_SET_DUMPABLE)`).
597///
598/// Contrôle si le processus peut être « dumpé » : produit un core, peut être
599/// attaché par ptrace, peut voir ses pages mémoire via `/proc/PID/mem`, etc.
600#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
601#[non_exhaustive]
602pub enum DumpableMode {
603    /// `SUID_DUMP_DISABLE` (0) : non-dumpable. État après `execve` d'un
604    /// binaire SUID/SGID/cap-augmenté.
605    NotDumpable = 0,
606    /// `SUID_DUMP_USER` (1) : dumpable par le propriétaire du processus.
607    /// Valeur par défaut pour la majorité des processus.
608    Dumpable = 1,
609    /// `SUID_DUMP_ROOT` (2, déprécié) : dumpable uniquement par root.
610    /// Conservé pour compatibilité historique ; les kernels récents le
611    /// traitent essentiellement comme `Dumpable`.
612    SuidDumpable = 2,
613}
614
615// ─────────────────────────────────────────────────────────────────────────
616// rlimits (sous-section 6 de family-process.md).
617// ─────────────────────────────────────────────────────────────────────────
618
619/// Type de ressource pour `getrlimit`/`setrlimit`/`prlimit`.
620///
621/// Valeurs `RLIMIT_*` extraites de `asm-generic/resource.h` (identiques
622/// `x86_64` et `aarch64` — ABI kernel stable hors archs hors ADR-014).
623#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
624#[non_exhaustive]
625pub enum Resource {
626    /// `RLIMIT_CPU` (0) : temps CPU max en secondes.
627    Cpu = 0,
628    /// `RLIMIT_FSIZE` (1) : taille max d'un fichier (octets).
629    FileSize = 1,
630    /// `RLIMIT_DATA` (2) : taille max du segment data.
631    Data = 2,
632    /// `RLIMIT_STACK` (3) : taille max de la pile.
633    Stack = 3,
634    /// `RLIMIT_CORE` (4) : taille max d'un core dump.
635    Core = 4,
636    /// `RLIMIT_RSS` (5) : taille max du RSS (largement ignoré depuis 2.4).
637    Rss = 5,
638    /// `RLIMIT_NPROC` (6) : nombre max de processus pour l'UID réel.
639    NProc = 6,
640    /// `RLIMIT_NOFILE` (7) : numéro max + 1 de FD ouvrable.
641    NoFile = 7,
642    /// `RLIMIT_MEMLOCK` (8) : taille max de mémoire `mlock()`'ée.
643    MemLock = 8,
644    /// `RLIMIT_AS` (9) : taille max de l'espace d'adressage virtuel.
645    As = 9,
646    /// `RLIMIT_LOCKS` (10) : nombre max de verrous `flock()` / `fcntl()`.
647    Locks = 10,
648    /// `RLIMIT_SIGPENDING` (11) : nombre max de signaux pendants en file.
649    SigPending = 11,
650    /// `RLIMIT_MSGQUEUE` (12) : octets max alloués aux POSIX message queues.
651    MsgQueue = 12,
652    /// `RLIMIT_NICE` (13) : niveau `nice` ceiling (offset depuis 20).
653    Nice = 13,
654    /// `RLIMIT_RTPRIO` (14) : priorité temps-réel ceiling.
655    RtPrio = 14,
656    /// `RLIMIT_RTTIME` (15) : temps CPU max (µs) en politique RT sans bloquer.
657    RtTime = 15,
658}
659
660/// Valeur d'une limite de ressource. `Infinity` correspond à
661/// `RLIM_INFINITY` (`u64::MAX` sur LP64).
662#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
663pub enum RlimitValue {
664    /// Limite finie.
665    Finite(u64),
666    /// `RLIM_INFINITY` côté kernel — pas de limite.
667    Infinity,
668}
669
670/// Paire (soft, hard) de limites de ressource.
671///
672/// La soft limit est celle réellement appliquée. La hard limit est le
673/// plafond que la soft limit ne peut excéder ; baisser la hard limit est
674/// possible pour tout processus, la relever exige `CAP_SYS_RESOURCE`.
675#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
676pub struct Rlimit {
677    /// Limite douce (effectivement appliquée).
678    pub soft: RlimitValue,
679    /// Limite dure (plafond pour la soft limit).
680    pub hard: RlimitValue,
681}
682
683// ─────────────────────────────────────────────────────────────────────────
684// Capabilities (sous-section 7 de family-process.md).
685// ─────────────────────────────────────────────────────────────────────────
686
687/// Capability Linux — permission fine remplaçant le modèle binaire
688/// root/non-root.
689///
690/// **Ensemble complet.** Les **41** capabilities du noyau (`CAP_CHOWN` = 0 …
691/// `CAP_CHECKPOINT_RESTORE` = 40 = `CAP_LAST_CAP` sur Linux ≥ 5.9) sont **toutes**
692/// nommées (ADR-126, `couche-0-v1.14`). Un consommateur peut donc désigner
693/// n'importe quelle capability — en particulier pour **réduire un bounding set à un
694/// sous-ensemble exact** (privsep, ADR-124), ce que le stub partiel initial (20/41)
695/// ne permettait pas.
696///
697/// **Valeurs numériques.** Tirées de `include/uapi/linux/capability.h` ;
698/// identiques `x86_64` et `aarch64` (ABI kernel stable hors archs hors
699/// ADR-014).
700///
701/// **Note de discipline (mot haut).** Les valeurs 32-40 (`CAP_MAC_OVERRIDE` …
702/// `CAP_CHECKPOINT_RESTORE`) occupent le **mot haut** (bits 32-63) du
703/// `CapabilityMask` (`u64`) et donc le second mot `u32` de l'ABI `capget`/`capset`.
704/// La conversion lo/hi est faite par les helpers d'`air-sys-syscall`
705/// (`mask_to_words`/`words_to_mask`) et testée explicitement (masque à bit ≥ 32)
706/// pour catcher une éventuelle inversion lo/hi.
707#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
708#[non_exhaustive]
709#[repr(u32)]
710pub enum Capability {
711    /// `CAP_CHOWN` (0) : changer l'ownership d'un fichier arbitrairement.
712    Chown = 0,
713    /// `CAP_DAC_OVERRIDE` (1) : passer outre les vérifications DAC.
714    DacOverride = 1,
715    /// `CAP_DAC_READ_SEARCH` (2) : lire/parcourir tout fichier/répertoire.
716    DacReadSearch = 2,
717    /// `CAP_FOWNER` (3) : opérations habituellement réservées au propriétaire.
718    Fowner = 3,
719    /// `CAP_FSETID` (4) : ne pas vider SUID/SGID sur modification.
720    Fsetid = 4,
721    /// `CAP_KILL` (5) : envoyer signal à n'importe quel processus.
722    Kill = 5,
723    /// `CAP_SETGID` (6) : manipulation arbitraire des GID.
724    Setgid = 6,
725    /// `CAP_SETUID` (7) : manipulation arbitraire des UID.
726    Setuid = 7,
727    /// `CAP_SETPCAP` (8) : transfert/retrait de capabilities (dont `PR_CAPBSET_DROP`).
728    Setpcap = 8,
729    /// `CAP_LINUX_IMMUTABLE` (9) : poser les attributs `IMMUTABLE`/`APPEND`.
730    LinuxImmutable = 9,
731    /// `CAP_NET_BIND_SERVICE` (10) : bind sur ports < 1024.
732    NetBindService = 10,
733    /// `CAP_NET_BROADCAST` (11) : broadcast et écoute multicast.
734    NetBroadcast = 11,
735    /// `CAP_NET_ADMIN` (12) : configuration réseau (interfaces, routage…).
736    NetAdmin = 12,
737    /// `CAP_NET_RAW` (13) : sockets raw et packet.
738    NetRaw = 13,
739    /// `CAP_IPC_LOCK` (14) : verrouiller de la mémoire (`mlock`, `SHM_LOCK`).
740    IpcLock = 14,
741    /// `CAP_IPC_OWNER` (15) : passer outre les vérifications de propriété IPC.
742    IpcOwner = 15,
743    /// `CAP_SYS_MODULE` (16) : load/unload de modules kernel.
744    SysModule = 16,
745    /// `CAP_SYS_RAWIO` (17) : I/O ports, `/dev/mem`, etc.
746    SysRawio = 17,
747    /// `CAP_SYS_CHROOT` (18) : `chroot(2)`.
748    SysChroot = 18,
749    /// `CAP_SYS_PTRACE` (19) : `ptrace` arbitraire.
750    SysPtrace = 19,
751    /// `CAP_SYS_PACCT` (20) : `acct(2)` (comptabilité des processus).
752    SysPacct = 20,
753    /// `CAP_SYS_ADMIN` (21) : « capability fourre-tout » système.
754    SysAdmin = 21,
755    /// `CAP_SYS_BOOT` (22) : `reboot(2)` et `kexec_load(2)`.
756    SysBoot = 22,
757    /// `CAP_SYS_NICE` (23) : priorités CPU et politiques RT/IO.
758    SysNice = 23,
759    /// `CAP_SYS_RESOURCE` (24) : relever les hard limits et autres ceilings.
760    SysResource = 24,
761    /// `CAP_SYS_TIME` (25) : horloge système et RTC.
762    SysTime = 25,
763    /// `CAP_SYS_TTY_CONFIG` (26) : configuration TTY et `vhangup(2)`.
764    SysTtyConfig = 26,
765    /// `CAP_MKNOD` (27) : créer des fichiers spéciaux via `mknod(2)`.
766    Mknod = 27,
767    /// `CAP_LEASE` (28) : poser des baux (`fcntl(F_SETLEASE)`).
768    Lease = 28,
769    /// `CAP_AUDIT_WRITE` (29) : écrire des enregistrements dans le journal d'audit.
770    AuditWrite = 29,
771    /// `CAP_AUDIT_CONTROL` (30) : configurer/désactiver l'audit du kernel.
772    AuditControl = 30,
773    /// `CAP_SETFCAP` (31) : poser des file-capabilities.
774    Setfcap = 31,
775    /// `CAP_MAC_OVERRIDE` (32) : passer outre le MAC (LSM Smack…).
776    MacOverride = 32,
777    /// `CAP_MAC_ADMIN` (33) : administrer la politique MAC.
778    MacAdmin = 33,
779    /// `CAP_SYSLOG` (34) : opérations `syslog(2)` privilégiées.
780    Syslog = 34,
781    /// `CAP_WAKE_ALARM` (35) : armer un réveil qui sort le système de veille.
782    WakeAlarm = 35,
783    /// `CAP_BLOCK_SUSPEND` (36) : empêcher la mise en veille du système.
784    BlockSuspend = 36,
785    /// `CAP_AUDIT_READ` (37) : lire le journal d'audit via socket multicast.
786    AuditRead = 37,
787    /// `CAP_PERFMON` (38) : monitoring de performance (`perf_event_open`…).
788    Perfmon = 38,
789    /// `CAP_BPF` (39) : opérations `bpf(2)` privilégiées.
790    Bpf = 39,
791    /// `CAP_CHECKPOINT_RESTORE` (40) : checkpoint/restore (`CAP_LAST_CAP`).
792    CheckpointRestore = 40,
793}
794
795impl Capability {
796    /// Retourne l'index ABI kernel (valeur numérique de `CAP_*`).
797    #[must_use]
798    #[inline]
799    pub const fn as_raw(self) -> u32 {
800        self as u32
801    }
802}
803
804/// Bitmask des capabilities.
805///
806/// Représentation interne `u64` : le bit `i` est positionné ssi la
807/// `Capability` de valeur numérique `i` est dans le masque. Les Linux v3
808/// capabilities (kernel ≥ 2.6.25) utilisent 64 bits côté kernel, exposés
809/// par l'ABI `capget`/`capset` sous forme de deux mots `u32` (bits 0-31
810/// puis bits 32-63) ; la conversion est réalisée par les helpers du
811/// wrapper `air-sys-syscall`.
812#[repr(transparent)]
813#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
814pub struct CapabilityMask(u64);
815
816impl CapabilityMask {
817    /// Masque vide (aucune capability).
818    #[must_use]
819    #[inline]
820    pub const fn empty() -> Self {
821        Self(0)
822    }
823
824    /// Construit un masque depuis sa représentation brute u64.
825    #[must_use]
826    #[inline]
827    pub const fn from_bits(bits: u64) -> Self {
828        Self(bits)
829    }
830
831    /// Représentation brute u64 du masque.
832    #[must_use]
833    #[inline]
834    pub const fn bits(self) -> u64 {
835        self.0
836    }
837
838    /// Vrai si `cap` est dans le masque.
839    #[must_use]
840    #[inline]
841    pub const fn contains(self, cap: Capability) -> bool {
842        (self.0 & (1_u64 << cap.as_raw())) != 0
843    }
844
845    /// Retourne un nouveau masque avec `cap` positionné.
846    #[must_use]
847    #[inline]
848    pub const fn with(self, cap: Capability) -> Self {
849        Self(self.0 | (1_u64 << cap.as_raw()))
850    }
851
852    /// Retourne un nouveau masque avec `cap` retiré.
853    #[must_use]
854    #[inline]
855    pub const fn without(self, cap: Capability) -> Self {
856        Self(self.0 & !(1_u64 << cap.as_raw()))
857    }
858}
859
860/// Triple des ensembles de capabilities pour un thread.
861///
862/// Trois ensembles indépendants :
863/// - `effective` : capabilities **actuellement utilisables** par le thread.
864/// - `permitted` : capabilities que le thread **peut activer** dans
865///   `effective` (super-ensemble de `effective` typiquement).
866/// - `inheritable` : capabilities **transmises** à travers `execve(2)`.
867#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
868pub struct CapabilitySet {
869    /// Capabilities actuellement actives.
870    pub effective: CapabilityMask,
871    /// Capabilities activables dans `effective`.
872    pub permitted: CapabilityMask,
873    /// Capabilities transmises à `execve(2)`.
874    pub inheritable: CapabilityMask,
875}
876
877/// Cible d'un appel `capget`/`capset`.
878///
879/// Per ADR-021 convention 1, l'invocation « pour soi-même » est exprimée
880/// par la variante dédiée [`CapabilityTarget::CurrentThread`] plutôt que par une
881/// sentinelle kernel `pid == 0`.
882#[derive(Debug, Clone, Copy)]
883pub enum CapabilityTarget {
884    /// Thread courant (équivalent kernel `pid == 0`).
885    CurrentThread,
886    /// Thread désigné par son TID Linux.
887    Thread(Tid),
888    /// Processus désigné par son PID (premier thread du groupe).
889    Process(Pid),
890}
891
892#[cfg(test)]
893mod tests;
894
895// ─────────────────────────────────────────────────────────────────────────
896// Ressources — `getrusage(2)` (ADR-051, re-sceau couche-0-v1.7)
897// ─────────────────────────────────────────────────────────────────────────
898
899/// `struct timeval` du kernel (`time_t tv_sec`, `suseconds_t tv_usec`), tel que
900/// rempli par `getrusage(2)`. Layout `#[repr(C)]` fidèle, cibles 64-bit.
901#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
902#[repr(C)]
903pub struct Timeval {
904    /// Secondes.
905    pub tv_sec: i64,
906    /// Microsecondes (`[0, 1_000_000)`).
907    pub tv_usec: i64,
908}
909
910/// Cible d'une mesure [`Rusage`] pour `getrusage(2)`. Re-présente `RUSAGE_*` en
911/// enum typé (pas de constante magique exposée — ADR-021).
912#[derive(Debug, Clone, Copy, PartialEq, Eq)]
913pub enum RusageWho {
914    /// Le processus appelant (`RUSAGE_SELF`).
915    SelfProcess,
916    /// Tous les enfants terminés **et attendus** (`RUSAGE_CHILDREN`).
917    Children,
918    /// Le thread appelant (`RUSAGE_THREAD`).
919    Thread,
920}
921
922/// `struct rusage` du kernel : statistiques de ressources (`getrusage(2)`).
923///
924/// Layout `#[repr(C)]` **fidèle au kernel** (2 `timeval` + 14 `long`), cibles
925/// 64-bit (ADR-014). Plusieurs champs ne sont **pas maintenus** par Linux (legacy
926/// BSD) et restent à `0` — on les expose tels quels (transparence, kernel = bible).
927#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
928#[repr(C)]
929pub struct Rusage {
930    /// Temps CPU passé en mode **utilisateur**.
931    pub ru_utime: Timeval,
932    /// Temps CPU passé en mode **noyau**.
933    pub ru_stime: Timeval,
934    /// Taille mémoire résidente **maximale** (Kio).
935    pub ru_maxrss: i64,
936    /// Mémoire partagée (non maintenu par Linux).
937    pub ru_ixrss: i64,
938    /// Mémoire data non partagée (non maintenu).
939    pub ru_idrss: i64,
940    /// Mémoire pile non partagée (non maintenu).
941    pub ru_isrss: i64,
942    /// Fautes de page **mineures** (sans I/O).
943    pub ru_minflt: i64,
944    /// Fautes de page **majeures** (avec I/O).
945    pub ru_majflt: i64,
946    /// Swaps (non maintenu).
947    pub ru_nswap: i64,
948    /// Lectures bloc en entrée.
949    pub ru_inblock: i64,
950    /// Écritures bloc en sortie.
951    pub ru_oublock: i64,
952    /// Messages IPC envoyés (non maintenu).
953    pub ru_msgsnd: i64,
954    /// Messages IPC reçus (non maintenu).
955    pub ru_msgrcv: i64,
956    /// Signaux reçus (non maintenu).
957    pub ru_nsignals: i64,
958    /// Changements de contexte **volontaires**.
959    pub ru_nvcsw: i64,
960    /// Changements de contexte **involontaires**.
961    pub ru_nivcsw: i64,
962}
963
964#[cfg(test)]
965mod rusage_types_tests;