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 static void removeNotification( WatchTower* watch_tower, 65 std::shared_ptr<void> notification ); 66 void run(); 67 }; 68 69 // Class template implementation 70 71 #include <algorithm> 72 73 #include <sys/types.h> 74 #include <sys/stat.h> 75 #include <unistd.h> 76 77 #include "log.h" 78 79 namespace { 80 bool isSymLink( const std::string& file_name ); 81 std::string directory_path( const std::string& path ); 82 }; 83 84 template <typename Driver> 85 WatchTower<Driver>::WatchTower() 86 : driver_(), thread_(), 87 heartBeat_(std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {})) 88 { 89 running_ = true; 90 thread_ = std::thread( &WatchTower::run, this ); 91 } 92 93 template <typename Driver> 94 WatchTower<Driver>::~WatchTower() 95 { 96 running_ = false; 97 driver_.interruptWait(); 98 thread_.join(); 99 } 100 101 template <typename Driver> 102 Registration WatchTower<Driver>::addFile( 103 const std::string& file_name, 104 std::function<void()> notification ) 105 { 106 // LOG(logDEBUG) << "WatchTower::addFile " << file_name; 107 108 std::weak_ptr<void> weakHeartBeat(heartBeat_); 109 110 std::lock_guard<std::mutex> lock( observers_mutex_ ); 111 112 auto existing_observed_file = 113 observed_file_list_.searchByName( file_name ); 114 115 std::shared_ptr<std::function<void()>> ptr( new std::function<void()>(std::move( notification )) ); 116 117 if ( ! existing_observed_file ) 118 { 119 typename Driver::SymlinkId symlink_id; 120 121 auto file_id = driver_.addFile( file_name ); 122 123 if ( isSymLink( file_name ) ) 124 { 125 // We want to follow the name (as opposed to the inode) 126 // so we watch the symlink as well. 127 symlink_id = driver_.addSymlink( file_name ); 128 } 129 130 auto new_file = observed_file_list_.addNewObservedFile( 131 ObservedFile<Driver>( file_name, ptr, file_id, symlink_id ) ); 132 133 auto dir = observed_file_list_.watchedDirectoryForFile( file_name ); 134 if ( ! dir ) 135 { 136 LOG(logDEBUG) << "WatchTower::addFile dir for " << file_name 137 << " not watched, adding..."; 138 dir = observed_file_list_.addWatchedDirectoryForFile( file_name, 139 [this, weakHeartBeat] (ObservedDir<Driver>* dir) { 140 if ( auto heart_beat = weakHeartBeat.lock() ) { 141 driver_.removeDir( dir->dir_id_ ); 142 } } ); 143 144 dir->dir_id_ = driver_.addDir( dir->path ); 145 } 146 147 // Associate the dir to the file 148 new_file->dir_ = dir; 149 150 LOG(logDEBUG) << "dir ref count is " << dir.use_count(); 151 } 152 else 153 { 154 existing_observed_file->addCallback( ptr ); 155 } 156 157 // Returns a shared pointer that removes its own entry 158 // from the list of watched stuff when it goes out of scope! 159 // Uses a custom deleter to do the work. 160 return std::shared_ptr<void>( 0x0, [this, ptr, weakHeartBeat] (void*) { 161 if ( auto heart_beat = weakHeartBeat.lock() ) 162 WatchTower<Driver>::removeNotification( this, ptr ); 163 } ); 164 } 165 166 template <typename Driver> 167 unsigned int WatchTower<Driver>::numberWatchedDirectories() const 168 { 169 return observed_file_list_.numberWatchedDirectories(); 170 } 171 172 // 173 // Private functions 174 // 175 176 // Called by the dtor for a registration object 177 template <typename Driver> 178 void WatchTower<Driver>::removeNotification( 179 WatchTower* watch_tower, std::shared_ptr<void> notification ) 180 { 181 LOG(logDEBUG) << "WatchTower::removeNotification"; 182 183 std::lock_guard<std::mutex> lock( watch_tower->observers_mutex_ ); 184 185 auto file = 186 watch_tower->observed_file_list_.removeCallback( notification ); 187 188 if ( file ) 189 { 190 watch_tower->driver_.removeFile( file->file_id_ ); 191 watch_tower->driver_.removeSymlink( file->symlink_id_ ); 192 } 193 } 194 195 // Run in its own thread 196 template <typename Driver> 197 void WatchTower<Driver>::run() 198 { 199 while ( running_ ) { 200 std::unique_lock<std::mutex> lock( observers_mutex_ ); 201 202 auto files = driver_.waitAndProcessEvents( 203 &observed_file_list_, &lock ); 204 LOG(logDEBUG) << "WatchTower::run: waitAndProcessEvents returned " 205 << files.size() << " files."; 206 207 for ( auto file: files ) { 208 for ( auto observer: file->callbacks ) { 209 LOG(logDEBUG) << "WatchTower::run: notifying the client!"; 210 // Here we have to cast our generic pointer back to 211 // the function pointer in order to perform the call 212 const std::shared_ptr<std::function<void()>> fptr = 213 std::static_pointer_cast<std::function<void()>>( observer ); 214 // The observer is called with the mutex held, 215 // Let's hope it doesn't do anything too funky. 216 (*fptr)(); 217 } 218 } 219 } 220 } 221 222 namespace { 223 bool isSymLink( const std::string& file_name ) 224 { 225 #ifdef HAVE_SYMLINK 226 struct stat buf; 227 228 lstat( file_name.c_str(), &buf ); 229 return ( S_ISLNK(buf.st_mode) ); 230 #else 231 return false; 232 #endif 233 } 234 }; 235 236 #endif 237