xref: /rk3399_rockchip-uboot/test/py/u_boot_spawn.py (revision 93134e18e8772ad87a3c94d5d64970659835c944)
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
41d201506cSStephen Warren
42d201506cSStephen Warren        (self.pid, self.fd) = pty.fork()
43d201506cSStephen Warren        if self.pid == 0:
44d201506cSStephen Warren            try:
45d201506cSStephen Warren                # For some reason, SIGHUP is set to SIG_IGN at this point when
46d201506cSStephen Warren                # run under "go" (www.go.cd). Perhaps this happens under any
47d201506cSStephen Warren                # background (non-interactive) system?
48d201506cSStephen Warren                signal.signal(signal.SIGHUP, signal.SIG_DFL)
49d27f2fc1SStephen Warren                if cwd:
50d27f2fc1SStephen Warren                    os.chdir(cwd)
51d201506cSStephen Warren                os.execvp(args[0], args)
52d201506cSStephen Warren            except:
53d201506cSStephen Warren                print 'CHILD EXECEPTION:'
54d201506cSStephen Warren                import traceback
55d201506cSStephen Warren                traceback.print_exc()
56d201506cSStephen Warren            finally:
57d201506cSStephen Warren                os._exit(255)
58d201506cSStephen Warren
59*93134e18SStephen Warren        try:
60d201506cSStephen Warren            self.poll = select.poll()
61d201506cSStephen Warren            self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR | select.POLLHUP | select.POLLNVAL)
62*93134e18SStephen Warren        except:
63*93134e18SStephen Warren            self.close()
64*93134e18SStephen Warren            raise
65d201506cSStephen Warren
66d201506cSStephen Warren    def kill(self, sig):
67e8debf39SStephen Warren        """Send unix signal "sig" to the child process.
68d201506cSStephen Warren
69d201506cSStephen Warren        Args:
70d201506cSStephen Warren            sig: The signal number to send.
71d201506cSStephen Warren
72d201506cSStephen Warren        Returns:
73d201506cSStephen Warren            Nothing.
74e8debf39SStephen Warren        """
75d201506cSStephen Warren
76d201506cSStephen Warren        os.kill(self.pid, sig)
77d201506cSStephen Warren
78d201506cSStephen Warren    def isalive(self):
79e8debf39SStephen Warren        """Determine whether the child process is still running.
80d201506cSStephen Warren
81d201506cSStephen Warren        Args:
82d201506cSStephen Warren            None.
83d201506cSStephen Warren
84d201506cSStephen Warren        Returns:
85d201506cSStephen Warren            Boolean indicating whether process is alive.
86e8debf39SStephen Warren        """
87d201506cSStephen Warren
88d201506cSStephen Warren        if self.waited:
89d201506cSStephen Warren            return False
90d201506cSStephen Warren
91d201506cSStephen Warren        w = os.waitpid(self.pid, os.WNOHANG)
92d201506cSStephen Warren        if w[0] == 0:
93d201506cSStephen Warren            return True
94d201506cSStephen Warren
95d201506cSStephen Warren        self.waited = True
96d201506cSStephen Warren        return False
97d201506cSStephen Warren
98d201506cSStephen Warren    def send(self, data):
99e8debf39SStephen Warren        """Send data to the sub-process's stdin.
100d201506cSStephen Warren
101d201506cSStephen Warren        Args:
102d201506cSStephen Warren            data: The data to send to the process.
103d201506cSStephen Warren
104d201506cSStephen Warren        Returns:
105d201506cSStephen Warren            Nothing.
106e8debf39SStephen Warren        """
107d201506cSStephen Warren
108d201506cSStephen Warren        os.write(self.fd, data)
109d201506cSStephen Warren
110d201506cSStephen Warren    def expect(self, patterns):
111e8debf39SStephen Warren        """Wait for the sub-process to emit specific data.
112d201506cSStephen Warren
113d201506cSStephen Warren        This function waits for the process to emit one pattern from the
114d201506cSStephen Warren        supplied list of patterns, or for a timeout to occur.
115d201506cSStephen Warren
116d201506cSStephen Warren        Args:
117d201506cSStephen Warren            patterns: A list of strings or regex objects that we expect to
118d201506cSStephen Warren                see in the sub-process' stdout.
119d201506cSStephen Warren
120d201506cSStephen Warren        Returns:
121d201506cSStephen Warren            The index within the patterns array of the pattern the process
122d201506cSStephen Warren            emitted.
123d201506cSStephen Warren
124d201506cSStephen Warren        Notable exceptions:
125d201506cSStephen Warren            Timeout, if the process did not emit any of the patterns within
126d201506cSStephen Warren            the expected time.
127e8debf39SStephen Warren        """
128d201506cSStephen Warren
129d201506cSStephen Warren        for pi in xrange(len(patterns)):
130d201506cSStephen Warren            if type(patterns[pi]) == type(''):
131d201506cSStephen Warren                patterns[pi] = re.compile(patterns[pi])
132d201506cSStephen Warren
133d314e247SStephen Warren        tstart_s = time.time()
134d201506cSStephen Warren        try:
135d201506cSStephen Warren            while True:
136d201506cSStephen Warren                earliest_m = None
137d201506cSStephen Warren                earliest_pi = None
138d201506cSStephen Warren                for pi in xrange(len(patterns)):
139d201506cSStephen Warren                    pattern = patterns[pi]
140d201506cSStephen Warren                    m = pattern.search(self.buf)
141d201506cSStephen Warren                    if not m:
142d201506cSStephen Warren                        continue
14344ac762bSStephen Warren                    if earliest_m and m.start() >= earliest_m.start():
144d201506cSStephen Warren                        continue
145d201506cSStephen Warren                    earliest_m = m
146d201506cSStephen Warren                    earliest_pi = pi
147d201506cSStephen Warren                if earliest_m:
148d201506cSStephen Warren                    pos = earliest_m.start()
149d8926811SStephen Warren                    posafter = earliest_m.end()
150d201506cSStephen Warren                    self.before = self.buf[:pos]
151d201506cSStephen Warren                    self.after = self.buf[pos:posafter]
152d201506cSStephen Warren                    self.buf = self.buf[posafter:]
153d201506cSStephen Warren                    return earliest_pi
154d314e247SStephen Warren                tnow_s = time.time()
15589ab8410SStephen Warren                if self.timeout:
156d314e247SStephen Warren                    tdelta_ms = (tnow_s - tstart_s) * 1000
15789ab8410SStephen Warren                    poll_maxwait = self.timeout - tdelta_ms
158d314e247SStephen Warren                    if tdelta_ms > self.timeout:
159d314e247SStephen Warren                        raise Timeout()
16089ab8410SStephen Warren                else:
16189ab8410SStephen Warren                    poll_maxwait = None
16289ab8410SStephen Warren                events = self.poll.poll(poll_maxwait)
163d201506cSStephen Warren                if not events:
164d201506cSStephen Warren                    raise Timeout()
165d201506cSStephen Warren                c = os.read(self.fd, 1024)
166d201506cSStephen Warren                if not c:
167d201506cSStephen Warren                    raise EOFError()
168d201506cSStephen Warren                if self.logfile_read:
169d201506cSStephen Warren                    self.logfile_read.write(c)
170d201506cSStephen Warren                self.buf += c
171d201506cSStephen Warren        finally:
172d201506cSStephen Warren            if self.logfile_read:
173d201506cSStephen Warren                self.logfile_read.flush()
174d201506cSStephen Warren
175d201506cSStephen Warren    def close(self):
176e8debf39SStephen Warren        """Close the stdio connection to the sub-process.
177d201506cSStephen Warren
178d201506cSStephen Warren        This also waits a reasonable time for the sub-process to stop running.
179d201506cSStephen Warren
180d201506cSStephen Warren        Args:
181d201506cSStephen Warren            None.
182d201506cSStephen Warren
183d201506cSStephen Warren        Returns:
184d201506cSStephen Warren            Nothing.
185e8debf39SStephen Warren        """
186d201506cSStephen Warren
187d201506cSStephen Warren        os.close(self.fd)
188d201506cSStephen Warren        for i in xrange(100):
189d201506cSStephen Warren            if not self.isalive():
190d201506cSStephen Warren                break
191d201506cSStephen Warren            time.sleep(0.1)
192