kqueue/
lib.rs

1use kqueue_sys::{kevent, kqueue};
2use libc::{close, pid_t, uintptr_t};
3use std::fmt::Debug;
4use std::fs::File;
5use std::io::{self, Error, Result};
6use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
7use std::path::Path;
8use std::ptr;
9use std::time::Duration;
10
11pub use kqueue_sys::constants::*;
12
13mod os;
14use crate::os::vnode;
15
16mod time;
17use crate::time::duration_to_timespec;
18
19/// The watched object that fired the event
20#[derive(Debug, Eq, Clone)]
21pub enum Ident {
22    Filename(RawFd, String),
23    Fd(RawFd),
24    Pid(pid_t),
25    Signal(i32),
26    Timer(i32),
27}
28
29#[doc(hidden)]
30#[derive(Debug, PartialEq, Clone)]
31pub struct Watched {
32    filter: EventFilter,
33    flags: FilterFlag,
34    ident: Ident,
35}
36
37/// Watches one or more resources
38///
39/// These can be created with `Watcher::new()`. You can create as many
40/// `Watcher`s as you want, and they can watch as many objects as you wish.
41/// The objects do not need to be the same type.
42///
43/// Each `Watcher` is backed by a `kqueue(2)` queue. These resources are freed
44/// on the `Watcher`s destruction. If the destructor cannot run for whatever
45/// reason, the underlying kernel object will be leaked.
46///
47/// Files and file descriptors given to the `Watcher` are presumed to be owned
48/// by the `Watcher`, and will be closed when they're removed from the `Watcher`
49/// or on `Drop`. In a future version, the API will make this explicit via
50/// `OwnedFd`s
51#[derive(Debug)]
52pub struct Watcher {
53    watched: Vec<Watched>,
54    queue: RawFd,
55    started: bool,
56    opts: KqueueOpts,
57}
58
59/// Vnode events
60///
61/// These are OS-specific, and may not all be supported on your platform. Check
62/// `kqueue(2)` for more information.
63#[derive(Debug)]
64#[non_exhaustive]
65pub enum Vnode {
66    /// The file was deleted
67    Delete,
68
69    /// The file received a write
70    Write,
71
72    /// The file was extended with `truncate(2)`
73    Extend,
74
75    /// The file was shrunk with `truncate(2)`
76    Truncate,
77
78    /// The attributes of the file were changed
79    Attrib,
80
81    /// The link count of the file was changed
82    Link,
83
84    /// The file was renamed
85    Rename,
86
87    /// Access to the file was revoked with `revoke(2)` or the fs was unmounted
88    Revoke,
89
90    /// File was opened by a process (FreeBSD-specific)
91    Open,
92
93    /// File was closed and the descriptor had write access (FreeBSD-specific)
94    CloseWrite,
95
96    /// File was closed and the descriptor had read access (FreeBSD-specific)
97    Close,
98}
99
100/// Process events
101///
102/// These are OS-specific, and may not all be supported on your platform. Check
103/// `kqueue(2)` for more information.
104#[derive(Debug)]
105pub enum Proc {
106    /// The watched process exited with the returned exit code
107    Exit(usize),
108
109    /// The process called `fork(2)`
110    Fork,
111
112    /// The process called `exec(2)`
113    Exec,
114
115    /// The process called `fork(2)`, and returned the child pid.
116    Track(libc::pid_t),
117
118    /// The process called `fork(2)`, but we were not able to track the child
119    Trackerr,
120
121    /// The process called `fork(2)`, and returned the child pid.
122    // TODO: this is FreeBSD-specific. We can probably convert this to `Track`.
123    Child(libc::pid_t),
124}
125
126/// Event-specific data returned with the event.
127///
128/// Like much of this library, this is OS-specific. Check `kqueue(2)` for more
129/// details on your target OS.
130#[derive(Debug)]
131pub enum EventData {
132    /// Data relating to `Vnode` events
133    Vnode(Vnode),
134
135    /// Data relating to process events
136    Proc(Proc),
137
138    /// The returned number of bytes are ready for reading from the watched
139    /// descriptor
140    ReadReady(usize),
141
142    /// The file is ready for writing. On some files (like sockets, pipes, etc),
143    /// the number of bytes in the write buffer will be returned.
144    WriteReady(usize),
145
146    /// One of the watched signals fired. The number of times this signal was received
147    /// is returned.
148    Signal(usize),
149
150    /// One of the watched timers fired. The number of times this timer fired
151    /// is returned.
152    Timer(usize),
153
154    /// Some error was received
155    Error(Error),
156}
157
158/// An event from a `Watcher` object.
159///
160/// An event contains both the a signifier of the watched object that triggered
161/// the event, as well as any event-specific. See the `EventData` enum for info
162/// on what event-specific data is returned for each event.
163#[derive(Debug)]
164pub struct Event {
165    /// The watched resource that triggered the event
166    pub ident: Ident,
167
168    /// Any event-specific data returned with the event.
169    pub data: EventData,
170}
171
172pub struct EventIter<'a> {
173    watcher: &'a Watcher,
174}
175
176/// Options for a `Watcher`
177#[derive(Debug)]
178pub struct KqueueOpts {
179    /// Clear state on watched objects
180    clear: bool,
181}
182
183impl Default for KqueueOpts {
184    /// Returns the default options for a `Watcher`
185    ///
186    /// `clear` is set to `true`
187    fn default() -> KqueueOpts {
188        KqueueOpts { clear: true }
189    }
190}
191
192// We don't have enough information to turn a `usize` into
193// an `Ident`, so we only implement `Into<usize>` here.
194#[allow(clippy::from_over_into)]
195impl Into<usize> for Ident {
196    fn into(self) -> usize {
197        match self {
198            Ident::Filename(fd, _) => fd as usize,
199            Ident::Fd(fd) => fd as usize,
200            Ident::Pid(pid) => pid as usize,
201            Ident::Signal(sig) => sig as usize,
202            Ident::Timer(timer) => timer as usize,
203        }
204    }
205}
206
207impl PartialEq<Ident> for Ident {
208    fn eq(&self, other: &Ident) -> bool {
209        match *self {
210            Ident::Filename(_, ref name) => {
211                if let Ident::Filename(_, ref othername) = *other {
212                    name == othername
213                } else {
214                    false
215                }
216            }
217            _ => self.as_usize() == other.as_usize(),
218        }
219    }
220}
221
222impl Ident {
223    fn as_usize(&self) -> usize {
224        match *self {
225            Ident::Filename(fd, _) => fd as usize,
226            Ident::Fd(fd) => fd as usize,
227            Ident::Pid(pid) => pid as usize,
228            Ident::Signal(sig) => sig as usize,
229            Ident::Timer(timer) => timer as usize,
230        }
231    }
232}
233
234impl Watcher {
235    /// Creates a new `Watcher`
236    ///
237    /// Creates a brand new `Watcher` with `KqueueOpts::default()`. Will return
238    /// an `io::Error` if creation fails.
239    pub fn new() -> Result<Watcher> {
240        let queue = unsafe { kqueue() };
241
242        if queue == -1 {
243            Err(Error::last_os_error())
244        } else {
245            Ok(Watcher {
246                watched: Vec::new(),
247                queue,
248                started: false,
249                opts: Default::default(),
250            })
251        }
252    }
253
254    /// Disables the `clear` flag on a `Watcher`. New events will no longer
255    /// be added with the `EV_CLEAR` flag on `watch`.
256    pub fn disable_clears(&mut self) -> &mut Self {
257        self.opts.clear = false;
258        self
259    }
260
261    /// Adds a `pid` to the `Watcher` to be watched
262    pub fn add_pid(
263        &mut self,
264        pid: libc::pid_t,
265        filter: EventFilter,
266        flags: FilterFlag,
267    ) -> Result<()> {
268        let watch = Watched {
269            filter,
270            flags,
271            ident: Ident::Pid(pid),
272        };
273
274        if !self.watched.contains(&watch) {
275            self.watched.push(watch);
276        }
277
278        Ok(())
279    }
280
281    /// Adds a file by filename to be watched
282    ///
283    /// **NB**: `kqueue(2)` is an `fd`-based API. If you add a filename with
284    /// `add_filename`, internally we open it and pass the file descriptor to
285    /// `kqueue(2)`. If the file is moved or deleted, and a new file is created
286    /// with the same name, you will not receive new events for it without
287    /// calling `add_filename` again.
288    ///
289    /// TODO: Adding new files requires calling `Watcher.watch` again
290    pub fn add_filename<P: AsRef<Path>>(
291        &mut self,
292        filename: P,
293        filter: EventFilter,
294        flags: FilterFlag,
295    ) -> Result<()> {
296        let file = File::open(filename.as_ref())?;
297        let watch = Watched {
298            filter,
299            flags,
300            ident: Ident::Filename(
301                file.into_raw_fd(),
302                filename.as_ref().to_string_lossy().into_owned(),
303            ),
304        };
305
306        if !self.watched.contains(&watch) {
307            self.watched.push(watch);
308        }
309
310        Ok(())
311    }
312
313    /// Adds a descriptor to a `Watcher`. This or `add_file` is the preferred
314    /// way to watch a file
315    ///
316    /// TODO: Adding new files requires calling `Watcher.watch` again
317    pub fn add_fd(&mut self, fd: RawFd, filter: EventFilter, flags: FilterFlag) -> Result<()> {
318        let watch = Watched {
319            filter,
320            flags,
321            ident: Ident::Fd(fd),
322        };
323
324        if !self.watched.contains(&watch) {
325            self.watched.push(watch);
326        }
327
328        Ok(())
329    }
330
331    /// Adds a `File` to a `Watcher`. This, or `add_fd` is the preferred way
332    /// to watch a file
333    ///
334    /// TODO: Adding new files requires calling `Watcher.watch` again
335    pub fn add_file(&mut self, file: &File, filter: EventFilter, flags: FilterFlag) -> Result<()> {
336        self.add_fd(file.as_raw_fd(), filter, flags)
337    }
338
339    fn delete_kevents(&self, ident: Ident, filter: EventFilter) -> Result<()> {
340        let kev = &[kevent::new(
341            ident.as_usize(),
342            filter,
343            EventFlag::EV_DELETE,
344            FilterFlag::empty(),
345        )];
346
347        let ret = unsafe {
348            kevent(
349                self.queue,
350                kev.as_ptr(),
351                // On NetBSD, this is passed as a usize, not i32
352                #[allow(clippy::useless_conversion)]
353                i32::try_from(kev.len()).unwrap().try_into().unwrap(),
354                ptr::null_mut(),
355                0,
356                ptr::null(),
357            )
358        };
359
360        match ret {
361            -1 => Err(Error::last_os_error()),
362            _ => Ok(()),
363        }
364    }
365
366    /// Removes a pid from a `Watcher`
367    pub fn remove_pid(&mut self, pid: libc::pid_t, filter: EventFilter) -> Result<()> {
368        let new_watched = self
369            .watched
370            .drain(..)
371            .filter(|x| {
372                if let Ident::Pid(iterpid) = x.ident {
373                    iterpid != pid
374                } else {
375                    true
376                }
377            })
378            .collect();
379
380        self.watched = new_watched;
381        self.delete_kevents(Ident::Pid(pid), filter)
382    }
383
384    /// Removes a filename from a `Watcher`.
385    ///
386    /// *NB*: This matches the `filename` that this item was initially added under.
387    /// If a file has been moved, it will not be removable by the new name.
388    pub fn remove_filename<P: AsRef<Path> + Debug>(
389        &mut self,
390        filename: P,
391        filter: EventFilter,
392    ) -> Result<()> {
393        let mut fd: RawFd = 0;
394        let new_watched = self
395            .watched
396            .drain(..)
397            .filter(|x| {
398                if let Ident::Filename(iterfd, ref iterfile) = x.ident {
399                    if iterfile == filename.as_ref().to_str().unwrap() {
400                        fd = iterfd;
401                        false
402                    } else {
403                        true
404                    }
405                } else {
406                    true
407                }
408            })
409            .collect();
410
411        if fd == 0 {
412            return Err(Error::new(
413                io::ErrorKind::NotFound,
414                format!("{filename:?} was not being watched"),
415            ));
416        }
417
418        self.watched = new_watched;
419        let ret = self.delete_kevents(Ident::Fd(fd), filter);
420        let close_err = unsafe { close(fd) };
421        if close_err != 0 {
422            Err(Error::from_raw_os_error(close_err))
423        } else {
424            ret
425        }
426    }
427
428    /// Removes an fd from a `Watcher`. This closes the fd.
429    pub fn remove_fd(&mut self, fd: RawFd, filter: EventFilter) -> Result<()> {
430        let new_watched = self
431            .watched
432            .drain(..)
433            .filter(|x| {
434                if let Ident::Fd(iterfd) = x.ident {
435                    iterfd != fd
436                } else {
437                    true
438                }
439            })
440            .collect();
441
442        self.watched = new_watched;
443        let ret = self.delete_kevents(Ident::Fd(fd), filter);
444        let close_err = unsafe { close(fd) };
445        if close_err != 0 {
446            Err(Error::from_raw_os_error(close_err))
447        } else {
448            ret
449        }
450    }
451
452    /// Removes a `File` from a `Watcher`
453    pub fn remove_file(&mut self, file: &File, filter: EventFilter) -> Result<()> {
454        self.remove_fd(file.as_raw_fd(), filter)
455    }
456
457    /// Starts watching for events from `kqueue(2)`. This function needs to
458    /// be called before `Watcher.iter()` or `Watcher.poll()` to actually
459    /// start listening for events.
460    pub fn watch(&mut self) -> Result<()> {
461        let kevs: Vec<kevent> = self
462            .watched
463            .iter()
464            .map(|watched| {
465                let raw_ident = match watched.ident {
466                    Ident::Fd(fd) => fd as uintptr_t,
467                    Ident::Filename(fd, _) => fd as uintptr_t,
468                    Ident::Pid(pid) => pid as uintptr_t,
469                    Ident::Signal(sig) => sig as uintptr_t,
470                    Ident::Timer(ident) => ident as uintptr_t,
471                };
472
473                kevent::new(
474                    raw_ident,
475                    watched.filter,
476                    if self.opts.clear {
477                        EventFlag::EV_ADD | EventFlag::EV_CLEAR
478                    } else {
479                        EventFlag::EV_ADD
480                    },
481                    watched.flags,
482                )
483            })
484            .collect();
485
486        let ret = unsafe {
487            kevent(
488                self.queue,
489                kevs.as_ptr(),
490                // On NetBSD, this is passed as a usize, not i32
491                #[allow(clippy::useless_conversion)]
492                i32::try_from(kevs.len()).unwrap().try_into().unwrap(),
493                ptr::null_mut(),
494                0,
495                ptr::null(),
496            )
497        };
498
499        self.started = true;
500        match ret {
501            -1 => Err(Error::last_os_error()),
502            _ => Ok(()),
503        }
504    }
505
506    /// Polls for a new event, with an optional timeout. If no `timeout`
507    /// is passed, then it will return immediately.
508    pub fn poll(&self, timeout: Option<Duration>) -> Option<Event> {
509        // poll will not block indefinitely
510        // None -> return immediately
511        match timeout {
512            Some(timeout) => get_event(self, Some(timeout)),
513            None => get_event(self, Some(Duration::new(0, 0))),
514        }
515    }
516
517    /// Polls for a new event, with an optional timeout. If no `timeout`
518    /// is passed, then it will block until an event is received.
519    pub fn poll_forever(&self, timeout: Option<Duration>) -> Option<Event> {
520        if timeout.is_some() {
521            self.poll(timeout)
522        } else {
523            get_event(self, None)
524        }
525    }
526
527    /// Creates an iterator that iterates over the queue. This iterator will block
528    /// until a new event is received.
529    pub fn iter(&self) -> EventIter<'_> {
530        EventIter { watcher: self }
531    }
532}
533
534impl AsRawFd for Watcher {
535    fn as_raw_fd(&self) -> RawFd {
536        self.queue
537    }
538}
539
540impl Drop for Watcher {
541    fn drop(&mut self) {
542        unsafe { libc::close(self.queue) };
543        for watched in &self.watched {
544            match watched.ident {
545                Ident::Fd(fd) => unsafe { libc::close(fd) },
546                Ident::Filename(fd, _) => unsafe { libc::close(fd) },
547                _ => continue,
548            };
549        }
550    }
551}
552
553fn find_file_ident(watcher: &Watcher, fd: RawFd) -> Option<Ident> {
554    for watched in &watcher.watched {
555        match watched.ident.clone() {
556            Ident::Fd(ident_fd) => {
557                if fd == ident_fd {
558                    return Some(Ident::Fd(fd));
559                } else {
560                    continue;
561                }
562            }
563            Ident::Filename(ident_fd, ident_str) => {
564                if fd == ident_fd {
565                    return Some(Ident::Filename(ident_fd, ident_str));
566                } else {
567                    continue;
568                }
569            }
570            _ => continue,
571        }
572    }
573
574    None
575}
576
577fn get_event(watcher: &Watcher, timeout: Option<Duration>) -> Option<Event> {
578    let mut kev = kevent::new(
579        0,
580        EventFilter::EVFILT_SYSCOUNT,
581        EventFlag::empty(),
582        FilterFlag::empty(),
583    );
584    let ret = if let Some(ts) = timeout {
585        unsafe {
586            kevent(
587                watcher.queue,
588                ptr::null(),
589                0,
590                &mut kev,
591                1,
592                &duration_to_timespec(ts),
593            )
594        }
595    } else {
596        unsafe { kevent(watcher.queue, ptr::null(), 0, &mut kev, 1, ptr::null()) }
597    };
598
599    match ret {
600        -1 => Some(Event::from_error(kev, watcher)),
601        0 => None, // timeout expired
602        _ => Some(Event::new(kev, watcher)),
603    }
604}
605
606// OS specific
607// TODO: Events can have more than one filter flag
608impl Event {
609    #[doc(hidden)]
610    pub fn new(ev: kevent, watcher: &Watcher) -> Event {
611        let data = match ev.filter {
612            EventFilter::EVFILT_READ => EventData::ReadReady(ev.data as usize),
613            EventFilter::EVFILT_WRITE => EventData::WriteReady(ev.data as usize),
614            EventFilter::EVFILT_SIGNAL => EventData::Signal(ev.data as usize),
615            EventFilter::EVFILT_TIMER => EventData::Timer(ev.data as usize),
616            EventFilter::EVFILT_PROC => {
617                let inner = if ev.fflags.contains(FilterFlag::NOTE_EXIT) {
618                    Proc::Exit(ev.data as usize)
619                } else if ev.fflags.contains(FilterFlag::NOTE_FORK) {
620                    Proc::Fork
621                } else if ev.fflags.contains(FilterFlag::NOTE_EXEC) {
622                    Proc::Exec
623                } else if ev.fflags.contains(FilterFlag::NOTE_TRACK) {
624                    Proc::Track(ev.data as libc::pid_t)
625                } else if ev.fflags.contains(FilterFlag::NOTE_CHILD) {
626                    Proc::Child(ev.data as libc::pid_t)
627                } else {
628                    panic!("proc filterflag not supported: {0:?}", ev.fflags)
629                };
630
631                EventData::Proc(inner)
632            }
633            EventFilter::EVFILT_VNODE => {
634                let inner = if ev.fflags.contains(FilterFlag::NOTE_DELETE) {
635                    Vnode::Delete
636                } else if ev.fflags.contains(FilterFlag::NOTE_WRITE) {
637                    Vnode::Write
638                } else if ev.fflags.contains(FilterFlag::NOTE_EXTEND) {
639                    Vnode::Extend
640                } else if ev.fflags.contains(FilterFlag::NOTE_ATTRIB) {
641                    Vnode::Attrib
642                } else if ev.fflags.contains(FilterFlag::NOTE_LINK) {
643                    Vnode::Link
644                } else if ev.fflags.contains(FilterFlag::NOTE_RENAME) {
645                    Vnode::Rename
646                } else if ev.fflags.contains(FilterFlag::NOTE_REVOKE) {
647                    Vnode::Revoke
648                } else {
649                    // This handles any filter flags that are OS-specific
650                    vnode::handle_vnode_extras(ev.fflags)
651                };
652
653                EventData::Vnode(inner)
654            }
655            _ => panic!("eventfilter not supported: {0:?}", ev.filter),
656        };
657
658        let ident = match ev.filter {
659            EventFilter::EVFILT_READ => find_file_ident(watcher, ev.ident as RawFd).unwrap(),
660            EventFilter::EVFILT_WRITE => find_file_ident(watcher, ev.ident as RawFd).unwrap(),
661            EventFilter::EVFILT_VNODE => find_file_ident(watcher, ev.ident as RawFd).unwrap(),
662            EventFilter::EVFILT_SIGNAL => Ident::Signal(ev.ident as i32),
663            EventFilter::EVFILT_TIMER => Ident::Timer(ev.ident as i32),
664            EventFilter::EVFILT_PROC => Ident::Pid(ev.ident as pid_t),
665            _ => panic!("not supported"),
666        };
667
668        Event { ident, data }
669    }
670
671    #[doc(hidden)]
672    pub fn from_error(ev: kevent, watcher: &Watcher) -> Event {
673        let ident = match ev.filter {
674            EventFilter::EVFILT_READ => find_file_ident(watcher, ev.ident as RawFd).unwrap(),
675            EventFilter::EVFILT_WRITE => find_file_ident(watcher, ev.ident as RawFd).unwrap(),
676            EventFilter::EVFILT_VNODE => find_file_ident(watcher, ev.ident as RawFd).unwrap(),
677            EventFilter::EVFILT_SIGNAL => Ident::Signal(ev.ident as i32),
678            EventFilter::EVFILT_TIMER => Ident::Timer(ev.ident as i32),
679            EventFilter::EVFILT_PROC => Ident::Pid(ev.ident as pid_t),
680            _ => panic!("not supported"),
681        };
682
683        Event {
684            data: EventData::Error(io::Error::last_os_error()),
685            ident,
686        }
687    }
688
689    #[doc(hidden)]
690    pub fn is_err(&self) -> bool {
691        matches!(self.data, EventData::Error(_))
692    }
693}
694
695impl Iterator for EventIter<'_> {
696    type Item = Event;
697
698    // rather than call kevent(2) each time, we can likely optimize and
699    // call it once for like 100 items
700    fn next(&mut self) -> Option<Self::Item> {
701        if !self.watcher.started {
702            return None;
703        }
704
705        get_event(self.watcher, None)
706    }
707}
708
709#[cfg(test)]
710mod tests {
711    use super::{EventData, EventFilter, FilterFlag, Ident, Vnode, Watcher};
712    use std::fs;
713    use std::io::{ErrorKind, Write};
714    use std::os::unix::io::{AsRawFd, FromRawFd};
715    use std::path::Path;
716    use std::thread;
717    use std::time;
718
719    #[cfg(target_os = "freebsd")]
720    use std::process;
721
722    #[test]
723    fn test_new_watcher() {
724        let mut watcher = Watcher::new().expect("new failed");
725        let file = tempfile::tempfile().expect("Couldn't create tempfile");
726
727        watcher
728            .add_file(&file, EventFilter::EVFILT_VNODE, FilterFlag::NOTE_WRITE)
729            .expect("add failed");
730        watcher.watch().expect("watch failed");
731    }
732
733    #[test]
734    fn test_filename() {
735        let mut watcher = Watcher::new().expect("new failed");
736        let file = tempfile::NamedTempFile::new().expect("Couldn't create tempfile");
737
738        watcher
739            .add_filename(
740                file.path(),
741                EventFilter::EVFILT_VNODE,
742                FilterFlag::NOTE_WRITE,
743            )
744            .expect("add failed");
745        watcher.watch().expect("watch failed");
746
747        let mut new_file = fs::OpenOptions::new()
748            .write(true)
749            .open(file.path())
750            .expect("open failed");
751
752        new_file.write_all(b"foo").expect("write failed");
753
754        thread::sleep(time::Duration::from_secs(1));
755
756        let ev = watcher.iter().next().expect("Could not get a watch");
757        assert!(matches!(ev.data, EventData::Vnode(Vnode::Write)));
758
759        match ev.ident {
760            Ident::Filename(_, name) => assert!(Path::new(&name) == file.path()),
761            _ => panic!(),
762        };
763    }
764
765    #[test]
766    fn test_file() {
767        let mut watcher = Watcher::new().expect("new failed");
768        let mut file = tempfile::tempfile().expect("Could not create tempfile");
769
770        watcher
771            .add_file(&file, EventFilter::EVFILT_VNODE, FilterFlag::NOTE_WRITE)
772            .expect("add failed");
773        watcher.watch().expect("watch failed");
774        file.write_all(b"foo").expect("write failed");
775
776        thread::sleep(time::Duration::from_secs(1));
777
778        let ev = watcher.iter().next().expect("Didn't get an event");
779
780        assert!(matches!(ev.data, EventData::Vnode(Vnode::Write)));
781        assert!(matches!(ev.ident, Ident::Fd(_)));
782    }
783
784    #[test]
785    fn test_delete_filename() {
786        let mut watcher = Watcher::new().expect("new failed");
787
788        let file = tempfile::NamedTempFile::new().expect("Could not create tempfile");
789        let filename = file.path();
790
791        watcher
792            .add_filename(filename, EventFilter::EVFILT_VNODE, FilterFlag::NOTE_WRITE)
793            .expect("add failed");
794        watcher.watch().expect("watch failed");
795        watcher
796            .remove_filename(filename, EventFilter::EVFILT_VNODE)
797            .expect("delete failed");
798    }
799
800    #[test]
801    fn test_dupe() {
802        let mut watcher = Watcher::new().expect("new failed");
803        let file = tempfile::NamedTempFile::new().expect("Couldn't create tempfile");
804        let filename = file.path();
805
806        watcher
807            .add_filename(filename, EventFilter::EVFILT_VNODE, FilterFlag::NOTE_WRITE)
808            .expect("add failed");
809        watcher
810            .add_filename(filename, EventFilter::EVFILT_VNODE, FilterFlag::NOTE_WRITE)
811            .expect("second add failed");
812
813        assert_eq!(
814            watcher.watched.len(),
815            1,
816            "Did not get an expected number of events"
817        );
818    }
819
820    #[test]
821    fn test_two_files() {
822        let mut watcher = Watcher::new().expect("new failed");
823
824        let mut first_file = tempfile::tempfile().expect("Unable to create first temporary file");
825        let mut second_file = tempfile::tempfile().expect("Unable to create second temporary file");
826
827        watcher
828            .add_file(
829                &first_file,
830                EventFilter::EVFILT_VNODE,
831                FilterFlag::NOTE_WRITE,
832            )
833            .expect("add failed");
834
835        watcher
836            .add_file(
837                &second_file,
838                EventFilter::EVFILT_VNODE,
839                FilterFlag::NOTE_WRITE,
840            )
841            .expect("add failed");
842
843        watcher.watch().expect("watch failed");
844        first_file.write_all(b"foo").expect("first write failed");
845        second_file.write_all(b"foo").expect("second write failed");
846
847        thread::sleep(time::Duration::from_secs(1));
848
849        watcher.iter().next().expect("didn't get any events");
850        watcher.iter().next().expect("didn't get any events");
851    }
852
853    #[test]
854    fn test_nested_kqueue() {
855        let mut watcher = Watcher::new().expect("Failed to create main watcher");
856        let mut nested_watcher = Watcher::new().expect("Failed to create nested watcher");
857
858        let kqueue_file = unsafe { fs::File::from_raw_fd(nested_watcher.as_raw_fd()) };
859        watcher
860            .add_file(&kqueue_file, EventFilter::EVFILT_READ, FilterFlag::empty())
861            .expect("add_file failed for main watcher");
862
863        let mut file = tempfile::tempfile().expect("Couldn't create tempfile");
864        nested_watcher
865            .add_file(&file, EventFilter::EVFILT_VNODE, FilterFlag::NOTE_WRITE)
866            .expect("add_file failed for nested watcher");
867
868        watcher.watch().expect("watch failed on main watcher");
869        nested_watcher
870            .watch()
871            .expect("watch failed on nested watcher");
872
873        file.write_all(b"foo").expect("write failed");
874
875        thread::sleep(time::Duration::from_secs(1));
876
877        watcher.iter().next().expect("didn't get any events");
878        nested_watcher.iter().next().expect("didn't get any events");
879    }
880
881    #[test]
882    #[cfg(target_os = "freebsd")]
883    fn test_close_read() {
884        let mut watcher = Watcher::new().expect("new failed");
885
886        {
887            let file = tempfile::NamedTempFile::new().expect("temporary file failed to create");
888            watcher
889                .add_filename(
890                    file.path(),
891                    EventFilter::EVFILT_VNODE,
892                    FilterFlag::NOTE_CLOSE,
893                )
894                .expect("add failed");
895            watcher.watch().expect("watch failed");
896
897            // we launch this in a separate process since it appears that FreeBSD does not fire
898            // off a NOTE_CLOSE(_WRITE)? event for the same process closing a file descriptor.
899            process::Command::new("cat")
900                .arg(file.path())
901                .spawn()
902                .expect("should spawn a file");
903            thread::sleep(time::Duration::from_secs(1));
904        }
905        let ev = watcher.iter().next().expect("did not receive event");
906        assert!(matches!(ev.data, EventData::Vnode(Vnode::Close)));
907    }
908
909    #[test]
910    #[cfg(target_os = "freebsd")]
911    fn test_close_write() {
912        let mut watcher = match Watcher::new() {
913            Ok(wat) => wat,
914            Err(_) => panic!("new failed"),
915        };
916
917        {
918            let file = tempfile::NamedTempFile::new().expect("couldn't create tempfile");
919            watcher
920                .add_filename(
921                    file.path(),
922                    EventFilter::EVFILT_VNODE,
923                    FilterFlag::NOTE_CLOSE_WRITE,
924                )
925                .expect("add failed");
926            watcher.watch().expect("watch failed");
927
928            // See above for rationale as to why we use a separate process here
929            process::Command::new("cat")
930                .arg(file.path())
931                .spawn()
932                .expect("should spawn a file");
933            thread::sleep(time::Duration::from_secs(1));
934        }
935        let ev = watcher.iter().next().expect("didn't get an event");
936        assert!(matches!(ev.data, EventData::Vnode(Vnode::CloseWrite)));
937    }
938
939    #[test]
940    fn test_not_found_remove_watch() {
941        let mut watcher = Watcher::new().unwrap();
942
943        let ret = watcher.remove_filename("foo", EventFilter::EVFILT_VNODE);
944        assert!(ret.is_err());
945
946        let err = ret.unwrap_err();
947        assert_eq!(err.kind(), ErrorKind::NotFound);
948        assert_eq!(err.to_string(), "\"foo\" was not being watched");
949    }
950}