use std::ffi::{c_void, CStr, CString};
use std::fmt;
use std::fs::File;
use std::mem::MaybeUninit;
use std::os::fd::AsRawFd;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::{io, mem, ptr};
#[derive(Copy, Clone)]
pub struct Saved {
create_time: libc::timespec,
mod_time: libc::timespec,
access_time: libc::timespec,
add_time: libc::timespec,
}
impl Saved {
fn from_attr_buf(attr_buf: &AttrGetBuf) -> Self {
assert_eq!(attr_buf.len as usize, size_of_val(attr_buf));
Self {
create_time: attr_buf.create_time,
mod_time: attr_buf.mod_time,
access_time: attr_buf.access_time,
add_time: attr_buf.add_time,
}
}
}
impl fmt::Debug for Saved {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let time_debug = |time: &libc::timespec| format!("@{}.{:09}", time.tv_sec, time.tv_nsec);
f.debug_struct("Saved")
.field("create_time", &time_debug(&self.create_time))
.field("mod_time", &time_debug(&self.mod_time))
.field("access_time", &time_debug(&self.access_time))
.field("add_time", &time_debug(&self.add_time))
.finish()
}
}
#[repr(C, packed(4))]
struct AttrGetBuf {
len: u32,
returned_attrs: libc::attribute_set_t,
create_time: libc::timespec,
mod_time: libc::timespec,
access_time: libc::timespec,
add_time: libc::timespec,
}
#[repr(C, packed(4))]
struct AttrSetBuf {
create_time: libc::timespec,
mod_time: libc::timespec,
access_time: libc::timespec,
add_time: libc::timespec,
}
pub trait GetSet {
fn get_times(&self) -> io::Result<Saved>;
fn reset_times(&self, saved: &Saved) -> io::Result<()>;
}
fn attrlist_get() -> libc::attrlist {
let mut attrlist: libc::attrlist = unsafe { mem::zeroed() };
attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT;
attrlist.commonattr = libc::ATTR_CMN_RETURNED_ATTRS
| libc::ATTR_CMN_CRTIME
| libc::ATTR_CMN_MODTIME
| libc::ATTR_CMN_ACCTIME
| libc::ATTR_CMN_ADDEDTIME;
attrlist
}
fn attrlist_set() -> libc::attrlist {
let mut attrlist: libc::attrlist = unsafe { mem::zeroed() };
attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT;
attrlist.commonattr = libc::ATTR_CMN_CRTIME
| libc::ATTR_CMN_MODTIME
| libc::ATTR_CMN_ACCTIME
| libc::ATTR_CMN_ADDEDTIME;
attrlist
}
impl GetSet for File {
fn get_times(&self) -> io::Result<Saved> {
let mut attrlist = attrlist_get();
let mut attr_buf: MaybeUninit<AttrGetBuf> = MaybeUninit::uninit();
unsafe {
let rc = libc::fgetattrlist(
self.as_raw_fd(),
ptr::addr_of_mut!(attrlist).cast::<c_void>(),
attr_buf.as_mut_ptr().cast::<c_void>(),
size_of::<AttrGetBuf>(),
libc::FSOPT_PACK_INVAL_ATTRS,
);
if rc != 0 {
return Err(io::Error::last_os_error());
}
let attr_buf = attr_buf.assume_init_ref();
Ok(Saved::from_attr_buf(attr_buf))
}
}
fn reset_times(&self, saved: &Saved) -> io::Result<()> {
let mut attrlist = attrlist_set();
let mut attr_buf = AttrSetBuf {
create_time: saved.create_time,
mod_time: saved.mod_time,
access_time: saved.access_time,
add_time: saved.add_time,
};
unsafe {
let rc = libc::fsetattrlist(
self.as_raw_fd(),
ptr::addr_of_mut!(attrlist).cast::<c_void>(),
ptr::addr_of_mut!(attr_buf).cast::<c_void>(),
size_of::<AttrSetBuf>(),
0,
);
if rc != 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
}
}
impl GetSet for CStr {
fn get_times(&self) -> io::Result<Saved> {
let mut attrlist = attrlist_get();
let mut attr_buf: MaybeUninit<AttrGetBuf> = MaybeUninit::uninit();
unsafe {
let rc = libc::getattrlist(
self.as_ptr(),
ptr::addr_of_mut!(attrlist).cast::<c_void>(),
attr_buf.as_mut_ptr().cast::<c_void>(),
size_of::<AttrGetBuf>(),
libc::FSOPT_PACK_INVAL_ATTRS,
);
if rc != 0 {
return Err(io::Error::last_os_error());
}
let attr_buf = attr_buf.assume_init_ref();
Ok(Saved::from_attr_buf(attr_buf))
}
}
fn reset_times(&self, saved: &Saved) -> io::Result<()> {
let mut attrlist = attrlist_set();
let mut attr_buf = AttrSetBuf {
create_time: saved.create_time,
mod_time: saved.mod_time,
access_time: saved.access_time,
add_time: saved.add_time,
};
unsafe {
let rc = libc::setattrlist(
self.as_ptr(),
ptr::addr_of_mut!(attrlist).cast::<c_void>(),
ptr::addr_of_mut!(attr_buf).cast::<c_void>(),
size_of::<AttrSetBuf>(),
0,
);
if rc != 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
}
}
impl GetSet for Path {
fn get_times(&self) -> io::Result<Saved> {
let cstr = CString::new(self.as_os_str().as_bytes())?;
<CStr as GetSet>::get_times(&cstr)
}
fn reset_times(&self, saved: &Saved) -> io::Result<()> {
let cstr = CString::new(self.as_os_str().as_bytes())?;
<CStr as GetSet>::reset_times(&cstr, saved)
}
}
#[tracing::instrument(level = "debug")]
#[inline]
pub fn save_times<F: GetSet + fmt::Debug + ?Sized>(f: &F) -> io::Result<Saved> {
f.get_times()
}
#[tracing::instrument(level = "debug")]
#[inline]
pub fn reset_times<F: GetSet + fmt::Debug + ?Sized>(f: &F, saved: &Saved) -> io::Result<()> {
f.reset_times(saved)
}
#[derive(Debug)]
pub struct Resetter {
dir_path: CString,
saved_times: Saved,
activated: AtomicBool,
}
impl Resetter {
pub fn new(path: &Path, saved_times: Saved) -> io::Result<Self> {
let dir_path = CString::new(path.as_os_str().as_bytes())?;
Ok(Self {
dir_path,
saved_times,
activated: AtomicBool::new(false),
})
}
pub fn activate(&self) {
self.activated
.store(true, std::sync::atomic::Ordering::Relaxed);
}
}
impl Drop for Resetter {
fn drop(&mut self) {
if self.activated.load(std::sync::atomic::Ordering::Relaxed) {
let _ = reset_times(self.dir_path.as_c_str(), &self.saved_times);
}
}
}