1 #ifndef WATCHTOWER_H 2 #define WATCHTOWER_H 3 4 #include "config.h" 5 6 #include <memory> 7 #include <atomic> 8 #include <thread> 9 #include <mutex> 10 11 #include "watchtowerlist.h" 12 13 // Allow the client to register for notification on an arbitrary number 14 // of files. It will be notified to any change (creation/deletion/modification) 15 // on those files. 16 // It is passed a platform specific driver. 17 18 // FIXME: Where to put that so it is not dependant 19 // Registration object to implement RAII 20 #ifdef HAS_TEMPLATE_ALIASES 21 using Registration = std::shared_ptr<void>; 22 #else 23 typedef std::shared_ptr<void> Registration; 24 #endif 25 26 template<typename Driver> 27 class WatchTower { 28 public: 29 // Create an empty watchtower 30 WatchTower(); 31 // Destroy the object 32 ~WatchTower(); 33 34 // Add a file to the notification list. notification will be called when 35 // the file is modified, moved or deleted. 36 // Lifetime of the notification is tied to the Registration object returned. 37 // Note the notification function is called with a mutex held and in a 38 // third party thread, beware of races! 39 Registration addFile( const std::string& file_name, 40 std::function<void()> notification ); 41 42 // Number of watched directories (for tests) 43 unsigned int numberWatchedDirectories() const; 44 45 private: 46 // The driver (parametrised) 47 Driver driver_; 48 49 // List of files/dirs observed 50 ObservedFileList<Driver> observed_file_list_; 51 52 // Protects the observed_file_list_ 53 std::mutex observers_mutex_; 54 55 // Thread 56 std::atomic_bool running_; 57 std::thread thread_; 58 59 // Exist as long as the onject exists, to ensure observers won't try to 60 // call us if we are dead. 61 std::shared_ptr<void> heartBeat_; 62 63 // Private member functions 64 std::tuple<typename Driver::FileId, typename Driver::SymlinkId> 65 addFileToDriver( const std::string& ); 66 static void removeNotification( WatchTower* watch_tower, 67 std::shared_ptr<void> notification ); 68 void run(); 69 }; 70 71 // Class template implementation 72 73 #include <algorithm> 74 75 #include <sys/types.h> 76 #include <sys/stat.h> 77 #include <unistd.h> 78 79 #include "log.h" 80 81 namespace { 82 bool isSymLink( const std::string& file_name ); 83 std::string directory_path( const std::string& path ); 84 }; 85 86 template <typename Driver> 87 WatchTower<Driver>::WatchTower() 88 : driver_(), thread_(), 89 heartBeat_(std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {})) 90 { 91 running_ = true; 92 thread_ = std::thread( &WatchTower::run, this ); 93 } 94 95 template <typename Driver> 96 WatchTower<Driver>::~WatchTower() 97 { 98 running_ = false; 99 driver_.interruptWait(); 100 thread_.join(); 101 } 102 103 template <typename Driver> 104 Registration WatchTower<Driver>::addFile( 105 const std::string& file_name, 106 std::function<void()> notification ) 107 { 108 // LOG(logDEBUG) << "WatchTower::addFile " << file_name; 109 110 std::weak_ptr<void> weakHeartBeat(heartBeat_); 111 112 std::lock_guard<std::mutex> lock( observers_mutex_ ); 113 114 auto existing_observed_file = 115 observed_file_list_.searchByName( file_name ); 116 117 std::shared_ptr<std::function<void()>> ptr( new std::function<void()>(std::move( notification )) ); 118 119 if ( ! existing_observed_file ) 120 { 121 typename Driver::FileId file_id; 122 typename Driver::SymlinkId symlink_id; 123 124 std::tie( file_id, symlink_id ) = addFileToDriver( file_name ); 125 auto new_file = observed_file_list_.addNewObservedFile( 126 ObservedFile<Driver>( file_name, ptr, file_id, symlink_id ) ); 127 128 auto dir = observed_file_list_.watchedDirectoryForFile( file_name ); 129 if ( ! dir ) { 130 LOG(logDEBUG) << "WatchTower::addFile dir for " << file_name 131 << " not watched, adding..."; 132 133 dir = observed_file_list_.addWatchedDirectoryForFile( file_name, 134 [this, weakHeartBeat] (ObservedDir<Driver>* dir) { 135 if ( auto heart_beat = weakHeartBeat.lock() ) { 136 driver_.removeDir( dir->dir_id_ ); 137 } } ); 138 139 dir->dir_id_ = driver_.addDir( dir->path ); 140 141 if ( ! dir->dir_id_.valid() ) { 142 LOG(logWARNING) << "WatchTower::addFile driver failed to add dir"; 143 dir = nullptr; 144 } 145 } 146 else { 147 LOG(logDEBUG) << "WatchTower::addFile Found exisiting watch for dir " << file_name; 148 } 149 150 // Associate the dir to the file 151 if ( dir ) 152 new_file->dir_ = dir; 153 } 154 else 155 { 156 existing_observed_file->addCallback( ptr ); 157 } 158 159 // Returns a shared pointer that removes its own entry 160 // from the list of watched stuff when it goes out of scope! 161 // Uses a custom deleter to do the work. 162 return std::shared_ptr<void>( 0x0, [this, ptr, weakHeartBeat] (void*) { 163 if ( auto heart_beat = weakHeartBeat.lock() ) 164 WatchTower<Driver>::removeNotification( this, ptr ); 165 } ); 166 } 167 168 template <typename Driver> 169 unsigned int WatchTower<Driver>::numberWatchedDirectories() const 170 { 171 return observed_file_list_.numberWatchedDirectories(); 172 } 173 174 // 175 // Private functions 176 // 177 178 // Add the passed file name to the driver, returning the file and symlink id 179 template <typename Driver> 180 std::tuple<typename Driver::FileId, typename Driver::SymlinkId> 181 WatchTower<Driver>::addFileToDriver( const std::string& file_name ) 182 { 183 typename Driver::SymlinkId symlink_id; 184 auto file_id = driver_.addFile( file_name ); 185 186 if ( isSymLink( file_name ) ) 187 { 188 // We want to follow the name (as opposed to the inode) 189 // so we watch the symlink as well. 190 symlink_id = driver_.addSymlink( file_name ); 191 } 192 193 return std::make_tuple( file_id, symlink_id ); 194 } 195 196 // Called by the dtor for a registration object 197 template <typename Driver> 198 void WatchTower<Driver>::removeNotification( 199 WatchTower* watch_tower, std::shared_ptr<void> notification ) 200 { 201 LOG(logDEBUG) << "WatchTower::removeNotification"; 202 203 std::lock_guard<std::mutex> lock( watch_tower->observers_mutex_ ); 204 205 auto file = 206 watch_tower->observed_file_list_.removeCallback( notification ); 207 208 if ( file ) 209 { 210 watch_tower->driver_.removeFile( file->file_id_ ); 211 watch_tower->driver_.removeSymlink( file->symlink_id_ ); 212 } 213 } 214 215 // Run in its own thread 216 template <typename Driver> 217 void WatchTower<Driver>::run() 218 { 219 while ( running_ ) { 220 std::unique_lock<std::mutex> lock( observers_mutex_ ); 221 222 std::vector<ObservedFile<Driver>*> files_needing_readding; 223 224 auto files = driver_.waitAndProcessEvents( 225 &observed_file_list_, &lock, &files_needing_readding ); 226 LOG(logDEBUG) << "WatchTower::run: waitAndProcessEvents returned " 227 << files.size() << " files, " << files_needing_readding.size() 228 << " needing re-adding"; 229 230 for ( auto file: files_needing_readding ) { 231 // A file 'needing readding' has the same name, 232 // but probably a different inode, so it needs 233 // to be readded for some drivers that rely on the 234 // inode (e.g. inotify) 235 driver_.removeFile( file->file_id_ ); 236 driver_.removeSymlink( file->symlink_id_ ); 237 238 std::tie( file->file_id_, file->symlink_id_ ) = 239 addFileToDriver( file->file_name_ ); 240 } 241 242 for ( auto file: files ) { 243 for ( auto observer: file->callbacks ) { 244 LOG(logDEBUG) << "WatchTower::run: notifying the client!"; 245 // Here we have to cast our generic pointer back to 246 // the function pointer in order to perform the call 247 const std::shared_ptr<std::function<void()>> fptr = 248 std::static_pointer_cast<std::function<void()>>( observer ); 249 // The observer is called with the mutex held, 250 // Let's hope it doesn't do anything too funky. 251 (*fptr)(); 252 } 253 } 254 } 255 } 256 257 namespace { 258 bool isSymLink( const std::string& file_name ) 259 { 260 #ifdef HAVE_SYMLINK 261 struct stat buf; 262 263 lstat( file_name.c_str(), &buf ); 264 return ( S_ISLNK(buf.st_mode) ); 265 #else 266 return false; 267 #endif 268 } 269 }; 270 271 #endif 272