]> git.saurik.com Git - wxWidgets.git/blobdiff - src/common/selectdispatcher.cpp
fixing overrelease and out-of-bounds write, fixes #13725
[wxWidgets.git] / src / common / selectdispatcher.cpp
index 5ee2f0242df4bcc4b8f495c78f7edb36a250f84f..fd0a26938cb2e328204842cee0cf00022b990afe 100644 (file)
@@ -1,11 +1,11 @@
 ///////////////////////////////////////////////////////////////////////////////
-// Name:        src/common/socketevtdispatch.cpp
+// Name:        src/common/selectdispatcher.cpp
 // Purpose:     implements dispatcher for select() call
-// Author:      Lukasz Michalski
+// Author:      Lukasz Michalski and Vadim Zeitlin
 // Created:     December 2006
 // RCS-ID:      $Id$
 // Copyright:   (c) 2006 Lukasz Michalski
-// License:     wxWindows licence
+// Licence:     wxWindows licence
 ///////////////////////////////////////////////////////////////////////////////
 
 // ============================================================================
 // for compilers that support precompilation, includes "wx.h".
 #include "wx/wxprec.h"
 
+#ifdef __BORLANDC__
+    #pragma hdrstop
+#endif
+
+#if wxUSE_SELECT_DISPATCHER
+
 #include "wx/private/selectdispatcher.h"
-#include "wx/module.h"
-#include "wx/timer.h"
 #include "wx/unix/private.h"
-#include "wx/log.h"
 
 #ifndef WX_PRECOMP
     #include "wx/hash.h"
+    #include "wx/log.h"
+    #include "wx/intl.h"
 #endif
 
-#include <sys/time.h>
-#include <unistd.h>
-
-#ifdef HAVE_SYS_SELECT_H
-#   include <sys/select.h>
-#endif
+#include <errno.h>
 
 #define wxSelectDispatcher_Trace wxT("selectdispatcher")
 
 // ============================================================================
 
 // ----------------------------------------------------------------------------
-// wxSelectDispatcher
+// wxSelectSets
 // ----------------------------------------------------------------------------
 
-wxSelectDispatcher* wxSelectDispatcher::ms_instance = NULL;
+int wxSelectSets::ms_flags[wxSelectSets::Max] =
+{
+    wxFDIO_INPUT,
+    wxFDIO_OUTPUT,
+    wxFDIO_EXCEPTION,
+};
 
-/* static */
-wxSelectDispatcher& wxSelectDispatcher::Get()
+const char *wxSelectSets::ms_names[wxSelectSets::Max] =
 {
-    if ( !ms_instance )
-        ms_instance = new wxSelectDispatcher;
-    return *ms_instance;
-}
+    "input",
+    "output",
+    "exceptional",
+};
 
-void
-wxSelectDispatcher::RegisterFD(int fd, wxFDIOHandler* handler, int flags)
+wxSelectSets::Callback wxSelectSets::ms_handlers[wxSelectSets::Max] =
 {
-    if ((flags & wxSelectInput) == wxSelectInput) 
-    {
-        wxFD_SET(fd, &m_readset);
-        wxLogTrace(wxSelectDispatcher_Trace,wxT("Registered fd %d for input events"),fd);
-    };
+    &wxFDIOHandler::OnReadWaiting,
+    &wxFDIOHandler::OnWriteWaiting,
+    &wxFDIOHandler::OnExceptionWaiting,
+};
 
-    if ((flags & wxSelectOutput) == wxSelectOutput)
+wxSelectSets::wxSelectSets()
+{
+    for ( int n = 0; n < Max; n++ )
     {
-        wxFD_SET(fd, &m_writeset);
-        wxLogTrace(wxSelectDispatcher_Trace,wxT("Registered fd %d for output events"),fd);
+        wxFD_ZERO(&m_fds[n]);
     }
+}
 
-    if ((flags & wxSelectException) == wxSelectException)
+bool wxSelectSets::HasFD(int fd) const
+{
+    for ( int n = 0; n < Max; n++ )
     {
-        wxFD_SET(fd, &m_exeptset);
-        wxLogTrace(wxSelectDispatcher_Trace,wxT("Registered fd %d for exception events"),fd);
-    };
+        if ( wxFD_ISSET(fd, (fd_set*) &m_fds[n]) )
+            return true;
+    }
 
-    m_handlers[fd] = handler;
-    if (fd > m_maxFD)
-      m_maxFD = fd;
+    return false;
 }
 
-wxFDIOHandler*
-wxSelectDispatcher::UnregisterFD(int fd, int flags)
+bool wxSelectSets::SetFD(int fd, int flags)
 {
-    // GSocket likes to unregister -1 descriptor
-    if (fd == -1)
-      return NULL;
+    wxCHECK_MSG( fd >= 0, false, wxT("invalid descriptor") );
 
-    if ((flags & wxSelectInput) == wxSelectInput)
+    for ( int n = 0; n < Max; n++ )
     {
-        wxLogTrace(wxSelectDispatcher_Trace,wxT("Unregistered fd %d from input events"),fd);
-        wxFD_CLR(fd, &m_readset);
+        if ( flags & ms_flags[n] )
+        {
+            wxFD_SET(fd, &m_fds[n]);
+        }
+        else if ( wxFD_ISSET(fd,  (fd_set*) &m_fds[n]) )
+        {
+            wxFD_CLR(fd, &m_fds[n]);
+        }
     }
 
-    if ((flags & wxSelectOutput) == wxSelectOutput)
-    {
-        wxLogTrace(wxSelectDispatcher_Trace,wxT("Unregistered fd %d from output events"),fd);
-        wxFD_CLR(fd, &m_writeset);
-    }
+    return true;
+}
 
-    if ((flags & wxSelectException) == wxSelectException)
-    {
-        wxLogTrace(wxSelectDispatcher_Trace,wxT("Unregistered fd %d from exeption events"),fd);
-        wxFD_CLR(fd, &m_exeptset);
-    };
+int wxSelectSets::Select(int nfds, struct timeval *tv)
+{
+    return select(nfds, &m_fds[Read], &m_fds[Write], &m_fds[Except], tv);
+}
 
-    wxFDIOHandler* ret = NULL;
-    wxFDIOHandlerMap::const_iterator it = m_handlers.find(fd);
-    if (it != m_handlers.end())
+bool wxSelectSets::Handle(int fd, wxFDIOHandler& handler) const
+{
+    for ( int n = 0; n < Max; n++ )
     {
-        ret = it->second;
-        if (!wxFD_ISSET(fd,&m_readset) && !wxFD_ISSET(fd,&m_writeset) && !wxFD_ISSET(fd,&m_exeptset)) 
+        if ( wxFD_ISSET(fd, (fd_set*) &m_fds[n]) )
         {
-            m_handlers.erase(it);
-            if ( m_handlers.empty() )
-                m_maxFD = 0;
-        };
-    };
-    return ret;
+            wxLogTrace(wxSelectDispatcher_Trace,
+                       wxT("Got %s event on fd %d"), ms_names[n], fd);
+            (handler.*ms_handlers[n])();
+            // callback can modify sets and destroy handler
+            // this forces that one event can be processed at one time
+            return true;
+        }
+    }
+
+    return false;
 }
 
-void wxSelectDispatcher::ProcessSets(fd_set* readset, fd_set* writeset, fd_set* exeptset, int max_fd)
+// ----------------------------------------------------------------------------
+// wxSelectDispatcher
+// ----------------------------------------------------------------------------
+
+bool wxSelectDispatcher::RegisterFD(int fd, wxFDIOHandler *handler, int flags)
 {
-    // it is safe to remove handler from onXXX methods,
-    // if you unregister descriptor first.
-    wxFDIOHandlerMap::const_iterator it = m_handlers.begin();
-    for ( int i = 0; i < max_fd; i++ )
-    {
-        wxFDIOHandler* handler = NULL;
-        if (wxFD_ISSET(i, readset)) 
-        {
-            wxLogTrace(wxSelectDispatcher_Trace,wxT("Got read event on fd %d"),i);
-            handler = FindHandler(i);
-            if (handler != NULL && wxFD_ISSET(i,&m_readset))
-                handler->OnReadWaiting(i);
-            else
-            {
-              wxLogError(wxT("Lost fd in read fdset: %d, removing"),i);
-              wxFD_CLR(i,&m_readset);
-            };
-        };
+    if ( !wxMappedFDIODispatcher::RegisterFD(fd, handler, flags) )
+        return false;
 
-        if (wxFD_ISSET(i, writeset)) 
-        {
-            wxLogTrace(wxSelectDispatcher_Trace,wxT("Got write event on fd %d"),i);
-            if (handler == NULL)
-                handler = FindHandler(i);
-            if (handler != NULL && wxFD_ISSET(i,&m_writeset))
-                handler->OnWriteWaiting(i);
-            else
-            {
-              wxLogError(wxT("Lost fd in write fdset: %d, removing"),i);
-              wxFD_CLR(i,&m_writeset);
-            };
-        };
+    if ( !m_sets.SetFD(fd, flags) )
+       return false;
 
-        if (wxFD_ISSET(i, exeptset))
-        {
-            wxLogTrace(wxSelectDispatcher_Trace,wxT("Got exception event on fd %d"),i);
-            if (handler == NULL)
-                handler = FindHandler(i);
-            if (handler != NULL && wxFD_ISSET(i,&m_writeset))
-                handler->OnExceptionWaiting(i);
-            else
-            {
-              wxLogError(wxT("Lost fd in exept fdset: %d, removing"),i);
-              wxFD_CLR(i,&m_exeptset);
-            };
-        };
-    };
+    if ( fd > m_maxFD )
+      m_maxFD = fd;
+
+    wxLogTrace(wxSelectDispatcher_Trace,
+                wxT("Registered fd %d: input:%d, output:%d, exceptional:%d"), fd, (flags & wxFDIO_INPUT) == wxFDIO_INPUT, (flags & wxFDIO_OUTPUT), (flags & wxFDIO_EXCEPTION) == wxFDIO_EXCEPTION);
+    return true;
 }
 
-wxFDIOHandler* wxSelectDispatcher::FindHandler(int fd)
+bool wxSelectDispatcher::ModifyFD(int fd, wxFDIOHandler *handler, int flags)
 {
-    wxFDIOHandlerMap::const_iterator it = m_handlers.find(fd);
-    if (it != m_handlers.end())
-        return it->second;
-    return NULL;
-};
+    if ( !wxMappedFDIODispatcher::ModifyFD(fd, handler, flags) )
+        return false;
+
+    wxASSERT_MSG( fd <= m_maxFD, wxT("logic error: registered fd > m_maxFD?") );
+
+    wxLogTrace(wxSelectDispatcher_Trace,
+                wxT("Modified fd %d: input:%d, output:%d, exceptional:%d"), fd, (flags & wxFDIO_INPUT) == wxFDIO_INPUT, (flags & wxFDIO_OUTPUT) == wxFDIO_OUTPUT, (flags & wxFDIO_EXCEPTION) == wxFDIO_EXCEPTION);
+    return m_sets.SetFD(fd, flags);
+}
 
-void wxSelectDispatcher::RunLoop(int timeout)
+bool wxSelectDispatcher::UnregisterFD(int fd)
 {
-    struct timeval tv, *ptv = NULL;
-    if ( timeout != wxSELECT_TIMEOUT_INFINITE )
-    {
-        ptv = &tv;
-        tv.tv_sec = 0;
-        tv.tv_usec = timeout*10;
-    };
+    m_sets.ClearFD(fd);
 
-    int ret;
-    do
+    if ( !wxMappedFDIODispatcher::UnregisterFD(fd) )
+        return false;
+
+    // remove the handler if we don't need it any more
+    if ( !m_sets.HasFD(fd) )
     {
-        fd_set readset = m_readset;
-        fd_set writeset = m_writeset;
-        fd_set exeptset = m_exeptset;
-        wxStopWatch sw;
-        if ( ptv && timeout )
-          sw.Start(ptv->tv_usec/10);
-        ret = select(m_maxFD+1, &readset, &writeset, &exeptset, ptv);
-        switch ( ret )
+        if ( fd == m_maxFD )
         {
-            // TODO: handle unix signals here
-            case -1:
-                if ( !timeout )
+            // need to find new max fd
+            m_maxFD = -1;
+            for ( wxFDIOHandlerMap::const_iterator it = m_handlers.begin();
+                  it != m_handlers.end();
+                  ++it )
+            {
+                if ( it->first > m_maxFD )
                 {
-                    // it doesn't make sense to remain here
-                    return;
+                    m_maxFD = it->first;
                 }
+            }
+        }
+    }
 
-                if ( ptv )
-                {
-                    ptv->tv_sec = 0;
-                    ptv->tv_usec = timeout - sw.Time()*10;
-                }
-                break;
+    wxLogTrace(wxSelectDispatcher_Trace,
+                wxT("Removed fd %d, current max: %d"), fd, m_maxFD);
+    return true;
+}
+
+int wxSelectDispatcher::ProcessSets(const wxSelectSets& sets)
+{
+    int numEvents = 0;
+    for ( int fd = 0; fd <= m_maxFD; fd++ )
+    {
+        if ( !sets.HasFD(fd) )
+            continue;
+
+        wxFDIOHandler * const handler = FindHandler(fd);
+        if ( !handler )
+        {
+            wxFAIL_MSG( wxT("NULL handler in wxSelectDispatcher?") );
+            continue;
+        }
+
+        if ( sets.Handle(fd, *handler) )
+            numEvents++;
+    }
+
+    return numEvents;
+}
+
+int wxSelectDispatcher::DoSelect(wxSelectSets& sets, int timeout) const
+{
+    struct timeval tv,
+                  *ptv;
+    if ( timeout != TIMEOUT_INFINITE )
+    {
+        ptv = &tv;
+        tv.tv_sec = timeout / 1000;
+        tv.tv_usec = (timeout % 1000)*1000;
+    }
+    else // no timeout
+    {
+        ptv = NULL;
+    }
 
-            // timeout
-            case 0:
-                break;
+    int ret = sets.Select(m_maxFD + 1, ptv);
 
-            default:
-                ProcessSets(&readset, &writeset, &exeptset, m_maxFD+1);
-        };
-    } while (ret != 0);
+    // TODO: we need to restart select() in this case but for now just return
+    //       as if timeout expired
+    if ( ret == -1 && errno == EINTR )
+        ret = 0;
+
+    return ret;
 }
 
-// ----------------------------------------------------------------------------
-// wxSelectDispatcherModule
-// ----------------------------------------------------------------------------
+bool wxSelectDispatcher::HasPending() const
+{
+    wxSelectSets sets(m_sets);
+    return DoSelect(sets, 0) > 0;
+}
 
-class wxSelectDispatcherModule: public wxModule
+int wxSelectDispatcher::Dispatch(int timeout)
 {
-public:
-    bool OnInit() { wxLog::AddTraceMask(wxSelectDispatcher_Trace); return true; }
-    void OnExit() { wxDELETE(wxSelectDispatcher::ms_instance); }
+    wxSelectSets sets(m_sets);
+    switch ( DoSelect(sets, timeout) )
+    {
+        case -1:
+            wxLogSysError(_("Failed to monitor I/O channels"));
+            return -1;
 
-private:
-    DECLARE_DYNAMIC_CLASS(wxSelectDispatcherModule)
-};
+        case 0:
+            // timeout expired without anything happening
+            return 0;
 
-IMPLEMENT_DYNAMIC_CLASS(wxSelectDispatcherModule, wxModule)
+        default:
+            return ProcessSets(sets);
+    }
+}
 
+#endif // wxUSE_SELECT_DISPATCHER