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