1c342db35SBrad Bishop#
2eb8dc403SDave Cobbley# Copyright (C) 2013 Intel Corporation
3eb8dc403SDave Cobbley#
4c342db35SBrad Bishop# SPDX-License-Identifier: MIT
5c342db35SBrad Bishop#
6eb8dc403SDave Cobbley
7eb8dc403SDave Cobbley# This module provides a class for starting qemu images using runqemu.
8eb8dc403SDave Cobbley# It's used by testimage.bbclass.
9eb8dc403SDave Cobbley
10eb8dc403SDave Cobbleyimport subprocess
11eb8dc403SDave Cobbleyimport os
12eb8dc403SDave Cobbleyimport sys
13eb8dc403SDave Cobbleyimport time
14eb8dc403SDave Cobbleyimport signal
15eb8dc403SDave Cobbleyimport re
16eb8dc403SDave Cobbleyimport socket
17eb8dc403SDave Cobbleyimport select
18eb8dc403SDave Cobbleyimport errno
19eb8dc403SDave Cobbleyimport string
20eb8dc403SDave Cobbleyimport threading
21eb8dc403SDave Cobbleyimport codecs
22eb8dc403SDave Cobbleyimport logging
23c926e17cSAndrew Geisslerimport tempfile
24eb8dc403SDave Cobbleyfrom oeqa.utils.dump import HostDumper
2582c905dcSAndrew Geisslerfrom collections import defaultdict
26c926e17cSAndrew Geisslerimport importlib
27eb8dc403SDave Cobbley
28eb8dc403SDave Cobbley# Get Unicode non printable control chars
29eb8dc403SDave Cobbleycontrol_range = list(range(0,32))+list(range(127,160))
30eb8dc403SDave Cobbleycontrol_chars = [chr(x) for x in control_range
31eb8dc403SDave Cobbley                if chr(x) not in string.printable]
32eb8dc403SDave Cobbleyre_control_char = re.compile('[%s]' % re.escape("".join(control_chars)))
33eb8dc403SDave Cobbley
34eb8dc403SDave Cobbleyclass QemuRunner:
35eb8dc403SDave Cobbley
3619323693SBrad Bishop    def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds,
373b8a17c1SAndrew Geissler                 use_kvm, logger, use_slirp=False, serial_ports=2, boot_patterns = defaultdict(str), use_ovmf=False, workdir=None, tmpfsdir=None):
38eb8dc403SDave Cobbley
39eb8dc403SDave Cobbley        # Popen object for runqemu
40eb8dc403SDave Cobbley        self.runqemu = None
4182c905dcSAndrew Geissler        self.runqemu_exited = False
42eb8dc403SDave Cobbley        # pid of the qemu process that runqemu will start
43eb8dc403SDave Cobbley        self.qemupid = None
44eb8dc403SDave Cobbley        # target ip - from the command line or runqemu output
45eb8dc403SDave Cobbley        self.ip = None
46eb8dc403SDave Cobbley        # host ip - where qemu is running
47eb8dc403SDave Cobbley        self.server_ip = None
48eb8dc403SDave Cobbley        # target ip netmask
49eb8dc403SDave Cobbley        self.netmask = None
50eb8dc403SDave Cobbley
51eb8dc403SDave Cobbley        self.machine = machine
52eb8dc403SDave Cobbley        self.rootfs = rootfs
53eb8dc403SDave Cobbley        self.display = display
54eb8dc403SDave Cobbley        self.tmpdir = tmpdir
55eb8dc403SDave Cobbley        self.deploy_dir_image = deploy_dir_image
56eb8dc403SDave Cobbley        self.logfile = logfile
57eb8dc403SDave Cobbley        self.boottime = boottime
58eb8dc403SDave Cobbley        self.logged = False
59eb8dc403SDave Cobbley        self.thread = None
60eb8dc403SDave Cobbley        self.use_kvm = use_kvm
6182c905dcSAndrew Geissler        self.use_ovmf = use_ovmf
6219323693SBrad Bishop        self.use_slirp = use_slirp
6382c905dcSAndrew Geissler        self.serial_ports = serial_ports
64eb8dc403SDave Cobbley        self.msg = ''
6582c905dcSAndrew Geissler        self.boot_patterns = boot_patterns
663b8a17c1SAndrew Geissler        self.tmpfsdir = tmpfsdir
67eb8dc403SDave Cobbley
680903674eSAndrew Geissler        self.runqemutime = 300
69b7d28619SAndrew Geissler        if not workdir:
70b7d28619SAndrew Geissler            workdir = os.getcwd()
71b7d28619SAndrew Geissler        self.qemu_pidfile = workdir + '/pidfile_' + str(os.getpid())
72eb8dc403SDave Cobbley        self.host_dumper = HostDumper(dump_host_cmds, dump_dir)
7315ae2509SBrad Bishop        self.monitorpipe = None
74eb8dc403SDave Cobbley
75eb8dc403SDave Cobbley        self.logger = logger
76ac69b488SWilliam A. Kennington III        # Whether we're expecting an exit and should show related errors
77ac69b488SWilliam A. Kennington III        self.canexit = False
78eb8dc403SDave Cobbley
7982c905dcSAndrew Geissler        # Enable testing other OS's
8082c905dcSAndrew Geissler        # Set commands for target communication, and default to Linux ALWAYS
8182c905dcSAndrew Geissler        # Other OS's or baremetal applications need to provide their
8282c905dcSAndrew Geissler        # own implementation passing it through QemuRunner's constructor
8382c905dcSAndrew Geissler        # or by passing them through TESTIMAGE_BOOT_PATTERNS[flag]
8482c905dcSAndrew Geissler        # provided variables, where <flag> is one of the mentioned below.
8582c905dcSAndrew Geissler        accepted_patterns = ['search_reached_prompt', 'send_login_user', 'search_login_succeeded', 'search_cmd_finished']
8682c905dcSAndrew Geissler        default_boot_patterns = defaultdict(str)
8782c905dcSAndrew Geissler        # Default to the usual paterns used to communicate with the target
8882c905dcSAndrew Geissler        default_boot_patterns['search_reached_prompt'] = b' login:'
8982c905dcSAndrew Geissler        default_boot_patterns['send_login_user'] = 'root\n'
9082c905dcSAndrew Geissler        default_boot_patterns['search_login_succeeded'] = r"root@[a-zA-Z0-9\-]+:~#"
9182c905dcSAndrew Geissler        default_boot_patterns['search_cmd_finished'] = r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#"
9282c905dcSAndrew Geissler
9382c905dcSAndrew Geissler        # Only override patterns that were set e.g. login user TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n"
9482c905dcSAndrew Geissler        for pattern in accepted_patterns:
9582c905dcSAndrew Geissler            if not self.boot_patterns[pattern]:
9682c905dcSAndrew Geissler                self.boot_patterns[pattern] = default_boot_patterns[pattern]
9782c905dcSAndrew Geissler
98eb8dc403SDave Cobbley    def create_socket(self):
99eb8dc403SDave Cobbley        try:
100eb8dc403SDave Cobbley            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
101eb8dc403SDave Cobbley            sock.setblocking(0)
102eb8dc403SDave Cobbley            sock.bind(("127.0.0.1",0))
103eb8dc403SDave Cobbley            sock.listen(2)
104eb8dc403SDave Cobbley            port = sock.getsockname()[1]
105eb8dc403SDave Cobbley            self.logger.debug("Created listening socket for qemu serial console on: 127.0.0.1:%s" % port)
106eb8dc403SDave Cobbley            return (sock, port)
107eb8dc403SDave Cobbley
108eb8dc403SDave Cobbley        except socket.error:
109eb8dc403SDave Cobbley            sock.close()
110eb8dc403SDave Cobbley            raise
111eb8dc403SDave Cobbley
112eb8dc403SDave Cobbley    def log(self, msg):
113eb8dc403SDave Cobbley        if self.logfile:
114eb8dc403SDave Cobbley            # It is needed to sanitize the data received from qemu
115eb8dc403SDave Cobbley            # because is possible to have control characters
116eb8dc403SDave Cobbley            msg = msg.decode("utf-8", errors='ignore')
117eb8dc403SDave Cobbley            msg = re_control_char.sub('', msg)
118eb8dc403SDave Cobbley            self.msg += msg
119eb8dc403SDave Cobbley            with codecs.open(self.logfile, "a", encoding="utf-8") as f:
120eb8dc403SDave Cobbley                f.write("%s" % msg)
121eb8dc403SDave Cobbley
122eb8dc403SDave Cobbley    def getOutput(self, o):
123eb8dc403SDave Cobbley        import fcntl
124eb8dc403SDave Cobbley        fl = fcntl.fcntl(o, fcntl.F_GETFL)
125eb8dc403SDave Cobbley        fcntl.fcntl(o, fcntl.F_SETFL, fl | os.O_NONBLOCK)
126d159c7fbSAndrew Geissler        try:
127eb8dc403SDave Cobbley            return os.read(o.fileno(), 1000000).decode("utf-8")
128d159c7fbSAndrew Geissler        except BlockingIOError:
129d159c7fbSAndrew Geissler            return ""
130eb8dc403SDave Cobbley
131eb8dc403SDave Cobbley
132eb8dc403SDave Cobbley    def handleSIGCHLD(self, signum, frame):
133eb8dc403SDave Cobbley        if self.runqemu and self.runqemu.poll():
134eb8dc403SDave Cobbley            if self.runqemu.returncode:
13582c905dcSAndrew Geissler                self.logger.error('runqemu exited with code %d' % self.runqemu.returncode)
13682c905dcSAndrew Geissler                self.logger.error('Output from runqemu:\n%s' % self.getOutput(self.runqemu.stdout))
137eb8dc403SDave Cobbley                self.stop()
138eb8dc403SDave Cobbley                self._dump_host()
139eb8dc403SDave Cobbley
140eb8dc403SDave Cobbley    def start(self, qemuparams = None, get_ip = True, extra_bootparams = None, runqemuparams='', launch_cmd=None, discard_writes=True):
141eb8dc403SDave Cobbley        env = os.environ.copy()
142eb8dc403SDave Cobbley        if self.display:
143eb8dc403SDave Cobbley            env["DISPLAY"] = self.display
144eb8dc403SDave Cobbley            # Set this flag so that Qemu doesn't do any grabs as SDL grabs
145eb8dc403SDave Cobbley            # interact badly with screensavers.
146eb8dc403SDave Cobbley            env["QEMU_DONT_GRAB"] = "1"
147eb8dc403SDave Cobbley        if not os.path.exists(self.rootfs):
148eb8dc403SDave Cobbley            self.logger.error("Invalid rootfs %s" % self.rootfs)
149eb8dc403SDave Cobbley            return False
150eb8dc403SDave Cobbley        if not os.path.exists(self.tmpdir):
151eb8dc403SDave Cobbley            self.logger.error("Invalid TMPDIR path %s" % self.tmpdir)
152eb8dc403SDave Cobbley            return False
153eb8dc403SDave Cobbley        else:
154eb8dc403SDave Cobbley            env["OE_TMPDIR"] = self.tmpdir
155eb8dc403SDave Cobbley        if not os.path.exists(self.deploy_dir_image):
156eb8dc403SDave Cobbley            self.logger.error("Invalid DEPLOY_DIR_IMAGE path %s" % self.deploy_dir_image)
157eb8dc403SDave Cobbley            return False
158eb8dc403SDave Cobbley        else:
159eb8dc403SDave Cobbley            env["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image
160eb8dc403SDave Cobbley
1613b8a17c1SAndrew Geissler        if self.tmpfsdir:
1623b8a17c1SAndrew Geissler            env["RUNQEMU_TMPFS_DIR"] = self.tmpfsdir
1633b8a17c1SAndrew Geissler
164eb8dc403SDave Cobbley        if not launch_cmd:
16508902b01SBrad Bishop            launch_cmd = 'runqemu %s' % ('snapshot' if discard_writes else '')
166eb8dc403SDave Cobbley            if self.use_kvm:
167eb8dc403SDave Cobbley                self.logger.debug('Using kvm for runqemu')
168eb8dc403SDave Cobbley                launch_cmd += ' kvm'
169eb8dc403SDave Cobbley            else:
170eb8dc403SDave Cobbley                self.logger.debug('Not using kvm for runqemu')
171eb8dc403SDave Cobbley            if not self.display:
172eb8dc403SDave Cobbley                launch_cmd += ' nographic'
17319323693SBrad Bishop            if self.use_slirp:
17419323693SBrad Bishop                launch_cmd += ' slirp'
17582c905dcSAndrew Geissler            if self.use_ovmf:
17682c905dcSAndrew Geissler                launch_cmd += ' ovmf'
17708902b01SBrad Bishop            launch_cmd += ' %s %s %s' % (runqemuparams, self.machine, self.rootfs)
178eb8dc403SDave Cobbley
179eb8dc403SDave Cobbley        return self.launch(launch_cmd, qemuparams=qemuparams, get_ip=get_ip, extra_bootparams=extra_bootparams, env=env)
180eb8dc403SDave Cobbley
181eb8dc403SDave Cobbley    def launch(self, launch_cmd, get_ip = True, qemuparams = None, extra_bootparams = None, env = None):
182c926e17cSAndrew Geissler        # use logfile to determine the recipe-sysroot-native path and
183c926e17cSAndrew Geissler        # then add in the site-packages path components and add that
184c926e17cSAndrew Geissler        # to the python sys.path so qmp.py can be found.
185c926e17cSAndrew Geissler        python_path = os.path.dirname(os.path.dirname(self.logfile))
186eff27476SAndrew Geissler        python_path += "/recipe-sysroot-native/usr/lib/qemu-python"
187c926e17cSAndrew Geissler        sys.path.append(python_path)
188c926e17cSAndrew Geissler        importlib.invalidate_caches()
189c926e17cSAndrew Geissler        try:
190c926e17cSAndrew Geissler            qmp = importlib.import_module("qmp")
191c926e17cSAndrew Geissler        except:
192c926e17cSAndrew Geissler            self.logger.error("qemurunner: qmp.py missing, please ensure it's installed")
193c926e17cSAndrew Geissler            return False
194c926e17cSAndrew Geissler        # Path relative to tmpdir used as cwd for qemu below to avoid unix socket path length issues
195c926e17cSAndrew Geissler        qmp_file = "." + next(tempfile._get_candidate_names())
196c926e17cSAndrew Geissler        qmp_param = ' -S -qmp unix:./%s,server,wait' % (qmp_file)
197c926e17cSAndrew Geissler        qmp_port = self.tmpdir + "/" + qmp_file
1980903674eSAndrew Geissler        # Create a second socket connection for debugging use,
1990903674eSAndrew Geissler        # note this will NOT cause qemu to block waiting for the connection
2000903674eSAndrew Geissler        qmp_file2 = "." + next(tempfile._get_candidate_names())
2010903674eSAndrew Geissler        qmp_param += ' -qmp unix:./%s,server,nowait' % (qmp_file2)
2020903674eSAndrew Geissler        qmp_port2 = self.tmpdir + "/" + qmp_file2
2030903674eSAndrew Geissler        self.logger.info("QMP Available for connection at %s" % (qmp_port2))
204c926e17cSAndrew Geissler
205eb8dc403SDave Cobbley        try:
20682c905dcSAndrew Geissler            if self.serial_ports >= 2:
207f86d0556SBrad Bishop                self.threadsock, threadport = self.create_socket()
208eb8dc403SDave Cobbley            self.server_socket, self.serverport = self.create_socket()
209eb8dc403SDave Cobbley        except socket.error as msg:
210eb8dc403SDave Cobbley            self.logger.error("Failed to create listening socket: %s" % msg[1])
211eb8dc403SDave Cobbley            return False
212eb8dc403SDave Cobbley
21395ac1b8dSAndrew Geissler        bootparams = ' printk.time=1'
214eb8dc403SDave Cobbley        if extra_bootparams:
215eb8dc403SDave Cobbley            bootparams = bootparams + ' ' + extra_bootparams
216eb8dc403SDave Cobbley
217eb8dc403SDave Cobbley        # Ask QEMU to store the QEMU process PID in file, this way we don't have to parse running processes
218eb8dc403SDave Cobbley        # and analyze descendents in order to determine it.
219eb8dc403SDave Cobbley        if os.path.exists(self.qemu_pidfile):
220eb8dc403SDave Cobbley            os.remove(self.qemu_pidfile)
221c926e17cSAndrew Geissler        self.qemuparams = 'bootparams="{0}" qemuparams="-pidfile {1} {2}"'.format(bootparams, self.qemu_pidfile, qmp_param)
222c926e17cSAndrew Geissler
223eb8dc403SDave Cobbley        if qemuparams:
224eb8dc403SDave Cobbley            self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"'
225eb8dc403SDave Cobbley
22682c905dcSAndrew Geissler        if self.serial_ports >= 2:
22715ae2509SBrad Bishop            launch_cmd += ' tcpserial=%s:%s %s' % (threadport, self.serverport, self.qemuparams)
22882c905dcSAndrew Geissler        else:
22982c905dcSAndrew Geissler            launch_cmd += ' tcpserial=%s %s' % (self.serverport, self.qemuparams)
230eb8dc403SDave Cobbley
231eb8dc403SDave Cobbley        self.origchldhandler = signal.getsignal(signal.SIGCHLD)
232eb8dc403SDave Cobbley        signal.signal(signal.SIGCHLD, self.handleSIGCHLD)
233eb8dc403SDave Cobbley
234eb8dc403SDave Cobbley        self.logger.debug('launchcmd=%s'%(launch_cmd))
235eb8dc403SDave Cobbley
236eb8dc403SDave Cobbley        # FIXME: We pass in stdin=subprocess.PIPE here to work around stty
237eb8dc403SDave Cobbley        # blocking at the end of the runqemu script when using this within
238eb8dc403SDave Cobbley        # oe-selftest (this makes stty error out immediately). There ought
239eb8dc403SDave Cobbley        # to be a proper fix but this will suffice for now.
240c926e17cSAndrew Geissler        self.runqemu = subprocess.Popen(launch_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, preexec_fn=os.setpgrp, env=env, cwd=self.tmpdir)
241eb8dc403SDave Cobbley        output = self.runqemu.stdout
2425f35090dSAndrew Geissler        launch_time = time.time()
243eb8dc403SDave Cobbley
244eb8dc403SDave Cobbley        #
245eb8dc403SDave Cobbley        # We need the preexec_fn above so that all runqemu processes can easily be killed
246eb8dc403SDave Cobbley        # (by killing their process group). This presents a problem if this controlling
247eb8dc403SDave Cobbley        # process itself is killed however since those processes don't notice the death
248eb8dc403SDave Cobbley        # of the parent and merrily continue on.
249eb8dc403SDave Cobbley        #
250eb8dc403SDave Cobbley        # Rather than hack runqemu to deal with this, we add something here instead.
251eb8dc403SDave Cobbley        # Basically we fork off another process which holds an open pipe to the parent
252eb8dc403SDave Cobbley        # and also is setpgrp. If/when the pipe sees EOF from the parent dieing, it kills
253eb8dc403SDave Cobbley        # the process group. This is like pctrl's PDEATHSIG but for a process group
254eb8dc403SDave Cobbley        # rather than a single process.
255eb8dc403SDave Cobbley        #
256eb8dc403SDave Cobbley        r, w = os.pipe()
257eb8dc403SDave Cobbley        self.monitorpid = os.fork()
258eb8dc403SDave Cobbley        if self.monitorpid:
259eb8dc403SDave Cobbley            os.close(r)
260eb8dc403SDave Cobbley            self.monitorpipe = os.fdopen(w, "w")
261eb8dc403SDave Cobbley        else:
262eb8dc403SDave Cobbley            # child process
263eb8dc403SDave Cobbley            os.setpgrp()
264eb8dc403SDave Cobbley            os.close(w)
265eb8dc403SDave Cobbley            r = os.fdopen(r)
266eb8dc403SDave Cobbley            x = r.read()
267eb8dc403SDave Cobbley            os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
26893c203f3SPatrick Williams            os._exit(0)
269eb8dc403SDave Cobbley
270eb8dc403SDave Cobbley        self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid)
271eb8dc403SDave Cobbley        self.logger.debug("waiting at most %s seconds for qemu pid (%s)" %
272eb8dc403SDave Cobbley                          (self.runqemutime, time.strftime("%D %H:%M:%S")))
273eb8dc403SDave Cobbley        endtime = time.time() + self.runqemutime
274eb8dc403SDave Cobbley        while not self.is_alive() and time.time() < endtime:
275eb8dc403SDave Cobbley            if self.runqemu.poll():
27682c905dcSAndrew Geissler                if self.runqemu_exited:
277c926e17cSAndrew Geissler                    self.logger.warning("runqemu during is_alive() test")
27882c905dcSAndrew Geissler                    return False
279eb8dc403SDave Cobbley                if self.runqemu.returncode:
280eb8dc403SDave Cobbley                    # No point waiting any longer
28196ff1984SBrad Bishop                    self.logger.warning('runqemu exited with code %d' % self.runqemu.returncode)
282eb8dc403SDave Cobbley                    self._dump_host()
28396ff1984SBrad Bishop                    self.logger.warning("Output from runqemu:\n%s" % self.getOutput(output))
284f86d0556SBrad Bishop                    self.stop()
285eb8dc403SDave Cobbley                    return False
286eb8dc403SDave Cobbley            time.sleep(0.5)
287eb8dc403SDave Cobbley
28882c905dcSAndrew Geissler        if self.runqemu_exited:
289c926e17cSAndrew Geissler            self.logger.warning("runqemu after timeout")
29082c905dcSAndrew Geissler
291c926e17cSAndrew Geissler        if self.runqemu.returncode:
292c926e17cSAndrew Geissler            self.logger.warning('runqemu exited with code %d' % self.runqemu.returncode)
293c926e17cSAndrew Geissler
294c926e17cSAndrew Geissler        if not self.is_alive():
295ac69b488SWilliam A. Kennington III            self.logger.error("Qemu pid didn't appear in %s seconds (%s)" %
296ac69b488SWilliam A. Kennington III                              (self.runqemutime, time.strftime("%D %H:%M:%S")))
297ac69b488SWilliam A. Kennington III
298ac69b488SWilliam A. Kennington III            qemu_pid = None
299ac69b488SWilliam A. Kennington III            if os.path.isfile(self.qemu_pidfile):
300ac69b488SWilliam A. Kennington III                with open(self.qemu_pidfile, 'r') as f:
301ac69b488SWilliam A. Kennington III                    qemu_pid = f.read().strip()
302ac69b488SWilliam A. Kennington III
303ac69b488SWilliam A. Kennington III            self.logger.error("Status information, poll status: %s, pidfile exists: %s, pidfile contents %s, proc pid exists %s"
304ac69b488SWilliam A. Kennington III                % (self.runqemu.poll(), os.path.isfile(self.qemu_pidfile), str(qemu_pid), os.path.exists("/proc/" + str(qemu_pid))))
305ac69b488SWilliam A. Kennington III
306ac69b488SWilliam A. Kennington III            # Dump all processes to help us to figure out what is going on...
307ac69b488SWilliam A. Kennington III            ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,pri,ni,command '], stdout=subprocess.PIPE).communicate()[0]
308ac69b488SWilliam A. Kennington III            processes = ps.decode("utf-8")
309ac69b488SWilliam A. Kennington III            self.logger.debug("Running processes:\n%s" % processes)
310ac69b488SWilliam A. Kennington III            self._dump_host()
311ac69b488SWilliam A. Kennington III            op = self.getOutput(output)
312ac69b488SWilliam A. Kennington III            self.stop()
313ac69b488SWilliam A. Kennington III            if op:
314ac69b488SWilliam A. Kennington III                self.logger.error("Output from runqemu:\n%s" % op)
315ac69b488SWilliam A. Kennington III            else:
316ac69b488SWilliam A. Kennington III                self.logger.error("No output from runqemu.\n")
317c926e17cSAndrew Geissler            return False
318c926e17cSAndrew Geissler
319c926e17cSAndrew Geissler        # Create the client socket for the QEMU Monitor Control Socket
320c926e17cSAndrew Geissler        # This will allow us to read status from Qemu if the the process
321c926e17cSAndrew Geissler        # is still alive
322c926e17cSAndrew Geissler        self.logger.debug("QMP Initializing to %s" % (qmp_port))
323c926e17cSAndrew Geissler        # chdir dance for path length issues with unix sockets
324c926e17cSAndrew Geissler        origpath = os.getcwd()
325c926e17cSAndrew Geissler        try:
326c926e17cSAndrew Geissler            os.chdir(os.path.dirname(qmp_port))
327c926e17cSAndrew Geissler            try:
328c926e17cSAndrew Geissler               self.qmp = qmp.QEMUMonitorProtocol(os.path.basename(qmp_port))
329c926e17cSAndrew Geissler            except OSError as msg:
330c926e17cSAndrew Geissler                self.logger.warning("Failed to initialize qemu monitor socket: %s File: %s" % (msg, msg.filename))
331c926e17cSAndrew Geissler                return False
332c926e17cSAndrew Geissler
333c926e17cSAndrew Geissler            self.logger.debug("QMP Connecting to %s" % (qmp_port))
334c926e17cSAndrew Geissler            if not os.path.exists(qmp_port) and self.is_alive():
335c926e17cSAndrew Geissler                self.logger.debug("QMP Port does not exist waiting for it to be created")
336c926e17cSAndrew Geissler                endtime = time.time() + self.runqemutime
337c926e17cSAndrew Geissler                while not os.path.exists(qmp_port) and self.is_alive() and time.time() < endtime:
338c926e17cSAndrew Geissler                   self.logger.info("QMP port does not exist yet!")
339c926e17cSAndrew Geissler                   time.sleep(0.5)
340c926e17cSAndrew Geissler                if not os.path.exists(qmp_port) and self.is_alive():
341c926e17cSAndrew Geissler                    self.logger.warning("QMP Port still does not exist but QEMU is alive")
342c926e17cSAndrew Geissler                    return False
343c926e17cSAndrew Geissler
344c926e17cSAndrew Geissler            try:
345c926e17cSAndrew Geissler                self.qmp.connect()
3465f35090dSAndrew Geissler                connect_time = time.time()
3475f35090dSAndrew Geissler                self.logger.info("QMP connected to QEMU at %s and took %s seconds" %
3485f35090dSAndrew Geissler                                  (time.strftime("%D %H:%M:%S"),
3495f35090dSAndrew Geissler                                   time.time() - launch_time))
350c926e17cSAndrew Geissler            except OSError as msg:
351c926e17cSAndrew Geissler                self.logger.warning("Failed to connect qemu monitor socket: %s File: %s" % (msg, msg.filename))
352c926e17cSAndrew Geissler                return False
353c926e17cSAndrew Geissler            except qmp.QMPConnectError as msg:
354c926e17cSAndrew Geissler                self.logger.warning("Failed to communicate with qemu monitor: %s" % (msg))
355c926e17cSAndrew Geissler                return False
356c926e17cSAndrew Geissler        finally:
357c926e17cSAndrew Geissler            os.chdir(origpath)
358c926e17cSAndrew Geissler
3590903674eSAndrew Geissler        # We worry that mmap'd libraries may cause page faults which hang the qemu VM for periods
3600903674eSAndrew Geissler        # causing failures. Before we "start" qemu, read through it's mapped files to try and
3610903674eSAndrew Geissler        # ensure we don't hit page faults later
3620903674eSAndrew Geissler        mapdir = "/proc/" + str(self.qemupid) + "/map_files/"
3630903674eSAndrew Geissler        try:
3640903674eSAndrew Geissler            for f in os.listdir(mapdir):
3655f35090dSAndrew Geissler                try:
3660903674eSAndrew Geissler                    linktarget = os.readlink(os.path.join(mapdir, f))
3670903674eSAndrew Geissler                    if not linktarget.startswith("/") or linktarget.startswith("/dev") or "deleted" in linktarget:
3680903674eSAndrew Geissler                        continue
3690903674eSAndrew Geissler                    with open(linktarget, "rb") as readf:
3700903674eSAndrew Geissler                        data = True
3710903674eSAndrew Geissler                        while data:
3720903674eSAndrew Geissler                            data = readf.read(4096)
3735f35090dSAndrew Geissler                except FileNotFoundError:
3745f35090dSAndrew Geissler                    continue
3750903674eSAndrew Geissler        # Centos7 doesn't allow us to read /map_files/
3760903674eSAndrew Geissler        except PermissionError:
3770903674eSAndrew Geissler            pass
3780903674eSAndrew Geissler
3790903674eSAndrew Geissler        # Release the qemu process to continue running
380c926e17cSAndrew Geissler        self.run_monitor('cont')
3815f35090dSAndrew Geissler        self.logger.info("QMP released QEMU at %s and took %s seconds from connect" %
3825f35090dSAndrew Geissler                          (time.strftime("%D %H:%M:%S"),
3835f35090dSAndrew Geissler                           time.time() - connect_time))
384c926e17cSAndrew Geissler
385eb8dc403SDave Cobbley        # We are alive: qemu is running
386eb8dc403SDave Cobbley        out = self.getOutput(output)
387eb8dc403SDave Cobbley        netconf = False # network configuration is not required by default
388eb8dc403SDave Cobbley        self.logger.debug("qemu started in %s seconds - qemu procces pid is %s (%s)" %
389eb8dc403SDave Cobbley                          (time.time() - (endtime - self.runqemutime),
390eb8dc403SDave Cobbley                           self.qemupid, time.strftime("%D %H:%M:%S")))
391eb8dc403SDave Cobbley        cmdline = ''
39282c905dcSAndrew Geissler        if get_ip:
393eb8dc403SDave Cobbley            with open('/proc/%s/cmdline' % self.qemupid) as p:
394eb8dc403SDave Cobbley                cmdline = p.read()
395eb8dc403SDave Cobbley                # It is needed to sanitize the data received
396eb8dc403SDave Cobbley                # because is possible to have control characters
397eb8dc403SDave Cobbley                cmdline = re_control_char.sub(' ', cmdline)
398eb8dc403SDave Cobbley            try:
39919323693SBrad Bishop                if self.use_slirp:
40019323693SBrad Bishop                    tcp_ports = cmdline.split("hostfwd=tcp::")[1]
40119323693SBrad Bishop                    host_port = tcp_ports[:tcp_ports.find('-')]
40219323693SBrad Bishop                    self.ip = "localhost:%s" % host_port
40319323693SBrad Bishop                else:
404f86d0556SBrad Bishop                    ips = re.findall(r"((?:[0-9]{1,3}\.){3}[0-9]{1,3})", cmdline.split("ip=")[1])
405eb8dc403SDave Cobbley                    self.ip = ips[0]
406eb8dc403SDave Cobbley                    self.server_ip = ips[1]
407eb8dc403SDave Cobbley                self.logger.debug("qemu cmdline used:\n{}".format(cmdline))
408eb8dc403SDave Cobbley            except (IndexError, ValueError):
409eb8dc403SDave Cobbley                # Try to get network configuration from runqemu output
410595f6308SAndrew Geissler                match = re.match(r'.*Network configuration: (?:ip=)*([0-9.]+)::([0-9.]+):([0-9.]+).*',
411eb8dc403SDave Cobbley                                 out, re.MULTILINE|re.DOTALL)
412eb8dc403SDave Cobbley                if match:
413eb8dc403SDave Cobbley                    self.ip, self.server_ip, self.netmask = match.groups()
414eb8dc403SDave Cobbley                    # network configuration is required as we couldn't get it
415eb8dc403SDave Cobbley                    # from the runqemu command line, so qemu doesn't run kernel
416eb8dc403SDave Cobbley                    # and guest networking is not configured
417eb8dc403SDave Cobbley                    netconf = True
418eb8dc403SDave Cobbley                else:
419eb8dc403SDave Cobbley                    self.logger.error("Couldn't get ip from qemu command line and runqemu output! "
420eb8dc403SDave Cobbley                                 "Here is the qemu command line used:\n%s\n"
421eb8dc403SDave Cobbley                                 "and output from runqemu:\n%s" % (cmdline, out))
422eb8dc403SDave Cobbley                    self._dump_host()
423eb8dc403SDave Cobbley                    self.stop()
424eb8dc403SDave Cobbley                    return False
425eb8dc403SDave Cobbley
426eb8dc403SDave Cobbley        self.logger.debug("Target IP: %s" % self.ip)
427eb8dc403SDave Cobbley        self.logger.debug("Server IP: %s" % self.server_ip)
428eb8dc403SDave Cobbley
42982c905dcSAndrew Geissler        if self.serial_ports >= 2:
430f86d0556SBrad Bishop            self.thread = LoggingThread(self.log, self.threadsock, self.logger)
431eb8dc403SDave Cobbley            self.thread.start()
432eb8dc403SDave Cobbley            if not self.thread.connection_established.wait(self.boottime):
433eb8dc403SDave Cobbley                self.logger.error("Didn't receive a console connection from qemu. "
434eb8dc403SDave Cobbley                             "Here is the qemu command line used:\n%s\nand "
435eb8dc403SDave Cobbley                             "output from runqemu:\n%s" % (cmdline, out))
436eb8dc403SDave Cobbley                self.stop_thread()
437eb8dc403SDave Cobbley                return False
438eb8dc403SDave Cobbley
439eb8dc403SDave Cobbley        self.logger.debug("Output from runqemu:\n%s", out)
440eb8dc403SDave Cobbley        self.logger.debug("Waiting at most %d seconds for login banner (%s)" %
441eb8dc403SDave Cobbley                          (self.boottime, time.strftime("%D %H:%M:%S")))
442eb8dc403SDave Cobbley        endtime = time.time() + self.boottime
443eb8dc403SDave Cobbley        socklist = [self.server_socket]
444eb8dc403SDave Cobbley        reachedlogin = False
445eb8dc403SDave Cobbley        stopread = False
446eb8dc403SDave Cobbley        qemusock = None
447eb8dc403SDave Cobbley        bootlog = b''
448eb8dc403SDave Cobbley        data = b''
449eb8dc403SDave Cobbley        while time.time() < endtime and not stopread:
450eb8dc403SDave Cobbley            try:
451eb8dc403SDave Cobbley                sread, swrite, serror = select.select(socklist, [], [], 5)
452eb8dc403SDave Cobbley            except InterruptedError:
453eb8dc403SDave Cobbley                continue
454eb8dc403SDave Cobbley            for sock in sread:
455eb8dc403SDave Cobbley                if sock is self.server_socket:
456eb8dc403SDave Cobbley                    qemusock, addr = self.server_socket.accept()
457eb8dc403SDave Cobbley                    qemusock.setblocking(0)
458eb8dc403SDave Cobbley                    socklist.append(qemusock)
459eb8dc403SDave Cobbley                    socklist.remove(self.server_socket)
460eb8dc403SDave Cobbley                    self.logger.debug("Connection from %s:%s" % addr)
461eb8dc403SDave Cobbley                else:
462eb8dc403SDave Cobbley                    data = data + sock.recv(1024)
463eb8dc403SDave Cobbley                    if data:
464eb8dc403SDave Cobbley                        bootlog += data
46582c905dcSAndrew Geissler                        if self.serial_ports < 2:
46682c905dcSAndrew Geissler                            # this socket has mixed console/kernel data, log it to logfile
46782c905dcSAndrew Geissler                            self.log(data)
46882c905dcSAndrew Geissler
469eb8dc403SDave Cobbley                        data = b''
47082c905dcSAndrew Geissler                        if self.boot_patterns['search_reached_prompt'] in bootlog:
471eb8dc403SDave Cobbley                            self.server_socket = qemusock
472eb8dc403SDave Cobbley                            stopread = True
473eb8dc403SDave Cobbley                            reachedlogin = True
474eb8dc403SDave Cobbley                            self.logger.debug("Reached login banner in %s seconds (%s)" %
475eb8dc403SDave Cobbley                                              (time.time() - (endtime - self.boottime),
476eb8dc403SDave Cobbley                                              time.strftime("%D %H:%M:%S")))
477eb8dc403SDave Cobbley                    else:
478eb8dc403SDave Cobbley                        # no need to check if reachedlogin unless we support multiple connections
479eb8dc403SDave Cobbley                        self.logger.debug("QEMU socket disconnected before login banner reached. (%s)" %
480eb8dc403SDave Cobbley                                          time.strftime("%D %H:%M:%S"))
481eb8dc403SDave Cobbley                        socklist.remove(sock)
482eb8dc403SDave Cobbley                        sock.close()
483eb8dc403SDave Cobbley                        stopread = True
484eb8dc403SDave Cobbley
485eb8dc403SDave Cobbley        if not reachedlogin:
486eb8dc403SDave Cobbley            if time.time() >= endtime:
48796ff1984SBrad Bishop                self.logger.warning("Target didn't reach login banner in %d seconds (%s)" %
488eb8dc403SDave Cobbley                                  (self.boottime, time.strftime("%D %H:%M:%S")))
489eb8dc403SDave Cobbley            tail = lambda l: "\n".join(l.splitlines()[-25:])
490d89cb5f0SBrad Bishop            bootlog = bootlog.decode("utf-8")
491eb8dc403SDave Cobbley            # in case bootlog is empty, use tail qemu log store at self.msg
492eb8dc403SDave Cobbley            lines = tail(bootlog if bootlog else self.msg)
49396ff1984SBrad Bishop            self.logger.warning("Last 25 lines of text:\n%s" % lines)
49496ff1984SBrad Bishop            self.logger.warning("Check full boot log: %s" % self.logfile)
495eb8dc403SDave Cobbley            self._dump_host()
496eb8dc403SDave Cobbley            self.stop()
497eb8dc403SDave Cobbley            return False
498eb8dc403SDave Cobbley
499eb8dc403SDave Cobbley        # If we are not able to login the tests can continue
500eb8dc403SDave Cobbley        try:
501c3d88e4dSAndrew Geissler            (status, output) = self.run_serial(self.boot_patterns['send_login_user'], raw=True, timeout=120)
50282c905dcSAndrew Geissler            if re.search(self.boot_patterns['search_login_succeeded'], output):
503eb8dc403SDave Cobbley                self.logged = True
504eb8dc403SDave Cobbley                self.logger.debug("Logged as root in serial console")
505eb8dc403SDave Cobbley                if netconf:
506eb8dc403SDave Cobbley                    # configure guest networking
507eb8dc403SDave Cobbley                    cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip, self.netmask)
508eb8dc403SDave Cobbley                    output = self.run_serial(cmd, raw=True)[1]
509f86d0556SBrad Bishop                    if re.search(r"root@[a-zA-Z0-9\-]+:~#", output):
510eb8dc403SDave Cobbley                        self.logger.debug("configured ip address %s", self.ip)
511eb8dc403SDave Cobbley                    else:
512eb8dc403SDave Cobbley                        self.logger.debug("Couldn't configure guest networking")
513eb8dc403SDave Cobbley            else:
51496ff1984SBrad Bishop                self.logger.warning("Couldn't login into serial console"
515eb8dc403SDave Cobbley                            " as root using blank password")
51696ff1984SBrad Bishop                self.logger.warning("The output:\n%s" % output)
517eb8dc403SDave Cobbley        except:
51896ff1984SBrad Bishop            self.logger.warning("Serial console failed while trying to login")
519eb8dc403SDave Cobbley        return True
520eb8dc403SDave Cobbley
521eb8dc403SDave Cobbley    def stop(self):
522eb8dc403SDave Cobbley        if hasattr(self, "origchldhandler"):
523eb8dc403SDave Cobbley            signal.signal(signal.SIGCHLD, self.origchldhandler)
5241a4b7ee2SBrad Bishop        self.stop_thread()
5251a4b7ee2SBrad Bishop        self.stop_qemu_system()
526eb8dc403SDave Cobbley        if self.runqemu:
527eb8dc403SDave Cobbley            if hasattr(self, "monitorpid"):
528eb8dc403SDave Cobbley                os.kill(self.monitorpid, signal.SIGKILL)
529eb8dc403SDave Cobbley                self.logger.debug("Sending SIGTERM to runqemu")
530eb8dc403SDave Cobbley                try:
531eb8dc403SDave Cobbley                    os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
532eb8dc403SDave Cobbley                except OSError as e:
533eb8dc403SDave Cobbley                    if e.errno != errno.ESRCH:
534eb8dc403SDave Cobbley                        raise
535eb8dc403SDave Cobbley            endtime = time.time() + self.runqemutime
536eb8dc403SDave Cobbley            while self.runqemu.poll() is None and time.time() < endtime:
537eb8dc403SDave Cobbley                time.sleep(1)
538eb8dc403SDave Cobbley            if self.runqemu.poll() is None:
539eb8dc403SDave Cobbley                self.logger.debug("Sending SIGKILL to runqemu")
540eb8dc403SDave Cobbley                os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL)
541d159c7fbSAndrew Geissler            if not self.runqemu.stdout.closed:
542d159c7fbSAndrew Geissler                self.logger.info("Output from runqemu:\n%s" % self.getOutput(self.runqemu.stdout))
543f86d0556SBrad Bishop            self.runqemu.stdin.close()
544f86d0556SBrad Bishop            self.runqemu.stdout.close()
54582c905dcSAndrew Geissler            self.runqemu_exited = True
546f86d0556SBrad Bishop
547c926e17cSAndrew Geissler        if hasattr(self, 'qmp') and self.qmp:
548c926e17cSAndrew Geissler            self.qmp.close()
549c926e17cSAndrew Geissler            self.qmp = None
550eb8dc403SDave Cobbley        if hasattr(self, 'server_socket') and self.server_socket:
551eb8dc403SDave Cobbley            self.server_socket.close()
552eb8dc403SDave Cobbley            self.server_socket = None
553f86d0556SBrad Bishop        if hasattr(self, 'threadsock') and self.threadsock:
554f86d0556SBrad Bishop            self.threadsock.close()
555f86d0556SBrad Bishop            self.threadsock = None
556eb8dc403SDave Cobbley        self.qemupid = None
557eb8dc403SDave Cobbley        self.ip = None
558eb8dc403SDave Cobbley        if os.path.exists(self.qemu_pidfile):
55982c905dcSAndrew Geissler            try:
560eb8dc403SDave Cobbley                os.remove(self.qemu_pidfile)
56182c905dcSAndrew Geissler            except FileNotFoundError as e:
56282c905dcSAndrew Geissler                # We raced, ignore
56382c905dcSAndrew Geissler                pass
564f86d0556SBrad Bishop        if self.monitorpipe:
565f86d0556SBrad Bishop            self.monitorpipe.close()
566eb8dc403SDave Cobbley
567eb8dc403SDave Cobbley    def stop_qemu_system(self):
568eb8dc403SDave Cobbley        if self.qemupid:
569eb8dc403SDave Cobbley            try:
570eb8dc403SDave Cobbley                # qemu-system behaves well and a SIGTERM is enough
571eb8dc403SDave Cobbley                os.kill(self.qemupid, signal.SIGTERM)
572eb8dc403SDave Cobbley            except ProcessLookupError as e:
5731a4b7ee2SBrad Bishop                self.logger.warning('qemu-system ended unexpectedly')
574eb8dc403SDave Cobbley
575eb8dc403SDave Cobbley    def stop_thread(self):
576eb8dc403SDave Cobbley        if self.thread and self.thread.is_alive():
577eb8dc403SDave Cobbley            self.thread.stop()
578eb8dc403SDave Cobbley            self.thread.join()
579eb8dc403SDave Cobbley
580c926e17cSAndrew Geissler    def allowexit(self):
581ac69b488SWilliam A. Kennington III        self.canexit = True
582c926e17cSAndrew Geissler        if self.thread:
583c926e17cSAndrew Geissler            self.thread.allowexit()
584c926e17cSAndrew Geissler
585eb8dc403SDave Cobbley    def restart(self, qemuparams = None):
58696ff1984SBrad Bishop        self.logger.warning("Restarting qemu process")
587eb8dc403SDave Cobbley        if self.runqemu.poll() is None:
588eb8dc403SDave Cobbley            self.stop()
589eb8dc403SDave Cobbley        if self.start(qemuparams):
590eb8dc403SDave Cobbley            return True
591eb8dc403SDave Cobbley        return False
592eb8dc403SDave Cobbley
593eb8dc403SDave Cobbley    def is_alive(self):
59482c905dcSAndrew Geissler        if not self.runqemu or self.runqemu.poll() is not None or self.runqemu_exited:
595eb8dc403SDave Cobbley            return False
596eb8dc403SDave Cobbley        if os.path.isfile(self.qemu_pidfile):
59796ff1984SBrad Bishop            # when handling pidfile, qemu creates the file, stat it, lock it and then write to it
59896ff1984SBrad Bishop            # so it's possible that the file has been created but the content is empty
59996ff1984SBrad Bishop            pidfile_timeout = time.time() + 3
60096ff1984SBrad Bishop            while time.time() < pidfile_timeout:
60196ff1984SBrad Bishop                with open(self.qemu_pidfile, 'r') as f:
60296ff1984SBrad Bishop                    qemu_pid = f.read().strip()
60396ff1984SBrad Bishop                # file created but not yet written contents
60496ff1984SBrad Bishop                if not qemu_pid:
60596ff1984SBrad Bishop                    time.sleep(0.5)
60696ff1984SBrad Bishop                    continue
60796ff1984SBrad Bishop                else:
60896ff1984SBrad Bishop                    if os.path.exists("/proc/" + qemu_pid):
60996ff1984SBrad Bishop                        self.qemupid = int(qemu_pid)
610eb8dc403SDave Cobbley                        return True
611eb8dc403SDave Cobbley        return False
612eb8dc403SDave Cobbley
6135f35090dSAndrew Geissler    def run_monitor(self, command, args=None, timeout=60):
6145f35090dSAndrew Geissler        if hasattr(self, 'qmp') and self.qmp:
6155f35090dSAndrew Geissler            if args is not None:
6165f35090dSAndrew Geissler                return self.qmp.cmd(command, args)
6175f35090dSAndrew Geissler            else:
618c926e17cSAndrew Geissler                return self.qmp.cmd(command)
619c926e17cSAndrew Geissler
620977dc1acSBrad Bishop    def run_serial(self, command, raw=False, timeout=60):
621*92b42cb3SPatrick Williams        # Returns (status, output) where status is 1 on success and 0 on error
622*92b42cb3SPatrick Williams
623eb8dc403SDave Cobbley        # We assume target system have echo to get command status
624eb8dc403SDave Cobbley        if not raw:
625eb8dc403SDave Cobbley            command = "%s; echo $?\n" % command
626eb8dc403SDave Cobbley
627eb8dc403SDave Cobbley        data = ''
628eb8dc403SDave Cobbley        status = 0
629eb8dc403SDave Cobbley        self.server_socket.sendall(command.encode('utf-8'))
630eb8dc403SDave Cobbley        start = time.time()
631eb8dc403SDave Cobbley        end = start + timeout
632eb8dc403SDave Cobbley        while True:
633eb8dc403SDave Cobbley            now = time.time()
634eb8dc403SDave Cobbley            if now >= end:
635eb8dc403SDave Cobbley                data += "<<< run_serial(): command timed out after %d seconds without output >>>\r\n\r\n" % timeout
636eb8dc403SDave Cobbley                break
637eb8dc403SDave Cobbley            try:
638eb8dc403SDave Cobbley                sread, _, _ = select.select([self.server_socket],[],[], end - now)
639eb8dc403SDave Cobbley            except InterruptedError:
640eb8dc403SDave Cobbley                continue
641eb8dc403SDave Cobbley            if sread:
642eb8dc403SDave Cobbley                answer = self.server_socket.recv(1024)
643eb8dc403SDave Cobbley                if answer:
644eb8dc403SDave Cobbley                    data += answer.decode('utf-8')
645eb8dc403SDave Cobbley                    # Search the prompt to stop
64682c905dcSAndrew Geissler                    if re.search(self.boot_patterns['search_cmd_finished'], data):
647eb8dc403SDave Cobbley                        break
648eb8dc403SDave Cobbley                else:
649ac69b488SWilliam A. Kennington III                    if self.canexit:
650ac69b488SWilliam A. Kennington III                        return (1, "")
651ac69b488SWilliam A. Kennington III                    raise Exception("No data on serial console socket, connection closed?")
652eb8dc403SDave Cobbley
653eb8dc403SDave Cobbley        if data:
654eb8dc403SDave Cobbley            if raw:
655eb8dc403SDave Cobbley                status = 1
656eb8dc403SDave Cobbley            else:
657eb8dc403SDave Cobbley                # Remove first line (command line) and last line (prompt)
658eb8dc403SDave Cobbley                data = data[data.find('$?\r\n')+4:data.rfind('\r\n')]
659eb8dc403SDave Cobbley                index = data.rfind('\r\n')
660eb8dc403SDave Cobbley                if index == -1:
661eb8dc403SDave Cobbley                    status_cmd = data
662eb8dc403SDave Cobbley                    data = ""
663eb8dc403SDave Cobbley                else:
664eb8dc403SDave Cobbley                    status_cmd = data[index+2:]
665eb8dc403SDave Cobbley                    data = data[:index]
666eb8dc403SDave Cobbley                if (status_cmd == "0"):
667eb8dc403SDave Cobbley                    status = 1
668eb8dc403SDave Cobbley        return (status, str(data))
669eb8dc403SDave Cobbley
670eb8dc403SDave Cobbley
671eb8dc403SDave Cobbley    def _dump_host(self):
672eb8dc403SDave Cobbley        self.host_dumper.create_dir("qemu")
6731a4b7ee2SBrad Bishop        self.logger.warning("Qemu ended unexpectedly, dump data from host"
674eb8dc403SDave Cobbley                " is in %s" % self.host_dumper.dump_dir)
675eb8dc403SDave Cobbley        self.host_dumper.dump_host()
676eb8dc403SDave Cobbley
677eb8dc403SDave Cobbley# This class is for reading data from a socket and passing it to logfunc
678eb8dc403SDave Cobbley# to be processed. It's completely event driven and has a straightforward
679eb8dc403SDave Cobbley# event loop. The mechanism for stopping the thread is a simple pipe which
680eb8dc403SDave Cobbley# will wake up the poll and allow for tearing everything down.
681eb8dc403SDave Cobbleyclass LoggingThread(threading.Thread):
682eb8dc403SDave Cobbley    def __init__(self, logfunc, sock, logger):
683eb8dc403SDave Cobbley        self.connection_established = threading.Event()
684eb8dc403SDave Cobbley        self.serversock = sock
685eb8dc403SDave Cobbley        self.logfunc = logfunc
686eb8dc403SDave Cobbley        self.logger = logger
687eb8dc403SDave Cobbley        self.readsock = None
688eb8dc403SDave Cobbley        self.running = False
689c926e17cSAndrew Geissler        self.canexit = False
690eb8dc403SDave Cobbley
691eb8dc403SDave Cobbley        self.errorevents = select.POLLERR | select.POLLHUP | select.POLLNVAL
692eb8dc403SDave Cobbley        self.readevents = select.POLLIN | select.POLLPRI
693eb8dc403SDave Cobbley
694eb8dc403SDave Cobbley        threading.Thread.__init__(self, target=self.threadtarget)
695eb8dc403SDave Cobbley
696eb8dc403SDave Cobbley    def threadtarget(self):
697eb8dc403SDave Cobbley        try:
698eb8dc403SDave Cobbley            self.eventloop()
699eb8dc403SDave Cobbley        finally:
700eb8dc403SDave Cobbley            self.teardown()
701eb8dc403SDave Cobbley
702eb8dc403SDave Cobbley    def run(self):
703eb8dc403SDave Cobbley        self.logger.debug("Starting logging thread")
704eb8dc403SDave Cobbley        self.readpipe, self.writepipe = os.pipe()
705eb8dc403SDave Cobbley        threading.Thread.run(self)
706eb8dc403SDave Cobbley
707eb8dc403SDave Cobbley    def stop(self):
708eb8dc403SDave Cobbley        self.logger.debug("Stopping logging thread")
709eb8dc403SDave Cobbley        if self.running:
710eb8dc403SDave Cobbley            os.write(self.writepipe, bytes("stop", "utf-8"))
711eb8dc403SDave Cobbley
712eb8dc403SDave Cobbley    def teardown(self):
713eb8dc403SDave Cobbley        self.logger.debug("Tearing down logging thread")
714eb8dc403SDave Cobbley        self.close_socket(self.serversock)
715eb8dc403SDave Cobbley
716eb8dc403SDave Cobbley        if self.readsock is not None:
717eb8dc403SDave Cobbley            self.close_socket(self.readsock)
718eb8dc403SDave Cobbley
719eb8dc403SDave Cobbley        self.close_ignore_error(self.readpipe)
720eb8dc403SDave Cobbley        self.close_ignore_error(self.writepipe)
721eb8dc403SDave Cobbley        self.running = False
722eb8dc403SDave Cobbley
723c926e17cSAndrew Geissler    def allowexit(self):
724c926e17cSAndrew Geissler        self.canexit = True
725c926e17cSAndrew Geissler
726eb8dc403SDave Cobbley    def eventloop(self):
727eb8dc403SDave Cobbley        poll = select.poll()
728eb8dc403SDave Cobbley        event_read_mask = self.errorevents | self.readevents
729eb8dc403SDave Cobbley        poll.register(self.serversock.fileno())
730eb8dc403SDave Cobbley        poll.register(self.readpipe, event_read_mask)
731eb8dc403SDave Cobbley
732eb8dc403SDave Cobbley        breakout = False
733eb8dc403SDave Cobbley        self.running = True
734eb8dc403SDave Cobbley        self.logger.debug("Starting thread event loop")
735eb8dc403SDave Cobbley        while not breakout:
736eb8dc403SDave Cobbley            events = poll.poll()
737eb8dc403SDave Cobbley            for event in events:
738eb8dc403SDave Cobbley                # An error occurred, bail out
739eb8dc403SDave Cobbley                if event[1] & self.errorevents:
740eb8dc403SDave Cobbley                    raise Exception(self.stringify_event(event[1]))
741eb8dc403SDave Cobbley
742eb8dc403SDave Cobbley                # Event to stop the thread
743eb8dc403SDave Cobbley                if self.readpipe == event[0]:
744eb8dc403SDave Cobbley                    self.logger.debug("Stop event received")
745eb8dc403SDave Cobbley                    breakout = True
746eb8dc403SDave Cobbley                    break
747eb8dc403SDave Cobbley
748eb8dc403SDave Cobbley                # A connection request was received
749eb8dc403SDave Cobbley                elif self.serversock.fileno() == event[0]:
750eb8dc403SDave Cobbley                    self.logger.debug("Connection request received")
751eb8dc403SDave Cobbley                    self.readsock, _ = self.serversock.accept()
752eb8dc403SDave Cobbley                    self.readsock.setblocking(0)
753eb8dc403SDave Cobbley                    poll.unregister(self.serversock.fileno())
754eb8dc403SDave Cobbley                    poll.register(self.readsock.fileno(), event_read_mask)
755eb8dc403SDave Cobbley
756eb8dc403SDave Cobbley                    self.logger.debug("Setting connection established event")
757eb8dc403SDave Cobbley                    self.connection_established.set()
758eb8dc403SDave Cobbley
759eb8dc403SDave Cobbley                # Actual data to be logged
760eb8dc403SDave Cobbley                elif self.readsock.fileno() == event[0]:
761eb8dc403SDave Cobbley                    data = self.recv(1024)
762eb8dc403SDave Cobbley                    self.logfunc(data)
763eb8dc403SDave Cobbley
764eb8dc403SDave Cobbley    # Since the socket is non-blocking make sure to honor EAGAIN
765eb8dc403SDave Cobbley    # and EWOULDBLOCK.
766eb8dc403SDave Cobbley    def recv(self, count):
767eb8dc403SDave Cobbley        try:
768eb8dc403SDave Cobbley            data = self.readsock.recv(count)
769eb8dc403SDave Cobbley        except socket.error as e:
770eb8dc403SDave Cobbley            if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK:
771ac69b488SWilliam A. Kennington III                return b''
772eb8dc403SDave Cobbley            else:
773eb8dc403SDave Cobbley                raise
774eb8dc403SDave Cobbley
775eb8dc403SDave Cobbley        if data is None:
776eb8dc403SDave Cobbley            raise Exception("No data on read ready socket")
777eb8dc403SDave Cobbley        elif not data:
778eb8dc403SDave Cobbley            # This actually means an orderly shutdown
779eb8dc403SDave Cobbley            # happened. But for this code it counts as an
780eb8dc403SDave Cobbley            # error since the connection shouldn't go away
781eb8dc403SDave Cobbley            # until qemu exits.
782c926e17cSAndrew Geissler            if not self.canexit:
783eb8dc403SDave Cobbley                raise Exception("Console connection closed unexpectedly")
784ac69b488SWilliam A. Kennington III            return b''
785eb8dc403SDave Cobbley
786eb8dc403SDave Cobbley        return data
787eb8dc403SDave Cobbley
788eb8dc403SDave Cobbley    def stringify_event(self, event):
789eb8dc403SDave Cobbley        val = ''
790eb8dc403SDave Cobbley        if select.POLLERR == event:
791eb8dc403SDave Cobbley            val = 'POLLER'
792eb8dc403SDave Cobbley        elif select.POLLHUP == event:
793eb8dc403SDave Cobbley            val = 'POLLHUP'
794eb8dc403SDave Cobbley        elif select.POLLNVAL == event:
795eb8dc403SDave Cobbley            val = 'POLLNVAL'
796eb8dc403SDave Cobbley        return val
797eb8dc403SDave Cobbley
798eb8dc403SDave Cobbley    def close_socket(self, sock):
799eb8dc403SDave Cobbley        sock.shutdown(socket.SHUT_RDWR)
800eb8dc403SDave Cobbley        sock.close()
801eb8dc403SDave Cobbley
802eb8dc403SDave Cobbley    def close_ignore_error(self, fd):
803eb8dc403SDave Cobbley        try:
804eb8dc403SDave Cobbley            os.close(fd)
805eb8dc403SDave Cobbley        except OSError:
806eb8dc403SDave Cobbley            pass
807