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
109impl AsRef<FileId> for FileId {
110 fn as_ref(&self) -> &FileId {
111 self
112 }
113}
114
115#[cfg(target_family = "unix")]
117pub fn get_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
118 use std::os::unix::fs::MetadataExt;
119
120 let metadata = fs::metadata(path.as_ref())?;
121
122 Ok(FileId::new_inode(metadata.dev(), metadata.ino()))
123}
124
125#[cfg(target_family = "windows")]
127pub fn get_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
128 let file = open_file(path)?;
129
130 unsafe { get_file_info_ex(&file).or_else(|_| get_file_info(&file)) }
131}
132
133#[cfg(target_family = "windows")]
135pub fn get_low_res_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
136 let file = open_file(path)?;
137
138 unsafe { get_file_info(&file) }
139}
140
141#[cfg(target_family = "windows")]
143pub fn get_high_res_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
144 let file = open_file(path)?;
145
146 unsafe { get_file_info_ex(&file) }
147}
148
149#[cfg(target_family = "windows")]
150unsafe fn get_file_info_ex(file: &fs::File) -> Result<FileId, io::Error> {
151 use std::{mem, os::windows::prelude::*};
152 use windows_sys::Win32::{
153 Foundation::HANDLE,
154 Storage::FileSystem::{FileIdInfo, GetFileInformationByHandleEx, FILE_ID_INFO},
155 };
156
157 let mut info: FILE_ID_INFO = mem::zeroed();
158 let ret = GetFileInformationByHandleEx(
159 file.as_raw_handle() as HANDLE,
160 FileIdInfo,
161 &mut info as *mut FILE_ID_INFO as _,
162 mem::size_of::<FILE_ID_INFO>() as u32,
163 );
164
165 if ret == 0 {
166 return Err(io::Error::last_os_error());
167 };
168
169 Ok(FileId::new_high_res(
170 info.VolumeSerialNumber,
171 u128::from_le_bytes(info.FileId.Identifier),
172 ))
173}
174
175#[cfg(target_family = "windows")]
176unsafe fn get_file_info(file: &fs::File) -> Result<FileId, io::Error> {
177 use std::{mem, os::windows::prelude::*};
178 use windows_sys::Win32::{
179 Foundation::HANDLE,
180 Storage::FileSystem::{GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION},
181 };
182
183 let mut info: BY_HANDLE_FILE_INFORMATION = mem::zeroed();
184 let ret = GetFileInformationByHandle(file.as_raw_handle() as HANDLE, &mut info);
185 if ret == 0 {
186 return Err(io::Error::last_os_error());
187 };
188
189 Ok(FileId::new_low_res(
190 info.dwVolumeSerialNumber,
191 ((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64),
192 ))
193}
194
195#[cfg(target_family = "windows")]
196fn open_file<P: AsRef<Path>>(path: P) -> io::Result<fs::File> {
197 use std::{fs::OpenOptions, os::windows::fs::OpenOptionsExt};
198 use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS;
199
200 OpenOptions::new()
201 .access_mode(0)
202 .custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
203 .open(path)
204}