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