]> Git Repo - qemu.git/blob - python/qemu/machine/machine.py
python/machine: handle "fast" QEMU terminations
[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         try:
353             self._close_qmp_connection()
354         except Exception as err:  # pylint: disable=broad-except
355             LOG.warning(
356                 "Exception closing QMP connection: %s",
357                 str(err) if str(err) else type(err).__name__
358             )
359         finally:
360             assert self._qmp_connection is None
361
362         self._close_qemu_log_file()
363
364         self._load_io_log()
365
366         self._qemu_log_path = None
367
368         if self._temp_dir is not None:
369             shutil.rmtree(self._temp_dir)
370             self._temp_dir = None
371
372         while len(self._remove_files) > 0:
373             self._remove_if_exists(self._remove_files.pop())
374
375         exitcode = self.exitcode()
376         if (exitcode is not None and exitcode < 0
377                 and not (self._user_killed and exitcode == -signal.SIGKILL)):
378             msg = 'qemu received signal %i; command: "%s"'
379             if self._qemu_full_args:
380                 command = ' '.join(self._qemu_full_args)
381             else:
382                 command = ''
383             LOG.warning(msg, -int(exitcode), command)
384
385         self._quit_issued = False
386         self._user_killed = False
387         self._launched = False
388
389     def launch(self) -> None:
390         """
391         Launch the VM and make sure we cleanup and expose the
392         command line/output in case of exception
393         """
394
395         if self._launched:
396             raise QEMUMachineError('VM already launched')
397
398         try:
399             self._launch()
400         except:
401             # We may have launched the process but it may
402             # have exited before we could connect via QMP.
403             # Assume the VM didn't launch or is exiting.
404             # If we don't wait for the process, exitcode() may still be
405             # 'None' by the time control is ceded back to the caller.
406             if self._launched:
407                 self.wait()
408             else:
409                 self._post_shutdown()
410
411             LOG.debug('Error launching VM')
412             if self._qemu_full_args:
413                 LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
414             if self._iolog:
415                 LOG.debug('Output: %r', self._iolog)
416             raise
417
418     def _launch(self) -> None:
419         """
420         Launch the VM and establish a QMP connection
421         """
422         self._pre_launch()
423         LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
424
425         # Cleaning up of this subprocess is guaranteed by _do_shutdown.
426         # pylint: disable=consider-using-with
427         self._popen = subprocess.Popen(self._qemu_full_args,
428                                        stdin=subprocess.DEVNULL,
429                                        stdout=self._qemu_log_file,
430                                        stderr=subprocess.STDOUT,
431                                        shell=False,
432                                        close_fds=False)
433         self._launched = True
434         self._post_launch()
435
436     def _close_qmp_connection(self) -> None:
437         """
438         Close the underlying QMP connection, if any.
439
440         Dutifully report errors that occurred while closing, but assume
441         that any error encountered indicates an abnormal termination
442         process and not a failure to close.
443         """
444         if self._qmp_connection is None:
445             return
446
447         try:
448             self._qmp.close()
449         except EOFError:
450             # EOF can occur as an Exception here when using the Async
451             # QMP backend. It indicates that the server closed the
452             # stream. If we successfully issued 'quit' at any point,
453             # then this was expected. If the remote went away without
454             # our permission, it's worth reporting that as an abnormal
455             # shutdown case.
456             if not (self._user_killed or self._quit_issued):
457                 raise
458         finally:
459             self._qmp_connection = None
460
461     def _early_cleanup(self) -> None:
462         """
463         Perform any cleanup that needs to happen before the VM exits.
464
465         This method may be called twice upon shutdown, once each by soft
466         and hard shutdown in failover scenarios.
467         """
468         # If we keep the console socket open, we may deadlock waiting
469         # for QEMU to exit, while QEMU is waiting for the socket to
470         # become writeable.
471         if self._console_socket is not None:
472             self._console_socket.close()
473             self._console_socket = None
474
475     def _hard_shutdown(self) -> None:
476         """
477         Perform early cleanup, kill the VM, and wait for it to terminate.
478
479         :raise subprocess.Timeout: When timeout is exceeds 60 seconds
480             waiting for the QEMU process to terminate.
481         """
482         self._early_cleanup()
483         self._subp.kill()
484         self._subp.wait(timeout=60)
485
486     def _soft_shutdown(self, timeout: Optional[int]) -> None:
487         """
488         Perform early cleanup, attempt to gracefully shut down the VM, and wait
489         for it to terminate.
490
491         :param timeout: Timeout in seconds for graceful shutdown.
492                         A value of None is an infinite wait.
493
494         :raise ConnectionReset: On QMP communication errors
495         :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
496             the QEMU process to terminate.
497         """
498         self._early_cleanup()
499
500         if self._qmp_connection:
501             try:
502                 if not self._quit_issued:
503                     # May raise ExecInterruptedError or StateError if the
504                     # connection dies or has *already* died.
505                     self.qmp('quit')
506             finally:
507                 # Regardless, we want to quiesce the connection.
508                 self._close_qmp_connection()
509
510         # May raise subprocess.TimeoutExpired
511         self._subp.wait(timeout=timeout)
512
513     def _do_shutdown(self, timeout: Optional[int]) -> None:
514         """
515         Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
516
517         :param timeout: Timeout in seconds for graceful shutdown.
518                         A value of None is an infinite wait.
519
520         :raise AbnormalShutdown: When the VM could not be shut down gracefully.
521             The inner exception will likely be ConnectionReset or
522             subprocess.TimeoutExpired. In rare cases, non-graceful termination
523             may result in its own exceptions, likely subprocess.TimeoutExpired.
524         """
525         try:
526             self._soft_shutdown(timeout)
527         except Exception as exc:
528             self._hard_shutdown()
529             raise AbnormalShutdown("Could not perform graceful shutdown") \
530                 from exc
531
532     def shutdown(self,
533                  hard: bool = False,
534                  timeout: Optional[int] = 30) -> None:
535         """
536         Terminate the VM (gracefully if possible) and perform cleanup.
537         Cleanup will always be performed.
538
539         If the VM has not yet been launched, or shutdown(), wait(), or kill()
540         have already been called, this method does nothing.
541
542         :param hard: When true, do not attempt graceful shutdown, and
543                      suppress the SIGKILL warning log message.
544         :param timeout: Optional timeout in seconds for graceful shutdown.
545                         Default 30 seconds, A `None` value is an infinite wait.
546         """
547         if not self._launched:
548             return
549
550         try:
551             if hard:
552                 self._user_killed = True
553                 self._hard_shutdown()
554             else:
555                 self._do_shutdown(timeout)
556         finally:
557             self._post_shutdown()
558
559     def kill(self) -> None:
560         """
561         Terminate the VM forcefully, wait for it to exit, and perform cleanup.
562         """
563         self.shutdown(hard=True)
564
565     def wait(self, timeout: Optional[int] = 30) -> None:
566         """
567         Wait for the VM to power off and perform post-shutdown cleanup.
568
569         :param timeout: Optional timeout in seconds. Default 30 seconds.
570                         A value of `None` is an infinite wait.
571         """
572         self._quit_issued = True
573         self.shutdown(timeout=timeout)
574
575     def set_qmp_monitor(self, enabled: bool = True) -> None:
576         """
577         Set the QMP monitor.
578
579         @param enabled: if False, qmp monitor options will be removed from
580                         the base arguments of the resulting QEMU command
581                         line. Default is True.
582
583         .. note:: Call this function before launch().
584         """
585         self._qmp_set = enabled
586
587     @property
588     def _qmp(self) -> QEMUMonitorProtocol:
589         if self._qmp_connection is None:
590             raise QEMUMachineError("Attempt to access QMP with no connection")
591         return self._qmp_connection
592
593     @classmethod
594     def _qmp_args(cls, conv_keys: bool,
595                   args: Dict[str, Any]) -> Dict[str, object]:
596         if conv_keys:
597             return {k.replace('_', '-'): v for k, v in args.items()}
598
599         return args
600
601     def qmp(self, cmd: str,
602             args_dict: Optional[Dict[str, object]] = None,
603             conv_keys: Optional[bool] = None,
604             **args: Any) -> QMPMessage:
605         """
606         Invoke a QMP command and return the response dict
607         """
608         if args_dict is not None:
609             assert not args
610             assert conv_keys is None
611             args = args_dict
612             conv_keys = False
613
614         if conv_keys is None:
615             conv_keys = True
616
617         qmp_args = self._qmp_args(conv_keys, args)
618         ret = self._qmp.cmd(cmd, args=qmp_args)
619         if cmd == 'quit' and 'error' not in ret and 'return' in ret:
620             self._quit_issued = True
621         return ret
622
623     def command(self, cmd: str,
624                 conv_keys: bool = True,
625                 **args: Any) -> QMPReturnValue:
626         """
627         Invoke a QMP command.
628         On success return the response dict.
629         On failure raise an exception.
630         """
631         qmp_args = self._qmp_args(conv_keys, args)
632         ret = self._qmp.command(cmd, **qmp_args)
633         if cmd == 'quit':
634             self._quit_issued = True
635         return ret
636
637     def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
638         """
639         Poll for one queued QMP events and return it
640         """
641         if self._events:
642             return self._events.pop(0)
643         return self._qmp.pull_event(wait=wait)
644
645     def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
646         """
647         Poll for queued QMP events and return a list of dicts
648         """
649         events = self._qmp.get_events(wait=wait)
650         events.extend(self._events)
651         del self._events[:]
652         return events
653
654     @staticmethod
655     def event_match(event: Any, match: Optional[Any]) -> bool:
656         """
657         Check if an event matches optional match criteria.
658
659         The match criteria takes the form of a matching subdict. The event is
660         checked to be a superset of the subdict, recursively, with matching
661         values whenever the subdict values are not None.
662
663         This has a limitation that you cannot explicitly check for None values.
664
665         Examples, with the subdict queries on the left:
666          - None matches any object.
667          - {"foo": None} matches {"foo": {"bar": 1}}
668          - {"foo": None} matches {"foo": 5}
669          - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
670          - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
671         """
672         if match is None:
673             return True
674
675         try:
676             for key in match:
677                 if key in event:
678                     if not QEMUMachine.event_match(event[key], match[key]):
679                         return False
680                 else:
681                     return False
682             return True
683         except TypeError:
684             # either match or event wasn't iterable (not a dict)
685             return bool(match == event)
686
687     def event_wait(self, name: str,
688                    timeout: float = 60.0,
689                    match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
690         """
691         event_wait waits for and returns a named event from QMP with a timeout.
692
693         name: The event to wait for.
694         timeout: QEMUMonitorProtocol.pull_event timeout parameter.
695         match: Optional match criteria. See event_match for details.
696         """
697         return self.events_wait([(name, match)], timeout)
698
699     def events_wait(self,
700                     events: Sequence[Tuple[str, Any]],
701                     timeout: float = 60.0) -> Optional[QMPMessage]:
702         """
703         events_wait waits for and returns a single named event from QMP.
704         In the case of multiple qualifying events, this function returns the
705         first one.
706
707         :param events: A sequence of (name, match_criteria) tuples.
708                        The match criteria are optional and may be None.
709                        See event_match for details.
710         :param timeout: Optional timeout, in seconds.
711                         See QEMUMonitorProtocol.pull_event.
712
713         :raise QMPTimeoutError: If timeout was non-zero and no matching events
714                                 were found.
715         :return: A QMP event matching the filter criteria.
716                  If timeout was 0 and no event matched, None.
717         """
718         def _match(event: QMPMessage) -> bool:
719             for name, match in events:
720                 if event['event'] == name and self.event_match(event, match):
721                     return True
722             return False
723
724         event: Optional[QMPMessage]
725
726         # Search cached events
727         for event in self._events:
728             if _match(event):
729                 self._events.remove(event)
730                 return event
731
732         # Poll for new events
733         while True:
734             event = self._qmp.pull_event(wait=timeout)
735             if event is None:
736                 # NB: None is only returned when timeout is false-ish.
737                 # Timeouts raise QMPTimeoutError instead!
738                 break
739             if _match(event):
740                 return event
741             self._events.append(event)
742
743         return None
744
745     def get_log(self) -> Optional[str]:
746         """
747         After self.shutdown or failed qemu execution, this returns the output
748         of the qemu process.
749         """
750         return self._iolog
751
752     def add_args(self, *args: str) -> None:
753         """
754         Adds to the list of extra arguments to be given to the QEMU binary
755         """
756         self._args.extend(args)
757
758     def set_machine(self, machine_type: str) -> None:
759         """
760         Sets the machine type
761
762         If set, the machine type will be added to the base arguments
763         of the resulting QEMU command line.
764         """
765         self._machine = machine_type
766
767     def set_console(self,
768                     device_type: Optional[str] = None,
769                     console_index: int = 0) -> None:
770         """
771         Sets the device type for a console device
772
773         If set, the console device and a backing character device will
774         be added to the base arguments of the resulting QEMU command
775         line.
776
777         This is a convenience method that will either use the provided
778         device type, or default to a "-serial chardev:console" command
779         line argument.
780
781         The actual setting of command line arguments will be be done at
782         machine launch time, as it depends on the temporary directory
783         to be created.
784
785         @param device_type: the device type, such as "isa-serial".  If
786                             None is given (the default value) a "-serial
787                             chardev:console" command line argument will
788                             be used instead, resorting to the machine's
789                             default device type.
790         @param console_index: the index of the console device to use.
791                               If not zero, the command line will create
792                               'index - 1' consoles and connect them to
793                               the 'null' backing character device.
794         """
795         self._console_set = True
796         self._console_device_type = device_type
797         self._console_index = console_index
798
799     @property
800     def console_socket(self) -> socket.socket:
801         """
802         Returns a socket connected to the console
803         """
804         if self._console_socket is None:
805             self._console_socket = console_socket.ConsoleSocket(
806                 self._console_address,
807                 file=self._console_log_path,
808                 drain=self._drain_console)
809         return self._console_socket
810
811     @property
812     def temp_dir(self) -> str:
813         """
814         Returns a temporary directory to be used for this machine
815         """
816         if self._temp_dir is None:
817             self._temp_dir = tempfile.mkdtemp(prefix="qemu-machine-",
818                                               dir=self._base_temp_dir)
819         return self._temp_dir
820
821     @property
822     def sock_dir(self) -> str:
823         """
824         Returns the directory used for sockfiles by this machine.
825         """
826         if self._sock_dir:
827             return self._sock_dir
828         return self.temp_dir
829
830     @property
831     def log_dir(self) -> str:
832         """
833         Returns a directory to be used for writing logs
834         """
835         if self._log_dir is None:
836             return self.temp_dir
837         return self._log_dir
This page took 0.073741 seconds and 4 git commands to generate.