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 59*93134e18SStephen Warren try: 60d201506cSStephen Warren self.poll = select.poll() 61d201506cSStephen Warren self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR | select.POLLHUP | select.POLLNVAL) 62*93134e18SStephen Warren except: 63*93134e18SStephen Warren self.close() 64*93134e18SStephen Warren raise 65d201506cSStephen Warren 66d201506cSStephen Warren def kill(self, sig): 67e8debf39SStephen Warren """Send unix signal "sig" to the child process. 68d201506cSStephen Warren 69d201506cSStephen Warren Args: 70d201506cSStephen Warren sig: The signal number to send. 71d201506cSStephen Warren 72d201506cSStephen Warren Returns: 73d201506cSStephen Warren Nothing. 74e8debf39SStephen Warren """ 75d201506cSStephen Warren 76d201506cSStephen Warren os.kill(self.pid, sig) 77d201506cSStephen Warren 78d201506cSStephen Warren def isalive(self): 79e8debf39SStephen Warren """Determine whether the child process is still running. 80d201506cSStephen Warren 81d201506cSStephen Warren Args: 82d201506cSStephen Warren None. 83d201506cSStephen Warren 84d201506cSStephen Warren Returns: 85d201506cSStephen Warren Boolean indicating whether process is alive. 86e8debf39SStephen Warren """ 87d201506cSStephen Warren 88d201506cSStephen Warren if self.waited: 89d201506cSStephen Warren return False 90d201506cSStephen Warren 91d201506cSStephen Warren w = os.waitpid(self.pid, os.WNOHANG) 92d201506cSStephen Warren if w[0] == 0: 93d201506cSStephen Warren return True 94d201506cSStephen Warren 95d201506cSStephen Warren self.waited = True 96d201506cSStephen Warren return False 97d201506cSStephen Warren 98d201506cSStephen Warren def send(self, data): 99e8debf39SStephen Warren """Send data to the sub-process's stdin. 100d201506cSStephen Warren 101d201506cSStephen Warren Args: 102d201506cSStephen Warren data: The data to send to the process. 103d201506cSStephen Warren 104d201506cSStephen Warren Returns: 105d201506cSStephen Warren Nothing. 106e8debf39SStephen Warren """ 107d201506cSStephen Warren 108d201506cSStephen Warren os.write(self.fd, data) 109d201506cSStephen Warren 110d201506cSStephen Warren def expect(self, patterns): 111e8debf39SStephen Warren """Wait for the sub-process to emit specific data. 112d201506cSStephen Warren 113d201506cSStephen Warren This function waits for the process to emit one pattern from the 114d201506cSStephen Warren supplied list of patterns, or for a timeout to occur. 115d201506cSStephen Warren 116d201506cSStephen Warren Args: 117d201506cSStephen Warren patterns: A list of strings or regex objects that we expect to 118d201506cSStephen Warren see in the sub-process' stdout. 119d201506cSStephen Warren 120d201506cSStephen Warren Returns: 121d201506cSStephen Warren The index within the patterns array of the pattern the process 122d201506cSStephen Warren emitted. 123d201506cSStephen Warren 124d201506cSStephen Warren Notable exceptions: 125d201506cSStephen Warren Timeout, if the process did not emit any of the patterns within 126d201506cSStephen Warren the expected time. 127e8debf39SStephen Warren """ 128d201506cSStephen Warren 129d201506cSStephen Warren for pi in xrange(len(patterns)): 130d201506cSStephen Warren if type(patterns[pi]) == type(''): 131d201506cSStephen Warren patterns[pi] = re.compile(patterns[pi]) 132d201506cSStephen Warren 133d314e247SStephen Warren tstart_s = time.time() 134d201506cSStephen Warren try: 135d201506cSStephen Warren while True: 136d201506cSStephen Warren earliest_m = None 137d201506cSStephen Warren earliest_pi = None 138d201506cSStephen Warren for pi in xrange(len(patterns)): 139d201506cSStephen Warren pattern = patterns[pi] 140d201506cSStephen Warren m = pattern.search(self.buf) 141d201506cSStephen Warren if not m: 142d201506cSStephen Warren continue 14344ac762bSStephen Warren if earliest_m and m.start() >= earliest_m.start(): 144d201506cSStephen Warren continue 145d201506cSStephen Warren earliest_m = m 146d201506cSStephen Warren earliest_pi = pi 147d201506cSStephen Warren if earliest_m: 148d201506cSStephen Warren pos = earliest_m.start() 149d8926811SStephen Warren posafter = earliest_m.end() 150d201506cSStephen Warren self.before = self.buf[:pos] 151d201506cSStephen Warren self.after = self.buf[pos:posafter] 152d201506cSStephen Warren self.buf = self.buf[posafter:] 153d201506cSStephen Warren return earliest_pi 154d314e247SStephen Warren tnow_s = time.time() 15589ab8410SStephen Warren if self.timeout: 156d314e247SStephen Warren tdelta_ms = (tnow_s - tstart_s) * 1000 15789ab8410SStephen Warren poll_maxwait = self.timeout - tdelta_ms 158d314e247SStephen Warren if tdelta_ms > self.timeout: 159d314e247SStephen Warren raise Timeout() 16089ab8410SStephen Warren else: 16189ab8410SStephen Warren poll_maxwait = None 16289ab8410SStephen Warren events = self.poll.poll(poll_maxwait) 163d201506cSStephen Warren if not events: 164d201506cSStephen Warren raise Timeout() 165d201506cSStephen Warren c = os.read(self.fd, 1024) 166d201506cSStephen Warren if not c: 167d201506cSStephen Warren raise EOFError() 168d201506cSStephen Warren if self.logfile_read: 169d201506cSStephen Warren self.logfile_read.write(c) 170d201506cSStephen Warren self.buf += c 171d201506cSStephen Warren finally: 172d201506cSStephen Warren if self.logfile_read: 173d201506cSStephen Warren self.logfile_read.flush() 174d201506cSStephen Warren 175d201506cSStephen Warren def close(self): 176e8debf39SStephen Warren """Close the stdio connection to the sub-process. 177d201506cSStephen Warren 178d201506cSStephen Warren This also waits a reasonable time for the sub-process to stop running. 179d201506cSStephen Warren 180d201506cSStephen Warren Args: 181d201506cSStephen Warren None. 182d201506cSStephen Warren 183d201506cSStephen Warren Returns: 184d201506cSStephen Warren Nothing. 185e8debf39SStephen Warren """ 186d201506cSStephen Warren 187d201506cSStephen Warren os.close(self.fd) 188d201506cSStephen Warren for i in xrange(100): 189d201506cSStephen Warren if not self.isalive(): 190d201506cSStephen Warren break 191d201506cSStephen Warren time.sleep(0.1) 192