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