popen4(), class Popen4:  popen4() support for Unix.

popen2(), popen3():  Reversed order of bufsize and mode parameters to
                     comply with what was here before (Python 1.5.2).

class Popen3:  Factored the __init__() into a more basic initializer and
               a helper method, to allow some re-use by the Popen4 class.
               Use os.dup2() instead of os.dup() to create the proper
               file descriptors in the child process.

This closes SourceForge bug #115330 and partially closes #115353.
diff --git a/Lib/popen2.py b/Lib/popen2.py
index 64c34d4..4580f58 100644
--- a/Lib/popen2.py
+++ b/Lib/popen2.py
@@ -8,7 +8,6 @@
 
 import os
 import sys
-import string
 
 MAXFD = 256     # Max number of file descriptors (os.getdtablesize()???)
 
@@ -22,14 +21,15 @@
     """Class representing a child process.  Normally instances are created
     by the factory functions popen2() and popen3()."""
 
+    sts = -1                    # Child not completed yet
+
     def __init__(self, cmd, capturestderr=0, bufsize=-1):
         """The parameter 'cmd' is the shell command to execute in a
         sub-process.  The 'capturestderr' flag, if true, specifies that
         the object should capture standard error output of the child process.
         The default is false.  If the 'bufsize' parameter is specified, it
         specifies the size of the I/O buffers to/from the child process."""
-        if type(cmd) == type(''):
-            cmd = ['/bin/sh', '-c', cmd]
+        _cleanup()
         p2cread, p2cwrite = os.pipe()
         c2pread, c2pwrite = os.pipe()
         if capturestderr:
@@ -37,25 +37,11 @@
         self.pid = os.fork()
         if self.pid == 0:
             # Child
-            os.close(0)
-            os.close(1)
-            if os.dup(p2cread) <> 0:
-                sys.stderr.write('popen2: bad read dup\n')
-            if os.dup(c2pwrite) <> 1:
-                sys.stderr.write('popen2: bad write dup\n')
+            os.dup2(p2cread, 0)
+            os.dup2(c2pwrite, 1)
             if capturestderr:
-                os.close(2)
-                if os.dup(errin) <> 2: pass
-            for i in range(3, MAXFD):
-                try:
-                    os.close(i)
-                except: pass
-            try:
-                os.execvp(cmd[0], cmd)
-            finally:
-                os._exit(1)
-            # Shouldn't come here, I guess
-            os._exit(1)
+                os.dup2(errin, 2)
+            self._run_child(cmd)
         os.close(p2cread)
         self.tochild = os.fdopen(p2cwrite, 'w', bufsize)
         os.close(c2pwrite)
@@ -65,9 +51,21 @@
             self.childerr = os.fdopen(errout, 'r', bufsize)
         else:
             self.childerr = None
-        self.sts = -1 # Child not completed yet
         _active.append(self)
 
+    def _run_child(self, cmd):
+        if type(cmd) == type(''):
+            cmd = ['/bin/sh', '-c', cmd]
+        for i in range(3, MAXFD):
+            try:
+                os.close(i)
+            except:
+                pass
+        try:
+            os.execvp(cmd[0], cmd)
+        finally:
+            os._exit(1)
+
     def poll(self):
         """Return the exit status of the child process if it has finished,
         or -1 if it hasn't finished yet."""
@@ -90,55 +88,72 @@
         return self.sts
 
 
+class Popen4(Popen3):
+    childerr = None
+
+    def __init__(self, cmd, bufsize=-1):
+        _cleanup()
+        p2cread, p2cwrite = os.pipe()
+        c2pread, c2pwrite = os.pipe()
+        self.pid = os.fork()
+        if self.pid == 0:
+            # Child
+            os.dup2(p2cread, 0)
+            os.dup2(c2pwrite, 1)
+            os.dup2(c2pwrite, 2)
+            self._run_child(cmd)
+        os.close(p2cread)
+        self.tochild = os.fdopen(p2cwrite, 'w', bufsize)
+        os.close(c2pwrite)
+        self.fromchild = os.fdopen(c2pread, 'r', bufsize)
+        _active.append(self)
+
+
 if sys.platform[:3] == "win":
-    def popen2(cmd, mode='t', bufsize=-1):
+    # Some things don't make sense on non-Unix platforms.
+    del Popen3, Popen4, _active, _cleanup
+
+    def popen2(cmd, bufsize=-1, mode='t'):
         """Execute the shell command 'cmd' in a sub-process.  If 'bufsize' is
         specified, it sets the buffer size for the I/O pipes.  The file objects
         (child_stdout, child_stdin) are returned."""
         w, r = os.popen2(cmd, mode, bufsize)
         return r, w
-else:
-    def popen2(cmd, mode='t', bufsize=-1):
-        """Execute the shell command 'cmd' in a sub-process.  If 'bufsize' is
-        specified, it sets the buffer size for the I/O pipes.  The file objects
-        (child_stdout, child_stdin) are returned."""
-        if type(mode) is type(0) and bufsize == -1:
-            bufsize = mode
-            mode = 't'
-        assert mode in ('t', 'b')
-        _cleanup()
-        inst = Popen3(cmd, 0, bufsize)
-        return inst.fromchild, inst.tochild
 
-if sys.platform[:3] == "win":
-    def popen3(cmd, mode='t', bufsize=-1):
+    def popen3(cmd, bufsize=-1, mode='t'):
         """Execute the shell command 'cmd' in a sub-process.  If 'bufsize' is
         specified, it sets the buffer size for the I/O pipes.  The file objects
         (child_stdout, child_stdin, child_stderr) are returned."""
         w, r, e = os.popen3(cmd, mode, bufsize)
         return r, w, e
-else:
-    def popen3(cmd, mode='t', bufsize=-1):
-        """Execute the shell command 'cmd' in a sub-process.  If 'bufsize' is
-        specified, it sets the buffer size for the I/O pipes.  The file objects
-        (child_stdout, child_stdin, child_stderr) are returned."""
-        if type(mode) is type(0) and bufsize == -1:
-            bufsize = mode
-            mode = 't'
-        assert mode in ('t', 'b')
-        _cleanup()
-        inst = Popen3(cmd, 1, bufsize)
-        return inst.fromchild, inst.tochild, inst.childerr
 
-if sys.platform[:3] == "win":
-    def popen4(cmd, mode='t', bufsize=-1):
+    def popen4(cmd, bufsize=-1, mode='t'):
         """Execute the shell command 'cmd' in a sub-process.  If 'bufsize' is
         specified, it sets the buffer size for the I/O pipes.  The file objects
         (child_stdout_stderr, child_stdin) are returned."""
         w, r = os.popen4(cmd, mode, bufsize)
         return r, w
 else:
-    pass # not yet on unix
+    def popen2(cmd, bufsize=-1, mode='t'):
+        """Execute the shell command 'cmd' in a sub-process.  If 'bufsize' is
+        specified, it sets the buffer size for the I/O pipes.  The file objects
+        (child_stdout, child_stdin) are returned."""
+        inst = Popen3(cmd, 0, bufsize)
+        return inst.fromchild, inst.tochild
+
+    def popen3(cmd, bufsize=-1, mode='t'):
+        """Execute the shell command 'cmd' in a sub-process.  If 'bufsize' is
+        specified, it sets the buffer size for the I/O pipes.  The file objects
+        (child_stdout, child_stdin, child_stderr) are returned."""
+        inst = Popen3(cmd, 1, bufsize)
+        return inst.fromchild, inst.tochild, inst.childerr
+
+    def popen4(cmd, bufsize=-1, mode='t'):
+        """Execute the shell command 'cmd' in a sub-process.  If 'bufsize' is
+        specified, it sets the buffer size for the I/O pipes.  The file objects
+        (child_stdout_stderr, child_stdin) are returned."""
+        inst = Popen4(cmd, bufsize)
+        return inst.fromchild, inst.tochild
 
 
 def _test():