1c342db35SBrad Bishop#
2eb8dc403SDave Cobbley# Copyright (C) 2013 Intel Corporation
3eb8dc403SDave Cobbley#
4c342db35SBrad Bishop# SPDX-License-Identifier: MIT
5c342db35SBrad Bishop#
6eb8dc403SDave Cobbley
7eb8dc403SDave Cobbley# This module provides a class for starting qemu images using runqemu.
8eb8dc403SDave Cobbley# It's used by testimage.bbclass.
9eb8dc403SDave Cobbley
10eb8dc403SDave Cobbleyimport subprocess
11eb8dc403SDave Cobbleyimport os
12eb8dc403SDave Cobbleyimport sys
13eb8dc403SDave Cobbleyimport time
14eb8dc403SDave Cobbleyimport signal
15eb8dc403SDave Cobbleyimport re
16eb8dc403SDave Cobbleyimport socket
17eb8dc403SDave Cobbleyimport select
18eb8dc403SDave Cobbleyimport errno
19eb8dc403SDave Cobbleyimport string
20eb8dc403SDave Cobbleyimport threading
21eb8dc403SDave Cobbleyimport codecs
22eb8dc403SDave Cobbleyimport logging
23c926e17cSAndrew Geisslerimport tempfile
24eb8dc403SDave Cobbleyfrom oeqa.utils.dump import HostDumper
2582c905dcSAndrew Geisslerfrom collections import defaultdict
26c926e17cSAndrew Geisslerimport importlib
27eb8dc403SDave Cobbley
28eb8dc403SDave Cobbley# Get Unicode non printable control chars
29eb8dc403SDave Cobbleycontrol_range = list(range(0,32))+list(range(127,160))
30eb8dc403SDave Cobbleycontrol_chars = [chr(x) for x in control_range
31eb8dc403SDave Cobbley                if chr(x) not in string.printable]
32eb8dc403SDave Cobbleyre_control_char = re.compile('[%s]' % re.escape("".join(control_chars)))
33eb8dc403SDave Cobbley
34eb8dc403SDave Cobbleyclass QemuRunner:
35eb8dc403SDave Cobbley
3619323693SBrad Bishop    def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds,
373b8a17c1SAndrew Geissler                 use_kvm, logger, use_slirp=False, serial_ports=2, boot_patterns = defaultdict(str), use_ovmf=False, workdir=None, tmpfsdir=None):
38eb8dc403SDave Cobbley
39eb8dc403SDave Cobbley        # Popen object for runqemu
40eb8dc403SDave Cobbley        self.runqemu = None
4182c905dcSAndrew Geissler        self.runqemu_exited = False
42eb8dc403SDave Cobbley        # pid of the qemu process that runqemu will start
43eb8dc403SDave Cobbley        self.qemupid = None
44eb8dc403SDave Cobbley        # target ip - from the command line or runqemu output
45eb8dc403SDave Cobbley        self.ip = None
46eb8dc403SDave Cobbley        # host ip - where qemu is running
47eb8dc403SDave Cobbley        self.server_ip = None
48eb8dc403SDave Cobbley        # target ip netmask
49eb8dc403SDave Cobbley        self.netmask = None
50eb8dc403SDave Cobbley
51eb8dc403SDave Cobbley        self.machine = machine
52eb8dc403SDave Cobbley        self.rootfs = rootfs
53eb8dc403SDave Cobbley        self.display = display
54eb8dc403SDave Cobbley        self.tmpdir = tmpdir
55eb8dc403SDave Cobbley        self.deploy_dir_image = deploy_dir_image
56eb8dc403SDave Cobbley        self.logfile = logfile
57eb8dc403SDave Cobbley        self.boottime = boottime
58eb8dc403SDave Cobbley        self.logged = False
59eb8dc403SDave Cobbley        self.thread = None
60eb8dc403SDave Cobbley        self.use_kvm = use_kvm
6182c905dcSAndrew Geissler        self.use_ovmf = use_ovmf
6219323693SBrad Bishop        self.use_slirp = use_slirp
6382c905dcSAndrew Geissler        self.serial_ports = serial_ports
64eb8dc403SDave Cobbley        self.msg = ''
6582c905dcSAndrew Geissler        self.boot_patterns = boot_patterns
663b8a17c1SAndrew Geissler        self.tmpfsdir = tmpfsdir
67eb8dc403SDave Cobbley
68eb8dc403SDave Cobbley        self.runqemutime = 120
69b7d28619SAndrew Geissler        if not workdir:
70b7d28619SAndrew Geissler            workdir = os.getcwd()
71b7d28619SAndrew Geissler        self.qemu_pidfile = workdir + '/pidfile_' + str(os.getpid())
72eb8dc403SDave Cobbley        self.host_dumper = HostDumper(dump_host_cmds, dump_dir)
7315ae2509SBrad Bishop        self.monitorpipe = None
74eb8dc403SDave Cobbley
75eb8dc403SDave Cobbley        self.logger = logger
76*ac69b488SWilliam A. Kennington III        # Whether we're expecting an exit and should show related errors
77*ac69b488SWilliam A. Kennington III        self.canexit = False
78eb8dc403SDave Cobbley
7982c905dcSAndrew Geissler        # Enable testing other OS's
8082c905dcSAndrew Geissler        # Set commands for target communication, and default to Linux ALWAYS
8182c905dcSAndrew Geissler        # Other OS's or baremetal applications need to provide their
8282c905dcSAndrew Geissler        # own implementation passing it through QemuRunner's constructor
8382c905dcSAndrew Geissler        # or by passing them through TESTIMAGE_BOOT_PATTERNS[flag]
8482c905dcSAndrew Geissler        # provided variables, where <flag> is one of the mentioned below.
8582c905dcSAndrew Geissler        accepted_patterns = ['search_reached_prompt', 'send_login_user', 'search_login_succeeded', 'search_cmd_finished']
8682c905dcSAndrew Geissler        default_boot_patterns = defaultdict(str)
8782c905dcSAndrew Geissler        # Default to the usual paterns used to communicate with the target
8882c905dcSAndrew Geissler        default_boot_patterns['search_reached_prompt'] = b' login:'
8982c905dcSAndrew Geissler        default_boot_patterns['send_login_user'] = 'root\n'
9082c905dcSAndrew Geissler        default_boot_patterns['search_login_succeeded'] = r"root@[a-zA-Z0-9\-]+:~#"
9182c905dcSAndrew Geissler        default_boot_patterns['search_cmd_finished'] = r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#"
9282c905dcSAndrew Geissler
9382c905dcSAndrew Geissler        # Only override patterns that were set e.g. login user TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n"
9482c905dcSAndrew Geissler        for pattern in accepted_patterns:
9582c905dcSAndrew Geissler            if not self.boot_patterns[pattern]:
9682c905dcSAndrew Geissler                self.boot_patterns[pattern] = default_boot_patterns[pattern]
9782c905dcSAndrew Geissler
98eb8dc403SDave Cobbley    def create_socket(self):
99eb8dc403SDave Cobbley        try:
100eb8dc403SDave Cobbley            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
101eb8dc403SDave Cobbley            sock.setblocking(0)
102eb8dc403SDave Cobbley            sock.bind(("127.0.0.1",0))
103eb8dc403SDave Cobbley            sock.listen(2)
104eb8dc403SDave Cobbley            port = sock.getsockname()[1]
105eb8dc403SDave Cobbley            self.logger.debug("Created listening socket for qemu serial console on: 127.0.0.1:%s" % port)
106eb8dc403SDave Cobbley            return (sock, port)
107eb8dc403SDave Cobbley
108eb8dc403SDave Cobbley        except socket.error:
109eb8dc403SDave Cobbley            sock.close()
110eb8dc403SDave Cobbley            raise
111eb8dc403SDave Cobbley
112eb8dc403SDave Cobbley    def log(self, msg):
113eb8dc403SDave Cobbley        if self.logfile:
114eb8dc403SDave Cobbley            # It is needed to sanitize the data received from qemu
115eb8dc403SDave Cobbley            # because is possible to have control characters
116eb8dc403SDave Cobbley            msg = msg.decode("utf-8", errors='ignore')
117eb8dc403SDave Cobbley            msg = re_control_char.sub('', msg)
118eb8dc403SDave Cobbley            self.msg += msg
119eb8dc403SDave Cobbley            with codecs.open(self.logfile, "a", encoding="utf-8") as f:
120eb8dc403SDave Cobbley                f.write("%s" % msg)
121eb8dc403SDave Cobbley
122eb8dc403SDave Cobbley    def getOutput(self, o):
123eb8dc403SDave Cobbley        import fcntl
124eb8dc403SDave Cobbley        fl = fcntl.fcntl(o, fcntl.F_GETFL)
125eb8dc403SDave Cobbley        fcntl.fcntl(o, fcntl.F_SETFL, fl | os.O_NONBLOCK)
126eb8dc403SDave Cobbley        return os.read(o.fileno(), 1000000).decode("utf-8")
127eb8dc403SDave Cobbley
128eb8dc403SDave Cobbley
129eb8dc403SDave Cobbley    def handleSIGCHLD(self, signum, frame):
130eb8dc403SDave Cobbley        if self.runqemu and self.runqemu.poll():
131eb8dc403SDave Cobbley            if self.runqemu.returncode:
13282c905dcSAndrew Geissler                self.logger.error('runqemu exited with code %d' % self.runqemu.returncode)
13382c905dcSAndrew Geissler                self.logger.error('Output from runqemu:\n%s' % self.getOutput(self.runqemu.stdout))
134eb8dc403SDave Cobbley                self.stop()
135eb8dc403SDave Cobbley                self._dump_host()
136eb8dc403SDave Cobbley
137eb8dc403SDave Cobbley    def start(self, qemuparams = None, get_ip = True, extra_bootparams = None, runqemuparams='', launch_cmd=None, discard_writes=True):
138eb8dc403SDave Cobbley        env = os.environ.copy()
139eb8dc403SDave Cobbley        if self.display:
140eb8dc403SDave Cobbley            env["DISPLAY"] = self.display
141eb8dc403SDave Cobbley            # Set this flag so that Qemu doesn't do any grabs as SDL grabs
142eb8dc403SDave Cobbley            # interact badly with screensavers.
143eb8dc403SDave Cobbley            env["QEMU_DONT_GRAB"] = "1"
144eb8dc403SDave Cobbley        if not os.path.exists(self.rootfs):
145eb8dc403SDave Cobbley            self.logger.error("Invalid rootfs %s" % self.rootfs)
146eb8dc403SDave Cobbley            return False
147eb8dc403SDave Cobbley        if not os.path.exists(self.tmpdir):
148eb8dc403SDave Cobbley            self.logger.error("Invalid TMPDIR path %s" % self.tmpdir)
149eb8dc403SDave Cobbley            return False
150eb8dc403SDave Cobbley        else:
151eb8dc403SDave Cobbley            env["OE_TMPDIR"] = self.tmpdir
152eb8dc403SDave Cobbley        if not os.path.exists(self.deploy_dir_image):
153eb8dc403SDave Cobbley            self.logger.error("Invalid DEPLOY_DIR_IMAGE path %s" % self.deploy_dir_image)
154eb8dc403SDave Cobbley            return False
155eb8dc403SDave Cobbley        else:
156eb8dc403SDave Cobbley            env["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image
157eb8dc403SDave Cobbley
1583b8a17c1SAndrew Geissler        if self.tmpfsdir:
1593b8a17c1SAndrew Geissler            env["RUNQEMU_TMPFS_DIR"] = self.tmpfsdir
1603b8a17c1SAndrew Geissler
161eb8dc403SDave Cobbley        if not launch_cmd:
16208902b01SBrad Bishop            launch_cmd = 'runqemu %s' % ('snapshot' if discard_writes else '')
163eb8dc403SDave Cobbley            if self.use_kvm:
164eb8dc403SDave Cobbley                self.logger.debug('Using kvm for runqemu')
165eb8dc403SDave Cobbley                launch_cmd += ' kvm'
166eb8dc403SDave Cobbley            else:
167eb8dc403SDave Cobbley                self.logger.debug('Not using kvm for runqemu')
168eb8dc403SDave Cobbley            if not self.display:
169eb8dc403SDave Cobbley                launch_cmd += ' nographic'
17019323693SBrad Bishop            if self.use_slirp:
17119323693SBrad Bishop                launch_cmd += ' slirp'
17282c905dcSAndrew Geissler            if self.use_ovmf:
17382c905dcSAndrew Geissler                launch_cmd += ' ovmf'
17408902b01SBrad Bishop            launch_cmd += ' %s %s %s' % (runqemuparams, self.machine, self.rootfs)
175eb8dc403SDave Cobbley
176eb8dc403SDave Cobbley        return self.launch(launch_cmd, qemuparams=qemuparams, get_ip=get_ip, extra_bootparams=extra_bootparams, env=env)
177eb8dc403SDave Cobbley
178eb8dc403SDave Cobbley    def launch(self, launch_cmd, get_ip = True, qemuparams = None, extra_bootparams = None, env = None):
179c926e17cSAndrew Geissler        # use logfile to determine the recipe-sysroot-native path and
180c926e17cSAndrew Geissler        # then add in the site-packages path components and add that
181c926e17cSAndrew Geissler        # to the python sys.path so qmp.py can be found.
182c926e17cSAndrew Geissler        python_path = os.path.dirname(os.path.dirname(self.logfile))
183c926e17cSAndrew Geissler        python_path += "/recipe-sysroot-native/usr/lib/python3.9/site-packages"
184c926e17cSAndrew Geissler        sys.path.append(python_path)
185c926e17cSAndrew Geissler        importlib.invalidate_caches()
186c926e17cSAndrew Geissler        try:
187c926e17cSAndrew Geissler            qmp = importlib.import_module("qmp")
188c926e17cSAndrew Geissler        except:
189c926e17cSAndrew Geissler            self.logger.error("qemurunner: qmp.py missing, please ensure it's installed")
190c926e17cSAndrew Geissler            return False
191c926e17cSAndrew Geissler        # Path relative to tmpdir used as cwd for qemu below to avoid unix socket path length issues
192c926e17cSAndrew Geissler        qmp_file = "." + next(tempfile._get_candidate_names())
193c926e17cSAndrew Geissler        qmp_param = ' -S -qmp unix:./%s,server,wait' % (qmp_file)
194c926e17cSAndrew Geissler        qmp_port = self.tmpdir + "/" + qmp_file
195c926e17cSAndrew Geissler
196eb8dc403SDave Cobbley        try:
19782c905dcSAndrew Geissler            if self.serial_ports >= 2:
198f86d0556SBrad Bishop                self.threadsock, threadport = self.create_socket()
199eb8dc403SDave Cobbley            self.server_socket, self.serverport = self.create_socket()
200eb8dc403SDave Cobbley        except socket.error as msg:
201eb8dc403SDave Cobbley            self.logger.error("Failed to create listening socket: %s" % msg[1])
202eb8dc403SDave Cobbley            return False
203eb8dc403SDave Cobbley
20495ac1b8dSAndrew Geissler        bootparams = ' printk.time=1'
205eb8dc403SDave Cobbley        if extra_bootparams:
206eb8dc403SDave Cobbley            bootparams = bootparams + ' ' + extra_bootparams
207eb8dc403SDave Cobbley
208eb8dc403SDave Cobbley        # Ask QEMU to store the QEMU process PID in file, this way we don't have to parse running processes
209eb8dc403SDave Cobbley        # and analyze descendents in order to determine it.
210eb8dc403SDave Cobbley        if os.path.exists(self.qemu_pidfile):
211eb8dc403SDave Cobbley            os.remove(self.qemu_pidfile)
212c926e17cSAndrew Geissler        self.qemuparams = 'bootparams="{0}" qemuparams="-pidfile {1} {2}"'.format(bootparams, self.qemu_pidfile, qmp_param)
213c926e17cSAndrew Geissler
214eb8dc403SDave Cobbley        if qemuparams:
215eb8dc403SDave Cobbley            self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"'
216eb8dc403SDave Cobbley
21782c905dcSAndrew Geissler        if self.serial_ports >= 2:
21815ae2509SBrad Bishop            launch_cmd += ' tcpserial=%s:%s %s' % (threadport, self.serverport, self.qemuparams)
21982c905dcSAndrew Geissler        else:
22082c905dcSAndrew Geissler            launch_cmd += ' tcpserial=%s %s' % (self.serverport, self.qemuparams)
221eb8dc403SDave Cobbley
222eb8dc403SDave Cobbley        self.origchldhandler = signal.getsignal(signal.SIGCHLD)
223eb8dc403SDave Cobbley        signal.signal(signal.SIGCHLD, self.handleSIGCHLD)
224eb8dc403SDave Cobbley
225eb8dc403SDave Cobbley        self.logger.debug('launchcmd=%s'%(launch_cmd))
226eb8dc403SDave Cobbley
227eb8dc403SDave Cobbley        # FIXME: We pass in stdin=subprocess.PIPE here to work around stty
228eb8dc403SDave Cobbley        # blocking at the end of the runqemu script when using this within
229eb8dc403SDave Cobbley        # oe-selftest (this makes stty error out immediately). There ought
230eb8dc403SDave Cobbley        # to be a proper fix but this will suffice for now.
231c926e17cSAndrew Geissler        self.runqemu = subprocess.Popen(launch_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, preexec_fn=os.setpgrp, env=env, cwd=self.tmpdir)
232eb8dc403SDave Cobbley        output = self.runqemu.stdout
233eb8dc403SDave Cobbley
234eb8dc403SDave Cobbley        #
235eb8dc403SDave Cobbley        # We need the preexec_fn above so that all runqemu processes can easily be killed
236eb8dc403SDave Cobbley        # (by killing their process group). This presents a problem if this controlling
237eb8dc403SDave Cobbley        # process itself is killed however since those processes don't notice the death
238eb8dc403SDave Cobbley        # of the parent and merrily continue on.
239eb8dc403SDave Cobbley        #
240eb8dc403SDave Cobbley        # Rather than hack runqemu to deal with this, we add something here instead.
241eb8dc403SDave Cobbley        # Basically we fork off another process which holds an open pipe to the parent
242eb8dc403SDave Cobbley        # and also is setpgrp. If/when the pipe sees EOF from the parent dieing, it kills
243eb8dc403SDave Cobbley        # the process group. This is like pctrl's PDEATHSIG but for a process group
244eb8dc403SDave Cobbley        # rather than a single process.
245eb8dc403SDave Cobbley        #
246eb8dc403SDave Cobbley        r, w = os.pipe()
247eb8dc403SDave Cobbley        self.monitorpid = os.fork()
248eb8dc403SDave Cobbley        if self.monitorpid:
249eb8dc403SDave Cobbley            os.close(r)
250eb8dc403SDave Cobbley            self.monitorpipe = os.fdopen(w, "w")
251eb8dc403SDave Cobbley        else:
252eb8dc403SDave Cobbley            # child process
253eb8dc403SDave Cobbley            os.setpgrp()
254eb8dc403SDave Cobbley            os.close(w)
255eb8dc403SDave Cobbley            r = os.fdopen(r)
256eb8dc403SDave Cobbley            x = r.read()
257eb8dc403SDave Cobbley            os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
258eb8dc403SDave Cobbley            sys.exit(0)
259eb8dc403SDave Cobbley
260eb8dc403SDave Cobbley        self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid)
261eb8dc403SDave Cobbley        self.logger.debug("waiting at most %s seconds for qemu pid (%s)" %
262eb8dc403SDave Cobbley                          (self.runqemutime, time.strftime("%D %H:%M:%S")))
263eb8dc403SDave Cobbley        endtime = time.time() + self.runqemutime
264eb8dc403SDave Cobbley        while not self.is_alive() and time.time() < endtime:
265eb8dc403SDave Cobbley            if self.runqemu.poll():
26682c905dcSAndrew Geissler                if self.runqemu_exited:
267c926e17cSAndrew Geissler                    self.logger.warning("runqemu during is_alive() test")
26882c905dcSAndrew Geissler                    return False
269eb8dc403SDave Cobbley                if self.runqemu.returncode:
270eb8dc403SDave Cobbley                    # No point waiting any longer
27196ff1984SBrad Bishop                    self.logger.warning('runqemu exited with code %d' % self.runqemu.returncode)
272eb8dc403SDave Cobbley                    self._dump_host()
27396ff1984SBrad Bishop                    self.logger.warning("Output from runqemu:\n%s" % self.getOutput(output))
274f86d0556SBrad Bishop                    self.stop()
275eb8dc403SDave Cobbley                    return False
276eb8dc403SDave Cobbley            time.sleep(0.5)
277eb8dc403SDave Cobbley
27882c905dcSAndrew Geissler        if self.runqemu_exited:
279c926e17cSAndrew Geissler            self.logger.warning("runqemu after timeout")
28082c905dcSAndrew Geissler
281c926e17cSAndrew Geissler        if self.runqemu.returncode:
282c926e17cSAndrew Geissler            self.logger.warning('runqemu exited with code %d' % self.runqemu.returncode)
283c926e17cSAndrew Geissler
284c926e17cSAndrew Geissler        if not self.is_alive():
285*ac69b488SWilliam A. Kennington III            self.logger.error("Qemu pid didn't appear in %s seconds (%s)" %
286*ac69b488SWilliam A. Kennington III                              (self.runqemutime, time.strftime("%D %H:%M:%S")))
287*ac69b488SWilliam A. Kennington III
288*ac69b488SWilliam A. Kennington III            qemu_pid = None
289*ac69b488SWilliam A. Kennington III            if os.path.isfile(self.qemu_pidfile):
290*ac69b488SWilliam A. Kennington III                with open(self.qemu_pidfile, 'r') as f:
291*ac69b488SWilliam A. Kennington III                    qemu_pid = f.read().strip()
292*ac69b488SWilliam A. Kennington III
293*ac69b488SWilliam A. Kennington III            self.logger.error("Status information, poll status: %s, pidfile exists: %s, pidfile contents %s, proc pid exists %s"
294*ac69b488SWilliam A. Kennington III                % (self.runqemu.poll(), os.path.isfile(self.qemu_pidfile), str(qemu_pid), os.path.exists("/proc/" + str(qemu_pid))))
295*ac69b488SWilliam A. Kennington III
296*ac69b488SWilliam A. Kennington III            # Dump all processes to help us to figure out what is going on...
297*ac69b488SWilliam A. Kennington III            ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,pri,ni,command '], stdout=subprocess.PIPE).communicate()[0]
298*ac69b488SWilliam A. Kennington III            processes = ps.decode("utf-8")
299*ac69b488SWilliam A. Kennington III            self.logger.debug("Running processes:\n%s" % processes)
300*ac69b488SWilliam A. Kennington III            self._dump_host()
301*ac69b488SWilliam A. Kennington III            op = self.getOutput(output)
302*ac69b488SWilliam A. Kennington III            self.stop()
303*ac69b488SWilliam A. Kennington III            if op:
304*ac69b488SWilliam A. Kennington III                self.logger.error("Output from runqemu:\n%s" % op)
305*ac69b488SWilliam A. Kennington III            else:
306*ac69b488SWilliam A. Kennington III                self.logger.error("No output from runqemu.\n")
307c926e17cSAndrew Geissler            return False
308c926e17cSAndrew Geissler
309c926e17cSAndrew Geissler        # Create the client socket for the QEMU Monitor Control Socket
310c926e17cSAndrew Geissler        # This will allow us to read status from Qemu if the the process
311c926e17cSAndrew Geissler        # is still alive
312c926e17cSAndrew Geissler        self.logger.debug("QMP Initializing to %s" % (qmp_port))
313c926e17cSAndrew Geissler        # chdir dance for path length issues with unix sockets
314c926e17cSAndrew Geissler        origpath = os.getcwd()
315c926e17cSAndrew Geissler        try:
316c926e17cSAndrew Geissler            os.chdir(os.path.dirname(qmp_port))
317c926e17cSAndrew Geissler            try:
318c926e17cSAndrew Geissler               self.qmp = qmp.QEMUMonitorProtocol(os.path.basename(qmp_port))
319c926e17cSAndrew Geissler            except OSError as msg:
320c926e17cSAndrew Geissler                self.logger.warning("Failed to initialize qemu monitor socket: %s File: %s" % (msg, msg.filename))
321c926e17cSAndrew Geissler                return False
322c926e17cSAndrew Geissler
323c926e17cSAndrew Geissler            self.logger.debug("QMP Connecting to %s" % (qmp_port))
324c926e17cSAndrew Geissler            if not os.path.exists(qmp_port) and self.is_alive():
325c926e17cSAndrew Geissler                self.logger.debug("QMP Port does not exist waiting for it to be created")
326c926e17cSAndrew Geissler                endtime = time.time() + self.runqemutime
327c926e17cSAndrew Geissler                while not os.path.exists(qmp_port) and self.is_alive() and time.time() < endtime:
328c926e17cSAndrew Geissler                   self.logger.info("QMP port does not exist yet!")
329c926e17cSAndrew Geissler                   time.sleep(0.5)
330c926e17cSAndrew Geissler                if not os.path.exists(qmp_port) and self.is_alive():
331c926e17cSAndrew Geissler                    self.logger.warning("QMP Port still does not exist but QEMU is alive")
332c926e17cSAndrew Geissler                    return False
333c926e17cSAndrew Geissler
334c926e17cSAndrew Geissler            try:
335c926e17cSAndrew Geissler                self.qmp.connect()
336c926e17cSAndrew Geissler            except OSError as msg:
337c926e17cSAndrew Geissler                self.logger.warning("Failed to connect qemu monitor socket: %s File: %s" % (msg, msg.filename))
338c926e17cSAndrew Geissler                return False
339c926e17cSAndrew Geissler            except qmp.QMPConnectError as msg:
340c926e17cSAndrew Geissler                self.logger.warning("Failed to communicate with qemu monitor: %s" % (msg))
341c926e17cSAndrew Geissler                return False
342c926e17cSAndrew Geissler        finally:
343c926e17cSAndrew Geissler            os.chdir(origpath)
344c926e17cSAndrew Geissler
345c926e17cSAndrew Geissler        # Release the qemu porcess to continue running
346c926e17cSAndrew Geissler        self.run_monitor('cont')
347c926e17cSAndrew Geissler
348eb8dc403SDave Cobbley        # We are alive: qemu is running
349eb8dc403SDave Cobbley        out = self.getOutput(output)
350eb8dc403SDave Cobbley        netconf = False # network configuration is not required by default
351eb8dc403SDave Cobbley        self.logger.debug("qemu started in %s seconds - qemu procces pid is %s (%s)" %
352eb8dc403SDave Cobbley                          (time.time() - (endtime - self.runqemutime),
353eb8dc403SDave Cobbley                           self.qemupid, time.strftime("%D %H:%M:%S")))
354eb8dc403SDave Cobbley        cmdline = ''
35582c905dcSAndrew Geissler        if get_ip:
356eb8dc403SDave Cobbley            with open('/proc/%s/cmdline' % self.qemupid) as p:
357eb8dc403SDave Cobbley                cmdline = p.read()
358eb8dc403SDave Cobbley                # It is needed to sanitize the data received
359eb8dc403SDave Cobbley                # because is possible to have control characters
360eb8dc403SDave Cobbley                cmdline = re_control_char.sub(' ', cmdline)
361eb8dc403SDave Cobbley            try:
36219323693SBrad Bishop                if self.use_slirp:
36319323693SBrad Bishop                    tcp_ports = cmdline.split("hostfwd=tcp::")[1]
36419323693SBrad Bishop                    host_port = tcp_ports[:tcp_ports.find('-')]
36519323693SBrad Bishop                    self.ip = "localhost:%s" % host_port
36619323693SBrad Bishop                else:
367f86d0556SBrad Bishop                    ips = re.findall(r"((?:[0-9]{1,3}\.){3}[0-9]{1,3})", cmdline.split("ip=")[1])
368eb8dc403SDave Cobbley                    self.ip = ips[0]
369eb8dc403SDave Cobbley                    self.server_ip = ips[1]
370eb8dc403SDave Cobbley                self.logger.debug("qemu cmdline used:\n{}".format(cmdline))
371eb8dc403SDave Cobbley            except (IndexError, ValueError):
372eb8dc403SDave Cobbley                # Try to get network configuration from runqemu output
3734ed12e16SAndrew Geissler                match = re.match(r'.*Network configuration: (?:ip=)*([0-9.]+)::([0-9.]+):([0-9.]+)$.*',
374eb8dc403SDave Cobbley                                 out, re.MULTILINE|re.DOTALL)
375eb8dc403SDave Cobbley                if match:
376eb8dc403SDave Cobbley                    self.ip, self.server_ip, self.netmask = match.groups()
377eb8dc403SDave Cobbley                    # network configuration is required as we couldn't get it
378eb8dc403SDave Cobbley                    # from the runqemu command line, so qemu doesn't run kernel
379eb8dc403SDave Cobbley                    # and guest networking is not configured
380eb8dc403SDave Cobbley                    netconf = True
381eb8dc403SDave Cobbley                else:
382eb8dc403SDave Cobbley                    self.logger.error("Couldn't get ip from qemu command line and runqemu output! "
383eb8dc403SDave Cobbley                                 "Here is the qemu command line used:\n%s\n"
384eb8dc403SDave Cobbley                                 "and output from runqemu:\n%s" % (cmdline, out))
385eb8dc403SDave Cobbley                    self._dump_host()
386eb8dc403SDave Cobbley                    self.stop()
387eb8dc403SDave Cobbley                    return False
388eb8dc403SDave Cobbley
389eb8dc403SDave Cobbley        self.logger.debug("Target IP: %s" % self.ip)
390eb8dc403SDave Cobbley        self.logger.debug("Server IP: %s" % self.server_ip)
391eb8dc403SDave Cobbley
39282c905dcSAndrew Geissler        if self.serial_ports >= 2:
393f86d0556SBrad Bishop            self.thread = LoggingThread(self.log, self.threadsock, self.logger)
394eb8dc403SDave Cobbley            self.thread.start()
395eb8dc403SDave Cobbley            if not self.thread.connection_established.wait(self.boottime):
396eb8dc403SDave Cobbley                self.logger.error("Didn't receive a console connection from qemu. "
397eb8dc403SDave Cobbley                             "Here is the qemu command line used:\n%s\nand "
398eb8dc403SDave Cobbley                             "output from runqemu:\n%s" % (cmdline, out))
399eb8dc403SDave Cobbley                self.stop_thread()
400eb8dc403SDave Cobbley                return False
401eb8dc403SDave Cobbley
402eb8dc403SDave Cobbley        self.logger.debug("Output from runqemu:\n%s", out)
403eb8dc403SDave Cobbley        self.logger.debug("Waiting at most %d seconds for login banner (%s)" %
404eb8dc403SDave Cobbley                          (self.boottime, time.strftime("%D %H:%M:%S")))
405eb8dc403SDave Cobbley        endtime = time.time() + self.boottime
406eb8dc403SDave Cobbley        socklist = [self.server_socket]
407eb8dc403SDave Cobbley        reachedlogin = False
408eb8dc403SDave Cobbley        stopread = False
409eb8dc403SDave Cobbley        qemusock = None
410eb8dc403SDave Cobbley        bootlog = b''
411eb8dc403SDave Cobbley        data = b''
412eb8dc403SDave Cobbley        while time.time() < endtime and not stopread:
413eb8dc403SDave Cobbley            try:
414eb8dc403SDave Cobbley                sread, swrite, serror = select.select(socklist, [], [], 5)
415eb8dc403SDave Cobbley            except InterruptedError:
416eb8dc403SDave Cobbley                continue
417eb8dc403SDave Cobbley            for sock in sread:
418eb8dc403SDave Cobbley                if sock is self.server_socket:
419eb8dc403SDave Cobbley                    qemusock, addr = self.server_socket.accept()
420eb8dc403SDave Cobbley                    qemusock.setblocking(0)
421eb8dc403SDave Cobbley                    socklist.append(qemusock)
422eb8dc403SDave Cobbley                    socklist.remove(self.server_socket)
423eb8dc403SDave Cobbley                    self.logger.debug("Connection from %s:%s" % addr)
424eb8dc403SDave Cobbley                else:
425eb8dc403SDave Cobbley                    data = data + sock.recv(1024)
426eb8dc403SDave Cobbley                    if data:
427eb8dc403SDave Cobbley                        bootlog += data
42882c905dcSAndrew Geissler                        if self.serial_ports < 2:
42982c905dcSAndrew Geissler                            # this socket has mixed console/kernel data, log it to logfile
43082c905dcSAndrew Geissler                            self.log(data)
43182c905dcSAndrew Geissler
432eb8dc403SDave Cobbley                        data = b''
43382c905dcSAndrew Geissler                        if self.boot_patterns['search_reached_prompt'] in bootlog:
434eb8dc403SDave Cobbley                            self.server_socket = qemusock
435eb8dc403SDave Cobbley                            stopread = True
436eb8dc403SDave Cobbley                            reachedlogin = True
437eb8dc403SDave Cobbley                            self.logger.debug("Reached login banner in %s seconds (%s)" %
438eb8dc403SDave Cobbley                                              (time.time() - (endtime - self.boottime),
439eb8dc403SDave Cobbley                                              time.strftime("%D %H:%M:%S")))
440eb8dc403SDave Cobbley                    else:
441eb8dc403SDave Cobbley                        # no need to check if reachedlogin unless we support multiple connections
442eb8dc403SDave Cobbley                        self.logger.debug("QEMU socket disconnected before login banner reached. (%s)" %
443eb8dc403SDave Cobbley                                          time.strftime("%D %H:%M:%S"))
444eb8dc403SDave Cobbley                        socklist.remove(sock)
445eb8dc403SDave Cobbley                        sock.close()
446eb8dc403SDave Cobbley                        stopread = True
447eb8dc403SDave Cobbley
448eb8dc403SDave Cobbley        if not reachedlogin:
449eb8dc403SDave Cobbley            if time.time() >= endtime:
45096ff1984SBrad Bishop                self.logger.warning("Target didn't reach login banner in %d seconds (%s)" %
451eb8dc403SDave Cobbley                                  (self.boottime, time.strftime("%D %H:%M:%S")))
452eb8dc403SDave Cobbley            tail = lambda l: "\n".join(l.splitlines()[-25:])
453d89cb5f0SBrad Bishop            bootlog = bootlog.decode("utf-8")
454eb8dc403SDave Cobbley            # in case bootlog is empty, use tail qemu log store at self.msg
455eb8dc403SDave Cobbley            lines = tail(bootlog if bootlog else self.msg)
45696ff1984SBrad Bishop            self.logger.warning("Last 25 lines of text:\n%s" % lines)
45796ff1984SBrad Bishop            self.logger.warning("Check full boot log: %s" % self.logfile)
458eb8dc403SDave Cobbley            self._dump_host()
459eb8dc403SDave Cobbley            self.stop()
460eb8dc403SDave Cobbley            return False
461eb8dc403SDave Cobbley
462eb8dc403SDave Cobbley        # If we are not able to login the tests can continue
463eb8dc403SDave Cobbley        try:
464c3d88e4dSAndrew Geissler            (status, output) = self.run_serial(self.boot_patterns['send_login_user'], raw=True, timeout=120)
46582c905dcSAndrew Geissler            if re.search(self.boot_patterns['search_login_succeeded'], output):
466eb8dc403SDave Cobbley                self.logged = True
467eb8dc403SDave Cobbley                self.logger.debug("Logged as root in serial console")
468eb8dc403SDave Cobbley                if netconf:
469eb8dc403SDave Cobbley                    # configure guest networking
470eb8dc403SDave Cobbley                    cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip, self.netmask)
471eb8dc403SDave Cobbley                    output = self.run_serial(cmd, raw=True)[1]
472f86d0556SBrad Bishop                    if re.search(r"root@[a-zA-Z0-9\-]+:~#", output):
473eb8dc403SDave Cobbley                        self.logger.debug("configured ip address %s", self.ip)
474eb8dc403SDave Cobbley                    else:
475eb8dc403SDave Cobbley                        self.logger.debug("Couldn't configure guest networking")
476eb8dc403SDave Cobbley            else:
47796ff1984SBrad Bishop                self.logger.warning("Couldn't login into serial console"
478eb8dc403SDave Cobbley                            " as root using blank password")
47996ff1984SBrad Bishop                self.logger.warning("The output:\n%s" % output)
480eb8dc403SDave Cobbley        except:
48196ff1984SBrad Bishop            self.logger.warning("Serial console failed while trying to login")
482eb8dc403SDave Cobbley        return True
483eb8dc403SDave Cobbley
484eb8dc403SDave Cobbley    def stop(self):
485eb8dc403SDave Cobbley        if hasattr(self, "origchldhandler"):
486eb8dc403SDave Cobbley            signal.signal(signal.SIGCHLD, self.origchldhandler)
4871a4b7ee2SBrad Bishop        self.stop_thread()
4881a4b7ee2SBrad Bishop        self.stop_qemu_system()
489eb8dc403SDave Cobbley        if self.runqemu:
490eb8dc403SDave Cobbley            if hasattr(self, "monitorpid"):
491eb8dc403SDave Cobbley                os.kill(self.monitorpid, signal.SIGKILL)
492eb8dc403SDave Cobbley                self.logger.debug("Sending SIGTERM to runqemu")
493eb8dc403SDave Cobbley                try:
494eb8dc403SDave Cobbley                    os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
495eb8dc403SDave Cobbley                except OSError as e:
496eb8dc403SDave Cobbley                    if e.errno != errno.ESRCH:
497eb8dc403SDave Cobbley                        raise
498eb8dc403SDave Cobbley            endtime = time.time() + self.runqemutime
499eb8dc403SDave Cobbley            while self.runqemu.poll() is None and time.time() < endtime:
500eb8dc403SDave Cobbley                time.sleep(1)
501eb8dc403SDave Cobbley            if self.runqemu.poll() is None:
502eb8dc403SDave Cobbley                self.logger.debug("Sending SIGKILL to runqemu")
503eb8dc403SDave Cobbley                os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL)
504f86d0556SBrad Bishop            self.runqemu.stdin.close()
505f86d0556SBrad Bishop            self.runqemu.stdout.close()
50682c905dcSAndrew Geissler            self.runqemu_exited = True
507f86d0556SBrad Bishop
508c926e17cSAndrew Geissler        if hasattr(self, 'qmp') and self.qmp:
509c926e17cSAndrew Geissler            self.qmp.close()
510c926e17cSAndrew Geissler            self.qmp = None
511eb8dc403SDave Cobbley        if hasattr(self, 'server_socket') and self.server_socket:
512eb8dc403SDave Cobbley            self.server_socket.close()
513eb8dc403SDave Cobbley            self.server_socket = None
514f86d0556SBrad Bishop        if hasattr(self, 'threadsock') and self.threadsock:
515f86d0556SBrad Bishop            self.threadsock.close()
516f86d0556SBrad Bishop            self.threadsock = None
517eb8dc403SDave Cobbley        self.qemupid = None
518eb8dc403SDave Cobbley        self.ip = None
519eb8dc403SDave Cobbley        if os.path.exists(self.qemu_pidfile):
52082c905dcSAndrew Geissler            try:
521eb8dc403SDave Cobbley                os.remove(self.qemu_pidfile)
52282c905dcSAndrew Geissler            except FileNotFoundError as e:
52382c905dcSAndrew Geissler                # We raced, ignore
52482c905dcSAndrew Geissler                pass
525f86d0556SBrad Bishop        if self.monitorpipe:
526f86d0556SBrad Bishop            self.monitorpipe.close()
527eb8dc403SDave Cobbley
528eb8dc403SDave Cobbley    def stop_qemu_system(self):
529eb8dc403SDave Cobbley        if self.qemupid:
530eb8dc403SDave Cobbley            try:
531eb8dc403SDave Cobbley                # qemu-system behaves well and a SIGTERM is enough
532eb8dc403SDave Cobbley                os.kill(self.qemupid, signal.SIGTERM)
533eb8dc403SDave Cobbley            except ProcessLookupError as e:
5341a4b7ee2SBrad Bishop                self.logger.warning('qemu-system ended unexpectedly')
535eb8dc403SDave Cobbley
536eb8dc403SDave Cobbley    def stop_thread(self):
537eb8dc403SDave Cobbley        if self.thread and self.thread.is_alive():
538eb8dc403SDave Cobbley            self.thread.stop()
539eb8dc403SDave Cobbley            self.thread.join()
540eb8dc403SDave Cobbley
541c926e17cSAndrew Geissler    def allowexit(self):
542*ac69b488SWilliam A. Kennington III        self.canexit = True
543c926e17cSAndrew Geissler        if self.thread:
544c926e17cSAndrew Geissler            self.thread.allowexit()
545c926e17cSAndrew Geissler
546eb8dc403SDave Cobbley    def restart(self, qemuparams = None):
54796ff1984SBrad Bishop        self.logger.warning("Restarting qemu process")
548eb8dc403SDave Cobbley        if self.runqemu.poll() is None:
549eb8dc403SDave Cobbley            self.stop()
550eb8dc403SDave Cobbley        if self.start(qemuparams):
551eb8dc403SDave Cobbley            return True
552eb8dc403SDave Cobbley        return False
553eb8dc403SDave Cobbley
554eb8dc403SDave Cobbley    def is_alive(self):
55582c905dcSAndrew Geissler        if not self.runqemu or self.runqemu.poll() is not None or self.runqemu_exited:
556eb8dc403SDave Cobbley            return False
557eb8dc403SDave Cobbley        if os.path.isfile(self.qemu_pidfile):
55896ff1984SBrad Bishop            # when handling pidfile, qemu creates the file, stat it, lock it and then write to it
55996ff1984SBrad Bishop            # so it's possible that the file has been created but the content is empty
56096ff1984SBrad Bishop            pidfile_timeout = time.time() + 3
56196ff1984SBrad Bishop            while time.time() < pidfile_timeout:
56296ff1984SBrad Bishop                with open(self.qemu_pidfile, 'r') as f:
56396ff1984SBrad Bishop                    qemu_pid = f.read().strip()
56496ff1984SBrad Bishop                # file created but not yet written contents
56596ff1984SBrad Bishop                if not qemu_pid:
56696ff1984SBrad Bishop                    time.sleep(0.5)
56796ff1984SBrad Bishop                    continue
56896ff1984SBrad Bishop                else:
56996ff1984SBrad Bishop                    if os.path.exists("/proc/" + qemu_pid):
57096ff1984SBrad Bishop                        self.qemupid = int(qemu_pid)
571eb8dc403SDave Cobbley                        return True
572eb8dc403SDave Cobbley        return False
573eb8dc403SDave Cobbley
574c926e17cSAndrew Geissler    def run_monitor(self, command, timeout=60):
575c926e17cSAndrew Geissler        return self.qmp.cmd(command)
576c926e17cSAndrew Geissler
577977dc1acSBrad Bishop    def run_serial(self, command, raw=False, timeout=60):
578eb8dc403SDave Cobbley        # We assume target system have echo to get command status
579eb8dc403SDave Cobbley        if not raw:
580eb8dc403SDave Cobbley            command = "%s; echo $?\n" % command
581eb8dc403SDave Cobbley
582eb8dc403SDave Cobbley        data = ''
583eb8dc403SDave Cobbley        status = 0
584eb8dc403SDave Cobbley        self.server_socket.sendall(command.encode('utf-8'))
585eb8dc403SDave Cobbley        start = time.time()
586eb8dc403SDave Cobbley        end = start + timeout
587eb8dc403SDave Cobbley        while True:
588eb8dc403SDave Cobbley            now = time.time()
589eb8dc403SDave Cobbley            if now >= end:
590eb8dc403SDave Cobbley                data += "<<< run_serial(): command timed out after %d seconds without output >>>\r\n\r\n" % timeout
591eb8dc403SDave Cobbley                break
592eb8dc403SDave Cobbley            try:
593eb8dc403SDave Cobbley                sread, _, _ = select.select([self.server_socket],[],[], end - now)
594eb8dc403SDave Cobbley            except InterruptedError:
595eb8dc403SDave Cobbley                continue
596eb8dc403SDave Cobbley            if sread:
597eb8dc403SDave Cobbley                answer = self.server_socket.recv(1024)
598eb8dc403SDave Cobbley                if answer:
599eb8dc403SDave Cobbley                    data += answer.decode('utf-8')
600eb8dc403SDave Cobbley                    # Search the prompt to stop
60182c905dcSAndrew Geissler                    if re.search(self.boot_patterns['search_cmd_finished'], data):
602eb8dc403SDave Cobbley                        break
603eb8dc403SDave Cobbley                else:
604*ac69b488SWilliam A. Kennington III                    if self.canexit:
605*ac69b488SWilliam A. Kennington III                        return (1, "")
606*ac69b488SWilliam A. Kennington III                    raise Exception("No data on serial console socket, connection closed?")
607eb8dc403SDave Cobbley
608eb8dc403SDave Cobbley        if data:
609eb8dc403SDave Cobbley            if raw:
610eb8dc403SDave Cobbley                status = 1
611eb8dc403SDave Cobbley            else:
612eb8dc403SDave Cobbley                # Remove first line (command line) and last line (prompt)
613eb8dc403SDave Cobbley                data = data[data.find('$?\r\n')+4:data.rfind('\r\n')]
614eb8dc403SDave Cobbley                index = data.rfind('\r\n')
615eb8dc403SDave Cobbley                if index == -1:
616eb8dc403SDave Cobbley                    status_cmd = data
617eb8dc403SDave Cobbley                    data = ""
618eb8dc403SDave Cobbley                else:
619eb8dc403SDave Cobbley                    status_cmd = data[index+2:]
620eb8dc403SDave Cobbley                    data = data[:index]
621eb8dc403SDave Cobbley                if (status_cmd == "0"):
622eb8dc403SDave Cobbley                    status = 1
623eb8dc403SDave Cobbley        return (status, str(data))
624eb8dc403SDave Cobbley
625eb8dc403SDave Cobbley
626eb8dc403SDave Cobbley    def _dump_host(self):
627eb8dc403SDave Cobbley        self.host_dumper.create_dir("qemu")
6281a4b7ee2SBrad Bishop        self.logger.warning("Qemu ended unexpectedly, dump data from host"
629eb8dc403SDave Cobbley                " is in %s" % self.host_dumper.dump_dir)
630eb8dc403SDave Cobbley        self.host_dumper.dump_host()
631eb8dc403SDave Cobbley
632eb8dc403SDave Cobbley# This class is for reading data from a socket and passing it to logfunc
633eb8dc403SDave Cobbley# to be processed. It's completely event driven and has a straightforward
634eb8dc403SDave Cobbley# event loop. The mechanism for stopping the thread is a simple pipe which
635eb8dc403SDave Cobbley# will wake up the poll and allow for tearing everything down.
636eb8dc403SDave Cobbleyclass LoggingThread(threading.Thread):
637eb8dc403SDave Cobbley    def __init__(self, logfunc, sock, logger):
638eb8dc403SDave Cobbley        self.connection_established = threading.Event()
639eb8dc403SDave Cobbley        self.serversock = sock
640eb8dc403SDave Cobbley        self.logfunc = logfunc
641eb8dc403SDave Cobbley        self.logger = logger
642eb8dc403SDave Cobbley        self.readsock = None
643eb8dc403SDave Cobbley        self.running = False
644c926e17cSAndrew Geissler        self.canexit = False
645eb8dc403SDave Cobbley
646eb8dc403SDave Cobbley        self.errorevents = select.POLLERR | select.POLLHUP | select.POLLNVAL
647eb8dc403SDave Cobbley        self.readevents = select.POLLIN | select.POLLPRI
648eb8dc403SDave Cobbley
649eb8dc403SDave Cobbley        threading.Thread.__init__(self, target=self.threadtarget)
650eb8dc403SDave Cobbley
651eb8dc403SDave Cobbley    def threadtarget(self):
652eb8dc403SDave Cobbley        try:
653eb8dc403SDave Cobbley            self.eventloop()
654eb8dc403SDave Cobbley        finally:
655eb8dc403SDave Cobbley            self.teardown()
656eb8dc403SDave Cobbley
657eb8dc403SDave Cobbley    def run(self):
658eb8dc403SDave Cobbley        self.logger.debug("Starting logging thread")
659eb8dc403SDave Cobbley        self.readpipe, self.writepipe = os.pipe()
660eb8dc403SDave Cobbley        threading.Thread.run(self)
661eb8dc403SDave Cobbley
662eb8dc403SDave Cobbley    def stop(self):
663eb8dc403SDave Cobbley        self.logger.debug("Stopping logging thread")
664eb8dc403SDave Cobbley        if self.running:
665eb8dc403SDave Cobbley            os.write(self.writepipe, bytes("stop", "utf-8"))
666eb8dc403SDave Cobbley
667eb8dc403SDave Cobbley    def teardown(self):
668eb8dc403SDave Cobbley        self.logger.debug("Tearing down logging thread")
669eb8dc403SDave Cobbley        self.close_socket(self.serversock)
670eb8dc403SDave Cobbley
671eb8dc403SDave Cobbley        if self.readsock is not None:
672eb8dc403SDave Cobbley            self.close_socket(self.readsock)
673eb8dc403SDave Cobbley
674eb8dc403SDave Cobbley        self.close_ignore_error(self.readpipe)
675eb8dc403SDave Cobbley        self.close_ignore_error(self.writepipe)
676eb8dc403SDave Cobbley        self.running = False
677eb8dc403SDave Cobbley
678c926e17cSAndrew Geissler    def allowexit(self):
679c926e17cSAndrew Geissler        self.canexit = True
680c926e17cSAndrew Geissler
681eb8dc403SDave Cobbley    def eventloop(self):
682eb8dc403SDave Cobbley        poll = select.poll()
683eb8dc403SDave Cobbley        event_read_mask = self.errorevents | self.readevents
684eb8dc403SDave Cobbley        poll.register(self.serversock.fileno())
685eb8dc403SDave Cobbley        poll.register(self.readpipe, event_read_mask)
686eb8dc403SDave Cobbley
687eb8dc403SDave Cobbley        breakout = False
688eb8dc403SDave Cobbley        self.running = True
689eb8dc403SDave Cobbley        self.logger.debug("Starting thread event loop")
690eb8dc403SDave Cobbley        while not breakout:
691eb8dc403SDave Cobbley            events = poll.poll()
692eb8dc403SDave Cobbley            for event in events:
693eb8dc403SDave Cobbley                # An error occurred, bail out
694eb8dc403SDave Cobbley                if event[1] & self.errorevents:
695eb8dc403SDave Cobbley                    raise Exception(self.stringify_event(event[1]))
696eb8dc403SDave Cobbley
697eb8dc403SDave Cobbley                # Event to stop the thread
698eb8dc403SDave Cobbley                if self.readpipe == event[0]:
699eb8dc403SDave Cobbley                    self.logger.debug("Stop event received")
700eb8dc403SDave Cobbley                    breakout = True
701eb8dc403SDave Cobbley                    break
702eb8dc403SDave Cobbley
703eb8dc403SDave Cobbley                # A connection request was received
704eb8dc403SDave Cobbley                elif self.serversock.fileno() == event[0]:
705eb8dc403SDave Cobbley                    self.logger.debug("Connection request received")
706eb8dc403SDave Cobbley                    self.readsock, _ = self.serversock.accept()
707eb8dc403SDave Cobbley                    self.readsock.setblocking(0)
708eb8dc403SDave Cobbley                    poll.unregister(self.serversock.fileno())
709eb8dc403SDave Cobbley                    poll.register(self.readsock.fileno(), event_read_mask)
710eb8dc403SDave Cobbley
711eb8dc403SDave Cobbley                    self.logger.debug("Setting connection established event")
712eb8dc403SDave Cobbley                    self.connection_established.set()
713eb8dc403SDave Cobbley
714eb8dc403SDave Cobbley                # Actual data to be logged
715eb8dc403SDave Cobbley                elif self.readsock.fileno() == event[0]:
716eb8dc403SDave Cobbley                    data = self.recv(1024)
717eb8dc403SDave Cobbley                    self.logfunc(data)
718eb8dc403SDave Cobbley
719eb8dc403SDave Cobbley    # Since the socket is non-blocking make sure to honor EAGAIN
720eb8dc403SDave Cobbley    # and EWOULDBLOCK.
721eb8dc403SDave Cobbley    def recv(self, count):
722eb8dc403SDave Cobbley        try:
723eb8dc403SDave Cobbley            data = self.readsock.recv(count)
724eb8dc403SDave Cobbley        except socket.error as e:
725eb8dc403SDave Cobbley            if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK:
726*ac69b488SWilliam A. Kennington III                return b''
727eb8dc403SDave Cobbley            else:
728eb8dc403SDave Cobbley                raise
729eb8dc403SDave Cobbley
730eb8dc403SDave Cobbley        if data is None:
731eb8dc403SDave Cobbley            raise Exception("No data on read ready socket")
732eb8dc403SDave Cobbley        elif not data:
733eb8dc403SDave Cobbley            # This actually means an orderly shutdown
734eb8dc403SDave Cobbley            # happened. But for this code it counts as an
735eb8dc403SDave Cobbley            # error since the connection shouldn't go away
736eb8dc403SDave Cobbley            # until qemu exits.
737c926e17cSAndrew Geissler            if not self.canexit:
738eb8dc403SDave Cobbley                raise Exception("Console connection closed unexpectedly")
739*ac69b488SWilliam A. Kennington III            return b''
740eb8dc403SDave Cobbley
741eb8dc403SDave Cobbley        return data
742eb8dc403SDave Cobbley
743eb8dc403SDave Cobbley    def stringify_event(self, event):
744eb8dc403SDave Cobbley        val = ''
745eb8dc403SDave Cobbley        if select.POLLERR == event:
746eb8dc403SDave Cobbley            val = 'POLLER'
747eb8dc403SDave Cobbley        elif select.POLLHUP == event:
748eb8dc403SDave Cobbley            val = 'POLLHUP'
749eb8dc403SDave Cobbley        elif select.POLLNVAL == event:
750eb8dc403SDave Cobbley            val = 'POLLNVAL'
751eb8dc403SDave Cobbley        return val
752eb8dc403SDave Cobbley
753eb8dc403SDave Cobbley    def close_socket(self, sock):
754eb8dc403SDave Cobbley        sock.shutdown(socket.SHUT_RDWR)
755eb8dc403SDave Cobbley        sock.close()
756eb8dc403SDave Cobbley
757eb8dc403SDave Cobbley    def close_ignore_error(self, fd):
758eb8dc403SDave Cobbley        try:
759eb8dc403SDave Cobbley            os.close(fd)
760eb8dc403SDave Cobbley        except OSError:
761eb8dc403SDave Cobbley            pass
762