use std::{ ffi::{ OsStr, OsString, }, mem, os::unix::ffi::OsStrExt, sync::Weak, }; use inotify_sys as ffi; use crate::fd_guard::FdGuard; use crate::watches::WatchDescriptor; use crate::util::align_buffer; /// Iterator over inotify events /// /// Allows for iteration over the events returned by /// [`Inotify::read_events_blocking`] or [`Inotify::read_events`]. /// /// [`Inotify::read_events_blocking`]: struct.Inotify.html#method.read_events_blocking /// [`Inotify::read_events`]: struct.Inotify.html#method.read_events #[derive(Debug)] pub struct Events<'a> { fd : Weak, buffer : &'a [u8], num_bytes: usize, pos : usize, } impl<'a> Events<'a> { pub(crate) fn new(fd: Weak, buffer: &'a [u8], num_bytes: usize) -> Self { Events { fd : fd, buffer : buffer, num_bytes: num_bytes, pos : 0, } } } impl<'a> Iterator for Events<'a> { type Item = Event<&'a OsStr>; fn next(&mut self) -> Option { if self.pos < self.num_bytes { let (step, event) = Event::from_buffer(self.fd.clone(), &self.buffer[self.pos..]); self.pos += step; Some(event) } else { None } } } /// An inotify event /// /// A file system event that describes a change that the user previously /// registered interest in. To watch for events, call [`Inotify::add_watch`]. To /// retrieve events, call [`Inotify::read_events_blocking`] or /// [`Inotify::read_events`]. /// /// [`Inotify::add_watch`]: struct.Inotify.html#method.add_watch /// [`Inotify::read_events_blocking`]: struct.Inotify.html#method.read_events_blocking /// [`Inotify::read_events`]: struct.Inotify.html#method.read_events #[derive(Clone, Debug)] pub struct Event { /// Identifies the watch this event originates from /// /// This [`WatchDescriptor`] is equal to the one that [`Inotify::add_watch`] /// returned when interest for this event was registered. The /// [`WatchDescriptor`] can be used to remove the watch using /// [`Inotify::rm_watch`], thereby preventing future events of this type /// from being created. /// /// [`WatchDescriptor`]: struct.WatchDescriptor.html /// [`Inotify::add_watch`]: struct.Inotify.html#method.add_watch /// [`Inotify::rm_watch`]: struct.Inotify.html#method.rm_watch pub wd: WatchDescriptor, /// Indicates what kind of event this is pub mask: EventMask, /// Connects related events to each other /// /// When a file is renamed, this results two events: [`MOVED_FROM`] and /// [`MOVED_TO`]. The `cookie` field will be the same for both of them, /// thereby making is possible to connect the event pair. /// /// [`MOVED_FROM`]: event_mask/constant.MOVED_FROM.html /// [`MOVED_TO`]: event_mask/constant.MOVED_TO.html pub cookie: u32, /// The name of the file the event originates from /// /// This field is set only if the subject of the event is a file or directory in a /// watched directory. If the event concerns a file or directory that is /// watched directly, `name` will be `None`. pub name: Option, } impl<'a> Event<&'a OsStr> { fn new(fd: Weak, event: &ffi::inotify_event, name: &'a OsStr) -> Self { let mask = EventMask::from_bits(event.mask) .expect("Failed to convert event mask. This indicates a bug."); let wd = crate::WatchDescriptor { id: event.wd, fd, }; let name = if name == "" { None } else { Some(name) }; Event { wd, mask, cookie: event.cookie, name, } } /// Create an `Event` from a buffer /// /// Assumes that a full `inotify_event` plus its name is located at the /// beginning of `buffer`. /// /// Returns the number of bytes used from the buffer, and the event. /// /// # Panics /// /// Panics if the buffer does not contain a full event, including its name. pub(crate) fn from_buffer( fd : Weak, buffer: &'a [u8], ) -> (usize, Self) { let event_size = mem::size_of::(); let event_align = mem::align_of::(); // Make sure that the buffer can satisfy the alignment requirements for `inotify_event` assert!(buffer.len() >= event_align); // Discard the unaligned portion, if any, of the supplied buffer let buffer = align_buffer(buffer); // Make sure that the aligned buffer is big enough to contain an event, without // the name. Otherwise we can't safely convert it to an `inotify_event`. assert!(buffer.len() >= event_size); let event = buffer.as_ptr() as *const ffi::inotify_event; // We have a pointer to an `inotify_event`, pointing to the beginning of // `buffer`. Since we know, as per the assertion above, that there are // enough bytes in the buffer for at least one event, we can safely // convert that pointer into a reference. let event = unsafe { &*event }; // The name's length is given by `event.len`. There should always be // enough bytes left in the buffer to fit the name. Let's make sure that // is the case. let bytes_left_in_buffer = buffer.len() - event_size; assert!(bytes_left_in_buffer >= event.len as usize); // Directly after the event struct should be a name, if there's one // associated with the event. Let's make a new slice that starts with // that name. If there's no name, this slice might have a length of `0`. let bytes_consumed = event_size + event.len as usize; let name = &buffer[event_size..bytes_consumed]; // Remove trailing '\0' bytes // // The events in the buffer are aligned, and `name` is filled up // with '\0' up to the alignment boundary. Here we remove those // additional bytes. // // The `unwrap` here is safe, because `splitn` always returns at // least one result, even if the original slice contains no '\0'. let name = name .splitn(2, |b| b == &0u8) .next() .unwrap(); let event = Event::new( fd, event, OsStr::from_bytes(name), ); (bytes_consumed, event) } /// Returns an owned copy of the event. #[must_use = "cloning is often expensive and is not expected to have side effects"] pub fn into_owned(&self) -> EventOwned { Event { wd: self.wd.clone(), mask: self.mask, cookie: self.cookie, name: self.name.map(OsStr::to_os_string), } } } /// An owned version of `Event` pub type EventOwned = Event; bitflags! { /// Indicates the type of an event /// /// This struct can be retrieved from an [`Event`] via its `mask` field. /// You can determine the [`Event`]'s type by comparing the `EventMask` to /// its associated constants. /// /// Please refer to the documentation of [`Event`] for a usage example. /// /// [`Event`]: struct.Event.html pub struct EventMask: u32 { /// File was accessed /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_ACCESS`]. /// /// [`inotify_sys::IN_ACCESS`]: ../inotify_sys/constant.IN_ACCESS.html const ACCESS = ffi::IN_ACCESS; /// Metadata (permissions, timestamps, ...) changed /// /// When watching a directory, this event can be triggered for the /// directory itself, as well as objects inside the directory. /// /// See [`inotify_sys::IN_ATTRIB`]. /// /// [`inotify_sys::IN_ATTRIB`]: ../inotify_sys/constant.IN_ATTRIB.html const ATTRIB = ffi::IN_ATTRIB; /// File opened for writing was closed /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_CLOSE_WRITE`]. /// /// [`inotify_sys::IN_CLOSE_WRITE`]: ../inotify_sys/constant.IN_CLOSE_WRITE.html const CLOSE_WRITE = ffi::IN_CLOSE_WRITE; /// File or directory not opened for writing was closed /// /// When watching a directory, this event can be triggered for the /// directory itself, as well as objects inside the directory. /// /// See [`inotify_sys::IN_CLOSE_NOWRITE`]. /// /// [`inotify_sys::IN_CLOSE_NOWRITE`]: ../inotify_sys/constant.IN_CLOSE_NOWRITE.html const CLOSE_NOWRITE = ffi::IN_CLOSE_NOWRITE; /// File/directory created in watched directory /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_CREATE`]. /// /// [`inotify_sys::IN_CREATE`]: ../inotify_sys/constant.IN_CREATE.html const CREATE = ffi::IN_CREATE; /// File/directory deleted from watched directory /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_DELETE`]. /// /// [`inotify_sys::IN_DELETE`]: ../inotify_sys/constant.IN_DELETE.html const DELETE = ffi::IN_DELETE; /// Watched file/directory was deleted /// /// See [`inotify_sys::IN_DELETE_SELF`]. /// /// [`inotify_sys::IN_DELETE_SELF`]: ../inotify_sys/constant.IN_DELETE_SELF.html const DELETE_SELF = ffi::IN_DELETE_SELF; /// File was modified /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_MODIFY`]. /// /// [`inotify_sys::IN_MODIFY`]: ../inotify_sys/constant.IN_MODIFY.html const MODIFY = ffi::IN_MODIFY; /// Watched file/directory was moved /// /// See [`inotify_sys::IN_MOVE_SELF`]. /// /// [`inotify_sys::IN_MOVE_SELF`]: ../inotify_sys/constant.IN_MOVE_SELF.html const MOVE_SELF = ffi::IN_MOVE_SELF; /// File was renamed/moved; watched directory contained old name /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_MOVED_FROM`]. /// /// [`inotify_sys::IN_MOVED_FROM`]: ../inotify_sys/constant.IN_MOVED_FROM.html const MOVED_FROM = ffi::IN_MOVED_FROM; /// File was renamed/moved; watched directory contains new name /// /// When watching a directory, this event is only triggered for objects /// inside the directory, not the directory itself. /// /// See [`inotify_sys::IN_MOVED_TO`]. /// /// [`inotify_sys::IN_MOVED_TO`]: ../inotify_sys/constant.IN_MOVED_TO.html const MOVED_TO = ffi::IN_MOVED_TO; /// File or directory was opened /// /// When watching a directory, this event can be triggered for the /// directory itself, as well as objects inside the directory. /// /// See [`inotify_sys::IN_OPEN`]. /// /// [`inotify_sys::IN_OPEN`]: ../inotify_sys/constant.IN_OPEN.html const OPEN = ffi::IN_OPEN; /// Watch was removed /// /// This event will be generated, if the watch was removed explicitly /// (via [`Inotify::rm_watch`]), or automatically (because the file was /// deleted or the file system was unmounted). /// /// See [`inotify_sys::IN_IGNORED`]. /// /// [`inotify_sys::IN_IGNORED`]: ../inotify_sys/constant.IN_IGNORED.html const IGNORED = ffi::IN_IGNORED; /// Event related to a directory /// /// The subject of the event is a directory. /// /// See [`inotify_sys::IN_ISDIR`]. /// /// [`inotify_sys::IN_ISDIR`]: ../inotify_sys/constant.IN_ISDIR.html const ISDIR = ffi::IN_ISDIR; /// Event queue overflowed /// /// The event queue has overflowed and events have presumably been lost. /// /// See [`inotify_sys::IN_Q_OVERFLOW`]. /// /// [`inotify_sys::IN_Q_OVERFLOW`]: ../inotify_sys/constant.IN_Q_OVERFLOW.html const Q_OVERFLOW = ffi::IN_Q_OVERFLOW; /// File system containing watched object was unmounted. /// File system was unmounted /// /// The file system that contained the watched object has been /// unmounted. An event with [`WatchMask::IGNORED`] will subsequently be /// generated for the same watch descriptor. /// /// See [`inotify_sys::IN_UNMOUNT`]. /// /// [`WatchMask::IGNORED`]: #associatedconstant.IGNORED /// [`inotify_sys::IN_UNMOUNT`]: ../inotify_sys/constant.IN_UNMOUNT.html const UNMOUNT = ffi::IN_UNMOUNT; } } #[cfg(test)] mod tests { use std::{ io::prelude::*, mem, slice, sync, }; use crate::util; use inotify_sys as ffi; use super::Event; #[test] fn from_buffer_should_not_mistake_next_event_for_name_of_previous_event() { let mut buffer = [0u8; 1024]; // Make sure the buffer is properly aligned before writing raw events into it let buffer = util::align_buffer_mut(&mut buffer); // First, put a normal event into the buffer let event = ffi::inotify_event { wd: 0, mask: 0, cookie: 0, len: 0, // no name following after event }; let event = unsafe { slice::from_raw_parts( &event as *const _ as *const u8, mem::size_of_val(&event), ) }; (&mut buffer[..]).write(event) .expect("Failed to write into buffer"); // After that event, simulate an event that starts with a non-zero byte. buffer[mem::size_of_val(&event)] = 1; // Now create the event and verify that the name is actually `None`, as // dictated by the value `len` above. let (_, event) = Event::from_buffer( sync::Weak::new(), &buffer, ); assert_eq!(event.name, None); } }