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