Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
//! Configuration types
use std::time::Duration;
/// Indicates whether only the provided directory or its sub-directories as well should be watched
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub enum RecursiveMode {
/// Watch all sub-directories as well, including directories created after installing the watch
Recursive,
/// Watch only the provided directory
NonRecursive,
}
impl RecursiveMode {
pub(crate) fn is_recursive(&self) -> bool {
match *self {
RecursiveMode::Recursive => true,
RecursiveMode::NonRecursive => false,
}
}
}
/// Watcher Backend configuration
///
/// This contains multiple settings that may relate to only one specific backend,
/// such as to correctly configure each backend regardless of what is selected during runtime.
///
/// ```rust
/// # use std::time::Duration;
/// # use notify::Config;
/// let config = Config::default()
/// .with_poll_interval(Duration::from_secs(2))
/// .with_compare_contents(true);
/// ```
///
/// Some options can be changed during runtime, others have to be set when creating the watcher backend.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct Config {
/// See [Config::with_poll_interval]
poll_interval: Option<Duration>,
/// See [Config::with_compare_contents]
compare_contents: bool,
follow_symlinks: bool,
}
impl Config {
/// For the [`PollWatcher`](crate::PollWatcher) backend.
///
/// Interval between each re-scan attempt. This can be extremely expensive for large
/// file trees so it is recommended to measure and tune accordingly.
///
/// The default poll frequency is 30 seconds.
///
/// This will enable automatic polling, overwriting [`with_manual_polling()`](Config::with_manual_polling).
pub fn with_poll_interval(mut self, dur: Duration) -> Self {
// TODO: v7.0 break signature to option
self.poll_interval = Some(dur);
self
}
/// Returns current setting
pub fn poll_interval(&self) -> Option<Duration> {
// Changed Signature to Option
self.poll_interval
}
/// For the [`PollWatcher`](crate::PollWatcher) backend.
///
/// Disable automatic polling. Requires calling [`crate::PollWatcher::poll()`] manually.
///
/// This will disable automatic polling, overwriting [`with_poll_interval()`](Config::with_poll_interval).
pub fn with_manual_polling(mut self) -> Self {
self.poll_interval = None;
self
}
/// For the [`PollWatcher`](crate::PollWatcher) backend.
///
/// Optional feature that will evaluate the contents of changed files to determine if
/// they have indeed changed using a fast hashing algorithm. This is especially important
/// for pseudo filesystems like those on Linux under /sys and /proc which are not obligated
/// to respect any other filesystem norms such as modification timestamps, file sizes, etc.
/// By enabling this feature, performance will be significantly impacted as all files will
/// need to be read and hashed at each `poll_interval`.
///
/// This can't be changed during runtime. Off by default.
pub fn with_compare_contents(mut self, compare_contents: bool) -> Self {
self.compare_contents = compare_contents;
self
}
/// Returns current setting
pub fn compare_contents(&self) -> bool {
self.compare_contents
}
/// For the [INotifyWatcher](crate::INotifyWatcher), [KqueueWatcher](crate::KqueueWatcher),
/// and [PollWatcher](crate::PollWatcher).
///
/// Determine if symbolic links should be followed when recursively watching a directory.
///
/// This can't be changed during runtime. On by default.
pub fn with_follow_symlinks(mut self, follow_symlinks: bool) -> Self {
self.follow_symlinks = follow_symlinks;
self
}
/// Returns current setting
pub fn follow_symlinks(&self) -> bool {
self.follow_symlinks
}
}
impl Default for Config {
fn default() -> Self {
Self {
poll_interval: Some(Duration::from_secs(30)),
compare_contents: false,
follow_symlinks: true,
}
}
}
+171
View File
@@ -0,0 +1,171 @@
//! Error types
use crate::Config;
use std::error::Error as StdError;
use std::path::PathBuf;
use std::result::Result as StdResult;
use std::{self, fmt, io};
/// Type alias to use this library's `Error` type in a Result
pub type Result<T> = StdResult<T, Error>;
/// Error kinds
#[derive(Debug)]
pub enum ErrorKind {
/// Generic error
///
/// May be used in cases where a platform specific error is mapped to this type, or for opaque
/// internal errors.
Generic(String),
/// I/O errors.
Io(io::Error),
/// A path does not exist.
PathNotFound,
/// Attempted to remove a watch that does not exist.
WatchNotFound,
/// An invalid value was passed as runtime configuration.
InvalidConfig(Config),
/// Can't watch (more) files, limit on the total number of inotify watches reached
MaxFilesWatch,
}
/// Notify error type.
///
/// Errors are emitted either at creation time of a `Watcher`, or during the event stream. They
/// range from kernel errors to filesystem errors to argument errors.
///
/// Errors can be general, or they can be about specific paths or subtrees. In that later case, the
/// error's `paths` field will be populated.
#[derive(Debug)]
pub struct Error {
/// Kind of the error.
pub kind: ErrorKind,
/// Relevant paths to the error, if any.
pub paths: Vec<PathBuf>,
}
impl Error {
/// Adds a path to the error.
pub fn add_path(mut self, path: PathBuf) -> Self {
self.paths.push(path);
self
}
/// Replaces the paths for the error.
pub fn set_paths(mut self, paths: Vec<PathBuf>) -> Self {
self.paths = paths;
self
}
/// Creates a new Error with empty paths given its kind.
pub fn new(kind: ErrorKind) -> Self {
Self {
kind,
paths: Vec::new(),
}
}
/// Creates a new generic Error from a message.
pub fn generic(msg: &str) -> Self {
Self::new(ErrorKind::Generic(msg.into()))
}
/// Creates a new i/o Error from a stdlib `io::Error`.
pub fn io(err: io::Error) -> Self {
Self::new(ErrorKind::Io(err))
}
/// Similar to [`Error::io`], but specifically handles [`io::ErrorKind::NotFound`].
pub fn io_watch(err: io::Error) -> Self {
if err.kind() == io::ErrorKind::NotFound {
Self::path_not_found()
} else {
Self::io(err)
}
}
/// Creates a new "path not found" error.
pub fn path_not_found() -> Self {
Self::new(ErrorKind::PathNotFound)
}
/// Creates a new "watch not found" error.
pub fn watch_not_found() -> Self {
Self::new(ErrorKind::WatchNotFound)
}
/// Creates a new "invalid config" error from the given `Config`.
pub fn invalid_config(config: &Config) -> Self {
Self::new(ErrorKind::InvalidConfig(*config))
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let error = match self.kind {
ErrorKind::PathNotFound => "No path was found.".into(),
ErrorKind::WatchNotFound => "No watch was found.".into(),
ErrorKind::InvalidConfig(ref config) => format!("Invalid configuration: {:?}", config),
ErrorKind::Generic(ref err) => err.clone(),
ErrorKind::Io(ref err) => err.to_string(),
ErrorKind::MaxFilesWatch => "OS file watch limit reached.".into(),
};
if self.paths.is_empty() {
write!(f, "{}", error)
} else {
write!(f, "{} about {:?}", error, self.paths)
}
}
}
impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
match self.kind {
ErrorKind::Io(ref cause) => Some(cause),
_ => None,
}
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Error::io(err)
}
}
impl<T> From<std::sync::mpsc::SendError<T>> for Error {
fn from(err: std::sync::mpsc::SendError<T>) -> Self {
Error::generic(&format!("internal channel disconnect: {:?}", err))
}
}
impl From<std::sync::mpsc::RecvError> for Error {
fn from(err: std::sync::mpsc::RecvError) -> Self {
Error::generic(&format!("internal channel disconnect: {:?}", err))
}
}
impl<T> From<std::sync::PoisonError<T>> for Error {
fn from(err: std::sync::PoisonError<T>) -> Self {
Error::generic(&format!("internal mutex poisoned: {:?}", err))
}
}
#[test]
fn display_formatted_errors() {
let expected = "Some error";
assert_eq!(expected, format!("{}", Error::generic(expected)));
assert_eq!(
expected,
format!("{}", Error::io(io::Error::other(expected)))
);
}
+651
View File
@@ -0,0 +1,651 @@
//! Watcher implementation for Darwin's FSEvents API
//!
//! The FSEvents API provides a mechanism to notify clients about directories they ought to re-scan
//! in order to keep their internal data structures up-to-date with respect to the true state of
//! the file system. (For example, when files or directories are created, modified, or removed.) It
//! sends these notifications "in bulk", possibly notifying the client of changes to several
//! directories in a single callback.
//!
//! For more information see the [FSEvents API reference][ref].
//!
//! TODO: document event translation
//!
//! [ref]: https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/
#![allow(non_upper_case_globals, dead_code)]
use crate::event::*;
use crate::{
unbounded, Config, Error, EventHandler, PathsMut, RecursiveMode, Result, Sender, Watcher,
};
use fsevent_sys as fs;
use fsevent_sys::core_foundation as cf;
use std::collections::HashMap;
use std::ffi::CStr;
use std::fmt;
use std::os::raw;
use std::path::{Path, PathBuf};
use std::ptr;
use std::sync::{Arc, Mutex};
use std::thread;
bitflags::bitflags! {
#[repr(C)]
#[derive(Debug)]
struct StreamFlags: u32 {
const NONE = fs::kFSEventStreamEventFlagNone;
const MUST_SCAN_SUBDIRS = fs::kFSEventStreamEventFlagMustScanSubDirs;
const USER_DROPPED = fs::kFSEventStreamEventFlagUserDropped;
const KERNEL_DROPPED = fs::kFSEventStreamEventFlagKernelDropped;
const IDS_WRAPPED = fs::kFSEventStreamEventFlagEventIdsWrapped;
const HISTORY_DONE = fs::kFSEventStreamEventFlagHistoryDone;
const ROOT_CHANGED = fs::kFSEventStreamEventFlagRootChanged;
const MOUNT = fs::kFSEventStreamEventFlagMount;
const UNMOUNT = fs::kFSEventStreamEventFlagUnmount;
const ITEM_CREATED = fs::kFSEventStreamEventFlagItemCreated;
const ITEM_REMOVED = fs::kFSEventStreamEventFlagItemRemoved;
const INODE_META_MOD = fs::kFSEventStreamEventFlagItemInodeMetaMod;
const ITEM_RENAMED = fs::kFSEventStreamEventFlagItemRenamed;
const ITEM_MODIFIED = fs::kFSEventStreamEventFlagItemModified;
const FINDER_INFO_MOD = fs::kFSEventStreamEventFlagItemFinderInfoMod;
const ITEM_CHANGE_OWNER = fs::kFSEventStreamEventFlagItemChangeOwner;
const ITEM_XATTR_MOD = fs::kFSEventStreamEventFlagItemXattrMod;
const IS_FILE = fs::kFSEventStreamEventFlagItemIsFile;
const IS_DIR = fs::kFSEventStreamEventFlagItemIsDir;
const IS_SYMLINK = fs::kFSEventStreamEventFlagItemIsSymlink;
const OWN_EVENT = fs::kFSEventStreamEventFlagOwnEvent;
const IS_HARDLINK = fs::kFSEventStreamEventFlagItemIsHardlink;
const IS_LAST_HARDLINK = fs::kFSEventStreamEventFlagItemIsLastHardlink;
const ITEM_CLONED = fs::kFSEventStreamEventFlagItemCloned;
}
}
/// FSEvents-based `Watcher` implementation
pub struct FsEventWatcher {
paths: cf::CFMutableArrayRef,
since_when: fs::FSEventStreamEventId,
latency: cf::CFTimeInterval,
flags: fs::FSEventStreamCreateFlags,
event_handler: Arc<Mutex<dyn EventHandler>>,
runloop: Option<(cf::CFRunLoopRef, thread::JoinHandle<()>)>,
recursive_info: HashMap<PathBuf, bool>,
}
impl fmt::Debug for FsEventWatcher {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("FsEventWatcher")
.field("paths", &self.paths)
.field("since_when", &self.since_when)
.field("latency", &self.latency)
.field("flags", &self.flags)
.field("event_handler", &Arc::as_ptr(&self.event_handler))
.field("runloop", &self.runloop)
.field("recursive_info", &self.recursive_info)
.finish()
}
}
// CFMutableArrayRef is a type alias to *mut libc::c_void, so FsEventWatcher is not Send/Sync
// automatically. It's Send because the pointer is not used in other threads.
unsafe impl Send for FsEventWatcher {}
// It's Sync because all methods that change the mutable state use `&mut self`.
unsafe impl Sync for FsEventWatcher {}
fn translate_flags(flags: StreamFlags, precise: bool) -> Vec<Event> {
let mut evs = Vec::new();
// «Denotes a sentinel event sent to mark the end of the "historical" events
// sent as a result of specifying a `sinceWhen` value in the FSEvents.Create
// call that created this event stream. After invoking the client's callback
// with all the "historical" events that occurred before now, the client's
// callback will be invoked with an event where the HistoryDone flag is set.
// The client should ignore the path supplied in this callback.»
// — https://www.mbsplugins.eu/FSEventsNextEvent.shtml
//
// As a result, we just stop processing here and return an empty vec, which
// will ignore this completely and not emit any Events whatsoever.
if flags.contains(StreamFlags::HISTORY_DONE) {
return evs;
}
// FSEvents provides two possible hints as to why events were dropped,
// however documentation on what those mean is scant, so we just pass them
// through in the info attr field. The intent is clear enough, and the
// additional information is provided if the user wants it.
if flags.contains(StreamFlags::MUST_SCAN_SUBDIRS) {
let e = Event::new(EventKind::Other).set_flag(Flag::Rescan);
evs.push(if flags.contains(StreamFlags::USER_DROPPED) {
e.set_info("rescan: user dropped")
} else if flags.contains(StreamFlags::KERNEL_DROPPED) {
e.set_info("rescan: kernel dropped")
} else {
e
});
}
// In imprecise mode, let's not even bother parsing the kind of the event
// except for the above very special events.
if !precise {
evs.push(Event::new(EventKind::Any));
return evs;
}
// This is most likely a rename or a removal. We assume rename but may want
// to figure out if it was a removal some way later (TODO). To denote the
// special nature of the event, we add an info string.
if flags.contains(StreamFlags::ROOT_CHANGED) {
evs.push(
Event::new(EventKind::Modify(ModifyKind::Name(RenameMode::From)))
.set_info("root changed"),
);
}
// A path was mounted at the event path; we treat that as a create.
if flags.contains(StreamFlags::MOUNT) {
evs.push(Event::new(EventKind::Create(CreateKind::Other)).set_info("mount"));
}
// A path was unmounted at the event path; we treat that as a remove.
if flags.contains(StreamFlags::UNMOUNT) {
evs.push(Event::new(EventKind::Remove(RemoveKind::Other)).set_info("mount"));
}
if flags.contains(StreamFlags::ITEM_CREATED) {
evs.push(if flags.contains(StreamFlags::IS_DIR) {
Event::new(EventKind::Create(CreateKind::Folder))
} else if flags.contains(StreamFlags::IS_FILE) {
Event::new(EventKind::Create(CreateKind::File))
} else {
let e = Event::new(EventKind::Create(CreateKind::Other));
if flags.contains(StreamFlags::IS_SYMLINK) {
e.set_info("is: symlink")
} else if flags.contains(StreamFlags::IS_HARDLINK) {
e.set_info("is: hardlink")
} else if flags.contains(StreamFlags::ITEM_CLONED) {
e.set_info("is: clone")
} else {
Event::new(EventKind::Create(CreateKind::Any))
}
});
}
if flags.contains(StreamFlags::ITEM_REMOVED) {
evs.push(if flags.contains(StreamFlags::IS_DIR) {
Event::new(EventKind::Remove(RemoveKind::Folder))
} else if flags.contains(StreamFlags::IS_FILE) {
Event::new(EventKind::Remove(RemoveKind::File))
} else {
let e = Event::new(EventKind::Remove(RemoveKind::Other));
if flags.contains(StreamFlags::IS_SYMLINK) {
e.set_info("is: symlink")
} else if flags.contains(StreamFlags::IS_HARDLINK) {
e.set_info("is: hardlink")
} else if flags.contains(StreamFlags::ITEM_CLONED) {
e.set_info("is: clone")
} else {
Event::new(EventKind::Remove(RemoveKind::Any))
}
});
}
// FSEvents provides no mechanism to associate the old and new sides of a
// rename event.
if flags.contains(StreamFlags::ITEM_RENAMED) {
evs.push(Event::new(EventKind::Modify(ModifyKind::Name(
RenameMode::Any,
))));
}
// This is only described as "metadata changed", but it may be that it's
// only emitted for some more precise subset of events... if so, will need
// amending, but for now we have an Any-shaped bucket to put it in.
if flags.contains(StreamFlags::INODE_META_MOD) {
evs.push(Event::new(EventKind::Modify(ModifyKind::Metadata(
MetadataKind::Any,
))));
}
if flags.contains(StreamFlags::FINDER_INFO_MOD) {
evs.push(
Event::new(EventKind::Modify(ModifyKind::Metadata(MetadataKind::Other)))
.set_info("meta: finder info"),
);
}
if flags.contains(StreamFlags::ITEM_CHANGE_OWNER) {
evs.push(Event::new(EventKind::Modify(ModifyKind::Metadata(
MetadataKind::Ownership,
))));
}
if flags.contains(StreamFlags::ITEM_XATTR_MOD) {
evs.push(Event::new(EventKind::Modify(ModifyKind::Metadata(
MetadataKind::Extended,
))));
}
// This is specifically described as a data change, which we take to mean
// is a content change.
if flags.contains(StreamFlags::ITEM_MODIFIED) {
evs.push(Event::new(EventKind::Modify(ModifyKind::Data(
DataChange::Content,
))));
}
if flags.contains(StreamFlags::OWN_EVENT) {
for ev in &mut evs {
*ev = std::mem::take(ev).set_process_id(std::process::id());
}
}
evs
}
struct StreamContextInfo {
event_handler: Arc<Mutex<dyn EventHandler>>,
recursive_info: HashMap<PathBuf, bool>,
}
// Free the context when the stream created by `FSEventStreamCreate` is released.
extern "C" fn release_context(info: *const libc::c_void) {
// Safety:
// - The [documentation] for `FSEventStreamContext` states that `release` is only
// called when the stream is deallocated, so it is safe to convert `info` back into a
// box and drop it.
//
// [docs]: https://developer.apple.com/documentation/coreservices/fseventstreamcontext?language=objc
unsafe {
drop(Box::from_raw(
info as *const StreamContextInfo as *mut StreamContextInfo,
));
}
}
extern "C" {
/// Indicates whether the run loop is waiting for an event.
fn CFRunLoopIsWaiting(runloop: cf::CFRunLoopRef) -> cf::Boolean;
}
struct FsEventPathsMut<'a>(&'a mut FsEventWatcher);
impl<'a> FsEventPathsMut<'a> {
fn new(watcher: &'a mut FsEventWatcher) -> Self {
watcher.stop();
Self(watcher)
}
}
impl PathsMut for FsEventPathsMut<'_> {
fn add(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
self.0.append_path(path, recursive_mode)
}
fn remove(&mut self, path: &Path) -> Result<()> {
self.0.remove_path(path)
}
fn commit(self: Box<Self>) -> Result<()> {
// ignore return error: may be empty path list
let _ = self.0.run();
Ok(())
}
}
impl FsEventWatcher {
fn from_event_handler(event_handler: Arc<Mutex<dyn EventHandler>>) -> Result<Self> {
Ok(FsEventWatcher {
paths: unsafe {
cf::CFArrayCreateMutable(cf::kCFAllocatorDefault, 0, &cf::kCFTypeArrayCallBacks)
},
since_when: fs::kFSEventStreamEventIdSinceNow,
latency: 0.0,
flags: fs::kFSEventStreamCreateFlagFileEvents | fs::kFSEventStreamCreateFlagNoDefer,
event_handler,
runloop: None,
recursive_info: HashMap::new(),
})
}
fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
self.stop();
let result = self.append_path(path, recursive_mode);
// ignore return error: may be empty path list
let _ = self.run();
result
}
fn unwatch_inner(&mut self, path: &Path) -> Result<()> {
self.stop();
let result = self.remove_path(path);
// ignore return error: may be empty path list
let _ = self.run();
result
}
#[inline]
fn is_running(&self) -> bool {
self.runloop.is_some()
}
fn stop(&mut self) {
if !self.is_running() {
return;
}
if let Some((runloop, thread_handle)) = self.runloop.take() {
unsafe {
let runloop = runloop as *mut raw::c_void;
while CFRunLoopIsWaiting(runloop) == 0 {
thread::yield_now();
}
cf::CFRunLoopStop(runloop);
}
// Wait for the thread to shut down.
thread_handle.join().expect("thread to shut down");
}
}
fn remove_path(&mut self, path: &Path) -> Result<()> {
let str_path = path.to_str().unwrap();
unsafe {
let mut err: cf::CFErrorRef = ptr::null_mut();
let cf_path = cf::str_path_to_cfstring_ref(str_path, &mut err);
if cf_path.is_null() {
cf::CFRelease(err as cf::CFRef);
return Err(Error::watch_not_found().add_path(path.into()));
}
let mut to_remove = Vec::new();
for idx in 0..cf::CFArrayGetCount(self.paths) {
let item = cf::CFArrayGetValueAtIndex(self.paths, idx);
if cf::CFStringCompare(item, cf_path, cf::kCFCompareCaseInsensitive)
== cf::kCFCompareEqualTo
{
to_remove.push(idx);
}
}
cf::CFRelease(cf_path);
for idx in to_remove.iter().rev() {
cf::CFArrayRemoveValueAtIndex(self.paths, *idx);
}
}
let p = if let Ok(canonicalized_path) = path.canonicalize() {
canonicalized_path
} else {
path.to_owned()
};
match self.recursive_info.remove(&p) {
Some(_) => Ok(()),
None => Err(Error::watch_not_found()),
}
}
// https://github.com/thibaudgg/rb-fsevent/blob/master/ext/fsevent_watch/main.c
fn append_path(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
if !path.exists() {
return Err(Error::path_not_found().add_path(path.into()));
}
let canonical_path = path.to_path_buf().canonicalize()?;
let str_path = path.to_str().unwrap();
unsafe {
let mut err: cf::CFErrorRef = ptr::null_mut();
let cf_path = cf::str_path_to_cfstring_ref(str_path, &mut err);
if cf_path.is_null() {
// Most likely the directory was deleted, or permissions changed,
// while the above code was running.
cf::CFRelease(err as cf::CFRef);
return Err(Error::path_not_found().add_path(path.into()));
}
cf::CFArrayAppendValue(self.paths, cf_path);
cf::CFRelease(cf_path);
}
self.recursive_info
.insert(canonical_path, recursive_mode.is_recursive());
Ok(())
}
fn run(&mut self) -> Result<()> {
if unsafe { cf::CFArrayGetCount(self.paths) } == 0 {
// TODO: Reconstruct and add paths to error
return Err(Error::path_not_found());
}
// We need to associate the stream context with our callback in order to propagate events
// to the rest of the system. This will be owned by the stream, and will be freed when the
// stream is closed. This means we will leak the context if we panic before reaching
// `FSEventStreamRelease`.
let context = Box::into_raw(Box::new(StreamContextInfo {
event_handler: self.event_handler.clone(),
recursive_info: self.recursive_info.clone(),
}));
let stream_context = fs::FSEventStreamContext {
version: 0,
info: context as *mut libc::c_void,
retain: None,
release: Some(release_context),
copy_description: None,
};
let stream = unsafe {
fs::FSEventStreamCreate(
cf::kCFAllocatorDefault,
callback,
&stream_context,
self.paths,
self.since_when,
self.latency,
self.flags,
)
};
// Wrapper to help send CFRef types across threads.
struct CFSendWrapper(cf::CFRef);
// Safety:
// - According to the Apple documentation, it's safe to move `CFRef`s across threads.
// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html
unsafe impl Send for CFSendWrapper {}
// move into thread
let stream = CFSendWrapper(stream);
// channel to pass runloop around
let (rl_tx, rl_rx) = unbounded();
let thread_handle = thread::Builder::new()
.name("notify-rs fsevents loop".to_string())
.spawn(move || {
let _ = &stream;
let stream = stream.0;
unsafe {
let cur_runloop = cf::CFRunLoopGetCurrent();
fs::FSEventStreamScheduleWithRunLoop(
stream,
cur_runloop,
cf::kCFRunLoopDefaultMode,
);
fs::FSEventStreamStart(stream);
// the calling to CFRunLoopRun will be terminated by CFRunLoopStop call in drop()
rl_tx
.send(CFSendWrapper(cur_runloop))
.expect("Unable to send runloop to watcher");
cf::CFRunLoopRun();
fs::FSEventStreamStop(stream);
// There are edge-cases, when many events are pending,
// despite the stream being stopped, that the stream's
// associated callback will be invoked. Purging events
// is intended to prevent this.
let event_id = fs::FSEventsGetCurrentEventId();
let device = fs::FSEventStreamGetDeviceBeingWatched(stream);
fs::FSEventsPurgeEventsForDeviceUpToEventId(device, event_id);
fs::FSEventStreamInvalidate(stream);
fs::FSEventStreamRelease(stream);
}
})?;
// block until runloop has been sent
self.runloop = Some((rl_rx.recv().unwrap().0, thread_handle));
Ok(())
}
fn configure_raw_mode(&mut self, _config: Config, tx: Sender<Result<bool>>) {
tx.send(Ok(false))
.expect("configuration channel disconnect");
}
}
extern "C" fn callback(
stream_ref: fs::FSEventStreamRef,
info: *mut libc::c_void,
num_events: libc::size_t, // size_t numEvents
event_paths: *mut libc::c_void, // void *eventPaths
event_flags: *const fs::FSEventStreamEventFlags, // const FSEventStreamEventFlags eventFlags[]
event_ids: *const fs::FSEventStreamEventId, // const FSEventStreamEventId eventIds[]
) {
unsafe {
callback_impl(
stream_ref,
info,
num_events,
event_paths,
event_flags,
event_ids,
)
}
}
unsafe fn callback_impl(
_stream_ref: fs::FSEventStreamRef,
info: *mut libc::c_void,
num_events: libc::size_t, // size_t numEvents
event_paths: *mut libc::c_void, // void *eventPaths
event_flags: *const fs::FSEventStreamEventFlags, // const FSEventStreamEventFlags eventFlags[]
_event_ids: *const fs::FSEventStreamEventId, // const FSEventStreamEventId eventIds[]
) {
let event_paths = event_paths as *const *const libc::c_char;
let info = info as *const StreamContextInfo;
let event_handler = &(*info).event_handler;
for p in 0..num_events {
let path = CStr::from_ptr(*event_paths.add(p))
.to_str()
.expect("Invalid UTF8 string.");
let path = PathBuf::from(path);
let flag = *event_flags.add(p);
let flag = StreamFlags::from_bits(flag).unwrap_or_else(|| {
panic!("Unable to decode StreamFlags: {}", flag);
});
let mut handle_event = false;
for (p, r) in &(*info).recursive_info {
if path.starts_with(p) {
if *r || &path == p {
handle_event = true;
break;
} else if let Some(parent_path) = path.parent() {
if parent_path == p {
handle_event = true;
break;
}
}
}
}
if !handle_event {
continue;
}
log::trace!("FSEvent: path = `{}`, flag = {:?}", path.display(), flag);
for ev in translate_flags(flag, true).into_iter() {
// TODO: precise
let ev = ev.add_path(path.clone());
let mut event_handler = event_handler.lock().expect("lock not to be poisoned");
event_handler.handle_event(Ok(ev));
}
}
}
impl Watcher for FsEventWatcher {
/// Create a new watcher.
fn new<F: EventHandler>(event_handler: F, _config: Config) -> Result<Self> {
Self::from_event_handler(Arc::new(Mutex::new(event_handler)))
}
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
self.watch_inner(path, recursive_mode)
}
fn paths_mut<'me>(&'me mut self) -> Box<dyn PathsMut + 'me> {
Box::new(FsEventPathsMut::new(self))
}
fn unwatch(&mut self, path: &Path) -> Result<()> {
self.unwatch_inner(path)
}
fn configure(&mut self, config: Config) -> Result<bool> {
let (tx, rx) = unbounded();
self.configure_raw_mode(config, tx);
rx.recv()?
}
fn kind() -> crate::WatcherKind {
crate::WatcherKind::Fsevent
}
}
impl Drop for FsEventWatcher {
fn drop(&mut self) {
self.stop();
unsafe {
cf::CFRelease(self.paths);
}
}
}
#[test]
fn test_fsevent_watcher_drop() {
use super::*;
use std::time::Duration;
let dir = tempfile::tempdir().unwrap();
let (tx, rx) = std::sync::mpsc::channel();
{
let mut watcher = FsEventWatcher::new(tx, Default::default()).unwrap();
watcher.watch(dir.path(), RecursiveMode::Recursive).unwrap();
thread::sleep(Duration::from_millis(2000));
println!("is running -> {}", watcher.is_running());
thread::sleep(Duration::from_millis(1000));
watcher.unwatch(dir.path()).unwrap();
println!("is running -> {}", watcher.is_running());
}
thread::sleep(Duration::from_millis(1000));
for res in rx {
let e = res.unwrap();
println!("debug => {:?} {:?}", e.kind, e.paths);
}
println!("in test: {} works", file!());
}
#[test]
fn test_steam_context_info_send_and_sync() {
fn check_send<T: Send + Sync>() {}
check_send::<StreamContextInfo>();
}
+765
View File
@@ -0,0 +1,765 @@
//! Watcher implementation for the inotify Linux API
//!
//! The inotify API provides a mechanism for monitoring filesystem events. Inotify can be used to
//! monitor individual files, or to monitor directories. When a directory is monitored, inotify
//! will return events for the directory itself, and for files inside the directory.
use super::event::*;
use super::{Config, Error, ErrorKind, EventHandler, RecursiveMode, Result, Watcher};
use crate::{bounded, unbounded, BoundSender, Receiver, Sender};
use inotify as inotify_sys;
use inotify_sys::{EventMask, Inotify, WatchDescriptor, WatchMask};
use std::collections::HashMap;
use std::env;
use std::ffi::OsStr;
use std::fs::metadata;
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::thread;
use walkdir::WalkDir;
const INOTIFY: mio::Token = mio::Token(0);
const MESSAGE: mio::Token = mio::Token(1);
// The EventLoop will set up a mio::Poll and use it to wait for the following:
//
// - messages telling it what to do
//
// - events telling it that something has happened on one of the watched files.
struct EventLoop {
running: bool,
poll: mio::Poll,
event_loop_waker: Arc<mio::Waker>,
event_loop_tx: Sender<EventLoopMsg>,
event_loop_rx: Receiver<EventLoopMsg>,
inotify: Option<Inotify>,
event_handler: Box<dyn EventHandler>,
/// PathBuf -> (WatchDescriptor, WatchMask, is_recursive, is_dir)
watches: HashMap<PathBuf, (WatchDescriptor, WatchMask, bool, bool)>,
paths: HashMap<WatchDescriptor, PathBuf>,
rename_event: Option<Event>,
follow_links: bool,
}
/// Watcher implementation based on inotify
#[derive(Debug)]
pub struct INotifyWatcher {
channel: Sender<EventLoopMsg>,
waker: Arc<mio::Waker>,
}
enum EventLoopMsg {
AddWatch(PathBuf, RecursiveMode, Sender<Result<()>>),
RemoveWatch(PathBuf, Sender<Result<()>>),
Shutdown,
Configure(Config, BoundSender<Result<bool>>),
}
#[inline]
fn add_watch_by_event(
path: &PathBuf,
event: &inotify_sys::Event<&OsStr>,
watches: &HashMap<PathBuf, (WatchDescriptor, WatchMask, bool, bool)>,
add_watches: &mut Vec<PathBuf>,
) {
if event.mask.contains(EventMask::ISDIR) {
if let Some(parent_path) = path.parent() {
if let Some(&(_, _, is_recursive, _)) = watches.get(parent_path) {
if is_recursive {
add_watches.push(path.to_owned());
}
}
}
}
}
#[inline]
fn remove_watch_by_event(
path: &PathBuf,
watches: &HashMap<PathBuf, (WatchDescriptor, WatchMask, bool, bool)>,
remove_watches: &mut Vec<PathBuf>,
) {
if watches.contains_key(path) {
remove_watches.push(path.to_owned());
}
}
impl EventLoop {
pub fn new(
inotify: Inotify,
event_handler: Box<dyn EventHandler>,
follow_links: bool,
) -> Result<Self> {
let (event_loop_tx, event_loop_rx) = unbounded::<EventLoopMsg>();
let poll = mio::Poll::new()?;
let event_loop_waker = Arc::new(mio::Waker::new(poll.registry(), MESSAGE)?);
let inotify_fd = inotify.as_raw_fd();
let mut evented_inotify = mio::unix::SourceFd(&inotify_fd);
poll.registry()
.register(&mut evented_inotify, INOTIFY, mio::Interest::READABLE)?;
let event_loop = EventLoop {
running: true,
poll,
event_loop_waker,
event_loop_tx,
event_loop_rx,
inotify: Some(inotify),
event_handler,
watches: HashMap::new(),
paths: HashMap::new(),
rename_event: None,
follow_links,
};
Ok(event_loop)
}
// Run the event loop.
pub fn run(self) {
let _ = thread::Builder::new()
.name("notify-rs inotify loop".to_string())
.spawn(|| self.event_loop_thread());
}
fn event_loop_thread(mut self) {
let mut events = mio::Events::with_capacity(16);
loop {
// Wait for something to happen.
match self.poll.poll(&mut events, None) {
Err(ref e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => {
// System call was interrupted, we will retry
// TODO: Not covered by tests (to reproduce likely need to setup signal handlers)
}
Err(e) => panic!("poll failed: {}", e),
Ok(()) => {}
}
// Process whatever happened.
for event in &events {
self.handle_event(event);
}
// Stop, if we're done.
if !self.running {
break;
}
}
}
// Handle a single event.
fn handle_event(&mut self, event: &mio::event::Event) {
match event.token() {
MESSAGE => {
// The channel is readable - handle messages.
self.handle_messages()
}
INOTIFY => {
// inotify has something to tell us.
self.handle_inotify()
}
_ => unreachable!(),
}
}
fn handle_messages(&mut self) {
while let Ok(msg) = self.event_loop_rx.try_recv() {
match msg {
EventLoopMsg::AddWatch(path, recursive_mode, tx) => {
let _ = tx.send(self.add_watch(path, recursive_mode.is_recursive(), true));
}
EventLoopMsg::RemoveWatch(path, tx) => {
let _ = tx.send(self.remove_watch(path, false));
}
EventLoopMsg::Shutdown => {
let _ = self.remove_all_watches();
if let Some(inotify) = self.inotify.take() {
let _ = inotify.close();
}
self.running = false;
break;
}
EventLoopMsg::Configure(config, tx) => {
self.configure_raw_mode(config, tx);
}
}
}
}
fn configure_raw_mode(&mut self, _config: Config, tx: BoundSender<Result<bool>>) {
tx.send(Ok(false))
.expect("configuration channel disconnected");
}
fn handle_inotify(&mut self) {
let mut add_watches = Vec::new();
let mut remove_watches = Vec::new();
if let Some(ref mut inotify) = self.inotify {
let mut buffer = [0; 1024];
// Read all buffers available.
loop {
match inotify.read_events(&mut buffer) {
Ok(events) => {
let mut num_events = 0;
for event in events {
log::trace!("inotify event: {event:?}");
num_events += 1;
if event.mask.contains(EventMask::Q_OVERFLOW) {
let ev = Ok(Event::new(EventKind::Other).set_flag(Flag::Rescan));
self.event_handler.handle_event(ev);
}
let path = match event.name {
Some(name) => self.paths.get(&event.wd).map(|root| root.join(name)),
None => self.paths.get(&event.wd).cloned(),
};
let path = match path {
Some(path) => path,
None => {
log::debug!("inotify event with unknown descriptor: {event:?}");
continue;
}
};
let mut evs = Vec::new();
if event.mask.contains(EventMask::MOVED_FROM) {
remove_watch_by_event(&path, &self.watches, &mut remove_watches);
let event = Event::new(EventKind::Modify(ModifyKind::Name(
RenameMode::From,
)))
.add_path(path.clone())
.set_tracker(event.cookie as usize);
self.rename_event = Some(event.clone());
evs.push(event);
} else if event.mask.contains(EventMask::MOVED_TO) {
evs.push(
Event::new(EventKind::Modify(ModifyKind::Name(RenameMode::To)))
.set_tracker(event.cookie as usize)
.add_path(path.clone()),
);
let trackers_match =
self.rename_event.as_ref().and_then(|e| e.tracker())
== Some(event.cookie as usize);
if trackers_match {
let rename_event = self.rename_event.take().unwrap(); // unwrap is safe because `rename_event` must be set at this point
evs.push(
Event::new(EventKind::Modify(ModifyKind::Name(
RenameMode::Both,
)))
.set_tracker(event.cookie as usize)
.add_some_path(rename_event.paths.first().cloned())
.add_path(path.clone()),
);
}
add_watch_by_event(&path, &event, &self.watches, &mut add_watches);
}
if event.mask.contains(EventMask::MOVE_SELF) {
evs.push(
Event::new(EventKind::Modify(ModifyKind::Name(
RenameMode::From,
)))
.add_path(path.clone()),
);
// TODO stat the path and get to new path
// - emit To and Both events
// - change prefix for further events
}
if event.mask.contains(EventMask::CREATE) {
evs.push(
Event::new(EventKind::Create(
if event.mask.contains(EventMask::ISDIR) {
CreateKind::Folder
} else {
CreateKind::File
},
))
.add_path(path.clone()),
);
add_watch_by_event(&path, &event, &self.watches, &mut add_watches);
}
if event.mask.contains(EventMask::DELETE) {
evs.push(
Event::new(EventKind::Remove(
if event.mask.contains(EventMask::ISDIR) {
RemoveKind::Folder
} else {
RemoveKind::File
},
))
.add_path(path.clone()),
);
remove_watch_by_event(&path, &self.watches, &mut remove_watches);
}
if event.mask.contains(EventMask::DELETE_SELF) {
let remove_kind = match self.watches.get(&path) {
Some(&(_, _, _, true)) => RemoveKind::Folder,
Some(&(_, _, _, false)) => RemoveKind::File,
None => RemoveKind::Other,
};
evs.push(
Event::new(EventKind::Remove(remove_kind))
.add_path(path.clone()),
);
remove_watch_by_event(&path, &self.watches, &mut remove_watches);
}
if event.mask.contains(EventMask::MODIFY) {
evs.push(
Event::new(EventKind::Modify(ModifyKind::Data(
DataChange::Any,
)))
.add_path(path.clone()),
);
}
if event.mask.contains(EventMask::CLOSE_WRITE) {
evs.push(
Event::new(EventKind::Access(AccessKind::Close(
AccessMode::Write,
)))
.add_path(path.clone()),
);
}
if event.mask.contains(EventMask::CLOSE_NOWRITE) {
evs.push(
Event::new(EventKind::Access(AccessKind::Close(
AccessMode::Read,
)))
.add_path(path.clone()),
);
}
if event.mask.contains(EventMask::ATTRIB) {
evs.push(
Event::new(EventKind::Modify(ModifyKind::Metadata(
MetadataKind::Any,
)))
.add_path(path.clone()),
);
}
if event.mask.contains(EventMask::OPEN) {
evs.push(
Event::new(EventKind::Access(AccessKind::Open(
AccessMode::Any,
)))
.add_path(path.clone()),
);
}
for ev in evs {
self.event_handler.handle_event(Ok(ev));
}
}
// All events read. Break out.
if num_events == 0 {
break;
}
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
// No events read. Break out.
break;
}
Err(e) => {
self.event_handler.handle_event(Err(Error::io(e)));
}
}
}
}
for path in remove_watches {
self.remove_watch(path, true).ok();
}
for path in add_watches {
if let Err(add_watch_error) = self.add_watch(path, true, false) {
// The handler should be notified if we have reached the limit.
// Otherwise, the user might expect that a recursive watch
// is continuing to work correctly, but it's not.
if let ErrorKind::MaxFilesWatch = add_watch_error.kind {
self.event_handler.handle_event(Err(add_watch_error));
// After that kind of a error we should stop adding watches,
// because the limit has already reached and all next calls
// will return us only the same error.
break;
}
}
}
}
fn add_watch(&mut self, path: PathBuf, is_recursive: bool, mut watch_self: bool) -> Result<()> {
// If the watch is not recursive, or if we determine (by stat'ing the path to get its
// metadata) that the watched path is not a directory, add a single path watch.
if !is_recursive || !metadata(&path).map_err(Error::io_watch)?.is_dir() {
return self.add_single_watch(path, false, true);
}
for entry in WalkDir::new(path)
.follow_links(self.follow_links)
.into_iter()
.filter_map(filter_dir)
{
self.add_single_watch(entry.into_path(), is_recursive, watch_self)?;
watch_self = false;
}
Ok(())
}
fn add_single_watch(
&mut self,
path: PathBuf,
is_recursive: bool,
watch_self: bool,
) -> Result<()> {
let mut watchmask = WatchMask::ATTRIB
| WatchMask::CREATE
| WatchMask::OPEN
| WatchMask::DELETE
| WatchMask::CLOSE_WRITE
| WatchMask::MODIFY
| WatchMask::MOVED_FROM
| WatchMask::MOVED_TO;
if watch_self {
watchmask.insert(WatchMask::DELETE_SELF);
watchmask.insert(WatchMask::MOVE_SELF);
}
if let Some(&(_, old_watchmask, _, _)) = self.watches.get(&path) {
watchmask.insert(old_watchmask);
watchmask.insert(WatchMask::MASK_ADD);
}
if let Some(ref mut inotify) = self.inotify {
log::trace!("adding inotify watch: {}", path.display());
match inotify.watches().add(&path, watchmask) {
Err(e) => {
Err(if e.raw_os_error() == Some(libc::ENOSPC) {
// do not report inotify limits as "no more space" on linux #266
Error::new(ErrorKind::MaxFilesWatch)
} else if e.kind() == std::io::ErrorKind::NotFound {
Error::new(ErrorKind::PathNotFound)
} else {
Error::io(e)
}
.add_path(path))
}
Ok(w) => {
watchmask.remove(WatchMask::MASK_ADD);
let is_dir = metadata(&path).map_err(Error::io)?.is_dir();
self.watches
.insert(path.clone(), (w.clone(), watchmask, is_recursive, is_dir));
self.paths.insert(w, path);
Ok(())
}
}
} else {
Ok(())
}
}
fn remove_watch(&mut self, path: PathBuf, remove_recursive: bool) -> Result<()> {
match self.watches.remove(&path) {
None => return Err(Error::watch_not_found().add_path(path)),
Some((w, _, is_recursive, _)) => {
if let Some(ref mut inotify) = self.inotify {
let mut inotify_watches = inotify.watches();
log::trace!("removing inotify watch: {}", path.display());
inotify_watches
.remove(w.clone())
.map_err(|e| Error::io(e).add_path(path.clone()))?;
self.paths.remove(&w);
if is_recursive || remove_recursive {
let mut remove_list = Vec::new();
for (w, p) in &self.paths {
if p.starts_with(&path) {
inotify_watches
.remove(w.clone())
.map_err(|e| Error::io(e).add_path(p.into()))?;
self.watches.remove(p);
remove_list.push(w.clone());
}
}
for w in remove_list {
self.paths.remove(&w);
}
}
}
}
}
Ok(())
}
fn remove_all_watches(&mut self) -> Result<()> {
if let Some(ref mut inotify) = self.inotify {
let mut inotify_watches = inotify.watches();
for (w, p) in &self.paths {
inotify_watches
.remove(w.clone())
.map_err(|e| Error::io(e).add_path(p.into()))?;
}
self.watches.clear();
self.paths.clear();
}
Ok(())
}
}
/// return `DirEntry` when it is a directory
fn filter_dir(e: walkdir::Result<walkdir::DirEntry>) -> Option<walkdir::DirEntry> {
if let Ok(e) = e {
if let Ok(metadata) = e.metadata() {
if metadata.is_dir() {
return Some(e);
}
}
}
None
}
impl INotifyWatcher {
fn from_event_handler(
event_handler: Box<dyn EventHandler>,
follow_links: bool,
) -> Result<Self> {
let inotify = Inotify::init()?;
let event_loop = EventLoop::new(inotify, event_handler, follow_links)?;
let channel = event_loop.event_loop_tx.clone();
let waker = event_loop.event_loop_waker.clone();
event_loop.run();
Ok(INotifyWatcher { channel, waker })
}
fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
let pb = if path.is_absolute() {
path.to_owned()
} else {
let p = env::current_dir().map_err(Error::io)?;
p.join(path)
};
let (tx, rx) = unbounded();
let msg = EventLoopMsg::AddWatch(pb, recursive_mode, tx);
// we expect the event loop to live and reply => unwraps must not panic
self.channel.send(msg).unwrap();
self.waker.wake().unwrap();
rx.recv().unwrap()
}
fn unwatch_inner(&mut self, path: &Path) -> Result<()> {
let pb = if path.is_absolute() {
path.to_owned()
} else {
let p = env::current_dir().map_err(Error::io)?;
p.join(path)
};
let (tx, rx) = unbounded();
let msg = EventLoopMsg::RemoveWatch(pb, tx);
// we expect the event loop to live and reply => unwraps must not panic
self.channel.send(msg).unwrap();
self.waker.wake().unwrap();
rx.recv().unwrap()
}
}
impl Watcher for INotifyWatcher {
/// Create a new watcher.
fn new<F: EventHandler>(event_handler: F, config: Config) -> Result<Self> {
Self::from_event_handler(Box::new(event_handler), config.follow_symlinks())
}
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
self.watch_inner(path, recursive_mode)
}
fn unwatch(&mut self, path: &Path) -> Result<()> {
self.unwatch_inner(path)
}
fn configure(&mut self, config: Config) -> Result<bool> {
let (tx, rx) = bounded(1);
self.channel.send(EventLoopMsg::Configure(config, tx))?;
self.waker.wake()?;
rx.recv()?
}
fn kind() -> crate::WatcherKind {
crate::WatcherKind::Inotify
}
}
impl Drop for INotifyWatcher {
fn drop(&mut self) {
// we expect the event loop to live => unwrap must not panic
self.channel.send(EventLoopMsg::Shutdown).unwrap();
self.waker.wake().unwrap();
}
}
#[cfg(test)]
mod tests {
use std::{
sync::{atomic::AtomicBool, mpsc},
thread::available_parallelism,
};
use super::*;
#[test]
fn inotify_watcher_is_send_and_sync() {
fn check<T: Send + Sync>() {}
check::<INotifyWatcher>();
}
#[test]
fn native_error_type_on_missing_path() {
let mut watcher = INotifyWatcher::new(|_| {}, Config::default()).unwrap();
let result = watcher.watch(
&PathBuf::from("/some/non/existant/path"),
RecursiveMode::NonRecursive,
);
assert!(matches!(
result,
Err(Error {
paths: _,
kind: ErrorKind::PathNotFound
})
))
}
/// Runs manually.
///
/// * Save actual value of the limit: `MAX_USER_WATCHES=$(sysctl -n fs.inotify.max_user_watches)`
/// * Run the test.
/// * Set the limit to 0: `sudo sysctl fs.inotify.max_user_watches=0` while test is running
/// * Wait for the test to complete
/// * Restore the limit `sudo sysctl fs.inotify.max_user_watches=$MAX_USER_WATCHES`
#[test]
#[ignore = "requires changing sysctl fs.inotify.max_user_watches while test is running"]
fn recursive_watch_calls_handler_if_creating_a_file_raises_max_files_watch() {
use std::time::Duration;
let tmpdir = tempfile::tempdir().unwrap();
let (tx, rx) = std::sync::mpsc::channel();
let (proc_changed_tx, proc_changed_rx) = std::sync::mpsc::channel();
let proc_path = Path::new("/proc/sys/fs/inotify/max_user_watches");
let mut watcher = INotifyWatcher::new(
move |result: Result<Event>| match result {
Ok(event) => {
if event.paths.first().is_some_and(|path| path == proc_path) {
proc_changed_tx.send(()).unwrap();
}
}
Err(e) => tx.send(e).unwrap(),
},
Config::default(),
)
.unwrap();
watcher
.watch(tmpdir.path(), RecursiveMode::Recursive)
.unwrap();
watcher
.watch(proc_path, RecursiveMode::NonRecursive)
.unwrap();
// give the time to set the limit
proc_changed_rx
.recv_timeout(Duration::from_secs(30))
.unwrap();
let child_dir = tmpdir.path().join("child");
std::fs::create_dir(child_dir).unwrap();
let result = rx.recv_timeout(Duration::from_millis(500));
assert!(
matches!(
&result,
Ok(Error {
kind: ErrorKind::MaxFilesWatch,
paths: _,
})
),
"expected {:?}, found: {:#?}",
ErrorKind::MaxFilesWatch,
result
);
}
/// https://github.com/notify-rs/notify/issues/678
#[test]
fn race_condition_on_unwatch_and_pending_events_with_deleted_descriptor() {
let tmpdir = tempfile::tempdir().expect("tmpdir");
let (tx, rx) = mpsc::channel();
let mut inotify = INotifyWatcher::new(
move |e: Result<Event>| {
let e = match e {
Ok(e) if e.paths.is_empty() => e,
Ok(_) | Err(_) => return,
};
let _ = tx.send(e);
},
Config::default(),
)
.expect("inotify creation");
let dir_path = tmpdir.path();
let file_path = dir_path.join("foo");
std::fs::File::create(&file_path).unwrap();
let stop = Arc::new(AtomicBool::new(false));
let handles: Vec<_> = (0..available_parallelism().unwrap().get().max(4))
.map(|_| {
let file_path = file_path.clone();
let stop = stop.clone();
thread::spawn(move || {
while !stop.load(std::sync::atomic::Ordering::Relaxed) {
let _ = std::fs::File::open(&file_path).unwrap();
}
})
})
.collect();
let non_recursive = RecursiveMode::NonRecursive;
for _ in 0..(handles.len() * 4) {
inotify.watch(dir_path, non_recursive).unwrap();
inotify.unwatch(dir_path).unwrap();
}
stop.store(true, std::sync::atomic::Ordering::Relaxed);
handles
.into_iter()
.for_each(|handle| handle.join().ok().unwrap_or_default());
drop(inotify);
let events: Vec<_> = rx.into_iter().map(|e| format!("{e:?}")).collect();
const LOG_LEN: usize = 10;
let events_len = events.len();
assert!(
events.is_empty(),
"expected no events without path, but got {events_len}. first 10: {:#?}",
&events[..LOG_LEN.min(events_len)]
);
}
}
+481
View File
@@ -0,0 +1,481 @@
//! Watcher implementation for the kqueue API
//!
//! The kqueue() system call provides a generic method of notifying the user
//! when an event happens or a condition holds, based on the results of small
//! pieces of kernel code termed filters.
use super::event::*;
use super::{Config, Error, EventHandler, RecursiveMode, Result, Watcher};
use crate::{unbounded, Receiver, Sender};
use kqueue::{EventData, EventFilter, FilterFlag, Ident};
use std::collections::HashMap;
use std::env;
use std::fs::metadata;
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::thread;
use walkdir::WalkDir;
const KQUEUE: mio::Token = mio::Token(0);
const MESSAGE: mio::Token = mio::Token(1);
// The EventLoop will set up a mio::Poll and use it to wait for the following:
//
// - messages telling it what to do
//
// - events telling it that something has happened on one of the watched files.
struct EventLoop {
running: bool,
poll: mio::Poll,
event_loop_waker: Arc<mio::Waker>,
event_loop_tx: Sender<EventLoopMsg>,
event_loop_rx: Receiver<EventLoopMsg>,
kqueue: kqueue::Watcher,
event_handler: Box<dyn EventHandler>,
watches: HashMap<PathBuf, bool>,
follow_symlinks: bool,
}
/// Watcher implementation based on inotify
#[derive(Debug)]
pub struct KqueueWatcher {
channel: Sender<EventLoopMsg>,
waker: Arc<mio::Waker>,
}
enum EventLoopMsg {
AddWatch(PathBuf, RecursiveMode, Sender<Result<()>>),
RemoveWatch(PathBuf, Sender<Result<()>>),
Shutdown,
}
impl EventLoop {
pub fn new(
kqueue: kqueue::Watcher,
event_handler: Box<dyn EventHandler>,
follow_symlinks: bool,
) -> Result<Self> {
let (event_loop_tx, event_loop_rx) = unbounded::<EventLoopMsg>();
let poll = mio::Poll::new()?;
let event_loop_waker = Arc::new(mio::Waker::new(poll.registry(), MESSAGE)?);
let kqueue_fd = kqueue.as_raw_fd();
let mut evented_kqueue = mio::unix::SourceFd(&kqueue_fd);
poll.registry()
.register(&mut evented_kqueue, KQUEUE, mio::Interest::READABLE)?;
let event_loop = EventLoop {
running: true,
poll,
event_loop_waker,
event_loop_tx,
event_loop_rx,
kqueue,
event_handler,
watches: HashMap::new(),
follow_symlinks,
};
Ok(event_loop)
}
// Run the event loop.
pub fn run(self) {
let _ = thread::Builder::new()
.name("notify-rs kqueue loop".to_string())
.spawn(|| self.event_loop_thread());
}
fn event_loop_thread(mut self) {
let mut events = mio::Events::with_capacity(16);
loop {
// Wait for something to happen.
match self.poll.poll(&mut events, None) {
Err(ref e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => {
// System call was interrupted, we will retry
// TODO: Not covered by tests (to reproduce likely need to setup signal handlers)
}
Err(e) => panic!("poll failed: {}", e),
Ok(()) => {}
}
// Process whatever happened.
for event in &events {
self.handle_event(event);
}
// Stop, if we're done.
if !self.running {
break;
}
}
}
// Handle a single event.
fn handle_event(&mut self, event: &mio::event::Event) {
match event.token() {
MESSAGE => {
// The channel is readable - handle messages.
self.handle_messages()
}
KQUEUE => {
// inotify has something to tell us.
self.handle_kqueue()
}
_ => unreachable!(),
}
}
fn handle_messages(&mut self) {
while let Ok(msg) = self.event_loop_rx.try_recv() {
match msg {
EventLoopMsg::AddWatch(path, recursive_mode, tx) => {
let _ = tx.send(self.add_watch(path, recursive_mode.is_recursive()));
}
EventLoopMsg::RemoveWatch(path, tx) => {
let _ = tx.send(self.remove_watch(path, false));
}
EventLoopMsg::Shutdown => {
self.running = false;
break;
}
}
}
}
fn handle_kqueue(&mut self) {
let mut add_watches = Vec::new();
let mut remove_watches = Vec::new();
while let Some(event) = self.kqueue.poll(None) {
log::trace!("kqueue event: {event:?}");
match event {
kqueue::Event {
data: EventData::Vnode(data),
ident: Ident::Filename(_, path),
} => {
let path = PathBuf::from(path);
let event = match data {
/*
TODO: Differentiate folders and files
kqueue doesn't tell us if this was a file or a dir, so we
could only emulate this inotify behavior if we keep track of
all files and directories internally and then perform a
lookup.
*/
kqueue::Vnode::Delete => {
remove_watches.push(path.clone());
Ok(Event::new(EventKind::Remove(RemoveKind::Any)).add_path(path))
}
// a write to a directory means that a new file was created in it, let's
// figure out which file this was
kqueue::Vnode::Write if path.is_dir() => {
// find which file is new in the directory by comparing it with our
// list of known watches
std::fs::read_dir(&path)
.map(|dir| {
dir.filter_map(std::result::Result::ok)
.map(|f| f.path())
.find(|f| !self.watches.contains_key(f))
})
.map(|file| {
if let Some(file) = file {
// watch this new file
add_watches.push(file.clone());
Event::new(EventKind::Create(if file.is_dir() {
CreateKind::Folder
} else if file.is_file() {
CreateKind::File
} else {
CreateKind::Other
}))
.add_path(file)
} else {
Event::new(EventKind::Modify(ModifyKind::Data(
DataChange::Any,
)))
.add_path(path)
}
})
.map_err(Into::into)
}
// data was written to this file
kqueue::Vnode::Write => Ok(Event::new(EventKind::Modify(
ModifyKind::Data(DataChange::Any),
))
.add_path(path)),
/*
Extend and Truncate are just different names for the same
operation, extend is only used on FreeBSD, truncate everywhere
else
*/
kqueue::Vnode::Extend | kqueue::Vnode::Truncate => Ok(Event::new(
EventKind::Modify(ModifyKind::Data(DataChange::Size)),
)
.add_path(path)),
/*
this kevent has the same problem as the delete kevent. The
only way i can think of providing "better" event with more
information is to do the diff our self, while this maybe do
able of delete. In this case it would somewhat expensive to
keep track and compare ever peace of metadata for every file
*/
kqueue::Vnode::Attrib => Ok(Event::new(EventKind::Modify(
ModifyKind::Metadata(MetadataKind::Any),
))
.add_path(path)),
/*
The link count on a file changed => subdirectory created or
delete.
*/
kqueue::Vnode::Link => {
// As we currently don't have a solution that would allow us
// to only add/remove the new/delete directory and that dosn't include a
// possible race condition. On possible solution would be to
// create a `HashMap<PathBuf, Vec<PathBuf>>` which would
// include every directory and this content add the time of
// adding it to kqueue. While this should allow us to do the
// diff and only add/remove the files necessary. This would
// also introduce a race condition, where multiple files could
// all ready be remove from the directory, and we could get out
// of sync.
// So for now, until we find a better solution, let remove and
// readd the whole directory.
// This is a expensive operation, as we recursive through all
// subdirectories.
remove_watches.push(path.clone());
add_watches.push(path.clone());
Ok(Event::new(EventKind::Modify(ModifyKind::Any)).add_path(path))
}
// Kqueue not provide us with the information necessary to provide
// the new file name to the event.
kqueue::Vnode::Rename => {
remove_watches.push(path.clone());
Ok(
Event::new(EventKind::Modify(ModifyKind::Name(RenameMode::Any)))
.add_path(path),
)
}
// Access to the file was revoked via revoke(2) or the underlying file system was unmounted.
kqueue::Vnode::Revoke => {
remove_watches.push(path.clone());
Ok(Event::new(EventKind::Remove(RemoveKind::Any)).add_path(path))
}
// On different BSD variants, different extra events may be present
#[allow(unreachable_patterns)]
_ => Ok(Event::new(EventKind::Other)),
};
self.event_handler.handle_event(event);
}
// as we don't add any other EVFILTER to kqueue we should never get here
kqueue::Event { ident: _, data: _ } => unreachable!(),
}
}
for path in remove_watches {
self.remove_watch(path, true).ok();
}
for path in add_watches {
self.add_watch(path, true).ok();
}
}
fn add_watch(&mut self, path: PathBuf, is_recursive: bool) -> Result<()> {
// If the watch is not recursive, or if we determine (by stat'ing the path to get its
// metadata) that the watched path is not a directory, add a single path watch.
if !is_recursive || !metadata(&path).map_err(Error::io)?.is_dir() {
self.add_single_watch(path, false)?;
} else {
for entry in WalkDir::new(path)
.follow_links(self.follow_symlinks)
.into_iter()
{
let entry = entry.map_err(map_walkdir_error)?;
self.add_single_watch(entry.into_path(), is_recursive)?;
}
}
// Only make a single `kevent` syscall to add all the watches.
self.kqueue.watch()?;
Ok(())
}
/// Adds a single watch to the kqueue.
///
/// The caller of this function must call `self.kqueue.watch()` afterwards to register the new watch.
fn add_single_watch(&mut self, path: PathBuf, is_recursive: bool) -> Result<()> {
let event_filter = EventFilter::EVFILT_VNODE;
let filter_flags = FilterFlag::NOTE_DELETE
| FilterFlag::NOTE_WRITE
| FilterFlag::NOTE_EXTEND
| FilterFlag::NOTE_ATTRIB
| FilterFlag::NOTE_LINK
| FilterFlag::NOTE_RENAME
| FilterFlag::NOTE_REVOKE;
log::trace!("adding kqueue watch: {}", path.display());
self.kqueue
.add_filename(&path, event_filter, filter_flags)
.map_err(|e| Error::io(e).add_path(path.clone()))?;
self.watches.insert(path, is_recursive);
Ok(())
}
fn remove_watch(&mut self, path: PathBuf, remove_recursive: bool) -> Result<()> {
log::trace!("removing kqueue watch: {}", path.display());
match self.watches.remove(&path) {
None => return Err(Error::watch_not_found()),
Some(is_recursive) => {
if is_recursive || remove_recursive {
for entry in WalkDir::new(path)
.follow_links(self.follow_symlinks)
.into_iter()
{
let p = entry.map_err(map_walkdir_error)?.into_path();
self.kqueue
.remove_filename(&p, EventFilter::EVFILT_VNODE)
.map_err(|e| Error::io(e).add_path(p))?;
}
} else {
self.kqueue
.remove_filename(&path, EventFilter::EVFILT_VNODE)
.map_err(|e| Error::io(e).add_path(path.clone()))?;
}
self.kqueue.watch()?;
}
}
Ok(())
}
}
fn map_walkdir_error(e: walkdir::Error) -> Error {
if e.io_error().is_some() {
// save to unwrap otherwise we whouldn't be in this branch
Error::io(e.into_io_error().unwrap())
} else {
Error::generic(&e.to_string())
}
}
impl KqueueWatcher {
fn from_event_handler(
event_handler: Box<dyn EventHandler>,
follow_symlinks: bool,
) -> Result<Self> {
let kqueue = kqueue::Watcher::new()?;
let event_loop = EventLoop::new(kqueue, event_handler, follow_symlinks)?;
let channel = event_loop.event_loop_tx.clone();
let waker = event_loop.event_loop_waker.clone();
event_loop.run();
Ok(KqueueWatcher { channel, waker })
}
fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
let pb = if path.is_absolute() {
path.to_owned()
} else {
let p = env::current_dir().map_err(Error::io)?;
p.join(path)
};
let (tx, rx) = unbounded();
let msg = EventLoopMsg::AddWatch(pb, recursive_mode, tx);
self.channel
.send(msg)
.map_err(|e| Error::generic(&e.to_string()))?;
self.waker
.wake()
.map_err(|e| Error::generic(&e.to_string()))?;
rx.recv()
.unwrap()
.map_err(|e| Error::generic(&e.to_string()))
}
fn unwatch_inner(&mut self, path: &Path) -> Result<()> {
let pb = if path.is_absolute() {
path.to_owned()
} else {
let p = env::current_dir().map_err(Error::io)?;
p.join(path)
};
let (tx, rx) = unbounded();
let msg = EventLoopMsg::RemoveWatch(pb, tx);
self.channel
.send(msg)
.map_err(|e| Error::generic(&e.to_string()))?;
self.waker
.wake()
.map_err(|e| Error::generic(&e.to_string()))?;
rx.recv()
.unwrap()
.map_err(|e| Error::generic(&e.to_string()))
}
}
impl Watcher for KqueueWatcher {
/// Create a new watcher.
fn new<F: EventHandler>(event_handler: F, config: Config) -> Result<Self> {
Self::from_event_handler(Box::new(event_handler), config.follow_symlinks())
}
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
self.watch_inner(path, recursive_mode)
}
fn unwatch(&mut self, path: &Path) -> Result<()> {
self.unwatch_inner(path)
}
fn kind() -> crate::WatcherKind {
crate::WatcherKind::Kqueue
}
}
impl Drop for KqueueWatcher {
fn drop(&mut self) {
// we expect the event loop to live => unwrap must not panic
self.channel.send(EventLoopMsg::Shutdown).unwrap();
self.waker.wake().unwrap();
}
}
mod tests {
use super::{Config, KqueueWatcher, RecursiveMode};
use crate::Watcher;
use std::error::Error;
use std::path::PathBuf;
use std::result::Result;
#[test]
fn test_remove_recursive() -> Result<(), Box<dyn Error>> {
let path = PathBuf::from("src");
let mut watcher = KqueueWatcher::new(|event| println!("{:?}", event), Config::default())?;
watcher.watch(&path, RecursiveMode::Recursive)?;
let result = watcher.unwatch(&path);
assert!(
result.is_ok(),
"unwatch yielded error: {}",
result.unwrap_err()
);
Ok(())
}
}
+615
View File
@@ -0,0 +1,615 @@
//! Cross-platform file system notification library
//!
//! # Installation
//!
//! ```toml
//! [dependencies]
//! notify = "8.1.0"
//! ```
//!
//! If you want debounced events (or don't need them in-order), see [notify-debouncer-mini](https://docs.rs/notify-debouncer-mini/latest/notify_debouncer_mini/)
//! or [notify-debouncer-full](https://docs.rs/notify-debouncer-full/latest/notify_debouncer_full/).
//!
//! ## Features
//!
//! List of compilation features, see below for details
//!
//! - `serde` for serialization of events
//! - `macos_fsevent` enabled by default, for fsevent backend on macos
//! - `macos_kqueue` for kqueue backend on macos
//! - `serialization-compat-6` restores the serialization behavior of notify 6, off by default
//!
//! ### Serde
//!
//! Events are serializable via [serde](https://serde.rs) if the `serde` feature is enabled:
//!
//! ```toml
//! notify = { version = "8.1.0", features = ["serde"] }
//! ```
//!
//! # Known Problems
//!
//! ### Network filesystems
//!
//! Network mounted filesystems like NFS may not emit any events for notify to listen to.
//! This applies especially to WSL programs watching windows paths ([issue #254](https://github.com/notify-rs/notify/issues/254)).
//!
//! A workaround is the [`PollWatcher`] backend.
//!
//! ### Docker with Linux on macOS M1
//!
//! Docker on macOS M1 [throws](https://github.com/notify-rs/notify/issues/423) `Function not implemented (os error 38)`.
//! You have to manually use the [`PollWatcher`], as the native backend isn't available inside the emulation.
//!
//! ### macOS, FSEvents and unowned files
//!
//! Due to the inner security model of FSEvents (see [FileSystemEventSecurity](https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/FSEvents_ProgGuide/FileSystemEventSecurity/FileSystemEventSecurity.html)),
//! some events cannot be observed easily when trying to follow files that do not
//! belong to you. In this case, reverting to the pollwatcher can fix the issue,
//! with a slight performance cost.
//!
//! ### Editor Behaviour
//!
//! If you rely on precise events (Write/Delete/Create..), you will notice that the actual events
//! can differ a lot between file editors. Some truncate the file on save, some create a new one and replace the old one.
//! See also [this](https://github.com/notify-rs/notify/issues/247) and [this](https://github.com/notify-rs/notify/issues/113#issuecomment-281836995) issues for example.
//!
//! ### Parent folder deletion
//!
//! If you want to receive an event for a deletion of folder `b` for the path `/a/b/..`, you will have to watch its parent `/a`.
//! See [here](https://github.com/notify-rs/notify/issues/403) for more details.
//!
//! ### Pseudo Filesystems like /proc, /sys
//!
//! Some filesystems like `/proc` and `/sys` on *nix do not emit change events or use correct file change dates.
//! To circumvent that problem you can use the [`PollWatcher`] with the `compare_contents` option.
//!
//! ### Linux: Bad File Descriptor / No space left on device
//!
//! This may be the case of running into the max-files watched limits of your user or system.
//! (Files also includes folders.) Note that for recursive watched folders each file and folder inside counts towards the limit.
//!
//! You may increase this limit in linux via
//! ```sh
//! sudo sysctl fs.inotify.max_user_instances=8192 # example number
//! sudo sysctl fs.inotify.max_user_watches=524288 # example number
//! sudo sysctl -p
//! ```
//!
//! Note that the [`PollWatcher`] is not restricted by this limitation, so it may be an alternative if your users can't increase the limit.
//!
//! ### Watching large directories
//!
//! When watching a very large amount of files, notify may fail to receive all events.
//! For example the linux backend is documented to not be a 100% reliable source. See also issue [#412](https://github.com/notify-rs/notify/issues/412).
//!
//! # Examples
//!
//! For more examples visit the [examples folder](https://github.com/notify-rs/notify/tree/main/examples) in the repository.
//!
//! ```rust
//! use notify::{Event, RecursiveMode, Result, Watcher};
//! use std::{path::Path, sync::mpsc};
//!
//! fn main() -> Result<()> {
//! let (tx, rx) = mpsc::channel::<Result<Event>>();
//!
//! // Use recommended_watcher() to automatically select the best implementation
//! // for your platform. The `EventHandler` passed to this constructor can be a
//! // closure, a `std::sync::mpsc::Sender`, a `crossbeam_channel::Sender`, or
//! // another type the trait is implemented for.
//! let mut watcher = notify::recommended_watcher(tx)?;
//!
//! // Add a path to be watched. All files and directories at that path and
//! // below will be monitored for changes.
//! # #[cfg(not(any(
//! # target_os = "freebsd",
//! # target_os = "openbsd",
//! # target_os = "dragonfly",
//! # target_os = "netbsd")))]
//! # { // "." doesn't exist on BSD for some reason in CI
//! watcher.watch(Path::new("."), RecursiveMode::Recursive)?;
//! # }
//! # #[cfg(any())]
//! # { // don't run this in doctests, it blocks forever
//! // Block forever, printing out events as they come in
//! for res in rx {
//! match res {
//! Ok(event) => println!("event: {:?}", event),
//! Err(e) => println!("watch error: {:?}", e),
//! }
//! }
//! # }
//!
//! Ok(())
//! }
//! ```
//!
//! ## With different configurations
//!
//! It is possible to create several watchers with different configurations or implementations that
//! all call the same event function. This can accommodate advanced behaviour or work around limits.
//!
//! ```rust
//! # use notify::{RecursiveMode, Result, Watcher};
//! # use std::path::Path;
//! #
//! # fn main() -> Result<()> {
//! fn event_fn(res: Result<notify::Event>) {
//! match res {
//! Ok(event) => println!("event: {:?}", event),
//! Err(e) => println!("watch error: {:?}", e),
//! }
//! }
//!
//! let mut watcher1 = notify::recommended_watcher(event_fn)?;
//! // we will just use the same watcher kind again here
//! let mut watcher2 = notify::recommended_watcher(event_fn)?;
//! # #[cfg(not(any(
//! # target_os = "freebsd",
//! # target_os = "openbsd",
//! # target_os = "dragonfly",
//! # target_os = "netbsd")))]
//! # { // "." doesn't exist on BSD for some reason in CI
//! # watcher1.watch(Path::new("."), RecursiveMode::Recursive)?;
//! # watcher2.watch(Path::new("."), RecursiveMode::Recursive)?;
//! # }
//! // dropping the watcher1/2 here (no loop etc) will end the program
//! #
//! # Ok(())
//! # }
//! ```
#![deny(missing_docs)]
pub use config::{Config, RecursiveMode};
pub use error::{Error, ErrorKind, Result};
pub use notify_types::event::{self, Event, EventKind};
use std::path::Path;
pub(crate) type Receiver<T> = std::sync::mpsc::Receiver<T>;
pub(crate) type Sender<T> = std::sync::mpsc::Sender<T>;
#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
pub(crate) type BoundSender<T> = std::sync::mpsc::SyncSender<T>;
#[inline]
pub(crate) fn unbounded<T>() -> (Sender<T>, Receiver<T>) {
std::sync::mpsc::channel()
}
#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
#[inline]
pub(crate) fn bounded<T>(cap: usize) -> (BoundSender<T>, Receiver<T>) {
std::sync::mpsc::sync_channel(cap)
}
#[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))]
pub use crate::fsevent::FsEventWatcher;
#[cfg(any(target_os = "linux", target_os = "android"))]
pub use crate::inotify::INotifyWatcher;
#[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
target_os = "ios",
all(target_os = "macos", feature = "macos_kqueue")
))]
pub use crate::kqueue::KqueueWatcher;
pub use null::NullWatcher;
pub use poll::PollWatcher;
#[cfg(target_os = "windows")]
pub use windows::ReadDirectoryChangesWatcher;
#[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))]
pub mod fsevent;
#[cfg(any(target_os = "linux", target_os = "android"))]
pub mod inotify;
#[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "dragonfly",
target_os = "netbsd",
target_os = "ios",
all(target_os = "macos", feature = "macos_kqueue")
))]
pub mod kqueue;
#[cfg(target_os = "windows")]
pub mod windows;
pub mod null;
pub mod poll;
mod config;
mod error;
/// The set of requirements for watcher event handling functions.
///
/// # Example implementation
///
/// ```no_run
/// use notify::{Event, Result, EventHandler};
///
/// /// Prints received events
/// struct EventPrinter;
///
/// impl EventHandler for EventPrinter {
/// fn handle_event(&mut self, event: Result<Event>) {
/// if let Ok(event) = event {
/// println!("Event: {:?}", event);
/// }
/// }
/// }
/// ```
pub trait EventHandler: Send + 'static {
/// Handles an event.
fn handle_event(&mut self, event: Result<Event>);
}
impl<F> EventHandler for F
where
F: FnMut(Result<Event>) + Send + 'static,
{
fn handle_event(&mut self, event: Result<Event>) {
(self)(event);
}
}
#[cfg(feature = "crossbeam-channel")]
impl EventHandler for crossbeam_channel::Sender<Result<Event>> {
fn handle_event(&mut self, event: Result<Event>) {
let _ = self.send(event);
}
}
#[cfg(feature = "flume")]
impl EventHandler for flume::Sender<Result<Event>> {
fn handle_event(&mut self, event: Result<Event>) {
let _ = self.send(event);
}
}
impl EventHandler for std::sync::mpsc::Sender<Result<Event>> {
fn handle_event(&mut self, event: Result<Event>) {
let _ = self.send(event);
}
}
/// Watcher kind enumeration
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum WatcherKind {
/// inotify backend (linux)
Inotify,
/// FS-Event backend (mac)
Fsevent,
/// KQueue backend (bsd,optionally mac)
Kqueue,
/// Polling based backend (fallback)
PollWatcher,
/// Windows backend
ReadDirectoryChangesWatcher,
/// Fake watcher for testing
NullWatcher,
}
/// Providing methods for adding and removing paths to watch.
///
/// `Box<dyn PathsMut>` is created by [`Watcher::paths_mut`]. See its documentation for more.
pub trait PathsMut {
/// Add a new path to watch. See [`Watcher::watch`] for more.
fn add(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()>;
/// Remove a path from watching. See [`Watcher::unwatch`] for more.
fn remove(&mut self, path: &Path) -> Result<()>;
/// Ensure added/removed paths are applied.
///
/// The behaviour of dropping a [`PathsMut`] without calling [`commit`] is unspecified.
/// The implementation is free to ignore the changes or not, and may leave the watcher in a started or stopped state.
fn commit(self: Box<Self>) -> Result<()>;
}
/// Type that can deliver file activity notifications
///
/// `Watcher` is implemented per platform using the best implementation available on that platform.
/// In addition to such event driven implementations, a polling implementation is also provided
/// that should work on any platform.
pub trait Watcher {
/// Create a new watcher with an initial Config.
fn new<F: EventHandler>(event_handler: F, config: config::Config) -> Result<Self>
where
Self: Sized;
/// Begin watching a new path.
///
/// If the `path` is a directory, `recursive_mode` will be evaluated. If `recursive_mode` is
/// `RecursiveMode::Recursive` events will be delivered for all files in that tree. Otherwise
/// only the directory and its immediate children will be watched.
///
/// If the `path` is a file, `recursive_mode` will be ignored and events will be delivered only
/// for the file.
///
/// On some platforms, if the `path` is renamed or removed while being watched, behaviour may
/// be unexpected. See discussions in [#165] and [#166]. If less surprising behaviour is wanted
/// one may non-recursively watch the _parent_ directory as well and manage related events.
///
/// [#165]: https://github.com/notify-rs/notify/issues/165
/// [#166]: https://github.com/notify-rs/notify/issues/166
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()>;
/// Stop watching a path.
///
/// # Errors
///
/// Returns an error in the case that `path` has not been watched or if removing the watch
/// fails.
fn unwatch(&mut self, path: &Path) -> Result<()>;
/// Add/remove paths to watch.
///
/// For some watcher implementations this method provides better performance than multiple calls to [`Watcher::watch`] and [`Watcher::unwatch`] if you want to add/remove many paths at once.
///
/// # Examples
///
/// ```
/// # use notify::{Watcher, RecursiveMode, Result};
/// # use std::path::Path;
/// # fn main() -> Result<()> {
/// # let many_paths_to_add = vec![];
/// let mut watcher = notify::recommended_watcher(|_event| { /* event handler */ })?;
/// let mut watcher_paths = watcher.paths_mut();
/// for path in many_paths_to_add {
/// watcher_paths.add(path, RecursiveMode::Recursive)?;
/// }
/// watcher_paths.commit()?;
/// # Ok(())
/// # }
/// ```
fn paths_mut<'me>(&'me mut self) -> Box<dyn PathsMut + 'me> {
struct DefaultPathsMut<'a, T: ?Sized>(&'a mut T);
impl<'a, T: Watcher + ?Sized> PathsMut for DefaultPathsMut<'a, T> {
fn add(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
self.0.watch(path, recursive_mode)
}
fn remove(&mut self, path: &Path) -> Result<()> {
self.0.unwatch(path)
}
fn commit(self: Box<Self>) -> Result<()> {
Ok(())
}
}
Box::new(DefaultPathsMut(self))
}
/// Configure the watcher at runtime.
///
/// See the [`Config`](config/struct.Config.html) struct for all configuration options.
///
/// # Returns
///
/// - `Ok(true)` on success.
/// - `Ok(false)` if the watcher does not support or implement the option.
/// - `Err(notify::Error)` on failure.
fn configure(&mut self, _option: Config) -> Result<bool> {
Ok(false)
}
/// Returns the watcher kind, allowing to perform backend-specific tasks
fn kind() -> WatcherKind
where
Self: Sized;
}
/// The recommended [`Watcher`] implementation for the current platform
#[cfg(any(target_os = "linux", target_os = "android"))]
pub type RecommendedWatcher = INotifyWatcher;
/// The recommended [`Watcher`] implementation for the current platform
#[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))]
pub type RecommendedWatcher = FsEventWatcher;
/// The recommended [`Watcher`] implementation for the current platform
#[cfg(target_os = "windows")]
pub type RecommendedWatcher = ReadDirectoryChangesWatcher;
/// The recommended [`Watcher`] implementation for the current platform
#[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
target_os = "ios",
all(target_os = "macos", feature = "macos_kqueue")
))]
pub type RecommendedWatcher = KqueueWatcher;
/// The recommended [`Watcher`] implementation for the current platform
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "windows",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
target_os = "ios"
)))]
pub type RecommendedWatcher = PollWatcher;
/// Convenience method for creating the [`RecommendedWatcher`] for the current platform.
pub fn recommended_watcher<F>(event_handler: F) -> Result<RecommendedWatcher>
where
F: EventHandler,
{
// All recommended watchers currently implement `new`, so just call that.
RecommendedWatcher::new(event_handler, Config::default())
}
#[cfg(test)]
mod tests {
use std::{
fs, iter,
time::{Duration, Instant},
};
use tempfile::tempdir;
use super::*;
#[test]
fn test_object_safe() {
let _watcher: &dyn Watcher = &NullWatcher;
}
#[test]
fn test_debug_impl() {
macro_rules! assert_debug_impl {
($t:ty) => {{
#[allow(dead_code)]
trait NeedsDebug: std::fmt::Debug {}
impl NeedsDebug for $t {}
}};
}
assert_debug_impl!(Config);
assert_debug_impl!(Error);
assert_debug_impl!(ErrorKind);
assert_debug_impl!(NullWatcher);
assert_debug_impl!(PollWatcher);
assert_debug_impl!(RecommendedWatcher);
assert_debug_impl!(RecursiveMode);
assert_debug_impl!(WatcherKind);
}
fn iter_with_timeout(rx: &Receiver<Result<Event>>) -> impl Iterator<Item = Event> + '_ {
// wait for up to 10 seconds for the events
let deadline = Instant::now() + Duration::from_secs(10);
iter::from_fn(move || {
if Instant::now() >= deadline {
return None;
}
Some(
rx.recv_timeout(deadline - Instant::now())
.expect("did not receive expected event")
.expect("received an error"),
)
})
}
#[test]
fn integration() -> std::result::Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
// set up the watcher
let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
watcher.watch(dir.path(), RecursiveMode::Recursive)?;
// create a new file
let file_path = dir.path().join("file.txt");
fs::write(&file_path, b"Lorem ipsum")?;
println!("waiting for event at {}", file_path.display());
// wait for the create event, ignore all other events
for event in iter_with_timeout(&rx) {
if event.paths == vec![file_path.clone()]
|| event.paths == vec![file_path.canonicalize()?]
{
return Ok(());
}
println!("unexpected event: {event:?}");
}
panic!("did not receive expected event");
}
#[test]
#[cfg(target_os = "windows")]
fn test_windows_trash_dir() -> std::result::Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let child_dir = dir.path().join("child");
fs::create_dir(&child_dir)?;
let mut watcher = recommended_watcher(|_| {
// Do something with the event
})?;
watcher.watch(&child_dir, RecursiveMode::NonRecursive)?;
trash::delete(&child_dir)?;
watcher.watch(dir.path(), RecursiveMode::NonRecursive)?;
Ok(())
}
#[test]
fn test_paths_mut() -> std::result::Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let dir_a = dir.path().join("a");
let dir_b = dir.path().join("b");
fs::create_dir(&dir_a)?;
fs::create_dir(&dir_b)?;
let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
// start watching a and b
{
let mut watcher_paths = watcher.paths_mut();
watcher_paths.add(&dir_a, RecursiveMode::Recursive)?;
watcher_paths.add(&dir_b, RecursiveMode::Recursive)?;
watcher_paths.commit()?;
}
// create file1 in both a and b
let a_file1 = dir_a.join("file1");
let b_file1 = dir_b.join("file1");
fs::write(&a_file1, b"Lorem ipsum")?;
fs::write(&b_file1, b"Lorem ipsum")?;
// wait for create events of a/file1 and b/file1
let mut a_file1_encountered: bool = false;
let mut b_file1_encountered: bool = false;
for event in iter_with_timeout(&rx) {
for path in event.paths {
a_file1_encountered =
a_file1_encountered || (path == a_file1 || path == a_file1.canonicalize()?);
b_file1_encountered =
b_file1_encountered || (path == b_file1 || path == b_file1.canonicalize()?);
}
if a_file1_encountered && b_file1_encountered {
break;
}
}
assert!(a_file1_encountered, "Did not receive event of {a_file1:?}");
assert!(b_file1_encountered, "Did not receive event of {b_file1:?}");
// stop watching a
{
let mut watcher_paths = watcher.paths_mut();
watcher_paths.remove(&dir_a)?;
watcher_paths.commit()?;
}
// create file2 in both a and b
let a_file2 = dir_a.join("file2");
let b_file2 = dir_b.join("file2");
fs::write(&a_file2, b"Lorem ipsum")?;
fs::write(&b_file2, b"Lorem ipsum")?;
// wait for the create event of b/file2 only
for event in iter_with_timeout(&rx) {
for path in event.paths {
assert!(
path != a_file2 || path != a_file2.canonicalize()?,
"Event of {a_file2:?} should not be received"
);
if path == b_file2 || path == b_file2.canonicalize()? {
return Ok(());
}
}
}
panic!("Did not receive the event of {b_file2:?}");
}
}
+39
View File
@@ -0,0 +1,39 @@
//! Stub Watcher implementation
#![allow(unused_variables)]
use crate::Config;
use super::{RecursiveMode, Result, Watcher};
use std::path::Path;
/// Stub `Watcher` implementation
///
/// Events are never delivered from this watcher.
#[derive(Debug)]
pub struct NullWatcher;
impl Watcher for NullWatcher {
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
Ok(())
}
fn unwatch(&mut self, path: &Path) -> Result<()> {
Ok(())
}
fn new<F: crate::EventHandler>(event_handler: F, config: Config) -> Result<Self>
where
Self: Sized,
{
Ok(NullWatcher)
}
fn configure(&mut self, config: Config) -> Result<bool> {
Ok(false)
}
fn kind() -> crate::WatcherKind {
crate::WatcherKind::NullWatcher
}
}
+676
View File
@@ -0,0 +1,676 @@
//! Generic Watcher implementation based on polling
//!
//! Checks the `watch`ed paths periodically to detect changes. This implementation only uses
//! Rust stdlib APIs and should work on all of the platforms it supports.
use crate::{unbounded, Config, Error, EventHandler, Receiver, RecursiveMode, Sender, Watcher};
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
thread,
time::Duration,
};
/// Event sent for registered handlers on initial directory scans
pub type ScanEvent = crate::Result<PathBuf>;
/// Handler trait for receivers of [`ScanEvent`].
/// Very much the same as [`EventHandler`], but including the Result.
///
/// See the full example for more information.
pub trait ScanEventHandler: Send + 'static {
/// Handles an event.
fn handle_event(&mut self, event: ScanEvent);
}
impl<F> ScanEventHandler for F
where
F: FnMut(ScanEvent) + Send + 'static,
{
fn handle_event(&mut self, event: ScanEvent) {
(self)(event);
}
}
#[cfg(feature = "crossbeam-channel")]
impl ScanEventHandler for crossbeam_channel::Sender<ScanEvent> {
fn handle_event(&mut self, event: ScanEvent) {
let _ = self.send(event);
}
}
#[cfg(feature = "flume")]
impl ScanEventHandler for flume::Sender<ScanEvent> {
fn handle_event(&mut self, event: ScanEvent) {
let _ = self.send(event);
}
}
impl ScanEventHandler for std::sync::mpsc::Sender<ScanEvent> {
fn handle_event(&mut self, event: ScanEvent) {
let _ = self.send(event);
}
}
impl ScanEventHandler for () {
fn handle_event(&mut self, _event: ScanEvent) {}
}
use data::{DataBuilder, WatchData};
mod data {
use crate::{
event::{CreateKind, DataChange, Event, EventKind, MetadataKind, ModifyKind, RemoveKind},
EventHandler,
};
use std::{
cell::RefCell,
collections::{hash_map::RandomState, HashMap},
fmt::{self, Debug},
fs::{self, File, Metadata},
hash::{BuildHasher, Hasher},
io::{self, Read},
path::{Path, PathBuf},
time::Instant,
};
use walkdir::WalkDir;
use super::ScanEventHandler;
fn system_time_to_seconds(time: std::time::SystemTime) -> i64 {
match time.duration_since(std::time::SystemTime::UNIX_EPOCH) {
Ok(d) => d.as_secs() as i64,
Err(e) => -(e.duration().as_secs() as i64),
}
}
/// Builder for [`WatchData`] & [`PathData`].
pub(super) struct DataBuilder {
emitter: EventEmitter,
scan_emitter: Option<Box<RefCell<dyn ScanEventHandler>>>,
// TODO: May allow user setup their custom BuildHasher / BuildHasherDefault
// in future.
build_hasher: Option<RandomState>,
// current timestamp for building Data.
now: Instant,
}
impl DataBuilder {
pub(super) fn new<F, G>(
event_handler: F,
compare_content: bool,
scan_emitter: Option<G>,
) -> Self
where
F: EventHandler,
G: ScanEventHandler,
{
let scan_emitter = match scan_emitter {
None => None,
Some(v) => {
// workaround for a weird type resolution bug when directly going to dyn Trait
let intermediate: Box<RefCell<dyn ScanEventHandler>> =
Box::new(RefCell::new(v));
Some(intermediate)
}
};
Self {
emitter: EventEmitter::new(event_handler),
scan_emitter,
build_hasher: compare_content.then(RandomState::default),
now: Instant::now(),
}
}
/// Update internal timestamp.
pub(super) fn update_timestamp(&mut self) {
self.now = Instant::now();
}
/// Create [`WatchData`].
///
/// This function will return `Err(_)` if can not retrieve metadata from
/// the path location. (e.g., not found).
pub(super) fn build_watch_data(
&self,
root: PathBuf,
is_recursive: bool,
follow_symlinks: bool,
) -> Option<WatchData> {
WatchData::new(self, root, is_recursive, follow_symlinks)
}
/// Create [`PathData`].
fn build_path_data(&self, meta_path: &MetaPath) -> PathData {
PathData::new(self, meta_path)
}
}
impl Debug for DataBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("DataBuilder")
.field("build_hasher", &self.build_hasher)
.field("now", &self.now)
.finish()
}
}
#[derive(Debug)]
pub(super) struct WatchData {
// config part, won't change.
root: PathBuf,
is_recursive: bool,
follow_symlinks: bool,
// current status part.
all_path_data: HashMap<PathBuf, PathData>,
}
impl WatchData {
/// Scan filesystem and create a new `WatchData`.
///
/// # Side effect
///
/// This function may send event by `data_builder.emitter`.
fn new(
data_builder: &DataBuilder,
root: PathBuf,
is_recursive: bool,
follow_symlinks: bool,
) -> Option<Self> {
// If metadata read error at `root` path, it will emit
// a error event and stop to create the whole `WatchData`.
//
// QUESTION: inconsistent?
//
// When user try to *CREATE* a watch by `poll_watcher.watch(root, ..)`,
// if `root` path hit an io error, then watcher will reject to
// create this new watch.
//
// This may inconsistent with *POLLING* a watch. When watcher
// continue polling, io error at root path will not delete
// a existing watch. polling still working.
//
// So, consider a config file may not exists at first time but may
// create after a while, developer cannot watch it.
//
// FIXME: Can we always allow to watch a path, even file not
// found at this path?
if let Err(e) = fs::metadata(&root) {
data_builder.emitter.emit_io_err(e, Some(&root));
return None;
}
let all_path_data = Self::scan_all_path_data(
data_builder,
root.clone(),
is_recursive,
follow_symlinks,
true,
)
.collect();
Some(Self {
root,
is_recursive,
follow_symlinks,
all_path_data,
})
}
/// Rescan filesystem and update this `WatchData`.
///
/// # Side effect
///
/// This function may emit event by `data_builder.emitter`.
pub(super) fn rescan(&mut self, data_builder: &mut DataBuilder) {
// scan current filesystem.
for (path, new_path_data) in Self::scan_all_path_data(
data_builder,
self.root.clone(),
self.is_recursive,
self.follow_symlinks,
false,
) {
let old_path_data = self
.all_path_data
.insert(path.clone(), new_path_data.clone());
// emit event
let event =
PathData::compare_to_event(path, old_path_data.as_ref(), Some(&new_path_data));
if let Some(event) = event {
data_builder.emitter.emit_ok(event);
}
}
// scan for disappeared paths.
let mut disappeared_paths = Vec::new();
for (path, path_data) in self.all_path_data.iter() {
if path_data.last_check < data_builder.now {
disappeared_paths.push(path.clone());
}
}
// remove disappeared paths
for path in disappeared_paths {
let old_path_data = self.all_path_data.remove(&path);
// emit event
let event = PathData::compare_to_event(path, old_path_data.as_ref(), None);
if let Some(event) = event {
data_builder.emitter.emit_ok(event);
}
}
}
/// Get all `PathData` by given configuration.
///
/// # Side Effect
///
/// This function may emit some IO Error events by `data_builder.emitter`.
fn scan_all_path_data(
data_builder: &'_ DataBuilder,
root: PathBuf,
is_recursive: bool,
follow_symlinks: bool,
// whether this is an initial scan, used only for events
is_initial: bool,
) -> impl Iterator<Item = (PathBuf, PathData)> + '_ {
log::trace!("rescanning {root:?}");
// WalkDir return only one entry if root is a file (not a folder),
// so we can use single logic to do the both file & dir's jobs.
//
// See: https://docs.rs/walkdir/2.0.1/walkdir/struct.WalkDir.html#method.new
WalkDir::new(root)
.follow_links(follow_symlinks)
.max_depth(Self::dir_scan_depth(is_recursive))
.into_iter()
.filter_map(|entry_res| match entry_res {
Ok(entry) => Some(entry),
Err(err) => {
log::warn!("walkdir error scanning {err:?}");
if let Some(io_error) = err.io_error() {
// clone an io::Error, so we have to create a new one.
let new_io_error = io::Error::new(io_error.kind(), err.to_string());
data_builder.emitter.emit_io_err(new_io_error, err.path());
} else {
let crate_err =
crate::Error::new(crate::ErrorKind::Generic(err.to_string()));
data_builder.emitter.emit(Err(crate_err));
}
None
}
})
.filter_map(move |entry| match entry.metadata() {
Ok(metadata) => {
let path = entry.into_path();
if is_initial {
// emit initial scans
if let Some(ref emitter) = data_builder.scan_emitter {
emitter.borrow_mut().handle_event(Ok(path.clone()));
}
}
let meta_path = MetaPath::from_parts_unchecked(path, metadata);
let data_path = data_builder.build_path_data(&meta_path);
Some((meta_path.into_path(), data_path))
}
Err(e) => {
// emit event.
let path = entry.into_path();
data_builder.emitter.emit_io_err(e, Some(path));
None
}
})
}
fn dir_scan_depth(is_recursive: bool) -> usize {
if is_recursive {
usize::MAX
} else {
1
}
}
}
/// Stored data for a one path locations.
///
/// See [`WatchData`] for more detail.
#[derive(Debug, Clone)]
struct PathData {
/// File updated time.
mtime: i64,
/// Content's hash value, only available if user request compare file
/// contents and read successful.
hash: Option<u64>,
/// Checked time.
last_check: Instant,
}
impl PathData {
/// Create a new `PathData`.
fn new(data_builder: &DataBuilder, meta_path: &MetaPath) -> PathData {
let metadata = meta_path.metadata();
PathData {
mtime: metadata.modified().map_or(0, system_time_to_seconds),
hash: data_builder
.build_hasher
.as_ref()
.filter(|_| metadata.is_file())
.and_then(|build_hasher| {
Self::get_content_hash(build_hasher, meta_path.path()).ok()
}),
last_check: data_builder.now,
}
}
/// Get hash value for the data content in given file `path`.
fn get_content_hash(build_hasher: &RandomState, path: &Path) -> io::Result<u64> {
let mut hasher = build_hasher.build_hasher();
let mut file = File::open(path)?;
let mut buf = [0; 512];
loop {
let n = match file.read(&mut buf) {
Ok(0) => break,
Ok(len) => len,
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
};
hasher.write(&buf[..n]);
}
Ok(hasher.finish())
}
/// Get [`Event`] by compare two optional [`PathData`].
fn compare_to_event<P>(
path: P,
old: Option<&PathData>,
new: Option<&PathData>,
) -> Option<Event>
where
P: Into<PathBuf>,
{
match (old, new) {
(Some(old), Some(new)) => {
if new.mtime > old.mtime {
Some(EventKind::Modify(ModifyKind::Metadata(
MetadataKind::WriteTime,
)))
} else if new.hash != old.hash {
Some(EventKind::Modify(ModifyKind::Data(DataChange::Any)))
} else {
None
}
}
(None, Some(_new)) => Some(EventKind::Create(CreateKind::Any)),
(Some(_old), None) => Some(EventKind::Remove(RemoveKind::Any)),
(None, None) => None,
}
.map(|event_kind| Event::new(event_kind).add_path(path.into()))
}
}
/// Compose path and its metadata.
///
/// This data structure designed for make sure path and its metadata can be
/// transferred in consistent way, and may avoid some duplicated
/// `fs::metadata()` function call in some situations.
#[derive(Debug)]
pub(super) struct MetaPath {
path: PathBuf,
metadata: Metadata,
}
impl MetaPath {
/// Create `MetaPath` by given parts.
///
/// # Invariant
///
/// User must make sure the input `metadata` are associated with `path`.
fn from_parts_unchecked(path: PathBuf, metadata: Metadata) -> Self {
Self { path, metadata }
}
fn path(&self) -> &Path {
&self.path
}
fn metadata(&self) -> &Metadata {
&self.metadata
}
fn into_path(self) -> PathBuf {
self.path
}
}
/// Thin wrapper for outer event handler, for easy to use.
struct EventEmitter(
// Use `RefCell` to make sure `emit()` only need shared borrow of self (&self).
// Use `Box` to make sure EventEmitter is Sized.
Box<RefCell<dyn EventHandler>>,
);
impl EventEmitter {
fn new<F: EventHandler>(event_handler: F) -> Self {
Self(Box::new(RefCell::new(event_handler)))
}
/// Emit single event.
fn emit(&self, event: crate::Result<Event>) {
self.0.borrow_mut().handle_event(event);
}
/// Emit event.
fn emit_ok(&self, event: Event) {
self.emit(Ok(event))
}
/// Emit io error event.
fn emit_io_err<E, P>(&self, err: E, path: Option<P>)
where
E: Into<io::Error>,
P: Into<PathBuf>,
{
let e = crate::Error::io(err.into());
if let Some(path) = path {
self.emit(Err(e.add_path(path.into())));
} else {
self.emit(Err(e));
}
}
}
}
/// Polling based `Watcher` implementation.
///
/// By default scans through all files and checks for changed entries based on their change date.
/// Can also be changed to perform file content change checks.
///
/// See [Config] for more details.
#[derive(Debug)]
pub struct PollWatcher {
watches: Arc<Mutex<HashMap<PathBuf, WatchData>>>,
data_builder: Arc<Mutex<DataBuilder>>,
want_to_stop: Arc<AtomicBool>,
/// channel to the poll loop
/// currently used only for manual polling
message_channel: Sender<()>,
delay: Option<Duration>,
follow_sylinks: bool,
}
impl PollWatcher {
/// Create a new [`PollWatcher`], configured as needed.
pub fn new<F: EventHandler>(event_handler: F, config: Config) -> crate::Result<PollWatcher> {
Self::with_opt::<_, ()>(event_handler, config, None)
}
/// Actively poll for changes. Can be combined with a timeout of 0 to perform only manual polling.
pub fn poll(&self) -> crate::Result<()> {
self.message_channel
.send(())
.map_err(|_| Error::generic("failed to send poll message"))?;
Ok(())
}
/// Create a new [`PollWatcher`] with an scan event handler.
///
/// `scan_fallback` is called on the initial scan with all files seen by the pollwatcher.
pub fn with_initial_scan<F: EventHandler, G: ScanEventHandler>(
event_handler: F,
config: Config,
scan_callback: G,
) -> crate::Result<PollWatcher> {
Self::with_opt(event_handler, config, Some(scan_callback))
}
/// create a new [`PollWatcher`] with all options.
fn with_opt<F: EventHandler, G: ScanEventHandler>(
event_handler: F,
config: Config,
scan_callback: Option<G>,
) -> crate::Result<PollWatcher> {
let data_builder =
DataBuilder::new(event_handler, config.compare_contents(), scan_callback);
let (tx, rx) = unbounded();
let poll_watcher = PollWatcher {
watches: Default::default(),
data_builder: Arc::new(Mutex::new(data_builder)),
want_to_stop: Arc::new(AtomicBool::new(false)),
delay: config.poll_interval(),
follow_sylinks: config.follow_symlinks(),
message_channel: tx,
};
poll_watcher.run(rx);
Ok(poll_watcher)
}
fn run(&self, rx: Receiver<()>) {
let watches = Arc::clone(&self.watches);
let data_builder = Arc::clone(&self.data_builder);
let want_to_stop = Arc::clone(&self.want_to_stop);
let delay = self.delay;
let _ = thread::Builder::new()
.name("notify-rs poll loop".to_string())
.spawn(move || {
loop {
if want_to_stop.load(Ordering::SeqCst) {
break;
}
// HINT: Make sure always lock in the same order to avoid deadlock.
//
// FIXME: inconsistent: some place mutex poison cause panic,
// some place just ignore.
if let (Ok(mut watches), Ok(mut data_builder)) =
(watches.lock(), data_builder.lock())
{
data_builder.update_timestamp();
let vals = watches.values_mut();
for watch_data in vals {
watch_data.rescan(&mut data_builder);
}
}
// TODO: v7.0 use delay - (Instant::now().saturating_duration_since(start))
if let Some(delay) = delay {
let _ = rx.recv_timeout(delay);
} else {
let _ = rx.recv();
}
}
});
}
/// Watch a path location.
///
/// QUESTION: this function never return an Error, is it as intend?
/// Please also consider the IO Error event problem.
fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) {
// HINT: Make sure always lock in the same order to avoid deadlock.
//
// FIXME: inconsistent: some place mutex poison cause panic, some place just ignore.
if let (Ok(mut watches), Ok(mut data_builder)) =
(self.watches.lock(), self.data_builder.lock())
{
data_builder.update_timestamp();
let watch_data = data_builder.build_watch_data(
path.to_path_buf(),
recursive_mode.is_recursive(),
self.follow_sylinks,
);
// if create watch_data successful, add it to watching list.
if let Some(watch_data) = watch_data {
watches.insert(path.to_path_buf(), watch_data);
}
}
}
/// Unwatch a path.
///
/// Return `Err(_)` if given path has't be monitored.
fn unwatch_inner(&mut self, path: &Path) -> crate::Result<()> {
// FIXME: inconsistent: some place mutex poison cause panic, some place just ignore.
self.watches
.lock()
.unwrap()
.remove(path)
.map(|_| ())
.ok_or_else(crate::Error::watch_not_found)
}
}
impl Watcher for PollWatcher {
/// Create a new [`PollWatcher`].
fn new<F: EventHandler>(event_handler: F, config: Config) -> crate::Result<Self> {
Self::new(event_handler, config)
}
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> crate::Result<()> {
self.watch_inner(path, recursive_mode);
Ok(())
}
fn unwatch(&mut self, path: &Path) -> crate::Result<()> {
self.unwatch_inner(path)
}
fn kind() -> crate::WatcherKind {
crate::WatcherKind::PollWatcher
}
}
impl Drop for PollWatcher {
fn drop(&mut self) {
self.want_to_stop.store(true, Ordering::Relaxed);
}
}
#[test]
fn poll_watcher_is_send_and_sync() {
fn check<T: Send + Sync>() {}
check::<PollWatcher>();
}
+602
View File
@@ -0,0 +1,602 @@
#![allow(missing_docs)]
//! Watcher implementation for Windows' directory management APIs
//!
//! For more information see the [ReadDirectoryChangesW reference][ref].
//!
//! [ref]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa363950(v=vs.85).aspx
use crate::{bounded, unbounded, BoundSender, Config, Receiver, Sender};
use crate::{event::*, WatcherKind};
use crate::{Error, EventHandler, RecursiveMode, Result, Watcher};
use std::alloc;
use std::collections::HashMap;
use std::env;
use std::ffi::OsString;
use std::os::raw::c_void;
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::path::{Path, PathBuf};
use std::ptr;
use std::slice;
use std::sync::{Arc, Mutex};
use std::thread;
use windows_sys::Win32::Foundation::{
CloseHandle, ERROR_ACCESS_DENIED, ERROR_OPERATION_ABORTED, ERROR_SUCCESS, HANDLE,
INVALID_HANDLE_VALUE, WAIT_OBJECT_0,
};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, ReadDirectoryChangesW, FILE_ACTION_ADDED, FILE_ACTION_MODIFIED,
FILE_ACTION_REMOVED, FILE_ACTION_RENAMED_NEW_NAME, FILE_ACTION_RENAMED_OLD_NAME,
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OVERLAPPED, FILE_LIST_DIRECTORY,
FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_NOTIFY_CHANGE_CREATION, FILE_NOTIFY_CHANGE_DIR_NAME,
FILE_NOTIFY_CHANGE_FILE_NAME, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_NOTIFY_CHANGE_SECURITY,
FILE_NOTIFY_CHANGE_SIZE, FILE_NOTIFY_INFORMATION, FILE_SHARE_DELETE, FILE_SHARE_READ,
FILE_SHARE_WRITE, OPEN_EXISTING,
};
use windows_sys::Win32::System::Threading::{
CreateSemaphoreW, ReleaseSemaphore, WaitForSingleObjectEx, INFINITE,
};
use windows_sys::Win32::System::IO::{CancelIo, OVERLAPPED};
const BUF_SIZE: u32 = 16384;
#[derive(Clone)]
struct ReadData {
dir: PathBuf, // directory that is being watched
file: Option<PathBuf>, // if a file is being watched, this is its full path
complete_sem: HANDLE,
is_recursive: bool,
}
struct ReadDirectoryRequest {
event_handler: Arc<Mutex<dyn EventHandler>>,
buffer: [u8; BUF_SIZE as usize],
handle: HANDLE,
data: ReadData,
action_tx: Sender<Action>,
}
impl ReadDirectoryRequest {
fn unwatch(&self) {
let _ = self.action_tx.send(Action::Unwatch(self.data.dir.clone()));
}
}
enum Action {
Watch(PathBuf, RecursiveMode),
Unwatch(PathBuf),
Stop,
Configure(Config, BoundSender<Result<bool>>),
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum MetaEvent {
SingleWatchComplete,
WatcherAwakened,
}
struct WatchState {
dir_handle: HANDLE,
complete_sem: HANDLE,
}
struct ReadDirectoryChangesServer {
tx: Sender<Action>,
rx: Receiver<Action>,
event_handler: Arc<Mutex<dyn EventHandler>>,
meta_tx: Sender<MetaEvent>,
cmd_tx: Sender<Result<PathBuf>>,
watches: HashMap<PathBuf, WatchState>,
wakeup_sem: HANDLE,
}
impl ReadDirectoryChangesServer {
fn start(
event_handler: Arc<Mutex<dyn EventHandler>>,
meta_tx: Sender<MetaEvent>,
cmd_tx: Sender<Result<PathBuf>>,
wakeup_sem: HANDLE,
) -> Sender<Action> {
let (action_tx, action_rx) = unbounded();
// it is, in fact, ok to send the semaphore across threads
let sem_temp = wakeup_sem as u64;
let _ = thread::Builder::new()
.name("notify-rs windows loop".to_string())
.spawn({
let tx = action_tx.clone();
move || {
let wakeup_sem = sem_temp as HANDLE;
let server = ReadDirectoryChangesServer {
tx,
rx: action_rx,
event_handler,
meta_tx,
cmd_tx,
watches: HashMap::new(),
wakeup_sem,
};
server.run();
}
});
action_tx
}
fn run(mut self) {
loop {
// process all available actions first
let mut stopped = false;
while let Ok(action) = self.rx.try_recv() {
match action {
Action::Watch(path, recursive_mode) => {
let res = self.add_watch(path, recursive_mode.is_recursive());
let _ = self.cmd_tx.send(res);
}
Action::Unwatch(path) => self.remove_watch(path),
Action::Stop => {
stopped = true;
for ws in self.watches.values() {
stop_watch(ws, &self.meta_tx);
}
break;
}
Action::Configure(config, tx) => {
self.configure_raw_mode(config, tx);
}
}
}
if stopped {
break;
}
unsafe {
// wait with alertable flag so that the completion routine fires
let waitres = WaitForSingleObjectEx(self.wakeup_sem, 100, 1);
if waitres == WAIT_OBJECT_0 {
let _ = self.meta_tx.send(MetaEvent::WatcherAwakened);
}
}
}
// we have to clean this up, since the watcher may be long gone
unsafe {
CloseHandle(self.wakeup_sem);
}
}
fn add_watch(&mut self, path: PathBuf, is_recursive: bool) -> Result<PathBuf> {
// path must exist and be either a file or directory
if !path.is_dir() && !path.is_file() {
return Err(
Error::generic("Input watch path is neither a file nor a directory.")
.add_path(path),
);
}
let (watching_file, dir_target) = {
if path.is_dir() {
(false, path.clone())
} else {
// emulate file watching by watching the parent directory
(true, path.parent().unwrap().to_path_buf())
}
};
let encoded_path: Vec<u16> = dir_target
.as_os_str()
.encode_wide()
.chain(Some(0))
.collect();
let handle;
unsafe {
handle = CreateFileW(
encoded_path.as_ptr(),
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
ptr::null_mut(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
ptr::null_mut(),
);
if handle == INVALID_HANDLE_VALUE {
return Err(if watching_file {
Error::generic(
"You attempted to watch a single file, but parent \
directory could not be opened.",
)
.add_path(path)
} else {
// TODO: Call GetLastError for better error info?
Error::path_not_found().add_path(path)
});
}
}
let wf = if watching_file {
Some(path.clone())
} else {
None
};
// every watcher gets its own semaphore to signal completion
let semaphore = unsafe { CreateSemaphoreW(ptr::null_mut(), 0, 1, ptr::null_mut()) };
if semaphore.is_null() || semaphore == INVALID_HANDLE_VALUE {
unsafe {
CloseHandle(handle);
}
return Err(Error::generic("Failed to create semaphore for watch.").add_path(path));
}
let rd = ReadData {
dir: dir_target,
file: wf,
complete_sem: semaphore,
is_recursive,
};
let ws = WatchState {
dir_handle: handle,
complete_sem: semaphore,
};
self.watches.insert(path.clone(), ws);
start_read(&rd, self.event_handler.clone(), handle, self.tx.clone());
Ok(path)
}
fn remove_watch(&mut self, path: PathBuf) {
if let Some(ws) = self.watches.remove(&path) {
stop_watch(&ws, &self.meta_tx);
}
}
fn configure_raw_mode(&mut self, _config: Config, tx: BoundSender<Result<bool>>) {
tx.send(Ok(false))
.expect("configuration channel disconnect");
}
}
fn stop_watch(ws: &WatchState, meta_tx: &Sender<MetaEvent>) {
unsafe {
let cio = CancelIo(ws.dir_handle);
let ch = CloseHandle(ws.dir_handle);
// have to wait for it, otherwise we leak the memory allocated for there read request
if cio != 0 && ch != 0 {
while WaitForSingleObjectEx(ws.complete_sem, INFINITE, 1) != WAIT_OBJECT_0 {
// drain the apc queue, fix for https://github.com/notify-rs/notify/issues/287#issuecomment-801465550
}
}
CloseHandle(ws.complete_sem);
}
let _ = meta_tx.send(MetaEvent::SingleWatchComplete);
}
fn start_read(
rd: &ReadData,
event_handler: Arc<Mutex<dyn EventHandler>>,
handle: HANDLE,
action_tx: Sender<Action>,
) {
let request = Box::new(ReadDirectoryRequest {
event_handler,
handle,
buffer: [0u8; BUF_SIZE as usize],
data: rd.clone(),
action_tx,
});
let flags = FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_ATTRIBUTES
| FILE_NOTIFY_CHANGE_SIZE
| FILE_NOTIFY_CHANGE_LAST_WRITE
| FILE_NOTIFY_CHANGE_CREATION
| FILE_NOTIFY_CHANGE_SECURITY;
let monitor_subdir = if request.data.file.is_none() && request.data.is_recursive {
1
} else {
0
};
unsafe {
let overlapped = alloc::alloc_zeroed(alloc::Layout::new::<OVERLAPPED>()) as *mut OVERLAPPED;
// When using callback based async requests, we are allowed to use the hEvent member
// for our own purposes
let request = Box::leak(request);
(*overlapped).hEvent = request as *mut _ as _;
// This is using an asynchronous call with a completion routine for receiving notifications
// An I/O completion port would probably be more performant
let ret = ReadDirectoryChangesW(
handle,
request.buffer.as_mut_ptr() as *mut c_void,
BUF_SIZE,
monitor_subdir,
flags,
&mut 0u32 as *mut u32, // not used for async reqs
overlapped,
Some(handle_event),
);
if ret == 0 {
// error reading. retransmute request memory to allow drop.
// Because of the error, ownership of the `overlapped` alloc was not passed
// over to `ReadDirectoryChangesW`.
// So we can claim ownership back.
let _overlapped = Box::from_raw(overlapped);
let request = Box::from_raw(request);
ReleaseSemaphore(request.data.complete_sem, 1, ptr::null_mut());
}
}
}
unsafe extern "system" fn handle_event(
error_code: u32,
_bytes_written: u32,
overlapped: *mut OVERLAPPED,
) {
let overlapped: Box<OVERLAPPED> = Box::from_raw(overlapped);
let request: Box<ReadDirectoryRequest> = Box::from_raw(overlapped.hEvent as *mut _);
match error_code {
ERROR_OPERATION_ABORTED => {
// received when dir is unwatched or watcher is shutdown; return and let overlapped/request get drop-cleaned
ReleaseSemaphore(request.data.complete_sem, 1, ptr::null_mut());
return;
}
ERROR_ACCESS_DENIED => {
// This could happen when the watched directory is deleted or trashed, first check if it's the case.
// If so, unwatch the directory and return, otherwise, continue to handle the event.
if !request.data.dir.exists() {
request.unwatch();
ReleaseSemaphore(request.data.complete_sem, 1, ptr::null_mut());
return;
}
}
ERROR_SUCCESS => {
// Success, continue to handle the event
}
_ => {
// Some unidentified error occurred, log and unwatch the directory, then return.
log::error!(
"unknown error in ReadDirectoryChangesW for directory {}: {}",
request.data.dir.display(),
error_code
);
request.unwatch();
ReleaseSemaphore(request.data.complete_sem, 1, ptr::null_mut());
return;
}
}
// Get the next request queued up as soon as possible
start_read(
&request.data,
request.event_handler.clone(),
request.handle,
request.action_tx,
);
// The FILE_NOTIFY_INFORMATION struct has a variable length due to the variable length
// string as its last member. Each struct contains an offset for getting the next entry in
// the buffer.
let mut cur_offset: *const u8 = request.buffer.as_ptr();
// In Wine, FILE_NOTIFY_INFORMATION structs are packed placed in the buffer;
// they are aligned to 16bit (WCHAR) boundary instead of 32bit required by FILE_NOTIFY_INFORMATION.
// Hence, we need to use `read_unaligned` here to avoid UB.
let mut cur_entry = ptr::read_unaligned(cur_offset as *const FILE_NOTIFY_INFORMATION);
loop {
// filename length is size in bytes, so / 2
let len = cur_entry.FileNameLength as usize / 2;
let encoded_path: &[u16] = slice::from_raw_parts(
cur_offset.offset(std::mem::offset_of!(FILE_NOTIFY_INFORMATION, FileName) as isize)
as _,
len,
);
// prepend root to get a full path
let path = request
.data
.dir
.join(PathBuf::from(OsString::from_wide(encoded_path)));
// if we are watching a single file, ignore the event unless the path is exactly
// the watched file
let skip = match request.data.file {
None => false,
Some(ref watch_path) => *watch_path != path,
};
if !skip {
log::trace!(
"Event: path = `{}`, action = {:?}",
path.display(),
cur_entry.Action
);
let newe = Event::new(EventKind::Any).add_path(path);
fn emit_event(event_handler: &Mutex<dyn EventHandler>, res: Result<Event>) {
if let Ok(mut guard) = event_handler.lock() {
let f: &mut dyn EventHandler = &mut *guard;
f.handle_event(res);
}
}
let event_handler = |res| emit_event(&request.event_handler, res);
if cur_entry.Action == FILE_ACTION_RENAMED_OLD_NAME {
let mode = RenameMode::From;
let kind = ModifyKind::Name(mode);
let kind = EventKind::Modify(kind);
let ev = newe.set_kind(kind);
event_handler(Ok(ev))
} else {
match cur_entry.Action {
FILE_ACTION_RENAMED_NEW_NAME => {
let kind = EventKind::Modify(ModifyKind::Name(RenameMode::To));
let ev = newe.set_kind(kind);
event_handler(Ok(ev));
}
FILE_ACTION_ADDED => {
let kind = EventKind::Create(CreateKind::Any);
let ev = newe.set_kind(kind);
event_handler(Ok(ev));
}
FILE_ACTION_REMOVED => {
let kind = EventKind::Remove(RemoveKind::Any);
let ev = newe.set_kind(kind);
event_handler(Ok(ev));
}
FILE_ACTION_MODIFIED => {
let kind = EventKind::Modify(ModifyKind::Any);
let ev = newe.set_kind(kind);
event_handler(Ok(ev));
}
_ => (),
};
}
}
if cur_entry.NextEntryOffset == 0 {
break;
}
cur_offset = cur_offset.offset(cur_entry.NextEntryOffset as isize);
cur_entry = ptr::read_unaligned(cur_offset as *const FILE_NOTIFY_INFORMATION);
}
}
/// Watcher implementation based on ReadDirectoryChanges
#[derive(Debug)]
pub struct ReadDirectoryChangesWatcher {
tx: Sender<Action>,
cmd_rx: Receiver<Result<PathBuf>>,
wakeup_sem: HANDLE,
}
impl ReadDirectoryChangesWatcher {
pub fn create(
event_handler: Arc<Mutex<dyn EventHandler>>,
meta_tx: Sender<MetaEvent>,
) -> Result<ReadDirectoryChangesWatcher> {
let (cmd_tx, cmd_rx) = unbounded();
let wakeup_sem = unsafe { CreateSemaphoreW(ptr::null_mut(), 0, 1, ptr::null_mut()) };
if wakeup_sem.is_null() || wakeup_sem == INVALID_HANDLE_VALUE {
return Err(Error::generic("Failed to create wakeup semaphore."));
}
let action_tx =
ReadDirectoryChangesServer::start(event_handler, meta_tx, cmd_tx, wakeup_sem);
Ok(ReadDirectoryChangesWatcher {
tx: action_tx,
cmd_rx,
wakeup_sem,
})
}
fn wakeup_server(&mut self) {
// breaks the server out of its wait state. right now this is really just an optimization,
// so that if you add a watch you don't block for 100ms in watch() while the
// server sleeps.
unsafe {
ReleaseSemaphore(self.wakeup_sem, 1, ptr::null_mut());
}
}
fn send_action_require_ack(&mut self, action: Action, pb: &PathBuf) -> Result<()> {
self.tx
.send(action)
.map_err(|_| Error::generic("Error sending to internal channel"))?;
// wake 'em up, we don't want to wait around for the ack
self.wakeup_server();
let ack_pb = self
.cmd_rx
.recv()
.map_err(|_| Error::generic("Error receiving from command channel"))?
.map_err(|e| Error::generic(&format!("Error in watcher: {:?}", e)))?;
if pb.as_path() != ack_pb.as_path() {
Err(Error::generic(&format!(
"Expected ack for {:?} but got \
ack for {:?}",
pb, ack_pb
)))
} else {
Ok(())
}
}
fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
let pb = if path.is_absolute() {
path.to_owned()
} else {
let p = env::current_dir().map_err(Error::io)?;
p.join(path)
};
// path must exist and be either a file or directory
if !pb.is_dir() && !pb.is_file() {
return Err(Error::generic(
"Input watch path is neither a file nor a directory.",
));
}
self.send_action_require_ack(Action::Watch(pb.clone(), recursive_mode), &pb)
}
fn unwatch_inner(&mut self, path: &Path) -> Result<()> {
let pb = if path.is_absolute() {
path.to_owned()
} else {
let p = env::current_dir().map_err(Error::io)?;
p.join(path)
};
let res = self
.tx
.send(Action::Unwatch(pb))
.map_err(|_| Error::generic("Error sending to internal channel"));
self.wakeup_server();
res
}
}
impl Watcher for ReadDirectoryChangesWatcher {
fn new<F: EventHandler>(event_handler: F, _config: Config) -> Result<Self> {
// create dummy channel for meta event
// TODO: determine the original purpose of this - can we remove it?
let (meta_tx, _) = unbounded();
let event_handler = Arc::new(Mutex::new(event_handler));
Self::create(event_handler, meta_tx)
}
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
self.watch_inner(path, recursive_mode)
}
fn unwatch(&mut self, path: &Path) -> Result<()> {
self.unwatch_inner(path)
}
fn configure(&mut self, config: Config) -> Result<bool> {
let (tx, rx) = bounded(1);
self.tx.send(Action::Configure(config, tx))?;
rx.recv()?
}
fn kind() -> crate::WatcherKind {
WatcherKind::ReadDirectoryChangesWatcher
}
}
impl Drop for ReadDirectoryChangesWatcher {
fn drop(&mut self) {
let _ = self.tx.send(Action::Stop);
// better wake it up
self.wakeup_server();
}
}
// `ReadDirectoryChangesWatcher` is not Send/Sync because of the semaphore Handle.
// As said elsewhere it's perfectly safe to send it across threads.
unsafe impl Send for ReadDirectoryChangesWatcher {}
// Because all public methods are `&mut self` it's also perfectly safe to share references.
unsafe impl Sync for ReadDirectoryChangesWatcher {}