inotify/
watches.rs

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