xref: /openbmc/qemu/python/qemu/machine/machine.py (revision 2e1cacfb)
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            sock = None
339            if self._monitor_address is None:
340                self._sock_pair = socket.socketpair()
341                os.set_inheritable(self._sock_pair[0].fileno(), True)
342                sock = self._sock_pair[1]
343            if isinstance(self._monitor_address, str):
344                self._remove_files.append(self._monitor_address)
345
346            sock_or_addr = self._monitor_address or sock
347            assert sock_or_addr is not None
348
349            self._qmp_connection = QEMUMonitorProtocol(
350                sock_or_addr,
351                server=bool(self._monitor_address),
352                nickname=self._name
353            )
354
355        if self._console_set:
356            self._cons_sock_pair = socket.socketpair()
357            os.set_inheritable(self._cons_sock_pair[0].fileno(), True)
358
359        # NOTE: Make sure any opened resources are *definitely* freed in
360        # _post_shutdown()!
361        # pylint: disable=consider-using-with
362        self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
363        self._qemu_log_file = open(self._qemu_log_path, 'wb')
364
365        self._iolog = None
366        self._qemu_full_args = tuple(chain(
367            self._wrapper,
368            [self._binary],
369            self._base_args,
370            self._args
371        ))
372
373    def _post_launch(self) -> None:
374        if self._sock_pair:
375            self._sock_pair[0].close()
376        if self._cons_sock_pair:
377            self._cons_sock_pair[0].close()
378
379        if self._qmp_connection:
380            if self._sock_pair:
381                self._qmp.connect()
382            else:
383                self._qmp.accept(self._qmp_timer)
384
385    def _close_qemu_log_file(self) -> None:
386        if self._qemu_log_file is not None:
387            self._qemu_log_file.close()
388            self._qemu_log_file = None
389
390    def _post_shutdown(self) -> None:
391        """
392        Called to cleanup the VM instance after the process has exited.
393        May also be called after a failed launch.
394        """
395        LOG.debug("Cleaning up after VM process")
396        try:
397            self._close_qmp_connection()
398        except Exception as err:  # pylint: disable=broad-except
399            LOG.warning(
400                "Exception closing QMP connection: %s",
401                str(err) if str(err) else type(err).__name__
402            )
403        finally:
404            assert self._qmp_connection is None
405
406        if self._sock_pair:
407            self._sock_pair[0].close()
408            self._sock_pair[1].close()
409            self._sock_pair = None
410
411        self._close_qemu_log_file()
412
413        self._load_io_log()
414
415        self._qemu_log_path = None
416
417        if self._temp_dir is not None:
418            shutil.rmtree(self._temp_dir)
419            self._temp_dir = None
420
421        while len(self._remove_files) > 0:
422            self._remove_if_exists(self._remove_files.pop())
423
424        exitcode = self.exitcode()
425        if (exitcode is not None and exitcode < 0
426                and not (self._user_killed and exitcode == -signal.SIGKILL)):
427            msg = 'qemu received signal %i; command: "%s"'
428            if self._qemu_full_args:
429                command = ' '.join(self._qemu_full_args)
430            else:
431                command = ''
432            LOG.warning(msg, -int(exitcode), command)
433
434        self._quit_issued = False
435        self._user_killed = False
436        self._launched = False
437
438    def launch(self) -> None:
439        """
440        Launch the VM and make sure we cleanup and expose the
441        command line/output in case of exception
442        """
443
444        if self._launched:
445            raise QEMUMachineError('VM already launched')
446
447        try:
448            self._launch()
449        except BaseException as exc:
450            # We may have launched the process but it may
451            # have exited before we could connect via QMP.
452            # Assume the VM didn't launch or is exiting.
453            # If we don't wait for the process, exitcode() may still be
454            # 'None' by the time control is ceded back to the caller.
455            if self._launched:
456                self.wait()
457            else:
458                self._post_shutdown()
459
460            if isinstance(exc, Exception):
461                raise VMLaunchFailure(
462                    exitcode=self.exitcode(),
463                    command=' '.join(self._qemu_full_args),
464                    output=self._iolog
465                ) from exc
466
467            # Don't wrap 'BaseException'; doing so would downgrade
468            # that exception. However, we still want to clean up.
469            raise
470
471    def _launch(self) -> None:
472        """
473        Launch the VM and establish a QMP connection
474        """
475        self._pre_launch()
476        LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
477
478        # Cleaning up of this subprocess is guaranteed by _do_shutdown.
479        # pylint: disable=consider-using-with
480        self._popen = subprocess.Popen(self._qemu_full_args,
481                                       stdin=subprocess.DEVNULL,
482                                       stdout=self._qemu_log_file,
483                                       stderr=subprocess.STDOUT,
484                                       shell=False,
485                                       close_fds=False)
486        self._launched = True
487        self._post_launch()
488
489    def _close_qmp_connection(self) -> None:
490        """
491        Close the underlying QMP connection, if any.
492
493        Dutifully report errors that occurred while closing, but assume
494        that any error encountered indicates an abnormal termination
495        process and not a failure to close.
496        """
497        if self._qmp_connection is None:
498            return
499
500        try:
501            self._qmp.close()
502        except EOFError:
503            # EOF can occur as an Exception here when using the Async
504            # QMP backend. It indicates that the server closed the
505            # stream. If we successfully issued 'quit' at any point,
506            # then this was expected. If the remote went away without
507            # our permission, it's worth reporting that as an abnormal
508            # shutdown case.
509            if not (self._user_killed or self._quit_issued):
510                raise
511        finally:
512            self._qmp_connection = None
513
514    def _early_cleanup(self) -> None:
515        """
516        Perform any cleanup that needs to happen before the VM exits.
517
518        This method may be called twice upon shutdown, once each by soft
519        and hard shutdown in failover scenarios.
520        """
521        # If we keep the console socket open, we may deadlock waiting
522        # for QEMU to exit, while QEMU is waiting for the socket to
523        # become writable.
524        if self._console_file is not None:
525            LOG.debug("Closing console file")
526            self._console_file.close()
527            self._console_file = None
528
529        if self._console_socket is not None:
530            LOG.debug("Closing console socket")
531            self._console_socket.close()
532            self._console_socket = None
533
534        if self._cons_sock_pair:
535            self._cons_sock_pair[0].close()
536            self._cons_sock_pair[1].close()
537            self._cons_sock_pair = None
538
539    def _hard_shutdown(self) -> None:
540        """
541        Perform early cleanup, kill the VM, and wait for it to terminate.
542
543        :raise subprocess.Timeout: When timeout is exceeds 60 seconds
544            waiting for the QEMU process to terminate.
545        """
546        LOG.debug("Performing hard shutdown")
547        self._early_cleanup()
548        self._subp.kill()
549        self._subp.wait(timeout=60)
550
551    def _soft_shutdown(self, timeout: Optional[int]) -> None:
552        """
553        Perform early cleanup, attempt to gracefully shut down the VM, and wait
554        for it to terminate.
555
556        :param timeout: Timeout in seconds for graceful shutdown.
557                        A value of None is an infinite wait.
558
559        :raise ConnectionReset: On QMP communication errors
560        :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
561            the QEMU process to terminate.
562        """
563        LOG.debug("Attempting graceful termination")
564
565        self._early_cleanup()
566
567        if self._quit_issued:
568            LOG.debug(
569                "Anticipating QEMU termination due to prior 'quit' command, "
570                "or explicit call to wait()"
571            )
572        else:
573            LOG.debug("Politely asking QEMU to terminate")
574
575        if self._qmp_connection:
576            try:
577                if not self._quit_issued:
578                    # May raise ExecInterruptedError or StateError if the
579                    # connection dies or has *already* died.
580                    self.qmp('quit')
581            finally:
582                # Regardless, we want to quiesce the connection.
583                self._close_qmp_connection()
584        elif not self._quit_issued:
585            LOG.debug(
586                "Not anticipating QEMU quit and no QMP connection present, "
587                "issuing SIGTERM"
588            )
589            self._subp.terminate()
590
591        # May raise subprocess.TimeoutExpired
592        LOG.debug(
593            "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate",
594            timeout, self._subp.pid
595        )
596        self._subp.wait(timeout=timeout)
597
598    def _do_shutdown(self, timeout: Optional[int]) -> None:
599        """
600        Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
601
602        :param timeout: Timeout in seconds for graceful shutdown.
603                        A value of None is an infinite wait.
604
605        :raise AbnormalShutdown: When the VM could not be shut down gracefully.
606            The inner exception will likely be ConnectionReset or
607            subprocess.TimeoutExpired. In rare cases, non-graceful termination
608            may result in its own exceptions, likely subprocess.TimeoutExpired.
609        """
610        try:
611            self._soft_shutdown(timeout)
612        except Exception as exc:
613            if isinstance(exc, subprocess.TimeoutExpired):
614                LOG.debug("Timed out waiting for QEMU process to exit")
615            LOG.debug("Graceful shutdown failed", exc_info=True)
616            LOG.debug("Falling back to hard shutdown")
617            self._hard_shutdown()
618            raise AbnormalShutdown("Could not perform graceful shutdown") \
619                from exc
620
621    def shutdown(self,
622                 hard: bool = False,
623                 timeout: Optional[int] = 30) -> None:
624        """
625        Terminate the VM (gracefully if possible) and perform cleanup.
626        Cleanup will always be performed.
627
628        If the VM has not yet been launched, or shutdown(), wait(), or kill()
629        have already been called, this method does nothing.
630
631        :param hard: When true, do not attempt graceful shutdown, and
632                     suppress the SIGKILL warning log message.
633        :param timeout: Optional timeout in seconds for graceful shutdown.
634                        Default 30 seconds, A `None` value is an infinite wait.
635        """
636        if not self._launched:
637            return
638
639        LOG.debug("Shutting down VM appliance; timeout=%s", timeout)
640        if hard:
641            LOG.debug("Caller requests immediate termination of QEMU process.")
642
643        try:
644            if hard:
645                self._user_killed = True
646                self._hard_shutdown()
647            else:
648                self._do_shutdown(timeout)
649        finally:
650            self._post_shutdown()
651
652    def kill(self) -> None:
653        """
654        Terminate the VM forcefully, wait for it to exit, and perform cleanup.
655        """
656        self.shutdown(hard=True)
657
658    def wait(self, timeout: Optional[int] = 30) -> None:
659        """
660        Wait for the VM to power off and perform post-shutdown cleanup.
661
662        :param timeout: Optional timeout in seconds. Default 30 seconds.
663                        A value of `None` is an infinite wait.
664        """
665        self._quit_issued = True
666        self.shutdown(timeout=timeout)
667
668    def set_qmp_monitor(self, enabled: bool = True) -> None:
669        """
670        Set the QMP monitor.
671
672        @param enabled: if False, qmp monitor options will be removed from
673                        the base arguments of the resulting QEMU command
674                        line. Default is True.
675
676        .. note:: Call this function before launch().
677        """
678        self._qmp_set = enabled
679
680    @property
681    def _qmp(self) -> QEMUMonitorProtocol:
682        if self._qmp_connection is None:
683            raise QEMUMachineError("Attempt to access QMP with no connection")
684        return self._qmp_connection
685
686    @classmethod
687    def _qmp_args(cls, conv_keys: bool,
688                  args: Dict[str, Any]) -> Dict[str, object]:
689        if conv_keys:
690            return {k.replace('_', '-'): v for k, v in args.items()}
691
692        return args
693
694    def qmp(self, cmd: str,
695            args_dict: Optional[Dict[str, object]] = None,
696            conv_keys: Optional[bool] = None,
697            **args: Any) -> QMPMessage:
698        """
699        Invoke a QMP command and return the response dict
700        """
701        if args_dict is not None:
702            assert not args
703            assert conv_keys is None
704            args = args_dict
705            conv_keys = False
706
707        if conv_keys is None:
708            conv_keys = True
709
710        qmp_args = self._qmp_args(conv_keys, args)
711        ret = self._qmp.cmd_raw(cmd, args=qmp_args)
712        if cmd == 'quit' and 'error' not in ret and 'return' in ret:
713            self._quit_issued = True
714        return ret
715
716    def cmd(self, cmd: str,
717            args_dict: Optional[Dict[str, object]] = None,
718            conv_keys: Optional[bool] = None,
719            **args: Any) -> QMPReturnValue:
720        """
721        Invoke a QMP command.
722        On success return the response dict.
723        On failure raise an exception.
724        """
725        if args_dict is not None:
726            assert not args
727            assert conv_keys is None
728            args = args_dict
729            conv_keys = False
730
731        if conv_keys is None:
732            conv_keys = True
733
734        qmp_args = self._qmp_args(conv_keys, args)
735        ret = self._qmp.cmd(cmd, **qmp_args)
736        if cmd == 'quit':
737            self._quit_issued = True
738        return ret
739
740    def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
741        """
742        Poll for one queued QMP events and return it
743        """
744        if self._events:
745            return self._events.pop(0)
746        return self._qmp.pull_event(wait=wait)
747
748    def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
749        """
750        Poll for queued QMP events and return a list of dicts
751        """
752        events = self._qmp.get_events(wait=wait)
753        events.extend(self._events)
754        del self._events[:]
755        return events
756
757    @staticmethod
758    def event_match(event: Any, match: Optional[Any]) -> bool:
759        """
760        Check if an event matches optional match criteria.
761
762        The match criteria takes the form of a matching subdict. The event is
763        checked to be a superset of the subdict, recursively, with matching
764        values whenever the subdict values are not None.
765
766        This has a limitation that you cannot explicitly check for None values.
767
768        Examples, with the subdict queries on the left:
769         - None matches any object.
770         - {"foo": None} matches {"foo": {"bar": 1}}
771         - {"foo": None} matches {"foo": 5}
772         - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
773         - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
774        """
775        if match is None:
776            return True
777
778        try:
779            for key in match:
780                if key in event:
781                    if not QEMUMachine.event_match(event[key], match[key]):
782                        return False
783                else:
784                    return False
785            return True
786        except TypeError:
787            # either match or event wasn't iterable (not a dict)
788            return bool(match == event)
789
790    def event_wait(self, name: str,
791                   timeout: float = 60.0,
792                   match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
793        """
794        event_wait waits for and returns a named event from QMP with a timeout.
795
796        name: The event to wait for.
797        timeout: QEMUMonitorProtocol.pull_event timeout parameter.
798        match: Optional match criteria. See event_match for details.
799        """
800        return self.events_wait([(name, match)], timeout)
801
802    def events_wait(self,
803                    events: Sequence[Tuple[str, Any]],
804                    timeout: float = 60.0) -> Optional[QMPMessage]:
805        """
806        events_wait waits for and returns a single named event from QMP.
807        In the case of multiple qualifying events, this function returns the
808        first one.
809
810        :param events: A sequence of (name, match_criteria) tuples.
811                       The match criteria are optional and may be None.
812                       See event_match for details.
813        :param timeout: Optional timeout, in seconds.
814                        See QEMUMonitorProtocol.pull_event.
815
816        :raise asyncio.TimeoutError:
817            If timeout was non-zero and no matching events were found.
818
819        :return: A QMP event matching the filter criteria.
820                 If timeout was 0 and no event matched, None.
821        """
822        def _match(event: QMPMessage) -> bool:
823            for name, match in events:
824                if event['event'] == name and self.event_match(event, match):
825                    return True
826            return False
827
828        event: Optional[QMPMessage]
829
830        # Search cached events
831        for event in self._events:
832            if _match(event):
833                self._events.remove(event)
834                return event
835
836        # Poll for new events
837        while True:
838            event = self._qmp.pull_event(wait=timeout)
839            if event is None:
840                # NB: None is only returned when timeout is false-ish.
841                # Timeouts raise asyncio.TimeoutError instead!
842                break
843            if _match(event):
844                return event
845            self._events.append(event)
846
847        return None
848
849    def get_log(self) -> Optional[str]:
850        """
851        After self.shutdown or failed qemu execution, this returns the output
852        of the qemu process.
853        """
854        return self._iolog
855
856    def add_args(self, *args: str) -> None:
857        """
858        Adds to the list of extra arguments to be given to the QEMU binary
859        """
860        self._args.extend(args)
861
862    def set_machine(self, machine_type: str) -> None:
863        """
864        Sets the machine type
865
866        If set, the machine type will be added to the base arguments
867        of the resulting QEMU command line.
868        """
869        self._machine = machine_type
870
871    def set_console(self,
872                    device_type: Optional[str] = None,
873                    console_index: int = 0) -> None:
874        """
875        Sets the device type for a console device
876
877        If set, the console device and a backing character device will
878        be added to the base arguments of the resulting QEMU command
879        line.
880
881        This is a convenience method that will either use the provided
882        device type, or default to a "-serial chardev:console" command
883        line argument.
884
885        The actual setting of command line arguments will be be done at
886        machine launch time, as it depends on the temporary directory
887        to be created.
888
889        @param device_type: the device type, such as "isa-serial".  If
890                            None is given (the default value) a "-serial
891                            chardev:console" command line argument will
892                            be used instead, resorting to the machine's
893                            default device type.
894        @param console_index: the index of the console device to use.
895                              If not zero, the command line will create
896                              'index - 1' consoles and connect them to
897                              the 'null' backing character device.
898        """
899        self._console_set = True
900        self._console_device_type = device_type
901        self._console_index = console_index
902
903    @property
904    def console_socket(self) -> socket.socket:
905        """
906        Returns a socket connected to the console
907        """
908        if self._console_socket is None:
909            LOG.debug("Opening console socket")
910            if not self._console_set:
911                raise QEMUMachineError(
912                    "Attempt to access console socket with no connection")
913            assert self._cons_sock_pair is not None
914            # os.dup() is used here for sock_fd because otherwise we'd
915            # have two rich python socket objects that would each try to
916            # close the same underlying fd when either one gets garbage
917            # collected.
918            self._console_socket = console_socket.ConsoleSocket(
919                sock_fd=os.dup(self._cons_sock_pair[1].fileno()),
920                file=self._console_log_path,
921                drain=self._drain_console)
922            self._cons_sock_pair[1].close()
923        return self._console_socket
924
925    @property
926    def console_file(self) -> socket.SocketIO:
927        """
928        Returns a file associated with the console socket
929        """
930        if self._console_file is None:
931            LOG.debug("Opening console file")
932            self._console_file = self.console_socket.makefile(mode='rb',
933                                                              buffering=0,
934                                                              encoding='utf-8')
935        return self._console_file
936
937    @property
938    def temp_dir(self) -> str:
939        """
940        Returns a temporary directory to be used for this machine
941        """
942        if self._temp_dir is None:
943            self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
944                                              dir=self._base_temp_dir)
945        return self._temp_dir
946
947    @property
948    def log_dir(self) -> str:
949        """
950        Returns a directory to be used for writing logs
951        """
952        if self._log_dir is None:
953            return self.temp_dir
954        return self._log_dir
955