-
Notifications
You must be signed in to change notification settings - Fork 229
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
209 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
name = "file-id" | ||
version = "0.1.0" | ||
rust-version = "1.60" | ||
description = "Platform independent file id library" | ||
description = "Utility for reading inode numbers (Linux, MacOS) and file IDs (Windows)" | ||
documentation = "https://docs.rs/notify" | ||
homepage = "https://github.com/notify-rs/notify" | ||
repository = "https://github.com/notify-rs/notify.git" | ||
|
@@ -14,11 +14,15 @@ authors = ["Daniel Faust <[email protected]>"] | |
|
||
edition = "2021" | ||
|
||
[[bin]] | ||
name = "file-id" | ||
path = "bin/file_id.rs" | ||
|
||
[dependencies] | ||
serde = { version = "1.0.89", features = ["derive"], optional = true } | ||
|
||
[target.'cfg(windows)'.dependencies.winapi-util] | ||
version = "0.1.5" | ||
[target.'cfg(windows)'.dependencies] | ||
windows-sys = { version = "0.48.0", features = ["Win32_Storage_FileSystem", "Win32_Foundation"] } | ||
|
||
[dev-dependencies] | ||
tempfile = "3.2.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
use std::io; | ||
|
||
use file_id::FileId; | ||
|
||
fn main() { | ||
let path = std::env::args().nth(1).expect("no path given"); | ||
|
||
print_file_id(&path); | ||
} | ||
|
||
#[cfg(target_family = "unix")] | ||
fn print_file_id(path: &str) { | ||
print_result(file_id::get_file_id(path)); | ||
} | ||
|
||
#[cfg(target_family = "windows")] | ||
fn print_file_id(path: &str) { | ||
print_result(file_id::get_low_res_file_id(path)); | ||
print_result(file_id::get_high_res_file_id(path)); | ||
} | ||
|
||
fn print_result(result: io::Result<FileId>) { | ||
match result { | ||
Ok(file_id) => println!("{file_id:?}"), | ||
Err(error) => println!("Error: {error}"), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,65 +1,198 @@ | ||
//! A utility to read file IDs that are unique on a given device. | ||
//! 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 index`. | ||
//! Together with the `device id`, a file can be identified uniquely on a device at a given time. | ||
//! 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 | ||
//! | ||
//! ```rust | ||
//! ``` | ||
//! 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::{io, path::Path}; | ||
use std::{fs, io, path::Path}; | ||
|
||
#[cfg(feature = "serde")] | ||
use serde::{Deserialize, Serialize}; | ||
|
||
/// Unique identifier of a file | ||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||
pub struct FileId { | ||
/// Device ID or volume serial number | ||
pub device: u64, | ||
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, | ||
|
||
/// Inode number or file index | ||
pub file: u64, | ||
/// 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(device: u64, file: u64) -> Self { | ||
Self { device, file } | ||
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 at `path` | ||
/// 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::fs; | ||
use std::os::unix::fs::MetadataExt; | ||
|
||
let metadata = fs::metadata(path.as_ref())?; | ||
|
||
Ok(FileId { | ||
device: metadata.dev(), | ||
file: metadata.ino(), | ||
}) | ||
Ok(FileId::new_inode(metadata.dev(), metadata.ino())) | ||
} | ||
|
||
/// Get the `FileId` for the file at `path` | ||
/// 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> { | ||
use winapi_util::{file::information, Handle}; | ||
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_ex(&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(&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, | ||
); | ||
|
||
let handle = Handle::from_path_any(path.as_ref())?; | ||
let info = information(&handle)?; | ||
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; | ||
|
||
Ok(FileId { | ||
device: info.volume_serial_number(), | ||
file: info.file_index(), | ||
}) | ||
OpenOptions::new() | ||
.read(true) | ||
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS) | ||
.open(path) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters