diff options
Diffstat (limited to 'pym/portage/package/ebuild/_pty.py')
-rw-r--r-- | pym/portage/package/ebuild/_pty.py | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/pym/portage/package/ebuild/_pty.py b/pym/portage/package/ebuild/_pty.py new file mode 100644 index 000000000..7cc1e4422 --- /dev/null +++ b/pym/portage/package/ebuild/_pty.py @@ -0,0 +1,192 @@ +# Copyright 2010 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Id$ + +import array +import fcntl +import platform +import pty +import select +import sys +import termios + +from portage import os, _unicode_decode, _unicode_encode +from portage.process import spawn_bash +from portage.util import writemsg + +def _can_test_pty_eof(): + """ + The _test_pty_eof() function seems to hang on most + kernels other than Linux. + This was reported for the following kernels which used to work fine + without this EOF test: Darwin, AIX, FreeBSD. They seem to hang on + the slave_file.close() call. Note that Python's implementation of + openpty on Solaris already caused random hangs without this EOF test + and hence is globally disabled. + @rtype: bool + @returns: True if _test_pty_eof() won't hang, False otherwise. + """ + return platform.system() in ("Linux",) + +def _test_pty_eof(): + """ + Returns True if this issues is fixed for the currently + running version of python: http://bugs.python.org/issue5380 + Raises an EnvironmentError from openpty() if it fails. + """ + + use_fork = False + + test_string = 2 * "blah blah blah\n" + test_string = _unicode_decode(test_string, + encoding='utf_8', errors='strict') + + # may raise EnvironmentError + master_fd, slave_fd = pty.openpty() + + # Non-blocking mode is required for Darwin kernel. + fcntl.fcntl(master_fd, fcntl.F_SETFL, + fcntl.fcntl(master_fd, fcntl.F_GETFL) | os.O_NONBLOCK) + + # Disable post-processing of output since otherwise weird + # things like \n -> \r\n transformations may occur. + mode = termios.tcgetattr(slave_fd) + mode[1] &= ~termios.OPOST + termios.tcsetattr(slave_fd, termios.TCSANOW, mode) + + # Simulate a subprocess writing some data to the + # slave end of the pipe, and then exiting. + pid = None + if use_fork: + pids = spawn_bash(_unicode_encode("echo -n '%s'" % test_string, + encoding='utf_8', errors='strict'), env=os.environ, + fd_pipes={0:sys.stdin.fileno(), 1:slave_fd, 2:slave_fd}, + returnpid=True) + if isinstance(pids, int): + os.close(master_fd) + os.close(slave_fd) + raise EnvironmentError('spawn failed') + pid = pids[0] + else: + os.write(slave_fd, _unicode_encode(test_string, + encoding='utf_8', errors='strict')) + os.close(slave_fd) + + # If using a fork, we must wait for the child here, + # in order to avoid a race condition that would + # lead to inconsistent results. + if pid is not None: + os.waitpid(pid, 0) + + master_file = os.fdopen(master_fd, 'rb') + eof = False + data = [] + iwtd = [master_file] + owtd = [] + ewtd = [] + + while not eof: + + events = select.select(iwtd, owtd, ewtd) + if not events[0]: + eof = True + break + + buf = array.array('B') + try: + buf.fromfile(master_file, 1024) + except EOFError: + eof = True + except IOError: + # This is where data loss occurs. + eof = True + + if not buf: + eof = True + else: + data.append(_unicode_decode(buf.tostring(), + encoding='utf_8', errors='strict')) + + master_file.close() + + return test_string == ''.join(data) + +# If _test_pty_eof() can't be used for runtime detection of +# http://bugs.python.org/issue5380, openpty can't safely be used +# unless we can guarantee that the current version of python has +# been fixed (affects all current versions of python3). When +# this issue is fixed in python3, we can add another sys.hexversion +# conditional to enable openpty support in the fixed versions. +if sys.hexversion >= 0x3000000 and not _can_test_pty_eof(): + _disable_openpty = True +else: + # Disable the use of openpty on Solaris as it seems Python's openpty + # implementation doesn't play nice on Solaris with Portage's + # behaviour causing hangs/deadlocks. + # Additional note for the future: on Interix, pipes do NOT work, so + # _disable_openpty on Interix must *never* be True + _disable_openpty = platform.system() in ("SunOS",) +_tested_pty = False + +if not _can_test_pty_eof(): + # Skip _test_pty_eof() on systems where it hangs. + _tested_pty = True + +def _create_pty_or_pipe(copy_term_size=None): + """ + Try to create a pty and if then fails then create a normal + pipe instead. + + @param copy_term_size: If a tty file descriptor is given + then the term size will be copied to the pty. + @type copy_term_size: int + @rtype: tuple + @returns: A tuple of (is_pty, master_fd, slave_fd) where + is_pty is True if a pty was successfully allocated, and + False if a normal pipe was allocated. + """ + + got_pty = False + + global _disable_openpty, _tested_pty + if not (_tested_pty or _disable_openpty): + try: + if not _test_pty_eof(): + _disable_openpty = True + except EnvironmentError as e: + _disable_openpty = True + writemsg("openpty failed: '%s'\n" % str(e), + noiselevel=-1) + del e + _tested_pty = True + + if _disable_openpty: + master_fd, slave_fd = os.pipe() + else: + from pty import openpty + try: + master_fd, slave_fd = openpty() + got_pty = True + except EnvironmentError as e: + _disable_openpty = True + writemsg("openpty failed: '%s'\n" % str(e), + noiselevel=-1) + del e + master_fd, slave_fd = os.pipe() + + if got_pty: + # Disable post-processing of output since otherwise weird + # things like \n -> \r\n transformations may occur. + import termios + mode = termios.tcgetattr(slave_fd) + mode[1] &= ~termios.OPOST + termios.tcsetattr(slave_fd, termios.TCSANOW, mode) + + if got_pty and \ + copy_term_size is not None and \ + os.isatty(copy_term_size): + from portage.output import get_term_size, set_term_size + rows, columns = get_term_size() + set_term_size(rows, columns, slave_fd) + + return (got_pty, master_fd, slave_fd) |