Skip to main content

notify/
inotify.rs

1//! Inotify watcher implementation for Linux, Android, and FreeBSD 14.5+.
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::{
9    Config, Error, ErrorKind, EventHandler, RecursiveMode, Result, WatchPathConfig, Watcher,
10};
11use crate::paths::{
12    WalkRoots, WatchMetadata, WatchPath, absolute_path, is_preserved_watch_root,
13    preserved_watch_mode, preserved_watch_roots, recursive_user_watch_ancestor, reported_path,
14};
15use crate::{BoundSender, Receiver, Sender, bounded, unbounded};
16use inotify as inotify_sys;
17use inotify_sys::{EventMask, Inotify, WatchDescriptor, WatchMask};
18use notify_types::event::EventKindMask;
19use std::collections::{BTreeMap, HashMap, HashSet};
20use std::ffi::OsStr;
21use std::fs::{Metadata, metadata, read_dir, symlink_metadata};
22use std::os::unix::fs::MetadataExt;
23use std::os::unix::io::AsRawFd;
24use std::path::{Path, PathBuf};
25use std::sync::Arc;
26use std::thread;
27
28const INOTIFY: mio::Token = mio::Token(0);
29const MESSAGE: mio::Token = mio::Token(1);
30
31/// Flags for a single `inotify_add_watch` call, not event kinds. Never store them in
32/// [`Watch::watch_mask`], which is merged into later adds for the same path.
33const RESOLUTION_FLAGS: WatchMask = WatchMask::DONT_FOLLOW
34    .union(WatchMask::MASK_ADD)
35    .union(WatchMask::MASK_CREATE)
36    .union(WatchMask::ONLYDIR);
37
38/// Convert an EventKindMask to the corresponding inotify WatchMask.
39///
40/// When `is_recursive` is true, CREATE and MOVED_TO are always included
41/// to enable tracking of newly created subdirectories.
42fn event_kind_mask_to_watch_mask(mask: EventKindMask, is_recursive: bool) -> WatchMask {
43    let mut watch_mask = WatchMask::empty();
44
45    if is_recursive {
46        watch_mask |= WatchMask::CREATE | WatchMask::MOVED_TO;
47    }
48
49    if mask.intersects(EventKindMask::CREATE) {
50        watch_mask |= WatchMask::CREATE | WatchMask::MOVED_TO;
51    }
52
53    if mask.intersects(EventKindMask::REMOVE) {
54        watch_mask |= WatchMask::DELETE | WatchMask::MOVED_FROM;
55    }
56
57    if mask.intersects(EventKindMask::MODIFY_DATA) {
58        // Note: CLOSE_WRITE is intentionally NOT included here because it generates
59        // Access(Close(Write)) events, not Modify events. Users who want CLOSE_WRITE
60        // events should use ACCESS_CLOSE.
61        watch_mask |= WatchMask::MODIFY;
62    }
63
64    if mask.intersects(EventKindMask::MODIFY_META) {
65        watch_mask |= WatchMask::ATTRIB;
66    }
67
68    if mask.intersects(EventKindMask::MODIFY_NAME) {
69        watch_mask |= WatchMask::MOVE_SELF;
70    }
71
72    if mask.intersects(EventKindMask::ACCESS_OPEN) {
73        watch_mask |= WatchMask::OPEN;
74    }
75
76    if mask.intersects(EventKindMask::ACCESS_CLOSE) {
77        watch_mask |= WatchMask::CLOSE_WRITE;
78    }
79
80    if mask.intersects(EventKindMask::ACCESS_CLOSE_NOWRITE) {
81        watch_mask |= WatchMask::CLOSE_NOWRITE;
82    }
83
84    watch_mask
85}
86
87// The EventLoop will set up a mio::Poll and use it to wait for the following:
88//
89// -  messages telling it what to do
90//
91// -  events telling it that something has happened on one of the watched files.
92
93struct EventLoop {
94    running: bool,
95    poll: mio::Poll,
96    event_loop_waker: Arc<mio::Waker>,
97    event_loop_tx: Sender<EventLoopMsg>,
98    event_loop_rx: Receiver<EventLoopMsg>,
99    inotify: Option<Inotify>,
100    event_handler: Box<dyn EventHandler>,
101    /// Absolute path -> inotify descriptor and watch metadata.
102    watches: HashMap<PathBuf, Watch>,
103    paths: HashMap<WatchDescriptor, PathBuf>,
104    rename_event: Option<Event>,
105    follow_links: bool,
106    event_kind_mask: EventKindMask,
107}
108
109struct Watch {
110    watch_descriptor: WatchDescriptor,
111    watch_mask: WatchMask,
112    is_dir: bool,
113    dereference: bool,
114    metadata: WatchMetadata,
115}
116
117/// Watcher implementation based on inotify
118#[derive(Debug)]
119pub struct INotifyWatcher {
120    channel: Sender<EventLoopMsg>,
121    waker: Arc<mio::Waker>,
122}
123
124enum EventLoopMsg {
125    AddWatch(WatchPath, WatchPathConfig, Sender<Result<()>>),
126    RemoveWatch(PathBuf, Sender<Result<()>>),
127    GetWatchedPaths(Sender<Vec<(PathBuf, RecursiveMode)>>),
128    Shutdown,
129    Configure(Config, BoundSender<Result<bool>>),
130}
131
132#[inline]
133fn watch_metadata(path: &Path, dereference: bool) -> std::io::Result<Metadata> {
134    if dereference {
135        metadata(path)
136    } else {
137        symlink_metadata(path)
138    }
139}
140
141#[inline]
142fn add_watch_by_event(
143    path: &PathBuf,
144    event: &inotify_sys::Event<&OsStr>,
145    watches: &HashMap<PathBuf, Watch>,
146    add_watches: &mut Vec<WatchPath>,
147) {
148    if event.mask.contains(EventMask::ISDIR) {
149        if let Some(parent_path) = path.parent() {
150            if let Some(watch) = watches.get(parent_path) {
151                if watch.metadata.is_recursive {
152                    add_watches.push(WatchPath::from_parts(
153                        path.to_owned(),
154                        reported_path(parent_path, &watch.metadata.reported_path, path),
155                    ));
156                }
157            }
158        }
159    }
160}
161
162/// Queue `path` for removal, if it is watched and `descriptor` still matches its watch.
163#[inline]
164fn queue_watch_removal(
165    path: &Path,
166    descriptor: Option<&WatchDescriptor>,
167    watches: &HashMap<PathBuf, Watch>,
168    remove_watches: &mut BTreeMap<PathBuf, WatchRemoval>,
169    removal: WatchRemoval,
170) {
171    let watched = watches
172        .get(path)
173        .is_some_and(|watch| descriptor.is_none_or(|wd| &watch.watch_descriptor == wd));
174    if watched {
175        let pending = remove_watches.entry(path.to_owned()).or_insert(removal);
176        *pending = (*pending).max(removal);
177    }
178}
179
180/// Variant order is load-bearing: coalescing keeps the greater of two pending removals.
181#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
182enum WatchRemoval {
183    /// The watch may still be installed, so remove it from inotify as well as local state.
184    WithOsCall,
185    /// The kernel removed the event's root descriptor, but descendant descriptors may still be
186    /// installed if their filesystem objects survived, for example after being moved elsewhere.
187    DescriptorAlreadyRemoved,
188}
189
190#[inline]
191fn unmount_event(path: PathBuf) -> Event {
192    Event::new(EventKind::Remove(RemoveKind::Other))
193        .add_path(path)
194        .set_info("unmount")
195}
196
197impl EventLoop {
198    pub fn new(
199        inotify: Inotify,
200        event_handler: Box<dyn EventHandler>,
201        config: &Config,
202    ) -> Result<Self> {
203        let (event_loop_tx, event_loop_rx) = unbounded::<EventLoopMsg>();
204        let poll = mio::Poll::new()?;
205
206        let event_loop_waker = Arc::new(mio::Waker::new(poll.registry(), MESSAGE)?);
207
208        let inotify_fd = inotify.as_raw_fd();
209        let mut evented_inotify = mio::unix::SourceFd(&inotify_fd);
210        poll.registry()
211            .register(&mut evented_inotify, INOTIFY, mio::Interest::READABLE)?;
212
213        let event_loop = EventLoop {
214            running: true,
215            poll,
216            event_loop_waker,
217            event_loop_tx,
218            event_loop_rx,
219            inotify: Some(inotify),
220            event_handler,
221            watches: HashMap::new(),
222            paths: HashMap::new(),
223            rename_event: None,
224            follow_links: config.follow_symlinks(),
225            event_kind_mask: config.event_kinds(),
226        };
227        Ok(event_loop)
228    }
229
230    // Run the event loop.
231    pub fn run(self) {
232        let _ = thread::Builder::new()
233            .name("notify-rs inotify loop".to_string())
234            .spawn(|| self.event_loop_thread());
235    }
236
237    fn event_loop_thread(mut self) {
238        let mut events = mio::Events::with_capacity(16);
239        loop {
240            // Wait for something to happen.
241            match self.poll.poll(&mut events, None) {
242                Err(ref e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => {
243                    // System call was interrupted, we will retry
244                    // TODO: Not covered by tests (to reproduce likely need to setup signal handlers)
245                }
246                Err(e) => panic!("poll failed: {e}"),
247                Ok(()) => {}
248            }
249
250            // Process whatever happened.
251            for event in &events {
252                self.handle_event(event);
253            }
254
255            // Stop, if we're done.
256            if !self.running {
257                break;
258            }
259        }
260    }
261
262    // Handle a single event.
263    fn handle_event(&mut self, event: &mio::event::Event) {
264        match event.token() {
265            MESSAGE => {
266                // The channel is readable - handle messages.
267                self.handle_messages()
268            }
269            INOTIFY => {
270                // inotify has something to tell us.
271                self.handle_inotify()
272            }
273            _ => unreachable!(),
274        }
275    }
276
277    fn handle_messages(&mut self) {
278        while let Ok(msg) = self.event_loop_rx.try_recv() {
279            match msg {
280                EventLoopMsg::AddWatch(path, config, tx) => {
281                    let _ = tx.send(self.add_watch(path, config, true));
282                }
283                EventLoopMsg::RemoveWatch(path, tx) => {
284                    let _ = tx.send(self.remove_watch(path, false));
285                }
286                EventLoopMsg::GetWatchedPaths(tx) => {
287                    let _ = tx.send(
288                        self.watches
289                            .iter()
290                            .filter(|(_path, watch)| watch.metadata.is_user_watch)
291                            .map(|(_path, watch)| {
292                                (
293                                    watch.metadata.reported_path.clone(),
294                                    if watch.metadata.user_is_recursive {
295                                        RecursiveMode::Recursive
296                                    } else {
297                                        RecursiveMode::NonRecursive
298                                    },
299                                )
300                            })
301                            .collect(),
302                    );
303                }
304                EventLoopMsg::Shutdown => {
305                    let _ = self.remove_all_watches();
306                    if let Some(inotify) = self.inotify.take() {
307                        let _ = inotify.close();
308                    }
309                    self.running = false;
310                    break;
311                }
312                EventLoopMsg::Configure(config, tx) => {
313                    self.configure_raw_mode(config, tx);
314                }
315            }
316        }
317    }
318
319    fn configure_raw_mode(&mut self, _config: Config, tx: BoundSender<Result<bool>>) {
320        tx.send(Ok(false))
321            .expect("configuration channel disconnected");
322    }
323
324    fn handle_inotify(&mut self) {
325        let mut add_watches = Vec::new();
326        let mut remove_watches = BTreeMap::new();
327
328        if let Some(ref mut inotify) = self.inotify {
329            let mut buffer = [0; 1024];
330            // Read all buffers available.
331            loop {
332                match inotify.read_events(&mut buffer) {
333                    Ok(events) => {
334                        let mut num_events = 0;
335                        for event in events {
336                            log::trace!("inotify event: {event:?}");
337
338                            num_events += 1;
339                            if event.mask.contains(EventMask::Q_OVERFLOW) {
340                                let ev = Ok(Event::new(EventKind::Other).set_flag(Flag::Rescan));
341                                self.event_handler.handle_event(ev);
342                            }
343
344                            if event.mask.contains(EventMask::IGNORED) {
345                                // The kernel sends IGNORED whenever it removes a descriptor,
346                                // regardless of the configured mask. A replacement watch may
347                                // already occupy the same pathname, hence the descriptor check.
348                                if let Some(path) = self.paths.get(&event.wd).cloned() {
349                                    if self
350                                        .watches
351                                        .get(&path)
352                                        .is_some_and(|watch| watch.watch_descriptor == event.wd)
353                                    {
354                                        queue_watch_removal(
355                                            &path,
356                                            None,
357                                            &self.watches,
358                                            &mut remove_watches,
359                                            WatchRemoval::DescriptorAlreadyRemoved,
360                                        );
361                                    } else {
362                                        self.paths.remove(&event.wd);
363                                    }
364                                }
365                                continue;
366                            }
367
368                            let paths = self.paths.get(&event.wd).and_then(|root| {
369                                self.watches.get(root).map(|watch| match event.name {
370                                    Some(name) => {
371                                        let path = root.join(name);
372                                        let reported_path = reported_path(
373                                            root,
374                                            &watch.metadata.reported_path,
375                                            &path,
376                                        );
377                                        (path, reported_path)
378                                    }
379                                    None => (root.clone(), watch.metadata.reported_path.clone()),
380                                })
381                            });
382
383                            let (path, event_path) = match paths {
384                                Some(paths) => paths,
385                                None => {
386                                    log::debug!("inotify event with unknown descriptor: {event:?}");
387                                    continue;
388                                }
389                            };
390
391                            let mut evs = Vec::new();
392
393                            if event.mask.contains(EventMask::MOVED_FROM) {
394                                queue_watch_removal(
395                                    &path,
396                                    None,
397                                    &self.watches,
398                                    &mut remove_watches,
399                                    WatchRemoval::WithOsCall,
400                                );
401
402                                let event = Event::new(EventKind::Modify(ModifyKind::Name(
403                                    RenameMode::From,
404                                )))
405                                .add_path(event_path.clone())
406                                .set_tracker(event.cookie as usize);
407
408                                self.rename_event = Some(event.clone());
409
410                                evs.push(event);
411                            } else if event.mask.contains(EventMask::MOVED_TO) {
412                                evs.push(
413                                    Event::new(EventKind::Modify(ModifyKind::Name(RenameMode::To)))
414                                        .set_tracker(event.cookie as usize)
415                                        .add_path(event_path.clone()),
416                                );
417
418                                let trackers_match =
419                                    self.rename_event.as_ref().and_then(|e| e.tracker())
420                                        == Some(event.cookie as usize);
421
422                                if trackers_match {
423                                    let rename_event = self.rename_event.take().unwrap(); // unwrap is safe because `rename_event` must be set at this point
424                                    evs.push(
425                                        Event::new(EventKind::Modify(ModifyKind::Name(
426                                            RenameMode::Both,
427                                        )))
428                                        .set_tracker(event.cookie as usize)
429                                        .add_some_path(rename_event.paths.first().cloned())
430                                        .add_path(event_path.clone()),
431                                    );
432                                }
433                                add_watch_by_event(&path, &event, &self.watches, &mut add_watches);
434                            }
435                            if event.mask.contains(EventMask::MOVE_SELF) {
436                                evs.push(
437                                    Event::new(EventKind::Modify(ModifyKind::Name(
438                                        RenameMode::From,
439                                    )))
440                                    .add_path(event_path.clone()),
441                                );
442                                // TODO stat the path and get to new path
443                                // - emit To and Both events
444                                // - change prefix for further events
445                            }
446                            if event.mask.contains(EventMask::CREATE) {
447                                evs.push(
448                                    Event::new(EventKind::Create(
449                                        if event.mask.contains(EventMask::ISDIR) {
450                                            CreateKind::Folder
451                                        } else {
452                                            CreateKind::File
453                                        },
454                                    ))
455                                    .add_path(event_path.clone()),
456                                );
457                                add_watch_by_event(&path, &event, &self.watches, &mut add_watches);
458                            }
459                            if event.mask.contains(EventMask::DELETE) {
460                                evs.push(
461                                    Event::new(EventKind::Remove(
462                                        if event.mask.contains(EventMask::ISDIR) {
463                                            RemoveKind::Folder
464                                        } else {
465                                            RemoveKind::File
466                                        },
467                                    ))
468                                    .add_path(event_path.clone()),
469                                );
470                                queue_watch_removal(
471                                    &path,
472                                    None,
473                                    &self.watches,
474                                    &mut remove_watches,
475                                    WatchRemoval::WithOsCall,
476                                );
477                            }
478                            if event.mask.contains(EventMask::DELETE_SELF) {
479                                let remove_kind = match self.watches.get(&path) {
480                                    Some(watch) if watch.is_dir => RemoveKind::Folder,
481                                    Some(_) => RemoveKind::File,
482                                    None => RemoveKind::Other,
483                                };
484                                evs.push(
485                                    Event::new(EventKind::Remove(remove_kind))
486                                        .add_path(event_path.clone()),
487                                );
488                                // Deleting a watched inode removes its watch in the kernel and
489                                // queues IGNORED, so calling inotify_rm_watch would return EINVAL.
490                                queue_watch_removal(
491                                    &path,
492                                    Some(&event.wd),
493                                    &self.watches,
494                                    &mut remove_watches,
495                                    WatchRemoval::DescriptorAlreadyRemoved,
496                                );
497                            }
498                            if event.mask.contains(EventMask::UNMOUNT) {
499                                evs.push(unmount_event(event_path.clone()));
500                                // The kernel has already removed this watch descriptor and will
501                                // emit IGNORED; clean up internal state without inotify_rm_watch.
502                                // ref. https://www.man7.org/linux/man-pages/man7/inotify.7.html
503                                queue_watch_removal(
504                                    &path,
505                                    Some(&event.wd),
506                                    &self.watches,
507                                    &mut remove_watches,
508                                    WatchRemoval::DescriptorAlreadyRemoved,
509                                );
510                            }
511                            if event.mask.contains(EventMask::MODIFY) {
512                                evs.push(
513                                    Event::new(EventKind::Modify(ModifyKind::Data(
514                                        DataChange::Any,
515                                    )))
516                                    .add_path(event_path.clone()),
517                                );
518                            }
519                            if event.mask.contains(EventMask::CLOSE_WRITE) {
520                                evs.push(
521                                    Event::new(EventKind::Access(AccessKind::Close(
522                                        AccessMode::Write,
523                                    )))
524                                    .add_path(event_path.clone()),
525                                );
526                            }
527                            if event.mask.contains(EventMask::CLOSE_NOWRITE) {
528                                evs.push(
529                                    Event::new(EventKind::Access(AccessKind::Close(
530                                        AccessMode::Read,
531                                    )))
532                                    .add_path(event_path.clone()),
533                                );
534                            }
535                            if event.mask.contains(EventMask::ATTRIB) {
536                                evs.push(
537                                    Event::new(EventKind::Modify(ModifyKind::Metadata(
538                                        MetadataKind::Any,
539                                    )))
540                                    .add_path(event_path.clone()),
541                                );
542                            }
543                            if event.mask.contains(EventMask::OPEN) {
544                                evs.push(
545                                    Event::new(EventKind::Access(AccessKind::Open(
546                                        AccessMode::Any,
547                                    )))
548                                    .add_path(event_path.clone()),
549                                );
550                            }
551
552                            // Filter events based on EventKindMask before delivery
553                            for ev in evs {
554                                if self.event_kind_mask.matches(&ev.kind) {
555                                    self.event_handler.handle_event(Ok(ev));
556                                }
557                            }
558                        }
559
560                        // All events read. Break out.
561                        if num_events == 0 {
562                            break;
563                        }
564                    }
565                    Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
566                        // No events read. Break out.
567                        break;
568                    }
569                    Err(e) => {
570                        self.event_handler.handle_event(Err(Error::io(e)));
571                    }
572                }
573            }
574        }
575
576        // An ancestor sorts before its descendants, so reverse order removes descendants first:
577        // recursive ancestor cleanup then neither repeats syscalls for kernel-removed watches nor
578        // drops the mapping for a live, moved-out descendant.
579        for (path, removal) in remove_watches.into_iter().rev() {
580            if !self.watches.contains_key(&path) {
581                continue;
582            }
583
584            let result = match removal {
585                WatchRemoval::WithOsCall => self.remove_watch(path, true),
586                WatchRemoval::DescriptorAlreadyRemoved => {
587                    self.remove_watch_without_root_os_call(path, true)
588                }
589            };
590            if let Err(err) = result {
591                log::warn!("Unable to remove the path from the watches: {err:?}");
592            }
593        }
594
595        for path in add_watches {
596            let config = WatchPathConfig::new(RecursiveMode::Recursive);
597            if let Err(err) = self.add_watch(path, config, false) {
598                match WalkFailure::of(&err.kind) {
599                    WalkFailure::Skip => {}
600                    WalkFailure::Report => self.event_handler.handle_event(Err(err)),
601                    WalkFailure::Abort => {
602                        self.event_handler.handle_event(Err(err));
603                        break;
604                    }
605                }
606            }
607        }
608    }
609
610    fn add_watch(
611        &mut self,
612        path: WatchPath,
613        config: WatchPathConfig,
614        watch_self: bool,
615    ) -> Result<()> {
616        let is_recursive = config.recursive_mode().is_recursive();
617        // a recursive watch has to resolve the path before it can walk it
618        let dereference = config.dereference_symlinks() || is_recursive;
619        let path_is_dir = watch_metadata(&path.absolute, dereference)
620            .map_err(Error::io_watch)?
621            .is_dir();
622        let requested_is_recursive = is_recursive && path_is_dir;
623        if watch_self {
624            if let Some(watch) = self
625                .watches
626                .get(&path.absolute)
627                .filter(|watch| watch.metadata.is_user_watch)
628            {
629                if watch.metadata.user_is_recursive == requested_is_recursive
630                    && watch.metadata.reported_path == path.requested
631                    && watch.dereference == dereference
632                {
633                    return Ok(());
634                }
635
636                // Rewatching an explicit user watch replaces its requested mode and reported path
637                // instead of merging with the previous metadata. If the current entry also carries
638                // recursive coverage from an ancestor, remember that ancestor before removal so we
639                // can rebuild that inherited coverage below.
640                let inherited_recursive_root =
641                    if !requested_is_recursive && path_is_dir && watch.metadata.is_recursive {
642                        recursive_user_watch_ancestor(
643                            &path.absolute,
644                            self.watches
645                                .iter()
646                                .map(|(path, watch)| (path, &watch.metadata)),
647                        )
648                    } else {
649                        None
650                    };
651                let replaced_path = path.absolute.clone();
652                self.remove_watch(replaced_path.clone(), false)?;
653
654                if let Some((ancestor_path, ancestor_reported_path)) = inherited_recursive_root {
655                    // Removing a directory watch removes its recursively inherited children too.
656                    // Re-add them as non-user watches so the ancestor recursive watch still covers
657                    // this subtree after the user watch is replaced.
658                    let barriers = self.recursive_walk_barriers();
659                    let root = WatchPath::from_parts(ancestor_path, ancestor_reported_path);
660                    let entries = walk_dirs(
661                        replaced_path.clone(),
662                        root.clone(),
663                        self.follow_links,
664                        &barriers,
665                    );
666                    self.add_watches_for_paths(root, entries, true, true, false)?;
667                }
668            } else if self.watches.get(&path.absolute).is_some_and(|watch| {
669                !dereference && !path_is_dir && watch.is_dir && watch.metadata.is_recursive
670            }) {
671                // A recursive walk already followed this link. Remove its inherited subtree before
672                // replacing the root with an explicit watch on the link itself.
673                self.remove_watch(path.absolute.clone(), false)?;
674            }
675        }
676
677        // If the watch is not recursive, or if we determine (by stat'ing the path to get its
678        // metadata) that the watched path is not a directory, add a single path watch.
679        if !requested_is_recursive {
680            return self.add_single_watch(path, false, dereference, true);
681        }
682
683        let barriers = self.recursive_walk_barriers();
684        let entries = walk_dirs(
685            path.absolute.clone(),
686            path.clone(),
687            self.follow_links,
688            &barriers,
689        );
690
691        self.add_watches_for_paths(path, entries, is_recursive, dereference, watch_self)
692    }
693
694    fn recursive_walk_barriers(&self) -> HashSet<PathBuf> {
695        self.watches
696            .iter()
697            .filter(|(_, watch)| {
698                watch.metadata.is_user_watch && !watch.dereference && !watch.is_dir
699            })
700            .map(|(path, _)| path.clone())
701            .collect()
702    }
703
704    fn add_watches_for_paths<I>(
705        &mut self,
706        root: WatchPath,
707        paths: I,
708        is_recursive: bool,
709        dereference: bool,
710        mut watch_self: bool,
711    ) -> Result<()>
712    where
713        I: IntoIterator<Item = Result<WatchPath>>,
714    {
715        let mut last_failed = None;
716        let mut roots = WalkRoots::new(root);
717
718        for path in paths {
719            let path = path.map(|path| {
720                let existing = self
721                    .watches
722                    .get(&path.absolute)
723                    .map(|watch| &watch.metadata);
724                roots.entry(path.absolute, existing)
725            });
726            // entries below the root were reached by following links, so they observe what they
727            // resolved to
728            let entry_dereference = if watch_self { dereference } else { true };
729            // A path the walk could not reach fails the same way one it could not watch does.
730            match path.and_then(|path| {
731                self.add_single_watch(path, is_recursive, entry_dereference, watch_self)
732            }) {
733                Ok(()) => last_failed = None,
734                // The requested path itself failing is the caller's problem.
735                Err(err) if watch_self => return Err(err),
736                // A directory that cannot be read cannot be watched either, and the walk reports
737                // it right after the directory itself. Report the pair once. Errors that name no
738                // path are never the same failure twice, so they stay out of this.
739                Err(err) if last_failed.is_some() && last_failed.as_ref() == err.paths.first() => {}
740                Err(err) => {
741                    last_failed = err.paths.first().cloned();
742                    match WalkFailure::of(&err.kind) {
743                        WalkFailure::Skip => {}
744                        WalkFailure::Report => self.event_handler.handle_event(Err(err)),
745                        WalkFailure::Abort => return Err(err),
746                    }
747                }
748            }
749            watch_self = false;
750        }
751
752        Ok(())
753    }
754
755    fn add_single_watch(
756        &mut self,
757        path: WatchPath,
758        is_recursive: bool,
759        requested_dereference: bool,
760        watch_self: bool,
761    ) -> Result<()> {
762        // Build watch mask from configured event kinds for kernel-level filtering
763        let mut watchmask = event_kind_mask_to_watch_mask(self.event_kind_mask, is_recursive);
764
765        if watch_self {
766            watchmask.insert(WatchMask::DELETE_SELF);
767            watchmask.insert(WatchMask::MOVE_SELF);
768        }
769
770        let existing_watch = self.watches.get(&path.absolute);
771        // an explicit watch decides for its own path, a walk must not overrule it #255
772        let dereference = if watch_self {
773            requested_dereference
774        } else {
775            existing_watch
776                .filter(|watch| watch.metadata.is_user_watch)
777                .map_or(requested_dereference, |watch| watch.dereference)
778        };
779        let previous_descriptor = existing_watch.map(|watch| watch.watch_descriptor.clone());
780
781        let mut add_mask = watchmask;
782        if let Some(watch) = existing_watch {
783            watchmask.insert(watch.watch_mask);
784            add_mask = watchmask | WatchMask::MASK_ADD;
785        }
786        if !dereference {
787            add_mask.insert(WatchMask::DONT_FOLLOW);
788        }
789
790        if let Some(ref mut inotify) = self.inotify {
791            log::trace!("adding inotify watch: {}", path.absolute.display());
792
793            match inotify.watches().add(&path.absolute, add_mask) {
794                Err(e) => {
795                    Err(if e.raw_os_error() == Some(libc::ENOSPC) {
796                        // do not report inotify limits as "no more space" on linux #266
797                        Error::new(ErrorKind::MaxFilesWatch)
798                    } else if e.kind() == std::io::ErrorKind::NotFound {
799                        Error::new(ErrorKind::PathNotFound)
800                    } else {
801                        Error::io(e)
802                    }
803                    .add_path(path.requested))
804                }
805                Ok(w) => {
806                    debug_assert!(!watchmask.intersects(RESOLUTION_FLAGS));
807                    let is_dir = match watch_metadata(&path.absolute, dereference) {
808                        Ok(metadata) => metadata.is_dir(),
809                        Err(e) => {
810                            // Avoid leaking an inotify watch if we can't stat after adding it.
811                            // This can happen due to racy deletions.
812                            let _ = inotify.watches().remove(w.clone());
813                            return Err(Error::io_watch(e).add_path(path.requested));
814                        }
815                    };
816                    let metadata = WatchMetadata::new(
817                        &path,
818                        is_recursive,
819                        watch_self,
820                        existing_watch.map(|watch| &watch.metadata),
821                    );
822
823                    // re-resolving a path can land on a different inode, so release the old
824                    // descriptor or it keeps reporting under this path
825                    if let Some(previous) = previous_descriptor.filter(|previous| *previous != w) {
826                        // a walk that followed the link shares this descriptor #255
827                        let still_watched = self
828                            .watches
829                            .iter()
830                            .find(|(other, watch)| {
831                                *other != &path.absolute && watch.watch_descriptor == previous
832                            })
833                            .map(|(other, _)| other.clone());
834                        match still_watched {
835                            Some(other) => {
836                                self.paths.insert(previous, other);
837                            }
838                            None => {
839                                self.paths.remove(&previous);
840                                Self::remove_single_descriptor(&mut inotify.watches(), previous);
841                            }
842                        }
843                    }
844
845                    self.watches.insert(
846                        path.absolute.clone(),
847                        Watch {
848                            watch_descriptor: w.clone(),
849                            watch_mask: watchmask,
850                            is_dir,
851                            dereference,
852                            metadata,
853                        },
854                    );
855                    self.paths.insert(w, path.absolute);
856                    Ok(())
857                }
858            }
859        } else {
860            Ok(())
861        }
862    }
863
864    fn remove_watch(&mut self, path: PathBuf, remove_recursive: bool) -> Result<()> {
865        let preserved_roots = preserved_watch_roots(
866            &path,
867            remove_recursive,
868            self.watches
869                .iter()
870                .map(|(path, watch)| (path, &watch.metadata)),
871        );
872
873        let watch = self
874            .watches
875            .remove(&path)
876            .ok_or_else(|| Error::watch_not_found().add_path(path.clone()))?;
877        log::trace!("removing inotify watch for {path:?}, remove_recursive: {remove_recursive:?}");
878
879        let mut removed_descriptors = vec![watch.watch_descriptor];
880        if watch.metadata.is_recursive || remove_recursive {
881            let mut remove_list = Vec::new();
882            let mut reset_list = Vec::new();
883            for candidate in self.watches.keys() {
884                if candidate.starts_with(&path) {
885                    if let Some(user_is_recursive) =
886                        preserved_watch_mode(candidate, &preserved_roots)
887                    {
888                        if !user_is_recursive
889                            || is_preserved_watch_root(candidate, &preserved_roots)
890                        {
891                            reset_list.push(candidate.clone());
892                        }
893                        continue;
894                    }
895
896                    remove_list.push(candidate.clone());
897                }
898            }
899
900            for path in remove_list {
901                if let Some(watch) = self.watches.remove(&path) {
902                    removed_descriptors.push(watch.watch_descriptor);
903                }
904            }
905            for path in reset_list {
906                if let Some(watch) = self.watches.get_mut(&path) {
907                    watch.metadata.is_recursive = watch.metadata.user_is_recursive;
908                }
909            }
910        }
911
912        self.release_descriptors(removed_descriptors);
913        Ok(())
914    }
915
916    /// Remove descriptors that no remaining logical path uses, and repoint shared descriptors.
917    fn release_descriptors<I>(&mut self, descriptors: I)
918    where
919        I: IntoIterator<Item = WatchDescriptor>,
920    {
921        let descriptors: HashSet<_> = descriptors.into_iter().collect();
922        let mut remaining_owners = HashMap::new();
923        for (path, watch) in &self.watches {
924            if descriptors.contains(&watch.watch_descriptor) {
925                remaining_owners
926                    .entry(watch.watch_descriptor.clone())
927                    .or_insert_with(|| path.clone());
928            }
929        }
930
931        for descriptor in descriptors {
932            if let Some(path) = remaining_owners.remove(&descriptor) {
933                self.paths.insert(descriptor, path);
934                continue;
935            }
936
937            self.paths.remove(&descriptor);
938            if let Some(ref mut inotify) = self.inotify {
939                Self::remove_single_descriptor(&mut inotify.watches(), descriptor);
940            }
941        }
942    }
943
944    /// Remove a root watch after the kernel has already invalidated its descriptor.
945    ///
946    /// Only the root skips `inotify_rm_watch`. Recursive descendants are removed individually
947    /// because their filesystem objects—and therefore their descriptors—may still be live.
948    fn remove_watch_without_root_os_call(
949        &mut self,
950        path: PathBuf,
951        remove_recursive: bool,
952    ) -> Result<()> {
953        let preserved_roots = preserved_watch_roots(
954            &path,
955            remove_recursive,
956            self.watches
957                .iter()
958                .map(|(path, watch)| (path, &watch.metadata)),
959        );
960
961        match self.watches.remove(&path) {
962            None => return Err(Error::watch_not_found().add_path(path)),
963            Some(watch) => {
964                self.paths.remove(&watch.watch_descriptor);
965
966                if watch.metadata.is_recursive || remove_recursive {
967                    let mut inotify_watches =
968                        self.inotify.as_mut().map(|inotify| inotify.watches());
969                    let mut remove_list = Vec::new();
970                    let mut reset_list = Vec::new();
971                    for (w, p) in &self.paths {
972                        if p.starts_with(&path) {
973                            if let Some(user_is_recursive) =
974                                preserved_watch_mode(p, &preserved_roots)
975                            {
976                                if !user_is_recursive
977                                    || is_preserved_watch_root(p, &preserved_roots)
978                                {
979                                    reset_list.push(p.clone());
980                                }
981                                continue;
982                            }
983
984                            // The kernel removing the root does not prove that this descendant's
985                            // descriptor was removed, for example if it was moved elsewhere.
986                            if let Some(inotify_watches) = inotify_watches.as_mut() {
987                                Self::remove_single_descriptor(inotify_watches, w.clone());
988                            }
989                            self.watches.remove(p);
990                            remove_list.push(w.clone());
991                        }
992                    }
993                    for w in remove_list {
994                        self.paths.remove(&w);
995                    }
996                    for p in reset_list {
997                        if let Some(watch) = self.watches.get_mut(&p) {
998                            watch.metadata.is_recursive = watch.metadata.user_is_recursive;
999                        }
1000                    }
1001                }
1002            }
1003        }
1004
1005        Ok(())
1006    }
1007
1008    /// Remove a descriptor while tolerating the deletion race documented by inotify.
1009    ///
1010    /// Linux may invalidate a watch before its queued deletion event is handled. In that case
1011    /// `inotify_rm_watch` returns EINVAL, but the requested end state has already been reached.
1012    /// Other errors indicate an unexpected descriptor or inotify-instance failure and stay visible.
1013    fn remove_single_descriptor(watches: &mut inotify::Watches, wd: WatchDescriptor) {
1014        if let Err(err) = watches.remove(wd) {
1015            if err.raw_os_error() == Some(libc::EINVAL) {
1016                log::trace!("watch descriptor was already removed from inotify: {err:?}");
1017            } else {
1018                log::warn!("unable to remove watch descriptor from inotify: {err:?}");
1019            }
1020        }
1021    }
1022
1023    fn remove_all_watches(&mut self) -> Result<()> {
1024        if let Some(ref mut inotify) = self.inotify {
1025            let mut inotify_watches = inotify.watches();
1026            for (w, p) in &self.paths {
1027                inotify_watches
1028                    .remove(w.clone())
1029                    .map_err(|e| Error::io(e).add_path(p.into()))?;
1030            }
1031            self.watches.clear();
1032            self.paths.clear();
1033        }
1034        Ok(())
1035    }
1036}
1037
1038/// What a failed watch installation means for the rest of a recursive walk.
1039#[derive(Debug, PartialEq, Eq)]
1040enum WalkFailure {
1041    /// Nothing was lost, so the walk carries on.
1042    Skip,
1043    /// This path errored, but the rest of the tree will still be walked.
1044    Report,
1045    /// Every directory not reached yet would fail the same way, so the walk ends here.
1046    Abort,
1047}
1048
1049impl WalkFailure {
1050    fn of(kind: &ErrorKind) -> Self {
1051        match kind {
1052            // TOCTOU: a directory can disappear between being listed and being watched.
1053            ErrorKind::PathNotFound => Self::Skip,
1054            ErrorKind::MaxFilesWatch => Self::Abort,
1055            _ => Self::Report,
1056        }
1057    }
1058}
1059
1060/// Yields the directories to watch below `from`, and the errors that hid part of the tree.
1061///
1062/// Each directory is named the way `root` was asked for, and `barriers` are the paths the walk
1063/// must not descend into. Dropping the walk's errors would leave whole subtrees unwatched with
1064/// nothing to notice, the same silence a failed watch used to cause.
1065fn walk_dirs(
1066    from: PathBuf,
1067    root: WatchPath,
1068    follow_links: bool,
1069    barriers: &HashSet<PathBuf>,
1070) -> impl Iterator<Item = Result<WatchPath>> {
1071    enum Step {
1072        Visit(WatchPath),
1073        Read(WatchPath),
1074        Leave,
1075        Error(Error),
1076    }
1077
1078    let mut pending = vec![Step::Visit(root.child(from))];
1079    let mut ancestors = Vec::new();
1080
1081    std::iter::from_fn(move || {
1082        loop {
1083            match pending.pop()? {
1084                Step::Visit(path) => {
1085                    // Exclude links before resolving them: resolution errors may not name the link.
1086                    if barriers.contains(&path.absolute) {
1087                        continue;
1088                    }
1089                    // Recheck descendant types after enumeration; the walk root is always followed.
1090                    let mut metadata = match symlink_metadata(&path.absolute) {
1091                        Ok(metadata) => metadata,
1092                        Err(err) => {
1093                            return Some(Err(Error::io_watch(err).add_path(path.requested)));
1094                        }
1095                    };
1096                    let is_symlink = metadata.file_type().is_symlink();
1097                    if is_symlink && (follow_links || ancestors.is_empty()) {
1098                        metadata = match std::fs::metadata(&path.absolute) {
1099                            Ok(metadata) => metadata,
1100                            Err(err) => {
1101                                return Some(Err(Error::io_watch(err).add_path(path.requested)));
1102                            }
1103                        };
1104                    }
1105                    if !metadata.is_dir() {
1106                        continue;
1107                    }
1108                    let identity = (metadata.dev(), metadata.ino());
1109                    // A bind mount can share an ancestor's inode without forming a symlink loop.
1110                    if is_symlink && ancestors.contains(&identity) {
1111                        return Some(Err(
1112                            Error::generic("symbolic link loop").add_path(path.requested)
1113                        ));
1114                    }
1115                    ancestors.push(identity);
1116                    pending.push(Step::Leave);
1117                    // Yield the directory before reading its contents so its watch is installed first.
1118                    pending.push(Step::Read(path.clone()));
1119                    return Some(Ok(path));
1120                }
1121                Step::Read(path) => {
1122                    let entries = match read_dir(&path.absolute) {
1123                        Ok(entries) => entries,
1124                        Err(err) => {
1125                            return Some(Err(Error::io_watch(err).add_path(path.requested)));
1126                        }
1127                    };
1128                    let start = pending.len();
1129                    // Buffer paths, not directory handles, so deep trees do not exhaust descriptors.
1130                    for entry in entries {
1131                        let entry = match entry {
1132                            Ok(entry) => entry,
1133                            Err(err) => {
1134                                pending.push(Step::Error(
1135                                    Error::io_watch(err).add_path(path.requested.clone()),
1136                                ));
1137                                continue;
1138                            }
1139                        };
1140                        let absolute = entry.path();
1141                        if barriers.contains(&absolute) {
1142                            continue;
1143                        }
1144                        match entry.file_type() {
1145                            Ok(kind) if kind.is_dir() || (follow_links && kind.is_symlink()) => {
1146                                pending.push(Step::Visit(root.child(absolute)));
1147                            }
1148                            Ok(_) => {}
1149                            Err(err) => pending.push(Step::Error(
1150                                Error::io_watch(err).add_path(root.child(absolute).requested),
1151                            )),
1152                        }
1153                    }
1154                    pending[start..].reverse();
1155                }
1156                Step::Leave => {
1157                    ancestors.pop();
1158                }
1159                Step::Error(err) => return Some(Err(err)),
1160            }
1161        }
1162    })
1163}
1164
1165impl INotifyWatcher {
1166    fn from_event_handler(event_handler: Box<dyn EventHandler>, config: &Config) -> Result<Self> {
1167        let inotify = Inotify::init()?;
1168        let event_loop = EventLoop::new(inotify, event_handler, config)?;
1169        let channel = event_loop.event_loop_tx.clone();
1170        let waker = event_loop.event_loop_waker.clone();
1171        event_loop.run();
1172        Ok(INotifyWatcher { channel, waker })
1173    }
1174
1175    fn watch_inner(&mut self, path: &Path, config: WatchPathConfig) -> Result<()> {
1176        let pb = WatchPath::new(path)?;
1177        let (tx, rx) = unbounded();
1178        let msg = EventLoopMsg::AddWatch(pb, config, tx);
1179
1180        self.channel.send(msg)?;
1181        self.waker.wake()?;
1182        rx.recv().map_err(Error::from)?
1183    }
1184
1185    fn unwatch_inner(&mut self, path: &Path) -> Result<()> {
1186        let pb = absolute_path(path)?;
1187        let (tx, rx) = unbounded();
1188        let msg = EventLoopMsg::RemoveWatch(pb, tx);
1189
1190        self.channel.send(msg)?;
1191        self.waker.wake()?;
1192        rx.recv().map_err(Error::from)?
1193    }
1194
1195    fn watched_paths_inner(&self) -> Result<Vec<(PathBuf, RecursiveMode)>> {
1196        let (tx, rx) = unbounded();
1197        self.channel.send(EventLoopMsg::GetWatchedPaths(tx))?;
1198        self.waker.wake()?;
1199        rx.recv().map_err(Error::from)
1200    }
1201}
1202
1203impl Watcher for INotifyWatcher {
1204    /// Create a new watcher.
1205    fn new<F: EventHandler>(event_handler: F, config: Config) -> Result<Self> {
1206        Self::from_event_handler(Box::new(event_handler), &config)
1207    }
1208
1209    fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
1210        self.watch_inner(path, WatchPathConfig::new(recursive_mode))
1211    }
1212
1213    fn watch_with(&mut self, path: &Path, config: WatchPathConfig) -> Result<()> {
1214        self.watch_inner(path, config)
1215    }
1216
1217    fn unwatch(&mut self, path: &Path) -> Result<()> {
1218        self.unwatch_inner(path)
1219    }
1220
1221    fn configure(&mut self, config: Config) -> Result<bool> {
1222        let (tx, rx) = bounded(1);
1223        self.channel.send(EventLoopMsg::Configure(config, tx))?;
1224        self.waker.wake()?;
1225        rx.recv()?
1226    }
1227
1228    fn watched_paths(&self) -> Result<Vec<(PathBuf, RecursiveMode)>> {
1229        self.watched_paths_inner()
1230    }
1231
1232    fn kind() -> crate::WatcherKind {
1233        crate::WatcherKind::Inotify
1234    }
1235}
1236
1237impl Drop for INotifyWatcher {
1238    fn drop(&mut self) {
1239        // we expect the event loop to live => unwrap must not panic
1240        self.channel.send(EventLoopMsg::Shutdown).unwrap();
1241        self.waker.wake().unwrap();
1242    }
1243}
1244
1245#[cfg(test)]
1246mod tests {
1247    use std::{
1248        path::{Path, PathBuf},
1249        sync::{Arc, atomic::AtomicBool, mpsc},
1250        thread::{self, available_parallelism},
1251        time::Duration,
1252    };
1253
1254    use super::inotify_sys::WatchMask;
1255    use super::{
1256        Config, Error, ErrorKind, Event, EventKind, EventLoop, INotifyWatcher, RecursiveMode,
1257        Result, WatchPath, WatchPathConfig, Watcher,
1258    };
1259    use notify_types::event::{EventKindMask, RemoveKind};
1260
1261    use crate::test::*;
1262
1263    /// Only data changes, so access events do not disturb exact assertions.
1264    fn watcher_with_data_events() -> (TestWatcher<INotifyWatcher>, Receiver) {
1265        channel_with_config(
1266            ChannelConfig::default().with_watcher_config(
1267                Config::default().with_event_kinds(EventKindMask::MODIFY_DATA),
1268            ),
1269        )
1270    }
1271
1272    fn recursive_watch() -> WatchPathConfig {
1273        WatchPathConfig::new(RecursiveMode::Recursive)
1274    }
1275
1276    fn non_recursive_watch() -> WatchPathConfig {
1277        WatchPathConfig::new(RecursiveMode::NonRecursive)
1278    }
1279
1280    fn watcher() -> (TestWatcher<INotifyWatcher>, Receiver) {
1281        channel()
1282    }
1283
1284    fn test_event_loop_with_config(config: &Config) -> EventLoop {
1285        let inotify = super::inotify_sys::Inotify::init().unwrap();
1286        EventLoop::new(inotify, Box::new(|_| {}), config).unwrap()
1287    }
1288
1289    fn test_event_loop() -> EventLoop {
1290        test_event_loop_with_config(&Config::default())
1291    }
1292
1293    /// Create a watcher configured to receive ALL events including Access events.
1294    /// Use this for tests that verify Access event behavior.
1295    fn watcher_with_all_events() -> (TestWatcher<INotifyWatcher>, Receiver) {
1296        channel_with_config(
1297            ChannelConfig::default()
1298                .with_timeout(std::time::Duration::from_secs(1))
1299                .with_watcher_config(Config::default().with_event_kinds(EventKindMask::ALL)),
1300        )
1301    }
1302
1303    #[test]
1304    fn inotify_watcher_is_send_and_sync() {
1305        fn check<T: Send + Sync>() {}
1306        check::<INotifyWatcher>();
1307    }
1308
1309    #[test]
1310    fn native_error_type_on_missing_path() {
1311        let mut watcher = INotifyWatcher::new(|_| {}, Config::default()).unwrap();
1312
1313        let result = watcher.watch(
1314            &PathBuf::from("/some/non/existant/path"),
1315            RecursiveMode::NonRecursive,
1316        );
1317
1318        assert!(matches!(
1319            result,
1320            Err(Error {
1321                paths: _,
1322                kind: ErrorKind::PathNotFound
1323            })
1324        ))
1325    }
1326
1327    #[test]
1328    fn stored_watch_mask_keeps_no_resolution_flags() {
1329        let tmpdir = tempfile::tempdir().unwrap();
1330        let root = tmpdir.path().to_path_buf();
1331        let child = root.join("child");
1332        std::fs::create_dir(&child).unwrap();
1333
1334        let inotify = super::inotify_sys::Inotify::init().unwrap();
1335        let mut event_loop = EventLoop::new(inotify, Box::new(|_| {}), &Config::default()).unwrap();
1336
1337        event_loop
1338            .add_watch(WatchPath::new(&root).unwrap(), recursive_watch(), true)
1339            .expect("watch recursively");
1340        event_loop
1341            .add_watch(WatchPath::new(&root).unwrap(), non_recursive_watch(), true)
1342            .expect("rewatch non-recursively");
1343
1344        for (path, watch) in &event_loop.watches {
1345            assert!(
1346                !watch.watch_mask.intersects(super::RESOLUTION_FLAGS),
1347                "{path:?} stored {:?}",
1348                watch.watch_mask
1349            );
1350        }
1351    }
1352
1353    // Regression test for https://github.com/notify-rs/notify/issues/579.
1354    #[test]
1355    fn recursive_watch_ignores_missing_subdir_during_initial_scan() {
1356        use std::fs;
1357
1358        let tmpdir = tempfile::tempdir().unwrap();
1359        let root = tmpdir.path().to_path_buf();
1360        let disappearing = root.join("disappearing");
1361        fs::create_dir(&disappearing).unwrap();
1362        fs::remove_dir_all(&disappearing).unwrap();
1363
1364        let mut event_loop = test_event_loop();
1365
1366        // Simulate the TOCTOU: we *intend* to watch a subdirectory discovered during initial scan,
1367        // but it's already gone by the time we call `inotify_add_watch`.
1368        let result = event_loop.add_watches_for_paths(
1369            WatchPath::new(&root).unwrap(),
1370            [root, disappearing]
1371                .into_iter()
1372                .map(|path| Ok(WatchPath::new(&path).unwrap())),
1373            true,
1374            true,
1375            true,
1376        );
1377        assert!(
1378            result.is_ok(),
1379            "expected recursive watch to succeed, got: {result:?}"
1380        );
1381    }
1382
1383    /// The policy every failed watch in a recursive walk goes through.
1384    #[test]
1385    fn walk_failure_policy() {
1386        use super::WalkFailure;
1387        use std::io;
1388
1389        assert_eq!(WalkFailure::of(&ErrorKind::PathNotFound), WalkFailure::Skip);
1390        assert_eq!(
1391            WalkFailure::of(&ErrorKind::MaxFilesWatch),
1392            WalkFailure::Abort
1393        );
1394        assert_eq!(
1395            WalkFailure::of(&ErrorKind::Io(io::ErrorKind::PermissionDenied.into())),
1396            WalkFailure::Report
1397        );
1398    }
1399
1400    /// Create a directory `inotify_add_watch` refuses; false if it stayed readable
1401    /// (happens when running as root)
1402    fn unwatchable_dir(path: &Path) -> bool {
1403        use std::fs;
1404        use std::os::unix::fs::PermissionsExt;
1405
1406        fs::create_dir(path).unwrap();
1407        fs::set_permissions(path, fs::Permissions::from_mode(0o000)).unwrap();
1408        fs::read_dir(path).is_err()
1409    }
1410
1411    /// Undo `unwatchable_dir`, so that the tempdir can be removed again.
1412    fn make_readable(path: &Path) {
1413        use std::fs;
1414        use std::os::unix::fs::PermissionsExt;
1415
1416        fs::set_permissions(path, fs::Permissions::from_mode(0o755)).unwrap();
1417    }
1418
1419    /// Wait for an event the predicate accepts, repeating `poke` until one arrives. The event
1420    /// loop installs watches in the background, so a change made once can be made before the
1421    /// watch that would report it exists.
1422    fn wait_for(
1423        rx: &mpsc::Receiver<Result<Event>>,
1424        poke: impl Fn(),
1425        accept: impl Fn(&Result<Event>) -> bool,
1426    ) -> Option<Result<Event>> {
1427        use std::time::Instant;
1428
1429        let deadline = Instant::now() + Duration::from_secs(5);
1430        while Instant::now() < deadline {
1431            poke();
1432            if let Ok(event) = rx.recv_timeout(Duration::from_millis(200)) {
1433                if accept(&event) {
1434                    return Some(event);
1435                }
1436            }
1437        }
1438        None
1439    }
1440
1441    /// A directory that cannot be watched must not cause the later ones to get ignored.
1442    #[test]
1443    fn recursive_watch_survives_an_unwatchable_subdir() {
1444        use std::fs;
1445        use std::sync::Mutex;
1446
1447        let tmpdir = tempfile::tempdir().unwrap();
1448        let root = tmpdir.path().to_path_buf();
1449        let unwatchable = root.join("unwatchable");
1450        let readable = root.join("readable");
1451        let deeper = readable.join("deeper");
1452        fs::create_dir_all(&deeper).unwrap();
1453        if !unwatchable_dir(&unwatchable) {
1454            return; // running as root, which can watch a directory it cannot read
1455        }
1456
1457        let errors = Arc::new(Mutex::new(Vec::new()));
1458        let collected = errors.clone();
1459        let inotify = super::inotify_sys::Inotify::init().unwrap();
1460        let mut event_loop = EventLoop::new(
1461            inotify,
1462            Box::new(move |event: Result<Event>| {
1463                if let Err(e) = event {
1464                    collected.lock().unwrap().push(e);
1465                }
1466            }),
1467            &Config::default(),
1468        )
1469        .unwrap();
1470
1471        let result = event_loop.add_watch(WatchPath::new(&root).unwrap(), recursive_watch(), true);
1472        // Before the asserts: a directory the test cannot read is one tempfile cannot remove.
1473        make_readable(&unwatchable);
1474
1475        assert!(
1476            result.is_ok(),
1477            "expected the watch to succeed, got: {result:?}"
1478        );
1479        assert!(event_loop.watches.contains_key(&readable));
1480        assert!(event_loop.watches.contains_key(&deeper));
1481        let errors = errors.lock().unwrap();
1482        // Once, not twice: it can neither be watched nor be read, which is one loss to the caller.
1483        assert_eq!(
1484            errors
1485                .iter()
1486                .filter(|e| e.paths.contains(&unwatchable))
1487                .count(),
1488            1,
1489            "expected the unwatchable directory to be reported once, got: {errors:?}"
1490        );
1491    }
1492
1493    /// A subtree the walk cannot enter is as unwatched as one that cannot be watched, so it has to
1494    /// be reported too. Symlinks are followed by default, so a link into its own ancestor is the
1495    /// easiest walk error to provoke.
1496    #[test]
1497    fn recursive_watch_reports_a_walk_error() {
1498        use std::fs;
1499        use std::os::unix::fs::symlink;
1500        use std::sync::Mutex;
1501
1502        let tmpdir = tempfile::tempdir().unwrap();
1503        let root = tmpdir.path().to_path_buf();
1504        let readable = root.join("readable");
1505        fs::create_dir(&readable).unwrap();
1506        symlink(&root, root.join("loop")).unwrap();
1507
1508        let errors = Arc::new(Mutex::new(Vec::new()));
1509        let collected = errors.clone();
1510        let inotify = super::inotify_sys::Inotify::init().unwrap();
1511        let mut event_loop = EventLoop::new(
1512            inotify,
1513            Box::new(move |event: Result<Event>| {
1514                if let Err(e) = event {
1515                    collected.lock().unwrap().push(e);
1516                }
1517            }),
1518            &Config::default(),
1519        )
1520        .unwrap();
1521
1522        let result = event_loop.add_watch(WatchPath::new(&root).unwrap(), recursive_watch(), true);
1523
1524        assert!(
1525            result.is_ok(),
1526            "expected the watch to succeed, got: {result:?}"
1527        );
1528        assert!(event_loop.watches.contains_key(&readable));
1529        let errors = errors.lock().unwrap();
1530        assert!(
1531            errors
1532                .iter()
1533                .any(|e| e.paths.iter().any(|p| p.ends_with("loop"))),
1534            "expected the symlink loop to be reported, got: {errors:?}"
1535        );
1536    }
1537
1538    #[test]
1539    fn recursive_walk_reports_loops_without_skipping_directory_aliases() {
1540        let tmpdir = tempfile::tempdir().unwrap();
1541        let root = tmpdir.path().join("root");
1542        let destination = tmpdir.path().join("destination");
1543        std::fs::create_dir(&root).unwrap();
1544        std::fs::create_dir_all(destination.join("nested")).unwrap();
1545        std::os::unix::fs::symlink(".", destination.join("loop")).unwrap();
1546        for alias in ["a", "b"] {
1547            std::os::unix::fs::symlink(&destination, root.join(alias)).unwrap();
1548        }
1549
1550        let reported = PathBuf::from("relative-root");
1551        let barriers = Default::default();
1552        let entries = super::walk_dirs(
1553            root.clone(),
1554            WatchPath::from_parts(root, reported.clone()),
1555            true,
1556            &barriers,
1557        );
1558        let mut watched = Vec::new();
1559        let mut failures = Vec::new();
1560        for entry in entries {
1561            match entry {
1562                Ok(path) => watched.push(path.requested),
1563                Err(err) => failures.extend(err.paths),
1564            }
1565        }
1566        watched.sort();
1567        failures.sort();
1568        assert_eq!(
1569            watched,
1570            ["", "a", "a/nested", "b", "b/nested"].map(|path| reported.join(path))
1571        );
1572        assert_eq!(failures, [reported.join("a/loop"), reported.join("b/loop")]);
1573    }
1574
1575    #[test]
1576    fn recursive_walk_visits_directory_aliases_under_a_parent_link() {
1577        let tmpdir = tempfile::tempdir().unwrap();
1578        let root = tmpdir.path().join("child");
1579        std::fs::create_dir_all(root.join("nested")).unwrap();
1580        std::os::unix::fs::symlink("..", root.join("parent")).unwrap();
1581
1582        let reported = PathBuf::from("relative-root");
1583        let barriers = Default::default();
1584        let entries = super::walk_dirs(
1585            root.clone(),
1586            WatchPath::from_parts(root, reported.clone()),
1587            true,
1588            &barriers,
1589        );
1590        let mut watched = Vec::new();
1591        let mut failures = Vec::new();
1592        for entry in entries {
1593            match entry {
1594                Ok(path) => watched.push(path.requested),
1595                Err(err) => failures.extend(err.paths),
1596            }
1597        }
1598        watched.sort();
1599        // The parent link reaches `child` again through a regular directory entry.
1600        assert_eq!(
1601            watched,
1602            [
1603                "",
1604                "nested",
1605                "parent",
1606                "parent/child",
1607                "parent/child/nested"
1608            ]
1609            .map(|path| reported.join(path))
1610        );
1611        assert_eq!(failures, [reported.join("parent/child/parent")]);
1612    }
1613
1614    #[test]
1615    fn recursive_walk_honors_follow_symlinks_after_directory_replacement() {
1616        for follow_links in [false, true] {
1617            let tmpdir = tempfile::tempdir().unwrap();
1618            let root = tmpdir.path().join("root");
1619            let children = ["a", "b", "c"].map(|name| root.join(name));
1620            for child in &children {
1621                std::fs::create_dir_all(child).unwrap();
1622            }
1623            let destination = tmpdir.path().join("destination");
1624            std::fs::create_dir_all(destination.join("nested")).unwrap();
1625
1626            let barriers = Default::default();
1627            let mut entries = super::walk_dirs(
1628                root.clone(),
1629                WatchPath::new(&root).unwrap(),
1630                follow_links,
1631                &barriers,
1632            );
1633            assert_eq!(entries.next().unwrap().unwrap().absolute, root);
1634            let first = entries.next().unwrap().unwrap().absolute;
1635
1636            // The siblings have been listed but not visited. Pick one regardless of entry order.
1637            let replaced = children
1638                .iter()
1639                .find(|path| **path != first)
1640                .unwrap()
1641                .clone();
1642            std::fs::remove_dir(&replaced).unwrap();
1643            std::os::unix::fs::symlink(&destination, &replaced).unwrap();
1644
1645            let mut remaining: Vec<_> = entries.map(|entry| entry.unwrap().absolute).collect();
1646            let mut expected: Vec<_> = children
1647                .into_iter()
1648                .filter(|path| *path != first && (follow_links || *path != replaced))
1649                .collect();
1650            if follow_links {
1651                expected.push(replaced.join("nested"));
1652            }
1653            remaining.sort();
1654            expected.sort();
1655            assert_eq!(remaining, expected, "follow_links = {follow_links}");
1656        }
1657    }
1658
1659    /// The same when the directory appears later: the failure has to reach the handler.
1660    #[test]
1661    fn watch_failure_after_a_directory_appears_is_reported() {
1662        use std::fs;
1663
1664        let tmpdir = tempfile::tempdir().unwrap();
1665        let root = tmpdir.path().join("root");
1666        let staging = tmpdir.path().join("staging");
1667        fs::create_dir(&root).unwrap();
1668        fs::create_dir_all(staging.join("readable")).unwrap();
1669        if !unwatchable_dir(&staging.join("unwatchable")) {
1670            return; // running as root, which can watch a directory it cannot read
1671        }
1672
1673        let (tx, rx) = mpsc::channel();
1674        let mut watcher = INotifyWatcher::new(
1675            move |event| {
1676                let _ = tx.send(event);
1677            },
1678            Config::default(),
1679        )
1680        .unwrap();
1681        watcher.watch(&root, RecursiveMode::Recursive).unwrap();
1682
1683        // Moved in whole, so the walk it triggers is certain to meet the unwatchable directory.
1684        let appearing = root.join("appearing");
1685        fs::rename(&staging, &appearing).unwrap();
1686
1687        let reported = wait_for(&rx, || {}, Result::is_err);
1688        make_readable(&appearing.join("unwatchable"));
1689        let reported = reported
1690            .expect("expected the unwatchable directory to be reported")
1691            .unwrap_err();
1692        assert!(
1693            reported.paths.iter().any(|p| p.ends_with("unwatchable")),
1694            "expected the unwatchable directory to be named, got: {reported:?}"
1695        );
1696
1697        // Its sibling is watched, so changes under it are still reported. The failure can reach
1698        // the handler before the walk reaches the sibling, so keep writing until it does.
1699        let file = appearing.join("readable").join("file");
1700        assert!(
1701            wait_for(
1702                &rx,
1703                || fs::write(&file, "x").unwrap(),
1704                |event| matches!(event, Ok(event) if event.paths.contains(&file))
1705            )
1706            .is_some(),
1707            "expected a change under the sibling directory to be reported"
1708        );
1709    }
1710
1711    #[test]
1712    fn rewatching_same_path_replaces_recursive_state() {
1713        let tmpdir = tempfile::tempdir().unwrap();
1714        let root = tmpdir.path().to_path_buf();
1715        let child = root.join("child");
1716        std::fs::create_dir(&child).unwrap();
1717
1718        let mut event_loop = test_event_loop();
1719
1720        event_loop
1721            .add_watch(WatchPath::new(&root).unwrap(), recursive_watch(), true)
1722            .expect("watch recursively");
1723        assert!(event_loop.watches.contains_key(&child));
1724
1725        event_loop
1726            .add_watch(WatchPath::new(&root).unwrap(), non_recursive_watch(), true)
1727            .expect("rewatch non-recursively");
1728
1729        let watch = event_loop.watches.get(&root).expect("root watch");
1730        assert!(watch.metadata.is_user_watch);
1731        assert!(!watch.metadata.user_is_recursive);
1732        assert!(!watch.metadata.is_recursive);
1733        assert!(!event_loop.watches.contains_key(&child));
1734    }
1735
1736    #[test]
1737    fn rewatching_child_preserves_recursive_parent_state() {
1738        let tmpdir = tempfile::tempdir().unwrap();
1739        let root = tmpdir.path().to_path_buf();
1740        let child = root.join("child");
1741        let grandchild = child.join("grandchild");
1742        std::fs::create_dir_all(&grandchild).unwrap();
1743
1744        let mut event_loop = test_event_loop();
1745
1746        event_loop
1747            .add_watch(WatchPath::new(&root).unwrap(), recursive_watch(), true)
1748            .expect("watch root recursively");
1749        event_loop
1750            .add_watch(WatchPath::new(&child).unwrap(), non_recursive_watch(), true)
1751            .expect("watch child non-recursively");
1752        event_loop
1753            .add_watch(
1754                WatchPath::from_parts(child.clone(), PathBuf::from("reported-child")),
1755                non_recursive_watch(),
1756                true,
1757            )
1758            .expect("rewatch child non-recursively");
1759
1760        let child_watch = event_loop.watches.get(&child).expect("child watch");
1761        assert!(child_watch.metadata.is_user_watch);
1762        assert!(!child_watch.metadata.user_is_recursive);
1763        assert!(child_watch.metadata.is_recursive);
1764        assert_eq!(
1765            child_watch.metadata.reported_path,
1766            PathBuf::from("reported-child")
1767        );
1768
1769        let grandchild_watch = event_loop
1770            .watches
1771            .get(&grandchild)
1772            .expect("grandchild still covered by recursive parent");
1773        assert!(!grandchild_watch.metadata.is_user_watch);
1774        assert!(grandchild_watch.metadata.is_recursive);
1775    }
1776
1777    #[test]
1778    fn nested_recursive_watch_keeps_its_spelling_under_an_outer_watch() {
1779        let tmpdir = tempfile::tempdir().unwrap();
1780        let root = tmpdir.path().to_path_buf();
1781        let child = root.join("child");
1782        let grandchild = child.join("grandchild");
1783        std::fs::create_dir_all(&grandchild).unwrap();
1784
1785        let mut event_loop = test_event_loop();
1786
1787        event_loop
1788            .add_watch(
1789                WatchPath::from_parts(child.clone(), PathBuf::from("reported-child")),
1790                recursive_watch(),
1791                true,
1792            )
1793            .expect("watch child recursively under its own spelling");
1794        event_loop
1795            .add_watch(WatchPath::new(&root).unwrap(), recursive_watch(), true)
1796            .expect("watch root recursively");
1797
1798        let reported = |event_loop: &EventLoop, path: &Path| {
1799            let watch = event_loop.watches.get(path).expect("watch");
1800            watch.metadata.reported_path.clone()
1801        };
1802        assert_eq!(reported(&event_loop, &root), root);
1803        assert_eq!(reported(&event_loop, &child), Path::new("reported-child"));
1804        assert_eq!(
1805            reported(&event_loop, &grandchild),
1806            Path::new("reported-child/grandchild")
1807        );
1808
1809        event_loop
1810            .remove_watch(root.clone(), false)
1811            .expect("unwatch root");
1812
1813        assert_eq!(
1814            reported(&event_loop, &grandchild),
1815            Path::new("reported-child/grandchild")
1816        );
1817    }
1818
1819    #[test]
1820    fn late_create_event_keeps_the_spelling_of_a_watch_added_since() {
1821        let tmpdir = tempfile::tempdir().unwrap();
1822        let root = tmpdir.path().to_path_buf();
1823        let child = root.join("child");
1824        let grandchild = child.join("grandchild");
1825
1826        let mut event_loop = test_event_loop();
1827        event_loop
1828            .add_watch(WatchPath::new(&root).unwrap(), recursive_watch(), true)
1829            .expect("watch root recursively");
1830        std::fs::create_dir_all(&grandchild).unwrap();
1831        event_loop
1832            .add_watch(
1833                WatchPath::from_parts(child.clone(), PathBuf::from("reported-child")),
1834                recursive_watch(),
1835                true,
1836            )
1837            .expect("watch child before its create event is handled");
1838
1839        event_loop.handle_inotify();
1840
1841        let watch = event_loop.watches.get(&grandchild).expect("grandchild");
1842        assert_eq!(
1843            watch.metadata.reported_path,
1844            Path::new("reported-child/grandchild")
1845        );
1846    }
1847
1848    #[test]
1849    fn rewatching_carved_out_child_does_not_restore_parent_recursive_state() {
1850        let tmpdir = tempfile::tempdir().unwrap();
1851        let root = tmpdir.path().to_path_buf();
1852        let child = root.join("child");
1853        let grandchild = child.join("grandchild");
1854        std::fs::create_dir_all(&grandchild).unwrap();
1855
1856        let mut event_loop = test_event_loop();
1857
1858        event_loop
1859            .add_watch(WatchPath::new(&root).unwrap(), recursive_watch(), true)
1860            .expect("watch root recursively");
1861        event_loop
1862            .remove_watch(child.clone(), false)
1863            .expect("carve out child");
1864        event_loop
1865            .add_watch(WatchPath::new(&child).unwrap(), non_recursive_watch(), true)
1866            .expect("watch child non-recursively");
1867        event_loop
1868            .add_watch(
1869                WatchPath::from_parts(child.clone(), PathBuf::from("reported-child")),
1870                non_recursive_watch(),
1871                true,
1872            )
1873            .expect("rewatch child non-recursively");
1874
1875        let child_watch = event_loop.watches.get(&child).expect("child watch");
1876        assert!(child_watch.metadata.is_user_watch);
1877        assert!(!child_watch.metadata.user_is_recursive);
1878        assert!(!child_watch.metadata.is_recursive);
1879        assert_eq!(
1880            child_watch.metadata.reported_path,
1881            PathBuf::from("reported-child")
1882        );
1883        assert!(!event_loop.watches.contains_key(&grandchild));
1884    }
1885
1886    /// Runs manually.
1887    ///
1888    /// * Save actual value of the limit: `MAX_USER_WATCHES=$(sysctl -n fs.inotify.max_user_watches)`
1889    /// * Run the test.
1890    /// * Set the limit to 0: `sudo sysctl fs.inotify.max_user_watches=0` while test is running
1891    /// * Wait for the test to complete
1892    /// * Restore the limit `sudo sysctl fs.inotify.max_user_watches=$MAX_USER_WATCHES`
1893    #[test]
1894    #[ignore = "requires changing sysctl fs.inotify.max_user_watches while test is running"]
1895    fn recursive_watch_calls_handler_if_creating_a_file_raises_max_files_watch() {
1896        use std::time::Duration;
1897
1898        let tmpdir = tempfile::tempdir().unwrap();
1899        let (tx, rx) = std::sync::mpsc::channel();
1900        let (proc_changed_tx, proc_changed_rx) = std::sync::mpsc::channel();
1901        let proc_path = Path::new("/proc/sys/fs/inotify/max_user_watches");
1902        let mut watcher = INotifyWatcher::new(
1903            move |result: Result<Event>| match result {
1904                Ok(event) => {
1905                    if event.paths.first().is_some_and(|path| path == proc_path) {
1906                        proc_changed_tx.send(()).unwrap();
1907                    }
1908                }
1909                Err(e) => tx.send(e).unwrap(),
1910            },
1911            Config::default(),
1912        )
1913        .unwrap();
1914
1915        watcher
1916            .watch(tmpdir.path(), RecursiveMode::Recursive)
1917            .unwrap();
1918        watcher
1919            .watch(proc_path, RecursiveMode::NonRecursive)
1920            .unwrap();
1921
1922        // give the time to set the limit
1923        proc_changed_rx
1924            .recv_timeout(Duration::from_secs(30))
1925            .unwrap();
1926
1927        let child_dir = tmpdir.path().join("child");
1928        std::fs::create_dir(child_dir).unwrap();
1929
1930        let result = rx.recv_timeout(Duration::from_millis(500));
1931
1932        assert!(
1933            matches!(
1934                &result,
1935                Ok(Error {
1936                    kind: ErrorKind::MaxFilesWatch,
1937                    paths: _,
1938                })
1939            ),
1940            "expected {:?}, found: {:#?}",
1941            ErrorKind::MaxFilesWatch,
1942            result
1943        );
1944    }
1945
1946    /// https://github.com/notify-rs/notify/issues/678
1947    #[test]
1948    fn race_condition_on_unwatch_and_pending_events_with_deleted_descriptor() {
1949        let tmpdir = tempfile::tempdir().expect("tmpdir");
1950        let (tx, rx) = mpsc::channel();
1951        // Use CORE to exclude access events - the parallel threads opening files
1952        // would otherwise flood the queue with OPEN events causing Rescan
1953        let mut inotify = INotifyWatcher::new(
1954            move |e: Result<Event>| {
1955                let e = match e {
1956                    Ok(e) if e.paths.is_empty() => e,
1957                    Ok(_) | Err(_) => return,
1958                };
1959                let _ = tx.send(e);
1960            },
1961            Config::default().with_event_kinds(EventKindMask::CORE),
1962        )
1963        .expect("inotify creation");
1964
1965        let dir_path = tmpdir.path();
1966        let file_path = dir_path.join("foo");
1967        std::fs::File::create(&file_path).unwrap();
1968
1969        let stop = Arc::new(AtomicBool::new(false));
1970
1971        let handles: Vec<_> = (0..available_parallelism().unwrap().get().max(4))
1972            .map(|_| {
1973                let file_path = file_path.clone();
1974                let stop = stop.clone();
1975                thread::spawn(move || {
1976                    while !stop.load(std::sync::atomic::Ordering::Relaxed) {
1977                        let _ = std::fs::File::open(&file_path).unwrap();
1978                    }
1979                })
1980            })
1981            .collect();
1982
1983        let non_recursive = RecursiveMode::NonRecursive;
1984        for _ in 0..(handles.len() * 4) {
1985            inotify.watch(dir_path, non_recursive).unwrap();
1986            inotify.unwatch(dir_path).unwrap();
1987        }
1988
1989        stop.store(true, std::sync::atomic::Ordering::Relaxed);
1990        handles
1991            .into_iter()
1992            .for_each(|handle| handle.join().ok().unwrap_or_default());
1993
1994        drop(inotify);
1995
1996        let events: Vec<_> = rx.into_iter().map(|e| format!("{e:?}")).collect();
1997
1998        const LOG_LEN: usize = 10;
1999        let events_len = events.len();
2000        assert!(
2001            events.is_empty(),
2002            "expected no events without path, but got {events_len}. first 10: {:#?}",
2003            &events[..LOG_LEN.min(events_len)]
2004        );
2005    }
2006
2007    /// https://github.com/notify-rs/notify/issues/709
2008    #[test]
2009    fn remove_a_subdir_in_a_recursively_watched_parent() {
2010        let tmpdir = tempfile::tempdir().expect("tmpdir");
2011        let subdirectory_path_1 = tmpdir.path().join("subdir");
2012        let subdirectory_path_2 = subdirectory_path_1.join("nested");
2013        std::fs::create_dir(&subdirectory_path_1).expect("unable to create a subdir");
2014        std::fs::create_dir(&subdirectory_path_2).expect("unable to create a nested dir");
2015
2016        let mut watcher =
2017            INotifyWatcher::new(|_| (), Config::default()).expect("unable to create watcher");
2018        watcher
2019            .watch(tmpdir.path(), RecursiveMode::Recursive)
2020            .expect("unable to watch");
2021        std::fs::remove_dir_all(&subdirectory_path_1).expect("unable to remove a subdir");
2022        let unwatch_result = watcher.unwatch(tmpdir.path());
2023
2024        assert!(
2025            matches!(unwatch_result, Ok(())),
2026            "error: {unwatch_result:#?}"
2027        );
2028    }
2029
2030    // FIXME: FreeBSD 15.1 does not generate IN_IGNORED unless IN_DELETE_SELF was requested.
2031    // Remove this ignore once CI uses a release containing the fix:
2032    // https://cgit.freebsd.org/src/commit/?id=242c9c86c8cad6aa29bc1af9161d4f0eec45f29b
2033    #[cfg_attr(
2034        target_os = "freebsd",
2035        ignore = "FreeBSD 15.1 does not generate IN_IGNORED unconditionally"
2036    )]
2037    #[test]
2038    fn ignored_event_removes_watch_when_remove_events_are_filtered_out() {
2039        let tmpdir = tempfile::tempdir().expect("tmpdir");
2040        let root = tmpdir.path().to_path_buf();
2041        let child = root.join("child");
2042        std::fs::create_dir(&child).expect("create child");
2043
2044        // Recursive setup still installs a watch for `child`, but the parent does not receive a
2045        // DELETE event with this mask. IGNORED is therefore the only cleanup signal and must be
2046        // handled independently of the user-visible event filter.
2047        let config = Config::default().with_event_kinds(EventKindMask::MODIFY_DATA);
2048        let mut event_loop = test_event_loop_with_config(&config);
2049        event_loop
2050            .add_watch(WatchPath::new(&root).unwrap(), recursive_watch(), true)
2051            .expect("watch recursively");
2052
2053        let child_descriptor = event_loop
2054            .watches
2055            .get(&child)
2056            .expect("child watch")
2057            .watch_descriptor
2058            .clone();
2059
2060        std::fs::remove_dir(&child).expect("remove child");
2061        event_loop.handle_inotify();
2062
2063        assert!(event_loop.watches.contains_key(&root));
2064        assert!(
2065            !event_loop.watches.contains_key(&child),
2066            "IGNORED must remove the stale child watch"
2067        );
2068        assert!(
2069            !event_loop.paths.contains_key(&child_descriptor),
2070            "IGNORED must remove the stale descriptor-to-path mapping"
2071        );
2072    }
2073
2074    #[test]
2075    fn duplicate_watch_removals_are_coalesced() {
2076        let tmpdir = tempfile::tempdir().expect("tmpdir");
2077        let path = tmpdir.path().to_path_buf();
2078        let mut event_loop = test_event_loop();
2079        event_loop
2080            .add_watch(WatchPath::new(&path).unwrap(), non_recursive_watch(), true)
2081            .expect("add_watch");
2082
2083        // DELETE may queue a normal removal before DELETE_SELF or IGNORED confirms that the
2084        // kernel already removed the same descriptor. Keep one entry with the stronger mode.
2085        let mut queued = std::collections::BTreeMap::new();
2086        for removal in [
2087            super::WatchRemoval::WithOsCall,
2088            super::WatchRemoval::DescriptorAlreadyRemoved,
2089        ] {
2090            super::queue_watch_removal(&path, None, &event_loop.watches, &mut queued, removal);
2091        }
2092
2093        assert_eq!(
2094            queued.into_iter().collect::<Vec<_>>(),
2095            vec![(path, super::WatchRemoval::DescriptorAlreadyRemoved)]
2096        );
2097    }
2098
2099    #[test]
2100    fn deleting_parent_still_removes_live_moved_out_descendant_descriptor() {
2101        let tmpdir = tempfile::tempdir().expect("tmpdir");
2102        let root = tmpdir.path().join("root");
2103        let child = root.join("child");
2104        let moved_child = tmpdir.path().join("moved-child");
2105        std::fs::create_dir_all(&child).expect("create watched tree");
2106
2107        // Excluding rename/remove events deliberately leaves the moved child's old path in the
2108        // recursive bookkeeping until deleting the root triggers descriptor cleanup. Unlike the
2109        // deleted root descriptor, the descriptor follows the moved child and remains live.
2110        let config = Config::default().with_event_kinds(EventKindMask::MODIFY_DATA);
2111        let mut event_loop = test_event_loop_with_config(&config);
2112        event_loop
2113            .add_watch(WatchPath::new(&root).unwrap(), recursive_watch(), true)
2114            .expect("watch recursively");
2115
2116        let child_descriptor = event_loop
2117            .watches
2118            .get(&child)
2119            .expect("child watch")
2120            .watch_descriptor
2121            .clone();
2122
2123        std::fs::rename(&child, &moved_child).expect("move child out of watched root");
2124        std::fs::remove_dir(&root).expect("remove watched root");
2125        event_loop.handle_inotify();
2126
2127        assert!(event_loop.watches.is_empty());
2128        assert!(event_loop.paths.is_empty());
2129
2130        // EINVAL here is expected because recursive cleanup should already have issued rm_watch
2131        // for the live moved-out descendant. Skipping every syscall for a deleted root would leak
2132        // that descriptor even though the local maps looked empty.
2133        let remove_result = event_loop
2134            .inotify
2135            .as_mut()
2136            .expect("inotify instance")
2137            .watches()
2138            .remove(child_descriptor);
2139        assert_eq!(
2140            remove_result.unwrap_err().raw_os_error(),
2141            Some(libc::EINVAL),
2142            "the moved-out descendant descriptor must already have been removed"
2143        );
2144    }
2145
2146    #[test]
2147    fn unmount_event_maps_to_remove_other_with_unmount_info() {
2148        let path = PathBuf::from("/tmp/notify-unmount");
2149        let event = super::unmount_event(path.clone());
2150
2151        assert_eq!(event.kind, EventKind::Remove(RemoveKind::Other));
2152        assert_eq!(event.paths, vec![path]);
2153        assert_eq!(event.info(), Some("unmount"));
2154    }
2155
2156    #[test]
2157    fn descriptor_removed_cleanup_removes_internal_state() {
2158        let tmpdir = tempfile::tempdir().unwrap();
2159        let watched = tmpdir.path().join("watched");
2160        std::fs::create_dir(&watched).unwrap();
2161
2162        let mut event_loop = test_event_loop();
2163
2164        event_loop
2165            .add_watch(
2166                WatchPath::new(&watched).unwrap(),
2167                non_recursive_watch(),
2168                true,
2169            )
2170            .expect("add_watch");
2171
2172        event_loop
2173            .remove_watch_without_root_os_call(watched.clone(), true)
2174            .expect("remove_watch_without_root_os_call");
2175
2176        let result = event_loop.remove_watch(watched.clone(), false);
2177        assert!(
2178            matches!(
2179                result,
2180                Err(Error {
2181                    kind: ErrorKind::WatchNotFound,
2182                    ..
2183                })
2184            ),
2185            "expected WatchNotFound, got: {result:?}"
2186        );
2187    }
2188
2189    #[test]
2190    fn create_file() {
2191        let tmpdir = testdir();
2192        let (mut watcher, mut rx) = watcher_with_all_events();
2193        watcher.watch_recursively(&tmpdir);
2194
2195        let path = tmpdir.path().join("entry");
2196        std::fs::File::create_new(&path).expect("create");
2197
2198        // Use wait_ordered (not _exact) because with ALL events we may get
2199        // directory access events that are timing-dependent
2200        rx.wait_ordered([
2201            expected(&path).create_file(),
2202            expected(&path).access_open_any(),
2203            expected(&path).access_close_write(),
2204        ]);
2205    }
2206
2207    #[test]
2208    fn write_file() {
2209        let tmpdir = testdir();
2210        let (mut watcher, mut rx) = watcher_with_all_events();
2211
2212        let path = tmpdir.path().join("entry");
2213        std::fs::File::create_new(&path).expect("create");
2214
2215        watcher.watch_recursively(&tmpdir);
2216        std::fs::write(&path, b"123").expect("write");
2217
2218        // Use wait_ordered (not _exact) because with ALL events we may get
2219        // directory access events that are timing-dependent
2220        rx.wait_ordered([
2221            expected(&path).access_open_any(),
2222            expected(&path).modify_data_any().multiple(),
2223            expected(&path).access_close_write(),
2224        ]);
2225    }
2226
2227    #[test]
2228    fn chmod_file() {
2229        let tmpdir = testdir();
2230        let (mut watcher, mut rx) = watcher();
2231
2232        let path = tmpdir.path().join("entry");
2233        let file = std::fs::File::create_new(&path).expect("create");
2234        let mut permissions = file.metadata().expect("metadata").permissions();
2235        permissions.set_readonly(true);
2236
2237        watcher.watch_recursively(&tmpdir);
2238        file.set_permissions(permissions).expect("set_permissions");
2239
2240        // Use wait_ordered (not _exact) because with ALL events we may get
2241        // directory access events that are timing-dependent
2242        rx.wait_ordered([expected(&path).modify_meta_any()]);
2243    }
2244
2245    #[test]
2246    fn rename_file() {
2247        let tmpdir = testdir();
2248        let (mut watcher, mut rx) = watcher();
2249
2250        let path = tmpdir.path().join("entry");
2251        std::fs::File::create_new(&path).expect("create");
2252
2253        watcher.watch_recursively(&tmpdir);
2254        let new_path = tmpdir.path().join("renamed");
2255
2256        std::fs::rename(&path, &new_path).expect("rename");
2257
2258        // Use wait_ordered (not _exact) because with ALL events we may get
2259        // directory access events that are timing-dependent
2260        rx.wait_ordered([
2261            expected(&path).rename_from(),
2262            expected(&new_path).rename_to(),
2263            expected([path, new_path]).rename_both(),
2264        ])
2265        .ensure_trackers_len(1);
2266    }
2267
2268    #[test]
2269    fn delete_file() {
2270        let tmpdir = testdir();
2271        let (mut watcher, mut rx) = watcher();
2272        let file = tmpdir.path().join("file");
2273        std::fs::write(&file, "").expect("write");
2274
2275        watcher.watch_nonrecursively(&tmpdir);
2276
2277        std::fs::remove_file(&file).expect("remove");
2278
2279        rx.wait_ordered_exact([expected(&file).remove_file()]);
2280    }
2281
2282    #[test]
2283    fn delete_self_file() {
2284        let tmpdir = testdir();
2285        let (mut watcher, mut rx) = watcher();
2286        let file = tmpdir.path().join("file");
2287        std::fs::write(&file, "").expect("write");
2288
2289        watcher.watch_nonrecursively(&file);
2290
2291        std::fs::remove_file(&file).expect("remove");
2292
2293        rx.wait_ordered_exact([
2294            expected(&file).modify_meta_any(),
2295            expected(&file).remove_file(),
2296        ]);
2297    }
2298
2299    #[test]
2300    fn delete_self_dir() {
2301        let tmpdir = testdir();
2302        let dir = tmpdir.path().join("dir");
2303        std::fs::create_dir(&dir).expect("create");
2304
2305        let (mut watcher, mut rx) = watcher();
2306        watcher.watch_nonrecursively(&dir);
2307
2308        std::fs::remove_dir(&dir).expect("remove");
2309
2310        rx.wait_unordered([expected(&dir).remove_folder()]);
2311    }
2312
2313    #[test]
2314    fn create_write_overwrite() {
2315        let tmpdir = testdir();
2316        let (mut watcher, mut rx) = watcher_with_all_events();
2317        let overwritten_file = tmpdir.path().join("overwritten_file");
2318        let overwriting_file = tmpdir.path().join("overwriting_file");
2319        std::fs::write(&overwritten_file, "123").expect("write1");
2320
2321        watcher.watch_nonrecursively(&tmpdir);
2322
2323        std::fs::File::create(&overwriting_file).expect("create");
2324        std::fs::write(&overwriting_file, "321").expect("write2");
2325        std::fs::rename(&overwriting_file, &overwritten_file).expect("rename");
2326
2327        // Use wait_ordered (not _exact) because with ALL events we may get
2328        // directory access events that are timing-dependent
2329        rx.wait_ordered([
2330            expected(&overwriting_file).create_file(),
2331            expected(&overwriting_file).access_open_any(),
2332            expected(&overwriting_file).access_close_write(),
2333            expected(&overwriting_file).access_open_any(),
2334            expected(&overwriting_file).modify_data_any().multiple(),
2335            expected(&overwriting_file).access_close_write(),
2336            expected(&overwriting_file).rename_from(),
2337            expected(&overwritten_file).rename_to(),
2338            expected([overwriting_file, overwritten_file]).rename_both(),
2339        ])
2340        .ensure_trackers_len(1);
2341    }
2342
2343    #[test]
2344    fn create_dir() {
2345        let tmpdir = testdir();
2346        let (mut watcher, mut rx) = watcher();
2347        watcher.watch_recursively(&tmpdir);
2348
2349        let path = tmpdir.path().join("entry");
2350        std::fs::create_dir(&path).expect("create");
2351
2352        // Use wait_ordered (not _exact) because with ALL events we may get
2353        // directory access events that are timing-dependent
2354        rx.wait_ordered([expected(&path).create_folder()]);
2355    }
2356
2357    #[test]
2358    fn chmod_dir() {
2359        let tmpdir = testdir();
2360        let (mut watcher, mut rx) = watcher();
2361
2362        let path = tmpdir.path().join("entry");
2363        std::fs::create_dir(&path).expect("create_dir");
2364        let mut permissions = std::fs::metadata(&path).expect("metadata").permissions();
2365        permissions.set_readonly(true);
2366
2367        watcher.watch_recursively(&tmpdir);
2368        std::fs::set_permissions(&path, permissions).expect("set_permissions");
2369
2370        // Use wait_ordered (not _exact) because with ALL events we may get
2371        // directory access events that are timing-dependent
2372        rx.wait_ordered([
2373            expected(&path).access_open_any().optional(),
2374            expected(&path).modify_meta_any(),
2375            expected(&path).modify_meta_any(),
2376        ]);
2377    }
2378
2379    #[test]
2380    fn rename_dir() {
2381        let tmpdir = testdir();
2382        let (mut watcher, mut rx) = watcher();
2383
2384        let path = tmpdir.path().join("entry");
2385        let new_path = tmpdir.path().join("new_path");
2386        std::fs::create_dir(&path).expect("create_dir");
2387
2388        watcher.watch_recursively(&tmpdir);
2389
2390        std::fs::rename(&path, &new_path).expect("rename");
2391
2392        // Use wait_ordered (not _exact) because with ALL events we may get
2393        // directory access events that are timing-dependent
2394        rx.wait_ordered([
2395            expected(&path).access_open_any().optional(),
2396            expected(&path).rename_from(),
2397            expected(&new_path).rename_to(),
2398            expected([path, new_path]).rename_both(),
2399        ])
2400        .ensure_trackers_len(1);
2401    }
2402
2403    #[test]
2404    fn delete_dir() {
2405        let tmpdir = testdir();
2406        let (mut watcher, mut rx) = watcher();
2407
2408        let path = tmpdir.path().join("entry");
2409        std::fs::create_dir(&path).expect("create_dir");
2410
2411        watcher.watch_recursively(&tmpdir);
2412        std::fs::remove_dir(&path).expect("remove");
2413
2414        // Use wait_ordered (not _exact) because with ALL events we may get
2415        // directory access events that are timing-dependent
2416        rx.wait_ordered([
2417            expected(&path).access_open_any().optional(),
2418            expected(&path).remove_folder(),
2419        ]);
2420    }
2421
2422    #[test]
2423    fn rename_dir_twice() {
2424        let tmpdir = testdir();
2425        let (mut watcher, mut rx) = watcher();
2426
2427        let path = tmpdir.path().join("entry");
2428        let new_path = tmpdir.path().join("new_path");
2429        let new_path2 = tmpdir.path().join("new_path2");
2430        std::fs::create_dir(&path).expect("create_dir");
2431
2432        watcher.watch_recursively(&tmpdir);
2433        std::fs::rename(&path, &new_path).expect("rename");
2434        std::fs::rename(&new_path, &new_path2).expect("rename2");
2435
2436        // Use wait_ordered (not _exact) because we may get extra events
2437        // due to directory traversal/rescan on rename
2438        rx.wait_ordered([
2439            expected(&path).access_open_any().optional(),
2440            expected(&path).rename_from(),
2441            expected(&new_path).rename_to(),
2442            expected([&path, &new_path]).rename_both(),
2443            expected(&new_path).access_open_any().optional(),
2444            expected(&new_path).rename_from(),
2445            expected(&new_path2).rename_to(),
2446            expected([&new_path, &new_path2]).rename_both(),
2447        ])
2448        .ensure_trackers_len(2);
2449    }
2450
2451    #[test]
2452    fn move_out_of_watched_dir() {
2453        let tmpdir = testdir();
2454        let subdir = tmpdir.path().join("subdir");
2455        let (mut watcher, mut rx) = watcher();
2456
2457        let path = subdir.join("entry");
2458        std::fs::create_dir_all(&subdir).expect("create_dir_all");
2459        std::fs::File::create_new(&path).expect("create");
2460
2461        watcher.watch_recursively(&subdir);
2462        let new_path = tmpdir.path().join("entry");
2463
2464        std::fs::rename(&path, &new_path).expect("rename");
2465
2466        // With ALL events, we may get Access events on the directory.
2467        // Skip Access events to find the rename event.
2468        let event = loop {
2469            let event = rx.recv();
2470            if !matches!(event.kind, EventKind::Access(_)) {
2471                break event;
2472            }
2473        };
2474        let tracker = event.attrs.tracker();
2475        assert_eq!(event, expected(&path).rename_from());
2476        assert!(tracker.is_some(), "tracker is none: {event:#?}");
2477    }
2478
2479    #[test]
2480    fn create_write_write_rename_write_remove() {
2481        let tmpdir = testdir();
2482        let (mut watcher, mut rx) = watcher_with_all_events();
2483
2484        let file1 = tmpdir.path().join("entry");
2485        let file2 = tmpdir.path().join("entry2");
2486        std::fs::File::create_new(&file2).expect("create file2");
2487        let new_path = tmpdir.path().join("renamed");
2488
2489        watcher.watch_recursively(&tmpdir);
2490        std::fs::write(&file1, "123").expect("write 1");
2491        std::fs::write(&file2, "321").expect("write 2");
2492        std::fs::rename(&file1, &new_path).expect("rename");
2493        std::fs::write(&new_path, b"1").expect("write 3");
2494        std::fs::remove_file(&new_path).expect("remove");
2495
2496        // Use wait_ordered (not _exact) because with ALL events we may get
2497        // directory access events that are timing-dependent
2498        rx.wait_ordered([
2499            expected(&file1).create_file(),
2500            expected(&file1).access_open_any(),
2501            expected(&file1).modify_data_any().multiple(),
2502            expected(&file1).access_close_write(),
2503            expected(&file2).access_open_any(),
2504            expected(&file2).modify_data_any().multiple(),
2505            expected(&file2).access_close_write(),
2506            expected(&file1).access_open_any().optional(),
2507            expected(&file1).rename_from(),
2508            expected(&new_path).rename_to(),
2509            expected([&file1, &new_path]).rename_both(),
2510            expected(&new_path).access_open_any(),
2511            expected(&new_path).modify_data_any().multiple(),
2512            expected(&new_path).access_close_write(),
2513            expected(&new_path).remove_file(),
2514        ]);
2515    }
2516
2517    #[test]
2518    fn rename_twice() {
2519        let tmpdir = testdir();
2520        let (mut watcher, mut rx) = watcher();
2521
2522        let path = tmpdir.path().join("entry");
2523        std::fs::File::create_new(&path).expect("create");
2524
2525        watcher.watch_recursively(&tmpdir);
2526        let new_path1 = tmpdir.path().join("renamed1");
2527        let new_path2 = tmpdir.path().join("renamed2");
2528
2529        std::fs::rename(&path, &new_path1).expect("rename1");
2530        std::fs::rename(&new_path1, &new_path2).expect("rename2");
2531
2532        // Use wait_ordered (not _exact) because with ALL events we may get
2533        // directory access events that are timing-dependent
2534        rx.wait_ordered([
2535            expected(&path).access_open_any().optional(),
2536            expected(&path).rename_from(),
2537            expected(&new_path1).rename_to(),
2538            expected([&path, &new_path1]).rename_both(),
2539            expected(&new_path1).access_open_any().optional(),
2540            expected(&new_path1).rename_from(),
2541            expected(&new_path2).rename_to(),
2542            expected([&new_path1, &new_path2]).rename_both(),
2543        ])
2544        .ensure_trackers_len(2);
2545    }
2546
2547    #[test]
2548    fn set_file_mtime() {
2549        let tmpdir = testdir();
2550        let (mut watcher, mut rx) = watcher();
2551
2552        let path = tmpdir.path().join("entry");
2553        let file = std::fs::File::create_new(&path).expect("create");
2554
2555        watcher.watch_recursively(&tmpdir);
2556
2557        file.set_modified(
2558            std::time::SystemTime::now()
2559                .checked_sub(Duration::from_secs(60 * 60))
2560                .expect("time"),
2561        )
2562        .expect("set_time");
2563
2564        // With ALL events, we may get Access events on the directory.
2565        // Skip Access events to find the modify event.
2566        let event = loop {
2567            let event = rx.recv();
2568            if !matches!(event.kind, EventKind::Access(_)) {
2569                break event;
2570            }
2571        };
2572        // Linux and FreeBSD classify this modification differently.
2573        assert_eq!(event, expected(&path).modify());
2574    }
2575
2576    #[test]
2577    fn write_file_non_recursive_watch() {
2578        let tmpdir = testdir();
2579        let (mut watcher, mut rx) = watcher_with_all_events();
2580
2581        let path = tmpdir.path().join("entry");
2582        std::fs::File::create_new(&path).expect("create");
2583
2584        watcher.watch_nonrecursively(&path);
2585
2586        std::fs::write(&path, b"123").expect("write");
2587
2588        // Use wait_ordered (not _exact) because with ALL events we may get
2589        // directory access events that are timing-dependent
2590        rx.wait_ordered([
2591            expected(&path).access_open_any(),
2592            expected(&path).modify_data_any().multiple(),
2593            expected(&path).access_close_write(),
2594        ]);
2595    }
2596
2597    #[test]
2598    fn watch_recursively_then_unwatch_child_stops_events_from_child() {
2599        let tmpdir = testdir();
2600        let (mut watcher, mut rx) = watcher_with_all_events();
2601
2602        let subdir = tmpdir.path().join("subdir");
2603        let file = subdir.join("file");
2604        std::fs::create_dir(&subdir).expect("create");
2605
2606        watcher.watch_recursively(&tmpdir);
2607
2608        std::fs::File::create(&file).expect("create");
2609
2610        // Use wait_ordered (not _exact) because with ALL events we may get
2611        // directory access events that are timing-dependent
2612        rx.wait_ordered([
2613            expected(&subdir).access_open_any().optional(),
2614            expected(&file).create_file(),
2615            expected(&file).access_open_any(),
2616            expected(&file).access_close_write(),
2617        ]);
2618
2619        watcher.watcher.unwatch(&subdir).expect("unwatch");
2620
2621        std::fs::write(&file, b"123").expect("write");
2622
2623        std::fs::remove_dir_all(&subdir).expect("remove_dir_all");
2624
2625        // Use wait_ordered (not _exact) because with ALL events we may get
2626        // directory access events that are timing-dependent
2627        rx.wait_ordered([
2628            expected(&subdir).access_open_any().optional(),
2629            expected(&subdir).remove_folder(),
2630        ]);
2631    }
2632
2633    #[test]
2634    fn write_to_a_hardlink_pointed_to_the_watched_file_triggers_an_event() {
2635        let tmpdir = testdir();
2636        let (mut watcher, mut rx) = watcher_with_all_events();
2637
2638        let subdir = tmpdir.path().join("subdir");
2639        let file = subdir.join("file");
2640        let hardlink = tmpdir.path().join("hardlink");
2641
2642        std::fs::create_dir(&subdir).expect("create");
2643        std::fs::write(&file, "").expect("file");
2644        std::fs::hard_link(&file, &hardlink).expect("hardlink");
2645
2646        watcher.watch_nonrecursively(&file);
2647
2648        std::fs::write(&hardlink, "123123").expect("write to the hard link");
2649
2650        // Use wait_ordered (not _exact) because with ALL events we may get
2651        // directory access events that are timing-dependent
2652        rx.wait_ordered([
2653            expected(&file).access_open_any(),
2654            expected(&file).modify_data_any().multiple(),
2655            expected(&file).access_close_write(),
2656        ]);
2657    }
2658
2659    // FreeBSD reports writes through any hard link to the watched inode.
2660    #[test]
2661    #[cfg(not(target_os = "freebsd"))]
2662    fn write_to_a_hardlink_pointed_to_the_file_in_the_watched_dir_doesnt_trigger_an_event() {
2663        let tmpdir = testdir();
2664        let (mut watcher, mut rx) = watcher();
2665
2666        let subdir = tmpdir.path().join("subdir");
2667        let file = subdir.join("file");
2668        let hardlink = tmpdir.path().join("hardlink");
2669
2670        std::fs::create_dir(&subdir).expect("create");
2671        std::fs::write(&file, "").expect("file");
2672        std::fs::hard_link(&file, &hardlink).expect("hardlink");
2673
2674        watcher.watch_nonrecursively(&subdir);
2675
2676        std::fs::write(&hardlink, "123123").expect("write to the hard link");
2677
2678        // With ALL events, we may get Access events on the watched directory.
2679        // Filter those out - we only care about non-Access events on the file.
2680        let events: Vec<_> = rx
2681            .iter()
2682            .filter(|e| !matches!(e.kind, EventKind::Access(_)))
2683            .collect();
2684        assert!(events.is_empty(), "unexpected events: {events:#?}");
2685    }
2686
2687    #[test]
2688    #[ignore = "see https://github.com/notify-rs/notify/issues/727"]
2689    fn recursive_creation() {
2690        let tmpdir = testdir();
2691        let nested1 = tmpdir.path().join("1");
2692        let nested2 = tmpdir.path().join("1/2");
2693        let nested3 = tmpdir.path().join("1/2/3");
2694        let nested4 = tmpdir.path().join("1/2/3/4");
2695        let nested5 = tmpdir.path().join("1/2/3/4/5");
2696        let nested6 = tmpdir.path().join("1/2/3/4/5/6");
2697        let nested7 = tmpdir.path().join("1/2/3/4/5/6/7");
2698        let nested8 = tmpdir.path().join("1/2/3/4/5/6/7/8");
2699        let nested9 = tmpdir.path().join("1/2/3/4/5/6/7/8/9");
2700
2701        let (mut watcher, mut rx) = watcher();
2702
2703        watcher.watch_recursively(&tmpdir);
2704
2705        std::fs::create_dir_all(&nested9).expect("create_dir_all");
2706        rx.wait_ordered([
2707            expected(&nested1).create_folder(),
2708            expected(&nested2).create_folder(),
2709            expected(&nested3).create_folder(),
2710            expected(&nested4).create_folder(),
2711            expected(&nested5).create_folder(),
2712            expected(&nested6).create_folder(),
2713            expected(&nested7).create_folder(),
2714            expected(&nested8).create_folder(),
2715            expected(&nested9).create_folder(),
2716        ]);
2717    }
2718
2719    // ============================================================
2720    // EventKindMask filtering tests
2721    // ============================================================
2722
2723    /// Test that CORE config does not produce Access events.
2724    #[test]
2725    fn event_kind_mask_core_config_no_access_events() {
2726        let tmpdir = testdir();
2727        // Use explicit CORE mask to exclude access events
2728        let (mut watcher, mut rx) = channel_with_config::<INotifyWatcher>(
2729            ChannelConfig::default()
2730                .with_timeout(std::time::Duration::from_secs(1))
2731                .with_watcher_config(Config::default().with_event_kinds(EventKindMask::CORE)),
2732        );
2733        watcher.watch_recursively(&tmpdir);
2734
2735        let path = tmpdir.path().join("entry");
2736        std::fs::File::create_new(&path).expect("create");
2737
2738        // With CORE config, we should only get create event, no access events
2739        rx.wait_ordered_exact([expected(&path).create_file()])
2740            .ensure_no_tail();
2741    }
2742
2743    /// Test that EventKindMask::ALL config produces Access events.
2744    #[test]
2745    fn event_kind_mask_all_config_produces_access_events() {
2746        let tmpdir = testdir();
2747        let (mut watcher, mut rx) = watcher_with_all_events();
2748        watcher.watch_recursively(&tmpdir);
2749
2750        let path = tmpdir.path().join("entry");
2751        std::fs::File::create_new(&path).expect("create");
2752
2753        // With ALL config, we should get create + access events
2754        // Use wait_ordered (not _exact) because we may get directory access events too
2755        rx.wait_ordered([
2756            expected(&path).create_file(),
2757            expected(&path).access_open_any(),
2758            expected(&path).access_close_write(),
2759        ]);
2760    }
2761
2762    /// Test that CREATE | REMOVE only mask filters out Modify events.
2763    #[test]
2764    fn event_kind_mask_create_remove_only_no_modify() {
2765        let tmpdir = testdir();
2766        let mask = EventKindMask::CREATE | EventKindMask::REMOVE;
2767        let (mut watcher, mut rx) = channel_with_config::<INotifyWatcher>(
2768            ChannelConfig::default()
2769                .with_timeout(std::time::Duration::from_secs(1))
2770                .with_watcher_config(Config::default().with_event_kinds(mask)),
2771        );
2772        watcher.watch_recursively(&tmpdir);
2773
2774        let path = tmpdir.path().join("entry");
2775        std::fs::write(&path, b"123").expect("write");
2776
2777        // With CREATE | REMOVE mask, we should only get create event, no modify events
2778        rx.wait_ordered_exact([expected(&path).create_file()])
2779            .ensure_no_tail();
2780    }
2781
2782    /// Test unit tests for event_kind_mask_to_watch_mask helper function.
2783    #[test]
2784    fn event_kind_mask_to_watch_mask_core() {
2785        use super::event_kind_mask_to_watch_mask;
2786
2787        let mask = EventKindMask::CORE;
2788        let watch_mask = event_kind_mask_to_watch_mask(mask, false);
2789
2790        // CORE includes CREATE, REMOVE, MODIFY_DATA, MODIFY_META, MODIFY_NAME
2791        assert!(watch_mask.intersects(WatchMask::CREATE));
2792        assert!(watch_mask.intersects(WatchMask::MOVED_TO));
2793        assert!(watch_mask.intersects(WatchMask::DELETE));
2794        assert!(watch_mask.intersects(WatchMask::MOVED_FROM));
2795        assert!(watch_mask.intersects(WatchMask::MODIFY));
2796        assert!(watch_mask.intersects(WatchMask::ATTRIB));
2797        assert!(watch_mask.intersects(WatchMask::MOVE_SELF));
2798
2799        // CORE does NOT include ACCESS (OPEN, CLOSE_WRITE, CLOSE_NOWRITE)
2800        // Note: CLOSE_WRITE generates Access events, not Modify events
2801        assert!(!watch_mask.intersects(WatchMask::OPEN));
2802        assert!(!watch_mask.intersects(WatchMask::CLOSE_WRITE));
2803        assert!(!watch_mask.intersects(WatchMask::CLOSE_NOWRITE));
2804    }
2805
2806    #[test]
2807    fn event_kind_mask_to_watch_mask_all() {
2808        use super::event_kind_mask_to_watch_mask;
2809
2810        let mask = EventKindMask::ALL;
2811        let watch_mask = event_kind_mask_to_watch_mask(mask, false);
2812
2813        // ALL includes everything from CORE plus ACCESS
2814        assert!(watch_mask.intersects(WatchMask::OPEN));
2815        assert!(watch_mask.intersects(WatchMask::CLOSE_WRITE));
2816        assert!(watch_mask.intersects(WatchMask::CLOSE_NOWRITE));
2817    }
2818
2819    #[test]
2820    fn event_kind_mask_to_watch_mask_empty() {
2821        use super::event_kind_mask_to_watch_mask;
2822
2823        let mask = EventKindMask::empty();
2824        let watch_mask = event_kind_mask_to_watch_mask(mask, false);
2825
2826        // Empty mask should produce empty watch mask
2827        assert!(watch_mask.is_empty());
2828    }
2829
2830    #[test]
2831    fn event_kind_mask_to_watch_mask_access_only() {
2832        use super::event_kind_mask_to_watch_mask;
2833
2834        // ACCESS_CLOSE only maps to CLOSE_WRITE (not CLOSE_NOWRITE)
2835        let mask = EventKindMask::ACCESS_OPEN | EventKindMask::ACCESS_CLOSE;
2836        let watch_mask = event_kind_mask_to_watch_mask(mask, false);
2837
2838        assert!(watch_mask.intersects(WatchMask::OPEN));
2839        assert!(watch_mask.intersects(WatchMask::CLOSE_WRITE));
2840        assert!(!watch_mask.intersects(WatchMask::CLOSE_NOWRITE));
2841
2842        // Should NOT have create/modify/remove
2843        assert!(!watch_mask.intersects(WatchMask::CREATE));
2844        assert!(!watch_mask.intersects(WatchMask::DELETE));
2845        assert!(!watch_mask.intersects(WatchMask::MODIFY));
2846        assert!(!watch_mask.intersects(WatchMask::ATTRIB));
2847    }
2848
2849    #[test]
2850    fn event_kind_mask_to_watch_mask_all_access() {
2851        use super::event_kind_mask_to_watch_mask;
2852
2853        // ALL_ACCESS includes OPEN, CLOSE_WRITE, and CLOSE_NOWRITE
2854        let mask = EventKindMask::ALL_ACCESS;
2855        let watch_mask = event_kind_mask_to_watch_mask(mask, false);
2856
2857        assert!(watch_mask.intersects(WatchMask::OPEN));
2858        assert!(watch_mask.intersects(WatchMask::CLOSE_WRITE));
2859        assert!(watch_mask.intersects(WatchMask::CLOSE_NOWRITE));
2860    }
2861
2862    #[test]
2863    fn event_kind_mask_to_watch_mask_recursive_includes_create() {
2864        use super::event_kind_mask_to_watch_mask;
2865
2866        // Recursive mode includes CREATE|MOVED_TO even without CREATE in mask
2867        let watch_mask = event_kind_mask_to_watch_mask(EventKindMask::MODIFY_DATA, true);
2868        assert!(watch_mask.intersects(WatchMask::CREATE));
2869        assert!(watch_mask.intersects(WatchMask::MOVED_TO));
2870        assert!(watch_mask.intersects(WatchMask::MODIFY));
2871
2872        // Empty mask with recursive still includes CREATE|MOVED_TO
2873        let watch_mask = event_kind_mask_to_watch_mask(EventKindMask::empty(), true);
2874        assert!(watch_mask.intersects(WatchMask::CREATE));
2875        assert!(watch_mask.intersects(WatchMask::MOVED_TO));
2876        assert!(!watch_mask.intersects(WatchMask::MODIFY));
2877
2878        // Non-recursive mode does not include CREATE when not requested
2879        let watch_mask = event_kind_mask_to_watch_mask(EventKindMask::MODIFY_DATA, false);
2880        assert!(!watch_mask.intersects(WatchMask::CREATE));
2881        assert!(!watch_mask.intersects(WatchMask::MOVED_TO));
2882        assert!(watch_mask.intersects(WatchMask::MODIFY));
2883    }
2884
2885    /// Recursive watches with MODIFY-only mask still track new subdirectories.
2886    #[test]
2887    fn recursive_watch_tracks_subdirs_without_create_mask() {
2888        let tmpdir = testdir();
2889        let (mut watcher, mut rx) = channel_with_config::<INotifyWatcher>(
2890            ChannelConfig::default()
2891                .with_timeout(std::time::Duration::from_secs(2))
2892                .with_watcher_config(
2893                    Config::default().with_event_kinds(EventKindMask::MODIFY_DATA),
2894                ),
2895        );
2896        watcher.watch_recursively(&tmpdir);
2897
2898        let subdir = tmpdir.path().join("subdir");
2899        std::fs::create_dir(&subdir).expect("create subdir");
2900
2901        // Wait for watch to be added on new subdirectory
2902        std::thread::sleep(std::time::Duration::from_millis(50));
2903
2904        let file_path = subdir.join("file.txt");
2905        let mut file = std::fs::File::create_new(&file_path).expect("create file");
2906
2907        use std::io::Write;
2908        file.write_all(b"hello").expect("write");
2909        file.flush().expect("flush");
2910        drop(file);
2911
2912        // Receives MODIFY from subdir (tracking works), no CREATE events (filtered)
2913        rx.wait_ordered_exact([expected(&file_path).modify_data()])
2914            .ensure_no_tail();
2915    }
2916
2917    /// A recursive watch resolves the path it walks, so the flag cannot silence it.
2918    #[test]
2919    fn recursive_watch_of_a_symlinked_dir_without_dereference_still_walks() {
2920        let tmpdir = testdir();
2921        let (mut watcher, mut rx) = watcher_with_data_events();
2922        let destination = tmpdir.path().join("destination");
2923        std::fs::create_dir(&destination).expect("create");
2924        let file = destination.join("file");
2925        std::fs::write(&file, "").expect("write");
2926        let link = tmpdir.path().join("link");
2927        std::os::unix::fs::symlink(&destination, &link).expect("symlink");
2928
2929        watcher
2930            .watcher
2931            .watch_with(&link, recursive_watch().with_dereference_symlinks(false))
2932            .expect("watch link recursively");
2933
2934        std::fs::write(&file, "123").expect("write");
2935
2936        rx.wait_ordered([expected(link.join("file")).modify_data_any().multiple()]);
2937    }
2938
2939    // Regression tests for https://github.com/notify-rs/notify/issues/255.
2940    #[test]
2941    fn watching_a_dangling_symlink_without_dereference() {
2942        let tmpdir = tempfile::tempdir().unwrap();
2943        let link = tmpdir.path().join("link");
2944        std::os::unix::fs::symlink(tmpdir.path().join("missing"), &link).unwrap();
2945
2946        let inotify = super::inotify_sys::Inotify::init().unwrap();
2947        let mut event_loop = EventLoop::new(inotify, Box::new(|_| {}), &Config::default()).unwrap();
2948
2949        event_loop
2950            .add_watch(
2951                WatchPath::new(&link).unwrap(),
2952                non_recursive_watch().with_dereference_symlinks(false),
2953                true,
2954            )
2955            .expect("watch dangling symlink");
2956
2957        let watch = event_loop.watches.get(&link).expect("link watch");
2958        assert!(!watch.is_dir);
2959        assert!(!watch.dereference);
2960    }
2961
2962    #[test]
2963    fn watching_a_dangling_symlink_with_dereference_is_not_found() {
2964        let tmpdir = tempfile::tempdir().unwrap();
2965        let link = tmpdir.path().join("link");
2966        std::os::unix::fs::symlink(tmpdir.path().join("missing"), &link).unwrap();
2967
2968        let inotify = super::inotify_sys::Inotify::init().unwrap();
2969        let mut event_loop = EventLoop::new(inotify, Box::new(|_| {}), &Config::default()).unwrap();
2970
2971        let result =
2972            event_loop.add_watch(WatchPath::new(&link).unwrap(), non_recursive_watch(), true);
2973
2974        assert!(matches!(
2975            result,
2976            Err(Error {
2977                paths: _,
2978                kind: ErrorKind::PathNotFound
2979            })
2980        ))
2981    }
2982
2983    #[test]
2984    fn recursive_watch_of_a_symlink_to_a_file_still_follows_it() {
2985        let tmpdir = tempfile::tempdir().unwrap();
2986        let destination = tmpdir.path().join("destination");
2987        let link = tmpdir.path().join("link");
2988        std::fs::write(&destination, "").unwrap();
2989        std::os::unix::fs::symlink(&destination, &link).unwrap();
2990
2991        let inotify = super::inotify_sys::Inotify::init().unwrap();
2992        let mut event_loop = EventLoop::new(inotify, Box::new(|_| {}), &Config::default()).unwrap();
2993
2994        event_loop
2995            .add_watch(WatchPath::new(&link).unwrap(), recursive_watch(), true)
2996            .expect("watch link recursively");
2997
2998        let watch = event_loop.watches.get(&link).expect("link watch");
2999        assert!(watch.dereference);
3000        assert!(!watch.watch_mask.intersects(super::RESOLUTION_FLAGS));
3001    }
3002
3003    #[test]
3004    fn a_recursive_walk_stops_at_an_explicit_non_dereferenced_link() {
3005        let tmpdir = tempfile::tempdir().unwrap();
3006        let outside = tempfile::tempdir().unwrap();
3007        let root = tmpdir.path().to_path_buf();
3008        let destination = outside.path().join("destination");
3009        let nested = destination.join("nested");
3010        let link = root.join("link");
3011        std::fs::create_dir_all(&nested).unwrap();
3012        std::os::unix::fs::symlink(&destination, &link).unwrap();
3013
3014        let inotify = super::inotify_sys::Inotify::init().unwrap();
3015        let mut event_loop = EventLoop::new(inotify, Box::new(|_| {}), &Config::default()).unwrap();
3016
3017        event_loop
3018            .add_watch(
3019                WatchPath::new(&link).unwrap(),
3020                non_recursive_watch().with_dereference_symlinks(false),
3021                true,
3022            )
3023            .expect("watch link");
3024        event_loop
3025            .add_watch(WatchPath::new(&root).unwrap(), recursive_watch(), true)
3026            .expect("watch root recursively");
3027
3028        let watch = event_loop.watches.get(&link).expect("link watch");
3029        assert!(!watch.dereference);
3030        assert!(watch.metadata.is_user_watch);
3031        assert!(
3032            !event_loop.watches.contains_key(&link.join("nested")),
3033            "the recursive walk crossed an explicit non-dereferenced link"
3034        );
3035    }
3036
3037    #[test]
3038    fn recursive_watch_does_not_report_errors_for_an_explicit_non_dereferenced_link() {
3039        let tmpdir = tempfile::tempdir().unwrap();
3040        let root = tmpdir.path().to_path_buf();
3041        let link = root.join("loop");
3042        std::os::unix::fs::symlink(".", &link).unwrap();
3043
3044        let (tx, rx) = mpsc::channel::<Result<Event>>();
3045        let inotify = super::inotify_sys::Inotify::init().unwrap();
3046        let mut event_loop = EventLoop::new(inotify, Box::new(tx), &Config::default()).unwrap();
3047
3048        event_loop
3049            .add_watch(
3050                WatchPath::new(&link).unwrap(),
3051                non_recursive_watch().with_dereference_symlinks(false),
3052                true,
3053            )
3054            .expect("watch link itself");
3055        event_loop
3056            .add_watch(WatchPath::new(&root).unwrap(), recursive_watch(), true)
3057            .expect("watch root recursively");
3058
3059        assert!(event_loop.watches.contains_key(&root));
3060        let watch = event_loop.watches.get(&link).expect("link remains watched");
3061        assert!(!watch.dereference);
3062
3063        // The walk reports errors synchronously from add_watch.
3064        let errors: Vec<_> = rx.try_iter().filter_map(Result::err).collect();
3065        assert!(
3066            errors.is_empty(),
3067            "the excluded link must not produce walk errors: {errors:?}"
3068        );
3069    }
3070
3071    #[test]
3072    fn recursive_watch_only_reports_unreadable_links_that_are_not_excluded() {
3073        let tmpdir = tempfile::tempdir().unwrap();
3074        let root = tmpdir.path().join("root");
3075        let destination = tmpdir.path().join("unreadable");
3076        std::fs::create_dir(&root).unwrap();
3077        if !unwatchable_dir(&destination) {
3078            return;
3079        }
3080        let excluded = root.join("excluded");
3081        let included = root.join("included");
3082        std::os::unix::fs::symlink(&destination, &excluded).unwrap();
3083        std::os::unix::fs::symlink(&destination, &included).unwrap();
3084
3085        let (tx, rx) = mpsc::channel::<Result<Event>>();
3086        let inotify = super::inotify_sys::Inotify::init().unwrap();
3087        let mut event_loop = EventLoop::new(inotify, Box::new(tx), &Config::default()).unwrap();
3088        event_loop
3089            .add_watch(
3090                WatchPath::new(&excluded).unwrap(),
3091                non_recursive_watch().with_dereference_symlinks(false),
3092                true,
3093            )
3094            .expect("watch excluded link itself");
3095
3096        let result = event_loop.add_watch(WatchPath::new(&root).unwrap(), recursive_watch(), true);
3097        make_readable(&destination);
3098        result.expect("watch root recursively");
3099
3100        let errors: Vec<_> = rx.try_iter().filter_map(Result::err).collect();
3101        assert_eq!(errors.len(), 1, "unexpected walk errors: {errors:?}");
3102        assert_eq!(errors[0].paths, [included]);
3103        assert!(matches!(
3104            &errors[0].kind,
3105            ErrorKind::Io(err) if err.kind() == std::io::ErrorKind::PermissionDenied
3106        ));
3107    }
3108
3109    #[test]
3110    fn recursive_watch_follows_only_root_symlink_when_follow_symlinks_is_disabled() {
3111        let tmpdir = tempfile::tempdir().unwrap();
3112        let destination = tmpdir.path().join("destination");
3113        std::fs::create_dir_all(destination.join("child")).unwrap();
3114        std::os::unix::fs::symlink("child", destination.join("alias")).unwrap();
3115        std::os::unix::fs::symlink(".", destination.join("loop")).unwrap();
3116        let root = tmpdir.path().join("root");
3117        std::os::unix::fs::symlink(&destination, &root).unwrap();
3118
3119        let (tx, rx) = mpsc::channel::<Result<Event>>();
3120        let inotify = super::inotify_sys::Inotify::init().unwrap();
3121        let mut event_loop = EventLoop::new(
3122            inotify,
3123            Box::new(tx),
3124            &Config::default().with_follow_symlinks(false),
3125        )
3126        .unwrap();
3127        event_loop
3128            .add_watch(WatchPath::new(&root).unwrap(), recursive_watch(), true)
3129            .expect("watch root recursively");
3130
3131        assert_eq!(event_loop.watches.len(), 2);
3132        assert!(event_loop.watches[&root].metadata.is_user_watch);
3133        assert!(event_loop.watches.contains_key(&root.join("child")));
3134        let errors: Vec<_> = rx.try_iter().filter_map(Result::err).collect();
3135        assert!(errors.is_empty(), "unexpected walk errors: {errors:?}");
3136    }
3137
3138    #[test]
3139    fn watch_with_dereference_disabled_reports_the_link_not_its_destination() {
3140        let tmpdir = testdir();
3141        let (mut watcher, mut rx) = watcher_with_data_events();
3142        let destination = tmpdir.path().join("destination");
3143        let link = tmpdir.path().join("link");
3144        std::fs::write(&destination, "").expect("write");
3145        std::os::unix::fs::symlink(&destination, &link).expect("symlink");
3146
3147        watcher
3148            .watcher
3149            .watch_with(
3150                &link,
3151                non_recursive_watch().with_dereference_symlinks(false),
3152            )
3153            .expect("watch link");
3154        watcher.watch_nonrecursively(&destination);
3155
3156        std::fs::write(&destination, "123").expect("write");
3157
3158        rx.wait_ordered_exact([expected(&destination).modify_data_any().multiple()]);
3159    }
3160
3161    /// Taking a watch off a link must not drop an ancestor's coverage of the destination.
3162    #[test]
3163    fn watching_a_link_itself_keeps_the_ancestors_recursive_coverage() {
3164        let tmpdir = testdir();
3165        let (mut watcher, mut rx) = watcher_with_data_events();
3166        let destination = tmpdir.path().join("destination");
3167        std::fs::create_dir(&destination).expect("create");
3168        let file = destination.join("file");
3169        std::fs::write(&file, "").expect("write");
3170        let link = tmpdir.path().join("link");
3171        std::os::unix::fs::symlink(&destination, &link).expect("symlink");
3172
3173        watcher.watch_nonrecursively(&link);
3174        watcher.watch_recursively(tmpdir.path());
3175        watcher
3176            .watcher
3177            .watch_with(
3178                &link,
3179                non_recursive_watch().with_dereference_symlinks(false),
3180            )
3181            .expect("watch the link itself");
3182
3183        std::fs::write(&file, "123").expect("write");
3184
3185        let reported: std::collections::HashSet<_> =
3186            rx.iter().flat_map(|event| event.paths).collect();
3187        assert_eq!(
3188            reported,
3189            std::collections::HashSet::from([file]),
3190            "the ancestor recursive watch must keep reporting the destination by its real path"
3191        );
3192    }
3193
3194    /// It does drop it when the link is the only way the tree reaches the destination.
3195    #[test]
3196    fn watching_a_link_itself_drops_a_destination_reached_only_through_it() {
3197        let outside = testdir();
3198        let destination = outside.path().join("destination");
3199        let nested = destination.join("nested");
3200        std::fs::create_dir_all(&nested).expect("create");
3201        let file = nested.join("file");
3202        std::fs::write(&file, "").expect("write");
3203
3204        let tmpdir = testdir();
3205        let (mut watcher, mut rx) = watcher_with_data_events();
3206        let link = tmpdir.path().join("link");
3207        std::os::unix::fs::symlink(&destination, &link).expect("symlink");
3208
3209        watcher.watch_recursively(tmpdir.path());
3210        watcher
3211            .watcher
3212            .watch_with(
3213                &link,
3214                non_recursive_watch().with_dereference_symlinks(false),
3215            )
3216            .expect("watch the link itself");
3217
3218        std::fs::write(&file, "123").expect("write");
3219
3220        let reported: Vec<_> = rx.iter().flat_map(|event| event.paths).collect();
3221        assert!(
3222            reported.is_empty(),
3223            "the destination is still reported after the caller asked not to follow the link: \
3224             {reported:?}"
3225        );
3226    }
3227}