xref: /rk3399_rockchip-uboot/test/py/u_boot_spawn.py (revision 89ab841088f5ccc78f0d501641fc99ea4d8c26f2)
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
59d201506cSStephen Warren        self.poll = select.poll()
60d201506cSStephen Warren        self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR | select.POLLHUP | select.POLLNVAL)
61d201506cSStephen Warren
62d201506cSStephen Warren    def kill(self, sig):
63e8debf39SStephen Warren        """Send unix signal "sig" to the child process.
64d201506cSStephen Warren
65d201506cSStephen Warren        Args:
66d201506cSStephen Warren            sig: The signal number to send.
67d201506cSStephen Warren
68d201506cSStephen Warren        Returns:
69d201506cSStephen Warren            Nothing.
70e8debf39SStephen Warren        """
71d201506cSStephen Warren
72d201506cSStephen Warren        os.kill(self.pid, sig)
73d201506cSStephen Warren
74d201506cSStephen Warren    def isalive(self):
75e8debf39SStephen Warren        """Determine whether the child process is still running.
76d201506cSStephen Warren
77d201506cSStephen Warren        Args:
78d201506cSStephen Warren            None.
79d201506cSStephen Warren
80d201506cSStephen Warren        Returns:
81d201506cSStephen Warren            Boolean indicating whether process is alive.
82e8debf39SStephen Warren        """
83d201506cSStephen Warren
84d201506cSStephen Warren        if self.waited:
85d201506cSStephen Warren            return False
86d201506cSStephen Warren
87d201506cSStephen Warren        w = os.waitpid(self.pid, os.WNOHANG)
88d201506cSStephen Warren        if w[0] == 0:
89d201506cSStephen Warren            return True
90d201506cSStephen Warren
91d201506cSStephen Warren        self.waited = True
92d201506cSStephen Warren        return False
93d201506cSStephen Warren
94d201506cSStephen Warren    def send(self, data):
95e8debf39SStephen Warren        """Send data to the sub-process's stdin.
96d201506cSStephen Warren
97d201506cSStephen Warren        Args:
98d201506cSStephen Warren            data: The data to send to the process.
99d201506cSStephen Warren
100d201506cSStephen Warren        Returns:
101d201506cSStephen Warren            Nothing.
102e8debf39SStephen Warren        """
103d201506cSStephen Warren
104d201506cSStephen Warren        os.write(self.fd, data)
105d201506cSStephen Warren
106d201506cSStephen Warren    def expect(self, patterns):
107e8debf39SStephen Warren        """Wait for the sub-process to emit specific data.
108d201506cSStephen Warren
109d201506cSStephen Warren        This function waits for the process to emit one pattern from the
110d201506cSStephen Warren        supplied list of patterns, or for a timeout to occur.
111d201506cSStephen Warren
112d201506cSStephen Warren        Args:
113d201506cSStephen Warren            patterns: A list of strings or regex objects that we expect to
114d201506cSStephen Warren                see in the sub-process' stdout.
115d201506cSStephen Warren
116d201506cSStephen Warren        Returns:
117d201506cSStephen Warren            The index within the patterns array of the pattern the process
118d201506cSStephen Warren            emitted.
119d201506cSStephen Warren
120d201506cSStephen Warren        Notable exceptions:
121d201506cSStephen Warren            Timeout, if the process did not emit any of the patterns within
122d201506cSStephen Warren            the expected time.
123e8debf39SStephen Warren        """
124d201506cSStephen Warren
125d201506cSStephen Warren        for pi in xrange(len(patterns)):
126d201506cSStephen Warren            if type(patterns[pi]) == type(''):
127d201506cSStephen Warren                patterns[pi] = re.compile(patterns[pi])
128d201506cSStephen Warren
129d314e247SStephen Warren        tstart_s = time.time()
130d201506cSStephen Warren        try:
131d201506cSStephen Warren            while True:
132d201506cSStephen Warren                earliest_m = None
133d201506cSStephen Warren                earliest_pi = None
134d201506cSStephen Warren                for pi in xrange(len(patterns)):
135d201506cSStephen Warren                    pattern = patterns[pi]
136d201506cSStephen Warren                    m = pattern.search(self.buf)
137d201506cSStephen Warren                    if not m:
138d201506cSStephen Warren                        continue
13944ac762bSStephen Warren                    if earliest_m and m.start() >= earliest_m.start():
140d201506cSStephen Warren                        continue
141d201506cSStephen Warren                    earliest_m = m
142d201506cSStephen Warren                    earliest_pi = pi
143d201506cSStephen Warren                if earliest_m:
144d201506cSStephen Warren                    pos = earliest_m.start()
145d201506cSStephen Warren                    posafter = earliest_m.end() + 1
146d201506cSStephen Warren                    self.before = self.buf[:pos]
147d201506cSStephen Warren                    self.after = self.buf[pos:posafter]
148d201506cSStephen Warren                    self.buf = self.buf[posafter:]
149d201506cSStephen Warren                    return earliest_pi
150d314e247SStephen Warren                tnow_s = time.time()
151*89ab8410SStephen Warren                if self.timeout:
152d314e247SStephen Warren                    tdelta_ms = (tnow_s - tstart_s) * 1000
153*89ab8410SStephen Warren                    poll_maxwait = self.timeout - tdelta_ms
154d314e247SStephen Warren                    if tdelta_ms > self.timeout:
155d314e247SStephen Warren                        raise Timeout()
156*89ab8410SStephen Warren                else:
157*89ab8410SStephen Warren                    poll_maxwait = None
158*89ab8410SStephen Warren                events = self.poll.poll(poll_maxwait)
159d201506cSStephen Warren                if not events:
160d201506cSStephen Warren                    raise Timeout()
161d201506cSStephen Warren                c = os.read(self.fd, 1024)
162d201506cSStephen Warren                if not c:
163d201506cSStephen Warren                    raise EOFError()
164d201506cSStephen Warren                if self.logfile_read:
165d201506cSStephen Warren                    self.logfile_read.write(c)
166d201506cSStephen Warren                self.buf += c
167d201506cSStephen Warren        finally:
168d201506cSStephen Warren            if self.logfile_read:
169d201506cSStephen Warren                self.logfile_read.flush()
170d201506cSStephen Warren
171d201506cSStephen Warren    def close(self):
172e8debf39SStephen Warren        """Close the stdio connection to the sub-process.
173d201506cSStephen Warren
174d201506cSStephen Warren        This also waits a reasonable time for the sub-process to stop running.
175d201506cSStephen Warren
176d201506cSStephen Warren        Args:
177d201506cSStephen Warren            None.
178d201506cSStephen Warren
179d201506cSStephen Warren        Returns:
180d201506cSStephen Warren            Nothing.
181e8debf39SStephen Warren        """
182d201506cSStephen Warren
183d201506cSStephen Warren        os.close(self.fd)
184d201506cSStephen Warren        for i in xrange(100):
185d201506cSStephen Warren            if not self.isalive():
186d201506cSStephen Warren                break
187d201506cSStephen Warren            time.sleep(0.1)
188