1 | //! Linux `statx`. |
2 | |
3 | use crate::fd::AsFd; |
4 | use crate::fs::AtFlags; |
5 | use crate::{backend, io, path}; |
6 | |
7 | pub use backend::fs::types::{Statx, StatxFlags, StatxTimestamp}; |
8 | |
9 | #[cfg (feature = "linux_4_11" )] |
10 | use backend::fs::syscalls::statx as _statx; |
11 | #[cfg (not(feature = "linux_4_11" ))] |
12 | use compat::statx as _statx; |
13 | |
14 | /// `statx(dirfd, path, flags, mask, statxbuf)` |
15 | /// |
16 | /// This function returns [`io::Errno::NOSYS`] if `statx` is not available on |
17 | /// the platform, such as Linux before 4.11. This also includes older Docker |
18 | /// versions where the actual syscall fails with different error codes; rustix |
19 | /// handles this and translates them into `NOSYS`. |
20 | /// |
21 | /// # References |
22 | /// - [Linux] |
23 | /// |
24 | /// # Examples |
25 | /// |
26 | /// ``` |
27 | /// # use std::path::Path; |
28 | /// # use std::io; |
29 | /// # use rustix::fs::{AtFlags, StatxFlags}; |
30 | /// # use rustix::fd::BorrowedFd; |
31 | /// /// Try to determine if the provided path is a mount root. Will return |
32 | /// /// `Ok(None)` if the kernel is not new enough to support `statx` or |
33 | /// /// [`libc::STATX_ATTR_MOUNT_ROOT`]. |
34 | /// fn is_mountpoint(root: BorrowedFd<'_>, path: &Path) -> io::Result<Option<bool>> { |
35 | /// use rustix::fs::{AtFlags, StatxFlags}; |
36 | /// |
37 | /// let mountroot_flag = libc::STATX_ATTR_MOUNT_ROOT as u64; |
38 | /// match rustix::fs::statx( |
39 | /// root, |
40 | /// path, |
41 | /// AtFlags::NO_AUTOMOUNT | AtFlags::SYMLINK_NOFOLLOW, |
42 | /// StatxFlags::empty(), |
43 | /// ) { |
44 | /// Ok(r) => { |
45 | /// let present = (r.stx_attributes_mask & mountroot_flag) > 0; |
46 | /// Ok(present.then(|| r.stx_attributes & mountroot_flag > 0)) |
47 | /// } |
48 | /// Err(e) if e == rustix::io::Errno::NOSYS => Ok(None), |
49 | /// Err(e) => Err(e.into()), |
50 | /// } |
51 | /// } |
52 | /// ``` |
53 | /// |
54 | /// [Linux]: https://man7.org/linux/man-pages/man2/statx.2.html |
55 | #[inline ] |
56 | pub fn statx<P: path::Arg, Fd: AsFd>( |
57 | dirfd: Fd, |
58 | path: P, |
59 | flags: AtFlags, |
60 | mask: StatxFlags, |
61 | ) -> io::Result<Statx> { |
62 | path.into_with_c_str(|path: &CStr| _statx(dirfd:dirfd.as_fd(), path, flags, mask)) |
63 | } |
64 | |
65 | #[cfg (not(feature = "linux_4_11" ))] |
66 | mod compat { |
67 | use crate::fd::BorrowedFd; |
68 | use crate::ffi::CStr; |
69 | use crate::fs::AtFlags; |
70 | use crate::{backend, io}; |
71 | use core::sync::atomic::{AtomicU8, Ordering}; |
72 | |
73 | use backend::fs::types::{Statx, StatxFlags}; |
74 | |
75 | // Linux kernel prior to 4.11 and old versions of Docker don't support |
76 | // `statx`. We store the availability in a global to avoid unnecessary |
77 | // syscalls. |
78 | // |
79 | // 0: Unknown |
80 | // 1: Not available |
81 | // 2: Available |
82 | static STATX_STATE: AtomicU8 = AtomicU8::new(0); |
83 | |
84 | #[inline ] |
85 | pub fn statx( |
86 | dirfd: BorrowedFd<'_>, |
87 | path: &CStr, |
88 | flags: AtFlags, |
89 | mask: StatxFlags, |
90 | ) -> io::Result<Statx> { |
91 | match STATX_STATE.load(Ordering::Relaxed) { |
92 | 0 => statx_init(dirfd, path, flags, mask), |
93 | 1 => Err(io::Errno::NOSYS), |
94 | _ => backend::fs::syscalls::statx(dirfd, path, flags, mask), |
95 | } |
96 | } |
97 | |
98 | /// The first `statx` call. We don't know if `statx` is available yet. |
99 | fn statx_init( |
100 | dirfd: BorrowedFd<'_>, |
101 | path: &CStr, |
102 | flags: AtFlags, |
103 | mask: StatxFlags, |
104 | ) -> io::Result<Statx> { |
105 | match backend::fs::syscalls::statx(dirfd, path, flags, mask) { |
106 | Err(io::Errno::NOSYS) => statx_error_nosys(), |
107 | Err(io::Errno::PERM) => statx_error_perm(), |
108 | result => { |
109 | STATX_STATE.store(2, Ordering::Relaxed); |
110 | result |
111 | } |
112 | } |
113 | } |
114 | |
115 | /// The first `statx` call failed with `NOSYS` (or something we're treating |
116 | /// like `NOSYS`). |
117 | #[cold ] |
118 | fn statx_error_nosys() -> io::Result<Statx> { |
119 | STATX_STATE.store(1, Ordering::Relaxed); |
120 | Err(io::Errno::NOSYS) |
121 | } |
122 | |
123 | /// The first `statx` call failed with `PERM`. |
124 | #[cold ] |
125 | fn statx_error_perm() -> io::Result<Statx> { |
126 | // Some old versions of Docker have `statx` fail with `PERM` when it |
127 | // isn't recognized. Check whether `statx` really is available, and if |
128 | // so, fail with `PERM`, and if not, treat it like `NOSYS`. |
129 | if backend::fs::syscalls::is_statx_available() { |
130 | STATX_STATE.store(2, Ordering::Relaxed); |
131 | Err(io::Errno::PERM) |
132 | } else { |
133 | statx_error_nosys() |
134 | } |
135 | } |
136 | } |
137 | |