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