use std::{ cmp::Ordering, ffi::CString, hash::{ Hash, Hasher, }, io, os::raw::c_int, os::unix::ffi::OsStrExt, path::Path, sync::{ Arc, Weak, }, }; use inotify_sys as ffi; use crate::fd_guard::FdGuard; bitflags! { /// Describes a file system watch /// /// Passed to [`Watches::add`], to describe what file system events /// to watch for, and how to do that. /// /// # Examples /// /// `WatchMask` constants can be passed to [`Watches::add`] as is. For /// example, here's how to create a watch that triggers an event when a file /// is accessed: /// /// ``` rust /// # use inotify::{ /// # Inotify, /// # WatchMask, /// # }; /// # /// # let mut inotify = Inotify::init().unwrap(); /// # /// # // Create a temporary file, so `Watches::add` won't return an error. /// # use std::fs::File; /// # File::create("/tmp/inotify-rs-test-file") /// # .expect("Failed to create test file"); /// # /// inotify.watches().add("/tmp/inotify-rs-test-file", WatchMask::ACCESS) /// .expect("Error adding watch"); /// ``` /// /// You can also combine multiple `WatchMask` constants. Here we add a watch /// this is triggered both when files are created or deleted in a directory: /// /// ``` rust /// # use inotify::{ /// # Inotify, /// # WatchMask, /// # }; /// # /// # let mut inotify = Inotify::init().unwrap(); /// inotify.watches().add("/tmp/", WatchMask::CREATE | WatchMask::DELETE) /// .expect("Error adding watch"); /// ``` #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] pub struct WatchMask: 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`]. 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`]. 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`]. 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`]. 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`]. 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`]. const DELETE = ffi::IN_DELETE; /// Watched file/directory was deleted /// /// See [`inotify_sys::IN_DELETE_SELF`]. 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`]. const MODIFY = ffi::IN_MODIFY; /// Watched file/directory was moved /// /// See [`inotify_sys::IN_MOVE_SELF`]. 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`]. 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`]. 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`]. const OPEN = ffi::IN_OPEN; /// Watch for all events /// /// This constant is simply a convenient combination of the following /// other constants: /// /// - [`ACCESS`](Self::ACCESS) /// - [`ATTRIB`](Self::ATTRIB) /// - [`CLOSE_WRITE`](Self::CLOSE_WRITE) /// - [`CLOSE_NOWRITE`](Self::CLOSE_NOWRITE) /// - [`CREATE`](Self::CREATE) /// - [`DELETE`](Self::DELETE) /// - [`DELETE_SELF`](Self::DELETE_SELF) /// - [`MODIFY`](Self::MODIFY) /// - [`MOVE_SELF`](Self::MOVE_SELF) /// - [`MOVED_FROM`](Self::MOVED_FROM) /// - [`MOVED_TO`](Self::MOVED_TO) /// - [`OPEN`](Self::OPEN) /// /// See [`inotify_sys::IN_ALL_EVENTS`]. const ALL_EVENTS = ffi::IN_ALL_EVENTS; /// Watch for all move events /// /// This constant is simply a convenient combination of the following /// other constants: /// /// - [`MOVED_FROM`](Self::MOVED_FROM) /// - [`MOVED_TO`](Self::MOVED_TO) /// /// See [`inotify_sys::IN_MOVE`]. const MOVE = ffi::IN_MOVE; /// Watch for all close events /// /// This constant is simply a convenient combination of the following /// other constants: /// /// - [`CLOSE_WRITE`](Self::CLOSE_WRITE) /// - [`CLOSE_NOWRITE`](Self::CLOSE_NOWRITE) /// /// See [`inotify_sys::IN_CLOSE`]. const CLOSE = ffi::IN_CLOSE; /// Don't dereference the path if it is a symbolic link /// /// See [`inotify_sys::IN_DONT_FOLLOW`]. const DONT_FOLLOW = ffi::IN_DONT_FOLLOW; /// Filter events for directory entries that have been unlinked /// /// See [`inotify_sys::IN_EXCL_UNLINK`]. const EXCL_UNLINK = ffi::IN_EXCL_UNLINK; /// If a watch for the inode exists, amend it instead of replacing it /// /// See [`inotify_sys::IN_MASK_ADD`]. const MASK_ADD = ffi::IN_MASK_ADD; /// Only receive one event, then remove the watch /// /// See [`inotify_sys::IN_ONESHOT`]. const ONESHOT = ffi::IN_ONESHOT; /// Only watch path, if it is a directory /// /// See [`inotify_sys::IN_ONLYDIR`]. const ONLYDIR = ffi::IN_ONLYDIR; } } impl WatchMask { /// Wrapper around [`Self::from_bits_retain`] for backwards compatibility /// /// # Safety /// /// This function is not actually unsafe. It is just a wrapper around the /// safe [`Self::from_bits_retain`]. #[deprecated = "Use the safe `from_bits_retain` method instead"] pub unsafe fn from_bits_unchecked(bits: u32) -> Self { Self::from_bits_retain(bits) } } impl WatchDescriptor { /// Getter method for a watcher's id. /// /// Can be used to distinguish events for files with the same name. pub fn get_watch_descriptor_id(&self) -> c_int { self.id } } /// Interface for adding and removing watches #[derive(Clone, Debug)] pub struct Watches { pub(crate) fd: Arc, } impl Watches { /// Init watches with an inotify file descriptor pub(crate) fn new(fd: Arc) -> Self { Watches { fd, } } /// Adds or updates a watch for the given path /// /// Adds a new watch or updates an existing one for the file referred to by /// `path`. Returns a watch descriptor that can be used to refer to this /// watch later. /// /// The `mask` argument defines what kind of changes the file should be /// watched for, and how to do that. See the documentation of [`WatchMask`] /// for details. /// /// If this method is used to add a new watch, a new [`WatchDescriptor`] is /// returned. If it is used to update an existing watch, a /// [`WatchDescriptor`] that equals the previously returned /// [`WatchDescriptor`] for that watch is returned instead. /// /// Under the hood, this method just calls [`inotify_add_watch`] and does /// some trivial translation between the types on the Rust side and the C /// side. /// /// # Attention: Updating watches and hardlinks /// /// As mentioned above, this method can be used to update an existing watch. /// This is usually done by calling this method with the same `path` /// argument that it has been called with before. But less obviously, it can /// also happen if the method is called with a different path that happens /// to link to the same inode. /// /// You can detect this by keeping track of [`WatchDescriptor`]s and the /// paths they have been returned for. If the same [`WatchDescriptor`] is /// returned for a different path (and you haven't freed the /// [`WatchDescriptor`] by removing the watch), you know you have two paths /// pointing to the same inode, being watched by the same watch. /// /// # Errors /// /// Directly returns the error from the call to /// [`inotify_add_watch`][`inotify_add_watch`] (translated into an /// `io::Error`), without adding any error conditions of /// its own. /// /// # Examples /// /// ``` /// use inotify::{ /// Inotify, /// WatchMask, /// }; /// /// let mut inotify = Inotify::init() /// .expect("Failed to initialize an inotify instance"); /// /// # // Create a temporary file, so `Watches::add` won't return an error. /// # use std::fs::File; /// # File::create("/tmp/inotify-rs-test-file") /// # .expect("Failed to create test file"); /// # /// inotify.watches().add("/tmp/inotify-rs-test-file", WatchMask::MODIFY) /// .expect("Failed to add file watch"); /// /// // Handle events for the file here /// ``` /// /// [`inotify_add_watch`]: inotify_sys::inotify_add_watch pub fn add

(&mut self, path: P, mask: WatchMask) -> io::Result where P: AsRef { let path = CString::new(path.as_ref().as_os_str().as_bytes())?; let wd = unsafe { ffi::inotify_add_watch( **self.fd, path.as_ptr() as *const _, mask.bits(), ) }; match wd { -1 => Err(io::Error::last_os_error()), _ => Ok(WatchDescriptor{ id: wd, fd: Arc::downgrade(&self.fd) }), } } /// Stops watching a file /// /// Removes the watch represented by the provided [`WatchDescriptor`] by /// calling [`inotify_rm_watch`]. [`WatchDescriptor`]s can be obtained via /// [`Watches::add`], or from the `wd` field of [`Event`]. /// /// # Errors /// /// Directly returns the error from the call to [`inotify_rm_watch`]. /// Returns an [`io::Error`] with [`ErrorKind`]`::InvalidInput`, if the given /// [`WatchDescriptor`] did not originate from this [`Inotify`] instance. /// /// # Examples /// /// ``` /// use inotify::Inotify; /// /// let mut inotify = Inotify::init() /// .expect("Failed to initialize an inotify instance"); /// /// # // Create a temporary file, so `Watches::add` won't return an error. /// # use std::fs::File; /// # let mut test_file = File::create("/tmp/inotify-rs-test-file") /// # .expect("Failed to create test file"); /// # /// # // Add a watch and modify the file, so the code below doesn't block /// # // forever. /// # use inotify::WatchMask; /// # inotify.watches().add("/tmp/inotify-rs-test-file", WatchMask::MODIFY) /// # .expect("Failed to add file watch"); /// # use std::io::Write; /// # write!(&mut test_file, "something\n") /// # .expect("Failed to write something to test file"); /// # /// let mut buffer = [0; 1024]; /// let events = inotify /// .read_events_blocking(&mut buffer) /// .expect("Error while waiting for events"); /// let mut watches = inotify.watches(); /// /// for event in events { /// watches.remove(event.wd); /// } /// ``` /// /// [`inotify_rm_watch`]: inotify_sys::inotify_rm_watch /// [`Event`]: crate::Event /// [`Inotify`]: crate::Inotify /// [`io::Error`]: std::io::Error /// [`ErrorKind`]: std::io::ErrorKind pub fn remove(&mut self, wd: WatchDescriptor) -> io::Result<()> { if wd.fd.upgrade().as_ref() != Some(&self.fd) { return Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid WatchDescriptor", )); } let result = unsafe { ffi::inotify_rm_watch(**self.fd, wd.id) }; match result { 0 => Ok(()), -1 => Err(io::Error::last_os_error()), _ => panic!( "unexpected return code from inotify_rm_watch ({})", result) } } } /// Represents a watch on an inode /// /// Can be obtained from [`Watches::add`] or from an [`Event`]. A watch /// descriptor can be used to get inotify to stop watching an inode by passing /// it to [`Watches::remove`]. /// /// [`Event`]: crate::Event #[derive(Clone, Debug)] pub struct WatchDescriptor{ pub(crate) id: c_int, pub(crate) fd: Weak, } impl Eq for WatchDescriptor {} impl PartialEq for WatchDescriptor { fn eq(&self, other: &Self) -> bool { let self_fd = self.fd.upgrade(); let other_fd = other.fd.upgrade(); self.id == other.id && self_fd.is_some() && self_fd == other_fd } } impl Ord for WatchDescriptor { fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } } impl PartialOrd for WatchDescriptor { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Hash for WatchDescriptor { fn hash(&self, state: &mut H) { // This function only takes `self.id` into account, as `self.fd` is a // weak pointer that might no longer be available. Since neither // panicking nor changing the hash depending on whether it's available // is acceptable, we just don't look at it at all. // I don't think that this influences storage in a `HashMap` or // `HashSet` negatively, as storing `WatchDescriptor`s from different // `Inotify` instances seems like something of an anti-pattern anyway. self.id.hash(state); } }