1 ///////////////////////////////////////////////////////////////////////////// 
   2 // Name:        src/unix/fswatcher_inotify.cpp 
   3 // Purpose:     inotify-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/inotify.h> 
  26 #include "wx/private/fswatcher.h" 
  28 // ============================================================================ 
  29 // wxFSWatcherImpl implementation & helper wxFSWSourceHandler implementation 
  30 // ============================================================================ 
  32 // inotify watch descriptor => wxFSWatchEntry* map 
  33 WX_DECLARE_HASH_MAP(int, wxFSWatchEntry
*, wxIntegerHash
, wxIntegerEqual
, 
  34                                               wxFSWatchEntryDescriptors
); 
  36 // inotify event cookie => inotify_event* map 
  37 WX_DECLARE_HASH_MAP(int, inotify_event
*, wxIntegerHash
, wxIntegerEqual
, 
  41  * Helper class encapsulating inotify mechanism 
  43 class wxFSWatcherImplUnix 
: public wxFSWatcherImpl
 
  46     wxFSWatcherImplUnix(wxFileSystemWatcherBase
* watcher
) : 
  47         wxFSWatcherImpl(watcher
), 
  51         m_handler 
= new wxFSWSourceHandler(this); 
  54     ~wxFSWatcherImplUnix() 
  56         // we close inotify only if initialized before 
  67         wxCHECK_MSG( !IsOk(), false, "Inotify already initialized" ); 
  69         wxEventLoopBase 
*loop 
= wxEventLoopBase::GetActive(); 
  70         wxCHECK_MSG( loop
, false, "File system watcher needs an event loop" ); 
  72         m_ifd 
= inotify_init(); 
  75             wxLogSysError( _("Unable to create inotify instance") ); 
  79         m_source 
= loop
->AddSourceForFD
 
  83                           wxEVENT_SOURCE_INPUT 
| wxEVENT_SOURCE_EXCEPTION
 
  86         return m_source 
!= NULL
; 
  92                     "Inotify not initialized or invalid inotify descriptor" ); 
  96         if ( close(m_ifd
) != 0 ) 
  98             wxLogSysError( _("Unable to close inotify instance") ); 
 102     virtual bool DoAdd(wxSharedPtr
<wxFSWatchEntryUnix
> watch
) 
 104         wxCHECK_MSG( IsOk(), false, 
 105                     "Inotify not initialized or invalid inotify descriptor" ); 
 107         int wd 
= DoAddInotify(watch
.get()); 
 110             wxLogSysError( _("Unable to add inotify watch") ); 
 114         wxFSWatchEntryDescriptors::value_type 
val(wd
, watch
.get()); 
 115         if (!m_watchMap
.insert(val
).second
) 
 117             wxFAIL_MSG( wxString::Format( "Path %s is already watched", 
 125     virtual bool DoRemove(wxSharedPtr
<wxFSWatchEntryUnix
> watch
) 
 127         wxCHECK_MSG( IsOk(), false, 
 128                     "Inotify not initialized or invalid inotify descriptor" ); 
 130         int ret 
= DoRemoveInotify(watch
.get()); 
 133             wxLogSysError( _("Unable to remove inotify watch") ); 
 137         if (m_watchMap
.erase(watch
->GetWatchDescriptor()) != 1) 
 139             wxFAIL_MSG( wxString::Format("Path %s is not watched", 
 142         watch
->SetWatchDescriptor(-1); 
 146     virtual bool RemoveAll() 
 148         wxFSWatchEntries::iterator it 
= m_watches
.begin(); 
 149         for ( ; it 
!= m_watches
.end(); ++it 
) 
 151             (void) DoRemove(it
->second
); 
 159         wxCHECK_MSG( IsOk(), -1, 
 160                     "Inotify not initialized or invalid inotify descriptor" ); 
 163         // TODO differentiate depending on params 
 164         char buf
[128 * sizeof(inotify_event
)]; 
 165         int left 
= ReadEventsToBuf(buf
, sizeof(buf
)); 
 169         // left > 0, we have events 
 172         while (left 
> 0) // OPT checking 'memory' would suffice 
 175             inotify_event
* e 
= (inotify_event
*)memory
; 
 177             // process one inotify_event 
 178             ProcessNativeEvent(*e
); 
 180             int offset 
= sizeof(inotify_event
) + e
->len
; 
 185         // take care of unmatched renames 
 188         wxLogTrace(wxTRACE_FSWATCHER
, "We had %d native events", event_count
); 
 194         return m_source 
!= NULL
; 
 198     int DoAddInotify(wxFSWatchEntry
* watch
) 
 200         int flags 
= Watcher2NativeFlags(watch
->GetFlags()); 
 201         int wd 
= inotify_add_watch(m_ifd
, watch
->GetPath().fn_str(), flags
); 
 202         // finally we can set watch descriptor 
 203         watch
->SetWatchDescriptor(wd
); 
 207     int DoRemoveInotify(wxFSWatchEntry
* watch
) 
 209         return inotify_rm_watch(m_ifd
, watch
->GetWatchDescriptor()); 
 212     void ProcessNativeEvent(const inotify_event
& inevt
) 
 214         wxLogTrace(wxTRACE_FSWATCHER
, InotifyEventToString(inevt
)); 
 216         // after removing inotify watch we get IN_IGNORED for it, but the watch 
 217         // will be already removed from our list at that time 
 218         if (inevt
.mask 
& IN_IGNORED
) 
 223         // get watch entry for this event 
 224         wxFSWatchEntryDescriptors::iterator it 
= m_watchMap
.find(inevt
.wd
); 
 225         wxCHECK_RET(it 
!= m_watchMap
.end(), 
 226                              "Watch descriptor not present in the watch map!"); 
 228         wxFSWatchEntry
& watch 
= *(it
->second
); 
 229         int nativeFlags 
= inevt
.mask
; 
 230         int flags 
= Native2WatcherFlags(nativeFlags
); 
 232         // check out for error/warning condition 
 233         if (flags 
& wxFSW_EVENT_WARNING 
|| flags 
& wxFSW_EVENT_ERROR
) 
 235             wxString errMsg 
= GetErrorDescription(Watcher2NativeFlags(flags
)); 
 236             wxFileSystemWatcherEvent 
event(flags
, errMsg
); 
 239         // filter out ignored events and those not asked for. 
 240         // we never filter out warnings or exceptions 
 241         else if ((flags 
== 0) || !(flags 
& watch
.GetFlags())) 
 246         else if (nativeFlags 
& IN_MOVE
) 
 248             wxInotifyCookies::iterator it 
= m_cookies
.find(inevt
.cookie
); 
 249             if ( it 
== m_cookies
.end() ) 
 251                 int size 
= sizeof(inevt
) + inevt
.len
; 
 252                 inotify_event
* e 
= (inotify_event
*) operator new (size
); 
 253                 memcpy(e
, &inevt
, size
); 
 255                 wxInotifyCookies::value_type 
val(e
->cookie
, e
); 
 256                 m_cookies
.insert(val
); 
 260                 inotify_event
& oldinevt 
= *(it
->second
); 
 262                 wxFileSystemWatcherEvent 
event(flags
); 
 263                 if ( inevt
.mask 
& IN_MOVED_FROM 
) 
 265                     event
.SetPath(GetEventPath(watch
, inevt
)); 
 266                     event
.SetNewPath(GetEventPath(watch
, oldinevt
)); 
 270                     event
.SetPath(GetEventPath(watch
, oldinevt
)); 
 271                     event
.SetNewPath(GetEventPath(watch
, inevt
)); 
 279         // every other kind of event 
 282             wxFileName path 
= GetEventPath(watch
, inevt
); 
 283             wxFileSystemWatcherEvent 
event(flags
, path
, path
); 
 288     void ProcessRenames() 
 290         wxInotifyCookies::iterator it 
= m_cookies
.begin(); 
 291         while ( it 
!= m_cookies
.end() ) 
 293             inotify_event
& inevt 
= *(it
->second
); 
 295             wxLogTrace(wxTRACE_FSWATCHER
, "Processing pending rename events"); 
 296             wxLogTrace(wxTRACE_FSWATCHER
, InotifyEventToString(inevt
)); 
 298             // get watch entry for this event 
 299             wxFSWatchEntryDescriptors::iterator wit 
= m_watchMap
.find(inevt
.wd
); 
 300             wxCHECK_RET(wit 
!= m_watchMap
.end(), 
 301                              "Watch descriptor not present in the watch map!"); 
 303             wxFSWatchEntry
& watch 
= *(wit
->second
); 
 304             int flags 
= Native2WatcherFlags(inevt
.mask
); 
 305             wxFileName path 
= GetEventPath(watch
, inevt
); 
 306             wxFileSystemWatcherEvent 
event(flags
, path
, path
); 
 311             it 
= m_cookies
.begin(); 
 315     void SendEvent(wxFileSystemWatcherEvent
& evt
) 
 317         wxLogTrace(wxTRACE_FSWATCHER
, evt
.ToString()); 
 318         m_watcher
->GetOwner()->ProcessEvent(evt
); 
 321     int ReadEventsToBuf(char* buf
, int size
) 
 323         wxCHECK_MSG( IsOk(), false, 
 324                     "Inotify not initialized or invalid inotify descriptor" ); 
 326         memset(buf
, 0, size
); 
 327         ssize_t left 
= read(m_ifd
, buf
, size
); 
 330             wxLogSysError(_("Unable to read from inotify descriptor")); 
 335             wxLogWarning(_("EOF while reading from inotify descriptor")); 
 342     static wxString 
InotifyEventToString(const inotify_event
& inevt
) 
 344         wxString mask 
= (inevt
.mask 
& IN_ISDIR
) ? 
 345                         wxString::Format("IS_DIR | %u", inevt
.mask 
& ~IN_ISDIR
) : 
 346                         wxString::Format("%u", inevt
.mask
); 
 347         return wxString::Format("Event: wd=%d, mask=%s, cookie=%u, len=%u, " 
 348                                 "name=%s", inevt
.wd
, mask
, inevt
.cookie
, 
 349                                 inevt
.len
, inevt
.name
); 
 352     static wxFileName 
GetEventPath(const wxFSWatchEntry
& watch
, 
 353                                    const inotify_event
& inevt
) 
 355         // only when dir is watched, we have non-empty e.name 
 356         wxFileName path 
= watch
.GetPath(); 
 359             path 
= wxFileName(path
.GetPath(), inevt
.name
); 
 364     static int Watcher2NativeFlags(int WXUNUSED(flags
)) 
 366         // TODO: it would be nice to subscribe only to the events we really need 
 367         return IN_ALL_EVENTS
; 
 370     static int Native2WatcherFlags(int flags
) 
 372         static const int flag_mapping
[][2] = { 
 373             { IN_ACCESS
,        wxFSW_EVENT_ACCESS 
}, // generated during read! 
 374             { IN_MODIFY
,        wxFSW_EVENT_MODIFY 
}, 
 376             { IN_CLOSE_WRITE
,   0 }, 
 377             { IN_CLOSE_NOWRITE
, 0 }, 
 379             { IN_MOVED_FROM
,    wxFSW_EVENT_RENAME 
}, 
 380             { IN_MOVED_TO
,      wxFSW_EVENT_RENAME 
}, 
 381             { IN_CREATE
,        wxFSW_EVENT_CREATE 
}, 
 382             { IN_DELETE
,        wxFSW_EVENT_DELETE 
}, 
 383             { IN_DELETE_SELF
,   wxFSW_EVENT_DELETE 
}, 
 384             { IN_MOVE_SELF
,     wxFSW_EVENT_DELETE 
}, 
 386             { IN_UNMOUNT
,       wxFSW_EVENT_ERROR  
}, 
 387             { IN_Q_OVERFLOW
,    wxFSW_EVENT_WARNING
}, 
 389             // ignored, because this is genereted mainly by watcher::Remove() 
 394         for ( ; i 
< WXSIZEOF(flag_mapping
); ++i
) { 
 395             // in this mapping multiple flags at once don't happen 
 396             if (flags 
& flag_mapping
[i
][0]) 
 397                 return flag_mapping
[i
][1]; 
 401         wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flags
)); 
 406      * Returns error description for specified inotify mask 
 408     static const wxString 
GetErrorDescription(int flag
) 
 413             return _("File system containing watched object was unmounted"); 
 415             return _("Event queue overflowed"); 
 419         wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flag
)); 
 420         return wxEmptyString
; 
 423     wxFSWSourceHandler
* m_handler
;        // handler for inotify event source 
 424     wxFSWatchEntryDescriptors m_watchMap
; // inotify wd=>wxFSWatchEntry* map 
 425     wxInotifyCookies m_cookies
;           // map to track renames 
 426     wxEventLoopSource
* m_source
;          // our event loop source 
 428     // file descriptor created by inotify_init() 
 433 // ============================================================================ 
 434 // wxFSWSourceHandler implementation 
 435 // ============================================================================ 
 437 // once we get signaled to read, actuall event reading occurs 
 438 void wxFSWSourceHandler::OnReadWaiting() 
 440     wxLogTrace(wxTRACE_FSWATCHER
, "--- OnReadWaiting ---"); 
 441     m_service
->ReadEvents(); 
 444 void wxFSWSourceHandler::OnWriteWaiting() 
 446     wxFAIL_MSG("We never write to inotify descriptor."); 
 449 void wxFSWSourceHandler::OnExceptionWaiting() 
 451     wxFAIL_MSG("We never receive exceptions on inotify descriptor."); 
 455 // ============================================================================ 
 456 // wxInotifyFileSystemWatcher implementation 
 457 // ============================================================================ 
 459 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher() 
 460     : wxFileSystemWatcherBase() 
 465 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher(const wxFileName
& path
, 
 467     : wxFileSystemWatcherBase() 
 479 wxInotifyFileSystemWatcher::~wxInotifyFileSystemWatcher() 
 483 bool wxInotifyFileSystemWatcher::Init() 
 485     m_service 
= new wxFSWatcherImplUnix(this); 
 486     return m_service
->Init(); 
 489 #endif // wxHAS_INOTIFY 
 491 #endif // wxUSE_FSWATCHER