cachepc-qemu

Fork of AMDESE/qemu with changes for cachepc side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-qemu
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

machine.py (26786B)


      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 (  # pylint: disable=import-error
     44    QEMUMonitorProtocol,
     45    QMPMessage,
     46    QMPReturnValue,
     47    SocketAddrT,
     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 AbnormalShutdown(QEMUMachineError):
     73    """
     74    Exception raised when a graceful shutdown was requested, but not performed.
     75    """
     76
     77
     78_T = TypeVar('_T', bound='QEMUMachine')
     79
     80
     81class QEMUMachine:
     82    """
     83    A QEMU VM.
     84
     85    Use this object as a context manager to ensure
     86    the QEMU process terminates::
     87
     88        with VM(binary) as vm:
     89            ...
     90        # vm is guaranteed to be shut down here
     91    """
     92    # pylint: disable=too-many-instance-attributes, too-many-public-methods
     93
     94    def __init__(self,
     95                 binary: str,
     96                 args: Sequence[str] = (),
     97                 wrapper: Sequence[str] = (),
     98                 name: Optional[str] = None,
     99                 base_temp_dir: str = "/var/tmp",
    100                 monitor_address: Optional[SocketAddrT] = None,
    101                 sock_dir: Optional[str] = None,
    102                 drain_console: bool = False,
    103                 console_log: Optional[str] = None,
    104                 log_dir: Optional[str] = None,
    105                 qmp_timer: Optional[float] = None):
    106        '''
    107        Initialize a QEMUMachine
    108
    109        @param binary: path to the qemu binary
    110        @param args: list of extra arguments
    111        @param wrapper: list of arguments used as prefix to qemu binary
    112        @param name: prefix for socket and log file names (default: qemu-PID)
    113        @param base_temp_dir: default location where temp files are created
    114        @param monitor_address: address for QMP monitor
    115        @param sock_dir: where to create socket (defaults to base_temp_dir)
    116        @param drain_console: (optional) True to drain console socket to buffer
    117        @param console_log: (optional) path to console log file
    118        @param log_dir: where to create and keep log files
    119        @param qmp_timer: (optional) default QMP socket timeout
    120        @note: Qemu process is not started until launch() is used.
    121        '''
    122        # pylint: disable=too-many-arguments
    123
    124        # Direct user configuration
    125
    126        self._binary = binary
    127        self._args = list(args)
    128        self._wrapper = wrapper
    129        self._qmp_timer = qmp_timer
    130
    131        self._name = name or "qemu-%d" % os.getpid()
    132        self._base_temp_dir = base_temp_dir
    133        self._sock_dir = sock_dir or self._base_temp_dir
    134        self._log_dir = log_dir
    135
    136        if monitor_address is not None:
    137            self._monitor_address = monitor_address
    138            self._remove_monitor_sockfile = False
    139        else:
    140            self._monitor_address = os.path.join(
    141                self._sock_dir, f"{self._name}-monitor.sock"
    142            )
    143            self._remove_monitor_sockfile = True
    144
    145        self._console_log_path = console_log
    146        if self._console_log_path:
    147            # In order to log the console, buffering needs to be enabled.
    148            self._drain_console = True
    149        else:
    150            self._drain_console = drain_console
    151
    152        # Runstate
    153        self._qemu_log_path: Optional[str] = None
    154        self._qemu_log_file: Optional[BinaryIO] = None
    155        self._popen: Optional['subprocess.Popen[bytes]'] = None
    156        self._events: List[QMPMessage] = []
    157        self._iolog: Optional[str] = None
    158        self._qmp_set = True   # Enable QMP monitor by default.
    159        self._qmp_connection: Optional[QEMUMonitorProtocol] = None
    160        self._qemu_full_args: Tuple[str, ...] = ()
    161        self._temp_dir: Optional[str] = None
    162        self._launched = False
    163        self._machine: Optional[str] = None
    164        self._console_index = 0
    165        self._console_set = False
    166        self._console_device_type: Optional[str] = None
    167        self._console_address = os.path.join(
    168            self._sock_dir, f"{self._name}-console.sock"
    169        )
    170        self._console_socket: Optional[socket.socket] = None
    171        self._remove_files: List[str] = []
    172        self._user_killed = False
    173
    174    def __enter__(self: _T) -> _T:
    175        return self
    176
    177    def __exit__(self,
    178                 exc_type: Optional[Type[BaseException]],
    179                 exc_val: Optional[BaseException],
    180                 exc_tb: Optional[TracebackType]) -> None:
    181        self.shutdown()
    182
    183    def add_monitor_null(self) -> None:
    184        """
    185        This can be used to add an unused monitor instance.
    186        """
    187        self._args.append('-monitor')
    188        self._args.append('null')
    189
    190    def add_fd(self: _T, fd: int, fdset: int,
    191               opaque: str, opts: str = '') -> _T:
    192        """
    193        Pass a file descriptor to the VM
    194        """
    195        options = ['fd=%d' % fd,
    196                   'set=%d' % fdset,
    197                   'opaque=%s' % opaque]
    198        if opts:
    199            options.append(opts)
    200
    201        # This did not exist before 3.4, but since then it is
    202        # mandatory for our purpose
    203        if hasattr(os, 'set_inheritable'):
    204            os.set_inheritable(fd, True)
    205
    206        self._args.append('-add-fd')
    207        self._args.append(','.join(options))
    208        return self
    209
    210    def send_fd_scm(self, fd: Optional[int] = None,
    211                    file_path: Optional[str] = None) -> int:
    212        """
    213        Send an fd or file_path to the remote via SCM_RIGHTS.
    214
    215        Exactly one of fd and file_path must be given.  If it is
    216        file_path, the file will be opened read-only and the new file
    217        descriptor will be sent to the remote.
    218        """
    219        if file_path is not None:
    220            assert fd is None
    221            with open(file_path, "rb") as passfile:
    222                fd = passfile.fileno()
    223                self._qmp.send_fd_scm(fd)
    224        else:
    225            assert fd is not None
    226            self._qmp.send_fd_scm(fd)
    227
    228        return 0
    229
    230    @staticmethod
    231    def _remove_if_exists(path: str) -> None:
    232        """
    233        Remove file object at path if it exists
    234        """
    235        try:
    236            os.remove(path)
    237        except OSError as exception:
    238            if exception.errno == errno.ENOENT:
    239                return
    240            raise
    241
    242    def is_running(self) -> bool:
    243        """Returns true if the VM is running."""
    244        return self._popen is not None and self._popen.poll() is None
    245
    246    @property
    247    def _subp(self) -> 'subprocess.Popen[bytes]':
    248        if self._popen is None:
    249            raise QEMUMachineError('Subprocess pipe not present')
    250        return self._popen
    251
    252    def exitcode(self) -> Optional[int]:
    253        """Returns the exit code if possible, or None."""
    254        if self._popen is None:
    255            return None
    256        return self._popen.poll()
    257
    258    def get_pid(self) -> Optional[int]:
    259        """Returns the PID of the running process, or None."""
    260        if not self.is_running():
    261            return None
    262        return self._subp.pid
    263
    264    def _load_io_log(self) -> None:
    265        # Assume that the output encoding of QEMU's terminal output is
    266        # defined by our locale. If indeterminate, allow open() to fall
    267        # back to the platform default.
    268        _, encoding = locale.getlocale()
    269        if self._qemu_log_path is not None:
    270            with open(self._qemu_log_path, "r", encoding=encoding) as iolog:
    271                self._iolog = iolog.read()
    272
    273    @property
    274    def _base_args(self) -> List[str]:
    275        args = ['-display', 'none', '-vga', 'none']
    276
    277        if self._qmp_set:
    278            if isinstance(self._monitor_address, tuple):
    279                moncdev = "socket,id=mon,host={},port={}".format(
    280                    *self._monitor_address
    281                )
    282            else:
    283                moncdev = f"socket,id=mon,path={self._monitor_address}"
    284            args.extend(['-chardev', moncdev, '-mon',
    285                         'chardev=mon,mode=control'])
    286
    287        if self._machine is not None:
    288            args.extend(['-machine', self._machine])
    289        for _ in range(self._console_index):
    290            args.extend(['-serial', 'null'])
    291        if self._console_set:
    292            chardev = ('socket,id=console,path=%s,server=on,wait=off' %
    293                       self._console_address)
    294            args.extend(['-chardev', chardev])
    295            if self._console_device_type is None:
    296                args.extend(['-serial', 'chardev:console'])
    297            else:
    298                device = '%s,chardev=console' % self._console_device_type
    299                args.extend(['-device', device])
    300        return args
    301
    302    @property
    303    def args(self) -> List[str]:
    304        """Returns the list of arguments given to the QEMU binary."""
    305        return self._args
    306
    307    def _pre_launch(self) -> None:
    308        if self._console_set:
    309            self._remove_files.append(self._console_address)
    310
    311        if self._qmp_set:
    312            if self._remove_monitor_sockfile:
    313                assert isinstance(self._monitor_address, str)
    314                self._remove_files.append(self._monitor_address)
    315            self._qmp_connection = QEMUMonitorProtocol(
    316                self._monitor_address,
    317                server=True,
    318                nickname=self._name
    319            )
    320
    321        # NOTE: Make sure any opened resources are *definitely* freed in
    322        # _post_shutdown()!
    323        # pylint: disable=consider-using-with
    324        self._qemu_log_path = os.path.join(self.log_dir, self._name + ".log")
    325        self._qemu_log_file = open(self._qemu_log_path, 'wb')
    326
    327    def _post_launch(self) -> None:
    328        if self._qmp_connection:
    329            self._qmp.accept(self._qmp_timer)
    330
    331    def _close_qemu_log_file(self) -> None:
    332        if self._qemu_log_file is not None:
    333            self._qemu_log_file.close()
    334            self._qemu_log_file = None
    335
    336    def _post_shutdown(self) -> None:
    337        """
    338        Called to cleanup the VM instance after the process has exited.
    339        May also be called after a failed launch.
    340        """
    341        # Comprehensive reset for the failed launch case:
    342        self._early_cleanup()
    343
    344        if self._qmp_connection:
    345            self._qmp.close()
    346            self._qmp_connection = None
    347
    348        self._close_qemu_log_file()
    349
    350        self._load_io_log()
    351
    352        self._qemu_log_path = None
    353
    354        if self._temp_dir is not None:
    355            shutil.rmtree(self._temp_dir)
    356            self._temp_dir = None
    357
    358        while len(self._remove_files) > 0:
    359            self._remove_if_exists(self._remove_files.pop())
    360
    361        exitcode = self.exitcode()
    362        if (exitcode is not None and exitcode < 0
    363                and not (self._user_killed and exitcode == -signal.SIGKILL)):
    364            msg = 'qemu received signal %i; command: "%s"'
    365            if self._qemu_full_args:
    366                command = ' '.join(self._qemu_full_args)
    367            else:
    368                command = ''
    369            LOG.warning(msg, -int(exitcode), command)
    370
    371        self._user_killed = False
    372        self._launched = False
    373
    374    def launch(self) -> None:
    375        """
    376        Launch the VM and make sure we cleanup and expose the
    377        command line/output in case of exception
    378        """
    379
    380        if self._launched:
    381            raise QEMUMachineError('VM already launched')
    382
    383        self._iolog = None
    384        self._qemu_full_args = ()
    385        try:
    386            self._launch()
    387            self._launched = True
    388        except:
    389            self._post_shutdown()
    390
    391            LOG.debug('Error launching VM')
    392            if self._qemu_full_args:
    393                LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
    394            if self._iolog:
    395                LOG.debug('Output: %r', self._iolog)
    396            raise
    397
    398    def _launch(self) -> None:
    399        """
    400        Launch the VM and establish a QMP connection
    401        """
    402        self._pre_launch()
    403        self._qemu_full_args = tuple(
    404            chain(self._wrapper,
    405                  [self._binary],
    406                  self._base_args,
    407                  self._args)
    408        )
    409        LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
    410
    411        # Cleaning up of this subprocess is guaranteed by _do_shutdown.
    412        # pylint: disable=consider-using-with
    413        self._popen = subprocess.Popen(self._qemu_full_args,
    414                                       stdin=subprocess.DEVNULL,
    415                                       stdout=self._qemu_log_file,
    416                                       stderr=subprocess.STDOUT,
    417                                       shell=False,
    418                                       close_fds=False)
    419        self._post_launch()
    420
    421    def _early_cleanup(self) -> None:
    422        """
    423        Perform any cleanup that needs to happen before the VM exits.
    424
    425        May be invoked by both soft and hard shutdown in failover scenarios.
    426        Called additionally by _post_shutdown for comprehensive cleanup.
    427        """
    428        # If we keep the console socket open, we may deadlock waiting
    429        # for QEMU to exit, while QEMU is waiting for the socket to
    430        # become writeable.
    431        if self._console_socket is not None:
    432            self._console_socket.close()
    433            self._console_socket = None
    434
    435    def _hard_shutdown(self) -> None:
    436        """
    437        Perform early cleanup, kill the VM, and wait for it to terminate.
    438
    439        :raise subprocess.Timeout: When timeout is exceeds 60 seconds
    440            waiting for the QEMU process to terminate.
    441        """
    442        self._early_cleanup()
    443        self._subp.kill()
    444        self._subp.wait(timeout=60)
    445
    446    def _soft_shutdown(self, timeout: Optional[int],
    447                       has_quit: bool = False) -> None:
    448        """
    449        Perform early cleanup, attempt to gracefully shut down the VM, and wait
    450        for it to terminate.
    451
    452        :param timeout: Timeout in seconds for graceful shutdown.
    453                        A value of None is an infinite wait.
    454        :param has_quit: When True, don't attempt to issue 'quit' QMP command
    455
    456        :raise ConnectionReset: On QMP communication errors
    457        :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
    458            the QEMU process to terminate.
    459        """
    460        self._early_cleanup()
    461
    462        if self._qmp_connection:
    463            if not has_quit:
    464                # Might raise ConnectionReset
    465                self._qmp.cmd('quit')
    466
    467        # May raise subprocess.TimeoutExpired
    468        self._subp.wait(timeout=timeout)
    469
    470    def _do_shutdown(self, timeout: Optional[int],
    471                     has_quit: bool = False) -> None:
    472        """
    473        Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
    474
    475        :param timeout: Timeout in seconds for graceful shutdown.
    476                        A value of None is an infinite wait.
    477        :param has_quit: When True, don't attempt to issue 'quit' QMP command
    478
    479        :raise AbnormalShutdown: When the VM could not be shut down gracefully.
    480            The inner exception will likely be ConnectionReset or
    481            subprocess.TimeoutExpired. In rare cases, non-graceful termination
    482            may result in its own exceptions, likely subprocess.TimeoutExpired.
    483        """
    484        try:
    485            self._soft_shutdown(timeout, has_quit)
    486        except Exception as exc:
    487            self._hard_shutdown()
    488            raise AbnormalShutdown("Could not perform graceful shutdown") \
    489                from exc
    490
    491    def shutdown(self, has_quit: bool = False,
    492                 hard: bool = False,
    493                 timeout: Optional[int] = 30) -> None:
    494        """
    495        Terminate the VM (gracefully if possible) and perform cleanup.
    496        Cleanup will always be performed.
    497
    498        If the VM has not yet been launched, or shutdown(), wait(), or kill()
    499        have already been called, this method does nothing.
    500
    501        :param has_quit: When true, do not attempt to issue 'quit' QMP command.
    502        :param hard: When true, do not attempt graceful shutdown, and
    503                     suppress the SIGKILL warning log message.
    504        :param timeout: Optional timeout in seconds for graceful shutdown.
    505                        Default 30 seconds, A `None` value is an infinite wait.
    506        """
    507        if not self._launched:
    508            return
    509
    510        try:
    511            if hard:
    512                self._user_killed = True
    513                self._hard_shutdown()
    514            else:
    515                self._do_shutdown(timeout, has_quit)
    516        finally:
    517            self._post_shutdown()
    518
    519    def kill(self) -> None:
    520        """
    521        Terminate the VM forcefully, wait for it to exit, and perform cleanup.
    522        """
    523        self.shutdown(hard=True)
    524
    525    def wait(self, timeout: Optional[int] = 30) -> None:
    526        """
    527        Wait for the VM to power off and perform post-shutdown cleanup.
    528
    529        :param timeout: Optional timeout in seconds. Default 30 seconds.
    530                        A value of `None` is an infinite wait.
    531        """
    532        self.shutdown(has_quit=True, timeout=timeout)
    533
    534    def set_qmp_monitor(self, enabled: bool = True) -> None:
    535        """
    536        Set the QMP monitor.
    537
    538        @param enabled: if False, qmp monitor options will be removed from
    539                        the base arguments of the resulting QEMU command
    540                        line. Default is True.
    541
    542        .. note:: Call this function before launch().
    543        """
    544        self._qmp_set = enabled
    545
    546    @property
    547    def _qmp(self) -> QEMUMonitorProtocol:
    548        if self._qmp_connection is None:
    549            raise QEMUMachineError("Attempt to access QMP with no connection")
    550        return self._qmp_connection
    551
    552    @classmethod
    553    def _qmp_args(cls, conv_keys: bool,
    554                  args: Dict[str, Any]) -> Dict[str, object]:
    555        if conv_keys:
    556            return {k.replace('_', '-'): v for k, v in args.items()}
    557
    558        return args
    559
    560    def qmp(self, cmd: str,
    561            args_dict: Optional[Dict[str, object]] = None,
    562            conv_keys: Optional[bool] = None,
    563            **args: Any) -> QMPMessage:
    564        """
    565        Invoke a QMP command and return the response dict
    566        """
    567        if args_dict is not None:
    568            assert not args
    569            assert conv_keys is None
    570            args = args_dict
    571            conv_keys = False
    572
    573        if conv_keys is None:
    574            conv_keys = True
    575
    576        qmp_args = self._qmp_args(conv_keys, args)
    577        return self._qmp.cmd(cmd, args=qmp_args)
    578
    579    def command(self, cmd: str,
    580                conv_keys: bool = True,
    581                **args: Any) -> QMPReturnValue:
    582        """
    583        Invoke a QMP command.
    584        On success return the response dict.
    585        On failure raise an exception.
    586        """
    587        qmp_args = self._qmp_args(conv_keys, args)
    588        return self._qmp.command(cmd, **qmp_args)
    589
    590    def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
    591        """
    592        Poll for one queued QMP events and return it
    593        """
    594        if self._events:
    595            return self._events.pop(0)
    596        return self._qmp.pull_event(wait=wait)
    597
    598    def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
    599        """
    600        Poll for queued QMP events and return a list of dicts
    601        """
    602        events = self._qmp.get_events(wait=wait)
    603        events.extend(self._events)
    604        del self._events[:]
    605        return events
    606
    607    @staticmethod
    608    def event_match(event: Any, match: Optional[Any]) -> bool:
    609        """
    610        Check if an event matches optional match criteria.
    611
    612        The match criteria takes the form of a matching subdict. The event is
    613        checked to be a superset of the subdict, recursively, with matching
    614        values whenever the subdict values are not None.
    615
    616        This has a limitation that you cannot explicitly check for None values.
    617
    618        Examples, with the subdict queries on the left:
    619         - None matches any object.
    620         - {"foo": None} matches {"foo": {"bar": 1}}
    621         - {"foo": None} matches {"foo": 5}
    622         - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
    623         - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
    624        """
    625        if match is None:
    626            return True
    627
    628        try:
    629            for key in match:
    630                if key in event:
    631                    if not QEMUMachine.event_match(event[key], match[key]):
    632                        return False
    633                else:
    634                    return False
    635            return True
    636        except TypeError:
    637            # either match or event wasn't iterable (not a dict)
    638            return bool(match == event)
    639
    640    def event_wait(self, name: str,
    641                   timeout: float = 60.0,
    642                   match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
    643        """
    644        event_wait waits for and returns a named event from QMP with a timeout.
    645
    646        name: The event to wait for.
    647        timeout: QEMUMonitorProtocol.pull_event timeout parameter.
    648        match: Optional match criteria. See event_match for details.
    649        """
    650        return self.events_wait([(name, match)], timeout)
    651
    652    def events_wait(self,
    653                    events: Sequence[Tuple[str, Any]],
    654                    timeout: float = 60.0) -> Optional[QMPMessage]:
    655        """
    656        events_wait waits for and returns a single named event from QMP.
    657        In the case of multiple qualifying events, this function returns the
    658        first one.
    659
    660        :param events: A sequence of (name, match_criteria) tuples.
    661                       The match criteria are optional and may be None.
    662                       See event_match for details.
    663        :param timeout: Optional timeout, in seconds.
    664                        See QEMUMonitorProtocol.pull_event.
    665
    666        :raise QMPTimeoutError: If timeout was non-zero and no matching events
    667                                were found.
    668        :return: A QMP event matching the filter criteria.
    669                 If timeout was 0 and no event matched, None.
    670        """
    671        def _match(event: QMPMessage) -> bool:
    672            for name, match in events:
    673                if event['event'] == name and self.event_match(event, match):
    674                    return True
    675            return False
    676
    677        event: Optional[QMPMessage]
    678
    679        # Search cached events
    680        for event in self._events:
    681            if _match(event):
    682                self._events.remove(event)
    683                return event
    684
    685        # Poll for new events
    686        while True:
    687            event = self._qmp.pull_event(wait=timeout)
    688            if event is None:
    689                # NB: None is only returned when timeout is false-ish.
    690                # Timeouts raise QMPTimeoutError instead!
    691                break
    692            if _match(event):
    693                return event
    694            self._events.append(event)
    695
    696        return None
    697
    698    def get_log(self) -> Optional[str]:
    699        """
    700        After self.shutdown or failed qemu execution, this returns the output
    701        of the qemu process.
    702        """
    703        return self._iolog
    704
    705    def add_args(self, *args: str) -> None:
    706        """
    707        Adds to the list of extra arguments to be given to the QEMU binary
    708        """
    709        self._args.extend(args)
    710
    711    def set_machine(self, machine_type: str) -> None:
    712        """
    713        Sets the machine type
    714
    715        If set, the machine type will be added to the base arguments
    716        of the resulting QEMU command line.
    717        """
    718        self._machine = machine_type
    719
    720    def set_console(self,
    721                    device_type: Optional[str] = None,
    722                    console_index: int = 0) -> None:
    723        """
    724        Sets the device type for a console device
    725
    726        If set, the console device and a backing character device will
    727        be added to the base arguments of the resulting QEMU command
    728        line.
    729
    730        This is a convenience method that will either use the provided
    731        device type, or default to a "-serial chardev:console" command
    732        line argument.
    733
    734        The actual setting of command line arguments will be be done at
    735        machine launch time, as it depends on the temporary directory
    736        to be created.
    737
    738        @param device_type: the device type, such as "isa-serial".  If
    739                            None is given (the default value) a "-serial
    740                            chardev:console" command line argument will
    741                            be used instead, resorting to the machine's
    742                            default device type.
    743        @param console_index: the index of the console device to use.
    744                              If not zero, the command line will create
    745                              'index - 1' consoles and connect them to
    746                              the 'null' backing character device.
    747        """
    748        self._console_set = True
    749        self._console_device_type = device_type
    750        self._console_index = console_index
    751
    752    @property
    753    def console_socket(self) -> socket.socket:
    754        """
    755        Returns a socket connected to the console
    756        """
    757        if self._console_socket is None:
    758            self._console_socket = console_socket.ConsoleSocket(
    759                self._console_address,
    760                file=self._console_log_path,
    761                drain=self._drain_console)
    762        return self._console_socket
    763
    764    @property
    765    def temp_dir(self) -> str:
    766        """
    767        Returns a temporary directory to be used for this machine
    768        """
    769        if self._temp_dir is None:
    770            self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
    771                                              dir=self._base_temp_dir)
    772        return self._temp_dir
    773
    774    @property
    775    def log_dir(self) -> str:
    776        """
    777        Returns a directory to be used for writing logs
    778        """
    779        if self._log_dir is None:
    780            return self.temp_dir
    781        return self._log_dir