rustix/fs/inotify.rs
1//! inotify support for working with inotify objects.
2//!
3//! # Examples
4//!
5//! ```
6//! use rustix::fs::inotify;
7//! use rustix::io;
8//! use std::mem::MaybeUninit;
9//!
10//! # fn test() -> io::Result<()> {
11//! // Create an inotify object. In this example, we use `NONBLOCK` so that the
12//! // reader fails with `WOULDBLOCK` when no events are ready. Otherwise it
13//! // will block until at least one event is ready.
14//! let inotify = inotify::init(inotify::CreateFlags::NONBLOCK)?;
15//!
16//! // Add a directory to watch.
17//! inotify::add_watch(
18//! &inotify,
19//! "/path/to/some/directory/to/watch",
20//! inotify::WatchFlags::ALL_EVENTS,
21//! )?;
22//!
23//! // Generate some events in the watched directory…
24//!
25//! // Loop over pending events.
26//! let mut buf = [MaybeUninit::uninit(); 512];
27//! let mut iter = inotify::Reader::new(inotify, &mut buf);
28//! loop {
29//! let entry = match iter.next() {
30//! // Stop iterating if there are no more events for now.
31//! Err(io::Errno::WOULDBLOCK) => break,
32//! Err(e) => return Err(e),
33//! Ok(entry) => entry,
34//! };
35//!
36//! // Use `entry`…
37//! }
38//!
39//! # Ok(())
40//! # }
41
42#![allow(unused_qualifications)]
43
44use super::inotify;
45pub use crate::backend::fs::inotify::{CreateFlags, ReadFlags, WatchFlags};
46use crate::backend::fs::syscalls;
47use crate::fd::{AsFd, OwnedFd};
48use crate::ffi::CStr;
49use crate::io;
50use crate::io::{read, Errno};
51use core::mem::{align_of, size_of, MaybeUninit};
52use linux_raw_sys::general::inotify_event;
53
54/// `inotify_init1(flags)`—Creates a new inotify object.
55///
56/// Use the [`CreateFlags::CLOEXEC`] flag to prevent the resulting file
57/// descriptor from being implicitly passed across `exec` boundaries.
58#[doc(alias = "inotify_init1")]
59#[inline]
60pub fn init(flags: inotify::CreateFlags) -> io::Result<OwnedFd> {
61 syscalls::inotify_init1(flags)
62}
63
64/// `inotify_add_watch(self, path, flags)`—Adds a watch to inotify.
65///
66/// This registers or updates a watch for the filesystem path `path` and
67/// returns a watch descriptor corresponding to this watch.
68///
69/// Note: Due to the existence of hardlinks, providing two different paths to
70/// this method may result in it returning the same watch descriptor. An
71/// application should keep track of this externally to avoid logic errors.
72#[doc(alias = "inotify_add_watch")]
73#[inline]
74pub fn add_watch<P: crate::path::Arg, Fd: AsFd>(
75 inot: Fd,
76 path: P,
77 flags: inotify::WatchFlags,
78) -> io::Result<i32> {
79 path.into_with_c_str(|path| syscalls::inotify_add_watch(inot.as_fd(), path, flags))
80}
81
82/// `inotify_rm_watch(self, wd)`—Removes a watch from this inotify.
83///
84/// The watch descriptor provided should have previously been returned by
85/// [`inotify::add_watch`] and not previously have been removed.
86#[doc(alias = "inotify_rm_watch")]
87#[inline]
88pub fn remove_watch<Fd: AsFd>(inot: Fd, wd: i32) -> io::Result<()> {
89 syscalls::inotify_rm_watch(inot.as_fd(), wd)
90}
91
92/// An inotify event iterator implemented with the read syscall.
93///
94/// See the [`RawDir`] API for more details and usage examples as this API is
95/// based on it.
96///
97/// [`RawDir`]: crate::fs::raw_dir::RawDir
98pub struct Reader<'buf, Fd: AsFd> {
99 fd: Fd,
100 buf: &'buf mut [MaybeUninit<u8>],
101 initialized: usize,
102 offset: usize,
103}
104
105impl<'buf, Fd: AsFd> Reader<'buf, Fd> {
106 /// Create a new iterator from the given file descriptor and buffer.
107 pub fn new(fd: Fd, buf: &'buf mut [MaybeUninit<u8>]) -> Self {
108 Self {
109 fd,
110 buf: {
111 let offset = buf.as_ptr().align_offset(align_of::<inotify_event>());
112 if offset < buf.len() {
113 &mut buf[offset..]
114 } else {
115 &mut []
116 }
117 },
118 initialized: 0,
119 offset: 0,
120 }
121 }
122}
123
124/// An inotify event.
125#[doc(alias = "inotify_event")]
126#[derive(Debug)]
127pub struct Event<'a> {
128 wd: i32,
129 events: ReadFlags,
130 cookie: u32,
131 file_name: Option<&'a CStr>,
132}
133
134impl<'a> Event<'a> {
135 /// Returns the watch for which this event occurs.
136 #[inline]
137 pub fn wd(&self) -> i32 {
138 self.wd
139 }
140
141 /// Returns a description of the events.
142 #[inline]
143 #[doc(alias = "mask")]
144 pub fn events(&self) -> ReadFlags {
145 self.events
146 }
147
148 /// Returns the unique cookie associating related events.
149 #[inline]
150 pub fn cookie(&self) -> u32 {
151 self.cookie
152 }
153
154 /// Returns the file name of this event, if any.
155 #[inline]
156 pub fn file_name(&self) -> Option<&CStr> {
157 self.file_name
158 }
159}
160
161impl<'buf, Fd: AsFd> Reader<'buf, Fd> {
162 /// Read the next inotify event.
163 ///
164 /// This is similar to [`Iterator::next`] except that it doesn't return an
165 /// `Option`, because the stream doesn't have an ending. It always returns
166 /// events or errors.
167 ///
168 /// If there are no events in the buffer and none ready to be read:
169 /// - If the file descriptor was opened with
170 /// [`inotify::CreateFlags::NONBLOCK`], this will fail with
171 /// [`Errno::AGAIN`].
172 /// - Otherwise this will block until at least one event is ready or an
173 /// error occurs.
174 #[allow(unsafe_code)]
175 #[allow(clippy::should_implement_trait)]
176 pub fn next(&mut self) -> io::Result<Event<'_>> {
177 if self.is_buffer_empty() {
178 match read(self.fd.as_fd(), &mut *self.buf).map(|(init, _)| init.len()) {
179 Ok(0) => return Err(Errno::INVAL),
180 Ok(bytes_read) => {
181 self.initialized = bytes_read;
182 self.offset = 0;
183 }
184 Err(e) => return Err(e),
185 }
186 }
187
188 let ptr = self.buf[self.offset..].as_ptr();
189
190 // SAFETY:
191 // - This data is initialized by the check above.
192 // - Assumption: the kernel will not give us partial structs.
193 // - Assumption: the kernel uses proper alignment between structs.
194 // - The starting pointer is aligned (performed in `Reader::new`).
195 let event = unsafe { &*ptr.cast::<inotify_event>() };
196
197 self.offset += size_of::<inotify_event>() + usize::try_from(event.len).unwrap();
198
199 Ok(Event {
200 wd: event.wd,
201 events: ReadFlags::from_bits_retain(event.mask),
202 cookie: event.cookie,
203 file_name: if event.len > 0 {
204 // SAFETY: The kernel guarantees a NUL-terminated string.
205 Some(unsafe { CStr::from_ptr(event.name.as_ptr().cast()) })
206 } else {
207 None
208 },
209 })
210 }
211
212 /// Returns true if the internal buffer is empty and will be refilled when
213 /// calling [`next`]. This is useful to avoid further blocking reads.
214 ///
215 /// [`next`]: Self::next
216 pub fn is_buffer_empty(&self) -> bool {
217 self.offset >= self.initialized
218 }
219}