/* * Copyright (C) 2015 Nicolas Bonnefon and other contributors * * This file is part of glogg. * * glogg is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * glogg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with glogg. If not, see . */ #include "winwatchtowerdriver.h" #define WIN32_LEAN_AND_MEAN #include #include #include #include "watchtowerlist.h" #include "utils.h" #include "log.h" namespace { std::string shortstringize( const std::wstring& long_string ); std::wstring longstringize( const std::string& short_string ); }; // Utility classes WinNotificationInfoList::WinNotificationInfoList( const char* buffer, size_t buffer_size ) { pointer_ = buffer; next_ = updateCurrentNotification( pointer_ ); } const char* WinNotificationInfoList::updateCurrentNotification( const char* new_position ) { using Action = WinNotificationInfo::Action; static const std::map int_to_action = { { FILE_ACTION_ADDED, Action::ADDED }, { FILE_ACTION_REMOVED, Action::REMOVED }, { FILE_ACTION_MODIFIED, Action::MODIFIED }, { FILE_ACTION_RENAMED_OLD_NAME, Action::RENAMED_OLD_NAME }, { FILE_ACTION_RENAMED_NEW_NAME, Action::RENAMED_NEW_NAME }, }; uint32_t next_offset = *( reinterpret_cast( new_position ) ); uint32_t action = *( reinterpret_cast( new_position ) + 1 ); uint32_t length = *( reinterpret_cast( new_position ) + 2 ); const std::wstring file_name = { reinterpret_cast( new_position + 12 ), length / 2 }; LOG(logDEBUG) << "Next: " << next_offset; LOG(logDEBUG) << "Action: " << action; LOG(logDEBUG) << "Length: " << length; current_notification_ = WinNotificationInfo( int_to_action.at( action ), file_name ); return ( next_offset == 0 ) ? nullptr : new_position + next_offset; } const char* WinNotificationInfoList::advanceToNext() { pointer_ = next_; if ( pointer_ ) next_ = updateCurrentNotification( pointer_ ); return pointer_; } // WinWatchTowerDriver::FileChangeToken void WinWatchTowerDriver::FileChangeToken::readFromFile( const std::string& file_name ) { // On Windows, we open the file and get its last written date/time // That seems to work alright in my tests, but who knows for sure? // On further investigation, in some situations (e.g. putty logging that // has been stop/re-started), the size of the file is being updated but not // the date (confirmed by "Properties" in the file explorer), so we add the // file size to the token. Ha the joy of Windows programming... HANDLE hFile = CreateFile( #ifdef UNICODE longstringize( file_name ).c_str(), #else ( file_name ).c_str(), #endif 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if ( hFile == (HANDLE)-1 ) { DWORD err = GetLastError(); LOG(logERROR) << "FileChangeToken::readFromFile: failed with " << err; low_date_time_ = 0; high_date_time_ = 0; low_file_size_ = 0; high_file_size_ = 0; return; } else { BY_HANDLE_FILE_INFORMATION file_info; if ( GetFileInformationByHandle( hFile, &file_info ) ) { low_date_time_ = file_info.ftLastWriteTime.dwLowDateTime; high_date_time_ = file_info.ftLastWriteTime.dwHighDateTime; low_file_size_ = file_info.nFileSizeLow; high_file_size_ = file_info.nFileSizeHigh; LOG(logDEBUG) << "FileChangeToken::readFromFile: low_date_time_ " << low_date_time_; LOG(logDEBUG) << "FileChangeToken::readFromFile: high_date_time_ " << high_date_time_; } else { DWORD err = GetLastError(); LOG(logERROR) << "FileChangeToken::readFromFile: failed with " << err; low_date_time_ = 0; high_date_time_ = 0; } } CloseHandle(hFile); } // WinWatchTowerDriver WinWatchTowerDriver::WinWatchTowerDriver() { hCompPort_ = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0x0, 0); } WinWatchTowerDriver::~WinWatchTowerDriver() { } WinWatchTowerDriver::FileId WinWatchTowerDriver::addFile( const std::string& file_name ) { // Nothing for Windows return { }; } WinWatchTowerDriver::SymlinkId WinWatchTowerDriver::addSymlink( const std::string& file_name ) { // Nothing for Windows return { }; } // This implementation is blocking, i.e. it will wait until the file // is effectively loaded in the watchtower thread. WinWatchTowerDriver::DirId WinWatchTowerDriver::addDir( const std::string& file_name ) { DirId dir_id { }; // Add will be done in the watchtower thread { /* std::lock_guard lk( action_mutex_ ); scheduled_action_ = std::make_unique( [this, file_name, &dir_id] { serialisedAddDir( file_name, dir_id ); } ); */ serialisedAddDir( file_name, dir_id ); } // Poke the thread interruptWait(); // Wait for the add task to be completed { /* std::unique_lock lk( action_mutex_ ); action_done_cv_.wait( lk, [this]{ return ( scheduled_action_ == nullptr ); } ); */ } LOG(logDEBUG) << "addDir returned " << dir_id.dir_record_; return dir_id; } void WinWatchTowerDriver::removeFile( const WinWatchTowerDriver::FileId& ) { } void WinWatchTowerDriver::removeSymlink( const SymlinkId& ) { } void WinWatchTowerDriver::removeDir( const DirId& dir_id ) { LOG(logDEBUG) << "Entering driver::removeDir"; if ( dir_id.dir_record_ ) { void* handle = dir_id.dir_record_->handle_; LOG(logDEBUG) << "WinWatchTowerDriver::removeDir handle=" << std::hex << handle; CloseHandle( handle ); } else { /* Happens when an error occured when creating the dir_record_ */ } } // // Private functions // // Add a file (run in the context of the WatchTower thread) void WinWatchTowerDriver::serialisedAddDir( const std::string& dir_name, DirId& dir_id ) { bool inserted = false; auto dir_record = std::make_shared( dir_name ); // The index we will be inserting this record (if success), plus 1 (to avoid // 0 which is used as a magic value) unsigned int index_record = dir_records_.size() + 1; LOG(logDEBUG) << "Adding dir for: " << dir_name; // Open the directory HANDLE hDir = CreateFile( #ifdef UNICODE longstringize( dir_name ).c_str(), #else ( dir_name ).c_str(), #endif FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL ); if ( hDir == INVALID_HANDLE_VALUE ) { LOG(logERROR) << "CreateFile failed for dir " << dir_name; } else { dir_record->handle_ = hDir; //create a IO completion port/or associate this key with //the existing IO completion port hCompPort_ = CreateIoCompletionPort( hDir, hCompPort_, //if m_hCompPort is NULL, hDir is associated with a NEW completion port, //if m_hCompPort is NON-NULL, hDir is associated with the existing completion port that the handle m_hCompPort references // We use the index (plus 1) of the weak_ptr as a key index_record, 0 ); LOG(logDEBUG) << "Weak ptr address stored: " << index_record; memset( &overlapped_, 0, sizeof overlapped_ ); inserted = ReadDirectoryChangesW( hDir, dir_record->buffer_, dir_record->buffer_length_, false, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, &buffer_length_, // not set when using asynchronous mechanisms... &overlapped_, NULL ); // no completion routine if ( ! inserted ) { LOG(logERROR) << "ReadDirectoryChangesW failed (" << GetLastError() << ")"; CloseHandle( hDir ); } else { dir_id.dir_record_ = dir_record; } } if ( inserted ) { dir_records_.push_back( std::weak_ptr( dir_record ) ); } } std::vector*> WinWatchTowerDriver::waitAndProcessEvents( ObservedFileList* list, std::unique_lock* lock, std::vector*>* /* not needed in WinWatchTowerDriver */, int timeout_ms ) { std::vector*> files_to_notify { }; ULONG_PTR key = 0; DWORD num_bytes = 0; LPOVERLAPPED lpOverlapped = 0; if ( timeout_ms == 0 ) timeout_ms = INFINITE; lock->unlock(); LOG(logDEBUG) << "waitAndProcessEvents now blocking..."; BOOL status = GetQueuedCompletionStatus( hCompPort_, &num_bytes, &key, &lpOverlapped, timeout_ms ); lock->lock(); LOG(logDEBUG) << "Event (" << status << ") key: " << std::hex << key; if ( key ) { // Extract the dir from the completion key auto dir_record_ptr = dir_records_[key - 1]; LOG(logDEBUG) << "use_count = " << dir_record_ptr.use_count(); if ( std::shared_ptr dir_record = dir_record_ptr.lock() ) { LOG(logDEBUG) << "Got event for dir " << dir_record.get(); WinNotificationInfoList notification_info( dir_record->buffer_, dir_record->buffer_length_ ); for ( auto notification : notification_info ) { std::string file_path = dir_record->path_ + shortstringize( notification.fileName() ); LOG(logDEBUG) << "File is " << file_path; auto file = list->searchByName( file_path ); if ( file ) { files_to_notify.push_back( file ); } } // Re-listen for changes status = ReadDirectoryChangesW( dir_record->handle_, dir_record->buffer_, dir_record->buffer_length_, false, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, &buffer_length_,// not set when using asynchronous mechanisms... &overlapped_, NULL ); // no completion routine } else { LOG(logWARNING) << "Looks like our dir_record disappeared!"; } } else { LOG(logDEBUG) << "Signaled"; } { std::lock_guard lk( action_mutex_ ); if ( scheduled_action_ ) { (*scheduled_action_)(); scheduled_action_ = nullptr; action_done_cv_.notify_all(); } } /* // Just in case someone is waiting for an action to complete std::lock_guard lk( action_mutex_ ); scheduled_action_ = nullptr; action_done_cv_.notify_all(); */ return files_to_notify; } void WinWatchTowerDriver::interruptWait() { LOG(logDEBUG) << "Driver::interruptWait()"; PostQueuedCompletionStatus( hCompPort_, 0, 0, NULL ); } namespace { std::string shortstringize( const std::wstring& long_string ) { std::string short_result {}; for ( wchar_t c : long_string ) { // FIXME: that does not work for non ASCII char!! char short_c = static_cast( c & 0x00FF ); short_result += short_c; } return short_result; } std::wstring longstringize( const std::string& short_string ) { std::wstring long_result {}; for ( char c : short_string ) { wchar_t long_c = static_cast( c ); long_result += long_c; } return long_result; } };