xref: /glogg/src/watchtowerlist.h (revision dfb2a39cb717e84f8a91ff75f7d49658243c4ca1)
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 
14 #include "log.h"
15 
16 // Utility classes
17 struct ProtocolInfo {
18     // Win32 notification variables
19     static const int READ_DIR_CHANGE_BUFFER_SIZE = 4096;
20 
21     void* handle_;
22     static const unsigned long buffer_length_ = READ_DIR_CHANGE_BUFFER_SIZE;
23     char buffer_[buffer_length_];
24 };
25 
26 // List of files and observers
27 template <typename Driver>
28 struct ObservedDir {
29     ObservedDir( const std::string this_path ) : path { this_path } {}
30 
31     // Returns the address of the protocol specific informations
32     ProtocolInfo* protocolInfo() { return &protocol_info_; }
33 
34     std::string path;
35     typename Driver::DirId dir_id_;
36     // Contains data specific to the protocol (inotify/Win32...)
37     ProtocolInfo protocol_info_;
38 };
39 
40 template <typename Driver>
41 struct ObservedFile {
42     ObservedFile(
43             const std::string& file_name,
44             std::shared_ptr<void> callback,
45             typename Driver::FileId file_id,
46             typename Driver::SymlinkId symlink_id )
47         : file_name_( file_name ) {
48         addCallback( callback );
49 
50         file_id_    = file_id;
51         symlink_id_ = symlink_id;
52         dir_        = nullptr;
53     }
54 
55     void addCallback( std::shared_ptr<void> callback ) {
56         callbacks.push_back( callback );
57     }
58 
59     std::string file_name_;
60     // List of callbacks for this file
61     std::vector<std::shared_ptr<void>> callbacks;
62 
63     // watch descriptor for the file itself
64     typename Driver::FileId file_id_;
65     // watch descriptor for the symlink (if file is a symlink)
66     typename Driver::SymlinkId symlink_id_;
67 
68     // link to the dir containing the file
69     std::shared_ptr<ObservedDir<Driver>> dir_;
70 };
71 
72 // A list of the observed files and directories
73 // This class is not thread safe
74 template<typename Driver>
75 class ObservedFileList {
76     public:
77         ObservedFileList() :
78             heartBeat_ { std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {}) }
79             { }
80         ~ObservedFileList() = default;
81 
82         // The functions return a pointer to the existing file (if exists)
83         // but keep ownership of the object.
84         ObservedFile<Driver>* searchByName( const std::string& file_name );
85         ObservedFile<Driver>* searchByFileOrSymlinkWd(
86                 typename Driver::FileId file_id,
87                 typename Driver::SymlinkId symlink_id );
88         ObservedFile<Driver>* searchByDirWdAndName(
89                 typename Driver::DirId id, const char* name );
90 
91         // Add a new file, the list returns a pointer to the added file,
92         // but has ownership of the file.
93         ObservedFile<Driver>* addNewObservedFile( ObservedFile<Driver> new_observed );
94         // Remove a callback, remove and returns the file object if
95         // it was the last callback on this object, nullptr if not.
96         // The caller has ownership of the object.
97         std::shared_ptr<ObservedFile<Driver>> removeCallback(
98                 std::shared_ptr<void> callback );
99 
100         // Return the watched directory if it is watched, or nullptr
101         std::shared_ptr<ObservedDir<Driver>> watchedDirectory( const std::string& dir_name );
102         // Create a new watched directory for dir_name, the client is passed
103         // shared ownership and have to keep the shared_ptr (the list only
104         // maintain a weak link).
105         // The remove notification is called just before the reference to
106         // the directory is destroyed.
107         std::shared_ptr<ObservedDir<Driver>> addWatchedDirectory(
108             const std::string& dir_name,
109             std::function<void( ObservedDir<Driver>* )> remove_notification );
110 
111         // Similar to previous functions but extract the name of the
112         // directory from the file name.
113         std::shared_ptr<ObservedDir<Driver>> watchedDirectoryForFile( const std::string& file_name );
114         std::shared_ptr<ObservedDir<Driver>> addWatchedDirectoryForFile( const std::string& file_name,
115                 std::function<void( ObservedDir<Driver>* )> remove_notification );
116 
117         // Removal of directories is done when there is no shared reference
118         // left (RAII)
119 
120         // Number of watched directories (for tests)
121         unsigned int numberWatchedDirectories() const;
122 
123     private:
124         // List of observed files
125         std::list<ObservedFile<Driver>> observed_files_;
126 
127         // List of observed dirs, key-ed by name
128         std::map<std::string, std::weak_ptr<ObservedDir<Driver>>> observed_dirs_;
129 
130         // Map the inotify file (including symlinks) wds to the observed file
131         std::map<int, ObservedFile<Driver>*> by_file_wd_;
132         // Map the inotify directory wds to the observed files
133         std::map<int, ObservedFile<Driver>*> by_dir_wd_;
134 
135         // Heartbeat
136         std::shared_ptr<void> heartBeat_;
137 
138         // Clean all reference to any expired directory
139         void cleanRefsToExpiredDirs();
140 };
141 
142 namespace {
143     std::string directory_path( const std::string& path );
144 };
145 
146 // ObservedFileList class
147 template <typename Driver>
148 ObservedFile<Driver>* ObservedFileList<Driver>::searchByName(
149         const std::string& file_name )
150 {
151     // Look for an existing observer on this file
152     auto existing_observer = observed_files_.begin();
153     for ( ; existing_observer != observed_files_.end(); ++existing_observer )
154     {
155         if ( existing_observer->file_name_ == file_name )
156         {
157             LOG(logDEBUG) << "Found " << file_name;
158             break;
159         }
160     }
161 
162     if ( existing_observer != observed_files_.end() )
163         return &( *existing_observer );
164     else
165         return nullptr;
166 }
167 
168 template <typename Driver>
169 ObservedFile<Driver>* ObservedFileList<Driver>::searchByFileOrSymlinkWd(
170         typename Driver::FileId file_id,
171         typename Driver::SymlinkId symlink_id )
172 {
173     auto result = find_if( observed_files_.begin(), observed_files_.end(),
174             [file_id, symlink_id] (ObservedFile<Driver> file) -> bool {
175                 return ( file_id == file.file_id_ ) ||
176                     ( symlink_id == file.symlink_id_ );
177                 } );
178 
179     if ( result != observed_files_.end() )
180         return &( *result );
181     else
182         return nullptr;
183 }
184 
185 template <typename Driver>
186 ObservedFile<Driver>* ObservedFileList<Driver>::searchByDirWdAndName(
187         typename Driver::DirId id, const char* name )
188 {
189     auto dir = find_if( observed_dirs_.begin(), observed_dirs_.end(),
190             [id] (std::pair<std::string,std::weak_ptr<ObservedDir<Driver>>> d) -> bool {
191             if ( auto dir = d.second.lock() ) {
192                 return ( id == dir->dir_id_ );
193             }
194             else {
195                 return false; } } );
196 
197     if ( dir != observed_dirs_.end() ) {
198         std::string path = dir->first + "/" + name;
199 
200         // LOG(logDEBUG) << "Testing path: " << path;
201 
202         // Looking for the path in the files we are watching
203         return searchByName( path );
204     }
205     else {
206         return nullptr;
207     }
208 }
209 
210 template <typename Driver>
211 ObservedFile<Driver>* ObservedFileList<Driver>::addNewObservedFile(
212         ObservedFile<Driver> new_observed )
213 {
214     auto new_file = observed_files_.insert( std::begin( observed_files_ ), new_observed );
215 
216     return &( *new_file );
217 }
218 
219 template <typename Driver>
220 std::shared_ptr<ObservedFile<Driver>> ObservedFileList<Driver>::removeCallback(
221         std::shared_ptr<void> callback )
222 {
223     std::shared_ptr<ObservedFile<Driver>> returned_file = nullptr;
224 
225     for ( auto observer = begin( observed_files_ );
226             observer != end( observed_files_ ); )
227     {
228         std::vector<std::shared_ptr<void>>& callbacks = observer->callbacks;
229         callbacks.erase( std::remove(
230                     std::begin( callbacks ), std::end( callbacks ), callback ),
231                 std::end( callbacks ) );
232 
233         /* See if all notifications have been deleted for this file */
234         if ( callbacks.empty() ) {
235             LOG(logDEBUG) << "Empty notification list for " << observer->file_name_
236                 << ", removing the watched file";
237             returned_file = std::make_shared<ObservedFile<Driver>>( *observer );
238             observer = observed_files_.erase( observer );
239         }
240         else {
241             ++observer;
242         }
243     }
244 
245     return returned_file;
246 }
247 
248 template <typename Driver>
249 std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::watchedDirectory(
250         const std::string& dir_name )
251 {
252     std::shared_ptr<ObservedDir<Driver>> dir = nullptr;
253 
254     if ( observed_dirs_.find( dir_name ) != std::end( observed_dirs_ ) )
255         dir = observed_dirs_[ dir_name ].lock();
256 
257     return dir;
258 }
259 
260 template <typename Driver>
261 std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::addWatchedDirectory(
262         const std::string& dir_name,
263         std::function<void( ObservedDir<Driver>* )> remove_notification )
264 {
265     std::weak_ptr<void> weakHeartBeat(heartBeat_);
266 
267     std::shared_ptr<ObservedDir<Driver>> dir = {
268             new ObservedDir<Driver>( dir_name ),
269             [this, remove_notification, weakHeartBeat] (ObservedDir<Driver>* d) {
270                 if ( auto heart_beat = weakHeartBeat.lock() ) {
271                     remove_notification( d );
272                     this->cleanRefsToExpiredDirs();
273                 }
274                 delete d; } };
275 
276     observed_dirs_[ dir_name ] = std::weak_ptr<ObservedDir<Driver>>( dir );
277 
278     return dir;
279 }
280 
281 template <typename Driver>
282 std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::watchedDirectoryForFile(
283         const std::string& file_name )
284 {
285     return watchedDirectory( directory_path( file_name ) );
286 }
287 
288 template <typename Driver>
289 std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::addWatchedDirectoryForFile(
290         const std::string& file_name,
291         std::function<void( ObservedDir<Driver>* )> remove_notification )
292 {
293     return addWatchedDirectory( directory_path( file_name ),
294             remove_notification );
295 }
296 
297 template <typename Driver>
298 unsigned int ObservedFileList<Driver>::numberWatchedDirectories() const
299 {
300     return observed_dirs_.size();
301 }
302 
303 // Private functions
304 template <typename Driver>
305 void ObservedFileList<Driver>::cleanRefsToExpiredDirs()
306 {
307     for ( auto it = std::begin( observed_dirs_ );
308             it != std::end( observed_dirs_ ); )
309     {
310         if ( it->second.expired() ) {
311             LOG(logDEBUG) << "No lock ";
312             it = observed_dirs_.erase( it );
313         }
314         else {
315             ++it;
316         }
317     }
318 }
319 
320 namespace {
321     std::string directory_path( const std::string& path )
322     {
323         size_t slash_pos = path.rfind( '/' );
324 
325 #ifdef _WIN32
326         if ( slash_pos == std::string::npos ) {
327             slash_pos = path.rfind( '\\' );
328         }
329 
330         // We need to include the final slash on Windows
331         ++slash_pos;
332 #endif
333 
334         return std::string( path, 0, slash_pos );
335     }
336 };
337 #endif
338