blob: bcba7b5cd43bcff9a2e9c81cfd5b18d71cebe2d3 [file] [log] [blame]
Stephen Warren10e50632016-01-15 11:15:24 -07001# SPDX-License-Identifier: GPL-2.0
Tom Rini10e47792018-05-06 17:58:06 -04002# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
Stephen Warren10e50632016-01-15 11:15:24 -07003
Heinrich Schuchardt725bd312021-11-23 00:01:46 +01004"""
5Logic to spawn a sub-process and interact with its stdio.
6"""
Stephen Warren10e50632016-01-15 11:15:24 -07007
8import os
9import re
10import pty
11import signal
12import select
13import time
Heinrich Schuchardt725bd312021-11-23 00:01:46 +010014import traceback
Stephen Warren10e50632016-01-15 11:15:24 -070015
16class Timeout(Exception):
Stephen Warren75e731e2016-01-26 13:41:30 -070017 """An exception sub-class that indicates that a timeout occurred."""
Stephen Warren10e50632016-01-15 11:15:24 -070018
Simon Glass573171e2024-10-09 18:29:02 -060019class BootFail(Exception):
20 """An exception sub-class that indicates that a boot failure occurred.
21
22 This is used when a bad pattern is seen when waiting for the boot prompt.
23 It is regarded as fatal, to avoid trying to boot the again and again to no
24 avail.
25 """
26
27class Unexpected(Exception):
28 """An exception sub-class that indicates that unexpected test was seen."""
29
Heinrich Schuchardt725bd312021-11-23 00:01:46 +010030class Spawn:
Stephen Warren75e731e2016-01-26 13:41:30 -070031 """Represents the stdio of a freshly created sub-process. Commands may be
Stephen Warren10e50632016-01-15 11:15:24 -070032 sent to the process, and responses waited for.
Simon Glass9bc20832016-07-04 11:58:39 -060033
34 Members:
35 output: accumulated output from expect()
Stephen Warren75e731e2016-01-26 13:41:30 -070036 """
Stephen Warren10e50632016-01-15 11:15:24 -070037
Simon Glass3f0bd0c2024-06-23 14:30:30 -060038 def __init__(self, args, cwd=None, decode_signal=False):
Stephen Warren75e731e2016-01-26 13:41:30 -070039 """Spawn (fork/exec) the sub-process.
Stephen Warren10e50632016-01-15 11:15:24 -070040
41 Args:
Stephen Warrena85fce92016-01-27 23:57:53 -070042 args: array of processs arguments. argv[0] is the command to
43 execute.
44 cwd: the directory to run the process in, or None for no change.
Simon Glass3f0bd0c2024-06-23 14:30:30 -060045 decode_signal (bool): True to indicate the exception number when
46 something goes wrong
Stephen Warren10e50632016-01-15 11:15:24 -070047
48 Returns:
49 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070050 """
Simon Glass3f0bd0c2024-06-23 14:30:30 -060051 self.decode_signal = decode_signal
Stephen Warren10e50632016-01-15 11:15:24 -070052 self.waited = False
Simon Glassafd00042021-10-08 09:15:23 -060053 self.exit_code = 0
54 self.exit_info = ''
Stephen Warren10e50632016-01-15 11:15:24 -070055 self.buf = ''
Simon Glass9bc20832016-07-04 11:58:39 -060056 self.output = ''
Stephen Warren10e50632016-01-15 11:15:24 -070057 self.logfile_read = None
58 self.before = ''
59 self.after = ''
60 self.timeout = None
Stephen Warren8e7c3ec2016-07-06 10:34:30 -060061 # http://stackoverflow.com/questions/7857352/python-regex-to-match-vt100-escape-sequences
Tom Rini649bdaf2019-10-24 11:59:28 -040062 self.re_vt100 = re.compile(r'(\x1b\[|\x9b)[^@-_]*[@-_]|\x1b[@-_]', re.I)
Stephen Warren10e50632016-01-15 11:15:24 -070063
64 (self.pid, self.fd) = pty.fork()
65 if self.pid == 0:
66 try:
67 # For some reason, SIGHUP is set to SIG_IGN at this point when
68 # run under "go" (www.go.cd). Perhaps this happens under any
69 # background (non-interactive) system?
70 signal.signal(signal.SIGHUP, signal.SIG_DFL)
Stephen Warrena85fce92016-01-27 23:57:53 -070071 if cwd:
72 os.chdir(cwd)
Stephen Warren10e50632016-01-15 11:15:24 -070073 os.execvp(args[0], args)
74 except:
Paul Burton00f2d202017-09-14 14:34:43 -070075 print('CHILD EXECEPTION:')
Stephen Warren10e50632016-01-15 11:15:24 -070076 traceback.print_exc()
77 finally:
78 os._exit(255)
79
Stephen Warren65503db2016-02-10 16:54:37 -070080 try:
81 self.poll = select.poll()
Heinrich Schuchardt725bd312021-11-23 00:01:46 +010082 self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR |
83 select.POLLHUP | select.POLLNVAL)
Stephen Warren65503db2016-02-10 16:54:37 -070084 except:
85 self.close()
86 raise
Stephen Warren10e50632016-01-15 11:15:24 -070087
88 def kill(self, sig):
Stephen Warren75e731e2016-01-26 13:41:30 -070089 """Send unix signal "sig" to the child process.
Stephen Warren10e50632016-01-15 11:15:24 -070090
91 Args:
92 sig: The signal number to send.
93
94 Returns:
95 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070096 """
Stephen Warren10e50632016-01-15 11:15:24 -070097
98 os.kill(self.pid, sig)
99
Simon Glassafd00042021-10-08 09:15:23 -0600100 def checkalive(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700101 """Determine whether the child process is still running.
Stephen Warren10e50632016-01-15 11:15:24 -0700102
Stephen Warren10e50632016-01-15 11:15:24 -0700103 Returns:
Simon Glassafd00042021-10-08 09:15:23 -0600104 tuple:
105 True if process is alive, else False
106 0 if process is alive, else exit code of process
107 string describing what happened ('' or 'status/signal n')
Stephen Warren75e731e2016-01-26 13:41:30 -0700108 """
Stephen Warren10e50632016-01-15 11:15:24 -0700109
110 if self.waited:
Simon Glassafd00042021-10-08 09:15:23 -0600111 return False, self.exit_code, self.exit_info
Stephen Warren10e50632016-01-15 11:15:24 -0700112
113 w = os.waitpid(self.pid, os.WNOHANG)
114 if w[0] == 0:
Simon Glassafd00042021-10-08 09:15:23 -0600115 return True, 0, 'running'
116 status = w[1]
Stephen Warren10e50632016-01-15 11:15:24 -0700117
Simon Glassafd00042021-10-08 09:15:23 -0600118 if os.WIFEXITED(status):
119 self.exit_code = os.WEXITSTATUS(status)
120 self.exit_info = 'status %d' % self.exit_code
121 elif os.WIFSIGNALED(status):
122 signum = os.WTERMSIG(status)
123 self.exit_code = -signum
Heinrich Schuchardt725bd312021-11-23 00:01:46 +0100124 self.exit_info = 'signal %d (%s)' % (signum, signal.Signals(signum).name)
Stephen Warren10e50632016-01-15 11:15:24 -0700125 self.waited = True
Simon Glassafd00042021-10-08 09:15:23 -0600126 return False, self.exit_code, self.exit_info
127
128 def isalive(self):
129 """Determine whether the child process is still running.
130
131 Args:
132 None.
133
134 Returns:
135 Boolean indicating whether process is alive.
136 """
137 return self.checkalive()[0]
Stephen Warren10e50632016-01-15 11:15:24 -0700138
139 def send(self, data):
Stephen Warren75e731e2016-01-26 13:41:30 -0700140 """Send data to the sub-process's stdin.
Stephen Warren10e50632016-01-15 11:15:24 -0700141
142 Args:
143 data: The data to send to the process.
144
145 Returns:
146 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700147 """
Stephen Warren10e50632016-01-15 11:15:24 -0700148
Tom Rini6a990412019-10-24 11:59:21 -0400149 os.write(self.fd, data.encode(errors='replace'))
Stephen Warren10e50632016-01-15 11:15:24 -0700150
Simon Glassa5ab7ed2024-10-09 18:29:01 -0600151 def receive(self, num_bytes):
152 """Receive data from the sub-process's stdin.
153
154 Args:
155 num_bytes (int): Maximum number of bytes to read
156
157 Returns:
158 str: The data received
159
160 Raises:
161 ValueError if U-Boot died
162 """
163 try:
164 c = os.read(self.fd, num_bytes).decode(errors='replace')
165 except OSError as err:
166 # With sandbox, try to detect when U-Boot exits when it
167 # shouldn't and explain why. This is much more friendly than
168 # just dying with an I/O error
169 if self.decode_signal and err.errno == 5: # I/O error
170 alive, _, info = self.checkalive()
171 if alive:
172 raise err
173 raise ValueError('U-Boot exited with %s' % info)
174 raise
175 return c
176
Stephen Warren10e50632016-01-15 11:15:24 -0700177 def expect(self, patterns):
Stephen Warren75e731e2016-01-26 13:41:30 -0700178 """Wait for the sub-process to emit specific data.
Stephen Warren10e50632016-01-15 11:15:24 -0700179
180 This function waits for the process to emit one pattern from the
181 supplied list of patterns, or for a timeout to occur.
182
183 Args:
184 patterns: A list of strings or regex objects that we expect to
185 see in the sub-process' stdout.
186
187 Returns:
188 The index within the patterns array of the pattern the process
189 emitted.
190
191 Notable exceptions:
192 Timeout, if the process did not emit any of the patterns within
193 the expected time.
Stephen Warren75e731e2016-01-26 13:41:30 -0700194 """
Stephen Warren10e50632016-01-15 11:15:24 -0700195
Paul Burtond2849ed2017-09-14 14:34:44 -0700196 for pi in range(len(patterns)):
Stephen Warren10e50632016-01-15 11:15:24 -0700197 if type(patterns[pi]) == type(''):
198 patterns[pi] = re.compile(patterns[pi])
199
Stephen Warren22eba122016-01-22 12:30:07 -0700200 tstart_s = time.time()
Stephen Warren10e50632016-01-15 11:15:24 -0700201 try:
202 while True:
203 earliest_m = None
204 earliest_pi = None
Paul Burtond2849ed2017-09-14 14:34:44 -0700205 for pi in range(len(patterns)):
Stephen Warren10e50632016-01-15 11:15:24 -0700206 pattern = patterns[pi]
207 m = pattern.search(self.buf)
208 if not m:
209 continue
Stephen Warren3880dec2016-01-27 23:57:47 -0700210 if earliest_m and m.start() >= earliest_m.start():
Stephen Warren10e50632016-01-15 11:15:24 -0700211 continue
212 earliest_m = m
213 earliest_pi = pi
214 if earliest_m:
215 pos = earliest_m.start()
Stephen Warren4223a2f2016-02-05 18:04:42 -0700216 posafter = earliest_m.end()
Stephen Warren10e50632016-01-15 11:15:24 -0700217 self.before = self.buf[:pos]
218 self.after = self.buf[pos:posafter]
Simon Glass9bc20832016-07-04 11:58:39 -0600219 self.output += self.buf[:posafter]
Stephen Warren10e50632016-01-15 11:15:24 -0700220 self.buf = self.buf[posafter:]
221 return earliest_pi
Stephen Warren22eba122016-01-22 12:30:07 -0700222 tnow_s = time.time()
Stephen Warren33db1ee2016-02-04 16:11:50 -0700223 if self.timeout:
224 tdelta_ms = (tnow_s - tstart_s) * 1000
225 poll_maxwait = self.timeout - tdelta_ms
226 if tdelta_ms > self.timeout:
227 raise Timeout()
228 else:
229 poll_maxwait = None
230 events = self.poll.poll(poll_maxwait)
Stephen Warren10e50632016-01-15 11:15:24 -0700231 if not events:
232 raise Timeout()
Simon Glassa5ab7ed2024-10-09 18:29:01 -0600233 c = self.receive(1024)
Stephen Warren10e50632016-01-15 11:15:24 -0700234 if self.logfile_read:
235 self.logfile_read.write(c)
236 self.buf += c
Stephen Warren8e7c3ec2016-07-06 10:34:30 -0600237 # count=0 is supposed to be the default, which indicates
238 # unlimited substitutions, but in practice the version of
239 # Python in Ubuntu 14.04 appears to default to count=2!
240 self.buf = self.re_vt100.sub('', self.buf, count=1000000)
Stephen Warren10e50632016-01-15 11:15:24 -0700241 finally:
242 if self.logfile_read:
243 self.logfile_read.flush()
244
245 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700246 """Close the stdio connection to the sub-process.
Stephen Warren10e50632016-01-15 11:15:24 -0700247
248 This also waits a reasonable time for the sub-process to stop running.
249
250 Args:
251 None.
252
253 Returns:
254 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700255 """
Stephen Warren10e50632016-01-15 11:15:24 -0700256
257 os.close(self.fd)
Heinrich Schuchardt725bd312021-11-23 00:01:46 +0100258 for _ in range(100):
Stephen Warren10e50632016-01-15 11:15:24 -0700259 if not self.isalive():
260 break
261 time.sleep(0.1)
Simon Glass9bc20832016-07-04 11:58:39 -0600262
263 def get_expect_output(self):
264 """Return the output read by expect()
265
266 Returns:
267 The output processed by expect(), as a string.
268 """
269 return self.output