184b2179eSNicolas Bonnefon #ifndef WATCHTOWER_H 284b2179eSNicolas Bonnefon #define WATCHTOWER_H 384b2179eSNicolas Bonnefon 487e05652SNicolas Bonnefon #include <memory> 5c540156cSNicolas Bonnefon #include <atomic> 6c540156cSNicolas Bonnefon #include <thread> 7c540156cSNicolas Bonnefon #include <mutex> 887e05652SNicolas Bonnefon 9c540156cSNicolas Bonnefon #include "watchtowerlist.h" 10b278d183SNicolas Bonnefon 11b278d183SNicolas Bonnefon // FIXME 12b278d183SNicolas Bonnefon #include "inotifywatchtowerdriver.h" 13c540156cSNicolas Bonnefon 14c540156cSNicolas Bonnefon // Allow the client to register for notification on an arbitrary number 15c540156cSNicolas Bonnefon // of files. It will be notified to any change (creation/deletion/modification) 16c540156cSNicolas Bonnefon // on those files. 17c540156cSNicolas Bonnefon // It is passed a platform specific driver. 1896bde7d5SNicolas Bonnefon 1996bde7d5SNicolas Bonnefon // FIXME: Where to put that so it is not dependant 2087e05652SNicolas Bonnefon // Registration object to implement RAII 21dc7f5916SNicolas Bonnefon #if __GNUC_MINOR__ < 7 22dc7f5916SNicolas Bonnefon typedef std::shared_ptr<void> Registration; 23dc7f5916SNicolas Bonnefon #else 2487e05652SNicolas Bonnefon using Registration = std::shared_ptr<void>; 25dc7f5916SNicolas Bonnefon #endif 2687e05652SNicolas Bonnefon 2796bde7d5SNicolas Bonnefon template<typename Driver> 2896bde7d5SNicolas Bonnefon class WatchTower { 2996bde7d5SNicolas Bonnefon public: 3087e05652SNicolas Bonnefon // Create an empty watchtower 31b278d183SNicolas Bonnefon WatchTower(); 3287e05652SNicolas Bonnefon // Destroy the object 33c540156cSNicolas Bonnefon ~WatchTower(); 3487e05652SNicolas Bonnefon 3587e05652SNicolas Bonnefon // Add a file to the notification list. notification will be called when 3687e05652SNicolas Bonnefon // the file is modified, moved or deleted. 3787e05652SNicolas Bonnefon // Lifetime of the notification is tied to the Registration object returned. 3887e05652SNicolas Bonnefon // Note the notification function is called with a mutex held and in a 3987e05652SNicolas Bonnefon // third party thread, beware of races! 40c540156cSNicolas Bonnefon Registration addFile( const std::string& file_name, 41c540156cSNicolas Bonnefon std::function<void()> notification ); 42a0936e1eSNicolas Bonnefon 43a0936e1eSNicolas Bonnefon private: 44b278d183SNicolas Bonnefon // The driver (parametrised) 4596bde7d5SNicolas Bonnefon Driver driver_; 46a0936e1eSNicolas Bonnefon 47c540156cSNicolas Bonnefon // List of files/dirs observed 48c540156cSNicolas Bonnefon ObservedFileList observed_file_list_; 49a0936e1eSNicolas Bonnefon 50c540156cSNicolas Bonnefon // Protects the observed_file_list_ 51c540156cSNicolas Bonnefon std::mutex observers_mutex_; 52c540156cSNicolas Bonnefon 53c540156cSNicolas Bonnefon // Thread 54c540156cSNicolas Bonnefon std::atomic_bool running_; 55c540156cSNicolas Bonnefon std::thread thread_; 56c540156cSNicolas Bonnefon 57c540156cSNicolas Bonnefon // Exist as long as the onject exists, to ensure observers won't try to 58c540156cSNicolas Bonnefon // call us if we are dead. 59c540156cSNicolas Bonnefon std::shared_ptr<void> heartBeat_; 60c540156cSNicolas Bonnefon 61c540156cSNicolas Bonnefon // Private member functions 62c540156cSNicolas Bonnefon static void removeNotification( WatchTower* watch_tower, 63c540156cSNicolas Bonnefon std::shared_ptr<void> notification ); 64c540156cSNicolas Bonnefon void run(); 6587e05652SNicolas Bonnefon }; 6684b2179eSNicolas Bonnefon 6796bde7d5SNicolas Bonnefon // Class template implementation 6896bde7d5SNicolas Bonnefon 6996bde7d5SNicolas Bonnefon #include <algorithm> 7096bde7d5SNicolas Bonnefon 7196bde7d5SNicolas Bonnefon #include <sys/types.h> 7296bde7d5SNicolas Bonnefon #include <sys/stat.h> 7396bde7d5SNicolas Bonnefon #include <unistd.h> 7496bde7d5SNicolas Bonnefon 7596bde7d5SNicolas Bonnefon #include "log.h" 7696bde7d5SNicolas Bonnefon 7796bde7d5SNicolas Bonnefon namespace { 7896bde7d5SNicolas Bonnefon bool isSymLink( const std::string& file_name ); 7996bde7d5SNicolas Bonnefon std::string directory_path( const std::string& path ); 8096bde7d5SNicolas Bonnefon }; 8196bde7d5SNicolas Bonnefon 8296bde7d5SNicolas Bonnefon template<typename Driver> 8396bde7d5SNicolas Bonnefon WatchTower<Driver>::WatchTower() 8496bde7d5SNicolas Bonnefon : thread_(), driver_(), 8596bde7d5SNicolas Bonnefon heartBeat_(std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {})) 8696bde7d5SNicolas Bonnefon { 8796bde7d5SNicolas Bonnefon running_ = true; 8896bde7d5SNicolas Bonnefon thread_ = std::thread( &WatchTower::run, this ); 8996bde7d5SNicolas Bonnefon } 9096bde7d5SNicolas Bonnefon 9196bde7d5SNicolas Bonnefon template<typename Driver> 9296bde7d5SNicolas Bonnefon WatchTower<Driver>::~WatchTower() 9396bde7d5SNicolas Bonnefon { 9496bde7d5SNicolas Bonnefon running_ = false; 95*b0345991SNicolas Bonnefon driver_.interruptWait(); 9696bde7d5SNicolas Bonnefon thread_.join(); 9796bde7d5SNicolas Bonnefon } 9896bde7d5SNicolas Bonnefon 9996bde7d5SNicolas Bonnefon template<typename Driver> 10096bde7d5SNicolas Bonnefon Registration WatchTower<Driver>::addFile( 10196bde7d5SNicolas Bonnefon const std::string& file_name, 10296bde7d5SNicolas Bonnefon std::function<void()> notification ) 10396bde7d5SNicolas Bonnefon { 10496bde7d5SNicolas Bonnefon LOG(logDEBUG) << "WatchTower::addFile " << file_name; 10596bde7d5SNicolas Bonnefon 10696bde7d5SNicolas Bonnefon std::lock_guard<std::mutex> lock( observers_mutex_ ); 10796bde7d5SNicolas Bonnefon 10896bde7d5SNicolas Bonnefon ObservedFile* existing_observed_file = 10996bde7d5SNicolas Bonnefon observed_file_list_.searchByName( file_name ); 11096bde7d5SNicolas Bonnefon 11196bde7d5SNicolas Bonnefon std::shared_ptr<std::function<void()>> ptr( new std::function<void()>(std::move( notification ) ) ); 11296bde7d5SNicolas Bonnefon 11396bde7d5SNicolas Bonnefon if ( ! existing_observed_file ) 11496bde7d5SNicolas Bonnefon { 11596bde7d5SNicolas Bonnefon INotifyWatchTowerDriver::SymlinkId symlink_id; 11696bde7d5SNicolas Bonnefon 11796bde7d5SNicolas Bonnefon auto file_id = driver_.addFile( file_name ); 11896bde7d5SNicolas Bonnefon 11996bde7d5SNicolas Bonnefon if ( isSymLink( file_name ) ) 12096bde7d5SNicolas Bonnefon { 12196bde7d5SNicolas Bonnefon // We want to follow the name (as opposed to the inode) 12296bde7d5SNicolas Bonnefon // so we watch the symlink as well. 12396bde7d5SNicolas Bonnefon symlink_id = driver_.addSymlink( file_name ); 12496bde7d5SNicolas Bonnefon } 12596bde7d5SNicolas Bonnefon 12696bde7d5SNicolas Bonnefon auto new_file = observed_file_list_.addNewObservedFile( 12796bde7d5SNicolas Bonnefon ObservedFile( file_name, ptr, file_id, symlink_id ) ); 12896bde7d5SNicolas Bonnefon 12996bde7d5SNicolas Bonnefon auto dir = observed_file_list_.watchedDirectoryForFile( file_name ); 13096bde7d5SNicolas Bonnefon if ( ! dir ) 13196bde7d5SNicolas Bonnefon { 13296bde7d5SNicolas Bonnefon LOG(logDEBUG) << "INotifyWatchTower::addFile dir for " << file_name 13396bde7d5SNicolas Bonnefon << " not watched, adding..."; 13496bde7d5SNicolas Bonnefon dir = observed_file_list_.addWatchedDirectoryForFile( file_name ); 13596bde7d5SNicolas Bonnefon 13696bde7d5SNicolas Bonnefon dir->dir_id_ = driver_.addDir( dir->path ); 13796bde7d5SNicolas Bonnefon } 13896bde7d5SNicolas Bonnefon 13996bde7d5SNicolas Bonnefon new_file->dir_ = dir; 14096bde7d5SNicolas Bonnefon } 14196bde7d5SNicolas Bonnefon else 14296bde7d5SNicolas Bonnefon { 14396bde7d5SNicolas Bonnefon existing_observed_file->addCallback( ptr ); 14496bde7d5SNicolas Bonnefon } 14596bde7d5SNicolas Bonnefon 14696bde7d5SNicolas Bonnefon std::weak_ptr<void> weakHeartBeat(heartBeat_); 14796bde7d5SNicolas Bonnefon 14896bde7d5SNicolas Bonnefon // Returns a shared pointer that removes its own entry 14996bde7d5SNicolas Bonnefon // from the list of watched stuff when it goes out of scope! 15096bde7d5SNicolas Bonnefon // Uses a custom deleter to do the work. 15196bde7d5SNicolas Bonnefon return std::shared_ptr<void>( 0x0, [this, ptr, weakHeartBeat] (void*) { 15296bde7d5SNicolas Bonnefon if ( auto heart_beat = weakHeartBeat.lock() ) 153dc7f5916SNicolas Bonnefon WatchTower<Driver>::removeNotification( this, ptr ); 15496bde7d5SNicolas Bonnefon } ); 15596bde7d5SNicolas Bonnefon } 15696bde7d5SNicolas Bonnefon 15796bde7d5SNicolas Bonnefon // 15896bde7d5SNicolas Bonnefon // Private functions 15996bde7d5SNicolas Bonnefon // 16096bde7d5SNicolas Bonnefon 16196bde7d5SNicolas Bonnefon // Called by the dtor for a registration object 16296bde7d5SNicolas Bonnefon template<typename Driver> 16396bde7d5SNicolas Bonnefon void WatchTower<Driver>::removeNotification( 16496bde7d5SNicolas Bonnefon WatchTower* watch_tower, std::shared_ptr<void> notification ) 16596bde7d5SNicolas Bonnefon { 16696bde7d5SNicolas Bonnefon LOG(logDEBUG) << "WatchTower::removeNotification"; 16796bde7d5SNicolas Bonnefon 16896bde7d5SNicolas Bonnefon std::lock_guard<std::mutex> lock( watch_tower->observers_mutex_ ); 16996bde7d5SNicolas Bonnefon 17096bde7d5SNicolas Bonnefon auto file = 17196bde7d5SNicolas Bonnefon watch_tower->observed_file_list_.removeCallback( notification ); 17296bde7d5SNicolas Bonnefon 17396bde7d5SNicolas Bonnefon if ( file ) 17496bde7d5SNicolas Bonnefon { 17596bde7d5SNicolas Bonnefon watch_tower->driver_.removeFile( file->file_id_ ); 17696bde7d5SNicolas Bonnefon watch_tower->driver_.removeSymlink( file->symlink_id_ ); 17796bde7d5SNicolas Bonnefon } 17896bde7d5SNicolas Bonnefon } 17996bde7d5SNicolas Bonnefon 18096bde7d5SNicolas Bonnefon // Run in its own thread 18196bde7d5SNicolas Bonnefon template<typename Driver> 18296bde7d5SNicolas Bonnefon void WatchTower<Driver>::run() 18396bde7d5SNicolas Bonnefon { 18496bde7d5SNicolas Bonnefon while ( running_ ) { 18596bde7d5SNicolas Bonnefon auto files = driver_.waitAndProcessEvents( 18696bde7d5SNicolas Bonnefon &observed_file_list_, &observers_mutex_ ); 18796bde7d5SNicolas Bonnefon 18896bde7d5SNicolas Bonnefon for ( auto file: files ) { 18996bde7d5SNicolas Bonnefon for ( auto observer: file->callbacks ) { 19096bde7d5SNicolas Bonnefon // Here we have to cast our generic pointer back to 19196bde7d5SNicolas Bonnefon // the function pointer in order to perform the call 19296bde7d5SNicolas Bonnefon const std::shared_ptr<std::function<void()>> fptr = 19396bde7d5SNicolas Bonnefon std::static_pointer_cast<std::function<void()>>( observer ); 19496bde7d5SNicolas Bonnefon // The observer is called with the mutex held, 19596bde7d5SNicolas Bonnefon // Let's hope it doesn't do anything too funky. 19696bde7d5SNicolas Bonnefon (*fptr)(); 19796bde7d5SNicolas Bonnefon } 19896bde7d5SNicolas Bonnefon } 19996bde7d5SNicolas Bonnefon } 20096bde7d5SNicolas Bonnefon } 20196bde7d5SNicolas Bonnefon 20296bde7d5SNicolas Bonnefon namespace { 20396bde7d5SNicolas Bonnefon bool isSymLink( const std::string& file_name ) 20496bde7d5SNicolas Bonnefon { 20596bde7d5SNicolas Bonnefon struct stat buf; 20696bde7d5SNicolas Bonnefon 20796bde7d5SNicolas Bonnefon lstat( file_name.c_str(), &buf ); 20896bde7d5SNicolas Bonnefon return ( S_ISLNK(buf.st_mode) ); 20996bde7d5SNicolas Bonnefon } 21096bde7d5SNicolas Bonnefon }; 21196bde7d5SNicolas Bonnefon 21284b2179eSNicolas Bonnefon #endif 213