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}