|
@@ -86,8 +86,10 @@
|
|
|
import os
|
|
|
import re
|
|
|
import readline
|
|
|
+from subprocess import Popen
|
|
|
import sys
|
|
|
from typing import (
|
|
|
+ IO,
|
|
|
Iterator,
|
|
|
List,
|
|
|
NoReturn,
|
|
@@ -167,8 +169,11 @@ class QMPShell(QEMUMonitorProtocol):
|
|
|
:param verbose: Echo outgoing QMP messages to console.
|
|
|
"""
|
|
|
def __init__(self, address: SocketAddrT,
|
|
|
- pretty: bool = False, verbose: bool = False):
|
|
|
- super().__init__(address)
|
|
|
+ pretty: bool = False,
|
|
|
+ verbose: bool = False,
|
|
|
+ server: bool = False,
|
|
|
+ logfile: Optional[str] = None):
|
|
|
+ super().__init__(address, server=server)
|
|
|
self._greeting: Optional[QMPMessage] = None
|
|
|
self._completer = QMPCompleter()
|
|
|
self._transmode = False
|
|
@@ -177,6 +182,10 @@ def __init__(self, address: SocketAddrT,
|
|
|
'.qmp-shell_history')
|
|
|
self.pretty = pretty
|
|
|
self.verbose = verbose
|
|
|
+ self.logfile = None
|
|
|
+
|
|
|
+ if logfile is not None:
|
|
|
+ self.logfile = open(logfile, "w", encoding='utf-8')
|
|
|
|
|
|
def close(self) -> None:
|
|
|
# Hook into context manager of parent to save shell history.
|
|
@@ -317,11 +326,11 @@ def _build_cmd(self, cmdline: str) -> Optional[QMPMessage]:
|
|
|
self._cli_expr(cmdargs[1:], qmpcmd['arguments'])
|
|
|
return qmpcmd
|
|
|
|
|
|
- def _print(self, qmp_message: object) -> None:
|
|
|
+ def _print(self, qmp_message: object, fh: IO[str] = sys.stdout) -> None:
|
|
|
jsobj = json.dumps(qmp_message,
|
|
|
indent=4 if self.pretty else None,
|
|
|
sort_keys=self.pretty)
|
|
|
- print(str(jsobj))
|
|
|
+ print(str(jsobj), file=fh)
|
|
|
|
|
|
def _execute_cmd(self, cmdline: str) -> bool:
|
|
|
try:
|
|
@@ -344,6 +353,9 @@ def _execute_cmd(self, cmdline: str) -> bool:
|
|
|
print('Disconnected')
|
|
|
return False
|
|
|
self._print(resp)
|
|
|
+ if self.logfile is not None:
|
|
|
+ cmd = {**qmpcmd, **resp}
|
|
|
+ self._print(cmd, fh=self.logfile)
|
|
|
return True
|
|
|
|
|
|
def connect(self, negotiate: bool = True) -> None:
|
|
@@ -409,8 +421,11 @@ class HMPShell(QMPShell):
|
|
|
:param verbose: Echo outgoing QMP messages to console.
|
|
|
"""
|
|
|
def __init__(self, address: SocketAddrT,
|
|
|
- pretty: bool = False, verbose: bool = False):
|
|
|
- super().__init__(address, pretty, verbose)
|
|
|
+ pretty: bool = False,
|
|
|
+ verbose: bool = False,
|
|
|
+ server: bool = False,
|
|
|
+ logfile: Optional[str] = None):
|
|
|
+ super().__init__(address, pretty, verbose, server, logfile)
|
|
|
self._cpu_index = 0
|
|
|
|
|
|
def _cmd_completion(self) -> None:
|
|
@@ -503,6 +518,8 @@ def main() -> None:
|
|
|
help='Verbose (echo commands sent and received)')
|
|
|
parser.add_argument('-p', '--pretty', action='store_true',
|
|
|
help='Pretty-print JSON')
|
|
|
+ parser.add_argument('-l', '--logfile',
|
|
|
+ help='Save log of all QMP messages to PATH')
|
|
|
|
|
|
default_server = os.environ.get('QMP_SOCKET')
|
|
|
parser.add_argument('qmp_server', action='store',
|
|
@@ -521,7 +538,7 @@ def main() -> None:
|
|
|
parser.error(f"Bad port number: {args.qmp_server}")
|
|
|
return # pycharm doesn't know error() is noreturn
|
|
|
|
|
|
- with shell_class(address, args.pretty, args.verbose) as qemu:
|
|
|
+ with shell_class(address, args.pretty, args.verbose, args.logfile) as qemu:
|
|
|
try:
|
|
|
qemu.connect(negotiate=not args.skip_negotiation)
|
|
|
except ConnectError as err:
|
|
@@ -533,5 +550,60 @@ def main() -> None:
|
|
|
pass
|
|
|
|
|
|
|
|
|
+def main_wrap() -> None:
|
|
|
+ """
|
|
|
+ qmp-shell-wrap entry point: parse command line arguments and
|
|
|
+ start the REPL.
|
|
|
+ """
|
|
|
+ parser = argparse.ArgumentParser()
|
|
|
+ parser.add_argument('-H', '--hmp', action='store_true',
|
|
|
+ help='Use HMP interface')
|
|
|
+ parser.add_argument('-v', '--verbose', action='store_true',
|
|
|
+ help='Verbose (echo commands sent and received)')
|
|
|
+ parser.add_argument('-p', '--pretty', action='store_true',
|
|
|
+ help='Pretty-print JSON')
|
|
|
+ parser.add_argument('-l', '--logfile',
|
|
|
+ help='Save log of all QMP messages to PATH')
|
|
|
+
|
|
|
+ parser.add_argument('command', nargs=argparse.REMAINDER,
|
|
|
+ help='QEMU command line to invoke')
|
|
|
+
|
|
|
+ args = parser.parse_args()
|
|
|
+
|
|
|
+ cmd = args.command
|
|
|
+ if len(cmd) != 0 and cmd[0] == '--':
|
|
|
+ cmd = cmd[1:]
|
|
|
+ if len(cmd) == 0:
|
|
|
+ cmd = ["qemu-system-x86_64"]
|
|
|
+
|
|
|
+ sockpath = "qmp-shell-wrap-%d" % os.getpid()
|
|
|
+ cmd += ["-qmp", "unix:%s" % sockpath]
|
|
|
+
|
|
|
+ shell_class = HMPShell if args.hmp else QMPShell
|
|
|
+
|
|
|
+ try:
|
|
|
+ address = shell_class.parse_address(sockpath)
|
|
|
+ except QMPBadPortError:
|
|
|
+ parser.error(f"Bad port number: {sockpath}")
|
|
|
+ return # pycharm doesn't know error() is noreturn
|
|
|
+
|
|
|
+ try:
|
|
|
+ with shell_class(address, args.pretty, args.verbose,
|
|
|
+ True, args.logfile) as qemu:
|
|
|
+ with Popen(cmd):
|
|
|
+
|
|
|
+ try:
|
|
|
+ qemu.accept()
|
|
|
+ except ConnectError as err:
|
|
|
+ if isinstance(err.exc, OSError):
|
|
|
+ die(f"Couldn't connect to {args.qmp_server}: {err!s}")
|
|
|
+ die(str(err))
|
|
|
+
|
|
|
+ for _ in qemu.repl():
|
|
|
+ pass
|
|
|
+ finally:
|
|
|
+ os.unlink(sockpath)
|
|
|
+
|
|
|
+
|
|
|
if __name__ == '__main__':
|
|
|
main()
|