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}