| 1 | use std::{ |
| 2 | cmp::Ordering, |
| 3 | ffi::CString, |
| 4 | hash::{ |
| 5 | Hash, |
| 6 | Hasher, |
| 7 | }, |
| 8 | io, |
| 9 | os::raw::c_int, |
| 10 | os::unix::ffi::OsStrExt, |
| 11 | path::Path, |
| 12 | sync::{ |
| 13 | Arc, |
| 14 | Weak, |
| 15 | }, |
| 16 | }; |
| 17 | |
| 18 | use inotify_sys as ffi; |
| 19 | |
| 20 | use crate::fd_guard::FdGuard; |
| 21 | |
| 22 | bitflags! { |
| 23 | /// Describes a file system watch |
| 24 | /// |
| 25 | /// Passed to [`Watches::add`], to describe what file system events |
| 26 | /// to watch for, and how to do that. |
| 27 | /// |
| 28 | /// # Examples |
| 29 | /// |
| 30 | /// `WatchMask` constants can be passed to [`Watches::add`] as is. For |
| 31 | /// example, here's how to create a watch that triggers an event when a file |
| 32 | /// is accessed: |
| 33 | /// |
| 34 | /// ``` rust |
| 35 | /// # use inotify::{ |
| 36 | /// # Inotify, |
| 37 | /// # WatchMask, |
| 38 | /// # }; |
| 39 | /// # |
| 40 | /// # let mut inotify = Inotify::init().unwrap(); |
| 41 | /// # |
| 42 | /// # // Create a temporary file, so `Watches::add` won't return an error. |
| 43 | /// # use std::fs::File; |
| 44 | /// # File::create("/tmp/inotify-rs-test-file") |
| 45 | /// # .expect("Failed to create test file"); |
| 46 | /// # |
| 47 | /// inotify.watches().add("/tmp/inotify-rs-test-file", WatchMask::ACCESS) |
| 48 | /// .expect("Error adding watch"); |
| 49 | /// ``` |
| 50 | /// |
| 51 | /// You can also combine multiple `WatchMask` constants. Here we add a watch |
| 52 | /// this is triggered both when files are created or deleted in a directory: |
| 53 | /// |
| 54 | /// ``` rust |
| 55 | /// # use inotify::{ |
| 56 | /// # Inotify, |
| 57 | /// # WatchMask, |
| 58 | /// # }; |
| 59 | /// # |
| 60 | /// # let mut inotify = Inotify::init().unwrap(); |
| 61 | /// inotify.watches().add("/tmp/", WatchMask::CREATE | WatchMask::DELETE) |
| 62 | /// .expect("Error adding watch"); |
| 63 | /// ``` |
| 64 | #[derive (PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] |
| 65 | pub struct WatchMask: u32 { |
| 66 | /// File was accessed |
| 67 | /// |
| 68 | /// When watching a directory, this event is only triggered for objects |
| 69 | /// inside the directory, not the directory itself. |
| 70 | /// |
| 71 | /// See [`inotify_sys::IN_ACCESS`]. |
| 72 | const ACCESS = ffi::IN_ACCESS; |
| 73 | |
| 74 | /// Metadata (permissions, timestamps, ...) changed |
| 75 | /// |
| 76 | /// When watching a directory, this event can be triggered for the |
| 77 | /// directory itself, as well as objects inside the directory. |
| 78 | /// |
| 79 | /// See [`inotify_sys::IN_ATTRIB`]. |
| 80 | const ATTRIB = ffi::IN_ATTRIB; |
| 81 | |
| 82 | /// File opened for writing was closed |
| 83 | /// |
| 84 | /// When watching a directory, this event is only triggered for objects |
| 85 | /// inside the directory, not the directory itself. |
| 86 | /// |
| 87 | /// See [`inotify_sys::IN_CLOSE_WRITE`]. |
| 88 | const CLOSE_WRITE = ffi::IN_CLOSE_WRITE; |
| 89 | |
| 90 | /// File or directory not opened for writing was closed |
| 91 | /// |
| 92 | /// When watching a directory, this event can be triggered for the |
| 93 | /// directory itself, as well as objects inside the directory. |
| 94 | /// |
| 95 | /// See [`inotify_sys::IN_CLOSE_NOWRITE`]. |
| 96 | const CLOSE_NOWRITE = ffi::IN_CLOSE_NOWRITE; |
| 97 | |
| 98 | /// File/directory created in watched directory |
| 99 | /// |
| 100 | /// When watching a directory, this event is only triggered for objects |
| 101 | /// inside the directory, not the directory itself. |
| 102 | /// |
| 103 | /// See [`inotify_sys::IN_CREATE`]. |
| 104 | const CREATE = ffi::IN_CREATE; |
| 105 | |
| 106 | /// File/directory deleted from watched directory |
| 107 | /// |
| 108 | /// When watching a directory, this event is only triggered for objects |
| 109 | /// inside the directory, not the directory itself. |
| 110 | /// |
| 111 | /// See [`inotify_sys::IN_DELETE`]. |
| 112 | const DELETE = ffi::IN_DELETE; |
| 113 | |
| 114 | /// Watched file/directory was deleted |
| 115 | /// |
| 116 | /// See [`inotify_sys::IN_DELETE_SELF`]. |
| 117 | const DELETE_SELF = ffi::IN_DELETE_SELF; |
| 118 | |
| 119 | /// File was modified |
| 120 | /// |
| 121 | /// When watching a directory, this event is only triggered for objects |
| 122 | /// inside the directory, not the directory itself. |
| 123 | /// |
| 124 | /// See [`inotify_sys::IN_MODIFY`]. |
| 125 | const MODIFY = ffi::IN_MODIFY; |
| 126 | |
| 127 | /// Watched file/directory was moved |
| 128 | /// |
| 129 | /// See [`inotify_sys::IN_MOVE_SELF`]. |
| 130 | const MOVE_SELF = ffi::IN_MOVE_SELF; |
| 131 | |
| 132 | /// File was renamed/moved; watched directory contained old name |
| 133 | /// |
| 134 | /// When watching a directory, this event is only triggered for objects |
| 135 | /// inside the directory, not the directory itself. |
| 136 | /// |
| 137 | /// See [`inotify_sys::IN_MOVED_FROM`]. |
| 138 | const MOVED_FROM = ffi::IN_MOVED_FROM; |
| 139 | |
| 140 | /// File was renamed/moved; watched directory contains new name |
| 141 | /// |
| 142 | /// When watching a directory, this event is only triggered for objects |
| 143 | /// inside the directory, not the directory itself. |
| 144 | /// |
| 145 | /// See [`inotify_sys::IN_MOVED_TO`]. |
| 146 | const MOVED_TO = ffi::IN_MOVED_TO; |
| 147 | |
| 148 | /// File or directory was opened |
| 149 | /// |
| 150 | /// When watching a directory, this event can be triggered for the |
| 151 | /// directory itself, as well as objects inside the directory. |
| 152 | /// |
| 153 | /// See [`inotify_sys::IN_OPEN`]. |
| 154 | const OPEN = ffi::IN_OPEN; |
| 155 | |
| 156 | /// Watch for all events |
| 157 | /// |
| 158 | /// This constant is simply a convenient combination of the following |
| 159 | /// other constants: |
| 160 | /// |
| 161 | /// - [`ACCESS`](Self::ACCESS) |
| 162 | /// - [`ATTRIB`](Self::ATTRIB) |
| 163 | /// - [`CLOSE_WRITE`](Self::CLOSE_WRITE) |
| 164 | /// - [`CLOSE_NOWRITE`](Self::CLOSE_NOWRITE) |
| 165 | /// - [`CREATE`](Self::CREATE) |
| 166 | /// - [`DELETE`](Self::DELETE) |
| 167 | /// - [`DELETE_SELF`](Self::DELETE_SELF) |
| 168 | /// - [`MODIFY`](Self::MODIFY) |
| 169 | /// - [`MOVE_SELF`](Self::MOVE_SELF) |
| 170 | /// - [`MOVED_FROM`](Self::MOVED_FROM) |
| 171 | /// - [`MOVED_TO`](Self::MOVED_TO) |
| 172 | /// - [`OPEN`](Self::OPEN) |
| 173 | /// |
| 174 | /// See [`inotify_sys::IN_ALL_EVENTS`]. |
| 175 | const ALL_EVENTS = ffi::IN_ALL_EVENTS; |
| 176 | |
| 177 | /// Watch for all move events |
| 178 | /// |
| 179 | /// This constant is simply a convenient combination of the following |
| 180 | /// other constants: |
| 181 | /// |
| 182 | /// - [`MOVED_FROM`](Self::MOVED_FROM) |
| 183 | /// - [`MOVED_TO`](Self::MOVED_TO) |
| 184 | /// |
| 185 | /// See [`inotify_sys::IN_MOVE`]. |
| 186 | const MOVE = ffi::IN_MOVE; |
| 187 | |
| 188 | /// Watch for all close events |
| 189 | /// |
| 190 | /// This constant is simply a convenient combination of the following |
| 191 | /// other constants: |
| 192 | /// |
| 193 | /// - [`CLOSE_WRITE`](Self::CLOSE_WRITE) |
| 194 | /// - [`CLOSE_NOWRITE`](Self::CLOSE_NOWRITE) |
| 195 | /// |
| 196 | /// See [`inotify_sys::IN_CLOSE`]. |
| 197 | const CLOSE = ffi::IN_CLOSE; |
| 198 | |
| 199 | /// Don't dereference the path if it is a symbolic link |
| 200 | /// |
| 201 | /// See [`inotify_sys::IN_DONT_FOLLOW`]. |
| 202 | const DONT_FOLLOW = ffi::IN_DONT_FOLLOW; |
| 203 | |
| 204 | /// Filter events for directory entries that have been unlinked |
| 205 | /// |
| 206 | /// See [`inotify_sys::IN_EXCL_UNLINK`]. |
| 207 | const EXCL_UNLINK = ffi::IN_EXCL_UNLINK; |
| 208 | |
| 209 | /// If a watch for the inode exists, amend it instead of replacing it |
| 210 | /// |
| 211 | /// See [`inotify_sys::IN_MASK_ADD`]. |
| 212 | const MASK_ADD = ffi::IN_MASK_ADD; |
| 213 | |
| 214 | /// Only receive one event, then remove the watch |
| 215 | /// |
| 216 | /// See [`inotify_sys::IN_ONESHOT`]. |
| 217 | const ONESHOT = ffi::IN_ONESHOT; |
| 218 | |
| 219 | /// Only watch path, if it is a directory |
| 220 | /// |
| 221 | /// See [`inotify_sys::IN_ONLYDIR`]. |
| 222 | const ONLYDIR = ffi::IN_ONLYDIR; |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | impl WatchMask { |
| 227 | /// Wrapper around [`Self::from_bits_retain`] for backwards compatibility |
| 228 | /// |
| 229 | /// # Safety |
| 230 | /// |
| 231 | /// This function is not actually unsafe. It is just a wrapper around the |
| 232 | /// safe [`Self::from_bits_retain`]. |
| 233 | #[deprecated = "Use the safe `from_bits_retain` method instead" ] |
| 234 | pub unsafe fn from_bits_unchecked(bits: u32) -> Self { |
| 235 | Self::from_bits_retain(bits) |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | impl WatchDescriptor { |
| 240 | /// Getter method for a watcher's id. |
| 241 | /// |
| 242 | /// Can be used to distinguish events for files with the same name. |
| 243 | pub fn get_watch_descriptor_id(&self) -> c_int { |
| 244 | self.id |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | /// Interface for adding and removing watches |
| 249 | #[derive (Clone, Debug)] |
| 250 | pub struct Watches { |
| 251 | pub(crate) fd: Arc<FdGuard>, |
| 252 | } |
| 253 | |
| 254 | impl Watches { |
| 255 | /// Init watches with an inotify file descriptor |
| 256 | pub(crate) fn new(fd: Arc<FdGuard>) -> Self { |
| 257 | Watches { |
| 258 | fd, |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | /// Adds or updates a watch for the given path |
| 263 | /// |
| 264 | /// Adds a new watch or updates an existing one for the file referred to by |
| 265 | /// `path`. Returns a watch descriptor that can be used to refer to this |
| 266 | /// watch later. |
| 267 | /// |
| 268 | /// The `mask` argument defines what kind of changes the file should be |
| 269 | /// watched for, and how to do that. See the documentation of [`WatchMask`] |
| 270 | /// for details. |
| 271 | /// |
| 272 | /// If this method is used to add a new watch, a new [`WatchDescriptor`] is |
| 273 | /// returned. If it is used to update an existing watch, a |
| 274 | /// [`WatchDescriptor`] that equals the previously returned |
| 275 | /// [`WatchDescriptor`] for that watch is returned instead. |
| 276 | /// |
| 277 | /// Under the hood, this method just calls [`inotify_add_watch`] and does |
| 278 | /// some trivial translation between the types on the Rust side and the C |
| 279 | /// side. |
| 280 | /// |
| 281 | /// # Attention: Updating watches and hardlinks |
| 282 | /// |
| 283 | /// As mentioned above, this method can be used to update an existing watch. |
| 284 | /// This is usually done by calling this method with the same `path` |
| 285 | /// argument that it has been called with before. But less obviously, it can |
| 286 | /// also happen if the method is called with a different path that happens |
| 287 | /// to link to the same inode. |
| 288 | /// |
| 289 | /// You can detect this by keeping track of [`WatchDescriptor`]s and the |
| 290 | /// paths they have been returned for. If the same [`WatchDescriptor`] is |
| 291 | /// returned for a different path (and you haven't freed the |
| 292 | /// [`WatchDescriptor`] by removing the watch), you know you have two paths |
| 293 | /// pointing to the same inode, being watched by the same watch. |
| 294 | /// |
| 295 | /// # Errors |
| 296 | /// |
| 297 | /// Directly returns the error from the call to |
| 298 | /// [`inotify_add_watch`][`inotify_add_watch`] (translated into an |
| 299 | /// `io::Error`), without adding any error conditions of |
| 300 | /// its own. |
| 301 | /// |
| 302 | /// # Examples |
| 303 | /// |
| 304 | /// ``` |
| 305 | /// use inotify::{ |
| 306 | /// Inotify, |
| 307 | /// WatchMask, |
| 308 | /// }; |
| 309 | /// |
| 310 | /// let mut inotify = Inotify::init() |
| 311 | /// .expect("Failed to initialize an inotify instance" ); |
| 312 | /// |
| 313 | /// # // Create a temporary file, so `Watches::add` won't return an error. |
| 314 | /// # use std::fs::File; |
| 315 | /// # File::create("/tmp/inotify-rs-test-file" ) |
| 316 | /// # .expect("Failed to create test file" ); |
| 317 | /// # |
| 318 | /// inotify.watches().add("/tmp/inotify-rs-test-file" , WatchMask::MODIFY) |
| 319 | /// .expect("Failed to add file watch" ); |
| 320 | /// |
| 321 | /// // Handle events for the file here |
| 322 | /// ``` |
| 323 | /// |
| 324 | /// [`inotify_add_watch`]: inotify_sys::inotify_add_watch |
| 325 | pub fn add<P>(&mut self, path: P, mask: WatchMask) |
| 326 | -> io::Result<WatchDescriptor> |
| 327 | where P: AsRef<Path> |
| 328 | { |
| 329 | let path = CString::new(path.as_ref().as_os_str().as_bytes())?; |
| 330 | |
| 331 | let wd = unsafe { |
| 332 | ffi::inotify_add_watch( |
| 333 | **self.fd, |
| 334 | path.as_ptr() as *const _, |
| 335 | mask.bits(), |
| 336 | ) |
| 337 | }; |
| 338 | |
| 339 | match wd { |
| 340 | -1 => Err(io::Error::last_os_error()), |
| 341 | _ => Ok(WatchDescriptor{ id: wd, fd: Arc::downgrade(&self.fd) }), |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | /// Stops watching a file |
| 346 | /// |
| 347 | /// Removes the watch represented by the provided [`WatchDescriptor`] by |
| 348 | /// calling [`inotify_rm_watch`]. [`WatchDescriptor`]s can be obtained via |
| 349 | /// [`Watches::add`], or from the `wd` field of [`Event`]. |
| 350 | /// |
| 351 | /// # Errors |
| 352 | /// |
| 353 | /// Directly returns the error from the call to [`inotify_rm_watch`]. |
| 354 | /// Returns an [`io::Error`] with [`ErrorKind`]`::InvalidInput`, if the given |
| 355 | /// [`WatchDescriptor`] did not originate from this [`Inotify`] instance. |
| 356 | /// |
| 357 | /// # Examples |
| 358 | /// |
| 359 | /// ``` |
| 360 | /// use inotify::Inotify; |
| 361 | /// |
| 362 | /// let mut inotify = Inotify::init() |
| 363 | /// .expect("Failed to initialize an inotify instance" ); |
| 364 | /// |
| 365 | /// # // Create a temporary file, so `Watches::add` won't return an error. |
| 366 | /// # use std::fs::File; |
| 367 | /// # let mut test_file = File::create("/tmp/inotify-rs-test-file" ) |
| 368 | /// # .expect("Failed to create test file" ); |
| 369 | /// # |
| 370 | /// # // Add a watch and modify the file, so the code below doesn't block |
| 371 | /// # // forever. |
| 372 | /// # use inotify::WatchMask; |
| 373 | /// # inotify.watches().add("/tmp/inotify-rs-test-file" , WatchMask::MODIFY) |
| 374 | /// # .expect("Failed to add file watch" ); |
| 375 | /// # use std::io::Write; |
| 376 | /// # write!(&mut test_file, "something \n" ) |
| 377 | /// # .expect("Failed to write something to test file" ); |
| 378 | /// # |
| 379 | /// let mut buffer = [0; 1024]; |
| 380 | /// let events = inotify |
| 381 | /// .read_events_blocking(&mut buffer) |
| 382 | /// .expect("Error while waiting for events" ); |
| 383 | /// let mut watches = inotify.watches(); |
| 384 | /// |
| 385 | /// for event in events { |
| 386 | /// watches.remove(event.wd); |
| 387 | /// } |
| 388 | /// ``` |
| 389 | /// |
| 390 | /// [`inotify_rm_watch`]: inotify_sys::inotify_rm_watch |
| 391 | /// [`Event`]: crate::Event |
| 392 | /// [`Inotify`]: crate::Inotify |
| 393 | /// [`io::Error`]: std::io::Error |
| 394 | /// [`ErrorKind`]: std::io::ErrorKind |
| 395 | pub fn remove(&mut self, wd: WatchDescriptor) -> io::Result<()> { |
| 396 | if wd.fd.upgrade().as_ref() != Some(&self.fd) { |
| 397 | return Err(io::Error::new( |
| 398 | io::ErrorKind::InvalidInput, |
| 399 | "Invalid WatchDescriptor" , |
| 400 | )); |
| 401 | } |
| 402 | |
| 403 | let result = unsafe { ffi::inotify_rm_watch(**self.fd, wd.id) }; |
| 404 | match result { |
| 405 | 0 => Ok(()), |
| 406 | -1 => Err(io::Error::last_os_error()), |
| 407 | _ => panic!( |
| 408 | "unexpected return code from inotify_rm_watch ( {})" , result) |
| 409 | } |
| 410 | } |
| 411 | } |
| 412 | |
| 413 | |
| 414 | /// Represents a watch on an inode |
| 415 | /// |
| 416 | /// Can be obtained from [`Watches::add`] or from an [`Event`]. A watch |
| 417 | /// descriptor can be used to get inotify to stop watching an inode by passing |
| 418 | /// it to [`Watches::remove`]. |
| 419 | /// |
| 420 | /// [`Event`]: crate::Event |
| 421 | #[derive (Clone, Debug)] |
| 422 | pub struct WatchDescriptor{ |
| 423 | pub(crate) id: c_int, |
| 424 | pub(crate) fd: Weak<FdGuard>, |
| 425 | } |
| 426 | |
| 427 | impl Eq for WatchDescriptor {} |
| 428 | |
| 429 | impl PartialEq for WatchDescriptor { |
| 430 | fn eq(&self, other: &Self) -> bool { |
| 431 | let self_fd: Option> = self.fd.upgrade(); |
| 432 | let other_fd: Option> = other.fd.upgrade(); |
| 433 | |
| 434 | self.id == other.id && self_fd.is_some() && self_fd == other_fd |
| 435 | } |
| 436 | } |
| 437 | |
| 438 | impl Ord for WatchDescriptor { |
| 439 | fn cmp(&self, other: &Self) -> Ordering { |
| 440 | self.id.cmp(&other.id) |
| 441 | } |
| 442 | } |
| 443 | |
| 444 | impl PartialOrd for WatchDescriptor { |
| 445 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
| 446 | Some(self.cmp(other)) |
| 447 | } |
| 448 | } |
| 449 | |
| 450 | impl Hash for WatchDescriptor { |
| 451 | fn hash<H: Hasher>(&self, state: &mut H) { |
| 452 | // This function only takes `self.id` into account, as `self.fd` is a |
| 453 | // weak pointer that might no longer be available. Since neither |
| 454 | // panicking nor changing the hash depending on whether it's available |
| 455 | // is acceptable, we just don't look at it at all. |
| 456 | // I don't think that this influences storage in a `HashMap` or |
| 457 | // `HashSet` negatively, as storing `WatchDescriptor`s from different |
| 458 | // `Inotify` instances seems like something of an anti-pattern anyway. |
| 459 | self.id.hash(state); |
| 460 | } |
| 461 | } |
| 462 | |