1use super::event::*;
8use super::{Config, Error, ErrorKind, EventHandler, RecursiveMode, Result, WatchMode, Watcher};
9use crate::bimap::BiHashMap;
10use crate::{BoundSender, Receiver, Sender, TargetMode, bounded, unbounded};
11use inotify as inotify_sys;
12use inotify_sys::{EventMask, Inotify, WatchDescriptor, WatchMask};
13use rustc_hash::FxBuildHasher;
14use std::collections::HashMap;
15#[cfg(test)]
16use std::collections::HashSet;
17use std::env;
18use std::fs::metadata;
19use std::os::unix::fs::MetadataExt;
20use std::os::unix::io::AsRawFd;
21use std::path::{Path, PathBuf};
22use std::sync::Arc;
23use std::thread;
24use walkdir::WalkDir;
25
26const INOTIFY: mio::Token = mio::Token(0);
27const MESSAGE: mio::Token = mio::Token(1);
28
29struct EventLoop {
36 running: bool,
37 poll: mio::Poll,
38 event_loop_waker: Arc<mio::Waker>,
39 event_loop_tx: Sender<EventLoopMsg>,
40 event_loop_rx: Receiver<EventLoopMsg>,
41 inotify: Option<Inotify>,
42 event_handler: Box<dyn EventHandler>,
43 watches: HashMap<PathBuf, WatchMode, FxBuildHasher>,
44 watch_handles: BiHashMap<
45 WatchDescriptor,
46 PathBuf,
47 (bool, bool),
48 FxBuildHasher,
49 >,
50 rename_event: Option<Event>,
51 follow_links: bool,
52}
53
54#[derive(Debug)]
56pub struct INotifyWatcher {
57 channel: Sender<EventLoopMsg>,
58 waker: Arc<mio::Waker>,
59}
60
61enum EventLoopMsg {
62 AddWatch(PathBuf, WatchMode, Sender<Result<()>>),
63 RemoveWatch(PathBuf, Sender<Result<()>>),
64 Shutdown,
65 Configure(Config, BoundSender<Result<bool>>),
66 #[cfg(test)]
67 GetWatchHandles(BoundSender<HashSet<PathBuf>>),
68}
69
70#[inline]
71fn add_watch_by_event(
72 path: &PathBuf,
73 is_file_without_hardlinks: bool,
74 watches: &HashMap<PathBuf, WatchMode, FxBuildHasher>,
75 add_watches: &mut Vec<(PathBuf, bool, bool)>,
76) {
77 if let Some(watch_mode) = watches.get(path) {
78 add_watches.push((
79 path.to_owned(),
80 watch_mode.recursive_mode.is_recursive(),
81 is_file_without_hardlinks,
82 ));
83 return;
84 }
85
86 let Some(parent) = path.parent() else {
87 return;
88 };
89 if let Some(watch_mode) = watches.get(parent) {
90 add_watches.push((
91 path.to_owned(),
92 watch_mode.recursive_mode.is_recursive(),
93 is_file_without_hardlinks,
94 ));
95 return;
96 }
97
98 for ancestor in parent.ancestors().skip(1) {
99 if let Some(watch_mode) = watches.get(ancestor)
100 && watch_mode.recursive_mode == RecursiveMode::Recursive
101 {
102 add_watches.push((path.to_owned(), true, is_file_without_hardlinks));
103 return;
104 }
105 }
106}
107
108#[inline]
109fn remove_watch_by_event(
110 path: &PathBuf,
111 watch_handles: &BiHashMap<WatchDescriptor, PathBuf, (bool, bool), FxBuildHasher>,
112 remove_watches: &mut Vec<PathBuf>,
113) {
114 if watch_handles.contains_right(path) {
115 remove_watches.push(path.to_owned());
116 }
117}
118
119impl EventLoop {
120 pub fn new(
121 inotify: Inotify,
122 event_handler: Box<dyn EventHandler>,
123 follow_links: bool,
124 ) -> Result<Self> {
125 let (event_loop_tx, event_loop_rx) = unbounded::<EventLoopMsg>();
126 let poll = mio::Poll::new()?;
127
128 let event_loop_waker = Arc::new(mio::Waker::new(poll.registry(), MESSAGE)?);
129
130 let inotify_fd = inotify.as_raw_fd();
131 let mut evented_inotify = mio::unix::SourceFd(&inotify_fd);
132 poll.registry()
133 .register(&mut evented_inotify, INOTIFY, mio::Interest::READABLE)?;
134
135 let event_loop = EventLoop {
136 running: true,
137 poll,
138 event_loop_waker,
139 event_loop_tx,
140 event_loop_rx,
141 inotify: Some(inotify),
142 event_handler,
143 watches: HashMap::default(),
144 watch_handles: BiHashMap::default(),
145 rename_event: None,
146 follow_links,
147 };
148 Ok(event_loop)
149 }
150
151 pub fn run(self) {
153 let result = thread::Builder::new()
154 .name("notify-rs inotify loop".to_string())
155 .spawn(|| self.event_loop_thread());
156 if let Err(e) = result {
157 tracing::error!(?e, "failed to start inotify event loop thread");
158 }
159 }
160
161 fn event_loop_thread(mut self) {
162 let mut events = mio::Events::with_capacity(16);
163 loop {
164 match self.poll.poll(&mut events, None) {
166 Err(ref e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => {
167 }
170 Err(e) => panic!("poll failed: {e}"),
171 Ok(()) => {}
172 }
173
174 for event in &events {
176 self.handle_event(event);
177 }
178
179 if !self.running {
181 break;
182 }
183 }
184 }
185
186 fn handle_event(&mut self, event: &mio::event::Event) {
188 match event.token() {
189 MESSAGE => {
190 self.handle_messages();
192 }
193 INOTIFY => {
194 self.handle_inotify();
196 }
197 _ => unreachable!(),
198 }
199 }
200
201 fn handle_messages(&mut self) {
202 while let Ok(msg) = self.event_loop_rx.try_recv() {
203 match msg {
204 EventLoopMsg::AddWatch(path, watch_mode, tx) => {
205 let result = tx.send(self.add_watch(path, watch_mode));
206 if let Err(e) = result {
207 tracing::error!(?e, "failed to send AddWatch result");
208 }
209 }
210 EventLoopMsg::RemoveWatch(path, tx) => {
211 let result = tx.send(self.remove_watch(path));
212 if let Err(e) = result {
213 tracing::error!(?e, "failed to send RemoveWatch result");
214 }
215 }
216 EventLoopMsg::Shutdown => {
217 let result = self.remove_all_watches();
218 if let Err(e) = result {
219 tracing::error!(?e, "failed to remove all watches on shutdown");
220 }
221 if let Some(inotify) = self.inotify.take() {
222 let result = inotify.close();
223 if let Err(e) = result {
224 tracing::error!(?e, "failed to close inotify instance on shutdown");
225 }
226 }
227 self.running = false;
228 break;
229 }
230 EventLoopMsg::Configure(config, tx) => {
231 Self::configure_raw_mode(config, &tx);
232 }
233 #[cfg(test)]
234 EventLoopMsg::GetWatchHandles(tx) => {
235 let handles: HashSet<PathBuf> = self
236 .watch_handles
237 .iter()
238 .map(|(_, path, _)| path.clone())
239 .collect();
240 tx.send(handles).unwrap();
241 }
242 }
243 }
244 }
245
246 fn configure_raw_mode(_config: Config, tx: &BoundSender<Result<bool>>) {
247 tx.send(Ok(false))
248 .expect("configuration channel disconnected");
249 }
250
251 fn is_watched_path(watches: &HashMap<PathBuf, WatchMode, FxBuildHasher>, path: &Path) -> bool {
252 if watches.contains_key(path) {
253 return true;
254 }
255
256 let Some(parent) = path.parent() else {
257 return false;
258 };
259 if watches.contains_key(parent) {
260 return true;
261 }
262
263 parent.ancestors().skip(1).any(|ancestor| {
264 watches
265 .get(ancestor)
266 .is_some_and(|watch_mode| watch_mode.recursive_mode == RecursiveMode::Recursive)
267 })
268 }
269
270 #[expect(clippy::too_many_lines)]
271 fn handle_inotify(&mut self) {
272 let mut add_watches = Vec::new();
273 let mut remove_watches = Vec::new();
274 let mut remove_watches_no_syscall = Vec::new();
275
276 if let Some(ref mut inotify) = self.inotify {
277 let mut buffer = [0; 1024];
278 loop {
280 match inotify.read_events(&mut buffer) {
281 Ok(events) => {
282 let mut num_events = 0;
283 for event in events {
284 tracing::trace!(?event, "inotify event received");
285
286 num_events += 1;
287 if event.mask.contains(EventMask::Q_OVERFLOW) {
288 let ev = Ok(Event::new(EventKind::Other).set_flag(Flag::Rescan));
289 self.event_handler.handle_event(ev);
290 }
291
292 let path = match event.name {
293 Some(name) => self
294 .watch_handles
295 .get_by_left(&event.wd)
296 .map(|(root, _)| root.join(name)),
297 None => self
298 .watch_handles
299 .get_by_left(&event.wd)
300 .map(|(root, _)| root.clone()),
301 };
302
303 let Some(path) = path else {
304 tracing::debug!(?event, "inotify event with unknown descriptor");
305 continue;
306 };
307
308 let mut evs = Vec::new();
309
310 if event.mask.contains(EventMask::MOVED_FROM) {
311 remove_watch_by_event(
312 &path,
313 &self.watch_handles,
314 &mut remove_watches,
315 );
316
317 let event = Event::new(EventKind::Modify(ModifyKind::Name(
318 RenameMode::From,
319 )))
320 .add_path(path.clone())
321 .set_tracker(event.cookie as usize);
322
323 self.rename_event = Some(event.clone());
324
325 if Self::is_watched_path(&self.watches, &path) {
326 evs.push(event);
327 }
328 } else if event.mask.contains(EventMask::MOVED_TO) {
329 if Self::is_watched_path(&self.watches, &path) {
330 evs.push(
331 Event::new(EventKind::Modify(ModifyKind::Name(
332 RenameMode::To,
333 )))
334 .set_tracker(event.cookie as usize)
335 .add_path(path.clone()),
336 );
337
338 let trackers_match =
339 self.rename_event.as_ref().and_then(|e| e.tracker())
340 == Some(event.cookie as usize);
341
342 if trackers_match {
343 let rename_event = self.rename_event.take().unwrap(); let from_path = rename_event.paths.first();
345 if from_path.is_none_or(|from_path| {
346 Self::is_watched_path(&self.watches, from_path)
347 }) {
348 evs.push(
349 Event::new(EventKind::Modify(ModifyKind::Name(
350 RenameMode::Both,
351 )))
352 .set_tracker(event.cookie as usize)
353 .add_some_path(from_path.cloned())
354 .add_path(path.clone()),
355 );
356 }
357 }
358 }
359
360 let is_file_without_hardlinks = !event
361 .mask
362 .contains(EventMask::ISDIR)
363 && metadata(&path).is_ok_and(|m| m.is_file_without_hardlinks());
364 add_watch_by_event(
365 &path,
366 is_file_without_hardlinks,
367 &self.watches,
368 &mut add_watches,
369 );
370 }
371 if event.mask.contains(EventMask::MOVE_SELF) {
372 remove_watch_by_event(
373 &path,
374 &self.watch_handles,
375 &mut remove_watches,
376 );
377 if Self::is_watched_path(&self.watches, &path) {
378 evs.push(
379 Event::new(EventKind::Modify(ModifyKind::Name(
380 RenameMode::From,
381 )))
382 .add_path(path.clone()),
383 );
384 }
388 }
389 if event.mask.contains(EventMask::CREATE) {
390 let is_dir = event.mask.contains(EventMask::ISDIR);
391 if Self::is_watched_path(&self.watches, &path) {
392 evs.push(
393 Event::new(EventKind::Create(if is_dir {
394 CreateKind::Folder
395 } else {
396 CreateKind::File
397 }))
398 .add_path(path.clone()),
399 );
400 }
401 let is_file_without_hardlinks = !is_dir
402 && metadata(&path).is_ok_and(|m| m.is_file_without_hardlinks());
403 add_watch_by_event(
404 &path,
405 is_file_without_hardlinks,
406 &self.watches,
407 &mut add_watches,
408 );
409 }
410 if event.mask.contains(EventMask::DELETE) {
411 if Self::is_watched_path(&self.watches, &path) {
412 evs.push(
413 Event::new(EventKind::Remove(
414 if event.mask.contains(EventMask::ISDIR) {
415 RemoveKind::Folder
416 } else {
417 RemoveKind::File
418 },
419 ))
420 .add_path(path.clone()),
421 );
422 }
423 remove_watch_by_event(
424 &path,
425 &self.watch_handles,
426 &mut remove_watches,
427 );
428 }
429 if event.mask.contains(EventMask::DELETE_SELF) {
430 let remove_kind = match self.watch_handles.get_by_right(&path) {
431 Some((_, (_, true))) => RemoveKind::Folder,
432 Some((_, (_, false))) => RemoveKind::File,
433 None => RemoveKind::Other,
434 };
435 if Self::is_watched_path(&self.watches, &path) {
436 evs.push(
437 Event::new(EventKind::Remove(remove_kind))
438 .add_path(path.clone()),
439 );
440 }
441 remove_watch_by_event(
442 &path,
443 &self.watch_handles,
444 &mut remove_watches,
445 );
446 }
447 if event.mask.contains(EventMask::UNMOUNT) {
448 if Self::is_watched_path(&self.watches, &path) {
449 evs.push(
450 Event::new(EventKind::Remove(RemoveKind::Other))
451 .add_path(path.clone()),
452 );
453 }
454 remove_watch_by_event(
458 &path,
459 &self.watch_handles,
460 &mut remove_watches_no_syscall,
461 );
462 }
463 if event.mask.contains(EventMask::MODIFY)
464 && Self::is_watched_path(&self.watches, &path)
465 {
466 evs.push(
467 Event::new(EventKind::Modify(ModifyKind::Data(
468 DataChange::Any,
469 )))
470 .add_path(path.clone()),
471 );
472 }
473 if event.mask.contains(EventMask::CLOSE_WRITE)
474 && Self::is_watched_path(&self.watches, &path)
475 {
476 evs.push(
477 Event::new(EventKind::Access(AccessKind::Close(
478 AccessMode::Write,
479 )))
480 .add_path(path.clone()),
481 );
482 }
483 if event.mask.contains(EventMask::CLOSE_NOWRITE)
484 && Self::is_watched_path(&self.watches, &path)
485 {
486 evs.push(
487 Event::new(EventKind::Access(AccessKind::Close(
488 AccessMode::Read,
489 )))
490 .add_path(path.clone()),
491 );
492 }
493 if event.mask.contains(EventMask::ATTRIB)
494 && Self::is_watched_path(&self.watches, &path)
495 {
496 evs.push(
497 Event::new(EventKind::Modify(ModifyKind::Metadata(
498 MetadataKind::Any,
499 )))
500 .add_path(path.clone()),
501 );
502 }
503 if event.mask.contains(EventMask::OPEN)
504 && Self::is_watched_path(&self.watches, &path)
505 {
506 evs.push(
507 Event::new(EventKind::Access(AccessKind::Open(
508 AccessMode::Any,
509 )))
510 .add_path(path.clone()),
511 );
512 }
513
514 for ev in evs {
515 self.event_handler.handle_event(Ok(ev));
516 }
517 }
518
519 if num_events == 0 {
521 break;
522 }
523 }
524 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
525 break;
527 }
528 Err(e) => {
529 self.event_handler.handle_event(Err(Error::io(e)));
530 }
531 }
532 }
533 }
534
535 tracing::trace!(
536 ?add_watches,
537 ?remove_watches,
538 "processing inotify watch changes"
539 );
540
541 for path in remove_watches_no_syscall {
542 if self
543 .watches
544 .get(&path)
545 .is_some_and(|watch_mode| watch_mode.target_mode == TargetMode::NoTrack)
546 {
547 self.watches.remove(&path);
548 }
549 self.remove_maybe_recursive_watch(&path, true, true).ok();
550 }
551
552 for path in remove_watches {
553 if self
554 .watches
555 .get(&path)
556 .is_some_and(|watch_mode| watch_mode.target_mode == TargetMode::NoTrack)
557 {
558 self.watches.remove(&path);
559 }
560 self.remove_maybe_recursive_watch(&path, true, false).ok();
561 }
562
563 for (path, is_recursive, is_file_without_hardlinks) in add_watches {
564 if let Err(add_watch_error) =
565 self.add_maybe_recursive_watch(path, is_recursive, is_file_without_hardlinks, false)
566 {
567 if let ErrorKind::MaxFilesWatch = add_watch_error.kind {
571 self.event_handler.handle_event(Err(add_watch_error));
572
573 break;
577 }
578 }
579 }
580 }
581
582 #[tracing::instrument(level = "trace", skip(self))]
583 fn add_watch(&mut self, path: PathBuf, watch_mode: WatchMode) -> Result<()> {
584 if let Some(existing) = self.watches.get(&path) {
585 let need_upgrade_to_recursive = match existing.recursive_mode {
586 RecursiveMode::Recursive => false,
587 RecursiveMode::NonRecursive => {
588 watch_mode.recursive_mode == RecursiveMode::Recursive
589 }
590 };
591 let need_to_watch_parent_newly = match existing.target_mode {
592 TargetMode::TrackPath => false,
593 TargetMode::NoTrack => watch_mode.target_mode == TargetMode::TrackPath,
594 };
595 tracing::trace!(
596 ?need_upgrade_to_recursive,
597 ?need_to_watch_parent_newly,
598 "upgrading existing watch for path: {}",
599 path.display()
600 );
601 if need_to_watch_parent_newly && let Some(parent) = path.parent() {
602 self.add_single_watch(parent.to_path_buf(), true, false)?;
603 }
604 if !need_upgrade_to_recursive {
605 return Ok(());
606 }
607
608 if metadata(&path).map_err(Error::io)?.is_dir() {
610 self.add_maybe_recursive_watch(path.clone(), true, false, true)?;
611 }
612 self.watches
613 .get_mut(&path)
614 .unwrap()
615 .upgrade_with(watch_mode);
616 return Ok(());
617 }
618
619 if watch_mode.target_mode == TargetMode::TrackPath
620 && let Some(parent) = path.parent()
621 {
622 self.add_single_watch(parent.to_path_buf(), true, false)?;
623 }
624
625 let meta = match metadata(&path).map_err(Error::io_watch) {
626 Ok(metadata) => metadata,
627 Err(err) => {
628 if watch_mode.target_mode == TargetMode::TrackPath
629 && matches!(err.kind, ErrorKind::PathNotFound)
630 {
631 self.watches.insert(path, watch_mode);
632 return Ok(());
633 }
634 return Err(err);
635 }
636 };
637
638 self.add_maybe_recursive_watch(
639 path.clone(),
640 watch_mode.recursive_mode.is_recursive() && meta.is_dir(),
643 meta.is_file_without_hardlinks(),
644 watch_mode.target_mode != TargetMode::TrackPath, )?;
646
647 self.watches.insert(path, watch_mode);
648
649 Ok(())
650 }
651
652 #[tracing::instrument(level = "trace", skip(self))]
653 fn add_maybe_recursive_watch(
654 &mut self,
655 path: PathBuf,
656 is_recursive: bool,
657 is_file_without_hardlinks: bool,
658 mut watch_self: bool,
659 ) -> Result<()> {
660 if is_recursive {
661 for entry in WalkDir::new(&path)
662 .follow_links(self.follow_links)
663 .into_iter()
664 .filter_map(filter_dir)
665 {
666 self.add_single_watch(entry.into_path(), false, watch_self)?;
667 watch_self = false;
668 }
669 } else {
670 self.add_single_watch(path, is_file_without_hardlinks, watch_self)?;
671 }
672 Ok(())
673 }
674
675 #[tracing::instrument(level = "trace", skip(self))]
676 fn add_single_watch(
677 &mut self,
678 path: PathBuf,
679 is_file_without_hardlinks: bool,
680 watch_self: bool,
681 ) -> Result<()> {
682 if let Some((_, &(old_watch_self, _))) = self.watch_handles.get_by_right(&path)
683 && (old_watch_self || !watch_self)
685 {
686 tracing::trace!(
687 "watch handle already exists and no need to upgrade: {}",
688 path.display()
689 );
690 return Ok(());
691 }
692
693 if is_file_without_hardlinks
694 && let Some(parent) = path.parent()
695 && self.watch_handles.get_by_right(parent).is_some()
696 {
697 tracing::trace!(
698 "parent dir watch handle already exists and is a file without hardlinks: {}",
699 path.display()
700 );
701 return Ok(());
702 }
703
704 let mut watchmask = WatchMask::ATTRIB
705 | WatchMask::CREATE
706 | WatchMask::OPEN
707 | WatchMask::DELETE
708 | WatchMask::CLOSE_WRITE
709 | WatchMask::MODIFY
710 | WatchMask::MOVED_FROM
711 | WatchMask::MOVED_TO;
712 if watch_self {
713 watchmask.insert(WatchMask::DELETE_SELF);
714 watchmask.insert(WatchMask::MOVE_SELF);
715 }
716
717 if let Some(ref mut inotify) = self.inotify {
718 tracing::trace!("adding inotify watch: {}", path.display());
719
720 match inotify.watches().add(&path, watchmask) {
721 Err(e) => {
722 Err(if e.raw_os_error() == Some(libc::ENOSPC) {
723 Error::new(ErrorKind::MaxFilesWatch)
725 } else if e.kind() == std::io::ErrorKind::NotFound {
726 Error::new(ErrorKind::PathNotFound)
727 } else {
728 Error::io(e)
729 }
730 .add_path(path))
731 }
732 Ok(w) => {
733 watchmask.remove(WatchMask::MASK_ADD);
734 let is_dir = metadata(&path).map_err(Error::io)?.is_dir();
735 self.watch_handles.insert(w, path, (watch_self, is_dir));
736 Ok(())
737 }
738 }
739 } else {
740 Ok(())
741 }
742 }
743
744 #[tracing::instrument(level = "trace", skip(self))]
745 fn remove_watch(&mut self, path: PathBuf) -> Result<()> {
746 match self.watches.remove(&path) {
747 None => return Err(Error::watch_not_found().add_path(path)),
748 Some(watch_mode) => {
749 self.remove_maybe_recursive_watch(
750 &path,
751 watch_mode.recursive_mode.is_recursive(),
752 false,
753 )?;
754 }
755 }
756 Ok(())
757 }
758
759 #[tracing::instrument(level = "trace", skip(self))]
760 fn remove_maybe_recursive_watch(
761 &mut self,
762 path: &Path,
763 is_recursive: bool,
764 without_os_call: bool,
765 ) -> Result<()> {
766 let Some(ref mut inotify) = self.inotify else {
767 return Ok(());
768 };
769 let mut inotify_watches = inotify.watches();
770
771 if let Some((handle, _)) = self.watch_handles.remove_by_right(path) {
772 tracing::trace!("removing inotify watch: {}", path.display());
773
774 if !without_os_call {
775 inotify_watches
776 .remove(handle)
777 .map_err(|e| Error::io(e).add_path(path.to_path_buf()))?;
778 }
779 }
780
781 if is_recursive {
782 let mut remove_list = Vec::new();
783 for (w, p, _) in &self.watch_handles {
784 if p.starts_with(path) {
785 if !without_os_call {
786 inotify_watches
787 .remove(w.clone())
788 .map_err(|e| Error::io(e).add_path(p.into()))?;
789 }
790 remove_list.push(w.clone());
791 }
792 }
793 for w in remove_list {
794 self.watch_handles.remove_by_left(&w);
795 }
796 }
797 Ok(())
798 }
799
800 fn remove_all_watches(&mut self) -> Result<()> {
801 if let Some(ref mut inotify) = self.inotify {
802 let mut inotify_watches = inotify.watches();
803 for (w, p, _) in &self.watch_handles {
804 inotify_watches
805 .remove(w.clone())
806 .map_err(|e| Error::io(e).add_path(p.into()))?;
807 }
808 self.watch_handles.clear();
809 self.watches.clear();
810 }
811 Ok(())
812 }
813}
814
815fn filter_dir(e: walkdir::Result<walkdir::DirEntry>) -> Option<walkdir::DirEntry> {
817 if let Ok(e) = e
818 && e.file_type().is_dir()
819 {
820 return Some(e);
821 }
822 None
823}
824
825impl INotifyWatcher {
826 fn from_event_handler(
827 event_handler: Box<dyn EventHandler>,
828 follow_links: bool,
829 ) -> Result<Self> {
830 let inotify = Inotify::init()?;
831 let event_loop = EventLoop::new(inotify, event_handler, follow_links)?;
832 let channel = event_loop.event_loop_tx.clone();
833 let waker = Arc::clone(&event_loop.event_loop_waker);
834 event_loop.run();
835 Ok(INotifyWatcher { channel, waker })
836 }
837
838 fn watch_inner(&self, path: &Path, watch_mode: WatchMode) -> Result<()> {
839 let pb = if path.is_absolute() {
840 path.to_owned()
841 } else {
842 let p = env::current_dir().map_err(Error::io)?;
843 p.join(path)
844 };
845 let (tx, rx) = unbounded();
846 let msg = EventLoopMsg::AddWatch(pb, watch_mode, tx);
847
848 self.channel.send(msg).unwrap();
850 self.waker.wake().unwrap();
851 rx.recv().unwrap()
852 }
853
854 fn unwatch_inner(&self, path: &Path) -> Result<()> {
855 let pb = if path.is_absolute() {
856 path.to_owned()
857 } else {
858 let p = env::current_dir().map_err(Error::io)?;
859 p.join(path)
860 };
861 let (tx, rx) = unbounded();
862 let msg = EventLoopMsg::RemoveWatch(pb, tx);
863
864 self.channel.send(msg).unwrap();
866 self.waker.wake().unwrap();
867 rx.recv().unwrap()
868 }
869}
870
871impl Watcher for INotifyWatcher {
872 #[tracing::instrument(level = "debug", skip(event_handler))]
874 fn new<F: EventHandler>(event_handler: F, config: Config) -> Result<Self> {
875 Self::from_event_handler(Box::new(event_handler), config.follow_symlinks())
876 }
877
878 #[tracing::instrument(level = "debug", skip(self))]
879 fn watch(&mut self, path: &Path, watch_mode: WatchMode) -> Result<()> {
880 self.watch_inner(path, watch_mode)
881 }
882
883 #[tracing::instrument(level = "debug", skip(self))]
884 fn unwatch(&mut self, path: &Path) -> Result<()> {
885 self.unwatch_inner(path)
886 }
887
888 #[tracing::instrument(level = "debug", skip(self))]
889 fn configure(&mut self, config: Config) -> Result<bool> {
890 let (tx, rx) = bounded(1);
891 self.channel.send(EventLoopMsg::Configure(config, tx))?;
892 self.waker.wake()?;
893 rx.recv()?
894 }
895
896 fn kind() -> crate::WatcherKind {
897 crate::WatcherKind::Inotify
898 }
899
900 #[cfg(test)]
901 fn get_watch_handles(&self) -> std::collections::HashSet<std::path::PathBuf> {
902 let (tx, rx) = bounded(1);
903 self.channel
904 .send(EventLoopMsg::GetWatchHandles(tx))
905 .unwrap();
906 self.waker.wake().unwrap();
907 rx.recv().unwrap()
908 }
909}
910
911impl Drop for INotifyWatcher {
912 fn drop(&mut self) {
913 self.channel.send(EventLoopMsg::Shutdown).unwrap();
915 self.waker.wake().unwrap();
916 }
917}
918
919trait MetadataNotifyExt {
920 fn is_file_without_hardlinks(&self) -> bool;
921}
922
923impl MetadataNotifyExt for std::fs::Metadata {
924 #[inline]
925 fn is_file_without_hardlinks(&self) -> bool {
926 self.is_file() && self.nlink() == 1
927 }
928}
929
930#[cfg(test)]
931mod tests {
932 use std::{
933 collections::HashSet,
934 path::{Path, PathBuf},
935 sync::{Arc, atomic::AtomicBool, mpsc},
936 thread::{self, available_parallelism},
937 time::Duration,
938 };
939
940 use super::{Config, Error, ErrorKind, Event, INotifyWatcher, Result, Watcher};
941
942 use crate::{RecursiveMode, TargetMode, config::WatchMode, test::*};
943
944 fn watcher() -> (TestWatcher<INotifyWatcher>, Receiver) {
945 channel()
946 }
947
948 #[test]
949 fn inotify_watcher_is_send_and_sync() {
950 fn check<T: Send + Sync>() {}
951 check::<INotifyWatcher>();
952 }
953
954 #[test]
955 fn native_error_type_on_missing_path() {
956 let mut watcher = INotifyWatcher::new(|_| {}, Config::default()).unwrap();
957
958 let result = watcher.watch(
959 &PathBuf::from("/some/non/existant/path"),
960 WatchMode::non_recursive(),
961 );
962
963 assert!(matches!(
964 result,
965 Err(Error {
966 paths: _,
967 kind: ErrorKind::PathNotFound
968 })
969 ));
970 }
971
972 #[test]
980 #[ignore = "requires changing sysctl fs.inotify.max_user_watches while test is running"]
981 fn recursive_watch_calls_handler_if_creating_a_file_raises_max_files_watch() {
982 use std::time::Duration;
983
984 let tmpdir = tempfile::tempdir().unwrap();
985 let (tx, rx) = std::sync::mpsc::channel();
986 let (proc_changed_tx, proc_changed_rx) = std::sync::mpsc::channel();
987 let proc_path = Path::new("/proc/sys/fs/inotify/max_user_watches");
988 let mut watcher = INotifyWatcher::new(
989 move |result: Result<Event>| match result {
990 Ok(event) => {
991 if event.paths.first().is_some_and(|path| path == proc_path) {
992 proc_changed_tx.send(()).unwrap();
993 }
994 }
995 Err(e) => tx.send(e).unwrap(),
996 },
997 Config::default(),
998 )
999 .unwrap();
1000
1001 watcher
1002 .watch(tmpdir.path(), WatchMode::recursive())
1003 .unwrap();
1004 watcher
1005 .watch(proc_path, WatchMode::non_recursive())
1006 .unwrap();
1007
1008 proc_changed_rx
1010 .recv_timeout(Duration::from_secs(30))
1011 .unwrap();
1012
1013 let child_dir = tmpdir.path().join("child");
1014 std::fs::create_dir(child_dir).unwrap();
1015
1016 let result = rx.recv_timeout(Duration::from_millis(500));
1017
1018 assert!(
1019 matches!(
1020 &result,
1021 Ok(Error {
1022 kind: ErrorKind::MaxFilesWatch,
1023 paths: _,
1024 })
1025 ),
1026 "expected {:?}, found: {:#?}",
1027 ErrorKind::MaxFilesWatch,
1028 result
1029 );
1030 }
1031
1032 #[test]
1034 fn race_condition_on_unwatch_and_pending_events_with_deleted_descriptor() {
1035 let tmpdir = tempfile::tempdir().expect("tmpdir");
1036 let (tx, rx) = mpsc::channel();
1037 let mut inotify = INotifyWatcher::new(
1038 move |e: Result<Event>| {
1039 let e = match e {
1040 Ok(e) if e.paths.is_empty() => e,
1041 Ok(_) | Err(_) => return,
1042 };
1043 let _ = tx.send(e);
1044 },
1045 Config::default(),
1046 )
1047 .expect("inotify creation");
1048
1049 let dir_path = tmpdir.path();
1050 let file_path = dir_path.join("foo");
1051 std::fs::File::create(&file_path).unwrap();
1052
1053 let stop = Arc::new(AtomicBool::new(false));
1054
1055 let handles: Vec<_> = (0..available_parallelism().unwrap().get().max(4))
1056 .map(|_| {
1057 let file_path = file_path.clone();
1058 let stop = Arc::clone(&stop);
1059 thread::spawn(move || {
1060 while !stop.load(std::sync::atomic::Ordering::Relaxed) {
1061 let _ = std::fs::File::open(&file_path).unwrap();
1062 }
1063 })
1064 })
1065 .collect();
1066
1067 let non_recursive = WatchMode::non_recursive();
1068 for _ in 0..(handles.len() * 4) {
1069 inotify.watch(dir_path, non_recursive).unwrap();
1070 inotify.unwatch(dir_path).unwrap();
1071 }
1072
1073 stop.store(true, std::sync::atomic::Ordering::Relaxed);
1074 for handle in handles {
1075 handle.join().ok().unwrap_or_default();
1076 }
1077
1078 drop(inotify);
1079
1080 let events: Vec<_> = rx.into_iter().map(|e| format!("{e:?}")).collect();
1081
1082 const LOG_LEN: usize = 10;
1083 let events_len = events.len();
1084 assert!(
1085 events.is_empty(),
1086 "expected no events without path, but got {events_len}. first 10: {:#?}",
1087 &events[..LOG_LEN.min(events_len)]
1088 );
1089 }
1090
1091 #[test]
1092 fn create_file() {
1093 let tmpdir = testdir();
1094 let (mut watcher, rx) = watcher();
1095 watcher.watch_recursively(&tmpdir);
1096
1097 let path = tmpdir.path().join("entry");
1098 std::fs::File::create_new(&path).expect("create");
1099
1100 rx.wait_ordered_exact([
1101 expected(tmpdir.path()).access_open_any().optional(),
1102 expected(&path).create_file(),
1103 expected(&path).access_open_any(),
1104 expected(&path).access_close_write(),
1105 ]);
1106 assert_eq!(
1107 watcher.get_watch_handles(),
1108 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1109 );
1110 }
1111
1112 #[test]
1113 fn create_self_file() {
1114 let tmpdir = testdir();
1115 let (mut watcher, rx) = watcher();
1116
1117 let path = tmpdir.path().join("entry");
1118
1119 watcher.watch_nonrecursively(&path);
1120
1121 std::fs::File::create_new(&path).expect("create");
1122
1123 rx.wait_ordered_exact([
1124 expected(&path).create_file(),
1125 expected(&path).access_open_any(),
1126 expected(&path).access_close_write(),
1127 ]);
1128 assert_eq!(
1129 watcher.get_watch_handles(),
1130 HashSet::from([tmpdir.to_path_buf()])
1131 );
1132 }
1133
1134 #[test]
1135 fn create_self_file_no_track() {
1136 let tmpdir = testdir();
1137 let (mut watcher, _) = watcher();
1138
1139 let path = tmpdir.path().join("entry");
1140
1141 let result = watcher.watcher.watch(
1142 &path,
1143 WatchMode {
1144 recursive_mode: RecursiveMode::NonRecursive,
1145 target_mode: TargetMode::NoTrack,
1146 },
1147 );
1148 assert!(matches!(
1149 result,
1150 Err(Error {
1151 paths: _,
1152 kind: ErrorKind::PathNotFound
1153 })
1154 ));
1155 }
1156
1157 #[test]
1158 #[ignore = "TODO: not implemented"]
1159 fn create_self_file_nested() {
1160 let tmpdir = testdir();
1161 let (mut watcher, rx) = watcher();
1162
1163 let path = tmpdir.path().join("entry/nested");
1164
1165 watcher.watch_nonrecursively(&path);
1166
1167 std::fs::create_dir_all(path.parent().unwrap()).expect("create");
1168 std::fs::File::create_new(&path).expect("create");
1169
1170 rx.wait_ordered_exact([
1171 expected(&path).create_file(),
1172 expected(&path).access_open_any(),
1173 expected(&path).access_close_write(),
1174 ]);
1175 assert_eq!(
1176 watcher.get_watch_handles(),
1177 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1178 );
1179 }
1180
1181 #[test]
1182 fn create_file_nested_in_recursive_watch() {
1183 let tmpdir = testdir();
1184 let (mut watcher, rx) = watcher();
1185
1186 let nested1_dir = tmpdir.path().join("nested1");
1187 let nested2_dir = nested1_dir.join("nested2");
1188 std::fs::create_dir_all(&nested2_dir).expect("create_dir");
1189
1190 watcher.watch_recursively(&tmpdir);
1191
1192 let path = nested2_dir.join("entry");
1193 std::fs::File::create_new(&path).expect("create");
1194
1195 rx.wait_ordered_exact([
1196 expected(tmpdir.path()).access_open_any().optional(),
1197 expected(&nested1_dir).access_open_any().optional(),
1198 expected(&nested2_dir).access_open_any().optional(),
1199 expected(&path).create_file(),
1200 expected(&path).access_open_any(),
1201 expected(&path).access_close_write(),
1202 ]);
1203 assert_eq!(
1204 watcher.get_watch_handles(),
1205 HashSet::from([
1206 tmpdir.parent_path_buf(),
1207 tmpdir.to_path_buf(),
1208 nested1_dir,
1209 nested2_dir
1210 ])
1211 );
1212 }
1213
1214 #[test]
1215 fn write_file() {
1216 let tmpdir = testdir();
1217 let (mut watcher, rx) = watcher();
1218
1219 let path = tmpdir.path().join("entry");
1220 std::fs::File::create_new(&path).expect("create");
1221
1222 watcher.watch_recursively(&tmpdir);
1223 std::fs::write(&path, b"123").expect("write");
1224
1225 rx.wait_ordered_exact([
1226 expected(tmpdir.path()).access_open_any().optional(),
1227 expected(&path).access_open_any(),
1228 expected(&path).modify_data_any().multiple(),
1229 expected(&path).access_close_write(),
1230 ])
1231 .ensure_no_tail();
1232 assert_eq!(
1233 watcher.get_watch_handles(),
1234 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1235 );
1236 }
1237
1238 #[test]
1239 fn chmod_file() {
1240 let tmpdir = testdir();
1241 let (mut watcher, rx) = watcher();
1242
1243 let path = tmpdir.path().join("entry");
1244 let file = std::fs::File::create_new(&path).expect("create");
1245 let mut permissions = file.metadata().expect("metadata").permissions();
1246 permissions.set_readonly(true);
1247
1248 watcher.watch_recursively(&tmpdir);
1249 file.set_permissions(permissions).expect("set_permissions");
1250
1251 rx.wait_ordered_exact([
1252 expected(tmpdir.path()).access_open_any().optional(),
1253 expected(&path).modify_meta_any(),
1254 ]);
1255 assert_eq!(
1256 watcher.get_watch_handles(),
1257 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1258 );
1259 }
1260
1261 #[test]
1262 fn rename_file() {
1263 let tmpdir = testdir();
1264 let (mut watcher, rx) = watcher();
1265
1266 let path = tmpdir.path().join("entry");
1267 std::fs::File::create_new(&path).expect("create");
1268
1269 watcher.watch_recursively(&tmpdir);
1270 let new_path = tmpdir.path().join("renamed");
1271
1272 std::fs::rename(&path, &new_path).expect("rename");
1273
1274 rx.wait_ordered_exact([
1275 expected(tmpdir.path()).access_open_any().optional(),
1276 expected(&path).rename_from(),
1277 expected(&new_path).rename_to(),
1278 expected([path, new_path]).rename_both(),
1279 ])
1280 .ensure_trackers_len(1)
1281 .ensure_no_tail();
1282 assert_eq!(
1283 watcher.get_watch_handles(),
1284 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1285 );
1286 }
1287
1288 #[test]
1289 fn rename_self_file() {
1290 let tmpdir = testdir();
1291 let (mut watcher, rx) = watcher();
1292
1293 let path = tmpdir.path().join("entry");
1294 std::fs::File::create_new(&path).expect("create");
1295
1296 watcher.watch_nonrecursively(&path);
1297 let new_path = tmpdir.path().join("renamed");
1298
1299 std::fs::rename(&path, &new_path).expect("rename");
1300
1301 rx.wait_ordered_exact([expected(&path).rename_from()])
1302 .ensure_no_tail();
1303 assert_eq!(
1304 watcher.get_watch_handles(),
1305 HashSet::from([tmpdir.to_path_buf()])
1306 );
1307
1308 std::fs::rename(&new_path, &path).expect("rename2");
1309
1310 rx.wait_ordered_exact([expected(&path).rename_to()])
1311 .ensure_no_tail();
1312 assert_eq!(
1313 watcher.get_watch_handles(),
1314 HashSet::from([tmpdir.to_path_buf()])
1315 );
1316 }
1317
1318 #[test]
1319 fn rename_self_file_no_track() {
1320 let tmpdir = testdir();
1321 let (mut watcher, rx) = watcher();
1322
1323 let path = tmpdir.path().join("entry");
1324 std::fs::File::create_new(&path).expect("create");
1325
1326 watcher.watch(
1327 &path,
1328 WatchMode {
1329 recursive_mode: RecursiveMode::NonRecursive,
1330 target_mode: TargetMode::NoTrack,
1331 },
1332 );
1333
1334 let new_path = tmpdir.path().join("renamed");
1335
1336 std::fs::rename(&path, &new_path).expect("rename");
1337
1338 rx.wait_ordered_exact([expected(&path).rename_from()])
1339 .ensure_no_tail();
1340 assert_eq!(watcher.get_watch_handles(), HashSet::from([]));
1341
1342 let result = watcher.watcher.watch(
1343 &path,
1344 WatchMode {
1345 recursive_mode: RecursiveMode::NonRecursive,
1346 target_mode: TargetMode::NoTrack,
1347 },
1348 );
1349 assert!(matches!(
1350 result,
1351 Err(Error {
1352 paths: _,
1353 kind: ErrorKind::PathNotFound
1354 })
1355 ));
1356 }
1357
1358 #[test]
1359 fn delete_file() {
1360 let tmpdir = testdir();
1361 let (mut watcher, rx) = watcher();
1362 let file = tmpdir.path().join("file");
1363 std::fs::write(&file, "").expect("write");
1364
1365 watcher.watch_nonrecursively(&tmpdir);
1366
1367 std::fs::remove_file(&file).expect("remove");
1368
1369 rx.wait_ordered_exact([
1370 expected(tmpdir.path()).access_open_any().optional(),
1371 expected(&file).remove_file(),
1372 ]);
1373 assert_eq!(
1374 watcher.get_watch_handles(),
1375 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1376 );
1377 }
1378
1379 #[test]
1380 fn delete_self_file() {
1381 let tmpdir = testdir();
1382 let (mut watcher, rx) = watcher();
1383 let file = tmpdir.path().join("file");
1384 std::fs::write(&file, "").expect("write");
1385
1386 watcher.watch_nonrecursively(&file);
1387
1388 std::fs::remove_file(&file).expect("remove");
1389
1390 rx.wait_ordered_exact([expected(&file).remove_file()]);
1391 assert_eq!(
1392 watcher.get_watch_handles(),
1393 HashSet::from([tmpdir.to_path_buf()])
1394 );
1395
1396 std::fs::write(&file, "").expect("write");
1397
1398 rx.wait_ordered_exact([expected(&file).create_file()]);
1399 assert_eq!(
1400 watcher.get_watch_handles(),
1401 HashSet::from([tmpdir.to_path_buf()])
1402 );
1403 }
1404
1405 #[test]
1406 fn delete_self_file_no_track() {
1407 let tmpdir = testdir();
1408 let (mut watcher, rx) = watcher();
1409 let file = tmpdir.path().join("file");
1410 std::fs::write(&file, "").expect("write");
1411
1412 watcher.watch(
1413 &file,
1414 WatchMode {
1415 recursive_mode: RecursiveMode::NonRecursive,
1416 target_mode: TargetMode::NoTrack,
1417 },
1418 );
1419
1420 std::fs::remove_file(&file).expect("remove");
1421
1422 rx.wait_ordered_exact([
1423 expected(&file).modify_meta_any(),
1424 expected(&file).remove_file(),
1425 ]);
1426 assert_eq!(watcher.get_watch_handles(), HashSet::from([]));
1427
1428 std::fs::write(&file, "").expect("write");
1429
1430 rx.ensure_empty_with_wait();
1431 }
1432
1433 #[test]
1434 fn create_write_overwrite() {
1435 let tmpdir = testdir();
1436 let (mut watcher, rx) = watcher();
1437 let overwritten_file = tmpdir.path().join("overwritten_file");
1438 let overwriting_file = tmpdir.path().join("overwriting_file");
1439 std::fs::write(&overwritten_file, "123").expect("write1");
1440
1441 watcher.watch_nonrecursively(&tmpdir);
1442
1443 std::fs::File::create(&overwriting_file).expect("create");
1444 std::fs::write(&overwriting_file, "321").expect("write2");
1445 std::fs::rename(&overwriting_file, &overwritten_file).expect("rename");
1446
1447 rx.wait_ordered_exact([
1448 expected(tmpdir.path()).access_open_any().optional(),
1449 expected(&overwriting_file).create_file(),
1450 expected(&overwriting_file).access_open_any(),
1451 expected(&overwriting_file).access_close_write(),
1452 expected(&overwriting_file).access_open_any(),
1453 expected(&overwriting_file).modify_data_any().multiple(),
1454 expected(&overwriting_file).access_close_write().multiple(),
1455 expected(&overwriting_file).rename_from(),
1456 expected(&overwritten_file).rename_to(),
1457 expected([&overwriting_file, &overwritten_file]).rename_both(),
1458 ])
1459 .ensure_no_tail()
1460 .ensure_trackers_len(1);
1461 assert_eq!(
1462 watcher.get_watch_handles(),
1463 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1464 );
1465 }
1466
1467 #[test]
1468 fn create_self_write_overwrite() {
1469 let tmpdir = testdir();
1470 let (mut watcher, rx) = watcher();
1471 let overwritten_file = tmpdir.path().join("overwritten_file");
1472 let overwriting_file = tmpdir.path().join("overwriting_file");
1473 std::fs::write(&overwritten_file, "123").expect("write1");
1474
1475 watcher.watch_nonrecursively(&overwritten_file);
1476
1477 std::fs::File::create(&overwriting_file).expect("create");
1478 std::fs::write(&overwriting_file, "321").expect("write2");
1479 std::fs::rename(&overwriting_file, &overwritten_file).expect("rename");
1480
1481 rx.wait_ordered_exact([expected(&overwritten_file).rename_to()])
1482 .ensure_no_tail()
1483 .ensure_trackers_len(1);
1484 assert_eq!(
1485 watcher.get_watch_handles(),
1486 HashSet::from([tmpdir.to_path_buf()])
1487 );
1488 }
1489
1490 #[test]
1491 fn create_self_write_overwrite_no_track() {
1492 let tmpdir = testdir();
1493 let (mut watcher, rx) = watcher();
1494 let overwritten_file = tmpdir.path().join("overwritten_file");
1495 let overwriting_file = tmpdir.path().join("overwriting_file");
1496 std::fs::write(&overwritten_file, "123").expect("write1");
1497
1498 watcher.watch(
1499 &overwritten_file,
1500 WatchMode {
1501 recursive_mode: RecursiveMode::NonRecursive,
1502 target_mode: TargetMode::NoTrack,
1503 },
1504 );
1505
1506 std::fs::File::create(&overwriting_file).expect("create");
1507 std::fs::write(&overwriting_file, "321").expect("write2");
1508 std::fs::rename(&overwriting_file, &overwritten_file).expect("rename");
1509
1510 rx.wait_ordered_exact([
1511 expected(&overwritten_file).modify_meta_any(),
1512 expected(&overwritten_file).remove_file(),
1513 ])
1514 .ensure_no_tail()
1515 .ensure_trackers_len(0);
1516 assert_eq!(watcher.get_watch_handles(), HashSet::from([]));
1517 }
1518
1519 #[test]
1520 fn create_dir() {
1521 let tmpdir = testdir();
1522 let (mut watcher, rx) = watcher();
1523 watcher.watch_recursively(&tmpdir);
1524
1525 let path = tmpdir.path().join("entry");
1526 std::fs::create_dir(&path).expect("create");
1527
1528 rx.wait_ordered_exact([
1529 expected(tmpdir.path()).access_open_any().optional(),
1530 expected(&path).create_folder(),
1531 ]);
1532 assert_eq!(
1533 watcher.get_watch_handles(),
1534 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf(), path])
1535 );
1536 }
1537
1538 #[test]
1539 fn chmod_dir() {
1540 let tmpdir = testdir();
1541 let (mut watcher, rx) = watcher();
1542
1543 let path = tmpdir.path().join("entry");
1544 std::fs::create_dir(&path).expect("create_dir");
1545 let mut permissions = std::fs::metadata(&path).expect("metadata").permissions();
1546 permissions.set_readonly(true);
1547
1548 watcher.watch_recursively(&tmpdir);
1549 std::fs::set_permissions(&path, permissions).expect("set_permissions");
1550
1551 rx.wait_ordered_exact([
1552 expected(tmpdir.path()).access_open_any().optional(),
1553 expected(&path).access_open_any().optional(),
1554 expected(&path).modify_meta_any(),
1555 expected(&path).modify_meta_any(),
1556 ])
1557 .ensure_no_tail();
1558 assert_eq!(
1559 watcher.get_watch_handles(),
1560 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf(), path])
1561 );
1562 }
1563
1564 #[test]
1565 fn rename_dir() {
1566 let tmpdir = testdir();
1567 let (mut watcher, rx) = watcher();
1568
1569 let path = tmpdir.path().join("entry");
1570 let new_path = tmpdir.path().join("new_path");
1571 std::fs::create_dir(&path).expect("create_dir");
1572
1573 watcher.watch_recursively(&tmpdir);
1574
1575 std::fs::rename(&path, &new_path).expect("rename");
1576
1577 rx.wait_ordered_exact([
1578 expected(tmpdir.path()).access_open_any().optional(),
1579 expected(&path).access_open_any().optional(),
1580 expected(&path).rename_from(),
1581 expected(&new_path).rename_to(),
1582 expected([&path, &new_path]).rename_both(),
1583 ])
1584 .ensure_trackers_len(1);
1585 assert_eq!(
1586 watcher.get_watch_handles(),
1587 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf(), new_path])
1588 );
1589 }
1590
1591 #[test]
1592 fn delete_dir() {
1593 let tmpdir = testdir();
1594 let (mut watcher, rx) = watcher();
1595
1596 let path = tmpdir.path().join("entry");
1597 std::fs::create_dir(&path).expect("create_dir");
1598
1599 watcher.watch_recursively(&tmpdir);
1600 std::fs::remove_dir(&path).expect("remove");
1601
1602 rx.wait_ordered_exact([
1603 expected(tmpdir.path()).access_open_any().optional(),
1604 expected(&path).access_open_any().optional(),
1605 expected(&path).remove_folder(),
1606 ])
1607 .ensure_no_tail();
1608 assert_eq!(
1609 watcher.get_watch_handles(),
1610 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1611 );
1612 }
1613
1614 #[test]
1615 fn delete_self_dir() {
1616 let tmpdir = testdir();
1617 let (mut watcher, rx) = watcher();
1618
1619 let path = tmpdir.path().join("entry");
1620 std::fs::create_dir(&path).expect("create_dir");
1621
1622 watcher.watch_recursively(&path);
1623 std::fs::remove_dir(&path).expect("remove");
1624
1625 rx.wait_ordered_exact([
1626 expected(&path).access_open_any().optional(),
1627 expected(&path).remove_folder(),
1628 expected(&path).access_open_any().optional(),
1629 ])
1630 .ensure_no_tail();
1631 assert_eq!(
1632 watcher.get_watch_handles(),
1633 HashSet::from([tmpdir.to_path_buf()])
1634 );
1635
1636 std::fs::create_dir(&path).expect("create_dir2");
1637
1638 rx.wait_ordered_exact([
1639 expected(&path).access_open_any().optional(),
1640 expected(&path).create_folder(),
1641 expected(&path).access_open_any().optional(),
1642 ])
1643 .ensure_no_tail();
1644 assert_eq!(
1645 watcher.get_watch_handles(),
1646 HashSet::from([tmpdir.to_path_buf(), path.clone()])
1647 );
1648 }
1649
1650 #[test]
1651 fn delete_self_dir_no_track() {
1652 let tmpdir = testdir();
1653 let (mut watcher, rx) = watcher();
1654
1655 let path = tmpdir.path().join("entry");
1656 std::fs::create_dir(&path).expect("create_dir");
1657
1658 watcher
1659 .watcher
1660 .watch(
1661 &path,
1662 WatchMode {
1663 recursive_mode: RecursiveMode::Recursive,
1664 target_mode: TargetMode::NoTrack,
1665 },
1666 )
1667 .expect("watch");
1668 std::fs::remove_dir(&path).expect("remove");
1669
1670 rx.wait_ordered_exact([expected(&path).remove_folder()])
1671 .ensure_no_tail();
1672 assert_eq!(watcher.get_watch_handles(), HashSet::from([]));
1673
1674 std::fs::create_dir(&path).expect("create_dir2");
1675
1676 rx.ensure_empty_with_wait();
1677 }
1678
1679 #[test]
1680 fn rename_dir_twice() {
1681 let tmpdir = testdir();
1682 let (mut watcher, rx) = watcher();
1683
1684 let path = tmpdir.path().join("entry");
1685 let new_path = tmpdir.path().join("new_path");
1686 let new_path2 = tmpdir.path().join("new_path2");
1687 std::fs::create_dir(&path).expect("create_dir");
1688
1689 watcher.watch_recursively(&tmpdir);
1690 std::fs::rename(&path, &new_path).expect("rename");
1691 std::fs::rename(&new_path, &new_path2).expect("rename2");
1692
1693 rx.wait_ordered_exact([
1694 expected(tmpdir.path()).access_open_any().optional(),
1695 expected(&path).access_open_any().optional(),
1696 expected(&path).rename_from(),
1697 expected(&new_path).rename_to(),
1698 expected([&path, &new_path]).rename_both(),
1699 expected(&new_path).access_open_any().optional(),
1700 expected(&new_path).rename_from(),
1701 expected(&new_path2).rename_to(),
1702 expected([&new_path, &new_path2]).rename_both(),
1703 ])
1704 .ensure_trackers_len(2);
1705 assert_eq!(
1706 watcher.get_watch_handles(),
1707 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf(), new_path2])
1708 );
1709 }
1710
1711 #[test]
1712 fn move_out_of_watched_dir() {
1713 let tmpdir = testdir();
1714 let subdir = tmpdir.path().join("subdir");
1715 let (mut watcher, rx) = watcher();
1716
1717 let path = subdir.join("entry");
1718 std::fs::create_dir_all(&subdir).expect("create_dir_all");
1719 std::fs::File::create_new(&path).expect("create");
1720
1721 watcher.watch_recursively(&subdir);
1722 let new_path = tmpdir.path().join("entry");
1723
1724 std::fs::rename(&path, &new_path).expect("rename");
1725
1726 rx.wait_ordered_exact([
1727 expected(&subdir).access_open_any(),
1728 expected(&path).rename_from(),
1729 ])
1730 .ensure_trackers_len(1)
1731 .ensure_no_tail();
1732 assert_eq!(
1733 watcher.get_watch_handles(),
1734 HashSet::from([tmpdir.to_path_buf(), subdir])
1735 );
1736 }
1737
1738 #[test]
1739 fn create_write_write_rename_write_remove() {
1740 let tmpdir = testdir();
1741 let (mut watcher, rx) = watcher();
1742
1743 let file1 = tmpdir.path().join("entry");
1744 let file2 = tmpdir.path().join("entry2");
1745 std::fs::File::create_new(&file2).expect("create file2");
1746 let new_path = tmpdir.path().join("renamed");
1747
1748 watcher.watch_recursively(&tmpdir);
1749 std::fs::write(&file1, "123").expect("write 1");
1750 std::fs::write(&file2, "321").expect("write 2");
1751 std::fs::rename(&file1, &new_path).expect("rename");
1752 std::fs::write(&new_path, b"1").expect("write 3");
1753 std::fs::remove_file(&new_path).expect("remove");
1754
1755 rx.wait_ordered_exact([
1756 expected(tmpdir.path()).access_open_any().optional(),
1757 expected(&file1).create_file(),
1758 expected(&file1).access_open_any(),
1759 expected(&file1).modify_data_any().multiple(),
1760 expected(&file1).access_close_write(),
1761 expected(&file2).access_open_any(),
1762 expected(&file2).modify_data_any().multiple(),
1763 expected(&file2).access_close_write(),
1764 expected(&file1).access_open_any().optional(),
1765 expected(&file1).rename_from(),
1766 expected(&new_path).rename_to(),
1767 expected([&file1, &new_path]).rename_both(),
1768 expected(&new_path).access_open_any(),
1769 expected(&new_path).modify_data_any().multiple(),
1770 expected(&new_path).access_close_write(),
1771 expected(&new_path).remove_file(),
1772 ]);
1773 assert_eq!(
1774 watcher.get_watch_handles(),
1775 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1776 );
1777 }
1778
1779 #[test]
1780 fn rename_twice() {
1781 let tmpdir = testdir();
1782 let (mut watcher, rx) = watcher();
1783
1784 let path = tmpdir.path().join("entry");
1785 std::fs::File::create_new(&path).expect("create");
1786
1787 watcher.watch_recursively(&tmpdir);
1788 let new_path1 = tmpdir.path().join("renamed1");
1789 let new_path2 = tmpdir.path().join("renamed2");
1790
1791 std::fs::rename(&path, &new_path1).expect("rename1");
1792 std::fs::rename(&new_path1, &new_path2).expect("rename2");
1793
1794 rx.wait_ordered_exact([
1795 expected(tmpdir.path()).access_open_any().optional(),
1796 expected(&path).access_open_any().optional(),
1797 expected(&path).rename_from(),
1798 expected(&new_path1).rename_to(),
1799 expected([&path, &new_path1]).rename_both(),
1800 expected(&new_path1).access_open_any().optional(),
1801 expected(&new_path1).rename_from(),
1802 expected(&new_path2).rename_to(),
1803 expected([&new_path1, &new_path2]).rename_both(),
1804 ])
1805 .ensure_no_tail()
1806 .ensure_trackers_len(2);
1807 assert_eq!(
1808 watcher.get_watch_handles(),
1809 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1810 );
1811 }
1812
1813 #[test]
1814 fn set_file_mtime() {
1815 let tmpdir = testdir();
1816 let (mut watcher, rx) = watcher();
1817
1818 let path = tmpdir.path().join("entry");
1819 let file = std::fs::File::create_new(&path).expect("create");
1820
1821 watcher.watch_recursively(&tmpdir);
1822
1823 file.set_modified(
1824 std::time::SystemTime::now()
1825 .checked_sub(Duration::from_secs(60 * 60))
1826 .expect("time"),
1827 )
1828 .expect("set_time");
1829
1830 rx.wait_ordered_exact([
1831 expected(tmpdir.path()).access_open_any().optional(),
1832 expected(&path).modify_data_any(),
1833 ])
1834 .ensure_no_tail();
1835 assert_eq!(
1836 watcher.get_watch_handles(),
1837 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1838 );
1839 }
1840
1841 #[test]
1842 fn write_file_non_recursive_watch() {
1843 let tmpdir = testdir();
1844 let (mut watcher, rx) = watcher();
1845
1846 let path = tmpdir.path().join("entry");
1847 std::fs::File::create_new(&path).expect("create");
1848
1849 watcher.watch_nonrecursively(&path);
1850
1851 std::fs::write(&path, b"123").expect("write");
1852
1853 rx.wait_ordered_exact([
1854 expected(&path).access_open_any(),
1855 expected(&path).modify_data_any().multiple(),
1856 expected(&path).access_close_write(),
1857 ])
1858 .ensure_no_tail();
1859 assert_eq!(
1860 watcher.get_watch_handles(),
1861 HashSet::from([tmpdir.to_path_buf()])
1862 );
1863 }
1864
1865 #[test]
1866 fn watch_recursively_then_unwatch_child_stops_events_from_child() {
1867 let tmpdir = testdir();
1868 let (mut watcher, rx) = watcher();
1869
1870 let subdir = tmpdir.path().join("subdir");
1871 let file = subdir.join("file");
1872 std::fs::create_dir(&subdir).expect("create");
1873
1874 watcher.watch_recursively(&tmpdir);
1875
1876 std::fs::File::create(&file).expect("create");
1877
1878 rx.wait_ordered_exact([
1879 expected(tmpdir.path()).access_open_any().optional(),
1880 expected(&subdir).access_open_any().optional(),
1881 expected(&file).create_file(),
1882 expected(&file).access_open_any(),
1883 expected(&file).access_close_write(),
1884 ])
1885 .ensure_no_tail();
1886 assert_eq!(
1887 watcher.get_watch_handles(),
1888 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf(), subdir])
1889 );
1890
1891 }
1904
1905 #[test]
1906 fn write_to_a_hardlink_pointed_to_the_watched_file_triggers_an_event() {
1907 let tmpdir = testdir();
1908 let (mut watcher, rx) = watcher();
1909
1910 let subdir = tmpdir.path().join("subdir");
1911 let subdir2 = tmpdir.path().join("subdir2");
1912 let file = subdir.join("file");
1913 let hardlink = subdir2.join("hardlink");
1914
1915 std::fs::create_dir(&subdir).expect("create");
1916 std::fs::create_dir(&subdir2).expect("create2");
1917 std::fs::write(&file, "").expect("file");
1918 std::fs::hard_link(&file, &hardlink).expect("hardlink");
1919
1920 watcher.watch_nonrecursively(&file);
1921
1922 std::fs::write(&hardlink, "123123").expect("write to the hard link");
1923
1924 rx.wait_ordered_exact([
1925 expected(&file).access_open_any(),
1926 expected(&file).modify_data_any().multiple(),
1927 expected(&file).access_close_write(),
1928 ]);
1929 assert_eq!(watcher.get_watch_handles(), HashSet::from([subdir, file]));
1930 }
1931
1932 #[test]
1933 fn write_to_a_hardlink_pointed_to_the_watched_file_triggers_an_event_even_if_the_parent_is_watched()
1934 {
1935 let tmpdir = testdir();
1936 let (mut watcher, rx) = watcher();
1937
1938 let subdir1 = tmpdir.path().join("subdir1");
1939 let subdir2 = subdir1.join("subdir2");
1940 let file = subdir2.join("file");
1941 let hardlink = tmpdir.path().join("hardlink");
1942
1943 std::fs::create_dir_all(&subdir2).expect("create");
1944 std::fs::write(&file, "").expect("file");
1945 std::fs::hard_link(&file, &hardlink).expect("hardlink");
1946
1947 watcher.watch_nonrecursively(&subdir2);
1948 watcher.watch_nonrecursively(&file);
1949
1950 std::fs::write(&hardlink, "123123").expect("write to the hard link");
1951
1952 rx.wait_ordered_exact([
1953 expected(&subdir2).access_open_any().optional(),
1954 expected(&file).access_open_any(),
1955 expected(&file).modify_data_any().multiple(),
1956 expected(&file).access_close_write(),
1957 ]);
1958 assert_eq!(
1959 watcher.get_watch_handles(),
1960 HashSet::from([subdir1, subdir2, file])
1961 );
1962 }
1963
1964 #[test]
1965 fn write_to_a_hardlink_pointed_to_the_file_in_the_watched_dir_doesnt_trigger_an_event() {
1966 let tmpdir = testdir();
1967 let (mut watcher, rx) = watcher();
1968
1969 let subdir = tmpdir.path().join("subdir");
1970 let subdir2 = tmpdir.path().join("subdir2");
1971 let file = subdir.join("file");
1972 let hardlink = subdir2.join("hardlink");
1973
1974 std::fs::create_dir(&subdir).expect("create");
1975 std::fs::create_dir(&subdir2).expect("create");
1976 std::fs::write(&file, "").expect("file");
1977 std::fs::hard_link(&file, &hardlink).expect("hardlink");
1978
1979 watcher.watch_nonrecursively(&subdir);
1980
1981 std::fs::write(&hardlink, "123123").expect("write to the hard link");
1982
1983 rx.wait_ordered_exact([expected(&subdir).access_open_any().optional()])
1984 .ensure_no_tail();
1985 assert_eq!(
1986 watcher.get_watch_handles(),
1987 HashSet::from([tmpdir.to_path_buf(), subdir])
1988 );
1989 }
1990
1991 #[test]
1992 #[ignore = "see https://github.com/notify-rs/notify/issues/727"]
1993 fn recursive_creation() {
1994 let tmpdir = testdir();
1995 let nested1 = tmpdir.path().join("1");
1996 let nested2 = tmpdir.path().join("1/2");
1997 let nested3 = tmpdir.path().join("1/2/3");
1998 let nested4 = tmpdir.path().join("1/2/3/4");
1999 let nested5 = tmpdir.path().join("1/2/3/4/5");
2000 let nested6 = tmpdir.path().join("1/2/3/4/5/6");
2001 let nested7 = tmpdir.path().join("1/2/3/4/5/6/7");
2002 let nested8 = tmpdir.path().join("1/2/3/4/5/6/7/8");
2003 let nested9 = tmpdir.path().join("1/2/3/4/5/6/7/8/9");
2004
2005 let (mut watcher, rx) = watcher();
2006
2007 watcher.watch_recursively(&tmpdir);
2008
2009 std::fs::create_dir_all(&nested9).expect("create_dir_all");
2010 rx.wait_ordered([
2011 expected(&nested1).create_folder(),
2012 expected(&nested2).create_folder(),
2013 expected(&nested3).create_folder(),
2014 expected(&nested4).create_folder(),
2015 expected(&nested5).create_folder(),
2016 expected(&nested6).create_folder(),
2017 expected(&nested7).create_folder(),
2018 expected(&nested8).create_folder(),
2019 expected(&nested9).create_folder(),
2020 ]);
2021 assert_eq!(
2022 watcher.get_watch_handles(),
2023 HashSet::from([
2024 tmpdir.to_path_buf(),
2025 nested1,
2026 nested2,
2027 nested3,
2028 nested4,
2029 nested5,
2030 nested6,
2031 nested7,
2032 nested8,
2033 nested9
2034 ])
2035 );
2036 }
2037
2038 #[test]
2039 fn upgrade_to_recursive() {
2040 let tmpdir = testdir();
2041 let (mut watcher, rx) = watcher();
2042
2043 let path = tmpdir.path().join("upgrade");
2044 let deep = tmpdir.path().join("upgrade/deep");
2045 let file = tmpdir.path().join("upgrade/deep/file");
2046 std::fs::create_dir_all(&deep).expect("create_dir");
2047
2048 watcher.watch_nonrecursively(&path);
2049 std::fs::File::create_new(&file).expect("create");
2050 std::fs::remove_file(&file).expect("delete");
2051
2052 rx.ensure_empty_with_wait();
2053
2054 watcher.watch_recursively(&path);
2055 std::fs::File::create_new(&file).expect("create");
2056
2057 rx.wait_ordered([
2058 expected(&file).create_file(),
2059 expected(&file).access_open_any(),
2060 expected(&file).access_close_write(),
2061 ])
2062 .ensure_no_tail();
2063 assert_eq!(
2064 watcher.get_watch_handles(),
2065 HashSet::from([tmpdir.to_path_buf(), path, deep])
2066 );
2067 }
2068}