1use super::event::*;
8use super::{Config, Error, ErrorKind, EventHandler, RecursiveMode, Result, Watcher};
9use crate::bimap::BiHashMap;
10use crate::{BoundSender, Receiver, Sender, 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, RecursiveMode>,
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, RecursiveMode, 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, RecursiveMode>,
69 add_watches: &mut Vec<(PathBuf, bool, bool)>,
70) {
71 if let Some(recursive_mode) = watches.get(path) {
72 add_watches.push((
73 path.to_owned(),
74 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(recursive_mode) = watches.get(parent) {
84 add_watches.push((
85 path.to_owned(),
86 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(RecursiveMode::Recursive) = watches.get(parent) {
95 add_watches.push((path.to_owned(), true, is_file_without_hardlinks));
96 return;
97 }
98 current = parent;
99 }
100}
101
102#[inline]
103fn remove_watch_by_event(
104 path: &PathBuf,
105 watch_handles: &BiHashMap<WatchDescriptor, PathBuf, (bool, bool)>,
106 remove_watches: &mut Vec<PathBuf>,
107) {
108 if watch_handles.contains_right(path) {
109 remove_watches.push(path.to_owned());
110 }
111}
112
113impl EventLoop {
114 pub fn new(
115 inotify: Inotify,
116 event_handler: Box<dyn EventHandler>,
117 follow_links: bool,
118 ) -> Result<Self> {
119 let (event_loop_tx, event_loop_rx) = unbounded::<EventLoopMsg>();
120 let poll = mio::Poll::new()?;
121
122 let event_loop_waker = Arc::new(mio::Waker::new(poll.registry(), MESSAGE)?);
123
124 let inotify_fd = inotify.as_raw_fd();
125 let mut evented_inotify = mio::unix::SourceFd(&inotify_fd);
126 poll.registry()
127 .register(&mut evented_inotify, INOTIFY, mio::Interest::READABLE)?;
128
129 let event_loop = EventLoop {
130 running: true,
131 poll,
132 event_loop_waker,
133 event_loop_tx,
134 event_loop_rx,
135 inotify: Some(inotify),
136 event_handler,
137 watches: HashMap::new(),
138 watch_handles: BiHashMap::new(),
139 rename_event: None,
140 follow_links,
141 };
142 Ok(event_loop)
143 }
144
145 pub fn run(self) {
147 let _ = thread::Builder::new()
148 .name("notify-rs inotify loop".to_string())
149 .spawn(|| self.event_loop_thread());
150 }
151
152 fn event_loop_thread(mut self) {
153 let mut events = mio::Events::with_capacity(16);
154 loop {
155 match self.poll.poll(&mut events, None) {
157 Err(ref e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => {
158 }
161 Err(e) => panic!("poll failed: {}", e),
162 Ok(()) => {}
163 }
164
165 for event in &events {
167 self.handle_event(event);
168 }
169
170 if !self.running {
172 break;
173 }
174 }
175 }
176
177 fn handle_event(&mut self, event: &mio::event::Event) {
179 match event.token() {
180 MESSAGE => {
181 self.handle_messages()
183 }
184 INOTIFY => {
185 self.handle_inotify()
187 }
188 _ => unreachable!(),
189 }
190 }
191
192 fn handle_messages(&mut self) {
193 while let Ok(msg) = self.event_loop_rx.try_recv() {
194 match msg {
195 EventLoopMsg::AddWatch(path, recursive_mode, tx) => {
196 let _ = tx.send(self.add_watch(path, recursive_mode));
197 }
198 EventLoopMsg::RemoveWatch(path, tx) => {
199 let _ = tx.send(self.remove_watch(path));
200 }
201 EventLoopMsg::Shutdown => {
202 let _ = self.remove_all_watches();
203 if let Some(inotify) = self.inotify.take() {
204 let _ = inotify.close();
205 }
206 self.running = false;
207 break;
208 }
209 EventLoopMsg::Configure(config, tx) => {
210 self.configure_raw_mode(config, tx);
211 }
212 #[cfg(test)]
213 EventLoopMsg::GetWatchHandles(tx) => {
214 let handles: HashSet<PathBuf> = self
215 .watch_handles
216 .iter()
217 .map(|(_, path, _)| path.clone())
218 .collect();
219 tx.send(handles).unwrap();
220 }
221 }
222 }
223 }
224
225 fn configure_raw_mode(&mut self, _config: Config, tx: BoundSender<Result<bool>>) {
226 tx.send(Ok(false))
227 .expect("configuration channel disconnected");
228 }
229
230 fn handle_inotify(&mut self) {
231 let mut add_watches = Vec::new();
232 let mut remove_watches = Vec::new();
233
234 if let Some(ref mut inotify) = self.inotify {
235 let mut buffer = [0; 1024];
236 loop {
238 match inotify.read_events(&mut buffer) {
239 Ok(events) => {
240 let mut num_events = 0;
241 for event in events {
242 log::trace!("inotify event: {event:?}");
243
244 num_events += 1;
245 if event.mask.contains(EventMask::Q_OVERFLOW) {
246 let ev = Ok(Event::new(EventKind::Other).set_flag(Flag::Rescan));
247 self.event_handler.handle_event(ev);
248 }
249
250 let path = match event.name {
251 Some(name) => self
252 .watch_handles
253 .get_by_left(&event.wd)
254 .map(|(root, _)| root.join(name)),
255 None => self
256 .watch_handles
257 .get_by_left(&event.wd)
258 .map(|(root, _)| root.clone()),
259 };
260
261 let path = match path {
262 Some(path) => path,
263 None => {
264 log::debug!("inotify event with unknown descriptor: {event:?}");
265 continue;
266 }
267 };
268
269 let mut evs = Vec::new();
270
271 if event.mask.contains(EventMask::MOVED_FROM) {
272 remove_watch_by_event(
273 &path,
274 &self.watch_handles,
275 &mut remove_watches,
276 );
277
278 let event = Event::new(EventKind::Modify(ModifyKind::Name(
279 RenameMode::From,
280 )))
281 .add_path(path.clone())
282 .set_tracker(event.cookie as usize);
283
284 self.rename_event = Some(event.clone());
285
286 evs.push(event);
287 } else if event.mask.contains(EventMask::MOVED_TO) {
288 evs.push(
289 Event::new(EventKind::Modify(ModifyKind::Name(RenameMode::To)))
290 .set_tracker(event.cookie as usize)
291 .add_path(path.clone()),
292 );
293
294 let trackers_match =
295 self.rename_event.as_ref().and_then(|e| e.tracker())
296 == Some(event.cookie as usize);
297
298 if trackers_match {
299 let rename_event = self.rename_event.take().unwrap(); evs.push(
301 Event::new(EventKind::Modify(ModifyKind::Name(
302 RenameMode::Both,
303 )))
304 .set_tracker(event.cookie as usize)
305 .add_some_path(rename_event.paths.first().cloned())
306 .add_path(path.clone()),
307 );
308 }
309 let is_file_without_hardlinks =
310 !event.mask.contains(EventMask::ISDIR)
311 && metadata(&path)
312 .map(|m| m.is_file_without_hardlinks())
313 .unwrap_or_default();
314 add_watch_by_event(
315 &path,
316 is_file_without_hardlinks,
317 &self.watches,
318 &mut add_watches,
319 );
320 }
321 if event.mask.contains(EventMask::MOVE_SELF) {
322 evs.push(
323 Event::new(EventKind::Modify(ModifyKind::Name(
324 RenameMode::From,
325 )))
326 .add_path(path.clone()),
327 );
328 }
332 if event.mask.contains(EventMask::CREATE) {
333 let is_dir = event.mask.contains(EventMask::ISDIR);
334 evs.push(
335 Event::new(EventKind::Create(if is_dir {
336 CreateKind::Folder
337 } else {
338 CreateKind::File
339 }))
340 .add_path(path.clone()),
341 );
342 let is_file_without_hardlinks = !is_dir
343 && metadata(&path)
344 .map(|m| m.is_file_without_hardlinks())
345 .unwrap_or_default();
346 add_watch_by_event(
347 &path,
348 is_file_without_hardlinks,
349 &self.watches,
350 &mut add_watches,
351 );
352 }
353 if event.mask.contains(EventMask::DELETE) {
354 evs.push(
355 Event::new(EventKind::Remove(
356 if event.mask.contains(EventMask::ISDIR) {
357 RemoveKind::Folder
358 } else {
359 RemoveKind::File
360 },
361 ))
362 .add_path(path.clone()),
363 );
364 remove_watch_by_event(
365 &path,
366 &self.watch_handles,
367 &mut remove_watches,
368 );
369 }
370 if event.mask.contains(EventMask::DELETE_SELF) {
371 let remove_kind = match self.watch_handles.get_by_right(&path) {
372 Some((_, (_, true))) => RemoveKind::Folder,
373 Some((_, (_, false))) => RemoveKind::File,
374 None => RemoveKind::Other,
375 };
376 evs.push(
377 Event::new(EventKind::Remove(remove_kind))
378 .add_path(path.clone()),
379 );
380 remove_watch_by_event(
381 &path,
382 &self.watch_handles,
383 &mut remove_watches,
384 );
385 }
386 if event.mask.contains(EventMask::MODIFY) {
387 evs.push(
388 Event::new(EventKind::Modify(ModifyKind::Data(
389 DataChange::Any,
390 )))
391 .add_path(path.clone()),
392 );
393 }
394 if event.mask.contains(EventMask::CLOSE_WRITE) {
395 evs.push(
396 Event::new(EventKind::Access(AccessKind::Close(
397 AccessMode::Write,
398 )))
399 .add_path(path.clone()),
400 );
401 }
402 if event.mask.contains(EventMask::CLOSE_NOWRITE) {
403 evs.push(
404 Event::new(EventKind::Access(AccessKind::Close(
405 AccessMode::Read,
406 )))
407 .add_path(path.clone()),
408 );
409 }
410 if event.mask.contains(EventMask::ATTRIB) {
411 evs.push(
412 Event::new(EventKind::Modify(ModifyKind::Metadata(
413 MetadataKind::Any,
414 )))
415 .add_path(path.clone()),
416 );
417 }
418 if event.mask.contains(EventMask::OPEN) {
419 evs.push(
420 Event::new(EventKind::Access(AccessKind::Open(
421 AccessMode::Any,
422 )))
423 .add_path(path.clone()),
424 );
425 }
426
427 for ev in evs {
428 self.event_handler.handle_event(Ok(ev));
429 }
430 }
431
432 if num_events == 0 {
434 break;
435 }
436 }
437 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
438 break;
440 }
441 Err(e) => {
442 self.event_handler.handle_event(Err(Error::io(e)));
443 }
444 }
445 }
446 }
447
448 for path in remove_watches {
449 self.watches.remove(&path);
450 self.remove_maybe_recursive_watch(path, true).ok();
451 }
452
453 for (path, is_recursive, is_file_without_hardlinks) in add_watches {
454 if let Err(add_watch_error) =
455 self.add_maybe_recursive_watch(path, is_recursive, is_file_without_hardlinks, false)
456 {
457 if let ErrorKind::MaxFilesWatch = add_watch_error.kind {
461 self.event_handler.handle_event(Err(add_watch_error));
462
463 break;
467 }
468 }
469 }
470 }
471
472 fn add_watch(&mut self, path: PathBuf, recursive_mode: RecursiveMode) -> Result<()> {
473 if let Some(existing) = self.watches.get(&path) {
474 let need_upgrade_to_recursive = match *existing {
475 RecursiveMode::Recursive => false,
476 RecursiveMode::NonRecursive => recursive_mode == RecursiveMode::Recursive,
477 };
478 if !need_upgrade_to_recursive {
479 return Ok(());
480 }
481
482 if metadata(&path).map_err(Error::io)?.is_dir() {
484 self.add_maybe_recursive_watch(path.clone(), true, false, true)?;
485 }
486 *self.watches.get_mut(&path).unwrap() = RecursiveMode::Recursive;
487 return Ok(());
488 }
489
490 let meta = metadata(&path).map_err(Error::io_watch)?;
491 self.add_maybe_recursive_watch(
492 path.clone(),
493 recursive_mode.is_recursive() && meta.is_dir(),
496 meta.is_file_without_hardlinks(),
497 true,
498 )?;
499
500 self.watches.insert(path, recursive_mode);
501
502 Ok(())
503 }
504
505 fn add_maybe_recursive_watch(
506 &mut self,
507 path: PathBuf,
508 is_recursive: bool,
509 is_file_without_hardlinks: bool,
510 mut watch_self: bool,
511 ) -> Result<()> {
512 if is_recursive {
513 for entry in WalkDir::new(&path)
514 .follow_links(self.follow_links)
515 .into_iter()
516 .filter_map(filter_dir)
517 {
518 self.add_single_watch(entry.into_path(), false, watch_self)?;
519 watch_self = false;
520 }
521 } else {
522 self.add_single_watch(path.clone(), is_file_without_hardlinks, watch_self)?;
523 }
524 Ok(())
525 }
526
527 fn add_single_watch(
528 &mut self,
529 path: PathBuf,
530 is_file_without_hardlinks: bool,
531 watch_self: bool,
532 ) -> Result<()> {
533 if let Some((_, &(old_watch_self, _))) = self.watch_handles.get_by_right(&path)
534 && (old_watch_self || !watch_self)
536 {
537 return Ok(());
538 }
539
540 if is_file_without_hardlinks
541 && let Some(parent) = path.parent()
542 && self.watch_handles.get_by_right(parent).is_some()
543 {
544 return Ok(());
545 }
546
547 let mut watchmask = WatchMask::ATTRIB
548 | WatchMask::CREATE
549 | WatchMask::OPEN
550 | WatchMask::DELETE
551 | WatchMask::CLOSE_WRITE
552 | WatchMask::MODIFY
553 | WatchMask::MOVED_FROM
554 | WatchMask::MOVED_TO;
555 if watch_self {
556 watchmask.insert(WatchMask::DELETE_SELF);
557 watchmask.insert(WatchMask::MOVE_SELF);
558 }
559
560 if let Some(ref mut inotify) = self.inotify {
561 log::trace!("adding inotify watch: {}", path.display());
562
563 match inotify.watches().add(&path, watchmask) {
564 Err(e) => {
565 Err(if e.raw_os_error() == Some(libc::ENOSPC) {
566 Error::new(ErrorKind::MaxFilesWatch)
568 } else if e.kind() == std::io::ErrorKind::NotFound {
569 Error::new(ErrorKind::PathNotFound)
570 } else {
571 Error::io(e)
572 }
573 .add_path(path))
574 }
575 Ok(w) => {
576 watchmask.remove(WatchMask::MASK_ADD);
577 let is_dir = metadata(&path).map_err(Error::io)?.is_dir();
578 self.watch_handles.insert(w, path, (watch_self, is_dir));
579 Ok(())
580 }
581 }
582 } else {
583 Ok(())
584 }
585 }
586
587 fn remove_watch(&mut self, path: PathBuf) -> Result<()> {
588 match self.watches.remove(&path) {
589 None => return Err(Error::watch_not_found().add_path(path)),
590 Some(recursive_mode) => {
591 self.remove_maybe_recursive_watch(path, recursive_mode.is_recursive())?;
592 }
593 }
594 Ok(())
595 }
596
597 fn remove_maybe_recursive_watch(&mut self, path: PathBuf, is_recursive: bool) -> Result<()> {
598 let Some(ref mut inotify) = self.inotify else {
599 return Ok(());
600 };
601 let mut inotify_watches = inotify.watches();
602
603 log::trace!("removing inotify watch: {}", path.display());
604
605 if let Some((handle, _)) = self.watch_handles.remove_by_right(&path) {
606 inotify_watches
607 .remove(handle.clone())
608 .map_err(|e| Error::io(e).add_path(path.clone()))?;
609 }
610
611 if is_recursive {
612 let mut remove_list = Vec::new();
613 for (w, p, _) in &self.watch_handles {
614 if p.starts_with(&path) {
615 inotify_watches
616 .remove(w.clone())
617 .map_err(|e| Error::io(e).add_path(p.into()))?;
618 remove_list.push(w.clone());
619 }
620 }
621 for w in remove_list {
622 self.watch_handles.remove_by_left(&w);
623 }
624 }
625 Ok(())
626 }
627
628 fn remove_all_watches(&mut self) -> Result<()> {
629 if let Some(ref mut inotify) = self.inotify {
630 let mut inotify_watches = inotify.watches();
631 for (w, p, _) in &self.watch_handles {
632 inotify_watches
633 .remove(w.clone())
634 .map_err(|e| Error::io(e).add_path(p.into()))?;
635 }
636 self.watch_handles.clear();
637 self.watches.clear();
638 }
639 Ok(())
640 }
641}
642
643fn filter_dir(e: walkdir::Result<walkdir::DirEntry>) -> Option<walkdir::DirEntry> {
645 if let Ok(e) = e
646 && let Ok(metadata) = e.metadata()
647 && metadata.is_dir()
648 {
649 return Some(e);
650 }
651 None
652}
653
654impl INotifyWatcher {
655 fn from_event_handler(
656 event_handler: Box<dyn EventHandler>,
657 follow_links: bool,
658 ) -> Result<Self> {
659 let inotify = Inotify::init()?;
660 let event_loop = EventLoop::new(inotify, event_handler, follow_links)?;
661 let channel = event_loop.event_loop_tx.clone();
662 let waker = event_loop.event_loop_waker.clone();
663 event_loop.run();
664 Ok(INotifyWatcher { channel, waker })
665 }
666
667 fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
668 let pb = if path.is_absolute() {
669 path.to_owned()
670 } else {
671 let p = env::current_dir().map_err(Error::io)?;
672 p.join(path)
673 };
674 let (tx, rx) = unbounded();
675 let msg = EventLoopMsg::AddWatch(pb, recursive_mode, tx);
676
677 self.channel.send(msg).unwrap();
679 self.waker.wake().unwrap();
680 rx.recv().unwrap()
681 }
682
683 fn unwatch_inner(&mut self, path: &Path) -> Result<()> {
684 let pb = if path.is_absolute() {
685 path.to_owned()
686 } else {
687 let p = env::current_dir().map_err(Error::io)?;
688 p.join(path)
689 };
690 let (tx, rx) = unbounded();
691 let msg = EventLoopMsg::RemoveWatch(pb, tx);
692
693 self.channel.send(msg).unwrap();
695 self.waker.wake().unwrap();
696 rx.recv().unwrap()
697 }
698}
699
700impl Watcher for INotifyWatcher {
701 fn new<F: EventHandler>(event_handler: F, config: Config) -> Result<Self> {
703 Self::from_event_handler(Box::new(event_handler), config.follow_symlinks())
704 }
705
706 fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
707 self.watch_inner(path, recursive_mode)
708 }
709
710 fn unwatch(&mut self, path: &Path) -> Result<()> {
711 self.unwatch_inner(path)
712 }
713
714 fn configure(&mut self, config: Config) -> Result<bool> {
715 let (tx, rx) = bounded(1);
716 self.channel.send(EventLoopMsg::Configure(config, tx))?;
717 self.waker.wake()?;
718 rx.recv()?
719 }
720
721 fn kind() -> crate::WatcherKind {
722 crate::WatcherKind::Inotify
723 }
724
725 #[cfg(test)]
726 fn get_watch_handles(&self) -> std::collections::HashSet<std::path::PathBuf> {
727 let (tx, rx) = bounded(1);
728 self.channel
729 .send(EventLoopMsg::GetWatchHandles(tx))
730 .unwrap();
731 self.waker.wake().unwrap();
732 rx.recv().unwrap()
733 }
734}
735
736impl Drop for INotifyWatcher {
737 fn drop(&mut self) {
738 self.channel.send(EventLoopMsg::Shutdown).unwrap();
740 self.waker.wake().unwrap();
741 }
742}
743
744trait MetadataNotifyExt {
745 fn is_file_without_hardlinks(&self) -> bool;
746}
747
748impl MetadataNotifyExt for std::fs::Metadata {
749 #[inline]
750 fn is_file_without_hardlinks(&self) -> bool {
751 self.is_file() && self.nlink() == 1
752 }
753}
754
755#[cfg(test)]
756mod tests {
757 use std::{
758 collections::HashSet,
759 path::{Path, PathBuf},
760 sync::{Arc, atomic::AtomicBool, mpsc},
761 thread::{self, available_parallelism},
762 time::Duration,
763 };
764
765 use super::{Config, Error, ErrorKind, Event, INotifyWatcher, RecursiveMode, Result, Watcher};
766
767 use crate::test::*;
768
769 fn watcher() -> (TestWatcher<INotifyWatcher>, Receiver) {
770 channel()
771 }
772
773 #[test]
774 fn inotify_watcher_is_send_and_sync() {
775 fn check<T: Send + Sync>() {}
776 check::<INotifyWatcher>();
777 }
778
779 #[test]
780 fn native_error_type_on_missing_path() {
781 let mut watcher = INotifyWatcher::new(|_| {}, Config::default()).unwrap();
782
783 let result = watcher.watch(
784 &PathBuf::from("/some/non/existant/path"),
785 RecursiveMode::NonRecursive,
786 );
787
788 assert!(matches!(
789 result,
790 Err(Error {
791 paths: _,
792 kind: ErrorKind::PathNotFound
793 })
794 ))
795 }
796
797 #[test]
805 #[ignore = "requires changing sysctl fs.inotify.max_user_watches while test is running"]
806 fn recursive_watch_calls_handler_if_creating_a_file_raises_max_files_watch() {
807 use std::time::Duration;
808
809 let tmpdir = tempfile::tempdir().unwrap();
810 let (tx, rx) = std::sync::mpsc::channel();
811 let (proc_changed_tx, proc_changed_rx) = std::sync::mpsc::channel();
812 let proc_path = Path::new("/proc/sys/fs/inotify/max_user_watches");
813 let mut watcher = INotifyWatcher::new(
814 move |result: Result<Event>| match result {
815 Ok(event) => {
816 if event.paths.first().is_some_and(|path| path == proc_path) {
817 proc_changed_tx.send(()).unwrap();
818 }
819 }
820 Err(e) => tx.send(e).unwrap(),
821 },
822 Config::default(),
823 )
824 .unwrap();
825
826 watcher
827 .watch(tmpdir.path(), RecursiveMode::Recursive)
828 .unwrap();
829 watcher
830 .watch(proc_path, RecursiveMode::NonRecursive)
831 .unwrap();
832
833 proc_changed_rx
835 .recv_timeout(Duration::from_secs(30))
836 .unwrap();
837
838 let child_dir = tmpdir.path().join("child");
839 std::fs::create_dir(child_dir).unwrap();
840
841 let result = rx.recv_timeout(Duration::from_millis(500));
842
843 assert!(
844 matches!(
845 &result,
846 Ok(Error {
847 kind: ErrorKind::MaxFilesWatch,
848 paths: _,
849 })
850 ),
851 "expected {:?}, found: {:#?}",
852 ErrorKind::MaxFilesWatch,
853 result
854 );
855 }
856
857 #[test]
859 fn race_condition_on_unwatch_and_pending_events_with_deleted_descriptor() {
860 let tmpdir = tempfile::tempdir().expect("tmpdir");
861 let (tx, rx) = mpsc::channel();
862 let mut inotify = INotifyWatcher::new(
863 move |e: Result<Event>| {
864 let e = match e {
865 Ok(e) if e.paths.is_empty() => e,
866 Ok(_) | Err(_) => return,
867 };
868 let _ = tx.send(e);
869 },
870 Config::default(),
871 )
872 .expect("inotify creation");
873
874 let dir_path = tmpdir.path();
875 let file_path = dir_path.join("foo");
876 std::fs::File::create(&file_path).unwrap();
877
878 let stop = Arc::new(AtomicBool::new(false));
879
880 let handles: Vec<_> = (0..available_parallelism().unwrap().get().max(4))
881 .map(|_| {
882 let file_path = file_path.clone();
883 let stop = stop.clone();
884 thread::spawn(move || {
885 while !stop.load(std::sync::atomic::Ordering::Relaxed) {
886 let _ = std::fs::File::open(&file_path).unwrap();
887 }
888 })
889 })
890 .collect();
891
892 let non_recursive = RecursiveMode::NonRecursive;
893 for _ in 0..(handles.len() * 4) {
894 inotify.watch(dir_path, non_recursive).unwrap();
895 inotify.unwatch(dir_path).unwrap();
896 }
897
898 stop.store(true, std::sync::atomic::Ordering::Relaxed);
899 handles
900 .into_iter()
901 .for_each(|handle| handle.join().ok().unwrap_or_default());
902
903 drop(inotify);
904
905 let events: Vec<_> = rx.into_iter().map(|e| format!("{e:?}")).collect();
906
907 const LOG_LEN: usize = 10;
908 let events_len = events.len();
909 assert!(
910 events.is_empty(),
911 "expected no events without path, but got {events_len}. first 10: {:#?}",
912 &events[..LOG_LEN.min(events_len)]
913 );
914 }
915
916 #[test]
917 fn create_file() {
918 let tmpdir = testdir();
919 let (mut watcher, mut rx) = watcher();
920 watcher.watch_recursively(&tmpdir);
921
922 let path = tmpdir.path().join("entry");
923 std::fs::File::create_new(&path).expect("create");
924
925 rx.wait_ordered_exact([
926 expected(&path).create_file(),
927 expected(&path).access_open_any(),
928 expected(&path).access_close_write(),
929 ]);
930 assert_eq!(
931 watcher.get_watch_handles(),
932 HashSet::from([tmpdir.to_path_buf()])
933 );
934 }
935
936 #[test]
937 fn create_file_nested_in_recursive_watch() {
938 let tmpdir = testdir();
939 let (mut watcher, mut rx) = watcher();
940
941 let nested1_dir = tmpdir.path().join("nested1");
942 let nested2_dir = nested1_dir.join("nested2");
943 std::fs::create_dir_all(&nested2_dir).expect("create_dir");
944
945 watcher.watch_recursively(&tmpdir);
946
947 let path = nested2_dir.join("entry");
948 std::fs::File::create_new(&path).expect("create");
949
950 rx.wait_ordered_exact([
951 expected(&nested1_dir).access_open_any().optional(),
952 expected(&nested2_dir).access_open_any().optional(),
953 expected(&path).create_file(),
954 expected(&path).access_open_any(),
955 expected(&path).access_close_write(),
956 ]);
957 assert_eq!(
958 watcher.get_watch_handles(),
959 HashSet::from([tmpdir.to_path_buf(), nested1_dir, nested2_dir])
960 );
961 }
962
963 #[test]
964 fn write_file() {
965 let tmpdir = testdir();
966 let (mut watcher, mut rx) = watcher();
967
968 let path = tmpdir.path().join("entry");
969 std::fs::File::create_new(&path).expect("create");
970
971 watcher.watch_recursively(&tmpdir);
972 std::fs::write(&path, b"123").expect("write");
973
974 rx.wait_ordered_exact([
975 expected(&path).access_open_any(),
976 expected(&path).modify_data_any().multiple(),
977 expected(&path).access_close_write(),
978 ])
979 .ensure_no_tail();
980 assert_eq!(
981 watcher.get_watch_handles(),
982 HashSet::from([tmpdir.to_path_buf()])
983 );
984 }
985
986 #[test]
987 fn chmod_file() {
988 let tmpdir = testdir();
989 let (mut watcher, mut rx) = watcher();
990
991 let path = tmpdir.path().join("entry");
992 let file = std::fs::File::create_new(&path).expect("create");
993 let mut permissions = file.metadata().expect("metadata").permissions();
994 permissions.set_readonly(true);
995
996 watcher.watch_recursively(&tmpdir);
997 file.set_permissions(permissions).expect("set_permissions");
998
999 rx.wait_ordered_exact([expected(&path).modify_meta_any()]);
1000 assert_eq!(
1001 watcher.get_watch_handles(),
1002 HashSet::from([tmpdir.to_path_buf()])
1003 );
1004 }
1005
1006 #[test]
1007 fn rename_file() {
1008 let tmpdir = testdir();
1009 let (mut watcher, mut rx) = watcher();
1010
1011 let path = tmpdir.path().join("entry");
1012 std::fs::File::create_new(&path).expect("create");
1013
1014 watcher.watch_recursively(&tmpdir);
1015 let new_path = tmpdir.path().join("renamed");
1016
1017 std::fs::rename(&path, &new_path).expect("rename");
1018
1019 rx.wait_ordered_exact([
1020 expected(&path).rename_from(),
1021 expected(&new_path).rename_to(),
1022 expected([path, new_path]).rename_both(),
1023 ])
1024 .ensure_trackers_len(1)
1025 .ensure_no_tail();
1026 assert_eq!(
1027 watcher.get_watch_handles(),
1028 HashSet::from([tmpdir.to_path_buf()])
1029 );
1030 }
1031
1032 #[test]
1033 fn delete_file() {
1034 let tmpdir = testdir();
1035 let (mut watcher, mut rx) = watcher();
1036 let file = tmpdir.path().join("file");
1037 std::fs::write(&file, "").expect("write");
1038
1039 watcher.watch_nonrecursively(&tmpdir);
1040
1041 std::fs::remove_file(&file).expect("remove");
1042
1043 rx.wait_ordered_exact([expected(&file).remove_file()]);
1044 assert_eq!(
1045 watcher.get_watch_handles(),
1046 HashSet::from([tmpdir.to_path_buf()])
1047 );
1048 }
1049
1050 #[test]
1051 fn delete_self_file() {
1052 let tmpdir = testdir();
1053 let (mut watcher, mut rx) = watcher();
1054 let file = tmpdir.path().join("file");
1055 std::fs::write(&file, "").expect("write");
1056
1057 watcher.watch_nonrecursively(&file);
1058
1059 std::fs::remove_file(&file).expect("remove");
1060
1061 rx.wait_ordered_exact([
1062 expected(&file).modify_meta_any(),
1063 expected(&file).remove_file(),
1064 ]);
1065 assert_eq!(watcher.get_watch_handles(), HashSet::from([]));
1066 }
1067
1068 #[test]
1069 fn create_write_overwrite() {
1070 let tmpdir = testdir();
1071 let (mut watcher, mut rx) = watcher();
1072 let overwritten_file = tmpdir.path().join("overwritten_file");
1073 let overwriting_file = tmpdir.path().join("overwriting_file");
1074 std::fs::write(&overwritten_file, "123").expect("write1");
1075
1076 watcher.watch_nonrecursively(&tmpdir);
1077
1078 std::fs::File::create(&overwriting_file).expect("create");
1079 std::fs::write(&overwriting_file, "321").expect("write2");
1080 std::fs::rename(&overwriting_file, &overwritten_file).expect("rename");
1081
1082 rx.wait_ordered_exact([
1083 expected(&overwriting_file).create_file(),
1084 expected(&overwriting_file).access_open_any(),
1085 expected(&overwriting_file).access_close_write(),
1086 expected(&overwriting_file).access_open_any(),
1087 expected(&overwriting_file).modify_data_any().multiple(),
1088 expected(&overwriting_file).access_close_write().multiple(),
1089 expected(&overwriting_file).rename_from(),
1090 expected(&overwritten_file).rename_to(),
1091 expected([&overwriting_file, &overwritten_file]).rename_both(),
1092 ])
1093 .ensure_no_tail()
1094 .ensure_trackers_len(1);
1095 assert_eq!(
1096 watcher.get_watch_handles(),
1097 HashSet::from([tmpdir.to_path_buf()])
1098 );
1099 }
1100
1101 #[test]
1102 fn create_dir() {
1103 let tmpdir = testdir();
1104 let (mut watcher, mut rx) = watcher();
1105 watcher.watch_recursively(&tmpdir);
1106
1107 let path = tmpdir.path().join("entry");
1108 std::fs::create_dir(&path).expect("create");
1109
1110 rx.wait_ordered_exact([expected(&path).create_folder()]);
1111 assert_eq!(
1112 watcher.get_watch_handles(),
1113 HashSet::from([tmpdir.to_path_buf(), path])
1114 );
1115 }
1116
1117 #[test]
1118 fn chmod_dir() {
1119 let tmpdir = testdir();
1120 let (mut watcher, mut rx) = watcher();
1121
1122 let path = tmpdir.path().join("entry");
1123 std::fs::create_dir(&path).expect("create_dir");
1124 let mut permissions = std::fs::metadata(&path).expect("metadata").permissions();
1125 permissions.set_readonly(true);
1126
1127 watcher.watch_recursively(&tmpdir);
1128 std::fs::set_permissions(&path, permissions).expect("set_permissions");
1129
1130 rx.wait_ordered_exact([
1131 expected(&path).access_open_any().optional(),
1132 expected(&path).modify_meta_any(),
1133 expected(&path).modify_meta_any(),
1134 ])
1135 .ensure_no_tail();
1136 assert_eq!(
1137 watcher.get_watch_handles(),
1138 HashSet::from([tmpdir.to_path_buf(), path])
1139 );
1140 }
1141
1142 #[test]
1143 fn rename_dir() {
1144 let tmpdir = testdir();
1145 let (mut watcher, mut rx) = watcher();
1146
1147 let path = tmpdir.path().join("entry");
1148 let new_path = tmpdir.path().join("new_path");
1149 std::fs::create_dir(&path).expect("create_dir");
1150
1151 watcher.watch_recursively(&tmpdir);
1152
1153 std::fs::rename(&path, &new_path).expect("rename");
1154
1155 rx.wait_ordered_exact([
1156 expected(&path).access_open_any().optional(),
1157 expected(&path).rename_from(),
1158 expected(&new_path).rename_to(),
1159 expected([&path, &new_path]).rename_both(),
1160 ])
1161 .ensure_trackers_len(1);
1162 assert_eq!(
1163 watcher.get_watch_handles(),
1164 HashSet::from([tmpdir.to_path_buf(), new_path])
1165 );
1166 }
1167
1168 #[test]
1169 fn delete_dir() {
1170 let tmpdir = testdir();
1171 let (mut watcher, mut rx) = watcher();
1172
1173 let path = tmpdir.path().join("entry");
1174 std::fs::create_dir(&path).expect("create_dir");
1175
1176 watcher.watch_recursively(&tmpdir);
1177 std::fs::remove_dir(&path).expect("remove");
1178
1179 rx.wait_ordered_exact([
1180 expected(&path).access_open_any().optional(),
1181 expected(&path).remove_folder(),
1182 ])
1183 .ensure_no_tail();
1184 assert_eq!(
1185 watcher.get_watch_handles(),
1186 HashSet::from([tmpdir.to_path_buf()])
1187 );
1188 }
1189
1190 #[test]
1191 fn rename_dir_twice() {
1192 let tmpdir = testdir();
1193 let (mut watcher, mut rx) = watcher();
1194
1195 let path = tmpdir.path().join("entry");
1196 let new_path = tmpdir.path().join("new_path");
1197 let new_path2 = tmpdir.path().join("new_path2");
1198 std::fs::create_dir(&path).expect("create_dir");
1199
1200 watcher.watch_recursively(&tmpdir);
1201 std::fs::rename(&path, &new_path).expect("rename");
1202 std::fs::rename(&new_path, &new_path2).expect("rename2");
1203
1204 rx.wait_ordered_exact([
1205 expected(&path).access_open_any().optional(),
1206 expected(&path).rename_from(),
1207 expected(&new_path).rename_to(),
1208 expected([&path, &new_path]).rename_both(),
1209 expected(&new_path).access_open_any().optional(),
1210 expected(&new_path).rename_from(),
1211 expected(&new_path2).rename_to(),
1212 expected([&new_path, &new_path2]).rename_both(),
1213 ])
1214 .ensure_trackers_len(2);
1215 assert_eq!(
1216 watcher.get_watch_handles(),
1217 HashSet::from([tmpdir.to_path_buf(), new_path2])
1218 );
1219 }
1220
1221 #[test]
1222 fn move_out_of_watched_dir() {
1223 let tmpdir = testdir();
1224 let subdir = tmpdir.path().join("subdir");
1225 let (mut watcher, mut rx) = watcher();
1226
1227 let path = subdir.join("entry");
1228 std::fs::create_dir_all(&subdir).expect("create_dir_all");
1229 std::fs::File::create_new(&path).expect("create");
1230
1231 watcher.watch_recursively(&subdir);
1232 let new_path = tmpdir.path().join("entry");
1233
1234 std::fs::rename(&path, &new_path).expect("rename");
1235
1236 let event = rx.recv();
1237 let tracker = event.attrs.tracker();
1238 assert_eq!(event, expected(path).rename_from());
1239 assert!(tracker.is_some(), "tracker is none: [event:#?]");
1240 rx.ensure_empty();
1241 assert_eq!(watcher.get_watch_handles(), HashSet::from([subdir]));
1242 }
1243
1244 #[test]
1245 fn create_write_write_rename_write_remove() {
1246 let tmpdir = testdir();
1247 let (mut watcher, mut rx) = watcher();
1248
1249 let file1 = tmpdir.path().join("entry");
1250 let file2 = tmpdir.path().join("entry2");
1251 std::fs::File::create_new(&file2).expect("create file2");
1252 let new_path = tmpdir.path().join("renamed");
1253
1254 watcher.watch_recursively(&tmpdir);
1255 std::fs::write(&file1, "123").expect("write 1");
1256 std::fs::write(&file2, "321").expect("write 2");
1257 std::fs::rename(&file1, &new_path).expect("rename");
1258 std::fs::write(&new_path, b"1").expect("write 3");
1259 std::fs::remove_file(&new_path).expect("remove");
1260
1261 rx.wait_ordered_exact([
1262 expected(&file1).create_file(),
1263 expected(&file1).access_open_any(),
1264 expected(&file1).modify_data_any().multiple(),
1265 expected(&file1).access_close_write(),
1266 expected(&file2).access_open_any(),
1267 expected(&file2).modify_data_any().multiple(),
1268 expected(&file2).access_close_write(),
1269 expected(&file1).access_open_any().optional(),
1270 expected(&file1).rename_from(),
1271 expected(&new_path).rename_to(),
1272 expected([&file1, &new_path]).rename_both(),
1273 expected(&new_path).access_open_any(),
1274 expected(&new_path).modify_data_any().multiple(),
1275 expected(&new_path).access_close_write(),
1276 expected(&new_path).remove_file(),
1277 ]);
1278 assert_eq!(
1279 watcher.get_watch_handles(),
1280 HashSet::from([tmpdir.to_path_buf()])
1281 );
1282 }
1283
1284 #[test]
1285 fn rename_twice() {
1286 let tmpdir = testdir();
1287 let (mut watcher, mut rx) = watcher();
1288
1289 let path = tmpdir.path().join("entry");
1290 std::fs::File::create_new(&path).expect("create");
1291
1292 watcher.watch_recursively(&tmpdir);
1293 let new_path1 = tmpdir.path().join("renamed1");
1294 let new_path2 = tmpdir.path().join("renamed2");
1295
1296 std::fs::rename(&path, &new_path1).expect("rename1");
1297 std::fs::rename(&new_path1, &new_path2).expect("rename2");
1298
1299 rx.wait_ordered_exact([
1300 expected(&path).access_open_any().optional(),
1301 expected(&path).rename_from(),
1302 expected(&new_path1).rename_to(),
1303 expected([&path, &new_path1]).rename_both(),
1304 expected(&new_path1).access_open_any().optional(),
1305 expected(&new_path1).rename_from(),
1306 expected(&new_path2).rename_to(),
1307 expected([&new_path1, &new_path2]).rename_both(),
1308 ])
1309 .ensure_no_tail()
1310 .ensure_trackers_len(2);
1311 assert_eq!(
1312 watcher.get_watch_handles(),
1313 HashSet::from([tmpdir.to_path_buf()])
1314 );
1315 }
1316
1317 #[test]
1318 fn set_file_mtime() {
1319 let tmpdir = testdir();
1320 let (mut watcher, mut rx) = watcher();
1321
1322 let path = tmpdir.path().join("entry");
1323 let file = std::fs::File::create_new(&path).expect("create");
1324
1325 watcher.watch_recursively(&tmpdir);
1326
1327 file.set_modified(
1328 std::time::SystemTime::now()
1329 .checked_sub(Duration::from_secs(60 * 60))
1330 .expect("time"),
1331 )
1332 .expect("set_time");
1333
1334 assert_eq!(rx.recv(), expected(&path).modify_data_any());
1335 rx.ensure_empty();
1336 assert_eq!(
1337 watcher.get_watch_handles(),
1338 HashSet::from([tmpdir.to_path_buf()])
1339 );
1340 }
1341
1342 #[test]
1343 fn write_file_non_recursive_watch() {
1344 let tmpdir = testdir();
1345 let (mut watcher, mut rx) = watcher();
1346
1347 let path = tmpdir.path().join("entry");
1348 std::fs::File::create_new(&path).expect("create");
1349
1350 watcher.watch_nonrecursively(&path);
1351
1352 std::fs::write(&path, b"123").expect("write");
1353
1354 rx.wait_ordered_exact([
1355 expected(&path).access_open_any(),
1356 expected(&path).modify_data_any().multiple(),
1357 expected(&path).access_close_write(),
1358 ])
1359 .ensure_no_tail();
1360 assert_eq!(watcher.get_watch_handles(), HashSet::from([path]));
1361 }
1362
1363 #[test]
1364 fn watch_recursively_then_unwatch_child_stops_events_from_child() {
1365 let tmpdir = testdir();
1366 let (mut watcher, mut rx) = watcher();
1367
1368 let subdir = tmpdir.path().join("subdir");
1369 let file = subdir.join("file");
1370 std::fs::create_dir(&subdir).expect("create");
1371
1372 watcher.watch_recursively(&tmpdir);
1373
1374 std::fs::File::create(&file).expect("create");
1375
1376 rx.wait_ordered_exact([
1377 expected(&subdir).access_open_any().optional(),
1378 expected(&file).create_file(),
1379 expected(&file).access_open_any(),
1380 expected(&file).access_close_write(),
1381 ])
1382 .ensure_no_tail();
1383 assert_eq!(
1384 watcher.get_watch_handles(),
1385 HashSet::from([tmpdir.to_path_buf(), subdir])
1386 );
1387
1388 }
1401
1402 #[test]
1403 fn write_to_a_hardlink_pointed_to_the_watched_file_triggers_an_event() {
1404 let tmpdir = testdir();
1405 let (mut watcher, mut rx) = watcher();
1406
1407 let subdir = tmpdir.path().join("subdir");
1408 let file = subdir.join("file");
1409 let hardlink = tmpdir.path().join("hardlink");
1410
1411 std::fs::create_dir(&subdir).expect("create");
1412 std::fs::write(&file, "").expect("file");
1413 std::fs::hard_link(&file, &hardlink).expect("hardlink");
1414
1415 watcher.watch_nonrecursively(&file);
1416
1417 std::fs::write(&hardlink, "123123").expect("write to the hard link");
1418
1419 rx.wait_ordered_exact([
1420 expected(&file).access_open_any(),
1421 expected(&file).modify_data_any().multiple(),
1422 expected(&file).access_close_write(),
1423 ]);
1424 assert_eq!(watcher.get_watch_handles(), HashSet::from([file]));
1425 }
1426
1427 #[test]
1428 fn write_to_a_hardlink_pointed_to_the_watched_file_triggers_an_event_even_if_the_parent_is_watched()
1429 {
1430 let tmpdir = testdir();
1431 let (mut watcher, mut rx) = watcher();
1432
1433 let subdir1 = tmpdir.path().join("subdir1");
1434 let subdir2 = subdir1.join("subdir2");
1435 let file = subdir2.join("file");
1436 let hardlink = tmpdir.path().join("hardlink");
1437
1438 std::fs::create_dir_all(&subdir2).expect("create");
1439 std::fs::write(&file, "").expect("file");
1440 std::fs::hard_link(&file, &hardlink).expect("hardlink");
1441
1442 watcher.watch_nonrecursively(&subdir2);
1443 watcher.watch_nonrecursively(&file);
1444
1445 std::fs::write(&hardlink, "123123").expect("write to the hard link");
1446
1447 rx.wait_ordered_exact([
1448 expected(&file).access_open_any(),
1449 expected(&file).modify_data_any().multiple(),
1450 expected(&file).access_close_write(),
1451 ]);
1452 assert_eq!(watcher.get_watch_handles(), HashSet::from([subdir2, file]));
1453 }
1454
1455 #[test]
1456 fn write_to_a_hardlink_pointed_to_the_file_in_the_watched_dir_doesnt_trigger_an_event() {
1457 let tmpdir = testdir();
1458 let (mut watcher, mut rx) = watcher();
1459
1460 let subdir = tmpdir.path().join("subdir");
1461 let file = subdir.join("file");
1462 let hardlink = tmpdir.path().join("hardlink");
1463
1464 std::fs::create_dir(&subdir).expect("create");
1465 std::fs::write(&file, "").expect("file");
1466 std::fs::hard_link(&file, &hardlink).expect("hardlink");
1467
1468 watcher.watch_nonrecursively(&subdir);
1469
1470 std::fs::write(&hardlink, "123123").expect("write to the hard link");
1471
1472 let events = rx.iter().collect::<Vec<_>>();
1473 assert!(events.is_empty(), "unexpected events: {events:#?}");
1474 assert_eq!(watcher.get_watch_handles(), HashSet::from([subdir]));
1475 }
1476
1477 #[test]
1478 #[ignore = "see https://github.com/notify-rs/notify/issues/727"]
1479 fn recursive_creation() {
1480 let tmpdir = testdir();
1481 let nested1 = tmpdir.path().join("1");
1482 let nested2 = tmpdir.path().join("1/2");
1483 let nested3 = tmpdir.path().join("1/2/3");
1484 let nested4 = tmpdir.path().join("1/2/3/4");
1485 let nested5 = tmpdir.path().join("1/2/3/4/5");
1486 let nested6 = tmpdir.path().join("1/2/3/4/5/6");
1487 let nested7 = tmpdir.path().join("1/2/3/4/5/6/7");
1488 let nested8 = tmpdir.path().join("1/2/3/4/5/6/7/8");
1489 let nested9 = tmpdir.path().join("1/2/3/4/5/6/7/8/9");
1490
1491 let (mut watcher, mut rx) = watcher();
1492
1493 watcher.watch_recursively(&tmpdir);
1494
1495 std::fs::create_dir_all(&nested9).expect("create_dir_all");
1496 rx.wait_ordered([
1497 expected(&nested1).create_folder(),
1498 expected(&nested2).create_folder(),
1499 expected(&nested3).create_folder(),
1500 expected(&nested4).create_folder(),
1501 expected(&nested5).create_folder(),
1502 expected(&nested6).create_folder(),
1503 expected(&nested7).create_folder(),
1504 expected(&nested8).create_folder(),
1505 expected(&nested9).create_folder(),
1506 ]);
1507 assert_eq!(
1508 watcher.get_watch_handles(),
1509 HashSet::from([
1510 tmpdir.to_path_buf(),
1511 nested1,
1512 nested2,
1513 nested3,
1514 nested4,
1515 nested5,
1516 nested6,
1517 nested7,
1518 nested8,
1519 nested9
1520 ])
1521 );
1522 }
1523}