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         // Cache the wd in case any events arrive late 
 143         m_staleDescriptors
.Add(watch
->GetWatchDescriptor()); 
 145         watch
->SetWatchDescriptor(-1); 
 149     virtual bool RemoveAll() 
 151         wxFSWatchEntries::iterator it 
= m_watches
.begin(); 
 152         for ( ; it 
!= m_watches
.end(); ++it 
) 
 154             (void) DoRemove(it
->second
); 
 162         wxCHECK_MSG( IsOk(), -1, 
 163                     "Inotify not initialized or invalid inotify descriptor" ); 
 166         // TODO differentiate depending on params 
 167         char buf
[128 * sizeof(inotify_event
)]; 
 168         int left 
= ReadEventsToBuf(buf
, sizeof(buf
)); 
 172         // left > 0, we have events 
 175         while (left 
> 0) // OPT checking 'memory' would suffice 
 178             inotify_event
* e 
= (inotify_event
*)memory
; 
 180             // process one inotify_event 
 181             ProcessNativeEvent(*e
); 
 183             int offset 
= sizeof(inotify_event
) + e
->len
; 
 188         // take care of unmatched renames 
 191         wxLogTrace(wxTRACE_FSWATCHER
, "We had %d native events", event_count
); 
 197         return m_source 
!= NULL
; 
 201     int DoAddInotify(wxFSWatchEntry
* watch
) 
 203         int flags 
= Watcher2NativeFlags(watch
->GetFlags()); 
 204         int wd 
= inotify_add_watch(m_ifd
, watch
->GetPath().fn_str(), flags
); 
 205         // finally we can set watch descriptor 
 206         watch
->SetWatchDescriptor(wd
); 
 210     int DoRemoveInotify(wxFSWatchEntry
* watch
) 
 212         return inotify_rm_watch(m_ifd
, watch
->GetWatchDescriptor()); 
 215     void ProcessNativeEvent(const inotify_event
& inevt
) 
 217         wxLogTrace(wxTRACE_FSWATCHER
, InotifyEventToString(inevt
)); 
 219         // after removing inotify watch we get IN_IGNORED for it, but the watch 
 220         // will be already removed from our list at that time 
 221         if (inevt
.mask 
& IN_IGNORED
) 
 223             // It is now safe to remove it from the stale descriptors too, we 
 224             // won't get any more events for it. 
 225             // However if we're here because a dir that we're still watching 
 226             // has just been deleted, its wd won't be on this list 
 227             const int pos 
= m_staleDescriptors
.Index(inevt
.wd
); 
 228             if ( pos 
!= wxNOT_FOUND 
) 
 230                 m_staleDescriptors
.RemoveAt(static_cast<size_t>(pos
)); 
 231                 wxLogTrace(wxTRACE_FSWATCHER
, 
 232                        "Removed wd %i from the stale-wd cache", inevt
.wd
); 
 237         // get watch entry for this event 
 238         wxFSWatchEntryDescriptors::iterator it 
= m_watchMap
.find(inevt
.wd
); 
 239         if (it 
== m_watchMap
.end()) 
 241             // It's not in the map; check if was recently removed from it. 
 242             if (m_staleDescriptors
.Index(inevt
.wd
) != wxNOT_FOUND
) 
 244                 wxLogTrace(wxTRACE_FSWATCHER
, 
 245                            "Got an event for stale wd %i", inevt
.wd
); 
 249                 wxFAIL_MSG("Event for unknown watch descriptor."); 
 252             // In any case, don't process this event: it's either for an 
 253             // already removed entry, or for a completely unknown one. 
 257         wxFSWatchEntry
& watch 
= *(it
->second
); 
 258         int nativeFlags 
= inevt
.mask
; 
 259         int flags 
= Native2WatcherFlags(nativeFlags
); 
 261         // check out for error/warning condition 
 262         if (flags 
& wxFSW_EVENT_WARNING 
|| flags 
& wxFSW_EVENT_ERROR
) 
 264             wxString errMsg 
= GetErrorDescription(Watcher2NativeFlags(flags
)); 
 265             wxFileSystemWatcherEvent 
event(flags
, errMsg
); 
 268         // filter out ignored events and those not asked for. 
 269         // we never filter out warnings or exceptions 
 270         else if ((flags 
== 0) || !(flags 
& watch
.GetFlags())) 
 276         // We need do something here only if the original watch was recursive; 
 277         // we don't watch a child dir itself inside a non-tree watch. 
 278         // We watch only dirs explicitly, so we don't want file IN_CREATEs. 
 279         // Distinguish by whether nativeFlags contain IN_ISDIR 
 280         else if ((nativeFlags 
& IN_CREATE
) && 
 281                  (watch
.GetType() == wxFSWPath_Tree
) && (inevt
.mask 
& IN_ISDIR
)) 
 283             wxFileName fn 
= GetEventPath(watch
, inevt
); 
 284             // Though it's a dir, fn treats it as a file. So: 
 285             fn
.AssignDir(fn
.GetFullPath()); 
 287             if (m_watcher
->AddAny(fn
, wxFSW_EVENT_ALL
, 
 288                                    wxFSWPath_Tree
, watch
.GetFilespec())) 
 290                 // Tell the owner, in case it's interested 
 291                 // If there's a filespec, assume he's not 
 292                 if (watch
.GetFilespec().empty()) 
 294                     wxFileSystemWatcherEvent 
event(flags
, fn
, fn
); 
 301         // We watch only dirs explicitly, so we don't want file IN_DELETEs. 
 302         // We obviously can't check using DirExists() as the object has been 
 303         // deleted; and nativeFlags here doesn't contain IN_ISDIR, even for 
 304         // a dir. Fortunately IN_DELETE_SELF doesn't happen for files. We need 
 305         // to do something here only inside a tree watch, or if it's the parent 
 306         // dir that's deleted. Otherwise let the parent dir cope 
 307         else if ((nativeFlags 
& IN_DELETE_SELF
) && 
 308                     ((watch
.GetType() == wxFSWPath_Dir
) || 
 309                      (watch
.GetType() == wxFSWPath_Tree
))) 
 311             // We must remove the deleted directory from the map, so that 
 312             // DoRemoveInotify() isn't called on it in the future. Don't assert 
 313             // if the wd isn't found: repeated IN_DELETE_SELFs can occur 
 314             wxFileName fn 
= GetEventPath(watch
, inevt
); 
 315             wxString 
path(fn
.GetPathWithSep()); 
 317             if (m_watchMap
.erase(inevt
.wd
) == 1) 
 319                 // Delete from wxFileSystemWatcher 
 320                 wxDynamicCast(m_watcher
, wxInotifyFileSystemWatcher
)-> 
 323                 // Now remove from our local list of watched items 
 324                 wxFSWatchEntries::iterator wit 
= 
 325                                         m_watches
.find(path
); 
 326                 if (wit 
!= m_watches
.end()) 
 328                     m_watches
.erase(wit
); 
 331                 // Cache the wd in case any events arrive late 
 332                 m_staleDescriptors
.Add(inevt
.wd
); 
 335             // Tell the owner, in case it's interested 
 336             // If there's a filespec, assume he's not 
 337             if (watch
.GetFilespec().empty()) 
 339                 wxFileSystemWatcherEvent 
event(flags
, fn
, fn
); 
 345         else if (nativeFlags 
& IN_MOVE
) 
 347             wxInotifyCookies::iterator it2 
= m_cookies
.find(inevt
.cookie
); 
 348             if ( it2 
== m_cookies
.end() ) 
 350                 int size 
= sizeof(inevt
) + inevt
.len
; 
 351                 inotify_event
* e 
= (inotify_event
*) operator new (size
); 
 352                 memcpy(e
, &inevt
, size
); 
 354                 wxInotifyCookies::value_type 
val(e
->cookie
, e
); 
 355                 m_cookies
.insert(val
); 
 359                 inotify_event
& oldinevt 
= *(it2
->second
); 
 361                 // Tell the owner, in case it's interested 
 362                 // If there's a filespec, assume he's not 
 363                 if ( watch
.GetFilespec().empty() ) 
 365                     wxFileSystemWatcherEvent 
event(flags
); 
 366                     if ( inevt
.mask 
& IN_MOVED_FROM 
) 
 368                         event
.SetPath(GetEventPath(watch
, inevt
)); 
 369                         event
.SetNewPath(GetEventPath(watch
, oldinevt
)); 
 373                         event
.SetPath(GetEventPath(watch
, oldinevt
)); 
 374                         event
.SetNewPath(GetEventPath(watch
, inevt
)); 
 379                 m_cookies
.erase(it2
); 
 383         // every other kind of event 
 386             wxFileName path 
= GetEventPath(watch
, inevt
); 
 387             // For files, check that it matches any filespec 
 388             if ( MatchesFilespec(path
, watch
.GetFilespec()) ) 
 390                 wxFileSystemWatcherEvent 
event(flags
, path
, path
); 
 396     void ProcessRenames() 
 398         wxInotifyCookies::iterator it 
= m_cookies
.begin(); 
 399         while ( it 
!= m_cookies
.end() ) 
 401             inotify_event
& inevt 
= *(it
->second
); 
 403             wxLogTrace(wxTRACE_FSWATCHER
, "Processing pending rename events"); 
 404             wxLogTrace(wxTRACE_FSWATCHER
, InotifyEventToString(inevt
)); 
 406             // get watch entry for this event 
 407             wxFSWatchEntryDescriptors::iterator wit 
= m_watchMap
.find(inevt
.wd
); 
 408             wxCHECK_RET(wit 
!= m_watchMap
.end(), 
 409                              "Watch descriptor not present in the watch map!"); 
 411             // Tell the owner, in case it's interested 
 412             // If there's a filespec, assume he's not 
 413             wxFSWatchEntry
& watch 
= *(wit
->second
); 
 414             if ( watch
.GetFilespec().empty() ) 
 416                 int flags 
= Native2WatcherFlags(inevt
.mask
); 
 417                 wxFileName path 
= GetEventPath(watch
, inevt
); 
 419                     wxFileSystemWatcherEvent 
event(flags
, path
, path
); 
 426             it 
= m_cookies
.begin(); 
 430     void SendEvent(wxFileSystemWatcherEvent
& evt
) 
 432         wxLogTrace(wxTRACE_FSWATCHER
, evt
.ToString()); 
 433         m_watcher
->GetOwner()->ProcessEvent(evt
); 
 436     int ReadEventsToBuf(char* buf
, int size
) 
 438         wxCHECK_MSG( IsOk(), false, 
 439                     "Inotify not initialized or invalid inotify descriptor" ); 
 441         memset(buf
, 0, size
); 
 442         ssize_t left 
= read(m_ifd
, buf
, size
); 
 445             wxLogSysError(_("Unable to read from inotify descriptor")); 
 450             wxLogWarning(_("EOF while reading from inotify descriptor")); 
 457     static wxString 
InotifyEventToString(const inotify_event
& inevt
) 
 459         wxString mask 
= (inevt
.mask 
& IN_ISDIR
) ? 
 460                         wxString::Format("IS_DIR | %u", inevt
.mask 
& ~IN_ISDIR
) : 
 461                         wxString::Format("%u", inevt
.mask
); 
 462         const char* name 
= ""; 
 465         return wxString::Format("Event: wd=%d, mask=%s, cookie=%u, len=%u, " 
 466                                 "name=%s", inevt
.wd
, mask
, inevt
.cookie
, 
 470     static wxFileName 
GetEventPath(const wxFSWatchEntry
& watch
, 
 471                                    const inotify_event
& inevt
) 
 473         // only when dir is watched, we have non-empty e.name 
 474         wxFileName path 
= watch
.GetPath(); 
 475         if (path
.IsDir() && inevt
.len
) 
 477             path 
= wxFileName(path
.GetPath(), inevt
.name
); 
 482     static int Watcher2NativeFlags(int flags
) 
 484         // Start with the standard case of wanting all events 
 485         if (flags 
== wxFSW_EVENT_ALL
) 
 487             return IN_ALL_EVENTS
; 
 490         static const int flag_mapping
[][2] = { 
 491             { wxFSW_EVENT_ACCESS
, IN_ACCESS 
}, 
 492             { wxFSW_EVENT_MODIFY
, IN_MODIFY 
}, 
 493             { wxFSW_EVENT_RENAME
, IN_MOVE   
}, 
 494             { wxFSW_EVENT_CREATE
, IN_CREATE 
}, 
 495             { wxFSW_EVENT_DELETE
, IN_DELETE
|IN_DELETE_SELF
|IN_MOVE_SELF 
} 
 496             // wxFSW_EVENT_ERROR/WARNING make no sense here 
 499         int native_flags 
= 0; 
 500         for ( unsigned int i
=0; i 
< WXSIZEOF(flag_mapping
); ++i
) 
 502             if (flags 
& flag_mapping
[i
][0]) 
 503                 native_flags 
|= flag_mapping
[i
][1]; 
 509     static int Native2WatcherFlags(int flags
) 
 511         static const int flag_mapping
[][2] = { 
 512             { IN_ACCESS
,        wxFSW_EVENT_ACCESS 
}, // generated during read! 
 513             { IN_MODIFY
,        wxFSW_EVENT_MODIFY 
}, 
 515             { IN_CLOSE_WRITE
,   0 }, 
 516             { IN_CLOSE_NOWRITE
, 0 }, 
 518             { IN_MOVED_FROM
,    wxFSW_EVENT_RENAME 
}, 
 519             { IN_MOVED_TO
,      wxFSW_EVENT_RENAME 
}, 
 520             { IN_CREATE
,        wxFSW_EVENT_CREATE 
}, 
 521             { IN_DELETE
,        wxFSW_EVENT_DELETE 
}, 
 522             { IN_DELETE_SELF
,   wxFSW_EVENT_DELETE 
}, 
 523             { IN_MOVE_SELF
,     wxFSW_EVENT_DELETE 
}, 
 525             { IN_UNMOUNT
,       wxFSW_EVENT_ERROR  
}, 
 526             { IN_Q_OVERFLOW
,    wxFSW_EVENT_WARNING
}, 
 528             // ignored, because this is generated mainly by watcher::Remove() 
 533         for ( ; i 
< WXSIZEOF(flag_mapping
); ++i
) { 
 534             // in this mapping multiple flags at once don't happen 
 535             if (flags 
& flag_mapping
[i
][0]) 
 536                 return flag_mapping
[i
][1]; 
 540         wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flags
)); 
 545      * Returns error description for specified inotify mask 
 547     static const wxString 
GetErrorDescription(int flag
) 
 552             return _("File system containing watched object was unmounted"); 
 554             return _("Event queue overflowed"); 
 558         wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flag
)); 
 559         return wxEmptyString
; 
 562     wxFSWSourceHandler
* m_handler
;        // handler for inotify event source 
 563     wxFSWatchEntryDescriptors m_watchMap
; // inotify wd=>wxFSWatchEntry* map 
 564     wxArrayInt m_staleDescriptors
;        // stores recently-removed watches 
 565     wxInotifyCookies m_cookies
;           // map to track renames 
 566     wxEventLoopSource
* m_source
;          // our event loop source 
 568     // file descriptor created by inotify_init() 
 573 // ============================================================================ 
 574 // wxFSWSourceHandler implementation 
 575 // ============================================================================ 
 577 // once we get signaled to read, actuall event reading occurs 
 578 void wxFSWSourceHandler::OnReadWaiting() 
 580     wxLogTrace(wxTRACE_FSWATCHER
, "--- OnReadWaiting ---"); 
 581     m_service
->ReadEvents(); 
 584 void wxFSWSourceHandler::OnWriteWaiting() 
 586     wxFAIL_MSG("We never write to inotify descriptor."); 
 589 void wxFSWSourceHandler::OnExceptionWaiting() 
 591     wxFAIL_MSG("We never receive exceptions on inotify descriptor."); 
 595 // ============================================================================ 
 596 // wxInotifyFileSystemWatcher implementation 
 597 // ============================================================================ 
 599 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher() 
 600     : wxFileSystemWatcherBase() 
 605 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher(const wxFileName
& path
, 
 607     : wxFileSystemWatcherBase() 
 619 wxInotifyFileSystemWatcher::~wxInotifyFileSystemWatcher() 
 623 bool wxInotifyFileSystemWatcher::Init() 
 625     m_service 
= new wxFSWatcherImplUnix(this); 
 626     return m_service
->Init(); 
 629 void wxInotifyFileSystemWatcher::OnDirDeleted(const wxString
& path
) 
 633         wxFSWatchInfoMap::iterator it 
= m_watches
.find(path
); 
 634         wxCHECK_RET(it 
!= m_watches
.end(), 
 635                     wxString::Format("Path '%s' is not watched", path
)); 
 637         // path has been deleted, so we must forget it whatever its refcount 
 642 #endif // wxHAS_INOTIFY 
 644 #endif // wxUSE_FSWATCHER