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