X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/40152925d51548122ffee555fce9648016b96a1e..6b8ef0b35d674bc262eb2005ac1321762c831d31:/src/unix/fswatcher_inotify.cpp diff --git a/src/unix/fswatcher_inotify.cpp b/src/unix/fswatcher_inotify.cpp new file mode 100644 index 0000000000..229af98eee --- /dev/null +++ b/src/unix/fswatcher_inotify.cpp @@ -0,0 +1,517 @@ +///////////////////////////////////////////////////////////////////////////// +// 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_loop(NULL), + m_source(NULL) + { + 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" ); + wxCHECK_MSG( m_loop == NULL, false, "Event loop != NULL"); + + m_loop = (wxEventLoopBase::GetActive()); + wxCHECK_MSG( m_loop, false, "File system watcher needs an active loop" ); + + int fd = inotify_init(); + if (fd == -1) + { + wxLogSysError( _("Unable to create inotify instance") ); + return false; + } + + int flags = wxEVENT_SOURCE_INPUT | wxEVENT_SOURCE_EXCEPTION; + m_source = static_cast( + m_loop->CreateSource(fd, m_handler, flags)); + return RegisterSource(); + } + + bool Close() + { + wxCHECK_MSG( IsOk(), false, + "Inotify not initialized or invalid inotify descriptor" ); + wxCHECK_MSG( m_loop, false, + "m_loop shouldn't be null if inotify is initialized" ); + + // ignore errors + (void) UnregisterSource(); + + int ret = close(m_source->GetResource()); + if (ret == -1) + { + wxLogSysError( _("Unable to close inotify instance") ); + } + m_source->Invalidate(); + + return ret != -1; + } + + 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() + { + return m_source && m_source->IsOk(); + } + +protected: + bool RegisterSource() + { + wxCHECK_MSG( IsOk(), false, + "Inotify not initialized or invalid inotify descriptor" ); + + bool ret = m_loop->AddSource(m_source); + return ret; + } + + bool UnregisterSource() + { + wxCHECK_MSG( IsOk(), false, + "Inotify not initialized or invalid inotify descriptor" ); + wxCHECK_MSG( m_loop, false, + "m_loop shouldn't be null if inotify is initialized" ); + + bool ret = m_loop->RemoveSource(m_source); + m_loop = NULL; + return ret; + } + + int DoAddInotify(wxFSWatchEntry* watch) + { + int flags = Watcher2NativeFlags(watch->GetFlags()); + int wd = inotify_add_watch(m_source->GetResource(), + 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_source->GetResource(), + 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_source->GetResource(), 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 + wxEventLoopBase* m_loop; + wxUnixEventLoopSource* m_source; // our event loop source +}; + + +// ============================================================================ +// 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