Skip to main content

air_sys_syscall/
mem.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//! Wrappers de la famille `mem` — mappings, protection, memfd, etc.
6//!
7//! Cf. `docs/specs/layer-0/family-mem.md`.
8
9#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
10compile_error!("air-sys-syscall::mem supporte uniquement x86_64 et aarch64 (ADR-014).");
11
12use air_sys_types::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd};
13use core::ffi::CStr;
14use core::num::NonZeroI32;
15use core::ptr::NonNull;
16use core::sync::atomic::AtomicU32;
17
18// `MmapRegion` partage sa durée de vie via `Arc` (un seul compte fort par
19// mapping). Sous `--cfg loom`, on bascule sur l'`Arc` modélisé par loom pour
20// **prouver** le protocole de refcount (clones/drops concurrents de la garde
21// de vivacité) ; sinon `std::sync::Arc`. Même schéma que le protocole d'anneau
22// io_uring (`io_uring::ring`).
23#[cfg(not(loom))]
24use alloc::sync::Arc;
25#[cfg(loom)]
26use loom::sync::Arc;
27
28use air_sys_types::mem::{
29    MadviseAdvice, MapFlags, MappingPointer, MemfdFlags, MlockFlags, MlockallFlags, MremapFlags,
30    MsyncFlags, ProtectionFlags, RemoteIoSlice,
31};
32use air_sys_types::net::{IoSlice, IoSliceMut};
33use air_sys_types::{Errno, Pid};
34
35// ─────────────────────────────────────────────────────────────────────────
36// Helper commun : conversion d'une valeur de retour syscall négative en Errno.
37// ─────────────────────────────────────────────────────────────────────────
38
39fn errno_from_negative_syscall_ret(ret: i64) -> Errno {
40    debug_assert!(ret < 0 && ret > -4096);
41    #[allow(clippy::cast_possible_truncation)]
42    let raw = ret.wrapping_neg() as i32;
43    let nz = NonZeroI32::new(raw).expect("errno strictement positif par construction");
44    Errno::from_nonzero(nz)
45}
46
47// ─────────────────────────────────────────────────────────────────────────
48// Mapping — type RAII wrappant une plage mémoire mappée.
49//
50// Note d'architecture : `Mapping` est placé dans `air-sys-syscall::mem`
51// et non dans `air-sys-types`, contrairement à ce qu'indique la spec,
52// car son `Drop` doit appeler `munmap` (un syscall). Placer un appel
53// syscall dans `air-sys-types` violerait l'invariant d'architecture
54// « les types ne font pas de syscalls ». Voir QUESTIONS-implementation.md.
55// ─────────────────────────────────────────────────────────────────────────
56
57/// Mapping mémoire RAII.
58///
59/// Crée automatiquement par [`mmap_anonymous`], [`mmap_file`] et
60/// retourné par [`mremap`]. Appelle `munmap(2)` automatiquement à la
61/// destruction pour éviter tout fuite de mémoire mappée.
62///
63/// Contrairement à un [`MappingPointer`] brut, ce type garantit que
64/// `munmap` sera appelé exactement une fois, même en cas de panique.
65///
66/// Le `Drop` automatique ignore l'erreur de `munmap` (cas extrêmement
67/// rare sur Linux moderne). Pour récupérer une éventuelle erreur,
68/// utiliser [`munmap`] qui consomme le `Mapping` explicitement.
69#[derive(Debug)]
70pub struct Mapping {
71    addr: NonNull<u8>,
72    len: usize,
73}
74
75// SAFETY: voir MappingPointer — la synchronisation est à la charge de l'appelant.
76unsafe impl Send for Mapping {}
77// SAFETY: `&Mapping` ne donne que l'adresse et la longueur ; lire ou écrire la mémoire
78// désignée passe par du code `unsafe`, dont l'appelant porte la synchronisation.
79unsafe impl Sync for Mapping {}
80
81impl Mapping {
82    /// Retourne un pointeur constant vers le début du mapping.
83    #[must_use]
84    pub fn as_ptr(&self) -> *const u8 {
85        self.addr.as_ptr()
86    }
87
88    /// Retourne un pointeur mutable vers le début du mapping.
89    #[must_use]
90    pub fn as_mut_ptr(&mut self) -> *mut u8 {
91        self.addr.as_ptr()
92    }
93
94    /// Longueur du mapping en octets.
95    #[must_use]
96    pub fn len(&self) -> usize {
97        self.len
98    }
99
100    /// Retourne `true` si la longueur est zéro (mapping vide).
101    #[must_use]
102    pub fn is_empty(&self) -> bool {
103        self.len == 0
104    }
105
106    /// Vue en lecture du mapping sous forme de slice d'octets.
107    ///
108    /// # Safety
109    ///
110    /// Le mapping doit être lisible (au moins `ProtectionFlags::READ`) et le
111    /// contenu doit être valide pour la durée de vie de la référence
112    /// retournée.
113    #[must_use]
114    pub fn as_slice(&self) -> &[u8] {
115        // SAFETY: address est NonNull, length est la taille du mapping, et
116        // la durée de vie est liée à &self.
117        unsafe { core::slice::from_raw_parts(self.addr.as_ptr(), self.len) }
118    }
119
120    /// Vue en écriture du mapping sous forme de slice d'octets.
121    ///
122    /// # Safety
123    ///
124    /// Le mapping doit être accessible en écriture (au moins
125    /// `ProtectionFlags::WRITE`). L'appelant est responsable de ne pas créer
126    /// de références Rust aliasées sur la même plage mémoire.
127    pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] {
128        // SAFETY: préconditions documentées dans la section # Safety.
129        unsafe { core::slice::from_raw_parts_mut(self.addr.as_ptr(), self.len) }
130    }
131}
132
133impl Drop for Mapping {
134    fn drop(&mut self) {
135        // Appel munmap en ignorant l'erreur (rare sur Linux moderne).
136        // Pour récupérer l'erreur, utiliser munmap() explicite.
137        let _ = raw_munmap(self.addr.as_ptr(), self.len);
138    }
139}
140
141/// Appel syscall munmap brut, partagé entre Mapping::drop et munmap().
142fn raw_munmap(address: *mut u8, length: usize) -> Result<(), Errno> {
143    // SAFETY: munmap(2) ne lit ni n'écrit de mémoire utilisateur (il
144    // libère le mapping). address et length sont fournis tels quels au kernel.
145    let ret = unsafe { raw_syscall_munmap(address as u64, length) };
146    if ret < 0 {
147        return Err(errno_from_negative_syscall_ret(ret));
148    }
149    Ok(())
150}
151
152// ─────────────────────────────────────────────────────────────────────────
153// Helper interne : appel mmap brut commun.
154// ─────────────────────────────────────────────────────────────────────────
155
156/// Appel mmap brut partagé par mmap_anonymous, mmap_file et mmap_fixed.
157///
158/// # Safety
159///
160/// L'appelant doit fournir des arguments valides pour le syscall mmap(2).
161/// En particulier :
162/// - `fd` doit être -1 pour les mappings anonymes, ou un fd ouvert valide.
163/// - `offset` doit être aligné sur la page size.
164/// - Si `flags` contient `MAP_FIXED`, `address` peut écraser des mappings existants.
165unsafe fn raw_mmap(
166    address: u64,
167    length: usize,
168    prot: i32,
169    flags: i32,
170    fd: i64,
171    offset: u64,
172) -> Result<NonNull<u8>, Errno> {
173    // SAFETY: mmap(2) mappe de la mémoire ; l'appelant garantit les préconditions.
174    let ret = unsafe { raw_syscall_mmap(address, length, prot, flags, fd, offset) };
175    if ret < 0 {
176        return Err(errno_from_negative_syscall_ret(ret));
177    }
178    // ret est l'adresse du mapping (i64 positif, jamais 0 sur Linux).
179    #[allow(clippy::cast_sign_loss)]
180    let addr_raw = ret as u64 as *mut u8;
181    let address = NonNull::new(addr_raw).expect("mmap returned NULL (impossible)");
182    Ok(address)
183}
184
185// ─────────────────────────────────────────────────────────────────────────
186// Fonctions publiques — Mappings
187// ─────────────────────────────────────────────────────────────────────────
188
189/// Crée un mapping mémoire anonyme (sans fichier sous-jacent).
190///
191/// Wrappeur de `mmap(2)` avec `MAP_ANONYMOUS | MAP_PRIVATE` ajoutés
192/// automatiquement par le wrapper. Le flag [`MapFlags::ANONYMOUS`] est
193/// toujours positionné en interne — l'appelant n'a pas besoin de le
194/// passer dans `flags`.
195///
196/// La durée de vie du mapping est gérée par le [`Mapping`] retourné :
197/// `munmap` est appelé automatiquement à la destruction.
198///
199/// # Parameters
200///
201/// - `length` : taille souhaitée en octets. Le kernel arrondit à la page
202///   supérieure ; la taille effective est accessible via `Mapping::length()`.
203/// - `prot` : protections d'accès initiales.
204/// - `flags` : flags de comportement (sans `ANONYMOUS` ni `PRIVATE`, ajoutés
205///   automatiquement).
206///
207/// # Errors
208///
209/// - `EINVAL` : `length` est zéro, ou flags incompatibles (`SHARED | PRIVATE`).
210/// - `ENOMEM` : mémoire virtuelle ou physique insuffisante.
211/// - `EACCES` : permissions refusées par le kernel.
212///
213/// # Examples
214///
215/// ```no_run
216/// use air_sys_syscall::mem::mmap_anonymous;
217/// use air_sys_types::mem::{MapFlags, ProtectionFlags};
218///
219/// let mut m = mmap_anonymous(
220///     4096,
221///     ProtectionFlags::READ | ProtectionFlags::WRITE,
222///     MapFlags::PRIVATE,
223/// ).expect("mmap_anonymous");
224/// // munmap appelé automatiquement à la fin du scope
225/// ```
226pub fn mmap_anonymous(
227    length: usize,
228    prot: ProtectionFlags,
229    flags: MapFlags,
230) -> Result<Mapping, Errno> {
231    // MAP_ANONYMOUS | MAP_PRIVATE ajoutés systématiquement.
232    let effective_flags = (flags | MapFlags::ANONYMOUS | MapFlags::PRIVATE).bits();
233    // SAFETY: mmap anonyme — fd=-1, offset=0, address=0 (any). Pas d'accès
234    // à de la mémoire utilisateur existante.
235    let address = unsafe { raw_mmap(0, length, prot.bits(), effective_flags, -1, 0) }?;
236    Ok(Mapping {
237        addr: address,
238        len: length,
239    })
240}
241
242/// Crée un mapping de fichier.
243///
244/// Wrappeur de `mmap(2)` avec un FD fichier. `offset` doit être aligné
245/// sur la page size (vérification effectuée par le kernel).
246///
247/// # Parameters
248///
249/// - `fd` : FD du fichier à mapper.
250/// - `length` : longueur du mapping en octets.
251/// - `offset` : offset dans le fichier (doit être aligné sur la page size).
252/// - `prot` : protections d'accès.
253/// - `flags` : flags de mapping (`SHARED` ou `PRIVATE` obligatoire).
254///
255/// # Errors
256///
257/// - `EINVAL` : `offset` non aligné, `length` zéro, ou flags invalides.
258/// - `EACCES` : `prot` incompatible avec les permissions du FD.
259/// - `ENOMEM` : mémoire virtuelle insuffisante.
260/// - `ENODEV` : le FD référence un type de fichier non mappable.
261///
262/// # Examples
263///
264/// ```no_run
265/// use air_sys_syscall::mem::mmap_file;
266/// use air_sys_types::mem::{MapFlags, ProtectionFlags};
267/// use air_sys_types::fd::BorrowedFd;
268///
269/// // fd = FD d'un fichier de 4096 octets déjà ouvert
270/// # fn example(fd: BorrowedFd<'_>) {
271/// let m = mmap_file(fd, 4096, 0, ProtectionFlags::READ, MapFlags::PRIVATE)
272///     .expect("mmap_file");
273/// let _data: &[u8] = m.as_slice();
274/// # }
275/// ```
276pub fn mmap_file(
277    fd: BorrowedFd<'_>,
278    length: usize,
279    offset: u64,
280    prot: ProtectionFlags,
281    flags: MapFlags,
282) -> Result<Mapping, Errno> {
283    let raw_fd = i64::from(fd.as_raw_fd());
284    // SAFETY: fd est un BorrowedFd valide, donc le fd numérique est ouvert
285    // pour la durée de l'appel. offset est passé tel quel au kernel.
286    let address = unsafe { raw_mmap(0, length, prot.bits(), flags.bits(), raw_fd, offset) }?;
287    Ok(Mapping {
288        addr: address,
289        len: length,
290    })
291}
292
293/// Mappe à une adresse spécifique.
294///
295/// Wrappeur de `mmap(2)` avec `MAP_FIXED` ou `MAP_FIXED_NOREPLACE`.
296/// `unsafe` car peut écraser des mappings existants si `MAP_FIXED`
297/// est utilisé sans `FIXED_NOREPLACE`.
298///
299/// # Safety
300///
301/// L'appelant doit garantir que :
302/// 1. `address` est aligné sur la page size.
303/// 2. Si `MapFlags::FIXED` est dans `flags`, les mappings existants à
304///    cette adresse peuvent être détruits.
305/// 3. Préférer `MapFlags::FIXED_NOREPLACE` pour obtenir `EEXIST` en cas
306///    de collision plutôt que d'écraser silencieusement.
307///
308/// # Parameters
309///
310/// - `address` : adresse de base souhaitée.
311/// - `length` : longueur en octets.
312/// - `prot` : protections.
313/// - `flags` : flags de mapping (doit contenir `FIXED` ou `FIXED_NOREPLACE`).
314/// - `fd` : FD fichier optionnel (`None` pour mapping anonyme).
315/// - `offset` : offset dans le fichier (0 pour mapping anonyme).
316///
317/// # Errors
318///
319/// - `EINVAL` : adresse non alignée ou flags invalides.
320/// - `EEXIST` : adresse occupée et `FIXED_NOREPLACE` est utilisé.
321/// - `ENOMEM` : mémoire insuffisante.
322///
323/// # Examples
324///
325/// ```no_run
326/// use air_sys_syscall::mem::mmap_fixed;
327/// use air_sys_types::mem::{MapFlags, MappingPointer, ProtectionFlags};
328/// use core::ptr::NonNull;
329///
330/// # fn example(address: NonNull<u8>) {
331/// // unsafe : peut écraser des mappings existants si MAP_FIXED utilisé
332/// let ptr = unsafe {
333///     mmap_fixed(
334///         address,
335///         4096,
336///         ProtectionFlags::READ | ProtectionFlags::WRITE,
337///         MapFlags::FIXED_NOREPLACE | MapFlags::PRIVATE | MapFlags::ANONYMOUS,
338///         None,
339///         0,
340///     )
341/// }.expect("mmap_fixed");
342/// # }
343/// ```
344pub unsafe fn mmap_fixed(
345    address: NonNull<u8>,
346    length: usize,
347    prot: ProtectionFlags,
348    flags: MapFlags,
349    fd: Option<BorrowedFd<'_>>,
350    offset: u64,
351) -> Result<MappingPointer, Errno> {
352    let raw_fd: i64 = match fd {
353        Some(f) => i64::from(f.as_raw_fd()),
354        None => -1,
355    };
356    // Pour mapping anonyme sans fd, on ajoute MAP_ANONYMOUS.
357    let effective_flags = if fd.is_none() {
358        (flags | MapFlags::ANONYMOUS).bits()
359    } else {
360        flags.bits()
361    };
362    // SAFETY: l'appelant garantit que address est aligné sur la page size
363    // et que MAP_FIXED / MAP_FIXED_NOREPLACE est positionné.
364    let mapped_addr = unsafe {
365        raw_mmap(
366            address.as_ptr() as u64,
367            length,
368            prot.bits(),
369            effective_flags,
370            raw_fd,
371            offset,
372        )
373    }?;
374    Ok(MappingPointer {
375        address: mapped_addr,
376        length,
377    })
378}
379
380// ─────────────────────────────────────────────────────────────────────────
381// MmapRegion — mapping partageable, compatible avec l'asynchronie io_uring.
382//
383// Cf. docs/specs/layer-0/family-mem-mmap-region.md.
384//
385// `MmapRegion` (`Arc<MmapRegionInner>`) est l'**opt-in partageable** : à la
386// différence de `Mapping` (RAII unique, zéro-coût, **inchangé**), sa durée de
387// vie peut être partagée avec le slot S1 d'une opération io_uring en vol
388// (`madvise`, `futex_*`) via une `MmapRegionLiveness`. Le kernel lit la
389// mémoire de la région **de façon asynchrone**, jusqu'à la complétion ; la
390// garde garée dans le slot empêche `munmap` tant que l'op est en vol. `munmap`
391// n'a lieu qu'au **dernier** drop (région utilisateur ET toutes les gardes) →
392// **ni use-after-unmap, ni fuite**.
393//
394// **Exception ADR-021 c.4 (« pas d'alloc dans le happy path »), documentée :**
395// `MmapRegion` fait **une** allocation (l'`Arc` inner). C'est admis car (1)
396// opt-in — `Mapping` reste zéro-coût ; (2) négligeable et amorti devant le
397// syscall `mmap` qui crée la région ; (3) le partage de durée de vie est
398// **intrinsèquement nécessaire** à la sûreté de l'usage asynchrone (clause
399// « nécessité documentée » d'ADR-021 c.4).
400// ─────────────────────────────────────────────────────────────────────────
401
402/// Compteur de démappages simulés pour les preuves de refcount (Miri/loom).
403/// Sous `--cfg loom`, c'est l'`AtomicUsize` modélisé par loom.
404#[cfg(all(any(test, loom), loom))]
405type ProbeCounter = loom::sync::atomic::AtomicUsize;
406/// Compteur de démappages simulés pour les preuves de refcount (tests/Miri).
407#[cfg(all(any(test, loom), not(loom)))]
408type ProbeCounter = core::sync::atomic::AtomicUsize;
409
410/// État interne partagé d'une [`MmapRegion`], détenu par un `Arc`. Son `Drop`,
411/// au **dernier** détenteur fort uniquement, appelle `munmap` (erreur ignorée,
412/// comme [`Mapping`]).
413struct MmapRegionInner {
414    /// Adresse de base du mapping.
415    ptr: NonNull<u8>,
416    /// Longueur en octets.
417    len: usize,
418    /// Protections du mapping (détermine la lisibilité de [`MmapRegion::bytes`]
419    /// et l'accès à [`MmapRegion::futex_word`]).
420    prot: ProtectionFlags,
421    /// Présent uniquement en test/Miri/loom : si `Some`, la région est
422    /// **fictive** (créée par `new_for_test`, pointeur non déréférencé) et
423    /// `drop_unmap` incrémente ce compteur au lieu d'appeler le vrai `munmap`.
424    /// `None` pour les régions réelles (issues d'un `mmap`).
425    #[cfg(any(test, loom))]
426    unmap_probe: Option<Arc<ProbeCounter>>,
427}
428
429// SAFETY: comme `Mapping`/`MappingPointer`, le pointeur désigne un mapping dont
430// la **synchronisation des accès** est à la charge de l'appelant. Le partage de
431// propriété (compteur fort de l'`Arc`) est lui-même thread-safe.
432unsafe impl Send for MmapRegionInner {}
433// SAFETY: idem `Send` ci-dessus.
434unsafe impl Sync for MmapRegionInner {}
435
436impl MmapRegionInner {
437    /// Démappe la région (dernier drop). Sous test/Miri/loom **avec** une région
438    /// fictive, **n'appelle pas** le vrai syscall (Miri/loom ne peuvent pas
439    /// exécuter l'`asm!`, et la région n'a pas de vrai mapping) : incrémente le
440    /// compteur de preuve. Une région réelle (`unmap_probe = None`) démappe
441    /// pour de vrai hors Miri/loom.
442    fn drop_unmap(&mut self) {
443        #[cfg(any(test, loom))]
444        if let Some(counter) = &self.unmap_probe {
445            counter.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
446            return;
447        }
448        #[cfg(not(any(miri, loom)))]
449        {
450            // SAFETY: `ptr`/`len` proviennent d'un `mmap` réussi (constructeurs)
451            // et la région n'a pas encore été démappée — le **dernier** drop est
452            // unique par construction (compteur fort de l'`Arc`). `munmap`
453            // ignore l'erreur, comme `Mapping`.
454            let _ = raw_munmap(self.ptr.as_ptr(), self.len);
455        }
456    }
457}
458
459impl Drop for MmapRegionInner {
460    fn drop(&mut self) {
461        self.drop_unmap();
462    }
463}
464
465/// Mapping mmap **partageable** et compatible io_uring : ses pages restent
466/// valides tant qu'une opération io_uring en vol (`madvise`/`futex_*`) y
467/// référence de la mémoire. Compté par référence — `munmap` au **dernier**
468/// drop (la région **et** toutes les gardes de vivacité d'ops en vol).
469///
470/// Distinct de [`Mapping`] **à dessein** : `Mapping` reste l'usage synchrone
471/// unique/zéro-coût ; `MmapRegion` est l'opt-in partageable (une allocation
472/// `Arc`, cf. exception ADR-021 c.4 ci-dessus). Cloner incrémente le compteur
473/// fort ; aucune copie de pages, seulement du partage de propriété.
474#[derive(Clone)]
475pub struct MmapRegion {
476    /// Inner partagé. Le `munmap` n'a lieu qu'au drop du **dernier** `Arc`.
477    inner: Arc<MmapRegionInner>,
478}
479
480impl core::fmt::Debug for MmapRegion {
481    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
482        f.debug_struct("MmapRegion")
483            .field("ptr", &self.inner.ptr)
484            .field("len", &self.inner.len)
485            .field("prot", &self.inner.prot)
486            .finish()
487    }
488}
489
490impl MmapRegion {
491    /// Mapping anonyme partageable (équivalent [`mmap_anonymous`] rendant une
492    /// `MmapRegion`).
493    ///
494    /// # Errors
495    ///
496    /// Toute erreur de `mmap(2)` (cf. [`mmap_anonymous`]) : `EINVAL`, `ENOMEM`…
497    pub fn new_anonymous(
498        len: usize,
499        prot: ProtectionFlags,
500        flags: MapFlags,
501    ) -> Result<Self, Errno> {
502        let mapping = mmap_anonymous(len, prot, flags)?;
503        Ok(Self::from_mapping_with_prot(mapping, prot))
504    }
505
506    /// Mapping de fichier partageable (équivalent [`mmap_file`] rendant une
507    /// `MmapRegion`).
508    ///
509    /// # Errors
510    ///
511    /// Toute erreur de `mmap(2)` (cf. [`mmap_file`]) : `EINVAL`, `EACCES`…
512    pub fn from_file(
513        fd: BorrowedFd<'_>,
514        len: usize,
515        prot: ProtectionFlags,
516        flags: MapFlags,
517        offset: u64,
518    ) -> Result<Self, Errno> {
519        let mapping = mmap_file(fd, len, offset, prot, flags)?;
520        Ok(Self::from_mapping_with_prot(mapping, prot))
521    }
522
523    /// Promeut un [`Mapping`] unique en région partageable : **transfère** la
524    /// responsabilité du `munmap` à l'inner partagé. Le `Mapping` est consommé
525    /// et **neutralisé** (son `Drop` ne munmap plus) → **pas de double
526    /// `munmap`**. Une seule allocation (l'inner).
527    ///
528    /// **`prot` explicite (obligatoire).** Un `Mapping` nu ne mémorise pas ses
529    /// protections ; l'appelant **doit** fournir le `prot` avec lequel le
530    /// `Mapping` a été créé, sinon [`MmapRegion::bytes`] / [`MmapRegion::futex_word`]
531    /// décideraient de l'accessibilité sur une base fausse (référence qui
532    /// **faute** à l'usage). Il n'y a **pas** de défaut `READ|WRITE` : le `prot`
533    /// passé fait foi.
534    #[must_use]
535    pub fn from_mapping(mapping: Mapping, prot: ProtectionFlags) -> Self {
536        Self::from_mapping_with_prot(mapping, prot)
537    }
538
539    /// Cœur commun : consomme le `Mapping` (sans déclencher son `Drop`) et
540    /// construit l'inner partagé avec les protections connues.
541    fn from_mapping_with_prot(mapping: Mapping, prot: ProtectionFlags) -> Self {
542        // ManuallyDrop empêche `Mapping::drop` d'appeler `munmap` : la
543        // responsabilité passe à l'inner (pas de double munmap).
544        let md = core::mem::ManuallyDrop::new(mapping);
545        let inner = MmapRegionInner {
546            ptr: md.addr,
547            len: md.len,
548            prot,
549            #[cfg(any(test, loom))]
550            unmap_probe: None,
551        };
552        Self {
553            inner: Arc::new(inner),
554        }
555    }
556
557    /// Longueur du mapping en octets.
558    #[must_use]
559    pub fn len(&self) -> usize {
560        self.inner.len
561    }
562
563    /// `true` si la longueur est zéro.
564    #[must_use]
565    pub fn is_empty(&self) -> bool {
566        self.inner.len == 0
567    }
568
569    /// Pointeur brut vers le début du mapping (consommateurs avancés ; pas de
570    /// transfert de propriété — le `munmap` reste géré par la `MmapRegion`).
571    #[must_use]
572    pub fn as_ptr(&self) -> *const u8 {
573        self.inner.ptr.as_ptr()
574    }
575
576    /// Tranche d'octets en lecture, **bornée par construction**. Renvoie une
577    /// tranche **vide** si la région n'est pas lisible (`prot` sans `READ`) —
578    /// jamais de référence vers de la mémoire inaccessible.
579    #[must_use]
580    pub fn bytes(&self) -> &[u8] {
581        if !self.inner.prot.contains(ProtectionFlags::READ) {
582            return &[];
583        }
584        // SAFETY: région lisible (prot ⊇ READ), `ptr` non-null issu d'un mmap,
585        // `len` = taille mappée ; la durée de vie de la tranche est liée à
586        // `&self`, donc à l'`Arc` qui maintient le mapping vivant.
587        unsafe { core::slice::from_raw_parts(self.inner.ptr.as_ptr(), self.inner.len) }
588    }
589
590    /// Référence à un **mot futex** ([`AtomicU32`]) à `offset` octets.
591    ///
592    /// La référence est **mutable par partage** (`AtomicU32` : `store`/`load` via
593    /// `&`) ; la rendre sur des pages non inscriptibles serait *unsound* (une
594    /// écriture fauterait). La validation amont (Principe 4) exige donc que la
595    /// région soit **inscriptible** (`prot ⊇ WRITE`), `offset` **borné**
596    /// (`offset + 4 ≤ len`) et **aligné sur 4** (un futex est un `u32` aligné) —
597    /// sinon la référence n'est **pas** rendue.
598    ///
599    /// # Errors
600    ///
601    /// [`Errno::EINVAL`] si la région n'est pas inscriptible, ou si `offset` est
602    /// hors bornes ou non aligné sur 4.
603    pub fn futex_word(&self, offset: usize) -> Result<&AtomicU32, Errno> {
604        if !self.inner.prot.contains(ProtectionFlags::WRITE) {
605            return Err(Errno::EINVAL);
606        }
607        // Alignement sur 4 sans opérateur arithmétique nu (bit-and).
608        if offset & 0b11 != 0 {
609            return Err(Errno::EINVAL);
610        }
611        let end = offset.checked_add(4).ok_or(Errno::EINVAL)?;
612        if end > self.inner.len {
613            return Err(Errno::EINVAL);
614        }
615        // SAFETY: `offset` validé (aligné 4, `offset+4 ≤ len`) ⇒ `ptr+offset`
616        // désigne un `u32` **entièrement** dans le mapping inscriptible.
617        // `AtomicU32` a le même layout (et les mêmes exigences d'alignement)
618        // qu'un `u32` ; l'accès concurrent est admis (le kernel y accède aussi).
619        // La durée de vie est liée à `&self` (donc à l'`Arc`).
620        let word = unsafe { self.inner.ptr.as_ptr().add(offset).cast::<AtomicU32>() };
621        // SAFETY: voir ci-dessus — `word` est aligné, valide et vivant.
622        Ok(unsafe { &*word })
623    }
624
625    /// Produit une garde de vivacité (clone interne de l'`Arc`). Réservé aux
626    /// façades io_uring `submit_madvise`/`submit_futex_*` : la garde est garée
627    /// dans le slot S1 et libérée à la complétion → `munmap` au dernier drop.
628    pub(crate) fn liveness_handle(&self) -> MmapRegionLiveness {
629        MmapRegionLiveness {
630            inner: Arc::clone(&self.inner),
631        }
632    }
633
634    /// Construit une région **fictive** (sans `mmap`) pour les preuves de
635    /// refcount (Miri/loom) : le pointeur est *dangling* et **ne doit jamais**
636    /// être déréférencé (pas de `bytes()`/`futex_word()`) ; `munmap` est simulé
637    /// par l'incrément de `counter` au dernier drop.
638    #[cfg(any(test, loom))]
639    pub(crate) fn new_for_test(len: usize, counter: Arc<ProbeCounter>) -> Self {
640        Self {
641            inner: Arc::new(MmapRegionInner {
642                ptr: NonNull::<u8>::dangling(),
643                len,
644                prot: ProtectionFlags::READ | ProtectionFlags::WRITE,
645                unmap_probe: Some(counter),
646            }),
647        }
648    }
649
650    /// Nombre de détenteurs forts de l'inner (région + gardes en vol). Réservé
651    /// aux tests de refcount déterministes (non utilisé par la preuve loom).
652    #[cfg(any(test, loom))]
653    #[cfg_attr(loom, allow(dead_code))]
654    pub(crate) fn strong_count(&self) -> usize {
655        Arc::strong_count(&self.inner)
656    }
657}
658
659/// Garde de vivacité retenue par le slot S1 d'une opération io_uring en vol qui
660/// référence la région. Maintient les pages **mappées** sans donner d'accès :
661/// tant qu'une garde existe, `munmap` ne peut pas se produire.
662pub struct MmapRegionLiveness {
663    /// Clone fort de l'inner : maintient le compteur ≥ 1. Jamais relu — seul son
664    /// `Drop` (compteur −1) compte ; `munmap` survient au dernier drop.
665    inner: Arc<MmapRegionInner>,
666}
667
668impl core::fmt::Debug for MmapRegionLiveness {
669    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
670        f.debug_struct("MmapRegionLiveness")
671            .field("ptr", &self.inner.ptr)
672            .field("len", &self.inner.len)
673            .finish()
674    }
675}
676
677/// Dé-mappe explicitement un mapping et récupère une éventuelle erreur.
678///
679/// Consomme le [`Mapping`] ; l'appelant récupère l'erreur de `munmap`
680/// si elle survient (rare sur Linux moderne). Le `Drop` automatique de
681/// `Mapping` appelle aussi `munmap` mais ignore l'erreur.
682///
683/// # Errors
684///
685/// - `EINVAL` : adresse ou longueur invalide (ne se produit pas si le
686///   `Mapping` a été créé par ce module).
687///
688/// # Examples
689///
690/// ```no_run
691/// use air_sys_syscall::mem::{mmap_anonymous, munmap};
692/// use air_sys_types::mem::{MapFlags, ProtectionFlags};
693///
694/// let m = mmap_anonymous(4096, ProtectionFlags::READ, MapFlags::PRIVATE).expect("mmap");
695/// munmap(m).expect("munmap");
696/// ```
697pub fn munmap(mapping: Mapping) -> Result<(), Errno> {
698    // ManuallyDrop empêche le Drop de Mapping d'appeler munmap une seconde fois.
699    let md = core::mem::ManuallyDrop::new(mapping);
700    raw_munmap(md.addr.as_ptr(), md.len)
701}
702
703/// Libère un mapping par (**adresse, longueur**) brutes (`munmap(2)`) — **descellement
704/// additif [ADR-085](../../../docs/adrs/ADR-085-descellement-couche0-cumule-libc-std-fr.md)**
705/// pour la **face libc**, qui gère la vie du mapping par `(addr, len)` (comme le C `void
706/// *mmap`/`int munmap(void *, size_t)`), et **non** par l'objet RAII [`Mapping`]. La
707/// couche 1 [`air-memory`] l'expose à la libc via son `AirMemoryManager`.
708///
709/// # Safety
710/// `address`/`length` **doivent** désigner une région (ou un sous-ensemble de pages
711/// alignées) **effectivement mappée** par l'appelant : `munmap` d'une région invalide
712/// rend au mieux `EINVAL` (adresse non alignée), mais `munmap` d'une région **en cours
713/// d'usage** déprogramme de la mémoire vive (comme le `munmap` C). C'est le contrat C.
714pub unsafe fn munmap_raw(address: NonNull<u8>, length: usize) -> Result<(), Errno> {
715    // Délègue au wrapper interne : le kernel valide `(addr, len)` (alignement page).
716    raw_munmap(address.as_ptr(), length)
717}
718
719/// Redimensionne (et éventuellement déplace) un mapping existant.
720///
721/// Wrappeur de `mremap(2)`. Consomme le [`Mapping`] existant et retourne
722/// un nouveau [`Mapping`] à la nouvelle adresse.
723///
724/// # Parameters
725///
726/// - `mapping` : mapping source (ownership transféré).
727/// - `new_len` : nouvelle longueur souhaitée.
728/// - `flags` : [`MremapFlags`]. Inclure `MAYMOVE` pour autoriser le
729///   déplacement si le redimensionnement sur place est impossible.
730///
731/// # Errors
732///
733/// - `EINVAL` : longueur nulle ou flags invalides.
734/// - `ENOMEM` : mémoire insuffisante (avec `MAYMOVE` absent, si sur place
735///   impossible).
736/// - `EAGAIN` : mapping verrouillé en mémoire et RLIMIT atteint.
737///
738/// # Examples
739///
740/// ```no_run
741/// use air_sys_syscall::mem::{mmap_anonymous, mremap};
742/// use air_sys_types::mem::{MapFlags, MremapFlags, ProtectionFlags};
743///
744/// let m = mmap_anonymous(4096, ProtectionFlags::READ | ProtectionFlags::WRITE, MapFlags::PRIVATE)
745///     .expect("mmap");
746/// let m2 = mremap(m, 8192, MremapFlags::MAYMOVE).expect("mremap");
747/// ```
748pub fn mremap(mapping: Mapping, new_len: usize, flags: MremapFlags) -> Result<Mapping, Errno> {
749    // ManuallyDrop empêche le Drop de Mapping d'appeler munmap sur l'ancienne
750    // adresse, qui n'est plus valide après mremap.
751    let md = core::mem::ManuallyDrop::new(mapping);
752    let old_addr = md.addr.as_ptr() as u64;
753    let old_len = md.len;
754    // SAFETY: mremap(2) consomme l'ancien mapping et retourne un nouveau.
755    // L'adresse new_addr (arg4) est 0 car on ne passe pas MREMAP_FIXED.
756    let ret = unsafe { raw_syscall_mremap(old_addr, old_len, new_len, flags.bits(), 0_u64) };
757    if ret < 0 {
758        // Le mapping original n'a pas été modifié par le kernel en cas d'échec ;
759        // il faut éviter la fuite — on le restitue dans un nouveau Mapping.
760        // Cependant ManuallyDrop a déjà évité le drop. Si l'erreur est retournée,
761        // l'original reste mappé mais son handle Rust est perdu : fuite intentionnelle
762        // documentée (conforme à la spécification de la famille mem). L'erreur est
763        // remontée à l'appelant.
764        return Err(errno_from_negative_syscall_ret(ret));
765    }
766    // ret est la nouvelle adresse du mapping (i64 positif).
767    #[allow(clippy::cast_sign_loss)]
768    let addr_raw = ret as u64 as *mut u8;
769    let address = NonNull::new(addr_raw).expect("mremap returned NULL (impossible)");
770    Ok(Mapping {
771        addr: address,
772        len: new_len,
773    })
774}
775
776// ─────────────────────────────────────────────────────────────────────────
777// Protection
778// ─────────────────────────────────────────────────────────────────────────
779
780/// Modifie les permissions d'une plage mémoire mappée.
781///
782/// Wrappeur de `mprotect(2)`. Typiquement utilisé pour rétrograder les
783/// permissions après initialisation d'une zone en lecture seule.
784///
785/// # Parameters
786///
787/// - `address` : adresse de début (doit être alignée sur la page size).
788/// - `length` : longueur de la plage en octets.
789/// - `prot` : nouvelles permissions.
790///
791/// # Errors
792///
793/// - `EINVAL` : `address` non aligné, ou plage invalide.
794/// - `EACCES` : tentative d'ajouter `EXEC` sur un FS monté `noexec`.
795/// - `ENOMEM` : ressources kernel insuffisantes.
796///
797/// # Examples
798///
799/// ```no_run
800/// use air_sys_syscall::mem::{mmap_anonymous, mprotect};
801/// use air_sys_types::mem::{MapFlags, ProtectionFlags};
802/// use core::ptr::NonNull;
803///
804/// let mut m = mmap_anonymous(4096, ProtectionFlags::READ | ProtectionFlags::WRITE, MapFlags::PRIVATE)
805///     .expect("mmap");
806/// // Initialisez les données, puis passez en lecture seule :
807/// mprotect(
808///     NonNull::new(m.as_mut_ptr()).expect("non-null"),
809///     4096,
810///     ProtectionFlags::READ,
811/// ).expect("mprotect");
812/// ```
813pub fn mprotect(address: NonNull<u8>, length: usize, prot: ProtectionFlags) -> Result<(), Errno> {
814    // SAFETY: mprotect(2) modifie les permissions d'une plage mémoire.
815    // L'appelant garantit que address est aligné et que la plage est mappée.
816    let ret = unsafe { raw_syscall_mprotect(address.as_ptr() as u64, length, prot.bits()) };
817    if ret < 0 {
818        return Err(errno_from_negative_syscall_ret(ret));
819    }
820    Ok(())
821}
822
823// ─────────────────────────────────────────────────────────────────────────
824// Conseils kernel
825// ─────────────────────────────────────────────────────────────────────────
826
827/// Informe le kernel sur le pattern d'accès attendu à une plage mémoire.
828///
829/// Wrappeur de `madvise(2)`. Ces conseils sont des hints : le kernel peut
830/// les ignorer. Aucun effet sur la sémantique du programme — uniquement
831/// sur les performances.
832///
833/// # Parameters
834///
835/// - `address` : adresse de début (alignée sur la page size).
836/// - `length` : longueur en octets.
837/// - `advice` : conseil à donner au kernel.
838///
839/// # Errors
840///
841/// - `EINVAL` : adresse non alignée, plage invalide, ou conseil inconnu.
842/// - `EACCES` : `MADV_REMOVE` sur un mapping non partagé.
843/// - `ENOMEM` : plage non mappée.
844///
845/// # Examples
846///
847/// ```no_run
848/// use air_sys_syscall::mem::{madvise, mmap_anonymous};
849/// use air_sys_types::mem::{MadviseAdvice, MapFlags, ProtectionFlags};
850/// use core::ptr::NonNull;
851///
852/// let m = mmap_anonymous(65536, ProtectionFlags::READ | ProtectionFlags::WRITE, MapFlags::PRIVATE)
853///     .expect("mmap");
854/// madvise(
855///     NonNull::new(m.as_ptr() as *mut u8).expect("non-null"),
856///     65536,
857///     MadviseAdvice::Sequential,
858/// ).expect("madvise");
859/// ```
860pub fn madvise(address: NonNull<u8>, length: usize, advice: MadviseAdvice) -> Result<(), Errno> {
861    // MadviseAdvice est #[repr(i32)] donc `advice as i32` est une conversion
862    // exacte et sans perte.
863    #[allow(clippy::as_conversions)]
864    let advice_i32 = advice as i32;
865    // SAFETY: madvise(2) est un hint pour le kernel ; pas d'accès mémoire
866    // utilisateur par le syscall lui-même. L'appelant garantit que la plage
867    // [address, address+length) est mappée.
868    let ret = unsafe { raw_syscall_madvise(address.as_ptr() as u64, length, advice_i32) };
869    if ret < 0 {
870        return Err(errno_from_negative_syscall_ret(ret));
871    }
872    Ok(())
873}
874
875// ─────────────────────────────────────────────────────────────────────────
876// Verrouillage mémoire
877// ─────────────────────────────────────────────────────────────────────────
878
879/// Verrouille une plage mémoire en RAM (empêche le swap).
880///
881/// Wrappeur de `mlock(2)`. Nécessite soit des privilèges root, soit
882/// une limite `RLIMIT_MEMLOCK` suffisante.
883///
884/// # Errors
885///
886/// - `ENOMEM` : la plage contient des pages non mappées, ou
887///   `RLIMIT_MEMLOCK` serait dépassé.
888/// - `EPERM` : `RLIMIT_MEMLOCK` est zéro et le processus n'est pas
889///   privilégié.
890/// - `EINVAL` : adresse ou longueur invalide.
891///
892/// # Examples
893///
894/// ```no_run
895/// use air_sys_syscall::mem::{mlock, mmap_anonymous};
896/// use air_sys_types::mem::{MapFlags, ProtectionFlags};
897/// use core::ptr::NonNull;
898///
899/// let m = mmap_anonymous(4096, ProtectionFlags::READ | ProtectionFlags::WRITE, MapFlags::PRIVATE)
900///     .expect("mmap");
901/// mlock(NonNull::new(m.as_ptr() as *mut u8).expect("non-null"), 4096)
902///     .expect("mlock");
903/// ```
904pub fn mlock(address: NonNull<u8>, length: usize) -> Result<(), Errno> {
905    // SAFETY: mlock(2) demande au kernel de verrouiller une plage en RAM.
906    // L'appelant garantit que [address, address+length) est une plage mappée valide.
907    let ret = unsafe { raw_syscall_mlock(address.as_ptr() as u64, length) };
908    if ret < 0 {
909        return Err(errno_from_negative_syscall_ret(ret));
910    }
911    Ok(())
912}
913
914/// Verrouille une plage mémoire avec contrôle du moment du verrouillage.
915///
916/// Variante de `mlock` avec [`MlockFlags::ONFAULT`] (Linux 4.4+) pour
917/// un verrouillage différé au fur et à mesure des fautes mémoire.
918///
919/// # Errors
920///
921/// Voir [`mlock`].
922///
923/// # Examples
924///
925/// ```no_run
926/// use air_sys_syscall::mem::mlock2;
927/// use air_sys_types::mem::MlockFlags;
928/// use core::ptr::NonNull;
929///
930/// # fn example(address: NonNull<u8>) {
931/// mlock2(address, 4096, MlockFlags::ONFAULT).expect("mlock2");
932/// # }
933/// ```
934pub fn mlock2(address: NonNull<u8>, length: usize, flags: MlockFlags) -> Result<(), Errno> {
935    // SAFETY: mlock2(2) est identique à mlock avec un argument flags supplémentaire.
936    // L'appelant garantit que [address, address+length) est mappée.
937    let ret = unsafe { raw_syscall_mlock2(address.as_ptr() as u64, length, flags.bits()) };
938    if ret < 0 {
939        return Err(errno_from_negative_syscall_ret(ret));
940    }
941    Ok(())
942}
943
944/// Déverrouille une plage mémoire précédemment verrouillée.
945///
946/// # Errors
947///
948/// - `EINVAL` : adresse ou longueur invalide.
949///
950/// # Examples
951///
952/// ```no_run
953/// use air_sys_syscall::mem::munlock;
954/// use core::ptr::NonNull;
955///
956/// # fn example(address: NonNull<u8>) {
957/// munlock(address, 4096).expect("munlock");
958/// # }
959/// ```
960pub fn munlock(address: NonNull<u8>, length: usize) -> Result<(), Errno> {
961    // SAFETY: munlock(2) déverrouille une plage mémoire.
962    let ret = unsafe { raw_syscall_munlock(address.as_ptr() as u64, length) };
963    if ret < 0 {
964        return Err(errno_from_negative_syscall_ret(ret));
965    }
966    Ok(())
967}
968
969/// Verrouille toutes les pages du processus en RAM.
970///
971/// # Errors
972///
973/// - `EPERM` : non privilégié et `RLIMIT_MEMLOCK` insuffisant.
974/// - `ENOMEM` : mémoire physique insuffisante.
975///
976/// # Examples
977///
978/// ```no_run
979/// use air_sys_syscall::mem::mlockall;
980/// use air_sys_types::mem::MlockallFlags;
981///
982/// mlockall(MlockallFlags::CURRENT | MlockallFlags::FUTURE).expect("mlockall");
983/// ```
984pub fn mlockall(flags: MlockallFlags) -> Result<(), Errno> {
985    // SAFETY: mlockall(2) prend un seul scalaire i32. Pas d'accès mémoire.
986    let ret = unsafe { raw_syscall_mlockall(flags.bits()) };
987    if ret < 0 {
988        return Err(errno_from_negative_syscall_ret(ret));
989    }
990    Ok(())
991}
992
993/// Déverrouille toutes les pages du processus.
994///
995/// # Errors
996///
997/// Normalement sans erreur si `mlockall` a été appelé préalablement.
998///
999/// # Examples
1000///
1001/// ```no_run
1002/// use air_sys_syscall::mem::munlockall;
1003///
1004/// munlockall().expect("munlockall");
1005/// ```
1006pub fn munlockall() -> Result<(), Errno> {
1007    // SAFETY: munlockall(2) ne prend aucun argument. Pas d'accès mémoire.
1008    let ret = unsafe { raw_syscall_munlockall() };
1009    if ret < 0 {
1010        return Err(errno_from_negative_syscall_ret(ret));
1011    }
1012    Ok(())
1013}
1014
1015// ─────────────────────────────────────────────────────────────────────────
1016// memfd
1017// ─────────────────────────────────────────────────────────────────────────
1018
1019/// Crée un fichier mémoire anonyme (memfd).
1020///
1021/// Wrappeur de `memfd_create(2)` (Linux 3.17+, numéro x86_64: 319,
1022/// aarch64: 279). Primitive centrale pour la mémoire partagée moderne
1023/// sur Linux, utilisée par le data plane de Conduit (ADR-001).
1024///
1025/// Le FD retourné peut être :
1026/// - étendu avec `ftruncate(2)` ;
1027/// - mappé avec [`mmap_file`] ;
1028/// - partagé entre processus via `SCM_RIGHTS` sur un socket Unix ;
1029/// - scellé pour interdire des modifications futures.
1030///
1031/// # Parameters
1032///
1033/// - `name` : nom descriptif (visible dans `/proc/PID/fd/`, pas unique).
1034/// - `flags` : combinaison de [`MemfdFlags`]. `CLOEXEC` recommandé.
1035///
1036/// # Errors
1037///
1038/// - `EINVAL` : flags invalides ou `name` trop long.
1039/// - `EMFILE` : quota de FD du processus atteint.
1040/// - `ENFILE` : quota de FD système atteint.
1041/// - `ENOMEM` : mémoire kernel insuffisante.
1042///
1043/// # Examples
1044///
1045/// ```no_run
1046/// use air_sys_syscall::mem::memfd_create;
1047/// use air_sys_types::mem::MemfdFlags;
1048/// use core::ffi::CStr;
1049///
1050/// let fd = memfd_create(c"shared-buffer", MemfdFlags::CLOEXEC | MemfdFlags::ALLOW_SEALING)
1051///     .expect("memfd_create");
1052/// ```
1053pub fn memfd_create(name: &CStr, flags: MemfdFlags) -> Result<OwnedFd, Errno> {
1054    let name_ptr = name.as_ptr() as u64;
1055    // SAFETY: memfd_create(2) lit la chaîne NUL-terminée à `name_ptr`.
1056    // `name` est un &CStr valide, donc la chaîne est correctement terminée.
1057    // Le syscall retourne un fd valide ou une erreur négative.
1058    let ret = unsafe { raw_syscall_memfd_create(name_ptr, flags.bits()) };
1059    if ret < 0 {
1060        return Err(errno_from_negative_syscall_ret(ret));
1061    }
1062    // Linux borne les fd à i32::MAX ; la troncature est exacte.
1063    #[allow(clippy::cast_possible_truncation)]
1064    let raw_fd = ret as i32;
1065    // SAFETY: le kernel a alloué un fd valide ; Air en prend la propriété.
1066    let owned = unsafe { OwnedFd::from_raw_fd(raw_fd) };
1067    Ok(owned)
1068}
1069
1070/// Crée un fichier mémoire secret non accessible au kernel.
1071///
1072/// Wrappeur de `memfd_secret(2)` (Linux 5.14+, numéro 447). Réservé
1073/// aux secrets cryptographiques de haute valeur.
1074///
1075/// # Parameters
1076///
1077/// - `flags` : réservé pour usage futur, doit être 0 actuellement.
1078///
1079/// # Errors
1080///
1081/// - `ENOSYS` : kernel < 5.14 ou support non compilé.
1082/// - `ENOMEM` : mémoire insuffisante.
1083/// - `EMFILE`/`ENFILE` : quotas FD atteints.
1084///
1085/// # Examples
1086///
1087/// ```no_run
1088/// use air_sys_syscall::mem::memfd_secret;
1089///
1090/// let fd = memfd_secret(0).expect("memfd_secret");
1091/// ```
1092pub fn memfd_secret(flags: u32) -> Result<OwnedFd, Errno> {
1093    // SAFETY: memfd_secret(2) prend un seul argument scalaire u32.
1094    // Pas d'accès mémoire utilisateur. Retourne un fd ou une erreur.
1095    let ret = unsafe { raw_syscall_memfd_secret(flags) };
1096    if ret < 0 {
1097        return Err(errno_from_negative_syscall_ret(ret));
1098    }
1099    // Linux borne les fd à i32::MAX ; la troncature est exacte.
1100    #[allow(clippy::cast_possible_truncation)]
1101    let raw_fd = ret as i32;
1102    // SAFETY: le kernel a alloué un fd valide ; Air en prend la propriété.
1103    let owned = unsafe { OwnedFd::from_raw_fd(raw_fd) };
1104    Ok(owned)
1105}
1106
1107// ─────────────────────────────────────────────────────────────────────────
1108// Synchronisation
1109// ─────────────────────────────────────────────────────────────────────────
1110
1111/// Force la synchronisation d'un mapping `MAP_SHARED` vers le fichier.
1112///
1113/// Wrappeur de `msync(2)`. Utile pour garantir que les modifications
1114/// d'un mapping partagé sont visibles après une panne.
1115///
1116/// # Parameters
1117///
1118/// - `address` : adresse de début (alignée sur la page size).
1119/// - `length` : longueur en octets.
1120/// - `flags` : [`MsyncFlags::SYNC`] (bloquant) ou `ASYNC` (non-bloquant).
1121///
1122/// # Errors
1123///
1124/// - `EINVAL` : adresse non alignée, flags invalides, ou `SYNC | ASYNC`
1125///   simultanés.
1126/// - `ENOMEM` : plage non entièrement mappée.
1127///
1128/// # Examples
1129///
1130/// ```no_run
1131/// use air_sys_syscall::mem::msync;
1132/// use air_sys_types::mem::MsyncFlags;
1133/// use core::ptr::NonNull;
1134///
1135/// # fn example(address: NonNull<u8>) {
1136/// msync(address, 4096, MsyncFlags::SYNC).expect("msync");
1137/// # }
1138/// ```
1139pub fn msync(address: NonNull<u8>, length: usize, flags: MsyncFlags) -> Result<(), Errno> {
1140    // SAFETY: msync(2) synchronise une plage de pages vers le fichier
1141    // sous-jacent. L'appelant garantit que [address, address+length) est mappée.
1142    let ret = unsafe { raw_syscall_msync(address.as_ptr() as u64, length, flags.bits()) };
1143    if ret < 0 {
1144        return Err(errno_from_negative_syscall_ret(ret));
1145    }
1146    Ok(())
1147}
1148
1149// ─────────────────────────────────────────────────────────────────────────
1150// Accès inter-processus
1151// ─────────────────────────────────────────────────────────────────────────
1152
1153/// Lit la mémoire d'un autre processus sans `ptrace`.
1154///
1155/// Wrappeur de `process_vm_readv(2)` (Linux 3.2+). Lit depuis les
1156/// segments `remote_iov` du processus `pid` dans `local_iov`.
1157///
1158/// # Parameters
1159///
1160/// - `pid` : PID cible (doit avoir les mêmes UID/GID ou `CAP_SYS_PTRACE`).
1161/// - `local_iov` : buffers locaux de destination.
1162/// - `remote_iov` : segments dans l'espace d'adressage du processus cible.
1163/// - `flags` : réservé, doit être 0.
1164///
1165/// # Errors
1166///
1167/// - `ESRCH` : processus cible introuvable.
1168/// - `EPERM` : permissions insuffisantes.
1169/// - `EINVAL` : `flags` non nul, ou `iov` dépasse `UIO_MAXIOV`.
1170/// - `EFAULT` : adresse locale ou distante invalide.
1171///
1172/// # Examples
1173///
1174/// ```no_run
1175/// use air_sys_syscall::mem::process_vm_readv;
1176/// use air_sys_types::{Pid, mem::RemoteIoSlice};
1177/// use air_sys_types::net::IoSliceMut;
1178///
1179/// # fn example(target: Pid, remote_addr: u64) {
1180/// let mut buffer = [0u8; 64];
1181/// let mut local = [IoSliceMut::new(&mut buffer)];
1182/// let remote = [RemoteIoSlice { addr: remote_addr, len: 64 }];
1183/// let n = process_vm_readv(target, &mut local, &remote, 0)
1184///     .expect("process_vm_readv");
1185/// # }
1186/// ```
1187pub fn process_vm_readv(
1188    pid: Pid,
1189    local_iov: &mut [IoSliceMut<'_>],
1190    remote_iov: &[RemoteIoSlice],
1191    flags: u32,
1192) -> Result<usize, Errno> {
1193    let local_ptr = local_iov.as_ptr() as u64;
1194    let local_cnt = local_iov.len() as u64;
1195    let remote_ptr = remote_iov.as_ptr() as u64;
1196    let remote_cnt = remote_iov.len() as u64;
1197    // SAFETY:
1198    // - process_vm_readv(2) lit la mémoire distante et écrit dans les
1199    //   buffers locaux décrits par local_iov.
1200    // - IoSliceMut est #[repr(C)] compatible avec `struct iovec` Linux.
1201    // - RemoteIoSlice est #[repr(C)] compatible avec `struct iovec` Linux
1202    //   (layout identique : { ptr/addr: u64, len: usize } sur LP64).
1203    // - Les slices sont valides pour la durée du syscall (références Rust).
1204    // - pid est un Pid valide (newtype sur i32).
1205    let ret = unsafe {
1206        raw_syscall_process_vm_readv(
1207            pid.as_raw(),
1208            local_ptr,
1209            local_cnt,
1210            remote_ptr,
1211            remote_cnt,
1212            u64::from(flags),
1213        )
1214    };
1215    if ret < 0 {
1216        return Err(errno_from_negative_syscall_ret(ret));
1217    }
1218    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1219    Ok(ret as usize)
1220}
1221
1222/// Écrit dans la mémoire d'un autre processus sans `ptrace`.
1223///
1224/// Wrappeur de `process_vm_writev(2)` (Linux 3.2+). Écrit depuis
1225/// `local_iov` vers les segments `remote_iov` du processus `pid`.
1226///
1227/// # Parameters
1228///
1229/// - `pid` : PID cible.
1230/// - `local_iov` : buffers locaux source.
1231/// - `remote_iov` : segments cibles dans l'espace d'adressage distant.
1232/// - `flags` : réservé, doit être 0.
1233///
1234/// # Errors
1235///
1236/// Voir [`process_vm_readv`].
1237///
1238/// # Examples
1239///
1240/// ```no_run
1241/// use air_sys_syscall::mem::process_vm_writev;
1242/// use air_sys_types::{Pid, mem::RemoteIoSlice};
1243/// use air_sys_types::net::IoSlice;
1244///
1245/// # fn example(target: Pid, remote_addr: u64) {
1246/// let data = b"hello";
1247/// let local = [IoSlice::new(data)];
1248/// let remote = [RemoteIoSlice { addr: remote_addr, len: data.len() }];
1249/// process_vm_writev(target, &local, &remote, 0).expect("process_vm_writev");
1250/// # }
1251/// ```
1252pub fn process_vm_writev(
1253    pid: Pid,
1254    local_iov: &[IoSlice<'_>],
1255    remote_iov: &[RemoteIoSlice],
1256    flags: u32,
1257) -> Result<usize, Errno> {
1258    let local_ptr = local_iov.as_ptr() as u64;
1259    let local_cnt = local_iov.len() as u64;
1260    let remote_ptr = remote_iov.as_ptr() as u64;
1261    let remote_cnt = remote_iov.len() as u64;
1262    // SAFETY:
1263    // - process_vm_writev(2) lit les buffers locaux décrits par local_iov
1264    //   et écrit dans la mémoire distante décrite par remote_iov.
1265    // - IoSlice est #[repr(C)] compatible avec `struct iovec` Linux.
1266    // - RemoteIoSlice est #[repr(C)] compatible avec `struct iovec` Linux.
1267    // - Les slices sont valides pour la durée du syscall.
1268    // - pid est un Pid valide (newtype sur i32).
1269    let ret = unsafe {
1270        raw_syscall_process_vm_writev(
1271            pid.as_raw(),
1272            local_ptr,
1273            local_cnt,
1274            remote_ptr,
1275            remote_cnt,
1276            u64::from(flags),
1277        )
1278    };
1279    if ret < 0 {
1280        return Err(errno_from_negative_syscall_ret(ret));
1281    }
1282    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1283    Ok(ret as usize)
1284}
1285
1286// ─────────────────────────────────────────────────────────────────────────
1287// Helpers syscall bruts — mmap
1288// ─────────────────────────────────────────────────────────────────────────
1289
1290#[cfg(target_arch = "x86_64")]
1291#[inline]
1292unsafe fn raw_syscall_mmap(
1293    address: u64,
1294    length: usize,
1295    prot: i32,
1296    flags: i32,
1297    fd: i64,
1298    offset: u64,
1299) -> i64 {
1300    let ret: i64;
1301    // SAFETY: SYS_mmap (x86_64 = 9). 6 arguments : rdi, rsi, rdx, r10, r8, r9.
1302    // Le kernel mappe une plage de mémoire et retourne l'adresse ou -errno.
1303    unsafe {
1304        core::arch::asm!(
1305            "syscall",
1306            in("rax") 9_i64,
1307            in("rdi") address,
1308            in("rsi") length,
1309            in("rdx") i64::from(prot),
1310            in("r10") i64::from(flags),
1311            in("r8")  fd,
1312            in("r9")  offset,
1313            lateout("rax") ret,
1314            lateout("rcx") _,
1315            lateout("r11") _,
1316            options(nostack, preserves_flags),
1317        );
1318    }
1319    ret
1320}
1321
1322#[cfg(target_arch = "aarch64")]
1323#[inline]
1324unsafe fn raw_syscall_mmap(
1325    address: u64,
1326    length: usize,
1327    prot: i32,
1328    flags: i32,
1329    fd: i64,
1330    offset: u64,
1331) -> i64 {
1332    let ret: i64;
1333    // SAFETY: SYS_mmap (aarch64 = 222). x8=nr, x0-x5=args, retour en x0.
1334    unsafe {
1335        core::arch::asm!(
1336            "svc 0",
1337            in("x8")  222_i64,
1338            inout("x0") address => ret,
1339            in("x1")  length,
1340            in("x2")  i64::from(prot),
1341            in("x3")  i64::from(flags),
1342            in("x4")  fd,
1343            in("x5")  offset,
1344            options(nostack, preserves_flags),
1345        );
1346    }
1347    ret
1348}
1349
1350// ─────────────────────────────────────────────────────────────────────────
1351// Helpers syscall bruts — munmap
1352// ─────────────────────────────────────────────────────────────────────────
1353
1354#[cfg(target_arch = "x86_64")]
1355#[inline]
1356unsafe fn raw_syscall_munmap(address: u64, length: usize) -> i64 {
1357    let ret: i64;
1358    // SAFETY: SYS_munmap (x86_64 = 11). Libère le mapping ; pas d'accès
1359    // mémoire utilisateur par le syscall lui-même.
1360    unsafe {
1361        core::arch::asm!(
1362            "syscall",
1363            in("rax") 11_i64,
1364            in("rdi") address,
1365            in("rsi") length,
1366            lateout("rax") ret,
1367            lateout("rcx") _,
1368            lateout("r11") _,
1369            options(nostack, preserves_flags),
1370        );
1371    }
1372    ret
1373}
1374
1375#[cfg(target_arch = "aarch64")]
1376#[inline]
1377unsafe fn raw_syscall_munmap(address: u64, length: usize) -> i64 {
1378    let ret: i64;
1379    // SAFETY: SYS_munmap (aarch64 = 215).
1380    unsafe {
1381        core::arch::asm!(
1382            "svc 0",
1383            in("x8")  215_i64,
1384            inout("x0") address => ret,
1385            in("x1")  length,
1386            options(nostack, preserves_flags),
1387        );
1388    }
1389    ret
1390}
1391
1392// ─────────────────────────────────────────────────────────────────────────
1393// Helpers syscall bruts — mremap
1394// ─────────────────────────────────────────────────────────────────────────
1395
1396#[cfg(target_arch = "x86_64")]
1397#[inline]
1398unsafe fn raw_syscall_mremap(
1399    old_addr: u64,
1400    old_len: usize,
1401    new_len: usize,
1402    flags: i32,
1403    new_addr: u64,
1404) -> i64 {
1405    let ret: i64;
1406    // SAFETY: SYS_mremap (x86_64 = 25). 5 arguments : rdi, rsi, rdx, r10, r8.
1407    unsafe {
1408        core::arch::asm!(
1409            "syscall",
1410            in("rax") 25_i64,
1411            in("rdi") old_addr,
1412            in("rsi") old_len,
1413            in("rdx") new_len,
1414            in("r10") i64::from(flags),
1415            in("r8")  new_addr,
1416            lateout("rax") ret,
1417            lateout("rcx") _,
1418            lateout("r11") _,
1419            options(nostack, preserves_flags),
1420        );
1421    }
1422    ret
1423}
1424
1425#[cfg(target_arch = "aarch64")]
1426#[inline]
1427unsafe fn raw_syscall_mremap(
1428    old_addr: u64,
1429    old_len: usize,
1430    new_len: usize,
1431    flags: i32,
1432    new_addr: u64,
1433) -> i64 {
1434    let ret: i64;
1435    // SAFETY: SYS_mremap (aarch64 = 216).
1436    unsafe {
1437        core::arch::asm!(
1438            "svc 0",
1439            in("x8")  216_i64,
1440            inout("x0") old_addr => ret,
1441            in("x1")  old_len,
1442            in("x2")  new_len,
1443            in("x3")  i64::from(flags),
1444            in("x4")  new_addr,
1445            options(nostack, preserves_flags),
1446        );
1447    }
1448    ret
1449}
1450
1451// ─────────────────────────────────────────────────────────────────────────
1452// Helpers syscall bruts — mprotect
1453// ─────────────────────────────────────────────────────────────────────────
1454
1455#[cfg(target_arch = "x86_64")]
1456#[inline]
1457unsafe fn raw_syscall_mprotect(address: u64, length: usize, prot: i32) -> i64 {
1458    let ret: i64;
1459    // SAFETY: SYS_mprotect (x86_64 = 10). Scalaires uniquement.
1460    unsafe {
1461        core::arch::asm!(
1462            "syscall",
1463            in("rax") 10_i64,
1464            in("rdi") address,
1465            in("rsi") length,
1466            in("rdx") i64::from(prot),
1467            lateout("rax") ret,
1468            lateout("rcx") _,
1469            lateout("r11") _,
1470            options(nostack, preserves_flags),
1471        );
1472    }
1473    ret
1474}
1475
1476#[cfg(target_arch = "aarch64")]
1477#[inline]
1478unsafe fn raw_syscall_mprotect(address: u64, length: usize, prot: i32) -> i64 {
1479    let ret: i64;
1480    // SAFETY: SYS_mprotect (aarch64 = 226).
1481    unsafe {
1482        core::arch::asm!(
1483            "svc 0",
1484            in("x8")  226_i64,
1485            inout("x0") address => ret,
1486            in("x1")  length,
1487            in("x2")  i64::from(prot),
1488            options(nostack, preserves_flags),
1489        );
1490    }
1491    ret
1492}
1493
1494// ─────────────────────────────────────────────────────────────────────────
1495// Helpers syscall bruts — madvise
1496// ─────────────────────────────────────────────────────────────────────────
1497
1498#[cfg(target_arch = "x86_64")]
1499#[inline]
1500unsafe fn raw_syscall_madvise(address: u64, length: usize, advice: i32) -> i64 {
1501    let ret: i64;
1502    // SAFETY: SYS_madvise (x86_64 = 28). Scalaires uniquement ; readonly
1503    // serait incorrect (le kernel peut modifier des métadonnées internes).
1504    unsafe {
1505        core::arch::asm!(
1506            "syscall",
1507            in("rax") 28_i64,
1508            in("rdi") address,
1509            in("rsi") length,
1510            in("rdx") i64::from(advice),
1511            lateout("rax") ret,
1512            lateout("rcx") _,
1513            lateout("r11") _,
1514            options(nostack, preserves_flags),
1515        );
1516    }
1517    ret
1518}
1519
1520#[cfg(target_arch = "aarch64")]
1521#[inline]
1522unsafe fn raw_syscall_madvise(address: u64, length: usize, advice: i32) -> i64 {
1523    let ret: i64;
1524    // SAFETY: SYS_madvise (aarch64 = 233).
1525    unsafe {
1526        core::arch::asm!(
1527            "svc 0",
1528            in("x8")  233_i64,
1529            inout("x0") address => ret,
1530            in("x1")  length,
1531            in("x2")  i64::from(advice),
1532            options(nostack, preserves_flags),
1533        );
1534    }
1535    ret
1536}
1537
1538// ─────────────────────────────────────────────────────────────────────────
1539// Helpers syscall bruts — mlock / mlock2 / munlock / mlockall / munlockall
1540// ─────────────────────────────────────────────────────────────────────────
1541
1542#[cfg(target_arch = "x86_64")]
1543#[inline]
1544unsafe fn raw_syscall_mlock(address: u64, length: usize) -> i64 {
1545    let ret: i64;
1546    // SAFETY: SYS_mlock (x86_64 = 149).
1547    unsafe {
1548        core::arch::asm!(
1549            "syscall",
1550            in("rax") 149_i64,
1551            in("rdi") address,
1552            in("rsi") length,
1553            lateout("rax") ret,
1554            lateout("rcx") _,
1555            lateout("r11") _,
1556            options(nostack, preserves_flags),
1557        );
1558    }
1559    ret
1560}
1561
1562#[cfg(target_arch = "aarch64")]
1563#[inline]
1564unsafe fn raw_syscall_mlock(address: u64, length: usize) -> i64 {
1565    let ret: i64;
1566    // SAFETY: SYS_mlock (aarch64 = 228).
1567    unsafe {
1568        core::arch::asm!(
1569            "svc 0",
1570            in("x8")  228_i64,
1571            inout("x0") address => ret,
1572            in("x1")  length,
1573            options(nostack, preserves_flags),
1574        );
1575    }
1576    ret
1577}
1578
1579#[cfg(target_arch = "x86_64")]
1580#[inline]
1581unsafe fn raw_syscall_mlock2(address: u64, length: usize, flags: u32) -> i64 {
1582    let ret: i64;
1583    // SAFETY: SYS_mlock2 (x86_64 = 325).
1584    unsafe {
1585        core::arch::asm!(
1586            "syscall",
1587            in("rax") 325_i64,
1588            in("rdi") address,
1589            in("rsi") length,
1590            in("rdx") i64::from(flags),
1591            lateout("rax") ret,
1592            lateout("rcx") _,
1593            lateout("r11") _,
1594            options(nostack, preserves_flags),
1595        );
1596    }
1597    ret
1598}
1599
1600#[cfg(target_arch = "aarch64")]
1601#[inline]
1602unsafe fn raw_syscall_mlock2(address: u64, length: usize, flags: u32) -> i64 {
1603    let ret: i64;
1604    // SAFETY: SYS_mlock2 (aarch64 = 284).
1605    unsafe {
1606        core::arch::asm!(
1607            "svc 0",
1608            in("x8")  284_i64,
1609            inout("x0") address => ret,
1610            in("x1")  length,
1611            in("x2")  i64::from(flags),
1612            options(nostack, preserves_flags),
1613        );
1614    }
1615    ret
1616}
1617
1618#[cfg(target_arch = "x86_64")]
1619#[inline]
1620unsafe fn raw_syscall_munlock(address: u64, length: usize) -> i64 {
1621    let ret: i64;
1622    // SAFETY: SYS_munlock (x86_64 = 150).
1623    unsafe {
1624        core::arch::asm!(
1625            "syscall",
1626            in("rax") 150_i64,
1627            in("rdi") address,
1628            in("rsi") length,
1629            lateout("rax") ret,
1630            lateout("rcx") _,
1631            lateout("r11") _,
1632            options(nostack, preserves_flags),
1633        );
1634    }
1635    ret
1636}
1637
1638#[cfg(target_arch = "aarch64")]
1639#[inline]
1640unsafe fn raw_syscall_munlock(address: u64, length: usize) -> i64 {
1641    let ret: i64;
1642    // SAFETY: SYS_munlock (aarch64 = 229).
1643    unsafe {
1644        core::arch::asm!(
1645            "svc 0",
1646            in("x8")  229_i64,
1647            inout("x0") address => ret,
1648            in("x1")  length,
1649            options(nostack, preserves_flags),
1650        );
1651    }
1652    ret
1653}
1654
1655#[cfg(target_arch = "x86_64")]
1656#[inline]
1657unsafe fn raw_syscall_mlockall(flags: i32) -> i64 {
1658    let ret: i64;
1659    // SAFETY: SYS_mlockall (x86_64 = 151). Un seul scalaire.
1660    unsafe {
1661        core::arch::asm!(
1662            "syscall",
1663            in("rax") 151_i64,
1664            in("rdi") i64::from(flags),
1665            lateout("rax") ret,
1666            lateout("rcx") _,
1667            lateout("r11") _,
1668            options(nostack, preserves_flags),
1669        );
1670    }
1671    ret
1672}
1673
1674#[cfg(target_arch = "aarch64")]
1675#[inline]
1676unsafe fn raw_syscall_mlockall(flags: i32) -> i64 {
1677    let ret: i64;
1678    // SAFETY: SYS_mlockall (aarch64 = 230).
1679    unsafe {
1680        core::arch::asm!(
1681            "svc 0",
1682            in("x8")  230_i64,
1683            inout("x0") i64::from(flags) => ret,
1684            options(nostack, preserves_flags),
1685        );
1686    }
1687    ret
1688}
1689
1690#[cfg(target_arch = "x86_64")]
1691#[inline]
1692unsafe fn raw_syscall_munlockall() -> i64 {
1693    let ret: i64;
1694    // SAFETY: SYS_munlockall (x86_64 = 152). Aucun argument.
1695    unsafe {
1696        core::arch::asm!(
1697            "syscall",
1698            in("rax") 152_i64,
1699            lateout("rax") ret,
1700            lateout("rcx") _,
1701            lateout("r11") _,
1702            options(nostack, preserves_flags),
1703        );
1704    }
1705    ret
1706}
1707
1708#[cfg(target_arch = "aarch64")]
1709#[inline]
1710unsafe fn raw_syscall_munlockall() -> i64 {
1711    let ret: i64;
1712    // SAFETY: SYS_munlockall (aarch64 = 231). Aucun argument.
1713    unsafe {
1714        core::arch::asm!(
1715            "svc 0",
1716            in("x8")  231_i64,
1717            inout("x0") 0_i64 => ret,
1718            options(nostack, preserves_flags),
1719        );
1720    }
1721    ret
1722}
1723
1724// ─────────────────────────────────────────────────────────────────────────
1725// Helpers syscall bruts — memfd_create / memfd_secret
1726// ─────────────────────────────────────────────────────────────────────────
1727
1728#[cfg(target_arch = "x86_64")]
1729#[inline]
1730unsafe fn raw_syscall_memfd_create(name_ptr: u64, flags: u32) -> i64 {
1731    let ret: i64;
1732    // SAFETY: SYS_memfd_create (x86_64 = 319). Lit la chaîne NUL à `name_ptr`.
1733    unsafe {
1734        core::arch::asm!(
1735            "syscall",
1736            in("rax") 319_i64,
1737            in("rdi") name_ptr,
1738            in("rsi") i64::from(flags),
1739            lateout("rax") ret,
1740            lateout("rcx") _,
1741            lateout("r11") _,
1742            options(nostack, preserves_flags),
1743        );
1744    }
1745    ret
1746}
1747
1748#[cfg(target_arch = "aarch64")]
1749#[inline]
1750unsafe fn raw_syscall_memfd_create(name_ptr: u64, flags: u32) -> i64 {
1751    let ret: i64;
1752    // SAFETY: SYS_memfd_create (aarch64 = 279).
1753    unsafe {
1754        core::arch::asm!(
1755            "svc 0",
1756            in("x8")  279_i64,
1757            inout("x0") name_ptr => ret,
1758            in("x1")  i64::from(flags),
1759            options(nostack, preserves_flags),
1760        );
1761    }
1762    ret
1763}
1764
1765#[cfg(target_arch = "x86_64")]
1766#[inline]
1767unsafe fn raw_syscall_memfd_secret(flags: u32) -> i64 {
1768    let ret: i64;
1769    // SAFETY: SYS_memfd_secret (x86_64 = 447). Un seul scalaire.
1770    unsafe {
1771        core::arch::asm!(
1772            "syscall",
1773            in("rax") 447_i64,
1774            in("rdi") i64::from(flags),
1775            lateout("rax") ret,
1776            lateout("rcx") _,
1777            lateout("r11") _,
1778            options(nostack, preserves_flags, readonly),
1779        );
1780    }
1781    ret
1782}
1783
1784#[cfg(target_arch = "aarch64")]
1785#[inline]
1786unsafe fn raw_syscall_memfd_secret(flags: u32) -> i64 {
1787    let ret: i64;
1788    // SAFETY: SYS_memfd_secret (aarch64 = 447).
1789    unsafe {
1790        core::arch::asm!(
1791            "svc 0",
1792            in("x8")  447_i64,
1793            inout("x0") i64::from(flags) => ret,
1794            options(nostack, preserves_flags, readonly),
1795        );
1796    }
1797    ret
1798}
1799
1800// ─────────────────────────────────────────────────────────────────────────
1801// Helpers syscall bruts — msync
1802// ─────────────────────────────────────────────────────────────────────────
1803
1804#[cfg(target_arch = "x86_64")]
1805#[inline]
1806unsafe fn raw_syscall_msync(address: u64, length: usize, flags: i32) -> i64 {
1807    let ret: i64;
1808    // SAFETY: SYS_msync (x86_64 = 26). Scalaires ; le kernel lit les pages
1809    // mais ne modifie pas la mémoire utilisateur via le pointeur.
1810    unsafe {
1811        core::arch::asm!(
1812            "syscall",
1813            in("rax") 26_i64,
1814            in("rdi") address,
1815            in("rsi") length,
1816            in("rdx") i64::from(flags),
1817            lateout("rax") ret,
1818            lateout("rcx") _,
1819            lateout("r11") _,
1820            options(nostack, preserves_flags),
1821        );
1822    }
1823    ret
1824}
1825
1826#[cfg(target_arch = "aarch64")]
1827#[inline]
1828unsafe fn raw_syscall_msync(address: u64, length: usize, flags: i32) -> i64 {
1829    let ret: i64;
1830    // SAFETY: SYS_msync (aarch64 = 227).
1831    unsafe {
1832        core::arch::asm!(
1833            "svc 0",
1834            in("x8")  227_i64,
1835            inout("x0") address => ret,
1836            in("x1")  length,
1837            in("x2")  i64::from(flags),
1838            options(nostack, preserves_flags),
1839        );
1840    }
1841    ret
1842}
1843
1844// ─────────────────────────────────────────────────────────────────────────
1845// Helpers syscall bruts — process_vm_readv / process_vm_writev
1846// ─────────────────────────────────────────────────────────────────────────
1847
1848#[cfg(target_arch = "x86_64")]
1849#[inline]
1850unsafe fn raw_syscall_process_vm_readv(
1851    pid: i32,
1852    local_iov: u64,
1853    liovcnt: u64,
1854    remote_iov: u64,
1855    riovcnt: u64,
1856    flags: u64,
1857) -> i64 {
1858    let ret: i64;
1859    // SAFETY: SYS_process_vm_readv (x86_64 = 310). 6 arguments.
1860    // Le kernel lit depuis l'espace d'adressage distant et écrit dans
1861    // les buffers locaux décrits par local_iov.
1862    unsafe {
1863        core::arch::asm!(
1864            "syscall",
1865            in("rax") 310_i64,
1866            in("rdi") i64::from(pid),
1867            in("rsi") local_iov,
1868            in("rdx") liovcnt,
1869            in("r10") remote_iov,
1870            in("r8")  riovcnt,
1871            in("r9")  flags,
1872            lateout("rax") ret,
1873            lateout("rcx") _,
1874            lateout("r11") _,
1875            options(nostack, preserves_flags),
1876        );
1877    }
1878    ret
1879}
1880
1881#[cfg(target_arch = "aarch64")]
1882#[inline]
1883unsafe fn raw_syscall_process_vm_readv(
1884    pid: i32,
1885    local_iov: u64,
1886    liovcnt: u64,
1887    remote_iov: u64,
1888    riovcnt: u64,
1889    flags: u64,
1890) -> i64 {
1891    let ret: i64;
1892    // SAFETY: SYS_process_vm_readv (aarch64 = 270).
1893    unsafe {
1894        core::arch::asm!(
1895            "svc 0",
1896            in("x8")  270_i64,
1897            inout("x0") i64::from(pid) => ret,
1898            in("x1")  local_iov,
1899            in("x2")  liovcnt,
1900            in("x3")  remote_iov,
1901            in("x4")  riovcnt,
1902            in("x5")  flags,
1903            options(nostack, preserves_flags),
1904        );
1905    }
1906    ret
1907}
1908
1909#[cfg(target_arch = "x86_64")]
1910#[inline]
1911unsafe fn raw_syscall_process_vm_writev(
1912    pid: i32,
1913    local_iov: u64,
1914    liovcnt: u64,
1915    remote_iov: u64,
1916    riovcnt: u64,
1917    flags: u64,
1918) -> i64 {
1919    let ret: i64;
1920    // SAFETY: SYS_process_vm_writev (x86_64 = 311). 6 arguments.
1921    // Le kernel lit depuis les buffers locaux et écrit dans l'espace
1922    // d'adressage distant.
1923    unsafe {
1924        core::arch::asm!(
1925            "syscall",
1926            in("rax") 311_i64,
1927            in("rdi") i64::from(pid),
1928            in("rsi") local_iov,
1929            in("rdx") liovcnt,
1930            in("r10") remote_iov,
1931            in("r8")  riovcnt,
1932            in("r9")  flags,
1933            lateout("rax") ret,
1934            lateout("rcx") _,
1935            lateout("r11") _,
1936            options(nostack, preserves_flags),
1937        );
1938    }
1939    ret
1940}
1941
1942#[cfg(target_arch = "aarch64")]
1943#[inline]
1944unsafe fn raw_syscall_process_vm_writev(
1945    pid: i32,
1946    local_iov: u64,
1947    liovcnt: u64,
1948    remote_iov: u64,
1949    riovcnt: u64,
1950    flags: u64,
1951) -> i64 {
1952    let ret: i64;
1953    // SAFETY: SYS_process_vm_writev (aarch64 = 271).
1954    unsafe {
1955        core::arch::asm!(
1956            "svc 0",
1957            in("x8")  271_i64,
1958            inout("x0") i64::from(pid) => ret,
1959            in("x1")  local_iov,
1960            in("x2")  liovcnt,
1961            in("x3")  remote_iov,
1962            in("x4")  riovcnt,
1963            in("x5")  flags,
1964            options(nostack, preserves_flags),
1965        );
1966    }
1967    ret
1968}
1969
1970// ─────────────────────────────────────────────────────────────────────────
1971// Tests
1972// ─────────────────────────────────────────────────────────────────────────
1973
1974// Tests fonctionnels sur `Arc`/atomiques `std` (hors loom : créer un `Arc`
1975// loom hors d'un `loom::model` paniquerait). La preuve de concurrence vit dans
1976// `mod loom_refcount` (sous `cfg(loom)`).
1977#[cfg(all(test, target_os = "linux", not(loom)))]
1978mod tests;
1979
1980// ───────────────────────────────────────────────────────────────────────────
1981// Preuve de concurrence (loom) — le compteur fort de `MmapRegion` est correct
1982// sous réordonnancement : des drops **concurrents** des gardes de vivacité
1983// produisent `munmap` **exactement une fois**, jamais en avance.
1984//
1985// Exécuter : `RUSTFLAGS="--cfg loom" cargo test -p air-sys-syscall --lib loom`.
1986// ───────────────────────────────────────────────────────────────────────────
1987
1988#[cfg(loom)]
1989mod loom_refcount {
1990    use super::{MmapRegion, ProbeCounter};
1991    use core::sync::atomic::Ordering;
1992    use loom::sync::Arc;
1993
1994    #[test]
1995    fn munmap_exactly_once_under_concurrent_guard_drops() {
1996        loom::model(|| {
1997            let counter = Arc::new(ProbeCounter::new(0));
1998            let region = MmapRegion::new_for_test(64, Arc::clone(&counter));
1999
2000            // Deux gardes « en vol » ; l'utilisateur lâche la région.
2001            let g1 = region.liveness_handle();
2002            let g2 = region.liveness_handle();
2003            drop(region);
2004            assert_eq!(counter.load(Ordering::SeqCst), 0, "munmap en avance");
2005
2006            // Les deux gardes tombent en parallèle : le compteur fort garantit
2007            // un seul drop de l'inner → un seul `munmap`, quel que soit l'ordre.
2008            let t1 = loom::thread::spawn(move || drop(g1));
2009            let t2 = loom::thread::spawn(move || drop(g2));
2010            t1.join().expect("join t1");
2011            t2.join().expect("join t2");
2012
2013            assert_eq!(
2014                counter.load(Ordering::SeqCst),
2015                1,
2016                "munmap doit survenir exactement une fois"
2017            );
2018        });
2019    }
2020}