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. 18*96bde7d5SNicolas Bonnefon 19*96bde7d5SNicolas Bonnefon // FIXME: Where to put that so it is not dependant 2087e05652SNicolas Bonnefon // Registration object to implement RAII 2187e05652SNicolas Bonnefon using Registration = std::shared_ptr<void>; 2287e05652SNicolas Bonnefon 23*96bde7d5SNicolas Bonnefon template<typename Driver> 24*96bde7d5SNicolas Bonnefon class WatchTower { 25*96bde7d5SNicolas Bonnefon public: 2687e05652SNicolas Bonnefon // Create an empty watchtower 27b278d183SNicolas Bonnefon WatchTower(); 2887e05652SNicolas Bonnefon // Destroy the object 29c540156cSNicolas Bonnefon ~WatchTower(); 3087e05652SNicolas Bonnefon 3187e05652SNicolas Bonnefon // Add a file to the notification list. notification will be called when 3287e05652SNicolas Bonnefon // the file is modified, moved or deleted. 3387e05652SNicolas Bonnefon // Lifetime of the notification is tied to the Registration object returned. 3487e05652SNicolas Bonnefon // Note the notification function is called with a mutex held and in a 3587e05652SNicolas Bonnefon // third party thread, beware of races! 36c540156cSNicolas Bonnefon Registration addFile( const std::string& file_name, 37c540156cSNicolas Bonnefon std::function<void()> notification ); 38a0936e1eSNicolas Bonnefon 39a0936e1eSNicolas Bonnefon private: 40b278d183SNicolas Bonnefon // The driver (parametrised) 41*96bde7d5SNicolas Bonnefon Driver driver_; 42a0936e1eSNicolas Bonnefon 43c540156cSNicolas Bonnefon // List of files/dirs observed 44c540156cSNicolas Bonnefon ObservedFileList observed_file_list_; 45a0936e1eSNicolas Bonnefon 46c540156cSNicolas Bonnefon // Protects the observed_file_list_ 47c540156cSNicolas Bonnefon std::mutex observers_mutex_; 48c540156cSNicolas Bonnefon 49c540156cSNicolas Bonnefon // Thread 50c540156cSNicolas Bonnefon std::atomic_bool running_; 51c540156cSNicolas Bonnefon std::thread thread_; 52c540156cSNicolas Bonnefon 53c540156cSNicolas Bonnefon // Exist as long as the onject exists, to ensure observers won't try to 54c540156cSNicolas Bonnefon // call us if we are dead. 55c540156cSNicolas Bonnefon std::shared_ptr<void> heartBeat_; 56c540156cSNicolas Bonnefon 57c540156cSNicolas Bonnefon // Private member functions 58c540156cSNicolas Bonnefon static void removeNotification( WatchTower* watch_tower, 59c540156cSNicolas Bonnefon std::shared_ptr<void> notification ); 60c540156cSNicolas Bonnefon void run(); 6187e05652SNicolas Bonnefon }; 6284b2179eSNicolas Bonnefon 63*96bde7d5SNicolas Bonnefon // Class template implementation 64*96bde7d5SNicolas Bonnefon 65*96bde7d5SNicolas Bonnefon #include <algorithm> 66*96bde7d5SNicolas Bonnefon 67*96bde7d5SNicolas Bonnefon #include <sys/types.h> 68*96bde7d5SNicolas Bonnefon #include <sys/stat.h> 69*96bde7d5SNicolas Bonnefon #include <unistd.h> 70*96bde7d5SNicolas Bonnefon 71*96bde7d5SNicolas Bonnefon #include "log.h" 72*96bde7d5SNicolas Bonnefon 73*96bde7d5SNicolas Bonnefon namespace { 74*96bde7d5SNicolas Bonnefon bool isSymLink( const std::string& file_name ); 75*96bde7d5SNicolas Bonnefon std::string directory_path( const std::string& path ); 76*96bde7d5SNicolas Bonnefon }; 77*96bde7d5SNicolas Bonnefon 78*96bde7d5SNicolas Bonnefon template<typename Driver> 79*96bde7d5SNicolas Bonnefon WatchTower<Driver>::WatchTower() 80*96bde7d5SNicolas Bonnefon : thread_(), driver_(), 81*96bde7d5SNicolas Bonnefon heartBeat_(std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {})) 82*96bde7d5SNicolas Bonnefon { 83*96bde7d5SNicolas Bonnefon running_ = true; 84*96bde7d5SNicolas Bonnefon thread_ = std::thread( &WatchTower::run, this ); 85*96bde7d5SNicolas Bonnefon } 86*96bde7d5SNicolas Bonnefon 87*96bde7d5SNicolas Bonnefon template<typename Driver> 88*96bde7d5SNicolas Bonnefon WatchTower<Driver>::~WatchTower() 89*96bde7d5SNicolas Bonnefon { 90*96bde7d5SNicolas Bonnefon running_ = false; 91*96bde7d5SNicolas Bonnefon thread_.join(); 92*96bde7d5SNicolas Bonnefon } 93*96bde7d5SNicolas Bonnefon 94*96bde7d5SNicolas Bonnefon template<typename Driver> 95*96bde7d5SNicolas Bonnefon Registration WatchTower<Driver>::addFile( 96*96bde7d5SNicolas Bonnefon const std::string& file_name, 97*96bde7d5SNicolas Bonnefon std::function<void()> notification ) 98*96bde7d5SNicolas Bonnefon { 99*96bde7d5SNicolas Bonnefon LOG(logDEBUG) << "WatchTower::addFile " << file_name; 100*96bde7d5SNicolas Bonnefon 101*96bde7d5SNicolas Bonnefon std::lock_guard<std::mutex> lock( observers_mutex_ ); 102*96bde7d5SNicolas Bonnefon 103*96bde7d5SNicolas Bonnefon ObservedFile* existing_observed_file = 104*96bde7d5SNicolas Bonnefon observed_file_list_.searchByName( file_name ); 105*96bde7d5SNicolas Bonnefon 106*96bde7d5SNicolas Bonnefon std::shared_ptr<std::function<void()>> ptr( new std::function<void()>(std::move( notification ) ) ); 107*96bde7d5SNicolas Bonnefon 108*96bde7d5SNicolas Bonnefon if ( ! existing_observed_file ) 109*96bde7d5SNicolas Bonnefon { 110*96bde7d5SNicolas Bonnefon INotifyWatchTowerDriver::SymlinkId symlink_id; 111*96bde7d5SNicolas Bonnefon 112*96bde7d5SNicolas Bonnefon auto file_id = driver_.addFile( file_name ); 113*96bde7d5SNicolas Bonnefon 114*96bde7d5SNicolas Bonnefon if ( isSymLink( file_name ) ) 115*96bde7d5SNicolas Bonnefon { 116*96bde7d5SNicolas Bonnefon // We want to follow the name (as opposed to the inode) 117*96bde7d5SNicolas Bonnefon // so we watch the symlink as well. 118*96bde7d5SNicolas Bonnefon symlink_id = driver_.addSymlink( file_name ); 119*96bde7d5SNicolas Bonnefon } 120*96bde7d5SNicolas Bonnefon 121*96bde7d5SNicolas Bonnefon auto new_file = observed_file_list_.addNewObservedFile( 122*96bde7d5SNicolas Bonnefon ObservedFile( file_name, ptr, file_id, symlink_id ) ); 123*96bde7d5SNicolas Bonnefon 124*96bde7d5SNicolas Bonnefon auto dir = observed_file_list_.watchedDirectoryForFile( file_name ); 125*96bde7d5SNicolas Bonnefon if ( ! dir ) 126*96bde7d5SNicolas Bonnefon { 127*96bde7d5SNicolas Bonnefon LOG(logDEBUG) << "INotifyWatchTower::addFile dir for " << file_name 128*96bde7d5SNicolas Bonnefon << " not watched, adding..."; 129*96bde7d5SNicolas Bonnefon dir = observed_file_list_.addWatchedDirectoryForFile( file_name ); 130*96bde7d5SNicolas Bonnefon 131*96bde7d5SNicolas Bonnefon dir->dir_id_ = driver_.addDir( dir->path ); 132*96bde7d5SNicolas Bonnefon } 133*96bde7d5SNicolas Bonnefon 134*96bde7d5SNicolas Bonnefon new_file->dir_ = dir; 135*96bde7d5SNicolas Bonnefon } 136*96bde7d5SNicolas Bonnefon else 137*96bde7d5SNicolas Bonnefon { 138*96bde7d5SNicolas Bonnefon existing_observed_file->addCallback( ptr ); 139*96bde7d5SNicolas Bonnefon } 140*96bde7d5SNicolas Bonnefon 141*96bde7d5SNicolas Bonnefon std::weak_ptr<void> weakHeartBeat(heartBeat_); 142*96bde7d5SNicolas Bonnefon 143*96bde7d5SNicolas Bonnefon // Returns a shared pointer that removes its own entry 144*96bde7d5SNicolas Bonnefon // from the list of watched stuff when it goes out of scope! 145*96bde7d5SNicolas Bonnefon // Uses a custom deleter to do the work. 146*96bde7d5SNicolas Bonnefon return std::shared_ptr<void>( 0x0, [this, ptr, weakHeartBeat] (void*) { 147*96bde7d5SNicolas Bonnefon if ( auto heart_beat = weakHeartBeat.lock() ) 148*96bde7d5SNicolas Bonnefon removeNotification( this, ptr ); 149*96bde7d5SNicolas Bonnefon } ); 150*96bde7d5SNicolas Bonnefon } 151*96bde7d5SNicolas Bonnefon 152*96bde7d5SNicolas Bonnefon // 153*96bde7d5SNicolas Bonnefon // Private functions 154*96bde7d5SNicolas Bonnefon // 155*96bde7d5SNicolas Bonnefon 156*96bde7d5SNicolas Bonnefon // Called by the dtor for a registration object 157*96bde7d5SNicolas Bonnefon template<typename Driver> 158*96bde7d5SNicolas Bonnefon void WatchTower<Driver>::removeNotification( 159*96bde7d5SNicolas Bonnefon WatchTower* watch_tower, std::shared_ptr<void> notification ) 160*96bde7d5SNicolas Bonnefon { 161*96bde7d5SNicolas Bonnefon LOG(logDEBUG) << "WatchTower::removeNotification"; 162*96bde7d5SNicolas Bonnefon 163*96bde7d5SNicolas Bonnefon std::lock_guard<std::mutex> lock( watch_tower->observers_mutex_ ); 164*96bde7d5SNicolas Bonnefon 165*96bde7d5SNicolas Bonnefon auto file = 166*96bde7d5SNicolas Bonnefon watch_tower->observed_file_list_.removeCallback( notification ); 167*96bde7d5SNicolas Bonnefon 168*96bde7d5SNicolas Bonnefon if ( file ) 169*96bde7d5SNicolas Bonnefon { 170*96bde7d5SNicolas Bonnefon watch_tower->driver_.removeFile( file->file_id_ ); 171*96bde7d5SNicolas Bonnefon watch_tower->driver_.removeSymlink( file->symlink_id_ ); 172*96bde7d5SNicolas Bonnefon } 173*96bde7d5SNicolas Bonnefon } 174*96bde7d5SNicolas Bonnefon 175*96bde7d5SNicolas Bonnefon // Run in its own thread 176*96bde7d5SNicolas Bonnefon template<typename Driver> 177*96bde7d5SNicolas Bonnefon void WatchTower<Driver>::run() 178*96bde7d5SNicolas Bonnefon { 179*96bde7d5SNicolas Bonnefon while ( running_ ) { 180*96bde7d5SNicolas Bonnefon auto files = driver_.waitAndProcessEvents( 181*96bde7d5SNicolas Bonnefon &observed_file_list_, &observers_mutex_ ); 182*96bde7d5SNicolas Bonnefon 183*96bde7d5SNicolas Bonnefon for ( auto file: files ) { 184*96bde7d5SNicolas Bonnefon for ( auto observer: file->callbacks ) { 185*96bde7d5SNicolas Bonnefon // Here we have to cast our generic pointer back to 186*96bde7d5SNicolas Bonnefon // the function pointer in order to perform the call 187*96bde7d5SNicolas Bonnefon const std::shared_ptr<std::function<void()>> fptr = 188*96bde7d5SNicolas Bonnefon std::static_pointer_cast<std::function<void()>>( observer ); 189*96bde7d5SNicolas Bonnefon // The observer is called with the mutex held, 190*96bde7d5SNicolas Bonnefon // Let's hope it doesn't do anything too funky. 191*96bde7d5SNicolas Bonnefon (*fptr)(); 192*96bde7d5SNicolas Bonnefon } 193*96bde7d5SNicolas Bonnefon } 194*96bde7d5SNicolas Bonnefon } 195*96bde7d5SNicolas Bonnefon } 196*96bde7d5SNicolas Bonnefon 197*96bde7d5SNicolas Bonnefon namespace { 198*96bde7d5SNicolas Bonnefon bool isSymLink( const std::string& file_name ) 199*96bde7d5SNicolas Bonnefon { 200*96bde7d5SNicolas Bonnefon struct stat buf; 201*96bde7d5SNicolas Bonnefon 202*96bde7d5SNicolas Bonnefon lstat( file_name.c_str(), &buf ); 203*96bde7d5SNicolas Bonnefon return ( S_ISLNK(buf.st_mode) ); 204*96bde7d5SNicolas Bonnefon } 205*96bde7d5SNicolas Bonnefon }; 206*96bde7d5SNicolas Bonnefon 20784b2179eSNicolas Bonnefon #endif 208