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 let write_end = unsafe { OwnedFd::from_raw_fd(fds[1]) };
250 Ok((read_end, write_end))
251}
252
253/// Transfère jusqu'à `length` octets de `fd_in` vers `fd_out` sans copie
254/// userspace (zero-copy).
255///
256/// Wrappeur de `splice(2)` (Linux 2.6.17+). **Au moins un** des deux FDs
257/// doit être un pipe ; sinon le kernel retourne `EINVAL`.
258///
259/// Si `offset_in` / `offset_out` est `None`, la position courante du FD est
260/// utilisée et avancée. Si `Some`, la position donnée est utilisée sans
261/// modifier la position courante du FD.
262///
263/// # Parameters
264///
265/// - `fd_in` : FD source.
266/// - `offset_in` : offset dans `fd_in`, ou `None` pour la position courante.
267/// - `fd_out` : FD destination.
268/// - `offset_out` : offset dans `fd_out`, ou `None` pour la position courante.
269/// - `length` : nombre maximum d'octets à transférer.
270/// - `flags` : drapeaux [`SpliceFlags`].
271///
272/// # Errors
273///
274/// - `EINVAL` : ni `fd_in` ni `fd_out` n'est un pipe, ou flags invalides.
275/// - `EAGAIN` : mode `NONBLOCK` et l'opération bloquerait.
276/// - `EINTR` : interrompu par un signal (pas de retry automatique).
277/// - `EBADF` : FD invalide ou mode d'accès incorrect.
278/// - `ESPIPE` : `offset_in` non-nul sur un pipe (les pipes ne sont pas seekables).
279///
280/// # Examples
281///
282/// ```no_run
283/// use air_sys_syscall::ipc::{pipe2, splice};
284/// use air_sys_types::{PipeFlags, SpliceFlags};
285/// use air_sys_types::fd::AsFd;
286///
287/// let (read_fd, write_fd) = pipe2(PipeFlags::CLOEXEC).expect("pipe2");
288/// // Connecter un fichier source au pipe...
289/// let _ = splice(
290/// read_fd.as_fd(), None,
291/// write_fd.as_fd(), None,
292/// 4096,
293/// SpliceFlags::empty(),
294/// );
295/// ```
296pub fn splice(
297 fd_in: BorrowedFd<'_>,
298 offset_in: Option<&mut u64>,
299 fd_out: BorrowedFd<'_>,
300 offset_out: Option<&mut u64>,
301 length: usize,
302 flags: SpliceFlags,
303) -> Result<usize, Errno> {
304 // `None` → NULL pointer (0) ; `Some` → adresse du u64 mutable.
305 let off_in_ptr: u64 = match offset_in {
306 None => 0,
307 Some(p) => {
308 let raw: *mut u64 = p;
309 raw as u64
310 }
311 };
312 let off_out_ptr: u64 = match offset_out {
313 None => 0,
314 Some(p) => {
315 let raw: *mut u64 = p;
316 raw as u64
317 }
318 };
319 // SAFETY:
320 // - splice(2) lit/écrit optionnellement via `off_in_ptr`/`off_out_ptr`
321 // si non nuls ; les pointeurs sont valides (locaux à l'appelant, via
322 // `Option<&mut u64>`).
323 // - fd_in et fd_out sont garantis ouverts par BorrowedFd.
324 let ret = unsafe {
325 raw_syscall_splice(
326 fd_in.as_raw_fd(),
327 off_in_ptr,
328 fd_out.as_raw_fd(),
329 off_out_ptr,
330 length,
331 flags.bits(),
332 )
333 };
334 if ret < 0 {
335 return Err(errno_from_negative_syscall_ret(ret));
336 }
337 // splice retourne le nombre d'octets transférés : non négatif, ≤ length
338 // (qui est un usize). La troncature de i64 → usize est exacte sur LP64.
339 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
340 Ok(ret as usize)
341}
342
343/// Duplique le contenu d'un pipe source dans un pipe destination sans
344/// consommer les données de la source.
345///
346/// Wrappeur de `tee(2)` (Linux 2.6.17+). Les deux FDs **doivent** être
347/// des pipes. Contrairement à `splice`, les données restent disponibles
348/// dans `fd_in` après l'opération.
349///
350/// # Parameters
351///
352/// - `fd_in` : pipe source.
353/// - `fd_out` : pipe destination.
354/// - `length` : nombre maximum d'octets à dupliquer.
355/// - `flags` : drapeaux [`SpliceFlags`] (typiquement `NONBLOCK`).
356///
357/// # Errors
358///
359/// - `EINVAL` : `fd_in` ou `fd_out` n'est pas un pipe.
360/// - `EAGAIN` : mode `NONBLOCK` et l'opération bloquerait.
361/// - `EINTR` : interrompu par un signal.
362///
363/// # Examples
364///
365/// ```no_run
366/// use air_sys_syscall::ipc::{pipe2, tee};
367/// use air_sys_types::{PipeFlags, SpliceFlags};
368/// use air_sys_types::fd::AsFd;
369///
370/// let (r1, w1) = pipe2(PipeFlags::CLOEXEC).expect("pipe2");
371/// let (r2, w2) = pipe2(PipeFlags::CLOEXEC).expect("pipe2");
372/// let _ = tee(r1.as_fd(), w2.as_fd(), 4096, SpliceFlags::empty());
373/// ```
374pub fn tee(
375 fd_in: BorrowedFd<'_>,
376 fd_out: BorrowedFd<'_>,
377 length: usize,
378 flags: SpliceFlags,
379) -> Result<usize, Errno> {
380 // SAFETY: tee(2) ne touche aucune mémoire utilisateur ; il opère
381 // uniquement sur les pipe buffers internes au kernel.
382 // fd_in et fd_out sont garantis ouverts par BorrowedFd.
383 let ret =
384 unsafe { raw_syscall_tee(fd_in.as_raw_fd(), fd_out.as_raw_fd(), length, flags.bits()) };
385 if ret < 0 {
386 return Err(errno_from_negative_syscall_ret(ret));
387 }
388 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
389 Ok(ret as usize)
390}
391
392/// Transfère des buffers userspace vers un pipe sans copie intermédiaire.
393///
394/// Wrappeur de `vmsplice(2)` (Linux 2.6.17+). `fd` doit être un pipe.
395///
396/// **Mode GIFT (dangereux) :** si [`SpliceFlags::GIFT`] est positionné,
397/// les pages userspace sont données au kernel. Les régions décrites par
398/// `iov` **ne doivent plus jamais être modifiées** après l'appel —
399/// violation d'invariant = corruption mémoire silencieuse.
400///
401/// # Parameters
402///
403/// - `fd` : pipe destination.
404/// - `iov` : slices de buffers userspace à transférer.
405/// - `flags` : drapeaux [`SpliceFlags`].
406///
407/// # Safety
408///
409/// Si [`SpliceFlags::GIFT`] est utilisé, l'appelant doit garantir que
410/// les pages décrites par `iov` ne sont plus modifiées après l'appel.
411/// Modifier ces pages après un `vmsplice GIFT` produit un comportement
412/// indéfini.
413///
414/// # Errors
415///
416/// - `EINVAL` : `fd` n'est pas un pipe, ou `iov` dépasse `UIO_MAXIOV`.
417/// - `EAGAIN` : mode `NONBLOCK` et le pipe est plein.
418/// - `EINTR` : interrompu par un signal.
419///
420/// # Examples
421///
422/// ```no_run
423/// use air_sys_syscall::ipc::{pipe2, vmsplice};
424/// use air_sys_types::{PipeFlags, SpliceFlags};
425/// use air_sys_types::net::IoSlice;
426/// use air_sys_types::fd::AsFd;
427///
428/// let data = b"hello, world";
429/// let iov = [IoSlice::new(data)];
430/// let (_r, w) = pipe2(PipeFlags::CLOEXEC).expect("pipe2");
431/// let _ = vmsplice(w.as_fd(), &iov, SpliceFlags::empty());
432/// ```
433pub fn vmsplice(
434 fd: BorrowedFd<'_>,
435 iov: &[IoSlice<'_>],
436 flags: SpliceFlags,
437) -> Result<usize, Errno> {
438 let iov_ptr: *const IoSlice<'_> = iov.as_ptr();
439 // SAFETY:
440 // - vmsplice(2) lit les iovec décrits par `iov_ptr`/`iov_len` pour
441 // transférer des données userspace dans un pipe kernel.
442 // - Chaque IoSlice est #[repr(C)] compatible avec `struct iovec` Linux.
443 // - Les buffers pointés par les IoSlice doivent rester valides et
444 // immuables pour la durée du syscall (garantit par la durée de vie
445 // de la référence &[IoSlice<'_>]).
446 // - fd est garanti ouvert par BorrowedFd.
447 let ret =
448 unsafe { raw_syscall_vmsplice(fd.as_raw_fd(), iov_ptr as u64, iov.len(), flags.bits()) };
449 if ret < 0 {
450 return Err(errno_from_negative_syscall_ret(ret));
451 }
452 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
453 Ok(ret as usize)
454}
455
456// ─────────────────────────────────────────────────────────────────────────
457// Helpers syscall bruts : read / write (pour eventfd)
458// ─────────────────────────────────────────────────────────────────────────
459
460#[cfg(target_arch = "x86_64")]
461#[inline]
462unsafe fn raw_syscall_read(fd: i32, buffer: u64, count: usize) -> i64 {
463 let ret: i64;
464 // SAFETY: SYS_read (x86_64 = 0). Le kernel écrit `count` octets à
465 // l'adresse `buffer`. L'appelant garantit que `buffer` est valide pour
466 // `count` octets en écriture. Clobbers RCX + R11 (ABI syscall x86_64).
467 unsafe {
468 core::arch::asm!(
469 "syscall",
470 in("rax") 0_i64,
471 in("rdi") i64::from(fd),
472 in("rsi") buffer,
473 in("rdx") count,
474 lateout("rax") ret,
475 lateout("rcx") _,
476 lateout("r11") _,
477 options(nostack, preserves_flags),
478 );
479 }
480 ret
481}
482
483#[cfg(target_arch = "aarch64")]
484#[inline]
485unsafe fn raw_syscall_read(fd: i32, buffer: u64, count: usize) -> i64 {
486 let ret: i64;
487 // SAFETY: SYS_read (aarch64 = 63). Numéro en X8, args en X0..X2, retour en X0.
488 // Le kernel écrit `count` octets à `buffer`.
489 unsafe {
490 core::arch::asm!(
491 "svc 0",
492 in("x8") 63_i64,
493 inout("x0") i64::from(fd) => ret,
494 in("x1") buffer,
495 in("x2") count,
496 options(nostack, preserves_flags),
497 );
498 }
499 ret
500}
501
502#[cfg(target_arch = "x86_64")]
503#[inline]
504unsafe fn raw_syscall_write(fd: i32, buffer: u64, count: usize) -> i64 {
505 let ret: i64;
506 // SAFETY: SYS_write (x86_64 = 1). Le kernel lit `count` octets depuis
507 // l'adresse `buffer`. L'appelant garantit que `buffer` est valide pour
508 // `count` octets en lecture. `readonly` serait incorrect (le syscall
509 // avance la position du fd côté kernel), donc on n'utilise pas `readonly`.
510 unsafe {
511 core::arch::asm!(
512 "syscall",
513 in("rax") 1_i64,
514 in("rdi") i64::from(fd),
515 in("rsi") buffer,
516 in("rdx") count,
517 lateout("rax") ret,
518 lateout("rcx") _,
519 lateout("r11") _,
520 options(nostack, preserves_flags),
521 );
522 }
523 ret
524}
525
526#[cfg(target_arch = "aarch64")]
527#[inline]
528unsafe fn raw_syscall_write(fd: i32, buffer: u64, count: usize) -> i64 {
529 let ret: i64;
530 // SAFETY: SYS_write (aarch64 = 64). Numéro en X8, args en X0..X2, retour en X0.
531 unsafe {
532 core::arch::asm!(
533 "svc 0",
534 in("x8") 64_i64,
535 inout("x0") i64::from(fd) => ret,
536 in("x1") buffer,
537 in("x2") count,
538 options(nostack, preserves_flags),
539 );
540 }
541 ret
542}
543
544// ─────────────────────────────────────────────────────────────────────────
545// Helpers syscall bruts : eventfd2, pipe2, splice, tee, vmsplice
546// ─────────────────────────────────────────────────────────────────────────
547
548#[cfg(target_arch = "x86_64")]
549#[inline]
550unsafe fn raw_syscall_eventfd2(initial: u32, flags: i32) -> i64 {
551 let ret: i64;
552 // SAFETY: SYS_eventfd2 (x86_64 = 290). Scalaires seulement ; pas
553 // d'accès mémoire utilisateur. `readonly` correct.
554 unsafe {
555 core::arch::asm!(
556 "syscall",
557 in("rax") 290_i64,
558 in("rdi") i64::from(initial),
559 in("rsi") i64::from(flags),
560 lateout("rax") ret,
561 lateout("rcx") _,
562 lateout("r11") _,
563 options(nostack, preserves_flags, readonly),
564 );
565 }
566 ret
567}
568
569#[cfg(target_arch = "aarch64")]
570#[inline]
571unsafe fn raw_syscall_eventfd2(initial: u32, flags: i32) -> i64 {
572 let ret: i64;
573 // SAFETY: SYS_eventfd2 (aarch64 = 19). Scalaires ; `readonly` correct.
574 unsafe {
575 core::arch::asm!(
576 "svc 0",
577 in("x8") 19_i64,
578 inout("x0") i64::from(initial) => ret,
579 in("x1") i64::from(flags),
580 options(nostack, preserves_flags, readonly),
581 );
582 }
583 ret
584}
585
586#[cfg(target_arch = "x86_64")]
587#[inline]
588unsafe fn raw_syscall_pipe2(fds_ptr: u64, flags: i32) -> i64 {
589 let ret: i64;
590 // SAFETY: SYS_pipe2 (x86_64 = 293). Le kernel écrit 2 × sizeof(int)
591 // octets à `fds_ptr`. L'appelant garantit la validité du pointeur.
592 // **Pas de `readonly`** : le kernel écrit dans la mémoire utilisateur.
593 unsafe {
594 core::arch::asm!(
595 "syscall",
596 in("rax") 293_i64,
597 in("rdi") fds_ptr,
598 in("rsi") i64::from(flags),
599 lateout("rax") ret,
600 lateout("rcx") _,
601 lateout("r11") _,
602 options(nostack, preserves_flags),
603 );
604 }
605 ret
606}
607
608#[cfg(target_arch = "aarch64")]
609#[inline]
610unsafe fn raw_syscall_pipe2(fds_ptr: u64, flags: i32) -> i64 {
611 let ret: i64;
612 // SAFETY: SYS_pipe2 (aarch64 = 59). Le kernel écrit les deux fd à `fds_ptr`.
613 unsafe {
614 core::arch::asm!(
615 "svc 0",
616 in("x8") 59_i64,
617 inout("x0") fds_ptr => ret,
618 in("x1") i64::from(flags),
619 options(nostack, preserves_flags),
620 );
621 }
622 ret
623}
624
625#[cfg(target_arch = "x86_64")]
626#[inline]
627unsafe fn raw_syscall_splice(
628 fd_in: i32,
629 offset_in: u64,
630 fd_out: i32,
631 offset_out: u64,
632 length: usize,
633 flags: u32,
634) -> i64 {
635 let ret: i64;
636 // SAFETY: SYS_splice (x86_64 = 275). Le kernel peut lire/écrire
637 // optionnellement via `offset_in`/`offset_out` si non nuls.
638 // **Pas de `readonly`** : le kernel peut modifier les offsets.
639 unsafe {
640 core::arch::asm!(
641 "syscall",
642 in("rax") 275_i64,
643 in("rdi") i64::from(fd_in),
644 in("rsi") offset_in,
645 in("rdx") i64::from(fd_out),
646 in("r10") offset_out,
647 in("r8") length,
648 in("r9") i64::from(flags),
649 lateout("rax") ret,
650 lateout("rcx") _,
651 lateout("r11") _,
652 options(nostack, preserves_flags),
653 );
654 }
655 ret
656}
657
658#[cfg(target_arch = "aarch64")]
659#[inline]
660unsafe fn raw_syscall_splice(
661 fd_in: i32,
662 offset_in: u64,
663 fd_out: i32,
664 offset_out: u64,
665 length: usize,
666 flags: u32,
667) -> i64 {
668 let ret: i64;
669 // SAFETY: SYS_splice (aarch64 = 76). Args en X0..X5.
670 unsafe {
671 core::arch::asm!(
672 "svc 0",
673 in("x8") 76_i64,
674 inout("x0") i64::from(fd_in) => ret,
675 in("x1") offset_in,
676 in("x2") i64::from(fd_out),
677 in("x3") offset_out,
678 in("x4") length,
679 in("x5") i64::from(flags),
680 options(nostack, preserves_flags),
681 );
682 }
683 ret
684}
685
686#[cfg(target_arch = "x86_64")]
687#[inline]
688unsafe fn raw_syscall_tee(fd_in: i32, fd_out: i32, length: usize, flags: u32) -> i64 {
689 let ret: i64;
690 // SAFETY: SYS_tee (x86_64 = 276). Opère uniquement sur les pipe
691 // buffers kernel ; pas d'accès mémoire utilisateur.
692 // `readonly` serait inexact (modifie l'état interne des pipes).
693 unsafe {
694 core::arch::asm!(
695 "syscall",
696 in("rax") 276_i64,
697 in("rdi") i64::from(fd_in),
698 in("rsi") i64::from(fd_out),
699 in("rdx") length,
700 in("r10") i64::from(flags),
701 lateout("rax") ret,
702 lateout("rcx") _,
703 lateout("r11") _,
704 options(nostack, preserves_flags),
705 );
706 }
707 ret
708}
709
710#[cfg(target_arch = "aarch64")]
711#[inline]
712unsafe fn raw_syscall_tee(fd_in: i32, fd_out: i32, length: usize, flags: u32) -> i64 {
713 let ret: i64;
714 // SAFETY: SYS_tee (aarch64 = 77). Args en X0..X3.
715 unsafe {
716 core::arch::asm!(
717 "svc 0",
718 in("x8") 77_i64,
719 inout("x0") i64::from(fd_in) => ret,
720 in("x1") i64::from(fd_out),
721 in("x2") length,
722 in("x3") i64::from(flags),
723 options(nostack, preserves_flags),
724 );
725 }
726 ret
727}
728
729#[cfg(target_arch = "x86_64")]
730#[inline]
731unsafe fn raw_syscall_vmsplice(fd: i32, iov: u64, iov_len: usize, flags: u32) -> i64 {
732 let ret: i64;
733 // SAFETY: SYS_vmsplice (x86_64 = 278). Le kernel lit les iovec à
734 // `iov` pour transférer des données userspace dans le pipe.
735 // **Pas de `readonly`** : les données peuvent être consommées (mode
736 // GIFT donne la propriété des pages au kernel).
737 unsafe {
738 core::arch::asm!(
739 "syscall",
740 in("rax") 278_i64,
741 in("rdi") i64::from(fd),
742 in("rsi") iov,
743 in("rdx") iov_len,
744 in("r10") i64::from(flags),
745 lateout("rax") ret,
746 lateout("rcx") _,
747 lateout("r11") _,
748 options(nostack, preserves_flags),
749 );
750 }
751 ret
752}
753
754#[cfg(target_arch = "aarch64")]
755#[inline]
756unsafe fn raw_syscall_vmsplice(fd: i32, iov: u64, iov_len: usize, flags: u32) -> i64 {
757 let ret: i64;
758 // SAFETY: SYS_vmsplice (aarch64 = 75). Args en X0..X3.
759 unsafe {
760 core::arch::asm!(
761 "svc 0",
762 in("x8") 75_i64,
763 inout("x0") i64::from(fd) => ret,
764 in("x1") iov,
765 in("x2") iov_len,
766 in("x3") i64::from(flags),
767 options(nostack, preserves_flags),
768 );
769 }
770 ret
771}
772
773// ─────────────────────────────────────────────────────────────────────────
774// Tests
775// ─────────────────────────────────────────────────────────────────────────
776
777#[cfg(test)]
778#[cfg(target_os = "linux")]
779mod tests;