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