| 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 | pub fn init(flags: inotify::CreateFlags) -> io::Result<OwnedFd> { |
| 71 | syscalls::inotify_init1(flags) |
| 72 | } |
| 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 | pub fn add_watch<P: crate::path::Arg>( |
| 85 | inot: impl AsFd, |
| 86 | path: P, |
| 87 | flags: inotify::WatchFlags, |
| 88 | ) -> io::Result<i32> { |
| 89 | path.into_with_c_str(|path: &CStr| syscalls::inotify_add_watch(infd:inot.as_fd(), path, flags)) |
| 90 | } |
| 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 | pub fn remove_watch(inot: impl AsFd, wd: i32) -> io::Result<()> { |
| 99 | syscalls::inotify_rm_watch(infd:inot.as_fd(), wfd:wd) |
| 100 | } |
| 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 | pub fn new(fd: Fd, buf: &'buf mut [MaybeUninit<u8>]) -> Self { |
| 118 | Self { |
| 119 | fd, |
| 120 | buf: { |
| 121 | let offset: usize = buf.as_ptr().align_offset(align_of::<inotify_event>()); |
| 122 | if offset < buf.len() { |
| 123 | &mut buf[offset..] |
| 124 | } else { |
| 125 | &mut [] |
| 126 | } |
| 127 | }, |
| 128 | initialized: 0, |
| 129 | offset: 0, |
| 130 | } |
| 131 | } |
| 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 | pub fn wd(&self) -> i32 { |
| 147 | self.wd |
| 148 | } |
| 149 | |
| 150 | /// Returns a description of the events. |
| 151 | #[inline ] |
| 152 | #[doc (alias = "mask" )] |
| 153 | pub fn events(&self) -> ReadFlags { |
| 154 | self.events |
| 155 | } |
| 156 | |
| 157 | /// Returns the unique cookie associating related events. |
| 158 | #[inline ] |
| 159 | pub fn cookie(&self) -> u32 { |
| 160 | self.cookie |
| 161 | } |
| 162 | |
| 163 | /// Returns the file name of this event, if any. |
| 164 | #[inline ] |
| 165 | pub fn file_name(&self) -> Option<&CStr> { |
| 166 | self.file_name |
| 167 | } |
| 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 | pub fn next(&mut self) -> io::Result<InotifyEvent<'_>> { |
| 186 | if self.is_buffer_empty() { |
| 187 | match read_uninit(self.fd.as_fd(), self.buf).map(|(init, _)| init.len()) { |
| 188 | Ok(0) => return Err(Errno::INVAL), |
| 189 | Ok(bytes_read) => { |
| 190 | self.initialized = bytes_read; |
| 191 | self.offset = 0; |
| 192 | } |
| 193 | Err(e) => return Err(e), |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | let ptr = self.buf[self.offset..].as_ptr(); |
| 198 | |
| 199 | // SAFETY: |
| 200 | // - This data is initialized by the check above. |
| 201 | // - Assumption: the kernel will not give us partial structs. |
| 202 | // - Assumption: the kernel uses proper alignment between structs. |
| 203 | // - The starting pointer is aligned (performed in `Reader::new`). |
| 204 | let event = unsafe { &*ptr.cast::<inotify_event>() }; |
| 205 | |
| 206 | self.offset += size_of::<inotify_event>() + usize::try_from(event.len).unwrap(); |
| 207 | |
| 208 | Ok(InotifyEvent { |
| 209 | wd: event.wd, |
| 210 | events: ReadFlags::from_bits_retain(event.mask), |
| 211 | cookie: event.cookie, |
| 212 | file_name: if event.len > 0 { |
| 213 | // SAFETY: The kernel guarantees a NUL-terminated string. |
| 214 | Some(unsafe { CStr::from_ptr(event.name.as_ptr().cast()) }) |
| 215 | } else { |
| 216 | None |
| 217 | }, |
| 218 | }) |
| 219 | } |
| 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 | pub fn is_buffer_empty(&self) -> bool { |
| 226 | self.offset >= self.initialized |
| 227 | } |
| 228 | } |
| 229 | |