use core::{fmt, mem::MaybeUninit};
use linux_raw_sys::general::{
S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK,
};
use crate::{CStr, Dev, DevSplit, FileType, Mode, RawFd, StatAtFlags, Timestamp};
#[cfg_attr(
all(not(feature = "linux_4_11"), target_arch = "aarch64"),
path = "aarch64.rs"
)]
#[cfg_attr(all(not(feature = "linux_4_11"), target_arch = "arm"), path = "arm.rs")]
#[cfg_attr(
all(not(feature = "linux_4_11"), target_arch = "mips"),
path = "mips.rs"
)]
#[cfg_attr(
all(not(feature = "linux_4_11"), target_arch = "mips64"),
path = "mips64.rs"
)]
#[cfg_attr(
all(not(feature = "linux_4_11"), target_arch = "powerpc"),
path = "powerpc.rs"
)]
#[cfg_attr(
all(not(feature = "linux_4_11"), target_arch = "powerpc64"),
path = "powerpc64.rs"
)]
#[cfg_attr(
all(not(feature = "linux_4_11"), target_arch = "riscv64"),
path = "riscv64.rs"
)]
#[cfg_attr(
all(not(feature = "linux_4_11"), target_arch = "s390x"),
path = "s390x.rs"
)]
#[cfg_attr(all(not(feature = "linux_4_11"), target_arch = "x86"), path = "x86.rs")]
#[cfg_attr(
all(not(feature = "linux_4_11"), target_arch = "x86_64"),
path = "x86_64.rs"
)]
mod stat_imp;
#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
pub use stat_imp::stat;
use linux_syscalls::{bitflags, syscall, Errno, Sysno};
bitflags! {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum StatXMask: u32 {
TYPE = linux_raw_sys::general::STATX_TYPE,
MODE = linux_raw_sys::general::STATX_MODE,
NLINK = linux_raw_sys::general::STATX_NLINK,
UID = linux_raw_sys::general::STATX_UID,
GID = linux_raw_sys::general::STATX_GID,
ATIME = linux_raw_sys::general::STATX_ATIME,
MTIME = linux_raw_sys::general::STATX_MTIME,
CTIME = linux_raw_sys::general::STATX_CTIME,
INO = linux_raw_sys::general::STATX_INO,
SIZE = linux_raw_sys::general::STATX_SIZE,
BLOCKS = linux_raw_sys::general::STATX_BLOCKS,
BASIC_STATS = linux_raw_sys::general::STATX_BASIC_STATS,
BTIME = linux_raw_sys::general::STATX_BTIME,
ALL = linux_raw_sys::general::STATX_ALL,
MNT_ID = linux_raw_sys::general::STATX_MNT_ID,
DIOALIGN = linux_raw_sys::general::STATX_DIOALIGN,
}
}
bitflags! {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum StatXAttr: u64 {
COMPRESSED = linux_raw_sys::general::STATX_ATTR_COMPRESSED as u64,
IMMUTABLE = linux_raw_sys::general::STATX_ATTR_IMMUTABLE as u64,
APPEND = linux_raw_sys::general::STATX_ATTR_APPEND as u64,
NODUMP = linux_raw_sys::general::STATX_ATTR_NODUMP as u64,
ENCRYPTED = linux_raw_sys::general::STATX_ATTR_ENCRYPTED as u64,
AUTOMOUNT = linux_raw_sys::general::STATX_ATTR_AUTOMOUNT as u64,
MOUNT_ROOT = linux_raw_sys::general::STATX_ATTR_MOUNT_ROOT as u64,
VERITY = linux_raw_sys::general::STATX_ATTR_VERITY as u64,
DAX = linux_raw_sys::general::STATX_ATTR_DAX as u64,
}
}
#[repr(C)]
#[derive(Copy, Clone)]
#[allow(non_camel_case_types)]
struct timestamp {
tv_sec: i64,
tv_nsec: u32,
__pad: u32,
}
impl fmt::Debug for timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("timestamp")
.field("tv_sec", &self.tv_sec)
.field("tv_nsec", &self.tv_nsec)
.finish()
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct Statx {
stx_mask: StatXMask,
stx_blksize: i32,
stx_attributes: u64,
stx_nlink: u32,
stx_uid: u32,
stx_gid: u32,
stx_mode: u16,
stx_ino: u64,
stx_size: i64,
stx_blocks: i64,
stx_attributes_mask: StatXAttr,
stx_atime: timestamp,
stx_btime: timestamp,
stx_ctime: timestamp,
stx_mtime: timestamp,
stx_rdev_major: u32,
stx_rdev_minor: u32,
stx_dev_major: u32,
stx_dev_minor: u32,
stx_mnt_id: u64,
stx_dio_mem_align: u32,
stx_dio_offset_align: u32,
spare: [u64; 14],
}
#[inline(always)]
const fn file_type(mode: u16) -> FileType {
match mode as u32 & S_IFMT {
S_IFSOCK => FileType::Socket,
S_IFLNK => FileType::Link,
S_IFREG => FileType::Regular,
S_IFBLK => FileType::Block,
S_IFDIR => FileType::Directory,
S_IFCHR => FileType::Character,
S_IFIFO => FileType::Fifo,
_ => FileType::Unknown,
}
}
impl Statx {
#[inline]
pub const fn block_size(&self) -> i32 {
self.stx_blksize
}
#[inline]
pub const fn attributes(&self) -> u64 {
self.stx_attributes
}
#[inline]
pub const fn nlink(&self) -> u32 {
self.stx_nlink
}
#[inline]
pub const fn uid(&self) -> u32 {
self.stx_uid
}
#[inline]
pub const fn gid(&self) -> u32 {
self.stx_gid
}
#[inline]
pub const fn mode(&self) -> Mode {
Mode(self.stx_mode & !(S_IFMT as u16))
}
pub const fn file_type(&self) -> FileType {
file_type(self.stx_mode)
}
#[inline]
pub const fn is_socket(&self) -> bool {
self.stx_mode as u32 & S_IFMT == S_IFSOCK
}
#[inline]
pub const fn is_link(&self) -> bool {
self.stx_mode as u32 & S_IFMT == S_IFLNK
}
#[inline]
pub const fn is_regular(&self) -> bool {
self.stx_mode as u32 & S_IFMT == S_IFREG
}
#[inline]
pub const fn is_block(&self) -> bool {
self.stx_mode as u32 & S_IFMT == S_IFBLK
}
#[inline]
pub const fn is_directory(&self) -> bool {
self.stx_mode as u32 & S_IFMT == S_IFDIR
}
#[inline]
pub const fn is_dir(&self) -> bool {
self.is_directory()
}
#[inline]
pub const fn is_character(&self) -> bool {
self.stx_mode as u32 & S_IFMT == S_IFCHR
}
#[inline]
pub const fn is_char(&self) -> bool {
self.is_character()
}
#[inline]
pub const fn is_fifo(&self) -> bool {
self.stx_mode as u32 & S_IFMT == S_IFIFO
}
#[inline]
pub const fn inode(&self) -> u64 {
self.stx_ino
}
#[inline]
pub const fn size(&self) -> i64 {
self.stx_size
}
#[inline]
pub const fn blocks(&self) -> i64 {
self.stx_blocks
}
#[inline]
pub const fn attributes_mask(&self) -> StatXAttr {
self.stx_attributes_mask
}
#[inline]
pub const fn atime(&self) -> Timestamp {
Timestamp {
secs: self.stx_atime.tv_sec,
nsecs: self.stx_atime.tv_nsec,
}
}
#[inline]
pub const fn btime(&self) -> Timestamp {
Timestamp {
secs: self.stx_btime.tv_sec,
nsecs: self.stx_btime.tv_nsec,
}
}
#[inline]
pub const fn ctime(&self) -> Timestamp {
Timestamp {
secs: self.stx_ctime.tv_sec,
nsecs: self.stx_ctime.tv_nsec,
}
}
#[inline]
pub const fn mtime(&self) -> Timestamp {
Timestamp {
secs: self.stx_mtime.tv_sec,
nsecs: self.stx_mtime.tv_nsec,
}
}
#[inline]
pub const fn rdev_major(&self) -> u32 {
self.stx_rdev_major
}
#[inline]
pub const fn rdev_minor(&self) -> u32 {
self.stx_rdev_minor
}
#[inline]
pub const fn rdev(&self) -> Dev {
Dev::Split(DevSplit::new(self.rdev_major(), self.rdev_minor()))
}
#[inline]
pub const fn dev_major(&self) -> u32 {
self.stx_dev_major
}
#[inline]
pub const fn dev_minor(&self) -> u32 {
self.stx_dev_minor
}
#[inline]
pub const fn dev(&self) -> Dev {
Dev::Split(DevSplit::new(self.dev_major(), self.dev_minor()))
}
#[inline]
pub const fn mount_id(&self) -> u64 {
self.stx_mnt_id
}
#[inline]
pub const fn dio_mem_align(&self) -> u32 {
self.stx_dio_mem_align
}
#[inline]
pub const fn dio_offset_align(&self) -> u32 {
self.stx_dio_offset_align
}
pub(crate) fn debug(&self, f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result {
f.debug_struct(name)
.field("dev", &self.dev())
.field("ino", &self.inode())
.field("nlink", &self.nlink())
.field("mode", &self.mode())
.field("uid", &self.uid())
.field("gid", &self.gid())
.field("rdev", &self.rdev())
.field("size", &self.size())
.field("block_size", &self.block_size())
.field("blocks", &self.blocks())
.field("atime", &self.atime())
.field("btime", &self.btime())
.field("mtime", &self.mtime())
.field("ctime", &self.ctime())
.field("attributes", &self.attributes())
.field("attributes_mask", &self.attributes_mask())
.field("mount_id", &self.mount_id())
.field("dio_mem_align", &self.dio_mem_align())
.field("dio_offset_align", &self.dio_offset_align())
.finish()
}
}
impl fmt::Debug for Statx {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.debug(f, "Statx")
}
}
impl Statx {
#[doc(hidden)]
pub fn uninit() -> MaybeUninit<Self> {
let mut buf = MaybeUninit::<Self>::uninit();
unsafe {
let buf = &mut *buf.as_mut_ptr();
core::ptr::write_bytes(
&mut buf.spare[0] as *mut u64 as *mut u8,
0,
core::mem::size_of_val(&buf.spare),
);
}
buf
}
}
#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
impl stat {
#[inline]
pub const fn mode(&self) -> Mode {
Mode(self.raw_mode() & !(S_IFMT as u16))
}
pub const fn file_type(&self) -> FileType {
file_type(self.raw_mode())
}
#[inline]
pub const fn is_socket(&self) -> bool {
self.raw_mode() as u32 & S_IFMT == S_IFSOCK
}
#[inline]
pub const fn is_link(&self) -> bool {
self.raw_mode() as u32 & S_IFMT == S_IFLNK
}
#[inline]
pub const fn is_regular(&self) -> bool {
self.raw_mode() as u32 & S_IFMT == S_IFREG
}
#[inline]
pub const fn is_block(&self) -> bool {
self.raw_mode() as u32 & S_IFMT == S_IFBLK
}
#[inline]
pub const fn is_directory(&self) -> bool {
self.raw_mode() as u32 & S_IFMT == S_IFDIR
}
#[inline]
pub const fn is_dir(&self) -> bool {
self.is_directory()
}
#[inline]
pub const fn is_character(&self) -> bool {
self.raw_mode() as u32 & S_IFMT == S_IFCHR
}
#[inline]
pub const fn is_char(&self) -> bool {
self.is_character()
}
#[inline]
pub const fn is_fifo(&self) -> bool {
self.raw_mode() as u32 & S_IFMT == S_IFIFO
}
#[inline]
pub const fn dev_minor(&self) -> u32 {
self.dev().minor()
}
#[inline]
pub const fn dev_major(&self) -> u32 {
self.dev().major()
}
#[inline]
pub const fn rdev_minor(&self) -> u32 {
self.rdev().minor()
}
#[inline]
pub const fn rdev_major(&self) -> u32 {
self.rdev().major()
}
pub(crate) fn debug(&self, f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result {
f.debug_struct(name)
.field("dev", &self.dev())
.field("ino", &self.inode())
.field("nlink", &self.nlink())
.field("mode", &self.mode())
.field("uid", &self.uid())
.field("gid", &self.gid())
.field("rdev", &self.rdev())
.field("size", &self.size())
.field("block_size", &self.block_size())
.field("blocks", &self.blocks())
.field("atime", &self.atime())
.field("mtime", &self.mtime())
.field("ctime", &self.ctime())
.finish()
}
}
#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
impl fmt::Debug for stat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.debug(f, "stat")
}
}
#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
#[inline]
pub unsafe fn fstatat<P: AsRef<crate::Path>>(
dirfd: RawFd,
path: P,
flags: StatAtFlags,
) -> Result<stat, Errno> {
crate::run_with_cstr(path, |path| fstatat_cstr(dirfd, path, flags))
}
#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
#[inline]
pub unsafe fn fstatat_cstr(dirfd: RawFd, path: &CStr, flags: StatAtFlags) -> Result<stat, Errno> {
let mut buf = stat::uninit();
syscall!(
stat_imp::SYS_FSTATAT,
dirfd,
path.as_ptr(),
buf.as_mut_ptr(),
flags.bits()
)?;
Ok(buf.assume_init())
}
#[inline]
pub unsafe fn statx<P: AsRef<crate::Path>>(
dirfd: RawFd,
path: P,
flags: StatAtFlags,
mask: StatXMask,
) -> Result<Statx, Errno> {
crate::run_with_cstr(path, |path| statx_cstr(dirfd, path, flags, mask))
}
#[inline]
pub unsafe fn statx_cstr(
dirfd: RawFd,
path: &CStr,
flags: StatAtFlags,
mask: StatXMask,
) -> Result<Statx, Errno> {
let mut buf = Statx::uninit();
syscall!(
Sysno::statx,
dirfd,
path.as_ptr(),
flags.bits(),
mask.bits(),
buf.as_mut_ptr(),
)?;
Ok(buf.assume_init())
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
#[test]
#[allow(clippy::unnecessary_cast)]
fn stat64_dev_null() {
linux_syscalls::init();
let c_stat = crate::tests::retry(crate::tests::c_stat);
assert!(c_stat.is_ok());
let c_stat = c_stat.unwrap();
let stat = crate::tests::retry(|| unsafe {
fstatat(
crate::CURRENT_DIRECTORY,
crate::tests::dev_null(),
StatAtFlags::empty(),
)
});
assert!(stat.is_ok());
let stat = stat.unwrap();
assert_eq!(stat.dev(), c_stat.st_dev);
assert_eq!(stat.inode(), c_stat.st_ino as u64);
assert_eq!(stat.nlink(), c_stat.st_nlink as u32);
assert_eq!(
stat.mode().as_u16() | stat.file_type().as_u16(),
c_stat.st_mode as u16
);
assert_eq!(stat.uid(), c_stat.st_uid as u32);
assert_eq!(stat.gid(), c_stat.st_gid as u32);
assert_eq!(stat.rdev(), c_stat.st_rdev);
assert_eq!(stat.size(), c_stat.st_size as i64);
assert_eq!(stat.block_size(), c_stat.st_blksize as i32);
assert_eq!(stat.blocks(), c_stat.st_blocks as i64);
assert_eq!(stat.atime().secs, c_stat.st_atime as i64);
assert_eq!(stat.atime().nsecs, c_stat.st_atime_nsec as u32);
assert_eq!(stat.mtime().secs, c_stat.st_mtime as i64);
assert_eq!(stat.mtime().nsecs, c_stat.st_mtime_nsec as u32);
assert_eq!(stat.ctime().secs, c_stat.st_ctime as i64);
assert_eq!(stat.ctime().nsecs, c_stat.st_ctime_nsec as u32);
}
#[test]
#[allow(clippy::unnecessary_cast)]
#[cfg_attr(target_arch = "s390x", ignore)]
fn statx_dev_null() {
linux_syscalls::init();
let c_stat = crate::tests::retry(crate::tests::c_stat);
assert!(c_stat.is_ok());
let c_stat = c_stat.unwrap();
let statx = crate::tests::retry(|| unsafe {
statx(
crate::CURRENT_DIRECTORY,
crate::tests::dev_null(),
StatAtFlags::empty(),
StatXMask::empty(),
)
});
assert!(statx.is_ok());
let statx = statx.unwrap();
assert_eq!(statx.dev(), c_stat.st_dev);
assert_eq!(statx.inode(), c_stat.st_ino as u64);
assert_eq!(statx.nlink(), c_stat.st_nlink as u32);
assert_eq!(
statx.mode().as_u16() | statx.file_type().as_u16(),
c_stat.st_mode as u16
);
assert_eq!(statx.uid(), c_stat.st_uid as u32);
assert_eq!(statx.gid(), c_stat.st_gid as u32);
assert_eq!(statx.rdev(), c_stat.st_rdev);
assert_eq!(statx.size(), c_stat.st_size as i64);
assert_eq!(statx.block_size(), c_stat.st_blksize as i32);
assert_eq!(statx.blocks(), c_stat.st_blocks as i64);
assert_eq!(statx.atime().secs, c_stat.st_atime as i64);
assert_eq!(statx.atime().nsecs, c_stat.st_atime_nsec as u32);
assert_eq!(statx.mtime().secs, c_stat.st_mtime as i64);
assert_eq!(statx.mtime().nsecs, c_stat.st_mtime_nsec as u32);
assert_eq!(statx.ctime().secs, c_stat.st_ctime as i64);
assert_eq!(statx.ctime().nsecs, c_stat.st_ctime_nsec as u32);
}
}