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