Added
Link Here
|
1 |
///////////////////////////////////////////////////////////////////////////// |
2 |
// Name: src/unix/fswatcher_kqueue.cpp |
3 |
// Purpose: kqueue-based wxFileSystemWatcher implementation |
4 |
// Author: Bartosz Bekier |
5 |
// Created: 2009-05-26 |
6 |
// Copyright: (c) 2009 Bartosz Bekier <bartosz.bekier@gmail.com> |
7 |
// Licence: wxWindows licence |
8 |
///////////////////////////////////////////////////////////////////////////// |
9 |
|
10 |
// For compilers that support precompilation, includes "wx.h". |
11 |
#include "wx/wxprec.h" |
12 |
|
13 |
#ifdef __BORLANDC__ |
14 |
#pragma hdrstop |
15 |
#endif |
16 |
|
17 |
#if wxUSE_FSWATCHER |
18 |
|
19 |
#include "wx/fswatcher.h" |
20 |
|
21 |
#ifdef wxHAS_KQUEUE |
22 |
|
23 |
#include <inttypes.h> |
24 |
#include <sys/types.h> |
25 |
#include <sys/event.h> |
26 |
#include <sys/param.h> |
27 |
|
28 |
#include "wx/dynarray.h" |
29 |
#include "wx/evtloop.h" |
30 |
#include "wx/evtloopsrc.h" |
31 |
|
32 |
#include "wx/private/fswatcher.h" |
33 |
|
34 |
namespace |
35 |
{ |
36 |
|
37 |
// NetBSD is different as it uses intptr_t as type of kevent struct udata field |
38 |
// for some reason, instead of "void*" as all the other platforms using kqueue. |
39 |
#ifdef __NetBSD__ |
40 |
inline intptr_t ToUdata(void* d) { return reinterpret_cast<intptr_t>(d); } |
41 |
inline void* FromUdata(intptr_t d) { return reinterpret_cast<void*>(d); } |
42 |
#else |
43 |
inline void* ToUdata(void* d) { return d; } |
44 |
inline void* FromUdata(void* d) { return d; } |
45 |
#endif |
46 |
|
47 |
} // anonymous namespace |
48 |
|
49 |
// ============================================================================ |
50 |
// wxFSWSourceHandler helper class |
51 |
// ============================================================================ |
52 |
|
53 |
class wxFSWatcherImplKqueue; |
54 |
|
55 |
/** |
56 |
* Handler for handling i/o from inotify descriptor |
57 |
*/ |
58 |
class wxFSWSourceHandler : public wxEventLoopSourceHandler |
59 |
{ |
60 |
public: |
61 |
wxFSWSourceHandler(wxFSWatcherImplKqueue* service) : |
62 |
m_service(service) |
63 |
{ } |
64 |
|
65 |
virtual void OnReadWaiting(); |
66 |
virtual void OnWriteWaiting(); |
67 |
virtual void OnExceptionWaiting(); |
68 |
|
69 |
protected: |
70 |
wxFSWatcherImplKqueue* m_service; |
71 |
}; |
72 |
|
73 |
// ============================================================================ |
74 |
// wxFSWatcherImpl implementation & helper wxFSWSourceHandler implementation |
75 |
// ============================================================================ |
76 |
|
77 |
/** |
78 |
* Helper class encapsulating inotify mechanism |
79 |
*/ |
80 |
class wxFSWatcherImplKqueue : public wxFSWatcherImpl |
81 |
{ |
82 |
public: |
83 |
wxFSWatcherImplKqueue(wxFileSystemWatcherBase* watcher) : |
84 |
wxFSWatcherImpl(watcher), |
85 |
m_source(NULL), |
86 |
m_kfd(-1) |
87 |
{ |
88 |
m_handler = new wxFSWSourceHandler(this); |
89 |
} |
90 |
|
91 |
virtual ~wxFSWatcherImplKqueue() |
92 |
{ |
93 |
// we close kqueue only if initialized before |
94 |
if (IsOk()) |
95 |
{ |
96 |
Close(); |
97 |
} |
98 |
|
99 |
delete m_handler; |
100 |
} |
101 |
|
102 |
bool Init() |
103 |
{ |
104 |
wxCHECK_MSG( !IsOk(), false, |
105 |
"Kqueue appears to be already initialized" ); |
106 |
|
107 |
wxEventLoopBase *loop = wxEventLoopBase::GetActive(); |
108 |
wxCHECK_MSG( loop, false, "File system watcher needs an active loop" ); |
109 |
|
110 |
// create kqueue |
111 |
m_kfd = kqueue(); |
112 |
if (m_kfd == -1) |
113 |
{ |
114 |
wxLogSysError(_("Unable to create kqueue instance")); |
115 |
return false; |
116 |
} |
117 |
|
118 |
// create source |
119 |
m_source = loop->AddSourceForFD(m_kfd, m_handler, wxEVENT_SOURCE_INPUT); |
120 |
|
121 |
return m_source != NULL; |
122 |
} |
123 |
|
124 |
void Close() |
125 |
{ |
126 |
wxCHECK_RET( IsOk(), |
127 |
"Kqueue not initialized or invalid kqueue descriptor" ); |
128 |
|
129 |
if ( close(m_kfd) != 0 ) |
130 |
{ |
131 |
wxLogSysError(_("Error closing kqueue instance")); |
132 |
} |
133 |
|
134 |
wxDELETE(m_source); |
135 |
} |
136 |
|
137 |
virtual bool DoAdd(wxSharedPtr<wxFSWatchEntryKq> watch) |
138 |
{ |
139 |
wxCHECK_MSG( IsOk(), false, |
140 |
"Kqueue not initialized or invalid kqueue descriptor" ); |
141 |
|
142 |
struct kevent event; |
143 |
int action = EV_ADD | EV_ENABLE | EV_CLEAR | EV_ERROR; |
144 |
int flags = Watcher2NativeFlags(watch->GetFlags()); |
145 |
EV_SET( &event, watch->GetFileDescriptor(), EVFILT_VNODE, action, |
146 |
flags, 0, ToUdata(watch.get()) ); |
147 |
|
148 |
// TODO more error conditions according to man |
149 |
// TODO best deal with the error here |
150 |
int ret = kevent(m_kfd, &event, 1, NULL, 0, NULL); |
151 |
if (ret == -1) |
152 |
{ |
153 |
wxLogSysError(_("Unable to add kqueue watch")); |
154 |
return false; |
155 |
} |
156 |
|
157 |
return true; |
158 |
} |
159 |
|
160 |
virtual bool DoRemove(wxSharedPtr<wxFSWatchEntryKq> watch) |
161 |
{ |
162 |
wxCHECK_MSG( IsOk(), false, |
163 |
"Kqueue not initialized or invalid kqueue descriptor" ); |
164 |
|
165 |
// TODO more error conditions according to man |
166 |
// XXX closing file descriptor removes the watch. The logic resides in |
167 |
// the watch which is not nice, but effective and simple |
168 |
if ( !watch->Close() ) |
169 |
{ |
170 |
wxLogSysError(_("Unable to remove kqueue watch")); |
171 |
return false; |
172 |
} |
173 |
|
174 |
return true; |
175 |
} |
176 |
|
177 |
virtual bool RemoveAll() |
178 |
{ |
179 |
wxFSWatchEntries::iterator it = m_watches.begin(); |
180 |
for ( ; it != m_watches.end(); ++it ) |
181 |
{ |
182 |
(void) DoRemove(it->second); |
183 |
} |
184 |
m_watches.clear(); |
185 |
return true; |
186 |
} |
187 |
|
188 |
// return true if there was no error, false on error |
189 |
bool ReadEvents() |
190 |
{ |
191 |
wxCHECK_MSG( IsOk(), false, |
192 |
"Kqueue not initialized or invalid kqueue descriptor" ); |
193 |
|
194 |
// read events |
195 |
do |
196 |
{ |
197 |
struct kevent event; |
198 |
struct timespec timeout = {0, 0}; |
199 |
int ret = kevent(m_kfd, NULL, 0, &event, 1, &timeout); |
200 |
if (ret == -1) |
201 |
{ |
202 |
wxLogSysError(_("Unable to get events from kqueue")); |
203 |
return false; |
204 |
} |
205 |
else if (ret == 0) |
206 |
{ |
207 |
return true; |
208 |
} |
209 |
|
210 |
// we have event, so process it |
211 |
ProcessNativeEvent(event); |
212 |
} |
213 |
while (true); |
214 |
|
215 |
// when ret>0 we still have events, when ret<=0 we return |
216 |
wxFAIL_MSG( "Never reached" ); |
217 |
return true; |
218 |
} |
219 |
|
220 |
bool IsOk() const |
221 |
{ |
222 |
return m_source != NULL; |
223 |
} |
224 |
|
225 |
protected: |
226 |
// returns all new dirs/files present in the immediate level of the dir |
227 |
// pointed by watch.GetPath(). "new" means created between the last time |
228 |
// the state of watch was computed and now |
229 |
void FindChanges(wxFSWatchEntryKq& watch, |
230 |
wxArrayString& changedFiles, |
231 |
wxArrayInt& changedFlags) |
232 |
{ |
233 |
wxFSWatchEntryKq::wxDirState old = watch.GetLastState(); |
234 |
watch.RefreshState(); |
235 |
wxFSWatchEntryKq::wxDirState curr = watch.GetLastState(); |
236 |
|
237 |
// iterate over old/curr file lists and compute changes |
238 |
wxArrayString::iterator oit = old.files.begin(); |
239 |
wxArrayString::iterator cit = curr.files.begin(); |
240 |
for ( ; oit != old.files.end() && cit != curr.files.end(); ) |
241 |
{ |
242 |
if ( *cit == *oit ) |
243 |
{ |
244 |
++cit; |
245 |
++oit; |
246 |
} |
247 |
else if ( *cit <= *oit ) |
248 |
{ |
249 |
changedFiles.push_back(*cit); |
250 |
changedFlags.push_back(wxFSW_EVENT_CREATE); |
251 |
++cit; |
252 |
} |
253 |
else // ( *cit > *oit ) |
254 |
{ |
255 |
changedFiles.push_back(*oit); |
256 |
changedFlags.push_back(wxFSW_EVENT_DELETE); |
257 |
++oit; |
258 |
} |
259 |
} |
260 |
|
261 |
// border conditions |
262 |
if ( oit == old.files.end() ) |
263 |
{ |
264 |
for ( ; cit != curr.files.end(); ++cit ) |
265 |
{ |
266 |
changedFiles.push_back( *cit ); |
267 |
changedFlags.push_back(wxFSW_EVENT_CREATE); |
268 |
} |
269 |
} |
270 |
else if ( cit == curr.files.end() ) |
271 |
{ |
272 |
for ( ; oit != old.files.end(); ++oit ) |
273 |
{ |
274 |
changedFiles.push_back( *oit ); |
275 |
changedFlags.push_back(wxFSW_EVENT_DELETE); |
276 |
} |
277 |
} |
278 |
|
279 |
wxASSERT( changedFiles.size() == changedFlags.size() ); |
280 |
|
281 |
#if 0 |
282 |
wxLogTrace(wxTRACE_FSWATCHER, "Changed files:"); |
283 |
wxArrayString::iterator it = changedFiles.begin(); |
284 |
wxArrayInt::iterator it2 = changedFlags.begin(); |
285 |
for ( ; it != changedFiles.end(); ++it, ++it2) |
286 |
{ |
287 |
wxString action = (*it2 == wxFSW_EVENT_CREATE) ? |
288 |
"created" : "deleted"; |
289 |
wxLogTrace(wxTRACE_FSWATCHER, wxString::Format("File: '%s' %s", |
290 |
*it, action)); |
291 |
} |
292 |
#endif |
293 |
} |
294 |
|
295 |
void ProcessNativeEvent(const struct kevent& e) |
296 |
{ |
297 |
void* const udata = FromUdata(e.udata); |
298 |
|
299 |
wxASSERT_MSG(udata, "Null user data associated with kevent!"); |
300 |
wxLogTrace(wxTRACE_FSWATCHER, "Event: ident=%" PRIuPTR ", filter=%hd, flags=%hu, " |
301 |
#if __FreeBSD_version >= 1200033 |
302 |
"fflags=%u, data=%" PRId64 ", user_data=%p", |
303 |
#else |
304 |
"fflags=%u, data=%" PRIdPTR ", user_data=%p", |
305 |
#endif |
306 |
e.ident, e.filter, e.flags, e.fflags, e.data, udata); |
307 |
|
308 |
// for ease of use |
309 |
wxFSWatchEntryKq& w = *(static_cast<wxFSWatchEntry*>(udata)); |
310 |
int nflags = e.fflags; |
311 |
|
312 |
// clear ignored flags |
313 |
nflags &= ~NOTE_REVOKE; |
314 |
|
315 |
// TODO ignore events we didn't ask for + refactor this cascade ifs |
316 |
// check for events |
317 |
while ( nflags ) |
318 |
{ |
319 |
// when monitoring dir, this means create/delete |
320 |
const wxString basepath = w.GetPath(); |
321 |
if ( nflags & NOTE_WRITE && wxDirExists(basepath) ) |
322 |
{ |
323 |
// NOTE_LINK is set when the dir was created, but we |
324 |
// don't care - we look for new names in directory |
325 |
// regardless of type. Also, clear all this, because |
326 |
// it cannot mean more by itself |
327 |
nflags &= ~(NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK); |
328 |
|
329 |
wxArrayString changedFiles; |
330 |
wxArrayInt changedFlags; |
331 |
FindChanges(w, changedFiles, changedFlags); |
332 |
|
333 |
wxArrayString::iterator it = changedFiles.begin(); |
334 |
wxArrayInt::iterator changeType = changedFlags.begin(); |
335 |
for ( ; it != changedFiles.end(); ++it, ++changeType ) |
336 |
{ |
337 |
const wxString fullpath = w.GetPath() + |
338 |
wxFileName::GetPathSeparator() + |
339 |
*it; |
340 |
const wxFileName path(wxDirExists(fullpath) |
341 |
? wxFileName::DirName(fullpath) |
342 |
: wxFileName::FileName(fullpath)); |
343 |
|
344 |
wxFileSystemWatcherEvent event(*changeType, path, path); |
345 |
SendEvent(event); |
346 |
} |
347 |
} |
348 |
else if ( nflags & NOTE_RENAME ) |
349 |
{ |
350 |
// CHECK it'd be only possible to detect name if we had |
351 |
// parent files listing which we could confront with now and |
352 |
// still we couldn't be sure we have the right name... |
353 |
nflags &= ~NOTE_RENAME; |
354 |
wxFileSystemWatcherEvent event(wxFSW_EVENT_RENAME, |
355 |
basepath, wxFileName()); |
356 |
SendEvent(event); |
357 |
} |
358 |
else if ( nflags & NOTE_WRITE || nflags & NOTE_EXTEND ) |
359 |
{ |
360 |
nflags &= ~(NOTE_WRITE | NOTE_EXTEND); |
361 |
wxFileSystemWatcherEvent event(wxFSW_EVENT_MODIFY, |
362 |
basepath, basepath); |
363 |
SendEvent(event); |
364 |
} |
365 |
else if ( nflags & NOTE_DELETE ) |
366 |
{ |
367 |
nflags &= ~(NOTE_DELETE); |
368 |
wxFileSystemWatcherEvent event(wxFSW_EVENT_DELETE, |
369 |
basepath, basepath); |
370 |
SendEvent(event); |
371 |
} |
372 |
else if ( nflags & NOTE_ATTRIB ) |
373 |
{ |
374 |
nflags &= ~(NOTE_ATTRIB); |
375 |
wxFileSystemWatcherEvent event(wxFSW_EVENT_ACCESS, |
376 |
basepath, basepath); |
377 |
SendEvent(event); |
378 |
} |
379 |
|
380 |
// clear any flags that won't mean anything by themselves |
381 |
nflags &= ~(NOTE_LINK); |
382 |
} |
383 |
} |
384 |
|
385 |
void SendEvent(wxFileSystemWatcherEvent& evt) |
386 |
{ |
387 |
m_watcher->GetOwner()->ProcessEvent(evt); |
388 |
} |
389 |
|
390 |
static int Watcher2NativeFlags(int WXUNUSED(flags)) |
391 |
{ |
392 |
// TODO: it would be better to only subscribe to what we need |
393 |
return NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | |
394 |
NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME | |
395 |
NOTE_REVOKE; |
396 |
} |
397 |
|
398 |
wxFSWSourceHandler* m_handler; // handler for kqueue event source |
399 |
wxEventLoopSource* m_source; // our event loop source |
400 |
|
401 |
// descriptor created by kqueue() |
402 |
int m_kfd; |
403 |
}; |
404 |
|
405 |
|
406 |
// once we get signaled to read, actuall event reading occurs |
407 |
void wxFSWSourceHandler::OnReadWaiting() |
408 |
{ |
409 |
wxLogTrace(wxTRACE_FSWATCHER, "--- OnReadWaiting ---"); |
410 |
m_service->ReadEvents(); |
411 |
} |
412 |
|
413 |
void wxFSWSourceHandler::OnWriteWaiting() |
414 |
{ |
415 |
wxFAIL_MSG("We never write to kqueue descriptor."); |
416 |
} |
417 |
|
418 |
void wxFSWSourceHandler::OnExceptionWaiting() |
419 |
{ |
420 |
wxFAIL_MSG("We never receive exceptions on kqueue descriptor."); |
421 |
} |
422 |
|
423 |
|
424 |
// ============================================================================ |
425 |
// wxKqueueFileSystemWatcher implementation |
426 |
// ============================================================================ |
427 |
|
428 |
wxKqueueFileSystemWatcher::wxKqueueFileSystemWatcher() |
429 |
: wxFileSystemWatcherBase() |
430 |
{ |
431 |
Init(); |
432 |
} |
433 |
|
434 |
wxKqueueFileSystemWatcher::wxKqueueFileSystemWatcher(const wxFileName& path, |
435 |
int events) |
436 |
: wxFileSystemWatcherBase() |
437 |
{ |
438 |
if (!Init()) |
439 |
{ |
440 |
wxDELETE(m_service); |
441 |
return; |
442 |
} |
443 |
|
444 |
Add(path, events); |
445 |
} |
446 |
|
447 |
wxKqueueFileSystemWatcher::~wxKqueueFileSystemWatcher() |
448 |
{ |
449 |
} |
450 |
|
451 |
bool wxKqueueFileSystemWatcher::Init() |
452 |
{ |
453 |
m_service = new wxFSWatcherImplKqueue(this); |
454 |
return m_service->Init(); |
455 |
} |
456 |
|
457 |
#endif // wxHAS_KQUEUE |
458 |
|
459 |
#endif // wxUSE_FSWATCHER |