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