use std::io;
use std::path::Path;
use crate::error::{Error, ErrorContext, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FileIdentity {
#[cfg(unix)]
Unix { device: u64, inode: u64 },
#[cfg(windows)]
Windows { volume: u32, index: u64 },
#[cfg(not(any(unix, windows)))]
Other { length: u64, modified: Option<u64> },
}
impl FileIdentity {
pub(crate) fn capture(path: &Path) -> io::Result<Self> {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
let metadata = std::fs::metadata(path)?;
Ok(Self::Unix {
device: metadata.dev(),
inode: metadata.ino(),
})
}
#[cfg(windows)]
{
let file = std::fs::File::open(path)?;
Self::from_handle(&file)
}
#[cfg(not(any(unix, windows)))]
{
use std::time::SystemTime;
let metadata = std::fs::metadata(path)?;
Ok(Self::Other {
length: metadata.len(),
modified: metadata
.modified()
.ok()
.and_then(|time| time.duration_since(SystemTime::UNIX_EPOCH).ok())
.map(|duration| duration.as_secs()),
})
}
}
pub(crate) fn expect_unchanged(self, path: &Path) -> Result<()> {
let current = FileIdentity::capture(path)
.map_err(|error| Error::io(error, None).with_context(ErrorContext::File))?;
if current == self {
return Ok(());
}
Err(replaced(path))
}
#[cfg(windows)]
fn from_handle(file: &std::fs::File) -> io::Result<Self> {
use std::ffi::c_void;
use std::mem::zeroed;
use std::os::windows::io::AsRawHandle;
#[repr(C)]
struct FileTime {
low: u32,
high: u32,
}
#[repr(C)]
struct ByHandleFileInformation {
file_attributes: u32,
creation_time: FileTime,
last_access_time: FileTime,
last_write_time: FileTime,
volume_serial_number: u32,
file_size_high: u32,
file_size_low: u32,
number_of_links: u32,
file_index_high: u32,
file_index_low: u32,
}
const _: () = assert!(size_of::<ByHandleFileInformation>() == 52);
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetFileInformationByHandle(
file: *mut c_void,
information: *mut ByHandleFileInformation,
) -> i32;
}
let mut information: ByHandleFileInformation = unsafe { zeroed() };
let result = unsafe { GetFileInformationByHandle(file.as_raw_handle(), &mut information) };
if result == 0 {
return Err(io::Error::last_os_error());
}
Ok(Self::Windows {
volume: information.volume_serial_number,
index: (u64::from(information.file_index_high) << 32)
| u64::from(information.file_index_low),
})
}
}
pub(crate) fn replaced(path: &Path) -> Error {
Error::file_replaced(format!(
"the file at {} is not the file this reader opened; it was replaced \
and refresh refuses to adopt it",
path.display()
))
.with_context(ErrorContext::File)
}