notify/
inotify.rs

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