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