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}