Skip to main content

air_sys_types/
errno.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//! Type d'erreur minimaliste de la couche 0 (cf. ADR-019).
6//!
7//! **Jeu d'errno piloté par les besoins réels.** On n'expose que les
8//! constantes effectivement référencées — soit par le code/les tests de la
9//! couche 0, soit par un **contrat couche 1 landé** qui a besoin de mapper
10//! l'errno **par nom** (extension additive du sceau, prompt 074 ; cf. le bloc
11//! « errno complétés par les contrats couche 1 »). La spec couche 0 prévoit
12//! ~140 constantes errno et des méthodes auxiliaires (`name()`,
13//! `description()`) qui seront ajoutées dans la PR dédiée à ADR-019 / `Errno`
14//! complet. **Ne pas ajouter une constante sans appelant** (couche 0 ou
15//! contrat couche 1) — elle figerait son design hors de cette logique et
16//! créerait du code mort.
17//!
18//! `Errno` enveloppe un `NonZeroI32` correspondant à un code errno Linux.
19//! Aucune allocation, aucun contexte attaché : c'est l'objectif explicite
20//! du modèle d'erreurs hybride à deux niveaux (les couches supérieures
21//! enrichissent l'erreur avec du contexte de domaine).
22
23use core::num::NonZeroI32;
24
25/// Code d'erreur kernel Linux.
26///
27/// `#[repr(transparent)]` sur `NonZeroI32` : taille identique à un `i32`,
28/// et `Option<Errno>` occupe également cette taille (niche optimization).
29#[repr(transparent)]
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub struct Errno(NonZeroI32);
32
33impl Errno {
34    /// Operation not permitted (1).
35    ///
36    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
37    /// `x86_64` et `aarch64`.
38    pub const EPERM: Self = Self(unwrap_nz(1));
39
40    /// No such file or directory (2).
41    ///
42    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
43    /// `x86_64` et `aarch64`. Référencé : `openat`/`openat2` quand le
44    /// chemin n'existe pas.
45    pub const ENOENT: Self = Self(unwrap_nz(2));
46
47    /// No such process (3).
48    ///
49    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
50    /// `x86_64` et `aarch64`. Référencé : `pidfd_open` quand le PID
51    /// cible n'existe plus.
52    pub const ESRCH: Self = Self(unwrap_nz(3));
53
54    /// Interrupted system call (4).
55    ///
56    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
57    /// `x86_64` et `aarch64`. Référencé : convention 2 d'ADR-021,
58    /// remonté tel quel par `waitid`.
59    pub const EINTR: Self = Self(unwrap_nz(4));
60
61    /// Bad file descriptor (9).
62    ///
63    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
64    /// `x86_64` et `aarch64`. Référencé : test prouvant que `Drop` sur
65    /// `PidFd` ferme bien le FD (`waitid` sur le `RawFd` post-drop
66    /// retourne `EBADF`).
67    pub const EBADF: Self = Self(unwrap_nz(9));
68
69    /// Try again / operation would block (11).
70    ///
71    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
72    /// `x86_64` et `aarch64`. Référencé : lectures non bloquantes sur
73    /// `signalfd` et `timerfd` sans événement disponible.
74    pub const EAGAIN: Self = Self(unwrap_nz(11));
75
76    /// No child processes (10).
77    ///
78    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
79    /// `x86_64` et `aarch64`. Référencé : `waitid` quand la cible n'a
80    /// pas d'enfant.
81    pub const ECHILD: Self = Self(unwrap_nz(10));
82
83    /// Out of memory (12).
84    ///
85    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
86    /// `x86_64` et `aarch64`. Référencé : `getrandom` quand la mémoire
87    /// kernel est insuffisante.
88    pub const ENOMEM: Self = Self(unwrap_nz(12));
89
90    /// Bad address / fault (14).
91    ///
92    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
93    /// `x86_64` et `aarch64`. Référencé : syscalls qui accèdent à de la
94    /// mémoire utilisateur (ne se produit pas via l'API safe).
95    pub const EFAULT: Self = Self(unwrap_nz(14));
96
97    /// Invalid argument (22).
98    ///
99    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
100    /// `x86_64` et `aarch64`. Référencé : `clone3` rejet en amont
101    /// (`stack: Some(_)` ou flags création de thread), `waitid` options
102    /// vides.
103    pub const EINVAL: Self = Self(unwrap_nz(22));
104
105    /// File table overflow / quota de FD système atteint (23).
106    ///
107    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
108    /// `x86_64` et `aarch64`. Référencé : `eventfd2`, `pipe2` quand la
109    /// limite système de FD ouverts est atteinte.
110    pub const ENFILE: Self = Self(unwrap_nz(23));
111
112    /// Too many open files / quota de FD du processus atteint (24).
113    ///
114    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
115    /// `x86_64` et `aarch64`. Référencé : `eventfd2`, `pipe2` quand le
116    /// quota de FD du processus est épuisé.
117    pub const EMFILE: Self = Self(unwrap_nz(24));
118
119    /// Permission denied (13).
120    ///
121    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
122    /// `x86_64` et `aarch64`. Référencé : `mmap`/`mprotect` quand les
123    /// permissions sont refusées (ex. `PROT_EXEC` sur FS `noexec`).
124    pub const EACCES: Self = Self(unwrap_nz(13));
125
126    /// No such device (19).
127    ///
128    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
129    /// `x86_64` et `aarch64`. Référencé : `mmap` quand le FD référence
130    /// un type de fichier non mappable.
131    pub const ENODEV: Self = Self(unwrap_nz(19));
132
133    /// File exists (17).
134    ///
135    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
136    /// `x86_64` et `aarch64`. Référencé : `mmap` avec `MAP_FIXED_NOREPLACE`
137    /// quand l'adresse cible est déjà occupée.
138    pub const EEXIST: Self = Self(unwrap_nz(17));
139
140    /// Function not implemented (38).
141    ///
142    /// Valeur extraite de `asm-generic/errno.h` ; identique sur
143    /// `x86_64` et `aarch64`. Référencé : `memfd_secret` sur kernel < 5.14
144    /// ou sans support compilé.
145    pub const ENOSYS: Self = Self(unwrap_nz(38));
146
147    /// Illegal seek on pipe or FIFO (29).
148    ///
149    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
150    /// `x86_64` et `aarch64`. Référencé : `splice` quand un offset non
151    /// nul est passé pour un FD pipe (non seekable).
152    pub const ESPIPE: Self = Self(unwrap_nz(29));
153
154    /// Device or resource busy (16).
155    ///
156    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur
157    /// `x86_64` et `aarch64`. Référencé : back-pressure structurelle du slab
158    /// io_uring (S1) — `submit_*` refuse poliment quand `in_flight == capacity`,
159    /// avant tout syscall.
160    pub const EBUSY: Self = Self(unwrap_nz(16));
161
162    /// Timer expired (62).
163    ///
164    /// Valeur extraite de `asm-generic/errno.h` ; identique sur `x86_64` et
165    /// `aarch64`. Référencé : expiration d'attente io_uring
166    /// (`wait_completion_timeout`, selon le mapping interne `EXT_ARG`).
167    pub const ETIME: Self = Self(unwrap_nz(62));
168
169    /// Operation already in progress (114).
170    ///
171    /// Valeur extraite de `asm-generic/errno.h` ; identique sur `x86_64` et
172    /// `aarch64`. Référencé : `io_uring sync_cancel` quand une annulation est
173    /// déjà en cours pour la cible visée.
174    pub const EALREADY: Self = Self(unwrap_nz(114));
175
176    /// Operation canceled (125).
177    ///
178    /// Valeur extraite de `asm-generic/errno.h` ; identique sur `x86_64` et
179    /// `aarch64`. Référencé : `res == -ECANCELED` d'un CQE d'opération annulée
180    /// pendant la quiescence io_uring (téardown S2) — déroulement **nominal**,
181    /// pas un échec.
182    pub const ECANCELED: Self = Self(unwrap_nz(125));
183
184    /// Argument list too long (7).
185    ///
186    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur `x86_64`
187    /// et `aarch64`. Référencé : `bpf_map_create` quand la carte demandée est
188    /// trop grande.
189    pub const E2BIG: Self = Self(unwrap_nz(7));
190
191    /// Inappropriate ioctl for device (25).
192    ///
193    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur `x86_64`
194    /// et `aarch64`. Référencé : un `ioctl` `EVIOC*` sur un FD qui n'est pas un
195    /// périphérique d'entrée evdev.
196    pub const ENOTTY: Self = Self(unwrap_nz(25));
197
198    /// Numerical result out of range (34).
199    ///
200    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur `x86_64`
201    /// et `aarch64`. Extension additive du sceau pilotée par un contrat couche
202    /// 1 : `air_terminal::tty::ttyname` la retourne quand le tampon fourni par
203    /// l'appelant est trop petit pour le chemin du terminal (sémantique POSIX
204    /// `ttyname_r`).
205    pub const ERANGE: Self = Self(unwrap_nz(34));
206
207    /// No space left on device (28).
208    ///
209    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur `x86_64`
210    /// et `aarch64`. Référencé : buffer de log du vérifieur eBPF trop court
211    /// (`bpf_program_load` / `bpf_btf_load`).
212    pub const ENOSPC: Self = Self(unwrap_nz(28));
213
214    /// Protocol error (71).
215    ///
216    /// Valeur extraite de `asm-generic/errno.h` ; identique sur `x86_64` et
217    /// `aarch64`. Référencé : lecture evdev d'un nombre d'octets non multiple
218    /// de `size_of::<input_event>()`.
219    pub const EPROTO: Self = Self(unwrap_nz(71));
220
221    /// Bad message (74).
222    ///
223    /// Valeur extraite de `asm-generic/errno.h` ; identique sur `x86_64` et
224    /// `aarch64`. Référencé : décodage uevent — message vide, en-tête
225    /// `libudev` tronqué / magic absent / offsets hors borne.
226    pub const EBADMSG: Self = Self(unwrap_nz(74));
227
228    /// Message too long (90).
229    ///
230    /// Valeur extraite de `asm-generic/errno.h` ; identique sur `x86_64` et
231    /// `aarch64`. Référencé : message uevent tronqué (buffer de réception trop
232    /// petit).
233    pub const EMSGSIZE: Self = Self(unwrap_nz(90));
234
235    /// Operation not supported (95).
236    ///
237    /// Valeur extraite de `asm-generic/errno.h` ; identique sur `x86_64` et
238    /// `aarch64`. Référencé : opérations eBPF / `perf_event_open` non
239    /// supportées par le kernel courant.
240    pub const EOPNOTSUPP: Self = Self(unwrap_nz(95));
241
242    /// No buffer space available (105).
243    ///
244    /// Valeur extraite de `asm-generic/errno.h` ; identique sur `x86_64` et
245    /// `aarch64`. Référencé : file de réception netlink uevent saturée.
246    pub const ENOBUFS: Self = Self(unwrap_nz(105));
247
248    // ── errno complétés par les contrats couche 1 (extension additive du
249    //    sceau, prompt 074). Valeurs `asm-generic`, identiques x86_64/aarch64
250    //    (x86_64 `asm/errno.h` = `#include <asm-generic/errno.h>` ; aarch64
251    //    hérite de la même table générique — vérifié contre les headers uapi).
252    //    Ces constantes ont des appelants en couche 1 (mapping `From<Errno>`
253    //    d'`air-base-lib`, `# Errors` d'`air-filesystem`) : pas du code mort.
254
255    /// Cross-device link (18).
256    ///
257    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur `x86_64`
258    /// et `aarch64`. Référencé : contrat `air-filesystem` (`# Errors` —
259    /// copie/renommage inter-FS, repli `read`/`write` sur `EXDEV`).
260    pub const EXDEV: Self = Self(unwrap_nz(18));
261
262    /// Broken pipe (32).
263    ///
264    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur `x86_64`
265    /// et `aarch64`. Référencé : contrat `air-base-lib` §1
266    /// (`From<Errno>` → `AirErrorKind::BrokenResource`).
267    pub const EPIPE: Self = Self(unwrap_nz(32));
268
269    /// Too many symbolic links encountered (40).
270    ///
271    /// Valeur extraite de `asm-generic/errno.h` ; identique sur `x86_64` et
272    /// `aarch64`. Référencé : contrat `air-filesystem` (`# Errors` — boucle de
273    /// symlink, tentative d'évasion de confinement).
274    pub const ELOOP: Self = Self(unwrap_nz(40));
275
276    /// Connection reset by peer (104).
277    ///
278    /// Valeur extraite de `asm-generic/errno.h` ; identique sur `x86_64` et
279    /// `aarch64`. Référencé : contrat `air-base-lib` §1
280    /// (`From<Errno>` → `AirErrorKind::BrokenResource`).
281    pub const ECONNRESET: Self = Self(unwrap_nz(104));
282
283    /// Connection timed out (110).
284    ///
285    /// Valeur extraite de `asm-generic/errno.h` ; identique sur `x86_64` et
286    /// `aarch64`. Référencé : contrat `air-base-lib` §1
287    /// (`From<Errno>` → `AirErrorKind::TimedOut`). **Distinct** d'[`Errno::ETIME`]
288    /// (62, « timer expired ») — `ETIMEDOUT` est 110, « connection timed out ».
289    pub const ETIMEDOUT: Self = Self(unwrap_nz(110));
290
291    /// Exec format error (8).
292    ///
293    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur `x86_64`
294    /// et `aarch64`. Extension additive pilotée par le contrat couche 1
295    /// (`air-process`) : `execve`/`execveat` sur un fichier qui n'est pas dans
296    /// un format exécutable reconnu (ni ELF, ni script avec `#!`).
297    pub const ENOEXEC: Self = Self(unwrap_nz(8));
298
299    /// Not a directory (20).
300    ///
301    /// Valeur extraite de `asm-generic/errno-base.h` ; identique sur `x86_64`
302    /// et `aarch64`. Extension additive pilotée par le contrat couche 1
303    /// (`air-process`) : `fchdir` sur un fd qui ne référence pas un répertoire,
304    /// ou `chdir`/`execveat` dont un composant du chemin n'est pas un
305    /// répertoire.
306    pub const ENOTDIR: Self = Self(unwrap_nz(20));
307
308    /// Operation would block — **alias** de [`Errno::EAGAIN`] (valeur 11).
309    ///
310    /// Sur Linux `EWOULDBLOCK == EAGAIN` (même valeur). Exposé comme alias
311    /// nommé pour que les contrats qui le citent (`air-base-lib` §1 :
312    /// `EAGAIN`/`EWOULDBLOCK` → `WouldBlock`) puissent mapper par nom ;
313    /// `EWOULDBLOCK == EAGAIN` reste vrai (`PartialEq` sur la valeur brute).
314    pub const EWOULDBLOCK: Self = Self::EAGAIN;
315
316    /// Construit un `Errno` à partir d'un brut non nul déjà validé.
317    #[must_use]
318    #[inline]
319    pub const fn from_nonzero(raw: NonZeroI32) -> Self {
320        Self(raw)
321    }
322
323    /// Tente de construire un `Errno` à partir d'un entier brut.
324    ///
325    /// Retourne `None` si `raw` est nul (erreur kernel impossible) ou
326    /// négatif (les errno kernel sont des entiers positifs).
327    #[must_use]
328    #[inline]
329    pub const fn try_from_raw(raw: i32) -> Option<Self> {
330        match NonZeroI32::new(raw) {
331            Some(nz) if nz.get() > 0 => Some(Self(nz)),
332            _ => None,
333        }
334    }
335
336    /// Retourne la représentation brute (positive) du code errno.
337    #[must_use]
338    #[inline]
339    pub const fn as_raw(self) -> i32 {
340        self.0.get()
341    }
342}
343
344impl core::fmt::Display for Errno {
345    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
346        write!(f, "errno {}", self.as_raw())
347    }
348}
349
350impl core::error::Error for Errno {}
351
352/// Helper const pour construire un `NonZeroI32` à partir d'une constante
353/// littérale strictement positive. `NonZeroI32::new(...).unwrap()` n'est pas
354/// const-friendly avant Rust 1.83 ; cette construction l'est et reste
355/// vérifiée (panique au compile-time si l'invariant est violé).
356const fn unwrap_nz(n: i32) -> NonZeroI32 {
357    match NonZeroI32::new(n) {
358        Some(v) => v,
359        // Atteint UNIQUEMENT si une constante littérale ci-dessus est nulle —
360        // détecté au compile-time, jamais à l'exécution.
361        None => panic!("Errno constants must be non-zero"),
362    }
363}
364
365#[cfg(test)]
366mod tests;