file_id/
lib.rs

1//! Utility for reading inode numbers (Linux, macOS) and file ids (Windows) that uniquely identify a file on a single computer.
2//!
3//! Modern file systems assign a unique ID to each file. On Linux and macOS it is called an `inode number`,
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 = 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 = file_id::get_low_res_file_id(file.path()).unwrap();
25//! println!("{file_id:?}");
26//!
27//! let file_id = 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    /// Inode number, available on Linux and macOS.
40    #[cfg_attr(feature = "serde", serde(rename = "inode"))]
41    Inode {
42        /// Device ID
43        #[cfg_attr(feature = "serde", serde(rename = "device"))]
44        device_id: u64,
45
46        /// Inode number
47        #[cfg_attr(feature = "serde", serde(rename = "inode"))]
48        inode_number: u64,
49    },
50
51    /// Low resolution file ID, available on Windows XP and above.
52    ///
53    /// Compared to the high resolution variant, only the lower parts of the IDs are stored.
54    ///
55    /// On Windows, the low resolution variant can be requested explicitly with the `get_low_res_file_id` function.
56    ///
57    /// Details: <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle>.
58    #[cfg_attr(feature = "serde", serde(rename = "lowres"))]
59    LowRes {
60        /// Volume serial number
61        #[cfg_attr(feature = "serde", serde(rename = "volume"))]
62        volume_serial_number: u32,
63
64        /// File index
65        #[cfg_attr(feature = "serde", serde(rename = "index"))]
66        file_index: u64,
67    },
68
69    /// High resolution file ID, available on Windows Vista and above.
70    ///
71    /// On Windows, the high resolution variant can be requested explicitly with the `get_high_res_file_id` function.
72    ///
73    /// Details: <https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getfileinformationbyhandleex>.
74    #[cfg_attr(feature = "serde", serde(rename = "highres"))]
75    HighRes {
76        /// Volume serial number
77        #[cfg_attr(feature = "serde", serde(rename = "volume"))]
78        volume_serial_number: u64,
79
80        /// File ID
81        #[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/// Get the `FileId` for the file or directory at `path`
110#[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/// Get the `FileId` for the file or directory at `path`
120#[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/// Get the `FileId` with the low resolution variant for the file or directory at `path`
128#[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/// Get the `FileId` with the high resolution variant for the file or directory at `path`
136#[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}