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