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