xref: /glogg/src/winwatchtowerdriver.cpp (revision 6e2e573c451ec24d720c967fab68538eaa3f3ab5)
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) << "addDir returned " << dir_id.dir_record_;
122 
123     return dir_id;
124 }
125 
126 
127 void WinWatchTowerDriver::removeFile(
128         const WinWatchTowerDriver::FileId& )
129 {
130 }
131 
132 void WinWatchTowerDriver::removeSymlink( const SymlinkId& )
133 {
134 }
135 
136 void WinWatchTowerDriver::removeDir( const DirId& dir_id )
137 {
138     LOG(logDEBUG) << "Entering driver::removeDir";
139     if ( dir_id.dir_record_ ) {
140         void* handle = dir_id.dir_record_->handle_;
141 
142         LOG(logDEBUG) << "WinWatchTowerDriver::removeDir handle=" << std::hex << handle;
143 
144         CloseHandle( handle );
145     }
146     else {
147         /* Happens when an error occured when creating the dir_record_ */
148     }
149 }
150 
151 //
152 // Private functions
153 //
154 
155 // Add a file (run in the context of the WatchTower thread)
156 void WinWatchTowerDriver::serialisedAddDir(
157         const std::string& dir_name,
158         DirId& dir_id )
159 {
160     bool inserted = false;
161     auto dir_record = std::make_shared<WinWatchedDirRecord>( dir_name );
162     // The index we will be inserting this record (if success), plus 1 (to avoid
163     // 0 which is used as a magic value)
164     unsigned int index_record = dir_records_.size() + 1;
165 
166     LOG(logDEBUG) << "Adding dir for: " << dir_name;
167 
168     // Open the directory
169     HANDLE hDir = CreateFile(
170 #ifdef UNICODE
171             longstringize( dir_name ).c_str(),
172 #else
173             ( dir_name ).c_str(),
174 #endif
175             FILE_LIST_DIRECTORY,
176             FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
177             NULL,
178             OPEN_EXISTING,
179             FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
180             NULL );
181 
182     if ( hDir == INVALID_HANDLE_VALUE ) {
183         LOG(logERROR) << "CreateFile failed for dir " << dir_name;
184     }
185     else {
186         dir_record->handle_ = hDir;
187 
188         //create a IO completion port/or associate this key with
189         //the existing IO completion port
190         hCompPort_ = CreateIoCompletionPort( hDir,
191                 hCompPort_, //if m_hCompPort is NULL, hDir is associated with a NEW completion port,
192                 //if m_hCompPort is NON-NULL, hDir is associated with the existing completion port that the handle m_hCompPort references
193                 // We use the index (plus 1) of the weak_ptr as a key
194                 index_record,
195                 0 );
196 
197         LOG(logDEBUG) << "Weak ptr address stored: " << index_record;
198 
199         memset( &overlapped_, 0, sizeof overlapped_ );
200 
201         inserted = ReadDirectoryChangesW( hDir,
202                 dir_record->buffer_,
203                 dir_record->buffer_length_,
204                 false,
205                 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
206                 &buffer_length_, // not set when using asynchronous mechanisms...
207                 &overlapped_,
208                 NULL );          // no completion routine
209 
210         if ( ! inserted ) {
211             LOG(logERROR) << "ReadDirectoryChangesW failed (" << GetLastError() << ")";
212             CloseHandle( hDir );
213         }
214         else {
215             dir_id.dir_record_ = dir_record;
216         }
217     }
218 
219     if ( inserted ) {
220         dir_records_.push_back( std::weak_ptr<WinWatchedDirRecord>( dir_record ) );
221     }
222 }
223 
224 std::vector<ObservedFile<WinWatchTowerDriver>*> WinWatchTowerDriver::waitAndProcessEvents(
225         ObservedFileList<WinWatchTowerDriver>* list,
226         std::unique_lock<std::mutex>* lock,
227         std::vector<ObservedFile<WinWatchTowerDriver>*>* /* not needed in WinWatchTowerDriver */ )
228 {
229     std::vector<ObservedFile<WinWatchTowerDriver>*> files_to_notify { };
230 
231     ULONG_PTR key = 0;
232     DWORD num_bytes = 0;
233     LPOVERLAPPED lpOverlapped = 0;
234 
235     lock->unlock();
236     LOG(logDEBUG) << "waitAndProcessEvents now blocking...";
237     BOOL status = GetQueuedCompletionStatus( hCompPort_,
238             &num_bytes,
239             &key,
240             &lpOverlapped,
241             INFINITE );
242     lock->lock();
243 
244     LOG(logDEBUG) << "Event (" << status << ") key: " << std::hex << key;
245 
246     if ( key ) {
247         // Extract the dir from the completion key
248         auto dir_record_ptr = dir_records_[key - 1];
249         LOG(logDEBUG) << "use_count = " << dir_record_ptr.use_count();
250 
251         if ( std::shared_ptr<WinWatchedDirRecord> dir_record = dir_record_ptr.lock() )
252         {
253             LOG(logDEBUG) << "Got event for dir " << dir_record.get();
254 
255             WinNotificationInfoList notification_info(
256                     dir_record->buffer_,
257                     dir_record->buffer_length_ );
258 
259             for ( auto notification : notification_info ) {
260                 std::string file_path = dir_record->path_ + shortstringize( notification.fileName() );
261                 LOG(logDEBUG) << "File is " << file_path;
262                 auto file = list->searchByName( file_path );
263 
264                 if ( file )
265                 {
266                     files_to_notify.push_back( file );
267                 }
268             }
269 
270             // Re-listen for changes
271             status = ReadDirectoryChangesW(
272                     dir_record->handle_,
273                     dir_record->buffer_,
274                     dir_record->buffer_length_,
275                     false,
276                     FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
277                     &buffer_length_,// not set when using asynchronous mechanisms...
278                     &overlapped_,
279                     NULL );          // no completion routine
280         }
281         else {
282             LOG(logWARNING) << "Looks like our dir_record disappeared!";
283         }
284     }
285     else {
286         LOG(logDEBUG) << "Signaled";
287     }
288 
289     {
290         std::lock_guard<std::mutex> lk( action_mutex_ );
291         if ( scheduled_action_ ) {
292             (*scheduled_action_)();
293             scheduled_action_ = nullptr;
294             action_done_cv_.notify_all();
295         }
296     }
297 
298     /*
299     // Just in case someone is waiting for an action to complete
300     std::lock_guard<std::mutex> lk( action_mutex_ );
301     scheduled_action_ = nullptr;
302     action_done_cv_.notify_all();
303     */
304     return files_to_notify;
305 }
306 
307 void WinWatchTowerDriver::interruptWait()
308 {
309     LOG(logDEBUG) << "Driver::interruptWait()";
310     PostQueuedCompletionStatus( hCompPort_, 0, 0, NULL );
311 }
312 
313 namespace {
314     std::string shortstringize( const std::wstring& long_string )
315     {
316         std::string short_result {};
317 
318         for ( wchar_t c : long_string ) {
319             // FIXME: that does not work for non ASCII char!!
320             char short_c = static_cast<char>( c & 0x00FF );
321             short_result += short_c;
322         }
323 
324         return short_result;
325     }
326 
327     std::wstring longstringize( const std::string& short_string )
328     {
329         std::wstring long_result {};
330 
331         for ( char c : short_string ) {
332             wchar_t long_c = static_cast<wchar_t>( c );
333             long_result += long_c;
334         }
335 
336         return long_result;
337     }
338 };
339