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