inotify/
watches.rs

1use 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
18use inotify_sys as ffi;
19
20use crate::fd_guard::FdGuard;
21
22bitflags! {
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
226impl 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
239impl 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)]
250pub struct Watches {
251    pub(crate) fd: Arc<FdGuard>,
252}
253
254impl 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)]
422pub struct WatchDescriptor{
423    pub(crate) id: c_int,
424    pub(crate) fd: Weak<FdGuard>,
425}
426
427impl Eq for WatchDescriptor {}
428
429impl PartialEq for WatchDescriptor {
430    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
438impl Ord for WatchDescriptor {
439    fn cmp(&self, other: &Self) -> Ordering {
440        self.id.cmp(&other.id)
441    }
442}
443
444impl PartialOrd for WatchDescriptor {
445    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
446        Some(self.cmp(other))
447    }
448}
449
450impl 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}