• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::{AsRawFd, FromRawFd, 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, Clone, Copy)]
105 pub 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)]
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 { 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).
add_watch<P: ?Sized + NixPath>( self, path: &P, mask: AddWatchFlags, ) -> Result<WatchDescriptor>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).
rm_watch(self, wd: WatchDescriptor) -> Result<()>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.
read_events(self) -> Result<Vec<InotifyEvent>>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 
238 impl AsRawFd for Inotify {
as_raw_fd(&self) -> RawFd239     fn as_raw_fd(&self) -> RawFd {
240         self.fd
241     }
242 }
243 
244 impl FromRawFd for Inotify {
from_raw_fd(fd: RawFd) -> Self245     unsafe fn from_raw_fd(fd: RawFd) -> Self {
246         Inotify { fd }
247     }
248 }
249