1c540156cSNicolas Bonnefon #ifndef WATCHTOWERLIST_H 2c540156cSNicolas Bonnefon #define WATCHTOWERLIST_H 3c540156cSNicolas Bonnefon 4c540156cSNicolas Bonnefon // Utility classes for the WatchTower implementations 5c540156cSNicolas Bonnefon 6c540156cSNicolas Bonnefon #include <functional> 7c540156cSNicolas Bonnefon #include <string> 8c540156cSNicolas Bonnefon #include <vector> 9c540156cSNicolas Bonnefon #include <map> 10c540156cSNicolas Bonnefon #include <list> 11c540156cSNicolas Bonnefon #include <memory> 12c540156cSNicolas Bonnefon #include <algorithm> 13c540156cSNicolas Bonnefon 14f09fa651SNicolas Bonnefon #include "log.h" 15b278d183SNicolas Bonnefon 16c540156cSNicolas Bonnefon // Utility classes 17c540156cSNicolas Bonnefon struct ProtocolInfo { 18c540156cSNicolas Bonnefon // Win32 notification variables 19c540156cSNicolas Bonnefon static const int READ_DIR_CHANGE_BUFFER_SIZE = 4096; 20c540156cSNicolas Bonnefon 21c540156cSNicolas Bonnefon void* handle_; 22c540156cSNicolas Bonnefon static const unsigned long buffer_length_ = READ_DIR_CHANGE_BUFFER_SIZE; 23c540156cSNicolas Bonnefon char buffer_[buffer_length_]; 24c540156cSNicolas Bonnefon }; 25c540156cSNicolas Bonnefon 26c540156cSNicolas Bonnefon // List of files and observers 27f09fa651SNicolas Bonnefon template <typename Driver> 28c540156cSNicolas Bonnefon struct ObservedDir { 29c540156cSNicolas Bonnefon ObservedDir( const std::string this_path ) : path { this_path } {} 30c540156cSNicolas Bonnefon 31c540156cSNicolas Bonnefon // Returns the address of the protocol specific informations 32c540156cSNicolas Bonnefon ProtocolInfo* protocolInfo() { return &protocol_info_; } 33c540156cSNicolas Bonnefon 34c540156cSNicolas Bonnefon std::string path; 35f09fa651SNicolas Bonnefon typename Driver::DirId dir_id_; 36c540156cSNicolas Bonnefon // Contains data specific to the protocol (inotify/Win32...) 37c540156cSNicolas Bonnefon ProtocolInfo protocol_info_; 38c540156cSNicolas Bonnefon }; 39c540156cSNicolas Bonnefon 40f09fa651SNicolas Bonnefon template <typename Driver> 41c540156cSNicolas Bonnefon struct ObservedFile { 42c540156cSNicolas Bonnefon ObservedFile( 43c540156cSNicolas Bonnefon const std::string& file_name, 44c540156cSNicolas Bonnefon std::shared_ptr<void> callback, 45f09fa651SNicolas Bonnefon typename Driver::FileId file_id, 46f09fa651SNicolas Bonnefon typename Driver::SymlinkId symlink_id ) 47b278d183SNicolas Bonnefon : file_name_( file_name ) { 48c540156cSNicolas Bonnefon addCallback( callback ); 49c540156cSNicolas Bonnefon 50b278d183SNicolas Bonnefon file_id_ = file_id; 51b278d183SNicolas Bonnefon symlink_id_ = symlink_id; 52c540156cSNicolas Bonnefon dir_ = nullptr; 53c540156cSNicolas Bonnefon } 54c540156cSNicolas Bonnefon 55c540156cSNicolas Bonnefon void addCallback( std::shared_ptr<void> callback ) { 56c540156cSNicolas Bonnefon callbacks.push_back( callback ); 57c540156cSNicolas Bonnefon } 58c540156cSNicolas Bonnefon 59c540156cSNicolas Bonnefon std::string file_name_; 60c540156cSNicolas Bonnefon // List of callbacks for this file 61c540156cSNicolas Bonnefon std::vector<std::shared_ptr<void>> callbacks; 62c540156cSNicolas Bonnefon 63c540156cSNicolas Bonnefon // watch descriptor for the file itself 64f09fa651SNicolas Bonnefon typename Driver::FileId file_id_; 65c540156cSNicolas Bonnefon // watch descriptor for the symlink (if file is a symlink) 66f09fa651SNicolas Bonnefon typename Driver::SymlinkId symlink_id_; 67c540156cSNicolas Bonnefon 68c540156cSNicolas Bonnefon // link to the dir containing the file 69f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> dir_; 70c540156cSNicolas Bonnefon }; 71c540156cSNicolas Bonnefon 72c540156cSNicolas Bonnefon // A list of the observed files and directories 73c540156cSNicolas Bonnefon // This class is not thread safe 74f09fa651SNicolas Bonnefon template<typename Driver> 75c540156cSNicolas Bonnefon class ObservedFileList { 76c540156cSNicolas Bonnefon public: 77*3104b268SNicolas Bonnefon ObservedFileList() : 78*3104b268SNicolas Bonnefon heartBeat_ { std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {}) } 79*3104b268SNicolas Bonnefon { } 80c540156cSNicolas Bonnefon ~ObservedFileList() = default; 81c540156cSNicolas Bonnefon 82*3104b268SNicolas Bonnefon // The functions return a pointer to the existing file (if exists) 83*3104b268SNicolas Bonnefon // but keep ownership of the object. 84f09fa651SNicolas Bonnefon ObservedFile<Driver>* searchByName( const std::string& file_name ); 85f09fa651SNicolas Bonnefon ObservedFile<Driver>* searchByFileOrSymlinkWd( 86f09fa651SNicolas Bonnefon typename Driver::FileId file_id, 87f09fa651SNicolas Bonnefon typename Driver::SymlinkId symlink_id ); 88f09fa651SNicolas Bonnefon ObservedFile<Driver>* searchByDirWdAndName( 89f09fa651SNicolas Bonnefon typename Driver::DirId id, const char* name ); 90c540156cSNicolas Bonnefon 91*3104b268SNicolas Bonnefon // Add a new file, the list returns a pointer to the added file, 92*3104b268SNicolas Bonnefon // but has ownership of the file. 93f09fa651SNicolas Bonnefon ObservedFile<Driver>* addNewObservedFile( ObservedFile<Driver> new_observed ); 94c540156cSNicolas Bonnefon // Remove a callback, remove and returns the file object if 95c540156cSNicolas Bonnefon // it was the last callback on this object, nullptr if not. 96c540156cSNicolas Bonnefon // The caller has ownership of the object. 97f09fa651SNicolas Bonnefon std::shared_ptr<ObservedFile<Driver>> removeCallback( 98c540156cSNicolas Bonnefon std::shared_ptr<void> callback ); 99c540156cSNicolas Bonnefon 100c540156cSNicolas Bonnefon // Return the watched directory if it is watched, or nullptr 101f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> watchedDirectory( const std::string& dir_name ); 102*3104b268SNicolas Bonnefon // Create a new watched directory for dir_name, the client is passed 103*3104b268SNicolas Bonnefon // shared ownership and have to keep the shared_ptr (the list only 104*3104b268SNicolas Bonnefon // maintain a weak link). 105*3104b268SNicolas Bonnefon // The remove notification is called just before the reference to 106*3104b268SNicolas Bonnefon // the directory is destroyed. 107*3104b268SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> addWatchedDirectory( 108*3104b268SNicolas Bonnefon const std::string& dir_name, 109*3104b268SNicolas Bonnefon std::function<void( ObservedDir<Driver>* )> remove_notification ); 110c540156cSNicolas Bonnefon 111*3104b268SNicolas Bonnefon // Similar to previous functions but extract the name of the 112*3104b268SNicolas Bonnefon // directory from the file name. 113f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> watchedDirectoryForFile( const std::string& file_name ); 114*3104b268SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> addWatchedDirectoryForFile( const std::string& file_name, 115*3104b268SNicolas Bonnefon std::function<void( ObservedDir<Driver>* )> remove_notification ); 116*3104b268SNicolas Bonnefon 117*3104b268SNicolas Bonnefon // Removal of directories is done when there is no shared reference 118*3104b268SNicolas Bonnefon // left (RAII) 119*3104b268SNicolas Bonnefon 120*3104b268SNicolas Bonnefon // Number of watched directories (for tests) 121*3104b268SNicolas Bonnefon unsigned int numberWatchedDirectories() const; 122c540156cSNicolas Bonnefon 123c540156cSNicolas Bonnefon private: 124c540156cSNicolas Bonnefon // List of observed files 125f09fa651SNicolas Bonnefon std::list<ObservedFile<Driver>> observed_files_; 126c540156cSNicolas Bonnefon 127c540156cSNicolas Bonnefon // List of observed dirs, key-ed by name 128f09fa651SNicolas Bonnefon std::map<std::string, std::weak_ptr<ObservedDir<Driver>>> observed_dirs_; 129c540156cSNicolas Bonnefon 130c540156cSNicolas Bonnefon // Map the inotify file (including symlinks) wds to the observed file 131f09fa651SNicolas Bonnefon std::map<int, ObservedFile<Driver>*> by_file_wd_; 132c540156cSNicolas Bonnefon // Map the inotify directory wds to the observed files 133f09fa651SNicolas Bonnefon std::map<int, ObservedFile<Driver>*> by_dir_wd_; 134*3104b268SNicolas Bonnefon 135*3104b268SNicolas Bonnefon // Heartbeat 136*3104b268SNicolas Bonnefon std::shared_ptr<void> heartBeat_; 137*3104b268SNicolas Bonnefon 138*3104b268SNicolas Bonnefon // Clean all reference to any expired directory 139*3104b268SNicolas Bonnefon void cleanRefsToExpiredDirs(); 140c540156cSNicolas Bonnefon }; 141c540156cSNicolas Bonnefon 142f09fa651SNicolas Bonnefon namespace { 143f09fa651SNicolas Bonnefon std::string directory_path( const std::string& path ); 144f09fa651SNicolas Bonnefon }; 145f09fa651SNicolas Bonnefon 146f09fa651SNicolas Bonnefon // ObservedFileList class 147f09fa651SNicolas Bonnefon template <typename Driver> 148f09fa651SNicolas Bonnefon ObservedFile<Driver>* ObservedFileList<Driver>::searchByName( 149f09fa651SNicolas Bonnefon const std::string& file_name ) 150f09fa651SNicolas Bonnefon { 151f09fa651SNicolas Bonnefon // Look for an existing observer on this file 152f09fa651SNicolas Bonnefon auto existing_observer = observed_files_.begin(); 153f09fa651SNicolas Bonnefon for ( ; existing_observer != observed_files_.end(); ++existing_observer ) 154f09fa651SNicolas Bonnefon { 155f09fa651SNicolas Bonnefon if ( existing_observer->file_name_ == file_name ) 156f09fa651SNicolas Bonnefon { 157f09fa651SNicolas Bonnefon LOG(logDEBUG) << "Found " << file_name; 158f09fa651SNicolas Bonnefon break; 159f09fa651SNicolas Bonnefon } 160f09fa651SNicolas Bonnefon } 161f09fa651SNicolas Bonnefon 162f09fa651SNicolas Bonnefon if ( existing_observer != observed_files_.end() ) 163f09fa651SNicolas Bonnefon return &( *existing_observer ); 164f09fa651SNicolas Bonnefon else 165f09fa651SNicolas Bonnefon return nullptr; 166f09fa651SNicolas Bonnefon } 167f09fa651SNicolas Bonnefon 168f09fa651SNicolas Bonnefon template <typename Driver> 169f09fa651SNicolas Bonnefon ObservedFile<Driver>* ObservedFileList<Driver>::searchByFileOrSymlinkWd( 170f09fa651SNicolas Bonnefon typename Driver::FileId file_id, 171f09fa651SNicolas Bonnefon typename Driver::SymlinkId symlink_id ) 172f09fa651SNicolas Bonnefon { 173f09fa651SNicolas Bonnefon auto result = find_if( observed_files_.begin(), observed_files_.end(), 174f09fa651SNicolas Bonnefon [file_id, symlink_id] (ObservedFile<Driver> file) -> bool { 175f09fa651SNicolas Bonnefon return ( file_id == file.file_id_ ) || 176f09fa651SNicolas Bonnefon ( symlink_id == file.symlink_id_ ); 177f09fa651SNicolas Bonnefon } ); 178f09fa651SNicolas Bonnefon 179f09fa651SNicolas Bonnefon if ( result != observed_files_.end() ) 180f09fa651SNicolas Bonnefon return &( *result ); 181f09fa651SNicolas Bonnefon else 182f09fa651SNicolas Bonnefon return nullptr; 183f09fa651SNicolas Bonnefon } 184f09fa651SNicolas Bonnefon 185f09fa651SNicolas Bonnefon template <typename Driver> 186f09fa651SNicolas Bonnefon ObservedFile<Driver>* ObservedFileList<Driver>::searchByDirWdAndName( 187f09fa651SNicolas Bonnefon typename Driver::DirId id, const char* name ) 188f09fa651SNicolas Bonnefon { 189f09fa651SNicolas Bonnefon auto dir = find_if( observed_dirs_.begin(), observed_dirs_.end(), 190f09fa651SNicolas Bonnefon [id] (std::pair<std::string,std::weak_ptr<ObservedDir<Driver>>> d) -> bool { 191f09fa651SNicolas Bonnefon if ( auto dir = d.second.lock() ) { 192f09fa651SNicolas Bonnefon return ( id == dir->dir_id_ ); 193f09fa651SNicolas Bonnefon } 194f09fa651SNicolas Bonnefon else { 195f09fa651SNicolas Bonnefon return false; } } ); 196f09fa651SNicolas Bonnefon 197f09fa651SNicolas Bonnefon if ( dir != observed_dirs_.end() ) { 198f09fa651SNicolas Bonnefon std::string path = dir->first + "/" + name; 199f09fa651SNicolas Bonnefon 200f09fa651SNicolas Bonnefon // LOG(logDEBUG) << "Testing path: " << path; 201f09fa651SNicolas Bonnefon 202f09fa651SNicolas Bonnefon // Looking for the path in the files we are watching 203f09fa651SNicolas Bonnefon return searchByName( path ); 204f09fa651SNicolas Bonnefon } 205f09fa651SNicolas Bonnefon else { 206f09fa651SNicolas Bonnefon return nullptr; 207f09fa651SNicolas Bonnefon } 208f09fa651SNicolas Bonnefon } 209f09fa651SNicolas Bonnefon 210f09fa651SNicolas Bonnefon template <typename Driver> 211f09fa651SNicolas Bonnefon ObservedFile<Driver>* ObservedFileList<Driver>::addNewObservedFile( 212f09fa651SNicolas Bonnefon ObservedFile<Driver> new_observed ) 213f09fa651SNicolas Bonnefon { 214f09fa651SNicolas Bonnefon auto new_file = observed_files_.insert( std::begin( observed_files_ ), new_observed ); 215f09fa651SNicolas Bonnefon 216f09fa651SNicolas Bonnefon return &( *new_file ); 217f09fa651SNicolas Bonnefon } 218f09fa651SNicolas Bonnefon 219f09fa651SNicolas Bonnefon template <typename Driver> 220f09fa651SNicolas Bonnefon std::shared_ptr<ObservedFile<Driver>> ObservedFileList<Driver>::removeCallback( 221f09fa651SNicolas Bonnefon std::shared_ptr<void> callback ) 222f09fa651SNicolas Bonnefon { 223f09fa651SNicolas Bonnefon std::shared_ptr<ObservedFile<Driver>> returned_file = nullptr; 224f09fa651SNicolas Bonnefon 225f09fa651SNicolas Bonnefon for ( auto observer = begin( observed_files_ ); 226f09fa651SNicolas Bonnefon observer != end( observed_files_ ); ) 227f09fa651SNicolas Bonnefon { 228f09fa651SNicolas Bonnefon std::vector<std::shared_ptr<void>>& callbacks = observer->callbacks; 229f09fa651SNicolas Bonnefon callbacks.erase( std::remove( 230f09fa651SNicolas Bonnefon std::begin( callbacks ), std::end( callbacks ), callback ), 231f09fa651SNicolas Bonnefon std::end( callbacks ) ); 232f09fa651SNicolas Bonnefon 233f09fa651SNicolas Bonnefon /* See if all notifications have been deleted for this file */ 234f09fa651SNicolas Bonnefon if ( callbacks.empty() ) { 235*3104b268SNicolas Bonnefon LOG(logDEBUG) << "Empty notification list for " << observer->file_name_ 236*3104b268SNicolas Bonnefon << ", removing the watched file"; 237f09fa651SNicolas Bonnefon returned_file = std::make_shared<ObservedFile<Driver>>( *observer ); 238f09fa651SNicolas Bonnefon observer = observed_files_.erase( observer ); 239f09fa651SNicolas Bonnefon } 240f09fa651SNicolas Bonnefon else { 241f09fa651SNicolas Bonnefon ++observer; 242f09fa651SNicolas Bonnefon } 243f09fa651SNicolas Bonnefon } 244f09fa651SNicolas Bonnefon 245f09fa651SNicolas Bonnefon return returned_file; 246f09fa651SNicolas Bonnefon } 247f09fa651SNicolas Bonnefon 248f09fa651SNicolas Bonnefon template <typename Driver> 249f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::watchedDirectory( 250f09fa651SNicolas Bonnefon const std::string& dir_name ) 251f09fa651SNicolas Bonnefon { 252f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> dir = nullptr; 253f09fa651SNicolas Bonnefon 254f09fa651SNicolas Bonnefon if ( observed_dirs_.find( dir_name ) != std::end( observed_dirs_ ) ) 255f09fa651SNicolas Bonnefon dir = observed_dirs_[ dir_name ].lock(); 256f09fa651SNicolas Bonnefon 257f09fa651SNicolas Bonnefon return dir; 258f09fa651SNicolas Bonnefon } 259f09fa651SNicolas Bonnefon 260f09fa651SNicolas Bonnefon template <typename Driver> 261f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::addWatchedDirectory( 262*3104b268SNicolas Bonnefon const std::string& dir_name, 263*3104b268SNicolas Bonnefon std::function<void( ObservedDir<Driver>* )> remove_notification ) 264f09fa651SNicolas Bonnefon { 265*3104b268SNicolas Bonnefon std::weak_ptr<void> weakHeartBeat(heartBeat_); 266*3104b268SNicolas Bonnefon 267*3104b268SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> dir = { 268*3104b268SNicolas Bonnefon new ObservedDir<Driver>( dir_name ), 269*3104b268SNicolas Bonnefon [this, remove_notification, weakHeartBeat] (ObservedDir<Driver>* d) { 270*3104b268SNicolas Bonnefon if ( auto heart_beat = weakHeartBeat.lock() ) { 271*3104b268SNicolas Bonnefon remove_notification( d ); 272*3104b268SNicolas Bonnefon cleanRefsToExpiredDirs(); 273*3104b268SNicolas Bonnefon } 274*3104b268SNicolas Bonnefon delete d; } }; 275f09fa651SNicolas Bonnefon 276f09fa651SNicolas Bonnefon observed_dirs_[ dir_name ] = std::weak_ptr<ObservedDir<Driver>>( dir ); 277f09fa651SNicolas Bonnefon 278f09fa651SNicolas Bonnefon return dir; 279f09fa651SNicolas Bonnefon } 280f09fa651SNicolas Bonnefon 281f09fa651SNicolas Bonnefon template <typename Driver> 282f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::watchedDirectoryForFile( 283f09fa651SNicolas Bonnefon const std::string& file_name ) 284f09fa651SNicolas Bonnefon { 285f09fa651SNicolas Bonnefon return watchedDirectory( directory_path( file_name ) ); 286f09fa651SNicolas Bonnefon } 287f09fa651SNicolas Bonnefon 288f09fa651SNicolas Bonnefon template <typename Driver> 289f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::addWatchedDirectoryForFile( 290*3104b268SNicolas Bonnefon const std::string& file_name, 291*3104b268SNicolas Bonnefon std::function<void( ObservedDir<Driver>* )> remove_notification ) 292f09fa651SNicolas Bonnefon { 293*3104b268SNicolas Bonnefon return addWatchedDirectory( directory_path( file_name ), 294*3104b268SNicolas Bonnefon remove_notification ); 295*3104b268SNicolas Bonnefon } 296*3104b268SNicolas Bonnefon 297*3104b268SNicolas Bonnefon template <typename Driver> 298*3104b268SNicolas Bonnefon unsigned int ObservedFileList<Driver>::numberWatchedDirectories() const 299*3104b268SNicolas Bonnefon { 300*3104b268SNicolas Bonnefon return observed_dirs_.size(); 301*3104b268SNicolas Bonnefon } 302*3104b268SNicolas Bonnefon 303*3104b268SNicolas Bonnefon // Private functions 304*3104b268SNicolas Bonnefon template <typename Driver> 305*3104b268SNicolas Bonnefon void ObservedFileList<Driver>::cleanRefsToExpiredDirs() 306*3104b268SNicolas Bonnefon { 307*3104b268SNicolas Bonnefon for ( auto it = std::begin( observed_dirs_ ); 308*3104b268SNicolas Bonnefon it != std::end( observed_dirs_ ); ) 309*3104b268SNicolas Bonnefon { 310*3104b268SNicolas Bonnefon if ( it->second.expired() ) { 311*3104b268SNicolas Bonnefon LOG(logDEBUG) << "No lock "; 312*3104b268SNicolas Bonnefon it = observed_dirs_.erase( it ); 313*3104b268SNicolas Bonnefon } 314*3104b268SNicolas Bonnefon else { 315*3104b268SNicolas Bonnefon ++it; 316*3104b268SNicolas Bonnefon } 317*3104b268SNicolas Bonnefon } 318f09fa651SNicolas Bonnefon } 319f09fa651SNicolas Bonnefon 320f09fa651SNicolas Bonnefon namespace { 321f09fa651SNicolas Bonnefon std::string directory_path( const std::string& path ) 322f09fa651SNicolas Bonnefon { 323f09fa651SNicolas Bonnefon size_t slash_pos = path.rfind( '/' ); 324f09fa651SNicolas Bonnefon 325f09fa651SNicolas Bonnefon #ifdef _WIN32 326f09fa651SNicolas Bonnefon if ( slash_pos == std::string::npos ) { 327f09fa651SNicolas Bonnefon slash_pos = path.rfind( '\\' ); 328f09fa651SNicolas Bonnefon } 329f09fa651SNicolas Bonnefon 330f09fa651SNicolas Bonnefon // We need to include the final slash on Windows 331f09fa651SNicolas Bonnefon ++slash_pos; 332f09fa651SNicolas Bonnefon #endif 333f09fa651SNicolas Bonnefon 334f09fa651SNicolas Bonnefon return std::string( path, 0, slash_pos ); 335f09fa651SNicolas Bonnefon } 336f09fa651SNicolas Bonnefon }; 337c540156cSNicolas Bonnefon #endif 338