//! 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> { /// 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( dirfd: Fd, path: P, flags: AtFlags, mask: StatxFlags, ) -> io::Result { 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 { 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 { 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_STATE.store(1, Ordering::Relaxed); Err(io::Errno::NOSYS) } /// The first `statx` call failed with `PERM`. #[cold] fn statx_error_perm() -> io::Result { // 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() } } }