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}