1#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
10compile_error!("air-sys-syscall::fs supporte uniquement x86_64 et aarch64 (ADR-014).");
11
12use air_sys_types::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
13use alloc::vec::Vec;
14use core::ffi::CStr;
15use core::num::NonZeroI32;
16
17use air_sys_types::Errno;
18use air_sys_types::fs::{
19 AccessFlags, AccessMode, DirEntry, DirEntryType, DirFd, FallocateMode, FdFlags, FileHandle,
20 FileLock, FsType, Mode, NameToHandleFlags, OpenFlags, OpenHow, RenameFlags, Seals, SeekWhence,
21 StatFsResult, StatusFlags, StatxFlags, StatxMask, StatxResult, StatxTimestamp, UtimeValue,
22};
23use air_sys_types::net::{IoSlice, IoSliceMut};
24
25const AT_FDCWD: i32 = -100;
30const O_CLOEXEC: u64 = 0o2_000_000;
31
32pub mod inotify;
35
36const F_DUPFD_CLOEXEC: i32 = 1030;
38const F_GETFD: i32 = 1;
39const F_SETFD: i32 = 2;
40const F_GETFL: i32 = 3;
41const F_SETFL: i32 = 4;
42const F_GETPIPE_SZ: i32 = 1032;
43const F_SETPIPE_SZ: i32 = 1031;
44const F_SETLK: i32 = 6;
45const F_SETLKW: i32 = 7;
46const F_ADD_SEALS: i32 = 1033;
47const F_GET_SEALS: i32 = 1034;
48
49const UTIME_NOW: i64 = 0x3FFF_FFFF;
51const UTIME_OMIT: i64 = 0x3FFF_FFFE;
52
53fn dirfd_to_raw(dirfd: DirFd<'_>) -> i32 {
58 match dirfd {
59 DirFd::Cwd => AT_FDCWD,
60 DirFd::Fd(fd) => fd.as_raw_fd(),
61 }
62}
63
64#[inline]
69fn fd_to_u64(fd: i32) -> u64 {
70 #[allow(clippy::cast_sign_loss)]
74 {
75 fd as u64
76 }
77}
78
79#[inline]
81fn i32_to_u64(v: i32) -> u64 {
82 #[allow(clippy::cast_sign_loss)]
86 {
87 v as u64
88 }
89}
90
91#[inline]
95fn ret_to_usize(ret: i64) -> usize {
96 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
99 {
100 ret as usize
101 }
102}
103
104#[inline]
110fn i64_to_u64(v: i64) -> u64 {
111 #[allow(clippy::cast_sign_loss)]
112 {
113 v as u64
114 }
115}
116
117#[inline]
120fn ret_to_usize_ok(ret: i64) -> Result<usize, Errno> {
121 Ok(ret_to_usize(ret))
122}
123fn errno_from_negative_syscall_ret(ret: i64) -> Errno {
124 debug_assert!(ret < 0 && ret > -4096);
125 #[allow(clippy::cast_possible_truncation)]
127 let raw = ret.wrapping_neg() as i32;
128 let nz = NonZeroI32::new(raw).expect("errno strictement positif par construction");
129 Errno::from_nonzero(nz)
130}
131
132#[repr(C)]
137struct KernelOpenHow {
138 flags: u64,
139 mode: u64,
140 resolve: u64,
141}
142
143const _: () = assert!(core::mem::size_of::<KernelOpenHow>() == 24);
144
145#[repr(C)]
146struct KernelStatxTimestamp {
147 tv_sec: i64,
148 tv_nsec: u32,
149 __reserved: i32,
150}
151
152#[repr(C)]
153struct KernelStatx {
154 stx_mask: u32,
155 stx_blksize: u32,
156 stx_attributes: u64,
157 stx_nlink: u32,
158 stx_uid: u32,
159 stx_gid: u32,
160 stx_mode: u16,
161 __spare0: [u16; 1],
162 stx_ino: u64,
163 stx_size: u64,
164 stx_blocks: u64,
165 stx_attributes_mask: u64,
166 stx_atime: KernelStatxTimestamp,
167 stx_btime: KernelStatxTimestamp,
168 stx_ctime: KernelStatxTimestamp,
169 stx_mtime: KernelStatxTimestamp,
170 stx_rdev_major: u32,
171 stx_rdev_minor: u32,
172 stx_dev_major: u32,
173 stx_dev_minor: u32,
174 stx_mnt_id: u64,
175 __spare2: u64,
176 __spare3: [u64; 12],
177}
178
179const _: () = assert!(core::mem::size_of::<KernelStatx>() == 256);
180
181#[repr(C)]
182struct KernelStatfs {
183 f_type: i64,
184 f_bsize: i64,
185 f_blocks: u64,
186 f_bfree: u64,
187 f_bavail: u64,
188 f_files: u64,
189 f_ffree: u64,
190 f_fsid: [u32; 2],
191 f_namelen: i64,
192 f_frsize: i64,
193 f_flags: i64,
194 f_spare: [i64; 4],
195}
196
197const _: () = assert!(core::mem::size_of::<KernelStatfs>() == 120);
198
199#[repr(C)]
200struct KernelFlock {
201 l_type: i16,
202 l_whence: i16,
203 _pad: i32,
204 l_start: i64,
205 l_len: i64,
206 l_pid: i32,
207 _pad2: i32,
208}
209
210#[repr(C)]
211struct KernelFileHandle {
212 handle_bytes: u32,
213 handle_type: i32,
214 f_handle: [u8; 128],
215}
216
217#[cfg(target_arch = "x86_64")]
222#[inline]
223unsafe fn syscall1(nr: i64, a0: u64) -> i64 {
224 let ret: i64;
225 unsafe {
227 core::arch::asm!(
228 "syscall",
229 in("rax") nr,
230 in("rdi") a0,
231 lateout("rax") ret,
232 lateout("rcx") _,
233 lateout("r11") _,
234 options(nostack, preserves_flags),
235 );
236 }
237 ret
238}
239
240#[cfg(target_arch = "x86_64")]
241#[inline]
242unsafe fn syscall2(nr: i64, a0: u64, a1: u64) -> i64 {
243 let ret: i64;
244 unsafe {
246 core::arch::asm!(
247 "syscall",
248 in("rax") nr,
249 in("rdi") a0,
250 in("rsi") a1,
251 lateout("rax") ret,
252 lateout("rcx") _,
253 lateout("r11") _,
254 options(nostack, preserves_flags),
255 );
256 }
257 ret
258}
259
260#[cfg(target_arch = "x86_64")]
261#[inline]
262unsafe fn syscall3(nr: i64, a0: u64, a1: u64, a2: u64) -> i64 {
263 let ret: i64;
264 unsafe {
266 core::arch::asm!(
267 "syscall",
268 in("rax") nr,
269 in("rdi") a0,
270 in("rsi") a1,
271 in("rdx") a2,
272 lateout("rax") ret,
273 lateout("rcx") _,
274 lateout("r11") _,
275 options(nostack, preserves_flags),
276 );
277 }
278 ret
279}
280
281#[cfg(target_arch = "x86_64")]
282#[inline]
283unsafe fn syscall4(nr: i64, a0: u64, a1: u64, a2: u64, a3: u64) -> i64 {
284 let ret: i64;
285 unsafe {
287 core::arch::asm!(
288 "syscall",
289 in("rax") nr,
290 in("rdi") a0,
291 in("rsi") a1,
292 in("rdx") a2,
293 in("r10") a3,
294 lateout("rax") ret,
295 lateout("rcx") _,
296 lateout("r11") _,
297 options(nostack, preserves_flags),
298 );
299 }
300 ret
301}
302
303#[cfg(target_arch = "x86_64")]
304#[inline]
305unsafe fn syscall5(nr: i64, a0: u64, a1: u64, a2: u64, a3: u64, a4: u64) -> i64 {
306 let ret: i64;
307 unsafe {
309 core::arch::asm!(
310 "syscall",
311 in("rax") nr,
312 in("rdi") a0,
313 in("rsi") a1,
314 in("rdx") a2,
315 in("r10") a3,
316 in("r8") a4,
317 lateout("rax") ret,
318 lateout("rcx") _,
319 lateout("r11") _,
320 options(nostack, preserves_flags),
321 );
322 }
323 ret
324}
325
326#[cfg(target_arch = "x86_64")]
327#[inline]
328unsafe fn syscall6(nr: i64, a0: u64, a1: u64, a2: u64, a3: u64, a4: u64, a5: u64) -> i64 {
329 let ret: i64;
330 unsafe {
332 core::arch::asm!(
333 "syscall",
334 in("rax") nr,
335 in("rdi") a0,
336 in("rsi") a1,
337 in("rdx") a2,
338 in("r10") a3,
339 in("r8") a4,
340 in("r9") a5,
341 lateout("rax") ret,
342 lateout("rcx") _,
343 lateout("r11") _,
344 options(nostack, preserves_flags),
345 );
346 }
347 ret
348}
349
350#[cfg(target_arch = "aarch64")]
355#[inline]
356unsafe fn syscall1(nr: i64, a0: u64) -> i64 {
357 let ret: i64;
358 unsafe {
360 core::arch::asm!(
361 "svc 0",
362 in("x8") nr,
363 inout("x0") a0 => ret,
364 options(nostack, preserves_flags),
365 );
366 }
367 ret
368}
369
370#[cfg(target_arch = "aarch64")]
371#[inline]
372unsafe fn syscall2(nr: i64, a0: u64, a1: u64) -> i64 {
373 let ret: i64;
374 unsafe {
376 core::arch::asm!(
377 "svc 0",
378 in("x8") nr,
379 inout("x0") a0 => ret,
380 in("x1") a1,
381 options(nostack, preserves_flags),
382 );
383 }
384 ret
385}
386
387#[cfg(target_arch = "aarch64")]
388#[inline]
389unsafe fn syscall3(nr: i64, a0: u64, a1: u64, a2: u64) -> i64 {
390 let ret: i64;
391 unsafe {
393 core::arch::asm!(
394 "svc 0",
395 in("x8") nr,
396 inout("x0") a0 => ret,
397 in("x1") a1,
398 in("x2") a2,
399 options(nostack, preserves_flags),
400 );
401 }
402 ret
403}
404
405#[cfg(target_arch = "aarch64")]
406#[inline]
407unsafe fn syscall4(nr: i64, a0: u64, a1: u64, a2: u64, a3: u64) -> i64 {
408 let ret: i64;
409 unsafe {
411 core::arch::asm!(
412 "svc 0",
413 in("x8") nr,
414 inout("x0") a0 => ret,
415 in("x1") a1,
416 in("x2") a2,
417 in("x3") a3,
418 options(nostack, preserves_flags),
419 );
420 }
421 ret
422}
423
424#[cfg(target_arch = "aarch64")]
425#[inline]
426unsafe fn syscall5(nr: i64, a0: u64, a1: u64, a2: u64, a3: u64, a4: u64) -> i64 {
427 let ret: i64;
428 unsafe {
430 core::arch::asm!(
431 "svc 0",
432 in("x8") nr,
433 inout("x0") a0 => ret,
434 in("x1") a1,
435 in("x2") a2,
436 in("x3") a3,
437 in("x4") a4,
438 options(nostack, preserves_flags),
439 );
440 }
441 ret
442}
443
444#[cfg(target_arch = "aarch64")]
445#[inline]
446unsafe fn syscall6(nr: i64, a0: u64, a1: u64, a2: u64, a3: u64, a4: u64, a5: u64) -> i64 {
447 let ret: i64;
448 unsafe {
450 core::arch::asm!(
451 "svc 0",
452 in("x8") nr,
453 inout("x0") a0 => ret,
454 in("x1") a1,
455 in("x2") a2,
456 in("x3") a3,
457 in("x4") a4,
458 in("x5") a5,
459 options(nostack, preserves_flags),
460 );
461 }
462 ret
463}
464
465#[cfg(target_arch = "x86_64")]
470mod nr {
471 pub const READ: i64 = 0;
472 pub const WRITE: i64 = 1;
473 pub const CLOSE: i64 = 3;
474 pub const LSEEK: i64 = 8;
475 pub const PREAD64: i64 = 17;
476 pub const PWRITE64: i64 = 18;
477 pub const READV: i64 = 19;
478 pub const WRITEV: i64 = 20;
479 pub const PREADV: i64 = 295;
480 pub const PWRITEV: i64 = 296;
481 pub const FCNTL: i64 = 72;
482 pub const FLOCK: i64 = 73;
483 pub const FSYNC: i64 = 74;
484 pub const FDATASYNC: i64 = 75;
485 pub const FCHMOD: i64 = 91;
486 pub const FCHOWN: i64 = 93;
487 pub const SENDFILE: i64 = 40;
488 pub const FTRUNCATE: i64 = 77;
489 pub const GETDENTS64: i64 = 217;
490 pub const STATFS: i64 = 137;
491 pub const FSTATFS: i64 = 138;
492 pub const FALLOCATE: i64 = 285;
493 pub const OPENAT: i64 = 257;
494 pub const MKDIRAT: i64 = 258;
495 pub const MKNODAT: i64 = 259;
496 pub const FCHOWNAT: i64 = 260;
497 pub const UNLINKAT: i64 = 263;
498 pub const LINKAT: i64 = 265;
499 pub const SYMLINKAT: i64 = 266;
500 pub const READLINKAT: i64 = 267;
501 pub const FCHMODAT: i64 = 268;
502 pub const FACCESSAT: i64 = 269;
503 pub const FACCESSAT2: i64 = 439;
504 pub const UTIMENSAT: i64 = 280;
505 pub const COPY_FILE_RANGE: i64 = 326;
506 pub const STATX: i64 = 332;
507 pub const NAME_TO_HANDLE_AT: i64 = 303;
508 pub const OPEN_BY_HANDLE_AT: i64 = 304;
509 pub const SYNC_FILE_RANGE: i64 = 277;
510 pub const RENAMEAT2: i64 = 316;
511 pub const OPENAT2: i64 = 437;
512 pub const INOTIFY_INIT1: i64 = 294;
513 pub const INOTIFY_ADD_WATCH: i64 = 254;
514 pub const INOTIFY_RM_WATCH: i64 = 255;
515}
516
517#[cfg(target_arch = "aarch64")]
518mod nr {
519 pub const READ: i64 = 63;
520 pub const WRITE: i64 = 64;
521 pub const CLOSE: i64 = 57;
522 pub const LSEEK: i64 = 62;
523 pub const PREAD64: i64 = 67;
524 pub const PWRITE64: i64 = 68;
525 pub const READV: i64 = 65;
526 pub const WRITEV: i64 = 66;
527 pub const PREADV: i64 = 69;
528 pub const PWRITEV: i64 = 70;
529 pub const FCNTL: i64 = 25;
530 pub const FLOCK: i64 = 32;
531 pub const FSYNC: i64 = 82;
532 pub const FDATASYNC: i64 = 83;
533 pub const FCHMOD: i64 = 52;
534 pub const FCHOWN: i64 = 55;
535 pub const SENDFILE: i64 = 71;
536 pub const FTRUNCATE: i64 = 46;
537 pub const GETDENTS64: i64 = 61;
538 pub const STATFS: i64 = 43;
539 pub const FSTATFS: i64 = 44;
540 pub const FALLOCATE: i64 = 47;
541 pub const OPENAT: i64 = 56;
542 pub const MKDIRAT: i64 = 34;
543 pub const MKNODAT: i64 = 33;
544 pub const FCHOWNAT: i64 = 54;
545 pub const UNLINKAT: i64 = 35;
546 pub const LINKAT: i64 = 37;
547 pub const SYMLINKAT: i64 = 36;
548 pub const READLINKAT: i64 = 78;
549 pub const FCHMODAT: i64 = 53;
550 pub const FACCESSAT: i64 = 48;
551 pub const FACCESSAT2: i64 = 439;
552 pub const UTIMENSAT: i64 = 88;
553 pub const COPY_FILE_RANGE: i64 = 285;
554 pub const STATX: i64 = 291;
555 pub const NAME_TO_HANDLE_AT: i64 = 264;
556 pub const OPEN_BY_HANDLE_AT: i64 = 265;
557 pub const SYNC_FILE_RANGE: i64 = 84;
558 pub const RENAMEAT2: i64 = 276;
559 pub const OPENAT2: i64 = 437;
560 pub const INOTIFY_INIT1: i64 = 26;
561 pub const INOTIFY_ADD_WATCH: i64 = 27;
562 pub const INOTIFY_RM_WATCH: i64 = 28;
563}
564
565pub fn openat2(dirfd: DirFd<'_>, path: &CStr, how: OpenHow) -> Result<OwnedFd, Errno> {
611 let raw_dirfd = dirfd_to_raw(dirfd);
612 let kernel_how = KernelOpenHow {
613 flags: how.flags.bits() | O_CLOEXEC,
614 mode: u64::from(how.mode),
615 resolve: how.resolve.bits(),
616 };
617 let path_ptr = path.as_ptr() as u64;
618 let how_ptr = &kernel_how as *const KernelOpenHow as u64;
619 let how_size = core::mem::size_of::<KernelOpenHow>();
620
621 let ret = unsafe {
626 syscall4(
627 nr::OPENAT2,
628 fd_to_u64(raw_dirfd),
629 path_ptr,
630 how_ptr,
631 how_size as u64,
632 )
633 };
634
635 if ret < 0 {
636 let err = errno_from_negative_syscall_ret(ret);
637 if err == Errno::ENOSYS {
639 return openat(dirfd, path, how.flags, how.mode);
640 }
641 return Err(err);
642 }
643
644 #[allow(clippy::cast_possible_truncation)]
646 Ok(unsafe { OwnedFd::from_raw_fd(ret as i32) })
647}
648
649pub fn openat(
675 dirfd: DirFd<'_>,
676 path: &CStr,
677 flags: OpenFlags,
678 mode: Mode,
679) -> Result<OwnedFd, Errno> {
680 let raw_dirfd = dirfd_to_raw(dirfd);
681 let raw_flags = flags.bits() | O_CLOEXEC;
682 #[allow(clippy::cast_possible_truncation)]
684 let raw_flags_i32 = raw_flags as i32;
685 let path_ptr = path.as_ptr() as u64;
686
687 let ret = unsafe {
693 syscall4(
694 nr::OPENAT,
695 fd_to_u64(raw_dirfd),
696 path_ptr,
697 i32_to_u64(raw_flags_i32),
698 u64::from(mode),
699 )
700 };
701
702 if ret < 0 {
703 return Err(errno_from_negative_syscall_ret(ret));
704 }
705
706 #[allow(clippy::cast_possible_truncation)]
708 Ok(unsafe { OwnedFd::from_raw_fd(ret as i32) })
709}
710
711pub fn close(fd: OwnedFd) -> Result<(), Errno> {
734 let raw = fd.as_raw_fd();
735 core::mem::forget(fd);
737
738 let ret = unsafe { syscall1(nr::CLOSE, fd_to_u64(raw)) };
742
743 if ret < 0 {
744 return Err(errno_from_negative_syscall_ret(ret));
745 }
746 Ok(())
747}
748
749pub fn read(fd: BorrowedFd<'_>, buffer: &mut [u8]) -> Result<usize, Errno> {
777 let ret = unsafe {
782 syscall3(
783 nr::READ,
784 fd_to_u64(fd.as_raw_fd()),
785 buffer.as_mut_ptr() as u64,
786 buffer.len() as u64,
787 )
788 };
789
790 if ret < 0 {
791 return Err(errno_from_negative_syscall_ret(ret));
792 }
793
794 #[allow(clippy::cast_sign_loss)]
796 ret_to_usize_ok(ret)
797}
798
799pub fn write(fd: BorrowedFd<'_>, buffer: &[u8]) -> Result<usize, Errno> {
823 let ret = unsafe {
827 syscall3(
828 nr::WRITE,
829 fd_to_u64(fd.as_raw_fd()),
830 buffer.as_ptr() as u64,
831 buffer.len() as u64,
832 )
833 };
834
835 if ret < 0 {
836 return Err(errno_from_negative_syscall_ret(ret));
837 }
838
839 #[allow(clippy::cast_sign_loss)]
840 ret_to_usize_ok(ret)
841}
842
843pub fn pread(fd: BorrowedFd<'_>, buffer: &mut [u8], offset: u64) -> Result<usize, Errno> {
861 let ret = unsafe {
863 syscall4(
864 nr::PREAD64,
865 fd_to_u64(fd.as_raw_fd()),
866 buffer.as_mut_ptr() as u64,
867 buffer.len() as u64,
868 offset,
869 )
870 };
871
872 if ret < 0 {
873 return Err(errno_from_negative_syscall_ret(ret));
874 }
875
876 #[allow(clippy::cast_sign_loss)]
877 ret_to_usize_ok(ret)
878}
879
880pub fn pwrite(fd: BorrowedFd<'_>, buffer: &[u8], offset: u64) -> Result<usize, Errno> {
897 let ret = unsafe {
899 syscall4(
900 nr::PWRITE64,
901 fd_to_u64(fd.as_raw_fd()),
902 buffer.as_ptr() as u64,
903 buffer.len() as u64,
904 offset,
905 )
906 };
907
908 if ret < 0 {
909 return Err(errno_from_negative_syscall_ret(ret));
910 }
911
912 #[allow(clippy::cast_sign_loss)]
913 ret_to_usize_ok(ret)
914}
915
916pub fn readv(fd: BorrowedFd<'_>, iov: &mut [IoSliceMut<'_>]) -> Result<usize, Errno> {
937 let ret = unsafe {
941 syscall3(
942 nr::READV,
943 fd_to_u64(fd.as_raw_fd()),
944 iov.as_ptr() as u64,
945 iov.len() as u64,
946 )
947 };
948
949 if ret < 0 {
950 return Err(errno_from_negative_syscall_ret(ret));
951 }
952
953 #[allow(clippy::cast_sign_loss)]
954 ret_to_usize_ok(ret)
955}
956
957pub fn writev(fd: BorrowedFd<'_>, iov: &[IoSlice<'_>]) -> Result<usize, Errno> {
978 let ret = unsafe {
982 syscall3(
983 nr::WRITEV,
984 fd_to_u64(fd.as_raw_fd()),
985 iov.as_ptr() as u64,
986 iov.len() as u64,
987 )
988 };
989
990 if ret < 0 {
991 return Err(errno_from_negative_syscall_ret(ret));
992 }
993
994 #[allow(clippy::cast_sign_loss)]
995 ret_to_usize_ok(ret)
996}
997
998pub fn preadv(fd: BorrowedFd<'_>, iov: &mut [IoSliceMut<'_>], offset: u64) -> Result<usize, Errno> {
1000 let off_lo = offset & 0xFFFF_FFFF;
1006 let off_hi = offset >> 32;
1007
1008 let ret = unsafe {
1013 syscall6(
1014 nr::PREADV,
1015 fd_to_u64(fd.as_raw_fd()),
1016 iov.as_ptr() as u64,
1017 iov.len() as u64,
1018 off_lo,
1019 off_hi,
1020 0,
1021 )
1022 };
1023
1024 if ret < 0 {
1025 return Err(errno_from_negative_syscall_ret(ret));
1026 }
1027
1028 #[allow(clippy::cast_sign_loss)]
1029 ret_to_usize_ok(ret)
1030}
1031
1032pub fn pwritev(fd: BorrowedFd<'_>, iov: &[IoSlice<'_>], offset: u64) -> Result<usize, Errno> {
1034 let off_lo = offset & 0xFFFF_FFFF;
1035 let off_hi = offset >> 32;
1036
1037 let ret = unsafe {
1041 syscall6(
1042 nr::PWRITEV,
1043 fd_to_u64(fd.as_raw_fd()),
1044 iov.as_ptr() as u64,
1045 iov.len() as u64,
1046 off_lo,
1047 off_hi,
1048 0,
1049 )
1050 };
1051
1052 if ret < 0 {
1053 return Err(errno_from_negative_syscall_ret(ret));
1054 }
1055
1056 #[allow(clippy::cast_sign_loss)]
1057 ret_to_usize_ok(ret)
1058}
1059
1060pub fn lseek(fd: BorrowedFd<'_>, offset: i64, whence: SeekWhence) -> Result<u64, Errno> {
1091 let ret = unsafe {
1093 syscall3(
1094 nr::LSEEK,
1095 fd_to_u64(fd.as_raw_fd()),
1096 i64_to_u64(offset),
1097 i32_to_u64(whence as i32),
1098 )
1099 };
1100
1101 if ret < 0 {
1102 return Err(errno_from_negative_syscall_ret(ret));
1103 }
1104
1105 #[allow(clippy::cast_sign_loss)]
1107 #[allow(clippy::cast_sign_loss)]
1108 Ok(ret as u64)
1109}
1110
1111pub fn statx(
1156 dirfd: DirFd<'_>,
1157 path: &CStr,
1158 flags: StatxFlags,
1159 mask: StatxMask,
1160) -> Result<StatxResult, Errno> {
1161 let mut kstatx = KernelStatx {
1162 stx_mask: 0,
1163 stx_blksize: 0,
1164 stx_attributes: 0,
1165 stx_nlink: 0,
1166 stx_uid: 0,
1167 stx_gid: 0,
1168 stx_mode: 0,
1169 __spare0: [0],
1170 stx_ino: 0,
1171 stx_size: 0,
1172 stx_blocks: 0,
1173 stx_attributes_mask: 0,
1174 stx_atime: KernelStatxTimestamp {
1175 tv_sec: 0,
1176 tv_nsec: 0,
1177 __reserved: 0,
1178 },
1179 stx_btime: KernelStatxTimestamp {
1180 tv_sec: 0,
1181 tv_nsec: 0,
1182 __reserved: 0,
1183 },
1184 stx_ctime: KernelStatxTimestamp {
1185 tv_sec: 0,
1186 tv_nsec: 0,
1187 __reserved: 0,
1188 },
1189 stx_mtime: KernelStatxTimestamp {
1190 tv_sec: 0,
1191 tv_nsec: 0,
1192 __reserved: 0,
1193 },
1194 stx_rdev_major: 0,
1195 stx_rdev_minor: 0,
1196 stx_dev_major: 0,
1197 stx_dev_minor: 0,
1198 stx_mnt_id: 0,
1199 __spare2: 0,
1200 __spare3: [0u64; 12],
1201 };
1202
1203 let raw_dirfd = dirfd_to_raw(dirfd);
1204 let path_ptr = path.as_ptr() as u64;
1205 let buf_ptr = &mut kstatx as *mut KernelStatx as u64;
1206
1207 let ret = unsafe {
1212 syscall5(
1213 nr::STATX,
1214 fd_to_u64(raw_dirfd),
1215 path_ptr,
1216 u64::from(flags.bits()),
1217 u64::from(mask.bits()),
1218 buf_ptr,
1219 )
1220 };
1221
1222 if ret < 0 {
1223 return Err(errno_from_negative_syscall_ret(ret));
1224 }
1225
1226 let ts_from = |ts: &KernelStatxTimestamp| StatxTimestamp {
1227 seconds: ts.tv_sec,
1228 nanoseconds: ts.tv_nsec,
1229 };
1230
1231 Ok(StatxResult {
1232 mask: StatxMask::from_bits_truncate(kstatx.stx_mask),
1233 blksize: kstatx.stx_blksize,
1234 attributes: kstatx.stx_attributes,
1235 nlink: kstatx.stx_nlink,
1236 uid: kstatx.stx_uid,
1237 gid: kstatx.stx_gid,
1238 mode: kstatx.stx_mode,
1239 ino: kstatx.stx_ino,
1240 size: kstatx.stx_size,
1241 blocks: kstatx.stx_blocks,
1242 atime: ts_from(&kstatx.stx_atime),
1243 btime: ts_from(&kstatx.stx_btime),
1244 ctime: ts_from(&kstatx.stx_ctime),
1245 mtime: ts_from(&kstatx.stx_mtime),
1246 rdev_major: kstatx.stx_rdev_major,
1247 rdev_minor: kstatx.stx_rdev_minor,
1248 dev_major: kstatx.stx_dev_major,
1249 dev_minor: kstatx.stx_dev_minor,
1250 mount_id: kstatx.stx_mnt_id,
1251 })
1252}
1253
1254pub fn faccessat(
1290 dirfd: DirFd<'_>,
1291 path: &CStr,
1292 mode: AccessMode,
1293 flags: AccessFlags,
1294) -> Result<(), Errno> {
1295 let raw_dirfd = dirfd_to_raw(dirfd);
1296
1297 let ret = if flags.is_empty() {
1298 unsafe {
1304 syscall3(
1305 nr::FACCESSAT,
1306 fd_to_u64(raw_dirfd),
1307 path.as_ptr() as u64,
1308 u64::from(mode.bits()),
1309 )
1310 }
1311 } else {
1312 unsafe {
1317 syscall4(
1318 nr::FACCESSAT2,
1319 fd_to_u64(raw_dirfd),
1320 path.as_ptr() as u64,
1321 u64::from(mode.bits()),
1322 i32_to_u64(flags.bits()),
1323 )
1324 }
1325 };
1326
1327 if ret < 0 {
1328 return Err(errno_from_negative_syscall_ret(ret));
1329 }
1330 Ok(())
1331}
1332
1333pub fn mkdirat(dirfd: DirFd<'_>, path: &CStr, mode: Mode) -> Result<(), Errno> {
1354 let raw_dirfd = dirfd_to_raw(dirfd);
1355
1356 let ret = unsafe {
1358 syscall3(
1359 nr::MKDIRAT,
1360 fd_to_u64(raw_dirfd),
1361 path.as_ptr() as u64,
1362 u64::from(mode),
1363 )
1364 };
1365
1366 if ret < 0 {
1367 return Err(errno_from_negative_syscall_ret(ret));
1368 }
1369 Ok(())
1370}
1371
1372pub fn getdents64(fd: BorrowedFd<'_>, buffer: &mut [u8]) -> Result<usize, Errno> {
1407 let ret = unsafe {
1411 syscall3(
1412 nr::GETDENTS64,
1413 fd_to_u64(fd.as_raw_fd()),
1414 buffer.as_mut_ptr() as u64,
1415 buffer.len() as u64,
1416 )
1417 };
1418
1419 if ret < 0 {
1420 return Err(errno_from_negative_syscall_ret(ret));
1421 }
1422
1423 #[allow(clippy::cast_sign_loss)]
1424 ret_to_usize_ok(ret)
1425}
1426
1427pub fn parse_dirents(buffer: &[u8]) -> impl Iterator<Item = DirEntry> + '_ {
1444 ParseDirents {
1445 buf: buffer,
1446 offset: 0,
1447 }
1448}
1449
1450struct ParseDirents<'a> {
1452 buf: &'a [u8],
1453 offset: usize,
1454}
1455
1456impl<'a> Iterator for ParseDirents<'a> {
1457 type Item = DirEntry;
1458
1459 fn next(&mut self) -> Option<Self::Item> {
1460 if self.offset >= self.buf.len() {
1461 return None;
1462 }
1463
1464 let buffer = self.buf.get(self.offset..)?;
1465
1466 if buffer.len() < 19 {
1468 return None;
1469 }
1470
1471 let ino = u64::from_ne_bytes(buffer.get(0..8)?.try_into().ok()?);
1472 let offset = i64::from_ne_bytes(buffer.get(8..16)?.try_into().ok()?);
1473 let reclen = u16::from_ne_bytes(buffer.get(16..18)?.try_into().ok()?) as usize;
1474 let file_type_byte = *buffer.get(18)?;
1475
1476 if reclen == 0 || reclen > buffer.len() {
1477 return None;
1478 }
1479
1480 let name_region = buffer.get(19..reclen)?;
1482 let nul_pos = name_region
1483 .iter()
1484 .position(|&b| b == 0)
1485 .unwrap_or(name_region.len());
1486 let name_bytes = name_region.get(..nul_pos)?;
1487
1488 let file_type = match file_type_byte {
1489 1 => DirEntryType::Fifo,
1490 2 => DirEntryType::Character,
1491 4 => DirEntryType::Directory,
1492 6 => DirEntryType::Block,
1493 8 => DirEntryType::Regular,
1494 10 => DirEntryType::Symlink,
1495 12 => DirEntryType::Socket,
1496 _ => DirEntryType::Unknown,
1497 };
1498
1499 let name = name_bytes.to_vec();
1502
1503 self.offset = self.offset.checked_add(reclen)?;
1504
1505 Some(DirEntry {
1506 inode: ino,
1507 offset,
1508 file_type,
1509 name,
1510 })
1511 }
1512}
1513
1514pub fn unlinkat(dirfd: DirFd<'_>, path: &CStr, flags: i32) -> Result<(), Errno> {
1534 let raw_dirfd = dirfd_to_raw(dirfd);
1535
1536 let ret = unsafe {
1538 syscall3(
1539 nr::UNLINKAT,
1540 fd_to_u64(raw_dirfd),
1541 path.as_ptr() as u64,
1542 i32_to_u64(flags),
1543 )
1544 };
1545
1546 if ret < 0 {
1547 return Err(errno_from_negative_syscall_ret(ret));
1548 }
1549 Ok(())
1550}
1551
1552pub fn renameat2(
1573 old_dirfd: DirFd<'_>,
1574 old_path: &CStr,
1575 new_dirfd: DirFd<'_>,
1576 new_path: &CStr,
1577 flags: RenameFlags,
1578) -> Result<(), Errno> {
1579 let raw_old = dirfd_to_raw(old_dirfd);
1580 let raw_new = dirfd_to_raw(new_dirfd);
1581
1582 let ret = unsafe {
1584 syscall5(
1585 nr::RENAMEAT2,
1586 fd_to_u64(raw_old),
1587 old_path.as_ptr() as u64,
1588 fd_to_u64(raw_new),
1589 new_path.as_ptr() as u64,
1590 u64::from(flags.bits()),
1591 )
1592 };
1593
1594 if ret < 0 {
1595 return Err(errno_from_negative_syscall_ret(ret));
1596 }
1597 Ok(())
1598}
1599
1600pub fn linkat(
1602 old_dirfd: DirFd<'_>,
1603 old_path: &CStr,
1604 new_dirfd: DirFd<'_>,
1605 new_path: &CStr,
1606 flags: i32,
1607) -> Result<(), Errno> {
1608 let raw_old = dirfd_to_raw(old_dirfd);
1609 let raw_new = dirfd_to_raw(new_dirfd);
1610
1611 let ret = unsafe {
1613 syscall5(
1614 nr::LINKAT,
1615 fd_to_u64(raw_old),
1616 old_path.as_ptr() as u64,
1617 fd_to_u64(raw_new),
1618 new_path.as_ptr() as u64,
1619 i32_to_u64(flags),
1620 )
1621 };
1622
1623 if ret < 0 {
1624 return Err(errno_from_negative_syscall_ret(ret));
1625 }
1626 Ok(())
1627}
1628
1629pub fn symlinkat(target: &CStr, new_dirfd: DirFd<'_>, link_path: &CStr) -> Result<(), Errno> {
1631 let raw_new = dirfd_to_raw(new_dirfd);
1632
1633 let ret = unsafe {
1635 syscall3(
1636 nr::SYMLINKAT,
1637 target.as_ptr() as u64,
1638 fd_to_u64(raw_new),
1639 link_path.as_ptr() as u64,
1640 )
1641 };
1642
1643 if ret < 0 {
1644 return Err(errno_from_negative_syscall_ret(ret));
1645 }
1646 Ok(())
1647}
1648
1649pub fn readlinkat(dirfd: DirFd<'_>, path: &CStr) -> Result<Vec<u8>, Errno> {
1669 let mut buffer = vec![0u8; 4096];
1672 let raw_dirfd = dirfd_to_raw(dirfd);
1673
1674 loop {
1675 let cap = buffer.len();
1676
1677 let ret = unsafe {
1682 syscall4(
1683 nr::READLINKAT,
1684 fd_to_u64(raw_dirfd),
1685 path.as_ptr() as u64,
1686 buffer.as_mut_ptr() as u64,
1687 cap as u64,
1688 )
1689 };
1690
1691 if ret < 0 {
1692 return Err(errno_from_negative_syscall_ret(ret));
1693 }
1694
1695 let n = ret_to_usize(ret);
1696
1697 if n < cap {
1698 buffer.truncate(n);
1700 return Ok(buffer);
1701 }
1702
1703 let new_cap = cap.checked_mul(2).ok_or_else(|| {
1706 Errno::from_nonzero(NonZeroI32::new(libc_enomem()).expect("ENOMEM non-zero"))
1707 })?;
1708 buffer.resize(new_cap, 0);
1709 }
1710}
1711
1712#[inline(always)]
1714fn libc_enomem() -> i32 {
1715 12
1716}
1717
1718pub fn mknodat(dirfd: DirFd<'_>, path: &CStr, mode: Mode, dev: u64) -> Result<(), Errno> {
1720 let raw_dirfd = dirfd_to_raw(dirfd);
1721
1722 let ret = unsafe {
1724 syscall4(
1725 nr::MKNODAT,
1726 fd_to_u64(raw_dirfd),
1727 path.as_ptr() as u64,
1728 u64::from(mode),
1729 dev,
1730 )
1731 };
1732
1733 if ret < 0 {
1734 return Err(errno_from_negative_syscall_ret(ret));
1735 }
1736 Ok(())
1737}
1738
1739pub fn fchmodat(dirfd: DirFd<'_>, path: &CStr, mode: Mode, flags: i32) -> Result<(), Errno> {
1745 let raw_dirfd = dirfd_to_raw(dirfd);
1746
1747 let ret = unsafe {
1749 syscall4(
1750 nr::FCHMODAT,
1751 fd_to_u64(raw_dirfd),
1752 path.as_ptr() as u64,
1753 u64::from(mode),
1754 i32_to_u64(flags),
1755 )
1756 };
1757
1758 if ret < 0 {
1759 return Err(errno_from_negative_syscall_ret(ret));
1760 }
1761 Ok(())
1762}
1763
1764pub fn fchownat(
1766 dirfd: DirFd<'_>,
1767 path: &CStr,
1768 uid: u32,
1769 gid: u32,
1770 flags: i32,
1771) -> Result<(), Errno> {
1772 let raw_dirfd = dirfd_to_raw(dirfd);
1773
1774 let ret = unsafe {
1776 syscall5(
1777 nr::FCHOWNAT,
1778 fd_to_u64(raw_dirfd),
1779 path.as_ptr() as u64,
1780 u64::from(uid),
1781 u64::from(gid),
1782 i32_to_u64(flags),
1783 )
1784 };
1785
1786 if ret < 0 {
1787 return Err(errno_from_negative_syscall_ret(ret));
1788 }
1789 Ok(())
1790}
1791
1792pub fn utimensat(
1809 dirfd: DirFd<'_>,
1810 path: &CStr,
1811 atime: UtimeValue,
1812 mtime: UtimeValue,
1813 flags: i32,
1814) -> Result<(), Errno> {
1815 let raw_dirfd = dirfd_to_raw(dirfd);
1816
1817 let atime_ts = utimevalue_to_kernel(atime);
1819 let mtime_ts = utimevalue_to_kernel(mtime);
1820 let times: [[i64; 2]; 2] = [atime_ts, mtime_ts];
1822
1823 let ret = unsafe {
1828 syscall4(
1829 nr::UTIMENSAT,
1830 fd_to_u64(raw_dirfd),
1831 path.as_ptr() as u64,
1832 times.as_ptr() as u64,
1833 i32_to_u64(flags),
1834 )
1835 };
1836
1837 if ret < 0 {
1838 return Err(errno_from_negative_syscall_ret(ret));
1839 }
1840 Ok(())
1841}
1842
1843pub fn fchmod(fd: BorrowedFd<'_>, mode: Mode) -> Result<(), Errno> {
1847 let ret = unsafe { syscall2(nr::FCHMOD, fd_to_u64(fd.as_raw_fd()), u64::from(mode)) };
1849
1850 if ret < 0 {
1851 return Err(errno_from_negative_syscall_ret(ret));
1852 }
1853 Ok(())
1854}
1855
1856pub fn fchown(fd: BorrowedFd<'_>, uid: u32, gid: u32) -> Result<(), Errno> {
1862 let ret = unsafe {
1864 syscall3(
1865 nr::FCHOWN,
1866 fd_to_u64(fd.as_raw_fd()),
1867 u64::from(uid),
1868 u64::from(gid),
1869 )
1870 };
1871
1872 if ret < 0 {
1873 return Err(errno_from_negative_syscall_ret(ret));
1874 }
1875 Ok(())
1876}
1877
1878pub fn futimens(fd: BorrowedFd<'_>, atime: UtimeValue, mtime: UtimeValue) -> Result<(), Errno> {
1885 let times: [[i64; 2]; 2] = [utimevalue_to_kernel(atime), utimevalue_to_kernel(mtime)];
1887
1888 let ret = unsafe {
1894 syscall4(
1895 nr::UTIMENSAT,
1896 fd_to_u64(fd.as_raw_fd()),
1897 0, times.as_ptr() as u64,
1899 0, )
1901 };
1902
1903 if ret < 0 {
1904 return Err(errno_from_negative_syscall_ret(ret));
1905 }
1906 Ok(())
1907}
1908
1909pub fn sendfile(
1918 out_fd: BorrowedFd<'_>,
1919 in_fd: BorrowedFd<'_>,
1920 offset: Option<&mut u64>,
1921 count: usize,
1922) -> Result<usize, Errno> {
1923 let offset_ptr = match offset {
1926 Some(value) => core::ptr::from_mut(value) as u64,
1927 None => 0,
1928 };
1929 let ret = unsafe {
1934 syscall4(
1935 nr::SENDFILE,
1936 fd_to_u64(out_fd.as_raw_fd()),
1937 fd_to_u64(in_fd.as_raw_fd()),
1938 offset_ptr,
1939 count as u64,
1940 )
1941 };
1942
1943 if ret < 0 {
1944 return Err(errno_from_negative_syscall_ret(ret));
1945 }
1946 #[allow(clippy::cast_sign_loss)]
1948 ret_to_usize_ok(ret)
1949}
1950
1951fn utimevalue_to_kernel(v: UtimeValue) -> [i64; 2] {
1952 match v {
1953 UtimeValue::Now => [0, UTIME_NOW],
1954 UtimeValue::Omit => [0, UTIME_OMIT],
1955 UtimeValue::Time(ts) => [ts.seconds, i64::from(ts.nanoseconds)],
1956 }
1957}
1958
1959pub fn ftruncate(fd: BorrowedFd<'_>, length: i64) -> Result<(), Errno> {
1981 let ret = unsafe { syscall2(nr::FTRUNCATE, fd_to_u64(fd.as_raw_fd()), i64_to_u64(length)) };
1983
1984 if ret < 0 {
1985 return Err(errno_from_negative_syscall_ret(ret));
1986 }
1987 Ok(())
1988}
1989
1990pub fn truncate(dirfd: DirFd<'_>, path: &CStr, length: i64) -> Result<(), Errno> {
1995 use air_sys_types::fs::OpenFlags;
1996 let fd = openat(dirfd, path, OpenFlags::WRONLY, 0)?;
1997 let borrowed = unsafe { BorrowedFd::borrow_raw(fd.as_raw_fd()) };
1999 let result = ftruncate(borrowed, length);
2000 drop(fd);
2001 result
2002}
2003
2004pub fn fsync(fd: BorrowedFd<'_>) -> Result<(), Errno> {
2021 let ret = unsafe { syscall1(nr::FSYNC, fd_to_u64(fd.as_raw_fd())) };
2023
2024 if ret < 0 {
2025 return Err(errno_from_negative_syscall_ret(ret));
2026 }
2027 Ok(())
2028}
2029
2030pub fn fdatasync(fd: BorrowedFd<'_>) -> Result<(), Errno> {
2032 let ret = unsafe { syscall1(nr::FDATASYNC, fd_to_u64(fd.as_raw_fd())) };
2034
2035 if ret < 0 {
2036 return Err(errno_from_negative_syscall_ret(ret));
2037 }
2038 Ok(())
2039}
2040
2041pub fn fallocate(
2061 fd: BorrowedFd<'_>,
2062 mode: FallocateMode,
2063 offset: i64,
2064 length: i64,
2065) -> Result<(), Errno> {
2066 let ret = unsafe {
2068 syscall4(
2069 nr::FALLOCATE,
2070 fd_to_u64(fd.as_raw_fd()),
2071 i32_to_u64(mode.bits()),
2072 i64_to_u64(offset),
2073 i64_to_u64(length),
2074 )
2075 };
2076
2077 if ret < 0 {
2078 return Err(errno_from_negative_syscall_ret(ret));
2079 }
2080 Ok(())
2081}
2082
2083pub fn copy_file_range(
2104 fd_in: BorrowedFd<'_>,
2105 offset_in: Option<&mut u64>,
2106 fd_out: BorrowedFd<'_>,
2107 offset_out: Option<&mut u64>,
2108 length: usize,
2109 flags: u32,
2110) -> Result<usize, Errno> {
2111 let off_in_ptr: u64 = match offset_in {
2112 Some(p) => p as *mut u64 as u64,
2113 None => 0,
2114 };
2115 let off_out_ptr: u64 = match offset_out {
2116 Some(p) => p as *mut u64 as u64,
2117 None => 0,
2118 };
2119
2120 let ret = unsafe {
2125 syscall6(
2126 nr::COPY_FILE_RANGE,
2127 fd_to_u64(fd_in.as_raw_fd()),
2128 off_in_ptr,
2129 fd_to_u64(fd_out.as_raw_fd()),
2130 off_out_ptr,
2131 length as u64,
2132 u64::from(flags),
2133 )
2134 };
2135
2136 if ret < 0 {
2137 return Err(errno_from_negative_syscall_ret(ret));
2138 }
2139
2140 #[allow(clippy::cast_sign_loss)]
2141 ret_to_usize_ok(ret)
2142}
2143
2144pub fn flock(fd: BorrowedFd<'_>, operation: i32) -> Result<(), Errno> {
2146 let ret = unsafe { syscall2(nr::FLOCK, fd_to_u64(fd.as_raw_fd()), i32_to_u64(operation)) };
2148
2149 if ret < 0 {
2150 return Err(errno_from_negative_syscall_ret(ret));
2151 }
2152 Ok(())
2153}
2154
2155pub fn statfs(path: &CStr) -> Result<StatFsResult, Errno> {
2157 let mut kfs = KernelStatfs {
2158 f_type: 0,
2159 f_bsize: 0,
2160 f_blocks: 0,
2161 f_bfree: 0,
2162 f_bavail: 0,
2163 f_files: 0,
2164 f_ffree: 0,
2165 f_fsid: [0; 2],
2166 f_namelen: 0,
2167 f_frsize: 0,
2168 f_flags: 0,
2169 f_spare: [0; 4],
2170 };
2171
2172 let ret = unsafe {
2176 syscall2(
2177 nr::STATFS,
2178 path.as_ptr() as u64,
2179 &mut kfs as *mut KernelStatfs as u64,
2180 )
2181 };
2182
2183 if ret < 0 {
2184 return Err(errno_from_negative_syscall_ret(ret));
2185 }
2186
2187 Ok(kernel_statfs_to_result(&kfs))
2188}
2189
2190pub fn fstatfs(fd: BorrowedFd<'_>) -> Result<StatFsResult, Errno> {
2192 let mut kfs = KernelStatfs {
2193 f_type: 0,
2194 f_bsize: 0,
2195 f_blocks: 0,
2196 f_bfree: 0,
2197 f_bavail: 0,
2198 f_files: 0,
2199 f_ffree: 0,
2200 f_fsid: [0; 2],
2201 f_namelen: 0,
2202 f_frsize: 0,
2203 f_flags: 0,
2204 f_spare: [0; 4],
2205 };
2206
2207 let ret = unsafe {
2211 syscall2(
2212 nr::FSTATFS,
2213 fd_to_u64(fd.as_raw_fd()),
2214 &mut kfs as *mut KernelStatfs as u64,
2215 )
2216 };
2217
2218 if ret < 0 {
2219 return Err(errno_from_negative_syscall_ret(ret));
2220 }
2221
2222 Ok(kernel_statfs_to_result(&kfs))
2223}
2224
2225fn kernel_statfs_to_result(kfs: &KernelStatfs) -> StatFsResult {
2226 StatFsResult {
2227 f_type: FsType(kfs.f_type),
2228 f_bsize: kfs.f_bsize,
2229 f_frsize: kfs.f_frsize,
2230 f_blocks: kfs.f_blocks,
2231 f_bfree: kfs.f_bfree,
2232 f_bavail: kfs.f_bavail,
2233 f_files: kfs.f_files,
2234 f_ffree: kfs.f_ffree,
2235 f_fsid: kfs.f_fsid,
2236 f_namelen: kfs.f_namelen,
2237 f_flags: kfs.f_flags,
2238 }
2239}
2240
2241pub fn sync_file_range(
2250 fd: BorrowedFd<'_>,
2251 offset: i64,
2252 nbytes: u64,
2253 flags: u32,
2254) -> Result<(), Errno> {
2255 let ret = unsafe {
2257 syscall4(
2258 nr::SYNC_FILE_RANGE,
2259 fd_to_u64(fd.as_raw_fd()),
2260 i64_to_u64(offset),
2261 nbytes,
2262 u64::from(flags),
2263 )
2264 };
2265
2266 if ret < 0 {
2267 return Err(errno_from_negative_syscall_ret(ret));
2268 }
2269 Ok(())
2270}
2271
2272pub fn dup_fd(fd: BorrowedFd<'_>, min_fd: RawFd) -> Result<OwnedFd, Errno> {
2294 let ret = unsafe {
2296 syscall3(
2297 nr::FCNTL,
2298 fd_to_u64(fd.as_raw_fd()),
2299 i32_to_u64(F_DUPFD_CLOEXEC),
2300 fd_to_u64(min_fd),
2301 )
2302 };
2303
2304 if ret < 0 {
2305 return Err(errno_from_negative_syscall_ret(ret));
2306 }
2307
2308 #[allow(clippy::cast_possible_truncation)]
2310 Ok(unsafe { OwnedFd::from_raw_fd(ret as i32) })
2311}
2312
2313pub fn get_fd_flags(fd: BorrowedFd<'_>) -> Result<FdFlags, Errno> {
2315 let ret = unsafe { syscall3(nr::FCNTL, fd_to_u64(fd.as_raw_fd()), i32_to_u64(F_GETFD), 0) };
2317
2318 if ret < 0 {
2319 return Err(errno_from_negative_syscall_ret(ret));
2320 }
2321
2322 #[allow(clippy::cast_possible_truncation)]
2323 Ok(FdFlags::from_bits_truncate(ret as i32))
2324}
2325
2326pub fn set_fd_flags(fd: BorrowedFd<'_>, flags: FdFlags) -> Result<(), Errno> {
2328 let ret = unsafe {
2330 syscall3(
2331 nr::FCNTL,
2332 fd_to_u64(fd.as_raw_fd()),
2333 i32_to_u64(F_SETFD),
2334 i32_to_u64(flags.bits()),
2335 )
2336 };
2337
2338 if ret < 0 {
2339 return Err(errno_from_negative_syscall_ret(ret));
2340 }
2341 Ok(())
2342}
2343
2344pub fn get_status_flags(fd: BorrowedFd<'_>) -> Result<StatusFlags, Errno> {
2346 let ret = unsafe { syscall3(nr::FCNTL, fd_to_u64(fd.as_raw_fd()), i32_to_u64(F_GETFL), 0) };
2348
2349 if ret < 0 {
2350 return Err(errno_from_negative_syscall_ret(ret));
2351 }
2352
2353 #[allow(clippy::cast_possible_truncation)]
2354 Ok(StatusFlags::from_bits_truncate(ret as i32))
2355}
2356
2357pub fn set_status_flags(fd: BorrowedFd<'_>, flags: StatusFlags) -> Result<(), Errno> {
2359 let ret = unsafe {
2361 syscall3(
2362 nr::FCNTL,
2363 fd_to_u64(fd.as_raw_fd()),
2364 i32_to_u64(F_SETFL),
2365 i32_to_u64(flags.bits()),
2366 )
2367 };
2368
2369 if ret < 0 {
2370 return Err(errno_from_negative_syscall_ret(ret));
2371 }
2372 Ok(())
2373}
2374
2375pub fn set_pipe_size(fd: BorrowedFd<'_>, size: usize) -> Result<usize, Errno> {
2379 let ret = unsafe {
2381 syscall3(
2382 nr::FCNTL,
2383 fd_to_u64(fd.as_raw_fd()),
2384 i32_to_u64(F_SETPIPE_SZ),
2385 size as u64,
2386 )
2387 };
2388
2389 if ret < 0 {
2390 return Err(errno_from_negative_syscall_ret(ret));
2391 }
2392
2393 #[allow(clippy::cast_sign_loss)]
2394 ret_to_usize_ok(ret)
2395}
2396
2397pub fn get_pipe_size(fd: BorrowedFd<'_>) -> Result<usize, Errno> {
2399 let ret = unsafe {
2401 syscall3(
2402 nr::FCNTL,
2403 fd_to_u64(fd.as_raw_fd()),
2404 i32_to_u64(F_GETPIPE_SZ),
2405 0,
2406 )
2407 };
2408
2409 if ret < 0 {
2410 return Err(errno_from_negative_syscall_ret(ret));
2411 }
2412
2413 #[allow(clippy::cast_sign_loss)]
2414 ret_to_usize_ok(ret)
2415}
2416
2417pub fn try_lock(fd: BorrowedFd<'_>, lock: &FileLock) -> Result<bool, Errno> {
2421 let kflock = file_lock_to_kernel(lock);
2422
2423 let ret = unsafe {
2427 syscall3(
2428 nr::FCNTL,
2429 fd_to_u64(fd.as_raw_fd()),
2430 i32_to_u64(F_SETLK),
2431 &kflock as *const KernelFlock as u64,
2432 )
2433 };
2434
2435 if ret < 0 {
2436 let err = errno_from_negative_syscall_ret(ret);
2437 if err == Errno::EACCES || err == Errno::EAGAIN {
2439 return Ok(false);
2440 }
2441 return Err(err);
2442 }
2443 Ok(true)
2444}
2445
2446pub fn lock(fd: BorrowedFd<'_>, lock: &FileLock) -> Result<(), Errno> {
2453 let kflock = file_lock_to_kernel(lock);
2454
2455 let ret = unsafe {
2459 syscall3(
2460 nr::FCNTL,
2461 fd_to_u64(fd.as_raw_fd()),
2462 i32_to_u64(F_SETLKW),
2463 &kflock as *const KernelFlock as u64,
2464 )
2465 };
2466
2467 if ret < 0 {
2468 return Err(errno_from_negative_syscall_ret(ret));
2469 }
2470 Ok(())
2471}
2472
2473fn file_lock_to_kernel(lock: &FileLock) -> KernelFlock {
2474 KernelFlock {
2475 l_type: lock.lock_type as i16,
2476 l_whence: lock.whence as i16,
2477 _pad: 0,
2478 l_start: lock.start,
2479 l_len: lock.length,
2480 l_pid: lock.pid,
2481 _pad2: 0,
2482 }
2483}
2484
2485pub fn add_seals(fd: BorrowedFd<'_>, seals: Seals) -> Result<(), Errno> {
2487 let ret = unsafe {
2489 syscall3(
2490 nr::FCNTL,
2491 fd_to_u64(fd.as_raw_fd()),
2492 i32_to_u64(F_ADD_SEALS),
2493 u64::from(seals.bits()),
2494 )
2495 };
2496
2497 if ret < 0 {
2498 return Err(errno_from_negative_syscall_ret(ret));
2499 }
2500 Ok(())
2501}
2502
2503pub fn get_seals(fd: BorrowedFd<'_>) -> Result<Seals, Errno> {
2505 let ret = unsafe {
2507 syscall3(
2508 nr::FCNTL,
2509 fd_to_u64(fd.as_raw_fd()),
2510 i32_to_u64(F_GET_SEALS),
2511 0,
2512 )
2513 };
2514
2515 if ret < 0 {
2516 return Err(errno_from_negative_syscall_ret(ret));
2517 }
2518
2519 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
2520 Ok(Seals::from_bits_truncate(ret as u32))
2521}
2522
2523pub fn name_to_handle_at(
2547 dirfd: DirFd<'_>,
2548 path: &CStr,
2549 flags: NameToHandleFlags,
2550) -> Result<FileHandle, Errno> {
2551 let raw_dirfd = dirfd_to_raw(dirfd);
2552 let mut khandle = KernelFileHandle {
2553 handle_bytes: 128,
2554 handle_type: 0,
2555 f_handle: [0u8; 128],
2556 };
2557 let mut mount_id: i32 = 0;
2558
2559 let ret = unsafe {
2565 syscall5(
2566 nr::NAME_TO_HANDLE_AT,
2567 fd_to_u64(raw_dirfd),
2568 path.as_ptr() as u64,
2569 &mut khandle as *mut KernelFileHandle as u64,
2570 &mut mount_id as *mut i32 as u64,
2571 i32_to_u64(flags.bits()),
2572 )
2573 };
2574
2575 if ret < 0 {
2576 return Err(errno_from_negative_syscall_ret(ret));
2577 }
2578
2579 #[allow(clippy::cast_lossless)]
2581 let nbytes = khandle.handle_bytes as usize;
2582 let data = khandle
2583 .f_handle
2584 .get(..nbytes.min(128))
2585 .unwrap_or(&khandle.f_handle[..])
2586 .to_vec();
2587
2588 Ok(FileHandle {
2589 handle_type: khandle.handle_type,
2590 handle_bytes: data,
2591 mount_id,
2592 })
2593}
2594
2595pub fn open_by_handle_at(
2621 mount_fd: BorrowedFd<'_>,
2622 handle: &FileHandle,
2623 flags: OpenFlags,
2624) -> Result<OwnedFd, Errno> {
2625 let nbytes = handle.handle_bytes.len().min(128);
2627 #[allow(clippy::cast_possible_truncation)]
2629 let nbytes_u32 = nbytes as u32;
2630 let mut khandle = KernelFileHandle {
2631 handle_bytes: nbytes_u32,
2632 handle_type: handle.handle_type,
2633 f_handle: [0u8; 128],
2634 };
2635 khandle.f_handle[..nbytes].copy_from_slice(&handle.handle_bytes[..nbytes]);
2637
2638 let raw_flags = flags.bits() | O_CLOEXEC;
2639 #[allow(clippy::cast_possible_truncation)]
2640 let raw_flags_i32 = raw_flags as i32;
2641
2642 let ret = unsafe {
2647 syscall3(
2648 nr::OPEN_BY_HANDLE_AT,
2649 fd_to_u64(mount_fd.as_raw_fd()),
2650 &khandle as *const KernelFileHandle as u64,
2651 i32_to_u64(raw_flags_i32),
2652 )
2653 };
2654
2655 if ret < 0 {
2656 return Err(errno_from_negative_syscall_ret(ret));
2657 }
2658
2659 #[allow(clippy::cast_possible_truncation)]
2661 Ok(unsafe { OwnedFd::from_raw_fd(ret as i32) })
2662}
2663
2664#[cfg(all(test, target_os = "linux"))]
2669mod tests {
2670 use super::*;
2671 use air_sys_types::fs::{
2672 AccessFlags, AccessMode, DirFd, FallocateMode, FdFlags, FileLock, FsType, LockType,
2673 NameToHandleFlags, OpenFlags, RenameFlags, Seals, SeekWhence, StatusFlags, StatxFlags,
2674 StatxMask, StatxTimestamp, UtimeValue,
2675 };
2676
2677 const ENOTDIR: Errno = Errno::try_from_raw(20).unwrap();
2680 const EOPNOTSUPP: Errno = Errno::try_from_raw(95).unwrap();
2681
2682 fn invalid_fd() -> BorrowedFd<'static> {
2688 unsafe { BorrowedFd::borrow_raw(i32::MAX) }
2691 }
2692 use air_sys_types::fd::AsFd;
2693
2694 fn open_memfd() -> OwnedFd {
2698 #[cfg(target_arch = "x86_64")]
2700 let nr_memfd: i64 = 319;
2701 #[cfg(target_arch = "aarch64")]
2702 let nr_memfd: i64 = 279;
2703
2704 let name = c"test";
2705 let ret = unsafe { syscall2(nr_memfd, name.as_ptr() as u64, 1) };
2708 assert!(ret >= 0, "memfd_create failed: {ret}");
2709 #[allow(clippy::cast_possible_truncation)]
2710 unsafe {
2711 OwnedFd::from_raw_fd(ret as i32)
2712 }
2713 }
2714
2715 #[test]
2718 fn openat_opens_existing_file() {
2719 let fd = openat(DirFd::Cwd, c"/proc/self/status", OpenFlags::RDONLY, 0);
2720 assert!(fd.is_ok(), "openat /proc/self/status: {:?}", fd);
2721 }
2722
2723 #[test]
2724 fn openat_enoent_on_missing() {
2725 let result = openat(
2726 DirFd::Cwd,
2727 c"/nonexistent_air_test_file",
2728 OpenFlags::RDONLY,
2729 0,
2730 );
2731 assert_eq!(result.unwrap_err(), Errno::ENOENT);
2732 }
2733
2734 #[test]
2735 fn openat2_opens_existing_file() {
2736 use air_sys_types::fs::{OpenHow, ResolveFlags};
2737 let how = OpenHow {
2738 flags: OpenFlags::RDONLY,
2739 mode: 0,
2740 resolve: ResolveFlags::empty(),
2741 };
2742 let fd = openat2(DirFd::Cwd, c"/proc/self/status", how);
2743 assert!(fd.is_ok(), "openat2: {:?}", fd);
2744 }
2745
2746 #[test]
2749 fn read_write_roundtrip() {
2750 let fd = open_memfd();
2751 let data = b"hello air";
2752 let n = write(fd.as_fd(), data).expect("write");
2753 assert_eq!(n, data.len());
2754
2755 lseek(fd.as_fd(), 0, SeekWhence::Set).expect("lseek");
2756
2757 let mut buffer = [0u8; 64];
2758 let n2 = read(fd.as_fd(), &mut buffer).expect("read");
2759 assert_eq!(n2, data.len());
2760 assert_eq!(&buffer[..n2], data);
2761 }
2762
2763 #[test]
2766 fn pread_pwrite_roundtrip() {
2767 let fd = open_memfd();
2768 ftruncate(fd.as_fd(), 32).expect("ftruncate");
2769
2770 let n = pwrite(fd.as_fd(), b"abcd", 4).expect("pwrite");
2771 assert_eq!(n, 4);
2772
2773 let mut buffer = [0u8; 4];
2774 let n2 = pread(fd.as_fd(), &mut buffer, 4).expect("pread");
2775 assert_eq!(n2, 4);
2776 assert_eq!(&buffer, b"abcd");
2777 }
2778
2779 #[test]
2782 fn lseek_set_and_end() {
2783 let fd = open_memfd();
2784 write(fd.as_fd(), b"0123456789").expect("write");
2785
2786 let pos = lseek(fd.as_fd(), 0, SeekWhence::End).expect("lseek end");
2787 assert_eq!(pos, 10);
2788
2789 let pos2 = lseek(fd.as_fd(), 3, SeekWhence::Set).expect("lseek set");
2790 assert_eq!(pos2, 3);
2791 }
2792
2793 #[test]
2796 fn statx_on_tmp() {
2797 let result =
2798 statx(DirFd::Cwd, c"/tmp", StatxFlags::empty(), StatxMask::ALL).expect("statx /tmp");
2799 assert!(result.mask.contains(StatxMask::TYPE));
2800 }
2801
2802 #[test]
2805 fn mkdirat_unlinkat_roundtrip() {
2806 const AT_REMOVEDIR: i32 = 0x200;
2808 let dir = c"/tmp/air_fs_test_dir_mkdirat";
2809 let _ = unlinkat(DirFd::Cwd, dir, AT_REMOVEDIR); mkdirat(DirFd::Cwd, dir, 0o700).expect("mkdirat");
2811 unlinkat(DirFd::Cwd, dir, AT_REMOVEDIR).expect("unlinkat rmdir");
2812 }
2813
2814 #[test]
2817 fn getdents64_lists_entries() {
2818 let fd = openat(
2819 DirFd::Cwd,
2820 c"/tmp",
2821 OpenFlags::RDONLY | OpenFlags::DIRECTORY,
2822 0,
2823 )
2824 .expect("openat /tmp");
2825
2826 let mut buffer = vec![0u8; 4096];
2827 let n = getdents64(fd.as_fd(), &mut buffer).expect("getdents64");
2828 assert!(n > 0, "getdents64 returned 0 bytes");
2829
2830 let entries: Vec<_> = parse_dirents(&buffer[..n]).collect();
2831 assert!(!entries.is_empty(), "pas d'entrées dans /tmp");
2832
2833 let has_dot = entries.iter().any(|e| e.name == b".");
2835 let has_dotdot = entries.iter().any(|e| e.name == b"..");
2836 assert!(has_dot, ". absent");
2837 assert!(has_dotdot, ".. absent");
2838 }
2839
2840 #[test]
2843 fn ftruncate_sets_size() {
2844 let fd = open_memfd();
2845 ftruncate(fd.as_fd(), 1024).expect("ftruncate");
2846
2847 let pos = lseek(fd.as_fd(), 0, SeekWhence::End).expect("lseek end");
2848 assert_eq!(pos, 1024);
2849 }
2850
2851 #[test]
2854 fn fsync_on_memfd() {
2855 let fd = open_memfd();
2856 write(fd.as_fd(), b"data").expect("write");
2857 fsync(fd.as_fd()).expect("fsync");
2859 }
2860
2861 #[test]
2864 fn statfs_root() {
2865 let result = statfs(c"/").expect("statfs /");
2866 assert!(result.f_bsize > 0, "f_bsize nul");
2867 assert!(result.f_blocks > 0, "f_blocks nul");
2868 }
2869
2870 #[test]
2873 fn fstatfs_on_proc_fd() {
2874 let fd = openat(DirFd::Cwd, c"/proc/self/status", OpenFlags::RDONLY, 0).expect("openat");
2875 let result = fstatfs(fd.as_fd()).expect("fstatfs");
2876 assert_eq!(result.f_type, FsType::PROC);
2878 }
2879
2880 #[test]
2883 fn copy_file_range_between_memfds() {
2884 let src = open_memfd();
2885 write(src.as_fd(), b"hello world").expect("write src");
2886 lseek(src.as_fd(), 0, SeekWhence::Set).expect("lseek src");
2887
2888 let dst = open_memfd();
2889
2890 let n =
2891 copy_file_range(src.as_fd(), None, dst.as_fd(), None, 11, 0).expect("copy_file_range");
2892 assert_eq!(n, 11);
2893
2894 lseek(dst.as_fd(), 0, SeekWhence::Set).expect("lseek dst");
2895 let mut buffer = [0u8; 16];
2896 let n2 = read(dst.as_fd(), &mut buffer).expect("read dst");
2897 assert_eq!(&buffer[..n2], b"hello world");
2898 }
2899
2900 #[test]
2903 fn dup_fd_creates_new_fd() {
2904 let fd = open_memfd();
2905 let fd2 = dup_fd(fd.as_fd(), 0).expect("dup_fd");
2906 assert_ne!(fd.as_raw_fd(), fd2.as_raw_fd());
2908 write(fd.as_fd(), b"dup").expect("write original");
2909 lseek(fd2.as_fd(), 0, SeekWhence::Set).expect("lseek dup");
2910 let mut buffer = [0u8; 8];
2911 let n = read(fd2.as_fd(), &mut buffer).expect("read dup");
2912 assert_eq!(&buffer[..n], b"dup");
2913 }
2914
2915 fn make_pipe() -> (OwnedFd, OwnedFd) {
2918 let mut fds = [0i32; 2];
2919 #[cfg(target_arch = "x86_64")]
2920 let nr_pipe2: i64 = 293;
2921 #[cfg(target_arch = "aarch64")]
2922 let nr_pipe2: i64 = 59;
2923 let ret = unsafe { syscall2(nr_pipe2, fds.as_mut_ptr() as u64, 0o2_000_000) };
2925 assert!(ret >= 0, "pipe2 failed: {ret}");
2926 let r = unsafe { OwnedFd::from_raw_fd(fds[0]) };
2928 let w = unsafe { OwnedFd::from_raw_fd(fds[1]) };
2929 (r, w)
2930 }
2931
2932 #[test]
2935 fn dirfd_fd_branch_enoent() {
2936 let dir = openat(
2937 DirFd::Cwd,
2938 c"/tmp",
2939 OpenFlags::RDONLY | OpenFlags::DIRECTORY,
2940 0,
2941 )
2942 .expect("openat /tmp");
2943 let result = openat(
2944 DirFd::Fd(dir.as_fd()),
2945 c"air_test_no_such_file_dir_fd_42xy",
2946 OpenFlags::RDONLY,
2947 0,
2948 );
2949 assert_eq!(result.unwrap_err(), Errno::ENOENT);
2950 }
2951
2952 #[test]
2955 fn close_valid_fd_succeeds() {
2956 let fd = open_memfd();
2957 close(fd).expect("close");
2958 }
2959
2960 #[test]
2963 fn read_ebadf_on_invalid_fd() {
2964 let invalid = invalid_fd();
2965 let mut buffer = [0u8; 4];
2966 assert_eq!(read(invalid, &mut buffer).unwrap_err(), Errno::EBADF);
2967 }
2968
2969 #[test]
2970 fn write_ebadf_on_invalid_fd() {
2971 let invalid = invalid_fd();
2972 assert_eq!(write(invalid, b"data").unwrap_err(), Errno::EBADF);
2973 }
2974
2975 #[test]
2978 fn pread_ebadf_on_invalid_fd() {
2979 let invalid = invalid_fd();
2980 let mut buffer = [0u8; 4];
2981 assert_eq!(pread(invalid, &mut buffer, 0).unwrap_err(), Errno::EBADF);
2982 }
2983
2984 #[test]
2985 fn pwrite_ebadf_on_invalid_fd() {
2986 let invalid = invalid_fd();
2987 assert_eq!(pwrite(invalid, b"data", 0).unwrap_err(), Errno::EBADF);
2988 }
2989
2990 #[test]
2991 fn preadv_ebadf_on_invalid_fd() {
2992 use air_sys_types::net::IoSliceMut;
2993 let invalid = invalid_fd();
2994 let mut buffer = [0u8; 4];
2995 let mut iov = [IoSliceMut::new(&mut buffer)];
2996 assert_eq!(preadv(invalid, &mut iov, 0).unwrap_err(), Errno::EBADF);
2997 }
2998
2999 #[test]
3000 fn pwritev_ebadf_on_invalid_fd() {
3001 use air_sys_types::net::IoSlice;
3002 let invalid = invalid_fd();
3003 let data = *b"data";
3004 let iov = [IoSlice::new(&data)];
3005 assert_eq!(pwritev(invalid, &iov, 0).unwrap_err(), Errno::EBADF);
3006 }
3007
3008 #[test]
3009 fn fallocate_ebadf_on_invalid_fd() {
3010 let invalid = invalid_fd();
3011 let err = fallocate(invalid, FallocateMode::empty(), 0, 4).unwrap_err();
3012 assert_eq!(err, Errno::EBADF);
3013 }
3014
3015 #[test]
3016 fn copy_file_range_ebadf_on_invalid_fd() {
3017 let invalid = invalid_fd();
3018 let err = copy_file_range(invalid, None, invalid, None, 4, 0).unwrap_err();
3019 assert_eq!(err, Errno::EBADF);
3020 }
3021
3022 #[test]
3023 fn sync_file_range_ebadf_on_invalid_fd() {
3024 let invalid = invalid_fd();
3025 let err = sync_file_range(invalid, 0, 0, 0).unwrap_err();
3026 assert_eq!(err, Errno::EBADF);
3027 }
3028
3029 #[test]
3030 fn symlinkat_eexist_when_link_path_already_exists() {
3031 let err = symlinkat(c"target", DirFd::Cwd, c".").unwrap_err();
3034 assert_eq!(err, Errno::EEXIST);
3035 }
3036
3037 #[test]
3040 fn readv_writev_roundtrip() {
3041 use air_sys_types::net::{IoSlice, IoSliceMut};
3042 let fd = open_memfd();
3043 let a = b"hello";
3044 let b_data = b" world";
3045 let iov_w = [IoSlice::new(a), IoSlice::new(b_data)];
3046 let n = writev(fd.as_fd(), &iov_w).expect("writev");
3047 assert_eq!(n, 11);
3048 lseek(fd.as_fd(), 0, SeekWhence::Set).expect("lseek");
3049 let mut buf1 = [0u8; 5];
3050 let mut buf2 = [0u8; 6];
3051 let mut iov_r = [IoSliceMut::new(&mut buf1), IoSliceMut::new(&mut buf2)];
3052 let n2 = readv(fd.as_fd(), &mut iov_r).expect("readv");
3053 assert_eq!(n2, 11);
3054 assert_eq!(&buf1, b"hello");
3055 assert_eq!(&buf2, b" world");
3056 }
3057
3058 #[test]
3059 fn readv_ebadf() {
3060 use air_sys_types::net::IoSliceMut;
3061 let invalid = invalid_fd();
3062 let mut buffer = [0u8; 4];
3063 let mut iov = [IoSliceMut::new(&mut buffer)];
3064 assert_eq!(readv(invalid, &mut iov).unwrap_err(), Errno::EBADF);
3065 }
3066
3067 #[test]
3068 fn writev_ebadf() {
3069 use air_sys_types::net::IoSlice;
3070 let invalid = invalid_fd();
3071 let iov = [IoSlice::new(b"data")];
3072 assert_eq!(writev(invalid, &iov).unwrap_err(), Errno::EBADF);
3073 }
3074
3075 #[test]
3078 fn preadv_pwritev_roundtrip() {
3079 use air_sys_types::net::{IoSlice, IoSliceMut};
3080 let fd = open_memfd();
3081 ftruncate(fd.as_fd(), 16).expect("ftruncate");
3082 let data = b"abcdef";
3083 let iov_w = [IoSlice::new(data)];
3084 let n = pwritev(fd.as_fd(), &iov_w, 2).expect("pwritev");
3085 assert_eq!(n, 6);
3086 let mut out = [0u8; 6];
3087 let mut iov_r = [IoSliceMut::new(&mut out)];
3088 let n2 = preadv(fd.as_fd(), &mut iov_r, 2).expect("preadv");
3089 assert_eq!(n2, 6);
3090 assert_eq!(&out, b"abcdef");
3091 }
3092
3093 #[test]
3096 fn lseek_espipe_on_pipe() {
3097 let (r, _w) = make_pipe();
3098 let err = lseek(r.as_fd(), 0, SeekWhence::Set).unwrap_err();
3099 assert_eq!(err, Errno::ESPIPE);
3100 }
3101
3102 #[test]
3105 fn statx_enoent_on_missing_file() {
3106 let err = statx(
3107 DirFd::Cwd,
3108 c"/air_no_such_file_statx_42",
3109 StatxFlags::empty(),
3110 StatxMask::ALL,
3111 )
3112 .unwrap_err();
3113 assert_eq!(err, Errno::ENOENT);
3114 }
3115
3116 #[test]
3119 fn faccessat_existing_file_ok() {
3120 faccessat(
3121 DirFd::Cwd,
3122 c"/proc/self/status",
3123 AccessMode::F_OK,
3124 AccessFlags::empty(),
3125 )
3126 .expect("faccessat /proc/self/status");
3127 }
3128
3129 #[test]
3130 fn faccessat_missing_file_enoent() {
3131 let err = faccessat(
3132 DirFd::Cwd,
3133 c"/air_no_such_file_faccessat_42",
3134 AccessMode::F_OK,
3135 AccessFlags::empty(),
3136 )
3137 .unwrap_err();
3138 assert_eq!(err, Errno::ENOENT);
3139 }
3140
3141 #[test]
3142 fn faccessat_symlink_nofollow_effective() {
3143 let link = c"/tmp/air_fs_faccessat_dangling_42";
3149 let target = c"/air_no_such_target_for_dangling_symlink_42";
3150 let _ = unlinkat(DirFd::Cwd, link, 0);
3151 symlinkat(target, DirFd::Cwd, link).expect("crée le lien pendouillant");
3152
3153 faccessat(
3155 DirFd::Cwd,
3156 link,
3157 AccessMode::F_OK,
3158 AccessFlags::SYMLINK_NOFOLLOW,
3159 )
3160 .expect("le lien lui-même existe : F_OK doit réussir avec NOFOLLOW");
3161
3162 let err = faccessat(DirFd::Cwd, link, AccessMode::F_OK, AccessFlags::empty()).unwrap_err();
3164 assert_eq!(
3165 err,
3166 Errno::ENOENT,
3167 "sans NOFOLLOW, on suit vers la cible absente"
3168 );
3169
3170 let _ = unlinkat(DirFd::Cwd, link, 0);
3171 }
3172
3173 #[test]
3174 fn faccessat_eaccess_flag_routes_and_succeeds() {
3175 faccessat(
3179 DirFd::Cwd,
3180 c"/proc/self/status",
3181 AccessMode::R_OK,
3182 AccessFlags::EACCESS,
3183 )
3184 .expect("R_OK avec EACCESS sur un fichier lisible");
3185 }
3186
3187 #[test]
3190 fn mkdirat_eexist_on_second_call() {
3191 const AT_REMOVEDIR: i32 = 0x200;
3192 let dir = c"/tmp/air_fs_test_dir_eexist_42";
3193 let _ = unlinkat(DirFd::Cwd, dir, AT_REMOVEDIR);
3194 mkdirat(DirFd::Cwd, dir, 0o700).expect("mkdirat première fois");
3195 let err = mkdirat(DirFd::Cwd, dir, 0o700).unwrap_err();
3196 assert_eq!(err, Errno::EEXIST);
3197 unlinkat(DirFd::Cwd, dir, AT_REMOVEDIR).expect("cleanup");
3198 }
3199
3200 #[test]
3203 fn getdents64_enotdir_on_regular_file() {
3204 let fd = open_memfd();
3205 let mut buffer = vec![0u8; 4096];
3206 let err = getdents64(fd.as_fd(), &mut buffer).unwrap_err();
3207 assert_eq!(err, ENOTDIR);
3208 }
3209
3210 #[test]
3213 fn renameat2_renames_file() {
3214 let src = c"/tmp/air_fs_rename_src_42";
3215 let dst = c"/tmp/air_fs_rename_dst_42";
3216 let _ = unlinkat(DirFd::Cwd, src, 0);
3217 let _ = unlinkat(DirFd::Cwd, dst, 0);
3218 let fd = openat(
3219 DirFd::Cwd,
3220 src,
3221 OpenFlags::WRONLY | OpenFlags::CREAT | OpenFlags::EXCL,
3222 0o600,
3223 )
3224 .expect("crée src");
3225 drop(fd);
3226 renameat2(DirFd::Cwd, src, DirFd::Cwd, dst, RenameFlags::empty()).expect("renameat2");
3227 faccessat(DirFd::Cwd, dst, AccessMode::F_OK, AccessFlags::empty())
3229 .expect("dst doit exister");
3230 let _ = unlinkat(DirFd::Cwd, dst, 0);
3231 }
3232
3233 #[test]
3234 fn renameat2_noreplace_eexist() {
3235 let src = c"/tmp/air_fs_noreplace_src_42";
3236 let dst = c"/tmp/air_fs_noreplace_dst_42";
3237 let _ = unlinkat(DirFd::Cwd, src, 0);
3238 let _ = unlinkat(DirFd::Cwd, dst, 0);
3239 for p in [src, dst] {
3240 let fd = openat(
3241 DirFd::Cwd,
3242 p,
3243 OpenFlags::WRONLY | OpenFlags::CREAT | OpenFlags::EXCL,
3244 0o600,
3245 )
3246 .expect("crée fichier");
3247 drop(fd);
3248 }
3249 let err = renameat2(DirFd::Cwd, src, DirFd::Cwd, dst, RenameFlags::NOREPLACE).unwrap_err();
3250 assert_eq!(err, Errno::EEXIST);
3251 let _ = unlinkat(DirFd::Cwd, src, 0);
3252 let _ = unlinkat(DirFd::Cwd, dst, 0);
3253 }
3254
3255 #[test]
3258 fn linkat_creates_hard_link() {
3259 let src = c"/tmp/air_fs_linkat_src_42";
3260 let lnk = c"/tmp/air_fs_linkat_lnk_42";
3261 let _ = unlinkat(DirFd::Cwd, src, 0);
3262 let _ = unlinkat(DirFd::Cwd, lnk, 0);
3263 let fd = openat(
3264 DirFd::Cwd,
3265 src,
3266 OpenFlags::WRONLY | OpenFlags::CREAT | OpenFlags::EXCL,
3267 0o600,
3268 )
3269 .expect("crée src");
3270 drop(fd);
3271 linkat(DirFd::Cwd, src, DirFd::Cwd, lnk, 0).expect("linkat");
3272 let _ = unlinkat(DirFd::Cwd, src, 0);
3273 let _ = unlinkat(DirFd::Cwd, lnk, 0);
3274 }
3275
3276 #[test]
3277 fn linkat_enoent_on_missing_source() {
3278 let err = linkat(
3279 DirFd::Cwd,
3280 c"/air_no_such_linkat_src",
3281 DirFd::Cwd,
3282 c"/tmp/air_no_such_linkat_dst",
3283 0,
3284 )
3285 .unwrap_err();
3286 assert_eq!(err, Errno::ENOENT);
3287 }
3288
3289 #[test]
3290 fn symlinkat_readlinkat_roundtrip() {
3291 let target = c"/proc/self/status";
3292 let link = c"/tmp/air_fs_symlink_42";
3293 let _ = unlinkat(DirFd::Cwd, link, 0);
3294 symlinkat(target, DirFd::Cwd, link).expect("symlinkat");
3295 let read_back = readlinkat(DirFd::Cwd, link).expect("readlinkat");
3296 assert_eq!(read_back.as_slice(), b"/proc/self/status");
3297 let _ = unlinkat(DirFd::Cwd, link, 0);
3298 }
3299
3300 #[test]
3301 fn readlinkat_einval_on_non_symlink() {
3302 let err = readlinkat(DirFd::Cwd, c"/proc/self/status").unwrap_err();
3303 assert_eq!(err, Errno::EINVAL);
3304 }
3305
3306 #[test]
3307 fn readlinkat_enoent_on_missing() {
3308 let err = readlinkat(DirFd::Cwd, c"/air_no_such_symlink_42").unwrap_err();
3309 assert_eq!(err, Errno::ENOENT);
3310 }
3311
3312 #[test]
3318 fn libc_enomem_is_twelve() {
3319 assert_eq!(libc_enomem(), 12, "ENOMEM (asm-generic/errno.h)");
3320 }
3321
3322 #[test]
3325 fn mknodat_creates_fifo() {
3326 let path = c"/tmp/air_fs_mknodat_fifo_42";
3327 let _ = unlinkat(DirFd::Cwd, path, 0);
3328 mknodat(DirFd::Cwd, path, 0o10_600, 0).expect("mknodat FIFO");
3330 let _ = unlinkat(DirFd::Cwd, path, 0);
3331 }
3332
3333 #[test]
3334 fn mknodat_eexist_on_second_call() {
3335 let path = c"/tmp/air_fs_mknodat_eexist_42";
3336 let _ = unlinkat(DirFd::Cwd, path, 0);
3337 mknodat(DirFd::Cwd, path, 0o10_600, 0).expect("mknodat première fois");
3338 let err = mknodat(DirFd::Cwd, path, 0o10_600, 0).unwrap_err();
3339 assert_eq!(err, Errno::EEXIST);
3340 let _ = unlinkat(DirFd::Cwd, path, 0);
3341 }
3342
3343 #[test]
3346 fn fchmodat_changes_mode() {
3347 let path = c"/tmp/air_fs_fchmodat_42";
3348 let _ = unlinkat(DirFd::Cwd, path, 0);
3349 let fd = openat(
3350 DirFd::Cwd,
3351 path,
3352 OpenFlags::WRONLY | OpenFlags::CREAT | OpenFlags::EXCL,
3353 0o644,
3354 )
3355 .expect("crée fichier");
3356 drop(fd);
3357 fchmodat(DirFd::Cwd, path, 0o600, 0).expect("fchmodat");
3358 let _ = unlinkat(DirFd::Cwd, path, 0);
3359 }
3360
3361 #[test]
3362 fn fchmodat_enoent_on_missing() {
3363 let err = fchmodat(DirFd::Cwd, c"/air_no_such_file_fchmodat_42", 0o644, 0).unwrap_err();
3364 assert_eq!(err, Errno::ENOENT);
3365 }
3366
3367 #[test]
3370 fn fchownat_no_change_succeeds() {
3371 let path = c"/tmp/air_fs_fchownat_42";
3373 let _ = unlinkat(DirFd::Cwd, path, 0);
3374 let fd = openat(
3375 DirFd::Cwd,
3376 path,
3377 OpenFlags::WRONLY | OpenFlags::CREAT | OpenFlags::EXCL,
3378 0o644,
3379 )
3380 .expect("crée fichier");
3381 drop(fd);
3382 fchownat(DirFd::Cwd, path, u32::MAX, u32::MAX, 0).expect("fchownat no-op");
3383 let _ = unlinkat(DirFd::Cwd, path, 0);
3384 }
3385
3386 #[test]
3387 fn fchownat_enoent_on_missing() {
3388 let err = fchownat(
3389 DirFd::Cwd,
3390 c"/air_no_such_file_fchownat_42",
3391 u32::MAX,
3392 u32::MAX,
3393 0,
3394 )
3395 .unwrap_err();
3396 assert_eq!(err, Errno::ENOENT);
3397 }
3398
3399 #[test]
3402 fn fchmod_changes_mode_by_fd() {
3403 let fd = open_memfd();
3404 fchmod(fd.as_fd(), 0o600).expect("fchmod par fd");
3405 assert_eq!(fchmod(invalid_fd(), 0o600).unwrap_err(), Errno::EBADF);
3407 }
3408
3409 #[test]
3410 fn fchown_noop_by_fd() {
3411 let fd = open_memfd();
3412 fchown(fd.as_fd(), u32::MAX, u32::MAX).expect("fchown no-op par fd");
3414 assert_eq!(
3416 fchown(invalid_fd(), u32::MAX, u32::MAX).unwrap_err(),
3417 Errno::EBADF
3418 );
3419 }
3420
3421 #[test]
3422 fn futimens_sets_times_by_fd() {
3423 let fd = open_memfd();
3424 futimens(fd.as_fd(), UtimeValue::Now, UtimeValue::Omit).expect("futimens par fd");
3425 assert_eq!(
3427 futimens(invalid_fd(), UtimeValue::Now, UtimeValue::Now).unwrap_err(),
3428 Errno::EBADF
3429 );
3430 }
3431
3432 #[test]
3433 fn sendfile_transfers_with_and_without_offset() {
3434 let source = open_memfd();
3435 let data = b"SENDFILE";
3436 assert_eq!(write(source.as_fd(), data).expect("write source"), 8);
3437
3438 let sink = open_memfd();
3441 let mut offset = 0_u64;
3442 let sent = sendfile(sink.as_fd(), source.as_fd(), Some(&mut offset), 8).expect("sendfile");
3443 assert_eq!(sent, 8);
3444 assert_eq!(offset, 8, "l'offset explicite est avancé");
3445 lseek(sink.as_fd(), 0, SeekWhence::Set).expect("lseek sink");
3447 let mut buffer = [0_u8; 8];
3448 assert_eq!(read(sink.as_fd(), &mut buffer).expect("read sink"), 8);
3449 assert_eq!(&buffer, b"SENDFILE");
3450
3451 lseek(source.as_fd(), 0, SeekWhence::Set).expect("lseek source");
3454 let sink2 = open_memfd();
3455 assert_eq!(
3456 sendfile(sink2.as_fd(), source.as_fd(), None, 8).expect("sendfile sans offset"),
3457 8
3458 );
3459
3460 assert_eq!(
3462 sendfile(invalid_fd(), source.as_fd(), None, 8).unwrap_err(),
3463 Errno::EBADF
3464 );
3465 }
3466
3467 #[test]
3470 fn utimensat_now_and_omit() {
3471 let path = c"/tmp/air_fs_utimensat_42";
3472 let _ = unlinkat(DirFd::Cwd, path, 0);
3473 let fd = openat(
3474 DirFd::Cwd,
3475 path,
3476 OpenFlags::WRONLY | OpenFlags::CREAT | OpenFlags::EXCL,
3477 0o644,
3478 )
3479 .expect("crée fichier");
3480 drop(fd);
3481 utimensat(DirFd::Cwd, path, UtimeValue::Now, UtimeValue::Omit, 0)
3482 .expect("utimensat Now/Omit");
3483 let _ = unlinkat(DirFd::Cwd, path, 0);
3484 }
3485
3486 #[test]
3487 fn utimensat_specific_time() {
3488 let path = c"/tmp/air_fs_utimensat_time_42";
3489 let _ = unlinkat(DirFd::Cwd, path, 0);
3490 let fd = openat(
3491 DirFd::Cwd,
3492 path,
3493 OpenFlags::WRONLY | OpenFlags::CREAT | OpenFlags::EXCL,
3494 0o644,
3495 )
3496 .expect("crée fichier");
3497 drop(fd);
3498 let ts = StatxTimestamp {
3499 seconds: 1_000_000,
3500 nanoseconds: 123_456_789,
3501 };
3502 utimensat(DirFd::Cwd, path, UtimeValue::Time(ts), UtimeValue::Omit, 0)
3503 .expect("utimensat Time");
3504 let _ = unlinkat(DirFd::Cwd, path, 0);
3505 }
3506
3507 #[test]
3508 fn utimensat_enoent_on_missing() {
3509 let err = utimensat(
3510 DirFd::Cwd,
3511 c"/air_no_such_utimensat_42",
3512 UtimeValue::Now,
3513 UtimeValue::Now,
3514 0,
3515 )
3516 .unwrap_err();
3517 assert_eq!(err, Errno::ENOENT);
3518 }
3519
3520 #[test]
3523 fn truncate_via_path_sets_size() {
3524 let path = c"/tmp/air_fs_truncate_42";
3525 let _ = unlinkat(DirFd::Cwd, path, 0);
3526 let fd = openat(
3527 DirFd::Cwd,
3528 path,
3529 OpenFlags::WRONLY | OpenFlags::CREAT | OpenFlags::EXCL,
3530 0o644,
3531 )
3532 .expect("crée fichier");
3533 write(fd.as_fd(), b"hello world 12345").expect("write");
3534 drop(fd);
3535 truncate(DirFd::Cwd, path, 5).expect("truncate");
3536 let fd2 = openat(DirFd::Cwd, path, OpenFlags::RDONLY, 0).expect("reopen");
3537 let pos = lseek(fd2.as_fd(), 0, SeekWhence::End).expect("lseek end");
3538 assert_eq!(pos, 5);
3539 let _ = unlinkat(DirFd::Cwd, path, 0);
3540 }
3541
3542 #[test]
3543 fn truncate_enoent_on_missing() {
3544 let err = truncate(DirFd::Cwd, c"/air_no_such_truncate_42", 0).unwrap_err();
3545 assert_eq!(err, Errno::ENOENT);
3546 }
3547
3548 #[test]
3551 fn fdatasync_on_memfd() {
3552 let fd = open_memfd();
3553 write(fd.as_fd(), b"data").expect("write");
3554 fdatasync(fd.as_fd()).expect("fdatasync");
3555 }
3556
3557 #[test]
3558 fn fdatasync_ebadf() {
3559 let invalid = invalid_fd();
3560 assert_eq!(fdatasync(invalid).unwrap_err(), Errno::EBADF);
3561 }
3562
3563 #[test]
3566 fn fallocate_preallocates_space() {
3567 let fd = open_memfd();
3568 fallocate(fd.as_fd(), FallocateMode::empty(), 0, 4096).expect("fallocate");
3569 let pos = lseek(fd.as_fd(), 0, SeekWhence::End).expect("lseek");
3570 assert_eq!(pos, 4096);
3571 }
3572
3573 #[test]
3574 fn fallocate_keep_size_does_not_extend() {
3575 let fd = open_memfd();
3576 fallocate(fd.as_fd(), FallocateMode::KEEP_SIZE, 0, 4096).expect("fallocate KEEP_SIZE");
3577 let pos = lseek(fd.as_fd(), 0, SeekWhence::End).expect("lseek");
3579 assert_eq!(pos, 0);
3580 }
3581
3582 #[test]
3585 fn flock_lock_and_unlock() {
3586 let fd = open_memfd();
3587 flock(fd.as_fd(), 2).expect("flock LOCK_EX");
3589 flock(fd.as_fd(), 8).expect("flock LOCK_UN");
3590 }
3591
3592 #[test]
3593 fn flock_ebadf() {
3594 let invalid = invalid_fd();
3595 assert_eq!(flock(invalid, 2).unwrap_err(), Errno::EBADF);
3596 }
3597
3598 #[test]
3601 fn sync_file_range_on_memfd() {
3602 let fd = open_memfd();
3603 write(fd.as_fd(), b"sync data").expect("write");
3604 sync_file_range(fd.as_fd(), 0, 0, 2).expect("sync_file_range");
3606 }
3607
3608 #[test]
3611 fn fd_flags_get_set_roundtrip() {
3612 let fd = open_memfd();
3613 let flags = get_fd_flags(fd.as_fd()).expect("get_fd_flags");
3615 assert!(flags.contains(FdFlags::CLOEXEC));
3617 set_fd_flags(fd.as_fd(), FdFlags::empty()).expect("set_fd_flags");
3619 let flags2 = get_fd_flags(fd.as_fd()).expect("get_fd_flags après set");
3620 assert!(!flags2.contains(FdFlags::CLOEXEC));
3621 }
3622
3623 #[test]
3624 fn get_fd_flags_ebadf() {
3625 let invalid = invalid_fd();
3626 assert_eq!(get_fd_flags(invalid).unwrap_err(), Errno::EBADF);
3627 }
3628
3629 #[test]
3630 fn set_fd_flags_ebadf() {
3631 let invalid = invalid_fd();
3632 assert_eq!(
3633 set_fd_flags(invalid, FdFlags::empty()).unwrap_err(),
3634 Errno::EBADF
3635 );
3636 }
3637
3638 #[test]
3641 fn status_flags_get_set_roundtrip() {
3642 let fd = open_memfd();
3643 let flags = get_status_flags(fd.as_fd()).expect("get_status_flags");
3644 set_status_flags(fd.as_fd(), flags).expect("set_status_flags");
3646 }
3647
3648 #[test]
3649 fn get_status_flags_ebadf() {
3650 let invalid = invalid_fd();
3651 assert_eq!(get_status_flags(invalid).unwrap_err(), Errno::EBADF);
3652 }
3653
3654 #[test]
3655 fn set_status_flags_ebadf() {
3656 let invalid = invalid_fd();
3657 assert_eq!(
3658 set_status_flags(invalid, StatusFlags::empty()).unwrap_err(),
3659 Errno::EBADF
3660 );
3661 }
3662
3663 #[test]
3666 fn pipe_size_set_get_roundtrip() {
3667 let (r, _w) = make_pipe();
3668 let new_size = set_pipe_size(r.as_fd(), 65536).expect("set_pipe_size");
3669 assert!(new_size >= 65536);
3670 let got = get_pipe_size(r.as_fd()).expect("get_pipe_size");
3671 assert_eq!(got, new_size);
3672 }
3673
3674 #[test]
3675 fn set_pipe_size_ebadf() {
3676 let invalid = invalid_fd();
3677 assert_eq!(set_pipe_size(invalid, 4096).unwrap_err(), Errno::EBADF);
3678 }
3679
3680 #[test]
3681 fn get_pipe_size_ebadf() {
3682 let invalid = invalid_fd();
3683 assert_eq!(get_pipe_size(invalid).unwrap_err(), Errno::EBADF);
3684 }
3685
3686 #[test]
3689 fn try_lock_and_unlock() {
3690 let path = c"/tmp/air_fs_try_lock_42";
3691 let _ = unlinkat(DirFd::Cwd, path, 0);
3692 let fd = openat(
3693 DirFd::Cwd,
3694 path,
3695 OpenFlags::RDWR | OpenFlags::CREAT | OpenFlags::EXCL,
3696 0o644,
3697 )
3698 .expect("crée fichier");
3699 let lock_w = FileLock {
3700 lock_type: LockType::Write,
3701 whence: SeekWhence::Set,
3702 start: 0,
3703 length: 0,
3704 pid: 0,
3705 };
3706 let locked = try_lock(fd.as_fd(), &lock_w).expect("try_lock Write");
3707 assert!(locked, "devrait acquérir le verrou write");
3708 let unlock = FileLock {
3709 lock_type: LockType::Unlock,
3710 whence: SeekWhence::Set,
3711 start: 0,
3712 length: 0,
3713 pid: 0,
3714 };
3715 lock(fd.as_fd(), &unlock).expect("lock Unlock");
3716 let _ = unlinkat(DirFd::Cwd, path, 0);
3717 }
3718
3719 #[test]
3725 #[cfg_attr(miri, ignore = "fork/verrous fcntl non supportés par Miri")]
3726 fn try_lock_returns_ok_false_when_held_by_another_process() {
3727 use crate::ipc::pipe2;
3728 use crate::process::{clone3, exit_group, waitid};
3729 use air_sys_types::{
3730 CloneArgs, CloneFlags, CloneResult, PipeFlags, Signal, WaitEvent, WaitOptions,
3731 WaitTarget,
3732 };
3733
3734 let path = c"/tmp/air_fs_try_lock_contention_55";
3735 let _ = unlinkat(DirFd::Cwd, path, 0);
3736 let fd = openat(
3737 DirFd::Cwd,
3738 path,
3739 OpenFlags::RDWR | OpenFlags::CREAT | OpenFlags::EXCL,
3740 0o644,
3741 )
3742 .expect("crée fichier");
3743 let (locked_r, locked_w) = pipe2(PipeFlags::empty()).expect("pipe locked");
3745 let (release_r, release_w) = pipe2(PipeFlags::empty()).expect("pipe release");
3746
3747 let write_lock = FileLock {
3748 lock_type: LockType::Write,
3749 whence: SeekWhence::Set,
3750 start: 0,
3751 length: 0,
3752 pid: 0,
3753 };
3754
3755 let args = CloneArgs {
3756 flags: CloneFlags::empty(),
3757 exit_signal: Some(Signal::SIGCHLD),
3758 stack: None,
3759 child_tid: None,
3760 parent_tid: None,
3761 tls: None,
3762 };
3763 match unsafe { clone3(&args) }.expect("clone3 doit réussir") {
3766 CloneResult::Child => {
3767 let acquired = lock(fd.as_fd(), &write_lock).is_ok();
3771 let _ = write(locked_w.as_fd(), &[u8::from(acquired)]);
3772 let mut go = [0_u8; 1];
3773 let _ = read(release_r.as_fd(), &mut go);
3774 exit_group(i32::from(!acquired));
3775 }
3776 CloneResult::Parent { child_pid, .. } => {
3777 let mut signal = [0_u8; 1];
3779 assert_eq!(
3780 read(locked_r.as_fd(), &mut signal).expect("signal locked"),
3781 1
3782 );
3783 assert_eq!(signal[0], 1, "l'enfant a bien acquis le verrou exclusif");
3784 assert_eq!(
3786 try_lock(fd.as_fd(), &write_lock),
3787 Ok(false),
3788 "verrou tenu par un autre processus → Ok(false)"
3789 );
3790 let _ = write(release_w.as_fd(), &[1_u8]);
3792 let status = waitid(WaitTarget::Pid(child_pid), WaitOptions::EXITED)
3793 .expect("waitid")
3794 .expect("événement EXITED");
3795 assert!(
3796 matches!(status.event, WaitEvent::Exited { code: 0 }),
3797 "enfant sorti proprement : {:?}",
3798 status.event
3799 );
3800 }
3801 }
3802 let _ = unlinkat(DirFd::Cwd, path, 0);
3803 }
3804
3805 #[test]
3806 fn try_lock_ebadf() {
3807 let invalid = invalid_fd();
3808 let lock = FileLock {
3809 lock_type: LockType::Write,
3810 whence: SeekWhence::Set,
3811 start: 0,
3812 length: 0,
3813 pid: 0,
3814 };
3815 assert_eq!(try_lock(invalid, &lock).unwrap_err(), Errno::EBADF);
3816 }
3817
3818 #[test]
3819 fn lock_ebadf() {
3820 let invalid = invalid_fd();
3821 let unlock = FileLock {
3822 lock_type: LockType::Unlock,
3823 whence: SeekWhence::Set,
3824 start: 0,
3825 length: 0,
3826 pid: 0,
3827 };
3828 assert_eq!(lock(invalid, &unlock).unwrap_err(), Errno::EBADF);
3829 }
3830
3831 #[test]
3834 fn seals_add_and_get_roundtrip() {
3835 #[cfg(target_arch = "x86_64")]
3837 let nr_memfd: i64 = 319;
3838 #[cfg(target_arch = "aarch64")]
3839 let nr_memfd: i64 = 279;
3840 let name = c"test-seals";
3841 let ret = unsafe { syscall2(nr_memfd, name.as_ptr() as u64, 3) };
3845 if ret < 0 {
3846 return; }
3848 #[allow(clippy::cast_possible_truncation)]
3849 let fd = unsafe { OwnedFd::from_raw_fd(ret as i32) };
3852
3853 let initial = get_seals(fd.as_fd()).expect("get_seals initial");
3855 assert!(initial.is_empty());
3856
3857 add_seals(fd.as_fd(), Seals::SHRINK | Seals::GROW).expect("add_seals");
3859 let after = get_seals(fd.as_fd()).expect("get_seals après");
3860 assert!(after.contains(Seals::SHRINK));
3861 assert!(after.contains(Seals::GROW));
3862 }
3863
3864 #[test]
3865 fn add_seals_ebadf() {
3866 let invalid = invalid_fd();
3867 assert_eq!(add_seals(invalid, Seals::SHRINK).unwrap_err(), Errno::EBADF);
3868 }
3869
3870 #[test]
3871 fn get_seals_ebadf() {
3872 let invalid = invalid_fd();
3873 assert_eq!(get_seals(invalid).unwrap_err(), Errno::EBADF);
3874 }
3875
3876 #[test]
3879 fn name_to_handle_at_ok_or_eopnotsupp() {
3880 match name_to_handle_at(DirFd::Cwd, c"/tmp", NameToHandleFlags::empty()) {
3884 Ok(h) => {
3885 assert!(!h.handle_bytes.is_empty() || h.handle_type != 0 || h.mount_id != 0);
3887 }
3888 Err(EOPNOTSUPP) => {
3889 }
3892 Err(e) => panic!("name_to_handle_at inattendu : {e:?}"),
3893 }
3894 }
3895
3896 #[test]
3897 fn name_to_handle_at_enoent() {
3898 let err = name_to_handle_at(
3899 DirFd::Cwd,
3900 c"/air_no_such_handle_42",
3901 NameToHandleFlags::empty(),
3902 )
3903 .unwrap_err();
3904 assert_eq!(err, Errno::ENOENT);
3905 }
3906
3907 #[test]
3910 fn copy_file_range_with_explicit_offsets() {
3911 let src = open_memfd();
3912 write(src.as_fd(), b"abcdefgh").expect("write src");
3913
3914 let dst = open_memfd();
3915 ftruncate(dst.as_fd(), 16).expect("ftruncate dst");
3916
3917 let mut offset_in: u64 = 2;
3918 let mut offset_out: u64 = 4;
3919 let n = copy_file_range(
3920 src.as_fd(),
3921 Some(&mut offset_in),
3922 dst.as_fd(),
3923 Some(&mut offset_out),
3924 4,
3925 0,
3926 )
3927 .expect("copy_file_range avec offsets");
3928 assert_eq!(n, 4);
3929 assert_eq!(offset_in, 6);
3931 assert_eq!(offset_out, 8);
3932 lseek(dst.as_fd(), 4, SeekWhence::Set).expect("lseek dst");
3934 let mut buffer = [0u8; 4];
3935 let n2 = read(dst.as_fd(), &mut buffer).expect("read dst");
3936 assert_eq!(n2, 4);
3937 assert_eq!(&buffer, b"cdef");
3938 }
3939
3940 #[test]
3943 fn unlinkat_enoent_on_missing() {
3944 let err = unlinkat(DirFd::Cwd, c"/air_no_such_unlinkat_42", 0).unwrap_err();
3945 assert_eq!(err, Errno::ENOENT);
3946 }
3947
3948 #[test]
3951 fn openat2_enoent_on_missing() {
3952 use air_sys_types::fs::{OpenHow, ResolveFlags};
3953 let how = OpenHow {
3954 flags: OpenFlags::RDONLY,
3955 mode: 0,
3956 resolve: ResolveFlags::empty(),
3957 };
3958 let err = openat2(DirFd::Cwd, c"/air_no_such_openat2_42", how).unwrap_err();
3959 assert_eq!(err, Errno::ENOENT);
3960 }
3961
3962 #[test]
3965 fn ftruncate_ebadf() {
3966 let invalid = invalid_fd();
3967 assert_eq!(ftruncate(invalid, 0).unwrap_err(), Errno::EBADF);
3968 }
3969
3970 #[test]
3973 fn fsync_ebadf() {
3974 let invalid = invalid_fd();
3975 assert_eq!(fsync(invalid).unwrap_err(), Errno::EBADF);
3976 }
3977
3978 #[test]
3981 fn statfs_enoent_on_missing() {
3982 let err = statfs(c"/air_no_such_path_statfs_42").unwrap_err();
3983 assert_eq!(err, Errno::ENOENT);
3984 }
3985
3986 #[test]
3989 fn fstatfs_ebadf() {
3990 let invalid = invalid_fd();
3991 assert_eq!(fstatfs(invalid).unwrap_err(), Errno::EBADF);
3992 }
3993
3994 #[test]
3997 fn dup_fd_ebadf() {
3998 let invalid = invalid_fd();
3999 assert_eq!(dup_fd(invalid, 0).unwrap_err(), Errno::EBADF);
4000 }
4001
4002 fn make_dirent_buf(file_type_byte: u8) -> Vec<u8> {
4006 let reclen: u16 = 24;
4014 let mut buffer = vec![0u8; 24];
4015 buffer[0..8].copy_from_slice(&1u64.to_ne_bytes()); buffer[8..16].copy_from_slice(&1i64.to_ne_bytes()); buffer[16..18].copy_from_slice(&reclen.to_ne_bytes()); buffer[18] = file_type_byte; buffer[19] = b'x'; buffer[20] = 0; buffer
4022 }
4023
4024 #[test]
4025 fn parse_dirents_all_types() {
4026 for (byte, expected) in [
4028 (1u8, DirEntryType::Fifo),
4029 (2, DirEntryType::Character),
4030 (4, DirEntryType::Directory),
4031 (6, DirEntryType::Block),
4032 (8, DirEntryType::Regular),
4033 (10, DirEntryType::Symlink),
4034 (12, DirEntryType::Socket),
4035 (0, DirEntryType::Unknown),
4036 (255, DirEntryType::Unknown), ] {
4038 let buffer = make_dirent_buf(byte);
4039 let entries: Vec<_> = parse_dirents(&buffer).collect();
4040 assert_eq!(entries.len(), 1, "type_byte={byte}");
4041 assert_eq!(entries[0].file_type, expected, "type_byte={byte}");
4042 }
4043 }
4044
4045 #[test]
4046 fn parse_dirents_empty_buf() {
4047 let entries: Vec<_> = parse_dirents(&[]).collect();
4048 assert!(entries.is_empty());
4049 }
4050
4051 #[test]
4052 fn parse_dirents_truncated_header() {
4053 let buffer = vec![0u8; 10];
4055 let entries: Vec<_> = parse_dirents(&buffer).collect();
4056 assert!(entries.is_empty());
4057 }
4058
4059 #[test]
4060 fn parse_dirents_reclen_zero() {
4061 let mut buffer = vec![0u8; 24];
4063 buffer[0..8].copy_from_slice(&1u64.to_ne_bytes());
4064 buffer[8..16].copy_from_slice(&1i64.to_ne_bytes());
4065 buffer[16..18].copy_from_slice(&0u16.to_ne_bytes()); buffer[18] = 8;
4067 buffer[19] = b'x';
4068 buffer[20] = 0;
4069 let entries: Vec<_> = parse_dirents(&buffer).collect();
4070 assert!(entries.is_empty());
4071 }
4072
4073 #[test]
4074 fn parse_dirents_name_without_nul() {
4075 let reclen: u16 = 24;
4077 let mut buffer = vec![0u8; 24];
4078 buffer[0..8].copy_from_slice(&1u64.to_ne_bytes());
4079 buffer[8..16].copy_from_slice(&1i64.to_ne_bytes());
4080 buffer[16..18].copy_from_slice(&reclen.to_ne_bytes());
4081 buffer[18] = 8; for b in buffer[19..24].iter_mut() {
4084 *b = b'a';
4085 }
4086 let entries: Vec<_> = parse_dirents(&buffer).collect();
4087 assert_eq!(entries.len(), 1);
4088 }
4089}