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}