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}