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