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"] from_bits_unchecked(bits: u32) -> Self234 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. get_watch_descriptor_id(&self) -> c_int243 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 new(fd: Arc<FdGuard>) -> Self256 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 add<P>(&mut self, path: P, mask: WatchMask) -> io::Result<WatchDescriptor> where P: AsRef<Path>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 remove(&mut self, wd: WatchDescriptor) -> io::Result<()>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 { eq(&self, other: &Self) -> bool430 fn eq(&self, other: &Self) -> bool { 431 let self_fd = self.fd.upgrade(); 432 let other_fd = 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 { cmp(&self, other: &Self) -> Ordering439 fn cmp(&self, other: &Self) -> Ordering { 440 self.id.cmp(&other.id) 441 } 442 } 443 444 impl PartialOrd for WatchDescriptor { partial_cmp(&self, other: &Self) -> Option<Ordering>445 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 446 Some(self.cmp(other)) 447 } 448 } 449 450 impl Hash for WatchDescriptor { hash<H: Hasher>(&self, state: &mut H)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