When using a pty for logging, use setsid() to create a new session and make the pty...
authorZac Medico <zmedico@gentoo.org>
Thu, 7 Jun 2007 12:02:59 +0000 (12:02 -0000)
committerZac Medico <zmedico@gentoo.org>
Thu, 7 Jun 2007 12:02:59 +0000 (12:02 -0000)
svn path=/main/trunk/; revision=6747

pym/portage/__init__.py
pym/portage/output.py
pym/portage/process.py

index c931ed32a7bf5bd411dfda05f5b41f521d8e759b..b6fbf2cade71aff272701162c80c0c60dbf2fb73 100644 (file)
@@ -2290,6 +2290,8 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, **keyw
        slave_fd = None
        output_pid = None
        input_pid = None
+       stdin_termios = None
+       stdin_fd = None
        if logfile:
                del keywords["logfile"]
                fd_pipes = keywords.get("fd_pipes")
@@ -2299,12 +2301,41 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, **keyw
                        raise ValueError(fd_pipes)
                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(slave_fd)
-               term_attr[3] &= ~termios.ECHO
-               termios.tcsetattr(slave_fd, termios.TCSAFLUSH, term_attr)
+               fd_pipes.setdefault(0, sys.stdin.fileno())
+               stdin_fd = fd_pipes[0]
+               if os.isatty(stdin_fd):
+                       # Copy the termios attributes from stdin_fd to the slave_fd and put
+                       # the stdin_fd into raw mode with ECHO disabled.  The stdin
+                       # termios attributes are reverted before returning, or via the
+                       # atexit hook in portage.process when killed by a signal.
+                       import termios, tty
+                       stdin_termios = termios.tcgetattr(stdin_fd)
+                       tty.setraw(stdin_fd)
+                       term_attr = termios.tcgetattr(stdin_fd)
+                       term_attr[3] &= ~termios.ECHO
+                       termios.tcsetattr(stdin_fd, termios.TCSAFLUSH, term_attr)
+                       termios.tcsetattr(slave_fd, termios.TCSAFLUSH, stdin_termios)
+                       from output import get_term_size, set_term_size
+                       rows, columns = get_term_size()
+                       set_term_size(rows, columns, slave_fd)
+                       pre_exec = keywords.get("pre_exec")
+                       def setup_ctty():
+                               os.setsid()
+                               # Make it into the "controlling terminal".
+                               import termios
+                               if hasattr(termios, "TIOCSCTTY"):
+                                       # BSD 4.3 approach
+                                       import fcntl
+                                       fcntl.ioctl(0, termios.TIOCSCTTY)
+                               else:
+                                       # SVR4 approach
+                                       fd = os.open(os.ttyname(0), os.O_RDWR)
+                                       for x in 0, 1, 2:
+                                               os.dup2(fd, x)
+                                       os.close(fd)
+                               if pre_exec:
+                                       pre_exec()
+                       keywords["pre_exec"] = setup_ctty
                # 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],
@@ -2374,8 +2405,12 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, **keyw
                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)
+       try:
+               retval = os.waitpid(pid, 0)[1]
+               portage.process.spawned_pids.remove(pid)
+       finally:
+               if stdin_termios:
+                       termios.tcsetattr(stdin_fd, termios.TCSAFLUSH, stdin_termios)
        if retval != os.EX_OK:
                if retval & 0xff:
                        return (retval & 0xff) << 8
index f5e261c825e09b227eca55ba2eb525a5d5c14d89..b4877b30d31fabca96372407f0ba95751e023462 100644 (file)
@@ -260,6 +260,15 @@ def get_term_size():
                                pass
        return -1, -1
 
+def set_term_size(lines, columns, fd):
+       """
+       Set the number of lines and columns for the tty that is connected to fd.
+       For portability, this simply calls `stty rows $lines columns $columns`.
+       """
+       from portage.process import spawn
+       cmd = ["stty", "rows", str(lines), "columns", str(columns)]
+       spawn(cmd, env=os.environ, fd_pipes={0:fd})
+
 class EOutput:
        """
        Performs fancy terminal formatting for status and informational messages.
index dfc106e6bdd4705afdc6afe2689d064458ef1087..0cb175375cbf21f36bbc1cf53fef86c7e79397ce 100644 (file)
@@ -111,9 +111,19 @@ def cleanup():
 
 atexit_register(cleanup)
 
+# Make sure the original terminal attributes are reverted at exit.
+if sys.stdin.isatty():
+       import termios
+       _stdin_termios = termios.tcgetattr(sys.stdin.fileno())
+       def _reset_stdin_termios(stdin_termios):
+               import termios
+               termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, stdin_termios)
+       atexit_register(_reset_stdin_termios, _stdin_termios)
+       del termios, _stdin_termios, _reset_stdin_termios
+
 def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
           uid=None, gid=None, groups=None, umask=None, logfile=None,
-          path_lookup=True):
+          path_lookup=True, pre_exec=None):
        """
        Spawns a given command.
        
@@ -140,6 +150,8 @@ def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
        @type logfile: String
        @param path_lookup: If the binary is not fully specified then look for it in PATH
        @type path_lookup: Boolean
+       @param pre_exec: A function to be called with no arguments just prior to the exec call.
+       @type pre_exec: callable
        
        logfile requires stdout and stderr to be assigned to this process (ie not pointed
           somewhere else.)
@@ -194,7 +206,7 @@ def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
        if not pid:
                try:
                        _exec(binary, mycommand, opt_name, fd_pipes,
-                             env, gid, groups, uid, umask)
+                             env, gid, groups, uid, umask, pre_exec)
                except Exception, e:
                        # We need to catch _any_ exception so that it doesn't
                        # propogate out of this function and cause exiting
@@ -251,7 +263,8 @@ def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
        # Everything succeeded
        return 0
 
-def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask):
+def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
+       pre_exec):
 
        """
        Execute a given binary with options
@@ -274,6 +287,8 @@ def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask):
        @type uid: Integer
        @param umask: an int representing a unix umask (see man chmod for umask details)
        @type umask: Integer
+       @param pre_exec: A function to be called with no arguments just prior to the exec call.
+       @type pre_exec: callable
        @rtype: None
        @returns: Never returns (calls os.execve)
        """
@@ -315,6 +330,8 @@ def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask):
                os.setuid(uid)
        if umask:
                os.umask(umask)
+       if pre_exec:
+               pre_exec()
 
        # And switch to the new process.
        os.execve(binary, myargs, env)