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;