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