1"""Provides an interface like pexpect.spawn interface using subprocess.Popen 2""" 3import os 4import threading 5import subprocess 6import sys 7import time 8import signal 9import shlex 10 11try: 12 from queue import Queue, Empty # Python 3 13except ImportError: 14 from Queue import Queue, Empty # Python 2 15 16from .spawnbase import SpawnBase, PY3 17from .exceptions import EOF 18from .utils import string_types 19 20class PopenSpawn(SpawnBase): 21 def __init__(self, cmd, timeout=30, maxread=2000, searchwindowsize=None, 22 logfile=None, cwd=None, env=None, encoding=None, 23 codec_errors='strict', preexec_fn=None): 24 super(PopenSpawn, self).__init__(timeout=timeout, maxread=maxread, 25 searchwindowsize=searchwindowsize, logfile=logfile, 26 encoding=encoding, codec_errors=codec_errors) 27 28 # Note that `SpawnBase` initializes `self.crlf` to `\r\n` 29 # because the default behaviour for a PTY is to convert 30 # incoming LF to `\r\n` (see the `onlcr` flag and 31 # https://stackoverflow.com/a/35887657/5397009). Here we set 32 # it to `os.linesep` because that is what the spawned 33 # application outputs by default and `popen` doesn't translate 34 # anything. 35 if encoding is None: 36 self.crlf = os.linesep.encode ("ascii") 37 else: 38 self.crlf = self.string_type (os.linesep) 39 40 kwargs = dict(bufsize=0, stdin=subprocess.PIPE, 41 stderr=subprocess.STDOUT, stdout=subprocess.PIPE, 42 cwd=cwd, preexec_fn=preexec_fn, env=env) 43 44 if sys.platform == 'win32': 45 startupinfo = subprocess.STARTUPINFO() 46 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 47 kwargs['startupinfo'] = startupinfo 48 kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP 49 50 if isinstance(cmd, string_types) and sys.platform != 'win32': 51 cmd = shlex.split(cmd, posix=os.name == 'posix') 52 53 self.proc = subprocess.Popen(cmd, **kwargs) 54 self.pid = self.proc.pid 55 self.closed = False 56 self._buf = self.string_type() 57 58 self._read_queue = Queue() 59 self._read_thread = threading.Thread(target=self._read_incoming) 60 self._read_thread.setDaemon(True) 61 self._read_thread.start() 62 63 _read_reached_eof = False 64 65 def read_nonblocking(self, size, timeout): 66 buf = self._buf 67 if self._read_reached_eof: 68 # We have already finished reading. Use up any buffered data, 69 # then raise EOF 70 if buf: 71 self._buf = buf[size:] 72 return buf[:size] 73 else: 74 self.flag_eof = True 75 raise EOF('End Of File (EOF).') 76 77 if timeout == -1: 78 timeout = self.timeout 79 elif timeout is None: 80 timeout = 1e6 81 82 t0 = time.time() 83 while (time.time() - t0) < timeout and size and len(buf) < size: 84 try: 85 incoming = self._read_queue.get_nowait() 86 except Empty: 87 break 88 else: 89 if incoming is None: 90 self._read_reached_eof = True 91 break 92 93 buf += self._decoder.decode(incoming, final=False) 94 95 r, self._buf = buf[:size], buf[size:] 96 97 self._log(r, 'read') 98 return r 99 100 def _read_incoming(self): 101 """Run in a thread to move output from a pipe to a queue.""" 102 fileno = self.proc.stdout.fileno() 103 while 1: 104 buf = b'' 105 try: 106 buf = os.read(fileno, 1024) 107 except OSError as e: 108 self._log(e, 'read') 109 110 if not buf: 111 # This indicates we have reached EOF 112 self._read_queue.put(None) 113 return 114 115 self._read_queue.put(buf) 116 117 def write(self, s): 118 '''This is similar to send() except that there is no return value. 119 ''' 120 self.send(s) 121 122 def writelines(self, sequence): 123 '''This calls write() for each element in the sequence. 124 125 The sequence can be any iterable object producing strings, typically a 126 list of strings. This does not add line separators. There is no return 127 value. 128 ''' 129 for s in sequence: 130 self.send(s) 131 132 def send(self, s): 133 '''Send data to the subprocess' stdin. 134 135 Returns the number of bytes written. 136 ''' 137 s = self._coerce_send_string(s) 138 self._log(s, 'send') 139 140 b = self._encoder.encode(s, final=False) 141 if PY3: 142 return self.proc.stdin.write(b) 143 else: 144 # On Python 2, .write() returns None, so we return the length of 145 # bytes written ourselves. This assumes they all got written. 146 self.proc.stdin.write(b) 147 return len(b) 148 149 def sendline(self, s=''): 150 '''Wraps send(), sending string ``s`` to child process, with os.linesep 151 automatically appended. Returns number of bytes written. ''' 152 153 n = self.send(s) 154 return n + self.send(self.linesep) 155 156 def wait(self): 157 '''Wait for the subprocess to finish. 158 159 Returns the exit code. 160 ''' 161 status = self.proc.wait() 162 if status >= 0: 163 self.exitstatus = status 164 self.signalstatus = None 165 else: 166 self.exitstatus = None 167 self.signalstatus = -status 168 self.terminated = True 169 return status 170 171 def kill(self, sig): 172 '''Sends a Unix signal to the subprocess. 173 174 Use constants from the :mod:`signal` module to specify which signal. 175 ''' 176 if sys.platform == 'win32': 177 if sig in [signal.SIGINT, signal.CTRL_C_EVENT]: 178 sig = signal.CTRL_C_EVENT 179 elif sig in [signal.SIGBREAK, signal.CTRL_BREAK_EVENT]: 180 sig = signal.CTRL_BREAK_EVENT 181 else: 182 sig = signal.SIGTERM 183 184 os.kill(self.proc.pid, sig) 185 186 def sendeof(self): 187 '''Closes the stdin pipe from the writing end.''' 188 self.proc.stdin.close() 189