///////////////////////////////////////////////////////////////////////////// // Name: src/unix/fswatcher_inotify.cpp // Purpose: inotify-based wxFileSystemWatcher implementation // Author: Bartosz Bekier // Created: 2009-05-26 // RCS-ID: $Id$ // Copyright: (c) 2009 Bartosz Bekier // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #if wxUSE_FSWATCHER #include "wx/fswatcher.h" #ifdef wxHAS_INOTIFY #include #include #include "wx/private/fswatcher.h" // ============================================================================ // wxFSWatcherImpl implementation & helper wxFSWSourceHandler implementation // ============================================================================ // inotify watch descriptor => wxFSWatchEntry* map WX_DECLARE_HASH_MAP(int, wxFSWatchEntry*, wxIntegerHash, wxIntegerEqual, wxFSWatchEntryDescriptors); // inotify event cookie => inotify_event* map WX_DECLARE_HASH_MAP(int, inotify_event*, wxIntegerHash, wxIntegerEqual, wxInotifyCookies); /** * Helper class encapsulating inotify mechanism */ class wxFSWatcherImplUnix : public wxFSWatcherImpl { public: wxFSWatcherImplUnix(wxFileSystemWatcherBase* watcher) : wxFSWatcherImpl(watcher), m_source(NULL), m_ifd(-1) { m_handler = new wxFSWSourceHandler(this); } ~wxFSWatcherImplUnix() { // we close inotify only if initialized before if (IsOk()) { Close(); } delete m_handler; } bool Init() { wxCHECK_MSG( !IsOk(), false, "Inotify already initialized" ); wxEventLoopBase *loop = wxEventLoopBase::GetActive(); wxCHECK_MSG( loop, false, "File system watcher needs an event loop" ); m_ifd = inotify_init(); if ( m_ifd == -1 ) { wxLogSysError( _("Unable to create inotify instance") ); return false; } m_source = loop->AddSourceForFD ( m_ifd, m_handler, wxEVENT_SOURCE_INPUT | wxEVENT_SOURCE_EXCEPTION ); return m_source != NULL; } void Close() { wxCHECK_RET( IsOk(), "Inotify not initialized or invalid inotify descriptor" ); wxDELETE(m_source); if ( close(m_ifd) != 0 ) { wxLogSysError( _("Unable to close inotify instance") ); } } virtual bool DoAdd(wxSharedPtr watch) { wxCHECK_MSG( IsOk(), false, "Inotify not initialized or invalid inotify descriptor" ); int wd = DoAddInotify(watch.get()); if (wd == -1) { wxLogSysError( _("Unable to add inotify watch") ); return false; } wxFSWatchEntryDescriptors::value_type val(wd, watch.get()); if (!m_watchMap.insert(val).second) { wxFAIL_MSG( wxString::Format( "Path %s is already watched", watch->GetPath()) ); return false; } return true; } virtual bool DoRemove(wxSharedPtr watch) { wxCHECK_MSG( IsOk(), false, "Inotify not initialized or invalid inotify descriptor" ); int ret = DoRemoveInotify(watch.get()); if (ret == -1) { wxLogSysError( _("Unable to remove inotify watch") ); return false; } if (m_watchMap.erase(watch->GetWatchDescriptor()) != 1) { wxFAIL_MSG( wxString::Format("Path %s is not watched", watch->GetPath()) ); } watch->SetWatchDescriptor(-1); return true; } virtual bool RemoveAll() { wxFSWatchEntries::iterator it = m_watches.begin(); for ( ; it != m_watches.end(); ++it ) { (void) DoRemove(it->second); } m_watches.clear(); return true; } int ReadEvents() { wxCHECK_MSG( IsOk(), -1, "Inotify not initialized or invalid inotify descriptor" ); // read events // TODO differentiate depending on params char buf[128 * sizeof(inotify_event)]; int left = ReadEventsToBuf(buf, sizeof(buf)); if (left == -1) return -1; // left > 0, we have events char* memory = buf; int event_count = 0; while (left > 0) // OPT checking 'memory' would suffice { event_count++; inotify_event* e = (inotify_event*)memory; // process one inotify_event ProcessNativeEvent(*e); int offset = sizeof(inotify_event) + e->len; left -= offset; memory += offset; } // take care of unmatched renames ProcessRenames(); wxLogTrace(wxTRACE_FSWATCHER, "We had %d native events", event_count); return event_count; } bool IsOk() const { return m_source != NULL; } protected: int DoAddInotify(wxFSWatchEntry* watch) { int flags = Watcher2NativeFlags(watch->GetFlags()); int wd = inotify_add_watch(m_ifd, watch->GetPath().fn_str(), flags); // finally we can set watch descriptor watch->SetWatchDescriptor(wd); return wd; } int DoRemoveInotify(wxFSWatchEntry* watch) { return inotify_rm_watch(m_ifd, watch->GetWatchDescriptor()); } void ProcessNativeEvent(const inotify_event& inevt) { wxLogTrace(wxTRACE_FSWATCHER, InotifyEventToString(inevt)); // after removing inotify watch we get IN_IGNORED for it, but the watch // will be already removed from our list at that time if (inevt.mask & IN_IGNORED) { return; } // get watch entry for this event wxFSWatchEntryDescriptors::iterator it = m_watchMap.find(inevt.wd); wxCHECK_RET(it != m_watchMap.end(), "Watch descriptor not present in the watch map!"); wxFSWatchEntry& watch = *(it->second); int nativeFlags = inevt.mask; int flags = Native2WatcherFlags(nativeFlags); // check out for error/warning condition if (flags & wxFSW_EVENT_WARNING || flags & wxFSW_EVENT_ERROR) { wxString errMsg = GetErrorDescription(Watcher2NativeFlags(flags)); wxFileSystemWatcherEvent event(flags, errMsg); SendEvent(event); } // filter out ignored events and those not asked for. // we never filter out warnings or exceptions else if ((flags == 0) || !(flags & watch.GetFlags())) { return; } // renames else if (nativeFlags & IN_MOVE) { wxInotifyCookies::iterator it = m_cookies.find(inevt.cookie); if ( it == m_cookies.end() ) { int size = sizeof(inevt) + inevt.len; inotify_event* e = (inotify_event*) operator new (size); memcpy(e, &inevt, size); wxInotifyCookies::value_type val(e->cookie, e); m_cookies.insert(val); } else { inotify_event& oldinevt = *(it->second); wxFileSystemWatcherEvent event(flags); if ( inevt.mask & IN_MOVED_FROM ) { event.SetPath(GetEventPath(watch, inevt)); event.SetNewPath(GetEventPath(watch, oldinevt)); } else { event.SetPath(GetEventPath(watch, oldinevt)); event.SetNewPath(GetEventPath(watch, inevt)); } SendEvent(event); m_cookies.erase(it); delete &oldinevt; } } // every other kind of event else { wxFileName path = GetEventPath(watch, inevt); wxFileSystemWatcherEvent event(flags, path, path); SendEvent(event); } } void ProcessRenames() { wxInotifyCookies::iterator it = m_cookies.begin(); while ( it != m_cookies.end() ) { inotify_event& inevt = *(it->second); wxLogTrace(wxTRACE_FSWATCHER, "Processing pending rename events"); wxLogTrace(wxTRACE_FSWATCHER, InotifyEventToString(inevt)); // get watch entry for this event wxFSWatchEntryDescriptors::iterator wit = m_watchMap.find(inevt.wd); wxCHECK_RET(wit != m_watchMap.end(), "Watch descriptor not present in the watch map!"); wxFSWatchEntry& watch = *(wit->second); int flags = Native2WatcherFlags(inevt.mask); wxFileName path = GetEventPath(watch, inevt); wxFileSystemWatcherEvent event(flags, path, path); SendEvent(event); m_cookies.erase(it); delete &inevt; it = m_cookies.begin(); } } void SendEvent(wxFileSystemWatcherEvent& evt) { wxLogTrace(wxTRACE_FSWATCHER, evt.ToString()); m_watcher->GetOwner()->ProcessEvent(evt); } int ReadEventsToBuf(char* buf, int size) { wxCHECK_MSG( IsOk(), false, "Inotify not initialized or invalid inotify descriptor" ); memset(buf, 0, size); ssize_t left = read(m_ifd, buf, size); if (left == -1) { wxLogSysError(_("Unable to read from inotify descriptor")); return -1; } else if (left == 0) { wxLogWarning(_("EOF while reading from inotify descriptor")); return -1; } return left; } static wxString InotifyEventToString(const inotify_event& inevt) { wxString mask = (inevt.mask & IN_ISDIR) ? wxString::Format("IS_DIR | %u", inevt.mask & ~IN_ISDIR) : wxString::Format("%u", inevt.mask); return wxString::Format("Event: wd=%d, mask=%s, cookie=%u, len=%u, " "name=%s", inevt.wd, mask, inevt.cookie, inevt.len, inevt.name); } static wxFileName GetEventPath(const wxFSWatchEntry& watch, const inotify_event& inevt) { // only when dir is watched, we have non-empty e.name wxFileName path = watch.GetPath(); if (path.IsDir()) { path = wxFileName(path.GetPath(), inevt.name); } return path; } static int Watcher2NativeFlags(int WXUNUSED(flags)) { // TODO: it would be nice to subscribe only to the events we really need return IN_ALL_EVENTS; } static int Native2WatcherFlags(int flags) { static const int flag_mapping[][2] = { { IN_ACCESS, wxFSW_EVENT_ACCESS }, // generated during read! { IN_MODIFY, wxFSW_EVENT_MODIFY }, { IN_ATTRIB, 0 }, { IN_CLOSE_WRITE, 0 }, { IN_CLOSE_NOWRITE, 0 }, { IN_OPEN, 0 }, { IN_MOVED_FROM, wxFSW_EVENT_RENAME }, { IN_MOVED_TO, wxFSW_EVENT_RENAME }, { IN_CREATE, wxFSW_EVENT_CREATE }, { IN_DELETE, wxFSW_EVENT_DELETE }, { IN_DELETE_SELF, wxFSW_EVENT_DELETE }, { IN_MOVE_SELF, wxFSW_EVENT_DELETE }, { IN_UNMOUNT, wxFSW_EVENT_ERROR }, { IN_Q_OVERFLOW, wxFSW_EVENT_WARNING}, // ignored, because this is genereted mainly by watcher::Remove() { IN_IGNORED, 0 } }; unsigned int i=0; for ( ; i < WXSIZEOF(flag_mapping); ++i) { // in this mapping multiple flags at once don't happen if (flags & flag_mapping[i][0]) return flag_mapping[i][1]; } // never reached wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flags)); return -1; } /** * Returns error description for specified inotify mask */ static const wxString GetErrorDescription(int flag) { switch ( flag ) { case IN_UNMOUNT: return _("File system containing watched object was unmounted"); case IN_Q_OVERFLOW: return _("Event queue overflowed"); } // never reached wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flag)); return wxEmptyString; } wxFSWSourceHandler* m_handler; // handler for inotify event source wxFSWatchEntryDescriptors m_watchMap; // inotify wd=>wxFSWatchEntry* map wxInotifyCookies m_cookies; // map to track renames wxEventLoopSource* m_source; // our event loop source // file descriptor created by inotify_init() int m_ifd; }; // ============================================================================ // wxFSWSourceHandler implementation // ============================================================================ // once we get signaled to read, actuall event reading occurs void wxFSWSourceHandler::OnReadWaiting() { wxLogTrace(wxTRACE_FSWATCHER, "--- OnReadWaiting ---"); m_service->ReadEvents(); } void wxFSWSourceHandler::OnWriteWaiting() { wxFAIL_MSG("We never write to inotify descriptor."); } void wxFSWSourceHandler::OnExceptionWaiting() { wxFAIL_MSG("We never receive exceptions on inotify descriptor."); } // ============================================================================ // wxInotifyFileSystemWatcher implementation // ============================================================================ wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher() : wxFileSystemWatcherBase() { Init(); } wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher(const wxFileName& path, int events) : wxFileSystemWatcherBase() { if (!Init()) { if (m_service) delete m_service; return; } Add(path, events); } wxInotifyFileSystemWatcher::~wxInotifyFileSystemWatcher() { } bool wxInotifyFileSystemWatcher::Init() { m_service = new wxFSWatcherImplUnix(this); return m_service->Init(); } #endif // wxHAS_INOTIFY #endif // wxUSE_FSWATCHER