fs_id/
lib.rs

1use std::{ffi::OsStr, fs::File, io, path::Path};
2
3#[cfg_attr(windows, path = "windows.rs")]
4#[cfg_attr(unix, path = "unix.rs")]
5#[cfg_attr(target_os = "wasi", path = "wasi.rs")]
6mod sys;
7
8/// A file's identifier, can be compared with other `FileID`s to check if 2 variables point to the same file.
9/// 
10/// This struct is the combination of 2 identifiers:
11/// 
12/// * The id of the storage that contains the file.
13/// * The internal file id, unique only across files in the same storage.
14/// 
15/// Combining both allows to uniquely identify the file within the entire system.
16/// 
17/// `FileID` can be used to identify everything that implements [`AsRawFd`] (or [`AsRawHandle`] on Windows),
18/// but it may function differently when identifying non-files.  
19/// For example, when used on stdout/stderr/stdin,
20/// the 3 will share the same identifier if they belong to the same process.
21/// (Only works on Unix, on Windows it will just error...)
22/// 
23/// [`AsRawFd`]: os::fd::AsRawFd
24/// [`AsRawHandle`]: os::windows::io::AsRawHandle
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub struct FileID (sys::FileIDImpl);
27
28impl FileID {
29	/// Obtains the identifier of a file, directory, etc.
30	/// 
31	/// # Platform-specific behavior
32	/// 
33	/// While on Unix obtaining the identifier of a directory is possible,
34	/// on Windows an error will be returned instead.
35	/// 
36	/// This function uses:
37	/// * `fstat64` on Unix
38	/// * `GetFileInformationByHandleEx` on Windows.
39	/// * `fd_filestat_get` on WASI.
40	/// 
41	/// This may change in the future.
42	/// 
43	/// # Errors
44	///
45	/// This function will error if it fails to open the file
46	/// or fails to obtain the metadata containing the identifier.
47	/// 
48	/// # Examples
49	/// 
50	/// Basic usage:
51	/// 
52	/// ```rust,no_run
53	/// use fs_id::FileID;
54	/// 
55	/// fn main() -> std::io::Result<()> {
56	///     let file_id1 = FileID::new("/some/file/path.txt")?;
57	///     let file_id2 = FileID::new("/some/file/path.txt")?;
58	///     let file_id3 = FileID::new("/some/other/file.txt")?;
59	///     assert_eq!(file_id1, file_id2);
60	///     assert_ne!(file_id1, file_id3);
61	///     Ok(())
62	/// }
63	/// ```
64	/// 
65	/// Many different types can be used:
66	/// 
67	/// ```rust,no_run
68	/// use fs_id::FileID;
69	/// 
70	/// fn main() -> std::io::Result<()> {
71	///     let file_id1 = FileID::new("using_str.txt")?;
72	///     let file_id2 = FileID::new(std::ffi::OsStr::new("using_os_str.txt"))?;
73	///     let file_id3 = FileID::new(&std::fs::File::open("using_a_file.txt")?)?;
74	///     let file_id4 = FileID::new(&std::io::stdout())?;
75	///     // etc...
76	///     Ok(())
77	/// }
78	/// ```
79	pub fn new<T: GetID + ?Sized>(file: &T) -> io::Result<Self> {
80		file.get_id()
81	}
82
83	/// Returns the storage identifier from the file identifier.
84	/// 
85	/// # Platform-specific behavior
86	/// 
87	/// This returns:
88	/// * `st_dev` on Unix.
89	/// * `VolumeSerialNumber` on Windows.
90	/// * `dev` on WASI.
91	/// 
92	/// This may change in the future.
93	/// 
94	/// # Examples
95	/// 
96	/// ```rust,no_run
97	/// use fs_id::FileID;
98	/// 
99	/// fn main() -> std::io::Result<()> {
100	///     let file_id = FileID::new("/some/file/path.txt")?;
101	///     println!("{}", file_id.storage_id());
102	///     Ok(())
103	/// }
104	/// ```
105	#[must_use]
106	pub const fn storage_id(&self) -> u64 {
107		self.0.0
108	}
109
110	/// Returns the internal file identifier from the file identifier.  
111	/// Note that this value alone cannot uniquely identify the file within the system.
112	/// 
113	/// # Platform-specific behavior
114	/// 
115	/// This returns:
116	/// * `st_ino` on Unix.
117	/// * `FileId` on Windows.
118	/// * `ino` on WASI.
119	/// 
120	/// This may change in the future.
121	/// 
122	/// On Unix and WASI only 64 of the returned 128 bits are effectively used.
123	/// 
124	/// # Examples
125	/// 
126	/// ```rust,no_run
127	/// use fs_id::FileID;
128	/// 
129	/// fn main() -> std::io::Result<()> {
130	///     let file_id = FileID::new("/some/file/path.txt")?;
131	///     println!("{}", file_id.internal_file_id());
132	///     Ok(())
133	/// }
134	/// ```
135	#[must_use]
136	#[allow(clippy::unnecessary_cast)]
137	pub const fn internal_file_id(&self) -> u128 {
138		self.0.1 as u128
139	}
140}
141
142/// A trait to obtain the file identifier of an underlying object.
143pub trait GetID {
144	/// Obtains the file identifier, see [`FileID::new`] for more information.
145	fn get_id(&self) -> io::Result<FileID>;
146}
147
148impl GetID for FileID {
149	/// Returns a copy of itself wrapped inside `Ok`.
150	fn get_id(&self) -> io::Result<FileID> {
151		Ok(self.to_owned())
152	}
153}
154
155macro_rules! impl_get_id {
156	($($type:ty),+) => {
157		$(
158			impl GetID for $type {
159				fn get_id(&self) -> io::Result<FileID> {
160					File::open(self)?.get_id()
161				}
162			}
163		)+
164	};
165}
166
167impl_get_id!(Path, str, OsStr);
168
169/// Compares 2 different file identifiers, and returns `Ok(true)` if the 2 identifiers point to the same file,
170/// returning `Ok(false)` otherwise.
171/// 
172/// See [`FileID::new`] for more information on the identifiers.
173/// 
174/// # Errors 
175/// 
176/// Returns [`io::Error`] when failing to obtain any of the 2 identifiers.
177/// 
178/// # Examples
179/// 
180/// ```rust,no_run
181/// use fs_id::compare_ids;
182/// 
183/// fn main() -> std::io::Result<()> {
184///     println!("{}", compare_ids("/some/file/path.txt", "/some/other/path.txt")?);
185///     // works with more than just file paths....
186///     println!("{}", compare_ids(&std::io::stdout(), &std::io::stdout())?);
187///     Ok(())
188/// }
189/// ```
190pub fn compare_ids<T1: GetID + ?Sized, T2: GetID + ?Sized>(id1: &T1, id2: &T2) -> io::Result<bool> {
191	Ok(id1.get_id()? == id2.get_id()?)
192}
193
194#[cfg(test)]
195mod tests {
196	use crate::FileID;
197
198	#[test]
199	fn check_comparisons() -> std::io::Result<()> {
200		let id1 = FileID::new("Cargo.toml")?;
201		let id2 = FileID::new("Cargo.toml")?;
202		let id3 = FileID::new("LICENSE")?;
203		assert_eq!(id1, id2);
204		assert_ne!(id1, id3);
205		println!("id1: {id1:?}\nid2: {id2:?}\nid3: {id3:?}");
206		Ok(())
207	}
208}