xref: /third_party/rust/crates/rustix/src/fs/statx.rs (revision b8a62b91)
1//! Linux `statx`.
2
3use crate::fd::{AsFd, BorrowedFd};
4use crate::ffi::CStr;
5use crate::fs::AtFlags;
6use crate::{backend, io, path};
7use core::sync::atomic::{AtomicU8, Ordering};
8
9pub use backend::fs::types::{Statx, StatxFlags, StatxTimestamp};
10
11/// `statx(dirfd, path, flags, mask, statxbuf)`
12///
13/// This function returns [`io::Errno::NOSYS`] if `statx` is not available on
14/// the platform, such as Linux before 4.11. This also includes older Docker
15/// versions where the actual syscall fails with different error codes; Rustix
16/// handles this and translates them into `NOSYS`.
17///
18/// # References
19///  - [Linux]
20///
21/// [Linux]: https://man7.org/linux/man-pages/man2/statx.2.html
22#[inline]
23pub fn statx<P: path::Arg, Fd: AsFd>(
24    dirfd: Fd,
25    path: P,
26    flags: AtFlags,
27    mask: StatxFlags,
28) -> io::Result<Statx> {
29    path.into_with_c_str(|path| _statx(dirfd.as_fd(), path, flags, mask))
30}
31
32// Linux kernel prior to 4.11 old versions of Docker don't support `statx`. We
33// store the availability in a global to avoid unnecessary syscalls.
34//
35// 0: Unknown
36// 1: Not available
37// 2: Available
38static STATX_STATE: AtomicU8 = AtomicU8::new(0);
39
40#[inline]
41fn _statx(
42    dirfd: BorrowedFd<'_>,
43    path: &CStr,
44    flags: AtFlags,
45    mask: StatxFlags,
46) -> io::Result<Statx> {
47    match STATX_STATE.load(Ordering::Relaxed) {
48        0 => statx_init(dirfd, path, flags, mask),
49        1 => Err(io::Errno::NOSYS),
50        _ => backend::fs::syscalls::statx(dirfd, path, flags, mask),
51    }
52}
53
54/// The first `statx` call. We don't know if `statx` is available yet.
55fn statx_init(
56    dirfd: BorrowedFd<'_>,
57    path: &CStr,
58    flags: AtFlags,
59    mask: StatxFlags,
60) -> io::Result<Statx> {
61    match backend::fs::syscalls::statx(dirfd, path, flags, mask) {
62        Err(io::Errno::NOSYS) => statx_error_nosys(),
63        Err(io::Errno::PERM) => statx_error_perm(),
64        result => {
65            STATX_STATE.store(2, Ordering::Relaxed);
66            result
67        }
68    }
69}
70
71/// The first `statx` call failed with `NOSYS` (or something we're treating
72/// like `NOSYS`).
73#[cold]
74fn statx_error_nosys() -> io::Result<Statx> {
75    STATX_STATE.store(1, Ordering::Relaxed);
76    Err(io::Errno::NOSYS)
77}
78
79/// The first `statx` call failed with `PERM`.
80#[cold]
81fn statx_error_perm() -> io::Result<Statx> {
82    // Some old versions of Docker have `statx` fail with `PERM` when it isn't
83    // recognized. Check whether `statx` really is available, and if so, fail
84    // with `PERM`, and if not, treat it like `NOSYS`.
85    if backend::fs::syscalls::is_statx_available() {
86        STATX_STATE.store(2, Ordering::Relaxed);
87        Err(io::Errno::PERM)
88    } else {
89        statx_error_nosys()
90    }
91}
92