#include #if EFSW_PLATFORM == EFSW_PLATFORM_KQUEUE || EFSW_PLATFORM == EFSW_PLATFORM_FSEVENTS #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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( ke1 ); const KEvent* kev2 = reinterpret_cast( ke2 ); if ( NULL != kev2->udata ) { FileInfo* fi1 = reinterpret_cast( kev1->udata ); FileInfo* fi2 = reinterpret_cast( 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( 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 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::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( 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( 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; } } // namespace efsw #endif