Skip to main content

rustix/fs/
statx.rs

1//! Linux `statx`.
2
3use crate::fd::AsFd;
4use crate::fs::AtFlags;
5use crate::types::Padding;
6use crate::{backend, io, path};
7use backend::c;
8use bitflags::bitflags;
9
10#[cfg(feature = "linux_4_11")]
11use backend::fs::syscalls::statx as _statx;
12#[cfg(not(feature = "linux_4_11"))]
13use compat::statx as _statx;
14
15/// `struct statx` for use with [`statx`].
16#[repr(C)]
17#[derive(Debug, Copy, Clone)]
18#[allow(missing_docs)]
19#[non_exhaustive]
20pub struct Statx {
21    pub stx_mask: u32,
22    pub stx_blksize: u32,
23    pub stx_attributes: StatxAttributes,
24    pub stx_nlink: u32,
25    pub stx_uid: u32,
26    pub stx_gid: u32,
27    pub stx_mode: u16,
28    pub(crate) __spare0: Padding<[u16; 1]>,
29    pub stx_ino: u64,
30    pub stx_size: u64,
31    pub stx_blocks: u64,
32    pub stx_attributes_mask: StatxAttributes,
33    pub stx_atime: StatxTimestamp,
34    pub stx_btime: StatxTimestamp,
35    pub stx_ctime: StatxTimestamp,
36    pub stx_mtime: StatxTimestamp,
37    pub stx_rdev_major: u32,
38    pub stx_rdev_minor: u32,
39    pub stx_dev_major: u32,
40    pub stx_dev_minor: u32,
41    pub stx_mnt_id: u64,
42    pub stx_dio_mem_align: u32,
43    pub stx_dio_offset_align: u32,
44    pub stx_subvol: u64,
45    pub stx_atomic_write_unit_min: u32,
46    pub stx_atomic_write_unit_max: u32,
47    pub stx_atomic_write_segments_max: u32,
48    pub stx_dio_read_offset_align: u32,
49    pub stx_atomic_write_unit_max_opt: u32,
50    pub(crate) __spare2: Padding<[u32; 1usize]>,
51    pub(crate) __spare3: Padding<[u64; 8usize]>,
52}
53
54/// `struct statx_timestamp` for use with [`Statx`].
55#[repr(C)]
56#[derive(Debug, Copy, Clone)]
57#[non_exhaustive]
58pub struct StatxTimestamp {
59    /// Seconds.
60    pub tv_sec: i64,
61
62    /// Nanoseconds. Must be less than 1_000_000_000.
63    pub tv_nsec: u32,
64
65    pub(crate) __reserved: Padding<i32>,
66}
67
68bitflags! {
69    /// `STATX_*` constants for use with [`statx`].
70    ///
71    /// [`statx`]: crate::fs::statx
72    #[repr(transparent)]
73    #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
74    pub struct StatxFlags: u32 {
75        /// `STATX_TYPE`
76        const TYPE = c::STATX_TYPE;
77
78        /// `STATX_MODE`
79        const MODE = c::STATX_MODE;
80
81        /// `STATX_NLINK`
82        const NLINK = c::STATX_NLINK;
83
84        /// `STATX_UID`
85        const UID = c::STATX_UID;
86
87        /// `STATX_GID`
88        const GID = c::STATX_GID;
89
90        /// `STATX_ATIME`
91        const ATIME = c::STATX_ATIME;
92
93        /// `STATX_MTIME`
94        const MTIME = c::STATX_MTIME;
95
96        /// `STATX_CTIME`
97        const CTIME = c::STATX_CTIME;
98
99        /// `STATX_INO`
100        const INO = c::STATX_INO;
101
102        /// `STATX_SIZE`
103        const SIZE = c::STATX_SIZE;
104
105        /// `STATX_BLOCKS`
106        const BLOCKS = c::STATX_BLOCKS;
107
108        /// `STATX_BASIC_STATS`
109        const BASIC_STATS = c::STATX_BASIC_STATS;
110
111        /// `STATX_BTIME`
112        const BTIME = c::STATX_BTIME;
113
114        /// `STATX_MNT_ID` (since Linux 5.8)
115        const MNT_ID = c::STATX_MNT_ID;
116
117        /// `STATX_DIOALIGN` (since Linux 6.1)
118        const DIOALIGN = c::STATX_DIOALIGN;
119
120        /// `STATX_ALL`
121        const ALL = c::STATX_ALL;
122
123        /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
124        const _ = !0;
125    }
126}
127
128bitflags! {
129    /// `STATX_ATTR_*` flags for use with [`Statx`].
130    #[repr(transparent)]
131    #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
132    pub struct StatxAttributes: u64 {
133        /// `STATX_ATTR_COMPRESSED`
134        const COMPRESSED = c::STATX_ATTR_COMPRESSED as u64;
135
136        /// `STATX_ATTR_IMMUTABLE`
137        const IMMUTABLE = c::STATX_ATTR_IMMUTABLE as u64;
138
139        /// `STATX_ATTR_APPEND`
140        const APPEND = c::STATX_ATTR_APPEND as u64;
141
142        /// `STATX_ATTR_NODUMP`
143        const NODUMP = c::STATX_ATTR_NODUMP as u64;
144
145        /// `STATX_ATTR_ENCRYPTED`
146        const ENCRYPTED = c::STATX_ATTR_ENCRYPTED as u64;
147
148        /// `STATX_ATTR_AUTOMOUNT`
149        const AUTOMOUNT = c::STATX_ATTR_AUTOMOUNT as u64;
150
151        /// `STATX_ATTR_MOUNT_ROOT`
152        const MOUNT_ROOT = c::STATX_ATTR_MOUNT_ROOT as u64;
153
154        /// `STATX_ATTR_VERITY`
155        const VERITY = c::STATX_ATTR_VERITY as u64;
156
157        /// `STATX_ATTR_DAX`
158        const DAX = c::STATX_ATTR_DAX as u64;
159
160        /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
161        const _ = !0;
162    }
163}
164
165/// `statx(dirfd, path, flags, mask, statxbuf)`—Extended `stat`.
166///
167/// This function returns [`io::Errno::NOSYS`] if `statx` is not available on
168/// the platform, such as Linux before 4.11. This also includes older Docker
169/// versions where the actual syscall fails with different error codes; rustix
170/// handles this and translates them into `NOSYS`.
171///
172/// # References
173///  - [Linux]
174///
175/// # Examples
176///
177/// ```
178/// # use std::path::Path;
179/// # use std::io;
180/// # use rustix::fs::{AtFlags, StatxFlags};
181/// # use rustix::fd::BorrowedFd;
182/// /// Try to determine if the provided path is a mount root. Will return
183/// /// `Ok(None)` if the kernel is not new enough to support `statx` or
184/// /// [`StatxAttributes::MOUNT_ROOT`].
185/// fn is_mountpoint(root: BorrowedFd<'_>, path: &Path) -> io::Result<Option<bool>> {
186///     use rustix::fs::{AtFlags, StatxAttributes, StatxFlags};
187///
188///     match rustix::fs::statx(
189///         root,
190///         path,
191///         AtFlags::NO_AUTOMOUNT | AtFlags::SYMLINK_NOFOLLOW,
192///         StatxFlags::empty(),
193///     ) {
194///         Ok(r) => {
195///             let present = r.stx_attributes_mask.contains(StatxAttributes::MOUNT_ROOT);
196///             Ok(present.then(|| r.stx_attributes.contains(StatxAttributes::MOUNT_ROOT)))
197///         }
198///         Err(rustix::io::Errno::NOSYS) => Ok(None),
199///         Err(e) => Err(e.into()),
200///     }
201/// }
202/// ```
203///
204/// [Linux]: https://man7.org/linux/man-pages/man2/statx.2.html
205#[inline]
206pub fn statx<P: path::Arg, Fd: AsFd>(
207    dirfd: Fd,
208    path: P,
209    flags: AtFlags,
210    mask: StatxFlags,
211) -> io::Result<Statx> {
212    path.into_with_c_str(|path| _statx(dirfd.as_fd(), path, flags, mask))
213}
214
215#[cfg(not(feature = "linux_4_11"))]
216mod compat {
217    use crate::fd::BorrowedFd;
218    use crate::ffi::CStr;
219    use crate::fs::{AtFlags, Statx, StatxFlags};
220    use crate::{backend, io};
221    use core::sync::atomic::{AtomicU8, Ordering};
222
223    // Linux kernel prior to 4.11 and old versions of Docker don't support
224    // `statx`. We store the availability in a global to avoid unnecessary
225    // syscalls.
226    //
227    // 0: Unknown
228    // 1: Not available
229    // 2: Available
230    static STATX_STATE: AtomicU8 = AtomicU8::new(0);
231
232    #[inline]
233    pub fn statx(
234        dirfd: BorrowedFd<'_>,
235        path: &CStr,
236        flags: AtFlags,
237        mask: StatxFlags,
238    ) -> io::Result<Statx> {
239        match STATX_STATE.load(Ordering::Relaxed) {
240            0 => statx_init(dirfd, path, flags, mask),
241            1 => Err(io::Errno::NOSYS),
242            _ => backend::fs::syscalls::statx(dirfd, path, flags, mask),
243        }
244    }
245
246    /// The first `statx` call. We don't know if `statx` is available yet.
247    fn statx_init(
248        dirfd: BorrowedFd<'_>,
249        path: &CStr,
250        flags: AtFlags,
251        mask: StatxFlags,
252    ) -> io::Result<Statx> {
253        match backend::fs::syscalls::statx(dirfd, path, flags, mask) {
254            Err(err) => statx_error(err),
255            result => {
256                STATX_STATE.store(2, Ordering::Relaxed);
257                result
258            }
259        }
260    }
261
262    /// The first `statx` call failed. We can get a variety of error codes
263    /// from seccomp configs or faulty FUSE drivers, so we don't trust
264    /// `ENOSYS` or `EPERM` to tell us whether statx is available.
265    #[cold]
266    fn statx_error(err: io::Errno) -> io::Result<Statx> {
267        if backend::fs::syscalls::is_statx_available() {
268            // Statx is available. Record this, and fail with the error
269            // code of the initial `statx` call.
270            STATX_STATE.store(2, Ordering::Relaxed);
271            Err(err)
272        } else {
273            // Statx is not available. Record this, and fail with `NOSYS`.
274            STATX_STATE.store(1, Ordering::Relaxed);
275            Err(io::Errno::NOSYS)
276        }
277    }
278}