1 #ifndef WATCHTOWERLIST_H
2 #define WATCHTOWERLIST_H
3
4 // Utility classes for the WatchTower implementations
5
6 #include <functional>
7 #include <string>
8 #include <vector>
9 #include <map>
10 #include <list>
11 #include <memory>
12 #include <algorithm>
13 #include <chrono>
14
15 #include "log.h"
16
17 // Utility classes
18 struct ProtocolInfo {
19 // Win32 notification variables
20 static const int READ_DIR_CHANGE_BUFFER_SIZE = 4096;
21
22 void* handle_;
23 static const unsigned long buffer_length_ = READ_DIR_CHANGE_BUFFER_SIZE;
24 char buffer_[buffer_length_];
25 };
26
27 // List of files and observers
28 template <typename Driver>
29 struct ObservedDir {
ObservedDirObservedDir30 ObservedDir( const std::string this_path ) : path { this_path } {}
31
32 // Returns the address of the protocol specific informations
protocolInfoObservedDir33 ProtocolInfo* protocolInfo() { return &protocol_info_; }
34
35 std::string path;
36 typename Driver::DirId dir_id_;
37 // Contains data specific to the protocol (inotify/Win32...)
38 ProtocolInfo protocol_info_;
39 };
40
41 template <typename Driver>
42 struct ObservedFile {
ObservedFileObservedFile43 ObservedFile(
44 const std::string& file_name,
45 std::shared_ptr<void> callback,
46 typename Driver::FileId file_id,
47 typename Driver::SymlinkId symlink_id )
48 : file_name_( file_name ) {
49 addCallback( callback );
50
51 file_id_ = file_id;
52 symlink_id_ = symlink_id;
53 dir_ = nullptr;
54
55 markAsChanged();
56 }
57
addCallbackObservedFile58 void addCallback( std::shared_ptr<void> callback ) {
59 callbacks.push_back( callback );
60 }
61
62 // Records the file has changed
markAsChangedObservedFile63 void markAsChanged() {
64 change_token_.readFromFile( file_name_ );
65 last_check_time_ = std::chrono::steady_clock::now();
66 }
67
68 // Returns whether a file has changed
69 // (for polling)
hasChangedObservedFile70 bool hasChanged() {
71 typename Driver::FileChangeToken new_token( file_name_ );
72 last_check_time_ = std::chrono::steady_clock::now();
73 return change_token_ != new_token;
74 }
75
timeForLastCheckObservedFile76 std::chrono::steady_clock::time_point timeForLastCheck() {
77 return last_check_time_;
78 }
79
80 std::string file_name_;
81 // List of callbacks for this file
82 std::vector<std::shared_ptr<void>> callbacks;
83
84 // watch descriptor for the file itself
85 typename Driver::FileId file_id_;
86 // watch descriptor for the symlink (if file is a symlink)
87 typename Driver::SymlinkId symlink_id_;
88
89 // link to the dir containing the file
90 std::shared_ptr<ObservedDir<Driver>> dir_;
91
92 // token to identify modification
93 // (the token change when the file is modified, this is used when polling)
94 typename Driver::FileChangeToken change_token_;
95
96 // Last time a check has been done
97 std::chrono::steady_clock::time_point last_check_time_ =
98 std::chrono::steady_clock::time_point::min();
99 };
100
101 // A list of the observed files and directories
102 // This class is not thread safe
103 template<typename Driver>
104 class ObservedFileList {
105 public:
ObservedFileList()106 ObservedFileList() :
107 // Use an empty deleter since we don't really own this.
108 heartBeat_ { this, [] (void*) {} }
109 { }
110 ObservedFileList( const ObservedFileList& ) = delete;
111 ~ObservedFileList() = default;
112
113 // The functions return a pointer to the existing file (if exists)
114 // but keep ownership of the object.
115 ObservedFile<Driver>* searchByName( const std::string& file_name );
116 ObservedFile<Driver>* searchByFileOrSymlinkWd(
117 typename Driver::FileId file_id,
118 typename Driver::SymlinkId symlink_id );
119 ObservedFile<Driver>* searchByDirWdAndName(
120 typename Driver::DirId id, const char* name );
121 std::vector<ObservedFile<Driver>*> searchByDirWd(
122 typename Driver::DirId id );
123
124 // Add a new file, the list returns a pointer to the added file,
125 // but has ownership of the file.
126 ObservedFile<Driver>* addNewObservedFile( ObservedFile<Driver> new_observed );
127 // Remove a callback, remove and returns the file object if
128 // it was the last callback on this object, nullptr if not.
129 // The caller has ownership of the object.
130 std::shared_ptr<ObservedFile<Driver>> removeCallback(
131 std::shared_ptr<void> callback );
132
133 // Return the watched directory if it is watched, or nullptr
134 std::shared_ptr<ObservedDir<Driver>> watchedDirectory( const std::string& dir_name );
135 // Create a new watched directory for dir_name, the client is passed
136 // shared ownership and have to keep the shared_ptr (the list only
137 // maintain a weak link).
138 // The remove notification is called just before the reference to
139 // the directory is destroyed.
140 std::shared_ptr<ObservedDir<Driver>> addWatchedDirectory(
141 const std::string& dir_name,
142 std::function<void( ObservedDir<Driver>* )> remove_notification );
143
144 // Similar to previous functions but extract the name of the
145 // directory from the file name.
146 std::shared_ptr<ObservedDir<Driver>> watchedDirectoryForFile( const std::string& file_name );
147 std::shared_ptr<ObservedDir<Driver>> addWatchedDirectoryForFile( const std::string& file_name,
148 std::function<void( ObservedDir<Driver>* )> remove_notification );
149
150 // Removal of directories is done when there is no shared reference
151 // left (RAII)
152
153 // Number of watched directories (for tests)
154 unsigned int numberWatchedDirectories() const;
155
156 // Iterator
157 template<typename Container>
158 class iterator : std::iterator<std::input_iterator_tag, ObservedFile<Driver>> {
159 public:
iterator(Container * list,const typename Container::iterator & iter)160 iterator( Container* list,
161 const typename Container::iterator& iter )
162 { list_ = list; pos_ = iter; }
163
164 iterator operator++()
165 { ++pos_; return *this; }
166
167 bool operator==( const iterator& other )
168 { return ( pos_ == other.pos_ ); }
169
170 bool operator!=( const iterator& other )
171 { return ! operator==( other ); }
172
173 typename Container::iterator operator*()
174 { return pos_; }
175
176 private:
177 Container* list_;
178 typename Container::iterator pos_;
179 };
180
begin()181 iterator<std::list<ObservedFile<Driver>>> begin()
182 { return iterator<std::list<ObservedFile<Driver>>>( &observed_files_, observed_files_.begin() ); }
end()183 iterator<std::list<ObservedFile<Driver>>> end()
184 { return iterator<std::list<ObservedFile<Driver>>>( &observed_files_, observed_files_.end() ); }
185
186 private:
187 // List of observed files
188 std::list<ObservedFile<Driver>> observed_files_;
189
190 // List of observed dirs, key-ed by name
191 std::map<std::string, std::weak_ptr<ObservedDir<Driver>>> observed_dirs_;
192
193 // Map the inotify file (including symlinks) wds to the observed file
194 std::map<int, ObservedFile<Driver>*> by_file_wd_;
195 // Map the inotify directory wds to the observed files
196 std::map<int, ObservedFile<Driver>*> by_dir_wd_;
197
198 // A shared_ptr to self.
199 // weak_ptr's from this can check if the list is still alive.
200 // This probably shouldn't be copied.
201 std::shared_ptr<ObservedFileList> heartBeat_;
202
203 // Clean all reference to any expired directory
204 void cleanRefsToExpiredDirs();
205 };
206
207 namespace {
208 std::string directory_path( const std::string& path );
209 };
210
211 // ObservedFileList class
212 template <typename Driver>
searchByName(const std::string & file_name)213 ObservedFile<Driver>* ObservedFileList<Driver>::searchByName(
214 const std::string& file_name )
215 {
216 // Look for an existing observer on this file
217 auto existing_observer = observed_files_.begin();
218 for ( ; existing_observer != observed_files_.end(); ++existing_observer )
219 {
220 if ( existing_observer->file_name_ == file_name )
221 {
222 LOG(logDEBUG) << "Found " << file_name;
223 break;
224 }
225 }
226
227 if ( existing_observer != observed_files_.end() )
228 return &( *existing_observer );
229 else
230 return nullptr;
231 }
232
233 template <typename Driver>
searchByFileOrSymlinkWd(typename Driver::FileId file_id,typename Driver::SymlinkId symlink_id)234 ObservedFile<Driver>* ObservedFileList<Driver>::searchByFileOrSymlinkWd(
235 typename Driver::FileId file_id,
236 typename Driver::SymlinkId symlink_id )
237 {
238 auto result = find_if( observed_files_.begin(), observed_files_.end(),
239 [file_id, symlink_id] (ObservedFile<Driver> file) -> bool {
240 return ( file_id == file.file_id_ ) ||
241 ( symlink_id == file.symlink_id_ );
242 } );
243
244 if ( result != observed_files_.end() )
245 return &( *result );
246 else
247 return nullptr;
248 }
249
250 template <typename Driver>
searchByDirWdAndName(typename Driver::DirId id,const char * name)251 ObservedFile<Driver>* ObservedFileList<Driver>::searchByDirWdAndName(
252 typename Driver::DirId id, const char* name )
253 {
254 auto dir = find_if( observed_dirs_.begin(), observed_dirs_.end(),
255 [id] (std::pair<std::string,std::weak_ptr<ObservedDir<Driver>>> d) -> bool {
256 if ( auto dir = d.second.lock() ) {
257 return ( id == dir->dir_id_ );
258 }
259 else {
260 return false; } } );
261
262 if ( dir != observed_dirs_.end() ) {
263 std::string path = dir->first + "/" + name;
264
265 // LOG(logDEBUG) << "Testing path: " << path;
266
267 // Looking for the path in the files we are watching
268 return searchByName( path );
269 }
270 else {
271 return nullptr;
272 }
273 }
274
275 template <typename Driver>
searchByDirWd(typename Driver::DirId id)276 std::vector<ObservedFile<Driver>*> ObservedFileList<Driver>::searchByDirWd(
277 typename Driver::DirId id )
278 {
279 std::vector<ObservedFile<Driver>*> result;
280
281 auto dir = find_if( observed_dirs_.begin(), observed_dirs_.end(),
282 [id] (std::pair<std::string,std::weak_ptr<ObservedDir<Driver>>> d) -> bool {
283 if ( auto dir = d.second.lock() ) {
284 return ( id == dir->dir_id_ );
285 }
286 else {
287 return false; } } );
288
289 if ( dir != observed_dirs_.end() ) {
290 for ( auto i = observed_files_.begin(); i != observed_files_.end(); ++i ) {
291 if ( auto d = dir->second.lock() ) {
292 if ( d.get() == i->dir_.get() ) {
293 result.push_back( &(*i) );
294 }
295 }
296 }
297 }
298
299 return result;
300 }
301
302 template <typename Driver>
addNewObservedFile(ObservedFile<Driver> new_observed)303 ObservedFile<Driver>* ObservedFileList<Driver>::addNewObservedFile(
304 ObservedFile<Driver> new_observed )
305 {
306 auto new_file = observed_files_.insert( std::begin( observed_files_ ), new_observed );
307
308 return &( *new_file );
309 }
310
311 template <typename Driver>
removeCallback(std::shared_ptr<void> callback)312 std::shared_ptr<ObservedFile<Driver>> ObservedFileList<Driver>::removeCallback(
313 std::shared_ptr<void> callback )
314 {
315 std::shared_ptr<ObservedFile<Driver>> returned_file = nullptr;
316
317 for ( auto observer = std::begin( observed_files_ );
318 observer != std::end( observed_files_ ); )
319 {
320 std::vector<std::shared_ptr<void>>& callbacks = observer->callbacks;
321 callbacks.erase( std::remove(
322 std::begin( callbacks ), std::end( callbacks ), callback ),
323 std::end( callbacks ) );
324
325 /* See if all notifications have been deleted for this file */
326 if ( callbacks.empty() ) {
327 LOG(logDEBUG) << "Empty notification list for " << observer->file_name_
328 << ", removing the watched file";
329 returned_file = std::make_shared<ObservedFile<Driver>>( *observer );
330 observer = observed_files_.erase( observer );
331 }
332 else {
333 ++observer;
334 }
335 }
336
337 return returned_file;
338 }
339
340 template <typename Driver>
watchedDirectory(const std::string & dir_name)341 std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::watchedDirectory(
342 const std::string& dir_name )
343 {
344 std::shared_ptr<ObservedDir<Driver>> dir = nullptr;
345
346 if ( observed_dirs_.find( dir_name ) != std::end( observed_dirs_ ) )
347 dir = observed_dirs_[ dir_name ].lock();
348
349 return dir;
350 }
351
352 template <typename Driver>
addWatchedDirectory(const std::string & dir_name,std::function<void (ObservedDir<Driver> *)> remove_notification)353 std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::addWatchedDirectory(
354 const std::string& dir_name,
355 std::function<void( ObservedDir<Driver>* )> remove_notification )
356 {
357 std::weak_ptr<ObservedFileList> weakHeartBeat(heartBeat_);
358
359 std::shared_ptr<ObservedDir<Driver>> dir = {
360 new ObservedDir<Driver>( dir_name ),
361 [remove_notification, weakHeartBeat] (ObservedDir<Driver>* d) {
362 if ( auto list = weakHeartBeat.lock() ) {
363 remove_notification( d );
364 list->cleanRefsToExpiredDirs();
365 }
366 delete d; } };
367
368 observed_dirs_[ dir_name ] = std::weak_ptr<ObservedDir<Driver>>( dir );
369
370 return dir;
371 }
372
373 template <typename Driver>
watchedDirectoryForFile(const std::string & file_name)374 std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::watchedDirectoryForFile(
375 const std::string& file_name )
376 {
377 return watchedDirectory( directory_path( file_name ) );
378 }
379
380 template <typename Driver>
addWatchedDirectoryForFile(const std::string & file_name,std::function<void (ObservedDir<Driver> *)> remove_notification)381 std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::addWatchedDirectoryForFile(
382 const std::string& file_name,
383 std::function<void( ObservedDir<Driver>* )> remove_notification )
384 {
385 return addWatchedDirectory( directory_path( file_name ),
386 remove_notification );
387 }
388
389 template <typename Driver>
numberWatchedDirectories()390 unsigned int ObservedFileList<Driver>::numberWatchedDirectories() const
391 {
392 return observed_dirs_.size();
393 }
394
395 // Private functions
396 template <typename Driver>
cleanRefsToExpiredDirs()397 void ObservedFileList<Driver>::cleanRefsToExpiredDirs()
398 {
399 for ( auto it = std::begin( observed_dirs_ );
400 it != std::end( observed_dirs_ ); )
401 {
402 if ( it->second.expired() ) {
403 it = observed_dirs_.erase( it );
404 }
405 else {
406 ++it;
407 }
408 }
409 }
410
411 namespace {
directory_path(const std::string & path)412 std::string directory_path( const std::string& path )
413 {
414 size_t slash_pos = path.rfind( '/' );
415
416 #ifdef _WIN32
417 if ( slash_pos == std::string::npos ) {
418 slash_pos = path.rfind( '\\' );
419 }
420
421 // We need to include the final slash on Windows
422 ++slash_pos;
423 #endif
424
425 return std::string( path, 0, slash_pos );
426 }
427 };
428 #endif
429