use super::as_file;
use crate::SystemTimeSpec;
use std::{fs, io, path::Path, time::SystemTime};
#[cfg(not(windows))]
use {
posish::{
fs::{cwd, futimens, utimensat, AtFlags},
time::{timespec, UTIME_NOW, UTIME_OMIT},
},
std::{convert::TryInto, os::unix::io::AsRawFd},
};
#[cfg(windows)]
use {
std::{
os::windows::{fs::OpenOptionsExt, io::AsRawHandle},
ptr,
time::Duration,
},
winapi::{
shared::{
minwindef::{DWORD, FILETIME},
winerror::ERROR_NOT_SUPPORTED,
},
um::{
fileapi::SetFileTime,
winbase::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT},
},
},
};
#[inline]
pub fn set_atime<P: AsRef<Path>>(path: P, atime: SystemTimeSpec) -> io::Result<()> {
set_times(path, Some(atime), None)
}
#[inline]
pub fn set_mtime<P: AsRef<Path>>(path: P, mtime: SystemTimeSpec) -> io::Result<()> {
set_times(path, None, Some(mtime))
}
#[inline]
pub fn set_times<P: AsRef<Path>>(
path: P,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> io::Result<()> {
let path = path.as_ref();
_set_times(path, atime, mtime)
}
#[cfg(not(windows))]
fn _set_times(
path: &Path,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> io::Result<()> {
let times = [to_timespec(atime)?, to_timespec(mtime)?];
utimensat(&*cwd(), path, ×, AtFlags::empty())
}
#[cfg(windows)]
fn _set_times(
path: &Path,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> io::Result<()> {
let custom_flags = FILE_FLAG_BACKUP_SEMANTICS;
match fs::OpenOptions::new()
.write(true)
.custom_flags(custom_flags)
.open(path)
{
Ok(file) => return _set_file_times(&file, atime, mtime),
Err(err) => match err.kind() {
io::ErrorKind::PermissionDenied => (),
_ => return Err(err),
},
}
match fs::OpenOptions::new()
.read(true)
.custom_flags(custom_flags)
.open(path)
{
Ok(file) => return _set_file_times(&file, atime, mtime),
Err(err) => match err.kind() {
io::ErrorKind::PermissionDenied => (),
_ => return Err(err),
},
}
Err(io::Error::from_raw_os_error(ERROR_NOT_SUPPORTED as i32))
}
#[inline]
pub fn set_symlink_times<P: AsRef<Path>>(
path: P,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> io::Result<()> {
let path = path.as_ref();
_set_symlink_times(path, atime, mtime)
}
#[cfg(not(windows))]
fn _set_symlink_times(
path: &Path,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> io::Result<()> {
let times = [to_timespec(atime)?, to_timespec(mtime)?];
utimensat(&*cwd(), path, ×, AtFlags::SYMLINK_NOFOLLOW)
}
#[cfg(windows)]
fn _set_symlink_times(
path: &Path,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> io::Result<()> {
let custom_flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT;
match fs::OpenOptions::new()
.write(true)
.custom_flags(custom_flags)
.open(path)
{
Ok(file) => return _set_file_times(&file, atime, mtime),
Err(err) => match err.kind() {
io::ErrorKind::PermissionDenied => (),
_ => return Err(err),
},
}
match fs::OpenOptions::new()
.read(true)
.custom_flags(custom_flags)
.open(path)
{
Ok(file) => return _set_file_times(&file, atime, mtime),
Err(err) => match err.kind() {
io::ErrorKind::PermissionDenied => (),
_ => return Err(err),
},
}
Err(io::Error::from_raw_os_error(ERROR_NOT_SUPPORTED as i32))
}
pub trait SetTimes {
fn set_times(
&self,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> io::Result<()>;
}
#[cfg(not(windows))]
impl<T> SetTimes for T
where
T: AsRawFd,
{
#[inline]
fn set_times(
&self,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> io::Result<()> {
_set_file_times(unsafe { &as_file(self) }, atime, mtime)
}
}
#[cfg(not(windows))]
fn _set_file_times(
file: &fs::File,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> io::Result<()> {
let times = [to_timespec(atime)?, to_timespec(mtime)?];
futimens(file, ×)
}
#[cfg(not(windows))]
#[allow(clippy::useless_conversion)]
pub(crate) fn to_timespec(ft: Option<SystemTimeSpec>) -> io::Result<timespec> {
Ok(match ft {
None => timespec {
tv_sec: 0,
tv_nsec: UTIME_OMIT.into(),
},
Some(SystemTimeSpec::SymbolicNow) => timespec {
tv_sec: 0,
tv_nsec: UTIME_NOW.into(),
},
Some(SystemTimeSpec::Absolute(ft)) => {
let duration = ft.duration_since(SystemTime::UNIX_EPOCH).unwrap();
let nanoseconds = duration.subsec_nanos();
assert_ne!(i64::from(nanoseconds), i64::from(UTIME_OMIT));
assert_ne!(i64::from(nanoseconds), i64::from(UTIME_NOW));
timespec {
tv_sec: duration
.as_secs()
.try_into()
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?,
tv_nsec: nanoseconds.try_into().unwrap(),
}
}
})
}
#[cfg(windows)]
impl<T> SetTimes for T
where
T: AsRawHandle,
{
#[inline]
fn set_times(
&self,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> io::Result<()> {
_set_file_times(unsafe { &*as_file(self) }, atime, mtime)
}
}
#[cfg(windows)]
fn _set_file_times(
file: &fs::File,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> io::Result<()> {
let mut now = None;
let atime = match atime {
None => None,
Some(SystemTimeSpec::SymbolicNow) => {
let right_now = SystemTime::now();
now = Some(right_now);
Some(right_now)
}
Some(SystemTimeSpec::Absolute(time)) => Some(time),
};
let mtime = match mtime {
None => None,
Some(SystemTimeSpec::SymbolicNow) => {
if let Some(prev_now) = now {
Some(prev_now)
} else {
Some(SystemTime::now())
}
}
Some(SystemTimeSpec::Absolute(time)) => Some(time),
};
let atime = atime.map(to_filetime).transpose()?;
let mtime = mtime.map(to_filetime).transpose()?;
if unsafe {
SetFileTime(
file.as_raw_handle(),
ptr::null(),
atime.as_ref().map(|r| r as *const _).unwrap_or(ptr::null()),
mtime.as_ref().map(|r| r as *const _).unwrap_or(ptr::null()),
)
} != 0
{
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
#[cfg(windows)]
fn to_filetime(ft: SystemTime) -> io::Result<FILETIME> {
let ft = ft
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
let ft = ft + Duration::from_secs(11_644_473_600);
let intervals = ft.as_secs() * (1_000_000_000 / 100) + u64::from(ft.subsec_nanos() / 100);
if intervals == 0 {
return Err(io::Error::from_raw_os_error(ERROR_NOT_SUPPORTED as i32));
}
Ok(FILETIME {
dwLowDateTime: intervals as DWORD,
dwHighDateTime: (intervals >> 32) as DWORD,
})
}