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