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 2482c905dcSAndrew Geisslerfrom collections import defaultdict 25c926e17cSAndrew Geisslerimport importlib 26eb8dc403SDave Cobbley 27eb8dc403SDave Cobbley# Get Unicode non printable control chars 28eb8dc403SDave Cobbleycontrol_range = list(range(0,32))+list(range(127,160)) 29eb8dc403SDave Cobbleycontrol_chars = [chr(x) for x in control_range 30eb8dc403SDave Cobbley if chr(x) not in string.printable] 31eb8dc403SDave Cobbleyre_control_char = re.compile('[%s]' % re.escape("".join(control_chars))) 32eb8dc403SDave Cobbley 33eb8dc403SDave Cobbleyclass QemuRunner: 34eb8dc403SDave Cobbley 35*8f840685SAndrew Geissler def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, use_kvm, logger, use_slirp=False, 36*8f840685SAndrew Geissler serial_ports=2, boot_patterns = defaultdict(str), use_ovmf=False, workdir=None, tmpfsdir=None): 37eb8dc403SDave Cobbley 38eb8dc403SDave Cobbley # Popen object for runqemu 39eb8dc403SDave Cobbley self.runqemu = None 4082c905dcSAndrew Geissler self.runqemu_exited = False 41eb8dc403SDave Cobbley # pid of the qemu process that runqemu will start 42eb8dc403SDave Cobbley self.qemupid = None 43eb8dc403SDave Cobbley # target ip - from the command line or runqemu output 44eb8dc403SDave Cobbley self.ip = None 45eb8dc403SDave Cobbley # host ip - where qemu is running 46eb8dc403SDave Cobbley self.server_ip = None 47eb8dc403SDave Cobbley # target ip netmask 48eb8dc403SDave Cobbley self.netmask = None 49eb8dc403SDave Cobbley 50eb8dc403SDave Cobbley self.machine = machine 51eb8dc403SDave Cobbley self.rootfs = rootfs 52eb8dc403SDave Cobbley self.display = display 53eb8dc403SDave Cobbley self.tmpdir = tmpdir 54eb8dc403SDave Cobbley self.deploy_dir_image = deploy_dir_image 55eb8dc403SDave Cobbley self.logfile = logfile 56eb8dc403SDave Cobbley self.boottime = boottime 57eb8dc403SDave Cobbley self.logged = False 58eb8dc403SDave Cobbley self.thread = None 59eb8dc403SDave Cobbley self.use_kvm = use_kvm 6082c905dcSAndrew Geissler self.use_ovmf = use_ovmf 6119323693SBrad Bishop self.use_slirp = use_slirp 6282c905dcSAndrew Geissler self.serial_ports = serial_ports 63eb8dc403SDave Cobbley self.msg = '' 6482c905dcSAndrew Geissler self.boot_patterns = boot_patterns 653b8a17c1SAndrew Geissler self.tmpfsdir = tmpfsdir 66eb8dc403SDave Cobbley 670903674eSAndrew Geissler self.runqemutime = 300 68b7d28619SAndrew Geissler if not workdir: 69b7d28619SAndrew Geissler workdir = os.getcwd() 70b7d28619SAndrew Geissler self.qemu_pidfile = workdir + '/pidfile_' + str(os.getpid()) 7115ae2509SBrad Bishop self.monitorpipe = None 72eb8dc403SDave Cobbley 73eb8dc403SDave Cobbley self.logger = logger 74ac69b488SWilliam A. Kennington III # Whether we're expecting an exit and should show related errors 75ac69b488SWilliam A. Kennington III self.canexit = False 76eb8dc403SDave Cobbley 7782c905dcSAndrew Geissler # Enable testing other OS's 7882c905dcSAndrew Geissler # Set commands for target communication, and default to Linux ALWAYS 7982c905dcSAndrew Geissler # Other OS's or baremetal applications need to provide their 8082c905dcSAndrew Geissler # own implementation passing it through QemuRunner's constructor 8182c905dcSAndrew Geissler # or by passing them through TESTIMAGE_BOOT_PATTERNS[flag] 8282c905dcSAndrew Geissler # provided variables, where <flag> is one of the mentioned below. 8382c905dcSAndrew Geissler accepted_patterns = ['search_reached_prompt', 'send_login_user', 'search_login_succeeded', 'search_cmd_finished'] 8482c905dcSAndrew Geissler default_boot_patterns = defaultdict(str) 8582c905dcSAndrew Geissler # Default to the usual paterns used to communicate with the target 8687f5cff0SAndrew Geissler default_boot_patterns['search_reached_prompt'] = ' login:' 8782c905dcSAndrew Geissler default_boot_patterns['send_login_user'] = 'root\n' 8882c905dcSAndrew Geissler default_boot_patterns['search_login_succeeded'] = r"root@[a-zA-Z0-9\-]+:~#" 8982c905dcSAndrew Geissler default_boot_patterns['search_cmd_finished'] = r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#" 9082c905dcSAndrew Geissler 9182c905dcSAndrew Geissler # Only override patterns that were set e.g. login user TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n" 9282c905dcSAndrew Geissler for pattern in accepted_patterns: 9382c905dcSAndrew Geissler if not self.boot_patterns[pattern]: 9482c905dcSAndrew Geissler self.boot_patterns[pattern] = default_boot_patterns[pattern] 9582c905dcSAndrew Geissler 96eb8dc403SDave Cobbley def create_socket(self): 97eb8dc403SDave Cobbley try: 98eb8dc403SDave Cobbley sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 99eb8dc403SDave Cobbley sock.setblocking(0) 100eb8dc403SDave Cobbley sock.bind(("127.0.0.1",0)) 101eb8dc403SDave Cobbley sock.listen(2) 102eb8dc403SDave Cobbley port = sock.getsockname()[1] 103eb8dc403SDave Cobbley self.logger.debug("Created listening socket for qemu serial console on: 127.0.0.1:%s" % port) 104eb8dc403SDave Cobbley return (sock, port) 105eb8dc403SDave Cobbley 106eb8dc403SDave Cobbley except socket.error: 107eb8dc403SDave Cobbley sock.close() 108eb8dc403SDave Cobbley raise 109eb8dc403SDave Cobbley 11087f5cff0SAndrew Geissler def decode_qemulog(self, todecode): 11187f5cff0SAndrew Geissler # Sanitize the data received from qemu as it may contain control characters 11287f5cff0SAndrew Geissler msg = todecode.decode("utf-8", errors='ignore') 11387f5cff0SAndrew Geissler msg = re_control_char.sub('', msg) 11487f5cff0SAndrew Geissler return msg 11587f5cff0SAndrew Geissler 116eb8dc403SDave Cobbley def log(self, msg): 117eb8dc403SDave Cobbley if self.logfile: 11887f5cff0SAndrew Geissler msg = self.decode_qemulog(msg) 119eb8dc403SDave Cobbley self.msg += msg 120eb8dc403SDave Cobbley with codecs.open(self.logfile, "a", encoding="utf-8") as f: 121eb8dc403SDave Cobbley f.write("%s" % msg) 122eb8dc403SDave Cobbley 123eb8dc403SDave Cobbley def getOutput(self, o): 124eb8dc403SDave Cobbley import fcntl 125eb8dc403SDave Cobbley fl = fcntl.fcntl(o, fcntl.F_GETFL) 126eb8dc403SDave Cobbley fcntl.fcntl(o, fcntl.F_SETFL, fl | os.O_NONBLOCK) 127d159c7fbSAndrew Geissler try: 128eb8dc403SDave Cobbley return os.read(o.fileno(), 1000000).decode("utf-8") 129d159c7fbSAndrew Geissler except BlockingIOError: 130d159c7fbSAndrew Geissler return "" 131eb8dc403SDave Cobbley 132eb8dc403SDave Cobbley 133eb8dc403SDave Cobbley def handleSIGCHLD(self, signum, frame): 134eb8dc403SDave Cobbley if self.runqemu and self.runqemu.poll(): 135eb8dc403SDave Cobbley if self.runqemu.returncode: 13682c905dcSAndrew Geissler self.logger.error('runqemu exited with code %d' % self.runqemu.returncode) 13782c905dcSAndrew Geissler self.logger.error('Output from runqemu:\n%s' % self.getOutput(self.runqemu.stdout)) 138eb8dc403SDave Cobbley self.stop() 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' 177517393d9SAndrew Geissler launch_cmd += ' %s %s' % (runqemuparams, self.machine) 178517393d9SAndrew Geissler if self.rootfs.endswith('.vmdk'): 179517393d9SAndrew Geissler self.logger.debug('Bypassing VMDK rootfs for runqemu') 180517393d9SAndrew Geissler else: 181517393d9SAndrew Geissler launch_cmd += ' %s' % (self.rootfs) 182eb8dc403SDave Cobbley 183eb8dc403SDave Cobbley return self.launch(launch_cmd, qemuparams=qemuparams, get_ip=get_ip, extra_bootparams=extra_bootparams, env=env) 184eb8dc403SDave Cobbley 185eb8dc403SDave Cobbley def launch(self, launch_cmd, get_ip = True, qemuparams = None, extra_bootparams = None, env = None): 186c926e17cSAndrew Geissler # use logfile to determine the recipe-sysroot-native path and 187c926e17cSAndrew Geissler # then add in the site-packages path components and add that 188b542dec1SPatrick Williams # to the python sys.path so the qmp module can be found. 189c926e17cSAndrew Geissler python_path = os.path.dirname(os.path.dirname(self.logfile)) 190eff27476SAndrew Geissler python_path += "/recipe-sysroot-native/usr/lib/qemu-python" 191c926e17cSAndrew Geissler sys.path.append(python_path) 192c926e17cSAndrew Geissler importlib.invalidate_caches() 193c926e17cSAndrew Geissler try: 194c926e17cSAndrew Geissler qmp = importlib.import_module("qmp") 19587f5cff0SAndrew Geissler except Exception as e: 196b542dec1SPatrick Williams self.logger.error("qemurunner: qmp module missing, please ensure it's installed in %s (%s)" % (python_path, str(e))) 197c926e17cSAndrew Geissler return False 198c926e17cSAndrew Geissler # Path relative to tmpdir used as cwd for qemu below to avoid unix socket path length issues 199c926e17cSAndrew Geissler qmp_file = "." + next(tempfile._get_candidate_names()) 200c926e17cSAndrew Geissler qmp_param = ' -S -qmp unix:./%s,server,wait' % (qmp_file) 201c926e17cSAndrew Geissler qmp_port = self.tmpdir + "/" + qmp_file 2020903674eSAndrew Geissler # Create a second socket connection for debugging use, 2030903674eSAndrew Geissler # note this will NOT cause qemu to block waiting for the connection 2040903674eSAndrew Geissler qmp_file2 = "." + next(tempfile._get_candidate_names()) 2050903674eSAndrew Geissler qmp_param += ' -qmp unix:./%s,server,nowait' % (qmp_file2) 2060903674eSAndrew Geissler qmp_port2 = self.tmpdir + "/" + qmp_file2 2070903674eSAndrew Geissler self.logger.info("QMP Available for connection at %s" % (qmp_port2)) 208c926e17cSAndrew Geissler 209eb8dc403SDave Cobbley try: 21082c905dcSAndrew Geissler if self.serial_ports >= 2: 211f86d0556SBrad Bishop self.threadsock, threadport = self.create_socket() 212eb8dc403SDave Cobbley self.server_socket, self.serverport = self.create_socket() 213eb8dc403SDave Cobbley except socket.error as msg: 214eb8dc403SDave Cobbley self.logger.error("Failed to create listening socket: %s" % msg[1]) 215eb8dc403SDave Cobbley return False 216eb8dc403SDave Cobbley 21795ac1b8dSAndrew Geissler bootparams = ' printk.time=1' 218eb8dc403SDave Cobbley if extra_bootparams: 219eb8dc403SDave Cobbley bootparams = bootparams + ' ' + extra_bootparams 220eb8dc403SDave Cobbley 221eb8dc403SDave Cobbley # Ask QEMU to store the QEMU process PID in file, this way we don't have to parse running processes 222eb8dc403SDave Cobbley # and analyze descendents in order to determine it. 223eb8dc403SDave Cobbley if os.path.exists(self.qemu_pidfile): 224eb8dc403SDave Cobbley os.remove(self.qemu_pidfile) 225c926e17cSAndrew Geissler self.qemuparams = 'bootparams="{0}" qemuparams="-pidfile {1} {2}"'.format(bootparams, self.qemu_pidfile, qmp_param) 226c926e17cSAndrew Geissler 227eb8dc403SDave Cobbley if qemuparams: 228eb8dc403SDave Cobbley self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' 229eb8dc403SDave Cobbley 23082c905dcSAndrew Geissler if self.serial_ports >= 2: 23115ae2509SBrad Bishop launch_cmd += ' tcpserial=%s:%s %s' % (threadport, self.serverport, self.qemuparams) 23282c905dcSAndrew Geissler else: 23382c905dcSAndrew Geissler launch_cmd += ' tcpserial=%s %s' % (self.serverport, self.qemuparams) 234eb8dc403SDave Cobbley 235eb8dc403SDave Cobbley self.origchldhandler = signal.getsignal(signal.SIGCHLD) 236eb8dc403SDave Cobbley signal.signal(signal.SIGCHLD, self.handleSIGCHLD) 237eb8dc403SDave Cobbley 238eb8dc403SDave Cobbley self.logger.debug('launchcmd=%s' % (launch_cmd)) 239eb8dc403SDave Cobbley 240eb8dc403SDave Cobbley # FIXME: We pass in stdin=subprocess.PIPE here to work around stty 241eb8dc403SDave Cobbley # blocking at the end of the runqemu script when using this within 242eb8dc403SDave Cobbley # oe-selftest (this makes stty error out immediately). There ought 243eb8dc403SDave Cobbley # to be a proper fix but this will suffice for now. 244c926e17cSAndrew 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) 245eb8dc403SDave Cobbley output = self.runqemu.stdout 2465f35090dSAndrew Geissler launch_time = time.time() 247eb8dc403SDave Cobbley 248eb8dc403SDave Cobbley # 249eb8dc403SDave Cobbley # We need the preexec_fn above so that all runqemu processes can easily be killed 250eb8dc403SDave Cobbley # (by killing their process group). This presents a problem if this controlling 251eb8dc403SDave Cobbley # process itself is killed however since those processes don't notice the death 252eb8dc403SDave Cobbley # of the parent and merrily continue on. 253eb8dc403SDave Cobbley # 254eb8dc403SDave Cobbley # Rather than hack runqemu to deal with this, we add something here instead. 255eb8dc403SDave Cobbley # Basically we fork off another process which holds an open pipe to the parent 256eb8dc403SDave Cobbley # and also is setpgrp. If/when the pipe sees EOF from the parent dieing, it kills 257eb8dc403SDave Cobbley # the process group. This is like pctrl's PDEATHSIG but for a process group 258eb8dc403SDave Cobbley # rather than a single process. 259eb8dc403SDave Cobbley # 260eb8dc403SDave Cobbley r, w = os.pipe() 261eb8dc403SDave Cobbley self.monitorpid = os.fork() 262eb8dc403SDave Cobbley if self.monitorpid: 263eb8dc403SDave Cobbley os.close(r) 264eb8dc403SDave Cobbley self.monitorpipe = os.fdopen(w, "w") 265eb8dc403SDave Cobbley else: 266eb8dc403SDave Cobbley # child process 267eb8dc403SDave Cobbley os.setpgrp() 268eb8dc403SDave Cobbley os.close(w) 269eb8dc403SDave Cobbley r = os.fdopen(r) 270eb8dc403SDave Cobbley x = r.read() 271eb8dc403SDave Cobbley os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM) 27293c203f3SPatrick Williams os._exit(0) 273eb8dc403SDave Cobbley 274eb8dc403SDave Cobbley self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid) 2758e7b46e2SPatrick Williams self.logger.debug("waiting at most %d seconds for qemu pid (%s)" % 276eb8dc403SDave Cobbley (self.runqemutime, time.strftime("%D %H:%M:%S"))) 277eb8dc403SDave Cobbley endtime = time.time() + self.runqemutime 278eb8dc403SDave Cobbley while not self.is_alive() and time.time() < endtime: 279eb8dc403SDave Cobbley if self.runqemu.poll(): 28082c905dcSAndrew Geissler if self.runqemu_exited: 281c926e17cSAndrew Geissler self.logger.warning("runqemu during is_alive() test") 28282c905dcSAndrew Geissler return False 283eb8dc403SDave Cobbley if self.runqemu.returncode: 284eb8dc403SDave Cobbley # No point waiting any longer 28596ff1984SBrad Bishop self.logger.warning('runqemu exited with code %d' % self.runqemu.returncode) 28696ff1984SBrad Bishop self.logger.warning("Output from runqemu:\n%s" % self.getOutput(output)) 287f86d0556SBrad Bishop self.stop() 288eb8dc403SDave Cobbley return False 289eb8dc403SDave Cobbley time.sleep(0.5) 290eb8dc403SDave Cobbley 29182c905dcSAndrew Geissler if self.runqemu_exited: 292c926e17cSAndrew Geissler self.logger.warning("runqemu after timeout") 29382c905dcSAndrew Geissler 294c926e17cSAndrew Geissler if self.runqemu.returncode: 295c926e17cSAndrew Geissler self.logger.warning('runqemu exited with code %d' % self.runqemu.returncode) 296c926e17cSAndrew Geissler 297c926e17cSAndrew Geissler if not self.is_alive(): 2988e7b46e2SPatrick Williams self.logger.error("Qemu pid didn't appear in %d seconds (%s)" % 299ac69b488SWilliam A. Kennington III (self.runqemutime, time.strftime("%D %H:%M:%S"))) 300ac69b488SWilliam A. Kennington III 301ac69b488SWilliam A. Kennington III qemu_pid = None 302ac69b488SWilliam A. Kennington III if os.path.isfile(self.qemu_pidfile): 303ac69b488SWilliam A. Kennington III with open(self.qemu_pidfile, 'r') as f: 304ac69b488SWilliam A. Kennington III qemu_pid = f.read().strip() 305ac69b488SWilliam A. Kennington III 306ac69b488SWilliam A. Kennington III self.logger.error("Status information, poll status: %s, pidfile exists: %s, pidfile contents %s, proc pid exists %s" 307ac69b488SWilliam A. Kennington III % (self.runqemu.poll(), os.path.isfile(self.qemu_pidfile), str(qemu_pid), os.path.exists("/proc/" + str(qemu_pid)))) 308ac69b488SWilliam A. Kennington III 309ac69b488SWilliam A. Kennington III # Dump all processes to help us to figure out what is going on... 310ac69b488SWilliam A. Kennington III ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,pri,ni,command '], stdout=subprocess.PIPE).communicate()[0] 311ac69b488SWilliam A. Kennington III processes = ps.decode("utf-8") 312ac69b488SWilliam A. Kennington III self.logger.debug("Running processes:\n%s" % processes) 313ac69b488SWilliam A. Kennington III op = self.getOutput(output) 314ac69b488SWilliam A. Kennington III self.stop() 315ac69b488SWilliam A. Kennington III if op: 316ac69b488SWilliam A. Kennington III self.logger.error("Output from runqemu:\n%s" % op) 317ac69b488SWilliam A. Kennington III else: 318ac69b488SWilliam A. Kennington III self.logger.error("No output from runqemu.\n") 319c926e17cSAndrew Geissler return False 320c926e17cSAndrew Geissler 321c926e17cSAndrew Geissler # Create the client socket for the QEMU Monitor Control Socket 322c926e17cSAndrew Geissler # This will allow us to read status from Qemu if the the process 323c926e17cSAndrew Geissler # is still alive 324c926e17cSAndrew Geissler self.logger.debug("QMP Initializing to %s" % (qmp_port)) 325c926e17cSAndrew Geissler # chdir dance for path length issues with unix sockets 326c926e17cSAndrew Geissler origpath = os.getcwd() 327c926e17cSAndrew Geissler try: 328c926e17cSAndrew Geissler os.chdir(os.path.dirname(qmp_port)) 329c926e17cSAndrew Geissler try: 33087f5cff0SAndrew Geissler from qmp.legacy import QEMUMonitorProtocol 33187f5cff0SAndrew Geissler self.qmp = QEMUMonitorProtocol(os.path.basename(qmp_port)) 332c926e17cSAndrew Geissler except OSError as msg: 333c926e17cSAndrew Geissler self.logger.warning("Failed to initialize qemu monitor socket: %s File: %s" % (msg, msg.filename)) 334c926e17cSAndrew Geissler return False 335c926e17cSAndrew Geissler 336c926e17cSAndrew Geissler self.logger.debug("QMP Connecting to %s" % (qmp_port)) 337c926e17cSAndrew Geissler if not os.path.exists(qmp_port) and self.is_alive(): 338c926e17cSAndrew Geissler self.logger.debug("QMP Port does not exist waiting for it to be created") 339c926e17cSAndrew Geissler endtime = time.time() + self.runqemutime 340c926e17cSAndrew Geissler while not os.path.exists(qmp_port) and self.is_alive() and time.time() < endtime: 341c926e17cSAndrew Geissler self.logger.info("QMP port does not exist yet!") 342c926e17cSAndrew Geissler time.sleep(0.5) 343c926e17cSAndrew Geissler if not os.path.exists(qmp_port) and self.is_alive(): 344c926e17cSAndrew Geissler self.logger.warning("QMP Port still does not exist but QEMU is alive") 345c926e17cSAndrew Geissler return False 346c926e17cSAndrew Geissler 347c926e17cSAndrew Geissler try: 3486aa7eec5SAndrew Geissler # set timeout value for all QMP calls 3496aa7eec5SAndrew Geissler self.qmp.settimeout(self.runqemutime) 350c926e17cSAndrew Geissler self.qmp.connect() 3515f35090dSAndrew Geissler connect_time = time.time() 3528e7b46e2SPatrick Williams self.logger.info("QMP connected to QEMU at %s and took %.2f seconds" % 3535f35090dSAndrew Geissler (time.strftime("%D %H:%M:%S"), 3545f35090dSAndrew Geissler time.time() - launch_time)) 355c926e17cSAndrew Geissler except OSError as msg: 356c926e17cSAndrew Geissler self.logger.warning("Failed to connect qemu monitor socket: %s File: %s" % (msg, msg.filename)) 357c926e17cSAndrew Geissler return False 3587784c429SPatrick Williams except qmp.legacy.QMPError as msg: 359c926e17cSAndrew Geissler self.logger.warning("Failed to communicate with qemu monitor: %s" % (msg)) 360c926e17cSAndrew Geissler return False 361c926e17cSAndrew Geissler finally: 362c926e17cSAndrew Geissler os.chdir(origpath) 363c926e17cSAndrew Geissler 3640903674eSAndrew Geissler # We worry that mmap'd libraries may cause page faults which hang the qemu VM for periods 3650903674eSAndrew Geissler # causing failures. Before we "start" qemu, read through it's mapped files to try and 3660903674eSAndrew Geissler # ensure we don't hit page faults later 3670903674eSAndrew Geissler mapdir = "/proc/" + str(self.qemupid) + "/map_files/" 3680903674eSAndrew Geissler try: 3690903674eSAndrew Geissler for f in os.listdir(mapdir): 3705f35090dSAndrew Geissler try: 3710903674eSAndrew Geissler linktarget = os.readlink(os.path.join(mapdir, f)) 3720903674eSAndrew Geissler if not linktarget.startswith("/") or linktarget.startswith("/dev") or "deleted" in linktarget: 3730903674eSAndrew Geissler continue 3740903674eSAndrew Geissler with open(linktarget, "rb") as readf: 3750903674eSAndrew Geissler data = True 3760903674eSAndrew Geissler while data: 3770903674eSAndrew Geissler data = readf.read(4096) 3785f35090dSAndrew Geissler except FileNotFoundError: 3795f35090dSAndrew Geissler continue 3800903674eSAndrew Geissler # Centos7 doesn't allow us to read /map_files/ 3810903674eSAndrew Geissler except PermissionError: 3820903674eSAndrew Geissler pass 3830903674eSAndrew Geissler 3840903674eSAndrew Geissler # Release the qemu process to continue running 385c926e17cSAndrew Geissler self.run_monitor('cont') 3868e7b46e2SPatrick Williams self.logger.info("QMP released QEMU at %s and took %.2f seconds from connect" % 3875f35090dSAndrew Geissler (time.strftime("%D %H:%M:%S"), 3885f35090dSAndrew Geissler time.time() - connect_time)) 389c926e17cSAndrew Geissler 390eb8dc403SDave Cobbley # We are alive: qemu is running 391eb8dc403SDave Cobbley out = self.getOutput(output) 392eb8dc403SDave Cobbley netconf = False # network configuration is not required by default 3938e7b46e2SPatrick Williams self.logger.debug("qemu started in %.2f seconds - qemu procces pid is %s (%s)" % 394eb8dc403SDave Cobbley (time.time() - (endtime - self.runqemutime), 395eb8dc403SDave Cobbley self.qemupid, time.strftime("%D %H:%M:%S"))) 396eb8dc403SDave Cobbley cmdline = '' 39782c905dcSAndrew Geissler if get_ip: 398eb8dc403SDave Cobbley with open('/proc/%s/cmdline' % self.qemupid) as p: 399eb8dc403SDave Cobbley cmdline = p.read() 400eb8dc403SDave Cobbley # It is needed to sanitize the data received 401eb8dc403SDave Cobbley # because is possible to have control characters 402eb8dc403SDave Cobbley cmdline = re_control_char.sub(' ', cmdline) 403eb8dc403SDave Cobbley try: 40419323693SBrad Bishop if self.use_slirp: 405517393d9SAndrew Geissler tcp_ports = cmdline.split("hostfwd=tcp:")[1] 406517393d9SAndrew Geissler ip, tcp_ports = tcp_ports.split(":")[:2] 40719323693SBrad Bishop host_port = tcp_ports[:tcp_ports.find('-')] 408517393d9SAndrew Geissler self.ip = "%s:%s" % (ip, host_port) 40919323693SBrad Bishop else: 410f86d0556SBrad Bishop ips = re.findall(r"((?:[0-9]{1,3}\.){3}[0-9]{1,3})", cmdline.split("ip=")[1]) 411eb8dc403SDave Cobbley self.ip = ips[0] 412eb8dc403SDave Cobbley self.server_ip = ips[1] 413eb8dc403SDave Cobbley self.logger.debug("qemu cmdline used:\n{}".format(cmdline)) 414eb8dc403SDave Cobbley except (IndexError, ValueError): 415eb8dc403SDave Cobbley # Try to get network configuration from runqemu output 416595f6308SAndrew Geissler match = re.match(r'.*Network configuration: (?:ip=)*([0-9.]+)::([0-9.]+):([0-9.]+).*', 417eb8dc403SDave Cobbley out, re.MULTILINE | re.DOTALL) 418eb8dc403SDave Cobbley if match: 419eb8dc403SDave Cobbley self.ip, self.server_ip, self.netmask = match.groups() 420eb8dc403SDave Cobbley # network configuration is required as we couldn't get it 421eb8dc403SDave Cobbley # from the runqemu command line, so qemu doesn't run kernel 422eb8dc403SDave Cobbley # and guest networking is not configured 423eb8dc403SDave Cobbley netconf = True 424eb8dc403SDave Cobbley else: 425eb8dc403SDave Cobbley self.logger.error("Couldn't get ip from qemu command line and runqemu output! " 426eb8dc403SDave Cobbley "Here is the qemu command line used:\n%s\n" 427eb8dc403SDave Cobbley "and output from runqemu:\n%s" % (cmdline, out)) 428eb8dc403SDave Cobbley self.stop() 429eb8dc403SDave Cobbley return False 430eb8dc403SDave Cobbley 431eb8dc403SDave Cobbley self.logger.debug("Target IP: %s" % self.ip) 432eb8dc403SDave Cobbley self.logger.debug("Server IP: %s" % self.server_ip) 433eb8dc403SDave Cobbley 43482c905dcSAndrew Geissler if self.serial_ports >= 2: 435f86d0556SBrad Bishop self.thread = LoggingThread(self.log, self.threadsock, self.logger) 436eb8dc403SDave Cobbley self.thread.start() 437eb8dc403SDave Cobbley if not self.thread.connection_established.wait(self.boottime): 438eb8dc403SDave Cobbley self.logger.error("Didn't receive a console connection from qemu. " 439eb8dc403SDave Cobbley "Here is the qemu command line used:\n%s\nand " 440eb8dc403SDave Cobbley "output from runqemu:\n%s" % (cmdline, out)) 441eb8dc403SDave Cobbley self.stop_thread() 442eb8dc403SDave Cobbley return False 443eb8dc403SDave Cobbley 444eb8dc403SDave Cobbley self.logger.debug("Output from runqemu:\n%s", out) 445eb8dc403SDave Cobbley self.logger.debug("Waiting at most %d seconds for login banner (%s)" % 446eb8dc403SDave Cobbley (self.boottime, time.strftime("%D %H:%M:%S"))) 447eb8dc403SDave Cobbley endtime = time.time() + self.boottime 448e760df85SPatrick Williams filelist = [self.server_socket, self.runqemu.stdout] 449eb8dc403SDave Cobbley reachedlogin = False 450eb8dc403SDave Cobbley stopread = False 451eb8dc403SDave Cobbley qemusock = None 452eb8dc403SDave Cobbley bootlog = b'' 453eb8dc403SDave Cobbley data = b'' 454eb8dc403SDave Cobbley while time.time() < endtime and not stopread: 455eb8dc403SDave Cobbley try: 456e760df85SPatrick Williams sread, swrite, serror = select.select(filelist, [], [], 5) 457eb8dc403SDave Cobbley except InterruptedError: 458eb8dc403SDave Cobbley continue 459e760df85SPatrick Williams for file in sread: 460e760df85SPatrick Williams if file is self.server_socket: 461eb8dc403SDave Cobbley qemusock, addr = self.server_socket.accept() 462e760df85SPatrick Williams qemusock.setblocking(False) 463e760df85SPatrick Williams filelist.append(qemusock) 464e760df85SPatrick Williams filelist.remove(self.server_socket) 465eb8dc403SDave Cobbley self.logger.debug("Connection from %s:%s" % addr) 466eb8dc403SDave Cobbley else: 4676aa7eec5SAndrew Geissler # try to avoid reading only a single character at a time 4686aa7eec5SAndrew Geissler time.sleep(0.1) 469e760df85SPatrick Williams if hasattr(file, 'read'): 470e760df85SPatrick Williams read = file.read(1024) 471e760df85SPatrick Williams elif hasattr(file, 'recv'): 472e760df85SPatrick Williams read = file.recv(1024) 473e760df85SPatrick Williams else: 474e760df85SPatrick Williams self.logger.error('Invalid file type: %s\n%s' % (file)) 475e760df85SPatrick Williams read = b'' 476e760df85SPatrick Williams 477e760df85SPatrick Williams self.logger.debug2('Partial boot log:\n%s' % (read.decode('utf-8', errors='ignore'))) 478e760df85SPatrick Williams data = data + read 479eb8dc403SDave Cobbley if data: 480eb8dc403SDave Cobbley bootlog += data 48182c905dcSAndrew Geissler if self.serial_ports < 2: 482e760df85SPatrick Williams # this file has mixed console/kernel data, log it to logfile 48382c905dcSAndrew Geissler self.log(data) 48482c905dcSAndrew Geissler 485eb8dc403SDave Cobbley data = b'' 48687f5cff0SAndrew Geissler 48787f5cff0SAndrew Geissler decodedlog = self.decode_qemulog(bootlog) 48887f5cff0SAndrew Geissler if self.boot_patterns['search_reached_prompt'] in decodedlog: 489e760df85SPatrick Williams self.server_socket.close() 490eb8dc403SDave Cobbley self.server_socket = qemusock 491eb8dc403SDave Cobbley stopread = True 492eb8dc403SDave Cobbley reachedlogin = True 4938e7b46e2SPatrick Williams self.logger.debug("Reached login banner in %.2f seconds (%s)" % 494eb8dc403SDave Cobbley (time.time() - (endtime - self.boottime), 4958e7b46e2SPatrick Williams time.strftime("%D %H:%M:%S"))) 496eb8dc403SDave Cobbley else: 497eb8dc403SDave Cobbley # no need to check if reachedlogin unless we support multiple connections 498eb8dc403SDave Cobbley self.logger.debug("QEMU socket disconnected before login banner reached. (%s)" % 499eb8dc403SDave Cobbley time.strftime("%D %H:%M:%S")) 500e760df85SPatrick Williams filelist.remove(file) 501e760df85SPatrick Williams file.close() 502eb8dc403SDave Cobbley stopread = True 503eb8dc403SDave Cobbley 504eb8dc403SDave Cobbley if not reachedlogin: 505eb8dc403SDave Cobbley if time.time() >= endtime: 50696ff1984SBrad Bishop self.logger.warning("Target didn't reach login banner in %d seconds (%s)" % 507eb8dc403SDave Cobbley (self.boottime, time.strftime("%D %H:%M:%S"))) 508eb8dc403SDave Cobbley tail = lambda l: "\n".join(l.splitlines()[-25:]) 50987f5cff0SAndrew Geissler bootlog = self.decode_qemulog(bootlog) 510eb8dc403SDave Cobbley # in case bootlog is empty, use tail qemu log store at self.msg 511eb8dc403SDave Cobbley lines = tail(bootlog if bootlog else self.msg) 51287f5cff0SAndrew Geissler self.logger.warning("Last 25 lines of text (%d):\n%s" % (len(bootlog), lines)) 51396ff1984SBrad Bishop self.logger.warning("Check full boot log: %s" % self.logfile) 514eb8dc403SDave Cobbley self.stop() 515eb8dc403SDave Cobbley return False 516eb8dc403SDave Cobbley 517eb8dc403SDave Cobbley # If we are not able to login the tests can continue 518eb8dc403SDave Cobbley try: 519c3d88e4dSAndrew Geissler (status, output) = self.run_serial(self.boot_patterns['send_login_user'], raw=True, timeout=120) 52082c905dcSAndrew Geissler if re.search(self.boot_patterns['search_login_succeeded'], output): 521eb8dc403SDave Cobbley self.logged = True 5228e7b46e2SPatrick Williams self.logger.debug("Logged in as %s in serial console" % self.boot_patterns['send_login_user'].replace("\n", "")) 523eb8dc403SDave Cobbley if netconf: 524eb8dc403SDave Cobbley # configure guest networking 525eb8dc403SDave Cobbley cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip, self.netmask) 526eb8dc403SDave Cobbley output = self.run_serial(cmd, raw=True)[1] 527f86d0556SBrad Bishop if re.search(r"root@[a-zA-Z0-9\-]+:~#", output): 528eb8dc403SDave Cobbley self.logger.debug("configured ip address %s", self.ip) 529eb8dc403SDave Cobbley else: 530eb8dc403SDave Cobbley self.logger.debug("Couldn't configure guest networking") 531eb8dc403SDave Cobbley else: 53296ff1984SBrad Bishop self.logger.warning("Couldn't login into serial console" 5338e7b46e2SPatrick Williams " as %s using blank password" % self.boot_patterns['send_login_user'].replace("\n", "")) 53496ff1984SBrad Bishop self.logger.warning("The output:\n%s" % output) 535eb8dc403SDave Cobbley except: 53696ff1984SBrad Bishop self.logger.warning("Serial console failed while trying to login") 537eb8dc403SDave Cobbley return True 538eb8dc403SDave Cobbley 539eb8dc403SDave Cobbley def stop(self): 540eb8dc403SDave Cobbley if hasattr(self, "origchldhandler"): 541eb8dc403SDave Cobbley signal.signal(signal.SIGCHLD, self.origchldhandler) 5421a4b7ee2SBrad Bishop self.stop_thread() 5431a4b7ee2SBrad Bishop self.stop_qemu_system() 544eb8dc403SDave Cobbley if self.runqemu: 545eb8dc403SDave Cobbley if hasattr(self, "monitorpid"): 546eb8dc403SDave Cobbley os.kill(self.monitorpid, signal.SIGKILL) 547eb8dc403SDave Cobbley self.logger.debug("Sending SIGTERM to runqemu") 548eb8dc403SDave Cobbley try: 549eb8dc403SDave Cobbley os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM) 550eb8dc403SDave Cobbley except OSError as e: 551eb8dc403SDave Cobbley if e.errno != errno.ESRCH: 552eb8dc403SDave Cobbley raise 553864cc43bSPatrick Williams try: 554864cc43bSPatrick Williams outs, errs = self.runqemu.communicate(timeout=self.runqemutime) 555864cc43bSPatrick Williams if outs: 556864cc43bSPatrick Williams self.logger.info("Output from runqemu:\n%s", outs.decode("utf-8")) 557864cc43bSPatrick Williams if errs: 558864cc43bSPatrick Williams self.logger.info("Stderr from runqemu:\n%s", errs.decode("utf-8")) 5598e7b46e2SPatrick Williams except subprocess.TimeoutExpired: 560eb8dc403SDave Cobbley self.logger.debug("Sending SIGKILL to runqemu") 561eb8dc403SDave Cobbley os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL) 562d159c7fbSAndrew Geissler if not self.runqemu.stdout.closed: 563d159c7fbSAndrew Geissler self.logger.info("Output from runqemu:\n%s" % self.getOutput(self.runqemu.stdout)) 564f86d0556SBrad Bishop self.runqemu.stdin.close() 565f86d0556SBrad Bishop self.runqemu.stdout.close() 56682c905dcSAndrew Geissler self.runqemu_exited = True 567f86d0556SBrad Bishop 568c926e17cSAndrew Geissler if hasattr(self, 'qmp') and self.qmp: 569c926e17cSAndrew Geissler self.qmp.close() 570c926e17cSAndrew Geissler self.qmp = None 571eb8dc403SDave Cobbley if hasattr(self, 'server_socket') and self.server_socket: 572eb8dc403SDave Cobbley self.server_socket.close() 573eb8dc403SDave Cobbley self.server_socket = None 574f86d0556SBrad Bishop if hasattr(self, 'threadsock') and self.threadsock: 575f86d0556SBrad Bishop self.threadsock.close() 576f86d0556SBrad Bishop self.threadsock = None 577eb8dc403SDave Cobbley self.qemupid = None 578eb8dc403SDave Cobbley self.ip = None 579eb8dc403SDave Cobbley if os.path.exists(self.qemu_pidfile): 58082c905dcSAndrew Geissler try: 581eb8dc403SDave Cobbley os.remove(self.qemu_pidfile) 58282c905dcSAndrew Geissler except FileNotFoundError as e: 58382c905dcSAndrew Geissler # We raced, ignore 58482c905dcSAndrew Geissler pass 585f86d0556SBrad Bishop if self.monitorpipe: 586f86d0556SBrad Bishop self.monitorpipe.close() 587eb8dc403SDave Cobbley 588eb8dc403SDave Cobbley def stop_qemu_system(self): 589eb8dc403SDave Cobbley if self.qemupid: 590eb8dc403SDave Cobbley try: 591eb8dc403SDave Cobbley # qemu-system behaves well and a SIGTERM is enough 592eb8dc403SDave Cobbley os.kill(self.qemupid, signal.SIGTERM) 593eb8dc403SDave Cobbley except ProcessLookupError as e: 5941a4b7ee2SBrad Bishop self.logger.warning('qemu-system ended unexpectedly') 595eb8dc403SDave Cobbley 596eb8dc403SDave Cobbley def stop_thread(self): 597eb8dc403SDave Cobbley if self.thread and self.thread.is_alive(): 598eb8dc403SDave Cobbley self.thread.stop() 599eb8dc403SDave Cobbley self.thread.join() 600eb8dc403SDave Cobbley 601c926e17cSAndrew Geissler def allowexit(self): 602ac69b488SWilliam A. Kennington III self.canexit = True 603c926e17cSAndrew Geissler if self.thread: 604c926e17cSAndrew Geissler self.thread.allowexit() 605c926e17cSAndrew Geissler 606eb8dc403SDave Cobbley def restart(self, qemuparams = None): 60796ff1984SBrad Bishop self.logger.warning("Restarting qemu process") 608eb8dc403SDave Cobbley if self.runqemu.poll() is None: 609eb8dc403SDave Cobbley self.stop() 610eb8dc403SDave Cobbley if self.start(qemuparams): 611eb8dc403SDave Cobbley return True 612eb8dc403SDave Cobbley return False 613eb8dc403SDave Cobbley 614eb8dc403SDave Cobbley def is_alive(self): 61582c905dcSAndrew Geissler if not self.runqemu or self.runqemu.poll() is not None or self.runqemu_exited: 616eb8dc403SDave Cobbley return False 617eb8dc403SDave Cobbley if os.path.isfile(self.qemu_pidfile): 61896ff1984SBrad Bishop # when handling pidfile, qemu creates the file, stat it, lock it and then write to it 61996ff1984SBrad Bishop # so it's possible that the file has been created but the content is empty 62096ff1984SBrad Bishop pidfile_timeout = time.time() + 3 62196ff1984SBrad Bishop while time.time() < pidfile_timeout: 62296ff1984SBrad Bishop with open(self.qemu_pidfile, 'r') as f: 62396ff1984SBrad Bishop qemu_pid = f.read().strip() 62496ff1984SBrad Bishop # file created but not yet written contents 62596ff1984SBrad Bishop if not qemu_pid: 62696ff1984SBrad Bishop time.sleep(0.5) 62796ff1984SBrad Bishop continue 62896ff1984SBrad Bishop else: 62996ff1984SBrad Bishop if os.path.exists("/proc/" + qemu_pid): 63096ff1984SBrad Bishop self.qemupid = int(qemu_pid) 631eb8dc403SDave Cobbley return True 632eb8dc403SDave Cobbley return False 633eb8dc403SDave Cobbley 6345f35090dSAndrew Geissler def run_monitor(self, command, args=None, timeout=60): 6355f35090dSAndrew Geissler if hasattr(self, 'qmp') and self.qmp: 6366aa7eec5SAndrew Geissler self.qmp.settimeout(timeout) 6375f35090dSAndrew Geissler if args is not None: 6385f35090dSAndrew Geissler return self.qmp.cmd(command, args) 6395f35090dSAndrew Geissler else: 640c926e17cSAndrew Geissler return self.qmp.cmd(command) 641c926e17cSAndrew Geissler 642977dc1acSBrad Bishop def run_serial(self, command, raw=False, timeout=60): 64392b42cb3SPatrick Williams # Returns (status, output) where status is 1 on success and 0 on error 64492b42cb3SPatrick Williams 645eb8dc403SDave Cobbley # We assume target system have echo to get command status 646eb8dc403SDave Cobbley if not raw: 647eb8dc403SDave Cobbley command = "%s; echo $?\n" % command 648eb8dc403SDave Cobbley 649eb8dc403SDave Cobbley data = '' 650eb8dc403SDave Cobbley status = 0 651eb8dc403SDave Cobbley self.server_socket.sendall(command.encode('utf-8')) 652eb8dc403SDave Cobbley start = time.time() 653eb8dc403SDave Cobbley end = start + timeout 654eb8dc403SDave Cobbley while True: 655eb8dc403SDave Cobbley now = time.time() 656eb8dc403SDave Cobbley if now >= end: 657eb8dc403SDave Cobbley data += "<<< run_serial(): command timed out after %d seconds without output >>>\r\n\r\n" % timeout 658eb8dc403SDave Cobbley break 659eb8dc403SDave Cobbley try: 660eb8dc403SDave Cobbley sread, _, _ = select.select([self.server_socket],[],[], end - now) 661eb8dc403SDave Cobbley except InterruptedError: 662eb8dc403SDave Cobbley continue 663eb8dc403SDave Cobbley if sread: 6646aa7eec5SAndrew Geissler # try to avoid reading single character at a time 6656aa7eec5SAndrew Geissler time.sleep(0.1) 666eb8dc403SDave Cobbley answer = self.server_socket.recv(1024) 667eb8dc403SDave Cobbley if answer: 668eb8dc403SDave Cobbley data += answer.decode('utf-8') 669eb8dc403SDave Cobbley # Search the prompt to stop 67082c905dcSAndrew Geissler if re.search(self.boot_patterns['search_cmd_finished'], data): 671eb8dc403SDave Cobbley break 672eb8dc403SDave Cobbley else: 673ac69b488SWilliam A. Kennington III if self.canexit: 674ac69b488SWilliam A. Kennington III return (1, "") 675ac69b488SWilliam A. Kennington III raise Exception("No data on serial console socket, connection closed?") 676eb8dc403SDave Cobbley 677eb8dc403SDave Cobbley if data: 678eb8dc403SDave Cobbley if raw: 679eb8dc403SDave Cobbley status = 1 680eb8dc403SDave Cobbley else: 681eb8dc403SDave Cobbley # Remove first line (command line) and last line (prompt) 682eb8dc403SDave Cobbley data = data[data.find('$?\r\n')+4:data.rfind('\r\n')] 683eb8dc403SDave Cobbley index = data.rfind('\r\n') 684eb8dc403SDave Cobbley if index == -1: 685eb8dc403SDave Cobbley status_cmd = data 686eb8dc403SDave Cobbley data = "" 687eb8dc403SDave Cobbley else: 688eb8dc403SDave Cobbley status_cmd = data[index+2:] 689eb8dc403SDave Cobbley data = data[:index] 690eb8dc403SDave Cobbley if (status_cmd == "0"): 691eb8dc403SDave Cobbley status = 1 692eb8dc403SDave Cobbley return (status, str(data)) 693eb8dc403SDave Cobbley 694eb8dc403SDave Cobbley# This class is for reading data from a socket and passing it to logfunc 695eb8dc403SDave Cobbley# to be processed. It's completely event driven and has a straightforward 696eb8dc403SDave Cobbley# event loop. The mechanism for stopping the thread is a simple pipe which 697eb8dc403SDave Cobbley# will wake up the poll and allow for tearing everything down. 698eb8dc403SDave Cobbleyclass LoggingThread(threading.Thread): 699eb8dc403SDave Cobbley def __init__(self, logfunc, sock, logger): 700eb8dc403SDave Cobbley self.connection_established = threading.Event() 701eb8dc403SDave Cobbley self.serversock = sock 702eb8dc403SDave Cobbley self.logfunc = logfunc 703eb8dc403SDave Cobbley self.logger = logger 704eb8dc403SDave Cobbley self.readsock = None 705eb8dc403SDave Cobbley self.running = False 706c926e17cSAndrew Geissler self.canexit = False 707eb8dc403SDave Cobbley 708eb8dc403SDave Cobbley self.errorevents = select.POLLERR | select.POLLHUP | select.POLLNVAL 709eb8dc403SDave Cobbley self.readevents = select.POLLIN | select.POLLPRI 710eb8dc403SDave Cobbley 711eb8dc403SDave Cobbley threading.Thread.__init__(self, target=self.threadtarget) 712eb8dc403SDave Cobbley 713eb8dc403SDave Cobbley def threadtarget(self): 714eb8dc403SDave Cobbley try: 715eb8dc403SDave Cobbley self.eventloop() 716eb8dc403SDave Cobbley finally: 717eb8dc403SDave Cobbley self.teardown() 718eb8dc403SDave Cobbley 719eb8dc403SDave Cobbley def run(self): 720eb8dc403SDave Cobbley self.logger.debug("Starting logging thread") 721eb8dc403SDave Cobbley self.readpipe, self.writepipe = os.pipe() 722eb8dc403SDave Cobbley threading.Thread.run(self) 723eb8dc403SDave Cobbley 724eb8dc403SDave Cobbley def stop(self): 725eb8dc403SDave Cobbley self.logger.debug("Stopping logging thread") 726eb8dc403SDave Cobbley if self.running: 727eb8dc403SDave Cobbley os.write(self.writepipe, bytes("stop", "utf-8")) 728eb8dc403SDave Cobbley 729eb8dc403SDave Cobbley def teardown(self): 730eb8dc403SDave Cobbley self.logger.debug("Tearing down logging thread") 731eb8dc403SDave Cobbley self.close_socket(self.serversock) 732eb8dc403SDave Cobbley 733eb8dc403SDave Cobbley if self.readsock is not None: 734eb8dc403SDave Cobbley self.close_socket(self.readsock) 735eb8dc403SDave Cobbley 736eb8dc403SDave Cobbley self.close_ignore_error(self.readpipe) 737eb8dc403SDave Cobbley self.close_ignore_error(self.writepipe) 738eb8dc403SDave Cobbley self.running = False 739eb8dc403SDave Cobbley 740c926e17cSAndrew Geissler def allowexit(self): 741c926e17cSAndrew Geissler self.canexit = True 742c926e17cSAndrew Geissler 743eb8dc403SDave Cobbley def eventloop(self): 744eb8dc403SDave Cobbley poll = select.poll() 745eb8dc403SDave Cobbley event_read_mask = self.errorevents | self.readevents 746eb8dc403SDave Cobbley poll.register(self.serversock.fileno()) 747eb8dc403SDave Cobbley poll.register(self.readpipe, event_read_mask) 748eb8dc403SDave Cobbley 749eb8dc403SDave Cobbley breakout = False 750eb8dc403SDave Cobbley self.running = True 751eb8dc403SDave Cobbley self.logger.debug("Starting thread event loop") 752eb8dc403SDave Cobbley while not breakout: 753eb8dc403SDave Cobbley events = poll.poll() 754eb8dc403SDave Cobbley for event in events: 755eb8dc403SDave Cobbley # An error occurred, bail out 756eb8dc403SDave Cobbley if event[1] & self.errorevents: 757eb8dc403SDave Cobbley raise Exception(self.stringify_event(event[1])) 758eb8dc403SDave Cobbley 759eb8dc403SDave Cobbley # Event to stop the thread 760eb8dc403SDave Cobbley if self.readpipe == event[0]: 761eb8dc403SDave Cobbley self.logger.debug("Stop event received") 762eb8dc403SDave Cobbley breakout = True 763eb8dc403SDave Cobbley break 764eb8dc403SDave Cobbley 765eb8dc403SDave Cobbley # A connection request was received 766eb8dc403SDave Cobbley elif self.serversock.fileno() == event[0]: 767eb8dc403SDave Cobbley self.logger.debug("Connection request received") 768eb8dc403SDave Cobbley self.readsock, _ = self.serversock.accept() 769eb8dc403SDave Cobbley self.readsock.setblocking(0) 770eb8dc403SDave Cobbley poll.unregister(self.serversock.fileno()) 771eb8dc403SDave Cobbley poll.register(self.readsock.fileno(), event_read_mask) 772eb8dc403SDave Cobbley 773eb8dc403SDave Cobbley self.logger.debug("Setting connection established event") 774eb8dc403SDave Cobbley self.connection_established.set() 775eb8dc403SDave Cobbley 776eb8dc403SDave Cobbley # Actual data to be logged 777eb8dc403SDave Cobbley elif self.readsock.fileno() == event[0]: 778eb8dc403SDave Cobbley data = self.recv(1024) 779eb8dc403SDave Cobbley self.logfunc(data) 780eb8dc403SDave Cobbley 781eb8dc403SDave Cobbley # Since the socket is non-blocking make sure to honor EAGAIN 782eb8dc403SDave Cobbley # and EWOULDBLOCK. 783eb8dc403SDave Cobbley def recv(self, count): 784eb8dc403SDave Cobbley try: 785eb8dc403SDave Cobbley data = self.readsock.recv(count) 786eb8dc403SDave Cobbley except socket.error as e: 787eb8dc403SDave Cobbley if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK: 788ac69b488SWilliam A. Kennington III return b'' 789eb8dc403SDave Cobbley else: 790eb8dc403SDave Cobbley raise 791eb8dc403SDave Cobbley 792eb8dc403SDave Cobbley if data is None: 793eb8dc403SDave Cobbley raise Exception("No data on read ready socket") 794eb8dc403SDave Cobbley elif not data: 795eb8dc403SDave Cobbley # This actually means an orderly shutdown 796eb8dc403SDave Cobbley # happened. But for this code it counts as an 797eb8dc403SDave Cobbley # error since the connection shouldn't go away 798eb8dc403SDave Cobbley # until qemu exits. 799c926e17cSAndrew Geissler if not self.canexit: 800eb8dc403SDave Cobbley raise Exception("Console connection closed unexpectedly") 801ac69b488SWilliam A. Kennington III return b'' 802eb8dc403SDave Cobbley 803eb8dc403SDave Cobbley return data 804eb8dc403SDave Cobbley 805eb8dc403SDave Cobbley def stringify_event(self, event): 806eb8dc403SDave Cobbley val = '' 807eb8dc403SDave Cobbley if select.POLLERR == event: 808eb8dc403SDave Cobbley val = 'POLLER' 809eb8dc403SDave Cobbley elif select.POLLHUP == event: 810eb8dc403SDave Cobbley val = 'POLLHUP' 811eb8dc403SDave Cobbley elif select.POLLNVAL == event: 812eb8dc403SDave Cobbley val = 'POLLNVAL' 813eb8dc403SDave Cobbley return val 814eb8dc403SDave Cobbley 815eb8dc403SDave Cobbley def close_socket(self, sock): 816eb8dc403SDave Cobbley sock.shutdown(socket.SHUT_RDWR) 817eb8dc403SDave Cobbley sock.close() 818eb8dc403SDave Cobbley 819eb8dc403SDave Cobbley def close_ignore_error(self, fd): 820eb8dc403SDave Cobbley try: 821eb8dc403SDave Cobbley os.close(fd) 822eb8dc403SDave Cobbley except OSError: 823eb8dc403SDave Cobbley pass 824