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(), false, false)?;
603 }
604 if need_upgrade_to_recursive && metadata(&path).map_err(Error::io)?.is_dir() {
605 self.add_maybe_recursive_watch(path.clone(), true, false, true)?;
606 }
607 self.watches
608 .get_mut(&path)
609 .unwrap()
610 .upgrade_with(watch_mode);
611 return Ok(());
612 }
613
614 if watch_mode.target_mode == TargetMode::TrackPath
615 && let Some(parent) = path.parent()
616 {
617 self.add_single_watch(parent.to_path_buf(), false, false)?;
618 }
619
620 let meta = match metadata(&path).map_err(Error::io_watch) {
621 Ok(metadata) => metadata,
622 Err(err) => {
623 if watch_mode.target_mode == TargetMode::TrackPath
624 && matches!(err.kind, ErrorKind::PathNotFound)
625 {
626 self.watches.insert(path, watch_mode);
627 return Ok(());
628 }
629 return Err(err);
630 }
631 };
632
633 self.add_maybe_recursive_watch(
634 path.clone(),
635 watch_mode.recursive_mode.is_recursive() && meta.is_dir(),
638 meta.is_file_without_hardlinks(),
639 watch_mode.target_mode != TargetMode::TrackPath, )?;
641
642 self.watches.insert(path, watch_mode);
643
644 Ok(())
645 }
646
647 #[tracing::instrument(level = "trace", skip(self))]
648 fn add_maybe_recursive_watch(
649 &mut self,
650 path: PathBuf,
651 is_recursive: bool,
652 is_file_without_hardlinks: bool,
653 mut watch_self: bool,
654 ) -> Result<()> {
655 if is_recursive {
656 for entry in WalkDir::new(&path)
657 .follow_links(self.follow_links)
658 .into_iter()
659 .filter_map(filter_dir)
660 {
661 self.add_single_watch(entry.into_path(), false, watch_self)?;
662 watch_self = false;
663 }
664 } else {
665 self.add_single_watch(path, is_file_without_hardlinks, watch_self)?;
666 }
667 Ok(())
668 }
669
670 #[tracing::instrument(level = "trace", skip(self))]
671 fn add_single_watch(
672 &mut self,
673 path: PathBuf,
674 is_file_without_hardlinks: bool,
675 watch_self: bool,
676 ) -> Result<()> {
677 if let Some((_, &(old_watch_self, _))) = self.watch_handles.get_by_right(&path)
678 && (old_watch_self || !watch_self)
680 {
681 tracing::trace!(
682 "watch handle already exists and no need to upgrade: {}",
683 path.display()
684 );
685 return Ok(());
686 }
687
688 if is_file_without_hardlinks
689 && let Some(parent) = path.parent()
690 && self.watch_handles.get_by_right(parent).is_some()
691 {
692 tracing::trace!(
693 "parent dir watch handle already exists and is a file without hardlinks: {}",
694 path.display()
695 );
696 return Ok(());
697 }
698
699 let mut watchmask = WatchMask::ATTRIB
700 | WatchMask::CREATE
701 | WatchMask::OPEN
702 | WatchMask::DELETE
703 | WatchMask::CLOSE_WRITE
704 | WatchMask::MODIFY
705 | WatchMask::MOVED_FROM
706 | WatchMask::MOVED_TO;
707 if watch_self {
708 watchmask.insert(WatchMask::DELETE_SELF);
709 watchmask.insert(WatchMask::MOVE_SELF);
710 }
711
712 if let Some(ref mut inotify) = self.inotify {
713 tracing::trace!("adding inotify watch: {}", path.display());
714
715 match inotify.watches().add(&path, watchmask) {
716 Err(e) => {
717 Err(if e.raw_os_error() == Some(libc::ENOSPC) {
718 Error::new(ErrorKind::MaxFilesWatch)
720 } else if e.kind() == std::io::ErrorKind::NotFound {
721 Error::new(ErrorKind::PathNotFound)
722 } else {
723 Error::io(e)
724 }
725 .add_path(path))
726 }
727 Ok(w) => {
728 watchmask.remove(WatchMask::MASK_ADD);
729 let is_dir = metadata(&path).map_err(Error::io)?.is_dir();
730 self.watch_handles.insert(w, path, (watch_self, is_dir));
731 Ok(())
732 }
733 }
734 } else {
735 Ok(())
736 }
737 }
738
739 #[tracing::instrument(level = "trace", skip(self))]
740 fn remove_watch(&mut self, path: PathBuf) -> Result<()> {
741 match self.watches.remove(&path) {
742 None => return Err(Error::watch_not_found().add_path(path)),
743 Some(watch_mode) => {
744 self.remove_maybe_recursive_watch(
745 &path,
746 watch_mode.recursive_mode.is_recursive(),
747 false,
748 )?;
749 }
750 }
751 Ok(())
752 }
753
754 #[tracing::instrument(level = "trace", skip(self))]
755 fn remove_maybe_recursive_watch(
756 &mut self,
757 path: &Path,
758 is_recursive: bool,
759 without_os_call: bool,
760 ) -> Result<()> {
761 let Some(ref mut inotify) = self.inotify else {
762 return Ok(());
763 };
764 let mut inotify_watches = inotify.watches();
765
766 if let Some((handle, _)) = self.watch_handles.remove_by_right(path) {
767 tracing::trace!("removing inotify watch: {}", path.display());
768
769 if !without_os_call {
770 inotify_watches
771 .remove(handle)
772 .map_err(|e| Error::io(e).add_path(path.to_path_buf()))?;
773 }
774 }
775
776 if is_recursive {
777 let mut remove_list = Vec::new();
778 for (w, p, _) in &self.watch_handles {
779 if p.starts_with(path) {
780 if !without_os_call {
781 inotify_watches
782 .remove(w.clone())
783 .map_err(|e| Error::io(e).add_path(p.into()))?;
784 }
785 remove_list.push(w.clone());
786 }
787 }
788 for w in remove_list {
789 self.watch_handles.remove_by_left(&w);
790 }
791 }
792 Ok(())
793 }
794
795 fn remove_all_watches(&mut self) -> Result<()> {
796 if let Some(ref mut inotify) = self.inotify {
797 let mut inotify_watches = inotify.watches();
798 for (w, p, _) in &self.watch_handles {
799 inotify_watches
800 .remove(w.clone())
801 .map_err(|e| Error::io(e).add_path(p.into()))?;
802 }
803 self.watch_handles.clear();
804 self.watches.clear();
805 }
806 Ok(())
807 }
808}
809
810fn filter_dir(e: walkdir::Result<walkdir::DirEntry>) -> Option<walkdir::DirEntry> {
812 if let Ok(e) = e
813 && e.file_type().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::{
938 RecursiveMode, TargetMode,
939 config::WatchMode,
940 event::{EventKind, ModifyKind},
941 test::*,
942 };
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 fn assert_track_path_continues_after_recreating_file_in_nested_directory(
1491 upgrade_from_no_track: bool,
1492 ) {
1493 let tmpdir = testdir();
1494 let (mut watcher, mut rx) = watcher();
1495 let nested_dir = tmpdir.path().join("nested");
1496 let watched_file = nested_dir.join("watched");
1497 let moved_file = tmpdir.path().join("moved");
1498 std::fs::create_dir(&nested_dir).expect("create nested dir");
1499 std::fs::write(&watched_file, "initial").expect("write watched file");
1500
1501 watcher.watch_nonrecursively(&tmpdir);
1502 if upgrade_from_no_track {
1503 watcher.watch(
1504 &watched_file,
1505 WatchMode {
1506 recursive_mode: RecursiveMode::NonRecursive,
1507 target_mode: TargetMode::NoTrack,
1508 },
1509 );
1510 }
1511 watcher.watch_nonrecursively(&watched_file);
1512 let mut expected_handles = HashSet::from([
1513 tmpdir.parent_path_buf(),
1514 tmpdir.to_path_buf(),
1515 nested_dir.clone(),
1516 ]);
1517 if upgrade_from_no_track {
1518 expected_handles.insert(watched_file.clone());
1519 }
1520 assert_eq!(watcher.get_watch_handles(), expected_handles);
1521
1522 std::fs::rename(&watched_file, &moved_file).expect("move watched file");
1523 std::fs::copy(&moved_file, &watched_file).expect("recreate watched file");
1524 std::fs::remove_file(&moved_file).expect("remove moved file");
1525
1526 for _ in rx.iter() {}
1528
1529 std::fs::write(&watched_file, "updated").expect("update watched file");
1530 let received_change = rx.iter().any(|event| {
1531 event.paths.iter().any(|path| path == &watched_file)
1532 && matches!(
1533 event.kind,
1534 EventKind::Create(_) | EventKind::Modify(ModifyKind::Data(_))
1535 )
1536 });
1537
1538 assert!(
1539 received_change,
1540 "expected a change event after recreating the watched file"
1541 );
1542 assert_eq!(
1543 watcher.get_watch_handles(),
1544 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf(), nested_dir,])
1545 );
1546 }
1547
1548 #[test]
1549 fn track_path_continues_after_recreating_file_in_nested_directory() {
1550 assert_track_path_continues_after_recreating_file_in_nested_directory(false);
1551 }
1552
1553 #[test]
1554 fn track_path_upgrade_continues_after_recreating_file_in_nested_directory() {
1555 assert_track_path_continues_after_recreating_file_in_nested_directory(true);
1556 }
1557
1558 #[test]
1559 fn create_self_write_overwrite_no_track() {
1560 let tmpdir = testdir();
1561 let (mut watcher, rx) = watcher();
1562 let overwritten_file = tmpdir.path().join("overwritten_file");
1563 let overwriting_file = tmpdir.path().join("overwriting_file");
1564 std::fs::write(&overwritten_file, "123").expect("write1");
1565
1566 watcher.watch(
1567 &overwritten_file,
1568 WatchMode {
1569 recursive_mode: RecursiveMode::NonRecursive,
1570 target_mode: TargetMode::NoTrack,
1571 },
1572 );
1573
1574 std::fs::File::create(&overwriting_file).expect("create");
1575 std::fs::write(&overwriting_file, "321").expect("write2");
1576 std::fs::rename(&overwriting_file, &overwritten_file).expect("rename");
1577
1578 rx.wait_ordered_exact([
1579 expected(&overwritten_file).modify_meta_any(),
1580 expected(&overwritten_file).remove_file(),
1581 ])
1582 .ensure_no_tail()
1583 .ensure_trackers_len(0);
1584 assert_eq!(watcher.get_watch_handles(), HashSet::from([]));
1585 }
1586
1587 #[test]
1588 fn create_dir() {
1589 let tmpdir = testdir();
1590 let (mut watcher, rx) = watcher();
1591 watcher.watch_recursively(&tmpdir);
1592
1593 let path = tmpdir.path().join("entry");
1594 std::fs::create_dir(&path).expect("create");
1595
1596 rx.wait_ordered_exact([
1597 expected(tmpdir.path()).access_open_any().optional(),
1598 expected(&path).create_folder(),
1599 ]);
1600 assert_eq!(
1601 watcher.get_watch_handles(),
1602 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf(), path])
1603 );
1604 }
1605
1606 #[test]
1607 fn chmod_dir() {
1608 let tmpdir = testdir();
1609 let (mut watcher, rx) = watcher();
1610
1611 let path = tmpdir.path().join("entry");
1612 std::fs::create_dir(&path).expect("create_dir");
1613 let mut permissions = std::fs::metadata(&path).expect("metadata").permissions();
1614 permissions.set_readonly(true);
1615
1616 watcher.watch_recursively(&tmpdir);
1617 std::fs::set_permissions(&path, permissions).expect("set_permissions");
1618
1619 rx.wait_ordered_exact([
1620 expected(tmpdir.path()).access_open_any().optional(),
1621 expected(&path).access_open_any().optional(),
1622 expected(&path).modify_meta_any(),
1623 expected(&path).modify_meta_any(),
1624 ])
1625 .ensure_no_tail();
1626 assert_eq!(
1627 watcher.get_watch_handles(),
1628 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf(), path])
1629 );
1630 }
1631
1632 #[test]
1633 fn rename_dir() {
1634 let tmpdir = testdir();
1635 let (mut watcher, rx) = watcher();
1636
1637 let path = tmpdir.path().join("entry");
1638 let new_path = tmpdir.path().join("new_path");
1639 std::fs::create_dir(&path).expect("create_dir");
1640
1641 watcher.watch_recursively(&tmpdir);
1642
1643 std::fs::rename(&path, &new_path).expect("rename");
1644
1645 rx.wait_ordered_exact([
1646 expected(tmpdir.path()).access_open_any().optional(),
1647 expected(&path).access_open_any().optional(),
1648 expected(&path).rename_from(),
1649 expected(&new_path).rename_to(),
1650 expected([&path, &new_path]).rename_both(),
1651 ])
1652 .ensure_trackers_len(1);
1653 assert_eq!(
1654 watcher.get_watch_handles(),
1655 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf(), new_path])
1656 );
1657 }
1658
1659 #[test]
1660 fn delete_dir() {
1661 let tmpdir = testdir();
1662 let (mut watcher, rx) = watcher();
1663
1664 let path = tmpdir.path().join("entry");
1665 std::fs::create_dir(&path).expect("create_dir");
1666
1667 watcher.watch_recursively(&tmpdir);
1668 std::fs::remove_dir(&path).expect("remove");
1669
1670 rx.wait_ordered_exact([
1671 expected(tmpdir.path()).access_open_any().optional(),
1672 expected(&path).access_open_any().optional(),
1673 expected(&path).remove_folder(),
1674 ])
1675 .ensure_no_tail();
1676 assert_eq!(
1677 watcher.get_watch_handles(),
1678 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1679 );
1680 }
1681
1682 #[test]
1683 fn delete_self_dir() {
1684 let tmpdir = testdir();
1685 let (mut watcher, rx) = watcher();
1686
1687 let path = tmpdir.path().join("entry");
1688 std::fs::create_dir(&path).expect("create_dir");
1689
1690 watcher.watch_recursively(&path);
1691 std::fs::remove_dir(&path).expect("remove");
1692
1693 rx.wait_ordered_exact([
1694 expected(&path).access_open_any().optional(),
1695 expected(&path).remove_folder(),
1696 expected(&path).access_open_any().optional(),
1697 ])
1698 .ensure_no_tail();
1699 assert_eq!(
1700 watcher.get_watch_handles(),
1701 HashSet::from([tmpdir.to_path_buf()])
1702 );
1703
1704 std::fs::create_dir(&path).expect("create_dir2");
1705
1706 rx.wait_ordered_exact([
1707 expected(&path).access_open_any().optional(),
1708 expected(&path).create_folder(),
1709 expected(&path).access_open_any().optional(),
1710 ])
1711 .ensure_no_tail();
1712 assert_eq!(
1713 watcher.get_watch_handles(),
1714 HashSet::from([tmpdir.to_path_buf(), path.clone()])
1715 );
1716 }
1717
1718 #[test]
1719 fn delete_self_dir_no_track() {
1720 let tmpdir = testdir();
1721 let (mut watcher, rx) = watcher();
1722
1723 let path = tmpdir.path().join("entry");
1724 std::fs::create_dir(&path).expect("create_dir");
1725
1726 watcher
1727 .watcher
1728 .watch(
1729 &path,
1730 WatchMode {
1731 recursive_mode: RecursiveMode::Recursive,
1732 target_mode: TargetMode::NoTrack,
1733 },
1734 )
1735 .expect("watch");
1736 std::fs::remove_dir(&path).expect("remove");
1737
1738 rx.wait_ordered_exact([expected(&path).remove_folder()])
1739 .ensure_no_tail();
1740 assert_eq!(watcher.get_watch_handles(), HashSet::from([]));
1741
1742 std::fs::create_dir(&path).expect("create_dir2");
1743
1744 rx.ensure_empty_with_wait();
1745 }
1746
1747 #[test]
1748 fn rename_dir_twice() {
1749 let tmpdir = testdir();
1750 let (mut watcher, rx) = watcher();
1751
1752 let path = tmpdir.path().join("entry");
1753 let new_path = tmpdir.path().join("new_path");
1754 let new_path2 = tmpdir.path().join("new_path2");
1755 std::fs::create_dir(&path).expect("create_dir");
1756
1757 watcher.watch_recursively(&tmpdir);
1758 std::fs::rename(&path, &new_path).expect("rename");
1759 std::fs::rename(&new_path, &new_path2).expect("rename2");
1760
1761 rx.wait_ordered_exact([
1762 expected(tmpdir.path()).access_open_any().optional(),
1763 expected(&path).access_open_any().optional(),
1764 expected(&path).rename_from(),
1765 expected(&new_path).rename_to(),
1766 expected([&path, &new_path]).rename_both(),
1767 expected(&new_path).access_open_any().optional(),
1768 expected(&new_path).rename_from(),
1769 expected(&new_path2).rename_to(),
1770 expected([&new_path, &new_path2]).rename_both(),
1771 ])
1772 .ensure_trackers_len(2);
1773 assert_eq!(
1774 watcher.get_watch_handles(),
1775 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf(), new_path2])
1776 );
1777 }
1778
1779 #[test]
1780 fn move_out_of_watched_dir() {
1781 let tmpdir = testdir();
1782 let subdir = tmpdir.path().join("subdir");
1783 let (mut watcher, rx) = watcher();
1784
1785 let path = subdir.join("entry");
1786 std::fs::create_dir_all(&subdir).expect("create_dir_all");
1787 std::fs::File::create_new(&path).expect("create");
1788
1789 watcher.watch_recursively(&subdir);
1790 let new_path = tmpdir.path().join("entry");
1791
1792 std::fs::rename(&path, &new_path).expect("rename");
1793
1794 rx.wait_ordered_exact([
1795 expected(&subdir).access_open_any(),
1796 expected(&path).rename_from(),
1797 ])
1798 .ensure_trackers_len(1)
1799 .ensure_no_tail();
1800 assert_eq!(
1801 watcher.get_watch_handles(),
1802 HashSet::from([tmpdir.to_path_buf(), subdir])
1803 );
1804 }
1805
1806 #[test]
1807 fn create_write_write_rename_write_remove() {
1808 let tmpdir = testdir();
1809 let (mut watcher, rx) = watcher();
1810
1811 let file1 = tmpdir.path().join("entry");
1812 let file2 = tmpdir.path().join("entry2");
1813 std::fs::File::create_new(&file2).expect("create file2");
1814 let new_path = tmpdir.path().join("renamed");
1815
1816 watcher.watch_recursively(&tmpdir);
1817 std::fs::write(&file1, "123").expect("write 1");
1818 std::fs::write(&file2, "321").expect("write 2");
1819 std::fs::rename(&file1, &new_path).expect("rename");
1820 std::fs::write(&new_path, b"1").expect("write 3");
1821 std::fs::remove_file(&new_path).expect("remove");
1822
1823 rx.wait_ordered_exact([
1824 expected(tmpdir.path()).access_open_any().optional(),
1825 expected(&file1).create_file(),
1826 expected(&file1).access_open_any(),
1827 expected(&file1).modify_data_any().multiple(),
1828 expected(&file1).access_close_write(),
1829 expected(&file2).access_open_any(),
1830 expected(&file2).modify_data_any().multiple(),
1831 expected(&file2).access_close_write(),
1832 expected(&file1).access_open_any().optional(),
1833 expected(&file1).rename_from(),
1834 expected(&new_path).rename_to(),
1835 expected([&file1, &new_path]).rename_both(),
1836 expected(&new_path).access_open_any(),
1837 expected(&new_path).modify_data_any().multiple(),
1838 expected(&new_path).access_close_write(),
1839 expected(&new_path).remove_file(),
1840 ]);
1841 assert_eq!(
1842 watcher.get_watch_handles(),
1843 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1844 );
1845 }
1846
1847 #[test]
1848 fn rename_twice() {
1849 let tmpdir = testdir();
1850 let (mut watcher, rx) = watcher();
1851
1852 let path = tmpdir.path().join("entry");
1853 std::fs::File::create_new(&path).expect("create");
1854
1855 watcher.watch_recursively(&tmpdir);
1856 let new_path1 = tmpdir.path().join("renamed1");
1857 let new_path2 = tmpdir.path().join("renamed2");
1858
1859 std::fs::rename(&path, &new_path1).expect("rename1");
1860 std::fs::rename(&new_path1, &new_path2).expect("rename2");
1861
1862 rx.wait_ordered_exact([
1863 expected(tmpdir.path()).access_open_any().optional(),
1864 expected(&path).access_open_any().optional(),
1865 expected(&path).rename_from(),
1866 expected(&new_path1).rename_to(),
1867 expected([&path, &new_path1]).rename_both(),
1868 expected(&new_path1).access_open_any().optional(),
1869 expected(&new_path1).rename_from(),
1870 expected(&new_path2).rename_to(),
1871 expected([&new_path1, &new_path2]).rename_both(),
1872 ])
1873 .ensure_no_tail()
1874 .ensure_trackers_len(2);
1875 assert_eq!(
1876 watcher.get_watch_handles(),
1877 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1878 );
1879 }
1880
1881 #[test]
1882 fn set_file_mtime() {
1883 let tmpdir = testdir();
1884 let (mut watcher, rx) = watcher();
1885
1886 let path = tmpdir.path().join("entry");
1887 let file = std::fs::File::create_new(&path).expect("create");
1888
1889 watcher.watch_recursively(&tmpdir);
1890
1891 file.set_modified(
1892 std::time::SystemTime::now()
1893 .checked_sub(Duration::from_secs(60 * 60))
1894 .expect("time"),
1895 )
1896 .expect("set_time");
1897
1898 rx.wait_ordered_exact([
1899 expected(tmpdir.path()).access_open_any().optional(),
1900 expected(&path).modify_data_any(),
1901 ])
1902 .ensure_no_tail();
1903 assert_eq!(
1904 watcher.get_watch_handles(),
1905 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf()])
1906 );
1907 }
1908
1909 #[test]
1910 fn write_file_non_recursive_watch() {
1911 let tmpdir = testdir();
1912 let (mut watcher, rx) = watcher();
1913
1914 let path = tmpdir.path().join("entry");
1915 std::fs::File::create_new(&path).expect("create");
1916
1917 watcher.watch_nonrecursively(&path);
1918
1919 std::fs::write(&path, b"123").expect("write");
1920
1921 rx.wait_ordered_exact([
1922 expected(&path).access_open_any(),
1923 expected(&path).modify_data_any().multiple(),
1924 expected(&path).access_close_write(),
1925 ])
1926 .ensure_no_tail();
1927 assert_eq!(
1928 watcher.get_watch_handles(),
1929 HashSet::from([tmpdir.to_path_buf()])
1930 );
1931 }
1932
1933 #[test]
1934 fn watch_recursively_then_unwatch_child_stops_events_from_child() {
1935 let tmpdir = testdir();
1936 let (mut watcher, rx) = watcher();
1937
1938 let subdir = tmpdir.path().join("subdir");
1939 let file = subdir.join("file");
1940 std::fs::create_dir(&subdir).expect("create");
1941
1942 watcher.watch_recursively(&tmpdir);
1943
1944 std::fs::File::create(&file).expect("create");
1945
1946 rx.wait_ordered_exact([
1947 expected(tmpdir.path()).access_open_any().optional(),
1948 expected(&subdir).access_open_any().optional(),
1949 expected(&file).create_file(),
1950 expected(&file).access_open_any(),
1951 expected(&file).access_close_write(),
1952 ])
1953 .ensure_no_tail();
1954 assert_eq!(
1955 watcher.get_watch_handles(),
1956 HashSet::from([tmpdir.parent_path_buf(), tmpdir.to_path_buf(), subdir])
1957 );
1958
1959 }
1972
1973 #[test]
1974 fn write_to_a_hardlink_pointed_to_the_watched_file_triggers_an_event() {
1975 let tmpdir = testdir();
1976 let (mut watcher, rx) = watcher();
1977
1978 let subdir = tmpdir.path().join("subdir");
1979 let subdir2 = tmpdir.path().join("subdir2");
1980 let file = subdir.join("file");
1981 let hardlink = subdir2.join("hardlink");
1982
1983 std::fs::create_dir(&subdir).expect("create");
1984 std::fs::create_dir(&subdir2).expect("create2");
1985 std::fs::write(&file, "").expect("file");
1986 std::fs::hard_link(&file, &hardlink).expect("hardlink");
1987
1988 watcher.watch_nonrecursively(&file);
1989
1990 std::fs::write(&hardlink, "123123").expect("write to the hard link");
1991
1992 rx.wait_ordered_exact([
1993 expected(&file).access_open_any(),
1994 expected(&file).modify_data_any().multiple(),
1995 expected(&file).access_close_write(),
1996 ]);
1997 assert_eq!(watcher.get_watch_handles(), HashSet::from([subdir, file]));
1998 }
1999
2000 #[test]
2001 fn write_to_a_hardlink_pointed_to_the_watched_file_triggers_an_event_even_if_the_parent_is_watched()
2002 {
2003 let tmpdir = testdir();
2004 let (mut watcher, rx) = watcher();
2005
2006 let subdir1 = tmpdir.path().join("subdir1");
2007 let subdir2 = subdir1.join("subdir2");
2008 let file = subdir2.join("file");
2009 let hardlink = tmpdir.path().join("hardlink");
2010
2011 std::fs::create_dir_all(&subdir2).expect("create");
2012 std::fs::write(&file, "").expect("file");
2013 std::fs::hard_link(&file, &hardlink).expect("hardlink");
2014
2015 watcher.watch_nonrecursively(&subdir2);
2016 watcher.watch_nonrecursively(&file);
2017
2018 std::fs::write(&hardlink, "123123").expect("write to the hard link");
2019
2020 rx.wait_ordered_exact([
2021 expected(&subdir2).access_open_any().optional(),
2022 expected(&file).access_open_any(),
2023 expected(&file).modify_data_any().multiple(),
2024 expected(&file).access_close_write(),
2025 ]);
2026 assert_eq!(
2027 watcher.get_watch_handles(),
2028 HashSet::from([subdir1, subdir2, file])
2029 );
2030 }
2031
2032 #[test]
2033 fn write_to_a_hardlink_pointed_to_the_file_in_the_watched_dir_doesnt_trigger_an_event() {
2034 let tmpdir = testdir();
2035 let (mut watcher, rx) = watcher();
2036
2037 let subdir = tmpdir.path().join("subdir");
2038 let subdir2 = tmpdir.path().join("subdir2");
2039 let file = subdir.join("file");
2040 let hardlink = subdir2.join("hardlink");
2041
2042 std::fs::create_dir(&subdir).expect("create");
2043 std::fs::create_dir(&subdir2).expect("create");
2044 std::fs::write(&file, "").expect("file");
2045 std::fs::hard_link(&file, &hardlink).expect("hardlink");
2046
2047 watcher.watch_nonrecursively(&subdir);
2048
2049 std::fs::write(&hardlink, "123123").expect("write to the hard link");
2050
2051 rx.wait_ordered_exact([expected(&subdir).access_open_any().optional()])
2052 .ensure_no_tail();
2053 assert_eq!(
2054 watcher.get_watch_handles(),
2055 HashSet::from([tmpdir.to_path_buf(), subdir])
2056 );
2057 }
2058
2059 #[test]
2060 #[ignore = "see https://github.com/notify-rs/notify/issues/727"]
2061 fn recursive_creation() {
2062 let tmpdir = testdir();
2063 let nested1 = tmpdir.path().join("1");
2064 let nested2 = tmpdir.path().join("1/2");
2065 let nested3 = tmpdir.path().join("1/2/3");
2066 let nested4 = tmpdir.path().join("1/2/3/4");
2067 let nested5 = tmpdir.path().join("1/2/3/4/5");
2068 let nested6 = tmpdir.path().join("1/2/3/4/5/6");
2069 let nested7 = tmpdir.path().join("1/2/3/4/5/6/7");
2070 let nested8 = tmpdir.path().join("1/2/3/4/5/6/7/8");
2071 let nested9 = tmpdir.path().join("1/2/3/4/5/6/7/8/9");
2072
2073 let (mut watcher, rx) = watcher();
2074
2075 watcher.watch_recursively(&tmpdir);
2076
2077 std::fs::create_dir_all(&nested9).expect("create_dir_all");
2078 rx.wait_ordered([
2079 expected(&nested1).create_folder(),
2080 expected(&nested2).create_folder(),
2081 expected(&nested3).create_folder(),
2082 expected(&nested4).create_folder(),
2083 expected(&nested5).create_folder(),
2084 expected(&nested6).create_folder(),
2085 expected(&nested7).create_folder(),
2086 expected(&nested8).create_folder(),
2087 expected(&nested9).create_folder(),
2088 ]);
2089 assert_eq!(
2090 watcher.get_watch_handles(),
2091 HashSet::from([
2092 tmpdir.to_path_buf(),
2093 nested1,
2094 nested2,
2095 nested3,
2096 nested4,
2097 nested5,
2098 nested6,
2099 nested7,
2100 nested8,
2101 nested9
2102 ])
2103 );
2104 }
2105
2106 #[test]
2107 fn upgrade_to_recursive() {
2108 let tmpdir = testdir();
2109 let (mut watcher, rx) = watcher();
2110
2111 let path = tmpdir.path().join("upgrade");
2112 let deep = tmpdir.path().join("upgrade/deep");
2113 let file = tmpdir.path().join("upgrade/deep/file");
2114 std::fs::create_dir_all(&deep).expect("create_dir");
2115
2116 watcher.watch_nonrecursively(&path);
2117 std::fs::File::create_new(&file).expect("create");
2118 std::fs::remove_file(&file).expect("delete");
2119
2120 rx.ensure_empty_with_wait();
2121
2122 watcher.watch_recursively(&path);
2123 std::fs::File::create_new(&file).expect("create");
2124
2125 rx.wait_ordered([
2126 expected(&file).create_file(),
2127 expected(&file).access_open_any(),
2128 expected(&file).access_close_write(),
2129 ])
2130 .ensure_no_tail();
2131 assert_eq!(
2132 watcher.get_watch_handles(),
2133 HashSet::from([tmpdir.to_path_buf(), path, deep])
2134 );
2135 }
2136}