Skip to main content

air_sys_syscall/
ipc.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 `ipc` — eventfd, pipe, opérations zero-copy.
6//!
7//! Cf. `docs/specs/layer-0/family-ipc.md`.
8
9#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
10compile_error!("air-sys-syscall::ipc supporte uniquement x86_64 et aarch64 (ADR-014).");
11
12use air_sys_types::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd};
13use core::num::NonZeroI32;
14
15use air_sys_types::Errno;
16use air_sys_types::ipc::{EventFdFlags, PipeFlags, SpliceFlags};
17use air_sys_types::net::IoSlice;
18
19// ─────────────────────────────────────────────────────────────────────────
20// Helper commun : conversion d'une valeur de retour syscall négative en Errno.
21// ─────────────────────────────────────────────────────────────────────────
22
23fn errno_from_negative_syscall_ret(ret: i64) -> Errno {
24    debug_assert!(ret < 0 && ret > -4096);
25    #[allow(clippy::cast_possible_truncation)]
26    let raw = ret.wrapping_neg() as i32;
27    let nz = NonZeroI32::new(raw).expect("errno strictement positif par construction");
28    Errno::from_nonzero(nz)
29}
30
31// ─────────────────────────────────────────────────────────────────────────
32// EventFd — compteur kernel exposé comme FD.
33// ─────────────────────────────────────────────────────────────────────────
34
35/// FD `eventfd` (cf. `eventfd2(2)`).
36///
37/// Encapsule un compteur kernel 64 bits exposé comme descripteur de
38/// fichier. Pattern privilégié pour les notifications légères
39/// inter-threads ou inter-processus, et pour l'intégration avec
40/// io_uring (wakeup du reactor).
41///
42/// La fermeture du FD est automatique à la destruction (RAII via
43/// [`OwnedFd`]).
44#[derive(Debug)]
45pub struct EventFd(OwnedFd);
46
47impl EventFd {
48    /// Vue empruntée du FD sous-jacent.
49    ///
50    /// Utile pour passer le FD à d'autres syscalls (poll, io_uring…)
51    /// sans transférer l'ownership.
52    #[must_use]
53    pub fn as_fd(&self) -> BorrowedFd<'_> {
54        self.0.as_fd()
55    }
56
57    /// Consomme le `EventFd` et restitue le [`OwnedFd`] sous-jacent.
58    #[must_use]
59    pub fn into_fd(self) -> OwnedFd {
60        self.0
61    }
62
63    /// Lit la valeur courante du compteur.
64    ///
65    /// **Mode normal** (sans `SEMAPHORE`) : retourne la valeur courante et
66    /// remet le compteur à zéro. Bloque si le compteur est zéro (sauf
67    /// si `NONBLOCK` est positionné).
68    ///
69    /// **Mode sémaphore** (avec `SEMAPHORE`) : retourne 1 et décrémente
70    /// de 1. Bloque si le compteur est zéro.
71    ///
72    /// # Errors
73    ///
74    /// - `EAGAIN` : compteur à zéro et le FD est en mode non-bloquant.
75    /// - `EINTR` : interrompu par un signal (ADR-021 convention 2 :
76    ///   pas de retry automatique).
77    /// - `EBADF` : FD invalide (ne se produit pas si `EventFd` bien formé).
78    ///
79    /// # Examples
80    ///
81    /// ```no_run
82    /// use air_sys_syscall::ipc::eventfd2;
83    /// use air_sys_types::EventFdFlags;
84    ///
85    /// let efd = eventfd2(0, EventFdFlags::empty()).expect("eventfd2");
86    /// efd.write(5).expect("write");
87    /// let val = efd.read().expect("read");
88    /// assert_eq!(val, 5);
89    /// ```
90    pub fn read(&self) -> Result<u64, Errno> {
91        let mut buffer: u64 = 0;
92        let buf_ptr: *mut u64 = &mut buffer;
93        // SAFETY:
94        // - SYS_read sur un eventfd lit exactement 8 octets dans `*buf_ptr`.
95        // - `buffer` est local valide pour toute la durée du syscall.
96        // - Le FD est garanti ouvert (EventFd possède un OwnedFd valide).
97        let ret = unsafe {
98            raw_syscall_read(
99                self.0.as_raw_fd(),
100                buf_ptr as u64,
101                core::mem::size_of::<u64>(),
102            )
103        };
104        if ret < 0 {
105            return Err(errno_from_negative_syscall_ret(ret));
106        }
107        Ok(buffer)
108    }
109
110    /// Incrémente le compteur de `value`.
111    ///
112    /// Bloque si le compteur atteindrait `u64::MAX - 1` (espace épuisé).
113    /// Retourne immédiatement `EAGAIN` si non-bloquant.
114    ///
115    /// # Parameters
116    ///
117    /// - `value` : incrément. La valeur maximale autorisée par incrémentation
118    ///   est `u64::MAX - 1` ; `u64::MAX` est interdit par le kernel.
119    ///
120    /// # Errors
121    ///
122    /// - `EINVAL` : `value` == `u64::MAX` (débordement interdit).
123    /// - `EAGAIN` : le FD est non-bloquant et l'incrément bloquerait.
124    /// - `EINTR` : interrompu par un signal (pas de retry automatique).
125    ///
126    /// # Examples
127    ///
128    /// ```no_run
129    /// use air_sys_syscall::ipc::eventfd2;
130    /// use air_sys_types::EventFdFlags;
131    ///
132    /// let efd = eventfd2(0, EventFdFlags::empty()).expect("eventfd2");
133    /// efd.write(1).expect("write notification");
134    /// ```
135    pub fn write(&self, value: u64) -> Result<(), Errno> {
136        let buf_ptr: *const u64 = &value;
137        // SAFETY:
138        // - SYS_write sur un eventfd lit exactement 8 octets depuis `*buf_ptr`.
139        // - `value` est local valide pour toute la durée du syscall.
140        // - Le FD est garanti ouvert (EventFd possède un OwnedFd valide).
141        let ret = unsafe {
142            raw_syscall_write(
143                self.0.as_raw_fd(),
144                buf_ptr as u64,
145                core::mem::size_of::<u64>(),
146            )
147        };
148        if ret < 0 {
149            return Err(errno_from_negative_syscall_ret(ret));
150        }
151        Ok(())
152    }
153}
154
155// ─────────────────────────────────────────────────────────────────────────
156// Fonctions publiques
157// ─────────────────────────────────────────────────────────────────────────
158
159/// Crée un `eventfd` avec compteur initial et flags.
160///
161/// Wrappeur de `eventfd2(2)` (Linux 2.6.27+, numéro x86_64: 290,
162/// aarch64: 19). `CLOEXEC` n'est **pas** ajouté automatiquement : l'appelant
163/// doit l'inclure dans `flags` si désiré (comportement recommandé).
164///
165/// # Parameters
166///
167/// - `initial` : valeur initiale du compteur (typiquement 0).
168/// - `flags` : combinaison de [`EventFdFlags`].
169///
170/// # Errors
171///
172/// - `EINVAL` : flags invalides.
173/// - `EMFILE` : quota de FD du processus atteint.
174/// - `ENFILE` : quota de FD système atteint.
175/// - `ENODEV` : impossible de monter le pseudo-périphérique anonyme.
176/// - `ENOMEM` : mémoire kernel insuffisante.
177///
178/// # Examples
179///
180/// ```no_run
181/// use air_sys_syscall::ipc::eventfd2;
182/// use air_sys_types::EventFdFlags;
183///
184/// let efd = eventfd2(0, EventFdFlags::CLOEXEC).expect("eventfd2");
185/// ```
186pub fn eventfd2(initial: u64, flags: EventFdFlags) -> Result<EventFd, Errno> {
187    // Le kernel eventfd2 prend un u32 : les bits hauts de `initial` sont
188    // ignorés par l'ABI kernel ; on passe les 32 bits bas.
189    #[allow(clippy::cast_possible_truncation)]
190    let initial_u32 = initial as u32;
191    // SAFETY: eventfd2(2) ne lit ni n'écrit aucune mémoire utilisateur ;
192    // il prend deux scalaires (u32 initial, i32 flags) et retourne un fd.
193    let ret = unsafe { raw_syscall_eventfd2(initial_u32, flags.bits()) };
194    if ret < 0 {
195        return Err(errno_from_negative_syscall_ret(ret));
196    }
197    // Linux borne les fd à i32::MAX ; la troncature est exacte.
198    #[allow(clippy::cast_possible_truncation)]
199    let raw_fd = ret as i32;
200    // SAFETY: le kernel vient d'allouer un fd valide et nous en a transféré
201    // la propriété ; Air le wrappe immédiatement dans OwnedFd. Drop fermera.
202    let owned = unsafe { OwnedFd::from_raw_fd(raw_fd) };
203    Ok(EventFd(owned))
204}
205
206/// Crée un pipe unidirectionnel et retourne `(read_end, write_end)`.
207///
208/// Wrappeur de `pipe2(2)` (Linux 2.6.27+). Retourne deux [`OwnedFd`] :
209/// le premier est le côté lecture, le second le côté écriture.
210///
211/// `CLOEXEC` est **fortement recommandé** pour éviter la fuite du pipe
212/// vers les processus fils après `exec`. Air n'impose pas `CLOEXEC`
213/// automatiquement car certains usages légitimes (héritage volontaire
214/// vers un enfant) en ont besoin.
215///
216/// # Parameters
217///
218/// - `flags` : combinaison de [`PipeFlags`].
219///
220/// # Errors
221///
222/// - `EFAULT` : pointeur interne invalide (ne se produit pas via cette API).
223/// - `EINVAL` : flags invalides.
224/// - `EMFILE` : quota de FD du processus atteint.
225/// - `ENFILE` : quota de FD système atteint.
226///
227/// # Examples
228///
229/// ```no_run
230/// use air_sys_syscall::ipc::pipe2;
231/// use air_sys_types::PipeFlags;
232///
233/// let (read_fd, write_fd) = pipe2(PipeFlags::CLOEXEC).expect("pipe2");
234/// ```
235pub fn pipe2(flags: PipeFlags) -> Result<(OwnedFd, OwnedFd), Errno> {
236    // Le kernel pipe2 écrit deux fd dans un tableau int[2] (= [i32; 2]).
237    let mut fds: [i32; 2] = [-1, -1];
238    let fds_ptr: *mut [i32; 2] = &mut fds;
239    // SAFETY:
240    // - pipe2(2) écrit exactement 2 × sizeof(int) = 8 octets à `fds_ptr`.
241    // - `fds` est local valide pour toute la durée du syscall.
242    let ret = unsafe { raw_syscall_pipe2(fds_ptr as u64, flags.bits()) };
243    if ret < 0 {
244        return Err(errno_from_negative_syscall_ret(ret));
245    }
246    // SAFETY: le kernel a écrit deux fd valides dans `fds` ; Air en prend
247    // la propriété. Drop fermera chacun.
248    let read_end = unsafe { OwnedFd::from_raw_fd(fds[0]) };
249    // SAFETY: le noyau a écrit un second fd valide dans `fds[1]` ; Air en prend la propriété, une
250    // seule fois.
251    let write_end = unsafe { OwnedFd::from_raw_fd(fds[1]) };
252    Ok((read_end, write_end))
253}
254
255/// Transfère jusqu'à `length` octets de `fd_in` vers `fd_out` sans copie
256/// userspace (zero-copy).
257///
258/// Wrappeur de `splice(2)` (Linux 2.6.17+). **Au moins un** des deux FDs
259/// doit être un pipe ; sinon le kernel retourne `EINVAL`.
260///
261/// Si `offset_in` / `offset_out` est `None`, la position courante du FD est
262/// utilisée et avancée. Si `Some`, la position donnée est utilisée sans
263/// modifier la position courante du FD.
264///
265/// # Parameters
266///
267/// - `fd_in` : FD source.
268/// - `offset_in` : offset dans `fd_in`, ou `None` pour la position courante.
269/// - `fd_out` : FD destination.
270/// - `offset_out` : offset dans `fd_out`, ou `None` pour la position courante.
271/// - `length` : nombre maximum d'octets à transférer.
272/// - `flags` : drapeaux [`SpliceFlags`].
273///
274/// # Errors
275///
276/// - `EINVAL` : ni `fd_in` ni `fd_out` n'est un pipe, ou flags invalides.
277/// - `EAGAIN` : mode `NONBLOCK` et l'opération bloquerait.
278/// - `EINTR` : interrompu par un signal (pas de retry automatique).
279/// - `EBADF` : FD invalide ou mode d'accès incorrect.
280/// - `ESPIPE` : `offset_in` non-nul sur un pipe (les pipes ne sont pas seekables).
281///
282/// # Examples
283///
284/// ```no_run
285/// use air_sys_syscall::ipc::{pipe2, splice};
286/// use air_sys_types::{PipeFlags, SpliceFlags};
287/// use air_sys_types::fd::AsFd;
288///
289/// let (read_fd, write_fd) = pipe2(PipeFlags::CLOEXEC).expect("pipe2");
290/// // Connecter un fichier source au pipe...
291/// let _ = splice(
292///     read_fd.as_fd(), None,
293///     write_fd.as_fd(), None,
294///     4096,
295///     SpliceFlags::empty(),
296/// );
297/// ```
298pub fn splice(
299    fd_in: BorrowedFd<'_>,
300    offset_in: Option<&mut u64>,
301    fd_out: BorrowedFd<'_>,
302    offset_out: Option<&mut u64>,
303    length: usize,
304    flags: SpliceFlags,
305) -> Result<usize, Errno> {
306    // `None` → NULL pointer (0) ; `Some` → adresse du u64 mutable.
307    let off_in_ptr: u64 = match offset_in {
308        None => 0,
309        Some(p) => {
310            let raw: *mut u64 = p;
311            raw as u64
312        }
313    };
314    let off_out_ptr: u64 = match offset_out {
315        None => 0,
316        Some(p) => {
317            let raw: *mut u64 = p;
318            raw as u64
319        }
320    };
321    // SAFETY:
322    // - splice(2) lit/écrit optionnellement via `off_in_ptr`/`off_out_ptr`
323    //   si non nuls ; les pointeurs sont valides (locaux à l'appelant, via
324    //   `Option<&mut u64>`).
325    // - fd_in et fd_out sont garantis ouverts par BorrowedFd.
326    let ret = unsafe {
327        raw_syscall_splice(
328            fd_in.as_raw_fd(),
329            off_in_ptr,
330            fd_out.as_raw_fd(),
331            off_out_ptr,
332            length,
333            flags.bits(),
334        )
335    };
336    if ret < 0 {
337        return Err(errno_from_negative_syscall_ret(ret));
338    }
339    // splice retourne le nombre d'octets transférés : non négatif, ≤ length
340    // (qui est un usize). La troncature de i64 → usize est exacte sur LP64.
341    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
342    Ok(ret as usize)
343}
344
345/// Duplique le contenu d'un pipe source dans un pipe destination sans
346/// consommer les données de la source.
347///
348/// Wrappeur de `tee(2)` (Linux 2.6.17+). Les deux FDs **doivent** être
349/// des pipes. Contrairement à `splice`, les données restent disponibles
350/// dans `fd_in` après l'opération.
351///
352/// # Parameters
353///
354/// - `fd_in` : pipe source.
355/// - `fd_out` : pipe destination.
356/// - `length` : nombre maximum d'octets à dupliquer.
357/// - `flags` : drapeaux [`SpliceFlags`] (typiquement `NONBLOCK`).
358///
359/// # Errors
360///
361/// - `EINVAL` : `fd_in` ou `fd_out` n'est pas un pipe.
362/// - `EAGAIN` : mode `NONBLOCK` et l'opération bloquerait.
363/// - `EINTR` : interrompu par un signal.
364///
365/// # Examples
366///
367/// ```no_run
368/// use air_sys_syscall::ipc::{pipe2, tee};
369/// use air_sys_types::{PipeFlags, SpliceFlags};
370/// use air_sys_types::fd::AsFd;
371///
372/// let (r1, w1) = pipe2(PipeFlags::CLOEXEC).expect("pipe2");
373/// let (r2, w2) = pipe2(PipeFlags::CLOEXEC).expect("pipe2");
374/// let _ = tee(r1.as_fd(), w2.as_fd(), 4096, SpliceFlags::empty());
375/// ```
376pub fn tee(
377    fd_in: BorrowedFd<'_>,
378    fd_out: BorrowedFd<'_>,
379    length: usize,
380    flags: SpliceFlags,
381) -> Result<usize, Errno> {
382    // SAFETY: tee(2) ne touche aucune mémoire utilisateur ; il opère
383    // uniquement sur les pipe buffers internes au kernel.
384    // fd_in et fd_out sont garantis ouverts par BorrowedFd.
385    let ret =
386        unsafe { raw_syscall_tee(fd_in.as_raw_fd(), fd_out.as_raw_fd(), length, flags.bits()) };
387    if ret < 0 {
388        return Err(errno_from_negative_syscall_ret(ret));
389    }
390    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
391    Ok(ret as usize)
392}
393
394/// Transfère des buffers userspace vers un pipe sans copie intermédiaire.
395///
396/// Wrappeur de `vmsplice(2)` (Linux 2.6.17+). `fd` doit être un pipe.
397///
398/// **Mode GIFT (dangereux) :** si [`SpliceFlags::GIFT`] est positionné,
399/// les pages userspace sont données au kernel. Les régions décrites par
400/// `iov` **ne doivent plus jamais être modifiées** après l'appel —
401/// violation d'invariant = corruption mémoire silencieuse.
402///
403/// # Parameters
404///
405/// - `fd` : pipe destination.
406/// - `iov` : slices de buffers userspace à transférer.
407/// - `flags` : drapeaux [`SpliceFlags`].
408///
409/// # Safety
410///
411/// Si [`SpliceFlags::GIFT`] est utilisé, l'appelant doit garantir que
412/// les pages décrites par `iov` ne sont plus modifiées après l'appel.
413/// Modifier ces pages après un `vmsplice GIFT` produit un comportement
414/// indéfini.
415///
416/// # Errors
417///
418/// - `EINVAL` : `fd` n'est pas un pipe, ou `iov` dépasse `UIO_MAXIOV`.
419/// - `EAGAIN` : mode `NONBLOCK` et le pipe est plein.
420/// - `EINTR` : interrompu par un signal.
421///
422/// # Examples
423///
424/// ```no_run
425/// use air_sys_syscall::ipc::{pipe2, vmsplice};
426/// use air_sys_types::{PipeFlags, SpliceFlags};
427/// use air_sys_types::net::IoSlice;
428/// use air_sys_types::fd::AsFd;
429///
430/// let data = b"hello, world";
431/// let iov = [IoSlice::new(data)];
432/// let (_r, w) = pipe2(PipeFlags::CLOEXEC).expect("pipe2");
433/// let _ = vmsplice(w.as_fd(), &iov, SpliceFlags::empty());
434/// ```
435pub fn vmsplice(
436    fd: BorrowedFd<'_>,
437    iov: &[IoSlice<'_>],
438    flags: SpliceFlags,
439) -> Result<usize, Errno> {
440    let iov_ptr: *const IoSlice<'_> = iov.as_ptr();
441    // SAFETY:
442    // - vmsplice(2) lit les iovec décrits par `iov_ptr`/`iov_len` pour
443    //   transférer des données userspace dans un pipe kernel.
444    // - Chaque IoSlice est #[repr(C)] compatible avec `struct iovec` Linux.
445    // - Les buffers pointés par les IoSlice doivent rester valides et
446    //   immuables pour la durée du syscall (garantit par la durée de vie
447    //   de la référence &[IoSlice<'_>]).
448    // - fd est garanti ouvert par BorrowedFd.
449    let ret =
450        unsafe { raw_syscall_vmsplice(fd.as_raw_fd(), iov_ptr as u64, iov.len(), flags.bits()) };
451    if ret < 0 {
452        return Err(errno_from_negative_syscall_ret(ret));
453    }
454    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
455    Ok(ret as usize)
456}
457
458// ─────────────────────────────────────────────────────────────────────────
459// Helpers syscall bruts : read / write (pour eventfd)
460// ─────────────────────────────────────────────────────────────────────────
461
462#[cfg(target_arch = "x86_64")]
463#[inline]
464unsafe fn raw_syscall_read(fd: i32, buffer: u64, count: usize) -> i64 {
465    let ret: i64;
466    // SAFETY: SYS_read (x86_64 = 0). Le kernel écrit `count` octets à
467    // l'adresse `buffer`. L'appelant garantit que `buffer` est valide pour
468    // `count` octets en écriture. Clobbers RCX + R11 (ABI syscall x86_64).
469    unsafe {
470        core::arch::asm!(
471            "syscall",
472            in("rax") 0_i64,
473            in("rdi") i64::from(fd),
474            in("rsi") buffer,
475            in("rdx") count,
476            lateout("rax") ret,
477            lateout("rcx") _,
478            lateout("r11") _,
479            options(nostack, preserves_flags),
480        );
481    }
482    ret
483}
484
485#[cfg(target_arch = "aarch64")]
486#[inline]
487unsafe fn raw_syscall_read(fd: i32, buffer: u64, count: usize) -> i64 {
488    let ret: i64;
489    // SAFETY: SYS_read (aarch64 = 63). Numéro en X8, args en X0..X2, retour en X0.
490    // Le kernel écrit `count` octets à `buffer`.
491    unsafe {
492        core::arch::asm!(
493            "svc 0",
494            in("x8") 63_i64,
495            inout("x0") i64::from(fd) => ret,
496            in("x1") buffer,
497            in("x2") count,
498            options(nostack, preserves_flags),
499        );
500    }
501    ret
502}
503
504#[cfg(target_arch = "x86_64")]
505#[inline]
506unsafe fn raw_syscall_write(fd: i32, buffer: u64, count: usize) -> i64 {
507    let ret: i64;
508    // SAFETY: SYS_write (x86_64 = 1). Le kernel lit `count` octets depuis
509    // l'adresse `buffer`. L'appelant garantit que `buffer` est valide pour
510    // `count` octets en lecture. `readonly` serait incorrect (le syscall
511    // avance la position du fd côté kernel), donc on n'utilise pas `readonly`.
512    unsafe {
513        core::arch::asm!(
514            "syscall",
515            in("rax") 1_i64,
516            in("rdi") i64::from(fd),
517            in("rsi") buffer,
518            in("rdx") count,
519            lateout("rax") ret,
520            lateout("rcx") _,
521            lateout("r11") _,
522            options(nostack, preserves_flags),
523        );
524    }
525    ret
526}
527
528#[cfg(target_arch = "aarch64")]
529#[inline]
530unsafe fn raw_syscall_write(fd: i32, buffer: u64, count: usize) -> i64 {
531    let ret: i64;
532    // SAFETY: SYS_write (aarch64 = 64). Numéro en X8, args en X0..X2, retour en X0.
533    unsafe {
534        core::arch::asm!(
535            "svc 0",
536            in("x8") 64_i64,
537            inout("x0") i64::from(fd) => ret,
538            in("x1") buffer,
539            in("x2") count,
540            options(nostack, preserves_flags),
541        );
542    }
543    ret
544}
545
546// ─────────────────────────────────────────────────────────────────────────
547// Helpers syscall bruts : eventfd2, pipe2, splice, tee, vmsplice
548// ─────────────────────────────────────────────────────────────────────────
549
550#[cfg(target_arch = "x86_64")]
551#[inline]
552unsafe fn raw_syscall_eventfd2(initial: u32, flags: i32) -> i64 {
553    let ret: i64;
554    // SAFETY: SYS_eventfd2 (x86_64 = 290). Scalaires seulement ; pas
555    // d'accès mémoire utilisateur. `readonly` correct.
556    unsafe {
557        core::arch::asm!(
558            "syscall",
559            in("rax") 290_i64,
560            in("rdi") i64::from(initial),
561            in("rsi") i64::from(flags),
562            lateout("rax") ret,
563            lateout("rcx") _,
564            lateout("r11") _,
565            options(nostack, preserves_flags, readonly),
566        );
567    }
568    ret
569}
570
571#[cfg(target_arch = "aarch64")]
572#[inline]
573unsafe fn raw_syscall_eventfd2(initial: u32, flags: i32) -> i64 {
574    let ret: i64;
575    // SAFETY: SYS_eventfd2 (aarch64 = 19). Scalaires ; `readonly` correct.
576    unsafe {
577        core::arch::asm!(
578            "svc 0",
579            in("x8") 19_i64,
580            inout("x0") i64::from(initial) => ret,
581            in("x1") i64::from(flags),
582            options(nostack, preserves_flags, readonly),
583        );
584    }
585    ret
586}
587
588#[cfg(target_arch = "x86_64")]
589#[inline]
590unsafe fn raw_syscall_pipe2(fds_ptr: u64, flags: i32) -> i64 {
591    let ret: i64;
592    // SAFETY: SYS_pipe2 (x86_64 = 293). Le kernel écrit 2 × sizeof(int)
593    // octets à `fds_ptr`. L'appelant garantit la validité du pointeur.
594    // **Pas de `readonly`** : le kernel écrit dans la mémoire utilisateur.
595    unsafe {
596        core::arch::asm!(
597            "syscall",
598            in("rax") 293_i64,
599            in("rdi") fds_ptr,
600            in("rsi") i64::from(flags),
601            lateout("rax") ret,
602            lateout("rcx") _,
603            lateout("r11") _,
604            options(nostack, preserves_flags),
605        );
606    }
607    ret
608}
609
610#[cfg(target_arch = "aarch64")]
611#[inline]
612unsafe fn raw_syscall_pipe2(fds_ptr: u64, flags: i32) -> i64 {
613    let ret: i64;
614    // SAFETY: SYS_pipe2 (aarch64 = 59). Le kernel écrit les deux fd à `fds_ptr`.
615    unsafe {
616        core::arch::asm!(
617            "svc 0",
618            in("x8") 59_i64,
619            inout("x0") fds_ptr => ret,
620            in("x1") i64::from(flags),
621            options(nostack, preserves_flags),
622        );
623    }
624    ret
625}
626
627#[cfg(target_arch = "x86_64")]
628#[inline]
629unsafe fn raw_syscall_splice(
630    fd_in: i32,
631    offset_in: u64,
632    fd_out: i32,
633    offset_out: u64,
634    length: usize,
635    flags: u32,
636) -> i64 {
637    let ret: i64;
638    // SAFETY: SYS_splice (x86_64 = 275). Le kernel peut lire/écrire
639    // optionnellement via `offset_in`/`offset_out` si non nuls.
640    // **Pas de `readonly`** : le kernel peut modifier les offsets.
641    unsafe {
642        core::arch::asm!(
643            "syscall",
644            in("rax") 275_i64,
645            in("rdi") i64::from(fd_in),
646            in("rsi") offset_in,
647            in("rdx") i64::from(fd_out),
648            in("r10") offset_out,
649            in("r8") length,
650            in("r9") i64::from(flags),
651            lateout("rax") ret,
652            lateout("rcx") _,
653            lateout("r11") _,
654            options(nostack, preserves_flags),
655        );
656    }
657    ret
658}
659
660#[cfg(target_arch = "aarch64")]
661#[inline]
662unsafe fn raw_syscall_splice(
663    fd_in: i32,
664    offset_in: u64,
665    fd_out: i32,
666    offset_out: u64,
667    length: usize,
668    flags: u32,
669) -> i64 {
670    let ret: i64;
671    // SAFETY: SYS_splice (aarch64 = 76). Args en X0..X5.
672    unsafe {
673        core::arch::asm!(
674            "svc 0",
675            in("x8") 76_i64,
676            inout("x0") i64::from(fd_in) => ret,
677            in("x1") offset_in,
678            in("x2") i64::from(fd_out),
679            in("x3") offset_out,
680            in("x4") length,
681            in("x5") i64::from(flags),
682            options(nostack, preserves_flags),
683        );
684    }
685    ret
686}
687
688#[cfg(target_arch = "x86_64")]
689#[inline]
690unsafe fn raw_syscall_tee(fd_in: i32, fd_out: i32, length: usize, flags: u32) -> i64 {
691    let ret: i64;
692    // SAFETY: SYS_tee (x86_64 = 276). Opère uniquement sur les pipe
693    // buffers kernel ; pas d'accès mémoire utilisateur.
694    // `readonly` serait inexact (modifie l'état interne des pipes).
695    unsafe {
696        core::arch::asm!(
697            "syscall",
698            in("rax") 276_i64,
699            in("rdi") i64::from(fd_in),
700            in("rsi") i64::from(fd_out),
701            in("rdx") length,
702            in("r10") i64::from(flags),
703            lateout("rax") ret,
704            lateout("rcx") _,
705            lateout("r11") _,
706            options(nostack, preserves_flags),
707        );
708    }
709    ret
710}
711
712#[cfg(target_arch = "aarch64")]
713#[inline]
714unsafe fn raw_syscall_tee(fd_in: i32, fd_out: i32, length: usize, flags: u32) -> i64 {
715    let ret: i64;
716    // SAFETY: SYS_tee (aarch64 = 77). Args en X0..X3.
717    unsafe {
718        core::arch::asm!(
719            "svc 0",
720            in("x8") 77_i64,
721            inout("x0") i64::from(fd_in) => ret,
722            in("x1") i64::from(fd_out),
723            in("x2") length,
724            in("x3") i64::from(flags),
725            options(nostack, preserves_flags),
726        );
727    }
728    ret
729}
730
731#[cfg(target_arch = "x86_64")]
732#[inline]
733unsafe fn raw_syscall_vmsplice(fd: i32, iov: u64, iov_len: usize, flags: u32) -> i64 {
734    let ret: i64;
735    // SAFETY: SYS_vmsplice (x86_64 = 278). Le kernel lit les iovec à
736    // `iov` pour transférer des données userspace dans le pipe.
737    // **Pas de `readonly`** : les données peuvent être consommées (mode
738    // GIFT donne la propriété des pages au kernel).
739    unsafe {
740        core::arch::asm!(
741            "syscall",
742            in("rax") 278_i64,
743            in("rdi") i64::from(fd),
744            in("rsi") iov,
745            in("rdx") iov_len,
746            in("r10") i64::from(flags),
747            lateout("rax") ret,
748            lateout("rcx") _,
749            lateout("r11") _,
750            options(nostack, preserves_flags),
751        );
752    }
753    ret
754}
755
756#[cfg(target_arch = "aarch64")]
757#[inline]
758unsafe fn raw_syscall_vmsplice(fd: i32, iov: u64, iov_len: usize, flags: u32) -> i64 {
759    let ret: i64;
760    // SAFETY: SYS_vmsplice (aarch64 = 75). Args en X0..X3.
761    unsafe {
762        core::arch::asm!(
763            "svc 0",
764            in("x8") 75_i64,
765            inout("x0") i64::from(fd) => ret,
766            in("x1") iov,
767            in("x2") iov_len,
768            in("x3") i64::from(flags),
769            options(nostack, preserves_flags),
770        );
771    }
772    ret
773}
774
775// ─────────────────────────────────────────────────────────────────────────
776// Tests
777// ─────────────────────────────────────────────────────────────────────────
778
779#[cfg(test)]
780#[cfg(target_os = "linux")]
781mod tests;