use std::error::Error as StdError;
use std::ffi::{CStr, c_int, c_uint};
use std::fmt;
use std::io;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd};
use crate::CPath;
use crate::error::io_bail_last;
use crate::mount_types::{MountId, ReusedMountId};
use crate::types::Device;
const STATX_MNT_ID_UNIQUE: u32 = 0x00004000;
const STATX_SUBVOL: u32 = 0x00008000;
#[derive(Clone, Copy, Debug)]
pub struct Stat<'a> {
mask: c_uint,
at_flags: c_int,
fd: Option<BorrowedFd<'a>>,
}
impl Default for Stat<'static> {
fn default() -> Self {
Self::new()
}
}
impl Stat<'static> {
pub const fn new() -> Self {
Self {
mask: libc::STATX_BASIC_STATS,
at_flags: 0,
fd: None,
}
}
pub const fn new_empty() -> Self {
Self {
mask: 0,
at_flags: 0,
fd: None,
}
}
}
macro_rules! impl_mask {
($(
$(#[$doc:meta])+
$name:ident : $value:expr
),+ $(,)?) => {
$(
$(#[$doc])+
pub fn $name(self, on: bool) -> Self {
self.set_mask(on, $value)
}
)+
};
}
impl Stat<'_> {
pub fn at_fd<F>(self, fd: &F) -> Stat<'_>
where
F: ?Sized + AsFd,
{
Stat {
mask: self.mask,
at_flags: self.at_flags,
fd: Some(fd.as_fd()),
}
}
fn set_mask(mut self, on: bool, value: c_uint) -> Self {
if on {
self.mask |= value;
} else {
self.mask &= !value;
}
self
}
impl_mask! {
file_type : libc::STATX_TYPE,
mode : libc::STATX_MODE,
nlink : libc::STATX_NLINK,
uid : libc::STATX_UID,
gid : libc::STATX_GID,
atime : libc::STATX_ATIME,
mtime : libc::STATX_MTIME,
ctime : libc::STATX_CTIME,
inode : libc::STATX_INO,
size : libc::STATX_SIZE,
blocks : libc::STATX_BLOCKS,
basic_stats : libc::STATX_BASIC_STATS,
btime : libc::STATX_BTIME,
reused_mount_id : libc::STATX_MNT_ID,
unique_mount_id : STATX_MNT_ID_UNIQUE,
dio_align : libc::STATX_DIOALIGN,
subvol : STATX_SUBVOL,
all : libc::STATX_ALL,
}
fn set_at_flags(mut self, on: bool, value: c_int) -> Self {
if on {
self.at_flags |= value;
} else {
self.at_flags &= !value;
}
self
}
pub fn no_auto_mount(self, on: bool) -> Self {
self.set_at_flags(on, libc::AT_NO_AUTOMOUNT)
}
pub fn no_final_symlink(self, on: bool) -> Self {
self.set_at_flags(on, libc::AT_SYMLINK_NOFOLLOW)
}
pub fn sync_as_stat(self, on: bool) -> Self {
self.set_at_flags(on, libc::AT_STATX_SYNC_AS_STAT)
}
pub fn force_sync(self, on: bool) -> Self {
self.set_at_flags(on, libc::AT_STATX_FORCE_SYNC)
}
pub fn no_sync(self, on: bool) -> Self {
self.set_at_flags(on, libc::AT_STATX_DONT_SYNC)
}
pub fn stat_fd(self) -> io::Result<Metadata> {
self.set_at_flags(true, libc::AT_EMPTY_PATH).stat("")
}
pub fn stat<P: ?Sized + CPath>(self, path: &P) -> io::Result<Metadata> {
path.c_path(|path| self.stat_raw(path))?
}
fn stat_raw(self, path: &CStr) -> io::Result<Metadata> {
let mut data: CStatx;
let rc = unsafe {
data = std::mem::zeroed();
libc::statx(
self.fd.map(|fd| fd.as_raw_fd()).unwrap_or(-libc::EBADF),
path.as_ptr(),
self.at_flags,
self.mask,
&raw mut data as *mut libc::statx,
)
};
if rc != 0 {
io_bail_last!();
}
Ok(Metadata::from(data))
}
}
#[derive(Clone)]
pub struct Metadata {
data: CStatx,
}
impl fmt::Debug for Metadata {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Metadata")
.field("stx_mask", &self.data.stx_mask)
.field("stx_blksize", &self.data.stx_blksize)
.field("stx_attributes", &self.data.stx_attributes)
.field("stx_nlink", &self.data.stx_nlink)
.field("stx_uid", &self.data.stx_uid)
.field("stx_gid", &self.data.stx_gid)
.field("stx_mode", &self.data.stx_mode)
.field("stx_ino", &self.data.stx_ino)
.field("stx_size", &self.data.stx_size)
.field("stx_blocks", &self.data.stx_blocks)
.field("stx_attributes_mask", &self.data.stx_attributes_mask)
.field("stx_atime.tv_sec", &self.data.stx_atime.tv_sec)
.field("stx_atime.tv_nsec", &self.data.stx_atime.tv_nsec)
.field("stx_btime.tv_sec", &self.data.stx_btime.tv_sec)
.field("stx_btime.tv_nsec", &self.data.stx_btime.tv_nsec)
.field("stx_ctime.tv_sec", &self.data.stx_ctime.tv_sec)
.field("stx_ctime.tv_nsec", &self.data.stx_ctime.tv_nsec)
.field("stx_mtime.tv_sec", &self.data.stx_mtime.tv_sec)
.field("stx_mtime.tv_nsec", &self.data.stx_mtime.tv_nsec)
.field("stx_rdev_major", &self.data.stx_rdev_major)
.field("stx_rdev_minor", &self.data.stx_rdev_minor)
.field("stx_dev_major", &self.data.stx_dev_major)
.field("stx_dev_minor", &self.data.stx_dev_minor)
.field("stx_mnt_id", &self.data.stx_mnt_id)
.field("stx_dio_mem_align", &self.data.stx_dio_mem_align)
.field("stx_dio_offset_align", &self.data.stx_dio_offset_align)
.finish()
}
}
impl From<CStatx> for Metadata {
fn from(data: CStatx) -> Self {
Self { data }
}
}
impl From<libc::statx> for Metadata {
fn from(data: libc::statx) -> Self {
let data = unsafe {
let size = std::mem::size_of::<libc::statx>().min(std::mem::size_of::<CStatx>());
let mut my_data: CStatx = std::mem::zeroed();
std::ptr::copy(
&raw const data as *const u8,
&raw mut my_data as *mut u8,
size,
);
my_data
};
Self::from(data)
}
}
impl Metadata {
fn maybe<T: Copy>(&self, mask: c_uint, value: T) -> Option<T> {
(self.data.stx_mask & mask != 0).then_some(value)
}
pub fn block_size(&self) -> u32 {
self.data.stx_blksize
}
pub fn is_compressed(&self) -> Option<bool> {
(self.data.stx_attributes_mask & (libc::STATX_ATTR_COMPRESSED as u64) != 0)
.then_some(self.data.stx_attributes & (libc::STATX_ATTR_COMPRESSED as u64) != 0)
}
pub fn is_immutable(&self) -> Option<bool> {
(self.data.stx_attributes_mask & (libc::STATX_ATTR_IMMUTABLE as u64) != 0)
.then_some(self.data.stx_attributes & (libc::STATX_ATTR_IMMUTABLE as u64) != 0)
}
pub fn is_append_only(&self) -> Option<bool> {
(self.data.stx_attributes_mask & (libc::STATX_ATTR_APPEND as u64) != 0)
.then_some(self.data.stx_attributes & (libc::STATX_ATTR_APPEND as u64) != 0)
}
pub fn is_no_dump(&self) -> Option<bool> {
(self.data.stx_attributes_mask & (libc::STATX_ATTR_NODUMP as u64) != 0)
.then_some(self.data.stx_attributes & (libc::STATX_ATTR_NODUMP as u64) != 0)
}
pub fn is_encrypted(&self) -> Option<bool> {
(self.data.stx_attributes_mask & (libc::STATX_ATTR_ENCRYPTED as u64) != 0)
.then_some(self.data.stx_attributes & (libc::STATX_ATTR_ENCRYPTED as u64) != 0)
}
pub fn is_automount(&self) -> Option<bool> {
(self.data.stx_attributes_mask & (libc::STATX_ATTR_AUTOMOUNT as u64) != 0)
.then_some(self.data.stx_attributes & (libc::STATX_ATTR_AUTOMOUNT as u64) != 0)
}
pub fn is_mount_root(&self) -> Option<bool> {
(self.data.stx_attributes_mask & (libc::STATX_ATTR_MOUNT_ROOT as u64) != 0)
.then_some(self.data.stx_attributes & (libc::STATX_ATTR_MOUNT_ROOT as u64) != 0)
}
pub fn is_verity(&self) -> Option<bool> {
(self.data.stx_attributes_mask & (libc::STATX_ATTR_VERITY as u64) != 0)
.then_some(self.data.stx_attributes & (libc::STATX_ATTR_VERITY as u64) != 0)
}
pub fn is_dax(&self) -> Option<bool> {
(self.data.stx_attributes_mask & (libc::STATX_ATTR_DAX as u64) != 0)
.then_some(self.data.stx_attributes & (libc::STATX_ATTR_DAX as u64) != 0)
}
pub fn hard_links(&self) -> Option<u32> {
self.maybe(libc::STATX_NLINK, self.data.stx_nlink)
}
pub fn uid(&self) -> Option<u32> {
self.maybe(libc::STATX_UID, self.data.stx_uid)
}
pub fn gid(&self) -> Option<u32> {
self.maybe(libc::STATX_GID, self.data.stx_gid)
}
pub fn file_type(&self) -> Option<u16> {
self.maybe(libc::STATX_MODE, self.data.stx_mode & (libc::S_IFMT as u16))
}
pub fn file_mode(&self) -> Option<u16> {
self.maybe(
libc::STATX_MODE,
self.data.stx_mode & ((libc::S_IFMT - 1) as u16),
)
}
pub fn inode(&self) -> Option<u64> {
self.maybe(libc::STATX_INO, self.data.stx_ino)
}
pub fn size(&self) -> Option<u64> {
self.maybe(libc::STATX_SIZE, self.data.stx_size)
}
pub fn blocks(&self) -> Option<u64> {
self.maybe(libc::STATX_BLOCKS, self.data.stx_blocks)
}
pub fn atime(&self) -> Option<Timestamp> {
self.maybe(libc::STATX_ATIME, self.data.stx_atime.into())
}
pub fn btime(&self) -> Option<Timestamp> {
self.maybe(libc::STATX_BTIME, self.data.stx_btime.into())
}
pub fn ctime(&self) -> Option<Timestamp> {
self.maybe(libc::STATX_CTIME, self.data.stx_ctime.into())
}
pub fn mtime(&self) -> Option<Timestamp> {
self.maybe(libc::STATX_MTIME, self.data.stx_mtime.into())
}
pub fn device(&self) -> Option<Device> {
let ty = self.file_type()? as u32;
if ty == libc::S_IFCHR || ty == libc::S_IFBLK {
Some(Device {
major: self.data.stx_rdev_major,
minor: self.data.stx_rdev_minor,
})
} else {
None
}
}
pub fn fs_device(&self) -> Device {
Device {
major: self.data.stx_dev_major,
minor: self.data.stx_dev_minor,
}
}
pub fn reused_mount_id(&self) -> Result<ReusedMountId, ReusedMountIdUnavailable> {
if self.data.stx_mask & STATX_MNT_ID_UNIQUE != 0 {
Err(ReusedMountIdUnavailable::UniqueIdAvailable(
MountId::from_raw(self.data.stx_mnt_id),
))
} else if self.data.stx_mask & libc::STATX_MNT_ID != 0 {
Ok(ReusedMountId::from_raw(self.data.stx_mnt_id as u32))
} else {
Err(ReusedMountIdUnavailable::Unavailable)
}
}
pub fn unique_mount_id(&self) -> Option<MountId> {
self.maybe(STATX_MNT_ID_UNIQUE, self.data.stx_mnt_id)
.map(MountId::from_raw)
}
pub fn dio_mem_align(&self) -> Option<u32> {
self.maybe(libc::STATX_DIOALIGN, self.data.stx_dio_mem_align)
}
pub fn dio_offset_align(&self) -> Option<u32> {
self.maybe(libc::STATX_DIOALIGN, self.data.stx_dio_offset_align)
}
pub fn subvolume_id(&self) -> Option<u64> {
self.maybe(STATX_SUBVOL, self.data.stx_subvol)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct Timestamp {
pub sec: i64,
pub nsec: u32,
}
impl From<libc::statx_timestamp> for Timestamp {
fn from(t: libc::statx_timestamp) -> Self {
Self {
sec: t.tv_sec,
nsec: t.tv_nsec,
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum ReusedMountIdUnavailable {
Unavailable,
UniqueIdAvailable(MountId),
}
impl StdError for ReusedMountIdUnavailable {}
impl fmt::Display for ReusedMountIdUnavailable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Unavailable => f.write_str("mount id not requested or kernel too old"),
Self::UniqueIdAvailable(_) => f.write_str("unique mount id replaces reused mount id"),
}
}
}
#[derive(Clone, Copy)]
#[repr(C)]
struct CStatx {
stx_mask: u32,
stx_blksize: u32,
stx_attributes: u64,
stx_nlink: u32,
stx_uid: u32,
stx_gid: u32,
stx_mode: u16,
__spare0: u16,
stx_ino: u64,
stx_size: u64,
stx_blocks: u64,
stx_attributes_mask: u64,
stx_atime: libc::statx_timestamp,
stx_btime: libc::statx_timestamp,
stx_ctime: libc::statx_timestamp,
stx_mtime: libc::statx_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,
stx_subvol: u64,
stx_atomic_write_unit_min: u32,
stx_atomic_write_unit_max: u32,
stx_atomic_write_segments_max: u32,
stx_dio_read_offset_align: u32,
__spare3: [u64; 9],
}