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