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