1""" 2QEMU machine module: 3 4The machine module primarily provides the QEMUMachine class, 5which provides facilities for managing the lifetime of a QEMU VM. 6""" 7 8# Copyright (C) 2015-2016 Red Hat Inc. 9# Copyright (C) 2012 IBM Corp. 10# 11# Authors: 12# Fam Zheng <famz@redhat.com> 13# 14# This work is licensed under the terms of the GNU GPL, version 2. See 15# the COPYING file in the top-level directory. 16# 17# Based on qmp.py. 18# 19 20import errno 21from itertools import chain 22import locale 23import logging 24import os 25import shutil 26import signal 27import socket 28import subprocess 29import tempfile 30from types import TracebackType 31from typing import ( 32 Any, 33 BinaryIO, 34 Dict, 35 List, 36 Optional, 37 Sequence, 38 Tuple, 39 Type, 40 TypeVar, 41) 42 43from qemu.qmp import ( # pylint: disable=import-error 44 QMPMessage, 45 QMPReturnValue, 46 SocketAddrT, 47) 48 49from . import console_socket 50 51 52if os.environ.get('QEMU_PYTHON_LEGACY_QMP'): 53 from qemu.qmp import QEMUMonitorProtocol 54else: 55 from qemu.aqmp.legacy import QEMUMonitorProtocol 56 57 58LOG = logging.getLogger(__name__) 59 60 61class QEMUMachineError(Exception): 62 """ 63 Exception called when an error in QEMUMachine happens. 64 """ 65 66 67class QEMUMachineAddDeviceError(QEMUMachineError): 68 """ 69 Exception raised when a request to add a device can not be fulfilled 70 71 The failures are caused by limitations, lack of information or conflicting 72 requests on the QEMUMachine methods. This exception does not represent 73 failures reported by the QEMU binary itself. 74 """ 75 76 77class 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 86class 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 838