win_file_id/
lib.rs

1//! Utility for reading file ids (Windows) that uniquely identify a file on a single computer.
2//!
3//! Modern file systems assign a unique ID to each file.
4//! On Windows it is called a `file id` or `file index`.
5//! Together with the `device id` (Linux, macOS) or the `volume serial number` (Windows),
6//! a file or directory can be uniquely identified on a single computer at a given time.
7//!
8//! Keep in mind though, that IDs may be re-used at some point.
9//!
10//! ## Example
11//!
12//! ```
13//! let file = tempfile::NamedTempFile::new().unwrap();
14//!
15//! let file_id = win_file_id::get_file_id(file.path()).unwrap();
16//! println!("{file_id:?}");
17//! ```
18//!
19//! ## Example (Windows Only)
20//!
21//! ```ignore
22//! let file = tempfile::NamedTempFile::new().unwrap();
23//!
24//! let file_id = win_file_id::get_low_res_file_id(file.path()).unwrap();
25//! println!("{file_id:?}");
26//!
27//! let file_id = win_file_id::get_high_res_file_id(file.path()).unwrap();
28//! println!("{file_id:?}");
29//! ```
30use std::{fs, io, path::Path};
31
32#[cfg(feature = "serde")]
33use serde::{Deserialize, Serialize};
34
35/// Unique identifier of a file
36#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
37#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38pub enum FileId {
39    /// Low resolution file ID, available on Windows XP and above.
40    ///
41    /// Compared to the high resolution variant, only the lower parts of the IDs are stored.
42    ///
43    /// On Windows, the low resolution variant can be requested explicitly with the `get_low_res_file_id` function.
44    ///
45    /// Details: <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle>.
46    #[cfg_attr(feature = "serde", serde(rename = "lowres"))]
47    LowRes {
48        /// Volume serial number
49        #[cfg_attr(feature = "serde", serde(rename = "volume"))]
50        volume_serial_number: u32,
51
52        /// File index
53        #[cfg_attr(feature = "serde", serde(rename = "index"))]
54        file_index: u64,
55    },
56
57    /// High resolution file ID, available on Windows Vista and above.
58    ///
59    /// On Windows, the high resolution variant can be requested explicitly with the `get_high_res_file_id` function.
60    ///
61    /// Details: <https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getfileinformationbyhandleex>.
62    #[cfg_attr(feature = "serde", serde(rename = "highres"))]
63    HighRes {
64        /// Volume serial number
65        #[cfg_attr(feature = "serde", serde(rename = "volume"))]
66        volume_serial_number: u64,
67
68        /// File ID
69        #[cfg_attr(feature = "serde", serde(rename = "file"))]
70        file_id: u128,
71    },
72}
73
74impl FileId {
75    pub fn new_low_res(volume_serial_number: u32, file_index: u64) -> Self {
76        FileId::LowRes {
77            volume_serial_number,
78            file_index,
79        }
80    }
81
82    pub fn new_high_res(volume_serial_number: u64, file_id: u128) -> Self {
83        FileId::HighRes {
84            volume_serial_number,
85            file_id,
86        }
87    }
88}
89
90/// Get the `FileId` for the file or directory at `path`
91pub fn get_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
92    let file = open_file(path)?;
93
94    unsafe { get_file_info_ex(&file).or_else(|_| get_file_info(&file)) }
95}
96
97/// Get the `FileId` with the low resolution variant for the file or directory at `path`
98pub fn get_low_res_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
99    let file = open_file(path)?;
100
101    unsafe { get_file_info(&file) }
102}
103
104/// Get the `FileId` with the high resolution variant for the file or directory at `path`
105pub fn get_high_res_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
106    let file = open_file(path)?;
107
108    unsafe { get_file_info_ex(&file) }
109}
110
111unsafe fn get_file_info_ex(file: &fs::File) -> Result<FileId, io::Error> {
112    use std::{mem, os::windows::prelude::*};
113    use windows_sys::Win32::{
114        Foundation::HANDLE,
115        Storage::FileSystem::{FileIdInfo, GetFileInformationByHandleEx, FILE_ID_INFO},
116    };
117
118    let mut info: FILE_ID_INFO = mem::zeroed();
119    let ret = GetFileInformationByHandleEx(
120        file.as_raw_handle() as HANDLE,
121        FileIdInfo,
122        &mut info as *mut FILE_ID_INFO as _,
123        mem::size_of::<FILE_ID_INFO>() as u32,
124    );
125
126    if ret == 0 {
127        return Err(io::Error::last_os_error());
128    };
129
130    Ok(FileId::new_high_res(
131        info.VolumeSerialNumber,
132        u128::from_le_bytes(info.FileId.Identifier),
133    ))
134}
135
136unsafe fn get_file_info(file: &fs::File) -> Result<FileId, io::Error> {
137    use std::{mem, os::windows::prelude::*};
138    use windows_sys::Win32::{
139        Foundation::HANDLE,
140        Storage::FileSystem::{GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION},
141    };
142
143    let mut info: BY_HANDLE_FILE_INFORMATION = mem::zeroed();
144    let ret = GetFileInformationByHandle(file.as_raw_handle() as HANDLE, &mut info);
145    if ret == 0 {
146        return Err(io::Error::last_os_error());
147    };
148
149    Ok(FileId::new_low_res(
150        info.dwVolumeSerialNumber,
151        ((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64),
152    ))
153}
154
155fn open_file<P: AsRef<Path>>(path: P) -> io::Result<fs::File> {
156    use std::{fs::OpenOptions, os::windows::fs::OpenOptionsExt};
157    use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS;
158
159    OpenOptions::new()
160        .access_mode(0)
161        .custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
162        .open(path)
163}