1//! libc syscalls supporting `rustix::fs`.
2
3use super::super::c;
4use super::super::conv::{
5    borrowed_fd, c_str, ret, ret_c_int, ret_off_t, ret_owned_fd, ret_ssize_t,
6};
7#[cfg(any(target_os = "android", target_os = "linux"))]
8use super::super::conv::{syscall_ret, syscall_ret_owned_fd, syscall_ret_ssize_t};
9#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
10use super::super::offset::libc_fallocate;
11#[cfg(not(any(
12    target_os = "dragonfly",
13    target_os = "haiku",
14    target_os = "illumos",
15    target_os = "ios",
16    target_os = "macos",
17    target_os = "netbsd",
18    target_os = "openbsd",
19    target_os = "redox",
20    target_os = "solaris",
21)))]
22use super::super::offset::libc_posix_fadvise;
23#[cfg(not(any(
24    target_os = "aix",
25    target_os = "android",
26    target_os = "dragonfly",
27    target_os = "fuchsia",
28    target_os = "illumos",
29    target_os = "ios",
30    target_os = "linux",
31    target_os = "macos",
32    target_os = "netbsd",
33    target_os = "openbsd",
34    target_os = "redox",
35    target_os = "solaris",
36)))]
37use super::super::offset::libc_posix_fallocate;
38use super::super::offset::{libc_fstat, libc_fstatat, libc_ftruncate, libc_lseek, libc_off_t};
39#[cfg(not(any(
40    target_os = "haiku",
41    target_os = "illumos",
42    target_os = "netbsd",
43    target_os = "redox",
44    target_os = "solaris",
45    target_os = "wasi",
46)))]
47use super::super::offset::{libc_fstatfs, libc_statfs};
48#[cfg(not(any(
49    target_os = "haiku",
50    target_os = "illumos",
51    target_os = "redox",
52    target_os = "solaris",
53    target_os = "wasi",
54)))]
55use super::super::offset::{libc_fstatvfs, libc_statvfs};
56#[cfg(all(
57    any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
58    target_env = "gnu",
59))]
60use super::super::time::types::LibcTimespec;
61use crate::fd::{BorrowedFd, OwnedFd};
62use crate::ffi::CStr;
63#[cfg(any(target_os = "ios", target_os = "macos"))]
64use crate::ffi::CString;
65#[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
66use crate::fs::Access;
67#[cfg(not(any(
68    target_os = "dragonfly",
69    target_os = "haiku",
70    target_os = "illumos",
71    target_os = "ios",
72    target_os = "macos",
73    target_os = "netbsd",
74    target_os = "openbsd",
75    target_os = "redox",
76    target_os = "solaris",
77)))]
78use crate::fs::Advice;
79#[cfg(not(any(
80    target_os = "aix",
81    target_os = "dragonfly",
82    target_os = "illumos",
83    target_os = "netbsd",
84    target_os = "openbsd",
85    target_os = "redox",
86    target_os = "solaris",
87)))]
88use crate::fs::FallocateFlags;
89#[cfg(not(any(target_os = "solaris", target_os = "wasi")))]
90use crate::fs::FlockOperation;
91#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
92use crate::fs::MemfdFlags;
93#[cfg(any(
94    target_os = "android",
95    target_os = "freebsd",
96    target_os = "fuchsia",
97    target_os = "linux",
98))]
99use crate::fs::SealFlags;
100#[cfg(not(any(
101    target_os = "haiku",
102    target_os = "illumos",
103    target_os = "netbsd",
104    target_os = "redox",
105    target_os = "solaris",
106    target_os = "wasi",
107)))]
108use crate::fs::StatFs;
109#[cfg(any(target_os = "android", target_os = "linux"))]
110use crate::fs::{cwd, RenameFlags, ResolveFlags, Statx, StatxFlags};
111#[cfg(not(any(
112    target_os = "ios",
113    target_os = "macos",
114    target_os = "redox",
115    target_os = "wasi",
116)))]
117use crate::fs::{Dev, FileType};
118use crate::fs::{Mode, OFlags, Stat, Timestamps};
119#[cfg(not(any(
120    target_os = "haiku",
121    target_os = "illumos",
122    target_os = "redox",
123    target_os = "solaris",
124    target_os = "wasi",
125)))]
126use crate::fs::{StatVfs, StatVfsMountFlags};
127use crate::io::{self, SeekFrom};
128#[cfg(not(target_os = "wasi"))]
129use crate::process::{Gid, Uid};
130#[cfg(not(all(
131    any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
132    target_env = "gnu",
133)))]
134use crate::utils::as_ptr;
135use core::convert::TryInto;
136#[cfg(any(
137    target_os = "android",
138    target_os = "ios",
139    target_os = "linux",
140    target_os = "macos",
141))]
142use core::mem::size_of;
143use core::mem::MaybeUninit;
144#[cfg(any(target_os = "android", target_os = "linux"))]
145use core::ptr::null;
146#[cfg(any(
147    target_os = "android",
148    target_os = "ios",
149    target_os = "linux",
150    target_os = "macos",
151))]
152use core::ptr::null_mut;
153#[cfg(any(target_os = "ios", target_os = "macos"))]
154use {
155    super::super::conv::nonnegative_ret,
156    crate::fs::{copyfile_state_t, CloneFlags, CopyfileFlags},
157};
158#[cfg(not(target_os = "redox"))]
159use {super::super::offset::libc_openat, crate::fs::AtFlags};
160
161#[cfg(all(
162    any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
163    target_env = "gnu",
164))]
165weak!(fn __utimensat64(c::c_int, *const c::c_char, *const LibcTimespec, c::c_int) -> c::c_int);
166#[cfg(all(
167    any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
168    target_env = "gnu",
169))]
170weak!(fn __futimens64(c::c_int, *const LibcTimespec) -> c::c_int);
171
172/// Use a direct syscall (via libc) for `openat`.
173///
174/// This is only currently necessary as a workaround for old glibc; see below.
175#[cfg(all(unix, target_env = "gnu"))]
176fn openat_via_syscall(
177    dirfd: BorrowedFd<'_>,
178    path: &CStr,
179    oflags: OFlags,
180    mode: Mode,
181) -> io::Result<OwnedFd> {
182    unsafe {
183        let dirfd = borrowed_fd(dirfd);
184        let path = c_str(path);
185        let oflags = oflags.bits();
186        let mode = c::c_uint::from(mode.bits());
187        ret_owned_fd(c::syscall(
188            c::SYS_openat,
189            c::c_long::from(dirfd),
190            path,
191            c::c_long::from(oflags),
192            mode as c::c_long,
193        ) as c::c_int)
194    }
195}
196
197#[cfg(not(target_os = "redox"))]
198pub(crate) fn openat(
199    dirfd: BorrowedFd<'_>,
200    path: &CStr,
201    oflags: OFlags,
202    mode: Mode,
203) -> io::Result<OwnedFd> {
204    // Work around <https://sourceware.org/bugzilla/show_bug.cgi?id=17523>.
205    // Basically old glibc versions don't handle O_TMPFILE correctly.
206    #[cfg(all(unix, target_env = "gnu"))]
207    if oflags.contains(OFlags::TMPFILE) && crate::backend::if_glibc_is_less_than_2_25() {
208        return openat_via_syscall(dirfd, path, oflags, mode);
209    }
210    unsafe {
211        // Pass `mode` as a `c_uint` even if `mode_t` is narrower, since
212        // `libc_openat` is declared as a variadic function and narrower
213        // arguments are promoted.
214        ret_owned_fd(libc_openat(
215            borrowed_fd(dirfd),
216            c_str(path),
217            oflags.bits(),
218            c::c_uint::from(mode.bits()),
219        ))
220    }
221}
222
223#[cfg(not(any(
224    target_os = "haiku",
225    target_os = "illumos",
226    target_os = "netbsd",
227    target_os = "redox",
228    target_os = "solaris",
229    target_os = "wasi",
230)))]
231#[inline]
232pub(crate) fn statfs(filename: &CStr) -> io::Result<StatFs> {
233    unsafe {
234        let mut result = MaybeUninit::<StatFs>::uninit();
235        ret(libc_statfs(c_str(filename), result.as_mut_ptr()))?;
236        Ok(result.assume_init())
237    }
238}
239
240#[cfg(not(any(
241    target_os = "haiku",
242    target_os = "illumos",
243    target_os = "redox",
244    target_os = "solaris",
245    target_os = "wasi",
246)))]
247#[inline]
248pub(crate) fn statvfs(filename: &CStr) -> io::Result<StatVfs> {
249    unsafe {
250        let mut result = MaybeUninit::<libc_statvfs>::uninit();
251        ret(libc_statvfs(c_str(filename), result.as_mut_ptr()))?;
252        Ok(libc_statvfs_to_statvfs(result.assume_init()))
253    }
254}
255
256#[cfg(not(target_os = "redox"))]
257#[inline]
258pub(crate) fn readlinkat(dirfd: BorrowedFd<'_>, path: &CStr, buf: &mut [u8]) -> io::Result<usize> {
259    unsafe {
260        ret_ssize_t(c::readlinkat(
261            borrowed_fd(dirfd),
262            c_str(path),
263            buf.as_mut_ptr().cast::<c::c_char>(),
264            buf.len(),
265        ))
266        .map(|nread| nread as usize)
267    }
268}
269
270#[cfg(not(target_os = "redox"))]
271pub(crate) fn mkdirat(dirfd: BorrowedFd<'_>, path: &CStr, mode: Mode) -> io::Result<()> {
272    unsafe {
273        ret(c::mkdirat(
274            borrowed_fd(dirfd),
275            c_str(path),
276            mode.bits() as c::mode_t,
277        ))
278    }
279}
280
281#[cfg(any(target_os = "android", target_os = "linux"))]
282pub(crate) fn getdents_uninit(
283    fd: BorrowedFd<'_>,
284    buf: &mut [MaybeUninit<u8>],
285) -> io::Result<usize> {
286    unsafe {
287        syscall_ret_ssize_t(c::syscall(
288            c::SYS_getdents64,
289            fd,
290            buf.as_mut_ptr().cast::<c::c_char>(),
291            buf.len(),
292        ))
293    }
294    .map(|nread| nread as usize)
295}
296
297#[cfg(not(target_os = "redox"))]
298pub(crate) fn linkat(
299    old_dirfd: BorrowedFd<'_>,
300    old_path: &CStr,
301    new_dirfd: BorrowedFd<'_>,
302    new_path: &CStr,
303    flags: AtFlags,
304) -> io::Result<()> {
305    unsafe {
306        ret(c::linkat(
307            borrowed_fd(old_dirfd),
308            c_str(old_path),
309            borrowed_fd(new_dirfd),
310            c_str(new_path),
311            flags.bits(),
312        ))
313    }
314}
315
316#[cfg(not(target_os = "redox"))]
317pub(crate) fn unlinkat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<()> {
318    unsafe { ret(c::unlinkat(borrowed_fd(dirfd), c_str(path), flags.bits())) }
319}
320
321#[cfg(not(target_os = "redox"))]
322pub(crate) fn renameat(
323    old_dirfd: BorrowedFd<'_>,
324    old_path: &CStr,
325    new_dirfd: BorrowedFd<'_>,
326    new_path: &CStr,
327) -> io::Result<()> {
328    unsafe {
329        ret(c::renameat(
330            borrowed_fd(old_dirfd),
331            c_str(old_path),
332            borrowed_fd(new_dirfd),
333            c_str(new_path),
334        ))
335    }
336}
337
338#[cfg(all(target_os = "linux", target_env = "gnu"))]
339pub(crate) fn renameat2(
340    old_dirfd: BorrowedFd<'_>,
341    old_path: &CStr,
342    new_dirfd: BorrowedFd<'_>,
343    new_path: &CStr,
344    flags: RenameFlags,
345) -> io::Result<()> {
346    // `getrandom` wasn't supported in glibc until 2.28.
347    weak_or_syscall! {
348        fn renameat2(
349            olddirfd: c::c_int,
350            oldpath: *const c::c_char,
351            newdirfd: c::c_int,
352            newpath: *const c::c_char,
353            flags: c::c_uint
354        ) via SYS_renameat2 -> c::c_int
355    }
356
357    unsafe {
358        ret(renameat2(
359            borrowed_fd(old_dirfd),
360            c_str(old_path),
361            borrowed_fd(new_dirfd),
362            c_str(new_path),
363            flags.bits(),
364        ))
365    }
366}
367
368/// At present, `libc` only has `renameat2` defined for glibc. On other
369/// ABIs, `RenameFlags` has no flags defined, and we use plain `renameat`.
370#[cfg(any(
371    target_os = "android",
372    all(target_os = "linux", not(target_env = "gnu")),
373))]
374#[inline]
375pub(crate) fn renameat2(
376    old_dirfd: BorrowedFd<'_>,
377    old_path: &CStr,
378    new_dirfd: BorrowedFd<'_>,
379    new_path: &CStr,
380    flags: RenameFlags,
381) -> io::Result<()> {
382    assert!(flags.is_empty());
383    renameat(old_dirfd, old_path, new_dirfd, new_path)
384}
385
386#[cfg(not(target_os = "redox"))]
387pub(crate) fn symlinkat(
388    old_path: &CStr,
389    new_dirfd: BorrowedFd<'_>,
390    new_path: &CStr,
391) -> io::Result<()> {
392    unsafe {
393        ret(c::symlinkat(
394            c_str(old_path),
395            borrowed_fd(new_dirfd),
396            c_str(new_path),
397        ))
398    }
399}
400
401#[cfg(not(target_os = "redox"))]
402pub(crate) fn statat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<Stat> {
403    // 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use
404    // `statx`.
405    #[cfg(all(
406        any(target_os = "android", target_os = "linux"),
407        any(target_pointer_width = "32", target_arch = "mips64"),
408    ))]
409    {
410        match statx(dirfd, path, flags, StatxFlags::BASIC_STATS) {
411            Ok(x) => statx_to_stat(x),
412            Err(io::Errno::NOSYS) => statat_old(dirfd, path, flags),
413            Err(err) => Err(err),
414        }
415    }
416
417    // Main version: libc is y2038 safe. Or, the platform is not y2038 safe and
418    // there's nothing practical we can do.
419    #[cfg(not(all(
420        any(target_os = "android", target_os = "linux"),
421        any(target_pointer_width = "32", target_arch = "mips64"),
422    )))]
423    unsafe {
424        let mut stat = MaybeUninit::<Stat>::uninit();
425        ret(libc_fstatat(
426            borrowed_fd(dirfd),
427            c_str(path),
428            stat.as_mut_ptr(),
429            flags.bits(),
430        ))?;
431        Ok(stat.assume_init())
432    }
433}
434
435#[cfg(all(
436    any(target_os = "android", target_os = "linux"),
437    any(target_pointer_width = "32", target_arch = "mips64"),
438))]
439fn statat_old(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<Stat> {
440    unsafe {
441        let mut result = MaybeUninit::<c::stat64>::uninit();
442        ret(libc_fstatat(
443            borrowed_fd(dirfd),
444            c_str(path),
445            result.as_mut_ptr(),
446            flags.bits(),
447        ))?;
448        stat64_to_stat(result.assume_init())
449    }
450}
451
452#[cfg(not(any(
453    target_os = "emscripten",
454    target_os = "illumos",
455    target_os = "redox",
456    target_os = "solaris",
457)))]
458pub(crate) fn accessat(
459    dirfd: BorrowedFd<'_>,
460    path: &CStr,
461    access: Access,
462    flags: AtFlags,
463) -> io::Result<()> {
464    unsafe {
465        ret(c::faccessat(
466            borrowed_fd(dirfd),
467            c_str(path),
468            access.bits(),
469            flags.bits(),
470        ))
471    }
472}
473
474#[cfg(target_os = "emscripten")]
475pub(crate) fn accessat(
476    _dirfd: BorrowedFd<'_>,
477    _path: &CStr,
478    _access: Access,
479    _flags: AtFlags,
480) -> io::Result<()> {
481    Ok(())
482}
483
484#[cfg(not(target_os = "redox"))]
485pub(crate) fn utimensat(
486    dirfd: BorrowedFd<'_>,
487    path: &CStr,
488    times: &Timestamps,
489    flags: AtFlags,
490) -> io::Result<()> {
491    // 32-bit gnu version: libc has `utimensat` but it is not y2038 safe by
492    // default.
493    #[cfg(all(
494        any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
495        target_env = "gnu",
496    ))]
497    unsafe {
498        if let Some(libc_utimensat) = __utimensat64.get() {
499            let libc_times: [LibcTimespec; 2] = [
500                times.last_access.clone().into(),
501                times.last_modification.clone().into(),
502            ];
503
504            ret(libc_utimensat(
505                borrowed_fd(dirfd),
506                c_str(path),
507                libc_times.as_ptr(),
508                flags.bits(),
509            ))
510        } else {
511            utimensat_old(dirfd, path, times, flags)
512        }
513    }
514
515    // Main version: libc is y2038 safe and has `utimensat`. Or, the platform
516    // is not y2038 safe and there's nothing practical we can do.
517    #[cfg(not(any(
518        target_os = "ios",
519        target_os = "macos",
520        all(
521            any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
522            target_env = "gnu",
523        )
524    )))]
525    unsafe {
526        // Assert that `Timestamps` has the expected layout.
527        let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone());
528
529        ret(c::utimensat(
530            borrowed_fd(dirfd),
531            c_str(path),
532            as_ptr(times).cast(),
533            flags.bits(),
534        ))
535    }
536
537    // `utimensat` was introduced in macOS 10.13.
538    #[cfg(any(target_os = "ios", target_os = "macos"))]
539    unsafe {
540        // ABI details
541        weak! {
542            fn utimensat(
543                c::c_int,
544                *const c::c_char,
545                *const c::timespec,
546                c::c_int
547            ) -> c::c_int
548        }
549        extern "C" {
550            fn setattrlist(
551                path: *const c::c_char,
552                attr_list: *const Attrlist,
553                attr_buf: *const c::c_void,
554                attr_buf_size: c::size_t,
555                options: c::c_ulong,
556            ) -> c::c_int;
557        }
558        const FSOPT_NOFOLLOW: c::c_ulong = 0x0000_0001;
559
560        // If we have `utimensat`, use it.
561        if let Some(have_utimensat) = utimensat.get() {
562            // Assert that `Timestamps` has the expected layout.
563            let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone());
564
565            return ret(have_utimensat(
566                borrowed_fd(dirfd),
567                c_str(path),
568                as_ptr(times).cast(),
569                flags.bits(),
570            ));
571        }
572
573        // `setattrlistat` was introduced in 10.13 along with `utimensat`, so if
574        // we don't have `utimensat`, we don't have `setattrlistat` either.
575        // Emulate it using `fork`, and `fchdir` and [`setattrlist`].
576        //
577        // [`setattrlist`]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/setattrlist.2.html
578        match c::fork() {
579            -1 => Err(io::Errno::IO),
580            0 => {
581                if c::fchdir(borrowed_fd(dirfd)) != 0 {
582                    let code = match libc_errno::errno().0 {
583                        c::EACCES => 2,
584                        c::ENOTDIR => 3,
585                        _ => 1,
586                    };
587                    c::_exit(code);
588                }
589
590                let mut flags_arg = 0;
591                if flags.contains(AtFlags::SYMLINK_NOFOLLOW) {
592                    flags_arg |= FSOPT_NOFOLLOW;
593                }
594
595                let (attrbuf_size, times, attrs) = times_to_attrlist(times);
596
597                if setattrlist(
598                    c_str(path),
599                    &attrs,
600                    as_ptr(&times).cast(),
601                    attrbuf_size,
602                    flags_arg,
603                ) != 0
604                {
605                    // Translate expected errno codes into ad-hoc integer
606                    // values suitable for exit statuses.
607                    let code = match libc_errno::errno().0 {
608                        c::EACCES => 2,
609                        c::ENOTDIR => 3,
610                        c::EPERM => 4,
611                        c::EROFS => 5,
612                        c::ELOOP => 6,
613                        c::ENOENT => 7,
614                        c::ENAMETOOLONG => 8,
615                        c::EINVAL => 9,
616                        c::ESRCH => 10,
617                        c::ENOTSUP => 11,
618                        _ => 1,
619                    };
620                    c::_exit(code);
621                }
622
623                c::_exit(0);
624            }
625            child_pid => {
626                let mut wstatus = 0;
627                let _ = ret_c_int(c::waitpid(child_pid, &mut wstatus, 0))?;
628                if c::WIFEXITED(wstatus) {
629                    // Translate our ad-hoc exit statuses back to errno codes.
630                    match c::WEXITSTATUS(wstatus) {
631                        0 => Ok(()),
632                        2 => Err(io::Errno::ACCESS),
633                        3 => Err(io::Errno::NOTDIR),
634                        4 => Err(io::Errno::PERM),
635                        5 => Err(io::Errno::ROFS),
636                        6 => Err(io::Errno::LOOP),
637                        7 => Err(io::Errno::NOENT),
638                        8 => Err(io::Errno::NAMETOOLONG),
639                        9 => Err(io::Errno::INVAL),
640                        10 => Err(io::Errno::SRCH),
641                        11 => Err(io::Errno::NOTSUP),
642                        _ => Err(io::Errno::IO),
643                    }
644                } else {
645                    Err(io::Errno::IO)
646                }
647            }
648        }
649    }
650}
651
652#[cfg(all(
653    any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
654    target_env = "gnu",
655))]
656unsafe fn utimensat_old(
657    dirfd: BorrowedFd<'_>,
658    path: &CStr,
659    times: &Timestamps,
660    flags: AtFlags,
661) -> io::Result<()> {
662    let old_times = [
663        c::timespec {
664            tv_sec: times
665                .last_access
666                .tv_sec
667                .try_into()
668                .map_err(|_| io::Errno::OVERFLOW)?,
669            tv_nsec: times.last_access.tv_nsec,
670        },
671        c::timespec {
672            tv_sec: times
673                .last_modification
674                .tv_sec
675                .try_into()
676                .map_err(|_| io::Errno::OVERFLOW)?,
677            tv_nsec: times.last_modification.tv_nsec,
678        },
679    ];
680    ret(c::utimensat(
681        borrowed_fd(dirfd),
682        c_str(path),
683        old_times.as_ptr(),
684        flags.bits(),
685    ))
686}
687
688#[cfg(not(any(
689    target_os = "android",
690    target_os = "linux",
691    target_os = "redox",
692    target_os = "wasi",
693)))]
694pub(crate) fn chmodat(dirfd: BorrowedFd<'_>, path: &CStr, mode: Mode) -> io::Result<()> {
695    unsafe { ret(c::fchmodat(borrowed_fd(dirfd), c_str(path), mode.bits(), 0)) }
696}
697
698#[cfg(any(target_os = "android", target_os = "linux"))]
699pub(crate) fn chmodat(dirfd: BorrowedFd<'_>, path: &CStr, mode: Mode) -> io::Result<()> {
700    // Linux's `fchmodat` does not have a flags argument.
701    unsafe {
702        // Pass `mode` as a `c_uint` even if `mode_t` is narrower, since
703        // `libc_openat` is declared as a variadic function and narrower
704        // arguments are promoted.
705        syscall_ret(c::syscall(
706            c::SYS_fchmodat,
707            borrowed_fd(dirfd),
708            c_str(path),
709            c::c_uint::from(mode.bits()),
710        ))
711    }
712}
713
714#[cfg(any(target_os = "ios", target_os = "macos"))]
715pub(crate) fn fclonefileat(
716    srcfd: BorrowedFd<'_>,
717    dst_dirfd: BorrowedFd<'_>,
718    dst: &CStr,
719    flags: CloneFlags,
720) -> io::Result<()> {
721    syscall! {
722        fn fclonefileat(
723            srcfd: BorrowedFd<'_>,
724            dst_dirfd: BorrowedFd<'_>,
725            dst: *const c::c_char,
726            flags: c::c_int
727        ) via SYS_fclonefileat -> c::c_int
728    }
729
730    unsafe { ret(fclonefileat(srcfd, dst_dirfd, c_str(dst), flags.bits())) }
731}
732
733#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
734pub(crate) fn chownat(
735    dirfd: BorrowedFd<'_>,
736    path: &CStr,
737    owner: Option<Uid>,
738    group: Option<Gid>,
739    flags: AtFlags,
740) -> io::Result<()> {
741    unsafe {
742        let (ow, gr) = crate::process::translate_fchown_args(owner, group);
743        ret(c::fchownat(
744            borrowed_fd(dirfd),
745            c_str(path),
746            ow,
747            gr,
748            flags.bits(),
749        ))
750    }
751}
752
753#[cfg(not(any(
754    target_os = "ios",
755    target_os = "macos",
756    target_os = "redox",
757    target_os = "wasi",
758)))]
759pub(crate) fn mknodat(
760    dirfd: BorrowedFd<'_>,
761    path: &CStr,
762    file_type: FileType,
763    mode: Mode,
764    dev: Dev,
765) -> io::Result<()> {
766    unsafe {
767        ret(c::mknodat(
768            borrowed_fd(dirfd),
769            c_str(path),
770            (mode.bits() | file_type.as_raw_mode()) as c::mode_t,
771            dev.try_into().map_err(|_e| io::Errno::PERM)?,
772        ))
773    }
774}
775
776#[cfg(any(target_os = "android", target_os = "linux"))]
777pub(crate) fn copy_file_range(
778    fd_in: BorrowedFd<'_>,
779    off_in: Option<&mut u64>,
780    fd_out: BorrowedFd<'_>,
781    off_out: Option<&mut u64>,
782    len: u64,
783) -> io::Result<u64> {
784    assert_eq!(size_of::<c::loff_t>(), size_of::<u64>());
785
786    let mut off_in_val: c::loff_t = 0;
787    let mut off_out_val: c::loff_t = 0;
788    // Silently cast; we'll get `EINVAL` if the value is negative.
789    let off_in_ptr = if let Some(off_in) = &off_in {
790        off_in_val = (**off_in) as i64;
791        &mut off_in_val
792    } else {
793        null_mut()
794    };
795    let off_out_ptr = if let Some(off_out) = &off_out {
796        off_out_val = (**off_out) as i64;
797        &mut off_out_val
798    } else {
799        null_mut()
800    };
801    let len: usize = len.try_into().unwrap_or(usize::MAX);
802    let copied = unsafe {
803        syscall_ret_ssize_t(c::syscall(
804            c::SYS_copy_file_range,
805            borrowed_fd(fd_in),
806            off_in_ptr,
807            borrowed_fd(fd_out),
808            off_out_ptr,
809            len,
810            0, // no flags are defined yet
811        ))?
812    };
813    if let Some(off_in) = off_in {
814        *off_in = off_in_val as u64;
815    }
816    if let Some(off_out) = off_out {
817        *off_out = off_out_val as u64;
818    }
819    Ok(copied as u64)
820}
821
822#[cfg(not(any(
823    target_os = "dragonfly",
824    target_os = "haiku",
825    target_os = "illumos",
826    target_os = "ios",
827    target_os = "macos",
828    target_os = "netbsd",
829    target_os = "openbsd",
830    target_os = "redox",
831    target_os = "solaris",
832)))]
833pub(crate) fn fadvise(fd: BorrowedFd<'_>, offset: u64, len: u64, advice: Advice) -> io::Result<()> {
834    let offset = offset as i64;
835    let len = len as i64;
836
837    // FreeBSD returns `EINVAL` on invalid offsets; emulate the POSIX behavior.
838    #[cfg(target_os = "freebsd")]
839    let offset = if (offset as i64) < 0 {
840        i64::MAX
841    } else {
842        offset
843    };
844
845    // FreeBSD returns `EINVAL` on overflow; emulate the POSIX behavior.
846    #[cfg(target_os = "freebsd")]
847    let len = if len > 0 && offset.checked_add(len).is_none() {
848        i64::MAX - offset
849    } else {
850        len
851    };
852
853    let err = unsafe { libc_posix_fadvise(borrowed_fd(fd), offset, len, advice as c::c_int) };
854
855    // `posix_fadvise` returns its error status rather than using `errno`.
856    if err == 0 {
857        Ok(())
858    } else {
859        Err(io::Errno(err))
860    }
861}
862
863pub(crate) fn fcntl_getfl(fd: BorrowedFd<'_>) -> io::Result<OFlags> {
864    unsafe { ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GETFL)).map(OFlags::from_bits_truncate) }
865}
866
867pub(crate) fn fcntl_setfl(fd: BorrowedFd<'_>, flags: OFlags) -> io::Result<()> {
868    unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_SETFL, flags.bits())) }
869}
870
871#[cfg(any(
872    target_os = "android",
873    target_os = "freebsd",
874    target_os = "fuchsia",
875    target_os = "linux",
876))]
877pub(crate) fn fcntl_get_seals(fd: BorrowedFd<'_>) -> io::Result<SealFlags> {
878    unsafe {
879        ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GET_SEALS))
880            .map(|flags| SealFlags::from_bits_unchecked(flags))
881    }
882}
883
884#[cfg(any(
885    target_os = "android",
886    target_os = "freebsd",
887    target_os = "fuchsia",
888    target_os = "linux",
889))]
890pub(crate) fn fcntl_add_seals(fd: BorrowedFd<'_>, seals: SealFlags) -> io::Result<()> {
891    unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_ADD_SEALS, seals.bits())) }
892}
893
894pub(crate) fn seek(fd: BorrowedFd<'_>, pos: SeekFrom) -> io::Result<u64> {
895    let (whence, offset): (c::c_int, libc_off_t) = match pos {
896        SeekFrom::Start(pos) => {
897            let pos: u64 = pos;
898            // Silently cast; we'll get `EINVAL` if the value is negative.
899            (c::SEEK_SET, pos as i64)
900        }
901        SeekFrom::End(offset) => (c::SEEK_END, offset),
902        SeekFrom::Current(offset) => (c::SEEK_CUR, offset),
903    };
904    let offset = unsafe { ret_off_t(libc_lseek(borrowed_fd(fd), offset, whence))? };
905    Ok(offset as u64)
906}
907
908pub(crate) fn tell(fd: BorrowedFd<'_>) -> io::Result<u64> {
909    let offset = unsafe { ret_off_t(libc_lseek(borrowed_fd(fd), 0, c::SEEK_CUR))? };
910    Ok(offset as u64)
911}
912
913#[cfg(not(any(target_os = "android", target_os = "linux", target_os = "wasi")))]
914pub(crate) fn fchmod(fd: BorrowedFd<'_>, mode: Mode) -> io::Result<()> {
915    unsafe { ret(c::fchmod(borrowed_fd(fd), mode.bits())) }
916}
917
918#[cfg(any(target_os = "android", target_os = "linux"))]
919pub(crate) fn fchmod(fd: BorrowedFd<'_>, mode: Mode) -> io::Result<()> {
920    // Use `c::syscall` rather than `c::fchmod` because some libc
921    // implementations, such as musl, add extra logic to `fchmod` to emulate
922    // support for `O_PATH`, which uses `/proc` outside our control and
923    // interferes with our own use of `O_PATH`.
924    unsafe {
925        syscall_ret(c::syscall(
926            c::SYS_fchmod,
927            borrowed_fd(fd),
928            c::c_uint::from(mode.bits()),
929        ))
930    }
931}
932
933#[cfg(any(target_os = "android", target_os = "linux"))]
934pub(crate) fn fchown(fd: BorrowedFd<'_>, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> {
935    // Use `c::syscall` rather than `c::fchown` because some libc
936    // implementations, such as musl, add extra logic to `fchown` to emulate
937    // support for `O_PATH`, which uses `/proc` outside our control and
938    // interferes with our own use of `O_PATH`.
939    unsafe {
940        let (ow, gr) = crate::process::translate_fchown_args(owner, group);
941        syscall_ret(c::syscall(c::SYS_fchown, borrowed_fd(fd), ow, gr))
942    }
943}
944
945#[cfg(not(any(target_os = "android", target_os = "linux", target_os = "wasi")))]
946pub(crate) fn fchown(fd: BorrowedFd<'_>, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> {
947    unsafe {
948        let (ow, gr) = crate::process::translate_fchown_args(owner, group);
949        ret(c::fchown(borrowed_fd(fd), ow, gr))
950    }
951}
952
953#[cfg(not(any(target_os = "solaris", target_os = "wasi")))]
954pub(crate) fn flock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> {
955    unsafe { ret(c::flock(borrowed_fd(fd), operation as c::c_int)) }
956}
957
958pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result<Stat> {
959    // 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use
960    // `statx`.
961    #[cfg(all(
962        any(target_os = "android", target_os = "linux"),
963        any(target_pointer_width = "32", target_arch = "mips64"),
964    ))]
965    {
966        match statx(fd, cstr!(""), AtFlags::EMPTY_PATH, StatxFlags::BASIC_STATS) {
967            Ok(x) => statx_to_stat(x),
968            Err(io::Errno::NOSYS) => fstat_old(fd),
969            Err(err) => Err(err),
970        }
971    }
972
973    // Main version: libc is y2038 safe. Or, the platform is not y2038 safe and
974    // there's nothing practical we can do.
975    #[cfg(not(all(
976        any(target_os = "android", target_os = "linux"),
977        any(target_pointer_width = "32", target_arch = "mips64"),
978    )))]
979    unsafe {
980        let mut stat = MaybeUninit::<Stat>::uninit();
981        ret(libc_fstat(borrowed_fd(fd), stat.as_mut_ptr()))?;
982        Ok(stat.assume_init())
983    }
984}
985
986#[cfg(all(
987    any(target_os = "android", target_os = "linux"),
988    any(target_pointer_width = "32", target_arch = "mips64"),
989))]
990fn fstat_old(fd: BorrowedFd<'_>) -> io::Result<Stat> {
991    unsafe {
992        let mut result = MaybeUninit::<c::stat64>::uninit();
993        ret(libc_fstat(borrowed_fd(fd), result.as_mut_ptr()))?;
994        stat64_to_stat(result.assume_init())
995    }
996}
997
998#[cfg(not(any(
999    target_os = "haiku",
1000    target_os = "illumos",
1001    target_os = "netbsd",
1002    target_os = "redox",
1003    target_os = "solaris",
1004    target_os = "wasi",
1005)))]
1006pub(crate) fn fstatfs(fd: BorrowedFd<'_>) -> io::Result<StatFs> {
1007    let mut statfs = MaybeUninit::<StatFs>::uninit();
1008    unsafe {
1009        ret(libc_fstatfs(borrowed_fd(fd), statfs.as_mut_ptr()))?;
1010        Ok(statfs.assume_init())
1011    }
1012}
1013
1014#[cfg(not(any(
1015    target_os = "haiku",
1016    target_os = "illumos",
1017    target_os = "redox",
1018    target_os = "solaris",
1019    target_os = "wasi",
1020)))]
1021pub(crate) fn fstatvfs(fd: BorrowedFd<'_>) -> io::Result<StatVfs> {
1022    let mut statvfs = MaybeUninit::<libc_statvfs>::uninit();
1023    unsafe {
1024        ret(libc_fstatvfs(borrowed_fd(fd), statvfs.as_mut_ptr()))?;
1025        Ok(libc_statvfs_to_statvfs(statvfs.assume_init()))
1026    }
1027}
1028
1029#[cfg(not(any(
1030    target_os = "haiku",
1031    target_os = "illumos",
1032    target_os = "redox",
1033    target_os = "solaris",
1034    target_os = "wasi"
1035)))]
1036fn libc_statvfs_to_statvfs(from: libc_statvfs) -> StatVfs {
1037    StatVfs {
1038        f_bsize: from.f_bsize as u64,
1039        f_frsize: from.f_frsize as u64,
1040        f_blocks: from.f_blocks as u64,
1041        f_bfree: from.f_bfree as u64,
1042        f_bavail: from.f_bavail as u64,
1043        f_files: from.f_files as u64,
1044        f_ffree: from.f_ffree as u64,
1045        f_favail: from.f_ffree as u64,
1046        f_fsid: from.f_fsid as u64,
1047        f_flag: unsafe { StatVfsMountFlags::from_bits_unchecked(from.f_flag as u64) },
1048        f_namemax: from.f_namemax as u64,
1049    }
1050}
1051
1052pub(crate) fn futimens(fd: BorrowedFd<'_>, times: &Timestamps) -> io::Result<()> {
1053    // 32-bit gnu version: libc has `futimens` but it is not y2038 safe by default.
1054    #[cfg(all(
1055        any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
1056        target_env = "gnu",
1057    ))]
1058    unsafe {
1059        if let Some(libc_futimens) = __futimens64.get() {
1060            let libc_times: [LibcTimespec; 2] = [
1061                times.last_access.clone().into(),
1062                times.last_modification.clone().into(),
1063            ];
1064
1065            ret(libc_futimens(borrowed_fd(fd), libc_times.as_ptr()))
1066        } else {
1067            futimens_old(fd, times)
1068        }
1069    }
1070
1071    // Main version: libc is y2038 safe and has `futimens`. Or, the platform
1072    // is not y2038 safe and there's nothing practical we can do.
1073    #[cfg(not(any(
1074        target_os = "ios",
1075        target_os = "macos",
1076        all(
1077            any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
1078            target_env = "gnu",
1079        )
1080    )))]
1081    unsafe {
1082        // Assert that `Timestamps` has the expected layout.
1083        let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone());
1084
1085        ret(c::futimens(borrowed_fd(fd), as_ptr(times).cast()))
1086    }
1087
1088    // `futimens` was introduced in macOS 10.13.
1089    #[cfg(any(target_os = "ios", target_os = "macos"))]
1090    unsafe {
1091        // ABI details.
1092        weak! {
1093            fn futimens(c::c_int, *const c::timespec) -> c::c_int
1094        }
1095        extern "C" {
1096            fn fsetattrlist(
1097                fd: c::c_int,
1098                attr_list: *const Attrlist,
1099                attr_buf: *const c::c_void,
1100                attr_buf_size: c::size_t,
1101                options: c::c_ulong,
1102            ) -> c::c_int;
1103        }
1104
1105        // If we have `futimens`, use it.
1106        if let Some(have_futimens) = futimens.get() {
1107            // Assert that `Timestamps` has the expected layout.
1108            let _ = core::mem::transmute::<Timestamps, [c::timespec; 2]>(times.clone());
1109
1110            return ret(have_futimens(borrowed_fd(fd), as_ptr(times).cast()));
1111        }
1112
1113        // Otherwise use `fsetattrlist`.
1114        let (attrbuf_size, times, attrs) = times_to_attrlist(times);
1115
1116        ret(fsetattrlist(
1117            borrowed_fd(fd),
1118            &attrs,
1119            as_ptr(&times).cast(),
1120            attrbuf_size,
1121            0,
1122        ))
1123    }
1124}
1125
1126#[cfg(all(
1127    any(target_arch = "arm", target_arch = "mips", target_arch = "x86"),
1128    target_env = "gnu",
1129))]
1130unsafe fn futimens_old(fd: BorrowedFd<'_>, times: &Timestamps) -> io::Result<()> {
1131    let old_times = [
1132        c::timespec {
1133            tv_sec: times
1134                .last_access
1135                .tv_sec
1136                .try_into()
1137                .map_err(|_| io::Errno::OVERFLOW)?,
1138            tv_nsec: times.last_access.tv_nsec,
1139        },
1140        c::timespec {
1141            tv_sec: times
1142                .last_modification
1143                .tv_sec
1144                .try_into()
1145                .map_err(|_| io::Errno::OVERFLOW)?,
1146            tv_nsec: times.last_modification.tv_nsec,
1147        },
1148    ];
1149
1150    ret(c::futimens(borrowed_fd(fd), old_times.as_ptr()))
1151}
1152
1153#[cfg(not(any(
1154    target_os = "aix",
1155    target_os = "dragonfly",
1156    target_os = "illumos",
1157    target_os = "ios",
1158    target_os = "macos",
1159    target_os = "netbsd",
1160    target_os = "openbsd",
1161    target_os = "redox",
1162    target_os = "solaris",
1163)))]
1164pub(crate) fn fallocate(
1165    fd: BorrowedFd<'_>,
1166    mode: FallocateFlags,
1167    offset: u64,
1168    len: u64,
1169) -> io::Result<()> {
1170    // Silently cast; we'll get `EINVAL` if the value is negative.
1171    let offset = offset as i64;
1172    let len = len as i64;
1173
1174    #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
1175    unsafe {
1176        ret(libc_fallocate(borrowed_fd(fd), mode.bits(), offset, len))
1177    }
1178
1179    #[cfg(not(any(target_os = "android", target_os = "fuchsia", target_os = "linux")))]
1180    {
1181        assert!(mode.is_empty());
1182        let err = unsafe { libc_posix_fallocate(borrowed_fd(fd), offset, len) };
1183
1184        // `posix_fallocate` returns its error status rather than using `errno`.
1185        if err == 0 {
1186            Ok(())
1187        } else {
1188            Err(io::Errno(err))
1189        }
1190    }
1191}
1192
1193#[cfg(any(target_os = "ios", target_os = "macos"))]
1194pub(crate) fn fallocate(
1195    fd: BorrowedFd<'_>,
1196    mode: FallocateFlags,
1197    offset: u64,
1198    len: u64,
1199) -> io::Result<()> {
1200    let offset: i64 = offset.try_into().map_err(|_e| io::Errno::INVAL)?;
1201    let len = len as i64;
1202
1203    assert!(mode.is_empty());
1204
1205    let new_len = offset.checked_add(len).ok_or(io::Errno::FBIG)?;
1206    let mut store = c::fstore_t {
1207        fst_flags: c::F_ALLOCATECONTIG,
1208        fst_posmode: c::F_PEOFPOSMODE,
1209        fst_offset: 0,
1210        fst_length: new_len,
1211        fst_bytesalloc: 0,
1212    };
1213    unsafe {
1214        if c::fcntl(borrowed_fd(fd), c::F_PREALLOCATE, &store) == -1 {
1215            store.fst_flags = c::F_ALLOCATEALL;
1216            let _ = ret_c_int(c::fcntl(borrowed_fd(fd), c::F_PREALLOCATE, &store))?;
1217        }
1218        ret(c::ftruncate(borrowed_fd(fd), new_len))
1219    }
1220}
1221
1222pub(crate) fn fsync(fd: BorrowedFd<'_>) -> io::Result<()> {
1223    unsafe { ret(c::fsync(borrowed_fd(fd))) }
1224}
1225
1226#[cfg(not(any(
1227    target_os = "dragonfly",
1228    target_os = "haiku",
1229    target_os = "ios",
1230    target_os = "macos",
1231    target_os = "redox",
1232)))]
1233pub(crate) fn fdatasync(fd: BorrowedFd<'_>) -> io::Result<()> {
1234    unsafe { ret(c::fdatasync(borrowed_fd(fd))) }
1235}
1236
1237pub(crate) fn ftruncate(fd: BorrowedFd<'_>, length: u64) -> io::Result<()> {
1238    let length = length.try_into().map_err(|_overflow_err| io::Errno::FBIG)?;
1239    unsafe { ret(libc_ftruncate(borrowed_fd(fd), length)) }
1240}
1241
1242#[cfg(any(target_os = "android", target_os = "freebsd", target_os = "linux"))]
1243pub(crate) fn memfd_create(path: &CStr, flags: MemfdFlags) -> io::Result<OwnedFd> {
1244    #[cfg(target_os = "freebsd")]
1245    weakcall! {
1246        fn memfd_create(
1247            name: *const c::c_char,
1248            flags: c::c_uint
1249        ) -> c::c_int
1250    }
1251
1252    #[cfg(any(target_os = "android", target_os = "linux"))]
1253    weak_or_syscall! {
1254        fn memfd_create(
1255            name: *const c::c_char,
1256            flags: c::c_uint
1257        ) via SYS_memfd_create -> c::c_int
1258    }
1259
1260    unsafe { ret_owned_fd(memfd_create(c_str(path), flags.bits())) }
1261}
1262
1263#[cfg(any(target_os = "android", target_os = "linux"))]
1264pub(crate) fn openat2(
1265    dirfd: BorrowedFd<'_>,
1266    path: &CStr,
1267    oflags: OFlags,
1268    mode: Mode,
1269    resolve: ResolveFlags,
1270) -> io::Result<OwnedFd> {
1271    let oflags: i32 = oflags.bits();
1272    let open_how = OpenHow {
1273        oflag: u64::from(oflags as u32),
1274        mode: u64::from(mode.bits()),
1275        resolve: resolve.bits(),
1276    };
1277
1278    unsafe {
1279        syscall_ret_owned_fd(c::syscall(
1280            SYS_OPENAT2,
1281            borrowed_fd(dirfd),
1282            c_str(path),
1283            &open_how,
1284            SIZEOF_OPEN_HOW,
1285        ))
1286    }
1287}
1288#[cfg(all(
1289    target_pointer_width = "32",
1290    any(target_os = "android", target_os = "linux"),
1291))]
1292const SYS_OPENAT2: i32 = 437;
1293#[cfg(all(
1294    target_pointer_width = "64",
1295    any(target_os = "android", target_os = "linux"),
1296))]
1297const SYS_OPENAT2: i64 = 437;
1298
1299#[cfg(any(target_os = "android", target_os = "linux"))]
1300#[repr(C)]
1301#[derive(Debug)]
1302struct OpenHow {
1303    oflag: u64,
1304    mode: u64,
1305    resolve: u64,
1306}
1307#[cfg(any(target_os = "android", target_os = "linux"))]
1308const SIZEOF_OPEN_HOW: usize = size_of::<OpenHow>();
1309
1310#[cfg(target_os = "linux")]
1311pub(crate) fn sendfile(
1312    out_fd: BorrowedFd<'_>,
1313    in_fd: BorrowedFd<'_>,
1314    offset: Option<&mut u64>,
1315    count: usize,
1316) -> io::Result<usize> {
1317    unsafe {
1318        let nsent = ret_ssize_t(c::sendfile64(
1319            borrowed_fd(out_fd),
1320            borrowed_fd(in_fd),
1321            offset.map_or(null_mut(), crate::utils::as_mut_ptr).cast(),
1322            count,
1323        ))?;
1324        Ok(nsent as usize)
1325    }
1326}
1327
1328/// Convert from a Linux `statx` value to rustix's `Stat`.
1329#[cfg(all(
1330    any(target_os = "android", target_os = "linux"),
1331    target_pointer_width = "32",
1332))]
1333fn statx_to_stat(x: crate::fs::Statx) -> io::Result<Stat> {
1334    Ok(Stat {
1335        st_dev: crate::fs::makedev(x.stx_dev_major, x.stx_dev_minor).into(),
1336        st_mode: x.stx_mode.into(),
1337        st_nlink: x.stx_nlink.into(),
1338        st_uid: x.stx_uid.into(),
1339        st_gid: x.stx_gid.into(),
1340        st_rdev: crate::fs::makedev(x.stx_rdev_major, x.stx_rdev_minor).into(),
1341        st_size: x.stx_size.try_into().map_err(|_| io::Errno::OVERFLOW)?,
1342        st_blksize: x.stx_blksize.into(),
1343        st_blocks: x.stx_blocks.into(),
1344        st_atime: x
1345            .stx_atime
1346            .tv_sec
1347            .try_into()
1348            .map_err(|_| io::Errno::OVERFLOW)?,
1349        st_atime_nsec: x.stx_atime.tv_nsec as _,
1350        st_mtime: x
1351            .stx_mtime
1352            .tv_sec
1353            .try_into()
1354            .map_err(|_| io::Errno::OVERFLOW)?,
1355        st_mtime_nsec: x.stx_mtime.tv_nsec as _,
1356        st_ctime: x
1357            .stx_ctime
1358            .tv_sec
1359            .try_into()
1360            .map_err(|_| io::Errno::OVERFLOW)?,
1361        st_ctime_nsec: x.stx_ctime.tv_nsec as _,
1362        st_ino: x.stx_ino.into(),
1363    })
1364}
1365
1366/// Convert from a Linux `statx` value to rustix's `Stat`.
1367///
1368/// mips64' `struct stat64` in libc has private fields, and `stx_blocks`
1369#[cfg(all(
1370    any(target_os = "android", target_os = "linux"),
1371    target_arch = "mips64",
1372))]
1373fn statx_to_stat(x: crate::fs::Statx) -> io::Result<Stat> {
1374    let mut result: Stat = unsafe { core::mem::zeroed() };
1375
1376    result.st_dev = crate::fs::makedev(x.stx_dev_major, x.stx_dev_minor);
1377    result.st_mode = x.stx_mode.into();
1378    result.st_nlink = x.stx_nlink.into();
1379    result.st_uid = x.stx_uid.into();
1380    result.st_gid = x.stx_gid.into();
1381    result.st_rdev = crate::fs::makedev(x.stx_rdev_major, x.stx_rdev_minor);
1382    result.st_size = x.stx_size.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1383    result.st_blksize = x.stx_blksize.into();
1384    result.st_blocks = x.stx_blocks.try_into().map_err(|_e| io::Errno::OVERFLOW)?;
1385    result.st_atime = x
1386        .stx_atime
1387        .tv_sec
1388        .try_into()
1389        .map_err(|_| io::Errno::OVERFLOW)?;
1390    result.st_atime_nsec = x.stx_atime.tv_nsec as _;
1391    result.st_mtime = x
1392        .stx_mtime
1393        .tv_sec
1394        .try_into()
1395        .map_err(|_| io::Errno::OVERFLOW)?;
1396    result.st_mtime_nsec = x.stx_mtime.tv_nsec as _;
1397    result.st_ctime = x
1398        .stx_ctime
1399        .tv_sec
1400        .try_into()
1401        .map_err(|_| io::Errno::OVERFLOW)?;
1402    result.st_ctime_nsec = x.stx_ctime.tv_nsec as _;
1403    result.st_ino = x.stx_ino.into();
1404
1405    Ok(result)
1406}
1407
1408/// Convert from a Linux `stat64` value to rustix's `Stat`.
1409#[cfg(all(
1410    any(target_os = "android", target_os = "linux"),
1411    target_pointer_width = "32",
1412))]
1413fn stat64_to_stat(s64: c::stat64) -> io::Result<Stat> {
1414    Ok(Stat {
1415        st_dev: s64.st_dev.try_into().map_err(|_| io::Errno::OVERFLOW)?,
1416        st_mode: s64.st_mode.try_into().map_err(|_| io::Errno::OVERFLOW)?,
1417        st_nlink: s64.st_nlink.try_into().map_err(|_| io::Errno::OVERFLOW)?,
1418        st_uid: s64.st_uid.try_into().map_err(|_| io::Errno::OVERFLOW)?,
1419        st_gid: s64.st_gid.try_into().map_err(|_| io::Errno::OVERFLOW)?,
1420        st_rdev: s64.st_rdev.try_into().map_err(|_| io::Errno::OVERFLOW)?,
1421        st_size: s64.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?,
1422        st_blksize: s64.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?,
1423        st_blocks: s64.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?,
1424        st_atime: s64.st_atime.try_into().map_err(|_| io::Errno::OVERFLOW)?,
1425        st_atime_nsec: s64
1426            .st_atime_nsec
1427            .try_into()
1428            .map_err(|_| io::Errno::OVERFLOW)?,
1429        st_mtime: s64.st_mtime.try_into().map_err(|_| io::Errno::OVERFLOW)?,
1430        st_mtime_nsec: s64
1431            .st_mtime_nsec
1432            .try_into()
1433            .map_err(|_| io::Errno::OVERFLOW)?,
1434        st_ctime: s64.st_ctime.try_into().map_err(|_| io::Errno::OVERFLOW)?,
1435        st_ctime_nsec: s64
1436            .st_ctime_nsec
1437            .try_into()
1438            .map_err(|_| io::Errno::OVERFLOW)?,
1439        st_ino: s64.st_ino.try_into().map_err(|_| io::Errno::OVERFLOW)?,
1440    })
1441}
1442
1443/// Convert from a Linux `stat64` value to rustix's `Stat`.
1444///
1445/// mips64' `struct stat64` in libc has private fields, and `st_blocks` has
1446/// type `i64`.
1447#[cfg(all(
1448    any(target_os = "android", target_os = "linux"),
1449    target_arch = "mips64",
1450))]
1451fn stat64_to_stat(s64: c::stat64) -> io::Result<Stat> {
1452    let mut result: Stat = unsafe { core::mem::zeroed() };
1453
1454    result.st_dev = s64.st_dev.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1455    result.st_mode = s64.st_mode.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1456    result.st_nlink = s64.st_nlink.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1457    result.st_uid = s64.st_uid.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1458    result.st_gid = s64.st_gid.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1459    result.st_rdev = s64.st_rdev.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1460    result.st_size = s64.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1461    result.st_blksize = s64.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1462    result.st_blocks = s64.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1463    result.st_atime = s64.st_atime.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1464    result.st_atime_nsec = s64
1465        .st_atime_nsec
1466        .try_into()
1467        .map_err(|_| io::Errno::OVERFLOW)?;
1468    result.st_mtime = s64.st_mtime.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1469    result.st_mtime_nsec = s64
1470        .st_mtime_nsec
1471        .try_into()
1472        .map_err(|_| io::Errno::OVERFLOW)?;
1473    result.st_ctime = s64.st_ctime.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1474    result.st_ctime_nsec = s64
1475        .st_ctime_nsec
1476        .try_into()
1477        .map_err(|_| io::Errno::OVERFLOW)?;
1478    result.st_ino = s64.st_ino.try_into().map_err(|_| io::Errno::OVERFLOW)?;
1479
1480    Ok(result)
1481}
1482
1483#[cfg(any(target_os = "android", target_os = "linux"))]
1484#[allow(non_upper_case_globals)]
1485mod sys {
1486    use super::{c, BorrowedFd, Statx};
1487
1488    #[cfg(all(target_os = "android", target_arch = "arm"))]
1489    const SYS_statx: c::c_long = 397;
1490    #[cfg(all(target_os = "android", target_arch = "x86"))]
1491    const SYS_statx: c::c_long = 383;
1492    #[cfg(all(target_os = "android", target_arch = "aarch64"))]
1493    const SYS_statx: c::c_long = 291;
1494    #[cfg(all(target_os = "android", target_arch = "x86_64"))]
1495    const SYS_statx: c::c_long = 332;
1496
1497    weak_or_syscall! {
1498        pub(super) fn statx(
1499            pirfd: BorrowedFd<'_>,
1500            path: *const c::c_char,
1501            flags: c::c_int,
1502            mask: c::c_uint,
1503            buf: *mut Statx
1504        ) via SYS_statx -> c::c_int
1505    }
1506}
1507
1508#[cfg(any(target_os = "android", target_os = "linux"))]
1509#[allow(non_upper_case_globals)]
1510pub(crate) fn statx(
1511    dirfd: BorrowedFd<'_>,
1512    path: &CStr,
1513    flags: AtFlags,
1514    mask: StatxFlags,
1515) -> io::Result<Statx> {
1516    // If a future Linux kernel adds more fields to `struct statx` and users
1517    // passing flags unknown to rustix in `StatxFlags`, we could end up
1518    // writing outside of the buffer. To prevent this possibility, we mask off
1519    // any flags that we don't know about.
1520    //
1521    // This includes `STATX__RESERVED`, which has a value that we know, but
1522    // which could take on arbitrary new meaning in the future. Linux currently
1523    // rejects this flag with `EINVAL`, so we do the same.
1524    //
1525    // This doesn't rely on `STATX_ALL` because [it's deprecated] and already
1526    // doesn't represent all the known flags.
1527    //
1528    // [it's deprecated]: https://patchwork.kernel.org/project/linux-fsdevel/patch/20200505095915.11275-7-mszeredi@redhat.com/
1529    #[cfg(not(any(target_os = "android", target_env = "musl")))]
1530    const STATX__RESERVED: u32 = libc::STATX__RESERVED as u32;
1531    #[cfg(any(target_os = "android", target_env = "musl"))]
1532    const STATX__RESERVED: u32 = linux_raw_sys::general::STATX__RESERVED;
1533    if (mask.bits() & STATX__RESERVED) == STATX__RESERVED {
1534        return Err(io::Errno::INVAL);
1535    }
1536    let mask = mask & StatxFlags::all();
1537
1538    let mut statx_buf = MaybeUninit::<Statx>::uninit();
1539    unsafe {
1540        ret(sys::statx(
1541            dirfd,
1542            c_str(path),
1543            flags.bits(),
1544            mask.bits(),
1545            statx_buf.as_mut_ptr(),
1546        ))?;
1547        Ok(statx_buf.assume_init())
1548    }
1549}
1550
1551#[cfg(any(target_os = "android", target_os = "linux"))]
1552#[inline]
1553pub(crate) fn is_statx_available() -> bool {
1554    unsafe {
1555        // Call `statx` with null pointers so that if it fails for any reason
1556        // other than `EFAULT`, we know it's not supported.
1557        matches!(
1558            ret(sys::statx(cwd(), null(), 0, 0, null_mut())),
1559            Err(io::Errno::FAULT)
1560        )
1561    }
1562}
1563
1564#[cfg(any(target_os = "ios", target_os = "macos"))]
1565pub(crate) unsafe fn fcopyfile(
1566    from: BorrowedFd<'_>,
1567    to: BorrowedFd<'_>,
1568    state: copyfile_state_t,
1569    flags: CopyfileFlags,
1570) -> io::Result<()> {
1571    extern "C" {
1572        fn fcopyfile(
1573            from: c::c_int,
1574            to: c::c_int,
1575            state: copyfile_state_t,
1576            flags: c::c_uint,
1577        ) -> c::c_int;
1578    }
1579
1580    nonnegative_ret(fcopyfile(
1581        borrowed_fd(from),
1582        borrowed_fd(to),
1583        state,
1584        flags.bits(),
1585    ))
1586}
1587
1588#[cfg(any(target_os = "ios", target_os = "macos"))]
1589pub(crate) fn copyfile_state_alloc() -> io::Result<copyfile_state_t> {
1590    extern "C" {
1591        fn copyfile_state_alloc() -> copyfile_state_t;
1592    }
1593
1594    let result = unsafe { copyfile_state_alloc() };
1595    if result.0.is_null() {
1596        Err(io::Errno::last_os_error())
1597    } else {
1598        Ok(result)
1599    }
1600}
1601
1602#[cfg(any(target_os = "ios", target_os = "macos"))]
1603pub(crate) unsafe fn copyfile_state_free(state: copyfile_state_t) -> io::Result<()> {
1604    extern "C" {
1605        fn copyfile_state_free(state: copyfile_state_t) -> c::c_int;
1606    }
1607
1608    nonnegative_ret(copyfile_state_free(state))
1609}
1610
1611#[cfg(any(target_os = "ios", target_os = "macos"))]
1612const COPYFILE_STATE_COPIED: u32 = 8;
1613
1614#[cfg(any(target_os = "ios", target_os = "macos"))]
1615pub(crate) unsafe fn copyfile_state_get_copied(state: copyfile_state_t) -> io::Result<u64> {
1616    let mut copied = MaybeUninit::<u64>::uninit();
1617    copyfile_state_get(state, COPYFILE_STATE_COPIED, copied.as_mut_ptr().cast())?;
1618    Ok(copied.assume_init())
1619}
1620
1621#[cfg(any(target_os = "ios", target_os = "macos"))]
1622pub(crate) unsafe fn copyfile_state_get(
1623    state: copyfile_state_t,
1624    flag: u32,
1625    dst: *mut c::c_void,
1626) -> io::Result<()> {
1627    extern "C" {
1628        fn copyfile_state_get(state: copyfile_state_t, flag: u32, dst: *mut c::c_void) -> c::c_int;
1629    }
1630
1631    nonnegative_ret(copyfile_state_get(state, flag, dst))
1632}
1633
1634#[cfg(any(target_os = "ios", target_os = "macos"))]
1635pub(crate) fn getpath(fd: BorrowedFd<'_>) -> io::Result<CString> {
1636    // The use of PATH_MAX is generally not encouraged, but it
1637    // is inevitable in this case because macOS defines `fcntl` with
1638    // `F_GETPATH` in terms of `MAXPATHLEN`, and there are no
1639    // alternatives. If a better method is invented, it should be used
1640    // instead.
1641    let mut buf = alloc::vec![0; c::PATH_MAX as usize];
1642
1643    // From the [macOS `fcntl` man page]:
1644    // `F_GETPATH` - Get the path of the file descriptor `Fildes`. The argument
1645    //               must be a buffer of size `MAXPATHLEN` or greater.
1646    //
1647    // [macOS `fcntl` man page]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
1648    unsafe {
1649        ret(c::fcntl(borrowed_fd(fd), c::F_GETPATH, buf.as_mut_ptr()))?;
1650    }
1651
1652    let l = buf.iter().position(|&c| c == 0).unwrap();
1653    buf.truncate(l);
1654
1655    // TODO: On Rust 1.56, we can use `shrink_to` here.
1656    //buf.shrink_to(l + 1);
1657    buf.shrink_to_fit();
1658
1659    Ok(CString::new(buf).unwrap())
1660}
1661
1662#[cfg(any(target_os = "ios", target_os = "macos"))]
1663pub(crate) fn fcntl_rdadvise(fd: BorrowedFd<'_>, offset: u64, len: u64) -> io::Result<()> {
1664    // From the [macOS `fcntl` man page]:
1665    // `F_RDADVISE` - Issue an advisory read async with no copy to user.
1666    //
1667    // The `F_RDADVISE` command operates on the following structure which holds
1668    // information passed from the user to the system:
1669    //
1670    // ```c
1671    // struct radvisory {
1672    //      off_t   ra_offset;  /* offset into the file */
1673    //      int     ra_count;   /* size of the read     */
1674    // };
1675    // ```
1676    //
1677    // [macOS `fcntl` man page]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html
1678    let ra_offset = match offset.try_into() {
1679        Ok(len) => len,
1680        // If this conversion fails, the user is providing an offset outside
1681        // any possible file extent, so just ignore it.
1682        Err(_) => return Ok(()),
1683    };
1684    let ra_count = match len.try_into() {
1685        Ok(len) => len,
1686        // If this conversion fails, the user is providing a dubiously large
1687        // hint which is unlikely to improve performance.
1688        Err(_) => return Ok(()),
1689    };
1690    unsafe {
1691        let radvisory = c::radvisory {
1692            ra_offset,
1693            ra_count,
1694        };
1695        ret(c::fcntl(borrowed_fd(fd), c::F_RDADVISE, &radvisory))
1696    }
1697}
1698
1699#[cfg(any(target_os = "ios", target_os = "macos"))]
1700pub(crate) fn fcntl_fullfsync(fd: BorrowedFd<'_>) -> io::Result<()> {
1701    unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_FULLFSYNC)) }
1702}
1703
1704/// Convert `times` from a `futimens`/`utimensat` argument into `setattrlist`
1705/// arguments.
1706#[cfg(any(target_os = "ios", target_os = "macos"))]
1707fn times_to_attrlist(times: &Timestamps) -> (c::size_t, [c::timespec; 2], Attrlist) {
1708    // ABI details.
1709    const ATTR_CMN_MODTIME: u32 = 0x0000_0400;
1710    const ATTR_CMN_ACCTIME: u32 = 0x0000_1000;
1711    const ATTR_BIT_MAP_COUNT: u16 = 5;
1712
1713    let mut times = times.clone();
1714
1715    // If we have any `UTIME_NOW` elements, replace them with the current time.
1716    if times.last_access.tv_nsec == c::UTIME_NOW || times.last_modification.tv_nsec == c::UTIME_NOW
1717    {
1718        let now = {
1719            let mut tv = c::timeval {
1720                tv_sec: 0,
1721                tv_usec: 0,
1722            };
1723            unsafe {
1724                let r = c::gettimeofday(&mut tv, null_mut());
1725                assert_eq!(r, 0);
1726            }
1727            c::timespec {
1728                tv_sec: tv.tv_sec,
1729                tv_nsec: (tv.tv_usec * 1000) as _,
1730            }
1731        };
1732        if times.last_access.tv_nsec == c::UTIME_NOW {
1733            times.last_access = now;
1734        }
1735        if times.last_modification.tv_nsec == c::UTIME_NOW {
1736            times.last_modification = now;
1737        }
1738    }
1739
1740    // Pack the return values following the rules for [`getattrlist`].
1741    //
1742    // [`getattrlist`]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getattrlist.2.html
1743    let mut times_size = 0;
1744    let mut attrs = Attrlist {
1745        bitmapcount: ATTR_BIT_MAP_COUNT,
1746        reserved: 0,
1747        commonattr: 0,
1748        volattr: 0,
1749        dirattr: 0,
1750        fileattr: 0,
1751        forkattr: 0,
1752    };
1753    let mut return_times = [c::timespec {
1754        tv_sec: 0,
1755        tv_nsec: 0,
1756    }; 2];
1757    let mut times_index = 0;
1758    if times.last_modification.tv_nsec != c::UTIME_OMIT {
1759        attrs.commonattr |= ATTR_CMN_MODTIME;
1760        return_times[times_index] = times.last_modification;
1761        times_index += 1;
1762        times_size += size_of::<c::timespec>();
1763    }
1764    if times.last_access.tv_nsec != c::UTIME_OMIT {
1765        attrs.commonattr |= ATTR_CMN_ACCTIME;
1766        return_times[times_index] = times.last_access;
1767        times_size += size_of::<c::timespec>();
1768    }
1769
1770    (times_size, return_times, attrs)
1771}
1772
1773/// Support type for `Attrlist`.
1774#[cfg(any(target_os = "ios", target_os = "macos"))]
1775type Attrgroup = u32;
1776
1777/// Attribute list for use with `setattrlist`.
1778#[cfg(any(target_os = "ios", target_os = "macos"))]
1779#[repr(C)]
1780struct Attrlist {
1781    bitmapcount: u16,
1782    reserved: u16,
1783    commonattr: Attrgroup,
1784    volattr: Attrgroup,
1785    dirattr: Attrgroup,
1786    fileattr: Attrgroup,
1787    forkattr: Attrgroup,
1788}
1789
1790#[cfg(any(target_os = "android", target_os = "linux"))]
1791pub(crate) fn mount(
1792    source: Option<&CStr>,
1793    target: &CStr,
1794    file_system_type: Option<&CStr>,
1795    flags: super::types::MountFlagsArg,
1796    data: Option<&CStr>,
1797) -> io::Result<()> {
1798    unsafe {
1799        ret(c::mount(
1800            source.map_or_else(null, CStr::as_ptr),
1801            target.as_ptr(),
1802            file_system_type.map_or_else(null, CStr::as_ptr),
1803            flags.0,
1804            data.map_or_else(null, CStr::as_ptr).cast(),
1805        ))
1806    }
1807}
1808