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