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