#[cfg(target_os = "linux")]
mod linux;
#[cfg(all(unix, not(target_os = "linux")))]
mod unix;
#[cfg(windows)]
mod windows;
#[cfg(target_os = "linux")]
use linux as sys;
#[cfg(all(unix, not(target_os = "linux")))]
use unix as sys;
#[cfg(windows)]
use windows as sys;
use std::{io, path::Path, time::SystemTime};
#[cfg(unix)]
use std::time::Duration;
#[cfg(unix)]
use crate::driver::ops::Op;
#[derive(Clone)]
pub struct Metadata(sys::Metadata);
impl Metadata {
#[cfg(target_os = "linux")]
pub(crate) fn from_statx(statx: libc::statx) -> Self {
Self(sys::Metadata::from_statx(statx))
}
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
))]
pub(crate) fn from_stat(stat: rustix::fs::Stat) -> Self {
Self(sys::Metadata::from_stat(stat))
}
#[cfg(windows)]
pub(crate) fn from_handle(handle: windows_sys::Win32::Foundation::HANDLE) -> io::Result<Self> {
sys::Metadata::from_handle(handle).map(Self)
}
#[cfg(windows)]
pub(crate) fn from_std(metadata: std::fs::Metadata) -> Self {
Self(sys::Metadata::from_std(metadata))
}
pub fn file_type(&self) -> FileType {
FileType(self.0.file_type())
}
pub fn is_dir(&self) -> bool {
self.0.is_dir()
}
pub fn is_file(&self) -> bool {
self.0.is_file()
}
pub fn is_symlink(&self) -> bool {
self.0.is_symlink()
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> u64 {
self.0.len()
}
pub fn permissions(&self) -> Permissions {
Permissions(self.0.permissions())
}
pub fn modified(&self) -> io::Result<SystemTime> {
self.0.modified()
}
pub fn accessed(&self) -> io::Result<SystemTime> {
self.0.accessed()
}
pub fn created(&self) -> io::Result<SystemTime> {
self.0.created()
}
}
#[cfg(windows)]
impl Metadata {
pub fn file_attributes(&self) -> u32 {
self.0.file_attributes()
}
pub fn creation_time(&self) -> u64 {
self.0.creation_time()
}
pub fn last_access_time(&self) -> u64 {
self.0.last_access_time()
}
pub fn last_write_time(&self) -> u64 {
self.0.last_write_time()
}
pub fn file_size(&self) -> u64 {
self.0.len()
}
pub fn volume_serial_number(&self) -> Option<u32> {
self.0.volume_serial_number()
}
pub fn number_of_links(&self) -> Option<u32> {
self.0.number_of_links()
}
pub fn file_index(&self) -> Option<u64> {
self.0.file_index()
}
pub fn change_time(&self) -> Option<u64> {
self.0.change_time()
}
}
#[cfg(unix)]
impl std::os::unix::fs::MetadataExt for Metadata {
fn dev(&self) -> u64 {
self.0.dev()
}
fn ino(&self) -> u64 {
self.0.ino()
}
fn mode(&self) -> u32 {
self.0.mode()
}
fn nlink(&self) -> u64 {
self.0.nlink()
}
fn uid(&self) -> u32 {
self.0.uid()
}
fn gid(&self) -> u32 {
self.0.gid()
}
fn rdev(&self) -> u64 {
self.0.rdev()
}
fn size(&self) -> u64 {
self.0.size()
}
fn atime(&self) -> i64 {
self.0.atime()
}
fn atime_nsec(&self) -> i64 {
self.0.atime_nsec()
}
fn mtime(&self) -> i64 {
self.0.mtime()
}
fn mtime_nsec(&self) -> i64 {
self.0.mtime_nsec()
}
fn ctime(&self) -> i64 {
self.0.ctime()
}
fn ctime_nsec(&self) -> i64 {
self.0.ctime_nsec()
}
fn blksize(&self) -> u64 {
self.0.blksize()
}
fn blocks(&self) -> u64 {
self.0.blocks()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct FileType(sys::FileType);
impl FileType {
pub(crate) fn from_std(file_type: std::fs::FileType) -> Self {
Self(sys::FileType::from_std(file_type))
}
pub fn is_dir(&self) -> bool {
self.0.is_dir()
}
pub fn is_file(&self) -> bool {
self.0.is_file()
}
pub fn is_symlink(&self) -> bool {
self.0.is_symlink()
}
}
#[cfg(windows)]
impl FileType {
pub fn is_symlink_dir(&self) -> bool {
self.0.is_symlink_dir()
}
pub fn is_symlink_file(&self) -> bool {
self.0.is_symlink_file()
}
}
#[cfg(unix)]
impl std::os::unix::fs::FileTypeExt for FileType {
fn is_block_device(&self) -> bool {
self.0.is_block_device()
}
fn is_char_device(&self) -> bool {
self.0.is_char_device()
}
fn is_fifo(&self) -> bool {
self.0.is_fifo()
}
fn is_socket(&self) -> bool {
self.0.is_socket()
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Permissions(sys::Permissions);
impl Permissions {
pub fn readonly(&self) -> bool {
self.0.readonly()
}
pub fn set_readonly(&mut self, readonly: bool) {
self.0.set_readonly(readonly);
}
#[cfg(windows)]
pub(crate) fn attrs(&self) -> u32 {
self.0.attrs()
}
}
pub async fn metadata(path: impl AsRef<Path>) -> io::Result<Metadata> {
metadata_impl(path.as_ref(), true).await
}
pub async fn symlink_metadata(path: impl AsRef<Path>) -> io::Result<Metadata> {
metadata_impl(path.as_ref(), false).await
}
pub(crate) async fn metadata_impl(path: &Path, follow_symlinks: bool) -> io::Result<Metadata> {
#[cfg(unix)]
{
Op::path_stat(path, follow_symlinks)?.await
}
#[cfg(windows)]
{
let path = path.to_owned();
super::asyncify(move || {
let metadata = if follow_symlinks {
std::fs::metadata(path)
} else {
std::fs::symlink_metadata(path)
}?;
Ok(Metadata::from_std(metadata))
})
.await
}
}
#[cfg(unix)]
fn system_time_from_unix(secs: i64, nanos: u32) -> io::Result<SystemTime> {
let time = if secs >= 0 {
SystemTime::UNIX_EPOCH.checked_add(Duration::new(secs as u64, nanos))
} else if nanos == 0 {
SystemTime::UNIX_EPOCH.checked_sub(Duration::from_secs(secs.unsigned_abs()))
} else {
SystemTime::UNIX_EPOCH.checked_sub(Duration::new(secs.unsigned_abs() - 1, 1_000_000_000 - nanos))
};
time.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "filesystem timestamp is out of range"))
}
#[cfg(unix)]
impl std::os::unix::fs::PermissionsExt for Permissions {
fn mode(&self) -> u32 {
self.0.mode()
}
fn set_mode(&mut self, mode: u32) {
self.0.set_mode(mode);
}
fn from_mode(mode: u32) -> Self {
Self(sys::Permissions::from_mode(mode))
}
}
#[cfg(all(test, unix))]
mod tests {
use std::time::{Duration, SystemTime};
use super::system_time_from_unix;
#[test]
fn converts_pre_epoch_timestamp_with_subsecond_precision() {
assert_eq!(
system_time_from_unix(-1, 500_000_000).unwrap(),
SystemTime::UNIX_EPOCH - Duration::from_millis(500)
);
}
}