nix/sys/
inotify.rs

1//! Monitoring API for filesystem events.
2//!
3//! Inotify is a Linux-only API to monitor filesystems events.
4//!
5//! For more documentation, please read [inotify(7)](https://man7.org/linux/man-pages/man7/inotify.7.html).
6//!
7//! # Examples
8//!
9//! Monitor all events happening in directory "test":
10//! ```no_run
11//! # use nix::sys::inotify::{AddWatchFlags,InitFlags,Inotify};
12//! #
13//! // We create a new inotify instance.
14//! let instance = Inotify::init(InitFlags::empty()).unwrap();
15//!
16//! // We add a new watch on directory "test" for all events.
17//! let wd = instance.add_watch("test", AddWatchFlags::IN_ALL_EVENTS).unwrap();
18//!
19//! loop {
20//!     // We read from our inotify instance for events.
21//!     let events = instance.read_events().unwrap();
22//!     println!("Events: {:?}", events);
23//! }
24//! ```
25
26use crate::errno::Errno;
27use crate::unistd::read;
28use crate::NixPath;
29use crate::Result;
30use cfg_if::cfg_if;
31use libc::{c_char, c_int};
32use std::ffi::{CStr, OsStr, OsString};
33use std::mem::{size_of, MaybeUninit};
34use std::os::unix::ffi::OsStrExt;
35use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
36use std::ptr;
37
38libc_bitflags! {
39    /// Configuration options for [`inotify_add_watch`](fn.inotify_add_watch.html).
40    pub struct AddWatchFlags: u32 {
41        /// File was accessed.
42        IN_ACCESS;
43        /// File was modified.
44        IN_MODIFY;
45        /// Metadata changed.
46        IN_ATTRIB;
47        /// Writable file was closed.
48        IN_CLOSE_WRITE;
49        /// Nonwritable file was closed.
50        IN_CLOSE_NOWRITE;
51        /// File was opened.
52        IN_OPEN;
53        /// File was moved from X.
54        IN_MOVED_FROM;
55        /// File was moved to Y.
56        IN_MOVED_TO;
57        /// Subfile was created.
58        IN_CREATE;
59        /// Subfile was deleted.
60        IN_DELETE;
61        /// Self was deleted.
62        IN_DELETE_SELF;
63        /// Self was moved.
64        IN_MOVE_SELF;
65
66        /// Backing filesystem was unmounted.
67        IN_UNMOUNT;
68        /// Event queue overflowed.
69        IN_Q_OVERFLOW;
70        /// File was ignored.
71        IN_IGNORED;
72
73        /// Combination of `IN_CLOSE_WRITE` and `IN_CLOSE_NOWRITE`.
74        IN_CLOSE;
75        /// Combination of `IN_MOVED_FROM` and `IN_MOVED_TO`.
76        IN_MOVE;
77
78        /// Only watch the path if it is a directory.
79        IN_ONLYDIR;
80        /// Don't follow symlinks.
81        IN_DONT_FOLLOW;
82
83        /// Event occurred against directory.
84        IN_ISDIR;
85        /// Only send event once.
86        IN_ONESHOT;
87        /// All of the events.
88        IN_ALL_EVENTS;
89    }
90}
91
92libc_bitflags! {
93    /// Configuration options for [`inotify_init1`](fn.inotify_init1.html).
94    pub struct InitFlags: c_int {
95        /// Set the `FD_CLOEXEC` flag on the file descriptor.
96        IN_CLOEXEC;
97        /// Set the `O_NONBLOCK` flag on the open file description referred to by the new file descriptor.
98        IN_NONBLOCK;
99    }
100}
101
102/// An inotify instance. This is also a file descriptor, you can feed it to
103/// other interfaces consuming file descriptors, epoll for example.
104#[derive(Debug, Clone, Copy)]
105pub struct Inotify {
106    fd: RawFd,
107}
108
109/// This object is returned when you create a new watch on an inotify instance.
110/// It is then returned as part of an event once triggered. It allows you to
111/// know which watch triggered which event.
112#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
113pub struct WatchDescriptor {
114    wd: i32,
115}
116
117/// A single inotify event.
118///
119/// For more documentation see, [inotify(7)](https://man7.org/linux/man-pages/man7/inotify.7.html).
120#[derive(Debug)]
121pub struct InotifyEvent {
122    /// Watch descriptor. This field corresponds to the watch descriptor you
123    /// were issued when calling add_watch. It allows you to know which watch
124    /// this event comes from.
125    pub wd: WatchDescriptor,
126    /// Event mask. This field is a bitfield describing the exact event that
127    /// occured.
128    pub mask: AddWatchFlags,
129    /// This cookie is a number that allows you to connect related events. For
130    /// now only IN_MOVED_FROM and IN_MOVED_TO can be connected.
131    pub cookie: u32,
132    /// Filename. This field exists only if the event was triggered for a file
133    /// inside the watched directory.
134    pub name: Option<OsString>,
135}
136
137impl Inotify {
138    /// Initialize a new inotify instance.
139    ///
140    /// Returns a Result containing an inotify instance.
141    ///
142    /// For more information see, [inotify_init(2)](https://man7.org/linux/man-pages/man2/inotify_init.2.html).
143    pub fn init(flags: InitFlags) -> Result<Inotify> {
144        let res = Errno::result(unsafe { libc::inotify_init1(flags.bits()) });
145
146        res.map(|fd| Inotify { fd })
147    }
148
149    /// Adds a new watch on the target file or directory.
150    ///
151    /// Returns a watch descriptor. This is not a File Descriptor!
152    ///
153    /// For more information see, [inotify_add_watch(2)](https://man7.org/linux/man-pages/man2/inotify_add_watch.2.html).
154    pub fn add_watch<P: ?Sized + NixPath>(
155        self,
156        path: &P,
157        mask: AddWatchFlags,
158    ) -> Result<WatchDescriptor> {
159        let res = path.with_nix_path(|cstr| unsafe {
160            libc::inotify_add_watch(self.fd, cstr.as_ptr(), mask.bits())
161        })?;
162
163        Errno::result(res).map(|wd| WatchDescriptor { wd })
164    }
165
166    /// Removes an existing watch using the watch descriptor returned by
167    /// inotify_add_watch.
168    ///
169    /// Returns an EINVAL error if the watch descriptor is invalid.
170    ///
171    /// For more information see, [inotify_rm_watch(2)](https://man7.org/linux/man-pages/man2/inotify_rm_watch.2.html).
172    pub fn rm_watch(self, wd: WatchDescriptor) -> Result<()> {
173        cfg_if! {
174            if #[cfg(target_os = "linux")] {
175                let arg = wd.wd;
176            } else if #[cfg(target_os = "android")] {
177                let arg = wd.wd as u32;
178            }
179        }
180        let res = unsafe { libc::inotify_rm_watch(self.fd, arg) };
181
182        Errno::result(res).map(drop)
183    }
184
185    /// Reads a collection of events from the inotify file descriptor. This call
186    /// can either be blocking or non blocking depending on whether IN_NONBLOCK
187    /// was set at initialization.
188    ///
189    /// Returns as many events as available. If the call was non blocking and no
190    /// events could be read then the EAGAIN error is returned.
191    pub fn read_events(self) -> Result<Vec<InotifyEvent>> {
192        let header_size = size_of::<libc::inotify_event>();
193        const BUFSIZ: usize = 4096;
194        let mut buffer = [0u8; BUFSIZ];
195        let mut events = Vec::new();
196        let mut offset = 0;
197
198        let nread = read(self.fd, &mut buffer)?;
199
200        while (nread - offset) >= header_size {
201            let event = unsafe {
202                let mut event = MaybeUninit::<libc::inotify_event>::uninit();
203                ptr::copy_nonoverlapping(
204                    buffer.as_ptr().add(offset),
205                    event.as_mut_ptr() as *mut u8,
206                    (BUFSIZ - offset).min(header_size),
207                );
208                event.assume_init()
209            };
210
211            let name = match event.len {
212                0 => None,
213                _ => {
214                    let ptr = unsafe {
215                        buffer.as_ptr().add(offset + header_size)
216                            as *const c_char
217                    };
218                    let cstr = unsafe { CStr::from_ptr(ptr) };
219
220                    Some(OsStr::from_bytes(cstr.to_bytes()).to_owned())
221                }
222            };
223
224            events.push(InotifyEvent {
225                wd: WatchDescriptor { wd: event.wd },
226                mask: AddWatchFlags::from_bits_truncate(event.mask),
227                cookie: event.cookie,
228                name,
229            });
230
231            offset += header_size + event.len as usize;
232        }
233
234        Ok(events)
235    }
236}
237
238impl AsRawFd for Inotify {
239    fn as_raw_fd(&self) -> RawFd {
240        self.fd
241    }
242}
243
244impl FromRawFd for Inotify {
245    unsafe fn from_raw_fd(fd: RawFd) -> Self {
246        Inotify { fd }
247    }
248}