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