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             m_staleDescriptors
.Remove(inevt
.wd
); 
 226             wxLogTrace(wxTRACE_FSWATCHER
, 
 227                        "Removed wd %i from the stale-wd cache", inevt
.wd
); 
 231         // get watch entry for this event 
 232         wxFSWatchEntryDescriptors::iterator it 
= m_watchMap
.find(inevt
.wd
); 
 233         if (it 
== m_watchMap
.end()) 
 235             // It's not in the map; check if was recently removed from it. 
 236             if (m_staleDescriptors
.Index(inevt
.wd
) != wxNOT_FOUND
) 
 238                 wxLogTrace(wxTRACE_FSWATCHER
, 
 239                            "Got an event for stale wd %i", inevt
.wd
); 
 243                 wxFAIL_MSG("Event for unknown watch descriptor."); 
 246             // In any case, don't process this event: it's either for an 
 247             // already removed entry, or for a completely unknown one. 
 251         wxFSWatchEntry
& watch 
= *(it
->second
); 
 252         int nativeFlags 
= inevt
.mask
; 
 253         int flags 
= Native2WatcherFlags(nativeFlags
); 
 255         // check out for error/warning condition 
 256         if (flags 
& wxFSW_EVENT_WARNING 
|| flags 
& wxFSW_EVENT_ERROR
) 
 258             wxString errMsg 
= GetErrorDescription(Watcher2NativeFlags(flags
)); 
 259             wxFileSystemWatcherEvent 
event(flags
, errMsg
); 
 262         // filter out ignored events and those not asked for. 
 263         // we never filter out warnings or exceptions 
 264         else if ((flags 
== 0) || !(flags 
& watch
.GetFlags())) 
 269         else if (nativeFlags 
& IN_MOVE
) 
 271             wxInotifyCookies::iterator it2 
= m_cookies
.find(inevt
.cookie
); 
 272             if ( it2 
== m_cookies
.end() ) 
 274                 int size 
= sizeof(inevt
) + inevt
.len
; 
 275                 inotify_event
* e 
= (inotify_event
*) operator new (size
); 
 276                 memcpy(e
, &inevt
, size
); 
 278                 wxInotifyCookies::value_type 
val(e
->cookie
, e
); 
 279                 m_cookies
.insert(val
); 
 283                 inotify_event
& oldinevt 
= *(it2
->second
); 
 285                 // Tell the owner, in case it's interested 
 286                 // If there's a filespec, assume he's not 
 287                 if ( watch
.GetFilespec().empty() ) 
 289                     wxFileSystemWatcherEvent 
event(flags
); 
 290                     if ( inevt
.mask 
& IN_MOVED_FROM 
) 
 292                         event
.SetPath(GetEventPath(watch
, inevt
)); 
 293                         event
.SetNewPath(GetEventPath(watch
, oldinevt
)); 
 297                         event
.SetPath(GetEventPath(watch
, oldinevt
)); 
 298                         event
.SetNewPath(GetEventPath(watch
, inevt
)); 
 303                 m_cookies
.erase(it2
); 
 307         // every other kind of event 
 310             wxFileName path 
= GetEventPath(watch
, inevt
); 
 311             // For files, check that it matches any filespec 
 312             if ( MatchesFilespec(path
, watch
.GetFilespec()) ) 
 314                 wxFileSystemWatcherEvent 
event(flags
, path
, path
); 
 320     void ProcessRenames() 
 322         wxInotifyCookies::iterator it 
= m_cookies
.begin(); 
 323         while ( it 
!= m_cookies
.end() ) 
 325             inotify_event
& inevt 
= *(it
->second
); 
 327             wxLogTrace(wxTRACE_FSWATCHER
, "Processing pending rename events"); 
 328             wxLogTrace(wxTRACE_FSWATCHER
, InotifyEventToString(inevt
)); 
 330             // get watch entry for this event 
 331             wxFSWatchEntryDescriptors::iterator wit 
= m_watchMap
.find(inevt
.wd
); 
 332             wxCHECK_RET(wit 
!= m_watchMap
.end(), 
 333                              "Watch descriptor not present in the watch map!"); 
 335             // Tell the owner, in case it's interested 
 336             // If there's a filespec, assume he's not 
 337             wxFSWatchEntry
& watch 
= *(wit
->second
); 
 338             if ( watch
.GetFilespec().empty() ) 
 340                 int flags 
= Native2WatcherFlags(inevt
.mask
); 
 341                 wxFileName path 
= GetEventPath(watch
, inevt
); 
 343                     wxFileSystemWatcherEvent 
event(flags
, path
, path
); 
 350             it 
= m_cookies
.begin(); 
 354     void SendEvent(wxFileSystemWatcherEvent
& evt
) 
 356         wxLogTrace(wxTRACE_FSWATCHER
, evt
.ToString()); 
 357         m_watcher
->GetOwner()->ProcessEvent(evt
); 
 360     int ReadEventsToBuf(char* buf
, int size
) 
 362         wxCHECK_MSG( IsOk(), false, 
 363                     "Inotify not initialized or invalid inotify descriptor" ); 
 365         memset(buf
, 0, size
); 
 366         ssize_t left 
= read(m_ifd
, buf
, size
); 
 369             wxLogSysError(_("Unable to read from inotify descriptor")); 
 374             wxLogWarning(_("EOF while reading from inotify descriptor")); 
 381     static wxString 
InotifyEventToString(const inotify_event
& inevt
) 
 383         wxString mask 
= (inevt
.mask 
& IN_ISDIR
) ? 
 384                         wxString::Format("IS_DIR | %u", inevt
.mask 
& ~IN_ISDIR
) : 
 385                         wxString::Format("%u", inevt
.mask
); 
 386         const char* name 
= ""; 
 389         return wxString::Format("Event: wd=%d, mask=%s, cookie=%u, len=%u, " 
 390                                 "name=%s", inevt
.wd
, mask
, inevt
.cookie
, 
 394     static wxFileName 
GetEventPath(const wxFSWatchEntry
& watch
, 
 395                                    const inotify_event
& inevt
) 
 397         // only when dir is watched, we have non-empty e.name 
 398         wxFileName path 
= watch
.GetPath(); 
 399         if (path
.IsDir() && inevt
.len
) 
 401             path 
= wxFileName(path
.GetPath(), inevt
.name
); 
 406     static int Watcher2NativeFlags(int WXUNUSED(flags
)) 
 408         // TODO: it would be nice to subscribe only to the events we really need 
 409         return IN_ALL_EVENTS
; 
 412     static int Native2WatcherFlags(int flags
) 
 414         static const int flag_mapping
[][2] = { 
 415             { IN_ACCESS
,        wxFSW_EVENT_ACCESS 
}, // generated during read! 
 416             { IN_MODIFY
,        wxFSW_EVENT_MODIFY 
}, 
 418             { IN_CLOSE_WRITE
,   0 }, 
 419             { IN_CLOSE_NOWRITE
, 0 }, 
 421             { IN_MOVED_FROM
,    wxFSW_EVENT_RENAME 
}, 
 422             { IN_MOVED_TO
,      wxFSW_EVENT_RENAME 
}, 
 423             { IN_CREATE
,        wxFSW_EVENT_CREATE 
}, 
 424             { IN_DELETE
,        wxFSW_EVENT_DELETE 
}, 
 425             { IN_DELETE_SELF
,   wxFSW_EVENT_DELETE 
}, 
 426             { IN_MOVE_SELF
,     wxFSW_EVENT_DELETE 
}, 
 428             { IN_UNMOUNT
,       wxFSW_EVENT_ERROR  
}, 
 429             { IN_Q_OVERFLOW
,    wxFSW_EVENT_WARNING
}, 
 431             // ignored, because this is genereted mainly by watcher::Remove() 
 436         for ( ; i 
< WXSIZEOF(flag_mapping
); ++i
) { 
 437             // in this mapping multiple flags at once don't happen 
 438             if (flags 
& flag_mapping
[i
][0]) 
 439                 return flag_mapping
[i
][1]; 
 443         wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flags
)); 
 448      * Returns error description for specified inotify mask 
 450     static const wxString 
GetErrorDescription(int flag
) 
 455             return _("File system containing watched object was unmounted"); 
 457             return _("Event queue overflowed"); 
 461         wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flag
)); 
 462         return wxEmptyString
; 
 465     wxFSWSourceHandler
* m_handler
;        // handler for inotify event source 
 466     wxFSWatchEntryDescriptors m_watchMap
; // inotify wd=>wxFSWatchEntry* map 
 467     wxArrayInt m_staleDescriptors
;        // stores recently-removed watches 
 468     wxInotifyCookies m_cookies
;           // map to track renames 
 469     wxEventLoopSource
* m_source
;          // our event loop source 
 471     // file descriptor created by inotify_init() 
 476 // ============================================================================ 
 477 // wxFSWSourceHandler implementation 
 478 // ============================================================================ 
 480 // once we get signaled to read, actuall event reading occurs 
 481 void wxFSWSourceHandler::OnReadWaiting() 
 483     wxLogTrace(wxTRACE_FSWATCHER
, "--- OnReadWaiting ---"); 
 484     m_service
->ReadEvents(); 
 487 void wxFSWSourceHandler::OnWriteWaiting() 
 489     wxFAIL_MSG("We never write to inotify descriptor."); 
 492 void wxFSWSourceHandler::OnExceptionWaiting() 
 494     wxFAIL_MSG("We never receive exceptions on inotify descriptor."); 
 498 // ============================================================================ 
 499 // wxInotifyFileSystemWatcher implementation 
 500 // ============================================================================ 
 502 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher() 
 503     : wxFileSystemWatcherBase() 
 508 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher(const wxFileName
& path
, 
 510     : wxFileSystemWatcherBase() 
 522 wxInotifyFileSystemWatcher::~wxInotifyFileSystemWatcher() 
 526 bool wxInotifyFileSystemWatcher::Init() 
 528     m_service 
= new wxFSWatcherImplUnix(this); 
 529     return m_service
->Init(); 
 532 #endif // wxHAS_INOTIFY 
 534 #endif // wxUSE_FSWATCHER