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