For bug #162404, use a pseudo-terminal device pair (instead of a normal pipe) for...
authorZac Medico <zmedico@gentoo.org>
Thu, 17 May 2007 09:13:00 +0000 (09:13 -0000)
committerZac Medico <zmedico@gentoo.org>
Thu, 17 May 2007 09:13:00 +0000 (09:13 -0000)
svn path=/main/trunk/; revision=6541

pym/portage/__init__.py

index cf27256aa8a780eab86a9bace051ddb9c4e6cb16..acb403b80986053cf97436aacfce6e951c27352b 100644 (file)
@@ -2258,10 +2258,12 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, **keyw
        # from shells and from binaries that belong to portage (the number of entry
        # points is minimized).  The "tee" binary is not among the allowed entry
        # points, so it is spawned outside of the sesandbox domain and reads from a
-       # pipe between two domains.
+       # pseudo-terminal that connects two domains.
        logfile = keywords.get("logfile")
        mypids = []
-       pw = None
+       slave_fd = None
+       output_pid = None
+       input_pid = None
        if logfile:
                del keywords["logfile"]
                fd_pipes = keywords.get("fd_pipes")
@@ -2269,12 +2271,29 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, **keyw
                        fd_pipes = {0:0, 1:1, 2:2}
                elif 1 not in fd_pipes or 2 not in fd_pipes:
                        raise ValueError(fd_pipes)
-               pr, pw = os.pipe()
-               mypids.extend(portage.process.spawn(('tee', '-i', '-a', logfile),
-                        returnpid=True, fd_pipes={0:pr, 1:fd_pipes[1], 2:fd_pipes[2]}))
-               os.close(pr)
-               fd_pipes[1] = pw
-               fd_pipes[2] = pw
+               from pty import openpty
+               master_fd, slave_fd = openpty()
+               # Disable the ECHO attribute so the terminal behaves properly
+               # if the subprocess needs to read input from stdin.
+               import termios
+               term_attr = termios.tcgetattr(master_fd)
+               term_attr[3] &= ~termios.ECHO
+               termios.tcsetattr(master_fd, termios.TCSAFLUSH, term_attr)
+               # tee will always exit with an IO error, so ignore it's stderr.
+               null_file = open('/dev/null', 'w')
+               mypids.extend(portage.process.spawn(['tee', '-i', '-a', logfile],
+                       returnpid=True, fd_pipes={0:master_fd, 1:fd_pipes[1],
+                       2:null_file.fileno()}))
+               output_pid = mypids[-1]
+               mypids.extend(portage.process.spawn(['cat'],
+                       returnpid=True, fd_pipes={0:fd_pipes[0], 1:master_fd,
+                       2:null_file.fileno()}))
+               input_pid = mypids[-1]
+               os.close(master_fd)
+               null_file.close()
+               fd_pipes[0] = slave_fd
+               fd_pipes[1] = slave_fd
+               fd_pipes[2] = slave_fd
                keywords["fd_pipes"] = fd_pipes
 
        features = mysettings.features
@@ -2307,29 +2326,32 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, **keyw
        try:
                mypids.extend(spawn_func(mystring, env=env, **keywords))
        finally:
-               if pw:
-                       os.close(pw)
+               if slave_fd:
+                       os.close(slave_fd)
                if sesandbox:
                        selinux.setexec(None)
 
        if returnpid:
                return mypids
 
-       while mypids:
-               pid = mypids.pop(0)
-               retval = os.waitpid(pid, 0)[1]
-               portage.process.spawned_pids.remove(pid)
-               if retval != os.EX_OK:
-                       for pid in mypids:
-                               if os.waitpid(pid, os.WNOHANG) == (0,0):
-                                       import signal
-                                       os.kill(pid, signal.SIGTERM)
-                                       os.waitpid(pid, 0)
-                               portage.process.spawned_pids.remove(pid)
-                       if retval & 0xff:
-                               return (retval & 0xff) << 8
-                       return retval >> 8
-       return os.EX_OK
+       if output_pid:
+               # tee will exit when the other end of the pseudo-terminal is closed.
+               os.waitpid(output_pid, 0)
+               portage.process.spawned_pids.remove(output_pid)
+       if input_pid:
+               # cat is blocking on stdin, so it must be killed.
+               import signal
+               os.kill(input_pid, signal.SIGTERM)
+               os.waitpid(input_pid, 0)
+               portage.process.spawned_pids.remove(input_pid)
+       pid = mypids[-1]
+       retval = os.waitpid(pid, 0)[1]
+       portage.process.spawned_pids.remove(pid)
+       if retval != os.EX_OK:
+               if retval & 0xff:
+                       return (retval & 0xff) << 8
+               return retval >> 8
+       return retval
 
 def fetch(myuris, mysettings, listonly=0, fetchonly=0, locks_in_subdir=".locks",use_locks=1, try_mirrors=1):
        "fetch files.  Will use digest file if available."