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