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