Vendor things
This commit is contained in:
parent
5deceec006
commit
977e3c17e5
19434 changed files with 10682014 additions and 0 deletions
913
third-party/vendor/kqueue/src/lib.rs
vendored
Normal file
913
third-party/vendor/kqueue/src/lib.rs
vendored
Normal file
|
|
@ -0,0 +1,913 @@
|
|||
use kqueue_sys::{kevent, kqueue};
|
||||
use libc::{pid_t, uintptr_t};
|
||||
use std::convert::{AsRef, Into, TryFrom, TryInto};
|
||||
use std::default::Default;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Error, Result};
|
||||
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
use std::time::Duration;
|
||||
|
||||
pub use kqueue_sys::constants::*;
|
||||
|
||||
mod os;
|
||||
use crate::os::vnode;
|
||||
|
||||
mod time;
|
||||
use crate::time::duration_to_timespec;
|
||||
|
||||
/// The watched object that fired the event
|
||||
#[derive(Debug, Eq, Clone)]
|
||||
pub enum Ident {
|
||||
Filename(RawFd, String),
|
||||
Fd(RawFd),
|
||||
Pid(pid_t),
|
||||
Signal(i32),
|
||||
Timer(i32),
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Watched {
|
||||
filter: EventFilter,
|
||||
flags: FilterFlag,
|
||||
ident: Ident,
|
||||
}
|
||||
|
||||
/// Watches one or more resources
|
||||
///
|
||||
/// These can be created with `Watcher::new()`. You can create as many
|
||||
/// `Watcher`s as you want, and they can watch as many objects as you wish.
|
||||
/// The objects do not need to be the same type.
|
||||
///
|
||||
/// Each `Watcher` is backed by a `kqueue(2)` queue. These resources are freed
|
||||
/// on the `Watcher`s destruction. If the destructor cannot run for whatever
|
||||
/// reason, the underlying kernel object will be leaked.
|
||||
#[derive(Debug)]
|
||||
pub struct Watcher {
|
||||
watched: Vec<Watched>,
|
||||
queue: RawFd,
|
||||
started: bool,
|
||||
opts: KqueueOpts,
|
||||
}
|
||||
|
||||
/// Vnode events
|
||||
///
|
||||
/// These are OS-specific, and may not all be supported on your platform. Check
|
||||
/// `kqueue(2)` for more information.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Vnode {
|
||||
/// The file was deleted
|
||||
Delete,
|
||||
|
||||
/// The file received a write
|
||||
Write,
|
||||
|
||||
/// The file was extended with `truncate(2)`
|
||||
Extend,
|
||||
|
||||
/// The file was shrunk with `truncate(2)`
|
||||
Truncate,
|
||||
|
||||
/// The attributes of the file were changed
|
||||
Attrib,
|
||||
|
||||
/// The link count of the file was changed
|
||||
Link,
|
||||
|
||||
/// The file was renamed
|
||||
Rename,
|
||||
|
||||
/// Access to the file was revoked with `revoke(2)` or the fs was unmounted
|
||||
Revoke,
|
||||
|
||||
/// File was opened by a process (FreeBSD-specific)
|
||||
Open,
|
||||
|
||||
/// File was closed and the descriptor had write access (FreeBSD-specific)
|
||||
CloseWrite,
|
||||
|
||||
/// File was closed and the descriptor had read access (FreeBSD-specific)
|
||||
Close,
|
||||
}
|
||||
|
||||
/// Process events
|
||||
///
|
||||
/// These are OS-specific, and may not all be supported on your platform. Check
|
||||
/// `kqueue(2)` for more information.
|
||||
#[derive(Debug)]
|
||||
pub enum Proc {
|
||||
/// The watched process exited with the returned exit code
|
||||
Exit(usize),
|
||||
|
||||
/// The process called `fork(2)`
|
||||
Fork,
|
||||
|
||||
/// The process called `exec(2)`
|
||||
Exec,
|
||||
|
||||
/// The process called `fork(2)`, and returned the child pid.
|
||||
Track(libc::pid_t),
|
||||
|
||||
/// The process called `fork(2)`, but we were not able to track the child
|
||||
Trackerr,
|
||||
|
||||
/// The process called `fork(2)`, and returned the child pid.
|
||||
// TODO: this is FreeBSD-specific. We can probably convert this to `Track`.
|
||||
Child(libc::pid_t),
|
||||
}
|
||||
|
||||
/// Event-specific data returned with the event.
|
||||
///
|
||||
/// Like much of this library, this is OS-specific. Check `kqueue(2)` for more
|
||||
/// details on your target OS.
|
||||
#[derive(Debug)]
|
||||
pub enum EventData {
|
||||
/// Data relating to `Vnode` events
|
||||
Vnode(Vnode),
|
||||
|
||||
/// Data relating to process events
|
||||
Proc(Proc),
|
||||
|
||||
/// The returned number of bytes are ready for reading from the watched
|
||||
/// descriptor
|
||||
ReadReady(usize),
|
||||
|
||||
/// The file is ready for writing. On some files (like sockets, pipes, etc),
|
||||
/// the number of bytes in the write buffer will be returned.
|
||||
WriteReady(usize),
|
||||
|
||||
/// One of the watched signals fired. The number of times this signal was received
|
||||
/// is returned.
|
||||
Signal(usize),
|
||||
|
||||
/// One of the watched timers fired. The number of times this timer fired
|
||||
/// is returned.
|
||||
Timer(usize),
|
||||
|
||||
/// Some error was received
|
||||
Error(Error),
|
||||
}
|
||||
|
||||
/// An event from a `Watcher` object.
|
||||
///
|
||||
/// An event contains both the a signifier of the watched object that triggered
|
||||
/// the event, as well as any event-specific. See the `EventData` enum for info
|
||||
/// on what event-specific data is returned for each event.
|
||||
#[derive(Debug)]
|
||||
pub struct Event {
|
||||
/// The watched resource that triggered the event
|
||||
pub ident: Ident,
|
||||
|
||||
/// Any event-specific data returned with the event.
|
||||
pub data: EventData,
|
||||
}
|
||||
|
||||
pub struct EventIter<'a> {
|
||||
watcher: &'a Watcher,
|
||||
}
|
||||
|
||||
/// Options for a `Watcher`
|
||||
#[derive(Debug)]
|
||||
pub struct KqueueOpts {
|
||||
/// Clear state on watched objects
|
||||
clear: bool,
|
||||
}
|
||||
|
||||
impl Default for KqueueOpts {
|
||||
/// Returns the default options for a `Watcher`
|
||||
///
|
||||
/// `clear` is set to `true`
|
||||
fn default() -> KqueueOpts {
|
||||
KqueueOpts { clear: true }
|
||||
}
|
||||
}
|
||||
|
||||
// We don't have enough information to turn a `usize` into
|
||||
// an `Ident`, so we only implement `Into<usize>` here.
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<usize> for Ident {
|
||||
fn into(self) -> usize {
|
||||
match self {
|
||||
Ident::Filename(fd, _) => fd as usize,
|
||||
Ident::Fd(fd) => fd as usize,
|
||||
Ident::Pid(pid) => pid as usize,
|
||||
Ident::Signal(sig) => sig as usize,
|
||||
Ident::Timer(timer) => timer as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Ident> for Ident {
|
||||
fn eq(&self, other: &Ident) -> bool {
|
||||
match *self {
|
||||
Ident::Filename(_, ref name) => {
|
||||
if let Ident::Filename(_, ref othername) = *other {
|
||||
name == othername
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => self.as_usize() == other.as_usize(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ident {
|
||||
fn as_usize(&self) -> usize {
|
||||
match *self {
|
||||
Ident::Filename(fd, _) => fd as usize,
|
||||
Ident::Fd(fd) => fd as usize,
|
||||
Ident::Pid(pid) => pid as usize,
|
||||
Ident::Signal(sig) => sig as usize,
|
||||
Ident::Timer(timer) => timer as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Watcher {
|
||||
/// Creates a new `Watcher`
|
||||
///
|
||||
/// Creates a brand new `Watcher` with `KqueueOpts::default()`. Will return
|
||||
/// an `io::Error` if creation fails.
|
||||
pub fn new() -> Result<Watcher> {
|
||||
let queue = unsafe { kqueue() };
|
||||
|
||||
if queue == -1 {
|
||||
Err(Error::last_os_error())
|
||||
} else {
|
||||
Ok(Watcher {
|
||||
watched: Vec::new(),
|
||||
queue,
|
||||
started: false,
|
||||
opts: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Disables the `clear` flag on a `Watcher`. New events will no longer
|
||||
/// be added with the `EV_CLEAR` flag on `watch`.
|
||||
pub fn disable_clears(&mut self) -> &mut Self {
|
||||
self.opts.clear = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a `pid` to the `Watcher` to be watched
|
||||
pub fn add_pid(
|
||||
&mut self,
|
||||
pid: libc::pid_t,
|
||||
filter: EventFilter,
|
||||
flags: FilterFlag,
|
||||
) -> Result<()> {
|
||||
let watch = Watched {
|
||||
filter,
|
||||
flags,
|
||||
ident: Ident::Pid(pid),
|
||||
};
|
||||
|
||||
if !self.watched.contains(&watch) {
|
||||
self.watched.push(watch);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds a file by filename to be watched
|
||||
///
|
||||
/// **NB**: `kqueue(2)` is an `fd`-based API. If you add a filename with
|
||||
/// `add_filename`, internally we open it and pass the file descriptor to
|
||||
/// `kqueue(2)`. If the file is moved or deleted, and a new file is created
|
||||
/// with the same name, you will not receive new events for it without
|
||||
/// calling `add_filename` again.
|
||||
///
|
||||
/// TODO: Adding new files requires calling `Watcher.watch` again
|
||||
pub fn add_filename<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
filename: P,
|
||||
filter: EventFilter,
|
||||
flags: FilterFlag,
|
||||
) -> Result<()> {
|
||||
let file = File::open(filename.as_ref())?;
|
||||
let watch = Watched {
|
||||
filter,
|
||||
flags,
|
||||
ident: Ident::Filename(
|
||||
file.into_raw_fd(),
|
||||
filename.as_ref().to_string_lossy().into_owned(),
|
||||
),
|
||||
};
|
||||
|
||||
if !self.watched.contains(&watch) {
|
||||
self.watched.push(watch);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds a descriptor to a `Watcher`. This or `add_file` is the preferred
|
||||
/// way to watch a file
|
||||
///
|
||||
/// TODO: Adding new files requires calling `Watcher.watch` again
|
||||
pub fn add_fd(&mut self, fd: RawFd, filter: EventFilter, flags: FilterFlag) -> Result<()> {
|
||||
let watch = Watched {
|
||||
filter,
|
||||
flags,
|
||||
ident: Ident::Fd(fd),
|
||||
};
|
||||
|
||||
if !self.watched.contains(&watch) {
|
||||
self.watched.push(watch);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds a `File` to a `Watcher`. This, or `add_fd` is the preferred way
|
||||
/// to watch a file
|
||||
///
|
||||
/// TODO: Adding new files requires calling `Watcher.watch` again
|
||||
pub fn add_file(&mut self, file: &File, filter: EventFilter, flags: FilterFlag) -> Result<()> {
|
||||
self.add_fd(file.as_raw_fd(), filter, flags)
|
||||
}
|
||||
|
||||
fn delete_kevents(&self, ident: Ident, filter: EventFilter) -> Result<()> {
|
||||
let kev = vec![kevent::new(
|
||||
ident.as_usize(),
|
||||
filter,
|
||||
EventFlag::EV_DELETE,
|
||||
FilterFlag::empty(),
|
||||
)];
|
||||
|
||||
let ret = unsafe {
|
||||
kevent(
|
||||
self.queue,
|
||||
kev.as_ptr(),
|
||||
// On NetBSD, this is passed as a usize, not i32
|
||||
#[allow(clippy::useless_conversion)]
|
||||
i32::try_from(kev.len()).unwrap().try_into().unwrap(),
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
ptr::null(),
|
||||
)
|
||||
};
|
||||
|
||||
match ret {
|
||||
-1 => Err(Error::last_os_error()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a pid from a `Watcher`
|
||||
pub fn remove_pid(&mut self, pid: libc::pid_t, filter: EventFilter) -> Result<()> {
|
||||
let new_watched = self
|
||||
.watched
|
||||
.drain(..)
|
||||
.filter(|x| {
|
||||
if let Ident::Pid(iterpid) = x.ident {
|
||||
iterpid != pid
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.watched = new_watched;
|
||||
self.delete_kevents(Ident::Pid(pid), filter)
|
||||
}
|
||||
|
||||
/// Removes a filename from a `Watcher`.
|
||||
///
|
||||
/// *NB*: This matches the `filename` that this item was initially added under.
|
||||
/// If a file has been moved, it will not be removable by the new name.
|
||||
pub fn remove_filename<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
filename: P,
|
||||
filter: EventFilter,
|
||||
) -> Result<()> {
|
||||
let mut fd: RawFd = 0;
|
||||
let new_watched = self
|
||||
.watched
|
||||
.drain(..)
|
||||
.filter(|x| {
|
||||
if let Ident::Filename(iterfd, ref iterfile) = x.ident {
|
||||
if iterfile == filename.as_ref().to_str().unwrap() {
|
||||
fd = iterfd;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.watched = new_watched;
|
||||
self.delete_kevents(Ident::Fd(fd), filter)
|
||||
}
|
||||
|
||||
/// Removes an fd from a `Watcher`
|
||||
pub fn remove_fd(&mut self, fd: RawFd, filter: EventFilter) -> Result<()> {
|
||||
let new_watched = self
|
||||
.watched
|
||||
.drain(..)
|
||||
.filter(|x| {
|
||||
if let Ident::Fd(iterfd) = x.ident {
|
||||
iterfd != fd
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.watched = new_watched;
|
||||
self.delete_kevents(Ident::Fd(fd), filter)
|
||||
}
|
||||
|
||||
/// Removes a `File` from a `Watcher`
|
||||
pub fn remove_file(&mut self, file: &File, filter: EventFilter) -> Result<()> {
|
||||
self.remove_fd(file.as_raw_fd(), filter)
|
||||
}
|
||||
|
||||
/// Starts watching for events from `kqueue(2)`. This function needs to
|
||||
/// be called before `Watcher.iter()` or `Watcher.poll()` to actually
|
||||
/// start listening for events.
|
||||
pub fn watch(&mut self) -> Result<()> {
|
||||
let mut kevs: Vec<kevent> = Vec::new();
|
||||
|
||||
for watched in &self.watched {
|
||||
let raw_ident = match watched.ident {
|
||||
Ident::Fd(fd) => fd as uintptr_t,
|
||||
Ident::Filename(fd, _) => fd as uintptr_t,
|
||||
Ident::Pid(pid) => pid as uintptr_t,
|
||||
Ident::Signal(sig) => sig as uintptr_t,
|
||||
Ident::Timer(ident) => ident as uintptr_t,
|
||||
};
|
||||
|
||||
kevs.push(kevent::new(
|
||||
raw_ident,
|
||||
watched.filter,
|
||||
if self.opts.clear {
|
||||
EventFlag::EV_ADD | EventFlag::EV_CLEAR
|
||||
} else {
|
||||
EventFlag::EV_ADD
|
||||
},
|
||||
watched.flags,
|
||||
));
|
||||
}
|
||||
|
||||
let ret = unsafe {
|
||||
kevent(
|
||||
self.queue,
|
||||
kevs.as_ptr(),
|
||||
// On NetBSD, this is passed as a usize, not i32
|
||||
#[allow(clippy::useless_conversion)]
|
||||
i32::try_from(kevs.len()).unwrap().try_into().unwrap(),
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
ptr::null(),
|
||||
)
|
||||
};
|
||||
|
||||
self.started = true;
|
||||
match ret {
|
||||
-1 => Err(Error::last_os_error()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Polls for a new event, with an optional timeout. If no `timeout`
|
||||
/// is passed, then it will return immediately.
|
||||
pub fn poll(&self, timeout: Option<Duration>) -> Option<Event> {
|
||||
// poll will not block indefinitely
|
||||
// None -> return immediately
|
||||
match timeout {
|
||||
Some(timeout) => get_event(self, Some(timeout)),
|
||||
None => get_event(self, Some(Duration::new(0, 0))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Polls for a new event, with an optional timeout. If no `timeout`
|
||||
/// is passed, then it will block until an event is received.
|
||||
pub fn poll_forever(&self, timeout: Option<Duration>) -> Option<Event> {
|
||||
if timeout.is_some() {
|
||||
self.poll(timeout)
|
||||
} else {
|
||||
get_event(self, None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an iterator that iterates over the queue. This iterator will block
|
||||
/// until a new event is received.
|
||||
pub fn iter(&self) -> EventIter {
|
||||
EventIter { watcher: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Watcher {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.queue
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Watcher {
|
||||
fn drop(&mut self) {
|
||||
unsafe { libc::close(self.queue) };
|
||||
for watched in &self.watched {
|
||||
match watched.ident {
|
||||
Ident::Fd(fd) => unsafe { libc::close(fd) },
|
||||
Ident::Filename(fd, _) => unsafe { libc::close(fd) },
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_file_ident(watcher: &Watcher, fd: RawFd) -> Option<Ident> {
|
||||
for watched in &watcher.watched {
|
||||
match watched.ident.clone() {
|
||||
Ident::Fd(ident_fd) => {
|
||||
if fd == ident_fd {
|
||||
return Some(Ident::Fd(fd));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Ident::Filename(ident_fd, ident_str) => {
|
||||
if fd == ident_fd {
|
||||
return Some(Ident::Filename(ident_fd, ident_str));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn get_event(watcher: &Watcher, timeout: Option<Duration>) -> Option<Event> {
|
||||
let mut kev = kevent::new(
|
||||
0,
|
||||
EventFilter::EVFILT_SYSCOUNT,
|
||||
EventFlag::empty(),
|
||||
FilterFlag::empty(),
|
||||
);
|
||||
let ret = if let Some(ts) = timeout {
|
||||
unsafe {
|
||||
kevent(
|
||||
watcher.queue,
|
||||
ptr::null(),
|
||||
0,
|
||||
&mut kev,
|
||||
1,
|
||||
&duration_to_timespec(ts),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
unsafe { kevent(watcher.queue, ptr::null(), 0, &mut kev, 1, ptr::null()) }
|
||||
};
|
||||
|
||||
match ret {
|
||||
-1 => Some(Event::from_error(kev, watcher)),
|
||||
0 => None, // timeout expired
|
||||
_ => Some(Event::new(kev, watcher)),
|
||||
}
|
||||
}
|
||||
|
||||
// OS specific
|
||||
// TODO: Events can have more than one filter flag
|
||||
impl Event {
|
||||
#[doc(hidden)]
|
||||
pub fn new(ev: kevent, watcher: &Watcher) -> Event {
|
||||
let data = match ev.filter {
|
||||
EventFilter::EVFILT_READ => EventData::ReadReady(ev.data as usize),
|
||||
EventFilter::EVFILT_WRITE => EventData::WriteReady(ev.data as usize),
|
||||
EventFilter::EVFILT_SIGNAL => EventData::Signal(ev.data as usize),
|
||||
EventFilter::EVFILT_TIMER => EventData::Timer(ev.data as usize),
|
||||
EventFilter::EVFILT_PROC => {
|
||||
let inner = if ev.fflags.contains(FilterFlag::NOTE_EXIT) {
|
||||
Proc::Exit(ev.data as usize)
|
||||
} else if ev.fflags.contains(FilterFlag::NOTE_FORK) {
|
||||
Proc::Fork
|
||||
} else if ev.fflags.contains(FilterFlag::NOTE_EXEC) {
|
||||
Proc::Exec
|
||||
} else if ev.fflags.contains(FilterFlag::NOTE_TRACK) {
|
||||
Proc::Track(ev.data as libc::pid_t)
|
||||
} else if ev.fflags.contains(FilterFlag::NOTE_CHILD) {
|
||||
Proc::Child(ev.data as libc::pid_t)
|
||||
} else {
|
||||
panic!("not supported: {:?}", ev.fflags)
|
||||
};
|
||||
|
||||
EventData::Proc(inner)
|
||||
}
|
||||
EventFilter::EVFILT_VNODE => {
|
||||
let inner = if ev.fflags.contains(FilterFlag::NOTE_DELETE) {
|
||||
Vnode::Delete
|
||||
} else if ev.fflags.contains(FilterFlag::NOTE_WRITE) {
|
||||
Vnode::Write
|
||||
} else if ev.fflags.contains(FilterFlag::NOTE_EXTEND) {
|
||||
Vnode::Extend
|
||||
} else if ev.fflags.contains(FilterFlag::NOTE_ATTRIB) {
|
||||
Vnode::Attrib
|
||||
} else if ev.fflags.contains(FilterFlag::NOTE_LINK) {
|
||||
Vnode::Link
|
||||
} else if ev.fflags.contains(FilterFlag::NOTE_RENAME) {
|
||||
Vnode::Rename
|
||||
} else if ev.fflags.contains(FilterFlag::NOTE_REVOKE) {
|
||||
Vnode::Revoke
|
||||
} else {
|
||||
// This handles any filter flags that are OS-specific
|
||||
vnode::handle_vnode_extras(ev.fflags)
|
||||
};
|
||||
|
||||
EventData::Vnode(inner)
|
||||
}
|
||||
_ => panic!("not supported"),
|
||||
};
|
||||
|
||||
let ident = match ev.filter {
|
||||
EventFilter::EVFILT_READ => find_file_ident(watcher, ev.ident as RawFd).unwrap(),
|
||||
EventFilter::EVFILT_WRITE => find_file_ident(watcher, ev.ident as RawFd).unwrap(),
|
||||
EventFilter::EVFILT_VNODE => find_file_ident(watcher, ev.ident as RawFd).unwrap(),
|
||||
EventFilter::EVFILT_SIGNAL => Ident::Signal(ev.ident as i32),
|
||||
EventFilter::EVFILT_TIMER => Ident::Timer(ev.ident as i32),
|
||||
EventFilter::EVFILT_PROC => Ident::Pid(ev.ident as pid_t),
|
||||
_ => panic!("not supported"),
|
||||
};
|
||||
|
||||
Event { ident, data }
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn from_error(ev: kevent, watcher: &Watcher) -> Event {
|
||||
let ident = match ev.filter {
|
||||
EventFilter::EVFILT_READ => find_file_ident(watcher, ev.ident as RawFd).unwrap(),
|
||||
EventFilter::EVFILT_WRITE => find_file_ident(watcher, ev.ident as RawFd).unwrap(),
|
||||
EventFilter::EVFILT_VNODE => find_file_ident(watcher, ev.ident as RawFd).unwrap(),
|
||||
EventFilter::EVFILT_SIGNAL => Ident::Signal(ev.ident as i32),
|
||||
EventFilter::EVFILT_TIMER => Ident::Timer(ev.ident as i32),
|
||||
EventFilter::EVFILT_PROC => Ident::Pid(ev.ident as pid_t),
|
||||
_ => panic!("not supported"),
|
||||
};
|
||||
|
||||
Event {
|
||||
data: EventData::Error(io::Error::last_os_error()),
|
||||
ident,
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn is_err(&self) -> bool {
|
||||
matches!(self.data, EventData::Error(_))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for EventIter<'a> {
|
||||
type Item = Event;
|
||||
|
||||
// rather than call kevent(2) each time, we can likely optimize and
|
||||
// call it once for like 100 items
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if !self.watcher.started {
|
||||
return None;
|
||||
}
|
||||
|
||||
get_event(self.watcher, None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{EventData, EventFilter, FilterFlag, Ident, Vnode, Watcher};
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use std::path::Path;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
use std::process;
|
||||
|
||||
#[test]
|
||||
fn test_new_watcher() {
|
||||
let mut watcher = Watcher::new().expect("new failed");
|
||||
let file = tempfile::tempfile().expect("Couldn't create tempfile");
|
||||
|
||||
watcher
|
||||
.add_file(&file, EventFilter::EVFILT_VNODE, FilterFlag::NOTE_WRITE)
|
||||
.expect("add failed");
|
||||
watcher.watch().expect("watch failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filename() {
|
||||
let mut watcher = Watcher::new().expect("new failed");
|
||||
let file = tempfile::NamedTempFile::new().expect("Couldn't create tempfile");
|
||||
|
||||
watcher
|
||||
.add_filename(
|
||||
file.path(),
|
||||
EventFilter::EVFILT_VNODE,
|
||||
FilterFlag::NOTE_WRITE,
|
||||
)
|
||||
.expect("add failed");
|
||||
watcher.watch().expect("watch failed");
|
||||
|
||||
let mut new_file = fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.open(file.path())
|
||||
.expect("open failed");
|
||||
|
||||
new_file.write_all(b"foo").expect("write failed");
|
||||
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
|
||||
let ev = watcher.iter().next().expect("Could not get a watch");
|
||||
assert!(matches!(ev.data, EventData::Vnode(Vnode::Write)));
|
||||
|
||||
match ev.ident {
|
||||
Ident::Filename(_, name) => assert!(Path::new(&name) == file.path()),
|
||||
_ => panic!(),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file() {
|
||||
let mut watcher = Watcher::new().expect("new failed");
|
||||
let mut file = tempfile::tempfile().expect("Could not create tempfile");
|
||||
|
||||
watcher
|
||||
.add_file(&file, EventFilter::EVFILT_VNODE, FilterFlag::NOTE_WRITE)
|
||||
.expect("add failed");
|
||||
watcher.watch().expect("watch failed");
|
||||
file.write_all(b"foo").expect("write failed");
|
||||
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
|
||||
let ev = watcher.iter().next().expect("Didn't get an event");
|
||||
|
||||
assert!(matches!(ev.data, EventData::Vnode(Vnode::Write)));
|
||||
assert!(matches!(ev.ident, Ident::Fd(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_filename() {
|
||||
let mut watcher = Watcher::new().expect("new failed");
|
||||
|
||||
let file = tempfile::NamedTempFile::new().expect("Could not create tempfile");
|
||||
let filename = file.path();
|
||||
|
||||
watcher
|
||||
.add_filename(filename, EventFilter::EVFILT_VNODE, FilterFlag::NOTE_WRITE)
|
||||
.expect("add failed");
|
||||
watcher.watch().expect("watch failed");
|
||||
watcher
|
||||
.remove_filename(filename, EventFilter::EVFILT_VNODE)
|
||||
.expect("delete failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dupe() {
|
||||
let mut watcher = Watcher::new().expect("new failed");
|
||||
let file = tempfile::NamedTempFile::new().expect("Couldn't create tempfile");
|
||||
let filename = file.path();
|
||||
|
||||
watcher
|
||||
.add_filename(filename, EventFilter::EVFILT_VNODE, FilterFlag::NOTE_WRITE)
|
||||
.expect("add failed");
|
||||
watcher
|
||||
.add_filename(filename, EventFilter::EVFILT_VNODE, FilterFlag::NOTE_WRITE)
|
||||
.expect("second add failed");
|
||||
|
||||
assert_eq!(
|
||||
watcher.watched.len(),
|
||||
1,
|
||||
"Did not get an expected number of events"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_two_files() {
|
||||
let mut watcher = Watcher::new().expect("new failed");
|
||||
|
||||
let mut first_file = tempfile::tempfile().expect("Unable to create first temporary file");
|
||||
let mut second_file = tempfile::tempfile().expect("Unable to create second temporary file");
|
||||
|
||||
watcher
|
||||
.add_file(
|
||||
&first_file,
|
||||
EventFilter::EVFILT_VNODE,
|
||||
FilterFlag::NOTE_WRITE,
|
||||
)
|
||||
.expect("add failed");
|
||||
|
||||
watcher
|
||||
.add_file(
|
||||
&second_file,
|
||||
EventFilter::EVFILT_VNODE,
|
||||
FilterFlag::NOTE_WRITE,
|
||||
)
|
||||
.expect("add failed");
|
||||
|
||||
watcher.watch().expect("watch failed");
|
||||
first_file.write_all(b"foo").expect("first write failed");
|
||||
second_file.write_all(b"foo").expect("second write failed");
|
||||
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
|
||||
watcher.iter().next().expect("didn't get any events");
|
||||
watcher.iter().next().expect("didn't get any events");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested_kqueue() {
|
||||
let mut watcher = Watcher::new().expect("Failed to create main watcher");
|
||||
let mut nested_watcher = Watcher::new().expect("Failed to create nested watcher");
|
||||
|
||||
let kqueue_file = unsafe { fs::File::from_raw_fd(nested_watcher.as_raw_fd()) };
|
||||
watcher
|
||||
.add_file(&kqueue_file, EventFilter::EVFILT_READ, FilterFlag::empty())
|
||||
.expect("add_file failed for main watcher");
|
||||
|
||||
let mut file = tempfile::tempfile().expect("Couldn't create tempfile");
|
||||
nested_watcher
|
||||
.add_file(&file, EventFilter::EVFILT_VNODE, FilterFlag::NOTE_WRITE)
|
||||
.expect("add_file failed for nested watcher");
|
||||
|
||||
watcher.watch().expect("watch failed on main watcher");
|
||||
nested_watcher
|
||||
.watch()
|
||||
.expect("watch failed on nested watcher");
|
||||
|
||||
file.write_all(b"foo").expect("write failed");
|
||||
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
|
||||
watcher.iter().next().expect("didn't get any events");
|
||||
nested_watcher.iter().next().expect("didn't get any events");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "freebsd")]
|
||||
fn test_close_read() {
|
||||
let mut watcher = Watcher::new().expect("new failed");
|
||||
|
||||
{
|
||||
let file = tempfile::NamedTempFile::new().expect("temporary file failed to create");
|
||||
watcher
|
||||
.add_filename(
|
||||
file.path(),
|
||||
EventFilter::EVFILT_VNODE,
|
||||
FilterFlag::NOTE_CLOSE,
|
||||
)
|
||||
.expect("add failed");
|
||||
watcher.watch().expect("watch failed");
|
||||
|
||||
// we launch this in a separate process since it appears that FreeBSD does not fire
|
||||
// off a NOTE_CLOSE(_WRITE)? event for the same process closing a file descriptor.
|
||||
process::Command::new("cat")
|
||||
.arg(file.path())
|
||||
.spawn()
|
||||
.expect("should spawn a file");
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
}
|
||||
let ev = watcher.iter().next().expect("did not receive event");
|
||||
assert!(matches!(ev.data, EventData::Vnode(Vnode::Close)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "freebsd")]
|
||||
fn test_close_write() {
|
||||
let mut watcher = match Watcher::new() {
|
||||
Ok(wat) => wat,
|
||||
Err(_) => panic!("new failed"),
|
||||
};
|
||||
|
||||
{
|
||||
let file = tempfile::NamedTempFile::new().expect("couldn't create tempfile");
|
||||
watcher
|
||||
.add_filename(
|
||||
file.path(),
|
||||
EventFilter::EVFILT_VNODE,
|
||||
FilterFlag::NOTE_CLOSE_WRITE,
|
||||
)
|
||||
.expect("add failed");
|
||||
watcher.watch().expect("watch failed");
|
||||
|
||||
// See above for rationale as to why we use a separate process here
|
||||
process::Command::new("cat")
|
||||
.arg(file.path())
|
||||
.spawn()
|
||||
.expect("should spawn a file");
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
}
|
||||
let ev = watcher.iter().next().expect("didn't get an event");
|
||||
assert!(matches!(ev.data, EventData::Vnode(Vnode::CloseWrite)));
|
||||
}
|
||||
}
|
||||
1
third-party/vendor/kqueue/src/os/mod.rs
vendored
Normal file
1
third-party/vendor/kqueue/src/os/mod.rs
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub(crate) mod vnode;
|
||||
21
third-party/vendor/kqueue/src/os/vnode.rs
vendored
Normal file
21
third-party/vendor/kqueue/src/os/vnode.rs
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use kqueue_sys::constants::FilterFlag;
|
||||
|
||||
use super::super::Vnode;
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
pub(crate) fn handle_vnode_extras(ff: FilterFlag) -> Vnode {
|
||||
if ff.contains(FilterFlag::NOTE_CLOSE_WRITE) {
|
||||
Vnode::CloseWrite
|
||||
} else if ff.contains(FilterFlag::NOTE_CLOSE) {
|
||||
Vnode::Close
|
||||
} else if ff.contains(FilterFlag::NOTE_OPEN) {
|
||||
Vnode::Open
|
||||
} else {
|
||||
panic!("not supported")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
pub(crate) fn handle_vnode_extras(_ff: FilterFlag) -> Vnode {
|
||||
panic!("not supported")
|
||||
}
|
||||
49
third-party/vendor/kqueue/src/time.rs
vendored
Normal file
49
third-party/vendor/kqueue/src/time.rs
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use libc::{c_long, time_t, timespec};
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
|
||||
type NSec = i64;
|
||||
#[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))]
|
||||
type NSec = c_long;
|
||||
|
||||
pub(crate) fn duration_to_timespec(d: Duration) -> timespec {
|
||||
let tv_sec = d.as_secs() as time_t;
|
||||
let tv_nsec = d.subsec_nanos() as NSec;
|
||||
|
||||
if tv_sec.is_negative() {
|
||||
panic!("Duration seconds is negative");
|
||||
}
|
||||
|
||||
if tv_nsec.is_negative() {
|
||||
panic!("Duration nsecs is negative");
|
||||
}
|
||||
|
||||
timespec { tv_sec, tv_nsec }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::duration_to_timespec;
|
||||
use std::time::Duration;
|
||||
use std::{i64, u32};
|
||||
|
||||
#[test]
|
||||
fn test_basic_duration_to_ts() {
|
||||
let d = Duration::new(4, 20);
|
||||
|
||||
let ts = duration_to_timespec(d);
|
||||
|
||||
assert_eq!(ts.tv_sec, 4);
|
||||
assert_eq!(ts.tv_nsec, 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_overflow() {
|
||||
let d = Duration::new(i64::MAX as u64 + 1, u32::MAX);
|
||||
let ts = duration_to_timespec(d);
|
||||
|
||||
assert_eq!(ts.tv_sec, 1);
|
||||
assert_eq!(ts.tv_nsec, 1);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue