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: (?:ip=)*([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