diff options
| author | Mikhail Borisov <[email protected]> | 2022-02-10 16:45:39 +0300 |
|---|---|---|
| committer | Daniil Cherednik <[email protected]> | 2022-02-10 16:45:39 +0300 |
| commit | a6a92afe03e02795227d2641b49819b687f088f8 (patch) | |
| tree | f6984a1d27d5a7ec88a6fdd6e20cd5b7693b6ece /contrib/python/ptyprocess | |
| parent | c6dc8b8bd530985bc4cce0137e9a5de32f1087cb (diff) | |
Restoring authorship annotation for Mikhail Borisov <[email protected]>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/ptyprocess')
| -rw-r--r-- | contrib/python/ptyprocess/ptyprocess/__init__.py | 4 | ||||
| -rw-r--r-- | contrib/python/ptyprocess/ptyprocess/_fork_pty.py | 150 | ||||
| -rw-r--r-- | contrib/python/ptyprocess/ptyprocess/ptyprocess.py | 1594 | ||||
| -rw-r--r-- | contrib/python/ptyprocess/ptyprocess/util.py | 132 | ||||
| -rw-r--r-- | contrib/python/ptyprocess/ya.make | 20 |
5 files changed, 950 insertions, 950 deletions
diff --git a/contrib/python/ptyprocess/ptyprocess/__init__.py b/contrib/python/ptyprocess/ptyprocess/__init__.py index 3a6268e8a6d..4dd4f630c5c 100644 --- a/contrib/python/ptyprocess/ptyprocess/__init__.py +++ b/contrib/python/ptyprocess/ptyprocess/__init__.py @@ -1,4 +1,4 @@ """Run a subprocess in a pseudo terminal""" -from .ptyprocess import PtyProcess, PtyProcessUnicode, PtyProcessError - +from .ptyprocess import PtyProcess, PtyProcessUnicode, PtyProcessError + __version__ = '0.7.0' diff --git a/contrib/python/ptyprocess/ptyprocess/_fork_pty.py b/contrib/python/ptyprocess/ptyprocess/_fork_pty.py index a8d05fe5a3d..151e097c402 100644 --- a/contrib/python/ptyprocess/ptyprocess/_fork_pty.py +++ b/contrib/python/ptyprocess/ptyprocess/_fork_pty.py @@ -1,78 +1,78 @@ -"""Substitute for the forkpty system call, to support Solaris. -""" -import os -import errno - -from pty import (STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, CHILD) +"""Substitute for the forkpty system call, to support Solaris. +""" +import os +import errno + +from pty import (STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, CHILD) from .util import PtyProcessError - -def fork_pty(): - '''This implements a substitute for the forkpty system call. This - should be more portable than the pty.fork() function. Specifically, - this should work on Solaris. - - Modified 10.06.05 by Geoff Marshall: Implemented __fork_pty() method to - resolve the issue with Python's pty.fork() not supporting Solaris, - particularly ssh. Based on patch to posixmodule.c authored by Noah - Spurrier:: - - http://mail.python.org/pipermail/python-dev/2003-May/035281.html - - ''' - - parent_fd, child_fd = os.openpty() - if parent_fd < 0 or child_fd < 0: - raise OSError("os.openpty() failed") - - pid = os.fork() - if pid == CHILD: - # Child. - os.close(parent_fd) - pty_make_controlling_tty(child_fd) - - os.dup2(child_fd, STDIN_FILENO) - os.dup2(child_fd, STDOUT_FILENO) - os.dup2(child_fd, STDERR_FILENO) - - else: - # Parent. - os.close(child_fd) - - return pid, parent_fd - -def pty_make_controlling_tty(tty_fd): - '''This makes the pseudo-terminal the controlling tty. This should be - more portable than the pty.fork() function. Specifically, this should - work on Solaris. ''' - - child_name = os.ttyname(tty_fd) - - # Disconnect from controlling tty, if any. Raises OSError of ENXIO - # if there was no controlling tty to begin with, such as when - # executed by a cron(1) job. - try: - fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) - os.close(fd) - except OSError as err: - if err.errno != errno.ENXIO: - raise - - os.setsid() - - # Verify we are disconnected from controlling tty by attempting to open - # it again. We expect that OSError of ENXIO should always be raised. - try: - fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) - os.close(fd) + +def fork_pty(): + '''This implements a substitute for the forkpty system call. This + should be more portable than the pty.fork() function. Specifically, + this should work on Solaris. + + Modified 10.06.05 by Geoff Marshall: Implemented __fork_pty() method to + resolve the issue with Python's pty.fork() not supporting Solaris, + particularly ssh. Based on patch to posixmodule.c authored by Noah + Spurrier:: + + http://mail.python.org/pipermail/python-dev/2003-May/035281.html + + ''' + + parent_fd, child_fd = os.openpty() + if parent_fd < 0 or child_fd < 0: + raise OSError("os.openpty() failed") + + pid = os.fork() + if pid == CHILD: + # Child. + os.close(parent_fd) + pty_make_controlling_tty(child_fd) + + os.dup2(child_fd, STDIN_FILENO) + os.dup2(child_fd, STDOUT_FILENO) + os.dup2(child_fd, STDERR_FILENO) + + else: + # Parent. + os.close(child_fd) + + return pid, parent_fd + +def pty_make_controlling_tty(tty_fd): + '''This makes the pseudo-terminal the controlling tty. This should be + more portable than the pty.fork() function. Specifically, this should + work on Solaris. ''' + + child_name = os.ttyname(tty_fd) + + # Disconnect from controlling tty, if any. Raises OSError of ENXIO + # if there was no controlling tty to begin with, such as when + # executed by a cron(1) job. + try: + fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) + os.close(fd) + except OSError as err: + if err.errno != errno.ENXIO: + raise + + os.setsid() + + # Verify we are disconnected from controlling tty by attempting to open + # it again. We expect that OSError of ENXIO should always be raised. + try: + fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) + os.close(fd) raise PtyProcessError("OSError of errno.ENXIO should be raised.") - except OSError as err: - if err.errno != errno.ENXIO: - raise - - # Verify we can open child pty. - fd = os.open(child_name, os.O_RDWR) - os.close(fd) - - # Verify we now have a controlling tty. - fd = os.open("/dev/tty", os.O_WRONLY) + except OSError as err: + if err.errno != errno.ENXIO: + raise + + # Verify we can open child pty. + fd = os.open(child_name, os.O_RDWR) + os.close(fd) + + # Verify we now have a controlling tty. + fd = os.open("/dev/tty", os.O_WRONLY) os.close(fd) diff --git a/contrib/python/ptyprocess/ptyprocess/ptyprocess.py b/contrib/python/ptyprocess/ptyprocess/ptyprocess.py index 78d19fdf8fd..ede9ec8e3b0 100644 --- a/contrib/python/ptyprocess/ptyprocess/ptyprocess.py +++ b/contrib/python/ptyprocess/ptyprocess/ptyprocess.py @@ -1,65 +1,65 @@ -import codecs -import errno -import fcntl -import io -import os -import pty -import resource -import signal -import struct -import sys -import termios -import time - -try: - import builtins # Python 3 -except ImportError: - import __builtin__ as builtins # Python 2 - -# Constants -from pty import (STDIN_FILENO, CHILD) - +import codecs +import errno +import fcntl +import io +import os +import pty +import resource +import signal +import struct +import sys +import termios +import time + +try: + import builtins # Python 3 +except ImportError: + import __builtin__ as builtins # Python 2 + +# Constants +from pty import (STDIN_FILENO, CHILD) + from .util import which, PtyProcessError - -_platform = sys.platform.lower() - -# Solaris uses internal __fork_pty(). All others use pty.fork(). -_is_solaris = ( - _platform.startswith('solaris') or - _platform.startswith('sunos')) - -if _is_solaris: - use_native_pty_fork = False - from . import _fork_pty -else: - use_native_pty_fork = True - -PY3 = sys.version_info[0] >= 3 - -if PY3: - def _byte(i): - return bytes([i]) -else: - def _byte(i): - return chr(i) - - class FileNotFoundError(OSError): pass - class TimeoutError(OSError): pass - -_EOF, _INTR = None, None - -def _make_eof_intr(): - """Set constants _EOF and _INTR. - - This avoids doing potentially costly operations on module load. - """ - global _EOF, _INTR - if (_EOF is not None) and (_INTR is not None): + +_platform = sys.platform.lower() + +# Solaris uses internal __fork_pty(). All others use pty.fork(). +_is_solaris = ( + _platform.startswith('solaris') or + _platform.startswith('sunos')) + +if _is_solaris: + use_native_pty_fork = False + from . import _fork_pty +else: + use_native_pty_fork = True + +PY3 = sys.version_info[0] >= 3 + +if PY3: + def _byte(i): + return bytes([i]) +else: + def _byte(i): + return chr(i) + + class FileNotFoundError(OSError): pass + class TimeoutError(OSError): pass + +_EOF, _INTR = None, None + +def _make_eof_intr(): + """Set constants _EOF and _INTR. + + This avoids doing potentially costly operations on module load. + """ + global _EOF, _INTR + if (_EOF is not None) and (_INTR is not None): return - - # inherit EOF and INTR definitions from controlling process. - try: - from termios import VEOF, VINTR + + # inherit EOF and INTR definitions from controlling process. + try: + from termios import VEOF, VINTR fd = None for name in 'stdin', 'stdout': stream = getattr(sys, '__%s__' % name, None) @@ -72,193 +72,193 @@ def _make_eof_intr(): if fd is None: # no fd, raise ValueError to fallback on CEOF, CINTR raise ValueError("No stream has a fileno") - intr = ord(termios.tcgetattr(fd)[6][VINTR]) - eof = ord(termios.tcgetattr(fd)[6][VEOF]) - except (ImportError, OSError, IOError, ValueError, termios.error): - # unless the controlling process is also not a terminal, - # such as cron(1), or when stdin and stdout are both closed. - # Fall-back to using CEOF and CINTR. There - try: - from termios import CEOF, CINTR - (intr, eof) = (CINTR, CEOF) - except ImportError: - # ^C, ^D - (intr, eof) = (3, 4) - - _INTR = _byte(intr) - _EOF = _byte(eof) - -# setecho and setwinsize are pulled out here because on some platforms, we need -# to do this from the child before we exec() - -def _setecho(fd, state): + intr = ord(termios.tcgetattr(fd)[6][VINTR]) + eof = ord(termios.tcgetattr(fd)[6][VEOF]) + except (ImportError, OSError, IOError, ValueError, termios.error): + # unless the controlling process is also not a terminal, + # such as cron(1), or when stdin and stdout are both closed. + # Fall-back to using CEOF and CINTR. There + try: + from termios import CEOF, CINTR + (intr, eof) = (CINTR, CEOF) + except ImportError: + # ^C, ^D + (intr, eof) = (3, 4) + + _INTR = _byte(intr) + _EOF = _byte(eof) + +# setecho and setwinsize are pulled out here because on some platforms, we need +# to do this from the child before we exec() + +def _setecho(fd, state): errmsg = 'setecho() may not be called on this platform (it may still be possible to enable/disable echo when spawning the child process)' - - try: - attr = termios.tcgetattr(fd) - except termios.error as err: - if err.args[0] == errno.EINVAL: - raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) - raise - - if state: - attr[3] = attr[3] | termios.ECHO - else: - attr[3] = attr[3] & ~termios.ECHO - - try: - # I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent and - # blocked on some platforms. TCSADRAIN would probably be ideal. - termios.tcsetattr(fd, termios.TCSANOW, attr) - except IOError as err: - if err.args[0] == errno.EINVAL: - raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) - raise - -def _setwinsize(fd, rows, cols): - # Some very old platforms have a bug that causes the value for - # termios.TIOCSWINSZ to be truncated. There was a hack here to work - # around this, but it caused problems with newer platforms so has been - # removed. For details see https://github.com/pexpect/pexpect/issues/39 - TIOCSWINSZ = getattr(termios, 'TIOCSWINSZ', -2146929561) - # Note, assume ws_xpixel and ws_ypixel are zero. - s = struct.pack('HHHH', rows, cols, 0, 0) - fcntl.ioctl(fd, TIOCSWINSZ, s) - -class PtyProcess(object): - '''This class represents a process running in a pseudoterminal. - - The main constructor is the :meth:`spawn` classmethod. - ''' - string_type = bytes - if PY3: - linesep = os.linesep.encode('ascii') - crlf = '\r\n'.encode('ascii') - - @staticmethod - def write_to_stdout(b): - try: - return sys.stdout.buffer.write(b) - except AttributeError: - # If stdout has been replaced, it may not have .buffer - return sys.stdout.write(b.decode('ascii', 'replace')) - else: - linesep = os.linesep - crlf = '\r\n' - write_to_stdout = sys.stdout.write - - encoding = None - - argv = None - env = None - launch_dir = None - - def __init__(self, pid, fd): - _make_eof_intr() # Ensure _EOF and _INTR are calculated - self.pid = pid - self.fd = fd + + try: + attr = termios.tcgetattr(fd) + except termios.error as err: + if err.args[0] == errno.EINVAL: + raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) + raise + + if state: + attr[3] = attr[3] | termios.ECHO + else: + attr[3] = attr[3] & ~termios.ECHO + + try: + # I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent and + # blocked on some platforms. TCSADRAIN would probably be ideal. + termios.tcsetattr(fd, termios.TCSANOW, attr) + except IOError as err: + if err.args[0] == errno.EINVAL: + raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) + raise + +def _setwinsize(fd, rows, cols): + # Some very old platforms have a bug that causes the value for + # termios.TIOCSWINSZ to be truncated. There was a hack here to work + # around this, but it caused problems with newer platforms so has been + # removed. For details see https://github.com/pexpect/pexpect/issues/39 + TIOCSWINSZ = getattr(termios, 'TIOCSWINSZ', -2146929561) + # Note, assume ws_xpixel and ws_ypixel are zero. + s = struct.pack('HHHH', rows, cols, 0, 0) + fcntl.ioctl(fd, TIOCSWINSZ, s) + +class PtyProcess(object): + '''This class represents a process running in a pseudoterminal. + + The main constructor is the :meth:`spawn` classmethod. + ''' + string_type = bytes + if PY3: + linesep = os.linesep.encode('ascii') + crlf = '\r\n'.encode('ascii') + + @staticmethod + def write_to_stdout(b): + try: + return sys.stdout.buffer.write(b) + except AttributeError: + # If stdout has been replaced, it may not have .buffer + return sys.stdout.write(b.decode('ascii', 'replace')) + else: + linesep = os.linesep + crlf = '\r\n' + write_to_stdout = sys.stdout.write + + encoding = None + + argv = None + env = None + launch_dir = None + + def __init__(self, pid, fd): + _make_eof_intr() # Ensure _EOF and _INTR are calculated + self.pid = pid + self.fd = fd readf = io.open(fd, 'rb', buffering=0) writef = io.open(fd, 'wb', buffering=0, closefd=False) self.fileobj = io.BufferedRWPair(readf, writef) - - self.terminated = False - self.closed = False - self.exitstatus = None - self.signalstatus = None - # status returned by os.waitpid - self.status = None - self.flag_eof = False - # Used by close() to give kernel time to update process status. - # Time in seconds. - self.delayafterclose = 0.1 - # Used by terminate() to give kernel time to update process status. - # Time in seconds. - self.delayafterterminate = 0.1 - - @classmethod - def spawn( - cls, argv, cwd=None, env=None, echo=True, preexec_fn=None, + + self.terminated = False + self.closed = False + self.exitstatus = None + self.signalstatus = None + # status returned by os.waitpid + self.status = None + self.flag_eof = False + # Used by close() to give kernel time to update process status. + # Time in seconds. + self.delayafterclose = 0.1 + # Used by terminate() to give kernel time to update process status. + # Time in seconds. + self.delayafterterminate = 0.1 + + @classmethod + def spawn( + cls, argv, cwd=None, env=None, echo=True, preexec_fn=None, dimensions=(24, 80), pass_fds=()): - '''Start the given command in a child process in a pseudo terminal. - - This does all the fork/exec type of stuff for a pty, and returns an - instance of PtyProcess. - - If preexec_fn is supplied, it will be called with no arguments in the - child process before exec-ing the specified command. - It may, for instance, set signal handlers to SIG_DFL or SIG_IGN. - - Dimensions of the psuedoterminal used for the subprocess can be - specified as a tuple (rows, cols), or the default (24, 80) will be used. + '''Start the given command in a child process in a pseudo terminal. + + This does all the fork/exec type of stuff for a pty, and returns an + instance of PtyProcess. + + If preexec_fn is supplied, it will be called with no arguments in the + child process before exec-ing the specified command. + It may, for instance, set signal handlers to SIG_DFL or SIG_IGN. + + Dimensions of the psuedoterminal used for the subprocess can be + specified as a tuple (rows, cols), or the default (24, 80) will be used. By default, all file descriptors except 0, 1 and 2 are closed. This behavior can be overridden with pass_fds, a list of file descriptors to keep open between the parent and the child. - ''' - # Note that it is difficult for this method to fail. - # You cannot detect if the child process cannot start. - # So the only way you can tell if the child process started - # or not is to try to read from the file descriptor. If you get - # EOF immediately then it means that the child is already dead. - # That may not necessarily be bad because you may have spawned a child - # that performs some task; creates no stdout output; and then dies. - - if not isinstance(argv, (list, tuple)): - raise TypeError("Expected a list or tuple for argv, got %r" % argv) - - # Shallow copy of argv so we can modify it - argv = argv[:] - command = argv[0] - - command_with_path = which(command) - if command_with_path is None: - raise FileNotFoundError('The command was not found or was not ' + - 'executable: %s.' % command) - command = command_with_path - argv[0] = command - - # [issue #119] To prevent the case where exec fails and the user is - # stuck interacting with a python child process instead of whatever - # was expected, we implement the solution from - # http://stackoverflow.com/a/3703179 to pass the exception to the - # parent process - - # [issue #119] 1. Before forking, open a pipe in the parent process. - exec_err_pipe_read, exec_err_pipe_write = os.pipe() - - if use_native_pty_fork: - pid, fd = pty.fork() - else: - # Use internal fork_pty, for Solaris - pid, fd = _fork_pty.fork_pty() - - # Some platforms must call setwinsize() and setecho() from the - # child process, and others from the master process. We do both, - # allowing IOError for either. - - if pid == CHILD: - # set window size - try: - _setwinsize(STDIN_FILENO, *dimensions) - except IOError as err: - if err.args[0] not in (errno.EINVAL, errno.ENOTTY): - raise - - # disable echo if spawn argument echo was unset - if not echo: - try: - _setecho(STDIN_FILENO, False) - except (IOError, termios.error) as err: - if err.args[0] not in (errno.EINVAL, errno.ENOTTY): - raise - - # [issue #119] 3. The child closes the reading end and sets the - # close-on-exec flag for the writing end. - os.close(exec_err_pipe_read) - fcntl.fcntl(exec_err_pipe_write, fcntl.F_SETFD, fcntl.FD_CLOEXEC) - - # Do not allow child to inherit open file descriptors from parent, - # with the exception of the exec_err_pipe_write of the pipe + ''' + # Note that it is difficult for this method to fail. + # You cannot detect if the child process cannot start. + # So the only way you can tell if the child process started + # or not is to try to read from the file descriptor. If you get + # EOF immediately then it means that the child is already dead. + # That may not necessarily be bad because you may have spawned a child + # that performs some task; creates no stdout output; and then dies. + + if not isinstance(argv, (list, tuple)): + raise TypeError("Expected a list or tuple for argv, got %r" % argv) + + # Shallow copy of argv so we can modify it + argv = argv[:] + command = argv[0] + + command_with_path = which(command) + if command_with_path is None: + raise FileNotFoundError('The command was not found or was not ' + + 'executable: %s.' % command) + command = command_with_path + argv[0] = command + + # [issue #119] To prevent the case where exec fails and the user is + # stuck interacting with a python child process instead of whatever + # was expected, we implement the solution from + # http://stackoverflow.com/a/3703179 to pass the exception to the + # parent process + + # [issue #119] 1. Before forking, open a pipe in the parent process. + exec_err_pipe_read, exec_err_pipe_write = os.pipe() + + if use_native_pty_fork: + pid, fd = pty.fork() + else: + # Use internal fork_pty, for Solaris + pid, fd = _fork_pty.fork_pty() + + # Some platforms must call setwinsize() and setecho() from the + # child process, and others from the master process. We do both, + # allowing IOError for either. + + if pid == CHILD: + # set window size + try: + _setwinsize(STDIN_FILENO, *dimensions) + except IOError as err: + if err.args[0] not in (errno.EINVAL, errno.ENOTTY): + raise + + # disable echo if spawn argument echo was unset + if not echo: + try: + _setecho(STDIN_FILENO, False) + except (IOError, termios.error) as err: + if err.args[0] not in (errno.EINVAL, errno.ENOTTY): + raise + + # [issue #119] 3. The child closes the reading end and sets the + # close-on-exec flag for the writing end. + os.close(exec_err_pipe_read) + fcntl.fcntl(exec_err_pipe_write, fcntl.F_SETFD, fcntl.FD_CLOEXEC) + + # Do not allow child to inherit open file descriptors from parent, + # with the exception of the exec_err_pipe_write of the pipe # and pass_fds. # Impose ceiling on max_fd: AIX bugfix for users with unlimited # nofiles where resource.RLIMIT_NOFILE is 2^63-1 and os.closerange() @@ -267,293 +267,293 @@ class PtyProcess(object): spass_fds = sorted(set(pass_fds) | {exec_err_pipe_write}) for pair in zip([2] + spass_fds, spass_fds + [max_fd]): os.closerange(pair[0]+1, pair[1]) - - if cwd is not None: - os.chdir(cwd) - - if preexec_fn is not None: - try: - preexec_fn() - except Exception as e: - ename = type(e).__name__ - tosend = '{}:0:{}'.format(ename, str(e)) - if PY3: - tosend = tosend.encode('utf-8') - - os.write(exec_err_pipe_write, tosend) - os.close(exec_err_pipe_write) - os._exit(1) - - try: - if env is None: - os.execv(command, argv) - else: - os.execvpe(command, argv, env) - except OSError as err: - # [issue #119] 5. If exec fails, the child writes the error - # code back to the parent using the pipe, then exits. - tosend = 'OSError:{}:{}'.format(err.errno, str(err)) - if PY3: - tosend = tosend.encode('utf-8') - os.write(exec_err_pipe_write, tosend) - os.close(exec_err_pipe_write) - os._exit(os.EX_OSERR) - - # Parent - inst = cls(pid, fd) - - # Set some informational attributes - inst.argv = argv - if env is not None: - inst.env = env - if cwd is not None: - inst.launch_dir = cwd - - # [issue #119] 2. After forking, the parent closes the writing end - # of the pipe and reads from the reading end. - os.close(exec_err_pipe_write) - exec_err_data = os.read(exec_err_pipe_read, 4096) - os.close(exec_err_pipe_read) - - # [issue #119] 6. The parent reads eof (a zero-length read) if the - # child successfully performed exec, since close-on-exec made - # successful exec close the writing end of the pipe. Or, if exec - # failed, the parent reads the error code and can proceed - # accordingly. Either way, the parent blocks until the child calls - # exec. - if len(exec_err_data) != 0: - try: - errclass, errno_s, errmsg = exec_err_data.split(b':', 2) - exctype = getattr(builtins, errclass.decode('ascii'), Exception) - - exception = exctype(errmsg.decode('utf-8', 'replace')) - if exctype is OSError: - exception.errno = int(errno_s) - except: - raise Exception('Subprocess failed, got bad error data: %r' - % exec_err_data) - else: - raise exception - - try: - inst.setwinsize(*dimensions) - except IOError as err: - if err.args[0] not in (errno.EINVAL, errno.ENOTTY, errno.ENXIO): - raise - - return inst - - def __repr__(self): - clsname = type(self).__name__ - if self.argv is not None: - args = [repr(self.argv)] - if self.env is not None: - args.append("env=%r" % self.env) - if self.launch_dir is not None: - args.append("cwd=%r" % self.launch_dir) - - return "{}.spawn({})".format(clsname, ", ".join(args)) - - else: - return "{}(pid={}, fd={})".format(clsname, self.pid, self.fd) - - @staticmethod - def _coerce_send_string(s): - if not isinstance(s, bytes): - return s.encode('utf-8') - return s - - @staticmethod - def _coerce_read_string(s): - return s - - def __del__(self): - '''This makes sure that no system resources are left open. Python only - garbage collects Python objects. OS file descriptors are not Python - objects, so they must be handled explicitly. If the child file - descriptor was opened outside of this class (passed to the constructor) - then this does not close it. ''' - - if not self.closed: - # It is possible for __del__ methods to execute during the - # teardown of the Python VM itself. Thus self.close() may - # trigger an exception because os.close may be None. - try: - self.close() + + if cwd is not None: + os.chdir(cwd) + + if preexec_fn is not None: + try: + preexec_fn() + except Exception as e: + ename = type(e).__name__ + tosend = '{}:0:{}'.format(ename, str(e)) + if PY3: + tosend = tosend.encode('utf-8') + + os.write(exec_err_pipe_write, tosend) + os.close(exec_err_pipe_write) + os._exit(1) + + try: + if env is None: + os.execv(command, argv) + else: + os.execvpe(command, argv, env) + except OSError as err: + # [issue #119] 5. If exec fails, the child writes the error + # code back to the parent using the pipe, then exits. + tosend = 'OSError:{}:{}'.format(err.errno, str(err)) + if PY3: + tosend = tosend.encode('utf-8') + os.write(exec_err_pipe_write, tosend) + os.close(exec_err_pipe_write) + os._exit(os.EX_OSERR) + + # Parent + inst = cls(pid, fd) + + # Set some informational attributes + inst.argv = argv + if env is not None: + inst.env = env + if cwd is not None: + inst.launch_dir = cwd + + # [issue #119] 2. After forking, the parent closes the writing end + # of the pipe and reads from the reading end. + os.close(exec_err_pipe_write) + exec_err_data = os.read(exec_err_pipe_read, 4096) + os.close(exec_err_pipe_read) + + # [issue #119] 6. The parent reads eof (a zero-length read) if the + # child successfully performed exec, since close-on-exec made + # successful exec close the writing end of the pipe. Or, if exec + # failed, the parent reads the error code and can proceed + # accordingly. Either way, the parent blocks until the child calls + # exec. + if len(exec_err_data) != 0: + try: + errclass, errno_s, errmsg = exec_err_data.split(b':', 2) + exctype = getattr(builtins, errclass.decode('ascii'), Exception) + + exception = exctype(errmsg.decode('utf-8', 'replace')) + if exctype is OSError: + exception.errno = int(errno_s) + except: + raise Exception('Subprocess failed, got bad error data: %r' + % exec_err_data) + else: + raise exception + + try: + inst.setwinsize(*dimensions) + except IOError as err: + if err.args[0] not in (errno.EINVAL, errno.ENOTTY, errno.ENXIO): + raise + + return inst + + def __repr__(self): + clsname = type(self).__name__ + if self.argv is not None: + args = [repr(self.argv)] + if self.env is not None: + args.append("env=%r" % self.env) + if self.launch_dir is not None: + args.append("cwd=%r" % self.launch_dir) + + return "{}.spawn({})".format(clsname, ", ".join(args)) + + else: + return "{}(pid={}, fd={})".format(clsname, self.pid, self.fd) + + @staticmethod + def _coerce_send_string(s): + if not isinstance(s, bytes): + return s.encode('utf-8') + return s + + @staticmethod + def _coerce_read_string(s): + return s + + def __del__(self): + '''This makes sure that no system resources are left open. Python only + garbage collects Python objects. OS file descriptors are not Python + objects, so they must be handled explicitly. If the child file + descriptor was opened outside of this class (passed to the constructor) + then this does not close it. ''' + + if not self.closed: + # It is possible for __del__ methods to execute during the + # teardown of the Python VM itself. Thus self.close() may + # trigger an exception because os.close may be None. + try: + self.close() # which exception, shouldn't we catch explicitly .. ? - except: - pass - - - def fileno(self): - '''This returns the file descriptor of the pty for the child. - ''' - return self.fd - - def close(self, force=True): - '''This closes the connection with the child application. Note that - calling close() more than once is valid. This emulates standard Python - behavior with files. Set force to True if you want to make sure that - the child is terminated (SIGKILL is sent if the child ignores SIGHUP - and SIGINT). ''' - if not self.closed: - self.flush() - self.fileobj.close() # Closes the file descriptor - # Give kernel time to update process status. - time.sleep(self.delayafterclose) - if self.isalive(): - if not self.terminate(force): - raise PtyProcessError('Could not terminate the child.') - self.fd = -1 - self.closed = True - #self.pid = None - - def flush(self): - '''This does nothing. It is here to support the interface for a - File-like object. ''' - - pass - - def isatty(self): - '''This returns True if the file descriptor is open and connected to a - tty(-like) device, else False. - - On SVR4-style platforms implementing streams, such as SunOS and HP-UX, - the child pty may not appear as a terminal device. This means - methods such as setecho(), setwinsize(), getwinsize() may raise an - IOError. ''' - - return os.isatty(self.fd) - - def waitnoecho(self, timeout=None): - '''This waits until the terminal ECHO flag is set False. This returns - True if the echo mode is off. This returns False if the ECHO flag was - not set False before the timeout. This can be used to detect when the - child is waiting for a password. Usually a child application will turn - off echo mode when it is waiting for the user to enter a password. For - example, instead of expecting the "password:" prompt you can wait for - the child to set ECHO off:: - - p = pexpect.spawn('ssh [email protected]') - p.waitnoecho() - p.sendline(mypassword) - - If timeout==None then this method to block until ECHO flag is False. - ''' - - if timeout is not None: - end_time = time.time() + timeout - while True: - if not self.getecho(): - return True - if timeout < 0 and timeout is not None: - return False - if timeout is not None: - timeout = end_time - time.time() - time.sleep(0.1) - - def getecho(self): - '''This returns the terminal echo mode. This returns True if echo is - on or False if echo is off. Child applications that are expecting you - to enter a password often set ECHO False. See waitnoecho(). - - Not supported on platforms where ``isatty()`` returns False. ''' - - try: - attr = termios.tcgetattr(self.fd) - except termios.error as err: - errmsg = 'getecho() may not be called on this platform' - if err.args[0] == errno.EINVAL: - raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) - raise - - self.echo = bool(attr[3] & termios.ECHO) - return self.echo - - def setecho(self, state): - '''This sets the terminal echo mode on or off. Note that anything the - child sent before the echo will be lost, so you should be sure that - your input buffer is empty before you call setecho(). For example, the - following will work as expected:: - - p = pexpect.spawn('cat') # Echo is on by default. - p.sendline('1234') # We expect see this twice from the child... - p.expect(['1234']) # ... once from the tty echo... - p.expect(['1234']) # ... and again from cat itself. - p.setecho(False) # Turn off tty echo - p.sendline('abcd') # We will set this only once (echoed by cat). - p.sendline('wxyz') # We will set this only once (echoed by cat) - p.expect(['abcd']) - p.expect(['wxyz']) - - The following WILL NOT WORK because the lines sent before the setecho - will be lost:: - - p = pexpect.spawn('cat') - p.sendline('1234') - p.setecho(False) # Turn off tty echo - p.sendline('abcd') # We will set this only once (echoed by cat). - p.sendline('wxyz') # We will set this only once (echoed by cat) - p.expect(['1234']) - p.expect(['1234']) - p.expect(['abcd']) - p.expect(['wxyz']) - - - Not supported on platforms where ``isatty()`` returns False. - ''' - _setecho(self.fd, state) - - self.echo = state - - def read(self, size=1024): - """Read and return at most ``size`` bytes from the pty. - - Can block if there is nothing to read. Raises :exc:`EOFError` if the - terminal was closed. - - Unlike Pexpect's ``read_nonblocking`` method, this doesn't try to deal - with the vagaries of EOF on platforms that do strange things, like IRIX - or older Solaris systems. It handles the errno=EIO pattern used on - Linux, and the empty-string return used on BSD platforms and (seemingly) - on recent Solaris. - """ - try: + except: + pass + + + def fileno(self): + '''This returns the file descriptor of the pty for the child. + ''' + return self.fd + + def close(self, force=True): + '''This closes the connection with the child application. Note that + calling close() more than once is valid. This emulates standard Python + behavior with files. Set force to True if you want to make sure that + the child is terminated (SIGKILL is sent if the child ignores SIGHUP + and SIGINT). ''' + if not self.closed: + self.flush() + self.fileobj.close() # Closes the file descriptor + # Give kernel time to update process status. + time.sleep(self.delayafterclose) + if self.isalive(): + if not self.terminate(force): + raise PtyProcessError('Could not terminate the child.') + self.fd = -1 + self.closed = True + #self.pid = None + + def flush(self): + '''This does nothing. It is here to support the interface for a + File-like object. ''' + + pass + + def isatty(self): + '''This returns True if the file descriptor is open and connected to a + tty(-like) device, else False. + + On SVR4-style platforms implementing streams, such as SunOS and HP-UX, + the child pty may not appear as a terminal device. This means + methods such as setecho(), setwinsize(), getwinsize() may raise an + IOError. ''' + + return os.isatty(self.fd) + + def waitnoecho(self, timeout=None): + '''This waits until the terminal ECHO flag is set False. This returns + True if the echo mode is off. This returns False if the ECHO flag was + not set False before the timeout. This can be used to detect when the + child is waiting for a password. Usually a child application will turn + off echo mode when it is waiting for the user to enter a password. For + example, instead of expecting the "password:" prompt you can wait for + the child to set ECHO off:: + + p = pexpect.spawn('ssh [email protected]') + p.waitnoecho() + p.sendline(mypassword) + + If timeout==None then this method to block until ECHO flag is False. + ''' + + if timeout is not None: + end_time = time.time() + timeout + while True: + if not self.getecho(): + return True + if timeout < 0 and timeout is not None: + return False + if timeout is not None: + timeout = end_time - time.time() + time.sleep(0.1) + + def getecho(self): + '''This returns the terminal echo mode. This returns True if echo is + on or False if echo is off. Child applications that are expecting you + to enter a password often set ECHO False. See waitnoecho(). + + Not supported on platforms where ``isatty()`` returns False. ''' + + try: + attr = termios.tcgetattr(self.fd) + except termios.error as err: + errmsg = 'getecho() may not be called on this platform' + if err.args[0] == errno.EINVAL: + raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) + raise + + self.echo = bool(attr[3] & termios.ECHO) + return self.echo + + def setecho(self, state): + '''This sets the terminal echo mode on or off. Note that anything the + child sent before the echo will be lost, so you should be sure that + your input buffer is empty before you call setecho(). For example, the + following will work as expected:: + + p = pexpect.spawn('cat') # Echo is on by default. + p.sendline('1234') # We expect see this twice from the child... + p.expect(['1234']) # ... once from the tty echo... + p.expect(['1234']) # ... and again from cat itself. + p.setecho(False) # Turn off tty echo + p.sendline('abcd') # We will set this only once (echoed by cat). + p.sendline('wxyz') # We will set this only once (echoed by cat) + p.expect(['abcd']) + p.expect(['wxyz']) + + The following WILL NOT WORK because the lines sent before the setecho + will be lost:: + + p = pexpect.spawn('cat') + p.sendline('1234') + p.setecho(False) # Turn off tty echo + p.sendline('abcd') # We will set this only once (echoed by cat). + p.sendline('wxyz') # We will set this only once (echoed by cat) + p.expect(['1234']) + p.expect(['1234']) + p.expect(['abcd']) + p.expect(['wxyz']) + + + Not supported on platforms where ``isatty()`` returns False. + ''' + _setecho(self.fd, state) + + self.echo = state + + def read(self, size=1024): + """Read and return at most ``size`` bytes from the pty. + + Can block if there is nothing to read. Raises :exc:`EOFError` if the + terminal was closed. + + Unlike Pexpect's ``read_nonblocking`` method, this doesn't try to deal + with the vagaries of EOF on platforms that do strange things, like IRIX + or older Solaris systems. It handles the errno=EIO pattern used on + Linux, and the empty-string return used on BSD platforms and (seemingly) + on recent Solaris. + """ + try: s = self.fileobj.read1(size) - except (OSError, IOError) as err: - if err.args[0] == errno.EIO: - # Linux-style EOF - self.flag_eof = True - raise EOFError('End Of File (EOF). Exception style platform.') - raise - if s == b'': - # BSD-style EOF (also appears to work on recent Solaris (OpenIndiana)) - self.flag_eof = True - raise EOFError('End Of File (EOF). Empty string style platform.') - - return s - - def readline(self): - """Read one line from the pseudoterminal, and return it as unicode. - - Can block if there is nothing to read. Raises :exc:`EOFError` if the - terminal was closed. - """ - try: - s = self.fileobj.readline() - except (OSError, IOError) as err: - if err.args[0] == errno.EIO: - # Linux-style EOF - self.flag_eof = True - raise EOFError('End Of File (EOF). Exception style platform.') - raise - if s == b'': - # BSD-style EOF (also appears to work on recent Solaris (OpenIndiana)) - self.flag_eof = True - raise EOFError('End Of File (EOF). Empty string style platform.') - - return s - + except (OSError, IOError) as err: + if err.args[0] == errno.EIO: + # Linux-style EOF + self.flag_eof = True + raise EOFError('End Of File (EOF). Exception style platform.') + raise + if s == b'': + # BSD-style EOF (also appears to work on recent Solaris (OpenIndiana)) + self.flag_eof = True + raise EOFError('End Of File (EOF). Empty string style platform.') + + return s + + def readline(self): + """Read one line from the pseudoterminal, and return it as unicode. + + Can block if there is nothing to read. Raises :exc:`EOFError` if the + terminal was closed. + """ + try: + s = self.fileobj.readline() + except (OSError, IOError) as err: + if err.args[0] == errno.EIO: + # Linux-style EOF + self.flag_eof = True + raise EOFError('End Of File (EOF). Exception style platform.') + raise + if s == b'': + # BSD-style EOF (also appears to work on recent Solaris (OpenIndiana)) + self.flag_eof = True + raise EOFError('End Of File (EOF). Empty string style platform.') + + return s + def _writeb(self, b, flush=True): n = self.fileobj.write(b) if flush: @@ -561,282 +561,282 @@ class PtyProcess(object): return n def write(self, s, flush=True): - """Write bytes to the pseudoterminal. - - Returns the number of bytes written. - """ + """Write bytes to the pseudoterminal. + + Returns the number of bytes written. + """ return self._writeb(s, flush=flush) - - def sendcontrol(self, char): - '''Helper method that wraps send() with mnemonic access for sending control - character to the child (such as Ctrl-C or Ctrl-D). For example, to send - Ctrl-G (ASCII 7, bell, '\a'):: - - child.sendcontrol('g') - - See also, sendintr() and sendeof(). - ''' - char = char.lower() - a = ord(char) - if 97 <= a <= 122: - a = a - ord('a') + 1 - byte = _byte(a) + + def sendcontrol(self, char): + '''Helper method that wraps send() with mnemonic access for sending control + character to the child (such as Ctrl-C or Ctrl-D). For example, to send + Ctrl-G (ASCII 7, bell, '\a'):: + + child.sendcontrol('g') + + See also, sendintr() and sendeof(). + ''' + char = char.lower() + a = ord(char) + if 97 <= a <= 122: + a = a - ord('a') + 1 + byte = _byte(a) return self._writeb(byte), byte - d = {'@': 0, '`': 0, - '[': 27, '{': 27, - '\\': 28, '|': 28, - ']': 29, '}': 29, - '^': 30, '~': 30, - '_': 31, - '?': 127} - if char not in d: - return 0, b'' - - byte = _byte(d[char]) + d = {'@': 0, '`': 0, + '[': 27, '{': 27, + '\\': 28, '|': 28, + ']': 29, '}': 29, + '^': 30, '~': 30, + '_': 31, + '?': 127} + if char not in d: + return 0, b'' + + byte = _byte(d[char]) return self._writeb(byte), byte - - def sendeof(self): - '''This sends an EOF to the child. This sends a character which causes - the pending parent output buffer to be sent to the waiting child - program without waiting for end-of-line. If it is the first character - of the line, the read() in the user program returns 0, which signifies - end-of-file. This means to work as expected a sendeof() has to be - called at the beginning of a line. This method does not send a newline. - It is the responsibility of the caller to ensure the eof is sent at the - beginning of a line. ''' - + + def sendeof(self): + '''This sends an EOF to the child. This sends a character which causes + the pending parent output buffer to be sent to the waiting child + program without waiting for end-of-line. If it is the first character + of the line, the read() in the user program returns 0, which signifies + end-of-file. This means to work as expected a sendeof() has to be + called at the beginning of a line. This method does not send a newline. + It is the responsibility of the caller to ensure the eof is sent at the + beginning of a line. ''' + return self._writeb(_EOF), _EOF - - def sendintr(self): - '''This sends a SIGINT to the child. It does not require - the SIGINT to be the first character on a line. ''' - + + def sendintr(self): + '''This sends a SIGINT to the child. It does not require + the SIGINT to be the first character on a line. ''' + return self._writeb(_INTR), _INTR - - def eof(self): - '''This returns True if the EOF exception was ever raised. - ''' - - return self.flag_eof - - def terminate(self, force=False): - '''This forces a child process to terminate. It starts nicely with - SIGHUP and SIGINT. If "force" is True then moves onto SIGKILL. This - returns True if the child was terminated. This returns False if the - child could not be terminated. ''' - - if not self.isalive(): - return True - try: - self.kill(signal.SIGHUP) - time.sleep(self.delayafterterminate) - if not self.isalive(): - return True - self.kill(signal.SIGCONT) - time.sleep(self.delayafterterminate) - if not self.isalive(): - return True - self.kill(signal.SIGINT) - time.sleep(self.delayafterterminate) - if not self.isalive(): - return True - if force: - self.kill(signal.SIGKILL) - time.sleep(self.delayafterterminate) - if not self.isalive(): - return True - else: - return False - return False - except OSError: - # I think there are kernel timing issues that sometimes cause - # this to happen. I think isalive() reports True, but the - # process is dead to the kernel. - # Make one last attempt to see if the kernel is up to date. - time.sleep(self.delayafterterminate) - if not self.isalive(): - return True - else: - return False - - def wait(self): - '''This waits until the child exits. This is a blocking call. This will - not read any data from the child, so this will block forever if the - child has unread output and has terminated. In other words, the child - may have printed output then called exit(), but, the child is - technically still alive until its output is read by the parent. ''' - - if self.isalive(): - pid, status = os.waitpid(self.pid, 0) - else: - return self.exitstatus - self.exitstatus = os.WEXITSTATUS(status) - if os.WIFEXITED(status): - self.status = status - self.exitstatus = os.WEXITSTATUS(status) - self.signalstatus = None - self.terminated = True - elif os.WIFSIGNALED(status): - self.status = status - self.exitstatus = None - self.signalstatus = os.WTERMSIG(status) - self.terminated = True - elif os.WIFSTOPPED(status): # pragma: no cover - # You can't call wait() on a child process in the stopped state. - raise PtyProcessError('Called wait() on a stopped child ' + - 'process. This is not supported. Is some other ' + - 'process attempting job control with our child pid?') - return self.exitstatus - - def isalive(self): - '''This tests if the child process is running or not. This is - non-blocking. If the child was terminated then this will read the - exitstatus or signalstatus of the child. This returns True if the child - process appears to be running or False if not. It can take literally - SECONDS for Solaris to return the right status. ''' - - if self.terminated: - return False - - if self.flag_eof: - # This is for Linux, which requires the blocking form - # of waitpid to get the status of a defunct process. - # This is super-lame. The flag_eof would have been set - # in read_nonblocking(), so this should be safe. - waitpid_options = 0 - else: - waitpid_options = os.WNOHANG - - try: - pid, status = os.waitpid(self.pid, waitpid_options) - except OSError as e: - # No child processes - if e.errno == errno.ECHILD: - raise PtyProcessError('isalive() encountered condition ' + - 'where "terminated" is 0, but there was no child ' + - 'process. Did someone else call waitpid() ' + - 'on our process?') - else: - raise - - # I have to do this twice for Solaris. - # I can't even believe that I figured this out... - # If waitpid() returns 0 it means that no child process - # wishes to report, and the value of status is undefined. - if pid == 0: - try: - ### os.WNOHANG) # Solaris! - pid, status = os.waitpid(self.pid, waitpid_options) - except OSError as e: # pragma: no cover - # This should never happen... - if e.errno == errno.ECHILD: - raise PtyProcessError('isalive() encountered condition ' + - 'that should never happen. There was no child ' + - 'process. Did someone else call waitpid() ' + - 'on our process?') - else: - raise - - # If pid is still 0 after two calls to waitpid() then the process - # really is alive. This seems to work on all platforms, except for - # Irix which seems to require a blocking call on waitpid or select, - # so I let read_nonblocking take care of this situation - # (unfortunately, this requires waiting through the timeout). - if pid == 0: - return True - - if pid == 0: - return True - - if os.WIFEXITED(status): - self.status = status - self.exitstatus = os.WEXITSTATUS(status) - self.signalstatus = None - self.terminated = True - elif os.WIFSIGNALED(status): - self.status = status - self.exitstatus = None - self.signalstatus = os.WTERMSIG(status) - self.terminated = True - elif os.WIFSTOPPED(status): - raise PtyProcessError('isalive() encountered condition ' + - 'where child process is stopped. This is not ' + - 'supported. Is some other process attempting ' + - 'job control with our child pid?') - return False - - def kill(self, sig): - """Send the given signal to the child application. - - In keeping with UNIX tradition it has a misleading name. It does not - necessarily kill the child unless you send the right signal. See the - :mod:`signal` module for constants representing signal numbers. - """ - - # Same as os.kill, but the pid is given for you. - if self.isalive(): - os.kill(self.pid, sig) - - def getwinsize(self): - """Return the window size of the pseudoterminal as a tuple (rows, cols). - """ - TIOCGWINSZ = getattr(termios, 'TIOCGWINSZ', 1074295912) - s = struct.pack('HHHH', 0, 0, 0, 0) - x = fcntl.ioctl(self.fd, TIOCGWINSZ, s) - return struct.unpack('HHHH', x)[0:2] - - def setwinsize(self, rows, cols): - """Set the terminal window size of the child tty. - - This will cause a SIGWINCH signal to be sent to the child. This does not - change the physical window size. It changes the size reported to - TTY-aware applications like vi or curses -- applications that respond to - the SIGWINCH signal. - """ - return _setwinsize(self.fd, rows, cols) - - -class PtyProcessUnicode(PtyProcess): - """Unicode wrapper around a process running in a pseudoterminal. - - This class exposes a similar interface to :class:`PtyProcess`, but its read - methods return unicode, and its :meth:`write` accepts unicode. - """ - if PY3: - string_type = str - else: - string_type = unicode # analysis:ignore - - def __init__(self, pid, fd, encoding='utf-8', codec_errors='strict'): - super(PtyProcessUnicode, self).__init__(pid, fd) - self.encoding = encoding - self.codec_errors = codec_errors - self.decoder = codecs.getincrementaldecoder(encoding)(errors=codec_errors) - - def read(self, size=1024): - """Read at most ``size`` bytes from the pty, return them as unicode. - - Can block if there is nothing to read. Raises :exc:`EOFError` if the - terminal was closed. - - The size argument still refers to bytes, not unicode code points. - """ - b = super(PtyProcessUnicode, self).read(size) - return self.decoder.decode(b, final=False) - - def readline(self): - """Read one line from the pseudoterminal, and return it as unicode. - - Can block if there is nothing to read. Raises :exc:`EOFError` if the - terminal was closed. - """ - b = super(PtyProcessUnicode, self).readline() - return self.decoder.decode(b, final=False) - - def write(self, s): - """Write the unicode string ``s`` to the pseudoterminal. - - Returns the number of bytes written. - """ - b = s.encode(self.encoding) - return super(PtyProcessUnicode, self).write(b) + + def eof(self): + '''This returns True if the EOF exception was ever raised. + ''' + + return self.flag_eof + + def terminate(self, force=False): + '''This forces a child process to terminate. It starts nicely with + SIGHUP and SIGINT. If "force" is True then moves onto SIGKILL. This + returns True if the child was terminated. This returns False if the + child could not be terminated. ''' + + if not self.isalive(): + return True + try: + self.kill(signal.SIGHUP) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + self.kill(signal.SIGCONT) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + self.kill(signal.SIGINT) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + if force: + self.kill(signal.SIGKILL) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + else: + return False + return False + except OSError: + # I think there are kernel timing issues that sometimes cause + # this to happen. I think isalive() reports True, but the + # process is dead to the kernel. + # Make one last attempt to see if the kernel is up to date. + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + else: + return False + + def wait(self): + '''This waits until the child exits. This is a blocking call. This will + not read any data from the child, so this will block forever if the + child has unread output and has terminated. In other words, the child + may have printed output then called exit(), but, the child is + technically still alive until its output is read by the parent. ''' + + if self.isalive(): + pid, status = os.waitpid(self.pid, 0) + else: + return self.exitstatus + self.exitstatus = os.WEXITSTATUS(status) + if os.WIFEXITED(status): + self.status = status + self.exitstatus = os.WEXITSTATUS(status) + self.signalstatus = None + self.terminated = True + elif os.WIFSIGNALED(status): + self.status = status + self.exitstatus = None + self.signalstatus = os.WTERMSIG(status) + self.terminated = True + elif os.WIFSTOPPED(status): # pragma: no cover + # You can't call wait() on a child process in the stopped state. + raise PtyProcessError('Called wait() on a stopped child ' + + 'process. This is not supported. Is some other ' + + 'process attempting job control with our child pid?') + return self.exitstatus + + def isalive(self): + '''This tests if the child process is running or not. This is + non-blocking. If the child was terminated then this will read the + exitstatus or signalstatus of the child. This returns True if the child + process appears to be running or False if not. It can take literally + SECONDS for Solaris to return the right status. ''' + + if self.terminated: + return False + + if self.flag_eof: + # This is for Linux, which requires the blocking form + # of waitpid to get the status of a defunct process. + # This is super-lame. The flag_eof would have been set + # in read_nonblocking(), so this should be safe. + waitpid_options = 0 + else: + waitpid_options = os.WNOHANG + + try: + pid, status = os.waitpid(self.pid, waitpid_options) + except OSError as e: + # No child processes + if e.errno == errno.ECHILD: + raise PtyProcessError('isalive() encountered condition ' + + 'where "terminated" is 0, but there was no child ' + + 'process. Did someone else call waitpid() ' + + 'on our process?') + else: + raise + + # I have to do this twice for Solaris. + # I can't even believe that I figured this out... + # If waitpid() returns 0 it means that no child process + # wishes to report, and the value of status is undefined. + if pid == 0: + try: + ### os.WNOHANG) # Solaris! + pid, status = os.waitpid(self.pid, waitpid_options) + except OSError as e: # pragma: no cover + # This should never happen... + if e.errno == errno.ECHILD: + raise PtyProcessError('isalive() encountered condition ' + + 'that should never happen. There was no child ' + + 'process. Did someone else call waitpid() ' + + 'on our process?') + else: + raise + + # If pid is still 0 after two calls to waitpid() then the process + # really is alive. This seems to work on all platforms, except for + # Irix which seems to require a blocking call on waitpid or select, + # so I let read_nonblocking take care of this situation + # (unfortunately, this requires waiting through the timeout). + if pid == 0: + return True + + if pid == 0: + return True + + if os.WIFEXITED(status): + self.status = status + self.exitstatus = os.WEXITSTATUS(status) + self.signalstatus = None + self.terminated = True + elif os.WIFSIGNALED(status): + self.status = status + self.exitstatus = None + self.signalstatus = os.WTERMSIG(status) + self.terminated = True + elif os.WIFSTOPPED(status): + raise PtyProcessError('isalive() encountered condition ' + + 'where child process is stopped. This is not ' + + 'supported. Is some other process attempting ' + + 'job control with our child pid?') + return False + + def kill(self, sig): + """Send the given signal to the child application. + + In keeping with UNIX tradition it has a misleading name. It does not + necessarily kill the child unless you send the right signal. See the + :mod:`signal` module for constants representing signal numbers. + """ + + # Same as os.kill, but the pid is given for you. + if self.isalive(): + os.kill(self.pid, sig) + + def getwinsize(self): + """Return the window size of the pseudoterminal as a tuple (rows, cols). + """ + TIOCGWINSZ = getattr(termios, 'TIOCGWINSZ', 1074295912) + s = struct.pack('HHHH', 0, 0, 0, 0) + x = fcntl.ioctl(self.fd, TIOCGWINSZ, s) + return struct.unpack('HHHH', x)[0:2] + + def setwinsize(self, rows, cols): + """Set the terminal window size of the child tty. + + This will cause a SIGWINCH signal to be sent to the child. This does not + change the physical window size. It changes the size reported to + TTY-aware applications like vi or curses -- applications that respond to + the SIGWINCH signal. + """ + return _setwinsize(self.fd, rows, cols) + + +class PtyProcessUnicode(PtyProcess): + """Unicode wrapper around a process running in a pseudoterminal. + + This class exposes a similar interface to :class:`PtyProcess`, but its read + methods return unicode, and its :meth:`write` accepts unicode. + """ + if PY3: + string_type = str + else: + string_type = unicode # analysis:ignore + + def __init__(self, pid, fd, encoding='utf-8', codec_errors='strict'): + super(PtyProcessUnicode, self).__init__(pid, fd) + self.encoding = encoding + self.codec_errors = codec_errors + self.decoder = codecs.getincrementaldecoder(encoding)(errors=codec_errors) + + def read(self, size=1024): + """Read at most ``size`` bytes from the pty, return them as unicode. + + Can block if there is nothing to read. Raises :exc:`EOFError` if the + terminal was closed. + + The size argument still refers to bytes, not unicode code points. + """ + b = super(PtyProcessUnicode, self).read(size) + return self.decoder.decode(b, final=False) + + def readline(self): + """Read one line from the pseudoterminal, and return it as unicode. + + Can block if there is nothing to read. Raises :exc:`EOFError` if the + terminal was closed. + """ + b = super(PtyProcessUnicode, self).readline() + return self.decoder.decode(b, final=False) + + def write(self, s): + """Write the unicode string ``s`` to the pseudoterminal. + + Returns the number of bytes written. + """ + b = s.encode(self.encoding) + return super(PtyProcessUnicode, self).write(b) diff --git a/contrib/python/ptyprocess/ptyprocess/util.py b/contrib/python/ptyprocess/ptyprocess/util.py index aadbd62c801..9bd6c4c34ce 100644 --- a/contrib/python/ptyprocess/ptyprocess/util.py +++ b/contrib/python/ptyprocess/ptyprocess/util.py @@ -1,69 +1,69 @@ -try: - from shutil import which # Python >= 3.3 -except ImportError: - import os, sys - - # This is copied from Python 3.4.1 - def which(cmd, mode=os.F_OK | os.X_OK, path=None): - """Given a command, mode, and a PATH string, return the path which - conforms to the given mode on the PATH, or None if there is no such - file. - - `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result - of os.environ.get("PATH"), or can be overridden with a custom search - path. - - """ - # Check that a given file can be accessed with the correct mode. - # Additionally check that `file` is not a directory, as on Windows - # directories pass the os.access check. - def _access_check(fn, mode): - return (os.path.exists(fn) and os.access(fn, mode) - and not os.path.isdir(fn)) - - # If we're given a path with a directory part, look it up directly rather - # than referring to PATH directories. This includes checking relative to the - # current directory, e.g. ./script - if os.path.dirname(cmd): - if _access_check(cmd, mode): - return cmd - return None - - if path is None: - path = os.environ.get("PATH", os.defpath) - if not path: - return None - path = path.split(os.pathsep) - - if sys.platform == "win32": - # The current directory takes precedence on Windows. - if not os.curdir in path: - path.insert(0, os.curdir) - - # PATHEXT is necessary to check on Windows. - pathext = os.environ.get("PATHEXT", "").split(os.pathsep) - # See if the given file matches any of the expected path extensions. - # This will allow us to short circuit when given "python.exe". - # If it does match, only test that one, otherwise we have to try - # others. - if any(cmd.lower().endswith(ext.lower()) for ext in pathext): - files = [cmd] - else: - files = [cmd + ext for ext in pathext] - else: - # On other platforms you don't have things like PATHEXT to tell you - # what file suffixes are executable, so just pass on cmd as-is. - files = [cmd] - - seen = set() - for dir in path: - normdir = os.path.normcase(dir) - if not normdir in seen: - seen.add(normdir) - for thefile in files: - name = os.path.join(dir, thefile) - if _access_check(name, mode): - return name +try: + from shutil import which # Python >= 3.3 +except ImportError: + import os, sys + + # This is copied from Python 3.4.1 + def which(cmd, mode=os.F_OK | os.X_OK, path=None): + """Given a command, mode, and a PATH string, return the path which + conforms to the given mode on the PATH, or None if there is no such + file. + + `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result + of os.environ.get("PATH"), or can be overridden with a custom search + path. + + """ + # Check that a given file can be accessed with the correct mode. + # Additionally check that `file` is not a directory, as on Windows + # directories pass the os.access check. + def _access_check(fn, mode): + return (os.path.exists(fn) and os.access(fn, mode) + and not os.path.isdir(fn)) + + # If we're given a path with a directory part, look it up directly rather + # than referring to PATH directories. This includes checking relative to the + # current directory, e.g. ./script + if os.path.dirname(cmd): + if _access_check(cmd, mode): + return cmd + return None + + if path is None: + path = os.environ.get("PATH", os.defpath) + if not path: + return None + path = path.split(os.pathsep) + + if sys.platform == "win32": + # The current directory takes precedence on Windows. + if not os.curdir in path: + path.insert(0, os.curdir) + + # PATHEXT is necessary to check on Windows. + pathext = os.environ.get("PATHEXT", "").split(os.pathsep) + # See if the given file matches any of the expected path extensions. + # This will allow us to short circuit when given "python.exe". + # If it does match, only test that one, otherwise we have to try + # others. + if any(cmd.lower().endswith(ext.lower()) for ext in pathext): + files = [cmd] + else: + files = [cmd + ext for ext in pathext] + else: + # On other platforms you don't have things like PATHEXT to tell you + # what file suffixes are executable, so just pass on cmd as-is. + files = [cmd] + + seen = set() + for dir in path: + normdir = os.path.normcase(dir) + if not normdir in seen: + seen.add(normdir) + for thefile in files: + name = os.path.join(dir, thefile) + if _access_check(name, mode): + return name return None diff --git a/contrib/python/ptyprocess/ya.make b/contrib/python/ptyprocess/ya.make index 5e780214f26..b80a63187ed 100644 --- a/contrib/python/ptyprocess/ya.make +++ b/contrib/python/ptyprocess/ya.make @@ -1,7 +1,7 @@ # Generated by devtools/yamaker (pypi). PY23_LIBRARY() - + OWNER(borman g:python-contrib) VERSION(0.7.0) @@ -10,18 +10,18 @@ LICENSE(ISC) NO_LINT() -PY_SRCS( - TOP_LEVEL - ptyprocess/__init__.py - ptyprocess/_fork_pty.py - ptyprocess/ptyprocess.py - ptyprocess/util.py -) - +PY_SRCS( + TOP_LEVEL + ptyprocess/__init__.py + ptyprocess/_fork_pty.py + ptyprocess/ptyprocess.py + ptyprocess/util.py +) + RESOURCE_FILES( PREFIX contrib/python/ptyprocess/ .dist-info/METADATA .dist-info/top_level.txt ) -END() +END() |
