]> git.saurik.com Git - wxWidgets.git/commitdiff
fixed IO redirection to work in wxExecute() in console applications (including the...
authorVadim Zeitlin <vadim@wxwidgets.org>
Sat, 22 Mar 2008 14:10:54 +0000 (14:10 +0000)
committerVadim Zeitlin <vadim@wxwidgets.org>
Sat, 22 Mar 2008 14:10:54 +0000 (14:10 +0000)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@52692 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

include/wx/unix/execute.h
src/unix/apptraits.cpp
src/unix/utilsunx.cpp

index 095435d5256dace9505c5fae949b59259f8504dd..f641e2e08fc0f8b56c0bb1fd3f6dc0326668835a 100644 (file)
@@ -40,9 +40,22 @@ struct wxExecuteData
 #if wxUSE_STREAMS
         bufOut =
         bufErr = NULL;
+
+        fdOut =
+        fdErr = wxPipe::INVALID_FD;
 #endif // wxUSE_STREAMS
     }
 
+    // get the FD corresponding to the read end of the process end detection
+    // pipe and close the write one
+    int GetEndProcReadFD()
+    {
+        const int fd = pipeEndProcDetect.Detach(wxPipe::Read);
+        pipeEndProcDetect.Close();
+        return fd;
+    }
+
+
     // wxExecute() flags
     int flags;
 
@@ -60,6 +73,10 @@ struct wxExecuteData
     // called bufOut and not bufIn
     wxStreamTempInputBuffer *bufOut,
                             *bufErr;
+
+    // the corresponding FDs, -1 if not redirected
+    int fdOut,
+        fdErr;
 #endif // wxUSE_STREAMS
 };
 
index b81b55db1717af5a03a9fd3d40e03f0383134678..97c34373078c9a3da50c2cfbfe744838d3337ae6 100644 (file)
@@ -58,11 +58,9 @@ int wxGUIAppTraits::WaitForChild(wxExecuteData& execData)
     endProcData.tag = AddProcessCallback
                       (
                          &endProcData,
-                         execData.pipeEndProcDetect.Detach(wxPipe::Read)
+                         execData.GetEndProcReadFD()
                       );
 
-    execData.pipeEndProcDetect.Close();
-
 
     // prepare to wait for the child termination: show to the user that we're
     // busy and refuse all input unless explicitly told otherwise
index 2db499dc9e22b609fa47f64f4a78282e8a9ebfdc..131aac407ce1621e50ab691ec256d80e740860f7 100644 (file)
@@ -41,6 +41,7 @@
 
 #include "wx/wfstream.h"
 
+#include "wx/private/selectdispatcher.h"
 #include "wx/private/fdiodispatcher.h"
 #include "wx/unix/execute.h"
 #include "wx/unix/private.h"
@@ -650,19 +651,17 @@ long wxExecute(wxChar **argv, int flags, wxProcess *process)
         // called bufOut and not bufIn
         wxStreamTempInputBuffer bufOut,
                                 bufErr;
-#endif // HAS_PIPE_INPUT_STREAM
 
         if ( process && process->IsRedirected() )
         {
-#if HAS_PIPE_INPUT_STREAM
             wxOutputStream *inStream =
                 new wxFileOutputStream(pipeIn.Detach(wxPipe::Write));
 
-            wxPipeInputStream *outStream =
-                new wxPipeInputStream(pipeOut.Detach(wxPipe::Read));
+            const int fdOut = pipeOut.Detach(wxPipe::Read);
+            wxPipeInputStream *outStream = new wxPipeInputStream(fdOut);
 
-            wxPipeInputStream *errStream =
-                new wxPipeInputStream(pipeErr.Detach(wxPipe::Read));
+            const int fdErr = pipeErr.Detach(wxPipe::Read);
+            wxPipeInputStream *errStream = new wxPipeInputStream(fdErr);
 
             process->SetPipeStreams(outStream, inStream, errStream);
 
@@ -671,8 +670,11 @@ long wxExecute(wxChar **argv, int flags, wxProcess *process)
 
             execData.bufOut = &bufOut;
             execData.bufErr = &bufErr;
-#endif // HAS_PIPE_INPUT_STREAM
+
+            execData.fdOut = fdOut;
+            execData.fdErr = fdErr;
         }
+#endif // HAS_PIPE_INPUT_STREAM
 
         if ( pipeIn.IsOk() )
         {
@@ -1325,82 +1327,169 @@ bool wxAppTraits::CheckForRedirectedIO(wxExecuteData& execData)
 #endif // HAS_PIPE_INPUT_STREAM/!HAS_PIPE_INPUT_STREAM
 }
 
-int wxAppTraits::WaitForChild(wxExecuteData& execData)
+class wxReadFDIOHandler : public wxFDIOHandler
 {
-    if ( execData.flags & wxEXEC_SYNC )
+public:
+    wxReadFDIOHandler(wxFDIODispatcher& disp, int fd) : m_fd(fd)
     {
-        // just block waiting for the child to exit
-        int status = 0;
+        if ( fd )
+            disp.RegisterFD(fd, this, wxFDIO_INPUT);
+    }
 
-        int result = waitpid(execData.pid, &status, 0);
-#ifdef __DARWIN__
-        /*  DE: waitpid manpage states that waitpid can fail with EINTR
-            if the call is interrupted by a caught signal.  I suppose
-            that means that this ought to be a while loop.
-
-            The odd thing is that it seems to fail EVERY time.  It fails
-            with a quickly exiting process (e.g. echo), and fails with a
-            slowly exiting process (e.g. sleep 2) but clearly after
-            having waited for the child to exit. Maybe it's a bug in
-            my particular version.
-
-            It works, however, from the CFSocket callback without this
-            trick but in that case it's used only after CFSocket calls
-            the callback and with the WNOHANG flag which would seem to
-            preclude it from being interrupted or at least make it much
-            less likely since it would not then be waiting.
-
-            If Darwin's man page is to be believed then this is definitely
-            necessary.  It's just weird that I've never seen it before
-            and apparently no one else has either or you'd think they'd
-            have reported it by now.  Perhaps blocking the GUI while
-            waiting for a child process to exit is simply not that common.
-         */
-        if ( result == -1 && errno == EINTR )
-        {
-            result = waitpid(execData.pid, &status, 0);
-        }
-#endif // __DARWIN__
+    int GetFD() const { return m_fd; }
 
-        if ( result == -1 )
-        {
-            wxLogLastError("waitpid");
-        }
-        else // child terminated
-        {
-            wxASSERT_MSG( result == execData.pid,
-                          "unexpected waitpid() return value" );
+    virtual void OnWriteWaiting() { wxFAIL_MSG("unreachable"); }
+    virtual void OnExceptionWaiting() { wxFAIL_MSG("unreachable"); }
 
-            if ( WIFEXITED(status) )
-            {
-                return WEXITSTATUS(status);
-            }
-            else // abnormal termination?
-            {
-                wxASSERT_MSG( WIFSIGNALED(status),
-                              "unexpected child wait status" );
-            }
-        }
+private:
+    const int m_fd;
+
+    DECLARE_NO_COPY_CLASS(wxReadFDIOHandler)
+};
+
+class wxEndHandler : public wxReadFDIOHandler
+{
+public:
+    wxEndHandler(wxFDIODispatcher& disp, int fd)
+        : wxReadFDIOHandler(disp, fd)
+    {
+        m_terminated = false;
+    }
+
+    bool Terminated() const { return m_terminated; }
 
-        wxLogSysError(_("Waiting for subprocess termination failed"));
+    virtual void OnReadWaiting() { m_terminated = true; }
 
-        return -1;
+private:
+    bool m_terminated;
+
+    DECLARE_NO_COPY_CLASS(wxEndHandler)
+};
+
+#if wxUSE_STREAMS
+
+class wxRedirectedIOHandler : public wxReadFDIOHandler
+{
+public:
+    wxRedirectedIOHandler(wxFDIODispatcher& disp,
+                          int fd,
+                          wxStreamTempInputBuffer *buf)
+        : wxReadFDIOHandler(disp, fd),
+          m_buf(buf)
+    {
     }
-    else // asynchronous execution
+
+    virtual void OnReadWaiting()
     {
+        m_buf->Update();
+    }
+
+private:
+    wxStreamTempInputBuffer * const m_buf;
+
+    DECLARE_NO_COPY_CLASS(wxRedirectedIOHandler)
+};
+
+#endif // wxUSE_STREAMS
+
+int wxAppTraits::WaitForChild(wxExecuteData& execData)
+{
+    if ( !(execData.flags & wxEXEC_SYNC) )
+    {
+        // asynchronous execution: just launch the process and return
         wxEndProcessData *endProcData = new wxEndProcessData;
         endProcData->process  = execData.process;
         endProcData->pid      = execData.pid;
         endProcData->tag = AddProcessCallback
                            (
                              endProcData,
-                             execData.pipeEndProcDetect.Detach(wxPipe::Read)
+                             execData.GetEndProcReadFD()
                            );
 
-        execData.pipeEndProcDetect.Close();
         return execData.pid;
+    }
+
+#if wxUSE_STREAMS
+    wxProcess * const process = execData.process;
+    if ( process && process->IsRedirected() )
+    {
+        // we can't simply block waiting for the child to terminate as we would
+        // dead lock if it writes more than the pipe buffer size (typically
+        // 4KB) bytes of output -- it would then block waiting for us to read
+        // the data while we'd block waiting for it to terminate
+        //
+        // so multiplex here waiting for any input from the child or closure of
+        // the pipe used to indicate its termination
+        wxSelectDispatcher disp;
+
+        wxEndHandler endHandler(disp, execData.GetEndProcReadFD());
+
+        wxRedirectedIOHandler outHandler(disp, execData.fdOut, execData.bufOut),
+                              errHandler(disp, execData.fdErr, execData.bufErr);
+
+        while ( !endHandler.Terminated() )
+        {
+            disp.Dispatch();
+        }
+    }
+    //else: no IO redirection, just block waiting for the child to exit
+#endif // wxUSE_STREAMS
+
+    int status = 0;
+
+    int result = waitpid(execData.pid, &status, 0);
+#ifdef __DARWIN__
+    /*  DE: waitpid manpage states that waitpid can fail with EINTR
+        if the call is interrupted by a caught signal.  I suppose
+        that means that this ought to be a while loop.
+
+        The odd thing is that it seems to fail EVERY time.  It fails
+        with a quickly exiting process (e.g. echo), and fails with a
+        slowly exiting process (e.g. sleep 2) but clearly after
+        having waited for the child to exit. Maybe it's a bug in
+        my particular version.
+
+        It works, however, from the CFSocket callback without this
+        trick but in that case it's used only after CFSocket calls
+        the callback and with the WNOHANG flag which would seem to
+        preclude it from being interrupted or at least make it much
+        less likely since it would not then be waiting.
+
+        If Darwin's man page is to be believed then this is definitely
+        necessary.  It's just weird that I've never seen it before
+        and apparently no one else has either or you'd think they'd
+        have reported it by now.  Perhaps blocking the GUI while
+        waiting for a child process to exit is simply not that common.
+     */
+    if ( result == -1 && errno == EINTR )
+    {
+        result = waitpid(execData.pid, &status, 0);
+    }
+#endif // __DARWIN__
+
+    if ( result == -1 )
+    {
+        wxLogLastError("waitpid");
+    }
+    else // child terminated
+    {
+        wxASSERT_MSG( result == execData.pid,
+                      "unexpected waitpid() return value" );
 
+        if ( WIFEXITED(status) )
+        {
+            return WEXITSTATUS(status);
+        }
+        else // abnormal termination?
+        {
+            wxASSERT_MSG( WIFSIGNALED(status),
+                          "unexpected child wait status" );
+        }
     }
+
+    wxLogSysError(_("Waiting for subprocess termination failed"));
+
+    return -1;
 }
 
 void wxHandleProcessTermination(wxEndProcessData *proc_data)