1d201506cSStephen Warren# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved. 2d201506cSStephen Warren# 3d201506cSStephen Warren# SPDX-License-Identifier: GPL-2.0 4d201506cSStephen Warren 5d201506cSStephen Warren# Logic to spawn a sub-process and interact with its stdio. 6d201506cSStephen Warren 7d201506cSStephen Warrenimport os 8d201506cSStephen Warrenimport re 9d201506cSStephen Warrenimport pty 10d201506cSStephen Warrenimport signal 11d201506cSStephen Warrenimport select 12d201506cSStephen Warrenimport time 13d201506cSStephen Warren 14d201506cSStephen Warrenclass Timeout(Exception): 15e8debf39SStephen Warren """An exception sub-class that indicates that a timeout occurred.""" 16d201506cSStephen Warren pass 17d201506cSStephen Warren 18d201506cSStephen Warrenclass Spawn(object): 19e8debf39SStephen Warren """Represents the stdio of a freshly created sub-process. Commands may be 20d201506cSStephen Warren sent to the process, and responses waited for. 21*ebec58fbSSimon Glass 22*ebec58fbSSimon Glass Members: 23*ebec58fbSSimon Glass output: accumulated output from expect() 24e8debf39SStephen Warren """ 25d201506cSStephen Warren 26d27f2fc1SStephen Warren def __init__(self, args, cwd=None): 27e8debf39SStephen Warren """Spawn (fork/exec) the sub-process. 28d201506cSStephen Warren 29d201506cSStephen Warren Args: 30d27f2fc1SStephen Warren args: array of processs arguments. argv[0] is the command to 31d27f2fc1SStephen Warren execute. 32d27f2fc1SStephen Warren cwd: the directory to run the process in, or None for no change. 33d201506cSStephen Warren 34d201506cSStephen Warren Returns: 35d201506cSStephen Warren Nothing. 36e8debf39SStephen Warren """ 37d201506cSStephen Warren 38d201506cSStephen Warren self.waited = False 39d201506cSStephen Warren self.buf = '' 40*ebec58fbSSimon Glass self.output = '' 41d201506cSStephen Warren self.logfile_read = None 42d201506cSStephen Warren self.before = '' 43d201506cSStephen Warren self.after = '' 44d201506cSStephen Warren self.timeout = None 45085e64ddSStephen Warren # http://stackoverflow.com/questions/7857352/python-regex-to-match-vt100-escape-sequences 46085e64ddSStephen Warren # Note that re.I doesn't seem to work with this regex (or perhaps the 47085e64ddSStephen Warren # version of Python in Ubuntu 14.04), hence the inclusion of a-z inside 48085e64ddSStephen Warren # [] instead. 49085e64ddSStephen Warren self.re_vt100 = re.compile('(\x1b\[|\x9b)[^@-_a-z]*[@-_a-z]|\x1b[@-_a-z]') 50d201506cSStephen Warren 51d201506cSStephen Warren (self.pid, self.fd) = pty.fork() 52d201506cSStephen Warren if self.pid == 0: 53d201506cSStephen Warren try: 54d201506cSStephen Warren # For some reason, SIGHUP is set to SIG_IGN at this point when 55d201506cSStephen Warren # run under "go" (www.go.cd). Perhaps this happens under any 56d201506cSStephen Warren # background (non-interactive) system? 57d201506cSStephen Warren signal.signal(signal.SIGHUP, signal.SIG_DFL) 58d27f2fc1SStephen Warren if cwd: 59d27f2fc1SStephen Warren os.chdir(cwd) 60d201506cSStephen Warren os.execvp(args[0], args) 61d201506cSStephen Warren except: 62d201506cSStephen Warren print 'CHILD EXECEPTION:' 63d201506cSStephen Warren import traceback 64d201506cSStephen Warren traceback.print_exc() 65d201506cSStephen Warren finally: 66d201506cSStephen Warren os._exit(255) 67d201506cSStephen Warren 6893134e18SStephen Warren try: 69d201506cSStephen Warren self.poll = select.poll() 70d201506cSStephen Warren self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR | select.POLLHUP | select.POLLNVAL) 7193134e18SStephen Warren except: 7293134e18SStephen Warren self.close() 7393134e18SStephen Warren raise 74d201506cSStephen Warren 75d201506cSStephen Warren def kill(self, sig): 76e8debf39SStephen Warren """Send unix signal "sig" to the child process. 77d201506cSStephen Warren 78d201506cSStephen Warren Args: 79d201506cSStephen Warren sig: The signal number to send. 80d201506cSStephen Warren 81d201506cSStephen Warren Returns: 82d201506cSStephen Warren Nothing. 83e8debf39SStephen Warren """ 84d201506cSStephen Warren 85d201506cSStephen Warren os.kill(self.pid, sig) 86d201506cSStephen Warren 87d201506cSStephen Warren def isalive(self): 88e8debf39SStephen Warren """Determine whether the child process is still running. 89d201506cSStephen Warren 90d201506cSStephen Warren Args: 91d201506cSStephen Warren None. 92d201506cSStephen Warren 93d201506cSStephen Warren Returns: 94d201506cSStephen Warren Boolean indicating whether process is alive. 95e8debf39SStephen Warren """ 96d201506cSStephen Warren 97d201506cSStephen Warren if self.waited: 98d201506cSStephen Warren return False 99d201506cSStephen Warren 100d201506cSStephen Warren w = os.waitpid(self.pid, os.WNOHANG) 101d201506cSStephen Warren if w[0] == 0: 102d201506cSStephen Warren return True 103d201506cSStephen Warren 104d201506cSStephen Warren self.waited = True 105d201506cSStephen Warren return False 106d201506cSStephen Warren 107d201506cSStephen Warren def send(self, data): 108e8debf39SStephen Warren """Send data to the sub-process's stdin. 109d201506cSStephen Warren 110d201506cSStephen Warren Args: 111d201506cSStephen Warren data: The data to send to the process. 112d201506cSStephen Warren 113d201506cSStephen Warren Returns: 114d201506cSStephen Warren Nothing. 115e8debf39SStephen Warren """ 116d201506cSStephen Warren 117d201506cSStephen Warren os.write(self.fd, data) 118d201506cSStephen Warren 119d201506cSStephen Warren def expect(self, patterns): 120e8debf39SStephen Warren """Wait for the sub-process to emit specific data. 121d201506cSStephen Warren 122d201506cSStephen Warren This function waits for the process to emit one pattern from the 123d201506cSStephen Warren supplied list of patterns, or for a timeout to occur. 124d201506cSStephen Warren 125d201506cSStephen Warren Args: 126d201506cSStephen Warren patterns: A list of strings or regex objects that we expect to 127d201506cSStephen Warren see in the sub-process' stdout. 128d201506cSStephen Warren 129d201506cSStephen Warren Returns: 130d201506cSStephen Warren The index within the patterns array of the pattern the process 131d201506cSStephen Warren emitted. 132d201506cSStephen Warren 133d201506cSStephen Warren Notable exceptions: 134d201506cSStephen Warren Timeout, if the process did not emit any of the patterns within 135d201506cSStephen Warren the expected time. 136e8debf39SStephen Warren """ 137d201506cSStephen Warren 138d201506cSStephen Warren for pi in xrange(len(patterns)): 139d201506cSStephen Warren if type(patterns[pi]) == type(''): 140d201506cSStephen Warren patterns[pi] = re.compile(patterns[pi]) 141d201506cSStephen Warren 142d314e247SStephen Warren tstart_s = time.time() 143d201506cSStephen Warren try: 144d201506cSStephen Warren while True: 145d201506cSStephen Warren earliest_m = None 146d201506cSStephen Warren earliest_pi = None 147d201506cSStephen Warren for pi in xrange(len(patterns)): 148d201506cSStephen Warren pattern = patterns[pi] 149d201506cSStephen Warren m = pattern.search(self.buf) 150d201506cSStephen Warren if not m: 151d201506cSStephen Warren continue 15244ac762bSStephen Warren if earliest_m and m.start() >= earliest_m.start(): 153d201506cSStephen Warren continue 154d201506cSStephen Warren earliest_m = m 155d201506cSStephen Warren earliest_pi = pi 156d201506cSStephen Warren if earliest_m: 157d201506cSStephen Warren pos = earliest_m.start() 158d8926811SStephen Warren posafter = earliest_m.end() 159d201506cSStephen Warren self.before = self.buf[:pos] 160d201506cSStephen Warren self.after = self.buf[pos:posafter] 161*ebec58fbSSimon Glass self.output += self.buf[:posafter] 162d201506cSStephen Warren self.buf = self.buf[posafter:] 163d201506cSStephen Warren return earliest_pi 164d314e247SStephen Warren tnow_s = time.time() 16589ab8410SStephen Warren if self.timeout: 166d314e247SStephen Warren tdelta_ms = (tnow_s - tstart_s) * 1000 16789ab8410SStephen Warren poll_maxwait = self.timeout - tdelta_ms 168d314e247SStephen Warren if tdelta_ms > self.timeout: 169d314e247SStephen Warren raise Timeout() 17089ab8410SStephen Warren else: 17189ab8410SStephen Warren poll_maxwait = None 17289ab8410SStephen Warren events = self.poll.poll(poll_maxwait) 173d201506cSStephen Warren if not events: 174d201506cSStephen Warren raise Timeout() 175d201506cSStephen Warren c = os.read(self.fd, 1024) 176d201506cSStephen Warren if not c: 177d201506cSStephen Warren raise EOFError() 178d201506cSStephen Warren if self.logfile_read: 179d201506cSStephen Warren self.logfile_read.write(c) 180d201506cSStephen Warren self.buf += c 181085e64ddSStephen Warren # count=0 is supposed to be the default, which indicates 182085e64ddSStephen Warren # unlimited substitutions, but in practice the version of 183085e64ddSStephen Warren # Python in Ubuntu 14.04 appears to default to count=2! 184085e64ddSStephen Warren self.buf = self.re_vt100.sub('', self.buf, count=1000000) 185d201506cSStephen Warren finally: 186d201506cSStephen Warren if self.logfile_read: 187d201506cSStephen Warren self.logfile_read.flush() 188d201506cSStephen Warren 189d201506cSStephen Warren def close(self): 190e8debf39SStephen Warren """Close the stdio connection to the sub-process. 191d201506cSStephen Warren 192d201506cSStephen Warren This also waits a reasonable time for the sub-process to stop running. 193d201506cSStephen Warren 194d201506cSStephen Warren Args: 195d201506cSStephen Warren None. 196d201506cSStephen Warren 197d201506cSStephen Warren Returns: 198d201506cSStephen Warren Nothing. 199e8debf39SStephen Warren """ 200d201506cSStephen Warren 201d201506cSStephen Warren os.close(self.fd) 202d201506cSStephen Warren for i in xrange(100): 203d201506cSStephen Warren if not self.isalive(): 204d201506cSStephen Warren break 205d201506cSStephen Warren time.sleep(0.1) 206*ebec58fbSSimon Glass 207*ebec58fbSSimon Glass def get_expect_output(self): 208*ebec58fbSSimon Glass """Return the output read by expect() 209*ebec58fbSSimon Glass 210*ebec58fbSSimon Glass Returns: 211*ebec58fbSSimon Glass The output processed by expect(), as a string. 212*ebec58fbSSimon Glass """ 213*ebec58fbSSimon Glass return self.output 214