1 /*
2 * Copyright (C) 2015 Nicolas Bonnefon and other contributors
3 *
4 * This file is part of glogg.
5 *
6 * glogg is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * glogg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with glogg. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #ifndef WATCHTOWER_H
21 #define WATCHTOWER_H
22
23 #include "config.h"
24
25 #include <memory>
26 #include <atomic>
27 #include <thread>
28 #include <mutex>
29
30 #include "watchtowerlist.h"
31
32 // Allow the client to register for notification on an arbitrary number
33 // of files. It will be notified to any change (creation/deletion/modification)
34 // on those files.
35 // It is passed a platform specific driver.
36
37 // FIXME: Where to put that so it is not dependant
38 // Registration object to implement RAII
39 #ifdef HAS_TEMPLATE_ALIASES
40 using Registration = std::shared_ptr<void>;
41 #else
42 typedef std::shared_ptr<void> Registration;
43 #endif
44
45 template<typename Driver>
46 class WatchTower {
47 public:
48 // Create an empty watchtower
49 WatchTower();
50 // Destroy the object
51 ~WatchTower();
52
53 // Set the polling interval (in ms)
54 // 0 disables polling and is the default
55 void setPollingInterval( uint32_t interval_ms );
56
57 // Add a file to the notification list. notification will be called when
58 // the file is modified, moved or deleted.
59 // Lifetime of the notification is tied to the Registration object returned.
60 // Note the notification function is called with a mutex held and in a
61 // third party thread, beware of races!
62 Registration addFile( const std::string& file_name,
63 std::function<void()> notification );
64
65 // Number of watched directories (for tests)
66 unsigned int numberWatchedDirectories() const;
67
68 private:
69 // The driver (parametrised)
70 Driver driver_;
71
72 // List of files/dirs observed
73 ObservedFileList<Driver> observed_file_list_;
74
75 // Protects the observed_file_list_
76 std::mutex observers_mutex_;
77
78 // Polling interval (0 disables polling)
79 uint32_t polling_interval_ms_ = 0;
80
81 // Thread
82 std::atomic_bool running_;
83 std::thread thread_;
84
85 // Exist as long as the onject exists, to ensure observers won't try to
86 // call us if we are dead.
87 std::shared_ptr<void> heartBeat_;
88
89 // Private member functions
90 std::tuple<typename Driver::FileId, typename Driver::SymlinkId>
91 addFileToDriver( const std::string& );
92 static void removeNotification( WatchTower* watch_tower,
93 std::shared_ptr<void> notification );
94 void run();
95 };
96
97 // Class template implementation
98
99 #include <algorithm>
100
101 #include <sys/types.h>
102 #include <sys/stat.h>
103 #include <unistd.h>
104
105 #include "log.h"
106
107 namespace {
108 bool isSymLink( const std::string& file_name );
109 std::string directory_path( const std::string& path );
110 };
111
112 template <typename Driver>
WatchTower()113 WatchTower<Driver>::WatchTower()
114 : driver_(), thread_(),
115 heartBeat_(std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {}))
116 {
117 running_ = true;
118 thread_ = std::thread( &WatchTower::run, this );
119 }
120
121 template <typename Driver>
~WatchTower()122 WatchTower<Driver>::~WatchTower()
123 {
124 running_ = false;
125 driver_.interruptWait();
126 thread_.join();
127 }
128
129 template <typename Driver>
setPollingInterval(uint32_t interval_ms)130 void WatchTower<Driver>::setPollingInterval( uint32_t interval_ms )
131 {
132 if ( polling_interval_ms_ != interval_ms ) {
133 polling_interval_ms_ = interval_ms;
134 // Break out of the wait
135 if ( polling_interval_ms_ > 0 )
136 driver_.interruptWait();
137 }
138 }
139
140 template <typename Driver>
addFile(const std::string & file_name,std::function<void ()> notification)141 Registration WatchTower<Driver>::addFile(
142 const std::string& file_name,
143 std::function<void()> notification )
144 {
145 LOG(logDEBUG) << "WatchTower::addFile " << file_name;
146
147 std::weak_ptr<void> weakHeartBeat(heartBeat_);
148
149 std::lock_guard<std::mutex> lock( observers_mutex_ );
150
151 auto existing_observed_file =
152 observed_file_list_.searchByName( file_name );
153
154 std::shared_ptr<std::function<void()>> ptr( new std::function<void()>(std::move( notification )) );
155
156 if ( ! existing_observed_file )
157 {
158 typename Driver::FileId file_id;
159 typename Driver::SymlinkId symlink_id;
160
161 std::tie( file_id, symlink_id ) = addFileToDriver( file_name );
162 auto new_file = observed_file_list_.addNewObservedFile(
163 ObservedFile<Driver>( file_name, ptr, file_id, symlink_id ) );
164
165 auto dir = observed_file_list_.watchedDirectoryForFile( file_name );
166 if ( ! dir ) {
167 LOG(logDEBUG) << "WatchTower::addFile dir for " << file_name
168 << " not watched, adding...";
169
170 dir = observed_file_list_.addWatchedDirectoryForFile( file_name,
171 [this, weakHeartBeat] (ObservedDir<Driver>* dir) {
172 if ( auto heart_beat = weakHeartBeat.lock() ) {
173 driver_.removeDir( dir->dir_id_ );
174 } } );
175
176 dir->dir_id_ = driver_.addDir( dir->path );
177
178 if ( ! dir->dir_id_.valid() ) {
179 LOG(logWARNING) << "WatchTower::addFile driver failed to add dir";
180 dir = nullptr;
181 }
182 }
183 else {
184 LOG(logDEBUG) << "WatchTower::addFile Found exisiting watch for dir " << file_name;
185 }
186
187 // Associate the dir to the file
188 if ( dir )
189 new_file->dir_ = dir;
190 }
191 else
192 {
193 LOG(logDEBUG) << "WatchTower::addFile add extra callback for already monitored " << file_name;
194 existing_observed_file->addCallback( ptr );
195 }
196
197 // Returns a shared pointer that removes its own entry
198 // from the list of watched stuff when it goes out of scope!
199 // Uses a custom deleter to do the work.
200 return std::shared_ptr<void>( 0x0, [this, ptr, weakHeartBeat] (void*) {
201 if ( auto heart_beat = weakHeartBeat.lock() )
202 WatchTower<Driver>::removeNotification( this, ptr );
203 } );
204 }
205
206 template <typename Driver>
numberWatchedDirectories()207 unsigned int WatchTower<Driver>::numberWatchedDirectories() const
208 {
209 return observed_file_list_.numberWatchedDirectories();
210 }
211
212 //
213 // Private functions
214 //
215
216 // Add the passed file name to the driver, returning the file and symlink id
217 template <typename Driver>
218 std::tuple<typename Driver::FileId, typename Driver::SymlinkId>
addFileToDriver(const std::string & file_name)219 WatchTower<Driver>::addFileToDriver( const std::string& file_name )
220 {
221 typename Driver::SymlinkId symlink_id;
222 auto file_id = driver_.addFile( file_name );
223
224 if ( isSymLink( file_name ) )
225 {
226 // We want to follow the name (as opposed to the inode)
227 // so we watch the symlink as well.
228 symlink_id = driver_.addSymlink( file_name );
229 }
230
231 return std::make_tuple( file_id, symlink_id );
232 }
233
234 // Called by the dtor for a registration object
235 template <typename Driver>
removeNotification(WatchTower * watch_tower,std::shared_ptr<void> notification)236 void WatchTower<Driver>::removeNotification(
237 WatchTower* watch_tower, std::shared_ptr<void> notification )
238 {
239 LOG(logDEBUG) << "WatchTower::removeNotification";
240
241 std::lock_guard<std::mutex> lock( watch_tower->observers_mutex_ );
242
243 auto file =
244 watch_tower->observed_file_list_.removeCallback( notification );
245
246 if ( file )
247 {
248 LOG(logDEBUG) << "WatchTower::removeNotification - remove the file";
249 watch_tower->driver_.removeFile( file->file_id_ );
250 watch_tower->driver_.removeSymlink( file->symlink_id_ );
251 }
252 }
253
254 // Run in its own thread
255 template <typename Driver>
run()256 void WatchTower<Driver>::run()
257 {
258 while ( running_ ) {
259 std::unique_lock<std::mutex> lock( observers_mutex_ );
260
261 std::vector<ObservedFile<Driver>*> files_needing_readding;
262
263 auto files = driver_.waitAndProcessEvents(
264 &observed_file_list_, &lock, &files_needing_readding, polling_interval_ms_ );
265 LOG(logDEBUG) << "WatchTower::run: waitAndProcessEvents returned "
266 << files.size() << " files, " << files_needing_readding.size()
267 << " needing re-adding";
268
269 for ( auto file: files_needing_readding ) {
270 // A file 'needing readding' has the same name,
271 // but probably a different inode, so it needs
272 // to be readded for some drivers that rely on the
273 // inode (e.g. inotify)
274 driver_.removeFile( file->file_id_ );
275 driver_.removeSymlink( file->symlink_id_ );
276
277 std::tie( file->file_id_, file->symlink_id_ ) =
278 addFileToDriver( file->file_name_ );
279 }
280
281 for ( auto file: files ) {
282 for ( auto observer: file->callbacks ) {
283 LOG(logDEBUG) << "WatchTower::run: notifying the client!";
284 // Here we have to cast our generic pointer back to
285 // the function pointer in order to perform the call
286 const std::shared_ptr<std::function<void()>> fptr =
287 std::static_pointer_cast<std::function<void()>>( observer );
288 // The observer is called with the mutex held,
289 // Let's hope it doesn't do anything too funky.
290 (*fptr)();
291
292 file->markAsChanged();
293 }
294 }
295
296 if ( polling_interval_ms_ > 0 ) {
297 // Also call files that have not been called for a while
298 for ( auto file: observed_file_list_ ) {
299 uint32_t ms_since_last_check =
300 std::chrono::duration_cast<std::chrono::milliseconds>(
301 std::chrono::steady_clock::now() - file->timeForLastCheck() ).count();
302 if ( ( ms_since_last_check > polling_interval_ms_ ) && file->hasChanged() ) {
303 LOG(logDEBUG) << "WatchTower::run: " << file->file_name_;
304 for ( auto observer: file->callbacks ) {
305 LOG(logDEBUG) << "WatchTower::run: notifying the client because of a timeout!";
306 // Here we have to cast our generic pointer back to
307 // the function pointer in order to perform the call
308 const std::shared_ptr<std::function<void()>> fptr =
309 std::static_pointer_cast<std::function<void()>>( observer );
310 // The observer is called with the mutex held,
311 // Let's hope it doesn't do anything too funky.
312 (*fptr)();
313 }
314 file->markAsChanged();
315 }
316 }
317 }
318 }
319 }
320
321 namespace {
isSymLink(const std::string & file_name)322 bool isSymLink( const std::string& file_name )
323 {
324 #ifdef HAVE_SYMLINK
325 struct stat buf;
326
327 lstat( file_name.c_str(), &buf );
328 return ( S_ISLNK(buf.st_mode) );
329 #else
330 return false;
331 #endif
332 }
333 };
334
335 #endif
336