summaryrefslogtreecommitdiffstats
path: root/python/qemu/qmp.py
diff options
context:
space:
mode:
authorPeter Maydell2020-07-15 14:04:27 +0200
committerPeter Maydell2020-07-15 14:04:27 +0200
commit673205379fb499d2b72f2985b47ec7114282f5fe (patch)
treeaf413522fcd260ebabd41361dbf41be5f66843bf /python/qemu/qmp.py
parentMerge remote-tracking branch 'remotes/philmd-gitlab/tags/sdcard-CVE-2020-1325... (diff)
parentpython/qmp.py: add QMPProtocolError (diff)
downloadqemu-673205379fb499d2b72f2985b47ec7114282f5fe.tar.gz
qemu-673205379fb499d2b72f2985b47ec7114282f5fe.tar.xz
qemu-673205379fb499d2b72f2985b47ec7114282f5fe.zip
Merge remote-tracking branch 'remotes/philmd-gitlab/tags/python-next-20200714' into staging
Python patches for 5.1 - Reduce race conditions on QEMUMachine::shutdown() 1. Remove the "bare except" pattern in the existing shutdown code, which can mask problems and make debugging difficult. 2. Ensure that post-shutdown cleanup is always performed, even when graceful termination fails. 3. Unify cleanup paths such that no matter how the VM is terminated, the same functions and steps are always taken to reset the object state. 4. Rewrite shutdown() such that any error encountered when attempting a graceful shutdown will be raised as an AbnormalShutdown exception. The pythonic idiom is to allow the caller to decide if this is a problem or not. - Modify part of the python/qemu library to comply with: . mypy --strict . pylint . flake8 - Script for the TCG Continuous Benchmarking project that uses callgrind to dissect QEMU execution into three main phases: . code generation . JIT execution . helpers execution CI jobs results: . https://cirrus-ci.com/build/5421349961203712 . https://gitlab.com/philmd/qemu/-/pipelines/166556001 . https://travis-ci.org/github/philmd/qemu/builds/708102347 # gpg: Signature made Tue 14 Jul 2020 21:40:05 BST # gpg: using RSA key FAABE75E12917221DCFD6BB2E3E32C2CDEADC0DE # gpg: Good signature from "Philippe Mathieu-Daudé (F4BUG) <f4bug@amsat.org>" [full] # Primary key fingerprint: FAAB E75E 1291 7221 DCFD 6BB2 E3E3 2C2C DEAD C0DE * remotes/philmd-gitlab/tags/python-next-20200714: python/qmp.py: add QMPProtocolError python/qmp.py: add casts to JSON deserialization python/qmp.py: Do not return None from cmd_obj python/qmp.py: re-absorb MonitorResponseError iotests.py: use qemu.qmp type aliases python/qmp.py: Define common types python/machine.py: change default wait timeout to 3 seconds python/machine.py: re-add sigkill warning suppression python/machine.py: split shutdown into hard and soft flavors tests/acceptance: Don't test reboot on cubieboard tests/acceptance: wait() instead of shutdown() where appropriate python/machine.py: Make wait() call shutdown() python/machine.py: Add a configurable timeout to shutdown() python/machine.py: Prohibit multiple shutdown() calls python/machine.py: Perform early cleanup for wait() calls, too python/machine.py: Add _early_cleanup hook python/machine.py: Close QMP socket in cleanup python/machine.py: consolidate _post_shutdown() scripts/performance: Add dissect.py script Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'python/qemu/qmp.py')
-rw-r--r--python/qemu/qmp.py67
1 files changed, 54 insertions, 13 deletions
diff --git a/python/qemu/qmp.py b/python/qemu/qmp.py
index e64b6b5faa..7935dababb 100644
--- a/python/qemu/qmp.py
+++ b/python/qemu/qmp.py
@@ -12,13 +12,32 @@ import errno
import socket
import logging
from typing import (
+ Any,
+ cast,
+ Dict,
Optional,
TextIO,
Type,
+ Tuple,
+ Union,
)
from types import TracebackType
+# QMPMessage is a QMP Message of any kind.
+# e.g. {'yee': 'haw'}
+#
+# QMPReturnValue is the inner value of return values only.
+# {'return': {}} is the QMPMessage,
+# {} is the QMPReturnValue.
+QMPMessage = Dict[str, Any]
+QMPReturnValue = Dict[str, Any]
+
+InternetAddrT = Tuple[str, str]
+UnixAddrT = str
+SocketAddrT = Union[InternetAddrT, UnixAddrT]
+
+
class QMPError(Exception):
"""
QMP base exception
@@ -43,6 +62,25 @@ class QMPTimeoutError(QMPError):
"""
+class QMPProtocolError(QMPError):
+ """
+ QMP protocol error; unexpected response
+ """
+
+
+class QMPResponseError(QMPError):
+ """
+ Represents erroneous QMP monitor reply
+ """
+ def __init__(self, reply: QMPMessage):
+ try:
+ desc = reply['error']['desc']
+ except KeyError:
+ desc = reply
+ super().__init__(desc)
+ self.reply = reply
+
+
class QEMUMonitorProtocol:
"""
Provide an API to connect to QEMU via QEMU Monitor Protocol (QMP) and then
@@ -99,7 +137,10 @@ class QEMUMonitorProtocol:
data = self.__sockfile.readline()
if not data:
return None
- resp = json.loads(data)
+ # By definition, any JSON received from QMP is a QMPMessage,
+ # and we are asserting only at static analysis time that it
+ # has a particular shape.
+ resp: QMPMessage = json.loads(data)
if 'event' in resp:
self.logger.debug("<<< %s", resp)
self.__events.append(resp)
@@ -194,22 +235,18 @@ class QEMUMonitorProtocol:
self.__sockfile = self.__sock.makefile(mode='r')
return self.__negotiate_capabilities()
- def cmd_obj(self, qmp_cmd):
+ def cmd_obj(self, qmp_cmd: QMPMessage) -> QMPMessage:
"""
Send a QMP command to the QMP Monitor.
@param qmp_cmd: QMP command to be sent as a Python dict
- @return QMP response as a Python dict or None if the connection has
- been closed
+ @return QMP response as a Python dict
"""
self.logger.debug(">>> %s", qmp_cmd)
- try:
- self.__sock.sendall(json.dumps(qmp_cmd).encode('utf-8'))
- except OSError as err:
- if err.errno == errno.EPIPE:
- return None
- raise err
+ self.__sock.sendall(json.dumps(qmp_cmd).encode('utf-8'))
resp = self.__json_read()
+ if resp is None:
+ raise QMPConnectError("Unexpected empty reply from server")
self.logger.debug("<<< %s", resp)
return resp
@@ -233,9 +270,13 @@ class QEMUMonitorProtocol:
Build and send a QMP command to the monitor, report errors if any
"""
ret = self.cmd(cmd, kwds)
- if "error" in ret:
- raise Exception(ret['error']['desc'])
- return ret['return']
+ if 'error' in ret:
+ raise QMPResponseError(ret)
+ if 'return' not in ret:
+ raise QMPProtocolError(
+ "'return' key not found in QMP response '{}'".format(str(ret))
+ )
+ return cast(QMPReturnValue, ret['return'])
def pull_event(self, wait=False):
"""