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
109impl AsRef<FileId> for FileId {
110    fn as_ref(&self) -> &FileId {
111        self
112    }
113}
114
115/// Get the `FileId` for the file or directory at `path`
116#[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/// Get the `FileId` for the file or directory at `path`
126#[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/// Get the `FileId` with the low resolution variant for the file or directory at `path`
134#[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/// Get the `FileId` with the high resolution variant for the file or directory at `path`
142#[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}