Skip to content

Commit

Permalink
Store file id in an enum
Browse files Browse the repository at this point in the history
  • Loading branch information
dfaust authored and 0xpr03 committed Aug 8, 2023
1 parent cd53ad6 commit 87b1264
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 33 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
v5 maintenance branch is on `v5_maintenance` after `5.2.0`
v4 commits split out to branch `v4_maintenance` starting with `4.0.16`

## file-id 0.2.0

- CHANGE: switch from winapi to windows-sys [#494]
- CHANGE: turn FileId struct into an enum [#494]
- FEATURE: support for high resolution file ids on Windows using GetFileInformationByHandleEx [#494]

[#494]: https://github.com/notify-rs/notify/pull/494

## notify 6.0.1 (2023-06-16)

- DOCS: fix swapped debouncer-full / -mini links in the readme/crates.io [4be6bde]
Expand Down Expand Up @@ -36,6 +44,10 @@ Newly introduced alternative debouncer with more features. [#480]
- FEATURE: don't emit duplicate create events
- FEATURE: don't emit `Modify` events after a `Create` event

## file-id 0.1.0 (2023-05-17)

Utility for reading inode numbers (Linux, MacOS) and file IDs (Windows). [#480]

[#480]: https://github.com/notify-rs/notify/pull/480

## notify 5.2.0 (2023-05-17)
Expand Down
10 changes: 7 additions & 3 deletions file-id/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
27 changes: 27 additions & 0 deletions file-id/bin/file_id.rs
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}"),
}
}
187 changes: 160 additions & 27 deletions file-id/src/lib.rs
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)
}
6 changes: 3 additions & 3 deletions notify-debouncer-full/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ impl schema::State {
.into_iter()
.map(|(path, id)| {
let path = PathBuf::from(path);
let id = FileId::new(id, id);
let id = FileId::new_inode(id, id);
(path, id)
})
.collect::<HashMap<_, _>>();
Expand All @@ -249,15 +249,15 @@ impl schema::State {
.into_iter()
.map(|(path, id)| {
let path = PathBuf::from(path);
let id = FileId::new(id, id);
let id = FileId::new_inode(id, id);
(path, id)
})
.collect::<HashMap<_, _>>();

let cache = TestCache::new(cache, file_system);

let rename_event = self.rename_event.map(|e| {
let file_id = e.file_id.map(|id| FileId::new(id, id));
let file_id = e.file_id.map(|id| FileId::new_inode(id, id));
let event = e.into_debounced_event(time, None);
(event, file_id)
});
Expand Down

0 comments on commit 87b1264

Please sign in to comment.