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