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"]
from_bits_unchecked(bits: u32) -> Self234     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.
get_watch_descriptor_id(&self) -> c_int243     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
new(fd: Arc<FdGuard>) -> Self256     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
add<P>(&mut self, path: P, mask: WatchMask) -> io::Result<WatchDescriptor> where P: AsRef<Path>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
remove(&mut self, wd: WatchDescriptor) -> io::Result<()>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 {
eq(&self, other: &Self) -> bool430     fn eq(&self, other: &Self) -> bool {
431         let self_fd  = self.fd.upgrade();
432         let other_fd = 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 {
cmp(&self, other: &Self) -> Ordering439     fn cmp(&self, other: &Self) -> Ordering {
440         self.id.cmp(&other.id)
441     }
442 }
443 
444 impl PartialOrd for WatchDescriptor {
partial_cmp(&self, other: &Self) -> Option<Ordering>445     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
446         Some(self.cmp(other))
447     }
448 }
449 
450 impl Hash for WatchDescriptor {
hash<H: Hasher>(&self, state: &mut H)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