Skip to main content

air_sys_types/
fs.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 de la famille `fs` — filesystem, ouverture, métadonnées, etc.
6//!
7//! Cf. `docs/specs/layer-0/family-fs.md`.
8
9use alloc::vec::Vec;
10
11use bitflags::bitflags;
12
13use crate::fd::BorrowedFd;
14
15// ─────────────────────────────────────────────────────────────────────────
16// Type de base : alias Mode
17// ─────────────────────────────────────────────────────────────────────────
18
19/// Permissions POSIX d'un fichier (bits `mode_t`).
20///
21/// Valeurs typiques : `0o644` (rw-r--r--), `0o755` (rwxr-xr-x),
22/// `0o600` (rw-------). Seuls les 12 bits inférieurs sont significatifs
23/// (9 bits de permission + 3 bits setuid/setgid/sticky).
24pub type Mode = u32;
25
26// ─────────────────────────────────────────────────────────────────────────
27// DirFd — base pour les opérations *at
28// ─────────────────────────────────────────────────────────────────────────
29
30/// Base d'un appel `*at` (cf. ADR-021 convention 1).
31///
32/// Remplace la sentinelle kernel `AT_FDCWD` (`-100`) par une variante
33/// `Cwd` typée. Air exige une base explicite sur toutes les opérations
34/// filesystem — pas de dépendance implicite au `cwd` global du processus.
35///
36/// # Examples
37///
38/// ```no_run
39/// use air_sys_types::fs::DirFd;
40/// // Ouvrir un fichier par rapport au répertoire courant
41/// let _dirfd = DirFd::Cwd;
42/// ```
43#[derive(Debug, Clone, Copy)]
44pub enum DirFd<'fd> {
45    /// Répertoire courant du processus (équivalent à `AT_FDCWD`).
46    Cwd,
47    /// FD d'un répertoire ouvert (accès relatif à ce répertoire).
48    Fd(BorrowedFd<'fd>),
49}
50
51// ─────────────────────────────────────────────────────────────────────────
52// openat2 / openat
53// ─────────────────────────────────────────────────────────────────────────
54
55bitflags! {
56    /// Flags d'ouverture de fichier pour `openat2(2)` / `openat(2)`.
57    ///
58    /// Correspond aux constantes `O_*` de `<fcntl.h>`.
59    /// `CLOEXEC` est ajouté **automatiquement** par le wrapper — l'appelant
60    /// ne doit pas l'insérer manuellement.
61    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
62    pub struct OpenFlags: u64 {
63        /// Lecture seule.
64        const RDONLY    = 0;
65        /// Écriture seule.
66        const WRONLY    = 1;
67        /// Lecture et écriture.
68        const RDWR      = 2;
69        /// Crée le fichier s'il n'existe pas.
70        const CREAT     = 0o0000_0100;
71        /// Échec si le fichier existe déjà (avec `CREAT`).
72        const EXCL      = 0o0000_0200;
73        /// Ne prend pas le terminal comme terminal de contrôle.
74        const NOCTTY    = 0o0000_0400;
75        /// Tronque le fichier existant à zéro.
76        const TRUNC     = 0o0000_1000;
77        /// Écriture en mode ajout.
78        const APPEND    = 0o0000_2000;
79        /// Non-bloquant.
80        const NONBLOCK  = 0o0000_4000;
81        /// Sync à chaque écriture sur les données et les métadonnées.
82        const DSYNC     = 0o0001_0000;
83        /// Mode asynchrone POSIX (SIGIO).
84        const ASYNC     = 0o0002_0000;
85        /// Accès direct (pas de cache kernel).
86        ///
87        /// **Valeur dépendante de l'architecture** (ABI `asm-generic` sur
88        /// aarch64 vs `x86`) : `0o40000` sur x86_64, `0o200000` sur aarch64.
89        const DIRECT    = if cfg!(target_arch = "x86_64") { 0o0004_0000 } else { 0o0020_0000 };
90        /// Fichier potentiellement large (obsolète en 64 bits).
91        ///
92        /// **Valeur dépendante de l'architecture** : `0o100000` sur x86_64,
93        /// `0o400000` sur aarch64.
94        const LARGEFILE = if cfg!(target_arch = "x86_64") { 0o0010_0000 } else { 0o0040_0000 };
95        /// Doit être un répertoire.
96        ///
97        /// **Valeur dépendante de l'architecture** : `0o200000` sur x86_64,
98        /// `0o40000` sur aarch64. (Une valeur figée x86 cassait `openat` avec
99        /// `EINVAL` sur aarch64 — elle y désignait en réalité `O_DIRECT`.)
100        const DIRECTORY = if cfg!(target_arch = "x86_64") { 0o0020_0000 } else { 0o0004_0000 };
101        /// Ne suit pas les liens symboliques.
102        ///
103        /// **Valeur dépendante de l'architecture** : `0o400000` sur x86_64,
104        /// `0o100000` sur aarch64.
105        const NOFOLLOW  = if cfg!(target_arch = "x86_64") { 0o0040_0000 } else { 0o0010_0000 };
106        /// Pas de mise à jour de `atime`.
107        const NOATIME   = 0o0100_0000;
108        /// Ferme le FD à l'`exec` (ajouté automatiquement par le wrapper).
109        const CLOEXEC   = 0o0200_0000;
110        /// Ouvre sans accéder à l'objet (FD de chemin pur).
111        const PATH      = 0o1000_0000;
112        /// Crée un fichier temporaire sans nom dans le répertoire.
113        ///
114        /// `__O_TMPFILE | O_DIRECTORY` : hérite donc de la divergence d'arch
115        /// d'`O_DIRECTORY` — `0o20200000` sur x86_64, `0o20040000` sur aarch64.
116        const TMPFILE   = if cfg!(target_arch = "x86_64") { 0o2020_0000 } else { 0o2004_0000 };
117    }
118}
119
120bitflags! {
121    /// Flags de `close_range(2)` (Linux 5.9+) — fermeture en masse d'un intervalle
122    /// de descripteurs. Utilisé par le confinement (ADR-122) : un enfant forké ferme
123    /// tous les fd hérités hormis un keep-set avant de poser sa cage.
124    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
125    pub struct CloseRangeFlags: u32 {
126        /// `CLOSE_RANGE_UNSHARE` — détache la table de fd partagée avant la
127        /// fermeture (n'affecte que ce processus, pas les partageurs de table).
128        const UNSHARE = 0x0000_0002;
129        /// `CLOSE_RANGE_CLOEXEC` — positionne `O_CLOEXEC` sur l'intervalle **au lieu**
130        /// de le fermer (fermeture différée à l'`execve`).
131        const CLOEXEC = 0x0000_0004;
132    }
133}
134
135bitflags! {
136    /// Flags de résolution de chemin pour `openat2(2)` (Linux 5.6+).
137    ///
138    /// Ces flags permettent une sandbox de résolution de chemin stricte.
139    /// `BENEATH` et `IN_ROOT` sont particulièrement utiles pour les
140    /// contextes sandboxés (launcher Air, couche 5).
141    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
142    pub struct ResolveFlags: u64 {
143        /// Refuse de traverser les frontières de système de fichiers.
144        const NO_XDEV        = 0x01;
145        /// Refuse les liens magiques (ex. `/proc/self/fd/X`).
146        const NO_MAGICLINKS  = 0x02;
147        /// Refuse tous les liens symboliques.
148        const NO_SYMLINKS    = 0x04;
149        /// La résolution ne peut pas remonter au-delà de `dirfd`
150        /// (protection contre `../../`).
151        const BENEATH        = 0x08;
152        /// Traite `dirfd` comme la racine de la résolution.
153        const IN_ROOT        = 0x10;
154        /// Utilise uniquement le cache VFS (pas d'E/S si absent).
155        const CACHED         = 0x20;
156    }
157}
158
159/// Structure d'ouverture pour `openat2(2)`.
160///
161/// Combine flags, mode et flags de résolution en un seul argument.
162/// `how.flags` inclut automatiquement `CLOEXEC` via le wrapper.
163#[derive(Debug, Clone, Copy, Default)]
164pub struct OpenHow {
165    /// Flags d'accès et de comportement.
166    pub flags: OpenFlags,
167    /// Permissions de création (ignoré si `CREAT` absent).
168    pub mode: Mode,
169    /// Politique de résolution de chemin (Linux 5.6+).
170    pub resolve: ResolveFlags,
171}
172
173// ─────────────────────────────────────────────────────────────────────────
174// statx
175// ─────────────────────────────────────────────────────────────────────────
176
177bitflags! {
178    /// Flags pour `statx(2)` (comportement de résolution de chemin).
179    ///
180    /// Cf. `linux/stat.h` : constantes `AT_*`.
181    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
182    pub struct StatxFlags: u32 {
183        /// Opère sur le FD lui-même sans résolution de chemin
184        /// (équivalent à `AT_EMPTY_PATH`).
185        const EMPTY_PATH        = 0x1000;
186        /// Ne déclenche pas de montage automount.
187        const NO_AUTOMOUNT      = 0x0800;
188        /// Ne suit pas les liens symboliques.
189        const SYMLINK_NOFOLLOW  = 0x0100;
190    }
191}
192
193bitflags! {
194    /// Masque des champs demandés / disponibles dans [`StatxResult`].
195    ///
196    /// Passé en entrée pour sélectionner les champs souhaités ;
197    /// retourné dans `StatxResult::mask` pour indiquer quels champs ont
198    /// été effectivement remplis par le kernel.
199    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
200    pub struct StatxMask: u32 {
201        /// Type de fichier (regular, dir, symlink…).
202        const TYPE   = 0x0001;
203        /// Bits de permissions (`mode & 0o7777`).
204        const MODE   = 0x0002;
205        /// Nombre de liens physiques.
206        const NLINK  = 0x0004;
207        /// UID du propriétaire.
208        const UID    = 0x0008;
209        /// GID du propriétaire.
210        const GID    = 0x0010;
211        /// Horodatage d'accès (`atime`).
212        const ATIME  = 0x0020;
213        /// Horodatage de modification de données (`mtime`).
214        const MTIME  = 0x0040;
215        /// Horodatage de modification de métadonnées (`ctime`).
216        const CTIME  = 0x0080;
217        /// Numéro d'inode.
218        const INO    = 0x0100;
219        /// Taille en octets.
220        const SIZE   = 0x0200;
221        /// Nombre de blocs de 512 octets alloués.
222        const BLOCKS = 0x0400;
223        /// Horodatage de création (`btime`, si supporté par le FS).
224        const BTIME  = 0x0800;
225        /// Identifiant de point de montage (Linux 5.8+).
226        const MNT_ID = 0x1000;
227        /// Tous les champs courants.
228        const ALL    = 0x0fff;
229    }
230}
231
232/// Horodatage retourné par `statx(2)`.
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
234pub struct StatxTimestamp {
235    /// Secondes depuis l'epoch Unix.
236    pub seconds: i64,
237    /// Nanosecondes (complément, toujours dans `[0, 999_999_999]`).
238    pub nanoseconds: u32,
239}
240
241/// Résultat de `statx(2)`.
242///
243/// Les champs non demandés (absent de `mask` en entrée) ou non disponibles
244/// (absent de `mask` en sortie) ont des valeurs indéfinies. Vérifiez
245/// toujours `mask.contains(StatxMask::XYZ)` avant de lire un champ.
246#[derive(Debug, Clone)]
247pub struct StatxResult {
248    /// Champs effectivement remplis par le kernel.
249    pub mask: StatxMask,
250    /// Granularité des E/S préférée (en octets).
251    pub blksize: u32,
252    /// Attributs supplémentaires (compressed, immutable, append-only…).
253    pub attributes: u64,
254    /// Nombre de liens physiques.
255    pub nlink: u32,
256    /// UID du propriétaire.
257    pub uid: u32,
258    /// GID du propriétaire.
259    pub gid: u32,
260    /// Type et permissions (`type << 12 | perm`).
261    pub mode: u16,
262    /// Numéro d'inode.
263    pub ino: u64,
264    /// Taille en octets.
265    pub size: u64,
266    /// Nombre de blocs de 512 octets alloués.
267    pub blocks: u64,
268    /// Horodatage d'accès.
269    pub atime: StatxTimestamp,
270    /// Horodatage de création.
271    pub btime: StatxTimestamp,
272    /// Horodatage de modification de métadonnées.
273    pub ctime: StatxTimestamp,
274    /// Horodatage de modification de données.
275    pub mtime: StatxTimestamp,
276    /// Numéro majeur du device (si fichier device).
277    pub rdev_major: u32,
278    /// Numéro mineur du device (si fichier device).
279    pub rdev_minor: u32,
280    /// Numéro majeur du device contenant le fichier.
281    pub dev_major: u32,
282    /// Numéro mineur du device contenant le fichier.
283    pub dev_minor: u32,
284    /// Identifiant du point de montage (Linux 5.8+, 0 si non disponible).
285    pub mount_id: u64,
286}
287
288// ─────────────────────────────────────────────────────────────────────────
289// faccessat
290// ─────────────────────────────────────────────────────────────────────────
291
292bitflags! {
293    /// Mode d'accès pour `faccessat(2)`.
294    ///
295    /// Cf. `linux/unistd.h` : constantes `F_OK`, `R_OK`, `W_OK`, `X_OK`.
296    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
297    pub struct AccessMode: u32 {
298        /// Vérifie l'existence du fichier.
299        const F_OK = 0;
300        /// Vérifie les droits de lecture.
301        const R_OK = 4;
302        /// Vérifie les droits d'écriture.
303        const W_OK = 2;
304        /// Vérifie les droits d'exécution.
305        const X_OK = 1;
306    }
307}
308
309bitflags! {
310    /// Flags comportementaux pour `faccessat(2)`.
311    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
312    pub struct AccessFlags: i32 {
313        /// Utilise l'UID/GID effectif (plutôt que réel).
314        const EACCESS         = 0x200;
315        /// Ne suit pas les liens symboliques.
316        const SYMLINK_NOFOLLOW = 0x100;
317    }
318}
319
320// ─────────────────────────────────────────────────────────────────────────
321// Répertoires
322// ─────────────────────────────────────────────────────────────────────────
323
324/// Type d'entrée de répertoire retourné par `getdents64(2)`.
325///
326/// Correspond au champ `d_type` de la struct `linux_dirent64`.
327/// La valeur `Unknown` (0) est produite sur les filesystems qui ne
328/// stockent pas le type (ex. certains systèmes de fichiers en réseau).
329#[derive(Debug, Clone, Copy, PartialEq, Eq)]
330#[repr(u8)]
331pub enum DirEntryType {
332    /// Type inconnu (filesystem ne stocke pas le type).
333    Unknown = 0,
334    /// FIFO (pipe nommé).
335    Fifo = 1,
336    /// Fichier caractère.
337    Character = 2,
338    /// Répertoire.
339    Directory = 4,
340    /// Fichier bloc.
341    Block = 6,
342    /// Fichier régulier.
343    Regular = 8,
344    /// Lien symbolique.
345    Symlink = 10,
346    /// Socket Unix.
347    Socket = 12,
348}
349
350/// Entrée de répertoire parsée depuis le buffer de `getdents64(2)`.
351///
352/// La durée de vie `'buffer` est liée au buffer de lecture — les données
353/// ne sont pas copiées.
354#[derive(Debug, Clone)]
355pub struct DirEntry {
356    /// Numéro d'inode.
357    pub inode: u64,
358    /// Offset de la prochaine entrée dans le flux de répertoire.
359    pub offset: i64,
360    /// Type de l'entrée.
361    pub file_type: DirEntryType,
362    /// Nom de l'entrée (sans le chemin parent), **octets bruts** propriétaires.
363    ///
364    /// Sur Unix un nom de fichier est une suite d'octets opaque (qui peut ne
365    /// pas être de l'UTF-8 valide), terminée par `NUL` dans le buffer kernel
366    /// mais stockée ici **sans** le `NUL`. Le type est volontairement
367    /// [`alloc::vec::Vec<u8>`] (octets) et non `OsString`/`String` : la couche 0
368    /// ne présume aucun encodage (Principe 3, doctrine « chemins = octets »).
369    pub name: alloc::vec::Vec<u8>,
370}
371
372bitflags! {
373    /// Flags pour `renameat2(2)` (cf. `linux/fs.h`).
374    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
375    pub struct RenameFlags: u32 {
376        /// Ne remplace pas la destination si elle existe.
377        const NOREPLACE = 1;
378        /// Échange atomiquement source et destination.
379        const EXCHANGE  = 2;
380        /// Pose un whiteout sur la source (union mounts).
381        const WHITEOUT  = 4;
382    }
383}
384
385// ─────────────────────────────────────────────────────────────────────────
386// lseek
387// ─────────────────────────────────────────────────────────────────────────
388
389/// Origine pour `lseek(2)`.
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
391#[repr(i32)]
392pub enum SeekWhence {
393    /// Depuis le début du fichier.
394    Set = 0,
395    /// Depuis la position courante.
396    Current = 1,
397    /// Depuis la fin du fichier.
398    End = 2,
399    /// Depuis la prochaine zone de données après l'offset (Linux).
400    Data = 3,
401    /// Depuis le prochain trou après l'offset (Linux).
402    Hole = 4,
403}
404
405// ─────────────────────────────────────────────────────────────────────────
406// utimensat
407// ─────────────────────────────────────────────────────────────────────────
408
409/// Valeur d'horodatage pour `utimensat(2)`.
410#[derive(Debug, Clone, Copy, PartialEq, Eq)]
411pub enum UtimeValue {
412    /// Met l'horodatage à l'heure courante (`UTIME_NOW`).
413    Now,
414    /// Ne modifie pas cet horodatage (`UTIME_OMIT`).
415    Omit,
416    /// Met l'horodatage à la valeur fournie.
417    Time(StatxTimestamp),
418}
419
420// ─────────────────────────────────────────────────────────────────────────
421// fallocate
422// ─────────────────────────────────────────────────────────────────────────
423
424bitflags! {
425    /// Mode pour `fallocate(2)` (cf. `linux/falloc.h`).
426    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
427    pub struct FallocateMode: i32 {
428        /// Garde la taille du fichier inchangée (alloue de l'espace sans étendre).
429        const KEEP_SIZE      = 0x01;
430        /// Perfore un trou dans le fichier (libère les blocs, laisse un trou).
431        const PUNCH_HOLE     = 0x02;
432        /// Collapse une plage du fichier en supprimant les blocs.
433        const COLLAPSE_RANGE = 0x08;
434        /// Remplace une plage par des zéros sans désallouer les blocs.
435        const ZERO_RANGE     = 0x10;
436        /// Insère de l'espace dans le fichier (décale le contenu).
437        const INSERT_RANGE   = 0x20;
438        /// Dé-partage les blocs copy-on-write.
439        const UNSHARE_RANGE  = 0x40;
440    }
441}
442
443// ─────────────────────────────────────────────────────────────────────────
444// fcntl individuel
445// ─────────────────────────────────────────────────────────────────────────
446
447bitflags! {
448    /// Flags de descripteur de fichier (`F_GETFD`/`F_SETFD`).
449    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
450    pub struct FdFlags: i32 {
451        /// Ferme le FD à l'`exec`.
452        const CLOEXEC = 1;
453    }
454}
455
456bitflags! {
457    /// Flags de statut de fichier (`F_GETFL`/`F_SETFL`).
458    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
459    pub struct StatusFlags: i32 {
460        /// Mode non-bloquant.
461        const NONBLOCK = 0x0000_0800;
462        /// Mode append.
463        const APPEND   = 0x0000_0400;
464        /// Sync asynchrone.
465        const ASYNC    = 0x0002_0000;
466        /// Sync synchrone sur les données.
467        const DSYNC    = 0x0000_1000;
468        /// Accès direct (no cache).
469        ///
470        /// **Valeur dépendante de l'architecture** (`O_DIRECT`) : `0x4000` sur
471        /// x86_64, `0x10000` sur aarch64.
472        const DIRECT   = if cfg!(target_arch = "x86_64") { 0x0000_4000 } else { 0x0001_0000 };
473    }
474}
475
476/// Type de verrou pour `flock` noyau (opérations `F_SETLK`/`F_GETLK`).
477#[derive(Debug, Clone, Copy, PartialEq, Eq)]
478#[repr(i16)]
479pub enum LockType {
480    /// Verrou de lecture (partagé).
481    Read = 0,
482    /// Verrou d'écriture (exclusif).
483    Write = 1,
484    /// Libère le verrou.
485    Unlock = 2,
486}
487
488/// Verrou de fichier POSIX (pour `fcntl F_SETLK`/`F_SETLKW`/`F_GETLK`).
489///
490/// Correspond à `struct flock` de `<fcntl.h>`.
491#[derive(Debug, Clone, Copy)]
492pub struct FileLock {
493    /// Type du verrou.
494    pub lock_type: LockType,
495    /// Origine pour `start` (comme `SeekWhence`, `SeekWhence::Set` = 0).
496    pub whence: SeekWhence,
497    /// Offset de début (relatif à `whence`).
498    pub start: i64,
499    /// Longueur verrouillée (0 = jusqu'à la fin du fichier).
500    pub length: i64,
501    /// PID du processus détenant le verrou (rempli par `F_GETLK`).
502    pub pid: i32,
503}
504
505bitflags! {
506    /// Scellements d'un `memfd` (cf. `linux/fcntl.h` : `F_SEAL_*`).
507    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
508    pub struct Seals: u32 {
509        /// Interdit d'ajouter de nouveaux scellements.
510        const SEAL       = 1;
511        /// Interdit de réduire la taille du fichier.
512        const SHRINK     = 2;
513        /// Interdit d'augmenter la taille du fichier.
514        const GROW       = 4;
515        /// Interdit toute écriture dans le fichier.
516        const WRITE      = 8;
517        /// Interdit les écritures futures même via des mappings existants.
518        const FUTURE_WRITE = 16;
519        /// Interdit la création de nouveaux mappings exécutables.
520        const EXEC       = 32;
521    }
522}
523
524// ─────────────────────────────────────────────────────────────────────────
525// name_to_handle_at / open_by_handle_at
526// ─────────────────────────────────────────────────────────────────────────
527
528bitflags! {
529    /// Flags pour `name_to_handle_at(2)`.
530    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
531    pub struct NameToHandleFlags: i32 {
532        /// Ne suit pas les liens symboliques.
533        const SYMLINK_NOFOLLOW = 0x100;
534        /// Opère sur le FD lui-même (équivalent à `AT_EMPTY_PATH`).
535        const EMPTY_PATH       = 0x1000;
536    }
537}
538
539/// Handle de fichier persistant retourné par `name_to_handle_at(2)`.
540///
541/// Permet de référencer un fichier même après un changement de chemin.
542/// Opaque : la taille et le contenu de `handle_bytes` dépendent du
543/// filesystem.
544///
545/// **Allocation heap :** la taille du handle est dynamique et déterminée
546/// par le kernel au premier appel. Nécessité documentée (ADR-021
547/// convention 4).
548#[derive(Debug, Clone)]
549pub struct FileHandle {
550    /// Identifiant de type de handle (opaque, défini par le filesystem).
551    pub handle_type: i32,
552    /// Octets du handle (taille et contenu définis par le filesystem).
553    pub handle_bytes: Vec<u8>,
554    /// Identifiant de point de montage (retourné par `name_to_handle_at`).
555    pub mount_id: i32,
556}
557
558// ─────────────────────────────────────────────────────────────────────────
559// statfs / fstatfs
560// ─────────────────────────────────────────────────────────────────────────
561
562/// Type de filesystem (champ `f_type` de `statfs(2)`).
563///
564/// Enveloppe la valeur magique retournée par le kernel identifiant le type de FS.
565#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
566pub struct FsType(pub i64);
567
568impl FsType {
569    /// ext4 (0xEF53)
570    pub const EXT4: Self = Self(0xEF53);
571    /// tmpfs (0x0102_1994)
572    pub const TMPFS: Self = Self(0x0102_1994);
573    /// proc (0x9FA0)
574    pub const PROC: Self = Self(0x9FA0);
575    /// sysfs (0x6265_6572)
576    pub const SYSFS: Self = Self(0x6265_6572);
577    /// btrfs (0x9123_683E)
578    pub const BTRFS: Self = Self(0x9123_683E);
579}
580
581/// Résultat de `statfs(2)` / `fstatfs(2)` — informations sur le système de fichiers.
582///
583/// Mappé depuis la struct kernel `statfs64` de manière indépendante de l'architecture.
584/// Le wrapper Air n'expose jamais le layout kernel brut.
585#[derive(Debug, Clone, Copy)]
586pub struct StatFsResult {
587    /// Type du système de fichiers (voir [`FsType`]).
588    pub f_type: FsType,
589    /// Taille optimale des blocs de transfert (octets).
590    pub f_bsize: i64,
591    /// Taille de fragment (blocs FS fondamentaux, octets).
592    pub f_frsize: i64,
593    /// Nombre total de blocs de données.
594    pub f_blocks: u64,
595    /// Nombre de blocs libres.
596    pub f_bfree: u64,
597    /// Nombre de blocs disponibles pour les non-privilégiés.
598    pub f_bavail: u64,
599    /// Nombre total d'inodes.
600    pub f_files: u64,
601    /// Nombre d'inodes libres.
602    pub f_ffree: u64,
603    /// Identifiant du système de fichiers (opaque).
604    pub f_fsid: [u32; 2],
605    /// Longueur maximale d'un nom de fichier.
606    pub f_namelen: i64,
607    /// Drapeaux du FS (ex. `ST_RDONLY`, `ST_NOEXEC`).
608    pub f_flags: i64,
609}
610
611// ─────────────────────────────────────────────────────────────────────────
612// Types partagés ajoutés pour io_uring Temps 2a (cf.
613// docs/specs/layer-0/io-uring-2a-filesystem.md). Ces types vivent dans la
614// famille `fs` car ils décrivent la sémantique des opérations filesystem,
615// qu'elles soient exposées de façon synchrone (`air-sys-syscall::fs`) ou
616// asynchrone (`air-sys-syscall::io_uring`). Valeurs : uapi Linux 6.12.
617// ─────────────────────────────────────────────────────────────────────────
618
619bitflags! {
620    /// Drapeaux de `fsync` via io_uring (`IORING_FSYNC_*`).
621    ///
622    /// `DATASYNC` sélectionne la sémantique `fdatasync` (ne synchronise pas les
623    /// métadonnées non essentielles à la relecture des données).
624    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
625    pub struct FsyncFlags: u32 {
626        /// `IORING_FSYNC_DATASYNC` : sémantique `fdatasync`.
627        const DATASYNC = 1 << 0;
628    }
629}
630
631bitflags! {
632    /// Drapeaux de `sync_file_range(2)` (cf. `linux/fs.h` : `SYNC_FILE_RANGE_*`).
633    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
634    pub struct SyncFileRangeFlags: u32 {
635        /// Attend l'écriture des pages déjà en cours avant de lancer la plage.
636        const WAIT_BEFORE = 1;
637        /// Lance l'écriture des pages sales de la plage.
638        const WRITE       = 2;
639        /// Attend l'écriture des pages de la plage après les avoir lancées.
640        const WAIT_AFTER  = 4;
641    }
642}
643
644bitflags! {
645    /// Drapeaux de changement de permissions pour `fchmodat2(2)` (`AT_*`).
646    ///
647    /// N'a de sens **qu'avec** `fchmodat2` (4 arguments, Linux ≥ 6.6) : le syscall
648    /// historique `fchmodat` n'en prend que 3 et ne peut donc porter aucun drapeau.
649    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
650    pub struct ChmodFlags: i32 {
651        /// `AT_SYMLINK_NOFOLLOW` : opère sur le **lien** lui-même plutôt que sur sa
652        /// cible. Un lien symbolique n'ayant pas de permissions modifiables sous
653        /// Linux, l'appel échoue alors en `EOPNOTSUPP` — ce qui en fait une garantie
654        /// utilisable : « ce chemin n'était pas un lien » (anti-TOCTOU).
655        const SYMLINK_NOFOLLOW = 0x100;
656    }
657}
658
659bitflags! {
660    /// Drapeaux de suppression pour `unlinkat(2)` (`AT_*`).
661    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
662    pub struct UnlinkFlags: i32 {
663        /// `AT_REMOVEDIR` : supprime un répertoire (sémantique `rmdir`).
664        const REMOVEDIR = 0x200;
665    }
666}
667
668bitflags! {
669    /// Drapeaux de lien pour `linkat(2)` (`AT_*`).
670    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
671    pub struct LinkFlags: i32 {
672        /// `AT_SYMLINK_FOLLOW` : suit la cible si l'ancien chemin est un lien.
673        const SYMLINK_FOLLOW = 0x400;
674        /// `AT_EMPTY_PATH` : lie le FD lui-même (chemin vide).
675        const EMPTY_PATH     = 0x1000;
676    }
677}
678
679bitflags! {
680    /// Drapeaux de création des attributs étendus (`setxattr(2)` : `XATTR_*`).
681    ///
682    /// Mutuellement exclusifs ; sans drapeau, l'attribut est créé ou remplacé.
683    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
684    pub struct XattrFlags: i32 {
685        /// `XATTR_CREATE` : échoue (`EEXIST`) si l'attribut existe déjà.
686        const CREATE  = 1;
687        /// `XATTR_REPLACE` : échoue (`ENODATA`) si l'attribut n'existe pas.
688        const REPLACE = 2;
689    }
690}
691
692/// Conseil d'accès pour `posix_fadvise(2)` (`POSIX_FADV_*`).
693///
694/// Distinct de [`crate::mem::MadviseAdvice`] : l'espace de valeurs de
695/// `posix_fadvise` (fichier) n'est pas celui de `madvise` (mémoire), même si
696/// la spec io_uring 2a les nomme tous deux « advice ». Sur les cibles tier-1
697/// d'Air (x86_64/aarch64), les constantes valent 0..=5.
698#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
699#[repr(i32)]
700pub enum FadviseAdvice {
701    /// `POSIX_FADV_NORMAL` : aucun conseil particulier.
702    Normal = 0,
703    /// `POSIX_FADV_RANDOM` : accès aléatoire (désactive le préfetch).
704    Random = 1,
705    /// `POSIX_FADV_SEQUENTIAL` : accès séquentiel (préfetch agressif).
706    Sequential = 2,
707    /// `POSIX_FADV_WILLNEED` : données bientôt nécessaires (préfetch).
708    WillNeed = 3,
709    /// `POSIX_FADV_DONTNEED` : données plus nécessaires (libère le cache).
710    DontNeed = 4,
711    /// `POSIX_FADV_NOREUSE` : données accédées une seule fois.
712    NoReuse = 5,
713}
714
715impl FadviseAdvice {
716    /// Valeur kernel (`POSIX_FADV_*`) du conseil.
717    #[must_use]
718    pub const fn as_raw(self) -> i32 {
719        self as i32
720    }
721}
722
723/// Horodatage brut du kernel (`struct statx_timestamp`, 16 octets).
724///
725/// Miroir `#[repr(C)]` écrit par le kernel ; cf. [`Statx`].
726#[repr(C)]
727#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
728pub struct StatxTimestampRaw {
729    /// Secondes depuis l'epoch Unix (`tv_sec`).
730    pub tv_sec: i64,
731    /// Nanosecondes (`tv_nsec`, dans `[0, 999_999_999]`).
732    pub tv_nsec: u32,
733    /// Réservé kernel (toujours 0).
734    reserved: i32,
735}
736
737/// Miroir brut `#[repr(C)]` de `struct statx` (256 octets, cf. `linux/stat.h`).
738///
739/// Contrairement à [`StatxResult`] (forme décodée, indépendante de l'ABI), ce
740/// type est le **tampon de sortie écrit directement par le kernel** :
741/// `io_uring`'s `IORING_OP_STATX` y dépose les métadonnées de façon asynchrone.
742/// Les champs portent les noms kernel (zone d'interface, ADR-029). Construire
743/// via [`Statx::default`] (zéro-initialisé) avant de le confier au ring.
744#[repr(C)]
745#[derive(Debug, Clone, Default)]
746pub struct Statx {
747    /// Champs effectivement remplis (`stx_mask`).
748    pub mask: u32,
749    /// Granularité d'E/S préférée (`stx_blksize`).
750    pub blksize: u32,
751    /// Attributs supplémentaires (`stx_attributes`).
752    pub attributes: u64,
753    /// Nombre de liens physiques (`stx_nlink`).
754    pub nlink: u32,
755    /// UID du propriétaire (`stx_uid`).
756    pub uid: u32,
757    /// GID du propriétaire (`stx_gid`).
758    pub gid: u32,
759    /// Type et permissions (`stx_mode`).
760    pub mode: u16,
761    spare0: [u16; 1],
762    /// Numéro d'inode (`stx_ino`).
763    pub ino: u64,
764    /// Taille en octets (`stx_size`).
765    pub size: u64,
766    /// Blocs de 512 octets alloués (`stx_blocks`).
767    pub blocks: u64,
768    /// Masque d'attributs supportés (`stx_attributes_mask`).
769    pub attributes_mask: u64,
770    /// Horodatage d'accès (`stx_atime`).
771    pub atime: StatxTimestampRaw,
772    /// Horodatage de création (`stx_btime`).
773    pub btime: StatxTimestampRaw,
774    /// Horodatage de modification de métadonnées (`stx_ctime`).
775    pub ctime: StatxTimestampRaw,
776    /// Horodatage de modification de données (`stx_mtime`).
777    pub mtime: StatxTimestampRaw,
778    /// Numéro majeur du device représenté (`stx_rdev_major`).
779    pub rdev_major: u32,
780    /// Numéro mineur du device représenté (`stx_rdev_minor`).
781    pub rdev_minor: u32,
782    /// Numéro majeur du device contenant (`stx_dev_major`).
783    pub dev_major: u32,
784    /// Numéro mineur du device contenant (`stx_dev_minor`).
785    pub dev_minor: u32,
786    /// Identifiant de point de montage (`stx_mnt_id`).
787    pub mnt_id: u64,
788    spare2: u64,
789    spare3: [u64; 12],
790}
791
792impl Statx {
793    /// Vrai si le bit de [`StatxMask`] `field` est présent dans `mask`.
794    #[must_use]
795    pub fn has(&self, field: StatxMask) -> bool {
796        self.mask & field.bits() == field.bits()
797    }
798}
799
800// Vérifications de layout ABI (figées par le kernel) : une dérive ferait
801// échouer la compilation avant tout appel kernel.
802const _: () = {
803    assert!(core::mem::size_of::<StatxTimestampRaw>() == 16);
804    assert!(core::mem::size_of::<Statx>() == 256);
805    assert!(core::mem::align_of::<Statx>() == 8);
806};
807
808// ─────────────────────────────────────────────────────────────────────────
809// inotify — surveillance de fichiers (sous-module `fs::inotify`)
810// Cf. `docs/specs/layer-0/family-fs-inotify.md`.
811// ─────────────────────────────────────────────────────────────────────────
812
813/// Descripteur de surveillance (retour d'`inotify_add_watch`). Newtype typé
814/// (jamais un `i32` brut) — ADR-029. Aussi le champ `wd` des événements lus.
815#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
816pub struct WatchDescriptor(i32);
817
818impl WatchDescriptor {
819    /// Construit depuis le `wd` brut rendu par `inotify_add_watch` (≥ 0) ou lu
820    /// dans un événement. Usage interne aux wrappers/au décodeur.
821    #[must_use]
822    pub const fn from_raw(wd: i32) -> Self {
823        Self(wd)
824    }
825
826    /// Valeur brute du descripteur (à passer à `inotify_rm_watch`).
827    #[must_use]
828    pub const fn as_raw(self) -> i32 {
829        self.0
830    }
831}
832
833bitflags! {
834    /// Masque d'événements / d'options inotify (`IN_*`). Noms kernel conservés
835    /// (ADR-029). Sert à la fois en **entrée** (`add_watch`) et en **sortie**
836    /// (bits positionnés par le kernel dans l'événement lu).
837    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
838    pub struct InotifyEventMask: u32 {
839        /// Fichier accédé (lecture).
840        const ACCESS        = 0x0000_0001;
841        /// Fichier modifié (écriture).
842        const MODIFY        = 0x0000_0002;
843        /// Métadonnées changées (permissions, horodatage…).
844        const ATTRIB        = 0x0000_0004;
845        /// Fichier ouvert en écriture fermé.
846        const CLOSE_WRITE   = 0x0000_0008;
847        /// Fichier ouvert en lecture seule fermé.
848        const CLOSE_NOWRITE = 0x0000_0010;
849        /// Fichier ouvert.
850        const OPEN          = 0x0000_0020;
851        /// Fichier déplacé **hors** du répertoire surveillé (corrèle par `cookie`).
852        const MOVED_FROM    = 0x0000_0040;
853        /// Fichier déplacé **dans** le répertoire surveillé (corrèle par `cookie`).
854        const MOVED_TO      = 0x0000_0080;
855        /// Fichier/répertoire créé.
856        const CREATE        = 0x0000_0100;
857        /// Fichier/répertoire supprimé.
858        const DELETE        = 0x0000_0200;
859        /// Le répertoire/fichier surveillé lui-même a été supprimé.
860        const DELETE_SELF   = 0x0000_0400;
861        /// Le répertoire/fichier surveillé lui-même a été déplacé.
862        const MOVE_SELF     = 0x0000_0800;
863        /// Le système de fichiers contenant l'objet surveillé a été démonté
864        /// (positionné par le kernel).
865        const UNMOUNT       = 0x0000_2000;
866        /// La file d'événements a débordé (positionné par le kernel ; `wd = -1`).
867        const Q_OVERFLOW    = 0x0000_4000;
868        /// Le watch a été retiré (explicitement, ou objet supprimé/démonté).
869        const IGNORED       = 0x0000_8000;
870        /// L'objet de l'événement est un répertoire (positionné par le kernel).
871        const ISDIR         = 0x4000_0000;
872        /// Option : ne surveiller que si `path` est un répertoire.
873        const ONLYDIR       = 0x0100_0000;
874        /// Option : ne pas déréférencer `path` s'il est un lien symbolique.
875        const DONT_FOLLOW   = 0x0200_0000;
876        /// Option : exclure les événements des fichiers déjà unlinkés.
877        const EXCL_UNLINK   = 0x0400_0000;
878        /// Option : ajouter à (au lieu de remplacer) le masque d'un watch existant.
879        const MASK_ADD      = 0x2000_0000;
880        /// Option : surveiller une seule fois puis retirer le watch.
881        const ONESHOT       = 0x8000_0000;
882    }
883}
884
885bitflags! {
886    /// Flags de création de l'instance inotify (`inotify_init1`). `CLOEXEC` est
887    /// posé **par défaut** par le wrapper ([`inotify_init`]).
888    ///
889    /// [`inotify_init`]: ../../../air_sys_syscall/fs/inotify/fn.inotify_init.html
890    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
891    pub struct InotifyFlags: i32 {
892        /// `IN_NONBLOCK` (= `O_NONBLOCK`) : FD non bloquant (`read` → `EAGAIN`).
893        const NONBLOCK = 0x800;
894        /// `IN_CLOEXEC` (= `O_CLOEXEC`) : posé par défaut par le wrapper.
895        const CLOEXEC  = 0x8_0000;
896    }
897}
898
899/// Un événement inotify décodé, **empruntant** le buffer de lecture (zéro copie).
900#[derive(Debug, Clone, Copy, PartialEq, Eq)]
901pub struct InotifyEvent<'b> {
902    /// Watch d'où provient l'événement (`-1` typé pour `Q_OVERFLOW`).
903    pub wd: WatchDescriptor,
904    /// Masque des événements survenus (`IN_*`).
905    pub mask: InotifyEventMask,
906    /// Corrèle un `MOVED_FROM` avec son `MOVED_TO` (0 si non applicable).
907    pub cookie: u32,
908    /// Nom (octets, **pas** d'UTF-8 présumé) relatif au watch, sans le NUL de
909    /// padding ; `None` si l'événement ne porte pas de nom.
910    pub name: Option<&'b [u8]>,
911}
912
913/// Entête fixe d'un `struct inotify_event` : `wd:i32 + mask:u32 + cookie:u32 +
914/// len:u32` = 16 octets, suivi de `len` octets de `name` (padded NUL).
915const INOTIFY_EVENT_HEADER_LEN: usize = 16;
916
917/// Vue empruntée sur un buffer rempli par `read(2)` : suite d'événements de
918/// **taille variable**, décodée par un itérateur **zéro allocation** (mêmes
919/// principes que le parseur uevent : `get()` partout, jamais d'indexation
920/// paniquante, `name` = `&[u8]`). **Zéro perte (ADR-032)** : tous les événements
921/// complets sont rendus ; un enregistrement final **tronqué** est *signalé* via
922/// [`InotifyEvents::truncated`] (jamais avalé silencieusement).
923#[derive(Debug, Clone)]
924pub struct InotifyEvents<'b> {
925    data: &'b [u8],
926    offset: usize,
927    truncated: bool,
928}
929
930impl<'b> InotifyEvents<'b> {
931    /// Décode un buffer (rempli par `read` du FD inotify). Donnée **externe**
932    /// (kernel) au sens du Principe 3 : aucune entrée ne panique (cf. fuzz).
933    #[must_use]
934    pub const fn parse(data: &'b [u8]) -> Self {
935        Self {
936            data,
937            offset: 0,
938            truncated: false,
939        }
940    }
941
942    /// `true` si, après itération, le buffer se terminait par un enregistrement
943    /// **incomplet** (entête ou `name` coupé). Signale à l'appelant qu'un
944    /// événement n'a pas pu être rendu (agrandir le buffer / relire) — ADR-032.
945    #[must_use]
946    pub const fn truncated(&self) -> bool {
947        self.truncated
948    }
949
950    /// Lit un `u32` natif à `off` dans `data` (borné par `get`).
951    fn read_u32(&self, off: usize) -> Option<u32> {
952        let end = off.checked_add(4)?;
953        let bytes: [u8; 4] = self.data.get(off..end)?.try_into().ok()?;
954        Some(u32::from_ne_bytes(bytes))
955    }
956}
957
958impl<'b> Iterator for InotifyEvents<'b> {
959    type Item = InotifyEvent<'b>;
960
961    fn next(&mut self) -> Option<Self::Item> {
962        let start = self.offset;
963        // Fin propre : exactement à une frontière d'enregistrement.
964        if start == self.data.len() {
965            return None;
966        }
967        // Entête (16 o) : s'il ne tient pas, c'est une queue tronquée.
968        let Some(name_len_u32) = self.read_u32(start.checked_add(12)?) else {
969            self.truncated = true;
970            return None;
971        };
972        let wd = self.read_u32(start)?.cast_signed();
973        let mask = self.read_u32(start.checked_add(4)?)?;
974        let cookie = self.read_u32(start.checked_add(8)?)?;
975        let name_len = usize::try_from(name_len_u32).ok()?;
976        let name_start = start.checked_add(INOTIFY_EVENT_HEADER_LEN)?;
977        let name_end = name_start.checked_add(name_len)?;
978        // `name` annoncé mais coupé par la fin du buffer → queue tronquée.
979        let Some(name_bytes) = self.data.get(name_start..name_end) else {
980            self.truncated = true;
981            return None;
982        };
983        self.offset = name_end;
984        // Retire le padding NUL ; `None` si aucun nom (len == 0 ou tout NUL).
985        let trimmed = match name_bytes.iter().position(|&b| b == 0) {
986            Some(nul) => name_bytes.get(..nul).unwrap_or(&[]),
987            None => name_bytes,
988        };
989        let name = if trimmed.is_empty() {
990            None
991        } else {
992            Some(trimmed)
993        };
994        Some(InotifyEvent {
995            wd: WatchDescriptor::from_raw(wd),
996            mask: InotifyEventMask::from_bits_truncate(mask),
997            cookie,
998            name,
999        })
1000    }
1001}
1002
1003#[cfg(test)]
1004mod io_uring_2a_types_tests;
1005
1006#[cfg(test)]
1007mod inotify_decoder_tests;