rustix/backend/linux_raw/fs/
syscalls.rs

1//! linux_raw syscalls supporting `rustix::fs`.
2//!
3//! # Safety
4//!
5//! See the `rustix::backend` module documentation for details.
6#![allow(unsafe_code)]
7#![allow(clippy::undocumented_unsafe_blocks)]
8
9use crate::backend::c;
10use crate::backend::conv::fs::oflags_for_open_how;
11#[cfg(any(
12    not(feature = "linux_4_11"),
13    target_arch = "aarch64",
14    target_arch = "riscv64",
15    target_arch = "mips",
16    target_arch = "mips32r6",
17))]
18use crate::backend::conv::zero;
19use crate::backend::conv::{
20    by_ref, c_int, c_uint, dev_t, opt_mut, pass_usize, raw_fd, ret, ret_c_int, ret_c_uint,
21    ret_infallible, ret_owned_fd, ret_usize, size_of, slice, slice_mut,
22};
23#[cfg(target_pointer_width = "64")]
24use crate::backend::conv::{loff_t, loff_t_from_u64, ret_u64};
25use crate::fd::{BorrowedFd, OwnedFd};
26use crate::ffi::CStr;
27#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
28use crate::fs::CWD;
29use crate::fs::{
30    inotify, Access, Advice, AtFlags, FallocateFlags, FileType, FlockOperation, Fsid, Gid,
31    MemfdFlags, Mode, OFlags, RenameFlags, ResolveFlags, SealFlags, SeekFrom, Stat, StatFs,
32    StatVfs, StatVfsMountFlags, Statx, StatxFlags, Timestamps, Uid, XattrFlags,
33};
34use crate::io;
35use core::mem::MaybeUninit;
36use core::num::NonZeroU64;
37#[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
38use linux_raw_sys::general::stat as linux_stat64;
39use linux_raw_sys::general::{
40    open_how, AT_EACCESS, AT_FDCWD, AT_REMOVEDIR, AT_SYMLINK_NOFOLLOW, F_ADD_SEALS, F_GETFL,
41    F_GET_SEALS, F_SETFL, SEEK_CUR, SEEK_DATA, SEEK_END, SEEK_HOLE, SEEK_SET, STATX__RESERVED,
42};
43#[cfg(target_pointer_width = "32")]
44use {
45    crate::backend::conv::{hi, lo, slice_just_addr},
46    linux_raw_sys::general::stat64 as linux_stat64,
47    linux_raw_sys::general::timespec as __kernel_old_timespec,
48};
49
50#[inline]
51pub(crate) fn open(path: &CStr, flags: OFlags, mode: Mode) -> io::Result<OwnedFd> {
52    // Always enable support for large files.
53    let flags = flags | OFlags::LARGEFILE;
54
55    #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
56    {
57        openat(CWD, path, flags, mode)
58    }
59    #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
60    unsafe {
61        ret_owned_fd(syscall_readonly!(__NR_open, path, flags, mode))
62    }
63}
64
65#[inline]
66pub(crate) fn openat(
67    dirfd: BorrowedFd<'_>,
68    path: &CStr,
69    flags: OFlags,
70    mode: Mode,
71) -> io::Result<OwnedFd> {
72    // Always enable support for large files.
73    let flags = flags | OFlags::LARGEFILE;
74
75    unsafe { ret_owned_fd(syscall_readonly!(__NR_openat, dirfd, path, flags, mode)) }
76}
77
78#[inline]
79pub(crate) fn openat2(
80    dirfd: BorrowedFd<'_>,
81    path: &CStr,
82    mut flags: OFlags,
83    mode: Mode,
84    resolve: ResolveFlags,
85) -> io::Result<OwnedFd> {
86    // Enable support for large files, but not with `O_PATH` because
87    // `openat2` doesn't like those flags together.
88    if !flags.contains(OFlags::PATH) {
89        flags |= OFlags::from_bits_retain(c::O_LARGEFILE);
90    }
91
92    unsafe {
93        ret_owned_fd(syscall_readonly!(
94            __NR_openat2,
95            dirfd,
96            path,
97            by_ref(&open_how {
98                flags: oflags_for_open_how(flags),
99                mode: u64::from(mode.bits()),
100                resolve: resolve.bits(),
101            }),
102            size_of::<open_how, _>()
103        ))
104    }
105}
106
107#[inline]
108pub(crate) fn chmod(path: &CStr, mode: Mode) -> io::Result<()> {
109    unsafe {
110        ret(syscall_readonly!(
111            __NR_fchmodat,
112            raw_fd(AT_FDCWD),
113            path,
114            mode
115        ))
116    }
117}
118
119#[inline]
120pub(crate) fn chmodat(
121    dirfd: BorrowedFd<'_>,
122    path: &CStr,
123    mode: Mode,
124    flags: AtFlags,
125) -> io::Result<()> {
126    if flags == AtFlags::SYMLINK_NOFOLLOW {
127        return Err(io::Errno::OPNOTSUPP);
128    }
129    if !flags.is_empty() {
130        return Err(io::Errno::INVAL);
131    }
132    unsafe { ret(syscall_readonly!(__NR_fchmodat, dirfd, path, mode)) }
133}
134
135#[inline]
136pub(crate) fn fchmod(fd: BorrowedFd<'_>, mode: Mode) -> io::Result<()> {
137    unsafe { ret(syscall_readonly!(__NR_fchmod, fd, mode)) }
138}
139
140#[inline]
141pub(crate) fn chownat(
142    dirfd: BorrowedFd<'_>,
143    path: &CStr,
144    owner: Option<Uid>,
145    group: Option<Gid>,
146    flags: AtFlags,
147) -> io::Result<()> {
148    unsafe {
149        let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
150        ret(syscall_readonly!(
151            __NR_fchownat,
152            dirfd,
153            path,
154            c_uint(ow),
155            c_uint(gr),
156            flags
157        ))
158    }
159}
160
161#[inline]
162pub(crate) fn chown(path: &CStr, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> {
163    // Most architectures have a `chown` syscall.
164    #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
165    unsafe {
166        let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
167        ret(syscall_readonly!(__NR_chown, path, c_uint(ow), c_uint(gr)))
168    }
169
170    // Aarch64 and RISC-V don't, so use `fchownat`.
171    #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
172    unsafe {
173        let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
174        ret(syscall_readonly!(
175            __NR_fchownat,
176            raw_fd(AT_FDCWD),
177            path,
178            c_uint(ow),
179            c_uint(gr),
180            zero()
181        ))
182    }
183}
184
185#[inline]
186pub(crate) fn fchown(fd: BorrowedFd<'_>, owner: Option<Uid>, group: Option<Gid>) -> io::Result<()> {
187    unsafe {
188        let (ow, gr) = crate::ugid::translate_fchown_args(owner, group);
189        ret(syscall_readonly!(__NR_fchown, fd, c_uint(ow), c_uint(gr)))
190    }
191}
192
193#[inline]
194pub(crate) fn mknodat(
195    dirfd: BorrowedFd<'_>,
196    path: &CStr,
197    file_type: FileType,
198    mode: Mode,
199    dev: u64,
200) -> io::Result<()> {
201    #[cfg(target_pointer_width = "32")]
202    unsafe {
203        ret(syscall_readonly!(
204            __NR_mknodat,
205            dirfd,
206            path,
207            (mode, file_type),
208            dev_t(dev)?
209        ))
210    }
211    #[cfg(target_pointer_width = "64")]
212    unsafe {
213        ret(syscall_readonly!(
214            __NR_mknodat,
215            dirfd,
216            path,
217            (mode, file_type),
218            dev_t(dev)
219        ))
220    }
221}
222
223#[inline]
224pub(crate) fn seek(fd: BorrowedFd<'_>, pos: SeekFrom) -> io::Result<u64> {
225    let (whence, offset) = match pos {
226        SeekFrom::Start(pos) => {
227            let pos: u64 = pos;
228            // Silently cast; we'll get `EINVAL` if the value is negative.
229            (SEEK_SET, pos as i64)
230        }
231        SeekFrom::End(offset) => (SEEK_END, offset),
232        SeekFrom::Current(offset) => (SEEK_CUR, offset),
233        SeekFrom::Data(pos) => {
234            let pos: u64 = pos;
235            // Silently cast; we'll get `EINVAL` if the value is negative.
236            (SEEK_DATA, pos as i64)
237        }
238        SeekFrom::Hole(pos) => {
239            let pos: u64 = pos;
240            // Silently cast; we'll get `EINVAL` if the value is negative.
241            (SEEK_HOLE, pos as i64)
242        }
243    };
244    _seek(fd, offset, whence)
245}
246
247#[inline]
248pub(crate) fn _seek(fd: BorrowedFd<'_>, offset: i64, whence: c::c_uint) -> io::Result<u64> {
249    #[cfg(target_pointer_width = "32")]
250    unsafe {
251        let mut result = MaybeUninit::<u64>::uninit();
252        ret(syscall!(
253            __NR__llseek,
254            fd,
255            // Don't use the hi/lo functions here because Linux's llseek
256            // takes its 64-bit argument differently from everything else.
257            pass_usize((offset >> 32) as usize),
258            pass_usize(offset as usize),
259            &mut result,
260            c_uint(whence)
261        ))?;
262        Ok(result.assume_init())
263    }
264    #[cfg(target_pointer_width = "64")]
265    unsafe {
266        ret_u64(syscall_readonly!(
267            __NR_lseek,
268            fd,
269            loff_t(offset),
270            c_uint(whence)
271        ))
272    }
273}
274
275#[inline]
276pub(crate) fn tell(fd: BorrowedFd<'_>) -> io::Result<u64> {
277    _seek(fd, 0, SEEK_CUR).map(|x| x as u64)
278}
279
280#[inline]
281pub(crate) fn ftruncate(fd: BorrowedFd<'_>, length: u64) -> io::Result<()> {
282    // <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/arm64/kernel/sys32.c?h=v6.13#n89>
283    #[cfg(all(
284        target_pointer_width = "32",
285        any(
286            target_arch = "arm",
287            target_arch = "mips",
288            target_arch = "mips32r6",
289            target_arch = "powerpc"
290        ),
291    ))]
292    unsafe {
293        ret(syscall_readonly!(
294            __NR_ftruncate64,
295            fd,
296            zero(),
297            hi(length),
298            lo(length)
299        ))
300    }
301    #[cfg(all(
302        target_pointer_width = "32",
303        not(any(
304            target_arch = "arm",
305            target_arch = "mips",
306            target_arch = "mips32r6",
307            target_arch = "powerpc"
308        )),
309    ))]
310    unsafe {
311        ret(syscall_readonly!(
312            __NR_ftruncate64,
313            fd,
314            hi(length),
315            lo(length)
316        ))
317    }
318    #[cfg(target_pointer_width = "64")]
319    unsafe {
320        ret(syscall_readonly!(
321            __NR_ftruncate,
322            fd,
323            loff_t_from_u64(length)
324        ))
325    }
326}
327
328#[inline]
329pub(crate) fn fallocate(
330    fd: BorrowedFd<'_>,
331    mode: FallocateFlags,
332    offset: u64,
333    len: u64,
334) -> io::Result<()> {
335    #[cfg(target_pointer_width = "32")]
336    unsafe {
337        ret(syscall_readonly!(
338            __NR_fallocate,
339            fd,
340            mode,
341            hi(offset),
342            lo(offset),
343            hi(len),
344            lo(len)
345        ))
346    }
347    #[cfg(target_pointer_width = "64")]
348    unsafe {
349        ret(syscall_readonly!(
350            __NR_fallocate,
351            fd,
352            mode,
353            loff_t_from_u64(offset),
354            loff_t_from_u64(len)
355        ))
356    }
357}
358
359#[inline]
360pub(crate) fn fadvise(
361    fd: BorrowedFd<'_>,
362    pos: u64,
363    len: Option<NonZeroU64>,
364    advice: Advice,
365) -> io::Result<()> {
366    let len = match len {
367        None => 0,
368        Some(len) => len.get(),
369    };
370
371    // On ARM, the arguments are reordered so that the `len` and `pos` argument
372    // pairs are aligned. And ARM has a custom syscall code for this.
373    #[cfg(target_arch = "arm")]
374    unsafe {
375        ret(syscall_readonly!(
376            __NR_arm_fadvise64_64,
377            fd,
378            advice,
379            hi(pos),
380            lo(pos),
381            hi(len),
382            lo(len)
383        ))
384    }
385
386    // On powerpc, the arguments are reordered as on ARM.
387    #[cfg(target_arch = "powerpc")]
388    unsafe {
389        ret(syscall_readonly!(
390            __NR_fadvise64_64,
391            fd,
392            advice,
393            hi(pos),
394            lo(pos),
395            hi(len),
396            lo(len)
397        ))
398    }
399
400    // On mips, the arguments are not reordered, and padding is inserted
401    // instead to ensure alignment.
402    #[cfg(any(target_arch = "mips", target_arch = "mips32r6"))]
403    unsafe {
404        ret(syscall_readonly!(
405            __NR_fadvise64,
406            fd,
407            zero(),
408            hi(pos),
409            lo(pos),
410            hi(len),
411            lo(len),
412            advice
413        ))
414    }
415
416    // For all other 32-bit architectures, use `fadvise64_64` so that we get a
417    // 64-bit length.
418    #[cfg(all(
419        target_pointer_width = "32",
420        not(any(
421            target_arch = "arm",
422            target_arch = "mips",
423            target_arch = "mips32r6",
424            target_arch = "powerpc"
425        )),
426    ))]
427    unsafe {
428        ret(syscall_readonly!(
429            __NR_fadvise64_64,
430            fd,
431            hi(pos),
432            lo(pos),
433            hi(len),
434            lo(len),
435            advice
436        ))
437    }
438
439    // On 64-bit architectures, use `fadvise64` which is sufficient.
440    #[cfg(target_pointer_width = "64")]
441    unsafe {
442        ret(syscall_readonly!(
443            __NR_fadvise64,
444            fd,
445            loff_t_from_u64(pos),
446            loff_t_from_u64(len),
447            advice
448        ))
449    }
450}
451
452#[inline]
453pub(crate) fn fsync(fd: BorrowedFd<'_>) -> io::Result<()> {
454    unsafe { ret(syscall_readonly!(__NR_fsync, fd)) }
455}
456
457#[inline]
458pub(crate) fn fdatasync(fd: BorrowedFd<'_>) -> io::Result<()> {
459    unsafe { ret(syscall_readonly!(__NR_fdatasync, fd)) }
460}
461
462#[inline]
463pub(crate) fn flock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> {
464    unsafe {
465        ret(syscall_readonly!(
466            __NR_flock,
467            fd,
468            c_uint(operation as c::c_uint)
469        ))
470    }
471}
472
473#[inline]
474pub(crate) fn syncfs(fd: BorrowedFd<'_>) -> io::Result<()> {
475    unsafe { ret(syscall_readonly!(__NR_syncfs, fd)) }
476}
477
478#[inline]
479pub(crate) fn sync() {
480    unsafe { ret_infallible(syscall_readonly!(__NR_sync)) }
481}
482
483#[inline]
484pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result<Stat> {
485    // 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use
486    // `statx`.
487    //
488    // And, some old platforms don't support `statx`, and some fail with a
489    // confusing error code, so we call `crate::fs::statx` to handle that. If
490    // `statx` isn't available, fall back to the buggy system call.
491    #[cfg(any(
492        target_pointer_width = "32",
493        target_arch = "mips64",
494        target_arch = "mips64r6"
495    ))]
496    {
497        match crate::fs::statx(fd, cstr!(""), AtFlags::EMPTY_PATH, StatxFlags::BASIC_STATS) {
498            Ok(x) => statx_to_stat(x),
499            Err(io::Errno::NOSYS) => fstat_old(fd),
500            Err(err) => Err(err),
501        }
502    }
503
504    #[cfg(all(
505        target_pointer_width = "64",
506        not(target_arch = "mips64"),
507        not(target_arch = "mips64r6")
508    ))]
509    unsafe {
510        let mut result = MaybeUninit::<Stat>::uninit();
511        ret(syscall!(__NR_fstat, fd, &mut result))?;
512        Ok(result.assume_init())
513    }
514}
515
516#[cfg(any(
517    target_pointer_width = "32",
518    target_arch = "mips64",
519    target_arch = "mips64r6",
520))]
521fn fstat_old(fd: BorrowedFd<'_>) -> io::Result<Stat> {
522    let mut result = MaybeUninit::<linux_stat64>::uninit();
523
524    #[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
525    unsafe {
526        ret(syscall!(__NR_fstat, fd, &mut result))?;
527        stat_to_stat(result.assume_init())
528    }
529
530    #[cfg(target_pointer_width = "32")]
531    unsafe {
532        ret(syscall!(__NR_fstat64, fd, &mut result))?;
533        stat_to_stat(result.assume_init())
534    }
535}
536
537#[inline]
538pub(crate) fn stat(path: &CStr) -> io::Result<Stat> {
539    // See the comments in `fstat` about using `crate::fs::statx` here.
540    #[cfg(any(
541        target_pointer_width = "32",
542        target_arch = "mips64",
543        target_arch = "mips64r6"
544    ))]
545    {
546        match crate::fs::statx(
547            crate::fs::CWD,
548            path,
549            AtFlags::empty(),
550            StatxFlags::BASIC_STATS,
551        ) {
552            Ok(x) => statx_to_stat(x),
553            Err(io::Errno::NOSYS) => stat_old(path),
554            Err(err) => Err(err),
555        }
556    }
557
558    #[cfg(all(
559        target_pointer_width = "64",
560        not(target_arch = "mips64"),
561        not(target_arch = "mips64r6"),
562    ))]
563    unsafe {
564        let mut result = MaybeUninit::<Stat>::uninit();
565        ret(syscall!(
566            __NR_newfstatat,
567            raw_fd(AT_FDCWD),
568            path,
569            &mut result,
570            c_uint(0)
571        ))?;
572        Ok(result.assume_init())
573    }
574}
575
576#[cfg(any(
577    target_pointer_width = "32",
578    target_arch = "mips64",
579    target_arch = "mips64r6"
580))]
581fn stat_old(path: &CStr) -> io::Result<Stat> {
582    let mut result = MaybeUninit::<linux_stat64>::uninit();
583
584    #[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
585    unsafe {
586        ret(syscall!(
587            __NR_newfstatat,
588            raw_fd(AT_FDCWD),
589            path,
590            &mut result,
591            c_uint(0)
592        ))?;
593        stat_to_stat(result.assume_init())
594    }
595
596    #[cfg(target_pointer_width = "32")]
597    unsafe {
598        ret(syscall!(
599            __NR_fstatat64,
600            raw_fd(AT_FDCWD),
601            path,
602            &mut result,
603            c_uint(0)
604        ))?;
605        stat_to_stat(result.assume_init())
606    }
607}
608
609#[inline]
610pub(crate) fn statat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<Stat> {
611    // See the comments in `fstat` about using `crate::fs::statx` here.
612    #[cfg(any(
613        target_pointer_width = "32",
614        target_arch = "mips64",
615        target_arch = "mips64r6"
616    ))]
617    {
618        match crate::fs::statx(dirfd, path, flags, StatxFlags::BASIC_STATS) {
619            Ok(x) => statx_to_stat(x),
620            Err(io::Errno::NOSYS) => statat_old(dirfd, path, flags),
621            Err(err) => Err(err),
622        }
623    }
624
625    #[cfg(all(
626        target_pointer_width = "64",
627        not(target_arch = "mips64"),
628        not(target_arch = "mips64r6"),
629    ))]
630    unsafe {
631        let mut result = MaybeUninit::<Stat>::uninit();
632        ret(syscall!(__NR_newfstatat, dirfd, path, &mut result, flags))?;
633        Ok(result.assume_init())
634    }
635}
636
637#[cfg(any(
638    target_pointer_width = "32",
639    target_arch = "mips64",
640    target_arch = "mips64r6"
641))]
642fn statat_old(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<Stat> {
643    let mut result = MaybeUninit::<linux_stat64>::uninit();
644
645    #[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
646    unsafe {
647        ret(syscall!(__NR_newfstatat, dirfd, path, &mut result, flags))?;
648        stat_to_stat(result.assume_init())
649    }
650
651    #[cfg(target_pointer_width = "32")]
652    unsafe {
653        ret(syscall!(__NR_fstatat64, dirfd, path, &mut result, flags))?;
654        stat_to_stat(result.assume_init())
655    }
656}
657
658#[inline]
659pub(crate) fn lstat(path: &CStr) -> io::Result<Stat> {
660    // See the comments in `fstat` about using `crate::fs::statx` here.
661    #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))]
662    {
663        match crate::fs::statx(
664            crate::fs::CWD,
665            path,
666            AtFlags::SYMLINK_NOFOLLOW,
667            StatxFlags::BASIC_STATS,
668        ) {
669            Ok(x) => statx_to_stat(x),
670            Err(io::Errno::NOSYS) => lstat_old(path),
671            Err(err) => Err(err),
672        }
673    }
674
675    #[cfg(all(target_pointer_width = "64", not(target_arch = "mips64")))]
676    unsafe {
677        let mut result = MaybeUninit::<Stat>::uninit();
678        ret(syscall!(
679            __NR_newfstatat,
680            raw_fd(AT_FDCWD),
681            path,
682            &mut result,
683            c_uint(AT_SYMLINK_NOFOLLOW)
684        ))?;
685        Ok(result.assume_init())
686    }
687}
688
689#[cfg(any(target_pointer_width = "32", target_arch = "mips64"))]
690fn lstat_old(path: &CStr) -> io::Result<Stat> {
691    let mut result = MaybeUninit::<linux_stat64>::uninit();
692
693    #[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
694    unsafe {
695        ret(syscall!(
696            __NR_newfstatat,
697            raw_fd(AT_FDCWD),
698            path,
699            &mut result,
700            c_uint(AT_SYMLINK_NOFOLLOW)
701        ))?;
702        stat_to_stat(result.assume_init())
703    }
704
705    #[cfg(target_pointer_width = "32")]
706    unsafe {
707        ret(syscall!(
708            __NR_fstatat64,
709            raw_fd(AT_FDCWD),
710            path,
711            &mut result,
712            c_uint(AT_SYMLINK_NOFOLLOW)
713        ))?;
714        stat_to_stat(result.assume_init())
715    }
716}
717
718/// Convert from a Linux `statx` value to rustix's `Stat`.
719#[cfg(any(
720    target_pointer_width = "32",
721    target_arch = "mips64",
722    target_arch = "mips64r6"
723))]
724fn statx_to_stat(x: crate::fs::Statx) -> io::Result<Stat> {
725    Ok(Stat {
726        st_dev: crate::fs::makedev(x.stx_dev_major, x.stx_dev_minor),
727        st_mode: x.stx_mode.into(),
728        st_nlink: x.stx_nlink.into(),
729        st_uid: x.stx_uid.into(),
730        st_gid: x.stx_gid.into(),
731        st_rdev: crate::fs::makedev(x.stx_rdev_major, x.stx_rdev_minor),
732        st_size: x.stx_size.try_into().map_err(|_| io::Errno::OVERFLOW)?,
733        st_blksize: x.stx_blksize.into(),
734        st_blocks: x.stx_blocks.into(),
735        st_atime: i64::from(x.stx_atime.tv_sec),
736        st_atime_nsec: x.stx_atime.tv_nsec.into(),
737        st_mtime: i64::from(x.stx_mtime.tv_sec),
738        st_mtime_nsec: x.stx_mtime.tv_nsec.into(),
739        st_ctime: i64::from(x.stx_ctime.tv_sec),
740        st_ctime_nsec: x.stx_ctime.tv_nsec.into(),
741        st_ino: x.stx_ino.into(),
742    })
743}
744
745/// Convert from a Linux `stat64` value to rustix's `Stat`.
746#[cfg(target_pointer_width = "32")]
747fn stat_to_stat(s64: linux_raw_sys::general::stat64) -> io::Result<Stat> {
748    Ok(Stat {
749        st_dev: s64.st_dev.try_into().map_err(|_| io::Errno::OVERFLOW)?,
750        st_mode: s64.st_mode.try_into().map_err(|_| io::Errno::OVERFLOW)?,
751        st_nlink: s64.st_nlink.try_into().map_err(|_| io::Errno::OVERFLOW)?,
752        st_uid: s64.st_uid.try_into().map_err(|_| io::Errno::OVERFLOW)?,
753        st_gid: s64.st_gid.try_into().map_err(|_| io::Errno::OVERFLOW)?,
754        st_rdev: s64.st_rdev.try_into().map_err(|_| io::Errno::OVERFLOW)?,
755        st_size: s64.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?,
756        st_blksize: s64.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?,
757        st_blocks: s64.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?,
758        st_atime: i64::from(s64.st_atime.to_signed()),
759        st_atime_nsec: s64
760            .st_atime_nsec
761            .try_into()
762            .map_err(|_| io::Errno::OVERFLOW)?,
763        st_mtime: i64::from(s64.st_mtime.to_signed()),
764        st_mtime_nsec: s64
765            .st_mtime_nsec
766            .try_into()
767            .map_err(|_| io::Errno::OVERFLOW)?,
768        st_ctime: i64::from(s64.st_ctime.to_signed()),
769        st_ctime_nsec: s64
770            .st_ctime_nsec
771            .try_into()
772            .map_err(|_| io::Errno::OVERFLOW)?,
773        st_ino: s64.st_ino.try_into().map_err(|_| io::Errno::OVERFLOW)?,
774    })
775}
776
777/// Convert from a Linux `stat` value to rustix's `Stat`.
778#[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
779fn stat_to_stat(s: linux_raw_sys::general::stat) -> io::Result<Stat> {
780    Ok(Stat {
781        st_dev: s.st_dev.try_into().map_err(|_| io::Errno::OVERFLOW)?,
782        st_mode: s.st_mode.try_into().map_err(|_| io::Errno::OVERFLOW)?,
783        st_nlink: s.st_nlink.try_into().map_err(|_| io::Errno::OVERFLOW)?,
784        st_uid: s.st_uid.try_into().map_err(|_| io::Errno::OVERFLOW)?,
785        st_gid: s.st_gid.try_into().map_err(|_| io::Errno::OVERFLOW)?,
786        st_rdev: s.st_rdev.try_into().map_err(|_| io::Errno::OVERFLOW)?,
787        st_size: s.st_size.try_into().map_err(|_| io::Errno::OVERFLOW)?,
788        st_blksize: s.st_blksize.try_into().map_err(|_| io::Errno::OVERFLOW)?,
789        st_blocks: s.st_blocks.try_into().map_err(|_| io::Errno::OVERFLOW)?,
790        st_atime: i64::from(s.st_atime.to_signed()),
791        st_atime_nsec: s
792            .st_atime_nsec
793            .try_into()
794            .map_err(|_| io::Errno::OVERFLOW)?,
795        st_mtime: i64::from(s.st_mtime.to_signed()),
796        st_mtime_nsec: s
797            .st_mtime_nsec
798            .try_into()
799            .map_err(|_| io::Errno::OVERFLOW)?,
800        st_ctime: i64::from(s.st_ctime.to_signed()),
801        st_ctime_nsec: s
802            .st_ctime_nsec
803            .try_into()
804            .map_err(|_| io::Errno::OVERFLOW)?,
805        st_ino: s.st_ino.try_into().map_err(|_| io::Errno::OVERFLOW)?,
806    })
807}
808
809#[inline]
810pub(crate) fn statx(
811    dirfd: BorrowedFd<'_>,
812    path: &CStr,
813    flags: AtFlags,
814    mask: StatxFlags,
815) -> io::Result<Statx> {
816    // If a future Linux kernel adds more fields to `struct statx` and users
817    // passing flags unknown to rustix in `StatxFlags`, we could end up
818    // writing outside of the buffer. To prevent this possibility, we mask off
819    // any flags that we don't know about.
820    //
821    // This includes `STATX__RESERVED`, which has a value that we know, but
822    // which could take on arbitrary new meaning in the future. Linux currently
823    // rejects this flag with `EINVAL`, so we do the same.
824    //
825    // This doesn't rely on `STATX_ALL` because [it's deprecated] and already
826    // doesn't represent all the known flags.
827    //
828    // [it's deprecated]: https://patchwork.kernel.org/project/linux-fsdevel/patch/20200505095915.11275-7-mszeredi@redhat.com/
829    if (mask.bits() & STATX__RESERVED) == STATX__RESERVED {
830        return Err(io::Errno::INVAL);
831    }
832    let mask = mask & StatxFlags::all();
833
834    unsafe {
835        let mut statx_buf = MaybeUninit::<Statx>::uninit();
836        ret(syscall!(
837            __NR_statx,
838            dirfd,
839            path,
840            flags,
841            mask,
842            &mut statx_buf
843        ))?;
844        Ok(statx_buf.assume_init())
845    }
846}
847
848#[cfg(not(feature = "linux_4_11"))]
849#[inline]
850pub(crate) fn is_statx_available() -> bool {
851    unsafe {
852        // Call `statx` with null pointers so that if it fails for any reason
853        // other than `EFAULT`, we know it's not supported. This can use
854        // "readonly" because we don't pass it a buffer to mutate.
855        matches!(
856            ret(syscall_readonly!(
857                __NR_statx,
858                raw_fd(AT_FDCWD),
859                zero(),
860                zero(),
861                zero(),
862                zero()
863            )),
864            Err(io::Errno::FAULT)
865        )
866    }
867}
868
869#[inline]
870pub(crate) fn fstatfs(fd: BorrowedFd<'_>) -> io::Result<StatFs> {
871    #[cfg(target_pointer_width = "32")]
872    unsafe {
873        let mut result = MaybeUninit::<StatFs>::uninit();
874        ret(syscall!(
875            __NR_fstatfs64,
876            fd,
877            size_of::<StatFs, _>(),
878            &mut result
879        ))?;
880        Ok(result.assume_init())
881    }
882
883    #[cfg(target_pointer_width = "64")]
884    unsafe {
885        let mut result = MaybeUninit::<StatFs>::uninit();
886        ret(syscall!(__NR_fstatfs, fd, &mut result))?;
887        Ok(result.assume_init())
888    }
889}
890
891#[inline]
892pub(crate) fn fstatvfs(fd: BorrowedFd<'_>) -> io::Result<StatVfs> {
893    // Linux doesn't have an `fstatvfs` syscall; we have to do `fstatfs` and
894    // translate the fields as best we can.
895    let statfs = fstatfs(fd)?;
896
897    Ok(statfs_to_statvfs(statfs))
898}
899
900#[inline]
901pub(crate) fn statfs(path: &CStr) -> io::Result<StatFs> {
902    #[cfg(target_pointer_width = "32")]
903    unsafe {
904        let mut result = MaybeUninit::<StatFs>::uninit();
905        ret(syscall!(
906            __NR_statfs64,
907            path,
908            size_of::<StatFs, _>(),
909            &mut result
910        ))?;
911        Ok(result.assume_init())
912    }
913    #[cfg(target_pointer_width = "64")]
914    unsafe {
915        let mut result = MaybeUninit::<StatFs>::uninit();
916        ret(syscall!(__NR_statfs, path, &mut result))?;
917        Ok(result.assume_init())
918    }
919}
920
921#[inline]
922pub(crate) fn statvfs(path: &CStr) -> io::Result<StatVfs> {
923    // Linux doesn't have a `statvfs` syscall; we have to do `statfs` and
924    // translate the fields as best we can.
925    let statfs = statfs(path)?;
926
927    Ok(statfs_to_statvfs(statfs))
928}
929
930fn statfs_to_statvfs(statfs: StatFs) -> StatVfs {
931    let Fsid { val } = Fsid {
932        val: statfs.f_fsid.val,
933    };
934    let [f_fsid_val0, f_fsid_val1]: [i32; 2] = val;
935
936    StatVfs {
937        f_bsize: statfs.f_bsize as u64,
938        f_frsize: if statfs.f_frsize != 0 {
939            statfs.f_frsize
940        } else {
941            statfs.f_bsize
942        } as u64,
943        f_blocks: statfs.f_blocks as u64,
944        f_bfree: statfs.f_bfree as u64,
945        f_bavail: statfs.f_bavail as u64,
946        f_files: statfs.f_files as u64,
947        f_ffree: statfs.f_ffree as u64,
948        f_favail: statfs.f_ffree as u64,
949        f_fsid: u64::from(f_fsid_val0 as u32) | (u64::from(f_fsid_val1 as u32) << 32),
950        f_flag: StatVfsMountFlags::from_bits_retain(statfs.f_flags as u64),
951        f_namemax: statfs.f_namelen as u64,
952    }
953}
954
955#[cfg(feature = "alloc")]
956#[inline]
957pub(crate) fn readlink(path: &CStr, buf: &mut [u8]) -> io::Result<usize> {
958    let (buf_addr_mut, buf_len) = slice_mut(buf);
959    unsafe {
960        ret_usize(syscall!(
961            __NR_readlinkat,
962            raw_fd(AT_FDCWD),
963            path,
964            buf_addr_mut,
965            buf_len
966        ))
967    }
968}
969
970#[inline]
971pub(crate) unsafe fn readlinkat(
972    dirfd: BorrowedFd<'_>,
973    path: &CStr,
974    buf: (*mut u8, usize),
975) -> io::Result<usize> {
976    ret_usize(syscall!(
977        __NR_readlinkat,
978        dirfd,
979        path,
980        buf.0,
981        pass_usize(buf.1)
982    ))
983}
984
985#[inline]
986pub(crate) fn fcntl_getfl(fd: BorrowedFd<'_>) -> io::Result<OFlags> {
987    #[cfg(target_pointer_width = "32")]
988    unsafe {
989        ret_c_uint(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_GETFL)))
990            .map(OFlags::from_bits_retain)
991    }
992    #[cfg(target_pointer_width = "64")]
993    unsafe {
994        ret_c_uint(syscall_readonly!(__NR_fcntl, fd, c_uint(F_GETFL))).map(OFlags::from_bits_retain)
995    }
996}
997
998#[inline]
999pub(crate) fn fcntl_setfl(fd: BorrowedFd<'_>, flags: OFlags) -> io::Result<()> {
1000    // Always enable support for large files.
1001    let flags = flags | OFlags::from_bits_retain(c::O_LARGEFILE);
1002
1003    #[cfg(target_pointer_width = "32")]
1004    unsafe {
1005        ret(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_SETFL), flags))
1006    }
1007    #[cfg(target_pointer_width = "64")]
1008    unsafe {
1009        ret(syscall_readonly!(__NR_fcntl, fd, c_uint(F_SETFL), flags))
1010    }
1011}
1012
1013#[inline]
1014pub(crate) fn fcntl_get_seals(fd: BorrowedFd<'_>) -> io::Result<SealFlags> {
1015    #[cfg(target_pointer_width = "32")]
1016    unsafe {
1017        ret_c_int(syscall_readonly!(__NR_fcntl64, fd, c_uint(F_GET_SEALS)))
1018            .map(|seals| SealFlags::from_bits_retain(seals as u32))
1019    }
1020    #[cfg(target_pointer_width = "64")]
1021    unsafe {
1022        ret_c_int(syscall_readonly!(__NR_fcntl, fd, c_uint(F_GET_SEALS)))
1023            .map(|seals| SealFlags::from_bits_retain(seals as u32))
1024    }
1025}
1026
1027#[inline]
1028pub(crate) fn fcntl_add_seals(fd: BorrowedFd<'_>, seals: SealFlags) -> io::Result<()> {
1029    #[cfg(target_pointer_width = "32")]
1030    unsafe {
1031        ret(syscall_readonly!(
1032            __NR_fcntl64,
1033            fd,
1034            c_uint(F_ADD_SEALS),
1035            seals
1036        ))
1037    }
1038    #[cfg(target_pointer_width = "64")]
1039    unsafe {
1040        ret(syscall_readonly!(
1041            __NR_fcntl,
1042            fd,
1043            c_uint(F_ADD_SEALS),
1044            seals
1045        ))
1046    }
1047}
1048
1049#[inline]
1050pub(crate) fn fcntl_lock(fd: BorrowedFd<'_>, operation: FlockOperation) -> io::Result<()> {
1051    #[cfg(target_pointer_width = "64")]
1052    use linux_raw_sys::general::{flock, F_SETLK, F_SETLKW};
1053    #[cfg(target_pointer_width = "32")]
1054    use linux_raw_sys::general::{flock64 as flock, F_SETLK64 as F_SETLK, F_SETLKW64 as F_SETLKW};
1055    use linux_raw_sys::general::{F_RDLCK, F_UNLCK, F_WRLCK};
1056
1057    let (cmd, l_type) = match operation {
1058        FlockOperation::LockShared => (F_SETLKW, F_RDLCK),
1059        FlockOperation::LockExclusive => (F_SETLKW, F_WRLCK),
1060        FlockOperation::Unlock => (F_SETLKW, F_UNLCK),
1061        FlockOperation::NonBlockingLockShared => (F_SETLK, F_RDLCK),
1062        FlockOperation::NonBlockingLockExclusive => (F_SETLK, F_WRLCK),
1063        FlockOperation::NonBlockingUnlock => (F_SETLK, F_UNLCK),
1064    };
1065
1066    let lock = flock {
1067        l_type: l_type as _,
1068
1069        // When `l_len` is zero, this locks all the bytes from
1070        // `l_whence`/`l_start` to the end of the file, even as the
1071        // file grows dynamically.
1072        l_whence: SEEK_SET as _,
1073        l_start: 0,
1074        l_len: 0,
1075
1076        // Unused.
1077        l_pid: 0,
1078    };
1079
1080    #[cfg(target_pointer_width = "32")]
1081    unsafe {
1082        ret(syscall_readonly!(
1083            __NR_fcntl64,
1084            fd,
1085            c_uint(cmd),
1086            by_ref(&lock)
1087        ))
1088    }
1089    #[cfg(target_pointer_width = "64")]
1090    unsafe {
1091        ret(syscall_readonly!(
1092            __NR_fcntl,
1093            fd,
1094            c_uint(cmd),
1095            by_ref(&lock)
1096        ))
1097    }
1098}
1099
1100#[inline]
1101pub(crate) fn rename(old_path: &CStr, new_path: &CStr) -> io::Result<()> {
1102    #[cfg(target_arch = "riscv64")]
1103    unsafe {
1104        ret(syscall_readonly!(
1105            __NR_renameat2,
1106            raw_fd(AT_FDCWD),
1107            old_path,
1108            raw_fd(AT_FDCWD),
1109            new_path,
1110            c_uint(0)
1111        ))
1112    }
1113    #[cfg(not(target_arch = "riscv64"))]
1114    unsafe {
1115        ret(syscall_readonly!(
1116            __NR_renameat,
1117            raw_fd(AT_FDCWD),
1118            old_path,
1119            raw_fd(AT_FDCWD),
1120            new_path
1121        ))
1122    }
1123}
1124
1125#[inline]
1126pub(crate) fn renameat(
1127    old_dirfd: BorrowedFd<'_>,
1128    old_path: &CStr,
1129    new_dirfd: BorrowedFd<'_>,
1130    new_path: &CStr,
1131) -> io::Result<()> {
1132    #[cfg(target_arch = "riscv64")]
1133    unsafe {
1134        ret(syscall_readonly!(
1135            __NR_renameat2,
1136            old_dirfd,
1137            old_path,
1138            new_dirfd,
1139            new_path,
1140            c_uint(0)
1141        ))
1142    }
1143    #[cfg(not(target_arch = "riscv64"))]
1144    unsafe {
1145        ret(syscall_readonly!(
1146            __NR_renameat,
1147            old_dirfd,
1148            old_path,
1149            new_dirfd,
1150            new_path
1151        ))
1152    }
1153}
1154
1155#[inline]
1156pub(crate) fn renameat2(
1157    old_dirfd: BorrowedFd<'_>,
1158    old_path: &CStr,
1159    new_dirfd: BorrowedFd<'_>,
1160    new_path: &CStr,
1161    flags: RenameFlags,
1162) -> io::Result<()> {
1163    unsafe {
1164        ret(syscall_readonly!(
1165            __NR_renameat2,
1166            old_dirfd,
1167            old_path,
1168            new_dirfd,
1169            new_path,
1170            flags
1171        ))
1172    }
1173}
1174
1175#[inline]
1176pub(crate) fn unlink(path: &CStr) -> io::Result<()> {
1177    unsafe {
1178        ret(syscall_readonly!(
1179            __NR_unlinkat,
1180            raw_fd(AT_FDCWD),
1181            path,
1182            c_uint(0)
1183        ))
1184    }
1185}
1186
1187#[inline]
1188pub(crate) fn unlinkat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result<()> {
1189    unsafe { ret(syscall_readonly!(__NR_unlinkat, dirfd, path, flags)) }
1190}
1191
1192#[inline]
1193pub(crate) fn rmdir(path: &CStr) -> io::Result<()> {
1194    unsafe {
1195        ret(syscall_readonly!(
1196            __NR_unlinkat,
1197            raw_fd(AT_FDCWD),
1198            path,
1199            c_uint(AT_REMOVEDIR)
1200        ))
1201    }
1202}
1203
1204#[inline]
1205pub(crate) fn link(old_path: &CStr, new_path: &CStr) -> io::Result<()> {
1206    unsafe {
1207        ret(syscall_readonly!(
1208            __NR_linkat,
1209            raw_fd(AT_FDCWD),
1210            old_path,
1211            raw_fd(AT_FDCWD),
1212            new_path,
1213            c_uint(0)
1214        ))
1215    }
1216}
1217
1218#[inline]
1219pub(crate) fn linkat(
1220    old_dirfd: BorrowedFd<'_>,
1221    old_path: &CStr,
1222    new_dirfd: BorrowedFd<'_>,
1223    new_path: &CStr,
1224    flags: AtFlags,
1225) -> io::Result<()> {
1226    unsafe {
1227        ret(syscall_readonly!(
1228            __NR_linkat,
1229            old_dirfd,
1230            old_path,
1231            new_dirfd,
1232            new_path,
1233            flags
1234        ))
1235    }
1236}
1237
1238#[inline]
1239pub(crate) fn symlink(old_path: &CStr, new_path: &CStr) -> io::Result<()> {
1240    unsafe {
1241        ret(syscall_readonly!(
1242            __NR_symlinkat,
1243            old_path,
1244            raw_fd(AT_FDCWD),
1245            new_path
1246        ))
1247    }
1248}
1249
1250#[inline]
1251pub(crate) fn symlinkat(old_path: &CStr, dirfd: BorrowedFd<'_>, new_path: &CStr) -> io::Result<()> {
1252    unsafe { ret(syscall_readonly!(__NR_symlinkat, old_path, dirfd, new_path)) }
1253}
1254
1255#[inline]
1256pub(crate) fn mkdir(path: &CStr, mode: Mode) -> io::Result<()> {
1257    unsafe {
1258        ret(syscall_readonly!(
1259            __NR_mkdirat,
1260            raw_fd(AT_FDCWD),
1261            path,
1262            mode
1263        ))
1264    }
1265}
1266
1267#[inline]
1268pub(crate) fn mkdirat(dirfd: BorrowedFd<'_>, path: &CStr, mode: Mode) -> io::Result<()> {
1269    unsafe { ret(syscall_readonly!(__NR_mkdirat, dirfd, path, mode)) }
1270}
1271
1272#[cfg(feature = "alloc")]
1273#[inline]
1274pub(crate) fn getdents(fd: BorrowedFd<'_>, dirent: &mut [u8]) -> io::Result<usize> {
1275    let (dirent_addr_mut, dirent_len) = slice_mut(dirent);
1276
1277    unsafe { ret_usize(syscall!(__NR_getdents64, fd, dirent_addr_mut, dirent_len)) }
1278}
1279
1280#[inline]
1281pub(crate) fn getdents_uninit(
1282    fd: BorrowedFd<'_>,
1283    dirent: &mut [MaybeUninit<u8>],
1284) -> io::Result<usize> {
1285    let (dirent_addr_mut, dirent_len) = slice_mut(dirent);
1286
1287    unsafe { ret_usize(syscall!(__NR_getdents64, fd, dirent_addr_mut, dirent_len)) }
1288}
1289
1290#[inline]
1291pub(crate) fn utimensat(
1292    dirfd: BorrowedFd<'_>,
1293    path: &CStr,
1294    times: &Timestamps,
1295    flags: AtFlags,
1296) -> io::Result<()> {
1297    _utimensat(dirfd, Some(path), times, flags)
1298}
1299
1300#[inline]
1301fn _utimensat(
1302    dirfd: BorrowedFd<'_>,
1303    path: Option<&CStr>,
1304    times: &Timestamps,
1305    flags: AtFlags,
1306) -> io::Result<()> {
1307    // `utimensat_time64` was introduced in Linux 5.1. The old `utimensat`
1308    // syscall is not y2038-compatible on 32-bit architectures.
1309    #[cfg(target_pointer_width = "32")]
1310    unsafe {
1311        match ret(syscall_readonly!(
1312            __NR_utimensat_time64,
1313            dirfd,
1314            path,
1315            by_ref(times),
1316            flags
1317        )) {
1318            Err(io::Errno::NOSYS) => _utimensat_old(dirfd, path, times, flags),
1319            otherwise => otherwise,
1320        }
1321    }
1322    #[cfg(target_pointer_width = "64")]
1323    unsafe {
1324        ret(syscall_readonly!(
1325            __NR_utimensat,
1326            dirfd,
1327            path,
1328            by_ref(times),
1329            flags
1330        ))
1331    }
1332}
1333
1334#[cfg(target_pointer_width = "32")]
1335unsafe fn _utimensat_old(
1336    dirfd: BorrowedFd<'_>,
1337    path: Option<&CStr>,
1338    times: &Timestamps,
1339    flags: AtFlags,
1340) -> io::Result<()> {
1341    // See the comments in `clock_gettime_via_syscall` about emulation.
1342    let old_times = [
1343        __kernel_old_timespec {
1344            tv_sec: times
1345                .last_access
1346                .tv_sec
1347                .try_into()
1348                .map_err(|_| io::Errno::OVERFLOW)?,
1349            tv_nsec: times
1350                .last_access
1351                .tv_nsec
1352                .try_into()
1353                .map_err(|_| io::Errno::INVAL)?,
1354        },
1355        __kernel_old_timespec {
1356            tv_sec: times
1357                .last_modification
1358                .tv_sec
1359                .try_into()
1360                .map_err(|_| io::Errno::OVERFLOW)?,
1361            tv_nsec: times
1362                .last_modification
1363                .tv_nsec
1364                .try_into()
1365                .map_err(|_| io::Errno::INVAL)?,
1366        },
1367    ];
1368    // The length of the array is fixed and not passed into the syscall.
1369    let old_times_addr = slice_just_addr(&old_times);
1370    ret(syscall_readonly!(
1371        __NR_utimensat,
1372        dirfd,
1373        path,
1374        old_times_addr,
1375        flags
1376    ))
1377}
1378
1379#[inline]
1380pub(crate) fn futimens(fd: BorrowedFd<'_>, times: &Timestamps) -> io::Result<()> {
1381    _utimensat(fd, None, times, AtFlags::empty())
1382}
1383
1384#[inline]
1385pub(crate) fn access(path: &CStr, access: Access) -> io::Result<()> {
1386    #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
1387    {
1388        accessat_noflags(CWD, path, access)
1389    }
1390
1391    #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))]
1392    unsafe {
1393        ret(syscall_readonly!(__NR_access, path, access))
1394    }
1395}
1396
1397pub(crate) fn accessat(
1398    dirfd: BorrowedFd<'_>,
1399    path: &CStr,
1400    access: Access,
1401    flags: AtFlags,
1402) -> io::Result<()> {
1403    if !flags
1404        .difference(AtFlags::EACCESS | AtFlags::SYMLINK_NOFOLLOW)
1405        .is_empty()
1406    {
1407        return Err(io::Errno::INVAL);
1408    }
1409
1410    // Linux's `faccessat` syscall doesn't have a flags argument, so if we have
1411    // any flags, use the newer `faccessat2` introduced in Linux 5.8 which
1412    // does. Unless we're on Android where using newer system calls can cause
1413    // seccomp to abort the process.
1414    #[cfg(not(target_os = "android"))]
1415    if !flags.is_empty() {
1416        unsafe {
1417            match ret(syscall_readonly!(
1418                __NR_faccessat2,
1419                dirfd,
1420                path,
1421                access,
1422                flags
1423            )) {
1424                Ok(()) => return Ok(()),
1425                Err(io::Errno::NOSYS) => {}
1426                Err(other) => return Err(other),
1427            }
1428        }
1429    }
1430
1431    // Linux's `faccessat` doesn't have a flags parameter. If we have
1432    // `AT_EACCESS` and we're not setuid or setgid, we can emulate it.
1433    if flags.is_empty()
1434        || (flags.bits() == AT_EACCESS
1435            && crate::backend::ugid::syscalls::getuid()
1436                == crate::backend::ugid::syscalls::geteuid()
1437            && crate::backend::ugid::syscalls::getgid()
1438                == crate::backend::ugid::syscalls::getegid())
1439    {
1440        return accessat_noflags(dirfd, path, access);
1441    }
1442
1443    Err(io::Errno::NOSYS)
1444}
1445
1446#[inline]
1447fn accessat_noflags(dirfd: BorrowedFd<'_>, path: &CStr, access: Access) -> io::Result<()> {
1448    unsafe { ret(syscall_readonly!(__NR_faccessat, dirfd, path, access)) }
1449}
1450
1451#[inline]
1452pub(crate) fn copy_file_range(
1453    fd_in: BorrowedFd<'_>,
1454    off_in: Option<&mut u64>,
1455    fd_out: BorrowedFd<'_>,
1456    off_out: Option<&mut u64>,
1457    len: usize,
1458) -> io::Result<usize> {
1459    unsafe {
1460        ret_usize(syscall!(
1461            __NR_copy_file_range,
1462            fd_in,
1463            opt_mut(off_in),
1464            fd_out,
1465            opt_mut(off_out),
1466            pass_usize(len),
1467            c_uint(0)
1468        ))
1469    }
1470}
1471
1472#[inline]
1473pub(crate) fn memfd_create(name: &CStr, flags: MemfdFlags) -> io::Result<OwnedFd> {
1474    unsafe { ret_owned_fd(syscall_readonly!(__NR_memfd_create, name, flags)) }
1475}
1476
1477#[inline]
1478pub(crate) fn sendfile(
1479    out_fd: BorrowedFd<'_>,
1480    in_fd: BorrowedFd<'_>,
1481    offset: Option<&mut u64>,
1482    count: usize,
1483) -> io::Result<usize> {
1484    #[cfg(target_pointer_width = "32")]
1485    unsafe {
1486        ret_usize(syscall!(
1487            __NR_sendfile64,
1488            out_fd,
1489            in_fd,
1490            opt_mut(offset),
1491            pass_usize(count)
1492        ))
1493    }
1494    #[cfg(target_pointer_width = "64")]
1495    unsafe {
1496        ret_usize(syscall!(
1497            __NR_sendfile,
1498            out_fd,
1499            in_fd,
1500            opt_mut(offset),
1501            pass_usize(count)
1502        ))
1503    }
1504}
1505
1506#[inline]
1507pub(crate) fn inotify_init1(flags: inotify::CreateFlags) -> io::Result<OwnedFd> {
1508    unsafe { ret_owned_fd(syscall_readonly!(__NR_inotify_init1, flags)) }
1509}
1510
1511#[inline]
1512pub(crate) fn inotify_add_watch(
1513    infd: BorrowedFd<'_>,
1514    path: &CStr,
1515    flags: inotify::WatchFlags,
1516) -> io::Result<i32> {
1517    unsafe { ret_c_int(syscall_readonly!(__NR_inotify_add_watch, infd, path, flags)) }
1518}
1519
1520#[inline]
1521pub(crate) fn inotify_rm_watch(infd: BorrowedFd<'_>, wfd: i32) -> io::Result<()> {
1522    unsafe { ret(syscall_readonly!(__NR_inotify_rm_watch, infd, c_int(wfd))) }
1523}
1524
1525#[inline]
1526pub(crate) unsafe fn getxattr(
1527    path: &CStr,
1528    name: &CStr,
1529    value: (*mut u8, usize),
1530) -> io::Result<usize> {
1531    ret_usize(syscall!(
1532        __NR_getxattr,
1533        path,
1534        name,
1535        value.0,
1536        pass_usize(value.1)
1537    ))
1538}
1539
1540#[inline]
1541pub(crate) unsafe fn lgetxattr(
1542    path: &CStr,
1543    name: &CStr,
1544    value: (*mut u8, usize),
1545) -> io::Result<usize> {
1546    ret_usize(syscall!(
1547        __NR_lgetxattr,
1548        path,
1549        name,
1550        value.0,
1551        pass_usize(value.1)
1552    ))
1553}
1554
1555#[inline]
1556pub(crate) unsafe fn fgetxattr(
1557    fd: BorrowedFd<'_>,
1558    name: &CStr,
1559    value: (*mut u8, usize),
1560) -> io::Result<usize> {
1561    ret_usize(syscall!(
1562        __NR_fgetxattr,
1563        fd,
1564        name,
1565        value.0,
1566        pass_usize(value.1)
1567    ))
1568}
1569
1570#[inline]
1571pub(crate) fn setxattr(
1572    path: &CStr,
1573    name: &CStr,
1574    value: &[u8],
1575    flags: XattrFlags,
1576) -> io::Result<()> {
1577    let (value_addr, value_len) = slice(value);
1578    unsafe {
1579        ret(syscall_readonly!(
1580            __NR_setxattr,
1581            path,
1582            name,
1583            value_addr,
1584            value_len,
1585            flags
1586        ))
1587    }
1588}
1589
1590#[inline]
1591pub(crate) fn lsetxattr(
1592    path: &CStr,
1593    name: &CStr,
1594    value: &[u8],
1595    flags: XattrFlags,
1596) -> io::Result<()> {
1597    let (value_addr, value_len) = slice(value);
1598    unsafe {
1599        ret(syscall_readonly!(
1600            __NR_lsetxattr,
1601            path,
1602            name,
1603            value_addr,
1604            value_len,
1605            flags
1606        ))
1607    }
1608}
1609
1610#[inline]
1611pub(crate) fn fsetxattr(
1612    fd: BorrowedFd<'_>,
1613    name: &CStr,
1614    value: &[u8],
1615    flags: XattrFlags,
1616) -> io::Result<()> {
1617    let (value_addr, value_len) = slice(value);
1618    unsafe {
1619        ret(syscall_readonly!(
1620            __NR_fsetxattr,
1621            fd,
1622            name,
1623            value_addr,
1624            value_len,
1625            flags
1626        ))
1627    }
1628}
1629
1630#[inline]
1631pub(crate) unsafe fn listxattr(path: &CStr, list: (*mut u8, usize)) -> io::Result<usize> {
1632    ret_usize(syscall!(__NR_listxattr, path, list.0, pass_usize(list.1)))
1633}
1634
1635#[inline]
1636pub(crate) unsafe fn llistxattr(path: &CStr, list: (*mut u8, usize)) -> io::Result<usize> {
1637    ret_usize(syscall!(__NR_llistxattr, path, list.0, pass_usize(list.1)))
1638}
1639
1640#[inline]
1641pub(crate) unsafe fn flistxattr(fd: BorrowedFd<'_>, list: (*mut u8, usize)) -> io::Result<usize> {
1642    ret_usize(syscall!(__NR_flistxattr, fd, list.0, pass_usize(list.1)))
1643}
1644
1645#[inline]
1646pub(crate) fn removexattr(path: &CStr, name: &CStr) -> io::Result<()> {
1647    unsafe { ret(syscall_readonly!(__NR_removexattr, path, name)) }
1648}
1649
1650#[inline]
1651pub(crate) fn lremovexattr(path: &CStr, name: &CStr) -> io::Result<()> {
1652    unsafe { ret(syscall_readonly!(__NR_lremovexattr, path, name)) }
1653}
1654
1655#[inline]
1656pub(crate) fn fremovexattr(fd: BorrowedFd<'_>, name: &CStr) -> io::Result<()> {
1657    unsafe { ret(syscall_readonly!(__NR_fremovexattr, fd, name)) }
1658}
1659
1660// Some linux_raw_sys structs have unsigned types for values which are
1661// interpreted as signed. This defines a utility or casting to the
1662// same-sized signed type.
1663#[cfg(any(
1664    target_pointer_width = "32",
1665    target_arch = "mips64",
1666    target_arch = "mips64r6"
1667))]
1668mod to_signed {
1669    pub(super) trait ToSigned {
1670        type Signed;
1671        fn to_signed(self) -> Self::Signed;
1672    }
1673    impl ToSigned for u32 {
1674        type Signed = i32;
1675
1676        fn to_signed(self) -> Self::Signed {
1677            self as _
1678        }
1679    }
1680    impl ToSigned for i32 {
1681        type Signed = i32;
1682
1683        fn to_signed(self) -> Self::Signed {
1684            self
1685        }
1686    }
1687    impl ToSigned for u64 {
1688        type Signed = i64;
1689
1690        fn to_signed(self) -> Self::Signed {
1691            self as _
1692        }
1693    }
1694    impl ToSigned for i64 {
1695        type Signed = i64;
1696
1697        fn to_signed(self) -> Self::Signed {
1698            self
1699        }
1700    }
1701}
1702#[cfg(any(
1703    target_pointer_width = "32",
1704    target_arch = "mips64",
1705    target_arch = "mips64r6"
1706))]
1707use to_signed::*;
1708
1709#[cfg(test)]
1710mod tests {
1711    use super::*;
1712
1713    #[test]
1714    fn test_sizes() {
1715        assert_eq_size!(linux_raw_sys::general::__kernel_loff_t, u64);
1716        assert_eq_align!(linux_raw_sys::general::__kernel_loff_t, u64);
1717
1718        // Assert that `Timestamps` has the expected layout.
1719        assert_eq_size!([linux_raw_sys::general::__kernel_timespec; 2], Timestamps);
1720        assert_eq_align!([linux_raw_sys::general::__kernel_timespec; 2], Timestamps);
1721    }
1722}