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