Skip to main content

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. Conflicts with [`WatchMask::MASK_CREATE`].
204        ///
205        /// See [`inotify_sys::IN_MASK_ADD`].
206        const MASK_ADD = ffi::IN_MASK_ADD;
207
208        /// Only create watches, errors if a watch on this inode already exists. Conflicts with [`WatchMask::MASK_ADD`].
209        ///
210        /// See [`inotify_sys::IN_MASK_CREATE`].
211        const MASK_CREATE = ffi::IN_MASK_CREATE;
212
213        /// Only receive one event, then remove the watch
214        ///
215        /// See [`inotify_sys::IN_ONESHOT`].
216        const ONESHOT = ffi::IN_ONESHOT;
217
218        /// Only watch path, if it is a directory
219        ///
220        /// See [`inotify_sys::IN_ONLYDIR`].
221        const ONLYDIR = ffi::IN_ONLYDIR;
222    }
223}
224
225impl WatchMask {
226    /// Wrapper around [`Self::from_bits_retain`] for backwards compatibility
227    ///
228    /// # Safety
229    ///
230    /// This function is not actually unsafe. It is just a wrapper around the
231    /// safe [`Self::from_bits_retain`].
232    #[deprecated = "Use the safe `from_bits_retain` method instead"]
233    pub unsafe fn from_bits_unchecked(bits: u32) -> Self {
234        Self::from_bits_retain(bits)
235    }
236}
237
238impl WatchDescriptor {
239    /// Getter method for a watcher's id.
240    ///
241    /// Can be used to distinguish events for files with the same name.
242    pub fn get_watch_descriptor_id(&self) -> c_int {
243        self.id
244    }
245}
246
247/// Interface for adding and removing watches
248#[derive(Clone, Debug)]
249pub struct Watches {
250    pub(crate) fd: Arc<FdGuard>,
251}
252
253impl Watches {
254    /// Init watches with an inotify file descriptor
255    pub(crate) fn new(fd: Arc<FdGuard>) -> Self {
256        Watches { fd }
257    }
258
259    /// Adds or updates a watch for the given path
260    ///
261    /// Adds a new watch or updates an existing one for the file referred to by
262    /// `path`. Returns a watch descriptor that can be used to refer to this
263    /// watch later.
264    ///
265    /// The `mask` argument defines what kind of changes the file should be
266    /// watched for, and how to do that. See the documentation of [`WatchMask`]
267    /// for details.
268    ///
269    /// If this method is used to add a new watch, a new [`WatchDescriptor`] is
270    /// returned. If it is used to update an existing watch, a
271    /// [`WatchDescriptor`] that equals the previously returned
272    /// [`WatchDescriptor`] for that watch is returned instead.
273    ///
274    /// Under the hood, this method just calls [`inotify_add_watch`] and does
275    /// some trivial translation between the types on the Rust side and the C
276    /// side.
277    ///
278    /// # Attention: Updating watches and hardlinks
279    ///
280    /// As mentioned above, this method can be used to update an existing watch.
281    /// This is usually done by calling this method with the same `path`
282    /// argument that it has been called with before. But less obviously, it can
283    /// also happen if the method is called with a different path that happens
284    /// to link to the same inode.
285    ///
286    /// You can detect this by keeping track of [`WatchDescriptor`]s and the
287    /// paths they have been returned for. If the same [`WatchDescriptor`] is
288    /// returned for a different path (and you haven't freed the
289    /// [`WatchDescriptor`] by removing the watch), you know you have two paths
290    /// pointing to the same inode, being watched by the same watch.
291    ///
292    /// # Errors
293    ///
294    /// Directly returns the error from the call to
295    /// [`inotify_add_watch`][`inotify_add_watch`] (translated into an
296    /// `io::Error`), without adding any error conditions of
297    /// its own.
298    ///
299    /// # Examples
300    ///
301    /// ```
302    /// use inotify::{
303    ///     Inotify,
304    ///     WatchMask,
305    /// };
306    ///
307    /// let mut inotify = Inotify::init()
308    ///     .expect("Failed to initialize an inotify instance");
309    ///
310    /// # // Create a temporary file, so `Watches::add` won't return an error.
311    /// # use std::fs::File;
312    /// # File::create("/tmp/inotify-rs-test-file")
313    /// #     .expect("Failed to create test file");
314    /// #
315    /// inotify.watches().add("/tmp/inotify-rs-test-file", WatchMask::MODIFY)
316    ///     .expect("Failed to add file watch");
317    ///
318    /// // Handle events for the file here
319    /// ```
320    ///
321    /// [`inotify_add_watch`]: inotify_sys::inotify_add_watch
322    pub fn add<P>(&mut self, path: P, mask: WatchMask) -> io::Result<WatchDescriptor>
323    where
324        P: AsRef<Path>,
325    {
326        let path = CString::new(path.as_ref().as_os_str().as_bytes())?;
327
328        let wd =
329            unsafe { ffi::inotify_add_watch(**self.fd, path.as_ptr() as *const _, mask.bits()) };
330
331        match wd {
332            -1 => Err(io::Error::last_os_error()),
333            _ => Ok(WatchDescriptor {
334                id: wd,
335                fd: Arc::downgrade(&self.fd),
336            }),
337        }
338    }
339
340    /// Stops watching a file
341    ///
342    /// Removes the watch represented by the provided [`WatchDescriptor`] by
343    /// calling [`inotify_rm_watch`]. [`WatchDescriptor`]s can be obtained via
344    /// [`Watches::add`], or from the `wd` field of [`Event`].
345    ///
346    /// # Errors
347    ///
348    /// Directly returns the error from the call to [`inotify_rm_watch`].
349    /// Returns an [`io::Error`] with [`ErrorKind`]`::InvalidInput`, if the given
350    /// [`WatchDescriptor`] did not originate from this [`Inotify`] instance.
351    ///
352    /// # Examples
353    ///
354    /// ```
355    /// use inotify::Inotify;
356    ///
357    /// let mut inotify = Inotify::init()
358    ///     .expect("Failed to initialize an inotify instance");
359    ///
360    /// # // Create a temporary file, so `Watches::add` won't return an error.
361    /// # use std::fs::File;
362    /// # let mut test_file = File::create("/tmp/inotify-rs-test-file")
363    /// #     .expect("Failed to create test file");
364    /// #
365    /// # // Add a watch and modify the file, so the code below doesn't block
366    /// # // forever.
367    /// # use inotify::WatchMask;
368    /// # inotify.watches().add("/tmp/inotify-rs-test-file", WatchMask::MODIFY)
369    /// #     .expect("Failed to add file watch");
370    /// # use std::io::Write;
371    /// # write!(&mut test_file, "something\n")
372    /// #     .expect("Failed to write something to test file");
373    /// #
374    /// let mut buffer = [0; 1024];
375    /// let events = inotify
376    ///     .read_events_blocking(&mut buffer)
377    ///     .expect("Error while waiting for events");
378    /// let mut watches = inotify.watches();
379    ///
380    /// for event in events {
381    ///     watches.remove(event.wd);
382    /// }
383    /// ```
384    ///
385    /// [`inotify_rm_watch`]: inotify_sys::inotify_rm_watch
386    /// [`Event`]: crate::Event
387    /// [`Inotify`]: crate::Inotify
388    /// [`io::Error`]: std::io::Error
389    /// [`ErrorKind`]: std::io::ErrorKind
390    pub fn remove(&mut self, wd: WatchDescriptor) -> io::Result<()> {
391        if wd.fd.upgrade().as_ref() != Some(&self.fd) {
392            return Err(io::Error::new(
393                io::ErrorKind::InvalidInput,
394                "Invalid WatchDescriptor",
395            ));
396        }
397
398        let result = unsafe { ffi::inotify_rm_watch(**self.fd, wd.id) };
399        match result {
400            0 => Ok(()),
401            -1 => Err(io::Error::last_os_error()),
402            _ => panic!("unexpected return code from inotify_rm_watch ({})", result),
403        }
404    }
405}
406
407/// Represents a watch on an inode
408///
409/// Can be obtained from [`Watches::add`] or from an [`Event`]. A watch
410/// descriptor can be used to get inotify to stop watching an inode by passing
411/// it to [`Watches::remove`].
412///
413/// [`Event`]: crate::Event
414#[derive(Clone, Debug)]
415pub struct WatchDescriptor {
416    pub(crate) id: c_int,
417    pub(crate) fd: Weak<FdGuard>,
418}
419
420impl Eq for WatchDescriptor {}
421
422impl PartialEq for WatchDescriptor {
423    fn eq(&self, other: &Self) -> bool {
424        let self_fd = self.fd.upgrade();
425        let other_fd = other.fd.upgrade();
426
427        self.id == other.id && self_fd.is_some() && self_fd == other_fd
428    }
429}
430
431impl Ord for WatchDescriptor {
432    fn cmp(&self, other: &Self) -> Ordering {
433        self.id.cmp(&other.id)
434    }
435}
436
437impl PartialOrd for WatchDescriptor {
438    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
439        Some(self.cmp(other))
440    }
441}
442
443impl Hash for WatchDescriptor {
444    fn hash<H: Hasher>(&self, state: &mut H) {
445        // This function only takes `self.id` into account, as `self.fd` is a
446        // weak pointer that might no longer be available. Since neither
447        // panicking nor changing the hash depending on whether it's available
448        // is acceptable, we just don't look at it at all.
449        // I don't think that this influences storage in a `HashMap` or
450        // `HashSet` negatively, as storing `WatchDescriptor`s from different
451        // `Inotify` instances seems like something of an anti-pattern anyway.
452        self.id.hash(state);
453    }
454}