# Copyright 2010-2011 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 import array import fcntl import platform import pty import select import sys import termios from portage import os, _unicode_decode, _unicode_encode from portage.output import get_term_size, set_term_size 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(fdopen_buffered=False): """ 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. NOTE: This issue is only problematic when array.fromfile() is used, rather than os.read(). However, array.fromfile() is preferred since it is approximately 10% faster. New development: It appears that array.fromfile() is usable with python3 as long as fdopen is called with a bufsize argument of 0. """ 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) if fdopen_buffered: master_file = os.fdopen(master_fd, 'rb') else: master_file = os.fdopen(master_fd, 'rb', 0) 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, IOError): 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 _fbsd_test_pty = platform.system() == 'FreeBSD' 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, _fbsd_test_pty, _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 _fbsd_test_pty and not _disable_openpty: # Test for python openpty breakage after freebsd7 to freebsd8 # upgrade, which results in a 'Function not implemented' error # and the process being killed. pid = os.fork() if pid == 0: pty.openpty() os._exit(os.EX_OK) pid, status = os.waitpid(pid, 0) if (status & 0xff) == 140: _disable_openpty = True _fbsd_test_pty = False if _disable_openpty: master_fd, slave_fd = os.pipe() else: try: master_fd, slave_fd = pty.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. 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): rows, columns = get_term_size() set_term_size(rows, columns, slave_fd) return (got_pty, master_fd, slave_fd)