2021-08-07 07:27:56 +00:00
// VDKQueue . m
// Created by Bryan D K Jones on 28 March 2012
// Copyright 2013 Bryan D K Jones
2013-01-19 05:03:00 +00:00
//
// Based heavily on UKKQueue , which was created and copyrighted by Uli Kusterer on 21 Dec 2003.
//
2021-08-07 07:27:56 +00:00
// This software is provided ' as - is ' , without any express or implied
// warranty . In no event will the authors be held liable for any damages
// arising from the use of this software .
// Permission is granted to anyone to use this software for any purpose ,
// including commercial applications , and to alter it and redistribute it
// freely , subject to the following restrictions :
// 1. The origin of this software must not be misrepresented ; you must not
// claim that you wrote the original software . If you use this software
// in a product , an acknowledgment in the product documentation would be
// appreciated but is not required .
// 2. Altered source versions must be plainly marked as such , and must not be
// misrepresented as being the original software .
// 3. This notice may not be removed or altered from any source
// distribution .
2013-01-19 05:03:00 +00:00
2016-09-10 17:08:58 +00:00
# import < AppKit / AppKit . h >
2013-01-19 05:03:00 +00:00
# import "VDKQueue.h"
2018-09-30 10:37:30 +00:00
2013-01-19 05:03:00 +00:00
# import < unistd . h >
# import < fcntl . h >
# include < sys / stat . h >
NSString * VDKQueueRenameNotification = @ "VDKQueueFileRenamedNotification" ;
NSString * VDKQueueWriteNotification = @ "VDKQueueFileWrittenToNotification" ;
NSString * VDKQueueDeleteNotification = @ "VDKQueueFileDeletedNotification" ;
NSString * VDKQueueAttributeChangeNotification = @ "VDKQueueFileAttributesChangedNotification" ;
NSString * VDKQueueSizeIncreaseNotification = @ "VDKQueueFileSizeIncreasedNotification" ;
NSString * VDKQueueLinkCountChangeNotification = @ "VDKQueueLinkCountChangedNotification" ;
NSString * VDKQueueAccessRevocationNotification = @ "VDKQueueAccessWasRevokedNotification" ;
# pragma mark -
# pragma mark VDKQueuePathEntry
# pragma mark -
# pragma - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// This is a simple model class used to hold info about each path we watch .
@ interface VDKQueuePathEntry : NSObject
{
2021-08-07 07:27:56 +00:00
NSString * _path ;
int _watchedFD ;
u_int _subscriptionFlags ;
2013-01-19 05:03:00 +00:00
}
2021-10-29 20:50:55 +00:00
- ( instancetype ) init NS_UNAVAILABLE ;
2021-08-07 07:27:56 +00:00
- ( instancetype ) initWithPath : ( NSString * ) inPath andSubscriptionFlags : ( u_int ) flags NS_DESIGNATED _INITIALIZER ;
2013-01-19 05:03:00 +00:00
@ property ( atomic , copy ) NSString * path ;
@ property ( atomic , assign ) int watchedFD ;
@ property ( atomic , assign ) u_int subscriptionFlags ;
@ end
@ implementation VDKQueuePathEntry
@ synthesize path = _path , watchedFD = _watchedFD , subscriptionFlags = _subscriptionFlags ;
2021-08-07 07:27:56 +00:00
- ( instancetype ) initWithPath : ( NSString * ) inPath andSubscriptionFlags : ( u_int ) flags ;
2013-01-19 05:03:00 +00:00
{
self = [ super init ] ;
2021-08-07 07:27:56 +00:00
if ( self )
{
_path = [ inPath copy ] ;
_watchedFD = open ( _path . fileSystemRepresentation , O_EVTONLY , 0 ) ;
if ( _watchedFD < 0 )
{
return nil ;
}
_subscriptionFlags = flags ;
}
return self ;
2013-01-19 05:03:00 +00:00
}
2021-08-07 07:27:56 +00:00
- ( void ) dealloc
2013-01-19 05:03:00 +00:00
{
2021-08-07 07:27:56 +00:00
if ( _watchedFD >= 0 ) close ( _watchedFD ) ;
_watchedFD = -1 ;
2013-01-19 05:03:00 +00:00
}
@ end
# pragma mark -
# pragma mark VDKQueue
# pragma mark -
# pragma - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ interface VDKQueue ( )
- ( void ) watcherThread : ( id ) sender ;
@ end
@ implementation VDKQueue
@ synthesize delegate = _delegate , alwaysPostNotifications = _alwaysPostNotifications ;
# pragma mark -
# pragma mark INIT / DEALLOC
2021-08-07 07:27:56 +00:00
- ( instancetype ) init
2013-01-19 05:03:00 +00:00
{
2021-08-07 07:27:56 +00:00
self = [ super init ] ;
if ( self )
{
_coreQueueFD = kqueue ( ) ;
if ( _coreQueueFD = = -1 )
{
return nil ;
}
2013-01-19 05:03:00 +00:00
_alwaysPostNotifications = NO ;
2021-08-07 07:27:56 +00:00
_watchedPathEntries = [ [ NSMutableDictionary alloc ] init ] ;
}
return self ;
2013-01-19 05:03:00 +00:00
}
- ( void ) dealloc
{
// Shut down the thread that ' s scanning for kQueue events
_keepWatcherThreadRunning = NO ;
// Do this to close all the open file descriptors for files we ' re watching
[ self removeAllPaths ] ;
_watchedPathEntries = nil ;
}
# pragma mark -
# pragma mark PRIVATE METHODS
2021-08-07 07:27:56 +00:00
- ( VDKQueuePathEntry * ) addPathToQueue : ( NSString * ) path notifyingAbout : ( u_int ) flags
2013-01-19 05:03:00 +00:00
{
2021-08-07 07:27:56 +00:00
@ synchronized ( self )
{
2013-01-19 05:03:00 +00:00
// Are we already watching this path ?
2021-08-07 07:27:56 +00:00
VDKQueuePathEntry * pathEntry = _watchedPathEntries [ path ] ;
2013-01-19 05:03:00 +00:00
if ( pathEntry )
2021-08-07 07:27:56 +00:00
{
2013-01-19 05:03:00 +00:00
// All flags already set ?
2021-08-07 07:27:56 +00:00
if ( ( pathEntry . subscriptionFlags & flags ) = = flags )
2013-01-19 05:03:00 +00:00
{
2021-08-07 07:27:56 +00:00
return pathEntry ;
2013-01-19 05:03:00 +00:00
}
2021-08-07 07:27:56 +00:00
flags | = pathEntry . subscriptionFlags ;
}
struct timespec nullts = { 0 , 0 } ;
struct kevent ev ;
if ( ! pathEntry )
2013-01-19 05:03:00 +00:00
{
2017-07-29 16:14:22 +00:00
pathEntry = [ [ VDKQueuePathEntry alloc ] initWithPath : path andSubscriptionFlags : flags ] ;
2013-01-19 05:03:00 +00:00
}
2021-08-07 07:27:56 +00:00
if ( pathEntry )
{
EV_SET ( & ev , [ pathEntry watchedFD ] , EVFILT_VNODE , EV_ADD | EV_ENABLE | EV_CLEAR , flags , 0 , ( __bridge void * ) pathEntry ) ;
pathEntry . subscriptionFlags = flags ;
2013-01-19 05:03:00 +00:00
2017-07-08 14:38:47 +00:00
_watchedPathEntries [ path ] = pathEntry ;
2013-01-19 05:03:00 +00:00
kevent ( _coreQueueFD , & ev , 1 , NULL , 0 , & nullts ) ;
2021-08-07 07:27:56 +00:00
// Start the thread that fetches and processes our events if it ' s not already running .
if ( ! _keepWatcherThreadRunning )
{
_keepWatcherThreadRunning = YES ;
[ NSThread detachNewThreadSelector : @ selector ( watcherThread : ) toTarget : self withObject : nil ] ;
}
2013-01-19 05:03:00 +00:00
}
2017-07-29 16:14:22 +00:00
return pathEntry ;
2013-01-19 05:03:00 +00:00
}
return nil ;
}
- ( void ) watcherThread : ( id ) sender
{
2021-08-07 07:27:56 +00:00
int n ;
struct kevent ev ;
2013-01-19 05:03:00 +00:00
struct timespec timeout = { 1 , 0 } ; // 1 second timeout . Should be longer , but we need this thread to exit when a kqueue is dealloced , so 1 second timeout is quite a while to wait .
2021-08-07 07:27:56 +00:00
int theFD = _coreQueueFD ; // So we don ' t have to risk accessing iVars when the thread is terminated .
2013-01-19 05:03:00 +00:00
NSMutableArray * notesToPost = [ [ NSMutableArray alloc ] initWithCapacity : 5 ] ;
# if DEBUG_LOG _THREAD _LIFETIME
2021-08-07 07:27:56 +00:00
NSLog ( @ "watcherThread started." ) ;
2013-01-19 05:03:00 +00:00
# endif
2021-08-07 07:27:56 +00:00
2013-01-19 05:03:00 +00:00
while ( _keepWatcherThreadRunning )
{
@ try
{
n = kevent ( theFD , NULL , 0 , & ev , 1 , & timeout ) ;
if ( n > 0 )
{
// NSLog ( @ "KEVENT returned %d" , n ) ;
if ( ev . filter = = EVFILT_VNODE )
{
// NSLog ( @ "KEVENT filter is EVFILT_VNODE" ) ;
if ( ev . fflags )
{
// NSLog ( @ "KEVENT flags are set" ) ;
//
// Note : VDKQueue gets tested by thousands of CodeKit users who each watch several thousand files at once .
// I was receiving about 3 EXC_BAD _ACCESS ( SIGSEGV ) crash reports a month that listed the ' path ' objc_msgSend
// as the culprit . That suggests the KEVENT is being sent back to us with a udata value that is NOT what we assigned
// to the queue , though I don ' t know why and I don ' t know why it ' s intermittent . Regardless , I ' ve added an extra
// check here to try to eliminate this ( infrequent ) problem . In theory , a KEVENT that does not have a VDKQueuePathEntry
// object attached as the udata parameter is not an event we registered for , so we should not be "missing" any events . In theory .
//
2017-07-29 16:14:22 +00:00
id pe = ( __bridge id ) ( ev . udata ) ;
2013-01-19 05:03:00 +00:00
if ( pe && [ pe respondsToSelector : @ selector ( path ) ] )
{
2017-07-29 16:14:22 +00:00
NSString * fpath = ( ( VDKQueuePathEntry * ) pe ) . path ;
2013-01-19 05:03:00 +00:00
if ( ! fpath ) continue ;
[ [ NSWorkspace sharedWorkspace ] noteFileSystemChanged : fpath ] ;
// Clear any old notifications
[ notesToPost removeAllObjects ] ;
// Figure out which notifications we need to issue
if ( ( ev . fflags & NOTE_RENAME ) = = NOTE_RENAME )
{
[ notesToPost addObject : VDKQueueRenameNotification ] ;
}
if ( ( ev . fflags & NOTE_WRITE ) = = NOTE_WRITE )
{
[ notesToPost addObject : VDKQueueWriteNotification ] ;
}
if ( ( ev . fflags & NOTE_DELETE ) = = NOTE_DELETE )
{
[ notesToPost addObject : VDKQueueDeleteNotification ] ;
}
if ( ( ev . fflags & NOTE_ATTRIB ) = = NOTE_ATTRIB )
{
[ notesToPost addObject : VDKQueueAttributeChangeNotification ] ;
}
if ( ( ev . fflags & NOTE_EXTEND ) = = NOTE_EXTEND )
{
[ notesToPost addObject : VDKQueueSizeIncreaseNotification ] ;
}
if ( ( ev . fflags & NOTE_LINK ) = = NOTE_LINK )
{
[ notesToPost addObject : VDKQueueLinkCountChangeNotification ] ;
}
if ( ( ev . fflags & NOTE_REVOKE ) = = NOTE_REVOKE )
{
[ notesToPost addObject : VDKQueueAccessRevocationNotification ] ;
}
NSArray * notes = [ [ NSArray alloc ] initWithArray : notesToPost ] ; // notesToPost will be changed in the next loop iteration , which will likely occur before the block below runs .
// Post the notifications ( or call the delegate method ) on the main thread .
dispatch_async ( dispatch_get _main _queue ( ) ,
^ {
for ( NSString * note in notes )
{
[ _delegate VDKQueue : self receivedNotification : note forPath : fpath ] ;
if ( ! _delegate || _alwaysPostNotifications )
{
2021-08-07 07:27:56 +00:00
NSDictionary * userInfoDict = @ { @ "path" : fpath } ;
[ [ NSWorkspace sharedWorkspace ] . notificationCenter postNotificationName : note object : self userInfo : userInfoDict ] ;
2013-01-19 05:03:00 +00:00
}
}
} ) ;
}
}
}
}
}
@ catch ( NSException * localException )
{
NSLog ( @ "Error in VDKQueue watcherThread: %@" , localException ) ;
}
}
2021-08-07 07:27:56 +00:00
// Close our kqueue ' s file descriptor
if ( close ( theFD ) = = -1 ) {
2013-01-19 05:03:00 +00:00
NSLog ( @ "VDKQueue watcherThread: Couldn't close main kqueue (%d)" , errno ) ;
}
# if DEBUG_LOG _THREAD _LIFETIME
2021-08-07 07:27:56 +00:00
NSLog ( @ "watcherThread finished." ) ;
2013-01-19 05:03:00 +00:00
# endif
}
# pragma mark -
# pragma mark PUBLIC METHODS
# pragma - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ( void ) addPath : ( NSString * ) aPath
{
if ( ! aPath ) return ;
@ synchronized ( self )
{
2017-07-08 14:38:47 +00:00
VDKQueuePathEntry * entry = _watchedPathEntries [ aPath ] ;
2013-01-19 05:03:00 +00:00
// Only add this path if we don ' t already have it .
if ( ! entry )
{
entry = [ self addPathToQueue : aPath notifyingAbout : VDKQueueNotifyDefault ] ;
if ( ! entry ) {
NSLog ( @ "VDKQueue tried to add the path %@ to watchedPathEntries, but the VDKQueuePathEntry was nil. \nIt's possible that the host process has hit its max open file descriptors limit." , aPath ) ;
}
}
}
}
- ( void ) addPath : ( NSString * ) aPath notifyingAbout : ( u_int ) flags
{
if ( ! aPath ) return ;
@ synchronized ( self )
{
2017-07-08 14:38:47 +00:00
VDKQueuePathEntry * entry = _watchedPathEntries [ aPath ] ;
2013-01-19 05:03:00 +00:00
// Only add this path if we don ' t already have it .
if ( ! entry )
{
entry = [ self addPathToQueue : aPath notifyingAbout : flags ] ;
if ( ! entry ) {
NSLog ( @ "VDKQueue tried to add the path %@ to watchedPathEntries, but the VDKQueuePathEntry was nil. \nIt's possible that the host process has hit its max open file descriptors limit." , aPath ) ;
}
}
}
}
- ( void ) removePath : ( NSString * ) aPath
{
if ( ! aPath ) return ;
@ synchronized ( self )
2021-08-07 07:27:56 +00:00
{
VDKQueuePathEntry * entry = _watchedPathEntries [ aPath ] ;
2013-01-19 05:03:00 +00:00
// Remove it only if we ' re watching it .
if ( entry ) {
[ _watchedPathEntries removeObjectForKey : aPath ] ;
}
2021-08-07 07:27:56 +00:00
}
2013-01-19 05:03:00 +00:00
}
- ( void ) removeAllPaths
{
@ synchronized ( self )
{
[ _watchedPathEntries removeAllObjects ] ;
}
}
- ( NSUInteger ) numberOfWatchedPaths
{
NSUInteger count ;
@ synchronized ( self )
{
2021-08-07 07:27:56 +00:00
count = _watchedPathEntries . count ;
2013-01-19 05:03:00 +00:00
}
return count ;
}
@ end