diff --git a/IPython/external/pexpect/_pexpect.py b/IPython/external/pexpect/_pexpect.py index 942d19f..cace43b 100644 --- a/IPython/external/pexpect/_pexpect.py +++ b/IPython/external/pexpect/_pexpect.py @@ -80,6 +80,7 @@ try: import traceback import signal import codecs + import stat except ImportError: # pragma: no cover err = sys.exc_info()[1] raise ImportError(str(err) + ''' @@ -87,7 +88,7 @@ except ImportError: # pragma: no cover A critical module was not found. Probably this operating system does not support it. Pexpect is intended for UNIX-like operating systems.''') -__version__ = '3.2' +__version__ = '3.3' __revision__ = '' __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu', 'which', 'split_command_line', '__version__', '__revision__'] @@ -284,6 +285,7 @@ class spawn(object): def _chr(c): return bytes([c]) linesep = os.linesep.encode('ascii') + crlf = '\r\n'.encode('ascii') @staticmethod def write_to_stdout(b): @@ -296,13 +298,14 @@ class spawn(object): allowed_string_types = (basestring,) # analysis:ignore _chr = staticmethod(chr) linesep = os.linesep + crlf = '\r\n' write_to_stdout = sys.stdout.write encoding = None def __init__(self, command, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None, - ignore_sighup=True): + ignore_sighup=True, echo=True): '''This is the constructor. The command parameter may be a string that includes a command and any arguments to the command. For example:: @@ -415,7 +418,16 @@ class spawn(object): signalstatus will store the signal value and exitstatus will be None. If you need more detail you can also read the self.status member which stores the status returned by os.waitpid. You can interpret this using - os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG. ''' + os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG. + + The echo attribute may be set to False to disable echoing of input. + As a pseudo-terminal, all input echoed by the "keyboard" (send() + or sendline()) will be repeated to output. For many cases, it is + not desirable to have echo enabled, and it may be later disabled + using setecho(False) followed by waitnoecho(). However, for some + platforms such as Solaris, this is not possible, and should be + disabled immediately on spawn. + ''' self.STDIN_FILENO = pty.STDIN_FILENO self.STDOUT_FILENO = pty.STDOUT_FILENO @@ -437,7 +449,7 @@ class spawn(object): self.status = None self.flag_eof = False self.pid = None - # the chile filedescriptor is initially closed + # the child file descriptor is initially closed self.child_fd = -1 self.timeout = timeout self.delimiter = EOF @@ -466,16 +478,30 @@ class spawn(object): self.closed = True self.cwd = cwd self.env = env + self.echo = echo self.ignore_sighup = ignore_sighup + _platform = sys.platform.lower() # This flags if we are running on irix - self.__irix_hack = (sys.platform.lower().find('irix') >= 0) + self.__irix_hack = _platform.startswith('irix') # Solaris uses internal __fork_pty(). All others use pty.fork(). - if ((sys.platform.lower().find('solaris') >= 0) - or (sys.platform.lower().find('sunos5') >= 0)): - self.use_native_pty_fork = False - else: - self.use_native_pty_fork = True - + self.use_native_pty_fork = not ( + _platform.startswith('solaris') or + _platform.startswith('sunos')) + # inherit EOF and INTR definitions from controlling process. + try: + from termios import VEOF, VINTR + fd = sys.__stdin__.fileno() + self._INTR = ord(termios.tcgetattr(fd)[6][VINTR]) + self._EOF = ord(termios.tcgetattr(fd)[6][VEOF]) + except (ImportError, OSError, IOError, termios.error): + # unless the controlling process is also not a terminal, + # such as cron(1). Fall-back to using CEOF and CINTR. + try: + from termios import CEOF, CINTR + (self._INTR, self._EOF) = (CINTR, CEOF) + except ImportError: + # ^C, ^D + (self._INTR, self._EOF) = (3, 4) # Support subclasses that do not use command or args. if command is None: self.command = None @@ -599,33 +625,39 @@ class spawn(object): if self.use_native_pty_fork: try: self.pid, self.child_fd = pty.fork() - except OSError: + except OSError: # pragma: no cover err = sys.exc_info()[1] raise ExceptionPexpect('pty.fork() failed: ' + str(err)) else: # Use internal __fork_pty self.pid, self.child_fd = self.__fork_pty() - if self.pid == 0: + # 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 self.pid == pty.CHILD: # Child + self.child_fd = self.STDIN_FILENO + + # set default window size of 24 rows by 80 columns try: - # used by setwinsize() - self.child_fd = sys.stdout.fileno() self.setwinsize(24, 80) - # which exception, shouldnt' we catch explicitly .. ? - except: - # Some platforms do not like setwinsize (Cygwin). - # This will cause problem when running applications that - # are very picky about window size. - # This is a serious limitation, but not a show stopper. - pass + 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 self.echo: + try: + self.setecho(self.echo) + except (IOError, termios.error) as err: + if err.args[0] not in (errno.EINVAL, errno.ENOTTY): + raise + # Do not allow child to inherit open file descriptors from parent. max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0] - for i in range(3, max_fd): - try: - os.close(i) - except OSError: - pass + os.closerange(3, max_fd) if self.ignore_sighup: signal.signal(signal.SIGHUP, signal.SIG_IGN) @@ -638,6 +670,13 @@ class spawn(object): os.execvpe(self.command, self.args, self.env) # Parent + try: + self.setwinsize(24, 80) + except IOError as err: + if err.args[0] not in (errno.EINVAL, errno.ENOTTY): + raise + + self.terminated = False self.closed = False @@ -660,19 +699,15 @@ class spawn(object): raise ExceptionPexpect("Could not open with os.openpty().") pid = os.fork() - if pid < 0: - raise ExceptionPexpect("Failed os.fork().") - elif pid == 0: + if pid == pty.CHILD: # Child. os.close(parent_fd) self.__pty_make_controlling_tty(child_fd) - os.dup2(child_fd, 0) - os.dup2(child_fd, 1) - os.dup2(child_fd, 2) + os.dup2(child_fd, self.STDIN_FILENO) + os.dup2(child_fd, self.STDOUT_FILENO) + os.dup2(child_fd, self.STDERR_FILENO) - if child_fd > 2: - os.close(child_fd) else: # Parent. os.close(child_fd) @@ -686,44 +721,36 @@ class spawn(object): child_name = os.ttyname(tty_fd) - # Disconnect from controlling tty. Harmless if not already connected. + # 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) - if fd >= 0: - os.close(fd) - # which exception, shouldnt' we catch explicitly .. ? - except: - # Already disconnected. This happens if running inside cron. - pass + 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. + # 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) - if fd >= 0: - os.close(fd) - raise ExceptionPexpect('Failed to disconnect from ' + - 'controlling tty. It is still possible to open /dev/tty.') - # which exception, shouldnt' we catch explicitly .. ? - except: - # Good! We are disconnected from a controlling tty. - pass + os.close(fd) + raise ExceptionPexpect("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) - if fd < 0: - raise ExceptionPexpect("Could not open child pty, " + child_name) - else: - os.close(fd) + os.close(fd) # Verify we now have a controlling tty. fd = os.open("/dev/tty", os.O_WRONLY) - if fd < 0: - raise ExceptionPexpect("Could not open controlling tty, /dev/tty") - else: - os.close(fd) + os.close(fd) + def fileno(self): '''This returns the file descriptor of the pty for the child. @@ -757,7 +784,12 @@ class spawn(object): def isatty(self): '''This returns True if the file descriptor is open and connected to a - tty(-like) device, else False. ''' + 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.child_fd) @@ -794,12 +826,20 @@ class spawn(object): 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(). ''' + to enter a password often set ECHO False. See waitnoecho(). - attr = termios.tcgetattr(self.child_fd) - if attr[3] & termios.ECHO: - return True - return False + Not supported on platforms where ``isatty()`` returns False. ''' + + try: + attr = termios.tcgetattr(self.child_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 @@ -829,18 +869,35 @@ class spawn(object): p.expect(['1234']) p.expect(['abcd']) p.expect(['wxyz']) + + + Not supported on platforms where ``isatty()`` returns False. ''' - self.child_fd - attr = termios.tcgetattr(self.child_fd) + errmsg = 'setecho() may not be called on this platform' + + try: + attr = termios.tcgetattr(self.child_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 - # I tried TCSADRAIN and TCSAFLUSH, but - # these were inconsistent and blocked on some platforms. - # TCSADRAIN would probably be ideal if it worked. - termios.tcsetattr(self.child_fd, termios.TCSANOW, attr) + + try: + # I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent and + # blocked on some platforms. TCSADRAIN would probably be ideal. + termios.tcsetattr(self.child_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 + + self.echo = state def _log(self, s, direction): if self.logfile is not None: @@ -913,12 +970,14 @@ class spawn(object): if self.child_fd in r: try: s = os.read(self.child_fd, size) - except OSError: - # Linux does this - self.flag_eof = True - raise EOF('End Of File (EOF). Exception style platform.') + except OSError as err: + if err.args[0] == errno.EIO: + # Linux-style EOF + self.flag_eof = True + raise EOF('End Of File (EOF). Exception style platform.') + raise if s == b'': - # BSD style + # BSD-style EOF self.flag_eof = True raise EOF('End Of File (EOF). Empty string style platform.') @@ -926,7 +985,7 @@ class spawn(object): self._log(s, 'read') return s - raise ExceptionPexpect('Reached an unexpected state.') + raise ExceptionPexpect('Reached an unexpected state.') # pragma: no cover def read(self, size=-1): '''This reads at most "size" bytes from the file (less if the read hits @@ -972,9 +1031,9 @@ class spawn(object): if size == 0: return self.string_type() # delimiter default is EOF - index = self.expect([b'\r\n', self.delimiter]) + index = self.expect([self.crlf, self.delimiter]) if index == 0: - return self.before + b'\r\n' + return self.before + self.crlf else: return self.before @@ -1075,40 +1134,14 @@ class spawn(object): It is the responsibility of the caller to ensure the eof is sent at the beginning of a line. ''' - ### Hmmm... how do I send an EOF? - ###C if ((m = write(pty, *buf, p - *buf)) < 0) - ###C return (errno == EWOULDBLOCK) ? n : -1; - #fd = sys.stdin.fileno() - #old = termios.tcgetattr(fd) # remember current state - #attr = termios.tcgetattr(fd) - #attr[3] = attr[3] | termios.ICANON # ICANON must be set to see EOF - #try: # use try/finally to ensure state gets restored - # termios.tcsetattr(fd, termios.TCSADRAIN, attr) - # if hasattr(termios, 'CEOF'): - # os.write(self.child_fd, '%c' % termios.CEOF) - # else: - # # Silly platform does not define CEOF so assume CTRL-D - # os.write(self.child_fd, '%c' % 4) - #finally: # restore state - # termios.tcsetattr(fd, termios.TCSADRAIN, old) - if hasattr(termios, 'VEOF'): - char = ord(termios.tcgetattr(self.child_fd)[6][termios.VEOF]) - else: - # platform does not define VEOF so assume CTRL-D - char = 4 - self.send(self._chr(char)) + self.send(self._chr(self._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. ''' - if hasattr(termios, 'VINTR'): - char = ord(termios.tcgetattr(self.child_fd)[6][termios.VINTR]) - else: - # platform does not define VINTR so assume CTRL-C - char = 3 - self.send(self._chr(char)) + self.send(self._chr(self._INTR)) def eof(self): @@ -1181,7 +1214,7 @@ class spawn(object): self.exitstatus = None self.signalstatus = os.WTERMSIG(status) self.terminated = True - elif os.WIFSTOPPED(status): + elif os.WIFSTOPPED(status): # pragma: no cover # You can't call wait() on a child process in the stopped state. raise ExceptionPexpect('Called wait() on a stopped child ' + 'process. This is not supported. Is some other ' + @@ -1201,7 +1234,7 @@ class spawn(object): if self.flag_eof: # This is for Linux, which requires the blocking form - # of waitpid to # get status of a defunct process. + # 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 @@ -1229,7 +1262,7 @@ class spawn(object): try: ### os.WNOHANG) # Solaris! pid, status = os.waitpid(self.pid, waitpid_options) - except OSError as e: + except OSError as e: # pragma: no cover # This should never happen... if e.errno == errno.ECHILD: raise ExceptionPexpect('isalive() encountered condition ' + @@ -1643,10 +1676,14 @@ class spawn(object): if self.child_fd in r: try: data = self.__interact_read(self.child_fd) - except OSError as e: - # The subprocess may have closed before we get to reading it - if e.errno != errno.EIO: - raise + except OSError as err: + if err.args[0] == errno.EIO: + # Linux-style EOF + break + raise + if data == b'': + # BSD-style EOF + break if output_filter: data = output_filter(data) if self.logfile is not None: @@ -1695,7 +1732,7 @@ class spawn(object): ############################################################################## # The following methods are no longer supported or allowed. - def setmaxread(self, maxread): + def setmaxread(self, maxread): # pragma: no cover '''This method is no longer supported or allowed. I don't like getters and setters without a good reason. ''' @@ -1704,7 +1741,7 @@ class spawn(object): 'or allowed. Just assign a value to the ' + 'maxread member variable.') - def setlog(self, fileobject): + def setlog(self, fileobject): # pragma: no cover '''This method is no longer supported or allowed. ''' @@ -1732,11 +1769,13 @@ class spawnu(spawn): allowed_string_types = (str, ) _chr = staticmethod(chr) linesep = os.linesep + crlf = '\r\n' else: string_type = unicode allowed_string_types = (unicode, ) _chr = staticmethod(unichr) linesep = os.linesep.decode('ascii') + crlf = '\r\n'.decode('ascii') # This can handle unicode in both Python 2 and 3 write_to_stdout = sys.stdout.write @@ -1959,16 +1998,56 @@ class searcher_re(object): return best_index -def which(filename): +def is_executable_file(path): + """Checks that path is an executable regular file (or a symlink to a file). + + This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``, but + on some platforms :func:`os.access` gives us the wrong answer, so this + checks permission bits directly. + """ + # follow symlinks, + fpath = os.path.realpath(path) + # return False for non-files (directories, fifo, etc.) + if not os.path.isfile(fpath): + return False + + # On Solaris, etc., "If the process has appropriate privileges, an + # implementation may indicate success for X_OK even if none of the + # execute file permission bits are set." + # + # For this reason, it is necessary to explicitly check st_mode + + # get file mode using os.stat, and check if `other', + # that is anybody, may read and execute. + mode = os.stat(fpath).st_mode + if mode & stat.S_IROTH and mode & stat.S_IXOTH: + return True + + # get current user's group ids, and check if `group', + # when matching ours, may read and execute. + user_gids = os.getgroups() + [os.getgid()] + if (os.stat(fpath).st_gid in user_gids and + mode & stat.S_IRGRP and mode & stat.S_IXGRP): + return True + + # finally, if file owner matches our effective userid, + # check if `user', may read and execute. + user_gids = os.getgroups() + [os.getgid()] + if (os.stat(fpath).st_uid == os.geteuid() and + mode & stat.S_IRUSR and mode & stat.S_IXUSR): + return True + + return False + +def which(filename): '''This takes a given filename; tries to find it in the environment path; then checks if it is executable. This returns the full path to the filename if found and executable. Otherwise this returns None.''' # Special case where filename contains an explicit path. - if os.path.dirname(filename) != '': - if os.access(filename, os.X_OK): - return filename + if os.path.dirname(filename) != '' and is_executable_file(filename): + return filename if 'PATH' not in os.environ or os.environ['PATH'] == '': p = os.defpath else: @@ -1976,7 +2055,7 @@ def which(filename): pathlist = p.split(os.pathsep) for path in pathlist: ff = os.path.join(path, filename) - if os.access(ff, os.X_OK): + if is_executable_file(ff): return ff return None @@ -2041,4 +2120,4 @@ def split_command_line(command_line): arg_list.append(arg) return arg_list -# vi:set sr et ts=4 sw=4 ft=python : +# vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent :