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. 21e8debf39SStephen Warren """ 22d201506cSStephen Warren 23d27f2fc1SStephen Warren def __init__(self, args, cwd=None): 24e8debf39SStephen Warren """Spawn (fork/exec) the sub-process. 25d201506cSStephen Warren 26d201506cSStephen Warren Args: 27d27f2fc1SStephen Warren args: array of processs arguments. argv[0] is the command to 28d27f2fc1SStephen Warren execute. 29d27f2fc1SStephen Warren cwd: the directory to run the process in, or None for no change. 30d201506cSStephen Warren 31d201506cSStephen Warren Returns: 32d201506cSStephen Warren Nothing. 33e8debf39SStephen Warren """ 34d201506cSStephen Warren 35d201506cSStephen Warren self.waited = False 36d201506cSStephen Warren self.buf = '' 37d201506cSStephen Warren self.logfile_read = None 38d201506cSStephen Warren self.before = '' 39d201506cSStephen Warren self.after = '' 40d201506cSStephen Warren self.timeout = None 41d201506cSStephen Warren 42d201506cSStephen Warren (self.pid, self.fd) = pty.fork() 43d201506cSStephen Warren if self.pid == 0: 44d201506cSStephen Warren try: 45d201506cSStephen Warren # For some reason, SIGHUP is set to SIG_IGN at this point when 46d201506cSStephen Warren # run under "go" (www.go.cd). Perhaps this happens under any 47d201506cSStephen Warren # background (non-interactive) system? 48d201506cSStephen Warren signal.signal(signal.SIGHUP, signal.SIG_DFL) 49d27f2fc1SStephen Warren if cwd: 50d27f2fc1SStephen Warren os.chdir(cwd) 51d201506cSStephen Warren os.execvp(args[0], args) 52d201506cSStephen Warren except: 53d201506cSStephen Warren print 'CHILD EXECEPTION:' 54d201506cSStephen Warren import traceback 55d201506cSStephen Warren traceback.print_exc() 56d201506cSStephen Warren finally: 57d201506cSStephen Warren os._exit(255) 58d201506cSStephen Warren 59d201506cSStephen Warren self.poll = select.poll() 60d201506cSStephen Warren self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR | select.POLLHUP | select.POLLNVAL) 61d201506cSStephen Warren 62d201506cSStephen Warren def kill(self, sig): 63e8debf39SStephen Warren """Send unix signal "sig" to the child process. 64d201506cSStephen Warren 65d201506cSStephen Warren Args: 66d201506cSStephen Warren sig: The signal number to send. 67d201506cSStephen Warren 68d201506cSStephen Warren Returns: 69d201506cSStephen Warren Nothing. 70e8debf39SStephen Warren """ 71d201506cSStephen Warren 72d201506cSStephen Warren os.kill(self.pid, sig) 73d201506cSStephen Warren 74d201506cSStephen Warren def isalive(self): 75e8debf39SStephen Warren """Determine whether the child process is still running. 76d201506cSStephen Warren 77d201506cSStephen Warren Args: 78d201506cSStephen Warren None. 79d201506cSStephen Warren 80d201506cSStephen Warren Returns: 81d201506cSStephen Warren Boolean indicating whether process is alive. 82e8debf39SStephen Warren """ 83d201506cSStephen Warren 84d201506cSStephen Warren if self.waited: 85d201506cSStephen Warren return False 86d201506cSStephen Warren 87d201506cSStephen Warren w = os.waitpid(self.pid, os.WNOHANG) 88d201506cSStephen Warren if w[0] == 0: 89d201506cSStephen Warren return True 90d201506cSStephen Warren 91d201506cSStephen Warren self.waited = True 92d201506cSStephen Warren return False 93d201506cSStephen Warren 94d201506cSStephen Warren def send(self, data): 95e8debf39SStephen Warren """Send data to the sub-process's stdin. 96d201506cSStephen Warren 97d201506cSStephen Warren Args: 98d201506cSStephen Warren data: The data to send to the process. 99d201506cSStephen Warren 100d201506cSStephen Warren Returns: 101d201506cSStephen Warren Nothing. 102e8debf39SStephen Warren """ 103d201506cSStephen Warren 104d201506cSStephen Warren os.write(self.fd, data) 105d201506cSStephen Warren 106d201506cSStephen Warren def expect(self, patterns): 107e8debf39SStephen Warren """Wait for the sub-process to emit specific data. 108d201506cSStephen Warren 109d201506cSStephen Warren This function waits for the process to emit one pattern from the 110d201506cSStephen Warren supplied list of patterns, or for a timeout to occur. 111d201506cSStephen Warren 112d201506cSStephen Warren Args: 113d201506cSStephen Warren patterns: A list of strings or regex objects that we expect to 114d201506cSStephen Warren see in the sub-process' stdout. 115d201506cSStephen Warren 116d201506cSStephen Warren Returns: 117d201506cSStephen Warren The index within the patterns array of the pattern the process 118d201506cSStephen Warren emitted. 119d201506cSStephen Warren 120d201506cSStephen Warren Notable exceptions: 121d201506cSStephen Warren Timeout, if the process did not emit any of the patterns within 122d201506cSStephen Warren the expected time. 123e8debf39SStephen Warren """ 124d201506cSStephen Warren 125d201506cSStephen Warren for pi in xrange(len(patterns)): 126d201506cSStephen Warren if type(patterns[pi]) == type(''): 127d201506cSStephen Warren patterns[pi] = re.compile(patterns[pi]) 128d201506cSStephen Warren 129d314e247SStephen Warren tstart_s = time.time() 130d201506cSStephen Warren try: 131d201506cSStephen Warren while True: 132d201506cSStephen Warren earliest_m = None 133d201506cSStephen Warren earliest_pi = None 134d201506cSStephen Warren for pi in xrange(len(patterns)): 135d201506cSStephen Warren pattern = patterns[pi] 136d201506cSStephen Warren m = pattern.search(self.buf) 137d201506cSStephen Warren if not m: 138d201506cSStephen Warren continue 13944ac762bSStephen Warren if earliest_m and m.start() >= earliest_m.start(): 140d201506cSStephen Warren continue 141d201506cSStephen Warren earliest_m = m 142d201506cSStephen Warren earliest_pi = pi 143d201506cSStephen Warren if earliest_m: 144d201506cSStephen Warren pos = earliest_m.start() 145d201506cSStephen Warren posafter = earliest_m.end() + 1 146d201506cSStephen Warren self.before = self.buf[:pos] 147d201506cSStephen Warren self.after = self.buf[pos:posafter] 148d201506cSStephen Warren self.buf = self.buf[posafter:] 149d201506cSStephen Warren return earliest_pi 150d314e247SStephen Warren tnow_s = time.time() 151*89ab8410SStephen Warren if self.timeout: 152d314e247SStephen Warren tdelta_ms = (tnow_s - tstart_s) * 1000 153*89ab8410SStephen Warren poll_maxwait = self.timeout - tdelta_ms 154d314e247SStephen Warren if tdelta_ms > self.timeout: 155d314e247SStephen Warren raise Timeout() 156*89ab8410SStephen Warren else: 157*89ab8410SStephen Warren poll_maxwait = None 158*89ab8410SStephen Warren events = self.poll.poll(poll_maxwait) 159d201506cSStephen Warren if not events: 160d201506cSStephen Warren raise Timeout() 161d201506cSStephen Warren c = os.read(self.fd, 1024) 162d201506cSStephen Warren if not c: 163d201506cSStephen Warren raise EOFError() 164d201506cSStephen Warren if self.logfile_read: 165d201506cSStephen Warren self.logfile_read.write(c) 166d201506cSStephen Warren self.buf += c 167d201506cSStephen Warren finally: 168d201506cSStephen Warren if self.logfile_read: 169d201506cSStephen Warren self.logfile_read.flush() 170d201506cSStephen Warren 171d201506cSStephen Warren def close(self): 172e8debf39SStephen Warren """Close the stdio connection to the sub-process. 173d201506cSStephen Warren 174d201506cSStephen Warren This also waits a reasonable time for the sub-process to stop running. 175d201506cSStephen Warren 176d201506cSStephen Warren Args: 177d201506cSStephen Warren None. 178d201506cSStephen Warren 179d201506cSStephen Warren Returns: 180d201506cSStephen Warren Nothing. 181e8debf39SStephen Warren """ 182d201506cSStephen Warren 183d201506cSStephen Warren os.close(self.fd) 184d201506cSStephen Warren for i in xrange(100): 185d201506cSStephen Warren if not self.isalive(): 186d201506cSStephen Warren break 187d201506cSStephen Warren time.sleep(0.1) 188