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