summaryrefslogtreecommitdiffstats
path: root/pym/portage/util/_pty.py
diff options
context:
space:
mode:
authorZac Medico <zmedico@gentoo.org>2010-03-02 09:47:26 +0000
committerZac Medico <zmedico@gentoo.org>2010-03-02 09:47:26 +0000
commit4cc14fc1dc2634f243f86a6d2993e89779ae4117 (patch)
treeaa4cb062b5b24a2ae44ac038f37d205c71d237b3 /pym/portage/util/_pty.py
parent4e61f693d4f5c4c2c6dcd545be4cfb99df11dc58 (diff)
downloadportage-4cc14fc1dc2634f243f86a6d2993e89779ae4117.tar.gz
portage-4cc14fc1dc2634f243f86a6d2993e89779ae4117.tar.bz2
portage-4cc14fc1dc2634f243f86a6d2993e89779ae4117.zip
Move _pty module to portage.util._pty.
svn path=/main/trunk/; revision=15515
Diffstat (limited to 'pym/portage/util/_pty.py')
-rw-r--r--pym/portage/util/_pty.py205
1 files changed, 205 insertions, 0 deletions
diff --git a/pym/portage/util/_pty.py b/pym/portage/util/_pty.py
new file mode 100644
index 000000000..c48584f58
--- /dev/null
+++ b/pym/portage/util/_pty.py
@@ -0,0 +1,205 @@
+# 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.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():
+ """
+ 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
+
+_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)