1 /* 2 * Copyright (C) 2015 Nicolas Bonnefon and other contributors 3 * 4 * This file is part of glogg. 5 * 6 * glogg is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * glogg is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with glogg. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #ifndef WATCHTOWER_H 21 #define WATCHTOWER_H 22 23 #include "config.h" 24 25 #include <memory> 26 #include <atomic> 27 #include <thread> 28 #include <mutex> 29 30 #include "watchtowerlist.h" 31 32 // Allow the client to register for notification on an arbitrary number 33 // of files. It will be notified to any change (creation/deletion/modification) 34 // on those files. 35 // It is passed a platform specific driver. 36 37 // FIXME: Where to put that so it is not dependant 38 // Registration object to implement RAII 39 #ifdef HAS_TEMPLATE_ALIASES 40 using Registration = std::shared_ptr<void>; 41 #else 42 typedef std::shared_ptr<void> Registration; 43 #endif 44 45 template<typename Driver> 46 class WatchTower { 47 public: 48 // Create an empty watchtower 49 WatchTower(); 50 // Destroy the object 51 ~WatchTower(); 52 53 // Set the polling interval (in ms) 54 // 0 disables polling and is the default 55 void setPollingInterval( uint32_t interval_ms ); 56 57 // Add a file to the notification list. notification will be called when 58 // the file is modified, moved or deleted. 59 // Lifetime of the notification is tied to the Registration object returned. 60 // Note the notification function is called with a mutex held and in a 61 // third party thread, beware of races! 62 Registration addFile( const std::string& file_name, 63 std::function<void()> notification ); 64 65 // Number of watched directories (for tests) 66 unsigned int numberWatchedDirectories() const; 67 68 private: 69 // The driver (parametrised) 70 Driver driver_; 71 72 // List of files/dirs observed 73 ObservedFileList<Driver> observed_file_list_; 74 75 // Protects the observed_file_list_ 76 std::mutex observers_mutex_; 77 78 // Polling interval (0 disables polling) 79 uint32_t polling_interval_ms_ = 0; 80 81 // Thread 82 std::atomic_bool running_; 83 std::thread thread_; 84 85 // Exist as long as the onject exists, to ensure observers won't try to 86 // call us if we are dead. 87 std::shared_ptr<void> heartBeat_; 88 89 // Private member functions 90 std::tuple<typename Driver::FileId, typename Driver::SymlinkId> 91 addFileToDriver( const std::string& ); 92 static void removeNotification( WatchTower* watch_tower, 93 std::shared_ptr<void> notification ); 94 void run(); 95 }; 96 97 // Class template implementation 98 99 #include <algorithm> 100 101 #include <sys/types.h> 102 #include <sys/stat.h> 103 #include <unistd.h> 104 105 #include "log.h" 106 107 namespace { 108 bool isSymLink( const std::string& file_name ); 109 std::string directory_path( const std::string& path ); 110 }; 111 112 template <typename Driver> 113 WatchTower<Driver>::WatchTower() 114 : driver_(), thread_(), 115 heartBeat_(std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {})) 116 { 117 running_ = true; 118 thread_ = std::thread( &WatchTower::run, this ); 119 } 120 121 template <typename Driver> 122 WatchTower<Driver>::~WatchTower() 123 { 124 running_ = false; 125 driver_.interruptWait(); 126 thread_.join(); 127 } 128 129 template <typename Driver> 130 void WatchTower<Driver>::setPollingInterval( uint32_t interval_ms ) 131 { 132 if ( polling_interval_ms_ != interval_ms ) { 133 polling_interval_ms_ = interval_ms; 134 // Break out of the wait 135 if ( polling_interval_ms_ > 0 ) 136 driver_.interruptWait(); 137 } 138 } 139 140 template <typename Driver> 141 Registration WatchTower<Driver>::addFile( 142 const std::string& file_name, 143 std::function<void()> notification ) 144 { 145 // LOG(logDEBUG) << "WatchTower::addFile " << file_name; 146 147 std::weak_ptr<void> weakHeartBeat(heartBeat_); 148 149 std::lock_guard<std::mutex> lock( observers_mutex_ ); 150 151 auto existing_observed_file = 152 observed_file_list_.searchByName( file_name ); 153 154 std::shared_ptr<std::function<void()>> ptr( new std::function<void()>(std::move( notification )) ); 155 156 if ( ! existing_observed_file ) 157 { 158 typename Driver::FileId file_id; 159 typename Driver::SymlinkId symlink_id; 160 161 std::tie( file_id, symlink_id ) = addFileToDriver( file_name ); 162 auto new_file = observed_file_list_.addNewObservedFile( 163 ObservedFile<Driver>( file_name, ptr, file_id, symlink_id ) ); 164 165 auto dir = observed_file_list_.watchedDirectoryForFile( file_name ); 166 if ( ! dir ) { 167 LOG(logDEBUG) << "WatchTower::addFile dir for " << file_name 168 << " not watched, adding..."; 169 170 dir = observed_file_list_.addWatchedDirectoryForFile( file_name, 171 [this, weakHeartBeat] (ObservedDir<Driver>* dir) { 172 if ( auto heart_beat = weakHeartBeat.lock() ) { 173 driver_.removeDir( dir->dir_id_ ); 174 } } ); 175 176 dir->dir_id_ = driver_.addDir( dir->path ); 177 178 if ( ! dir->dir_id_.valid() ) { 179 LOG(logWARNING) << "WatchTower::addFile driver failed to add dir"; 180 dir = nullptr; 181 } 182 } 183 else { 184 LOG(logDEBUG) << "WatchTower::addFile Found exisiting watch for dir " << file_name; 185 } 186 187 // Associate the dir to the file 188 if ( dir ) 189 new_file->dir_ = dir; 190 } 191 else 192 { 193 existing_observed_file->addCallback( ptr ); 194 } 195 196 // Returns a shared pointer that removes its own entry 197 // from the list of watched stuff when it goes out of scope! 198 // Uses a custom deleter to do the work. 199 return std::shared_ptr<void>( 0x0, [this, ptr, weakHeartBeat] (void*) { 200 if ( auto heart_beat = weakHeartBeat.lock() ) 201 WatchTower<Driver>::removeNotification( this, ptr ); 202 } ); 203 } 204 205 template <typename Driver> 206 unsigned int WatchTower<Driver>::numberWatchedDirectories() const 207 { 208 return observed_file_list_.numberWatchedDirectories(); 209 } 210 211 // 212 // Private functions 213 // 214 215 // Add the passed file name to the driver, returning the file and symlink id 216 template <typename Driver> 217 std::tuple<typename Driver::FileId, typename Driver::SymlinkId> 218 WatchTower<Driver>::addFileToDriver( const std::string& file_name ) 219 { 220 typename Driver::SymlinkId symlink_id; 221 auto file_id = driver_.addFile( file_name ); 222 223 if ( isSymLink( file_name ) ) 224 { 225 // We want to follow the name (as opposed to the inode) 226 // so we watch the symlink as well. 227 symlink_id = driver_.addSymlink( file_name ); 228 } 229 230 return std::make_tuple( file_id, symlink_id ); 231 } 232 233 // Called by the dtor for a registration object 234 template <typename Driver> 235 void WatchTower<Driver>::removeNotification( 236 WatchTower* watch_tower, std::shared_ptr<void> notification ) 237 { 238 LOG(logDEBUG) << "WatchTower::removeNotification"; 239 240 std::lock_guard<std::mutex> lock( watch_tower->observers_mutex_ ); 241 242 auto file = 243 watch_tower->observed_file_list_.removeCallback( notification ); 244 245 if ( file ) 246 { 247 watch_tower->driver_.removeFile( file->file_id_ ); 248 watch_tower->driver_.removeSymlink( file->symlink_id_ ); 249 } 250 } 251 252 // Run in its own thread 253 template <typename Driver> 254 void WatchTower<Driver>::run() 255 { 256 while ( running_ ) { 257 std::unique_lock<std::mutex> lock( observers_mutex_ ); 258 259 std::vector<ObservedFile<Driver>*> files_needing_readding; 260 261 auto files = driver_.waitAndProcessEvents( 262 &observed_file_list_, &lock, &files_needing_readding, polling_interval_ms_ ); 263 LOG(logDEBUG) << "WatchTower::run: waitAndProcessEvents returned " 264 << files.size() << " files, " << files_needing_readding.size() 265 << " needing re-adding"; 266 267 for ( auto file: files_needing_readding ) { 268 // A file 'needing readding' has the same name, 269 // but probably a different inode, so it needs 270 // to be readded for some drivers that rely on the 271 // inode (e.g. inotify) 272 driver_.removeFile( file->file_id_ ); 273 driver_.removeSymlink( file->symlink_id_ ); 274 275 std::tie( file->file_id_, file->symlink_id_ ) = 276 addFileToDriver( file->file_name_ ); 277 } 278 279 for ( auto file: files ) { 280 for ( auto observer: file->callbacks ) { 281 LOG(logDEBUG) << "WatchTower::run: notifying the client!"; 282 // Here we have to cast our generic pointer back to 283 // the function pointer in order to perform the call 284 const std::shared_ptr<std::function<void()>> fptr = 285 std::static_pointer_cast<std::function<void()>>( observer ); 286 // The observer is called with the mutex held, 287 // Let's hope it doesn't do anything too funky. 288 (*fptr)(); 289 290 file->markAsChanged(); 291 } 292 } 293 294 if ( polling_interval_ms_ > 0 ) { 295 // Also call files that have not been called for a while 296 for ( auto file: observed_file_list_ ) { 297 uint32_t ms_since_last_check = 298 std::chrono::duration_cast<std::chrono::milliseconds>( 299 std::chrono::steady_clock::now() - file->timeForLastCheck() ).count(); 300 if ( ( ms_since_last_check > polling_interval_ms_ ) && file->hasChanged() ) { 301 LOG(logDEBUG) << "WatchTower::run: " << file->file_name_; 302 for ( auto observer: file->callbacks ) { 303 LOG(logDEBUG) << "WatchTower::run: notifying the client because of a timeout!"; 304 // Here we have to cast our generic pointer back to 305 // the function pointer in order to perform the call 306 const std::shared_ptr<std::function<void()>> fptr = 307 std::static_pointer_cast<std::function<void()>>( observer ); 308 // The observer is called with the mutex held, 309 // Let's hope it doesn't do anything too funky. 310 (*fptr)(); 311 } 312 file->markAsChanged(); 313 } 314 } 315 } 316 } 317 } 318 319 namespace { 320 bool isSymLink( const std::string& file_name ) 321 { 322 #ifdef HAVE_SYMLINK 323 struct stat buf; 324 325 lstat( file_name.c_str(), &buf ); 326 return ( S_ISLNK(buf.st_mode) ); 327 #else 328 return false; 329 #endif 330 } 331 }; 332 333 #endif 334