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