use std::path::Path;
#[cfg(unix)]
pub fn change_token(_path: &Path, meta: &std::fs::Metadata) -> i64 {
use std::os::unix::fs::MetadataExt;
meta.ctime_nsec()
}
#[cfg(windows)]
#[allow(unsafe_code)] pub fn change_token(path: &Path, _meta: &std::fs::Metadata) -> i64 {
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, FileBasicInfo, GetFileInformationByHandleEx, FILE_ATTRIBUTE_NORMAL,
FILE_BASIC_INFO, FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ,
FILE_SHARE_WRITE, OPEN_EXISTING,
};
let wide: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
let handle = unsafe {
CreateFileW(
wide.as_ptr(),
FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
std::ptr::null(),
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
std::ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE || handle.is_null() {
return 0;
}
let mut info: FILE_BASIC_INFO = unsafe { std::mem::zeroed() };
let ok = unsafe {
GetFileInformationByHandleEx(
handle,
FileBasicInfo,
std::ptr::addr_of_mut!(info).cast(),
std::mem::size_of::<FILE_BASIC_INFO>() as u32,
)
};
unsafe {
CloseHandle(handle);
}
if ok == 0 {
0
} else {
info.ChangeTime
}
}
#[cfg(not(any(unix, windows)))]
pub fn change_token(_path: &Path, _meta: &std::fs::Metadata) -> i64 {
0
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[cfg(any(unix, windows))]
#[test]
fn token_moves_when_a_same_size_overwrite_preserves_the_mtime() {
let dir = tempdir().unwrap();
let path = dir.path().join("file.bin");
fs::write(&path, b"aaaa").unwrap();
let original_mtime = fs::metadata(&path).unwrap().modified().unwrap();
let first = change_token(&path, &fs::metadata(&path).unwrap());
settle_timestamp_clock();
fs::write(&path, b"bbbb").unwrap();
restore_mtime(&path, original_mtime);
let meta = fs::metadata(&path).unwrap();
let second = change_token(&path, &meta);
assert_eq!(
meta.len(),
4,
"the fixture must keep the same size for this to mean anything"
);
assert_ne!(
first, second,
"the change token did not move for a modified file"
);
}
#[cfg(any(unix, windows))]
#[test]
fn token_is_stable_for_an_untouched_file() {
let dir = tempdir().unwrap();
let path = dir.path().join("file.bin");
fs::write(&path, b"stable").unwrap();
let first = change_token(&path, &fs::metadata(&path).unwrap());
let second = change_token(&path, &fs::metadata(&path).unwrap());
assert_eq!(first, second, "an untouched file must not invalidate");
}
fn settle_timestamp_clock() {
std::thread::sleep(std::time::Duration::from_millis(30));
}
#[cfg(unix)]
fn restore_mtime(path: &Path, mtime: std::time::SystemTime) {
use std::os::unix::fs::PermissionsExt;
let mode = fs::metadata(path).unwrap().permissions().mode();
fs::set_permissions(path, fs::Permissions::from_mode(mode)).unwrap();
fs::File::options()
.write(true)
.open(path)
.unwrap()
.set_modified(mtime)
.unwrap();
}
#[cfg(windows)]
fn restore_mtime(path: &Path, mtime: std::time::SystemTime) {
let file = fs::File::options().write(true).open(path).unwrap();
file.set_modified(mtime).unwrap();
}
}