xref: /glogg/src/winwatchtowerdriver.cpp (revision fcaa75578d41fbd1a1b35740bd5dc8176496a14b)
1 #include "winwatchtowerdriver.h"
2 
3 #define WIN32_LEAN_AND_MEAN
4 #include <windows.h>
5 #include <winbase.h>
6 
7 #include <map>
8 
9 #include "watchtowerlist.h"
10 #include "utils.h"
11 #include "log.h"
12 
13 namespace {
14     std::string shortstringize( const std::wstring& long_string );
15     std::wstring longstringize( const std::string& short_string );
16 };
17 
18 // Utility classes
19 
20 WinNotificationInfoList::WinNotificationInfoList( const char* buffer, size_t buffer_size )
21 {
22     pointer_ = buffer;
23     next_ = updateCurrentNotification( pointer_ );
24 }
25 
26 const char* WinNotificationInfoList::updateCurrentNotification(
27         const char* new_position )
28 {
29     using Action = WinNotificationInfo::Action;
30 
31     static const std::map<uint16_t, Action> int_to_action = {
32         { FILE_ACTION_ADDED, Action::ADDED },
33         { FILE_ACTION_REMOVED, Action::REMOVED },
34         { FILE_ACTION_MODIFIED, Action::MODIFIED },
35         { FILE_ACTION_RENAMED_OLD_NAME, Action::RENAMED_OLD_NAME },
36         { FILE_ACTION_RENAMED_NEW_NAME, Action::RENAMED_NEW_NAME },
37     };
38 
39     uint32_t next_offset = *( reinterpret_cast<const uint32_t*>( new_position ) );
40     uint32_t action      = *( reinterpret_cast<const uint32_t*>( new_position ) + 1 );
41     uint32_t length      = *( reinterpret_cast<const uint32_t*>( new_position ) + 2 );
42 
43     const std::wstring file_name = { reinterpret_cast<const wchar_t*>( new_position + 12 ), length / 2 };
44 
45     LOG(logDEBUG) << "Next: " << next_offset;
46     LOG(logDEBUG) << "Action: " << action;
47     LOG(logDEBUG) << "Length: " << length;
48 
49     current_notification_ = WinNotificationInfo( int_to_action.at( action ), file_name );
50 
51     return ( next_offset == 0 ) ? nullptr : new_position + next_offset;
52 }
53 
54 const char* WinNotificationInfoList::advanceToNext()
55 {
56     pointer_ = next_;
57     if ( pointer_ )
58         next_ = updateCurrentNotification( pointer_ );
59 
60     return pointer_;
61 }
62 
63 // WinWatchTowerDriver::FileChangeToken
64 
65 void WinWatchTowerDriver::FileChangeToken::readFromFile(
66         const std::string& file_name )
67 {
68     // On Windows, we open the file and get its last written date/time
69     // That seems to work alright in my tests, but who knows for sure?
70 
71     HANDLE hFile = CreateFile(
72             file_name.c_str(),
73             0,
74             FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
75             NULL,
76             OPEN_EXISTING,
77             FILE_ATTRIBUTE_NORMAL,
78             NULL );
79 
80     if ( hFile == (HANDLE)-1 ) {
81         DWORD err = GetLastError();
82         LOG(logERROR) << "FileChangeToken::readFromFile: failed with " << err;
83 
84         low_date_time_ = 0;
85         high_date_time_ = 0;
86 
87         return;
88     }
89     else {
90         BY_HANDLE_FILE_INFORMATION file_info;
91 
92         if ( GetFileInformationByHandle(
93                     hFile,
94                     &file_info ) ) {
95             low_date_time_ = file_info.ftLastWriteTime.dwLowDateTime;
96             high_date_time_ = file_info.ftLastWriteTime.dwHighDateTime;
97 
98             LOG(logDEBUG) << "FileChangeToken::readFromFile: low_date_time_ " << low_date_time_;
99             LOG(logDEBUG) << "FileChangeToken::readFromFile: high_date_time_ " << high_date_time_;
100         }
101         else {
102             DWORD err = GetLastError();
103             LOG(logERROR) << "FileChangeToken::readFromFile: failed with " << err;
104 
105             low_date_time_ = 0;
106             high_date_time_ = 0;
107         }
108     }
109 
110     CloseHandle(hFile);
111 }
112 
113 // WinWatchTowerDriver
114 
115 WinWatchTowerDriver::WinWatchTowerDriver()
116 {
117     hCompPort_ = CreateIoCompletionPort( INVALID_HANDLE_VALUE,
118             NULL,
119             0x0,
120             0);
121 }
122 
123 WinWatchTowerDriver::~WinWatchTowerDriver()
124 {
125 }
126 
127 WinWatchTowerDriver::FileId WinWatchTowerDriver::addFile(
128         const std::string& file_name )
129 {
130     // Nothing for Windows
131     return { };
132 }
133 
134 WinWatchTowerDriver::SymlinkId WinWatchTowerDriver::addSymlink(
135         const std::string& file_name )
136 {
137     // Nothing for Windows
138     return { };
139 }
140 
141 // This implementation is blocking, i.e. it will wait until the file
142 // is effectively loaded in the watchtower thread.
143 WinWatchTowerDriver::DirId WinWatchTowerDriver::addDir(
144         const std::string& file_name )
145 {
146     DirId dir_id { };
147 
148     // Add will be done in the watchtower thread
149     {
150         /*
151         std::lock_guard<std::mutex> lk( action_mutex_ );
152         scheduled_action_ = std::make_unique<Action>( [this, file_name, &dir_id] {
153             serialisedAddDir( file_name, dir_id );
154         } );
155         */
156         serialisedAddDir( file_name, dir_id );
157     }
158 
159     // Poke the thread
160     PostQueuedCompletionStatus( hCompPort_, 0, 0, NULL );
161 
162     // Wait for the add task to be completed
163     {
164         /*
165         std::unique_lock<std::mutex> lk( action_mutex_ );
166         action_done_cv_.wait( lk,
167                 [this]{ return ( scheduled_action_ == nullptr ); } );
168                 */
169     }
170 
171     LOG(logDEBUG) << "addDir returned " << dir_id.dir_record_;
172 
173     return dir_id;
174 }
175 
176 
177 void WinWatchTowerDriver::removeFile(
178         const WinWatchTowerDriver::FileId& )
179 {
180 }
181 
182 void WinWatchTowerDriver::removeSymlink( const SymlinkId& )
183 {
184 }
185 
186 void WinWatchTowerDriver::removeDir( const DirId& dir_id )
187 {
188     LOG(logDEBUG) << "Entering driver::removeDir";
189     if ( dir_id.dir_record_ ) {
190         void* handle = dir_id.dir_record_->handle_;
191 
192         LOG(logDEBUG) << "WinWatchTowerDriver::removeDir handle=" << std::hex << handle;
193 
194         CloseHandle( handle );
195     }
196     else {
197         /* Happens when an error occured when creating the dir_record_ */
198     }
199 }
200 
201 //
202 // Private functions
203 //
204 
205 // Add a file (run in the context of the WatchTower thread)
206 void WinWatchTowerDriver::serialisedAddDir(
207         const std::string& dir_name,
208         DirId& dir_id )
209 {
210     bool inserted = false;
211     auto dir_record = std::make_shared<WinWatchedDirRecord>( dir_name );
212     // The index we will be inserting this record (if success), plus 1 (to avoid
213     // 0 which is used as a magic value)
214     unsigned int index_record = dir_records_.size() + 1;
215 
216     LOG(logDEBUG) << "Adding dir for: " << dir_name;
217 
218     // Open the directory
219     HANDLE hDir = CreateFile(
220 #ifdef UNICODE
221             longstringize( dir_name ).c_str(),
222 #else
223             ( dir_name ).c_str(),
224 #endif
225             FILE_LIST_DIRECTORY,
226             FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
227             NULL,
228             OPEN_EXISTING,
229             FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
230             NULL );
231 
232     if ( hDir == INVALID_HANDLE_VALUE ) {
233         LOG(logERROR) << "CreateFile failed for dir " << dir_name;
234     }
235     else {
236         dir_record->handle_ = hDir;
237 
238         //create a IO completion port/or associate this key with
239         //the existing IO completion port
240         hCompPort_ = CreateIoCompletionPort( hDir,
241                 hCompPort_, //if m_hCompPort is NULL, hDir is associated with a NEW completion port,
242                 //if m_hCompPort is NON-NULL, hDir is associated with the existing completion port that the handle m_hCompPort references
243                 // We use the index (plus 1) of the weak_ptr as a key
244                 index_record,
245                 0 );
246 
247         LOG(logDEBUG) << "Weak ptr address stored: " << index_record;
248 
249         memset( &overlapped_, 0, sizeof overlapped_ );
250 
251         inserted = ReadDirectoryChangesW( hDir,
252                 dir_record->buffer_,
253                 dir_record->buffer_length_,
254                 false,
255                 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
256                 &buffer_length_, // not set when using asynchronous mechanisms...
257                 &overlapped_,
258                 NULL );          // no completion routine
259 
260         if ( ! inserted ) {
261             LOG(logERROR) << "ReadDirectoryChangesW failed (" << GetLastError() << ")";
262             CloseHandle( hDir );
263         }
264         else {
265             dir_id.dir_record_ = dir_record;
266         }
267     }
268 
269     if ( inserted ) {
270         dir_records_.push_back( std::weak_ptr<WinWatchedDirRecord>( dir_record ) );
271     }
272 }
273 
274 std::vector<ObservedFile<WinWatchTowerDriver>*> WinWatchTowerDriver::waitAndProcessEvents(
275         ObservedFileList<WinWatchTowerDriver>* list,
276         std::unique_lock<std::mutex>* lock,
277         std::vector<ObservedFile<WinWatchTowerDriver>*>* /* not needed in WinWatchTowerDriver */,
278         int timeout_ms )
279 {
280     std::vector<ObservedFile<WinWatchTowerDriver>*> files_to_notify { };
281 
282     ULONG_PTR key = 0;
283     DWORD num_bytes = 0;
284     LPOVERLAPPED lpOverlapped = 0;
285 
286     if ( timeout_ms == 0 )
287         timeout_ms = INFINITE;
288 
289     lock->unlock();
290     LOG(logDEBUG) << "waitAndProcessEvents now blocking...";
291     BOOL status = GetQueuedCompletionStatus( hCompPort_,
292             &num_bytes,
293             &key,
294             &lpOverlapped,
295             timeout_ms );
296     lock->lock();
297 
298     LOG(logDEBUG) << "Event (" << status << ") key: " << std::hex << key;
299 
300     if ( key ) {
301         // Extract the dir from the completion key
302         auto dir_record_ptr = dir_records_[key - 1];
303         LOG(logDEBUG) << "use_count = " << dir_record_ptr.use_count();
304 
305         if ( std::shared_ptr<WinWatchedDirRecord> dir_record = dir_record_ptr.lock() )
306         {
307             LOG(logDEBUG) << "Got event for dir " << dir_record.get();
308 
309             WinNotificationInfoList notification_info(
310                     dir_record->buffer_,
311                     dir_record->buffer_length_ );
312 
313             for ( auto notification : notification_info ) {
314                 std::string file_path = dir_record->path_ + shortstringize( notification.fileName() );
315                 LOG(logDEBUG) << "File is " << file_path;
316                 auto file = list->searchByName( file_path );
317 
318                 if ( file )
319                 {
320                     files_to_notify.push_back( file );
321                 }
322             }
323 
324             // Re-listen for changes
325             status = ReadDirectoryChangesW(
326                     dir_record->handle_,
327                     dir_record->buffer_,
328                     dir_record->buffer_length_,
329                     false,
330                     FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
331                     &buffer_length_,// not set when using asynchronous mechanisms...
332                     &overlapped_,
333                     NULL );          // no completion routine
334         }
335         else {
336             LOG(logWARNING) << "Looks like our dir_record disappeared!";
337         }
338     }
339     else {
340         LOG(logDEBUG) << "Signaled";
341     }
342 
343     {
344         std::lock_guard<std::mutex> lk( action_mutex_ );
345         if ( scheduled_action_ ) {
346             (*scheduled_action_)();
347             scheduled_action_ = nullptr;
348             action_done_cv_.notify_all();
349         }
350     }
351 
352     /*
353     // Just in case someone is waiting for an action to complete
354     std::lock_guard<std::mutex> lk( action_mutex_ );
355     scheduled_action_ = nullptr;
356     action_done_cv_.notify_all();
357     */
358     return files_to_notify;
359 }
360 
361 void WinWatchTowerDriver::interruptWait()
362 {
363     LOG(logDEBUG) << "Driver::interruptWait()";
364     PostQueuedCompletionStatus( hCompPort_, 0, 0, NULL );
365 }
366 
367 namespace {
368     std::string shortstringize( const std::wstring& long_string )
369     {
370         std::string short_result {};
371 
372         for ( wchar_t c : long_string ) {
373             // FIXME: that does not work for non ASCII char!!
374             char short_c = static_cast<char>( c & 0x00FF );
375             short_result += short_c;
376         }
377 
378         return short_result;
379     }
380 
381     std::wstring longstringize( const std::string& short_string )
382     {
383         std::wstring long_result {};
384 
385         for ( char c : short_string ) {
386             wchar_t long_c = static_cast<wchar_t>( c );
387             long_result += long_c;
388         }
389 
390         return long_result;
391     }
392 };
393