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}