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