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