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