xref: /glogg/src/winwatchtowerdriver.cpp (revision 3104b26858f76d3848af58b8865d4e7d5735d2f8)
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
64 
65 WinWatchTowerDriver::WinWatchTowerDriver()
66 {
67     hCompPort_ = CreateIoCompletionPort( INVALID_HANDLE_VALUE,
68             NULL,
69             0x0,
70             0);
71 }
72 
73 WinWatchTowerDriver::~WinWatchTowerDriver()
74 {
75 }
76 
77 WinWatchTowerDriver::FileId WinWatchTowerDriver::addFile(
78         const std::string& file_name )
79 {
80     // Nothing for Windows
81     return { };
82 }
83 
84 WinWatchTowerDriver::SymlinkId WinWatchTowerDriver::addSymlink(
85         const std::string& file_name )
86 {
87     // Nothing for Windows
88     return { };
89 }
90 
91 // This implementation is blocking, i.e. it will wait until the file
92 // is effectively loaded in the watchtower thread.
93 WinWatchTowerDriver::DirId WinWatchTowerDriver::addDir(
94         const std::string& file_name )
95 {
96     DirId dir_id { };
97 
98     // Add will be done in the watchtower thread
99     {
100         /*
101         std::lock_guard<std::mutex> lk( action_mutex_ );
102         scheduled_action_ = std::make_unique<Action>( [this, file_name, &dir_id] {
103             serialisedAddDir( file_name, dir_id );
104         } );
105         */
106         serialisedAddDir( file_name, dir_id );
107     }
108 
109     // Poke the thread
110     PostQueuedCompletionStatus( hCompPort_, 0, 0, NULL );
111 
112     // Wait for the add task to be completed
113     {
114         /*
115         std::unique_lock<std::mutex> lk( action_mutex_ );
116         action_done_cv_.wait( lk,
117                 [this]{ return ( scheduled_action_ == nullptr ); } );
118                 */
119     }
120 
121     LOG(logDEBUG) << "Returned " << dir_id.dir_record_;
122 
123     return dir_id;
124 }
125 
126 
127 void WinWatchTowerDriver::removeFile(
128         const WinWatchTowerDriver::FileId& file_id )
129 {
130 }
131 
132 void WinWatchTowerDriver::removeSymlink( const SymlinkId& symlink_id )
133 {
134 }
135 
136 void WinWatchTowerDriver::removeDir( const DirId& dir_id )
137 {
138     void* handle = dir_id.dir_record_->handle_;
139 
140     LOG(logDEBUG) << "WinWatchTowerDriver::removeDir handle=" << std::hex << handle;
141 
142     CloseHandle( handle );
143 }
144 
145 //
146 // Private functions
147 //
148 
149 // Add a file (run in the context of the WatchTower thread)
150 void WinWatchTowerDriver::serialisedAddDir(
151         const std::string& dir_name,
152         DirId& dir_id )
153 {
154     auto dir_record = std::make_shared<WinWatchedDirRecord>( dir_name );
155     dir_records_.push_back( std::weak_ptr<WinWatchedDirRecord>( dir_record ) );
156     unsigned int index_record = dir_records_.size();
157 
158     LOG(logDEBUG) << "Adding dir for: " << dir_name;
159 
160     // Open the directory
161     HANDLE hDir = CreateFile(
162 #ifdef UNICODE
163             longstringize( dir_name ).c_str(),
164 #else
165             ( dir_name ).c_str(),
166 #endif
167             FILE_LIST_DIRECTORY,
168             FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
169             NULL,
170             OPEN_EXISTING,
171             FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
172             NULL );
173 
174     if ( hDir == INVALID_HANDLE_VALUE ) {
175         LOG(logERROR) << "CreateFile failed for dir " << dir_name;
176     }
177 
178     dir_record->handle_ = hDir;
179 
180     //create a IO completion port/or associate this key with
181     //the existing IO completion port
182     hCompPort_ = CreateIoCompletionPort( hDir,
183             hCompPort_, //if m_hCompPort is NULL, hDir is associated with a NEW completion port,
184             //if m_hCompPort is NON-NULL, hDir is associated with the existing completion port that the handle m_hCompPort references
185             // We use the index (plus 1) of the weak_ptr as a key
186             index_record,
187             0 );
188 
189     LOG(logDEBUG) << "Weak ptr address stored: " << index_record;
190 
191     memset( &overlapped_, 0, sizeof overlapped_ );
192 
193     bool status = ReadDirectoryChangesW( hDir,
194             dir_record->buffer_,
195             dir_record->buffer_length_,
196             false,
197             FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
198             &buffer_length_, // not set when using asynchronous mechanisms...
199             &overlapped_,
200             NULL );          // no completion routine
201 
202     if ( !status ) {
203         LOG(logERROR) << "ReadDirectoryChangesW failed (" << GetLastError() << ")";
204     }
205 
206     dir_id.dir_record_ = dir_record;
207 }
208 
209 std::vector<ObservedFile<WinWatchTowerDriver>*> WinWatchTowerDriver::waitAndProcessEvents(
210         ObservedFileList<WinWatchTowerDriver>* list,
211         std::unique_lock<std::mutex>* lock )
212 {
213     std::vector<ObservedFile<WinWatchTowerDriver>*> files_to_notify { };
214 
215     unsigned long long key = 0;
216     DWORD num_bytes = 0;
217     LPOVERLAPPED lpOverlapped = 0;
218 
219     lock->unlock();
220     BOOL status = GetQueuedCompletionStatus( hCompPort_,
221             &num_bytes,
222             &key,
223             &lpOverlapped,
224             INFINITE );
225     lock->lock();
226 
227     LOG(logDEBUG) << "Event (" << status << ") key: " << std::hex << key;
228 
229     if ( key ) {
230         // Extract the dir from the completion key
231         auto dir_record_ptr = dir_records_[key - 1];
232         LOG(logDEBUG) << "use_count = " << dir_record_ptr.use_count();
233 
234         if ( std::shared_ptr<WinWatchedDirRecord> dir_record = dir_record_ptr.lock() )
235         {
236             LOG(logDEBUG) << "Got event for dir " << dir_record.get();
237 
238             WinNotificationInfoList notification_info(
239                     dir_record->buffer_,
240                     dir_record->buffer_length_ );
241 
242             for ( auto notification : notification_info ) {
243                 std::string file_path = dir_record->path_ + shortstringize( notification.fileName() );
244                 LOG(logDEBUG) << "File is " << file_path;
245                 auto file = list->searchByName( file_path );
246 
247                 if ( file )
248                 {
249                     files_to_notify.push_back( file );
250                 }
251             }
252 
253             // Re-listen for changes
254             status = ReadDirectoryChangesW(
255                     dir_record->handle_,
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         else {
265             LOG(logWARNING) << "Looks like our dir_record disappeared!";
266         }
267     }
268     else {
269         LOG(logDEBUG) << "Signaled";
270     }
271 
272     {
273         std::lock_guard<std::mutex> lk( action_mutex_ );
274         if ( scheduled_action_ ) {
275             (*scheduled_action_)();
276             scheduled_action_ = nullptr;
277             action_done_cv_.notify_all();
278         }
279     }
280 
281     /*
282     // Just in case someone is waiting for an action to complete
283     std::lock_guard<std::mutex> lk( action_mutex_ );
284     scheduled_action_ = nullptr;
285     action_done_cv_.notify_all();
286     */
287     return files_to_notify;
288 }
289 
290 void WinWatchTowerDriver::interruptWait()
291 {
292     PostQueuedCompletionStatus( hCompPort_, 0, 0, NULL );
293 }
294 
295 namespace {
296     std::string shortstringize( const std::wstring& long_string )
297     {
298         std::string short_result {};
299 
300         for ( wchar_t c : long_string ) {
301             // FIXME: that does not work for non ASCII char!!
302             char short_c = static_cast<char>( c & 0x00FF );
303             short_result += short_c;
304         }
305 
306         return short_result;
307     }
308 
309     std::wstring longstringize( const std::string& short_string )
310     {
311         std::wstring long_result {};
312 
313         for ( char c : short_string ) {
314             wchar_t long_c = static_cast<wchar_t>( c );
315             long_result += long_c;
316         }
317 
318         return long_result;
319     }
320 };
321