| 1 | use std::{ |
| 2 | ffi::{ |
| 3 | OsStr, |
| 4 | OsString, |
| 5 | }, |
| 6 | mem, |
| 7 | os::unix::ffi::OsStrExt, |
| 8 | sync::Weak, |
| 9 | }; |
| 10 | |
| 11 | use inotify_sys as ffi; |
| 12 | |
| 13 | use crate::fd_guard::FdGuard; |
| 14 | use crate::watches::WatchDescriptor; |
| 15 | |
| 16 | |
| 17 | /// Iterator over inotify events |
| 18 | /// |
| 19 | /// Allows for iteration over the events returned by |
| 20 | /// [`Inotify::read_events_blocking`] or [`Inotify::read_events`]. |
| 21 | /// |
| 22 | /// [`Inotify::read_events_blocking`]: crate::Inotify::read_events_blocking |
| 23 | /// [`Inotify::read_events`]: crate::Inotify::read_events |
| 24 | #[derive (Debug)] |
| 25 | pub struct Events<'a> { |
| 26 | fd : Weak<FdGuard>, |
| 27 | buffer : &'a [u8], |
| 28 | num_bytes: usize, |
| 29 | pos : usize, |
| 30 | } |
| 31 | |
| 32 | impl<'a> Events<'a> { |
| 33 | pub(crate) fn new(fd: Weak<FdGuard>, buffer: &'a [u8], num_bytes: usize) |
| 34 | -> Self |
| 35 | { |
| 36 | Events { |
| 37 | fd, |
| 38 | buffer, |
| 39 | num_bytes, |
| 40 | pos: 0, |
| 41 | } |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | impl<'a> Iterator for Events<'a> { |
| 46 | type Item = Event<&'a OsStr>; |
| 47 | |
| 48 | fn next(&mut self) -> Option<Self::Item> { |
| 49 | if self.pos < self.num_bytes { |
| 50 | let (step: usize, event: Event<&OsStr>) = Event::from_buffer(self.fd.clone(), &self.buffer[self.pos..]); |
| 51 | self.pos += step; |
| 52 | |
| 53 | Some(event) |
| 54 | } |
| 55 | else { |
| 56 | None |
| 57 | } |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | |
| 62 | /// An inotify event |
| 63 | /// |
| 64 | /// A file system event that describes a change that the user previously |
| 65 | /// registered interest in. To watch for events, call [`Watches::add`]. To |
| 66 | /// retrieve events, call [`Inotify::read_events_blocking`] or |
| 67 | /// [`Inotify::read_events`]. |
| 68 | /// |
| 69 | /// [`Watches::add`]: crate::Watches::add |
| 70 | /// [`Inotify::read_events_blocking`]: crate::Inotify::read_events_blocking |
| 71 | /// [`Inotify::read_events`]: crate::Inotify::read_events |
| 72 | #[derive (Clone, Debug)] |
| 73 | pub struct Event<S> { |
| 74 | /// Identifies the watch this event originates from |
| 75 | /// |
| 76 | /// This [`WatchDescriptor`] is equal to the one that [`Watches::add`] |
| 77 | /// returned when interest for this event was registered. The |
| 78 | /// [`WatchDescriptor`] can be used to remove the watch using |
| 79 | /// [`Watches::remove`], thereby preventing future events of this type |
| 80 | /// from being created. |
| 81 | /// |
| 82 | /// [`Watches::add`]: crate::Watches::add |
| 83 | /// [`Watches::remove`]: crate::Watches::remove |
| 84 | pub wd: WatchDescriptor, |
| 85 | |
| 86 | /// Indicates what kind of event this is |
| 87 | pub mask: EventMask, |
| 88 | |
| 89 | /// Connects related events to each other |
| 90 | /// |
| 91 | /// When a file is renamed, this results two events: [`MOVED_FROM`] and |
| 92 | /// [`MOVED_TO`]. The `cookie` field will be the same for both of them, |
| 93 | /// thereby making is possible to connect the event pair. |
| 94 | /// |
| 95 | /// [`MOVED_FROM`]: EventMask::MOVED_FROM |
| 96 | /// [`MOVED_TO`]: EventMask::MOVED_TO |
| 97 | pub cookie: u32, |
| 98 | |
| 99 | /// The name of the file the event originates from |
| 100 | /// |
| 101 | /// This field is set only if the subject of the event is a file or directory in a |
| 102 | /// watched directory. If the event concerns a file or directory that is |
| 103 | /// watched directly, `name` will be `None`. |
| 104 | pub name: Option<S>, |
| 105 | } |
| 106 | |
| 107 | impl<'a> Event<&'a OsStr> { |
| 108 | fn new(fd: Weak<FdGuard>, event: &ffi::inotify_event, name: &'a OsStr) |
| 109 | -> Self |
| 110 | { |
| 111 | let mask = EventMask::from_bits(event.mask) |
| 112 | .expect("Failed to convert event mask. This indicates a bug." ); |
| 113 | |
| 114 | let wd = crate::WatchDescriptor { |
| 115 | id: event.wd, |
| 116 | fd, |
| 117 | }; |
| 118 | |
| 119 | let name = if name.is_empty() { |
| 120 | None |
| 121 | } |
| 122 | else { |
| 123 | Some(name) |
| 124 | }; |
| 125 | |
| 126 | Event { |
| 127 | wd, |
| 128 | mask, |
| 129 | cookie: event.cookie, |
| 130 | name, |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | /// Create an `Event` from a buffer |
| 135 | /// |
| 136 | /// Assumes that a full `inotify_event` plus its name is located at the |
| 137 | /// beginning of `buffer`. |
| 138 | /// |
| 139 | /// Returns the number of bytes used from the buffer, and the event. |
| 140 | /// |
| 141 | /// # Panics |
| 142 | /// |
| 143 | /// Panics if the buffer does not contain a full event, including its name. |
| 144 | pub(crate) fn from_buffer( |
| 145 | fd : Weak<FdGuard>, |
| 146 | buffer: &'a [u8], |
| 147 | ) |
| 148 | -> (usize, Self) |
| 149 | { |
| 150 | let event_size = mem::size_of::<ffi::inotify_event>(); |
| 151 | |
| 152 | // Make sure that the buffer is big enough to contain an event, without |
| 153 | // the name. Otherwise we can't safely convert it to an `inotify_event`. |
| 154 | assert!(buffer.len() >= event_size); |
| 155 | |
| 156 | let ffi_event_ptr = buffer.as_ptr() as *const ffi::inotify_event; |
| 157 | |
| 158 | // We have a pointer to an `inotify_event`, pointing to the beginning of |
| 159 | // `buffer`. Since we know, as per the assertion above, that there are |
| 160 | // enough bytes in the buffer for at least one event, we can safely |
| 161 | // read that `inotify_event`. |
| 162 | // We call `read_unaligned()` since the byte buffer has alignment 1 |
| 163 | // and `inotify_event` has a higher alignment, so `*` cannot be used to dereference |
| 164 | // the unaligned pointer (undefined behavior). |
| 165 | let ffi_event = unsafe { ffi_event_ptr.read_unaligned() }; |
| 166 | |
| 167 | // The name's length is given by `event.len`. There should always be |
| 168 | // enough bytes left in the buffer to fit the name. Let's make sure that |
| 169 | // is the case. |
| 170 | let bytes_left_in_buffer = buffer.len() - event_size; |
| 171 | assert!(bytes_left_in_buffer >= ffi_event.len as usize); |
| 172 | |
| 173 | // Directly after the event struct should be a name, if there's one |
| 174 | // associated with the event. Let's make a new slice that starts with |
| 175 | // that name. If there's no name, this slice might have a length of `0`. |
| 176 | let bytes_consumed = event_size + ffi_event.len as usize; |
| 177 | let name = &buffer[event_size..bytes_consumed]; |
| 178 | |
| 179 | // Remove trailing '\0' bytes |
| 180 | // |
| 181 | // The events in the buffer are aligned, and `name` is filled up |
| 182 | // with '\0' up to the alignment boundary. Here we remove those |
| 183 | // additional bytes. |
| 184 | // |
| 185 | // The `unwrap` here is safe, because `splitn` always returns at |
| 186 | // least one result, even if the original slice contains no '\0'. |
| 187 | let name = name |
| 188 | .splitn(2, |b| b == &0u8) |
| 189 | .next() |
| 190 | .unwrap(); |
| 191 | |
| 192 | let event = Event::new( |
| 193 | fd, |
| 194 | &ffi_event, |
| 195 | OsStr::from_bytes(name), |
| 196 | ); |
| 197 | |
| 198 | (bytes_consumed, event) |
| 199 | } |
| 200 | |
| 201 | /// Returns an owned copy of the event. |
| 202 | #[deprecated = "use `to_owned()` instead; methods named `into_owned()` usually take self by value" ] |
| 203 | #[allow (clippy::wrong_self_convention)] |
| 204 | pub fn into_owned(&self) -> EventOwned { |
| 205 | self.to_owned() |
| 206 | } |
| 207 | |
| 208 | /// Returns an owned copy of the event. |
| 209 | #[must_use = "cloning is often expensive and is not expected to have side effects" ] |
| 210 | pub fn to_owned(&self) -> EventOwned { |
| 211 | Event { |
| 212 | wd: self.wd.clone(), |
| 213 | mask: self.mask, |
| 214 | cookie: self.cookie, |
| 215 | name: self.name.map(OsStr::to_os_string), |
| 216 | } |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | |
| 221 | /// An owned version of `Event` |
| 222 | pub type EventOwned = Event<OsString>; |
| 223 | |
| 224 | |
| 225 | bitflags! { |
| 226 | /// Indicates the type of an event |
| 227 | /// |
| 228 | /// This struct can be retrieved from an [`Event`] via its `mask` field. |
| 229 | /// You can determine the [`Event`]'s type by comparing the `EventMask` to |
| 230 | /// its associated constants. |
| 231 | /// |
| 232 | /// Please refer to the documentation of [`Event`] for a usage example. |
| 233 | #[derive (PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] |
| 234 | pub struct EventMask: u32 { |
| 235 | /// File was accessed |
| 236 | /// |
| 237 | /// When watching a directory, this event is only triggered for objects |
| 238 | /// inside the directory, not the directory itself. |
| 239 | /// |
| 240 | /// See [`inotify_sys::IN_ACCESS`]. |
| 241 | const ACCESS = ffi::IN_ACCESS; |
| 242 | |
| 243 | /// Metadata (permissions, timestamps, ...) changed |
| 244 | /// |
| 245 | /// When watching a directory, this event can be triggered for the |
| 246 | /// directory itself, as well as objects inside the directory. |
| 247 | /// |
| 248 | /// See [`inotify_sys::IN_ATTRIB`]. |
| 249 | const ATTRIB = ffi::IN_ATTRIB; |
| 250 | |
| 251 | /// File opened for writing was closed |
| 252 | /// |
| 253 | /// When watching a directory, this event is only triggered for objects |
| 254 | /// inside the directory, not the directory itself. |
| 255 | /// |
| 256 | /// See [`inotify_sys::IN_CLOSE_WRITE`]. |
| 257 | const CLOSE_WRITE = ffi::IN_CLOSE_WRITE; |
| 258 | |
| 259 | /// File or directory not opened for writing was closed |
| 260 | /// |
| 261 | /// When watching a directory, this event can be triggered for the |
| 262 | /// directory itself, as well as objects inside the directory. |
| 263 | /// |
| 264 | /// See [`inotify_sys::IN_CLOSE_NOWRITE`]. |
| 265 | const CLOSE_NOWRITE = ffi::IN_CLOSE_NOWRITE; |
| 266 | |
| 267 | /// File/directory created in watched directory |
| 268 | /// |
| 269 | /// When watching a directory, this event is only triggered for objects |
| 270 | /// inside the directory, not the directory itself. |
| 271 | /// |
| 272 | /// See [`inotify_sys::IN_CREATE`]. |
| 273 | const CREATE = ffi::IN_CREATE; |
| 274 | |
| 275 | /// File/directory deleted from watched directory |
| 276 | /// |
| 277 | /// When watching a directory, this event is only triggered for objects |
| 278 | /// inside the directory, not the directory itself. |
| 279 | const DELETE = ffi::IN_DELETE; |
| 280 | |
| 281 | /// Watched file/directory was deleted |
| 282 | /// |
| 283 | /// See [`inotify_sys::IN_DELETE_SELF`]. |
| 284 | const DELETE_SELF = ffi::IN_DELETE_SELF; |
| 285 | |
| 286 | /// File was modified |
| 287 | /// |
| 288 | /// When watching a directory, this event is only triggered for objects |
| 289 | /// inside the directory, not the directory itself. |
| 290 | /// |
| 291 | /// See [`inotify_sys::IN_MODIFY`]. |
| 292 | const MODIFY = ffi::IN_MODIFY; |
| 293 | |
| 294 | /// Watched file/directory was moved |
| 295 | /// |
| 296 | /// See [`inotify_sys::IN_MOVE_SELF`]. |
| 297 | const MOVE_SELF = ffi::IN_MOVE_SELF; |
| 298 | |
| 299 | /// File was renamed/moved; watched directory contained old name |
| 300 | /// |
| 301 | /// When watching a directory, this event is only triggered for objects |
| 302 | /// inside the directory, not the directory itself. |
| 303 | /// |
| 304 | /// See [`inotify_sys::IN_MOVED_FROM`]. |
| 305 | const MOVED_FROM = ffi::IN_MOVED_FROM; |
| 306 | |
| 307 | /// File was renamed/moved; watched directory contains new name |
| 308 | /// |
| 309 | /// When watching a directory, this event is only triggered for objects |
| 310 | /// inside the directory, not the directory itself. |
| 311 | /// |
| 312 | /// See [`inotify_sys::IN_MOVED_TO`]. |
| 313 | const MOVED_TO = ffi::IN_MOVED_TO; |
| 314 | |
| 315 | /// File or directory was opened |
| 316 | /// |
| 317 | /// When watching a directory, this event can be triggered for the |
| 318 | /// directory itself, as well as objects inside the directory. |
| 319 | /// |
| 320 | /// See [`inotify_sys::IN_OPEN`]. |
| 321 | const OPEN = ffi::IN_OPEN; |
| 322 | |
| 323 | /// Watch was removed |
| 324 | /// |
| 325 | /// This event will be generated, if the watch was removed explicitly |
| 326 | /// (via [`Watches::remove`]), or automatically (because the file was |
| 327 | /// deleted or the file system was unmounted). |
| 328 | /// |
| 329 | /// See [`inotify_sys::IN_IGNORED`]. |
| 330 | /// |
| 331 | /// [`Watches::remove`]: crate::Watches::remove |
| 332 | const IGNORED = ffi::IN_IGNORED; |
| 333 | |
| 334 | /// Event related to a directory |
| 335 | /// |
| 336 | /// The subject of the event is a directory. |
| 337 | /// |
| 338 | /// See [`inotify_sys::IN_ISDIR`]. |
| 339 | const ISDIR = ffi::IN_ISDIR; |
| 340 | |
| 341 | /// Event queue overflowed |
| 342 | /// |
| 343 | /// The event queue has overflowed and events have presumably been lost. |
| 344 | /// |
| 345 | /// See [`inotify_sys::IN_Q_OVERFLOW`]. |
| 346 | const Q_OVERFLOW = ffi::IN_Q_OVERFLOW; |
| 347 | |
| 348 | /// File system containing watched object was unmounted. |
| 349 | /// File system was unmounted |
| 350 | /// |
| 351 | /// The file system that contained the watched object has been |
| 352 | /// unmounted. An event with [`EventMask::IGNORED`] will subsequently be |
| 353 | /// generated for the same watch descriptor. |
| 354 | /// |
| 355 | /// See [`inotify_sys::IN_UNMOUNT`]. |
| 356 | const UNMOUNT = ffi::IN_UNMOUNT; |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | impl EventMask { |
| 361 | /// Wrapper around [`Self::from_bits_retain`] for backwards compatibility |
| 362 | /// |
| 363 | /// # Safety |
| 364 | /// |
| 365 | /// This function is not actually unsafe. It is just a wrapper around the |
| 366 | /// safe [`Self::from_bits_retain`]. |
| 367 | #[deprecated = "Use the safe `from_bits_retain` method instead" ] |
| 368 | pub unsafe fn from_bits_unchecked(bits: u32) -> Self { |
| 369 | Self::from_bits_retain(bits) |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | |
| 374 | #[cfg (test)] |
| 375 | mod tests { |
| 376 | use std::{ |
| 377 | io::prelude::*, |
| 378 | mem, |
| 379 | slice, |
| 380 | sync, |
| 381 | }; |
| 382 | |
| 383 | use inotify_sys as ffi; |
| 384 | |
| 385 | use super::Event; |
| 386 | |
| 387 | |
| 388 | #[test ] |
| 389 | fn from_buffer_should_not_mistake_next_event_for_name_of_previous_event() { |
| 390 | let mut buffer = [0u8; 1024]; |
| 391 | |
| 392 | // First, put a normal event into the buffer |
| 393 | let event = ffi::inotify_event { |
| 394 | wd: 0, |
| 395 | mask: 0, |
| 396 | cookie: 0, |
| 397 | len: 0, // no name following after event |
| 398 | }; |
| 399 | let event = unsafe { |
| 400 | slice::from_raw_parts( |
| 401 | &event as *const _ as *const u8, |
| 402 | mem::size_of_val(&event), |
| 403 | ) |
| 404 | }; |
| 405 | (&mut buffer[..]).write(event) |
| 406 | .expect("Failed to write into buffer" ); |
| 407 | |
| 408 | // After that event, simulate an event that starts with a non-zero byte. |
| 409 | buffer[mem::size_of_val(&event)] = 1; |
| 410 | |
| 411 | // Now create the event and verify that the name is actually `None`, as |
| 412 | // dictated by the value `len` above. |
| 413 | let (_, event) = Event::from_buffer( |
| 414 | sync::Weak::new(), |
| 415 | &buffer, |
| 416 | ); |
| 417 | assert_eq!(event.name, None); |
| 418 | } |
| 419 | } |
| 420 | |