use std::{fs, io, path::Path};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum FileId {
#[cfg_attr(feature = "serde", serde(rename = "inode"))]
Inode {
#[cfg_attr(feature = "serde", serde(rename = "device"))]
device_id: u64,
#[cfg_attr(feature = "serde", serde(rename = "inode"))]
inode_number: u64,
},
#[cfg_attr(feature = "serde", serde(rename = "lowres"))]
LowRes {
#[cfg_attr(feature = "serde", serde(rename = "volume"))]
volume_serial_number: u32,
#[cfg_attr(feature = "serde", serde(rename = "index"))]
file_index: u64,
},
#[cfg_attr(feature = "serde", serde(rename = "highres"))]
HighRes {
#[cfg_attr(feature = "serde", serde(rename = "volume"))]
volume_serial_number: u64,
#[cfg_attr(feature = "serde", serde(rename = "file"))]
file_id: u128,
},
}
impl FileId {
pub fn new_inode(device_id: u64, inode_number: u64) -> Self {
FileId::Inode {
device_id,
inode_number,
}
}
pub fn new_low_res(volume_serial_number: u32, file_index: u64) -> Self {
FileId::LowRes {
volume_serial_number,
file_index,
}
}
pub fn new_high_res(volume_serial_number: u64, file_id: u128) -> Self {
FileId::HighRes {
volume_serial_number,
file_id,
}
}
}
#[cfg(target_family = "unix")]
pub fn get_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
use std::os::unix::fs::MetadataExt;
let metadata = fs::metadata(path.as_ref())?;
Ok(FileId::new_inode(metadata.dev(), metadata.ino()))
}
#[cfg(target_family = "windows")]
pub fn get_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
let file = open_file(path)?;
unsafe { get_file_info_ex(&file).or_else(|_| get_file_info(&file)) }
}
#[cfg(target_family = "windows")]
pub fn get_low_res_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
let file = open_file(path)?;
unsafe { get_file_info(&file) }
}
#[cfg(target_family = "windows")]
pub fn get_high_res_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
let file = open_file(path)?;
unsafe { get_file_info_ex(&file) }
}
#[cfg(target_family = "windows")]
unsafe fn get_file_info_ex(file: &fs::File) -> Result<FileId, io::Error> {
use std::{mem, os::windows::prelude::*};
use windows_sys::Win32::{
Foundation::HANDLE,
Storage::FileSystem::{FileIdInfo, GetFileInformationByHandleEx, FILE_ID_INFO},
};
let mut info: FILE_ID_INFO = mem::zeroed();
let ret = GetFileInformationByHandleEx(
file.as_raw_handle() as HANDLE,
FileIdInfo,
&mut info as *mut FILE_ID_INFO as _,
mem::size_of::<FILE_ID_INFO>() as u32,
);
if ret == 0 {
return Err(io::Error::last_os_error());
};
Ok(FileId::new_high_res(
info.VolumeSerialNumber,
u128::from_le_bytes(info.FileId.Identifier),
))
}
#[cfg(target_family = "windows")]
unsafe fn get_file_info(file: &fs::File) -> Result<FileId, io::Error> {
use std::{mem, os::windows::prelude::*};
use windows_sys::Win32::{
Foundation::HANDLE,
Storage::FileSystem::{GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION},
};
let mut info: BY_HANDLE_FILE_INFORMATION = mem::zeroed();
let ret = GetFileInformationByHandle(file.as_raw_handle() as HANDLE, &mut info);
if ret == 0 {
return Err(io::Error::last_os_error());
};
Ok(FileId::new_low_res(
info.dwVolumeSerialNumber,
((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64),
))
}
#[cfg(target_family = "windows")]
fn open_file<P: AsRef<Path>>(path: P) -> io::Result<fs::File> {
use std::{fs::OpenOptions, os::windows::fs::OpenOptionsExt};
use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS;
OpenOptions::new()
.access_mode(0)
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
.open(path)
}