#![allow(clippy::useless_conversion)] use crate::fs::{ImplFileTypeExt, ImplPermissionsExt, Metadata}; use crate::time::{Duration, SystemClock, SystemTime}; #[cfg(target_os = "linux")] use rustix::fs::{makedev, Statx, StatxFlags}; use rustix::fs::{RawMode, Stat}; use std::{fs, io}; #[derive(Debug, Clone)] pub(crate) struct ImplMetadataExt { dev: u64, ino: u64, #[cfg(not(target_os = "wasi"))] mode: u32, nlink: u64, #[cfg(not(target_os = "wasi"))] uid: u32, #[cfg(not(target_os = "wasi"))] gid: u32, #[cfg(not(target_os = "wasi"))] rdev: u64, size: u64, #[cfg(not(target_os = "wasi"))] atime: i64, #[cfg(not(target_os = "wasi"))] atime_nsec: i64, #[cfg(not(target_os = "wasi"))] mtime: i64, #[cfg(not(target_os = "wasi"))] mtime_nsec: i64, #[cfg(not(target_os = "wasi"))] ctime: i64, #[cfg(not(target_os = "wasi"))] ctime_nsec: i64, #[cfg(not(target_os = "wasi"))] blksize: u64, #[cfg(not(target_os = "wasi"))] blocks: u64, #[cfg(target_os = "wasi")] atim: u64, #[cfg(target_os = "wasi")] mtim: u64, #[cfg(target_os = "wasi")] ctim: u64, } impl ImplMetadataExt { /// Constructs a new instance of `Self` from the given [`std::fs::File`] /// and [`std::fs::Metadata`]. #[inline] #[allow(clippy::unnecessary_wraps)] pub(crate) fn from(_file: &fs::File, std: &fs::Metadata) -> io::Result { // On `rustix`-style platforms, the `Metadata` has everything we need. Ok(Self::from_just_metadata(std)) } /// Constructs a new instance of `Self` from the given /// [`std::fs::Metadata`]. #[inline] pub(crate) fn from_just_metadata(std: &fs::Metadata) -> Self { use rustix::fs::MetadataExt; Self { dev: std.dev(), ino: std.ino(), #[cfg(not(target_os = "wasi"))] mode: std.mode(), nlink: std.nlink(), #[cfg(not(target_os = "wasi"))] uid: std.uid(), #[cfg(not(target_os = "wasi"))] gid: std.gid(), #[cfg(not(target_os = "wasi"))] rdev: std.rdev(), size: std.size(), #[cfg(not(target_os = "wasi"))] atime: std.atime(), #[cfg(not(target_os = "wasi"))] atime_nsec: std.atime_nsec(), #[cfg(not(target_os = "wasi"))] mtime: std.mtime(), #[cfg(not(target_os = "wasi"))] mtime_nsec: std.mtime_nsec(), #[cfg(not(target_os = "wasi"))] ctime: std.ctime(), #[cfg(not(target_os = "wasi"))] ctime_nsec: std.ctime_nsec(), #[cfg(not(target_os = "wasi"))] blksize: std.blksize(), #[cfg(not(target_os = "wasi"))] blocks: std.blocks(), #[cfg(target_os = "wasi")] atim: std.atim(), #[cfg(target_os = "wasi")] mtim: std.mtim(), #[cfg(target_os = "wasi")] ctim: std.ctim(), } } /// Constructs a new instance of `Metadata` from the given `Stat`. #[inline] #[allow(unused_comparisons)] // NB: rust-lang/rust#115823 requires this here instead of on `st_dev` processing below pub(crate) fn from_rustix(stat: Stat) -> Metadata { #[cfg(not(target_os = "wasi"))] use rustix::fs::StatExt; Metadata { file_type: ImplFileTypeExt::from_raw_mode(stat.st_mode as RawMode), len: u64::try_from(stat.st_size).unwrap(), #[cfg(not(target_os = "wasi"))] permissions: ImplPermissionsExt::from_raw_mode(stat.st_mode as RawMode), #[cfg(target_os = "wasi")] permissions: ImplPermissionsExt::default(), #[cfg(not(any(target_os = "netbsd", target_os = "wasi")))] modified: system_time_from_rustix( stat.mtime().try_into().unwrap(), stat.st_mtime_nsec as _, ), #[cfg(not(any(target_os = "netbsd", target_os = "wasi")))] accessed: system_time_from_rustix( stat.atime().try_into().unwrap(), stat.st_atime_nsec as _, ), #[cfg(target_os = "netbsd")] modified: system_time_from_rustix( stat.st_mtime.try_into().unwrap(), stat.st_mtimensec as _, ), #[cfg(target_os = "netbsd")] accessed: system_time_from_rustix( stat.st_atime.try_into().unwrap(), stat.st_atimensec as _, ), #[cfg(target_os = "wasi")] modified: system_time_from_rustix(stat.st_mtim.tv_sec, stat.st_mtim.tv_nsec as _), #[cfg(target_os = "wasi")] accessed: system_time_from_rustix(stat.st_atim.tv_sec, stat.st_atim.tv_nsec as _), #[cfg(any( target_os = "freebsd", target_os = "openbsd", target_os = "macos", target_os = "ios" ))] created: system_time_from_rustix( stat.st_birthtime.try_into().unwrap(), stat.st_birthtime_nsec as _, ), #[cfg(target_os = "netbsd")] created: system_time_from_rustix( stat.st_birthtime.try_into().unwrap(), stat.st_birthtimensec as _, ), // `stat.st_ctime` is the latest status change; we want the creation. #[cfg(not(any( target_os = "freebsd", target_os = "openbsd", target_os = "macos", target_os = "ios", target_os = "netbsd" )))] created: None, ext: Self { // The type of `st_dev` is `dev_t` which is signed on some // platforms and unsigned on other platforms. A `u64` is enough // to work for all unsigned platforms, and for signed platforms // perform a sign extension to `i64` and then view that as an // unsigned 64-bit number instead. // // Note that the `unused_comparisons` is ignored here for // platforms where it's unsigned since the first branch here // will never be taken. dev: if stat.st_dev < 0 { i64::try_from(stat.st_dev).unwrap() as u64 } else { u64::try_from(stat.st_dev).unwrap() }, ino: stat.st_ino.into(), #[cfg(not(target_os = "wasi"))] mode: u32::from(stat.st_mode), nlink: u64::from(stat.st_nlink), #[cfg(not(target_os = "wasi"))] uid: stat.st_uid, #[cfg(not(target_os = "wasi"))] gid: stat.st_gid, #[cfg(not(target_os = "wasi"))] rdev: u64::try_from(stat.st_rdev).unwrap(), size: u64::try_from(stat.st_size).unwrap(), #[cfg(not(target_os = "wasi"))] atime: i64::try_from(stat.atime()).unwrap(), #[cfg(not(any(target_os = "netbsd", target_os = "wasi")))] atime_nsec: stat.st_atime_nsec as _, #[cfg(target_os = "netbsd")] atime_nsec: stat.st_atimensec as _, #[cfg(not(target_os = "wasi"))] mtime: i64::try_from(stat.mtime()).unwrap(), #[cfg(not(any(target_os = "netbsd", target_os = "wasi")))] mtime_nsec: stat.st_mtime_nsec as _, #[cfg(target_os = "netbsd")] mtime_nsec: stat.st_mtimensec as _, #[cfg(not(target_os = "wasi"))] ctime: i64::try_from(stat.ctime()).unwrap(), #[cfg(not(any(target_os = "netbsd", target_os = "wasi")))] ctime_nsec: stat.st_ctime_nsec as _, #[cfg(target_os = "netbsd")] ctime_nsec: stat.st_ctimensec as _, #[cfg(not(target_os = "wasi"))] blksize: u64::try_from(stat.st_blksize).unwrap(), #[cfg(not(target_os = "wasi"))] blocks: u64::try_from(stat.st_blocks).unwrap(), #[cfg(target_os = "wasi")] atim: u64::try_from( stat.st_atim.tv_sec as u64 * 1000000000 + stat.st_atim.tv_nsec as u64, ) .unwrap(), #[cfg(target_os = "wasi")] mtim: u64::try_from( stat.st_mtim.tv_sec as u64 * 1000000000 + stat.st_mtim.tv_nsec as u64, ) .unwrap(), #[cfg(target_os = "wasi")] ctim: u64::try_from( stat.st_ctim.tv_sec as u64 * 1000000000 + stat.st_ctim.tv_nsec as u64, ) .unwrap(), }, } } /// Constructs a new instance of `Metadata` from the given `Statx`. #[cfg(target_os = "linux")] #[inline] pub(crate) fn from_rustix_statx(statx: Statx) -> Metadata { Metadata { file_type: ImplFileTypeExt::from_raw_mode(RawMode::from(statx.stx_mode)), len: u64::try_from(statx.stx_size).unwrap(), permissions: ImplPermissionsExt::from_raw_mode(RawMode::from(statx.stx_mode)), modified: if statx.stx_mask & StatxFlags::MTIME.bits() != 0 { system_time_from_rustix(statx.stx_mtime.tv_sec, statx.stx_mtime.tv_nsec as _) } else { None }, accessed: if statx.stx_mask & StatxFlags::ATIME.bits() != 0 { system_time_from_rustix(statx.stx_atime.tv_sec, statx.stx_atime.tv_nsec as _) } else { None }, created: if statx.stx_mask & StatxFlags::BTIME.bits() != 0 { system_time_from_rustix(statx.stx_btime.tv_sec, statx.stx_btime.tv_nsec as _) } else { None }, ext: Self { dev: makedev(statx.stx_dev_major, statx.stx_dev_minor), ino: statx.stx_ino.into(), mode: u32::from(statx.stx_mode), nlink: u64::from(statx.stx_nlink), uid: statx.stx_uid, gid: statx.stx_gid, rdev: makedev(statx.stx_rdev_major, statx.stx_rdev_minor), size: statx.stx_size, atime: i64::from(statx.stx_atime.tv_sec), atime_nsec: statx.stx_atime.tv_nsec as _, mtime: i64::from(statx.stx_mtime.tv_sec), mtime_nsec: statx.stx_mtime.tv_nsec as _, ctime: i64::from(statx.stx_ctime.tv_sec), ctime_nsec: statx.stx_ctime.tv_nsec as _, blksize: u64::from(statx.stx_blksize), blocks: statx.stx_blocks, }, } } /// Determine if `self` and `other` refer to the same inode on the same /// device. pub(crate) const fn is_same_file(&self, other: &Self) -> bool { self.dev == other.dev && self.ino == other.ino } } #[allow(clippy::similar_names)] fn system_time_from_rustix(sec: i64, nsec: u64) -> Option { if sec >= 0 { SystemClock::UNIX_EPOCH.checked_add(Duration::new(u64::try_from(sec).unwrap(), nsec as _)) } else { SystemClock::UNIX_EPOCH .checked_sub(Duration::new(u64::try_from(-sec).unwrap(), 0)) .map(|t| t.checked_add(Duration::new(0, nsec as u32))) .flatten() } } impl crate::fs::MetadataExt for ImplMetadataExt { #[inline] fn dev(&self) -> u64 { self.dev } #[inline] fn ino(&self) -> u64 { self.ino } #[cfg(not(target_os = "wasi"))] #[inline] fn mode(&self) -> u32 { self.mode } #[inline] fn nlink(&self) -> u64 { self.nlink } #[cfg(not(target_os = "wasi"))] #[inline] fn uid(&self) -> u32 { self.uid } #[cfg(not(target_os = "wasi"))] #[inline] fn gid(&self) -> u32 { self.gid } #[cfg(not(target_os = "wasi"))] #[inline] fn rdev(&self) -> u64 { self.rdev } #[inline] fn size(&self) -> u64 { self.size } #[cfg(not(target_os = "wasi"))] #[inline] fn atime(&self) -> i64 { self.atime } #[cfg(not(target_os = "wasi"))] #[inline] fn atime_nsec(&self) -> i64 { self.atime_nsec } #[cfg(not(target_os = "wasi"))] #[inline] fn mtime(&self) -> i64 { self.mtime } #[cfg(not(target_os = "wasi"))] #[inline] fn mtime_nsec(&self) -> i64 { self.mtime_nsec } #[cfg(not(target_os = "wasi"))] #[inline] fn ctime(&self) -> i64 { self.ctime } #[cfg(not(target_os = "wasi"))] #[inline] fn ctime_nsec(&self) -> i64 { self.ctime_nsec } #[cfg(not(target_os = "wasi"))] #[inline] fn blksize(&self) -> u64 { self.blksize } #[cfg(not(target_os = "wasi"))] #[inline] fn blocks(&self) -> u64 { self.blocks } #[cfg(target_os = "wasi")] fn atim(&self) -> u64 { self.atim } #[cfg(target_os = "wasi")] fn mtim(&self) -> u64 { self.mtim } #[cfg(target_os = "wasi")] fn ctim(&self) -> u64 { self.ctim } } /// It should be possible to represent times before the Epoch. /// https://github.com/bytecodealliance/cap-std/issues/328 #[test] fn negative_time() { let system_time = system_time_from_rustix(-1, 1).unwrap(); let d = SystemClock::UNIX_EPOCH.duration_since(system_time).unwrap(); assert_eq!(d.as_secs(), 0); if !cfg!(emulate_second_only_system) { assert_eq!(d.subsec_nanos(), 999999999); } }