_process_posix.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. """Posix-specific implementation of process utilities.
  2. This file is only meant to be imported by process.py, not by end-users.
  3. """
  4. #-----------------------------------------------------------------------------
  5. # Copyright (C) 2010-2011 The IPython Development Team
  6. #
  7. # Distributed under the terms of the BSD License. The full license is in
  8. # the file COPYING, distributed as part of this software.
  9. #-----------------------------------------------------------------------------
  10. #-----------------------------------------------------------------------------
  11. # Imports
  12. #-----------------------------------------------------------------------------
  13. from __future__ import print_function
  14. # Stdlib
  15. import errno
  16. import os
  17. import subprocess as sp
  18. import sys
  19. import pexpect
  20. # Our own
  21. from ._process_common import getoutput, arg_split
  22. from IPython.utils import py3compat
  23. from IPython.utils.encoding import DEFAULT_ENCODING
  24. #-----------------------------------------------------------------------------
  25. # Function definitions
  26. #-----------------------------------------------------------------------------
  27. def _find_cmd(cmd):
  28. """Find the full path to a command using which."""
  29. path = sp.Popen(['/usr/bin/env', 'which', cmd],
  30. stdout=sp.PIPE, stderr=sp.PIPE).communicate()[0]
  31. return py3compat.bytes_to_str(path)
  32. class ProcessHandler(object):
  33. """Execute subprocesses under the control of pexpect.
  34. """
  35. # Timeout in seconds to wait on each reading of the subprocess' output.
  36. # This should not be set too low to avoid cpu overusage from our side,
  37. # since we read in a loop whose period is controlled by this timeout.
  38. read_timeout = 0.05
  39. # Timeout to give a process if we receive SIGINT, between sending the
  40. # SIGINT to the process and forcefully terminating it.
  41. terminate_timeout = 0.2
  42. # File object where stdout and stderr of the subprocess will be written
  43. logfile = None
  44. # Shell to call for subprocesses to execute
  45. _sh = None
  46. @property
  47. def sh(self):
  48. if self._sh is None:
  49. self._sh = pexpect.which('sh')
  50. if self._sh is None:
  51. raise OSError('"sh" shell not found')
  52. return self._sh
  53. def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
  54. """Arguments are used for pexpect calls."""
  55. self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
  56. None else read_timeout)
  57. self.terminate_timeout = (ProcessHandler.terminate_timeout if
  58. terminate_timeout is None else
  59. terminate_timeout)
  60. self.logfile = sys.stdout if logfile is None else logfile
  61. def getoutput(self, cmd):
  62. """Run a command and return its stdout/stderr as a string.
  63. Parameters
  64. ----------
  65. cmd : str
  66. A command to be executed in the system shell.
  67. Returns
  68. -------
  69. output : str
  70. A string containing the combination of stdout and stderr from the
  71. subprocess, in whatever order the subprocess originally wrote to its
  72. file descriptors (so the order of the information in this string is the
  73. correct order as would be seen if running the command in a terminal).
  74. """
  75. try:
  76. return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
  77. except KeyboardInterrupt:
  78. print('^C', file=sys.stderr, end='')
  79. def getoutput_pexpect(self, cmd):
  80. """Run a command and return its stdout/stderr as a string.
  81. Parameters
  82. ----------
  83. cmd : str
  84. A command to be executed in the system shell.
  85. Returns
  86. -------
  87. output : str
  88. A string containing the combination of stdout and stderr from the
  89. subprocess, in whatever order the subprocess originally wrote to its
  90. file descriptors (so the order of the information in this string is the
  91. correct order as would be seen if running the command in a terminal).
  92. """
  93. try:
  94. return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
  95. except KeyboardInterrupt:
  96. print('^C', file=sys.stderr, end='')
  97. def system(self, cmd):
  98. """Execute a command in a subshell.
  99. Parameters
  100. ----------
  101. cmd : str
  102. A command to be executed in the system shell.
  103. Returns
  104. -------
  105. int : child's exitstatus
  106. """
  107. # Get likely encoding for the output.
  108. enc = DEFAULT_ENCODING
  109. # Patterns to match on the output, for pexpect. We read input and
  110. # allow either a short timeout or EOF
  111. patterns = [pexpect.TIMEOUT, pexpect.EOF]
  112. # the index of the EOF pattern in the list.
  113. # even though we know it's 1, this call means we don't have to worry if
  114. # we change the above list, and forget to change this value:
  115. EOF_index = patterns.index(pexpect.EOF)
  116. # The size of the output stored so far in the process output buffer.
  117. # Since pexpect only appends to this buffer, each time we print we
  118. # record how far we've printed, so that next time we only print *new*
  119. # content from the buffer.
  120. out_size = 0
  121. try:
  122. # Since we're not really searching the buffer for text patterns, we
  123. # can set pexpect's search window to be tiny and it won't matter.
  124. # We only search for the 'patterns' timeout or EOF, which aren't in
  125. # the text itself.
  126. #child = pexpect.spawn(pcmd, searchwindowsize=1)
  127. if hasattr(pexpect, 'spawnb'):
  128. child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
  129. else:
  130. child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
  131. flush = sys.stdout.flush
  132. while True:
  133. # res is the index of the pattern that caused the match, so we
  134. # know whether we've finished (if we matched EOF) or not
  135. res_idx = child.expect_list(patterns, self.read_timeout)
  136. print(child.before[out_size:].decode(enc, 'replace'), end='')
  137. flush()
  138. if res_idx==EOF_index:
  139. break
  140. # Update the pointer to what we've already printed
  141. out_size = len(child.before)
  142. except KeyboardInterrupt:
  143. # We need to send ^C to the process. The ascii code for '^C' is 3
  144. # (the character is known as ETX for 'End of Text', see
  145. # curses.ascii.ETX).
  146. child.sendline(chr(3))
  147. # Read and print any more output the program might produce on its
  148. # way out.
  149. try:
  150. out_size = len(child.before)
  151. child.expect_list(patterns, self.terminate_timeout)
  152. print(child.before[out_size:].decode(enc, 'replace'), end='')
  153. sys.stdout.flush()
  154. except KeyboardInterrupt:
  155. # Impatient users tend to type it multiple times
  156. pass
  157. finally:
  158. # Ensure the subprocess really is terminated
  159. child.terminate(force=True)
  160. # add isalive check, to ensure exitstatus is set:
  161. child.isalive()
  162. # We follow the subprocess pattern, returning either the exit status
  163. # as a positive number, or the terminating signal as a negative
  164. # number.
  165. # on Linux, sh returns 128+n for signals terminating child processes on Linux
  166. # on BSD (OS X), the signal code is set instead
  167. if child.exitstatus is None:
  168. # on WIFSIGNALED, pexpect sets signalstatus, leaving exitstatus=None
  169. if child.signalstatus is None:
  170. # this condition may never occur,
  171. # but let's be certain we always return an integer.
  172. return 0
  173. return -child.signalstatus
  174. if child.exitstatus > 128:
  175. return -(child.exitstatus - 128)
  176. return child.exitstatus
  177. # Make system() with a functional interface for outside use. Note that we use
  178. # getoutput() from the _common utils, which is built on top of popen(). Using
  179. # pexpect to get subprocess output produces difficult to parse output, since
  180. # programs think they are talking to a tty and produce highly formatted output
  181. # (ls is a good example) that makes them hard.
  182. system = ProcessHandler().system
  183. def check_pid(pid):
  184. try:
  185. os.kill(pid, 0)
  186. except OSError as err:
  187. if err.errno == errno.ESRCH:
  188. return False
  189. elif err.errno == errno.EPERM:
  190. # Don't have permission to signal the process - probably means it exists
  191. return True
  192. raise
  193. else:
  194. return True