xref: /openbmc/qemu/python/qemu/machine/machine.py (revision 5f69e42d)
1"""
2QEMU machine module:
3
4The machine module primarily provides the QEMUMachine class,
5which provides facilities for managing the lifetime of a QEMU VM.
6"""
7
8# Copyright (C) 2015-2016 Red Hat Inc.
9# Copyright (C) 2012 IBM Corp.
10#
11# Authors:
12#  Fam Zheng <famz@redhat.com>
13#
14# This work is licensed under the terms of the GNU GPL, version 2.  See
15# the COPYING file in the top-level directory.
16#
17# Based on qmp.py.
18#
19
20import errno
21from itertools import chain
22import locale
23import logging
24import os
25import shutil
26import signal
27import socket
28import subprocess
29import tempfile
30from types import TracebackType
31from typing import (
32    Any,
33    BinaryIO,
34    Dict,
35    List,
36    Optional,
37    Sequence,
38    Tuple,
39    Type,
40    TypeVar,
41)
42
43from qemu.qmp import SocketAddrT
44from qemu.qmp.legacy import (
45    QEMUMonitorProtocol,
46    QMPMessage,
47    QMPReturnValue,
48)
49
50from . import console_socket
51
52
53LOG = logging.getLogger(__name__)
54
55
56class QEMUMachineError(Exception):
57    """
58    Exception called when an error in QEMUMachine happens.
59    """
60
61
62class QEMUMachineAddDeviceError(QEMUMachineError):
63    """
64    Exception raised when a request to add a device can not be fulfilled
65
66    The failures are caused by limitations, lack of information or conflicting
67    requests on the QEMUMachine methods.  This exception does not represent
68    failures reported by the QEMU binary itself.
69    """
70
71
72class VMLaunchFailure(QEMUMachineError):
73    """
74    Exception raised when a VM launch was attempted, but failed.
75    """
76    def __init__(self, exitcode: Optional[int],
77                 command: str, output: Optional[str]):
78        super().__init__(exitcode, command, output)
79        self.exitcode = exitcode
80        self.command = command
81        self.output = output
82
83    def __str__(self) -> str:
84        ret = ''
85        if self.__cause__ is not None:
86            name = type(self.__cause__).__name__
87            reason = str(self.__cause__)
88            if reason:
89                ret += f"{name}: {reason}"
90            else:
91                ret += f"{name}"
92        ret += '\n'
93
94        if self.exitcode is not None:
95            ret += f"\tExit code: {self.exitcode}\n"
96        ret += f"\tCommand: {self.command}\n"
97        ret += f"\tOutput: {self.output}\n"
98        return ret
99
100
101class AbnormalShutdown(QEMUMachineError):
102    """
103    Exception raised when a graceful shutdown was requested, but not performed.
104    """
105
106
107_T = TypeVar('_T', bound='QEMUMachine')
108
109
110class QEMUMachine:
111    """
112    A QEMU VM.
113
114    Use this object as a context manager to ensure
115    the QEMU process terminates::
116
117        with VM(binary) as vm:
118            ...
119        # vm is guaranteed to be shut down here
120    """
121    # pylint: disable=too-many-instance-attributes, too-many-public-methods
122
123    def __init__(self,
124                 binary: str,
125                 args: Sequence[str] = (),
126                 wrapper: Sequence[str] = (),
127                 name: Optional[str] = None,
128                 base_temp_dir: str = "/var/tmp",
129                 monitor_address: Optional[SocketAddrT] = None,
130                 drain_console: bool = False,
131                 console_log: Optional[str] = None,
132                 log_dir: Optional[str] = None,
133                 qmp_timer: Optional[float] = 30):
134        '''
135        Initialize a QEMUMachine
136
137        @param binary: path to the qemu binary
138        @param args: list of extra arguments
139        @param wrapper: list of arguments used as prefix to qemu binary
140        @param name: prefix for socket and log file names (default: qemu-PID)
141        @param base_temp_dir: default location where temp files are created
142        @param monitor_address: address for QMP monitor
143        @param drain_console: (optional) True to drain console socket to buffer
144        @param console_log: (optional) path to console log file
145        @param log_dir: where to create and keep log files
146        @param qmp_timer: (optional) default QMP socket timeout
147        @note: Qemu process is not started until launch() is used.
148        '''
149        # pylint: disable=too-many-arguments
150
151        # Direct user configuration
152
153        self._binary = binary
154        self._args = list(args)
155        self._wrapper = wrapper
156        self._qmp_timer = qmp_timer
157
158        self._name = name or f"{id(self):x}"
159        self._sock_pair: Optional[Tuple[socket.socket, socket.socket]] = None
160        self._cons_sock_pair: Optional[
161            Tuple[socket.socket, socket.socket]] = None
162        self._temp_dir: Optional[str] = None
163        self._base_temp_dir = base_temp_dir
164        self._log_dir = log_dir
165
166        self._monitor_address = monitor_address
167
168        self._console_log_path = console_log
169        if self._console_log_path:
170            # In order to log the console, buffering needs to be enabled.
171            self._drain_console = True
172        else:
173            self._drain_console = drain_console
174
175        # Runstate
176        self._qemu_log_path: Optional[str] = None
177        self._qemu_log_file: Optional[BinaryIO] = None
178        self._popen: Optional['subprocess.Popen[bytes]'] = None
179        self._events: List[QMPMessage] = []
180        self._iolog: Optional[str] = None
181        self._qmp_set = True   # Enable QMP monitor by default.
182        self._qmp_connection: Optional[QEMUMonitorProtocol] = None
183        self._qemu_full_args: Tuple[str, ...] = ()
184        self._launched = False
185        self._machine: Optional[str] = None
186        self._console_index = 0
187        self._console_set = False
188        self._console_device_type: Optional[str] = None
189        self._console_socket: Optional[socket.socket] = None
190        self._console_file: Optional[socket.SocketIO] = None
191        self._remove_files: List[str] = []
192        self._user_killed = False
193        self._quit_issued = False
194
195    def __enter__(self: _T) -> _T:
196        return self
197
198    def __exit__(self,
199                 exc_type: Optional[Type[BaseException]],
200                 exc_val: Optional[BaseException],
201                 exc_tb: Optional[TracebackType]) -> None:
202        self.shutdown()
203
204    def add_monitor_null(self) -> None:
205        """
206        This can be used to add an unused monitor instance.
207        """
208        self._args.append('-monitor')
209        self._args.append('null')
210
211    def add_fd(self: _T, fd: int, fdset: int,
212               opaque: str, opts: str = '') -> _T:
213        """
214        Pass a file descriptor to the VM
215        """
216        options = ['fd=%d' % fd,
217                   'set=%d' % fdset,
218                   'opaque=%s' % opaque]
219        if opts:
220            options.append(opts)
221
222        # This did not exist before 3.4, but since then it is
223        # mandatory for our purpose
224        if hasattr(os, 'set_inheritable'):
225            os.set_inheritable(fd, True)
226
227        self._args.append('-add-fd')
228        self._args.append(','.join(options))
229        return self
230
231    def send_fd_scm(self, fd: Optional[int] = None,
232                    file_path: Optional[str] = None) -> int:
233        """
234        Send an fd or file_path to the remote via SCM_RIGHTS.
235
236        Exactly one of fd and file_path must be given.  If it is
237        file_path, the file will be opened read-only and the new file
238        descriptor will be sent to the remote.
239        """
240        if file_path is not None:
241            assert fd is None
242            with open(file_path, "rb") as passfile:
243                fd = passfile.fileno()
244                self._qmp.send_fd_scm(fd)
245        else:
246            assert fd is not None
247            self._qmp.send_fd_scm(fd)
248
249        return 0
250
251    @staticmethod
252    def _remove_if_exists(path: str) -> None:
253        """
254        Remove file object at path if it exists
255        """
256        try:
257            os.remove(path)
258        except OSError as exception:
259            if exception.errno == errno.ENOENT:
260                return
261            raise
262
263    def is_running(self) -> bool:
264        """Returns true if the VM is running."""
265        return self._popen is not None and self._popen.poll() is None
266
267    @property
268    def _subp(self) -> 'subprocess.Popen[bytes]':
269        if self._popen is None:
270            raise QEMUMachineError('Subprocess pipe not present')
271        return self._popen
272
273    def exitcode(self) -> Optional[int]:
274        """Returns the exit code if possible, or None."""
275        if self._popen is None:
276            return None
277        return self._popen.poll()
278
279    def get_pid(self) -> Optional[int]:
280        """Returns the PID of the running process, or None."""
281        if not self.is_running():
282            return None
283        return self._subp.pid
284
285    def _load_io_log(self) -> None:
286        # Assume that the output encoding of QEMU's terminal output is
287        # defined by our locale. If indeterminate, allow open() to fall
288        # back to the platform default.
289        _, encoding = locale.getlocale()
290        if self._qemu_log_path is not None:
291            with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
292                self._iolog = iolog.read()
293
294    @property
295    def _base_args(self) -> List[str]:
296        args = ['-display', 'none', '-vga', 'none']
297
298        if self._qmp_set:
299            if self._sock_pair:
300                moncdev = f"socket,id=mon,fd={self._sock_pair[0].fileno()}"
301            elif isinstance(self._monitor_address, tuple):
302                moncdev = "socket,id=mon,host={},port={}".format(
303                    *self._monitor_address
304                )
305            else:
306                moncdev = f"socket,id=mon,path={self._monitor_address}"
307            args.extend(['-chardev', moncdev, '-mon',
308                         'chardev=mon,mode=control'])
309
310        if self._machine is not None:
311            args.extend(['-machine', self._machine])
312        for _ in range(self._console_index):
313            args.extend(['-serial', 'null'])
314        if self._console_set:
315            assert self._cons_sock_pair is not None
316            fd = self._cons_sock_pair[0].fileno()
317            chardev = f"socket,id=console,fd={fd}"
318            args.extend(['-chardev', chardev])
319            if self._console_device_type is None:
320                args.extend(['-serial', 'chardev:console'])
321            else:
322                device = '%s,chardev=console' % self._console_device_type
323                args.extend(['-device', device])
324        return args
325
326    @property
327    def args(self) -> List[str]:
328        """Returns the list of arguments given to the QEMU binary."""
329        return self._args
330
331    @property
332    def binary(self) -> str:
333        """Returns path to the QEMU binary"""
334        return self._binary
335
336    def _pre_launch(self) -> None:
337        if self._qmp_set:
338            if self._monitor_address is None:
339                self._sock_pair = socket.socketpair()
340                os.set_inheritable(self._sock_pair[0].fileno(), True)
341                sock = self._sock_pair[1]
342            if isinstance(self._monitor_address, str):
343                self._remove_files.append(self._monitor_address)
344
345            sock_or_addr = self._monitor_address or sock
346            assert sock_or_addr is not None
347
348            self._qmp_connection = QEMUMonitorProtocol(
349                sock_or_addr,
350                server=bool(self._monitor_address),
351                nickname=self._name
352            )
353
354        if self._console_set:
355            self._cons_sock_pair = socket.socketpair()
356            os.set_inheritable(self._cons_sock_pair[0].fileno(), True)
357
358        # NOTE: Make sure any opened resources are *definitely* freed in
359        # _post_shutdown()!
360        # pylint: disable=consider-using-with
361        self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
362        self._qemu_log_file = open(self._qemu_log_path, 'wb')
363
364        self._iolog = None
365        self._qemu_full_args = tuple(chain(
366            self._wrapper,
367            [self._binary],
368            self._base_args,
369            self._args
370        ))
371
372    def _post_launch(self) -> None:
373        if self._sock_pair:
374            self._sock_pair[0].close()
375        if self._cons_sock_pair:
376            self._cons_sock_pair[0].close()
377
378        if self._qmp_connection:
379            if self._sock_pair:
380                self._qmp.connect()
381            else:
382                self._qmp.accept(self._qmp_timer)
383
384    def _close_qemu_log_file(self) -> None:
385        if self._qemu_log_file is not None:
386            self._qemu_log_file.close()
387            self._qemu_log_file = None
388
389    def _post_shutdown(self) -> None:
390        """
391        Called to cleanup the VM instance after the process has exited.
392        May also be called after a failed launch.
393        """
394        LOG.debug("Cleaning up after VM process")
395        try:
396            self._close_qmp_connection()
397        except Exception as err:  # pylint: disable=broad-except
398            LOG.warning(
399                "Exception closing QMP connection: %s",
400                str(err) if str(err) else type(err).__name__
401            )
402        finally:
403            assert self._qmp_connection is None
404
405        if self._sock_pair:
406            self._sock_pair[0].close()
407            self._sock_pair[1].close()
408            self._sock_pair = None
409
410        self._close_qemu_log_file()
411
412        self._load_io_log()
413
414        self._qemu_log_path = None
415
416        if self._temp_dir is not None:
417            shutil.rmtree(self._temp_dir)
418            self._temp_dir = None
419
420        while len(self._remove_files) > 0:
421            self._remove_if_exists(self._remove_files.pop())
422
423        exitcode = self.exitcode()
424        if (exitcode is not None and exitcode < 0
425                and not (self._user_killed and exitcode == -signal.SIGKILL)):
426            msg = 'qemu received signal %i; command: "%s"'
427            if self._qemu_full_args:
428                command = ' '.join(self._qemu_full_args)
429            else:
430                command = ''
431            LOG.warning(msg, -int(exitcode), command)
432
433        self._quit_issued = False
434        self._user_killed = False
435        self._launched = False
436
437    def launch(self) -> None:
438        """
439        Launch the VM and make sure we cleanup and expose the
440        command line/output in case of exception
441        """
442
443        if self._launched:
444            raise QEMUMachineError('VM already launched')
445
446        try:
447            self._launch()
448        except BaseException as exc:
449            # We may have launched the process but it may
450            # have exited before we could connect via QMP.
451            # Assume the VM didn't launch or is exiting.
452            # If we don't wait for the process, exitcode() may still be
453            # 'None' by the time control is ceded back to the caller.
454            if self._launched:
455                self.wait()
456            else:
457                self._post_shutdown()
458
459            if isinstance(exc, Exception):
460                raise VMLaunchFailure(
461                    exitcode=self.exitcode(),
462                    command=' '.join(self._qemu_full_args),
463                    output=self._iolog
464                ) from exc
465
466            # Don't wrap 'BaseException'; doing so would downgrade
467            # that exception. However, we still want to clean up.
468            raise
469
470    def _launch(self) -> None:
471        """
472        Launch the VM and establish a QMP connection
473        """
474        self._pre_launch()
475        LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
476
477        # Cleaning up of this subprocess is guaranteed by _do_shutdown.
478        # pylint: disable=consider-using-with
479        self._popen = subprocess.Popen(self._qemu_full_args,
480                                       stdin=subprocess.DEVNULL,
481                                       stdout=self._qemu_log_file,
482                                       stderr=subprocess.STDOUT,
483                                       shell=False,
484                                       close_fds=False)
485        self._launched = True
486        self._post_launch()
487
488    def _close_qmp_connection(self) -> None:
489        """
490        Close the underlying QMP connection, if any.
491
492        Dutifully report errors that occurred while closing, but assume
493        that any error encountered indicates an abnormal termination
494        process and not a failure to close.
495        """
496        if self._qmp_connection is None:
497            return
498
499        try:
500            self._qmp.close()
501        except EOFError:
502            # EOF can occur as an Exception here when using the Async
503            # QMP backend. It indicates that the server closed the
504            # stream. If we successfully issued 'quit' at any point,
505            # then this was expected. If the remote went away without
506            # our permission, it's worth reporting that as an abnormal
507            # shutdown case.
508            if not (self._user_killed or self._quit_issued):
509                raise
510        finally:
511            self._qmp_connection = None
512
513    def _early_cleanup(self) -> None:
514        """
515        Perform any cleanup that needs to happen before the VM exits.
516
517        This method may be called twice upon shutdown, once each by soft
518        and hard shutdown in failover scenarios.
519        """
520        # If we keep the console socket open, we may deadlock waiting
521        # for QEMU to exit, while QEMU is waiting for the socket to
522        # become writable.
523        if self._console_file is not None:
524            LOG.debug("Closing console file")
525            self._console_file.close()
526            self._console_file = None
527
528        if self._console_socket is not None:
529            LOG.debug("Closing console socket")
530            self._console_socket.close()
531            self._console_socket = None
532
533        if self._cons_sock_pair:
534            self._cons_sock_pair[0].close()
535            self._cons_sock_pair[1].close()
536            self._cons_sock_pair = None
537
538    def _hard_shutdown(self) -> None:
539        """
540        Perform early cleanup, kill the VM, and wait for it to terminate.
541
542        :raise subprocess.Timeout: When timeout is exceeds 60 seconds
543            waiting for the QEMU process to terminate.
544        """
545        LOG.debug("Performing hard shutdown")
546        self._early_cleanup()
547        self._subp.kill()
548        self._subp.wait(timeout=60)
549
550    def _soft_shutdown(self, timeout: Optional[int]) -> None:
551        """
552        Perform early cleanup, attempt to gracefully shut down the VM, and wait
553        for it to terminate.
554
555        :param timeout: Timeout in seconds for graceful shutdown.
556                        A value of None is an infinite wait.
557
558        :raise ConnectionReset: On QMP communication errors
559        :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
560            the QEMU process to terminate.
561        """
562        LOG.debug("Attempting graceful termination")
563
564        self._early_cleanup()
565
566        if self._quit_issued:
567            LOG.debug(
568                "Anticipating QEMU termination due to prior 'quit' command, "
569                "or explicit call to wait()"
570            )
571        else:
572            LOG.debug("Politely asking QEMU to terminate")
573
574        if self._qmp_connection:
575            try:
576                if not self._quit_issued:
577                    # May raise ExecInterruptedError or StateError if the
578                    # connection dies or has *already* died.
579                    self.qmp('quit')
580            finally:
581                # Regardless, we want to quiesce the connection.
582                self._close_qmp_connection()
583        elif not self._quit_issued:
584            LOG.debug(
585                "Not anticipating QEMU quit and no QMP connection present, "
586                "issuing SIGTERM"
587            )
588            self._subp.terminate()
589
590        # May raise subprocess.TimeoutExpired
591        LOG.debug(
592            "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate",
593            timeout, self._subp.pid
594        )
595        self._subp.wait(timeout=timeout)
596
597    def _do_shutdown(self, timeout: Optional[int]) -> None:
598        """
599        Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
600
601        :param timeout: Timeout in seconds for graceful shutdown.
602                        A value of None is an infinite wait.
603
604        :raise AbnormalShutdown: When the VM could not be shut down gracefully.
605            The inner exception will likely be ConnectionReset or
606            subprocess.TimeoutExpired. In rare cases, non-graceful termination
607            may result in its own exceptions, likely subprocess.TimeoutExpired.
608        """
609        try:
610            self._soft_shutdown(timeout)
611        except Exception as exc:
612            if isinstance(exc, subprocess.TimeoutExpired):
613                LOG.debug("Timed out waiting for QEMU process to exit")
614            LOG.debug("Graceful shutdown failed", exc_info=True)
615            LOG.debug("Falling back to hard shutdown")
616            self._hard_shutdown()
617            raise AbnormalShutdown("Could not perform graceful shutdown") \
618                from exc
619
620    def shutdown(self,
621                 hard: bool = False,
622                 timeout: Optional[int] = 30) -> None:
623        """
624        Terminate the VM (gracefully if possible) and perform cleanup.
625        Cleanup will always be performed.
626
627        If the VM has not yet been launched, or shutdown(), wait(), or kill()
628        have already been called, this method does nothing.
629
630        :param hard: When true, do not attempt graceful shutdown, and
631                     suppress the SIGKILL warning log message.
632        :param timeout: Optional timeout in seconds for graceful shutdown.
633                        Default 30 seconds, A `None` value is an infinite wait.
634        """
635        if not self._launched:
636            return
637
638        LOG.debug("Shutting down VM appliance; timeout=%s", timeout)
639        if hard:
640            LOG.debug("Caller requests immediate termination of QEMU process.")
641
642        try:
643            if hard:
644                self._user_killed = True
645                self._hard_shutdown()
646            else:
647                self._do_shutdown(timeout)
648        finally:
649            self._post_shutdown()
650
651    def kill(self) -> None:
652        """
653        Terminate the VM forcefully, wait for it to exit, and perform cleanup.
654        """
655        self.shutdown(hard=True)
656
657    def wait(self, timeout: Optional[int] = 30) -> None:
658        """
659        Wait for the VM to power off and perform post-shutdown cleanup.
660
661        :param timeout: Optional timeout in seconds. Default 30 seconds.
662                        A value of `None` is an infinite wait.
663        """
664        self._quit_issued = True
665        self.shutdown(timeout=timeout)
666
667    def set_qmp_monitor(self, enabled: bool = True) -> None:
668        """
669        Set the QMP monitor.
670
671        @param enabled: if False, qmp monitor options will be removed from
672                        the base arguments of the resulting QEMU command
673                        line. Default is True.
674
675        .. note:: Call this function before launch().
676        """
677        self._qmp_set = enabled
678
679    @property
680    def _qmp(self) -> QEMUMonitorProtocol:
681        if self._qmp_connection is None:
682            raise QEMUMachineError("Attempt to access QMP with no connection")
683        return self._qmp_connection
684
685    @classmethod
686    def _qmp_args(cls, conv_keys: bool,
687                  args: Dict[str, Any]) -> Dict[str, object]:
688        if conv_keys:
689            return {k.replace('_', '-'): v for k, v in args.items()}
690
691        return args
692
693    def qmp(self, cmd: str,
694            args_dict: Optional[Dict[str, object]] = None,
695            conv_keys: Optional[bool] = None,
696            **args: Any) -> QMPMessage:
697        """
698        Invoke a QMP command and return the response dict
699        """
700        if args_dict is not None:
701            assert not args
702            assert conv_keys is None
703            args = args_dict
704            conv_keys = False
705
706        if conv_keys is None:
707            conv_keys = True
708
709        qmp_args = self._qmp_args(conv_keys, args)
710        ret = self._qmp.cmd_raw(cmd, args=qmp_args)
711        if cmd == 'quit' and 'error' not in ret and 'return' in ret:
712            self._quit_issued = True
713        return ret
714
715    def cmd(self, cmd: str,
716            args_dict: Optional[Dict[str, object]] = None,
717            conv_keys: Optional[bool] = None,
718            **args: Any) -> QMPReturnValue:
719        """
720        Invoke a QMP command.
721        On success return the response dict.
722        On failure raise an exception.
723        """
724        if args_dict is not None:
725            assert not args
726            assert conv_keys is None
727            args = args_dict
728            conv_keys = False
729
730        if conv_keys is None:
731            conv_keys = True
732
733        qmp_args = self._qmp_args(conv_keys, args)
734        ret = self._qmp.cmd(cmd, **qmp_args)
735        if cmd == 'quit':
736            self._quit_issued = True
737        return ret
738
739    def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
740        """
741        Poll for one queued QMP events and return it
742        """
743        if self._events:
744            return self._events.pop(0)
745        return self._qmp.pull_event(wait=wait)
746
747    def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
748        """
749        Poll for queued QMP events and return a list of dicts
750        """
751        events = self._qmp.get_events(wait=wait)
752        events.extend(self._events)
753        del self._events[:]
754        return events
755
756    @staticmethod
757    def event_match(event: Any, match: Optional[Any]) -> bool:
758        """
759        Check if an event matches optional match criteria.
760
761        The match criteria takes the form of a matching subdict. The event is
762        checked to be a superset of the subdict, recursively, with matching
763        values whenever the subdict values are not None.
764
765        This has a limitation that you cannot explicitly check for None values.
766
767        Examples, with the subdict queries on the left:
768         - None matches any object.
769         - {"foo": None} matches {"foo": {"bar": 1}}
770         - {"foo": None} matches {"foo": 5}
771         - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
772         - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
773        """
774        if match is None:
775            return True
776
777        try:
778            for key in match:
779                if key in event:
780                    if not QEMUMachine.event_match(event[key], match[key]):
781                        return False
782                else:
783                    return False
784            return True
785        except TypeError:
786            # either match or event wasn't iterable (not a dict)
787            return bool(match == event)
788
789    def event_wait(self, name: str,
790                   timeout: float = 60.0,
791                   match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
792        """
793        event_wait waits for and returns a named event from QMP with a timeout.
794
795        name: The event to wait for.
796        timeout: QEMUMonitorProtocol.pull_event timeout parameter.
797        match: Optional match criteria. See event_match for details.
798        """
799        return self.events_wait([(name, match)], timeout)
800
801    def events_wait(self,
802                    events: Sequence[Tuple[str, Any]],
803                    timeout: float = 60.0) -> Optional[QMPMessage]:
804        """
805        events_wait waits for and returns a single named event from QMP.
806        In the case of multiple qualifying events, this function returns the
807        first one.
808
809        :param events: A sequence of (name, match_criteria) tuples.
810                       The match criteria are optional and may be None.
811                       See event_match for details.
812        :param timeout: Optional timeout, in seconds.
813                        See QEMUMonitorProtocol.pull_event.
814
815        :raise asyncio.TimeoutError:
816            If timeout was non-zero and no matching events were found.
817
818        :return: A QMP event matching the filter criteria.
819                 If timeout was 0 and no event matched, None.
820        """
821        def _match(event: QMPMessage) -> bool:
822            for name, match in events:
823                if event['event'] == name and self.event_match(event, match):
824                    return True
825            return False
826
827        event: Optional[QMPMessage]
828
829        # Search cached events
830        for event in self._events:
831            if _match(event):
832                self._events.remove(event)
833                return event
834
835        # Poll for new events
836        while True:
837            event = self._qmp.pull_event(wait=timeout)
838            if event is None:
839                # NB: None is only returned when timeout is false-ish.
840                # Timeouts raise asyncio.TimeoutError instead!
841                break
842            if _match(event):
843                return event
844            self._events.append(event)
845
846        return None
847
848    def get_log(self) -> Optional[str]:
849        """
850        After self.shutdown or failed qemu execution, this returns the output
851        of the qemu process.
852        """
853        return self._iolog
854
855    def add_args(self, *args: str) -> None:
856        """
857        Adds to the list of extra arguments to be given to the QEMU binary
858        """
859        self._args.extend(args)
860
861    def set_machine(self, machine_type: str) -> None:
862        """
863        Sets the machine type
864
865        If set, the machine type will be added to the base arguments
866        of the resulting QEMU command line.
867        """
868        self._machine = machine_type
869
870    def set_console(self,
871                    device_type: Optional[str] = None,
872                    console_index: int = 0) -> None:
873        """
874        Sets the device type for a console device
875
876        If set, the console device and a backing character device will
877        be added to the base arguments of the resulting QEMU command
878        line.
879
880        This is a convenience method that will either use the provided
881        device type, or default to a "-serial chardev:console" command
882        line argument.
883
884        The actual setting of command line arguments will be be done at
885        machine launch time, as it depends on the temporary directory
886        to be created.
887
888        @param device_type: the device type, such as "isa-serial".  If
889                            None is given (the default value) a "-serial
890                            chardev:console" command line argument will
891                            be used instead, resorting to the machine's
892                            default device type.
893        @param console_index: the index of the console device to use.
894                              If not zero, the command line will create
895                              'index - 1' consoles and connect them to
896                              the 'null' backing character device.
897        """
898        self._console_set = True
899        self._console_device_type = device_type
900        self._console_index = console_index
901
902    @property
903    def console_socket(self) -> socket.socket:
904        """
905        Returns a socket connected to the console
906        """
907        if self._console_socket is None:
908            LOG.debug("Opening console socket")
909            if not self._console_set:
910                raise QEMUMachineError(
911                    "Attempt to access console socket with no connection")
912            assert self._cons_sock_pair is not None
913            # os.dup() is used here for sock_fd because otherwise we'd
914            # have two rich python socket objects that would each try to
915            # close the same underlying fd when either one gets garbage
916            # collected.
917            self._console_socket = console_socket.ConsoleSocket(
918                sock_fd=os.dup(self._cons_sock_pair[1].fileno()),
919                file=self._console_log_path,
920                drain=self._drain_console)
921            self._cons_sock_pair[1].close()
922        return self._console_socket
923
924    @property
925    def console_file(self) -> socket.SocketIO:
926        """
927        Returns a file associated with the console socket
928        """
929        if self._console_file is None:
930            LOG.debug("Opening console file")
931            self._console_file = self.console_socket.makefile(mode='rb',
932                                                              buffering=0,
933                                                              encoding='utf-8')
934        return self._console_file
935
936    @property
937    def temp_dir(self) -> str:
938        """
939        Returns a temporary directory to be used for this machine
940        """
941        if self._temp_dir is None:
942            self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
943                                              dir=self._base_temp_dir)
944        return self._temp_dir
945
946    @property
947    def log_dir(self) -> str:
948        """
949        Returns a directory to be used for writing logs
950        """
951        if self._log_dir is None:
952            return self.temp_dir
953        return self._log_dir
954