1use std::{fs, io, path::Path};
31
32#[cfg(feature = "serde")]
33use serde::{Deserialize, Serialize};
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
37#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38pub enum FileId {
39 #[cfg_attr(feature = "serde", serde(rename = "inode"))]
41 Inode {
42 #[cfg_attr(feature = "serde", serde(rename = "device"))]
44 device_id: u64,
45
46 #[cfg_attr(feature = "serde", serde(rename = "inode"))]
48 inode_number: u64,
49 },
50
51 #[cfg_attr(feature = "serde", serde(rename = "lowres"))]
59 LowRes {
60 #[cfg_attr(feature = "serde", serde(rename = "volume"))]
62 volume_serial_number: u32,
63
64 #[cfg_attr(feature = "serde", serde(rename = "index"))]
66 file_index: u64,
67 },
68
69 #[cfg_attr(feature = "serde", serde(rename = "highres"))]
75 HighRes {
76 #[cfg_attr(feature = "serde", serde(rename = "volume"))]
78 volume_serial_number: u64,
79
80 #[cfg_attr(feature = "serde", serde(rename = "file"))]
82 file_id: u128,
83 },
84}
85
86impl FileId {
87 pub fn new_inode(device_id: u64, inode_number: u64) -> Self {
88 FileId::Inode {
89 device_id,
90 inode_number,
91 }
92 }
93
94 pub fn new_low_res(volume_serial_number: u32, file_index: u64) -> Self {
95 FileId::LowRes {
96 volume_serial_number,
97 file_index,
98 }
99 }
100
101 pub fn new_high_res(volume_serial_number: u64, file_id: u128) -> Self {
102 FileId::HighRes {
103 volume_serial_number,
104 file_id,
105 }
106 }
107}
108
109#[cfg(target_family = "unix")]
111pub fn get_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
112 use std::os::unix::fs::MetadataExt;
113
114 let metadata = fs::metadata(path.as_ref())?;
115
116 Ok(FileId::new_inode(metadata.dev(), metadata.ino()))
117}
118
119#[cfg(target_family = "windows")]
121pub fn get_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
122 let file = open_file(path)?;
123
124 unsafe { get_file_info_ex(&file).or_else(|_| get_file_info(&file)) }
125}
126
127#[cfg(target_family = "windows")]
129pub fn get_low_res_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
130 let file = open_file(path)?;
131
132 unsafe { get_file_info(&file) }
133}
134
135#[cfg(target_family = "windows")]
137pub fn get_high_res_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
138 let file = open_file(path)?;
139
140 unsafe { get_file_info_ex(&file) }
141}
142
143#[cfg(target_family = "windows")]
144unsafe fn get_file_info_ex(file: &fs::File) -> Result<FileId, io::Error> {
145 use std::{mem, os::windows::prelude::*};
146 use windows_sys::Win32::{
147 Foundation::HANDLE,
148 Storage::FileSystem::{FileIdInfo, GetFileInformationByHandleEx, FILE_ID_INFO},
149 };
150
151 let mut info: FILE_ID_INFO = mem::zeroed();
152 let ret = GetFileInformationByHandleEx(
153 file.as_raw_handle() as HANDLE,
154 FileIdInfo,
155 &mut info as *mut FILE_ID_INFO as _,
156 mem::size_of::<FILE_ID_INFO>() as u32,
157 );
158
159 if ret == 0 {
160 return Err(io::Error::last_os_error());
161 };
162
163 Ok(FileId::new_high_res(
164 info.VolumeSerialNumber,
165 u128::from_le_bytes(info.FileId.Identifier),
166 ))
167}
168
169#[cfg(target_family = "windows")]
170unsafe fn get_file_info(file: &fs::File) -> Result<FileId, io::Error> {
171 use std::{mem, os::windows::prelude::*};
172 use windows_sys::Win32::{
173 Foundation::HANDLE,
174 Storage::FileSystem::{GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION},
175 };
176
177 let mut info: BY_HANDLE_FILE_INFORMATION = mem::zeroed();
178 let ret = GetFileInformationByHandle(file.as_raw_handle() as HANDLE, &mut info);
179 if ret == 0 {
180 return Err(io::Error::last_os_error());
181 };
182
183 Ok(FileId::new_low_res(
184 info.dwVolumeSerialNumber,
185 ((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64),
186 ))
187}
188
189#[cfg(target_family = "windows")]
190fn open_file<P: AsRef<Path>>(path: P) -> io::Result<fs::File> {
191 use std::{fs::OpenOptions, os::windows::fs::OpenOptionsExt};
192 use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS;
193
194 OpenOptions::new()
195 .access_mode(0)
196 .custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
197 .open(path)
198}