Skip to main content

notify/
poll.rs

1//! Generic Watcher implementation based on polling
2//!
3//! Checks the `watch`ed paths periodically to detect changes. This implementation only uses
4//! Rust stdlib APIs and should work on all of the platforms it supports.
5
6use crate::{
7    Config, Error, EventHandler, Receiver, RecursiveMode, Sender, Watcher,
8    paths::{WatchPath, absolute_path},
9    unbounded,
10};
11use std::{
12    collections::HashMap,
13    path::{Path, PathBuf},
14    sync::{
15        Arc, Mutex,
16        atomic::{AtomicBool, Ordering},
17    },
18    thread,
19    time::Duration,
20};
21
22pub(crate) enum PollMessage {
23    Poll,
24    PollAndWait(Sender<crate::Result<()>>),
25}
26
27/// Event sent for registered handlers on initial directory scans
28pub type ScanEvent = crate::Result<PathBuf>;
29
30/// Handler trait for receivers of [`ScanEvent`].
31/// Very much the same as [`EventHandler`], but including the Result.
32///
33/// See the full example for more information.
34pub trait ScanEventHandler: Send + 'static {
35    /// Handles an event.
36    fn handle_event(&mut self, event: ScanEvent);
37}
38
39impl<F> ScanEventHandler for F
40where
41    F: FnMut(ScanEvent) + Send + 'static,
42{
43    fn handle_event(&mut self, event: ScanEvent) {
44        (self)(event);
45    }
46}
47
48#[cfg(feature = "crossbeam-channel")]
49impl ScanEventHandler for crossbeam_channel::Sender<ScanEvent> {
50    fn handle_event(&mut self, event: ScanEvent) {
51        let _ = self.send(event);
52    }
53}
54
55#[cfg(feature = "flume")]
56impl ScanEventHandler for flume::Sender<ScanEvent> {
57    fn handle_event(&mut self, event: ScanEvent) {
58        let _ = self.send(event);
59    }
60}
61
62impl ScanEventHandler for std::sync::mpsc::Sender<ScanEvent> {
63    fn handle_event(&mut self, event: ScanEvent) {
64        let _ = self.send(event);
65    }
66}
67
68impl ScanEventHandler for () {
69    fn handle_event(&mut self, _event: ScanEvent) {}
70}
71
72use data::{DataBuilder, WatchData};
73mod data {
74    use crate::{
75        EventHandler, RecursiveMode,
76        event::{CreateKind, DataChange, Event, EventKind, MetadataKind, ModifyKind, RemoveKind},
77        paths::{WatchPath, reported_path},
78    };
79    use notify_types::event::EventKindMask;
80    use std::{
81        cell::RefCell,
82        collections::HashMap,
83        fmt::{self, Debug},
84        fs::{self, File, Metadata},
85        io::{self, Read},
86        path::{Path, PathBuf},
87        time::Instant,
88    };
89    use walkdir::WalkDir;
90    use xxhash_rust::xxh3::Xxh3Default;
91
92    use super::ScanEventHandler;
93
94    const NANOS_PER_SECOND: i128 = 1_000_000_000;
95
96    fn system_time_to_nanos(time: std::time::SystemTime) -> i128 {
97        match time.duration_since(std::time::SystemTime::UNIX_EPOCH) {
98            Ok(d) => d.as_nanos() as i128,
99            Err(e) => -(e.duration().as_nanos() as i128),
100        }
101    }
102
103    /// Builder for [`WatchData`] & [`PathData`].
104    pub(super) struct DataBuilder {
105        emitter: EventEmitter,
106        scan_emitter: Option<Box<RefCell<dyn ScanEventHandler>>>,
107        compare_contents: bool,
108
109        // current timestamp for building Data.
110        now: Instant,
111    }
112
113    impl DataBuilder {
114        pub(super) fn new<F, G>(
115            event_handler: F,
116            compare_content: bool,
117            scan_emitter: Option<G>,
118            event_kinds: EventKindMask,
119        ) -> Self
120        where
121            F: EventHandler,
122            G: ScanEventHandler,
123        {
124            let scan_emitter = match scan_emitter {
125                None => None,
126                Some(v) => {
127                    // workaround for a weird type resolution bug when directly going to dyn Trait
128                    let intermediate: Box<RefCell<dyn ScanEventHandler>> =
129                        Box::new(RefCell::new(v));
130                    Some(intermediate)
131                }
132            };
133            Self {
134                emitter: EventEmitter::new(event_handler, event_kinds),
135                scan_emitter,
136                compare_contents: compare_content,
137                now: Instant::now(),
138            }
139        }
140
141        /// Update internal timestamp.
142        pub(super) fn update_timestamp(&mut self) {
143            self.now = Instant::now();
144        }
145
146        /// Create [`WatchData`].
147        ///
148        /// This function will return `Err(_)` if can not retrieve metadata from
149        /// the path location. (e.g., not found).
150        pub(super) fn build_watch_data(
151            &self,
152            root: WatchPath,
153            is_recursive: bool,
154            follow_symlinks: bool,
155        ) -> crate::Result<WatchData> {
156            WatchData::new(self, root, is_recursive, follow_symlinks)
157        }
158
159        /// Create [`PathData`].
160        fn build_path_data(&self, meta_path: &MetaPath) -> PathData {
161            PathData::new(self, meta_path)
162        }
163    }
164
165    impl Debug for DataBuilder {
166        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
167            f.debug_struct("DataBuilder")
168                .field("compare_contents", &self.compare_contents)
169                .field("now", &self.now)
170                .finish()
171        }
172    }
173
174    #[derive(Debug)]
175    pub(super) struct WatchData {
176        // config part, won't change.
177        root: PathBuf,
178        requested_root: PathBuf,
179        is_recursive: bool,
180        follow_symlinks: bool,
181
182        // current status part.
183        all_path_data: HashMap<PathBuf, PathData>,
184    }
185
186    impl WatchData {
187        /// Scan filesystem and create a new `WatchData`.
188        ///
189        /// # Side effect
190        ///
191        /// This function may send event by `data_builder.emitter`.
192        fn new(
193            data_builder: &DataBuilder,
194            root: WatchPath,
195            is_recursive: bool,
196            follow_symlinks: bool,
197        ) -> crate::Result<Self> {
198            // Creating a watch on a path that cannot be stat-ed (e.g. because it
199            // does not exist) fails the `poll_watcher.watch(root, ..)` call, so
200            // the error is reported to the caller instead of silently skipping
201            // the path. `Error::io_watch` maps `io::ErrorKind::NotFound` to
202            // `ErrorKind::PathNotFound`, matching the other backends.
203            //
204            // This is intentionally different from *POLLING* a watch: when a
205            // watched path disappears, the existing watch is kept and io errors
206            // encountered while scanning are emitted as events instead.
207            fs::metadata(&root.absolute)
208                .map_err(|e| crate::Error::io_watch(e).add_path(root.requested.clone()))?;
209
210            let all_path_data = Self::scan_all_path_data(
211                data_builder,
212                root.absolute.clone(),
213                root.requested.clone(),
214                is_recursive,
215                follow_symlinks,
216                true,
217            )
218            .collect();
219
220            Ok(Self {
221                root: root.absolute,
222                requested_root: root.requested,
223                is_recursive,
224                follow_symlinks,
225                all_path_data,
226            })
227        }
228
229        /// Rescan filesystem and update this `WatchData`.
230        ///
231        /// # Side effect
232        ///
233        /// This function may emit event by `data_builder.emitter`.
234        pub(super) fn rescan(&mut self, data_builder: &mut DataBuilder) {
235            // scan current filesystem.
236            for (path, new_path_data) in Self::scan_all_path_data(
237                data_builder,
238                self.root.clone(),
239                self.requested_root.clone(),
240                self.is_recursive,
241                self.follow_symlinks,
242                false,
243            ) {
244                let event_kind = if let Some(old_path_data) = self.all_path_data.get_mut(&path) {
245                    let event_kind =
246                        PathData::compare_to_kind(Some(&*old_path_data), Some(&new_path_data));
247                    *old_path_data = new_path_data;
248                    event_kind
249                } else {
250                    let event_kind = PathData::compare_to_kind(None, Some(&new_path_data));
251                    self.all_path_data.insert(path.clone(), new_path_data);
252                    event_kind
253                };
254
255                if let Some(event_kind) = event_kind {
256                    let event = Event::new(event_kind).add_path(reported_path(
257                        &self.root,
258                        &self.requested_root,
259                        &path,
260                    ));
261                    data_builder.emitter.emit_ok(event);
262                }
263            }
264
265            // scan for disappeared paths.
266            let mut disappeared_paths = Vec::new();
267            for (path, path_data) in self.all_path_data.iter() {
268                if path_data.last_check < data_builder.now {
269                    disappeared_paths.push(path.clone());
270                }
271            }
272
273            // remove disappeared paths
274            for path in disappeared_paths {
275                let old_path_data = self.all_path_data.remove(&path);
276
277                if let Some(event_kind) = PathData::compare_to_kind(old_path_data.as_ref(), None) {
278                    let event = Event::new(event_kind).add_path(reported_path(
279                        &self.root,
280                        &self.requested_root,
281                        &path,
282                    ));
283                    data_builder.emitter.emit_ok(event);
284                }
285            }
286        }
287
288        /// Get all `PathData` by given configuration.
289        ///
290        /// # Side Effect
291        ///
292        /// This function may emit some IO Error events by `data_builder.emitter`.
293        fn scan_all_path_data(
294            data_builder: &'_ DataBuilder,
295            root: PathBuf,
296            requested_root: PathBuf,
297            is_recursive: bool,
298            follow_symlinks: bool,
299            // whether this is an initial scan, used only for events
300            is_initial: bool,
301        ) -> impl Iterator<Item = (PathBuf, PathData)> + '_ {
302            log::trace!("rescanning {root:?}");
303            // WalkDir return only one entry if root is a file (not a folder),
304            // so we can use single logic to do the both file & dir's jobs.
305            //
306            // See: https://docs.rs/walkdir/2.0.1/walkdir/struct.WalkDir.html#method.new
307            WalkDir::new(root.clone())
308                .follow_links(follow_symlinks)
309                .max_depth(Self::dir_scan_depth(is_recursive))
310                .into_iter()
311                .filter_map(|entry_res| match entry_res {
312                    Ok(entry) => Some(entry),
313                    Err(err) => {
314                        log::warn!("walkdir error scanning {err:?}");
315
316                        if let Some(io_error) = err.io_error() {
317                            // clone an io::Error, so we have to create a new one.
318                            let new_io_error = io::Error::new(io_error.kind(), err.to_string());
319                            data_builder.emitter.emit_io_err(new_io_error, err.path());
320                        } else {
321                            let crate_err =
322                                crate::Error::new(crate::ErrorKind::Generic(err.to_string()));
323                            data_builder.emitter.emit(Err(crate_err));
324                        }
325                        None
326                    }
327                })
328                .filter_map(move |entry| match entry.metadata() {
329                    Ok(metadata) => {
330                        let path = entry.into_path();
331                        if is_initial {
332                            // emit initial scans
333                            if let Some(ref emitter) = data_builder.scan_emitter {
334                                emitter.borrow_mut().handle_event(Ok(reported_path(
335                                    &root,
336                                    &requested_root,
337                                    &path,
338                                )));
339                            }
340                        }
341                        let meta_path = MetaPath::from_parts_unchecked(path, metadata);
342                        let data_path = data_builder.build_path_data(&meta_path);
343
344                        Some((meta_path.into_path(), data_path))
345                    }
346                    Err(e) => {
347                        // emit event.
348                        let path = entry.into_path();
349                        data_builder.emitter.emit_io_err(e, Some(path));
350
351                        None
352                    }
353                })
354        }
355
356        fn dir_scan_depth(is_recursive: bool) -> usize {
357            if is_recursive { usize::MAX } else { 1 }
358        }
359
360        pub(super) fn recursive_mode(&self) -> RecursiveMode {
361            if self.is_recursive {
362                RecursiveMode::Recursive
363            } else {
364                RecursiveMode::NonRecursive
365            }
366        }
367
368        pub(super) fn requested_root(&self) -> &Path {
369            &self.requested_root
370        }
371    }
372
373    /// Stored data for a one path locations.
374    ///
375    /// See [`WatchData`] for more detail.
376    #[derive(Debug, Clone)]
377    struct PathData {
378        /// File updated time in nanoseconds since the Unix epoch.
379        mtime: i128,
380
381        /// Content's hash value, only available if user request compare file
382        /// contents and read successful.
383        hash: Option<u64>,
384
385        /// Checked time.
386        last_check: Instant,
387    }
388
389    impl PathData {
390        /// Create a new `PathData`.
391        fn new(data_builder: &DataBuilder, meta_path: &MetaPath) -> PathData {
392            let metadata = meta_path.metadata();
393            let compare_contents = data_builder.compare_contents && metadata.is_file();
394            let mut mtime = metadata.modified().map_or(0, system_time_to_nanos);
395            if metadata.is_dir() || compare_contents {
396                // Child changes update directory mtimes. Preserve the existing second precision for
397                // directories to avoid emitting a redundant directory event for every file event.
398                // Files with content comparison also retain second precision so changes keep their
399                // existing event classification.
400                mtime = mtime / NANOS_PER_SECOND * NANOS_PER_SECOND;
401            }
402
403            PathData {
404                mtime,
405                hash: if compare_contents {
406                    content_hash(meta_path.path()).ok()
407                } else {
408                    None
409                },
410
411                last_check: data_builder.now,
412            }
413        }
414
415        fn compare_to_kind(old: Option<&PathData>, new: Option<&PathData>) -> Option<EventKind> {
416            match (old, new) {
417                (Some(old), Some(new)) => {
418                    if new.mtime > old.mtime {
419                        Some(EventKind::Modify(ModifyKind::Metadata(
420                            MetadataKind::WriteTime,
421                        )))
422                    } else if new.hash != old.hash {
423                        Some(EventKind::Modify(ModifyKind::Data(DataChange::Any)))
424                    } else {
425                        None
426                    }
427                }
428                (None, Some(_new)) => Some(EventKind::Create(CreateKind::Any)),
429                (Some(_old), None) => Some(EventKind::Remove(RemoveKind::Any)),
430                (None, None) => None,
431            }
432        }
433    }
434
435    /// Get hash value for the data content in given file `path`.
436    pub(super) fn content_hash(path: &Path) -> io::Result<u64> {
437        let mut hasher = Xxh3Default::new();
438        let mut file = File::open(path)?;
439        let mut buf = [0u8; 8 * 1024];
440
441        loop {
442            let n = match file.read(&mut buf) {
443                Ok(0) => break,
444                Ok(len) => len,
445                Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
446                Err(e) => return Err(e),
447            };
448
449            hasher.update(&buf[..n]);
450        }
451
452        Ok(hasher.digest())
453    }
454
455    /// Compose path and its metadata.
456    ///
457    /// This data structure designed for make sure path and its metadata can be
458    /// transferred in consistent way, and may avoid some duplicated
459    /// `fs::metadata()` function call in some situations.
460    #[derive(Debug)]
461    pub(super) struct MetaPath {
462        path: PathBuf,
463        metadata: Metadata,
464    }
465
466    impl MetaPath {
467        /// Create `MetaPath` by given parts.
468        ///
469        /// # Invariant
470        ///
471        /// User must make sure the input `metadata` are associated with `path`.
472        fn from_parts_unchecked(path: PathBuf, metadata: Metadata) -> Self {
473            Self { path, metadata }
474        }
475
476        fn path(&self) -> &Path {
477            &self.path
478        }
479
480        fn metadata(&self) -> &Metadata {
481            &self.metadata
482        }
483
484        fn into_path(self) -> PathBuf {
485            self.path
486        }
487    }
488
489    /// Thin wrapper for outer event handler, for easy to use.
490    struct EventEmitter {
491        // Use `RefCell` to make sure `emit()` only need shared borrow of self (&self).
492        // Use `Box` to make sure EventEmitter is Sized.
493        handler: Box<RefCell<dyn EventHandler>>,
494        /// Event kind filter - only events matching this mask are emitted.
495        event_kinds: EventKindMask,
496    }
497
498    impl EventEmitter {
499        fn new<F: EventHandler>(event_handler: F, event_kinds: EventKindMask) -> Self {
500            Self {
501                handler: Box::new(RefCell::new(event_handler)),
502                event_kinds,
503            }
504        }
505
506        /// Emit single event (errors always pass through).
507        fn emit(&self, event: crate::Result<Event>) {
508            self.handler.borrow_mut().handle_event(event);
509        }
510
511        /// Emit event, filtered by event_kinds mask.
512        fn emit_ok(&self, event: Event) {
513            // Only emit if the event kind matches the configured mask
514            if self.event_kinds.matches(&event.kind) {
515                self.emit(Ok(event))
516            }
517        }
518
519        /// Emit io error event.
520        fn emit_io_err<E, P>(&self, err: E, path: Option<P>)
521        where
522            E: Into<io::Error>,
523            P: Into<PathBuf>,
524        {
525            let e = crate::Error::io(err.into());
526            if let Some(path) = path {
527                self.emit(Err(e.add_path(path.into())));
528            } else {
529                self.emit(Err(e));
530            }
531        }
532    }
533}
534
535/// Polling based `Watcher` implementation.
536///
537/// By default scans through all files and checks for changed entries based on their change date.
538/// Can also be changed to perform file content change checks.
539///
540/// See [Config] for more details.
541#[derive(Debug)]
542pub struct PollWatcher {
543    watches: Arc<Mutex<HashMap<PathBuf, WatchData>>>,
544    data_builder: Arc<Mutex<DataBuilder>>,
545    want_to_stop: Arc<AtomicBool>,
546    /// channel to the poll loop
547    /// currently used only for manual polling
548    message_channel: Sender<PollMessage>,
549    delay: Option<Duration>,
550    follow_sylinks: bool,
551}
552
553impl PollWatcher {
554    /// Create a new [`PollWatcher`], configured as needed.
555    pub fn new<F: EventHandler>(event_handler: F, config: Config) -> crate::Result<PollWatcher> {
556        Self::with_opt::<_, ()>(event_handler, config, None)
557    }
558
559    /// Actively poll for changes. Can be combined with a timeout of 0 to perform only manual polling.
560    pub fn poll(&self) -> crate::Result<()> {
561        self.message_channel
562            .send(PollMessage::Poll)
563            .map_err(|_| Error::generic("failed to send poll message"))?;
564        Ok(())
565    }
566
567    /// Actively poll for changes and block until the poll cycle has completed.
568    ///
569    /// This is primarily useful together with [`Config::with_manual_polling`].
570    pub fn poll_blocking(&self) -> crate::Result<()> {
571        let (done_tx, done_rx) = std::sync::mpsc::channel();
572        self.message_channel
573            .send(PollMessage::PollAndWait(done_tx))
574            .map_err(|_| Error::generic("failed to send poll message"))?;
575        done_rx
576            .recv()
577            .map_err(|_| Error::generic("poll thread disconnected"))?
578    }
579
580    /// Returns a sender to initiate changes detection.
581    #[cfg(test)]
582    pub(crate) fn poll_sender(&self) -> Sender<PollMessage> {
583        self.message_channel.clone()
584    }
585
586    /// Create a new [`PollWatcher`] with an scan event handler.
587    ///
588    /// `scan_fallback` is called on the initial scan with all files seen by the pollwatcher.
589    pub fn with_initial_scan<F: EventHandler, G: ScanEventHandler>(
590        event_handler: F,
591        config: Config,
592        scan_callback: G,
593    ) -> crate::Result<PollWatcher> {
594        Self::with_opt(event_handler, config, Some(scan_callback))
595    }
596
597    /// create a new [`PollWatcher`] with all options.
598    fn with_opt<F: EventHandler, G: ScanEventHandler>(
599        event_handler: F,
600        config: Config,
601        scan_callback: Option<G>,
602    ) -> crate::Result<PollWatcher> {
603        let data_builder = DataBuilder::new(
604            event_handler,
605            config.compare_contents(),
606            scan_callback,
607            config.event_kinds(),
608        );
609
610        let (tx, rx) = unbounded::<PollMessage>();
611
612        let poll_watcher = PollWatcher {
613            watches: Default::default(),
614            data_builder: Arc::new(Mutex::new(data_builder)),
615            want_to_stop: Arc::new(AtomicBool::new(false)),
616            delay: config.poll_interval(),
617            follow_sylinks: config.follow_symlinks(),
618            message_channel: tx,
619        };
620
621        poll_watcher.run(rx);
622
623        Ok(poll_watcher)
624    }
625
626    fn run(&self, rx: Receiver<PollMessage>) {
627        let watches = Arc::clone(&self.watches);
628        let data_builder = Arc::clone(&self.data_builder);
629        let want_to_stop = Arc::clone(&self.want_to_stop);
630        let delay = self.delay;
631
632        let _ = thread::Builder::new()
633            .name("notify-rs poll loop".to_string())
634            .spawn(move || {
635                // do an immediate first scan, then sleep `delay` between subsequent scans.
636                let mut first_auto_scan = true;
637
638                loop {
639                    if want_to_stop.load(Ordering::SeqCst) {
640                        break;
641                    }
642
643                    let msg = match delay {
644                        Some(_delay) if first_auto_scan => {
645                            first_auto_scan = false;
646                            None
647                        }
648                        Some(delay) => match rx.recv_timeout(delay) {
649                            Ok(msg) => Some(msg),
650                            Err(std::sync::mpsc::RecvTimeoutError::Timeout) => None,
651                            Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => break,
652                        },
653                        None => match rx.recv() {
654                            Ok(msg) => Some(msg),
655                            Err(_) => break,
656                        },
657                    };
658
659                    // HINT: Make sure always lock in the same order to avoid deadlock.
660                    //
661                    // FIXME: inconsistent: some place mutex poison cause panic,
662                    // some place just ignore.
663                    let scan_res = {
664                        let mut watches = watches.lock().unwrap_or_else(|e| e.into_inner());
665                        let mut data_builder =
666                            data_builder.lock().unwrap_or_else(|e| e.into_inner());
667
668                        data_builder.update_timestamp();
669
670                        let vals = watches.values_mut();
671                        for watch_data in vals {
672                            watch_data.rescan(&mut data_builder);
673                        }
674
675                        Ok(())
676                    };
677
678                    // Acknowledge poll requests after a poll cycle has finished.
679                    if let Some(PollMessage::PollAndWait(done)) = msg {
680                        let _ = done.send(scan_res);
681                    }
682                }
683            });
684    }
685
686    /// Watch a path location.
687    ///
688    /// Return `Err(_)` if the path cannot be watched, e.g. because it does not
689    /// exist or its metadata cannot be read.
690    fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> crate::Result<()> {
691        let watch_path = WatchPath::new(path)?;
692
693        // HINT: Make sure always lock in the same order to avoid deadlock.
694        //
695        // FIXME: inconsistent: some place mutex poison cause panic, some place just ignore.
696        let mut watches = self.watches.lock().unwrap_or_else(|e| e.into_inner());
697        let mut data_builder = self.data_builder.lock().unwrap_or_else(|e| e.into_inner());
698
699        data_builder.update_timestamp();
700
701        let watch_data = data_builder.build_watch_data(
702            watch_path.clone(),
703            recursive_mode.is_recursive(),
704            self.follow_sylinks,
705        )?;
706
707        watches.insert(watch_path.absolute, watch_data);
708
709        Ok(())
710    }
711
712    /// Unwatch a path.
713    ///
714    /// Return `Err(_)` if given path has't be monitored.
715    fn unwatch_inner(&mut self, path: &Path) -> crate::Result<()> {
716        let path = absolute_path(path)?;
717
718        // FIXME: inconsistent: some place mutex poison cause panic, some place just ignore.
719        self.watches
720            .lock()
721            .unwrap_or_else(|e| e.into_inner())
722            .remove(&path)
723            .map(|_| ())
724            .ok_or_else(crate::Error::watch_not_found)
725    }
726}
727
728impl Watcher for PollWatcher {
729    /// Create a new [`PollWatcher`].
730    fn new<F: EventHandler>(event_handler: F, config: Config) -> crate::Result<Self> {
731        Self::new(event_handler, config)
732    }
733
734    fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> crate::Result<()> {
735        self.watch_inner(path, recursive_mode)
736    }
737
738    fn unwatch(&mut self, path: &Path) -> crate::Result<()> {
739        self.unwatch_inner(path)
740    }
741
742    fn watched_paths(&self) -> crate::Result<Vec<(PathBuf, RecursiveMode)>> {
743        let watches = self.watches.lock().map_err(crate::Error::from)?;
744        Ok(watches
745            .values()
746            .map(|watch| (watch.requested_root().to_path_buf(), watch.recursive_mode()))
747            .collect())
748    }
749
750    fn kind() -> crate::WatcherKind {
751        crate::WatcherKind::PollWatcher
752    }
753}
754
755impl Drop for PollWatcher {
756    fn drop(&mut self) {
757        self.want_to_stop.store(true, Ordering::Relaxed);
758    }
759}
760
761#[cfg(test)]
762mod tests {
763    use super::PollWatcher;
764    use crate::{Config, RecursiveMode, Watcher, test::*};
765
766    fn watcher() -> (TestWatcher<PollWatcher>, Receiver) {
767        poll_watcher_channel()
768    }
769
770    /// Dates a path's write time an hour ahead. `PathData::mtime` has whole-second resolution
771    /// and is compared before the content hash, so a change to an entry is reported as
772    /// `Metadata(WriteTime)` unless its recorded write time is one that nothing happening
773    /// afterwards can exceed.
774    fn set_write_time_ahead(path: &std::path::Path) {
775        let ahead = std::time::SystemTime::now() + std::time::Duration::from_secs(3600);
776        std::fs::File::options()
777            .write(true)
778            .open(path)
779            .expect("open to set write time")
780            .set_modified(ahead)
781            .expect("set write time");
782    }
783
784    #[test]
785    fn poll_watcher_is_send_and_sync() {
786        fn check<T: Send + Sync>() {}
787        check::<PollWatcher>();
788    }
789
790    #[test]
791    fn watch_nonexistent_path_returns_path_not_found() {
792        let tmpdir = testdir();
793        let path = tmpdir.path().join("does_not_exist");
794
795        let mut watcher = PollWatcher::new(|_| {}, Config::default()).expect("create watcher");
796        let err = watcher
797            .watch(&path, RecursiveMode::Recursive)
798            .expect_err("watching a nonexistent path must return an error");
799
800        assert!(matches!(err.kind, crate::ErrorKind::PathNotFound));
801        assert_eq!(err.paths, vec![path]);
802
803        let watched = watcher.watched_paths().expect("list watched paths");
804        assert!(
805            watched.is_empty(),
806            "a failed watch must not be registered, got {watched:?}"
807        );
808    }
809
810    #[test]
811    fn watch_existing_paths_succeed() {
812        let tmpdir = testdir();
813        let file = tmpdir.path().join("entry");
814        std::fs::write(&file, "contents").expect("write file");
815
816        let mut watcher = PollWatcher::new(|_| {}, Config::default()).expect("create watcher");
817        watcher
818            .watch(&file, RecursiveMode::NonRecursive)
819            .expect("watch existing file");
820        watcher
821            .watch(tmpdir.path(), RecursiveMode::Recursive)
822            .expect("watch existing directory");
823
824        let watched = watcher.watched_paths().expect("list watched paths");
825        assert_eq!(watched.len(), 2);
826    }
827
828    #[test]
829    fn detects_subsecond_mtime_change_without_content_hashing() {
830        use std::{
831            fs,
832            sync::mpsc,
833            time::{Duration, SystemTime},
834        };
835
836        use crate::event::{EventKind, MetadataKind, ModifyKind};
837
838        let tmpdir = testdir();
839        let path = tmpdir.path().join("entry");
840        fs::write(&path, "initial").expect("write initial contents");
841
842        let current_second = SystemTime::now()
843            .duration_since(SystemTime::UNIX_EPOCH)
844            .expect("current time must be after the Unix epoch")
845            .as_secs();
846        let initial_mtime = SystemTime::UNIX_EPOCH
847            + Duration::from_secs(current_second)
848            + Duration::from_millis(100);
849        fs::File::options()
850            .write(true)
851            .open(&path)
852            .expect("open file to set initial mtime")
853            .set_modified(initial_mtime)
854            .expect("set initial mtime");
855
856        let (tx, rx) = mpsc::channel();
857        let mut watcher =
858            PollWatcher::new(tx, Config::default().with_manual_polling()).expect("create watcher");
859        watcher
860            .watch(tmpdir.path(), RecursiveMode::Recursive)
861            .expect("watch directory");
862
863        fs::write(&path, "updated").expect("write updated contents");
864        fs::File::options()
865            .write(true)
866            .open(&path)
867            .expect("open file to set updated mtime")
868            .set_modified(initial_mtime + Duration::from_millis(100))
869            .expect("set updated mtime");
870        watcher.poll().expect("poll for changes");
871
872        let event = rx
873            .recv_timeout(Duration::from_secs(5))
874            .expect("receive subsecond mtime change")
875            .expect("watch event must not be an error");
876        assert_eq!(event.paths, vec![path]);
877        assert_eq!(
878            event.kind,
879            EventKind::Modify(ModifyKind::Metadata(MetadataKind::WriteTime))
880        );
881    }
882
883    #[cfg(unix)]
884    #[test]
885    fn detects_subsecond_symlink_mtime_changes() {
886        use std::{os::unix::fs::symlink, sync::mpsc};
887
888        use nix::{
889            fcntl::AT_FDCWD,
890            sys::{
891                stat::{UtimensatFlags, utimensat},
892                time::TimeSpec,
893            },
894        };
895
896        use crate::event::{Event, EventKind, MetadataKind, ModifyKind};
897
898        for compare_contents in [false, true] {
899            let tmpdir = testdir();
900            let path = tmpdir.path().join("link");
901            symlink("missing", &path).expect("create dangling symlink");
902
903            let set_mtime = |nanos| {
904                utimensat(
905                    AT_FDCWD,
906                    &path,
907                    &TimeSpec::UTIME_OMIT,
908                    &TimeSpec::new(1_700_000_000, nanos),
909                    UtimensatFlags::NoFollowSymlink,
910                )
911                .expect("set symlink mtime");
912            };
913            set_mtime(100_000_000);
914
915            let (tx, rx) = mpsc::channel();
916            let config = Config::default()
917                .with_manual_polling()
918                .with_follow_symlinks(false)
919                .with_compare_contents(compare_contents);
920            let mut watcher = PollWatcher::new(tx, config).expect("create watcher");
921            watcher
922                .watch(tmpdir.path(), RecursiveMode::Recursive)
923                .expect("watch directory");
924
925            for nanos in [200_000_000, 300_000_000] {
926                set_mtime(nanos);
927                watcher.poll_blocking().expect("poll for changes");
928
929                let events = rx
930                    .try_iter()
931                    .collect::<crate::Result<Vec<_>>>()
932                    .expect("watch events must not be errors");
933                assert_eq!(
934                    events,
935                    vec![
936                        Event::new(EventKind::Modify(ModifyKind::Metadata(
937                            MetadataKind::WriteTime,
938                        )))
939                        .add_path(path.clone())
940                    ],
941                    "compare_contents={compare_contents}, nanos={nanos}"
942                );
943            }
944
945            watcher.poll_blocking().expect("poll without changes");
946            assert!(rx.try_recv().is_err(), "unchanged symlink emitted an event");
947        }
948    }
949
950    #[test]
951    fn unwatch_with_poisoned_mutex_does_not_panic() {
952        use std::{path::Path, sync::Arc};
953
954        let mut watcher = PollWatcher::new(|_| {}, Config::default()).expect("create watcher");
955
956        let watches = Arc::clone(&watcher.watches);
957        let _ = std::thread::spawn(move || {
958            let _guard = watches.lock().expect("lock watches");
959            panic!("poison watches mutex for test");
960        })
961        .join();
962
963        // Ensure poisoned mutex recovery path does not panic in unwatch_inner.
964        let result = watcher.unwatch_inner(Path::new("/path/that/is/not/watched"));
965        assert!(result.is_err());
966    }
967
968    #[test]
969    fn watched_paths_reflect_watch_and_unwatch() {
970        let tmpdir = testdir();
971        let dir_a = tmpdir.path().join("a");
972        let dir_b = tmpdir.path().join("b");
973        std::fs::create_dir(&dir_a).expect("create dir a");
974        std::fs::create_dir(&dir_b).expect("create dir b");
975
976        let mut watcher = PollWatcher::new(|_| {}, Config::default()).expect("create watcher");
977
978        watcher
979            .watch(&dir_a, RecursiveMode::Recursive)
980            .expect("watch dir a");
981        watcher
982            .watch(&dir_b, RecursiveMode::NonRecursive)
983            .expect("watch dir b");
984
985        let watched = watcher.watched_paths().expect("list watched paths");
986        assert!(watched.contains(&(
987            dir_a.canonicalize().expect("canonicalize dir a"),
988            RecursiveMode::Recursive,
989        )));
990        assert!(watched.contains(&(
991            dir_b.canonicalize().expect("canonicalize dir b"),
992            RecursiveMode::NonRecursive,
993        )));
994
995        watcher.unwatch(&dir_a).expect("unwatch dir a");
996
997        let watched = watcher
998            .watched_paths()
999            .expect("list watched paths after unwatch");
1000        assert!(!watched.contains(&(
1001            dir_a.canonicalize().expect("canonicalize dir a"),
1002            RecursiveMode::Recursive,
1003        )));
1004        assert!(watched.contains(&(
1005            dir_b.canonicalize().expect("canonicalize dir b"),
1006            RecursiveMode::NonRecursive,
1007        )));
1008    }
1009
1010    #[test]
1011    fn rewatching_same_path_replaces_recursive_mode() {
1012        let tmpdir = testdir();
1013        let root = tmpdir.path().canonicalize().expect("canonicalize root");
1014
1015        let mut watcher = PollWatcher::new(|_| {}, Config::default()).expect("create watcher");
1016
1017        watcher
1018            .watch(tmpdir.path(), RecursiveMode::Recursive)
1019            .expect("watch recursively");
1020        watcher
1021            .watch(tmpdir.path(), RecursiveMode::NonRecursive)
1022            .expect("watch non-recursively");
1023
1024        let watched = watcher.watched_paths().expect("list watched paths");
1025        assert!(watched.contains(&(root.clone(), RecursiveMode::NonRecursive)));
1026        assert!(!watched.contains(&(root.clone(), RecursiveMode::Recursive)));
1027        assert_eq!(
1028            watched.iter().filter(|(path, _mode)| path == &root).count(),
1029            1
1030        );
1031
1032        watcher.unwatch(tmpdir.path()).expect("unwatch");
1033        let watched = watcher.watched_paths().expect("list watched paths");
1034        assert!(!watched.iter().any(|(path, _mode)| path == &root));
1035    }
1036
1037    #[test]
1038    fn create_file() {
1039        let tmpdir = testdir();
1040        let (mut watcher, mut rx) = watcher();
1041        watcher.watch_recursively(&tmpdir);
1042
1043        let path = tmpdir.path().join("entry");
1044        std::fs::File::create_new(&path).expect("Unable to create");
1045
1046        rx.sleep_until_parent_contains(&path);
1047        rx.sleep_until_exists(&path);
1048        rx.sleep_until_walkdir_returns_set(tmpdir.path(), [&path]);
1049
1050        rx.wait_unordered_exact([
1051            expected(&path).create_any(),
1052            expected(tmpdir.path()).modify_meta_mtime().optional(),
1053        ]);
1054    }
1055
1056    #[test]
1057    fn create_dir() {
1058        let tmpdir = testdir();
1059        let (mut watcher, mut rx) = watcher();
1060        watcher.watch_recursively(&tmpdir);
1061
1062        let path = tmpdir.path().join("entry");
1063        std::fs::create_dir(&path).expect("Unable to create");
1064
1065        rx.sleep_until_parent_contains(&path);
1066        rx.sleep_until_exists(&path);
1067        rx.sleep_until_walkdir_returns_set(tmpdir.path(), [&path]);
1068
1069        rx.wait_unordered_exact([
1070            expected(&path).create_any(),
1071            expected(tmpdir.path()).modify_meta_mtime().optional(),
1072        ]);
1073    }
1074
1075    #[test]
1076    fn modify_file() {
1077        let tmpdir = testdir();
1078        let (mut watcher, mut rx) = watcher();
1079        let path = tmpdir.path().join("entry");
1080        std::fs::File::create_new(&path).expect("Unable to create");
1081
1082        rx.sleep_until_parent_contains(&path);
1083        rx.sleep_until_exists(&path);
1084        rx.sleep_until_walkdir_returns_set(tmpdir.path(), [&path]);
1085
1086        set_write_time_ahead(&path);
1087        watcher.watch_recursively(&tmpdir);
1088
1089        std::fs::write(&path, b"123").expect("Unable to write");
1090
1091        assert!(
1092            rx.sleep_until(|| std::fs::read_to_string(&path).is_ok_and(|content| content == "123")),
1093            "the file wasn't modified"
1094        );
1095        rx.wait_ordered_exact([expected(&path).modify_data_any()]);
1096    }
1097
1098    #[test]
1099    fn remove_file() {
1100        let tmpdir = testdir();
1101        let (mut watcher, mut rx) = watcher();
1102        let path = tmpdir.path().join("entry");
1103        std::fs::File::create_new(&path).expect("Unable to create");
1104
1105        rx.sleep_until_parent_contains(&path);
1106        rx.sleep_until_exists(&path);
1107        rx.sleep_until_walkdir_returns_set(tmpdir.path(), [&path]);
1108
1109        watcher.watch_recursively(&tmpdir);
1110
1111        std::fs::remove_file(&path).expect("Unable to remove");
1112
1113        rx.sleep_while_exists(&path);
1114        rx.sleep_while_parent_contains(&path);
1115        rx.sleep_until_walkdir_returns_set::<&str>(tmpdir.path(), []);
1116
1117        rx.wait_unordered_exact([
1118            expected(&path).remove_any(),
1119            expected(tmpdir.path()).modify_meta_mtime().optional(),
1120        ]);
1121    }
1122
1123    #[test]
1124    fn rename_file() {
1125        let tmpdir = testdir();
1126        let (mut watcher, mut rx) = watcher();
1127        let path = tmpdir.path().join("entry");
1128        let new_path = tmpdir.path().join("new_entry");
1129        std::fs::File::create_new(&path).expect("Unable to create");
1130
1131        rx.sleep_until_parent_contains(&path);
1132        rx.sleep_until_exists(&path);
1133        rx.sleep_until_walkdir_returns_set(tmpdir.path(), [&path]);
1134
1135        watcher.watch_recursively(&tmpdir);
1136
1137        std::fs::rename(&path, &new_path).expect("Unable to remove");
1138
1139        rx.sleep_while_exists(&path);
1140        rx.sleep_until_exists(&new_path);
1141
1142        rx.sleep_while_parent_contains(&path);
1143        rx.sleep_until_parent_contains(&new_path);
1144
1145        rx.sleep_until_walkdir_returns_set(tmpdir.path(), [&new_path]);
1146
1147        rx.wait_unordered_exact([
1148            expected(&path).remove_any(),
1149            expected(&new_path).create_any(),
1150            expected(tmpdir.path()).modify_meta_mtime().optional(),
1151        ]);
1152    }
1153
1154    #[test]
1155    fn create_write_overwrite() {
1156        let tmpdir = testdir();
1157        let (mut watcher, mut rx) = watcher();
1158        let overwritten_file = tmpdir.path().join("overwritten_file");
1159        let overwriting_file = tmpdir.path().join("overwriting_file");
1160        std::fs::write(&overwritten_file, "123").expect("write1");
1161
1162        rx.sleep_until_parent_contains(&overwritten_file);
1163        rx.sleep_until_exists(&overwritten_file);
1164        rx.sleep_until_walkdir_returns_set(tmpdir.path(), [overwritten_file.clone()]);
1165
1166        set_write_time_ahead(&overwritten_file);
1167
1168        watcher.watch_nonrecursively(&tmpdir);
1169
1170        std::fs::File::create(&overwriting_file).expect("create");
1171        std::fs::write(&overwriting_file, "321").expect("write2");
1172        std::fs::rename(&overwriting_file, &overwritten_file).expect("rename");
1173
1174        rx.sleep_while_exists(&overwriting_file);
1175        rx.sleep_while_parent_contains(&overwriting_file);
1176        rx.sleep_until_walkdir_returns_set(tmpdir.path(), [overwritten_file.clone()]);
1177
1178        assert!(
1179            rx.sleep_until(
1180                || std::fs::read_to_string(&overwritten_file).is_ok_and(|cnt| cnt == "321")
1181            ),
1182            "file {overwritten_file:?} was not replaced"
1183        );
1184
1185        rx.wait_unordered([expected(&overwritten_file).modify_data_any()]);
1186    }
1187
1188    #[test]
1189    fn poll_watcher_respects_event_kind_mask() {
1190        use crate::{Config, Watcher};
1191        use notify_types::event::EventKindMask;
1192        use std::time::Duration;
1193
1194        let tmpdir = testdir();
1195        let (tx, rx) = std::sync::mpsc::channel();
1196
1197        // Create watcher with CREATE-only mask (no MODIFY events)
1198        let config = Config::default()
1199            .with_event_kinds(EventKindMask::CREATE)
1200            .with_compare_contents(true)
1201            .with_manual_polling();
1202
1203        let mut watcher = PollWatcher::new(tx, config).expect("create watcher");
1204        watcher
1205            .watch(tmpdir.path(), crate::RecursiveMode::Recursive)
1206            .expect("watch");
1207
1208        let path = tmpdir.path().join("test_file");
1209
1210        // Create a file - should generate CREATE event
1211        std::fs::write(&path, "initial").expect("write initial");
1212        watcher.poll().expect("poll 1");
1213
1214        // Wait for CREATE event (use blocking recv with timeout)
1215        let event = rx
1216            .recv_timeout(Duration::from_secs(1))
1217            .expect("should receive CREATE event")
1218            .expect("event should not be an error");
1219        assert!(
1220            event.kind.is_create(),
1221            "Expected CREATE event, got: {event:?}"
1222        );
1223
1224        // Modify the file - should NOT generate event (filtered by mask)
1225        std::fs::write(&path, "modified content").expect("write modified");
1226        watcher.poll().expect("poll 2");
1227
1228        // Give poll thread time to process, then verify no MODIFY events
1229        std::thread::sleep(Duration::from_millis(100));
1230
1231        // Should have no more events (MODIFY was filtered out)
1232        let remaining: Vec<_> = rx.try_iter().filter_map(|r| r.ok()).collect();
1233        assert!(
1234            !remaining.iter().any(|e| e.kind.is_modify()),
1235            "Should not receive MODIFY events with CREATE-only mask, got: {remaining:?}"
1236        );
1237    }
1238}