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