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