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