diff options
Diffstat (limited to 'dep/efsw/src/efsw/WatcherKqueue.cpp')
-rw-r--r-- | dep/efsw/src/efsw/WatcherKqueue.cpp | 667 |
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 |