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 // Use an empty deleter since we don't really own this. 108 heartBeat_ { this, [] (void*) {} } 109 { } 110 ObservedFileList( const ObservedFileList& ) = delete; 111 ~ObservedFileList() = default; 112 113 // The functions return a pointer to the existing file (if exists) 114 // but keep ownership of the object. 115 ObservedFile<Driver>* searchByName( const std::string& file_name ); 116 ObservedFile<Driver>* searchByFileOrSymlinkWd( 117 typename Driver::FileId file_id, 118 typename Driver::SymlinkId symlink_id ); 119 ObservedFile<Driver>* searchByDirWdAndName( 120 typename Driver::DirId id, const char* name ); 121 std::vector<ObservedFile<Driver>*> searchByDirWd( 122 typename Driver::DirId id ); 123 124 // Add a new file, the list returns a pointer to the added file, 125 // but has ownership of the file. 126 ObservedFile<Driver>* addNewObservedFile( ObservedFile<Driver> new_observed ); 127 // Remove a callback, remove and returns the file object if 128 // it was the last callback on this object, nullptr if not. 129 // The caller has ownership of the object. 130 std::shared_ptr<ObservedFile<Driver>> removeCallback( 131 std::shared_ptr<void> callback ); 132 133 // Return the watched directory if it is watched, or nullptr 134 std::shared_ptr<ObservedDir<Driver>> watchedDirectory( const std::string& dir_name ); 135 // Create a new watched directory for dir_name, the client is passed 136 // shared ownership and have to keep the shared_ptr (the list only 137 // maintain a weak link). 138 // The remove notification is called just before the reference to 139 // the directory is destroyed. 140 std::shared_ptr<ObservedDir<Driver>> addWatchedDirectory( 141 const std::string& dir_name, 142 std::function<void( ObservedDir<Driver>* )> remove_notification ); 143 144 // Similar to previous functions but extract the name of the 145 // directory from the file name. 146 std::shared_ptr<ObservedDir<Driver>> watchedDirectoryForFile( const std::string& file_name ); 147 std::shared_ptr<ObservedDir<Driver>> addWatchedDirectoryForFile( const std::string& file_name, 148 std::function<void( ObservedDir<Driver>* )> remove_notification ); 149 150 // Removal of directories is done when there is no shared reference 151 // left (RAII) 152 153 // Number of watched directories (for tests) 154 unsigned int numberWatchedDirectories() const; 155 156 // Iterator 157 template<typename Container> 158 class iterator : std::iterator<std::input_iterator_tag, ObservedFile<Driver>> { 159 public: 160 iterator( Container* list, 161 const typename Container::iterator& iter ) 162 { list_ = list; pos_ = iter; } 163 164 iterator operator++() 165 { ++pos_; return *this; } 166 167 bool operator==( const iterator& other ) 168 { return ( pos_ == other.pos_ ); } 169 170 bool operator!=( const iterator& other ) 171 { return ! operator==( other ); } 172 173 typename Container::iterator operator*() 174 { return pos_; } 175 176 private: 177 Container* list_; 178 typename Container::iterator pos_; 179 }; 180 181 iterator<std::list<ObservedFile<Driver>>> begin() 182 { return iterator<std::list<ObservedFile<Driver>>>( &observed_files_, observed_files_.begin() ); } 183 iterator<std::list<ObservedFile<Driver>>> end() 184 { return iterator<std::list<ObservedFile<Driver>>>( &observed_files_, observed_files_.end() ); } 185 186 private: 187 // List of observed files 188 std::list<ObservedFile<Driver>> observed_files_; 189 190 // List of observed dirs, key-ed by name 191 std::map<std::string, std::weak_ptr<ObservedDir<Driver>>> observed_dirs_; 192 193 // Map the inotify file (including symlinks) wds to the observed file 194 std::map<int, ObservedFile<Driver>*> by_file_wd_; 195 // Map the inotify directory wds to the observed files 196 std::map<int, ObservedFile<Driver>*> by_dir_wd_; 197 198 // A shared_ptr to self. 199 // weak_ptr's from this can check if the list is still alive. 200 // This probably shouldn't be copied. 201 std::shared_ptr<ObservedFileList> heartBeat_; 202 203 // Clean all reference to any expired directory 204 void cleanRefsToExpiredDirs(); 205 }; 206 207 namespace { 208 std::string directory_path( const std::string& path ); 209 }; 210 211 // ObservedFileList class 212 template <typename Driver> 213 ObservedFile<Driver>* ObservedFileList<Driver>::searchByName( 214 const std::string& file_name ) 215 { 216 // Look for an existing observer on this file 217 auto existing_observer = observed_files_.begin(); 218 for ( ; existing_observer != observed_files_.end(); ++existing_observer ) 219 { 220 if ( existing_observer->file_name_ == file_name ) 221 { 222 LOG(logDEBUG) << "Found " << file_name; 223 break; 224 } 225 } 226 227 if ( existing_observer != observed_files_.end() ) 228 return &( *existing_observer ); 229 else 230 return nullptr; 231 } 232 233 template <typename Driver> 234 ObservedFile<Driver>* ObservedFileList<Driver>::searchByFileOrSymlinkWd( 235 typename Driver::FileId file_id, 236 typename Driver::SymlinkId symlink_id ) 237 { 238 auto result = find_if( observed_files_.begin(), observed_files_.end(), 239 [file_id, symlink_id] (ObservedFile<Driver> file) -> bool { 240 return ( file_id == file.file_id_ ) || 241 ( symlink_id == file.symlink_id_ ); 242 } ); 243 244 if ( result != observed_files_.end() ) 245 return &( *result ); 246 else 247 return nullptr; 248 } 249 250 template <typename Driver> 251 ObservedFile<Driver>* ObservedFileList<Driver>::searchByDirWdAndName( 252 typename Driver::DirId id, const char* name ) 253 { 254 auto dir = find_if( observed_dirs_.begin(), observed_dirs_.end(), 255 [id] (std::pair<std::string,std::weak_ptr<ObservedDir<Driver>>> d) -> bool { 256 if ( auto dir = d.second.lock() ) { 257 return ( id == dir->dir_id_ ); 258 } 259 else { 260 return false; } } ); 261 262 if ( dir != observed_dirs_.end() ) { 263 std::string path = dir->first + "/" + name; 264 265 // LOG(logDEBUG) << "Testing path: " << path; 266 267 // Looking for the path in the files we are watching 268 return searchByName( path ); 269 } 270 else { 271 return nullptr; 272 } 273 } 274 275 template <typename Driver> 276 std::vector<ObservedFile<Driver>*> ObservedFileList<Driver>::searchByDirWd( 277 typename Driver::DirId id ) 278 { 279 std::vector<ObservedFile<Driver>*> result; 280 281 auto dir = find_if( observed_dirs_.begin(), observed_dirs_.end(), 282 [id] (std::pair<std::string,std::weak_ptr<ObservedDir<Driver>>> d) -> bool { 283 if ( auto dir = d.second.lock() ) { 284 return ( id == dir->dir_id_ ); 285 } 286 else { 287 return false; } } ); 288 289 if ( dir != observed_dirs_.end() ) { 290 for ( auto i = observed_files_.begin(); i != observed_files_.end(); ++i ) { 291 if ( auto d = dir->second.lock() ) { 292 if ( d.get() == i->dir_.get() ) { 293 result.push_back( &(*i) ); 294 } 295 } 296 } 297 } 298 299 return result; 300 } 301 302 template <typename Driver> 303 ObservedFile<Driver>* ObservedFileList<Driver>::addNewObservedFile( 304 ObservedFile<Driver> new_observed ) 305 { 306 auto new_file = observed_files_.insert( std::begin( observed_files_ ), new_observed ); 307 308 return &( *new_file ); 309 } 310 311 template <typename Driver> 312 std::shared_ptr<ObservedFile<Driver>> ObservedFileList<Driver>::removeCallback( 313 std::shared_ptr<void> callback ) 314 { 315 std::shared_ptr<ObservedFile<Driver>> returned_file = nullptr; 316 317 for ( auto observer = std::begin( observed_files_ ); 318 observer != std::end( observed_files_ ); ) 319 { 320 std::vector<std::shared_ptr<void>>& callbacks = observer->callbacks; 321 callbacks.erase( std::remove( 322 std::begin( callbacks ), std::end( callbacks ), callback ), 323 std::end( callbacks ) ); 324 325 /* See if all notifications have been deleted for this file */ 326 if ( callbacks.empty() ) { 327 LOG(logDEBUG) << "Empty notification list for " << observer->file_name_ 328 << ", removing the watched file"; 329 returned_file = std::make_shared<ObservedFile<Driver>>( *observer ); 330 observer = observed_files_.erase( observer ); 331 } 332 else { 333 ++observer; 334 } 335 } 336 337 return returned_file; 338 } 339 340 template <typename Driver> 341 std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::watchedDirectory( 342 const std::string& dir_name ) 343 { 344 std::shared_ptr<ObservedDir<Driver>> dir = nullptr; 345 346 if ( observed_dirs_.find( dir_name ) != std::end( observed_dirs_ ) ) 347 dir = observed_dirs_[ dir_name ].lock(); 348 349 return dir; 350 } 351 352 template <typename Driver> 353 std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::addWatchedDirectory( 354 const std::string& dir_name, 355 std::function<void( ObservedDir<Driver>* )> remove_notification ) 356 { 357 std::weak_ptr<ObservedFileList> weakHeartBeat(heartBeat_); 358 359 std::shared_ptr<ObservedDir<Driver>> dir = { 360 new ObservedDir<Driver>( dir_name ), 361 [remove_notification, weakHeartBeat] (ObservedDir<Driver>* d) { 362 if ( auto list = weakHeartBeat.lock() ) { 363 remove_notification( d ); 364 list->cleanRefsToExpiredDirs(); 365 } 366 delete d; } }; 367 368 observed_dirs_[ dir_name ] = std::weak_ptr<ObservedDir<Driver>>( dir ); 369 370 return dir; 371 } 372 373 template <typename Driver> 374 std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::watchedDirectoryForFile( 375 const std::string& file_name ) 376 { 377 return watchedDirectory( directory_path( file_name ) ); 378 } 379 380 template <typename Driver> 381 std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::addWatchedDirectoryForFile( 382 const std::string& file_name, 383 std::function<void( ObservedDir<Driver>* )> remove_notification ) 384 { 385 return addWatchedDirectory( directory_path( file_name ), 386 remove_notification ); 387 } 388 389 template <typename Driver> 390 unsigned int ObservedFileList<Driver>::numberWatchedDirectories() const 391 { 392 return observed_dirs_.size(); 393 } 394 395 // Private functions 396 template <typename Driver> 397 void ObservedFileList<Driver>::cleanRefsToExpiredDirs() 398 { 399 for ( auto it = std::begin( observed_dirs_ ); 400 it != std::end( observed_dirs_ ); ) 401 { 402 if ( it->second.expired() ) { 403 it = observed_dirs_.erase( it ); 404 } 405 else { 406 ++it; 407 } 408 } 409 } 410 411 namespace { 412 std::string directory_path( const std::string& path ) 413 { 414 size_t slash_pos = path.rfind( '/' ); 415 416 #ifdef _WIN32 417 if ( slash_pos == std::string::npos ) { 418 slash_pos = path.rfind( '\\' ); 419 } 420 421 // We need to include the final slash on Windows 422 ++slash_pos; 423 #endif 424 425 return std::string( path, 0, slash_pos ); 426 } 427 }; 428 #endif 429