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