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