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