xref: /glogg/src/watchtower.h (revision 91f7c70525aef93239c2facfe440e65d1672a468)
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         {
131             LOG(logDEBUG) << "WatchTower::addFile dir for " << file_name
132                 << " not watched, adding...";
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 
142         // Associate the dir to the file
143         new_file->dir_ = dir;
144 
145         LOG(logDEBUG) << "dir ref count is " << dir.use_count();
146     }
147     else
148     {
149         existing_observed_file->addCallback( ptr );
150     }
151 
152     // Returns a shared pointer that removes its own entry
153     // from the list of watched stuff when it goes out of scope!
154     // Uses a custom deleter to do the work.
155     return std::shared_ptr<void>( 0x0, [this, ptr, weakHeartBeat] (void*) {
156             if ( auto heart_beat = weakHeartBeat.lock() )
157                 WatchTower<Driver>::removeNotification( this, ptr );
158             } );
159 }
160 
161 template <typename Driver>
162 unsigned int WatchTower<Driver>::numberWatchedDirectories() const
163 {
164     return observed_file_list_.numberWatchedDirectories();
165 }
166 
167 //
168 // Private functions
169 //
170 
171 // Add the passed file name to the driver, returning the file and symlink id
172 template <typename Driver>
173 std::tuple<typename Driver::FileId, typename Driver::SymlinkId>
174 WatchTower<Driver>::addFileToDriver( const std::string& file_name )
175 {
176     typename Driver::SymlinkId symlink_id;
177     auto file_id = driver_.addFile( file_name );
178 
179     if ( isSymLink( file_name ) )
180     {
181         // We want to follow the name (as opposed to the inode)
182         // so we watch the symlink as well.
183         symlink_id = driver_.addSymlink( file_name );
184     }
185 
186     return std::make_tuple( file_id, symlink_id );
187 }
188 
189 // Called by the dtor for a registration object
190 template <typename Driver>
191 void WatchTower<Driver>::removeNotification(
192         WatchTower* watch_tower, std::shared_ptr<void> notification )
193 {
194     LOG(logDEBUG) << "WatchTower::removeNotification";
195 
196     std::lock_guard<std::mutex> lock( watch_tower->observers_mutex_ );
197 
198     auto file =
199         watch_tower->observed_file_list_.removeCallback( notification );
200 
201     if ( file )
202     {
203         watch_tower->driver_.removeFile( file->file_id_ );
204         watch_tower->driver_.removeSymlink( file->symlink_id_ );
205     }
206 }
207 
208 // Run in its own thread
209 template <typename Driver>
210 void WatchTower<Driver>::run()
211 {
212     while ( running_ ) {
213         std::unique_lock<std::mutex> lock( observers_mutex_ );
214 
215         std::vector<ObservedFile<Driver>*> files_needing_readding;
216 
217         auto files = driver_.waitAndProcessEvents(
218                 &observed_file_list_, &lock, &files_needing_readding );
219         LOG(logDEBUG) << "WatchTower::run: waitAndProcessEvents returned "
220             << files.size() << " files, " << files_needing_readding.size()
221             << " needing re-adding";
222 
223         for ( auto file: files_needing_readding ) {
224             // A file 'needing readding' has the same name,
225             // but probably a different inode, so it needs
226             // to be readded for some drivers that rely on the
227             // inode (e.g. inotify)
228             driver_.removeFile( file->file_id_ );
229             driver_.removeSymlink( file->symlink_id_ );
230 
231             std::tie( file->file_id_, file->symlink_id_ ) =
232                 addFileToDriver( file->file_name_ );
233         }
234 
235         for ( auto file: files ) {
236             for ( auto observer: file->callbacks ) {
237                 LOG(logDEBUG) << "WatchTower::run: notifying the client!";
238                 // Here we have to cast our generic pointer back to
239                 // the function pointer in order to perform the call
240                 const std::shared_ptr<std::function<void()>> fptr =
241                     std::static_pointer_cast<std::function<void()>>( observer );
242                 // The observer is called with the mutex held,
243                 // Let's hope it doesn't do anything too funky.
244                 (*fptr)();
245             }
246         }
247     }
248 }
249 
250 namespace {
251     bool isSymLink( const std::string& file_name )
252     {
253 #ifdef HAVE_SYMLINK
254         struct stat buf;
255 
256         lstat( file_name.c_str(), &buf );
257         return ( S_ISLNK(buf.st_mode) );
258 #else
259         return false;
260 #endif
261     }
262 };
263 
264 #endif
265