blob: 84e9454dfd13b281547223f27f096b3dfcb31c9b [file] [log] [blame]
//! This module contains a `Metadata` implementation that must be used instead of `std::fs::Metadata` to assure
//! that the `ctime` information is populated exactly like the one in `git`, which wouldn't be the case on unix.
#![allow(clippy::useless_conversion)] // on some MacOOS conversions are required, but on linux usually not.
#![allow(clippy::unnecessary_cast)]
// it's allowed for good measure, in case there are systems that use different types for that.
use std::{path::Path, time::SystemTime};
/// A structure to partially mirror [`std::fs::Metadata`].
#[cfg(not(windows))]
pub struct Metadata(rustix::fs::Stat);
#[cfg(windows)]
/// A structure to partially mirror [`std::fs::Metadata`].
pub struct Metadata(std::fs::Metadata);
/// Lifecycle
impl Metadata {
/// Obtain the metadata at `path` without following symlinks.
pub fn from_path_no_follow(path: &Path) -> Result<Self, std::io::Error> {
#[cfg(not(windows))]
{
rustix::fs::lstat(path).map(Metadata).map_err(Into::into)
}
#[cfg(windows)]
path.symlink_metadata().map(Metadata)
}
/// Obtain the metadata at `path` without following symlinks.
pub fn from_file(file: &std::fs::File) -> Result<Self, std::io::Error> {
#[cfg(not(windows))]
{
rustix::fs::fstat(file).map(Metadata).map_err(Into::into)
}
#[cfg(windows)]
file.metadata().map(Metadata)
}
}
/// Access
#[allow(clippy::len_without_is_empty)]
impl Metadata {
/// Return true if the metadata belongs to a directory
pub fn is_dir(&self) -> bool {
#[cfg(not(windows))]
{
(self.0.st_mode as u32 & libc::S_IFMT as u32) == libc::S_IFDIR as u32
}
#[cfg(windows)]
self.0.is_dir()
}
/// Return the time at which the underlying file was modified.
pub fn modified(&self) -> Option<SystemTime> {
#[cfg(not(windows))]
{
Some(system_time_from_secs_nanos(
#[cfg(not(target_os = "aix"))]
self.0.st_mtime.try_into().ok()?,
#[cfg(target_os = "aix")]
self.0.st_mtim.tv_sec.try_into().ok()?,
#[cfg(not(any(target_os = "netbsd", target_os = "aix")))]
self.0.st_mtime_nsec.try_into().ok()?,
#[cfg(target_os = "netbsd")]
self.0.st_mtimensec.try_into().ok()?,
#[cfg(target_os = "aix")]
self.0.st_mtim.tv_nsec.try_into().ok()?,
))
}
#[cfg(windows)]
self.0.modified().ok()
}
/// Return the time at which the underlying file was created.
///
/// Note that this differes from [`std::fs::Metadata::created()`] which would return
/// the inode birth time, which is notably different to what `git` does.
pub fn created(&self) -> Option<SystemTime> {
#[cfg(not(windows))]
{
Some(system_time_from_secs_nanos(
#[cfg(not(target_os = "aix"))]
self.0.st_ctime.try_into().ok()?,
#[cfg(target_os = "aix")]
self.0.st_ctim.tv_sec.try_into().ok()?,
#[cfg(not(any(target_os = "netbsd", target_os = "aix")))]
self.0.st_ctime_nsec.try_into().ok()?,
#[cfg(target_os = "netbsd")]
self.0.st_ctimensec.try_into().ok()?,
#[cfg(target_os = "aix")]
self.0.st_ctim.tv_nsec.try_into().ok()?,
))
}
#[cfg(windows)]
self.0.created().ok()
}
/// Return the size of the file in bytes.
pub fn len(&self) -> u64 {
#[cfg(not(windows))]
{
self.0.st_size as u64
}
#[cfg(windows)]
self.0.len()
}
/// Return the device id on which the file is located, or 0 on windows.
pub fn dev(&self) -> u64 {
#[cfg(not(windows))]
{
self.0.st_dev as u64
}
#[cfg(windows)]
0
}
/// Return the inode id tracking the file, or 0 on windows.
pub fn ino(&self) -> u64 {
#[cfg(not(windows))]
{
self.0.st_ino as u64
}
#[cfg(windows)]
0
}
/// Return the user-id of the file or 0 on windows.
pub fn uid(&self) -> u32 {
#[cfg(not(windows))]
{
self.0.st_uid as u32
}
#[cfg(windows)]
0
}
/// Return the group-id of the file or 0 on windows.
pub fn gid(&self) -> u32 {
#[cfg(not(windows))]
{
self.0.st_gid as u32
}
#[cfg(windows)]
0
}
/// Return `true` if the file's executable bit is set, or `false` on windows.
pub fn is_executable(&self) -> bool {
#[cfg(not(windows))]
{
(self.0.st_mode as u32 & libc::S_IFMT as u32) == libc::S_IFREG as u32
&& self.0.st_mode as u32 & libc::S_IXUSR as u32 == libc::S_IXUSR as u32
}
#[cfg(windows)]
gix_fs::is_executable(&self.0)
}
/// Return `true` if the file's is a symbolic link.
pub fn is_symlink(&self) -> bool {
#[cfg(not(windows))]
{
(self.0.st_mode as u32 & libc::S_IFMT as u32) == libc::S_IFLNK as u32
}
#[cfg(windows)]
self.0.is_symlink()
}
/// Return `true` if this is a regular file, executable or not.
pub fn is_file(&self) -> bool {
#[cfg(not(windows))]
{
(self.0.st_mode as u32 & libc::S_IFMT as u32) == libc::S_IFREG as u32
}
#[cfg(windows)]
self.0.is_file()
}
}
#[cfg(not(windows))]
fn system_time_from_secs_nanos(secs: u64, nanos: u32) -> SystemTime {
std::time::UNIX_EPOCH + std::time::Duration::new(secs, nanos)
}