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