summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorZac Medico <zmedico@gentoo.org>2007-06-07 12:02:59 +0000
committerZac Medico <zmedico@gentoo.org>2007-06-07 12:02:59 +0000
commit42e9ac4b3e8f34b054e45bfb61c3d6e4153be8b1 (patch)
treeb765cd3cea7d7c2769824112c77775366aa568c6
parent7c0107e9a4b5fa4f4c0a09af57693148ed0d0ffb (diff)
downloadportage-42e9ac4b3e8f34b054e45bfb61c3d6e4153be8b1.tar.gz
portage-42e9ac4b3e8f34b054e45bfb61c3d6e4153be8b1.tar.bz2
portage-42e9ac4b3e8f34b054e45bfb61c3d6e4153be8b1.zip
When using a pty for logging, use setsid() to create a new session and make the pty into the controlling terminal of the new session. This makes interactive ebuild behave properly in interactive cases like check_license() where ${PAGER:-less} is invoked.
svn path=/main/trunk/; revision=6747
-rw-r--r--pym/portage/__init__.py51
-rw-r--r--pym/portage/output.py9
-rw-r--r--pym/portage/process.py23
3 files changed, 72 insertions, 11 deletions
diff --git a/pym/portage/__init__.py b/pym/portage/__init__.py
index c931ed32a..b6fbf2cad 100644
--- a/pym/portage/__init__.py
+++ b/pym/portage/__init__.py
@@ -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
diff --git a/pym/portage/output.py b/pym/portage/output.py
index f5e261c82..b4877b30d 100644
--- a/pym/portage/output.py
+++ b/pym/portage/output.py
@@ -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.
diff --git a/pym/portage/process.py b/pym/portage/process.py
index dfc106e6b..0cb175375 100644
--- a/pym/portage/process.py
+++ b/pym/portage/process.py
@@ -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)