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