]>
git.ipfire.org Git - thirdparty/qemu.git/blob - python/qemu/machine/machine.py
ebb58d5b68c13cd94276a23ba711b39a2a9a6562
4 The machine module primarily provides the QEMUMachine class,
5 which provides facilities for managing the lifetime of a QEMU VM.
8 # Copyright (C) 2015-2016 Red Hat Inc.
9 # Copyright (C) 2012 IBM Corp.
12 # Fam Zheng <famz@redhat.com>
14 # This work is licensed under the terms of the GNU GPL, version 2. See
15 # the COPYING file in the top-level directory.
21 from itertools
import chain
30 from types
import TracebackType
43 from qemu
.qmp
import SocketAddrT
44 from qemu
.qmp
.legacy
import (
50 from . import console_socket
53 LOG
= logging
.getLogger(__name__
)
56 class QEMUMachineError(Exception):
58 Exception called when an error in QEMUMachine happens.
62 class QEMUMachineAddDeviceError(QEMUMachineError
):
64 Exception raised when a request to add a device can not be fulfilled
66 The failures are caused by limitations, lack of information or conflicting
67 requests on the QEMUMachine methods. This exception does not represent
68 failures reported by the QEMU binary itself.
72 class VMLaunchFailure(QEMUMachineError
):
74 Exception raised when a VM launch was attempted, but failed.
76 def __init__(self
, exitcode
: Optional
[int],
77 command
: str, output
: Optional
[str]):
78 super().__init
__(exitcode
, command
, output
)
79 self
.exitcode
= exitcode
80 self
.command
= command
83 def __str__(self
) -> str:
85 if self
.__cause
__ is not None:
86 name
= type(self
.__cause
__).__name
__
87 reason
= str(self
.__cause
__)
89 ret
+= f
"{name}: {reason}"
94 if self
.exitcode
is not None:
95 ret
+= f
"\tExit code: {self.exitcode}\n"
96 ret
+= f
"\tCommand: {self.command}\n"
97 ret
+= f
"\tOutput: {self.output}\n"
101 class AbnormalShutdown(QEMUMachineError
):
103 Exception raised when a graceful shutdown was requested, but not performed.
107 _T
= TypeVar('_T', bound
='QEMUMachine')
114 Use this object as a context manager to ensure
115 the QEMU process terminates::
117 with VM(binary) as vm:
119 # vm is guaranteed to be shut down here
121 # pylint: disable=too-many-instance-attributes, too-many-public-methods
125 args
: Sequence
[str] = (),
126 wrapper
: Sequence
[str] = (),
127 name
: Optional
[str] = None,
128 base_temp_dir
: str = "/var/tmp",
129 monitor_address
: Optional
[SocketAddrT
] = None,
130 drain_console
: bool = False,
131 console_log
: Optional
[str] = None,
132 log_dir
: Optional
[str] = None,
133 qmp_timer
: Optional
[float] = 30):
135 Initialize a QEMUMachine
137 @param binary: path to the qemu binary
138 @param args: list of extra arguments
139 @param wrapper: list of arguments used as prefix to qemu binary
140 @param name: prefix for socket and log file names (default: qemu-PID)
141 @param base_temp_dir: default location where temp files are created
142 @param monitor_address: address for QMP monitor
143 @param drain_console: (optional) True to drain console socket to buffer
144 @param console_log: (optional) path to console log file
145 @param log_dir: where to create and keep log files
146 @param qmp_timer: (optional) default QMP socket timeout
147 @note: Qemu process is not started until launch() is used.
149 # pylint: disable=too-many-arguments
151 # Direct user configuration
153 self
._binary
= binary
154 self
._args
= list(args
)
155 self
._wrapper
= wrapper
156 self
._qmp
_timer
= qmp_timer
158 self
._name
= name
or f
"{id(self):x}"
159 self
._sock
_pair
: Optional
[Tuple
[socket
.socket
, socket
.socket
]] = None
160 self
._cons
_sock
_pair
: Optional
[
161 Tuple
[socket
.socket
, socket
.socket
]] = None
162 self
._temp
_dir
: Optional
[str] = None
163 self
._base
_temp
_dir
= base_temp_dir
164 self
._log
_dir
= log_dir
166 self
._monitor
_address
= monitor_address
168 self
._console
_log
_path
= console_log
169 if self
._console
_log
_path
:
170 # In order to log the console, buffering needs to be enabled.
171 self
._drain
_console
= True
173 self
._drain
_console
= drain_console
176 self
._qemu
_log
_path
: Optional
[str] = None
177 self
._qemu
_log
_file
: Optional
[BinaryIO
] = None
178 self
._popen
: Optional
['subprocess.Popen[bytes]'] = None
179 self
._events
: List
[QMPMessage
] = []
180 self
._iolog
: Optional
[str] = None
181 self
._qmp
_set
= True # Enable QMP monitor by default.
182 self
._qmp
_connection
: Optional
[QEMUMonitorProtocol
] = None
183 self
._qemu
_full
_args
: Tuple
[str, ...] = ()
184 self
._launched
= False
185 self
._machine
: Optional
[str] = None
186 self
._console
_index
= 0
187 self
._console
_set
= False
188 self
._console
_device
_type
: Optional
[str] = None
189 self
._console
_socket
: Optional
[socket
.socket
] = None
190 self
._console
_file
: Optional
[socket
.SocketIO
] = None
191 self
._remove
_files
: List
[str] = []
192 self
._user
_killed
= False
193 self
._quit
_issued
= False
195 def __enter__(self
: _T
) -> _T
:
199 exc_type
: Optional
[Type
[BaseException
]],
200 exc_val
: Optional
[BaseException
],
201 exc_tb
: Optional
[TracebackType
]) -> None:
204 def add_monitor_null(self
) -> None:
206 This can be used to add an unused monitor instance.
208 self
._args
.append('-monitor')
209 self
._args
.append('null')
211 def add_fd(self
: _T
, fd
: int, fdset
: int,
212 opaque
: str, opts
: str = '') -> _T
:
214 Pass a file descriptor to the VM
216 options
= ['fd=%d' % fd
,
218 'opaque=%s' % opaque
]
222 # This did not exist before 3.4, but since then it is
223 # mandatory for our purpose
224 if hasattr(os
, 'set_inheritable'):
225 os
.set_inheritable(fd
, True)
227 self
._args
.append('-add-fd')
228 self
._args
.append(','.join(options
))
231 def send_fd_scm(self
, fd
: Optional
[int] = None,
232 file_path
: Optional
[str] = None) -> int:
234 Send an fd or file_path to the remote via SCM_RIGHTS.
236 Exactly one of fd and file_path must be given. If it is
237 file_path, the file will be opened read-only and the new file
238 descriptor will be sent to the remote.
240 if file_path
is not None:
242 with
open(file_path
, "rb") as passfile
:
243 fd
= passfile
.fileno()
244 self
._qmp
.send_fd_scm(fd
)
246 assert fd
is not None
247 self
._qmp
.send_fd_scm(fd
)
252 def _remove_if_exists(path
: str) -> None:
254 Remove file object at path if it exists
258 except OSError as exception
:
259 if exception
.errno
== errno
.ENOENT
:
263 def is_running(self
) -> bool:
264 """Returns true if the VM is running."""
265 return self
._popen
is not None and self
._popen
.poll() is None
268 def _subp(self
) -> 'subprocess.Popen[bytes]':
269 if self
._popen
is None:
270 raise QEMUMachineError('Subprocess pipe not present')
273 def exitcode(self
) -> Optional
[int]:
274 """Returns the exit code if possible, or None."""
275 if self
._popen
is None:
277 return self
._popen
.poll()
279 def get_pid(self
) -> Optional
[int]:
280 """Returns the PID of the running process, or None."""
281 if not self
.is_running():
283 return self
._subp
.pid
285 def _load_io_log(self
) -> None:
286 # Assume that the output encoding of QEMU's terminal output is
287 # defined by our locale. If indeterminate, allow open() to fall
288 # back to the platform default.
289 _
, encoding
= locale
.getlocale()
290 if self
._qemu
_log
_path
is not None:
291 with
open(self
._qemu
_log
_path
, "r", encoding
=encoding
) as iolog
:
292 self
._iolog
= iolog
.read()
295 def _base_args(self
) -> List
[str]:
296 args
= ['-display', 'none', '-vga', 'none']
300 moncdev
= f
"socket,id=mon,fd={self._sock_pair[0].fileno()}"
301 elif isinstance(self
._monitor
_address
, tuple):
302 moncdev
= "socket,id=mon,host={},port={}".format(
303 *self
._monitor
_address
306 moncdev
= f
"socket,id=mon,path={self._monitor_address}"
307 args
.extend(['-chardev', moncdev
, '-mon',
308 'chardev=mon,mode=control'])
310 if self
._machine
is not None:
311 args
.extend(['-machine', self
._machine
])
312 for _
in range(self
._console
_index
):
313 args
.extend(['-serial', 'null'])
314 if self
._console
_set
:
315 assert self
._cons
_sock
_pair
is not None
316 fd
= self
._cons
_sock
_pair
[0].fileno()
317 chardev
= f
"socket,id=console,fd={fd}"
318 args
.extend(['-chardev', chardev
])
319 if self
._console
_device
_type
is None:
320 args
.extend(['-serial', 'chardev:console'])
322 device
= '%s,chardev=console' % self
._console
_device
_type
323 args
.extend(['-device', device
])
327 def args(self
) -> List
[str]:
328 """Returns the list of arguments given to the QEMU binary."""
332 def binary(self
) -> str:
333 """Returns path to the QEMU binary"""
336 def _pre_launch(self
) -> None:
339 if self
._monitor
_address
is None:
340 self
._sock
_pair
= socket
.socketpair()
341 os
.set_inheritable(self
._sock
_pair
[0].fileno(), True)
342 sock
= self
._sock
_pair
[1]
343 if isinstance(self
._monitor
_address
, str):
344 self
._remove
_files
.append(self
._monitor
_address
)
346 sock_or_addr
= self
._monitor
_address
or sock
347 assert sock_or_addr
is not None
349 self
._qmp
_connection
= QEMUMonitorProtocol(
351 server
=bool(self
._monitor
_address
),
355 if self
._console
_set
:
356 self
._cons
_sock
_pair
= socket
.socketpair()
357 os
.set_inheritable(self
._cons
_sock
_pair
[0].fileno(), True)
359 # NOTE: Make sure any opened resources are *definitely* freed in
361 # pylint: disable=consider-using-with
362 self
._qemu
_log
_path
= os
.path
.join(self
.log_dir
, self
._name
+ ".log")
363 self
._qemu
_log
_file
= open(self
._qemu
_log
_path
, 'wb')
366 self
._qemu
_full
_args
= tuple(chain(
373 def _post_launch(self
) -> None:
375 self
._sock
_pair
[0].close()
376 if self
._cons
_sock
_pair
:
377 self
._cons
_sock
_pair
[0].close()
379 if self
._qmp
_connection
:
383 self
._qmp
.accept(self
._qmp
_timer
)
385 def _close_qemu_log_file(self
) -> None:
386 if self
._qemu
_log
_file
is not None:
387 self
._qemu
_log
_file
.close()
388 self
._qemu
_log
_file
= None
390 def _post_shutdown(self
) -> None:
392 Called to cleanup the VM instance after the process has exited.
393 May also be called after a failed launch.
395 LOG
.debug("Cleaning up after VM process")
397 self
._close
_qmp
_connection
()
398 except Exception as err
: # pylint: disable=broad-except
400 "Exception closing QMP connection: %s",
401 str(err
) if str(err
) else type(err
).__name
__
404 assert self
._qmp
_connection
is None
407 self
._sock
_pair
[0].close()
408 self
._sock
_pair
[1].close()
409 self
._sock
_pair
= None
411 self
._close
_qemu
_log
_file
()
415 self
._qemu
_log
_path
= None
417 if self
._temp
_dir
is not None:
418 shutil
.rmtree(self
._temp
_dir
)
419 self
._temp
_dir
= None
421 while len(self
._remove
_files
) > 0:
422 self
._remove
_if
_exists
(self
._remove
_files
.pop())
424 exitcode
= self
.exitcode()
425 if (exitcode
is not None and exitcode
< 0
426 and not (self
._user
_killed
and exitcode
== -signal
.SIGKILL
)):
427 msg
= 'qemu received signal %i; command: "%s"'
428 if self
._qemu
_full
_args
:
429 command
= ' '.join(self
._qemu
_full
_args
)
432 LOG
.warning(msg
, -int(exitcode
), command
)
434 self
._quit
_issued
= False
435 self
._user
_killed
= False
436 self
._launched
= False
438 def launch(self
) -> None:
440 Launch the VM and make sure we cleanup and expose the
441 command line/output in case of exception
445 raise QEMUMachineError('VM already launched')
449 except BaseException
as exc
:
450 # We may have launched the process but it may
451 # have exited before we could connect via QMP.
452 # Assume the VM didn't launch or is exiting.
453 # If we don't wait for the process, exitcode() may still be
454 # 'None' by the time control is ceded back to the caller.
458 self
._post
_shutdown
()
460 if isinstance(exc
, Exception):
461 raise VMLaunchFailure(
462 exitcode
=self
.exitcode(),
463 command
=' '.join(self
._qemu
_full
_args
),
467 # Don't wrap 'BaseException'; doing so would downgrade
468 # that exception. However, we still want to clean up.
471 def _launch(self
) -> None:
473 Launch the VM and establish a QMP connection
476 LOG
.debug('VM launch command: %r', ' '.join(self
._qemu
_full
_args
))
478 # Cleaning up of this subprocess is guaranteed by _do_shutdown.
479 # pylint: disable=consider-using-with
480 self
._popen
= subprocess
.Popen(self
._qemu
_full
_args
,
481 stdin
=subprocess
.DEVNULL
,
482 stdout
=self
._qemu
_log
_file
,
483 stderr
=subprocess
.STDOUT
,
486 self
._launched
= True
489 def _close_qmp_connection(self
) -> None:
491 Close the underlying QMP connection, if any.
493 Dutifully report errors that occurred while closing, but assume
494 that any error encountered indicates an abnormal termination
495 process and not a failure to close.
497 if self
._qmp
_connection
is None:
503 # EOF can occur as an Exception here when using the Async
504 # QMP backend. It indicates that the server closed the
505 # stream. If we successfully issued 'quit' at any point,
506 # then this was expected. If the remote went away without
507 # our permission, it's worth reporting that as an abnormal
509 if not (self
._user
_killed
or self
._quit
_issued
):
512 self
._qmp
_connection
= None
514 def _early_cleanup(self
) -> None:
516 Perform any cleanup that needs to happen before the VM exits.
518 This method may be called twice upon shutdown, once each by soft
519 and hard shutdown in failover scenarios.
521 # If we keep the console socket open, we may deadlock waiting
522 # for QEMU to exit, while QEMU is waiting for the socket to
524 if self
._console
_file
is not None:
525 LOG
.debug("Closing console file")
526 self
._console
_file
.close()
527 self
._console
_file
= None
529 if self
._console
_socket
is not None:
530 LOG
.debug("Closing console socket")
531 self
._console
_socket
.close()
532 self
._console
_socket
= None
534 if self
._cons
_sock
_pair
:
535 self
._cons
_sock
_pair
[0].close()
536 self
._cons
_sock
_pair
[1].close()
537 self
._cons
_sock
_pair
= None
539 def _hard_shutdown(self
) -> None:
541 Perform early cleanup, kill the VM, and wait for it to terminate.
543 :raise subprocess.Timeout: When timeout is exceeds 60 seconds
544 waiting for the QEMU process to terminate.
546 LOG
.debug("Performing hard shutdown")
547 self
._early
_cleanup
()
549 self
._subp
.wait(timeout
=60)
551 def _soft_shutdown(self
, timeout
: Optional
[int]) -> None:
553 Perform early cleanup, attempt to gracefully shut down the VM, and wait
556 :param timeout: Timeout in seconds for graceful shutdown.
557 A value of None is an infinite wait.
559 :raise ConnectionReset: On QMP communication errors
560 :raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
561 the QEMU process to terminate.
563 LOG
.debug("Attempting graceful termination")
565 self
._early
_cleanup
()
567 if self
._quit
_issued
:
569 "Anticipating QEMU termination due to prior 'quit' command, "
570 "or explicit call to wait()"
573 LOG
.debug("Politely asking QEMU to terminate")
575 if self
._qmp
_connection
:
577 if not self
._quit
_issued
:
578 # May raise ExecInterruptedError or StateError if the
579 # connection dies or has *already* died.
582 # Regardless, we want to quiesce the connection.
583 self
._close
_qmp
_connection
()
584 elif not self
._quit
_issued
:
586 "Not anticipating QEMU quit and no QMP connection present, "
589 self
._subp
.terminate()
591 # May raise subprocess.TimeoutExpired
593 "Waiting (timeout=%s) for QEMU process (pid=%s) to terminate",
594 timeout
, self
._subp
.pid
596 self
._subp
.wait(timeout
=timeout
)
598 def _do_shutdown(self
, timeout
: Optional
[int]) -> None:
600 Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
602 :param timeout: Timeout in seconds for graceful shutdown.
603 A value of None is an infinite wait.
605 :raise AbnormalShutdown: When the VM could not be shut down gracefully.
606 The inner exception will likely be ConnectionReset or
607 subprocess.TimeoutExpired. In rare cases, non-graceful termination
608 may result in its own exceptions, likely subprocess.TimeoutExpired.
611 self
._soft
_shutdown
(timeout
)
612 except Exception as exc
:
613 if isinstance(exc
, subprocess
.TimeoutExpired
):
614 LOG
.debug("Timed out waiting for QEMU process to exit")
615 LOG
.debug("Graceful shutdown failed", exc_info
=True)
616 LOG
.debug("Falling back to hard shutdown")
617 self
._hard
_shutdown
()
618 raise AbnormalShutdown("Could not perform graceful shutdown") \
623 timeout
: Optional
[int] = 30) -> None:
625 Terminate the VM (gracefully if possible) and perform cleanup.
626 Cleanup will always be performed.
628 If the VM has not yet been launched, or shutdown(), wait(), or kill()
629 have already been called, this method does nothing.
631 :param hard: When true, do not attempt graceful shutdown, and
632 suppress the SIGKILL warning log message.
633 :param timeout: Optional timeout in seconds for graceful shutdown.
634 Default 30 seconds, A `None` value is an infinite wait.
636 if not self
._launched
:
639 LOG
.debug("Shutting down VM appliance; timeout=%s", timeout
)
641 LOG
.debug("Caller requests immediate termination of QEMU process.")
645 self
._user
_killed
= True
646 self
._hard
_shutdown
()
648 self
._do
_shutdown
(timeout
)
650 self
._post
_shutdown
()
652 def kill(self
) -> None:
654 Terminate the VM forcefully, wait for it to exit, and perform cleanup.
656 self
.shutdown(hard
=True)
658 def wait(self
, timeout
: Optional
[int] = 30) -> None:
660 Wait for the VM to power off and perform post-shutdown cleanup.
662 :param timeout: Optional timeout in seconds. Default 30 seconds.
663 A value of `None` is an infinite wait.
665 self
._quit
_issued
= True
666 self
.shutdown(timeout
=timeout
)
668 def set_qmp_monitor(self
, enabled
: bool = True) -> None:
672 @param enabled: if False, qmp monitor options will be removed from
673 the base arguments of the resulting QEMU command
674 line. Default is True.
676 .. note:: Call this function before launch().
678 self
._qmp
_set
= enabled
681 def _qmp(self
) -> QEMUMonitorProtocol
:
682 if self
._qmp
_connection
is None:
683 raise QEMUMachineError("Attempt to access QMP with no connection")
684 return self
._qmp
_connection
687 def _qmp_args(cls
, conv_keys
: bool,
688 args
: Dict
[str, Any
]) -> Dict
[str, object]:
690 return {k
.replace('_', '-'): v
for k
, v
in args
.items()}
694 def qmp(self
, cmd
: str,
695 args_dict
: Optional
[Dict
[str, object]] = None,
696 conv_keys
: Optional
[bool] = None,
697 **args
: Any
) -> QMPMessage
:
699 Invoke a QMP command and return the response dict
701 if args_dict
is not None:
703 assert conv_keys
is None
707 if conv_keys
is None:
710 qmp_args
= self
._qmp
_args
(conv_keys
, args
)
711 ret
= self
._qmp
.cmd_raw(cmd
, args
=qmp_args
)
712 if cmd
== 'quit' and 'error' not in ret
and 'return' in ret
:
713 self
._quit
_issued
= True
716 def cmd(self
, cmd
: str,
717 args_dict
: Optional
[Dict
[str, object]] = None,
718 conv_keys
: Optional
[bool] = None,
719 **args
: Any
) -> QMPReturnValue
:
721 Invoke a QMP command.
722 On success return the response dict.
723 On failure raise an exception.
725 if args_dict
is not None:
727 assert conv_keys
is None
731 if conv_keys
is None:
734 qmp_args
= self
._qmp
_args
(conv_keys
, args
)
735 ret
= self
._qmp
.cmd(cmd
, **qmp_args
)
737 self
._quit
_issued
= True
740 def get_qmp_event(self
, wait
: bool = False) -> Optional
[QMPMessage
]:
742 Poll for one queued QMP events and return it
745 return self
._events
.pop(0)
746 return self
._qmp
.pull_event(wait
=wait
)
748 def get_qmp_events(self
, wait
: bool = False) -> List
[QMPMessage
]:
750 Poll for queued QMP events and return a list of dicts
752 events
= self
._qmp
.get_events(wait
=wait
)
753 events
.extend(self
._events
)
758 def event_match(event
: Any
, match
: Optional
[Any
]) -> bool:
760 Check if an event matches optional match criteria.
762 The match criteria takes the form of a matching subdict. The event is
763 checked to be a superset of the subdict, recursively, with matching
764 values whenever the subdict values are not None.
766 This has a limitation that you cannot explicitly check for None values.
768 Examples, with the subdict queries on the left:
769 - None matches any object.
770 - {"foo": None} matches {"foo": {"bar": 1}}
771 - {"foo": None} matches {"foo": 5}
772 - {"foo": {"abc": None}} does not match {"foo": {"bar": 1}}
773 - {"foo": {"rab": 2}} matches {"foo": {"bar": 1, "rab": 2}}
781 if not QEMUMachine
.event_match(event
[key
], match
[key
]):
787 # either match or event wasn't iterable (not a dict)
788 return bool(match
== event
)
790 def event_wait(self
, name
: str,
791 timeout
: float = 60.0,
792 match
: Optional
[QMPMessage
] = None) -> Optional
[QMPMessage
]:
794 event_wait waits for and returns a named event from QMP with a timeout.
796 name: The event to wait for.
797 timeout: QEMUMonitorProtocol.pull_event timeout parameter.
798 match: Optional match criteria. See event_match for details.
800 return self
.events_wait([(name
, match
)], timeout
)
802 def events_wait(self
,
803 events
: Sequence
[Tuple
[str, Any
]],
804 timeout
: float = 60.0) -> Optional
[QMPMessage
]:
806 events_wait waits for and returns a single named event from QMP.
807 In the case of multiple qualifying events, this function returns the
810 :param events: A sequence of (name, match_criteria) tuples.
811 The match criteria are optional and may be None.
812 See event_match for details.
813 :param timeout: Optional timeout, in seconds.
814 See QEMUMonitorProtocol.pull_event.
816 :raise asyncio.TimeoutError:
817 If timeout was non-zero and no matching events were found.
819 :return: A QMP event matching the filter criteria.
820 If timeout was 0 and no event matched, None.
822 def _match(event
: QMPMessage
) -> bool:
823 for name
, match
in events
:
824 if event
['event'] == name
and self
.event_match(event
, match
):
828 event
: Optional
[QMPMessage
]
830 # Search cached events
831 for event
in self
._events
:
833 self
._events
.remove(event
)
836 # Poll for new events
838 event
= self
._qmp
.pull_event(wait
=timeout
)
840 # NB: None is only returned when timeout is false-ish.
841 # Timeouts raise asyncio.TimeoutError instead!
845 self
._events
.append(event
)
849 def get_log(self
) -> Optional
[str]:
851 After self.shutdown or failed qemu execution, this returns the output
856 def add_args(self
, *args
: str) -> None:
858 Adds to the list of extra arguments to be given to the QEMU binary
860 self
._args
.extend(args
)
862 def set_machine(self
, machine_type
: str) -> None:
864 Sets the machine type
866 If set, the machine type will be added to the base arguments
867 of the resulting QEMU command line.
869 self
._machine
= machine_type
871 def set_console(self
,
872 device_type
: Optional
[str] = None,
873 console_index
: int = 0) -> None:
875 Sets the device type for a console device
877 If set, the console device and a backing character device will
878 be added to the base arguments of the resulting QEMU command
881 This is a convenience method that will either use the provided
882 device type, or default to a "-serial chardev:console" command
885 The actual setting of command line arguments will be be done at
886 machine launch time, as it depends on the temporary directory
889 @param device_type: the device type, such as "isa-serial". If
890 None is given (the default value) a "-serial
891 chardev:console" command line argument will
892 be used instead, resorting to the machine's
894 @param console_index: the index of the console device to use.
895 If not zero, the command line will create
896 'index - 1' consoles and connect them to
897 the 'null' backing character device.
899 self
._console
_set
= True
900 self
._console
_device
_type
= device_type
901 self
._console
_index
= console_index
904 def console_socket(self
) -> socket
.socket
:
906 Returns a socket connected to the console
908 if self
._console
_socket
is None:
909 LOG
.debug("Opening console socket")
910 if not self
._console
_set
:
911 raise QEMUMachineError(
912 "Attempt to access console socket with no connection")
913 assert self
._cons
_sock
_pair
is not None
914 # os.dup() is used here for sock_fd because otherwise we'd
915 # have two rich python socket objects that would each try to
916 # close the same underlying fd when either one gets garbage
918 self
._console
_socket
= console_socket
.ConsoleSocket(
919 sock_fd
=os
.dup(self
._cons
_sock
_pair
[1].fileno()),
920 file=self
._console
_log
_path
,
921 drain
=self
._drain
_console
)
922 self
._cons
_sock
_pair
[1].close()
923 return self
._console
_socket
926 def console_file(self
) -> socket
.SocketIO
:
928 Returns a file associated with the console socket
930 if self
._console
_file
is None:
931 LOG
.debug("Opening console file")
932 self
._console
_file
= self
.console_socket
.makefile(mode
='rb',
935 return self
._console
_file
938 def temp_dir(self
) -> str:
940 Returns a temporary directory to be used for this machine
942 if self
._temp
_dir
is None:
943 self
._temp
_dir
= tempfile
.mkdtemp(prefix
="qemu-machine-",
944 dir=self
._base
_temp
_dir
)
945 return self
._temp
_dir
948 def log_dir(self
) -> str:
950 Returns a directory to be used for writing logs
952 if self
._log
_dir
is None: