xref: /rk3399_rockchip-uboot/test/py/u_boot_spawn.py (revision d201506cca782c54309b488170623094f252aab5)
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