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