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