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