1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! Linux `statx`.

use crate::fd::AsFd;
use crate::fs::AtFlags;
use crate::{backend, io, path};
use backend::fs::types::{Statx, StatxFlags};

#[cfg(feature = "linux_4_11")]
use backend::fs::syscalls::statx as _statx;
#[cfg(not(feature = "linux_4_11"))]
use compat::statx as _statx;

/// `statx(dirfd, path, flags, mask, statxbuf)`
///
/// This function returns [`io::Errno::NOSYS`] if `statx` is not available on
/// the platform, such as Linux before 4.11. This also includes older Docker
/// versions where the actual syscall fails with different error codes; rustix
/// handles this and translates them into `NOSYS`.
///
/// # References
///  - [Linux]
///
/// # Examples
///
/// ```
/// # use std::path::Path;
/// # use std::io;
/// # use rustix::fs::{AtFlags, StatxFlags};
/// # use rustix::fd::BorrowedFd;
/// /// Try to determine if the provided path is a mount root. Will return
/// /// `Ok(None)` if the kernel is not new enough to support `statx` or
/// /// [`libc::STATX_ATTR_MOUNT_ROOT`].
/// fn is_mountpoint(root: BorrowedFd<'_>, path: &Path) -> io::Result<Option<bool>> {
///     use rustix::fs::{AtFlags, StatxFlags};
///
///     let mountroot_flag = libc::STATX_ATTR_MOUNT_ROOT as u64;
///     match rustix::fs::statx(
///         root,
///         path,
///         AtFlags::NO_AUTOMOUNT | AtFlags::SYMLINK_NOFOLLOW,
///         StatxFlags::empty(),
///     ) {
///         Ok(r) => {
///             let present = (r.stx_attributes_mask & mountroot_flag) > 0;
///             Ok(present.then(|| r.stx_attributes & mountroot_flag > 0))
///         }
///         Err(e) if e == rustix::io::Errno::NOSYS => Ok(None),
///         Err(e) => Err(e.into()),
///     }
/// }
/// ```
///
/// [Linux]: https://man7.org/linux/man-pages/man2/statx.2.html
#[inline]
pub fn statx<P: path::Arg, Fd: AsFd>(
    dirfd: Fd,
    path: P,
    flags: AtFlags,
    mask: StatxFlags,
) -> io::Result<Statx> {
    path.into_with_c_str(|path| _statx(dirfd.as_fd(), path, flags, mask))
}

#[cfg(not(feature = "linux_4_11"))]
mod compat {
    use crate::fd::BorrowedFd;
    use crate::ffi::CStr;
    use crate::fs::AtFlags;
    use crate::{backend, io};
    use core::sync::atomic::{AtomicU8, Ordering};

    use backend::fs::types::{Statx, StatxFlags};

    // Linux kernel prior to 4.11 and old versions of Docker don't support
    // `statx`. We store the availability in a global to avoid unnecessary
    // syscalls.
    //
    // 0: Unknown
    // 1: Not available
    // 2: Available
    static STATX_STATE: AtomicU8 = AtomicU8::new(0);

    #[inline]
    pub fn statx(
        dirfd: BorrowedFd<'_>,
        path: &CStr,
        flags: AtFlags,
        mask: StatxFlags,
    ) -> io::Result<Statx> {
        match STATX_STATE.load(Ordering::Relaxed) {
            0 => statx_init(dirfd, path, flags, mask),
            1 => Err(io::Errno::NOSYS),
            _ => backend::fs::syscalls::statx(dirfd, path, flags, mask),
        }
    }

    /// The first `statx` call. We don't know if `statx` is available yet.
    fn statx_init(
        dirfd: BorrowedFd<'_>,
        path: &CStr,
        flags: AtFlags,
        mask: StatxFlags,
    ) -> io::Result<Statx> {
        match backend::fs::syscalls::statx(dirfd, path, flags, mask) {
            Err(io::Errno::NOSYS) => statx_error_nosys(),
            Err(io::Errno::PERM) => statx_error_perm(),
            result => {
                STATX_STATE.store(2, Ordering::Relaxed);
                result
            }
        }
    }

    /// The first `statx` call failed with `NOSYS` (or something we're treating
    /// like `NOSYS`).
    #[cold]
    fn statx_error_nosys() -> io::Result<Statx> {
        STATX_STATE.store(1, Ordering::Relaxed);
        Err(io::Errno::NOSYS)
    }

    /// The first `statx` call failed with `PERM`.
    #[cold]
    fn statx_error_perm() -> io::Result<Statx> {
        // Some old versions of Docker have `statx` fail with `PERM` when it
        // isn't recognized. Check whether `statx` really is available, and if
        // so, fail with `PERM`, and if not, treat it like `NOSYS`.
        if backend::fs::syscalls::is_statx_available() {
            STATX_STATE.store(2, Ordering::Relaxed);
            Err(io::Errno::PERM)
        } else {
            statx_error_nosys()
        }
    }
}