184b2179eSNicolas Bonnefon #ifndef WATCHTOWER_H 284b2179eSNicolas Bonnefon #define WATCHTOWER_H 384b2179eSNicolas Bonnefon 4f09fa651SNicolas Bonnefon #include "config.h" 5f09fa651SNicolas Bonnefon 687e05652SNicolas Bonnefon #include <memory> 7c540156cSNicolas Bonnefon #include <atomic> 8c540156cSNicolas Bonnefon #include <thread> 9c540156cSNicolas Bonnefon #include <mutex> 1087e05652SNicolas Bonnefon 11c540156cSNicolas Bonnefon #include "watchtowerlist.h" 12b278d183SNicolas Bonnefon 13c540156cSNicolas Bonnefon // Allow the client to register for notification on an arbitrary number 14c540156cSNicolas Bonnefon // of files. It will be notified to any change (creation/deletion/modification) 15c540156cSNicolas Bonnefon // on those files. 16c540156cSNicolas Bonnefon // It is passed a platform specific driver. 1796bde7d5SNicolas Bonnefon 1896bde7d5SNicolas Bonnefon // FIXME: Where to put that so it is not dependant 1987e05652SNicolas Bonnefon // Registration object to implement RAII 2058f443c7SNicolas Bonnefon #ifdef HAS_TEMPLATE_ALIASES 2187e05652SNicolas Bonnefon using Registration = std::shared_ptr<void>; 2258f443c7SNicolas Bonnefon #else 2358f443c7SNicolas Bonnefon typedef std::shared_ptr<void> Registration; 24dc7f5916SNicolas Bonnefon #endif 2587e05652SNicolas Bonnefon 2696bde7d5SNicolas Bonnefon template<typename Driver> 2796bde7d5SNicolas Bonnefon class WatchTower { 2896bde7d5SNicolas Bonnefon public: 2987e05652SNicolas Bonnefon // Create an empty watchtower 30b278d183SNicolas Bonnefon WatchTower(); 3187e05652SNicolas Bonnefon // Destroy the object 32c540156cSNicolas Bonnefon ~WatchTower(); 3387e05652SNicolas Bonnefon 34*fcaa7557SNicolas Bonnefon // Set the polling interval (in ms) 35*fcaa7557SNicolas Bonnefon // 0 disables polling and is the default 36*fcaa7557SNicolas Bonnefon void setPollingInterval( int interval_ms ); 37*fcaa7557SNicolas Bonnefon 3887e05652SNicolas Bonnefon // Add a file to the notification list. notification will be called when 3987e05652SNicolas Bonnefon // the file is modified, moved or deleted. 4087e05652SNicolas Bonnefon // Lifetime of the notification is tied to the Registration object returned. 4187e05652SNicolas Bonnefon // Note the notification function is called with a mutex held and in a 4287e05652SNicolas Bonnefon // third party thread, beware of races! 43c540156cSNicolas Bonnefon Registration addFile( const std::string& file_name, 44c540156cSNicolas Bonnefon std::function<void()> notification ); 45a0936e1eSNicolas Bonnefon 463104b268SNicolas Bonnefon // Number of watched directories (for tests) 473104b268SNicolas Bonnefon unsigned int numberWatchedDirectories() const; 483104b268SNicolas Bonnefon 49a0936e1eSNicolas Bonnefon private: 50b278d183SNicolas Bonnefon // The driver (parametrised) 5196bde7d5SNicolas Bonnefon Driver driver_; 52a0936e1eSNicolas Bonnefon 53c540156cSNicolas Bonnefon // List of files/dirs observed 54f09fa651SNicolas Bonnefon ObservedFileList<Driver> observed_file_list_; 55a0936e1eSNicolas Bonnefon 56c540156cSNicolas Bonnefon // Protects the observed_file_list_ 57c540156cSNicolas Bonnefon std::mutex observers_mutex_; 58c540156cSNicolas Bonnefon 59*fcaa7557SNicolas Bonnefon // Polling interval (0 disables polling) 60*fcaa7557SNicolas Bonnefon uint32_t polling_interval_ms_ = 0; 61*fcaa7557SNicolas Bonnefon 62c540156cSNicolas Bonnefon // Thread 63c540156cSNicolas Bonnefon std::atomic_bool running_; 64c540156cSNicolas Bonnefon std::thread thread_; 65c540156cSNicolas Bonnefon 66c540156cSNicolas Bonnefon // Exist as long as the onject exists, to ensure observers won't try to 67c540156cSNicolas Bonnefon // call us if we are dead. 68c540156cSNicolas Bonnefon std::shared_ptr<void> heartBeat_; 69c540156cSNicolas Bonnefon 70c540156cSNicolas Bonnefon // Private member functions 7191f7c705SNicolas Bonnefon std::tuple<typename Driver::FileId, typename Driver::SymlinkId> 7291f7c705SNicolas Bonnefon addFileToDriver( const std::string& ); 73c540156cSNicolas Bonnefon static void removeNotification( WatchTower* watch_tower, 74c540156cSNicolas Bonnefon std::shared_ptr<void> notification ); 75c540156cSNicolas Bonnefon void run(); 7687e05652SNicolas Bonnefon }; 7784b2179eSNicolas Bonnefon 7896bde7d5SNicolas Bonnefon // Class template implementation 7996bde7d5SNicolas Bonnefon 8096bde7d5SNicolas Bonnefon #include <algorithm> 8196bde7d5SNicolas Bonnefon 8296bde7d5SNicolas Bonnefon #include <sys/types.h> 8396bde7d5SNicolas Bonnefon #include <sys/stat.h> 8496bde7d5SNicolas Bonnefon #include <unistd.h> 8596bde7d5SNicolas Bonnefon 8696bde7d5SNicolas Bonnefon #include "log.h" 8796bde7d5SNicolas Bonnefon 8896bde7d5SNicolas Bonnefon namespace { 8996bde7d5SNicolas Bonnefon bool isSymLink( const std::string& file_name ); 9096bde7d5SNicolas Bonnefon std::string directory_path( const std::string& path ); 9196bde7d5SNicolas Bonnefon }; 9296bde7d5SNicolas Bonnefon 9396bde7d5SNicolas Bonnefon template <typename Driver> 9496bde7d5SNicolas Bonnefon WatchTower<Driver>::WatchTower() 953104b268SNicolas Bonnefon : driver_(), thread_(), 9696bde7d5SNicolas Bonnefon heartBeat_(std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {})) 9796bde7d5SNicolas Bonnefon { 9896bde7d5SNicolas Bonnefon running_ = true; 9996bde7d5SNicolas Bonnefon thread_ = std::thread( &WatchTower::run, this ); 10096bde7d5SNicolas Bonnefon } 10196bde7d5SNicolas Bonnefon 10296bde7d5SNicolas Bonnefon template <typename Driver> 10396bde7d5SNicolas Bonnefon WatchTower<Driver>::~WatchTower() 10496bde7d5SNicolas Bonnefon { 10596bde7d5SNicolas Bonnefon running_ = false; 106b0345991SNicolas Bonnefon driver_.interruptWait(); 10796bde7d5SNicolas Bonnefon thread_.join(); 10896bde7d5SNicolas Bonnefon } 10996bde7d5SNicolas Bonnefon 11096bde7d5SNicolas Bonnefon template <typename Driver> 111*fcaa7557SNicolas Bonnefon void WatchTower<Driver>::setPollingInterval( int interval_ms ) 112*fcaa7557SNicolas Bonnefon { 113*fcaa7557SNicolas Bonnefon polling_interval_ms_ = interval_ms; 114*fcaa7557SNicolas Bonnefon } 115*fcaa7557SNicolas Bonnefon 116*fcaa7557SNicolas Bonnefon template <typename Driver> 11796bde7d5SNicolas Bonnefon Registration WatchTower<Driver>::addFile( 11896bde7d5SNicolas Bonnefon const std::string& file_name, 11996bde7d5SNicolas Bonnefon std::function<void()> notification ) 12096bde7d5SNicolas Bonnefon { 121f09fa651SNicolas Bonnefon // LOG(logDEBUG) << "WatchTower::addFile " << file_name; 12296bde7d5SNicolas Bonnefon 1233104b268SNicolas Bonnefon std::weak_ptr<void> weakHeartBeat(heartBeat_); 1243104b268SNicolas Bonnefon 12596bde7d5SNicolas Bonnefon std::lock_guard<std::mutex> lock( observers_mutex_ ); 12696bde7d5SNicolas Bonnefon 127f09fa651SNicolas Bonnefon auto existing_observed_file = 12896bde7d5SNicolas Bonnefon observed_file_list_.searchByName( file_name ); 12996bde7d5SNicolas Bonnefon 13096bde7d5SNicolas Bonnefon std::shared_ptr<std::function<void()>> ptr( new std::function<void()>(std::move( notification )) ); 13196bde7d5SNicolas Bonnefon 13296bde7d5SNicolas Bonnefon if ( ! existing_observed_file ) 13396bde7d5SNicolas Bonnefon { 13491f7c705SNicolas Bonnefon typename Driver::FileId file_id; 135f09fa651SNicolas Bonnefon typename Driver::SymlinkId symlink_id; 13696bde7d5SNicolas Bonnefon 13791f7c705SNicolas Bonnefon std::tie( file_id, symlink_id ) = addFileToDriver( file_name ); 13896bde7d5SNicolas Bonnefon auto new_file = observed_file_list_.addNewObservedFile( 139f09fa651SNicolas Bonnefon ObservedFile<Driver>( file_name, ptr, file_id, symlink_id ) ); 14096bde7d5SNicolas Bonnefon 14196bde7d5SNicolas Bonnefon auto dir = observed_file_list_.watchedDirectoryForFile( file_name ); 1428b11848fSNicolas Bonnefon if ( ! dir ) { 143f09fa651SNicolas Bonnefon LOG(logDEBUG) << "WatchTower::addFile dir for " << file_name 14496bde7d5SNicolas Bonnefon << " not watched, adding..."; 1458b11848fSNicolas Bonnefon 1463104b268SNicolas Bonnefon dir = observed_file_list_.addWatchedDirectoryForFile( file_name, 1473104b268SNicolas Bonnefon [this, weakHeartBeat] (ObservedDir<Driver>* dir) { 1483104b268SNicolas Bonnefon if ( auto heart_beat = weakHeartBeat.lock() ) { 1493104b268SNicolas Bonnefon driver_.removeDir( dir->dir_id_ ); 1503104b268SNicolas Bonnefon } } ); 15196bde7d5SNicolas Bonnefon 15296bde7d5SNicolas Bonnefon dir->dir_id_ = driver_.addDir( dir->path ); 1538b11848fSNicolas Bonnefon 1548b11848fSNicolas Bonnefon if ( ! dir->dir_id_.valid() ) { 1558b11848fSNicolas Bonnefon LOG(logWARNING) << "WatchTower::addFile driver failed to add dir"; 1568b11848fSNicolas Bonnefon dir = nullptr; 1578b11848fSNicolas Bonnefon } 1588b11848fSNicolas Bonnefon } 1598b11848fSNicolas Bonnefon else { 1608b11848fSNicolas Bonnefon LOG(logDEBUG) << "WatchTower::addFile Found exisiting watch for dir " << file_name; 16196bde7d5SNicolas Bonnefon } 16296bde7d5SNicolas Bonnefon 163f09fa651SNicolas Bonnefon // Associate the dir to the file 1648b11848fSNicolas Bonnefon if ( dir ) 16596bde7d5SNicolas Bonnefon new_file->dir_ = dir; 16696bde7d5SNicolas Bonnefon } 16796bde7d5SNicolas Bonnefon else 16896bde7d5SNicolas Bonnefon { 16996bde7d5SNicolas Bonnefon existing_observed_file->addCallback( ptr ); 17096bde7d5SNicolas Bonnefon } 17196bde7d5SNicolas Bonnefon 17296bde7d5SNicolas Bonnefon // Returns a shared pointer that removes its own entry 17396bde7d5SNicolas Bonnefon // from the list of watched stuff when it goes out of scope! 17496bde7d5SNicolas Bonnefon // Uses a custom deleter to do the work. 17596bde7d5SNicolas Bonnefon return std::shared_ptr<void>( 0x0, [this, ptr, weakHeartBeat] (void*) { 17696bde7d5SNicolas Bonnefon if ( auto heart_beat = weakHeartBeat.lock() ) 177dc7f5916SNicolas Bonnefon WatchTower<Driver>::removeNotification( this, ptr ); 17896bde7d5SNicolas Bonnefon } ); 17996bde7d5SNicolas Bonnefon } 18096bde7d5SNicolas Bonnefon 1813104b268SNicolas Bonnefon template <typename Driver> 1823104b268SNicolas Bonnefon unsigned int WatchTower<Driver>::numberWatchedDirectories() const 1833104b268SNicolas Bonnefon { 1843104b268SNicolas Bonnefon return observed_file_list_.numberWatchedDirectories(); 1853104b268SNicolas Bonnefon } 1863104b268SNicolas Bonnefon 18796bde7d5SNicolas Bonnefon // 18896bde7d5SNicolas Bonnefon // Private functions 18996bde7d5SNicolas Bonnefon // 19096bde7d5SNicolas Bonnefon 19191f7c705SNicolas Bonnefon // Add the passed file name to the driver, returning the file and symlink id 19291f7c705SNicolas Bonnefon template <typename Driver> 19391f7c705SNicolas Bonnefon std::tuple<typename Driver::FileId, typename Driver::SymlinkId> 19491f7c705SNicolas Bonnefon WatchTower<Driver>::addFileToDriver( const std::string& file_name ) 19591f7c705SNicolas Bonnefon { 19691f7c705SNicolas Bonnefon typename Driver::SymlinkId symlink_id; 19791f7c705SNicolas Bonnefon auto file_id = driver_.addFile( file_name ); 19891f7c705SNicolas Bonnefon 19991f7c705SNicolas Bonnefon if ( isSymLink( file_name ) ) 20091f7c705SNicolas Bonnefon { 20191f7c705SNicolas Bonnefon // We want to follow the name (as opposed to the inode) 20291f7c705SNicolas Bonnefon // so we watch the symlink as well. 20391f7c705SNicolas Bonnefon symlink_id = driver_.addSymlink( file_name ); 20491f7c705SNicolas Bonnefon } 20591f7c705SNicolas Bonnefon 20691f7c705SNicolas Bonnefon return std::make_tuple( file_id, symlink_id ); 20791f7c705SNicolas Bonnefon } 20891f7c705SNicolas Bonnefon 20996bde7d5SNicolas Bonnefon // Called by the dtor for a registration object 21096bde7d5SNicolas Bonnefon template <typename Driver> 21196bde7d5SNicolas Bonnefon void WatchTower<Driver>::removeNotification( 21296bde7d5SNicolas Bonnefon WatchTower* watch_tower, std::shared_ptr<void> notification ) 21396bde7d5SNicolas Bonnefon { 21496bde7d5SNicolas Bonnefon LOG(logDEBUG) << "WatchTower::removeNotification"; 21596bde7d5SNicolas Bonnefon 21696bde7d5SNicolas Bonnefon std::lock_guard<std::mutex> lock( watch_tower->observers_mutex_ ); 21796bde7d5SNicolas Bonnefon 21896bde7d5SNicolas Bonnefon auto file = 21996bde7d5SNicolas Bonnefon watch_tower->observed_file_list_.removeCallback( notification ); 22096bde7d5SNicolas Bonnefon 22196bde7d5SNicolas Bonnefon if ( file ) 22296bde7d5SNicolas Bonnefon { 22396bde7d5SNicolas Bonnefon watch_tower->driver_.removeFile( file->file_id_ ); 22496bde7d5SNicolas Bonnefon watch_tower->driver_.removeSymlink( file->symlink_id_ ); 22596bde7d5SNicolas Bonnefon } 22696bde7d5SNicolas Bonnefon } 22796bde7d5SNicolas Bonnefon 22896bde7d5SNicolas Bonnefon // Run in its own thread 22996bde7d5SNicolas Bonnefon template <typename Driver> 23096bde7d5SNicolas Bonnefon void WatchTower<Driver>::run() 23196bde7d5SNicolas Bonnefon { 23296bde7d5SNicolas Bonnefon while ( running_ ) { 2333104b268SNicolas Bonnefon std::unique_lock<std::mutex> lock( observers_mutex_ ); 2343104b268SNicolas Bonnefon 23591f7c705SNicolas Bonnefon std::vector<ObservedFile<Driver>*> files_needing_readding; 23691f7c705SNicolas Bonnefon 23796bde7d5SNicolas Bonnefon auto files = driver_.waitAndProcessEvents( 238*fcaa7557SNicolas Bonnefon &observed_file_list_, &lock, &files_needing_readding, polling_interval_ms_ ); 2393104b268SNicolas Bonnefon LOG(logDEBUG) << "WatchTower::run: waitAndProcessEvents returned " 24091f7c705SNicolas Bonnefon << files.size() << " files, " << files_needing_readding.size() 24191f7c705SNicolas Bonnefon << " needing re-adding"; 24291f7c705SNicolas Bonnefon 24391f7c705SNicolas Bonnefon for ( auto file: files_needing_readding ) { 24491f7c705SNicolas Bonnefon // A file 'needing readding' has the same name, 24591f7c705SNicolas Bonnefon // but probably a different inode, so it needs 24691f7c705SNicolas Bonnefon // to be readded for some drivers that rely on the 24791f7c705SNicolas Bonnefon // inode (e.g. inotify) 24891f7c705SNicolas Bonnefon driver_.removeFile( file->file_id_ ); 24991f7c705SNicolas Bonnefon driver_.removeSymlink( file->symlink_id_ ); 25091f7c705SNicolas Bonnefon 25191f7c705SNicolas Bonnefon std::tie( file->file_id_, file->symlink_id_ ) = 25291f7c705SNicolas Bonnefon addFileToDriver( file->file_name_ ); 25391f7c705SNicolas Bonnefon } 25496bde7d5SNicolas Bonnefon 25596bde7d5SNicolas Bonnefon for ( auto file: files ) { 25696bde7d5SNicolas Bonnefon for ( auto observer: file->callbacks ) { 2573104b268SNicolas Bonnefon LOG(logDEBUG) << "WatchTower::run: notifying the client!"; 25896bde7d5SNicolas Bonnefon // Here we have to cast our generic pointer back to 25996bde7d5SNicolas Bonnefon // the function pointer in order to perform the call 26096bde7d5SNicolas Bonnefon const std::shared_ptr<std::function<void()>> fptr = 26196bde7d5SNicolas Bonnefon std::static_pointer_cast<std::function<void()>>( observer ); 26296bde7d5SNicolas Bonnefon // The observer is called with the mutex held, 26396bde7d5SNicolas Bonnefon // Let's hope it doesn't do anything too funky. 26496bde7d5SNicolas Bonnefon (*fptr)(); 265*fcaa7557SNicolas Bonnefon 266*fcaa7557SNicolas Bonnefon file->markAsChanged(); 267*fcaa7557SNicolas Bonnefon } 268*fcaa7557SNicolas Bonnefon } 269*fcaa7557SNicolas Bonnefon 270*fcaa7557SNicolas Bonnefon if ( polling_interval_ms_ > 0 ) { 271*fcaa7557SNicolas Bonnefon // Also call files that have not been called for a while 272*fcaa7557SNicolas Bonnefon for ( auto file: observed_file_list_ ) { 273*fcaa7557SNicolas Bonnefon uint32_t ms_since_last_check = 274*fcaa7557SNicolas Bonnefon std::chrono::duration_cast<std::chrono::milliseconds>( 275*fcaa7557SNicolas Bonnefon std::chrono::steady_clock::now() - file->timeForLastCheck() ).count(); 276*fcaa7557SNicolas Bonnefon if ( ( ms_since_last_check > polling_interval_ms_ ) && file->hasChanged() ) { 277*fcaa7557SNicolas Bonnefon LOG(logDEBUG) << "WatchTower::run: " << file->file_name_; 278*fcaa7557SNicolas Bonnefon for ( auto observer: file->callbacks ) { 279*fcaa7557SNicolas Bonnefon LOG(logDEBUG) << "WatchTower::run: notifying the client because of a timeout!"; 280*fcaa7557SNicolas Bonnefon // Here we have to cast our generic pointer back to 281*fcaa7557SNicolas Bonnefon // the function pointer in order to perform the call 282*fcaa7557SNicolas Bonnefon const std::shared_ptr<std::function<void()>> fptr = 283*fcaa7557SNicolas Bonnefon std::static_pointer_cast<std::function<void()>>( observer ); 284*fcaa7557SNicolas Bonnefon // The observer is called with the mutex held, 285*fcaa7557SNicolas Bonnefon // Let's hope it doesn't do anything too funky. 286*fcaa7557SNicolas Bonnefon (*fptr)(); 287*fcaa7557SNicolas Bonnefon } 288*fcaa7557SNicolas Bonnefon file->markAsChanged(); 289*fcaa7557SNicolas Bonnefon } 29096bde7d5SNicolas Bonnefon } 29196bde7d5SNicolas Bonnefon } 29296bde7d5SNicolas Bonnefon } 29396bde7d5SNicolas Bonnefon } 29496bde7d5SNicolas Bonnefon 29596bde7d5SNicolas Bonnefon namespace { 29696bde7d5SNicolas Bonnefon bool isSymLink( const std::string& file_name ) 29796bde7d5SNicolas Bonnefon { 298f09fa651SNicolas Bonnefon #ifdef HAVE_SYMLINK 29996bde7d5SNicolas Bonnefon struct stat buf; 30096bde7d5SNicolas Bonnefon 30196bde7d5SNicolas Bonnefon lstat( file_name.c_str(), &buf ); 30296bde7d5SNicolas Bonnefon return ( S_ISLNK(buf.st_mode) ); 303f09fa651SNicolas Bonnefon #else 304f09fa651SNicolas Bonnefon return false; 305f09fa651SNicolas Bonnefon #endif 30696bde7d5SNicolas Bonnefon } 30796bde7d5SNicolas Bonnefon }; 30896bde7d5SNicolas Bonnefon 30984b2179eSNicolas Bonnefon #endif 310