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}