1 # Copyright 2010-2011 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
12 from portage import os, _unicode_decode, _unicode_encode
13 from portage.output import get_term_size, set_term_size
14 from portage.process import spawn_bash
15 from portage.util import writemsg
17 def _can_test_pty_eof():
19 The _test_pty_eof() function seems to hang on most
20 kernels other than Linux.
21 This was reported for the following kernels which used to work fine
22 without this EOF test: Darwin, AIX, FreeBSD. They seem to hang on
23 the slave_file.close() call. Note that Python's implementation of
24 openpty on Solaris already caused random hangs without this EOF test
25 and hence is globally disabled.
27 @returns: True if _test_pty_eof() won't hang, False otherwise.
29 return platform.system() in ("Linux",)
31 def _test_pty_eof(fdopen_buffered=False):
33 Returns True if this issues is fixed for the currently
34 running version of python: http://bugs.python.org/issue5380
35 Raises an EnvironmentError from openpty() if it fails.
37 NOTE: This issue is only problematic when array.fromfile()
38 is used, rather than os.read(). However, array.fromfile()
39 is preferred since it is approximately 10% faster.
41 New development: It appears that array.fromfile() is usable
42 with python3 as long as fdopen is called with a bufsize
48 test_string = 2 * "blah blah blah\n"
49 test_string = _unicode_decode(test_string,
50 encoding='utf_8', errors='strict')
52 # may raise EnvironmentError
53 master_fd, slave_fd = pty.openpty()
55 # Non-blocking mode is required for Darwin kernel.
56 fcntl.fcntl(master_fd, fcntl.F_SETFL,
57 fcntl.fcntl(master_fd, fcntl.F_GETFL) | os.O_NONBLOCK)
59 # Disable post-processing of output since otherwise weird
60 # things like \n -> \r\n transformations may occur.
61 mode = termios.tcgetattr(slave_fd)
62 mode[1] &= ~termios.OPOST
63 termios.tcsetattr(slave_fd, termios.TCSANOW, mode)
65 # Simulate a subprocess writing some data to the
66 # slave end of the pipe, and then exiting.
69 pids = spawn_bash(_unicode_encode("echo -n '%s'" % test_string,
70 encoding='utf_8', errors='strict'), env=os.environ,
71 fd_pipes={0:sys.stdin.fileno(), 1:slave_fd, 2:slave_fd},
73 if isinstance(pids, int):
76 raise EnvironmentError('spawn failed')
79 os.write(slave_fd, _unicode_encode(test_string,
80 encoding='utf_8', errors='strict'))
83 # If using a fork, we must wait for the child here,
84 # in order to avoid a race condition that would
85 # lead to inconsistent results.
90 master_file = os.fdopen(master_fd, 'rb')
92 master_file = os.fdopen(master_fd, 'rb', 0)
101 events = select.select(iwtd, owtd, ewtd)
106 buf = array.array('B')
108 buf.fromfile(master_file, 1024)
109 except (EOFError, IOError):
115 data.append(_unicode_decode(buf.tostring(),
116 encoding='utf_8', errors='strict'))
120 return test_string == ''.join(data)
122 # If _test_pty_eof() can't be used for runtime detection of
123 # http://bugs.python.org/issue5380, openpty can't safely be used
124 # unless we can guarantee that the current version of python has
125 # been fixed (affects all current versions of python3). When
126 # this issue is fixed in python3, we can add another sys.hexversion
127 # conditional to enable openpty support in the fixed versions.
128 if sys.hexversion >= 0x3000000 and not _can_test_pty_eof():
129 _disable_openpty = True
131 # Disable the use of openpty on Solaris as it seems Python's openpty
132 # implementation doesn't play nice on Solaris with Portage's
133 # behaviour causing hangs/deadlocks.
134 # Additional note for the future: on Interix, pipes do NOT work, so
135 # _disable_openpty on Interix must *never* be True
136 _disable_openpty = platform.system() in ("SunOS",)
139 if not _can_test_pty_eof():
140 # Skip _test_pty_eof() on systems where it hangs.
143 _fbsd_test_pty = platform.system() == 'FreeBSD'
145 def _create_pty_or_pipe(copy_term_size=None):
147 Try to create a pty and if then fails then create a normal
150 @param copy_term_size: If a tty file descriptor is given
151 then the term size will be copied to the pty.
152 @type copy_term_size: int
154 @returns: A tuple of (is_pty, master_fd, slave_fd) where
155 is_pty is True if a pty was successfully allocated, and
156 False if a normal pipe was allocated.
161 global _disable_openpty, _fbsd_test_pty, _tested_pty
162 if not (_tested_pty or _disable_openpty):
164 if not _test_pty_eof():
165 _disable_openpty = True
166 except EnvironmentError as e:
167 _disable_openpty = True
168 writemsg("openpty failed: '%s'\n" % str(e),
173 if _fbsd_test_pty and not _disable_openpty:
174 # Test for python openpty breakage after freebsd7 to freebsd8
175 # upgrade, which results in a 'Function not implemented' error
176 # and the process being killed.
181 pid, status = os.waitpid(pid, 0)
182 if (status & 0xff) == 140:
183 _disable_openpty = True
184 _fbsd_test_pty = False
187 master_fd, slave_fd = os.pipe()
190 master_fd, slave_fd = pty.openpty()
192 except EnvironmentError as e:
193 _disable_openpty = True
194 writemsg("openpty failed: '%s'\n" % str(e),
197 master_fd, slave_fd = os.pipe()
200 # Disable post-processing of output since otherwise weird
201 # things like \n -> \r\n transformations may occur.
202 mode = termios.tcgetattr(slave_fd)
203 mode[1] &= ~termios.OPOST
204 termios.tcsetattr(slave_fd, termios.TCSANOW, mode)
207 copy_term_size is not None and \
208 os.isatty(copy_term_size):
209 rows, columns = get_term_size()
210 set_term_size(rows, columns, slave_fd)
212 return (got_pty, master_fd, slave_fd)