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 
26 use crate::errno::Errno;
27 use crate::unistd::read;
28 use crate::NixPath;
29 use crate::Result;
30 use cfg_if::cfg_if;
31 use libc::{c_char, c_int};
32 use std::ffi::{CStr, OsStr, OsString};
33 use std::mem::{size_of, MaybeUninit};
34 use std::os::unix::ffi::OsStrExt;
35 use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
36 use std::ptr;
37 
38 libc_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 
92 libc_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)]
105 pub struct Inotify {
106     fd: OwnedFd,
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)]
113 pub 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)]
121 pub 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 
137 impl 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).
init(flags: InitFlags) -> Result<Inotify>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 {
147             fd: unsafe { OwnedFd::from_raw_fd(fd) },
148         })
149     }
150 
151     /// Adds a new watch on the target file or directory.
152     ///
153     /// Returns a watch descriptor. This is not a File Descriptor!
154     ///
155     /// For more information see, [inotify_add_watch(2)](https://man7.org/linux/man-pages/man2/inotify_add_watch.2.html).
add_watch<P: ?Sized + NixPath>( &self, path: &P, mask: AddWatchFlags, ) -> Result<WatchDescriptor>156     pub fn add_watch<P: ?Sized + NixPath>(
157         &self,
158         path: &P,
159         mask: AddWatchFlags,
160     ) -> Result<WatchDescriptor> {
161         let res = path.with_nix_path(|cstr| unsafe {
162             libc::inotify_add_watch(
163                 self.fd.as_raw_fd(),
164                 cstr.as_ptr(),
165                 mask.bits(),
166             )
167         })?;
168 
169         Errno::result(res).map(|wd| WatchDescriptor { wd })
170     }
171 
172     /// Removes an existing watch using the watch descriptor returned by
173     /// inotify_add_watch.
174     ///
175     /// Returns an EINVAL error if the watch descriptor is invalid.
176     ///
177     /// For more information see, [inotify_rm_watch(2)](https://man7.org/linux/man-pages/man2/inotify_rm_watch.2.html).
rm_watch(&self, wd: WatchDescriptor) -> Result<()>178     pub fn rm_watch(&self, wd: WatchDescriptor) -> Result<()> {
179         cfg_if! {
180             if #[cfg(target_os = "linux")] {
181                 let arg = wd.wd;
182             } else if #[cfg(target_os = "android")] {
183                 let arg = wd.wd as u32;
184             }
185         }
186         let res = unsafe { libc::inotify_rm_watch(self.fd.as_raw_fd(), arg) };
187 
188         Errno::result(res).map(drop)
189     }
190 
191     /// Reads a collection of events from the inotify file descriptor. This call
192     /// can either be blocking or non blocking depending on whether IN_NONBLOCK
193     /// was set at initialization.
194     ///
195     /// Returns as many events as available. If the call was non blocking and no
196     /// events could be read then the EAGAIN error is returned.
read_events(&self) -> Result<Vec<InotifyEvent>>197     pub fn read_events(&self) -> Result<Vec<InotifyEvent>> {
198         let header_size = size_of::<libc::inotify_event>();
199         const BUFSIZ: usize = 4096;
200         let mut buffer = [0u8; BUFSIZ];
201         let mut events = Vec::new();
202         let mut offset = 0;
203 
204         let nread = read(self.fd.as_raw_fd(), &mut buffer)?;
205 
206         while (nread - offset) >= header_size {
207             let event = unsafe {
208                 let mut event = MaybeUninit::<libc::inotify_event>::uninit();
209                 ptr::copy_nonoverlapping(
210                     buffer.as_ptr().add(offset),
211                     event.as_mut_ptr().cast(),
212                     (BUFSIZ - offset).min(header_size),
213                 );
214                 event.assume_init()
215             };
216 
217             let name = match event.len {
218                 0 => None,
219                 _ => {
220                     let ptr = unsafe {
221                         buffer.as_ptr().add(offset + header_size)
222                             as *const c_char
223                     };
224                     let cstr = unsafe { CStr::from_ptr(ptr) };
225 
226                     Some(OsStr::from_bytes(cstr.to_bytes()).to_owned())
227                 }
228             };
229 
230             events.push(InotifyEvent {
231                 wd: WatchDescriptor { wd: event.wd },
232                 mask: AddWatchFlags::from_bits_truncate(event.mask),
233                 cookie: event.cookie,
234                 name,
235             });
236 
237             offset += header_size + event.len as usize;
238         }
239 
240         Ok(events)
241     }
242 }
243 
244 impl FromRawFd for Inotify {
from_raw_fd(fd: RawFd) -> Self245     unsafe fn from_raw_fd(fd: RawFd) -> Self {
246         Inotify {
247             fd: unsafe { OwnedFd::from_raw_fd(fd) },
248         }
249     }
250 }
251 
252 impl AsFd for Inotify {
as_fd(&'_ self) -> BorrowedFd<'_>253     fn as_fd(&'_ self) -> BorrowedFd<'_> {
254         self.fd.as_fd()
255     }
256 }
257