aboutsummaryrefslogtreecommitdiff
path: root/dep/efsw/src/efsw/WatcherKqueue.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dep/efsw/src/efsw/WatcherKqueue.cpp')
-rw-r--r--dep/efsw/src/efsw/WatcherKqueue.cpp667
1 files changed, 667 insertions, 0 deletions
diff --git a/dep/efsw/src/efsw/WatcherKqueue.cpp b/dep/efsw/src/efsw/WatcherKqueue.cpp
new file mode 100644
index 00000000000..8347fb53439
--- /dev/null
+++ b/dep/efsw/src/efsw/WatcherKqueue.cpp
@@ -0,0 +1,667 @@
+#include <efsw/WatcherKqueue.hpp>
+
+#if EFSW_PLATFORM == EFSW_PLATFORM_KQUEUE || EFSW_PLATFORM == EFSW_PLATFORM_FSEVENTS
+
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <efsw/Debug.hpp>
+#include <efsw/String.hpp>
+#include <efsw/System.hpp>
+#include <efsw/FileSystem.hpp>
+#include <efsw/WatcherGeneric.hpp>
+#include <efsw/FileWatcherKqueue.hpp>
+
+#define KEVENT_RESERVE_VALUE (10)
+
+#ifndef O_EVTONLY
+#define O_EVTONLY (O_RDONLY | O_NONBLOCK)
+#endif
+
+namespace efsw {
+
+int comparator(const void* ke1, const void* ke2)
+{
+ const KEvent * kev1 = reinterpret_cast<const KEvent*>( ke1 );
+ const KEvent * kev2 = reinterpret_cast<const KEvent*>( ke2 );
+
+ if ( NULL != kev2->udata )
+ {
+ FileInfo * fi1 = reinterpret_cast<FileInfo*>( kev1->udata );
+ FileInfo * fi2 = reinterpret_cast<FileInfo*>( kev2->udata );
+
+ return strcmp( fi1->Filepath.c_str(), fi2->Filepath.c_str() );
+ }
+
+ return 1;
+}
+
+WatcherKqueue::WatcherKqueue(WatchID watchid, const std::string& dirname, FileWatchListener* listener, bool recursive, FileWatcherKqueue * watcher, WatcherKqueue * parent ) :
+ Watcher( watchid, dirname, listener, recursive ),
+ mLastWatchID(0),
+ mChangeListCount( 0 ),
+ mKqueue( kqueue() ),
+ mWatcher( watcher ),
+ mParent( parent ),
+ mInitOK( true ),
+ mErrno(0)
+{
+ if ( -1 == mKqueue )
+ {
+ efDEBUG( "kqueue() returned invalid descriptor for directory %s. File descriptors count: %ld\n", Directory.c_str(), mWatcher->mFileDescriptorCount );
+
+ mInitOK = false;
+ mErrno = errno;
+ }
+ else
+ {
+ mWatcher->addFD();
+ }
+}
+
+WatcherKqueue::~WatcherKqueue()
+{
+ // Remove the childs watchers ( sub-folders watches )
+ removeAll();
+
+ for ( size_t i = 0; i < mChangeListCount; i++ )
+ {
+ if ( NULL != mChangeList[i].udata )
+ {
+ FileInfo * fi = reinterpret_cast<FileInfo*>( mChangeList[i].udata );
+
+ efSAFE_DELETE( fi );
+ }
+ }
+
+ close( mKqueue );
+
+ mWatcher->removeFD();
+}
+
+void WatcherKqueue::addAll()
+{
+ if ( -1 == mKqueue )
+ {
+ return;
+ }
+
+ // scan directory and call addFile(name, false) on each file
+ FileSystem::dirAddSlashAtEnd( Directory );
+
+ efDEBUG( "addAll(): Added folder: %s\n", Directory.c_str());
+
+ // add base dir
+ int fd = open( Directory.c_str(), O_EVTONLY );
+
+ if ( -1 == fd )
+ {
+ efDEBUG( "addAll(): Couldn't open folder: %s\n", Directory.c_str() );
+
+ if ( EACCES != errno )
+ {
+ mInitOK = false;
+ }
+
+ mErrno = errno;
+
+ return;
+ }
+
+ mDirSnap.setDirectoryInfo( Directory );
+ mDirSnap.scan();
+
+ mChangeList.resize( KEVENT_RESERVE_VALUE );
+
+ // Creates the kevent for the folder
+ EV_SET(
+ &mChangeList[0],
+ fd,
+ EVFILT_VNODE,
+ EV_ADD | EV_ENABLE | EV_ONESHOT,
+ NOTE_DELETE | NOTE_EXTEND | NOTE_WRITE | NOTE_ATTRIB | NOTE_RENAME,
+ 0,
+ 0
+ );
+
+ mWatcher->addFD();
+
+ // Get the files and directories from the directory
+ FileInfoMap files = FileSystem::filesInfoFromPath( Directory );
+
+ for ( FileInfoMap::iterator it = files.begin(); it != files.end(); it++ )
+ {
+ FileInfo& fi = it->second;
+
+ if ( fi.isRegularFile() )
+ {
+ // Add the regular files kevent
+ addFile( fi.Filepath , false );
+ }
+ else if ( Recursive && fi.isDirectory() && fi.isReadable() )
+ {
+ // Create another watcher for the subfolders ( if recursive )
+ WatchID id = addWatch( fi.Filepath, Listener, Recursive, this );
+
+ // If the watcher is not adding the watcher means that the directory was created
+ if ( id > 0 && !mWatcher->isAddingWatcher() )
+ {
+ handleFolderAction( fi.Filepath, Actions::Add );
+ }
+ }
+ }
+}
+
+void WatcherKqueue::removeAll()
+{
+ efDEBUG( "removeAll(): Removing all child watchers\n" );
+
+ std::list<WatchID> erase;
+
+ for ( WatchMap::iterator it = mWatches.begin(); it != mWatches.end(); it++ )
+ {
+ efDEBUG( "removeAll(): Removed child watcher %s\n", it->second->Directory.c_str() );
+
+ erase.push_back( it->second->ID );
+ }
+
+ for ( std::list<WatchID>::iterator eit = erase.begin(); eit != erase.end(); eit++ )
+ {
+ removeWatch( *eit );
+ }
+}
+
+void WatcherKqueue::addFile(const std::string& name, bool emitEvents)
+{
+ efDEBUG( "addFile(): Added: %s\n", name.c_str() );
+
+ // Open the file to get the file descriptor
+ int fd = open( name.c_str(), O_EVTONLY );
+
+ if( fd == -1 )
+ {
+ efDEBUG( "addFile(): Could open file descriptor for %s. File descriptor count: %ld\n", name.c_str(), mWatcher->mFileDescriptorCount );
+
+ Errors::Log::createLastError( Errors::FileNotReadable, name );
+
+ if ( EACCES != errno )
+ {
+ mInitOK = false;
+ }
+
+ mErrno = errno;
+
+ return;
+ }
+
+ mWatcher->addFD();
+
+ // increase the file kevent file count
+ mChangeListCount++;
+
+ if ( mChangeListCount + KEVENT_RESERVE_VALUE > mChangeList.size() &&
+ mChangeListCount % KEVENT_RESERVE_VALUE == 0 )
+ {
+ size_t reserve_size = mChangeList.size() + KEVENT_RESERVE_VALUE;
+ mChangeList.resize( reserve_size );
+ efDEBUG( "addFile(): Reserverd more KEvents space for %s, space reserved %ld, list actual size %ld.\n", Directory.c_str(), reserve_size, mChangeListCount );
+ }
+
+ // create entry
+ FileInfo * entry = new FileInfo( name );
+
+ // set the event data at the end of the list
+ EV_SET(
+ &mChangeList[mChangeListCount],
+ fd,
+ EVFILT_VNODE,
+ EV_ADD | EV_ENABLE | EV_ONESHOT,
+ NOTE_DELETE | NOTE_EXTEND | NOTE_WRITE | NOTE_ATTRIB | NOTE_RENAME,
+ 0,
+ (void*)entry
+ );
+
+ // qsort sort the list by name
+ qsort(&mChangeList[1], mChangeListCount, sizeof(KEvent), comparator);
+
+ // handle action
+ if( emitEvents )
+ {
+ handleAction(name, Actions::Add);
+ }
+}
+
+void WatcherKqueue::removeFile( const std::string& name, bool emitEvents )
+{
+ efDEBUG( "removeFile(): Trying to remove file: %s\n", name.c_str() );
+
+ // bsearch
+ KEvent target;
+
+ // Create a temporary file info to search the kevent ( searching the directory )
+ FileInfo tempEntry( name );
+
+ target.udata = &tempEntry;
+
+ // Search the kevent
+ KEvent * ke = (KEvent*)bsearch(&target, &mChangeList[0], mChangeListCount + 1, sizeof(KEvent), comparator);
+
+ // Trying to remove a non-existing file?
+ if( !ke )
+ {
+ Errors::Log::createLastError( Errors::FileNotFound, name );
+ efDEBUG( "File not removed\n" );
+ return;
+ }
+
+ efDEBUG( "File removed\n" );
+
+ // handle action
+ if ( emitEvents )
+ {
+ handleAction( name, Actions::Delete );
+ }
+
+ // Delete the user data ( FileInfo ) from the kevent closed
+ FileInfo * del = reinterpret_cast<FileInfo*>( ke->udata );
+
+ efSAFE_DELETE( del );
+
+ // close the file descriptor from the kevent
+ close( ke->ident );
+
+ mWatcher->removeFD();
+
+ memset(ke, 0, sizeof(KEvent));
+
+ // move end to current
+ memcpy(ke, &mChangeList[mChangeListCount], sizeof(KEvent));
+ memset(&mChangeList[mChangeListCount], 0, sizeof(KEvent));
+ --mChangeListCount;
+}
+
+void WatcherKqueue::rescan()
+{
+ efDEBUG( "rescan(): Rescanning: %s\n", Directory.c_str() );
+
+ DirectorySnapshotDiff Diff = mDirSnap.scan();
+
+ if ( Diff.DirChanged )
+ {
+ sendDirChanged();
+ }
+
+ if ( Diff.changed() )
+ {
+ FileInfoList::iterator it;
+ MovedList::iterator mit;
+
+ /// Files
+ DiffIterator( FilesCreated )
+ {
+ addFile( (*it).Filepath );
+ }
+
+ DiffIterator( FilesModified )
+ {
+ handleAction( (*it).Filepath, Actions::Modified );
+ }
+
+ DiffIterator( FilesDeleted )
+ {
+ removeFile( (*it).Filepath );
+ }
+
+ DiffMovedIterator( FilesMoved )
+ {
+ handleAction( (*mit).second.Filepath, Actions::Moved, (*mit).first );
+ removeFile( Directory + (*mit).first, false );
+ addFile( (*mit).second.Filepath, false );
+ }
+
+ /// Directories
+ DiffIterator( DirsCreated )
+ {
+ handleFolderAction( (*it).Filepath, Actions::Add );
+ addWatch( (*it).Filepath, Listener, Recursive, this );
+ }
+
+ DiffIterator( DirsModified )
+ {
+ handleFolderAction( (*it).Filepath, Actions::Modified );
+ }
+
+ DiffIterator( DirsDeleted )
+ {
+ handleFolderAction( (*it).Filepath, Actions::Delete );
+
+ Watcher * watch = findWatcher( (*it).Filepath );
+
+ if ( NULL != watch )
+ {
+ removeWatch( watch->ID );
+
+ }
+ }
+
+ DiffMovedIterator( DirsMoved )
+ {
+ moveDirectory( Directory + (*mit).first, (*mit).second.Filepath );
+ }
+ }
+}
+
+WatchID WatcherKqueue::watchingDirectory( std::string dir )
+{
+ Watcher * watch = findWatcher( dir );
+
+ if ( NULL != watch )
+ {
+ return watch->ID;
+ }
+
+ return Errors::FileNotFound;
+}
+
+void WatcherKqueue::handleAction( const std::string& filename, efsw::Action action, const std::string& oldFilename )
+{
+ Listener->handleFileAction( ID, Directory, FileSystem::fileNameFromPath( filename ), action, FileSystem::fileNameFromPath( oldFilename ) );
+}
+
+void WatcherKqueue::handleFolderAction( std::string filename, efsw::Action action , const std::string &oldFilename )
+{
+ FileSystem::dirRemoveSlashAtEnd( filename );
+
+ handleAction( filename, action, oldFilename );
+}
+
+void WatcherKqueue::sendDirChanged()
+{
+ if ( NULL != mParent )
+ {
+ Listener->handleFileAction( mParent->ID, mParent->Directory, FileSystem::fileNameFromPath( Directory ), Actions::Modified );
+ }
+}
+
+void WatcherKqueue::watch()
+{
+ if ( -1 == mKqueue )
+ {
+ return;
+ }
+
+ int nev = 0;
+ KEvent event;
+
+ // First iterate the childs, to get the events from the deepest folder, to the watcher childs
+ for ( WatchMap::iterator it = mWatches.begin(); it != mWatches.end(); ++it )
+ {
+ it->second->watch();
+ }
+
+ bool needScan = false;
+
+ // Then we get the the events of the current folder
+ while( ( nev = kevent( mKqueue, &mChangeList[0], mChangeListCount + 1, &event, 1, &mWatcher->mTimeOut ) ) != 0 )
+ {
+ // An error ocurred?
+ if( nev == -1 )
+ {
+ efDEBUG( "watch(): Error on directory %s\n", Directory.c_str() );
+ perror("kevent");
+ break;
+ }
+ else
+ {
+ FileInfo * entry = NULL;
+
+ // If udate == NULL means that it is the fisrt element of the change list, the folder.
+ // otherwise it is an event of some file inside the folder
+ if( ( entry = reinterpret_cast<FileInfo*> ( event.udata ) ) != NULL )
+ {
+ efDEBUG( "watch(): File: %s ", entry->Filepath.c_str() );
+
+ // If the event flag is delete... the file was deleted
+ if ( event.fflags & NOTE_DELETE )
+ {
+ efDEBUG( "deleted\n" );
+
+ mDirSnap.removeFile( entry->Filepath );
+
+ removeFile( entry->Filepath );
+ }
+ else if ( event.fflags & NOTE_EXTEND ||
+ event.fflags & NOTE_WRITE ||
+ event.fflags & NOTE_ATTRIB
+ )
+ {
+ // The file was modified
+ efDEBUG( "modified\n" );
+
+ FileInfo fi( entry->Filepath );
+
+ if ( fi != *entry )
+ {
+ *entry = fi;
+
+ mDirSnap.updateFile( entry->Filepath );
+
+ handleAction( entry->Filepath, efsw::Actions::Modified );
+ }
+ }
+ else if ( event.fflags & NOTE_RENAME )
+ {
+ efDEBUG( "moved\n" );
+
+ needScan = true;
+ }
+ }
+ else
+ {
+ needScan = true;
+ }
+ }
+ }
+
+ if ( needScan )
+ {
+ rescan();
+ }
+}
+
+Watcher * WatcherKqueue::findWatcher( const std::string path )
+{
+ WatchMap::iterator it = mWatches.begin();
+
+ for ( ; it != mWatches.end(); it++ )
+ {
+ if ( it->second->Directory == path )
+ {
+ return it->second;
+ }
+ }
+
+ return NULL;
+}
+
+void WatcherKqueue::moveDirectory( std::string oldPath, std::string newPath, bool emitEvents )
+{
+ // Update the directory path if it's a watcher
+ std::string opath2( oldPath );
+ FileSystem::dirAddSlashAtEnd( opath2 );
+
+ Watcher * watch = findWatcher( opath2 );
+
+ if ( NULL != watch )
+ {
+ watch->Directory = opath2;
+ }
+
+ if ( emitEvents )
+ {
+ handleFolderAction( newPath, efsw::Actions::Moved, oldPath );
+ }
+}
+
+WatchID WatcherKqueue::addWatch( const std::string& directory, FileWatchListener* watcher, bool recursive , WatcherKqueue *parent)
+{
+ static long s_fc = 0;
+ static bool s_ug = false;
+
+ std::string dir( directory );
+
+ FileSystem::dirAddSlashAtEnd( dir );
+
+ // This should never happen here
+ if( !FileSystem::isDirectory( dir ) )
+ {
+ return Errors::Log::createLastError( Errors::FileNotFound, dir );
+ }
+ else if ( pathInWatches( dir ) || pathInParent( dir ) )
+ {
+ return Errors::Log::createLastError( Errors::FileRepeated, directory );
+ }
+ else if ( NULL != parent && FileSystem::isRemoteFS( dir ) )
+ {
+ return Errors::Log::createLastError( Errors::FileRemote, dir );
+ }
+
+ std::string curPath;
+ std::string link( FileSystem::getLinkRealPath( dir, curPath ) );
+
+ if ( "" != link )
+ {
+ /// Avoid adding symlinks directories if it's now enabled
+ if ( NULL != parent && !mWatcher->mFileWatcher->followSymlinks() )
+ {
+ return Errors::Log::createLastError( Errors::FileOutOfScope, dir );
+ }
+
+ if ( pathInWatches( link ) || pathInParent( link ) )
+ {
+ return Errors::Log::createLastError( Errors::FileRepeated, link );
+ }
+ else if ( !mWatcher->linkAllowed( curPath, link ) )
+ {
+ return Errors::Log::createLastError( Errors::FileOutOfScope, link );
+ }
+ else
+ {
+ dir = link;
+ }
+ }
+
+ if ( mWatcher->availablesFD() )
+ {
+ WatcherKqueue* watch = new WatcherKqueue( ++mLastWatchID, dir, watcher, recursive, mWatcher, parent );
+
+ mWatches.insert(std::make_pair(mLastWatchID, watch));
+
+ watch->addAll();
+
+ s_fc++;
+
+ // if failed to open the directory... erase the watcher
+ if ( !watch->initOK() )
+ {
+ int le = watch->lastErrno();
+
+ mWatches.erase( watch->ID );
+
+ efSAFE_DELETE( watch );
+
+ mLastWatchID--;
+
+ // Probably the folder has too many files, create a generic watcher
+ if ( EACCES != le )
+ {
+ WatcherGeneric * watch = new WatcherGeneric( ++mLastWatchID, dir, watcher, mWatcher, recursive );
+
+ mWatches.insert(std::make_pair(mLastWatchID, watch));
+ }
+ else
+ {
+ return Errors::Log::createLastError( Errors::Unspecified, link );
+ }
+ }
+ }
+ else
+ {
+ if ( !s_ug )
+ {
+ efDEBUG( "Started using WatcherGeneric, reached file descriptors limit: %ld. Folders added: %ld\n", mWatcher->mFileDescriptorCount, s_fc );
+ s_ug = true;
+ }
+
+ WatcherGeneric * watch = new WatcherGeneric( ++mLastWatchID, dir, watcher, mWatcher, recursive );
+
+ mWatches.insert(std::make_pair(mLastWatchID, watch));
+ }
+
+ return mLastWatchID;
+}
+
+bool WatcherKqueue::initOK()
+{
+ return mInitOK;
+}
+
+void WatcherKqueue::removeWatch( WatchID watchid )
+{
+ WatchMap::iterator iter = mWatches.find(watchid);
+
+ if(iter == mWatches.end())
+ return;
+
+ Watcher * watch = iter->second;
+
+ mWatches.erase(iter);
+
+ efSAFE_DELETE( watch );
+}
+
+bool WatcherKqueue::pathInWatches( const std::string& path )
+{
+ return NULL != findWatcher( path );
+}
+
+bool WatcherKqueue::pathInParent( const std::string &path )
+{
+ WatcherKqueue * pNext = mParent;
+
+ while ( NULL != pNext )
+ {
+ if ( pNext->pathInWatches( path ) )
+ {
+ return true;
+ }
+
+ pNext = pNext->mParent;
+ }
+
+ if ( mWatcher->pathInWatches( path ) )
+ {
+ return true;
+ }
+
+ if ( path == Directory )
+ {
+ return true;
+ }
+
+ return false;
+}
+
+int WatcherKqueue::lastErrno()
+{
+ return mErrno;
+}
+
+}
+
+#endif