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 22c926e17cSAndrew Geisslerimport tempfile 2382c905dcSAndrew Geisslerfrom collections import defaultdict 24169d7bccSPatrick Williamsfrom contextlib import contextmanager 25c926e17cSAndrew Geisslerimport importlib 26169d7bccSPatrick Williamsimport traceback 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 34169d7bccSPatrick Williamsdef getOutput(o): 35169d7bccSPatrick Williams import fcntl 36169d7bccSPatrick Williams fl = fcntl.fcntl(o, fcntl.F_GETFL) 37169d7bccSPatrick Williams fcntl.fcntl(o, fcntl.F_SETFL, fl | os.O_NONBLOCK) 38169d7bccSPatrick Williams try: 39169d7bccSPatrick Williams return os.read(o.fileno(), 1000000).decode("utf-8") 40169d7bccSPatrick Williams except BlockingIOError: 41169d7bccSPatrick Williams return "" 42169d7bccSPatrick Williams 43eb8dc403SDave Cobbleyclass QemuRunner: 44eb8dc403SDave Cobbley 458f840685SAndrew Geissler def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, use_kvm, logger, use_slirp=False, 468f840685SAndrew Geissler serial_ports=2, boot_patterns = defaultdict(str), use_ovmf=False, workdir=None, tmpfsdir=None): 47eb8dc403SDave Cobbley 48eb8dc403SDave Cobbley # Popen object for runqemu 49eb8dc403SDave Cobbley self.runqemu = None 5082c905dcSAndrew Geissler self.runqemu_exited = False 51eb8dc403SDave Cobbley # pid of the qemu process that runqemu will start 52eb8dc403SDave Cobbley self.qemupid = None 53eb8dc403SDave Cobbley # target ip - from the command line or runqemu output 54eb8dc403SDave Cobbley self.ip = None 55eb8dc403SDave Cobbley # host ip - where qemu is running 56eb8dc403SDave Cobbley self.server_ip = None 57eb8dc403SDave Cobbley # target ip netmask 58eb8dc403SDave Cobbley self.netmask = None 59eb8dc403SDave Cobbley 60eb8dc403SDave Cobbley self.machine = machine 61eb8dc403SDave Cobbley self.rootfs = rootfs 62eb8dc403SDave Cobbley self.display = display 63eb8dc403SDave Cobbley self.tmpdir = tmpdir 64eb8dc403SDave Cobbley self.deploy_dir_image = deploy_dir_image 65eb8dc403SDave Cobbley self.logfile = logfile 66eb8dc403SDave Cobbley self.boottime = boottime 67eb8dc403SDave Cobbley self.logged = False 68eb8dc403SDave Cobbley self.thread = None 69169d7bccSPatrick Williams self.threadsock = None 70eb8dc403SDave Cobbley self.use_kvm = use_kvm 7182c905dcSAndrew Geissler self.use_ovmf = use_ovmf 7219323693SBrad Bishop self.use_slirp = use_slirp 7382c905dcSAndrew Geissler self.serial_ports = serial_ports 74eb8dc403SDave Cobbley self.msg = '' 7582c905dcSAndrew Geissler self.boot_patterns = boot_patterns 763b8a17c1SAndrew Geissler self.tmpfsdir = tmpfsdir 77eb8dc403SDave Cobbley 780903674eSAndrew Geissler self.runqemutime = 300 79b7d28619SAndrew Geissler if not workdir: 80b7d28619SAndrew Geissler workdir = os.getcwd() 81b7d28619SAndrew Geissler self.qemu_pidfile = workdir + '/pidfile_' + str(os.getpid()) 8215ae2509SBrad Bishop self.monitorpipe = None 83eb8dc403SDave Cobbley 84eb8dc403SDave Cobbley self.logger = logger 85ac69b488SWilliam A. Kennington III # Whether we're expecting an exit and should show related errors 86ac69b488SWilliam A. Kennington III self.canexit = False 87eb8dc403SDave Cobbley 8882c905dcSAndrew Geissler # Enable testing other OS's 8982c905dcSAndrew Geissler # Set commands for target communication, and default to Linux ALWAYS 9082c905dcSAndrew Geissler # Other OS's or baremetal applications need to provide their 9182c905dcSAndrew Geissler # own implementation passing it through QemuRunner's constructor 9282c905dcSAndrew Geissler # or by passing them through TESTIMAGE_BOOT_PATTERNS[flag] 9382c905dcSAndrew Geissler # provided variables, where <flag> is one of the mentioned below. 9482c905dcSAndrew Geissler accepted_patterns = ['search_reached_prompt', 'send_login_user', 'search_login_succeeded', 'search_cmd_finished'] 9582c905dcSAndrew Geissler default_boot_patterns = defaultdict(str) 9682c905dcSAndrew Geissler # Default to the usual paterns used to communicate with the target 9787f5cff0SAndrew Geissler default_boot_patterns['search_reached_prompt'] = ' login:' 9882c905dcSAndrew Geissler default_boot_patterns['send_login_user'] = 'root\n' 9982c905dcSAndrew Geissler default_boot_patterns['search_login_succeeded'] = r"root@[a-zA-Z0-9\-]+:~#" 10082c905dcSAndrew Geissler default_boot_patterns['search_cmd_finished'] = r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#" 10182c905dcSAndrew Geissler 10282c905dcSAndrew Geissler # Only override patterns that were set e.g. login user TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n" 10382c905dcSAndrew Geissler for pattern in accepted_patterns: 10482c905dcSAndrew Geissler if not self.boot_patterns[pattern]: 10582c905dcSAndrew Geissler self.boot_patterns[pattern] = default_boot_patterns[pattern] 10682c905dcSAndrew Geissler 107eb8dc403SDave Cobbley def create_socket(self): 108eb8dc403SDave Cobbley try: 109eb8dc403SDave Cobbley sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 110eb8dc403SDave Cobbley sock.setblocking(0) 11120137395SAndrew Geissler sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 112eb8dc403SDave Cobbley sock.bind(("127.0.0.1",0)) 113eb8dc403SDave Cobbley sock.listen(2) 114eb8dc403SDave Cobbley port = sock.getsockname()[1] 115eb8dc403SDave Cobbley self.logger.debug("Created listening socket for qemu serial console on: 127.0.0.1:%s" % port) 116eb8dc403SDave Cobbley return (sock, port) 117eb8dc403SDave Cobbley 118eb8dc403SDave Cobbley except socket.error: 119eb8dc403SDave Cobbley sock.close() 120eb8dc403SDave Cobbley raise 121eb8dc403SDave Cobbley 12287f5cff0SAndrew Geissler def decode_qemulog(self, todecode): 12387f5cff0SAndrew Geissler # Sanitize the data received from qemu as it may contain control characters 12420137395SAndrew Geissler msg = todecode.decode("utf-8", errors='backslashreplace') 12587f5cff0SAndrew Geissler msg = re_control_char.sub('', msg) 12687f5cff0SAndrew Geissler return msg 12787f5cff0SAndrew Geissler 12820137395SAndrew Geissler def log(self, msg, extension=""): 129eb8dc403SDave Cobbley if self.logfile: 13020137395SAndrew Geissler with codecs.open(self.logfile + extension, "ab") as f: 13120137395SAndrew Geissler f.write(msg) 13220137395SAndrew Geissler self.msg += self.decode_qemulog(msg) 133eb8dc403SDave Cobbley 134eb8dc403SDave Cobbley def handleSIGCHLD(self, signum, frame): 135eb8dc403SDave Cobbley if self.runqemu and self.runqemu.poll(): 136eb8dc403SDave Cobbley if self.runqemu.returncode: 13782c905dcSAndrew Geissler self.logger.error('runqemu exited with code %d' % self.runqemu.returncode) 138169d7bccSPatrick Williams self.logger.error('Output from runqemu:\n%s' % getOutput(self.runqemu.stdout)) 139eb8dc403SDave Cobbley self.stop() 140eb8dc403SDave Cobbley 141eb8dc403SDave Cobbley def start(self, qemuparams = None, get_ip = True, extra_bootparams = None, runqemuparams='', launch_cmd=None, discard_writes=True): 142eb8dc403SDave Cobbley env = os.environ.copy() 143eb8dc403SDave Cobbley if self.display: 144eb8dc403SDave Cobbley env["DISPLAY"] = self.display 145eb8dc403SDave Cobbley # Set this flag so that Qemu doesn't do any grabs as SDL grabs 146eb8dc403SDave Cobbley # interact badly with screensavers. 147eb8dc403SDave Cobbley env["QEMU_DONT_GRAB"] = "1" 148eb8dc403SDave Cobbley if not os.path.exists(self.rootfs): 149eb8dc403SDave Cobbley self.logger.error("Invalid rootfs %s" % self.rootfs) 150eb8dc403SDave Cobbley return False 151eb8dc403SDave Cobbley if not os.path.exists(self.tmpdir): 152eb8dc403SDave Cobbley self.logger.error("Invalid TMPDIR path %s" % self.tmpdir) 153eb8dc403SDave Cobbley return False 154eb8dc403SDave Cobbley else: 155eb8dc403SDave Cobbley env["OE_TMPDIR"] = self.tmpdir 156eb8dc403SDave Cobbley if not os.path.exists(self.deploy_dir_image): 157eb8dc403SDave Cobbley self.logger.error("Invalid DEPLOY_DIR_IMAGE path %s" % self.deploy_dir_image) 158eb8dc403SDave Cobbley return False 159eb8dc403SDave Cobbley else: 160eb8dc403SDave Cobbley env["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image 161eb8dc403SDave Cobbley 1623b8a17c1SAndrew Geissler if self.tmpfsdir: 1633b8a17c1SAndrew Geissler env["RUNQEMU_TMPFS_DIR"] = self.tmpfsdir 1643b8a17c1SAndrew Geissler 165eb8dc403SDave Cobbley if not launch_cmd: 16608902b01SBrad Bishop launch_cmd = 'runqemu %s' % ('snapshot' if discard_writes else '') 167eb8dc403SDave Cobbley if self.use_kvm: 168eb8dc403SDave Cobbley self.logger.debug('Using kvm for runqemu') 169eb8dc403SDave Cobbley launch_cmd += ' kvm' 170eb8dc403SDave Cobbley else: 171eb8dc403SDave Cobbley self.logger.debug('Not using kvm for runqemu') 172eb8dc403SDave Cobbley if not self.display: 173eb8dc403SDave Cobbley launch_cmd += ' nographic' 17419323693SBrad Bishop if self.use_slirp: 17519323693SBrad Bishop launch_cmd += ' slirp' 17682c905dcSAndrew Geissler if self.use_ovmf: 17782c905dcSAndrew Geissler launch_cmd += ' ovmf' 178517393d9SAndrew Geissler launch_cmd += ' %s %s' % (runqemuparams, self.machine) 179517393d9SAndrew Geissler if self.rootfs.endswith('.vmdk'): 180517393d9SAndrew Geissler self.logger.debug('Bypassing VMDK rootfs for runqemu') 181517393d9SAndrew Geissler else: 182517393d9SAndrew Geissler launch_cmd += ' %s' % (self.rootfs) 183eb8dc403SDave Cobbley 184eb8dc403SDave Cobbley return self.launch(launch_cmd, qemuparams=qemuparams, get_ip=get_ip, extra_bootparams=extra_bootparams, env=env) 185eb8dc403SDave Cobbley 186eb8dc403SDave Cobbley def launch(self, launch_cmd, get_ip = True, qemuparams = None, extra_bootparams = None, env = None): 187c926e17cSAndrew Geissler # use logfile to determine the recipe-sysroot-native path and 188c926e17cSAndrew Geissler # then add in the site-packages path components and add that 189b542dec1SPatrick Williams # to the python sys.path so the qmp module can be found. 190c926e17cSAndrew Geissler python_path = os.path.dirname(os.path.dirname(self.logfile)) 191eff27476SAndrew Geissler python_path += "/recipe-sysroot-native/usr/lib/qemu-python" 192c926e17cSAndrew Geissler sys.path.append(python_path) 193c926e17cSAndrew Geissler importlib.invalidate_caches() 194c926e17cSAndrew Geissler try: 195c926e17cSAndrew Geissler qmp = importlib.import_module("qmp") 19687f5cff0SAndrew Geissler except Exception as e: 197b542dec1SPatrick Williams self.logger.error("qemurunner: qmp module missing, please ensure it's installed in %s (%s)" % (python_path, str(e))) 198c926e17cSAndrew Geissler return False 199c926e17cSAndrew Geissler # Path relative to tmpdir used as cwd for qemu below to avoid unix socket path length issues 200c926e17cSAndrew Geissler qmp_file = "." + next(tempfile._get_candidate_names()) 201c926e17cSAndrew Geissler qmp_param = ' -S -qmp unix:./%s,server,wait' % (qmp_file) 202c926e17cSAndrew Geissler qmp_port = self.tmpdir + "/" + qmp_file 2030903674eSAndrew Geissler # Create a second socket connection for debugging use, 2040903674eSAndrew Geissler # note this will NOT cause qemu to block waiting for the connection 2050903674eSAndrew Geissler qmp_file2 = "." + next(tempfile._get_candidate_names()) 2060903674eSAndrew Geissler qmp_param += ' -qmp unix:./%s,server,nowait' % (qmp_file2) 2070903674eSAndrew Geissler qmp_port2 = self.tmpdir + "/" + qmp_file2 2080903674eSAndrew Geissler self.logger.info("QMP Available for connection at %s" % (qmp_port2)) 209c926e17cSAndrew Geissler 210eb8dc403SDave Cobbley try: 21182c905dcSAndrew Geissler if self.serial_ports >= 2: 212f86d0556SBrad Bishop self.threadsock, threadport = self.create_socket() 213eb8dc403SDave Cobbley self.server_socket, self.serverport = self.create_socket() 214eb8dc403SDave Cobbley except socket.error as msg: 215eb8dc403SDave Cobbley self.logger.error("Failed to create listening socket: %s" % msg[1]) 216eb8dc403SDave Cobbley return False 217eb8dc403SDave Cobbley 21895ac1b8dSAndrew Geissler bootparams = ' printk.time=1' 219eb8dc403SDave Cobbley if extra_bootparams: 220eb8dc403SDave Cobbley bootparams = bootparams + ' ' + extra_bootparams 221eb8dc403SDave Cobbley 222eb8dc403SDave Cobbley # Ask QEMU to store the QEMU process PID in file, this way we don't have to parse running processes 223eb8dc403SDave Cobbley # and analyze descendents in order to determine it. 224eb8dc403SDave Cobbley if os.path.exists(self.qemu_pidfile): 225eb8dc403SDave Cobbley os.remove(self.qemu_pidfile) 226c926e17cSAndrew Geissler self.qemuparams = 'bootparams="{0}" qemuparams="-pidfile {1} {2}"'.format(bootparams, self.qemu_pidfile, qmp_param) 227c926e17cSAndrew Geissler 228eb8dc403SDave Cobbley if qemuparams: 229eb8dc403SDave Cobbley self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' 230eb8dc403SDave Cobbley 23182c905dcSAndrew Geissler if self.serial_ports >= 2: 23215ae2509SBrad Bishop launch_cmd += ' tcpserial=%s:%s %s' % (threadport, self.serverport, self.qemuparams) 23382c905dcSAndrew Geissler else: 23482c905dcSAndrew Geissler launch_cmd += ' tcpserial=%s %s' % (self.serverport, self.qemuparams) 235eb8dc403SDave Cobbley 236eb8dc403SDave Cobbley self.origchldhandler = signal.getsignal(signal.SIGCHLD) 237eb8dc403SDave Cobbley signal.signal(signal.SIGCHLD, self.handleSIGCHLD) 238eb8dc403SDave Cobbley 239eb8dc403SDave Cobbley self.logger.debug('launchcmd=%s' % (launch_cmd)) 240eb8dc403SDave Cobbley 241eb8dc403SDave Cobbley # FIXME: We pass in stdin=subprocess.PIPE here to work around stty 242eb8dc403SDave Cobbley # blocking at the end of the runqemu script when using this within 243eb8dc403SDave Cobbley # oe-selftest (this makes stty error out immediately). There ought 244eb8dc403SDave Cobbley # to be a proper fix but this will suffice for now. 245c926e17cSAndrew 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) 246eb8dc403SDave Cobbley output = self.runqemu.stdout 2475f35090dSAndrew Geissler launch_time = time.time() 248eb8dc403SDave Cobbley 249eb8dc403SDave Cobbley # 250eb8dc403SDave Cobbley # We need the preexec_fn above so that all runqemu processes can easily be killed 251eb8dc403SDave Cobbley # (by killing their process group). This presents a problem if this controlling 252eb8dc403SDave Cobbley # process itself is killed however since those processes don't notice the death 253eb8dc403SDave Cobbley # of the parent and merrily continue on. 254eb8dc403SDave Cobbley # 255eb8dc403SDave Cobbley # Rather than hack runqemu to deal with this, we add something here instead. 256eb8dc403SDave Cobbley # Basically we fork off another process which holds an open pipe to the parent 257eb8dc403SDave Cobbley # and also is setpgrp. If/when the pipe sees EOF from the parent dieing, it kills 258eb8dc403SDave Cobbley # the process group. This is like pctrl's PDEATHSIG but for a process group 259eb8dc403SDave Cobbley # rather than a single process. 260eb8dc403SDave Cobbley # 261eb8dc403SDave Cobbley r, w = os.pipe() 262eb8dc403SDave Cobbley self.monitorpid = os.fork() 263eb8dc403SDave Cobbley if self.monitorpid: 264eb8dc403SDave Cobbley os.close(r) 265eb8dc403SDave Cobbley self.monitorpipe = os.fdopen(w, "w") 266eb8dc403SDave Cobbley else: 267eb8dc403SDave Cobbley # child process 268eb8dc403SDave Cobbley os.setpgrp() 269eb8dc403SDave Cobbley os.close(w) 270eb8dc403SDave Cobbley r = os.fdopen(r) 271eb8dc403SDave Cobbley x = r.read() 272eb8dc403SDave Cobbley os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM) 27393c203f3SPatrick Williams os._exit(0) 274eb8dc403SDave Cobbley 275eb8dc403SDave Cobbley self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid) 2768e7b46e2SPatrick Williams self.logger.debug("waiting at most %d seconds for qemu pid (%s)" % 277eb8dc403SDave Cobbley (self.runqemutime, time.strftime("%D %H:%M:%S"))) 278eb8dc403SDave Cobbley endtime = time.time() + self.runqemutime 279eb8dc403SDave Cobbley while not self.is_alive() and time.time() < endtime: 280eb8dc403SDave Cobbley if self.runqemu.poll(): 28182c905dcSAndrew Geissler if self.runqemu_exited: 282c926e17cSAndrew Geissler self.logger.warning("runqemu during is_alive() test") 28382c905dcSAndrew Geissler return False 284eb8dc403SDave Cobbley if self.runqemu.returncode: 285eb8dc403SDave Cobbley # No point waiting any longer 28696ff1984SBrad Bishop self.logger.warning('runqemu exited with code %d' % self.runqemu.returncode) 287169d7bccSPatrick Williams self.logger.warning("Output from runqemu:\n%s" % getOutput(output)) 288f86d0556SBrad Bishop self.stop() 289eb8dc403SDave Cobbley return False 290eb8dc403SDave Cobbley time.sleep(0.5) 291eb8dc403SDave Cobbley 29282c905dcSAndrew Geissler if self.runqemu_exited: 293c926e17cSAndrew Geissler self.logger.warning("runqemu after timeout") 29482c905dcSAndrew Geissler 295c926e17cSAndrew Geissler if self.runqemu.returncode: 296c926e17cSAndrew Geissler self.logger.warning('runqemu exited with code %d' % self.runqemu.returncode) 297c926e17cSAndrew Geissler 298c926e17cSAndrew Geissler if not self.is_alive(): 2998e7b46e2SPatrick Williams self.logger.error("Qemu pid didn't appear in %d seconds (%s)" % 300ac69b488SWilliam A. Kennington III (self.runqemutime, time.strftime("%D %H:%M:%S"))) 301ac69b488SWilliam A. Kennington III 302ac69b488SWilliam A. Kennington III qemu_pid = None 303ac69b488SWilliam A. Kennington III if os.path.isfile(self.qemu_pidfile): 304ac69b488SWilliam A. Kennington III with open(self.qemu_pidfile, 'r') as f: 305ac69b488SWilliam A. Kennington III qemu_pid = f.read().strip() 306ac69b488SWilliam A. Kennington III 307ac69b488SWilliam A. Kennington III self.logger.error("Status information, poll status: %s, pidfile exists: %s, pidfile contents %s, proc pid exists %s" 308ac69b488SWilliam A. Kennington III % (self.runqemu.poll(), os.path.isfile(self.qemu_pidfile), str(qemu_pid), os.path.exists("/proc/" + str(qemu_pid)))) 309ac69b488SWilliam A. Kennington III 310ac69b488SWilliam A. Kennington III # Dump all processes to help us to figure out what is going on... 311ac69b488SWilliam A. Kennington III ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,pri,ni,command '], stdout=subprocess.PIPE).communicate()[0] 312ac69b488SWilliam A. Kennington III processes = ps.decode("utf-8") 313ac69b488SWilliam A. Kennington III self.logger.debug("Running processes:\n%s" % processes) 314169d7bccSPatrick Williams op = getOutput(output) 315ac69b488SWilliam A. Kennington III self.stop() 316ac69b488SWilliam A. Kennington III if op: 317ac69b488SWilliam A. Kennington III self.logger.error("Output from runqemu:\n%s" % op) 318ac69b488SWilliam A. Kennington III else: 319ac69b488SWilliam A. Kennington III self.logger.error("No output from runqemu.\n") 320c926e17cSAndrew Geissler return False 321c926e17cSAndrew Geissler 322c926e17cSAndrew Geissler # Create the client socket for the QEMU Monitor Control Socket 323c926e17cSAndrew Geissler # This will allow us to read status from Qemu if the the process 324c926e17cSAndrew Geissler # is still alive 325c926e17cSAndrew Geissler self.logger.debug("QMP Initializing to %s" % (qmp_port)) 326c926e17cSAndrew Geissler # chdir dance for path length issues with unix sockets 327c926e17cSAndrew Geissler origpath = os.getcwd() 328c926e17cSAndrew Geissler try: 329c926e17cSAndrew Geissler os.chdir(os.path.dirname(qmp_port)) 330c926e17cSAndrew Geissler try: 33187f5cff0SAndrew Geissler from qmp.legacy import QEMUMonitorProtocol 33287f5cff0SAndrew Geissler self.qmp = QEMUMonitorProtocol(os.path.basename(qmp_port)) 333c926e17cSAndrew Geissler except OSError as msg: 334c926e17cSAndrew Geissler self.logger.warning("Failed to initialize qemu monitor socket: %s File: %s" % (msg, msg.filename)) 335c926e17cSAndrew Geissler return False 336c926e17cSAndrew Geissler 337c926e17cSAndrew Geissler self.logger.debug("QMP Connecting to %s" % (qmp_port)) 338c926e17cSAndrew Geissler if not os.path.exists(qmp_port) and self.is_alive(): 339c926e17cSAndrew Geissler self.logger.debug("QMP Port does not exist waiting for it to be created") 340c926e17cSAndrew Geissler endtime = time.time() + self.runqemutime 341c926e17cSAndrew Geissler while not os.path.exists(qmp_port) and self.is_alive() and time.time() < endtime: 342c926e17cSAndrew Geissler self.logger.info("QMP port does not exist yet!") 343c926e17cSAndrew Geissler time.sleep(0.5) 344c926e17cSAndrew Geissler if not os.path.exists(qmp_port) and self.is_alive(): 345c926e17cSAndrew Geissler self.logger.warning("QMP Port still does not exist but QEMU is alive") 346c926e17cSAndrew Geissler return False 347c926e17cSAndrew Geissler 348c926e17cSAndrew Geissler try: 3496aa7eec5SAndrew Geissler # set timeout value for all QMP calls 3506aa7eec5SAndrew Geissler self.qmp.settimeout(self.runqemutime) 351c926e17cSAndrew Geissler self.qmp.connect() 3525f35090dSAndrew Geissler connect_time = time.time() 3538e7b46e2SPatrick Williams self.logger.info("QMP connected to QEMU at %s and took %.2f seconds" % 3545f35090dSAndrew Geissler (time.strftime("%D %H:%M:%S"), 3555f35090dSAndrew Geissler time.time() - launch_time)) 356c926e17cSAndrew Geissler except OSError as msg: 357c926e17cSAndrew Geissler self.logger.warning("Failed to connect qemu monitor socket: %s File: %s" % (msg, msg.filename)) 358c926e17cSAndrew Geissler return False 3597784c429SPatrick Williams except qmp.legacy.QMPError as msg: 360c926e17cSAndrew Geissler self.logger.warning("Failed to communicate with qemu monitor: %s" % (msg)) 361c926e17cSAndrew Geissler return False 362c926e17cSAndrew Geissler finally: 363c926e17cSAndrew Geissler os.chdir(origpath) 364c926e17cSAndrew Geissler 3650903674eSAndrew Geissler # We worry that mmap'd libraries may cause page faults which hang the qemu VM for periods 3660903674eSAndrew Geissler # causing failures. Before we "start" qemu, read through it's mapped files to try and 3670903674eSAndrew Geissler # ensure we don't hit page faults later 3680903674eSAndrew Geissler mapdir = "/proc/" + str(self.qemupid) + "/map_files/" 3690903674eSAndrew Geissler try: 3700903674eSAndrew Geissler for f in os.listdir(mapdir): 3715f35090dSAndrew Geissler try: 3720903674eSAndrew Geissler linktarget = os.readlink(os.path.join(mapdir, f)) 3730903674eSAndrew Geissler if not linktarget.startswith("/") or linktarget.startswith("/dev") or "deleted" in linktarget: 3740903674eSAndrew Geissler continue 3750903674eSAndrew Geissler with open(linktarget, "rb") as readf: 3760903674eSAndrew Geissler data = True 3770903674eSAndrew Geissler while data: 3780903674eSAndrew Geissler data = readf.read(4096) 3795f35090dSAndrew Geissler except FileNotFoundError: 3805f35090dSAndrew Geissler continue 3810903674eSAndrew Geissler # Centos7 doesn't allow us to read /map_files/ 3820903674eSAndrew Geissler except PermissionError: 3830903674eSAndrew Geissler pass 3840903674eSAndrew Geissler 3850903674eSAndrew Geissler # Release the qemu process to continue running 386c926e17cSAndrew Geissler self.run_monitor('cont') 3878e7b46e2SPatrick Williams self.logger.info("QMP released QEMU at %s and took %.2f seconds from connect" % 3885f35090dSAndrew Geissler (time.strftime("%D %H:%M:%S"), 3895f35090dSAndrew Geissler time.time() - connect_time)) 390c926e17cSAndrew Geissler 391eb8dc403SDave Cobbley # We are alive: qemu is running 392169d7bccSPatrick Williams out = getOutput(output) 393eb8dc403SDave Cobbley netconf = False # network configuration is not required by default 3948e7b46e2SPatrick Williams self.logger.debug("qemu started in %.2f seconds - qemu procces pid is %s (%s)" % 395eb8dc403SDave Cobbley (time.time() - (endtime - self.runqemutime), 396eb8dc403SDave Cobbley self.qemupid, time.strftime("%D %H:%M:%S"))) 397eb8dc403SDave Cobbley cmdline = '' 39882c905dcSAndrew Geissler if get_ip: 399eb8dc403SDave Cobbley with open('/proc/%s/cmdline' % self.qemupid) as p: 400eb8dc403SDave Cobbley cmdline = p.read() 401eb8dc403SDave Cobbley # It is needed to sanitize the data received 402eb8dc403SDave Cobbley # because is possible to have control characters 403eb8dc403SDave Cobbley cmdline = re_control_char.sub(' ', cmdline) 404eb8dc403SDave Cobbley try: 40519323693SBrad Bishop if self.use_slirp: 406517393d9SAndrew Geissler tcp_ports = cmdline.split("hostfwd=tcp:")[1] 407517393d9SAndrew Geissler ip, tcp_ports = tcp_ports.split(":")[:2] 40819323693SBrad Bishop host_port = tcp_ports[:tcp_ports.find('-')] 409517393d9SAndrew Geissler self.ip = "%s:%s" % (ip, host_port) 41019323693SBrad Bishop else: 411f86d0556SBrad Bishop ips = re.findall(r"((?:[0-9]{1,3}\.){3}[0-9]{1,3})", cmdline.split("ip=")[1]) 412eb8dc403SDave Cobbley self.ip = ips[0] 413eb8dc403SDave Cobbley self.server_ip = ips[1] 414eb8dc403SDave Cobbley self.logger.debug("qemu cmdline used:\n{}".format(cmdline)) 415eb8dc403SDave Cobbley except (IndexError, ValueError): 416eb8dc403SDave Cobbley # Try to get network configuration from runqemu output 417595f6308SAndrew Geissler match = re.match(r'.*Network configuration: (?:ip=)*([0-9.]+)::([0-9.]+):([0-9.]+).*', 418eb8dc403SDave Cobbley out, re.MULTILINE | re.DOTALL) 419eb8dc403SDave Cobbley if match: 420eb8dc403SDave Cobbley self.ip, self.server_ip, self.netmask = match.groups() 421eb8dc403SDave Cobbley # network configuration is required as we couldn't get it 422eb8dc403SDave Cobbley # from the runqemu command line, so qemu doesn't run kernel 423eb8dc403SDave Cobbley # and guest networking is not configured 424eb8dc403SDave Cobbley netconf = True 425eb8dc403SDave Cobbley else: 426eb8dc403SDave Cobbley self.logger.error("Couldn't get ip from qemu command line and runqemu output! " 427eb8dc403SDave Cobbley "Here is the qemu command line used:\n%s\n" 428eb8dc403SDave Cobbley "and output from runqemu:\n%s" % (cmdline, out)) 429eb8dc403SDave Cobbley self.stop() 430eb8dc403SDave Cobbley return False 431eb8dc403SDave Cobbley 432eb8dc403SDave Cobbley self.logger.debug("Target IP: %s" % self.ip) 433eb8dc403SDave Cobbley self.logger.debug("Server IP: %s" % self.server_ip) 434eb8dc403SDave Cobbley 435169d7bccSPatrick Williams self.thread = LoggingThread(self.log, self.threadsock, self.logger, self.runqemu.stdout) 436eb8dc403SDave Cobbley self.thread.start() 437169d7bccSPatrick Williams 438169d7bccSPatrick Williams if self.serial_ports >= 2: 439eb8dc403SDave Cobbley if not self.thread.connection_established.wait(self.boottime): 440eb8dc403SDave Cobbley self.logger.error("Didn't receive a console connection from qemu. " 441eb8dc403SDave Cobbley "Here is the qemu command line used:\n%s\nand " 442eb8dc403SDave Cobbley "output from runqemu:\n%s" % (cmdline, out)) 443eb8dc403SDave Cobbley self.stop_thread() 444eb8dc403SDave Cobbley return False 445eb8dc403SDave Cobbley 446eb8dc403SDave Cobbley self.logger.debug("Output from runqemu:\n%s", out) 447eb8dc403SDave Cobbley self.logger.debug("Waiting at most %d seconds for login banner (%s)" % 448eb8dc403SDave Cobbley (self.boottime, time.strftime("%D %H:%M:%S"))) 449eb8dc403SDave Cobbley endtime = time.time() + self.boottime 450169d7bccSPatrick Williams filelist = [self.server_socket] 451eb8dc403SDave Cobbley reachedlogin = False 452eb8dc403SDave Cobbley stopread = False 453eb8dc403SDave Cobbley qemusock = None 454eb8dc403SDave Cobbley bootlog = b'' 455eb8dc403SDave Cobbley data = b'' 456eb8dc403SDave Cobbley while time.time() < endtime and not stopread: 457eb8dc403SDave Cobbley try: 458e760df85SPatrick Williams sread, swrite, serror = select.select(filelist, [], [], 5) 459eb8dc403SDave Cobbley except InterruptedError: 460eb8dc403SDave Cobbley continue 461e760df85SPatrick Williams for file in sread: 462e760df85SPatrick Williams if file is self.server_socket: 463eb8dc403SDave Cobbley qemusock, addr = self.server_socket.accept() 464e760df85SPatrick Williams qemusock.setblocking(False) 465e760df85SPatrick Williams filelist.append(qemusock) 466e760df85SPatrick Williams filelist.remove(self.server_socket) 467eb8dc403SDave Cobbley self.logger.debug("Connection from %s:%s" % addr) 468eb8dc403SDave Cobbley else: 4696aa7eec5SAndrew Geissler # try to avoid reading only a single character at a time 4706aa7eec5SAndrew Geissler time.sleep(0.1) 471e760df85SPatrick Williams if hasattr(file, 'read'): 472e760df85SPatrick Williams read = file.read(1024) 473e760df85SPatrick Williams elif hasattr(file, 'recv'): 474e760df85SPatrick Williams read = file.recv(1024) 475e760df85SPatrick Williams else: 476e760df85SPatrick Williams self.logger.error('Invalid file type: %s\n%s' % (file)) 477e760df85SPatrick Williams read = b'' 478e760df85SPatrick Williams 47920137395SAndrew Geissler self.logger.debug2('Partial boot log:\n%s' % (read.decode('utf-8', errors='backslashreplace'))) 480e760df85SPatrick Williams data = data + read 481eb8dc403SDave Cobbley if data: 482eb8dc403SDave Cobbley bootlog += data 48320137395SAndrew Geissler self.log(data, extension = ".2") 484eb8dc403SDave Cobbley data = b'' 48587f5cff0SAndrew Geissler 48620137395SAndrew Geissler if bytes(self.boot_patterns['search_reached_prompt'], 'utf-8') in bootlog: 487e760df85SPatrick Williams self.server_socket.close() 488eb8dc403SDave Cobbley self.server_socket = qemusock 489eb8dc403SDave Cobbley stopread = True 490eb8dc403SDave Cobbley reachedlogin = True 4918e7b46e2SPatrick Williams self.logger.debug("Reached login banner in %.2f seconds (%s)" % 492eb8dc403SDave Cobbley (time.time() - (endtime - self.boottime), 4938e7b46e2SPatrick Williams time.strftime("%D %H:%M:%S"))) 494eb8dc403SDave Cobbley else: 495eb8dc403SDave Cobbley # no need to check if reachedlogin unless we support multiple connections 496eb8dc403SDave Cobbley self.logger.debug("QEMU socket disconnected before login banner reached. (%s)" % 497eb8dc403SDave Cobbley time.strftime("%D %H:%M:%S")) 498e760df85SPatrick Williams filelist.remove(file) 499e760df85SPatrick Williams file.close() 500eb8dc403SDave Cobbley stopread = True 501eb8dc403SDave Cobbley 502eb8dc403SDave Cobbley if not reachedlogin: 503eb8dc403SDave Cobbley if time.time() >= endtime: 50496ff1984SBrad Bishop self.logger.warning("Target didn't reach login banner in %d seconds (%s)" % 505eb8dc403SDave Cobbley (self.boottime, time.strftime("%D %H:%M:%S"))) 506eb8dc403SDave Cobbley tail = lambda l: "\n".join(l.splitlines()[-25:]) 50787f5cff0SAndrew Geissler bootlog = self.decode_qemulog(bootlog) 50820137395SAndrew Geissler self.logger.warning("Last 25 lines of login console (%d):\n%s" % (len(bootlog), tail(bootlog))) 50920137395SAndrew Geissler self.logger.warning("Last 25 lines of all logging (%d):\n%s" % (len(self.msg), tail(self.msg))) 51096ff1984SBrad Bishop self.logger.warning("Check full boot log: %s" % self.logfile) 511eb8dc403SDave Cobbley self.stop() 51220137395SAndrew Geissler data = True 51320137395SAndrew Geissler while data: 51420137395SAndrew Geissler try: 51520137395SAndrew Geissler time.sleep(1) 51620137395SAndrew Geissler data = qemusock.recv(1024) 51720137395SAndrew Geissler self.log(data, extension = ".2") 51820137395SAndrew Geissler self.logger.warning('Extra log data read: %s\n' % (data.decode('utf-8', errors='backslashreplace'))) 51920137395SAndrew Geissler except Exception as e: 52020137395SAndrew Geissler self.logger.warning('Extra log data exception %s' % repr(e)) 52120137395SAndrew Geissler data = None 522169d7bccSPatrick Williams self.thread.serial_lock.release() 523eb8dc403SDave Cobbley return False 524eb8dc403SDave Cobbley 525169d7bccSPatrick Williams with self.thread.serial_lock: 526169d7bccSPatrick Williams self.thread.set_serialsock(self.server_socket) 527169d7bccSPatrick Williams 528eb8dc403SDave Cobbley # If we are not able to login the tests can continue 529eb8dc403SDave Cobbley try: 530c3d88e4dSAndrew Geissler (status, output) = self.run_serial(self.boot_patterns['send_login_user'], raw=True, timeout=120) 53182c905dcSAndrew Geissler if re.search(self.boot_patterns['search_login_succeeded'], output): 532eb8dc403SDave Cobbley self.logged = True 5338e7b46e2SPatrick Williams self.logger.debug("Logged in as %s in serial console" % self.boot_patterns['send_login_user'].replace("\n", "")) 534eb8dc403SDave Cobbley if netconf: 535eb8dc403SDave Cobbley # configure guest networking 536eb8dc403SDave Cobbley cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip, self.netmask) 537eb8dc403SDave Cobbley output = self.run_serial(cmd, raw=True)[1] 538f86d0556SBrad Bishop if re.search(r"root@[a-zA-Z0-9\-]+:~#", output): 539eb8dc403SDave Cobbley self.logger.debug("configured ip address %s", self.ip) 540eb8dc403SDave Cobbley else: 541eb8dc403SDave Cobbley self.logger.debug("Couldn't configure guest networking") 542eb8dc403SDave Cobbley else: 54396ff1984SBrad Bishop self.logger.warning("Couldn't login into serial console" 5448e7b46e2SPatrick Williams " as %s using blank password" % self.boot_patterns['send_login_user'].replace("\n", "")) 54596ff1984SBrad Bishop self.logger.warning("The output:\n%s" % output) 546eb8dc403SDave Cobbley except: 54796ff1984SBrad Bishop self.logger.warning("Serial console failed while trying to login") 548eb8dc403SDave Cobbley return True 549eb8dc403SDave Cobbley 550eb8dc403SDave Cobbley def stop(self): 551eb8dc403SDave Cobbley if hasattr(self, "origchldhandler"): 552eb8dc403SDave Cobbley signal.signal(signal.SIGCHLD, self.origchldhandler) 5531a4b7ee2SBrad Bishop self.stop_thread() 5541a4b7ee2SBrad Bishop self.stop_qemu_system() 555eb8dc403SDave Cobbley if self.runqemu: 556eb8dc403SDave Cobbley if hasattr(self, "monitorpid"): 557eb8dc403SDave Cobbley os.kill(self.monitorpid, signal.SIGKILL) 558eb8dc403SDave Cobbley self.logger.debug("Sending SIGTERM to runqemu") 559eb8dc403SDave Cobbley try: 560eb8dc403SDave Cobbley os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM) 561eb8dc403SDave Cobbley except OSError as e: 562eb8dc403SDave Cobbley if e.errno != errno.ESRCH: 563eb8dc403SDave Cobbley raise 564864cc43bSPatrick Williams try: 565864cc43bSPatrick Williams outs, errs = self.runqemu.communicate(timeout=self.runqemutime) 566864cc43bSPatrick Williams if outs: 567864cc43bSPatrick Williams self.logger.info("Output from runqemu:\n%s", outs.decode("utf-8")) 568864cc43bSPatrick Williams if errs: 569864cc43bSPatrick Williams self.logger.info("Stderr from runqemu:\n%s", errs.decode("utf-8")) 5708e7b46e2SPatrick Williams except subprocess.TimeoutExpired: 571eb8dc403SDave Cobbley self.logger.debug("Sending SIGKILL to runqemu") 572eb8dc403SDave Cobbley os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL) 573d159c7fbSAndrew Geissler if not self.runqemu.stdout.closed: 574169d7bccSPatrick Williams self.logger.info("Output from runqemu:\n%s" % getOutput(self.runqemu.stdout)) 575f86d0556SBrad Bishop self.runqemu.stdin.close() 576f86d0556SBrad Bishop self.runqemu.stdout.close() 57782c905dcSAndrew Geissler self.runqemu_exited = True 578f86d0556SBrad Bishop 579c926e17cSAndrew Geissler if hasattr(self, 'qmp') and self.qmp: 580c926e17cSAndrew Geissler self.qmp.close() 581c926e17cSAndrew Geissler self.qmp = None 582eb8dc403SDave Cobbley if hasattr(self, 'server_socket') and self.server_socket: 583eb8dc403SDave Cobbley self.server_socket.close() 584eb8dc403SDave Cobbley self.server_socket = None 585f86d0556SBrad Bishop if hasattr(self, 'threadsock') and self.threadsock: 586f86d0556SBrad Bishop self.threadsock.close() 587f86d0556SBrad Bishop self.threadsock = None 588eb8dc403SDave Cobbley self.qemupid = None 589eb8dc403SDave Cobbley self.ip = None 590eb8dc403SDave Cobbley if os.path.exists(self.qemu_pidfile): 59182c905dcSAndrew Geissler try: 592eb8dc403SDave Cobbley os.remove(self.qemu_pidfile) 59382c905dcSAndrew Geissler except FileNotFoundError as e: 59482c905dcSAndrew Geissler # We raced, ignore 59582c905dcSAndrew Geissler pass 596f86d0556SBrad Bishop if self.monitorpipe: 597f86d0556SBrad Bishop self.monitorpipe.close() 598eb8dc403SDave Cobbley 599eb8dc403SDave Cobbley def stop_qemu_system(self): 600eb8dc403SDave Cobbley if self.qemupid: 601eb8dc403SDave Cobbley try: 602eb8dc403SDave Cobbley # qemu-system behaves well and a SIGTERM is enough 603eb8dc403SDave Cobbley os.kill(self.qemupid, signal.SIGTERM) 604eb8dc403SDave Cobbley except ProcessLookupError as e: 6051a4b7ee2SBrad Bishop self.logger.warning('qemu-system ended unexpectedly') 606eb8dc403SDave Cobbley 607eb8dc403SDave Cobbley def stop_thread(self): 608eb8dc403SDave Cobbley if self.thread and self.thread.is_alive(): 609eb8dc403SDave Cobbley self.thread.stop() 610eb8dc403SDave Cobbley self.thread.join() 611eb8dc403SDave Cobbley 612c926e17cSAndrew Geissler def allowexit(self): 613ac69b488SWilliam A. Kennington III self.canexit = True 614c926e17cSAndrew Geissler if self.thread: 615c926e17cSAndrew Geissler self.thread.allowexit() 616c926e17cSAndrew Geissler 617eb8dc403SDave Cobbley def restart(self, qemuparams = None): 61896ff1984SBrad Bishop self.logger.warning("Restarting qemu process") 619eb8dc403SDave Cobbley if self.runqemu.poll() is None: 620eb8dc403SDave Cobbley self.stop() 621eb8dc403SDave Cobbley if self.start(qemuparams): 622eb8dc403SDave Cobbley return True 623eb8dc403SDave Cobbley return False 624eb8dc403SDave Cobbley 625eb8dc403SDave Cobbley def is_alive(self): 62682c905dcSAndrew Geissler if not self.runqemu or self.runqemu.poll() is not None or self.runqemu_exited: 627eb8dc403SDave Cobbley return False 628eb8dc403SDave Cobbley if os.path.isfile(self.qemu_pidfile): 62996ff1984SBrad Bishop # when handling pidfile, qemu creates the file, stat it, lock it and then write to it 63096ff1984SBrad Bishop # so it's possible that the file has been created but the content is empty 63196ff1984SBrad Bishop pidfile_timeout = time.time() + 3 63296ff1984SBrad Bishop while time.time() < pidfile_timeout: 63373bd93f1SPatrick Williams try: 63496ff1984SBrad Bishop with open(self.qemu_pidfile, 'r') as f: 63596ff1984SBrad Bishop qemu_pid = f.read().strip() 63673bd93f1SPatrick Williams except FileNotFoundError: 63773bd93f1SPatrick Williams # Can be used to detect shutdown so the pid file can disappear 63873bd93f1SPatrick Williams return False 63996ff1984SBrad Bishop # file created but not yet written contents 64096ff1984SBrad Bishop if not qemu_pid: 64196ff1984SBrad Bishop time.sleep(0.5) 64296ff1984SBrad Bishop continue 64396ff1984SBrad Bishop else: 64496ff1984SBrad Bishop if os.path.exists("/proc/" + qemu_pid): 64596ff1984SBrad Bishop self.qemupid = int(qemu_pid) 646eb8dc403SDave Cobbley return True 647eb8dc403SDave Cobbley return False 648eb8dc403SDave Cobbley 6495f35090dSAndrew Geissler def run_monitor(self, command, args=None, timeout=60): 6505f35090dSAndrew Geissler if hasattr(self, 'qmp') and self.qmp: 6516aa7eec5SAndrew Geissler self.qmp.settimeout(timeout) 6525f35090dSAndrew Geissler if args is not None: 653*b58112e5SPatrick Williams return self.qmp.cmd_raw(command, args) 6545f35090dSAndrew Geissler else: 655*b58112e5SPatrick Williams return self.qmp.cmd_raw(command) 656c926e17cSAndrew Geissler 657977dc1acSBrad Bishop def run_serial(self, command, raw=False, timeout=60): 65892b42cb3SPatrick Williams # Returns (status, output) where status is 1 on success and 0 on error 65992b42cb3SPatrick Williams 660eb8dc403SDave Cobbley # We assume target system have echo to get command status 661eb8dc403SDave Cobbley if not raw: 662eb8dc403SDave Cobbley command = "%s; echo $?\n" % command 663eb8dc403SDave Cobbley 664eb8dc403SDave Cobbley data = '' 665eb8dc403SDave Cobbley status = 0 666169d7bccSPatrick Williams with self.thread.serial_lock: 667eb8dc403SDave Cobbley self.server_socket.sendall(command.encode('utf-8')) 668eb8dc403SDave Cobbley start = time.time() 669eb8dc403SDave Cobbley end = start + timeout 670eb8dc403SDave Cobbley while True: 671eb8dc403SDave Cobbley now = time.time() 672eb8dc403SDave Cobbley if now >= end: 673eb8dc403SDave Cobbley data += "<<< run_serial(): command timed out after %d seconds without output >>>\r\n\r\n" % timeout 674eb8dc403SDave Cobbley break 675eb8dc403SDave Cobbley try: 676eb8dc403SDave Cobbley sread, _, _ = select.select([self.server_socket],[],[], end - now) 677eb8dc403SDave Cobbley except InterruptedError: 678eb8dc403SDave Cobbley continue 679eb8dc403SDave Cobbley if sread: 6806aa7eec5SAndrew Geissler # try to avoid reading single character at a time 6816aa7eec5SAndrew Geissler time.sleep(0.1) 682eb8dc403SDave Cobbley answer = self.server_socket.recv(1024) 683eb8dc403SDave Cobbley if answer: 684eb8dc403SDave Cobbley data += answer.decode('utf-8') 685eb8dc403SDave Cobbley # Search the prompt to stop 68682c905dcSAndrew Geissler if re.search(self.boot_patterns['search_cmd_finished'], data): 687eb8dc403SDave Cobbley break 688eb8dc403SDave Cobbley else: 689ac69b488SWilliam A. Kennington III if self.canexit: 690ac69b488SWilliam A. Kennington III return (1, "") 691ac69b488SWilliam A. Kennington III raise Exception("No data on serial console socket, connection closed?") 692eb8dc403SDave Cobbley 693eb8dc403SDave Cobbley if data: 694eb8dc403SDave Cobbley if raw: 695eb8dc403SDave Cobbley status = 1 696eb8dc403SDave Cobbley else: 697eb8dc403SDave Cobbley # Remove first line (command line) and last line (prompt) 698eb8dc403SDave Cobbley data = data[data.find('$?\r\n')+4:data.rfind('\r\n')] 699eb8dc403SDave Cobbley index = data.rfind('\r\n') 700eb8dc403SDave Cobbley if index == -1: 701eb8dc403SDave Cobbley status_cmd = data 702eb8dc403SDave Cobbley data = "" 703eb8dc403SDave Cobbley else: 704eb8dc403SDave Cobbley status_cmd = data[index+2:] 705eb8dc403SDave Cobbley data = data[:index] 706eb8dc403SDave Cobbley if (status_cmd == "0"): 707eb8dc403SDave Cobbley status = 1 708eb8dc403SDave Cobbley return (status, str(data)) 709eb8dc403SDave Cobbley 710169d7bccSPatrick Williams@contextmanager 711169d7bccSPatrick Williamsdef nonblocking_lock(lock): 712169d7bccSPatrick Williams locked = lock.acquire(False) 713169d7bccSPatrick Williams try: 714169d7bccSPatrick Williams yield locked 715169d7bccSPatrick Williams finally: 716169d7bccSPatrick Williams if locked: 717169d7bccSPatrick Williams lock.release() 718169d7bccSPatrick Williams 719eb8dc403SDave Cobbley# This class is for reading data from a socket and passing it to logfunc 720eb8dc403SDave Cobbley# to be processed. It's completely event driven and has a straightforward 721eb8dc403SDave Cobbley# event loop. The mechanism for stopping the thread is a simple pipe which 722eb8dc403SDave Cobbley# will wake up the poll and allow for tearing everything down. 723eb8dc403SDave Cobbleyclass LoggingThread(threading.Thread): 724169d7bccSPatrick Williams def __init__(self, logfunc, sock, logger, qemuoutput): 725eb8dc403SDave Cobbley self.connection_established = threading.Event() 726169d7bccSPatrick Williams self.serial_lock = threading.Lock() 727169d7bccSPatrick Williams 728eb8dc403SDave Cobbley self.serversock = sock 729169d7bccSPatrick Williams self.serialsock = None 730169d7bccSPatrick Williams self.qemuoutput = qemuoutput 731eb8dc403SDave Cobbley self.logfunc = logfunc 732eb8dc403SDave Cobbley self.logger = logger 733eb8dc403SDave Cobbley self.readsock = None 734eb8dc403SDave Cobbley self.running = False 735c926e17cSAndrew Geissler self.canexit = False 736eb8dc403SDave Cobbley 737eb8dc403SDave Cobbley self.errorevents = select.POLLERR | select.POLLHUP | select.POLLNVAL 738eb8dc403SDave Cobbley self.readevents = select.POLLIN | select.POLLPRI 739eb8dc403SDave Cobbley 740eb8dc403SDave Cobbley threading.Thread.__init__(self, target=self.threadtarget) 741eb8dc403SDave Cobbley 742169d7bccSPatrick Williams def set_serialsock(self, serialsock): 743169d7bccSPatrick Williams self.serialsock = serialsock 744169d7bccSPatrick Williams 745eb8dc403SDave Cobbley def threadtarget(self): 746eb8dc403SDave Cobbley try: 747eb8dc403SDave Cobbley self.eventloop() 748169d7bccSPatrick Williams except Exception as e: 749169d7bccSPatrick Williams self.logger.warning("Exception %s in logging thread" % traceback.format_exception(e)) 750eb8dc403SDave Cobbley finally: 751eb8dc403SDave Cobbley self.teardown() 752eb8dc403SDave Cobbley 753eb8dc403SDave Cobbley def run(self): 754eb8dc403SDave Cobbley self.logger.debug("Starting logging thread") 755eb8dc403SDave Cobbley self.readpipe, self.writepipe = os.pipe() 756eb8dc403SDave Cobbley threading.Thread.run(self) 757eb8dc403SDave Cobbley 758eb8dc403SDave Cobbley def stop(self): 759eb8dc403SDave Cobbley self.logger.debug("Stopping logging thread") 760eb8dc403SDave Cobbley if self.running: 761eb8dc403SDave Cobbley os.write(self.writepipe, bytes("stop", "utf-8")) 762eb8dc403SDave Cobbley 763eb8dc403SDave Cobbley def teardown(self): 764eb8dc403SDave Cobbley self.logger.debug("Tearing down logging thread") 765169d7bccSPatrick Williams if self.serversock: 766eb8dc403SDave Cobbley self.close_socket(self.serversock) 767eb8dc403SDave Cobbley 768eb8dc403SDave Cobbley if self.readsock is not None: 769eb8dc403SDave Cobbley self.close_socket(self.readsock) 770eb8dc403SDave Cobbley 771eb8dc403SDave Cobbley self.close_ignore_error(self.readpipe) 772eb8dc403SDave Cobbley self.close_ignore_error(self.writepipe) 773eb8dc403SDave Cobbley self.running = False 774eb8dc403SDave Cobbley 775c926e17cSAndrew Geissler def allowexit(self): 776c926e17cSAndrew Geissler self.canexit = True 777c926e17cSAndrew Geissler 778eb8dc403SDave Cobbley def eventloop(self): 779eb8dc403SDave Cobbley poll = select.poll() 780eb8dc403SDave Cobbley event_read_mask = self.errorevents | self.readevents 781169d7bccSPatrick Williams if self.serversock: 782eb8dc403SDave Cobbley poll.register(self.serversock.fileno()) 783169d7bccSPatrick Williams serial_registered = False 784169d7bccSPatrick Williams poll.register(self.qemuoutput.fileno()) 785eb8dc403SDave Cobbley poll.register(self.readpipe, event_read_mask) 786eb8dc403SDave Cobbley 787eb8dc403SDave Cobbley breakout = False 788eb8dc403SDave Cobbley self.running = True 789eb8dc403SDave Cobbley self.logger.debug("Starting thread event loop") 790eb8dc403SDave Cobbley while not breakout: 791169d7bccSPatrick Williams events = poll.poll(2) 792169d7bccSPatrick Williams for fd, event in events: 793169d7bccSPatrick Williams 794eb8dc403SDave Cobbley # An error occurred, bail out 795169d7bccSPatrick Williams if event & self.errorevents: 796169d7bccSPatrick Williams raise Exception(self.stringify_event(event)) 797eb8dc403SDave Cobbley 798eb8dc403SDave Cobbley # Event to stop the thread 799169d7bccSPatrick Williams if self.readpipe == fd: 800eb8dc403SDave Cobbley self.logger.debug("Stop event received") 801eb8dc403SDave Cobbley breakout = True 802eb8dc403SDave Cobbley break 803eb8dc403SDave Cobbley 804eb8dc403SDave Cobbley # A connection request was received 805169d7bccSPatrick Williams elif self.serversock and self.serversock.fileno() == fd: 806eb8dc403SDave Cobbley self.logger.debug("Connection request received") 807eb8dc403SDave Cobbley self.readsock, _ = self.serversock.accept() 808eb8dc403SDave Cobbley self.readsock.setblocking(0) 809eb8dc403SDave Cobbley poll.unregister(self.serversock.fileno()) 810eb8dc403SDave Cobbley poll.register(self.readsock.fileno(), event_read_mask) 811eb8dc403SDave Cobbley 812eb8dc403SDave Cobbley self.logger.debug("Setting connection established event") 813eb8dc403SDave Cobbley self.connection_established.set() 814eb8dc403SDave Cobbley 815eb8dc403SDave Cobbley # Actual data to be logged 816169d7bccSPatrick Williams elif self.readsock and self.readsock.fileno() == fd: 817169d7bccSPatrick Williams data = self.recv(1024, self.readsock) 818eb8dc403SDave Cobbley self.logfunc(data) 819169d7bccSPatrick Williams elif self.qemuoutput.fileno() == fd: 820169d7bccSPatrick Williams data = self.qemuoutput.read() 821169d7bccSPatrick Williams self.logger.debug("Data received on qemu stdout %s" % data) 822169d7bccSPatrick Williams self.logfunc(data, ".stdout") 823169d7bccSPatrick Williams elif self.serialsock and self.serialsock.fileno() == fd: 824169d7bccSPatrick Williams if self.serial_lock.acquire(blocking=False): 825169d7bccSPatrick Williams data = self.recv(1024, self.serialsock) 826169d7bccSPatrick Williams self.logger.debug("Data received serial thread %s" % data.decode('utf-8', 'replace')) 827169d7bccSPatrick Williams self.logfunc(data, ".2") 828169d7bccSPatrick Williams self.serial_lock.release() 829169d7bccSPatrick Williams else: 830169d7bccSPatrick Williams serial_registered = False 831169d7bccSPatrick Williams poll.unregister(self.serialsock.fileno()) 832169d7bccSPatrick Williams 833169d7bccSPatrick Williams if not serial_registered and self.serialsock: 834169d7bccSPatrick Williams with nonblocking_lock(self.serial_lock) as l: 835169d7bccSPatrick Williams if l: 836169d7bccSPatrick Williams serial_registered = True 837169d7bccSPatrick Williams poll.register(self.serialsock.fileno(), event_read_mask) 838169d7bccSPatrick Williams 839eb8dc403SDave Cobbley 840eb8dc403SDave Cobbley # Since the socket is non-blocking make sure to honor EAGAIN 841eb8dc403SDave Cobbley # and EWOULDBLOCK. 842169d7bccSPatrick Williams def recv(self, count, sock): 843eb8dc403SDave Cobbley try: 844169d7bccSPatrick Williams data = sock.recv(count) 845eb8dc403SDave Cobbley except socket.error as e: 846eb8dc403SDave Cobbley if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK: 847ac69b488SWilliam A. Kennington III return b'' 848eb8dc403SDave Cobbley else: 849eb8dc403SDave Cobbley raise 850eb8dc403SDave Cobbley 851eb8dc403SDave Cobbley if data is None: 852eb8dc403SDave Cobbley raise Exception("No data on read ready socket") 853eb8dc403SDave Cobbley elif not data: 854eb8dc403SDave Cobbley # This actually means an orderly shutdown 855eb8dc403SDave Cobbley # happened. But for this code it counts as an 856eb8dc403SDave Cobbley # error since the connection shouldn't go away 857eb8dc403SDave Cobbley # until qemu exits. 858c926e17cSAndrew Geissler if not self.canexit: 859eb8dc403SDave Cobbley raise Exception("Console connection closed unexpectedly") 860ac69b488SWilliam A. Kennington III return b'' 861eb8dc403SDave Cobbley 862eb8dc403SDave Cobbley return data 863eb8dc403SDave Cobbley 864eb8dc403SDave Cobbley def stringify_event(self, event): 865eb8dc403SDave Cobbley val = '' 866eb8dc403SDave Cobbley if select.POLLERR == event: 867eb8dc403SDave Cobbley val = 'POLLER' 868eb8dc403SDave Cobbley elif select.POLLHUP == event: 869eb8dc403SDave Cobbley val = 'POLLHUP' 870eb8dc403SDave Cobbley elif select.POLLNVAL == event: 871eb8dc403SDave Cobbley val = 'POLLNVAL' 872169d7bccSPatrick Williams else: 873169d7bccSPatrick Williams val = "0x%x" % (event) 874169d7bccSPatrick Williams 875eb8dc403SDave Cobbley return val 876eb8dc403SDave Cobbley 877eb8dc403SDave Cobbley def close_socket(self, sock): 878eb8dc403SDave Cobbley sock.shutdown(socket.SHUT_RDWR) 879eb8dc403SDave Cobbley sock.close() 880eb8dc403SDave Cobbley 881eb8dc403SDave Cobbley def close_ignore_error(self, fd): 882eb8dc403SDave Cobbley try: 883eb8dc403SDave Cobbley os.close(fd) 884eb8dc403SDave Cobbley except OSError: 885eb8dc403SDave Cobbley pass 886