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