Skip to main content

notify/
inotify.rs

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