file_id/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//! Utility for reading inode numbers (Linux, macOS) and file ids (Windows) that uniquely identify a file on a single computer.
//!
//! Modern file systems assign a unique ID to each file. On Linux and macOS it is called an `inode number`,
//! on Windows it is called a `file id` or `file index`.
//! Together with the `device id` (Linux, macOS) or the `volume serial number` (Windows),
//! a file or directory can be uniquely identified on a single computer at a given time.
//!
//! Keep in mind though, that IDs may be re-used at some point.
//!
//! ## Example
//!
//! ```
//! let file = tempfile::NamedTempFile::new().unwrap();
//!
//! let file_id = file_id::get_file_id(file.path()).unwrap();
//! println!("{file_id:?}");
//! ```
//!
//! ## Example (Windows Only)
//!
//! ```ignore
//! let file = tempfile::NamedTempFile::new().unwrap();
//!
//! let file_id = file_id::get_low_res_file_id(file.path()).unwrap();
//! println!("{file_id:?}");
//!
//! let file_id = file_id::get_high_res_file_id(file.path()).unwrap();
//! println!("{file_id:?}");
//! ```
use std::{fs, io, path::Path};

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// Unique identifier of a file
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum FileId {
    /// Inode number, available on Linux and macOS.
    #[cfg_attr(feature = "serde", serde(rename = "inode"))]
    Inode {
        /// Device ID
        #[cfg_attr(feature = "serde", serde(rename = "device"))]
        device_id: u64,

        /// Inode number
        #[cfg_attr(feature = "serde", serde(rename = "inode"))]
        inode_number: u64,
    },

    /// Low resolution file ID, available on Windows XP and above.
    ///
    /// Compared to the high resolution variant, only the lower parts of the IDs are stored.
    ///
    /// On Windows, the low resolution variant can be requested explicitly with the `get_low_res_file_id` function.
    ///
    /// Details: <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle>.
    #[cfg_attr(feature = "serde", serde(rename = "lowres"))]
    LowRes {
        /// Volume serial number
        #[cfg_attr(feature = "serde", serde(rename = "volume"))]
        volume_serial_number: u32,

        /// File index
        #[cfg_attr(feature = "serde", serde(rename = "index"))]
        file_index: u64,
    },

    /// High resolution file ID, available on Windows Vista and above.
    ///
    /// On Windows, the high resolution variant can be requested explicitly with the `get_high_res_file_id` function.
    ///
    /// Details: <https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getfileinformationbyhandleex>.
    #[cfg_attr(feature = "serde", serde(rename = "highres"))]
    HighRes {
        /// Volume serial number
        #[cfg_attr(feature = "serde", serde(rename = "volume"))]
        volume_serial_number: u64,

        /// File ID
        #[cfg_attr(feature = "serde", serde(rename = "file"))]
        file_id: u128,
    },
}

impl FileId {
    pub fn new_inode(device_id: u64, inode_number: u64) -> Self {
        FileId::Inode {
            device_id,
            inode_number,
        }
    }

    pub fn new_low_res(volume_serial_number: u32, file_index: u64) -> Self {
        FileId::LowRes {
            volume_serial_number,
            file_index,
        }
    }

    pub fn new_high_res(volume_serial_number: u64, file_id: u128) -> Self {
        FileId::HighRes {
            volume_serial_number,
            file_id,
        }
    }
}

/// Get the `FileId` for the file or directory at `path`
#[cfg(target_family = "unix")]
pub fn get_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
    use std::os::unix::fs::MetadataExt;

    let metadata = fs::metadata(path.as_ref())?;

    Ok(FileId::new_inode(metadata.dev(), metadata.ino()))
}

/// Get the `FileId` for the file or directory at `path`
#[cfg(target_family = "windows")]
pub fn get_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
    let file = open_file(path)?;

    unsafe { get_file_info_ex(&file).or_else(|_| get_file_info(&file)) }
}

/// Get the `FileId` with the low resolution variant for the file or directory at `path`
#[cfg(target_family = "windows")]
pub fn get_low_res_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
    let file = open_file(path)?;

    unsafe { get_file_info(&file) }
}

/// Get the `FileId` with the high resolution variant for the file or directory at `path`
#[cfg(target_family = "windows")]
pub fn get_high_res_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
    let file = open_file(path)?;

    unsafe { get_file_info_ex(&file) }
}

#[cfg(target_family = "windows")]
unsafe fn get_file_info_ex(file: &fs::File) -> Result<FileId, io::Error> {
    use std::{mem, os::windows::prelude::*};
    use windows_sys::Win32::{
        Foundation::HANDLE,
        Storage::FileSystem::{FileIdInfo, GetFileInformationByHandleEx, FILE_ID_INFO},
    };

    let mut info: FILE_ID_INFO = mem::zeroed();
    let ret = GetFileInformationByHandleEx(
        file.as_raw_handle() as HANDLE,
        FileIdInfo,
        &mut info as *mut FILE_ID_INFO as _,
        mem::size_of::<FILE_ID_INFO>() as u32,
    );

    if ret == 0 {
        return Err(io::Error::last_os_error());
    };

    Ok(FileId::new_high_res(
        info.VolumeSerialNumber,
        u128::from_le_bytes(info.FileId.Identifier),
    ))
}

#[cfg(target_family = "windows")]
unsafe fn get_file_info(file: &fs::File) -> Result<FileId, io::Error> {
    use std::{mem, os::windows::prelude::*};
    use windows_sys::Win32::{
        Foundation::HANDLE,
        Storage::FileSystem::{GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION},
    };

    let mut info: BY_HANDLE_FILE_INFORMATION = mem::zeroed();
    let ret = GetFileInformationByHandle(file.as_raw_handle() as HANDLE, &mut info);
    if ret == 0 {
        return Err(io::Error::last_os_error());
    };

    Ok(FileId::new_low_res(
        info.dwVolumeSerialNumber,
        ((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64),
    ))
}

#[cfg(target_family = "windows")]
fn open_file<P: AsRef<Path>>(path: P) -> io::Result<fs::File> {
    use std::{fs::OpenOptions, os::windows::fs::OpenOptionsExt};
    use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS;

    OpenOptions::new()
        .access_mode(0)
        .custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
        .open(path)
}