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