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, 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
28// The EventLoop will set up a mio::Poll and use it to wait for the following:
29//
30// -  messages telling it what to do
31//
32// -  events telling it that something has happened on one of the watched files.
33
34struct 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, (/* watch_self */ bool, /* is_dir */ bool)>,
44    rename_event: Option<Event>,
45    follow_links: bool,
46}
47
48/// Watcher implementation based on inotify
49#[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    // Run the event loop.
146    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            // Wait for something to happen.
156            match self.poll.poll(&mut events, None) {
157                Err(ref e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => {
158                    // System call was interrupted, we will retry
159                    // TODO: Not covered by tests (to reproduce likely need to setup signal handlers)
160                }
161                Err(e) => panic!("poll failed: {}", e),
162                Ok(()) => {}
163            }
164
165            // Process whatever happened.
166            for event in &events {
167                self.handle_event(event);
168            }
169
170            // Stop, if we're done.
171            if !self.running {
172                break;
173            }
174        }
175    }
176
177    // Handle a single event.
178    fn handle_event(&mut self, event: &mio::event::Event) {
179        match event.token() {
180            MESSAGE => {
181                // The channel is readable - handle messages.
182                self.handle_messages()
183            }
184            INOTIFY => {
185                // inotify has something to tell us.
186                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            // Read all buffers available.
237            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(); // unwrap is safe because `rename_event` must be set at this point
300                                    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                                // TODO stat the path and get to new path
329                                // - emit To and Both events
330                                // - change prefix for further events
331                            }
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                        // All events read. Break out.
433                        if num_events == 0 {
434                            break;
435                        }
436                    }
437                    Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
438                        // No events read. Break out.
439                        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                // The handler should be notified if we have reached the limit.
458                // Otherwise, the user might expect that a recursive watch
459                // is continuing to work correctly, but it's not.
460                if let ErrorKind::MaxFilesWatch = add_watch_error.kind {
461                    self.event_handler.handle_event(Err(add_watch_error));
462
463                    // After that kind of a error we should stop adding watches,
464                    // because the limit has already reached and all next calls
465                    // will return us only the same error.
466                    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            // upgrade to recursive
483            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            // If the watch is not recursive, or if we determine (by stat'ing the path to get its
494            // metadata) that the watched path is not a directory, add a single path watch.
495            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            // if upgrade to watch self is not needed
535            && (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                        // do not report inotify limits as "no more space" on linux #266
567                        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
643/// return `DirEntry` when it is a directory
644fn 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        // we expect the event loop to live and reply => unwraps must not panic
678        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        // we expect the event loop to live and reply => unwraps must not panic
694        self.channel.send(msg).unwrap();
695        self.waker.wake().unwrap();
696        rx.recv().unwrap()
697    }
698}
699
700impl Watcher for INotifyWatcher {
701    /// Create a new watcher.
702    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        // we expect the event loop to live => unwrap must not panic
739        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    /// Runs manually.
798    ///
799    /// * Save actual value of the limit: `MAX_USER_WATCHES=$(sysctl -n fs.inotify.max_user_watches)`
800    /// * Run the test.
801    /// * Set the limit to 0: `sudo sysctl fs.inotify.max_user_watches=0` while test is running
802    /// * Wait for the test to complete
803    /// * Restore the limit `sudo sysctl fs.inotify.max_user_watches=$MAX_USER_WATCHES`
804    #[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        // give the time to set the limit
834        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    /// https://github.com/notify-rs/notify/issues/678
858    #[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        // TODO: https://github.com/rolldown/notify/issues/8
1389        // watcher.watcher.unwatch(&subdir).expect("unwatch");
1390
1391        // std::fs::write(&file, b"123").expect("write");
1392
1393        // std::fs::remove_dir_all(&subdir).expect("remove_dir_all");
1394
1395        // rx.wait_ordered_exact([
1396        //     expected(&subdir).access_open_any().optional(),
1397        //     expected(&subdir).remove_folder(),
1398        // ])
1399        // .ensure_no_tail();
1400    }
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}