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