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 // 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 147 WinWatchTowerDriver::WinWatchTowerDriver() 148 { 149 hCompPort_ = CreateIoCompletionPort( INVALID_HANDLE_VALUE, 150 NULL, 151 0x0, 152 0); 153 } 154 155 WinWatchTowerDriver::~WinWatchTowerDriver() 156 { 157 } 158 159 WinWatchTowerDriver::FileId WinWatchTowerDriver::addFile( 160 const std::string& file_name ) 161 { 162 // Nothing for Windows 163 return { }; 164 } 165 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. 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 209 void WinWatchTowerDriver::removeFile( 210 const WinWatchTowerDriver::FileId& ) 211 { 212 } 213 214 void WinWatchTowerDriver::removeSymlink( const SymlinkId& ) 215 { 216 } 217 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) 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 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 393 void WinWatchTowerDriver::interruptWait() 394 { 395 LOG(logDEBUG) << "Driver::interruptWait()"; 396 PostQueuedCompletionStatus( hCompPort_, 0, 0, NULL ); 397 } 398 399 namespace { 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 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