1 | use std::{ |
2 | cmp::Ordering, |
3 | ffi::CString, |
4 | hash::{ |
5 | Hash, |
6 | Hasher, |
7 | }, |
8 | io, |
9 | os::raw::c_int, |
10 | os::unix::ffi::OsStrExt, |
11 | path::Path, |
12 | sync::{ |
13 | Arc, |
14 | Weak, |
15 | }, |
16 | }; |
17 | |
18 | use inotify_sys as ffi; |
19 | |
20 | use crate::fd_guard::FdGuard; |
21 | |
22 | bitflags! { |
23 | /// Describes a file system watch |
24 | /// |
25 | /// Passed to [`Watches::add`], to describe what file system events |
26 | /// to watch for, and how to do that. |
27 | /// |
28 | /// # Examples |
29 | /// |
30 | /// `WatchMask` constants can be passed to [`Watches::add`] as is. For |
31 | /// example, here's how to create a watch that triggers an event when a file |
32 | /// is accessed: |
33 | /// |
34 | /// ``` rust |
35 | /// # use inotify::{ |
36 | /// # Inotify, |
37 | /// # WatchMask, |
38 | /// # }; |
39 | /// # |
40 | /// # let mut inotify = Inotify::init().unwrap(); |
41 | /// # |
42 | /// # // Create a temporary file, so `Watches::add` won't return an error. |
43 | /// # use std::fs::File; |
44 | /// # File::create("/tmp/inotify-rs-test-file") |
45 | /// # .expect("Failed to create test file"); |
46 | /// # |
47 | /// inotify.watches().add("/tmp/inotify-rs-test-file", WatchMask::ACCESS) |
48 | /// .expect("Error adding watch"); |
49 | /// ``` |
50 | /// |
51 | /// You can also combine multiple `WatchMask` constants. Here we add a watch |
52 | /// this is triggered both when files are created or deleted in a directory: |
53 | /// |
54 | /// ``` rust |
55 | /// # use inotify::{ |
56 | /// # Inotify, |
57 | /// # WatchMask, |
58 | /// # }; |
59 | /// # |
60 | /// # let mut inotify = Inotify::init().unwrap(); |
61 | /// inotify.watches().add("/tmp/", WatchMask::CREATE | WatchMask::DELETE) |
62 | /// .expect("Error adding watch"); |
63 | /// ``` |
64 | #[derive (PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] |
65 | pub struct WatchMask: u32 { |
66 | /// File was accessed |
67 | /// |
68 | /// When watching a directory, this event is only triggered for objects |
69 | /// inside the directory, not the directory itself. |
70 | /// |
71 | /// See [`inotify_sys::IN_ACCESS`]. |
72 | const ACCESS = ffi::IN_ACCESS; |
73 | |
74 | /// Metadata (permissions, timestamps, ...) changed |
75 | /// |
76 | /// When watching a directory, this event can be triggered for the |
77 | /// directory itself, as well as objects inside the directory. |
78 | /// |
79 | /// See [`inotify_sys::IN_ATTRIB`]. |
80 | const ATTRIB = ffi::IN_ATTRIB; |
81 | |
82 | /// File opened for writing was closed |
83 | /// |
84 | /// When watching a directory, this event is only triggered for objects |
85 | /// inside the directory, not the directory itself. |
86 | /// |
87 | /// See [`inotify_sys::IN_CLOSE_WRITE`]. |
88 | const CLOSE_WRITE = ffi::IN_CLOSE_WRITE; |
89 | |
90 | /// File or directory not opened for writing was closed |
91 | /// |
92 | /// When watching a directory, this event can be triggered for the |
93 | /// directory itself, as well as objects inside the directory. |
94 | /// |
95 | /// See [`inotify_sys::IN_CLOSE_NOWRITE`]. |
96 | const CLOSE_NOWRITE = ffi::IN_CLOSE_NOWRITE; |
97 | |
98 | /// File/directory created in watched directory |
99 | /// |
100 | /// When watching a directory, this event is only triggered for objects |
101 | /// inside the directory, not the directory itself. |
102 | /// |
103 | /// See [`inotify_sys::IN_CREATE`]. |
104 | const CREATE = ffi::IN_CREATE; |
105 | |
106 | /// File/directory deleted from watched directory |
107 | /// |
108 | /// When watching a directory, this event is only triggered for objects |
109 | /// inside the directory, not the directory itself. |
110 | /// |
111 | /// See [`inotify_sys::IN_DELETE`]. |
112 | const DELETE = ffi::IN_DELETE; |
113 | |
114 | /// Watched file/directory was deleted |
115 | /// |
116 | /// See [`inotify_sys::IN_DELETE_SELF`]. |
117 | const DELETE_SELF = ffi::IN_DELETE_SELF; |
118 | |
119 | /// File was modified |
120 | /// |
121 | /// When watching a directory, this event is only triggered for objects |
122 | /// inside the directory, not the directory itself. |
123 | /// |
124 | /// See [`inotify_sys::IN_MODIFY`]. |
125 | const MODIFY = ffi::IN_MODIFY; |
126 | |
127 | /// Watched file/directory was moved |
128 | /// |
129 | /// See [`inotify_sys::IN_MOVE_SELF`]. |
130 | const MOVE_SELF = ffi::IN_MOVE_SELF; |
131 | |
132 | /// File was renamed/moved; watched directory contained old name |
133 | /// |
134 | /// When watching a directory, this event is only triggered for objects |
135 | /// inside the directory, not the directory itself. |
136 | /// |
137 | /// See [`inotify_sys::IN_MOVED_FROM`]. |
138 | const MOVED_FROM = ffi::IN_MOVED_FROM; |
139 | |
140 | /// File was renamed/moved; watched directory contains new name |
141 | /// |
142 | /// When watching a directory, this event is only triggered for objects |
143 | /// inside the directory, not the directory itself. |
144 | /// |
145 | /// See [`inotify_sys::IN_MOVED_TO`]. |
146 | const MOVED_TO = ffi::IN_MOVED_TO; |
147 | |
148 | /// File or directory was opened |
149 | /// |
150 | /// When watching a directory, this event can be triggered for the |
151 | /// directory itself, as well as objects inside the directory. |
152 | /// |
153 | /// See [`inotify_sys::IN_OPEN`]. |
154 | const OPEN = ffi::IN_OPEN; |
155 | |
156 | /// Watch for all events |
157 | /// |
158 | /// This constant is simply a convenient combination of the following |
159 | /// other constants: |
160 | /// |
161 | /// - [`ACCESS`](Self::ACCESS) |
162 | /// - [`ATTRIB`](Self::ATTRIB) |
163 | /// - [`CLOSE_WRITE`](Self::CLOSE_WRITE) |
164 | /// - [`CLOSE_NOWRITE`](Self::CLOSE_NOWRITE) |
165 | /// - [`CREATE`](Self::CREATE) |
166 | /// - [`DELETE`](Self::DELETE) |
167 | /// - [`DELETE_SELF`](Self::DELETE_SELF) |
168 | /// - [`MODIFY`](Self::MODIFY) |
169 | /// - [`MOVE_SELF`](Self::MOVE_SELF) |
170 | /// - [`MOVED_FROM`](Self::MOVED_FROM) |
171 | /// - [`MOVED_TO`](Self::MOVED_TO) |
172 | /// - [`OPEN`](Self::OPEN) |
173 | /// |
174 | /// See [`inotify_sys::IN_ALL_EVENTS`]. |
175 | const ALL_EVENTS = ffi::IN_ALL_EVENTS; |
176 | |
177 | /// Watch for all move events |
178 | /// |
179 | /// This constant is simply a convenient combination of the following |
180 | /// other constants: |
181 | /// |
182 | /// - [`MOVED_FROM`](Self::MOVED_FROM) |
183 | /// - [`MOVED_TO`](Self::MOVED_TO) |
184 | /// |
185 | /// See [`inotify_sys::IN_MOVE`]. |
186 | const MOVE = ffi::IN_MOVE; |
187 | |
188 | /// Watch for all close events |
189 | /// |
190 | /// This constant is simply a convenient combination of the following |
191 | /// other constants: |
192 | /// |
193 | /// - [`CLOSE_WRITE`](Self::CLOSE_WRITE) |
194 | /// - [`CLOSE_NOWRITE`](Self::CLOSE_NOWRITE) |
195 | /// |
196 | /// See [`inotify_sys::IN_CLOSE`]. |
197 | const CLOSE = ffi::IN_CLOSE; |
198 | |
199 | /// Don't dereference the path if it is a symbolic link |
200 | /// |
201 | /// See [`inotify_sys::IN_DONT_FOLLOW`]. |
202 | const DONT_FOLLOW = ffi::IN_DONT_FOLLOW; |
203 | |
204 | /// Filter events for directory entries that have been unlinked |
205 | /// |
206 | /// See [`inotify_sys::IN_EXCL_UNLINK`]. |
207 | const EXCL_UNLINK = ffi::IN_EXCL_UNLINK; |
208 | |
209 | /// If a watch for the inode exists, amend it instead of replacing it |
210 | /// |
211 | /// See [`inotify_sys::IN_MASK_ADD`]. |
212 | const MASK_ADD = ffi::IN_MASK_ADD; |
213 | |
214 | /// Only receive one event, then remove the watch |
215 | /// |
216 | /// See [`inotify_sys::IN_ONESHOT`]. |
217 | const ONESHOT = ffi::IN_ONESHOT; |
218 | |
219 | /// Only watch path, if it is a directory |
220 | /// |
221 | /// See [`inotify_sys::IN_ONLYDIR`]. |
222 | const ONLYDIR = ffi::IN_ONLYDIR; |
223 | } |
224 | } |
225 | |
226 | impl WatchMask { |
227 | /// Wrapper around [`Self::from_bits_retain`] for backwards compatibility |
228 | /// |
229 | /// # Safety |
230 | /// |
231 | /// This function is not actually unsafe. It is just a wrapper around the |
232 | /// safe [`Self::from_bits_retain`]. |
233 | #[deprecated = "Use the safe `from_bits_retain` method instead" ] |
234 | pub unsafe fn from_bits_unchecked(bits: u32) -> Self { |
235 | Self::from_bits_retain(bits) |
236 | } |
237 | } |
238 | |
239 | impl WatchDescriptor { |
240 | /// Getter method for a watcher's id. |
241 | /// |
242 | /// Can be used to distinguish events for files with the same name. |
243 | pub fn get_watch_descriptor_id(&self) -> c_int { |
244 | self.id |
245 | } |
246 | } |
247 | |
248 | /// Interface for adding and removing watches |
249 | #[derive (Clone, Debug)] |
250 | pub struct Watches { |
251 | pub(crate) fd: Arc<FdGuard>, |
252 | } |
253 | |
254 | impl Watches { |
255 | /// Init watches with an inotify file descriptor |
256 | pub(crate) fn new(fd: Arc<FdGuard>) -> Self { |
257 | Watches { |
258 | fd, |
259 | } |
260 | } |
261 | |
262 | /// Adds or updates a watch for the given path |
263 | /// |
264 | /// Adds a new watch or updates an existing one for the file referred to by |
265 | /// `path`. Returns a watch descriptor that can be used to refer to this |
266 | /// watch later. |
267 | /// |
268 | /// The `mask` argument defines what kind of changes the file should be |
269 | /// watched for, and how to do that. See the documentation of [`WatchMask`] |
270 | /// for details. |
271 | /// |
272 | /// If this method is used to add a new watch, a new [`WatchDescriptor`] is |
273 | /// returned. If it is used to update an existing watch, a |
274 | /// [`WatchDescriptor`] that equals the previously returned |
275 | /// [`WatchDescriptor`] for that watch is returned instead. |
276 | /// |
277 | /// Under the hood, this method just calls [`inotify_add_watch`] and does |
278 | /// some trivial translation between the types on the Rust side and the C |
279 | /// side. |
280 | /// |
281 | /// # Attention: Updating watches and hardlinks |
282 | /// |
283 | /// As mentioned above, this method can be used to update an existing watch. |
284 | /// This is usually done by calling this method with the same `path` |
285 | /// argument that it has been called with before. But less obviously, it can |
286 | /// also happen if the method is called with a different path that happens |
287 | /// to link to the same inode. |
288 | /// |
289 | /// You can detect this by keeping track of [`WatchDescriptor`]s and the |
290 | /// paths they have been returned for. If the same [`WatchDescriptor`] is |
291 | /// returned for a different path (and you haven't freed the |
292 | /// [`WatchDescriptor`] by removing the watch), you know you have two paths |
293 | /// pointing to the same inode, being watched by the same watch. |
294 | /// |
295 | /// # Errors |
296 | /// |
297 | /// Directly returns the error from the call to |
298 | /// [`inotify_add_watch`][`inotify_add_watch`] (translated into an |
299 | /// `io::Error`), without adding any error conditions of |
300 | /// its own. |
301 | /// |
302 | /// # Examples |
303 | /// |
304 | /// ``` |
305 | /// use inotify::{ |
306 | /// Inotify, |
307 | /// WatchMask, |
308 | /// }; |
309 | /// |
310 | /// let mut inotify = Inotify::init() |
311 | /// .expect("Failed to initialize an inotify instance" ); |
312 | /// |
313 | /// # // Create a temporary file, so `Watches::add` won't return an error. |
314 | /// # use std::fs::File; |
315 | /// # File::create("/tmp/inotify-rs-test-file" ) |
316 | /// # .expect("Failed to create test file" ); |
317 | /// # |
318 | /// inotify.watches().add("/tmp/inotify-rs-test-file" , WatchMask::MODIFY) |
319 | /// .expect("Failed to add file watch" ); |
320 | /// |
321 | /// // Handle events for the file here |
322 | /// ``` |
323 | /// |
324 | /// [`inotify_add_watch`]: inotify_sys::inotify_add_watch |
325 | pub fn add<P>(&mut self, path: P, mask: WatchMask) |
326 | -> io::Result<WatchDescriptor> |
327 | where P: AsRef<Path> |
328 | { |
329 | let path = CString::new(path.as_ref().as_os_str().as_bytes())?; |
330 | |
331 | let wd = unsafe { |
332 | ffi::inotify_add_watch( |
333 | **self.fd, |
334 | path.as_ptr() as *const _, |
335 | mask.bits(), |
336 | ) |
337 | }; |
338 | |
339 | match wd { |
340 | -1 => Err(io::Error::last_os_error()), |
341 | _ => Ok(WatchDescriptor{ id: wd, fd: Arc::downgrade(&self.fd) }), |
342 | } |
343 | } |
344 | |
345 | /// Stops watching a file |
346 | /// |
347 | /// Removes the watch represented by the provided [`WatchDescriptor`] by |
348 | /// calling [`inotify_rm_watch`]. [`WatchDescriptor`]s can be obtained via |
349 | /// [`Watches::add`], or from the `wd` field of [`Event`]. |
350 | /// |
351 | /// # Errors |
352 | /// |
353 | /// Directly returns the error from the call to [`inotify_rm_watch`]. |
354 | /// Returns an [`io::Error`] with [`ErrorKind`]`::InvalidInput`, if the given |
355 | /// [`WatchDescriptor`] did not originate from this [`Inotify`] instance. |
356 | /// |
357 | /// # Examples |
358 | /// |
359 | /// ``` |
360 | /// use inotify::Inotify; |
361 | /// |
362 | /// let mut inotify = Inotify::init() |
363 | /// .expect("Failed to initialize an inotify instance" ); |
364 | /// |
365 | /// # // Create a temporary file, so `Watches::add` won't return an error. |
366 | /// # use std::fs::File; |
367 | /// # let mut test_file = File::create("/tmp/inotify-rs-test-file" ) |
368 | /// # .expect("Failed to create test file" ); |
369 | /// # |
370 | /// # // Add a watch and modify the file, so the code below doesn't block |
371 | /// # // forever. |
372 | /// # use inotify::WatchMask; |
373 | /// # inotify.watches().add("/tmp/inotify-rs-test-file" , WatchMask::MODIFY) |
374 | /// # .expect("Failed to add file watch" ); |
375 | /// # use std::io::Write; |
376 | /// # write!(&mut test_file, "something \n" ) |
377 | /// # .expect("Failed to write something to test file" ); |
378 | /// # |
379 | /// let mut buffer = [0; 1024]; |
380 | /// let events = inotify |
381 | /// .read_events_blocking(&mut buffer) |
382 | /// .expect("Error while waiting for events" ); |
383 | /// let mut watches = inotify.watches(); |
384 | /// |
385 | /// for event in events { |
386 | /// watches.remove(event.wd); |
387 | /// } |
388 | /// ``` |
389 | /// |
390 | /// [`inotify_rm_watch`]: inotify_sys::inotify_rm_watch |
391 | /// [`Event`]: crate::Event |
392 | /// [`Inotify`]: crate::Inotify |
393 | /// [`io::Error`]: std::io::Error |
394 | /// [`ErrorKind`]: std::io::ErrorKind |
395 | pub fn remove(&mut self, wd: WatchDescriptor) -> io::Result<()> { |
396 | if wd.fd.upgrade().as_ref() != Some(&self.fd) { |
397 | return Err(io::Error::new( |
398 | io::ErrorKind::InvalidInput, |
399 | "Invalid WatchDescriptor" , |
400 | )); |
401 | } |
402 | |
403 | let result = unsafe { ffi::inotify_rm_watch(**self.fd, wd.id) }; |
404 | match result { |
405 | 0 => Ok(()), |
406 | -1 => Err(io::Error::last_os_error()), |
407 | _ => panic!( |
408 | "unexpected return code from inotify_rm_watch ( {})" , result) |
409 | } |
410 | } |
411 | } |
412 | |
413 | |
414 | /// Represents a watch on an inode |
415 | /// |
416 | /// Can be obtained from [`Watches::add`] or from an [`Event`]. A watch |
417 | /// descriptor can be used to get inotify to stop watching an inode by passing |
418 | /// it to [`Watches::remove`]. |
419 | /// |
420 | /// [`Event`]: crate::Event |
421 | #[derive (Clone, Debug)] |
422 | pub struct WatchDescriptor{ |
423 | pub(crate) id: c_int, |
424 | pub(crate) fd: Weak<FdGuard>, |
425 | } |
426 | |
427 | impl Eq for WatchDescriptor {} |
428 | |
429 | impl PartialEq for WatchDescriptor { |
430 | fn eq(&self, other: &Self) -> bool { |
431 | let self_fd: Option> = self.fd.upgrade(); |
432 | let other_fd: Option> = other.fd.upgrade(); |
433 | |
434 | self.id == other.id && self_fd.is_some() && self_fd == other_fd |
435 | } |
436 | } |
437 | |
438 | impl Ord for WatchDescriptor { |
439 | fn cmp(&self, other: &Self) -> Ordering { |
440 | self.id.cmp(&other.id) |
441 | } |
442 | } |
443 | |
444 | impl PartialOrd for WatchDescriptor { |
445 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
446 | Some(self.cmp(other)) |
447 | } |
448 | } |
449 | |
450 | impl Hash for WatchDescriptor { |
451 | fn hash<H: Hasher>(&self, state: &mut H) { |
452 | // This function only takes `self.id` into account, as `self.fd` is a |
453 | // weak pointer that might no longer be available. Since neither |
454 | // panicking nor changing the hash depending on whether it's available |
455 | // is acceptable, we just don't look at it at all. |
456 | // I don't think that this influences storage in a `HashMap` or |
457 | // `HashSet` negatively, as storing `WatchDescriptor`s from different |
458 | // `Inotify` instances seems like something of an anti-pattern anyway. |
459 | self.id.hash(state); |
460 | } |
461 | } |
462 | |