--- /dev/null
+/////////////////////////////////////////////////////////////////////////////
+// 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 <bartosz.bekier@gmail.com>
+// 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 <sys/inotify.h>
+#include <unistd.h>
+#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<wxUnixEventLoopSource*>(
+ 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<wxFSWatchEntryUnix> 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<wxFSWatchEntryUnix> 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