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