1 ///////////////////////////////////////////////////////////////////////////// 
   2 // Name:        src/unix/fswatcher_kqueue.cpp 
   3 // Purpose:     kqueue-based wxFileSystemWatcher implementation 
   4 // Author:      Bartosz Bekier 
   7 // Copyright:   (c) 2009 Bartosz Bekier <bartosz.bekier@gmail.com> 
   8 // Licence:     wxWindows licence 
   9 ///////////////////////////////////////////////////////////////////////////// 
  11 // For compilers that support precompilation, includes "wx.h". 
  12 #include "wx/wxprec.h" 
  20 #include "wx/fswatcher.h" 
  24 #include <sys/types.h> 
  25 #include <sys/event.h> 
  27 #include "wx/dynarray.h" 
  28 #include "wx/evtloop.h" 
  29 #include "wx/evtloopsrc.h" 
  31 #include "wx/private/fswatcher.h" 
  33 // ============================================================================ 
  34 // wxFSWSourceHandler helper class 
  35 // ============================================================================ 
  37 class wxFSWatcherImplKqueue
; 
  40  * Handler for handling i/o from inotify descriptor 
  42 class wxFSWSourceHandler 
: public wxEventLoopSourceHandler
 
  45     wxFSWSourceHandler(wxFSWatcherImplKqueue
* service
) : 
  49     virtual void OnReadWaiting(); 
  50     virtual void OnWriteWaiting(); 
  51     virtual void OnExceptionWaiting(); 
  54     wxFSWatcherImplKqueue
* m_service
; 
  57 // ============================================================================ 
  58 // wxFSWatcherImpl implementation & helper wxFSWSourceHandler implementation 
  59 // ============================================================================ 
  62  * Helper class encapsulating inotify mechanism 
  64 class wxFSWatcherImplKqueue 
: public wxFSWatcherImpl
 
  67     wxFSWatcherImplKqueue(wxFileSystemWatcherBase
* watcher
) : 
  68         wxFSWatcherImpl(watcher
), 
  72         m_handler 
= new wxFSWSourceHandler(this); 
  75     virtual ~wxFSWatcherImplKqueue() 
  77         // we close kqueue only if initialized before 
  88         wxCHECK_MSG( !IsOk(), false, 
  89                      "Kqueue appears to be already initialized" ); 
  91         wxEventLoopBase 
*loop 
= wxEventLoopBase::GetActive(); 
  92         wxCHECK_MSG( loop
, false, "File system watcher needs an active loop" ); 
  98             wxLogSysError(_("Unable to create kqueue instance")); 
 103         m_source 
= loop
->AddSourceForFD(m_kfd
, m_handler
, wxEVENT_SOURCE_INPUT
); 
 105         return m_source 
!= NULL
; 
 111                     "Kqueue not initialized or invalid kqueue descriptor" ); 
 113         if ( close(m_kfd
) != 0 ) 
 115             wxLogSysError(_("Error closing kqueue instance")); 
 121     virtual bool DoAdd(wxSharedPtr
<wxFSWatchEntryKq
> watch
) 
 123         wxCHECK_MSG( IsOk(), false, 
 124                     "Kqueue not initialized or invalid kqueue descriptor" ); 
 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() ); 
 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
); 
 137             wxLogSysError(_("Unable to add kqueue watch")); 
 144     virtual bool DoRemove(wxSharedPtr
<wxFSWatchEntryKq
> watch
) 
 146         wxCHECK_MSG( IsOk(), false, 
 147                     "Kqueue not initialized or invalid kqueue descriptor" ); 
 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(); 
 155             wxLogSysError(_("Unable to remove kqueue watch")); 
 162     virtual bool RemoveAll() 
 164         wxFSWatchEntries::iterator it 
= m_watches
.begin(); 
 165         for ( ; it 
!= m_watches
.end(); ++it 
) 
 167             (void) DoRemove(it
->second
); 
 173     // return true if there was no error, false on error 
 176         wxCHECK_MSG( IsOk(), false, 
 177                     "Kqueue not initialized or invalid kqueue descriptor" ); 
 183             struct timespec timeout 
= {0, 0}; 
 184             int ret 
= kevent(m_kfd
, NULL
, 0, &event
, 1, &timeout
); 
 187                 wxLogSysError(_("Unable to get events from kqueue")); 
 195             // we have event, so process it 
 196             ProcessNativeEvent(event
); 
 200         // when ret>0 we still have events, when ret<=0 we return 
 201         wxFAIL_MSG( "Never reached" ); 
 207         return m_source 
!= NULL
; 
 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
) 
 218         wxFSWatchEntryKq::wxDirState old 
= watch
.GetLastState(); 
 219         watch
.RefreshState(); 
 220         wxFSWatchEntryKq::wxDirState curr 
= watch
.GetLastState(); 
 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(); ) 
 232             else if ( *cit 
<= *oit 
) 
 234                 changedFiles
.push_back(*cit
); 
 235                 changedFlags
.push_back(wxFSW_EVENT_CREATE
); 
 238             else // ( *cit > *oit ) 
 240                 changedFiles
.push_back(*oit
); 
 241                 changedFlags
.push_back(wxFSW_EVENT_DELETE
); 
 247         if ( oit 
== old
.files
.end() ) 
 249             for ( ; cit 
!= curr
.files
.end(); ++cit 
) 
 251                 changedFiles
.push_back( *cit 
); 
 252                 changedFlags
.push_back(wxFSW_EVENT_CREATE
); 
 255         else if ( cit 
== curr
.files
.end() ) 
 257             for ( ; oit 
!= old
.files
.end(); ++oit 
) 
 259                 changedFiles
.push_back( *oit 
); 
 260                 changedFlags
.push_back(wxFSW_EVENT_DELETE
); 
 264         wxASSERT( changedFiles
.size() == changedFlags
.size() ); 
 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
) 
 272             wxString action 
= (*it2 
== wxFSW_EVENT_CREATE
) ? 
 273                                 "created" : "deleted"; 
 274             wxLogTrace(wxTRACE_FSWATCHER
, wxString::Format("File: '%s' %s", 
 280     void ProcessNativeEvent(const struct kevent
& e
) 
 282         wxASSERT_MSG(e
.udata
, "Null user data associated with kevent!"); 
 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
); 
 289         wxFSWatchEntryKq
& w 
= *(static_cast<wxFSWatchEntry
*>(e
.udata
)); 
 290         int nflags 
= e
.fflags
; 
 292         // clear ignored flags 
 293         nflags 
&= ~NOTE_REVOKE
; 
 295         // TODO ignore events we didn't ask for + refactor this cascade ifs 
 299             // when monitoring dir, this means create/delete 
 300             const wxString basepath 
= w
.GetPath(); 
 301             if ( nflags 
& NOTE_WRITE 
&& wxDirExists(basepath
) ) 
 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
); 
 309                 wxArrayString changedFiles
; 
 310                 wxArrayInt changedFlags
; 
 311                 FindChanges(w
, changedFiles
, changedFlags
); 
 313                 wxArrayString::iterator it 
= changedFiles
.begin(); 
 314                 wxArrayInt::iterator changeType 
= changedFlags
.begin(); 
 315                 for ( ; it 
!= changedFiles
.end(); ++it
, ++changeType 
) 
 317                     const wxString fullpath 
= w
.GetPath() + 
 318                                                 wxFileName::GetPathSeparator() + 
 320                     const wxFileName 
path(wxDirExists(fullpath
) 
 321                                             ? wxFileName::DirName(fullpath
) 
 322                                             : wxFileName::FileName(fullpath
)); 
 324                     wxFileSystemWatcherEvent 
event(*changeType
, path
, path
); 
 328             else if ( nflags 
& NOTE_RENAME 
) 
 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()); 
 338             else if ( nflags 
& NOTE_WRITE 
|| nflags 
& NOTE_EXTEND 
) 
 340                 nflags 
&= ~(NOTE_WRITE 
| NOTE_EXTEND
); 
 341                 wxFileSystemWatcherEvent 
event(wxFSW_EVENT_MODIFY
, 
 345             else if ( nflags 
& NOTE_DELETE 
) 
 347                 nflags 
&= ~(NOTE_DELETE
); 
 348                 wxFileSystemWatcherEvent 
event(wxFSW_EVENT_DELETE
, 
 352             else if ( nflags 
& NOTE_ATTRIB 
) 
 354                 nflags 
&= ~(NOTE_ATTRIB
); 
 355                 wxFileSystemWatcherEvent 
event(wxFSW_EVENT_ACCESS
, 
 360             // clear any flags that won't mean anything by themselves 
 361             nflags 
&= ~(NOTE_LINK
); 
 365     void SendEvent(wxFileSystemWatcherEvent
& evt
) 
 367         m_watcher
->GetOwner()->ProcessEvent(evt
); 
 370     static int Watcher2NativeFlags(int WXUNUSED(flags
)) 
 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 
| 
 378     wxFSWSourceHandler
* m_handler
;        // handler for kqueue event source 
 379     wxEventLoopSource
* m_source
;          // our event loop source 
 381     // descriptor created by kqueue() 
 386 // once we get signaled to read, actuall event reading occurs 
 387 void wxFSWSourceHandler::OnReadWaiting() 
 389     wxLogTrace(wxTRACE_FSWATCHER
, "--- OnReadWaiting ---"); 
 390     m_service
->ReadEvents(); 
 393 void wxFSWSourceHandler::OnWriteWaiting() 
 395     wxFAIL_MSG("We never write to kqueue descriptor."); 
 398 void wxFSWSourceHandler::OnExceptionWaiting() 
 400     wxFAIL_MSG("We never receive exceptions on kqueue descriptor."); 
 404 // ============================================================================ 
 405 // wxKqueueFileSystemWatcher implementation 
 406 // ============================================================================ 
 408 wxKqueueFileSystemWatcher::wxKqueueFileSystemWatcher() 
 409     : wxFileSystemWatcherBase() 
 414 wxKqueueFileSystemWatcher::wxKqueueFileSystemWatcher(const wxFileName
& path
, 
 416     : wxFileSystemWatcherBase() 
 427 wxKqueueFileSystemWatcher::~wxKqueueFileSystemWatcher() 
 431 bool wxKqueueFileSystemWatcher::Init() 
 433     m_service 
= new wxFSWatcherImplKqueue(this); 
 434     return m_service
->Init(); 
 437 #endif // wxHAS_KQUEUE 
 439 #endif // wxUSE_FSWATCHER