X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/6b8ef0b35d674bc262eb2005ac1321762c831d31..06a32e049c1bad9249079ad2e91659303424a774:/src/unix/fswatcher_inotify.cpp?ds=sidebyside diff --git a/src/unix/fswatcher_inotify.cpp b/src/unix/fswatcher_inotify.cpp index 229af98eee..39b3a85aaa 100644 --- a/src/unix/fswatcher_inotify.cpp +++ b/src/unix/fswatcher_inotify.cpp @@ -45,8 +45,8 @@ class wxFSWatcherImplUnix : public wxFSWatcherImpl public: wxFSWatcherImplUnix(wxFileSystemWatcherBase* watcher) : wxFSWatcherImpl(watcher), - m_loop(NULL), - m_source(NULL) + m_source(NULL), + m_ifd(-1) { m_handler = new wxFSWSourceHandler(this); } @@ -65,42 +65,38 @@ public: 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" ); + wxEventLoopBase *loop = wxEventLoopBase::GetActive(); + wxCHECK_MSG( loop, false, "File system watcher needs an event loop" ); - int fd = inotify_init(); - if (fd == -1) + m_ifd = inotify_init(); + if ( m_ifd == -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(); + m_source = loop->AddSourceForFD + ( + m_ifd, + m_handler, + wxEVENT_SOURCE_INPUT | wxEVENT_SOURCE_EXCEPTION + ); + + return m_source != NULL; } - bool Close() + void Close() { - wxCHECK_MSG( IsOk(), false, + wxCHECK_RET( IsOk(), "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(); + wxDELETE(m_source); - int ret = close(m_source->GetResource()); - if (ret == -1) + if ( close(m_ifd) != 0 ) { wxLogSysError( _("Unable to close inotify instance") ); } - m_source->Invalidate(); - - return ret != -1; } virtual bool DoAdd(wxSharedPtr watch) @@ -143,6 +139,9 @@ public: wxFAIL_MSG( wxString::Format("Path %s is not watched", watch->GetPath()) ); } + // Cache the wd in case any events arrive late + m_staleDescriptors.Add(watch->GetWatchDescriptor()); + watch->SetWatchDescriptor(-1); return true; } @@ -193,39 +192,16 @@ public: return event_count; } - bool IsOk() + bool IsOk() const { - return m_source && m_source->IsOk(); + return m_source != NULL; } 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); + int wd = inotify_add_watch(m_ifd, watch->GetPath().fn_str(), flags); // finally we can set watch descriptor watch->SetWatchDescriptor(wd); return wd; @@ -233,8 +209,7 @@ protected: int DoRemoveInotify(wxFSWatchEntry* watch) { - return inotify_rm_watch(m_source->GetResource(), - watch->GetWatchDescriptor()); + return inotify_rm_watch(m_ifd, watch->GetWatchDescriptor()); } void ProcessNativeEvent(const inotify_event& inevt) @@ -245,24 +220,98 @@ protected: // will be already removed from our list at that time if (inevt.mask & IN_IGNORED) { + // It is now safe to remove it from the stale descriptors too, we + // won't get any more events for it. + // However if we're here because a dir that we're still watching + // has just been deleted, its wd won't be on this list + const int pos = m_staleDescriptors.Index(inevt.wd); + if ( pos != wxNOT_FOUND ) + { + m_staleDescriptors.RemoveAt(static_cast(pos)); + wxLogTrace(wxTRACE_FSWATCHER, + "Removed wd %i from the stale-wd cache", inevt.wd); + } 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); + // wd will be -1 for IN_Q_OVERFLOW, which would trigger the wxFAIL_MSG + if (inevt.wd != -1) + { + if (it == m_watchMap.end()) + { + // It's not in the map; check if was recently removed from it. + if (m_staleDescriptors.Index(inevt.wd) != wxNOT_FOUND) + { + wxLogTrace(wxTRACE_FSWATCHER, + "Got an event for stale wd %i", inevt.wd); + } + else + { + // In theory we shouldn't reach here. In practice, some + // events, e.g. IN_MODIFY, arrive just after the IN_IGNORED + // so their wd has already been discarded. Warn about them. + wxFileSystemWatcherEvent + event + ( + wxFSW_EVENT_WARNING, + wxString::Format + ( + _("Unexpected event for \"%s\": no " + "matching watch descriptor."), + inevt.len ? inevt.name : "" + ) + ); + SendEvent(event); + + } + + // In any case, don't process this event: it's either for an + // already removed entry, or for an unknown one. + return; + } + } + 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)); + wxString errMsg = GetErrorDescription(nativeFlags); wxFileSystemWatcherEvent event(flags, errMsg); SendEvent(event); + return; + } + + if (inevt.wd == -1) + { + // Although this is not supposed to happen, we seem to be getting + // occasional IN_ACCESS/IN_MODIFY events without valid watch value. + wxFileSystemWatcherEvent + event + ( + wxFSW_EVENT_WARNING, + wxString::Format + ( + _("Invalid inotify event for \"%s\""), + inevt.len ? inevt.name : "" + ) + ); + SendEvent(event); + return; + } + + wxFSWatchEntry& watch = *(it->second); + + // Now IN_UNMOUNT. We must do so here, as it's not in the watch flags + if (nativeFlags & IN_UNMOUNT) + { + wxFileName path = GetEventPath(watch, inevt); + wxFileSystemWatcherEvent event(wxFSW_EVENT_UNMOUNT, path, path); + SendEvent(event); } // filter out ignored events and those not asked for. // we never filter out warnings or exceptions @@ -270,11 +319,92 @@ protected: { return; } + + // Creation + // We need do something here only if the original watch was recursive; + // we don't watch a child dir itself inside a non-tree watch. + // We watch only dirs explicitly, so we don't want file IN_CREATEs. + // Distinguish by whether nativeFlags contain IN_ISDIR + else if ((nativeFlags & IN_CREATE) && + (watch.GetType() == wxFSWPath_Tree) && (inevt.mask & IN_ISDIR)) + { + wxFileName fn = GetEventPath(watch, inevt); + // Though it's a dir, fn treats it as a file. So: + fn.AssignDir(fn.GetFullPath()); + + if (m_watcher->AddAny(fn, wxFSW_EVENT_ALL, + wxFSWPath_Tree, watch.GetFilespec())) + { + // Tell the owner, in case it's interested + // If there's a filespec, assume he's not + if (watch.GetFilespec().empty()) + { + wxFileSystemWatcherEvent event(flags, fn, fn); + SendEvent(event); + } + } + } + + // Deletion + // We watch only dirs explicitly, so we don't want file IN_DELETEs. + // We obviously can't check using DirExists() as the object has been + // deleted; and nativeFlags here doesn't contain IN_ISDIR, even for + // a dir. Fortunately IN_DELETE_SELF doesn't happen for files. We need + // to do something here only inside a tree watch, or if it's the parent + // dir that's deleted. Otherwise let the parent dir cope + else if ((nativeFlags & IN_DELETE_SELF) && + ((watch.GetType() == wxFSWPath_Dir) || + (watch.GetType() == wxFSWPath_Tree))) + { + // We must remove the deleted directory from the map, so that + // DoRemoveInotify() isn't called on it in the future. Don't assert + // if the wd isn't found: repeated IN_DELETE_SELFs can occur + wxFileName fn = GetEventPath(watch, inevt); + wxString path(fn.GetPathWithSep()); + + if (m_watchMap.erase(inevt.wd) == 1) + { + // Delete from wxFileSystemWatcher + wxDynamicCast(m_watcher, wxInotifyFileSystemWatcher)-> + OnDirDeleted(path); + + // Now remove from our local list of watched items + wxFSWatchEntries::iterator wit = + m_watches.find(path); + if (wit != m_watches.end()) + { + m_watches.erase(wit); + } + + // Cache the wd in case any events arrive late + m_staleDescriptors.Add(inevt.wd); + } + + // Tell the owner, in case it's interested + // If there's a filespec, assume he's not + if (watch.GetFilespec().empty()) + { + wxFileSystemWatcherEvent event(flags, fn, fn); + SendEvent(event); + } + } + // renames else if (nativeFlags & IN_MOVE) { - wxInotifyCookies::iterator it = m_cookies.find(inevt.cookie); - if ( it == m_cookies.end() ) + // IN_MOVE events are produced in the following circumstances: + // * A move within a dir (what the user sees as a rename) gives an + // IN_MOVED_FROM and IN_MOVED_TO pair, each with the same path. + // * A move within watched dir foo/ e.g. from foo/bar1/ to foo/bar2/ + // will also produce such a pair, but with different paths. + // * A move to baz/ will give only an IN_MOVED_FROM for foo/bar1; + // if baz/ is inside a different watch, that gets the IN_MOVED_TO. + + // The first event to arrive, usually the IN_MOVED_FROM, is + // cached to await a matching IN_MOVED_TO; should none arrive, the + // unpaired event will be processed later in ProcessRenames(). + wxInotifyCookies::iterator it2 = m_cookies.find(inevt.cookie); + if ( it2 == m_cookies.end() ) { int size = sizeof(inevt) + inevt.len; inotify_event* e = (inotify_event*) operator new (size); @@ -285,22 +415,47 @@ protected: } else { - inotify_event& oldinevt = *(it->second); + inotify_event& oldinevt = *(it2->second); - wxFileSystemWatcherEvent event(flags); - if ( inevt.mask & IN_MOVED_FROM ) + // Tell the owner, in case it's interested + // If there's a filespec, assume he's not + if ( watch.GetFilespec().empty() ) { - event.SetPath(GetEventPath(watch, inevt)); - event.SetNewPath(GetEventPath(watch, oldinevt)); + // The the only way to know the path for the first event, + // normally the IN_MOVED_FROM, is to retrieve the watch + // corresponding to oldinevt. This is needed for a move + // within a watch. + wxFSWatchEntry* oldwatch; + wxFSWatchEntryDescriptors::iterator oldwatch_it = + m_watchMap.find(oldinevt.wd); + if (oldwatch_it != m_watchMap.end()) + { + oldwatch = oldwatch_it->second; + } + else + { + wxLogTrace(wxTRACE_FSWATCHER, + "oldinevt's watch descriptor not in the watch map"); + // For want of a better alternative, use 'watch'. That + // will work fine for renames, though not for moves + oldwatch = &watch; + } + + wxFileSystemWatcherEvent event(flags); + if ( inevt.mask & IN_MOVED_FROM ) + { + event.SetPath(GetEventPath(watch, inevt)); + event.SetNewPath(GetEventPath(*oldwatch, oldinevt)); + } + else + { + event.SetPath(GetEventPath(*oldwatch, oldinevt)); + event.SetNewPath(GetEventPath(watch, inevt)); + } + SendEvent(event); } - else - { - event.SetPath(GetEventPath(watch, oldinevt)); - event.SetNewPath(GetEventPath(watch, inevt)); - } - SendEvent(event); - m_cookies.erase(it); + m_cookies.erase(it2); delete &oldinevt; } } @@ -308,13 +463,19 @@ protected: else { wxFileName path = GetEventPath(watch, inevt); - wxFileSystemWatcherEvent event(flags, path, path); - SendEvent(event); + // For files, check that it matches any filespec + if ( MatchesFilespec(path, watch.GetFilespec()) ) + { + wxFileSystemWatcherEvent event(flags, path, path); + SendEvent(event); + } } } void ProcessRenames() { + // After all of a batch of events has been processed, this deals with + // any still-unpaired IN_MOVED_FROM or IN_MOVED_TO events. wxInotifyCookies::iterator it = m_cookies.begin(); while ( it != m_cookies.end() ) { @@ -325,14 +486,26 @@ protected: // 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); + if (wit == m_watchMap.end()) + { + wxLogTrace(wxTRACE_FSWATCHER, + "Watch descriptor not present in the watch map!"); + } + else + { + // Tell the owner, in case it's interested + // If there's a filespec, assume he's not + wxFSWatchEntry& watch = *(wit->second); + if ( watch.GetFilespec().empty() ) + { + int flags = Native2WatcherFlags(inevt.mask); + wxFileName path = GetEventPath(watch, inevt); + { + wxFileSystemWatcherEvent event(flags, path, path); + SendEvent(event); + } + } + } m_cookies.erase(it); delete &inevt; @@ -352,7 +525,7 @@ protected: "Inotify not initialized or invalid inotify descriptor" ); memset(buf, 0, size); - ssize_t left = read(m_source->GetResource(), buf, size); + ssize_t left = read(m_ifd, buf, size); if (left == -1) { wxLogSysError(_("Unable to read from inotify descriptor")); @@ -372,9 +545,12 @@ protected: wxString mask = (inevt.mask & IN_ISDIR) ? wxString::Format("IS_DIR | %u", inevt.mask & ~IN_ISDIR) : wxString::Format("%u", inevt.mask); + const char* name = ""; + if (inevt.len) + name = inevt.name; return wxString::Format("Event: wd=%d, mask=%s, cookie=%u, len=%u, " "name=%s", inevt.wd, mask, inevt.cookie, - inevt.len, inevt.name); + inevt.len, name); } static wxFileName GetEventPath(const wxFSWatchEntry& watch, @@ -382,17 +558,40 @@ protected: { // only when dir is watched, we have non-empty e.name wxFileName path = watch.GetPath(); - if (path.IsDir()) + if (path.IsDir() && inevt.len) { path = wxFileName(path.GetPath(), inevt.name); } return path; } - static int Watcher2NativeFlags(int WXUNUSED(flags)) + static int Watcher2NativeFlags(int flags) { - // TODO: it would be nice to subscribe only to the events we really need - return IN_ALL_EVENTS; + // Start with the standard case of wanting all events + if (flags == wxFSW_EVENT_ALL) + { + return IN_ALL_EVENTS; + } + + static const int flag_mapping[][2] = { + { wxFSW_EVENT_ACCESS, IN_ACCESS }, + { wxFSW_EVENT_MODIFY, IN_MODIFY }, + { wxFSW_EVENT_ATTRIB, IN_ATTRIB }, + { wxFSW_EVENT_RENAME, IN_MOVE }, + { wxFSW_EVENT_CREATE, IN_CREATE }, + { wxFSW_EVENT_DELETE, IN_DELETE|IN_DELETE_SELF|IN_MOVE_SELF }, + { wxFSW_EVENT_UNMOUNT, IN_UNMOUNT } + // wxFSW_EVENT_ERROR/WARNING make no sense here + }; + + int native_flags = 0; + for ( unsigned int i=0; i < WXSIZEOF(flag_mapping); ++i) + { + if (flags & flag_mapping[i][0]) + native_flags |= flag_mapping[i][1]; + } + + return native_flags; } static int Native2WatcherFlags(int flags) @@ -400,7 +599,7 @@ protected: static const int flag_mapping[][2] = { { IN_ACCESS, wxFSW_EVENT_ACCESS }, // generated during read! { IN_MODIFY, wxFSW_EVENT_MODIFY }, - { IN_ATTRIB, 0 }, + { IN_ATTRIB, wxFSW_EVENT_ATTRIB }, { IN_CLOSE_WRITE, 0 }, { IN_CLOSE_NOWRITE, 0 }, { IN_OPEN, 0 }, @@ -411,10 +610,10 @@ protected: { IN_DELETE_SELF, wxFSW_EVENT_DELETE }, { IN_MOVE_SELF, wxFSW_EVENT_DELETE }, - { IN_UNMOUNT, wxFSW_EVENT_ERROR }, + { IN_UNMOUNT, wxFSW_EVENT_UNMOUNT}, { IN_Q_OVERFLOW, wxFSW_EVENT_WARNING}, - // ignored, because this is genereted mainly by watcher::Remove() + // ignored, because this is generated mainly by watcher::Remove() { IN_IGNORED, 0 } }; @@ -437,8 +636,6 @@ protected: { switch ( flag ) { - case IN_UNMOUNT: - return _("File system containing watched object was unmounted"); case IN_Q_OVERFLOW: return _("Event queue overflowed"); } @@ -450,9 +647,12 @@ protected: wxFSWSourceHandler* m_handler; // handler for inotify event source wxFSWatchEntryDescriptors m_watchMap; // inotify wd=>wxFSWatchEntry* map + wxArrayInt m_staleDescriptors; // stores recently-removed watches wxInotifyCookies m_cookies; // map to track renames - wxEventLoopBase* m_loop; - wxUnixEventLoopSource* m_source; // our event loop source + wxEventLoopSource* m_source; // our event loop source + + // file descriptor created by inotify_init() + int m_ifd; }; @@ -512,6 +712,19 @@ bool wxInotifyFileSystemWatcher::Init() return m_service->Init(); } +void wxInotifyFileSystemWatcher::OnDirDeleted(const wxString& path) +{ + if (!path.empty()) + { + wxFSWatchInfoMap::iterator it = m_watches.find(path); + wxCHECK_RET(it != m_watches.end(), + wxString::Format("Path '%s' is not watched", path)); + + // path has been deleted, so we must forget it whatever its refcount + m_watches.erase(it); + } +} + #endif // wxHAS_INOTIFY #endif // wxUSE_FSWATCHER