use std::io;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
use std::os::darwin::fs::MetadataExt as AppleMetadataExt;
#[cfg(target_os = "linux")]
use std::os::linux::fs::MetadataExt as LinuxMetadataExt;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt as UnixMetadataExt;
#[cfg(windows)]
use std::os::windows::fs::MetadataExt as WindowsMetadataExt;
#[derive(Clone, Debug)]
pub struct Metadata {
inner: MetadataInner,
}
#[derive(Clone, Debug)]
enum MetadataInner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
Statx(libc::statx),
Std(std::fs::Metadata),
}
impl Metadata {
#[inline]
pub(crate) fn from_std(md: std::fs::Metadata) -> Self {
Self {
inner: MetadataInner::Std(md),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub(crate) fn from_statx(st: libc::statx) -> Self {
Self {
inner: MetadataInner::Statx(st),
}
}
#[allow(clippy::len_without_is_empty)]
#[inline]
pub fn len(&self) -> u64 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_size,
MetadataInner::Std(md) => md.len(),
}
}
#[inline]
pub fn permissions(&self) -> std::fs::Permissions {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => {
use std::os::unix::fs::PermissionsExt;
std::fs::Permissions::from_mode(st.stx_mode as u32)
}
MetadataInner::Std(md) => md.permissions(),
}
}
#[inline]
pub fn file_type(&self) -> FileType {
FileType {
is_dir: self.is_dir(),
is_file: self.is_file(),
is_symlink: self.is_symlink(),
}
}
#[inline]
pub fn is_dir(&self) -> bool {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => (st.stx_mode as u32 & libc::S_IFMT) == libc::S_IFDIR,
MetadataInner::Std(md) => md.is_dir(),
}
}
#[inline]
pub fn is_file(&self) -> bool {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => (st.stx_mode as u32 & libc::S_IFMT) == libc::S_IFREG,
MetadataInner::Std(md) => md.is_file(),
}
}
#[inline]
pub fn is_symlink(&self) -> bool {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => (st.stx_mode as u32 & libc::S_IFMT) == libc::S_IFLNK,
MetadataInner::Std(md) => md.file_type().is_symlink(),
}
}
#[inline]
pub fn accessed(&self) -> io::Result<SystemTime> {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => statx_timestamp_to_system_time(&st.stx_atime),
MetadataInner::Std(md) => md.accessed(),
}
}
#[inline]
pub fn created(&self) -> io::Result<SystemTime> {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => statx_timestamp_to_system_time(&st.stx_btime),
MetadataInner::Std(md) => md.created(),
}
}
#[inline]
pub fn modified(&self) -> io::Result<SystemTime> {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => statx_timestamp_to_system_time(&st.stx_mtime),
MetadataInner::Std(md) => md.modified(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_dev(&self) -> u64 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => libc::makedev(st.stx_dev_major, st.stx_dev_minor) as u64,
MetadataInner::Std(md) => md.dev(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_ino(&self) -> u64 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_ino, MetadataInner::Std(st) => st.st_ino(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_mode(&self) -> u32 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_mode as u32,
MetadataInner::Std(st) => st.st_mode(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_nlink(&self) -> u64 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_nlink as u64, MetadataInner::Std(st) => st.st_nlink(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_uid(&self) -> u32 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_uid, MetadataInner::Std(st) => st.st_uid(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_gid(&self) -> u32 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_gid, MetadataInner::Std(st) => st.st_gid(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_rdev(&self) -> u64 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => libc::makedev(st.stx_rdev_major, st.stx_rdev_minor) as u64,
MetadataInner::Std(md) => md.rdev(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_size(&self) -> u64 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_size, MetadataInner::Std(md) => md.st_size(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_atime(&self) -> i64 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_atime.tv_sec, MetadataInner::Std(md) => md.st_atime(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_atime_nsec(&self) -> i64 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_atime.tv_nsec as i64, MetadataInner::Std(md) => md.st_atime_nsec(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_mtime(&self) -> i64 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_mtime.tv_sec, MetadataInner::Std(md) => md.st_mtime(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_mtime_nsec(&self) -> i64 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_mtime.tv_nsec as i64, MetadataInner::Std(md) => md.st_mtime_nsec(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_ctime(&self) -> i64 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_ctime.tv_sec, MetadataInner::Std(md) => md.st_ctime(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_ctime_nsec(&self) -> i64 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_ctime.tv_nsec as i64, MetadataInner::Std(md) => md.st_ctime_nsec(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_blksize(&self) -> u64 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_blksize as u64, MetadataInner::Std(md) => md.st_blksize(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_blocks(&self) -> u64 {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_blocks, MetadataInner::Std(md) => md.st_blocks(),
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_subvol(&self) -> Option<u64> {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => Some(st.stx_subvol), MetadataInner::Std(_) => None,
}
}
#[cfg(target_os = "linux")]
#[inline]
pub fn st_attributes(&self) -> Option<u64> {
match &self.inner {
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => Some(st.stx_attributes), MetadataInner::Std(_) => None,
}
}
#[cfg(unix)]
#[inline]
pub fn dev(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.dev(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => libc::makedev(st.stx_dev_major, st.stx_dev_minor) as u64,
}
}
#[cfg(unix)]
#[inline]
pub fn ino(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.ino(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_ino,
}
}
#[cfg(unix)]
#[inline]
pub fn mode(&self) -> u32 {
match &self.inner {
MetadataInner::Std(md) => md.mode(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_mode as u32,
}
}
#[cfg(unix)]
#[inline]
pub fn nlink(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.nlink(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_nlink as u64,
}
}
#[cfg(unix)]
#[inline]
pub fn uid(&self) -> u32 {
match &self.inner {
MetadataInner::Std(md) => md.uid(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_uid,
}
}
#[cfg(unix)]
#[inline]
pub fn gid(&self) -> u32 {
match &self.inner {
MetadataInner::Std(md) => md.gid(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_gid,
}
}
#[cfg(unix)]
#[inline]
pub fn rdev(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.rdev(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => libc::makedev(st.stx_rdev_major, st.stx_rdev_minor) as u64,
}
}
#[cfg(unix)]
#[inline]
pub fn size(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.size(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_size,
}
}
#[cfg(unix)]
#[inline]
pub fn atime(&self) -> i64 {
match &self.inner {
MetadataInner::Std(md) => md.atime(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_atime.tv_sec,
}
}
#[cfg(unix)]
#[inline]
pub fn atime_nsec(&self) -> i64 {
match &self.inner {
MetadataInner::Std(md) => md.atime_nsec(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_atime.tv_nsec as i64,
}
}
#[cfg(unix)]
#[inline]
pub fn mtime(&self) -> i64 {
match &self.inner {
MetadataInner::Std(md) => md.mtime(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_mtime.tv_sec,
}
}
#[cfg(unix)]
#[inline]
pub fn mtime_nsec(&self) -> i64 {
match &self.inner {
MetadataInner::Std(md) => md.mtime_nsec(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_mtime.tv_nsec as i64,
}
}
#[cfg(unix)]
#[inline]
pub fn ctime(&self) -> i64 {
match &self.inner {
MetadataInner::Std(md) => md.ctime(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_ctime.tv_sec,
}
}
#[cfg(unix)]
#[inline]
pub fn ctime_nsec(&self) -> i64 {
match &self.inner {
MetadataInner::Std(md) => md.ctime_nsec(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_ctime.tv_nsec as i64,
}
}
#[cfg(unix)]
#[inline]
pub fn blksize(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.blksize(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_blksize as u64,
}
}
#[cfg(unix)]
#[inline]
pub fn blocks(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.blocks(),
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
MetadataInner::Statx(st) => st.stx_blocks,
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_dev(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.st_dev(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_ino(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.st_ino(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_mode(&self) -> u32 {
match &self.inner {
MetadataInner::Std(md) => md.st_mode(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_nlink(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.st_nlink(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_uid(&self) -> u32 {
match &self.inner {
MetadataInner::Std(md) => md.st_uid(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_gid(&self) -> u32 {
match &self.inner {
MetadataInner::Std(md) => md.st_gid(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_rdev(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.st_rdev(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_size(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.st_size(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_atime(&self) -> i64 {
match &self.inner {
MetadataInner::Std(md) => md.st_atime(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_atime_nsec(&self) -> i64 {
match &self.inner {
MetadataInner::Std(md) => md.st_atime_nsec(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_mtime(&self) -> i64 {
match &self.inner {
MetadataInner::Std(md) => md.st_mtime(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_mtime_nsec(&self) -> i64 {
match &self.inner {
MetadataInner::Std(md) => md.st_mtime_nsec(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_ctime(&self) -> i64 {
match &self.inner {
MetadataInner::Std(md) => md.st_ctime(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_ctime_nsec(&self) -> i64 {
match &self.inner {
MetadataInner::Std(md) => md.st_ctime_nsec(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_birthtime(&self) -> i64 {
match &self.inner {
MetadataInner::Std(md) => md.st_birthtime(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_birthtime_nsec(&self) -> i64 {
match &self.inner {
MetadataInner::Std(md) => md.st_birthtime_nsec(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_blksize(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.st_blksize(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_blocks(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.st_blocks(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_gen(&self) -> u32 {
match &self.inner {
MetadataInner::Std(md) => md.st_gen(),
}
}
#[cfg(all(target_vendor = "apple", not(any(target_os = "linux"))))]
#[inline]
pub fn st_flags(&self) -> u32 {
match &self.inner {
MetadataInner::Std(md) => md.st_flags(),
}
}
#[cfg(windows)]
#[inline]
pub fn file_attributes(&self) -> u32 {
match &self.inner {
MetadataInner::Std(md) => md.file_attributes(),
}
}
#[cfg(windows)]
#[inline]
pub fn creation_time(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.creation_time(),
}
}
#[cfg(windows)]
#[inline]
pub fn last_access_time(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.last_access_time(),
}
}
#[cfg(windows)]
#[inline]
pub fn last_write_time(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.last_write_time(),
}
}
#[cfg(windows)]
#[inline]
pub fn file_size(&self) -> u64 {
match &self.inner {
MetadataInner::Std(md) => md.file_size(),
}
}
}
#[cfg(all(target_os = "linux", any(target_env = "gnu", musl_v1_2_3)))]
#[inline]
fn statx_timestamp_to_system_time(ts: &libc::statx_timestamp) -> io::Result<SystemTime> {
let secs = ts.tv_sec;
let nanos = ts.tv_nsec;
if secs >= 0 {
Ok(UNIX_EPOCH + Duration::new(secs as u64, nanos))
} else {
UNIX_EPOCH
.checked_sub(Duration::new((-secs) as u64, nanos))
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid statx timestamp"))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct FileType {
is_dir: bool,
is_file: bool,
is_symlink: bool,
}
impl FileType {
#[inline]
pub fn is_dir(&self) -> bool {
self.is_dir
}
#[inline]
pub fn is_file(&self) -> bool {
self.is_file
}
#[inline]
pub fn is_symlink(&self) -> bool {
self.is_symlink
}
}