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