diff options
51 files changed, 545 insertions, 769 deletions
diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst index 246709ede8..7b968433a6 100644 --- a/docs/devel/qapi-code-gen.rst +++ b/docs/devel/qapi-code-gen.rst @@ -41,8 +41,8 @@ used internally. There are several kinds of types: simple types (a number of built-in types, such as ``int`` and ``str``; as well as enumerations), arrays, -complex types (structs and two flavors of unions), and alternate types -(a choice between other types). +complex types (structs and unions), and alternate types (a choice +between other types). Schema syntax diff --git a/hw/rx/rx-gdbsim.c b/hw/rx/rx-gdbsim.c index 64f897e5b1..be147b4bd9 100644 --- a/hw/rx/rx-gdbsim.c +++ b/hw/rx/rx-gdbsim.c @@ -141,7 +141,7 @@ static void rx_gdbsim_init(MachineState *machine) exit(1); } /* DTB is located at the end of SDRAM space. */ - dtb_offset = machine->ram_size - dtb_size; + dtb_offset = ROUND_DOWN(machine->ram_size - dtb_size, 16); rom_add_blob_fixed("dtb", dtb, dtb_size, SDRAM_BASE + dtb_offset); /* Set dtb address to R1 */ diff --git a/python/README.rst b/python/README.rst index fcf74f69ea..9c1fceaee7 100644 --- a/python/README.rst +++ b/python/README.rst @@ -59,7 +59,7 @@ Package installation also normally provides executable console scripts, so that tools like ``qmp-shell`` are always available via $PATH. To invoke them without installation, you can invoke e.g.: -``> PYTHONPATH=~/src/qemu/python python3 -m qemu.aqmp.qmp_shell`` +``> PYTHONPATH=~/src/qemu/python python3 -m qemu.qmp.qmp_shell`` The mappings between console script name and python module path can be found in ``setup.cfg``. diff --git a/python/qemu/aqmp/__init__.py b/python/qemu/aqmp/__init__.py deleted file mode 100644 index 4c22c38079..0000000000 --- a/python/qemu/aqmp/__init__.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -QEMU Monitor Protocol (QMP) development library & tooling. - -This package provides a fairly low-level class for communicating -asynchronously with QMP protocol servers, as implemented by QEMU, the -QEMU Guest Agent, and the QEMU Storage Daemon. - -`QMPClient` provides the main functionality of this package. All errors -raised by this library derive from `QMPError`, see `aqmp.error` for -additional detail. See `aqmp.events` for an in-depth tutorial on -managing QMP events. -""" - -# Copyright (C) 2020, 2021 John Snow for Red Hat, Inc. -# -# Authors: -# John Snow <jsnow@redhat.com> -# -# Based on earlier work by Luiz Capitulino <lcapitulino@redhat.com>. -# -# This work is licensed under the terms of the GNU GPL, version 2. See -# the COPYING file in the top-level directory. - -import logging - -from .error import QMPError -from .events import EventListener -from .message import Message -from .protocol import ( - ConnectError, - Runstate, - SocketAddrT, - StateError, -) -from .qmp_client import ExecInterruptedError, ExecuteError, QMPClient - - -# Suppress logging unless an application engages it. -logging.getLogger('qemu.aqmp').addHandler(logging.NullHandler()) - - -# The order of these fields impact the Sphinx documentation order. -__all__ = ( - # Classes, most to least important - 'QMPClient', - 'Message', - 'EventListener', - 'Runstate', - - # Exceptions, most generic to most explicit - 'QMPError', - 'StateError', - 'ConnectError', - 'ExecuteError', - 'ExecInterruptedError', - - # Type aliases - 'SocketAddrT', -) diff --git a/python/qemu/aqmp/legacy.py b/python/qemu/aqmp/legacy.py deleted file mode 100644 index 46026e9fdc..0000000000 --- a/python/qemu/aqmp/legacy.py +++ /dev/null @@ -1,177 +0,0 @@ -""" -Sync QMP Wrapper - -This class pretends to be qemu.qmp.QEMUMonitorProtocol. -""" - -import asyncio -from typing import ( - Any, - Awaitable, - Dict, - List, - Optional, - TypeVar, - Union, -) - -import qemu.qmp - -from .error import QMPError -from .protocol import Runstate, SocketAddrT -from .qmp_client import QMPClient - - -# (Temporarily) Re-export QMPBadPortError -QMPBadPortError = qemu.qmp.QMPBadPortError - -#: QMPMessage is an entire QMP message of any kind. -QMPMessage = Dict[str, Any] - -#: QMPReturnValue is the 'return' value of a command. -QMPReturnValue = object - -#: QMPObject is any object in a QMP message. -QMPObject = Dict[str, object] - -# QMPMessage can be outgoing commands or incoming events/returns. -# QMPReturnValue is usually a dict/json object, but due to QAPI's -# 'returns-whitelist', it can actually be anything. -# -# {'return': {}} is a QMPMessage, -# {} is the QMPReturnValue. - - -# pylint: disable=missing-docstring - - -class QEMUMonitorProtocol(qemu.qmp.QEMUMonitorProtocol): - def __init__(self, address: SocketAddrT, - server: bool = False, - nickname: Optional[str] = None): - - # pylint: disable=super-init-not-called - self._aqmp = QMPClient(nickname) - self._aloop = asyncio.get_event_loop() - self._address = address - self._timeout: Optional[float] = None - - if server: - self._sync(self._aqmp.start_server(self._address)) - - _T = TypeVar('_T') - - def _sync( - self, future: Awaitable[_T], timeout: Optional[float] = None - ) -> _T: - return self._aloop.run_until_complete( - asyncio.wait_for(future, timeout=timeout) - ) - - def _get_greeting(self) -> Optional[QMPMessage]: - if self._aqmp.greeting is not None: - # pylint: disable=protected-access - return self._aqmp.greeting._asdict() - return None - - # __enter__ and __exit__ need no changes - # parse_address needs no changes - - def connect(self, negotiate: bool = True) -> Optional[QMPMessage]: - self._aqmp.await_greeting = negotiate - self._aqmp.negotiate = negotiate - - self._sync( - self._aqmp.connect(self._address) - ) - return self._get_greeting() - - def accept(self, timeout: Optional[float] = 15.0) -> QMPMessage: - self._aqmp.await_greeting = True - self._aqmp.negotiate = True - - self._sync(self._aqmp.accept(), timeout) - - ret = self._get_greeting() - assert ret is not None - return ret - - def cmd_obj(self, qmp_cmd: QMPMessage) -> QMPMessage: - return dict( - self._sync( - # pylint: disable=protected-access - - # _raw() isn't a public API, because turning off - # automatic ID assignment is discouraged. For - # compatibility with iotests *only*, do it anyway. - self._aqmp._raw(qmp_cmd, assign_id=False), - self._timeout - ) - ) - - # Default impl of cmd() delegates to cmd_obj - - def command(self, cmd: str, **kwds: object) -> QMPReturnValue: - return self._sync( - self._aqmp.execute(cmd, kwds), - self._timeout - ) - - def pull_event(self, - wait: Union[bool, float] = False) -> Optional[QMPMessage]: - if not wait: - # wait is False/0: "do not wait, do not except." - if self._aqmp.events.empty(): - return None - - # If wait is 'True', wait forever. If wait is False/0, the events - # queue must not be empty; but it still needs some real amount - # of time to complete. - timeout = None - if wait and isinstance(wait, float): - timeout = wait - - return dict( - self._sync( - self._aqmp.events.get(), - timeout - ) - ) - - def get_events(self, wait: Union[bool, float] = False) -> List[QMPMessage]: - events = [dict(x) for x in self._aqmp.events.clear()] - if events: - return events - - event = self.pull_event(wait) - return [event] if event is not None else [] - - def clear_events(self) -> None: - self._aqmp.events.clear() - - def close(self) -> None: - self._sync( - self._aqmp.disconnect() - ) - - def settimeout(self, timeout: Optional[float]) -> None: - self._timeout = timeout - - def send_fd_scm(self, fd: int) -> None: - self._aqmp.send_fd_scm(fd) - - def __del__(self) -> None: - if self._aqmp.runstate == Runstate.IDLE: - return - - if not self._aloop.is_running(): - self.close() - else: - # Garbage collection ran while the event loop was running. - # Nothing we can do about it now, but if we don't raise our - # own error, the user will be treated to a lot of traceback - # they might not understand. - raise QMPError( - "QEMUMonitorProtocol.close()" - " was not called before object was garbage collected" - ) diff --git a/python/qemu/machine/machine.py b/python/qemu/machine/machine.py index a5972fab4d..07ac5a710b 100644 --- a/python/qemu/machine/machine.py +++ b/python/qemu/machine/machine.py @@ -40,21 +40,16 @@ from typing import ( TypeVar, ) -from qemu.qmp import ( # pylint: disable=import-error +from qemu.qmp import SocketAddrT +from qemu.qmp.legacy import ( + QEMUMonitorProtocol, QMPMessage, QMPReturnValue, - SocketAddrT, ) from . import console_socket -if os.environ.get('QEMU_PYTHON_LEGACY_QMP'): - from qemu.qmp import QEMUMonitorProtocol -else: - from qemu.aqmp.legacy import QEMUMonitorProtocol - - LOG = logging.getLogger(__name__) @@ -743,8 +738,9 @@ class QEMUMachine: :param timeout: Optional timeout, in seconds. See QEMUMonitorProtocol.pull_event. - :raise QMPTimeoutError: If timeout was non-zero and no matching events - were found. + :raise asyncio.TimeoutError: + If timeout was non-zero and no matching events were found. + :return: A QMP event matching the filter criteria. If timeout was 0 and no event matched, None. """ @@ -767,7 +763,7 @@ class QEMUMachine: event = self._qmp.pull_event(wait=timeout) if event is None: # NB: None is only returned when timeout is false-ish. - # Timeouts raise QMPTimeoutError instead! + # Timeouts raise asyncio.TimeoutError instead! break if _match(event): return event diff --git a/python/qemu/machine/qtest.py b/python/qemu/machine/qtest.py index f2f9aaa5e5..1a1fc6c9b0 100644 --- a/python/qemu/machine/qtest.py +++ b/python/qemu/machine/qtest.py @@ -26,7 +26,7 @@ from typing import ( TextIO, ) -from qemu.qmp import SocketAddrT # pylint: disable=import-error +from qemu.qmp import SocketAddrT from .machine import QEMUMachine diff --git a/python/qemu/qmp/README.rst b/python/qemu/qmp/README.rst deleted file mode 100644 index 5bfb82535f..0000000000 --- a/python/qemu/qmp/README.rst +++ /dev/null @@ -1,9 +0,0 @@ -qemu.qmp package -================ - -This package provides a library used for connecting to and communicating -with QMP servers. It is used extensively by iotests, vm tests, -avocado tests, and other utilities in the ./scripts directory. It is -not a fully-fledged SDK and is subject to change at any time. - -See the documentation in ``__init__.py`` for more information. diff --git a/python/qemu/qmp/__init__.py b/python/qemu/qmp/__init__.py index 358c0971d0..69190d057a 100644 --- a/python/qemu/qmp/__init__.py +++ b/python/qemu/qmp/__init__.py @@ -1,422 +1,59 @@ """ QEMU Monitor Protocol (QMP) development library & tooling. -This package provides a fairly low-level class for communicating to QMP -protocol servers, as implemented by QEMU, the QEMU Guest Agent, and the -QEMU Storage Daemon. This library is not intended for production use. - -`QEMUMonitorProtocol` is the primary class of interest, and all errors -raised derive from `QMPError`. +This package provides a fairly low-level class for communicating +asynchronously with QMP protocol servers, as implemented by QEMU, the +QEMU Guest Agent, and the QEMU Storage Daemon. + +`QMPClient` provides the main functionality of this package. All errors +raised by this library derive from `QMPError`, see `qmp.error` for +additional detail. See `qmp.events` for an in-depth tutorial on +managing QMP events. """ -# Copyright (C) 2009, 2010 Red Hat Inc. +# Copyright (C) 2020-2022 John Snow for Red Hat, Inc. # # Authors: -# Luiz Capitulino <lcapitulino@redhat.com> +# John Snow <jsnow@redhat.com> # -# This work is licensed under the terms of the GNU GPL, version 2. See -# the COPYING file in the top-level directory. - -import errno -import json -import logging -import socket -import struct -from types import TracebackType -from typing import ( - Any, - Dict, - List, - Optional, - TextIO, - Tuple, - Type, - TypeVar, - Union, - cast, -) - - -#: QMPMessage is an entire QMP message of any kind. -QMPMessage = Dict[str, Any] - -#: QMPReturnValue is the 'return' value of a command. -QMPReturnValue = object - -#: QMPObject is any object in a QMP message. -QMPObject = Dict[str, object] - -# QMPMessage can be outgoing commands or incoming events/returns. -# QMPReturnValue is usually a dict/json object, but due to QAPI's -# 'returns-whitelist', it can actually be anything. +# Based on earlier work by Luiz Capitulino <lcapitulino@redhat.com>. # -# {'return': {}} is a QMPMessage, -# {} is the QMPReturnValue. - - -InternetAddrT = Tuple[str, int] -UnixAddrT = str -SocketAddrT = Union[InternetAddrT, UnixAddrT] - - -class QMPError(Exception): - """ - QMP base exception - """ - - -class QMPConnectError(QMPError): - """ - QMP connection exception - """ - - -class QMPCapabilitiesError(QMPError): - """ - QMP negotiate capabilities exception - """ - - -class QMPTimeoutError(QMPError): - """ - QMP timeout exception - """ - - -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 QMPBadPortError(QMPError): - """ - Unable to parse socket address: Port was non-numerical. - """ - - -class QEMUMonitorProtocol: - """ - Provide an API to connect to QEMU via QEMU Monitor Protocol (QMP) and then - allow to handle commands and events. - """ - - #: Logger object for debugging messages - logger = logging.getLogger('QMP') - - def __init__(self, address: SocketAddrT, - server: bool = False, - nickname: Optional[str] = None): - """ - Create a QEMUMonitorProtocol class. - - @param address: QEMU address, can be either a unix socket path (string) - or a tuple in the form ( address, port ) for a TCP - connection - @param server: server mode listens on the socket (bool) - @raise OSError on socket connection errors - @note No connection is established, this is done by the connect() or - accept() methods - """ - self.__events: List[QMPMessage] = [] - self.__address = address - self.__sock = self.__get_sock() - self.__sockfile: Optional[TextIO] = None - self._nickname = nickname - if self._nickname: - self.logger = logging.getLogger('QMP').getChild(self._nickname) - if server: - self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.__sock.bind(self.__address) - self.__sock.listen(1) - - def __get_sock(self) -> socket.socket: - if isinstance(self.__address, tuple): - family = socket.AF_INET - else: - family = socket.AF_UNIX - return socket.socket(family, socket.SOCK_STREAM) - - def __negotiate_capabilities(self) -> QMPMessage: - greeting = self.__json_read() - if greeting is None or "QMP" not in greeting: - raise QMPConnectError - # Greeting seems ok, negotiate capabilities - resp = self.cmd('qmp_capabilities') - if resp and "return" in resp: - return greeting - raise QMPCapabilitiesError - - def __json_read(self, only_event: bool = False) -> Optional[QMPMessage]: - assert self.__sockfile is not None - while True: - data = self.__sockfile.readline() - if not data: - return None - # 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) - if not only_event: - continue - return resp - - def __get_events(self, wait: Union[bool, float] = False) -> None: - """ - Check for new events in the stream and cache them in __events. +# This work is licensed under the terms of the GNU LGPL, version 2 or +# later. See the COPYING file in the top-level directory. - @param wait (bool): block until an event is available. - @param wait (float): If wait is a float, treat it as a timeout value. - - @raise QMPTimeoutError: If a timeout float is provided and the timeout - period elapses. - @raise QMPConnectError: If wait is True but no events could be - retrieved or if some other error occurred. - """ - - # Current timeout and blocking status - current_timeout = self.__sock.gettimeout() - - # Check for new events regardless and pull them into the cache: - self.__sock.settimeout(0) # i.e. setblocking(False) - try: - self.__json_read() - except OSError as err: - # EAGAIN: No data available; not critical - if err.errno != errno.EAGAIN: - raise - finally: - self.__sock.settimeout(current_timeout) - - # Wait for new events, if needed. - # if wait is 0.0, this means "no wait" and is also implicitly false. - if not self.__events and wait: - if isinstance(wait, float): - self.__sock.settimeout(wait) - try: - ret = self.__json_read(only_event=True) - except socket.timeout as err: - raise QMPTimeoutError("Timeout waiting for event") from err - except Exception as err: - msg = "Error while reading from socket" - raise QMPConnectError(msg) from err - finally: - self.__sock.settimeout(current_timeout) - - if ret is None: - raise QMPConnectError("Error while reading from socket") - - T = TypeVar('T') - - def __enter__(self: T) -> T: - # Implement context manager enter function. - return self - - def __exit__(self, - # pylint: disable=duplicate-code - # see https://github.com/PyCQA/pylint/issues/3619 - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType]) -> None: - # Implement context manager exit function. - self.close() - - @classmethod - def parse_address(cls, address: str) -> SocketAddrT: - """ - Parse a string into a QMP address. - - Figure out if the argument is in the port:host form. - If it's not, it's probably a file path. - """ - components = address.split(':') - if len(components) == 2: - try: - port = int(components[1]) - except ValueError: - msg = f"Bad port: '{components[1]}' in '{address}'." - raise QMPBadPortError(msg) from None - return (components[0], port) - - # Treat as filepath. - return address - - def connect(self, negotiate: bool = True) -> Optional[QMPMessage]: - """ - Connect to the QMP Monitor and perform capabilities negotiation. - - @return QMP greeting dict, or None if negotiate is false - @raise OSError on socket connection errors - @raise QMPConnectError if the greeting is not received - @raise QMPCapabilitiesError if fails to negotiate capabilities - """ - self.__sock.connect(self.__address) - self.__sockfile = self.__sock.makefile(mode='r') - if negotiate: - return self.__negotiate_capabilities() - return None - - def accept(self, timeout: Optional[float] = 15.0) -> QMPMessage: - """ - Await connection from QMP Monitor and perform capabilities negotiation. - - @param timeout: timeout in seconds (nonnegative float number, or - None). The value passed will set the behavior of the - underneath QMP socket as described in [1]. - Default value is set to 15.0. - - @return QMP greeting dict - @raise OSError on socket connection errors - @raise QMPConnectError if the greeting is not received - @raise QMPCapabilitiesError if fails to negotiate capabilities - - [1] - https://docs.python.org/3/library/socket.html#socket.socket.settimeout - """ - self.__sock.settimeout(timeout) - self.__sock, _ = self.__sock.accept() - self.__sockfile = self.__sock.makefile(mode='r') - return self.__negotiate_capabilities() - - 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 - """ - self.logger.debug(">>> %s", qmp_cmd) - 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 - - def cmd(self, name: str, - args: Optional[Dict[str, object]] = None, - cmd_id: Optional[object] = None) -> QMPMessage: - """ - Build a QMP command and send it to the QMP Monitor. - - @param name: command name (string) - @param args: command arguments (dict) - @param cmd_id: command id (dict, list, string or int) - """ - qmp_cmd: QMPMessage = {'execute': name} - if args: - qmp_cmd['arguments'] = args - if cmd_id: - qmp_cmd['id'] = cmd_id - return self.cmd_obj(qmp_cmd) - - def command(self, cmd: str, **kwds: object) -> QMPReturnValue: - """ - Build and send a QMP command to the monitor, report errors if any - """ - ret = self.cmd(cmd, kwds) - 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: Union[bool, float] = False) -> Optional[QMPMessage]: - """ - Pulls a single event. - - @param wait (bool): block until an event is available. - @param wait (float): If wait is a float, treat it as a timeout value. - - @raise QMPTimeoutError: If a timeout float is provided and the timeout - period elapses. - @raise QMPConnectError: If wait is True but no events could be - retrieved or if some other error occurred. - - @return The first available QMP event, or None. - """ - self.__get_events(wait) - - if self.__events: - return self.__events.pop(0) - return None - - def get_events(self, wait: bool = False) -> List[QMPMessage]: - """ - Get a list of available QMP events and clear all pending events. - - @param wait (bool): block until an event is available. - @param wait (float): If wait is a float, treat it as a timeout value. - - @raise QMPTimeoutError: If a timeout float is provided and the timeout - period elapses. - @raise QMPConnectError: If wait is True but no events could be - retrieved or if some other error occurred. - - @return The list of available QMP events. - """ - self.__get_events(wait) - events = self.__events - self.__events = [] - return events +import logging - def clear_events(self) -> None: - """ - Clear current list of pending events. - """ - self.__events = [] +from .error import QMPError +from .events import EventListener +from .message import Message +from .protocol import ( + ConnectError, + Runstate, + SocketAddrT, + StateError, +) +from .qmp_client import ExecInterruptedError, ExecuteError, QMPClient - def close(self) -> None: - """ - Close the socket and socket file. - """ - if self.__sock: - self.__sock.close() - if self.__sockfile: - self.__sockfile.close() - def settimeout(self, timeout: Optional[float]) -> None: - """ - Set the socket timeout. +# Suppress logging unless an application engages it. +logging.getLogger('qemu.qmp').addHandler(logging.NullHandler()) - @param timeout (float): timeout in seconds (non-zero), or None. - @note This is a wrap around socket.settimeout - @raise ValueError: if timeout was set to 0. - """ - if timeout == 0: - msg = "timeout cannot be 0; this engages non-blocking mode." - msg += " Use 'None' instead to disable timeouts." - raise ValueError(msg) - self.__sock.settimeout(timeout) +# The order of these fields impact the Sphinx documentation order. +__all__ = ( + # Classes, most to least important + 'QMPClient', + 'Message', + 'EventListener', + 'Runstate', - def send_fd_scm(self, fd: int) -> None: - """ - Send a file descriptor to the remote via SCM_RIGHTS. - """ - if self.__sock.family != socket.AF_UNIX: - raise RuntimeError("Can't use SCM_RIGHTS on non-AF_UNIX socket.") + # Exceptions, most generic to most explicit + 'QMPError', + 'StateError', + 'ConnectError', + 'ExecuteError', + 'ExecInterruptedError', - self.__sock.sendmsg( - [b' '], - [(socket.SOL_SOCKET, socket.SCM_RIGHTS, struct.pack('@i', fd))] - ) + # Type aliases + 'SocketAddrT', +) diff --git a/python/qemu/aqmp/error.py b/python/qemu/qmp/error.py index 24ba4d5054..24ba4d5054 100644 --- a/python/qemu/aqmp/error.py +++ b/python/qemu/qmp/error.py diff --git a/python/qemu/aqmp/events.py b/python/qemu/qmp/events.py index f3d4e2b5e8..6199776cc6 100644 --- a/python/qemu/aqmp/events.py +++ b/python/qemu/qmp/events.py @@ -1,5 +1,5 @@ """ -AQMP Events and EventListeners +QMP Events and EventListeners Asynchronous QMP uses `EventListener` objects to listen for events. An `EventListener` is a FIFO event queue that can be pre-filtered to listen diff --git a/python/qemu/qmp/legacy.py b/python/qemu/qmp/legacy.py new file mode 100644 index 0000000000..03b5574618 --- /dev/null +++ b/python/qemu/qmp/legacy.py @@ -0,0 +1,315 @@ +""" +(Legacy) Sync QMP Wrapper + +This module provides the `QEMUMonitorProtocol` class, which is a +synchronous wrapper around `QMPClient`. + +Its design closely resembles that of the original QEMUMonitorProtocol +class, originally written by Luiz Capitulino. It is provided here for +compatibility with scripts inside the QEMU source tree that expect the +old interface. +""" + +# +# Copyright (C) 2009-2022 Red Hat Inc. +# +# Authors: +# Luiz Capitulino <lcapitulino@redhat.com> +# John Snow <jsnow@redhat.com> +# +# This work is licensed under the terms of the GNU GPL, version 2. See +# the COPYING file in the top-level directory. +# + +import asyncio +from types import TracebackType +from typing import ( + Any, + Awaitable, + Dict, + List, + Optional, + Type, + TypeVar, + Union, +) + +from .error import QMPError +from .protocol import Runstate, SocketAddrT +from .qmp_client import QMPClient + + +#: QMPMessage is an entire QMP message of any kind. +QMPMessage = Dict[str, Any] + +#: QMPReturnValue is the 'return' value of a command. +QMPReturnValue = object + +#: QMPObject is any object in a QMP message. +QMPObject = Dict[str, object] + +# QMPMessage can be outgoing commands or incoming events/returns. +# QMPReturnValue is usually a dict/json object, but due to QAPI's +# 'returns-whitelist', it can actually be anything. +# +# {'return': {}} is a QMPMessage, +# {} is the QMPReturnValue. + + +class QMPBadPortError(QMPError): + """ + Unable to parse socket address: Port was non-numerical. + """ + + +class QEMUMonitorProtocol: + """ + Provide an API to connect to QEMU via QEMU Monitor Protocol (QMP) + and then allow to handle commands and events. + + :param address: QEMU address, can be either a unix socket path (string) + or a tuple in the form ( address, port ) for a TCP + connection + :param server: Act as the socket server. (See 'accept') + :param nickname: Optional nickname used for logging. + """ + + def __init__(self, address: SocketAddrT, + server: bool = False, + nickname: Optional[str] = None): + + self._qmp = QMPClient(nickname) + self._aloop = asyncio.get_event_loop() + self._address = address + self._timeout: Optional[float] = None + + if server: + self._sync(self._qmp.start_server(self._address)) + + _T = TypeVar('_T') + + def _sync( + self, future: Awaitable[_T], timeout: Optional[float] = None + ) -> _T: + return self._aloop.run_until_complete( + asyncio.wait_for(future, timeout=timeout) + ) + + def _get_greeting(self) -> Optional[QMPMessage]: + if self._qmp.greeting is not None: + # pylint: disable=protected-access + return self._qmp.greeting._asdict() + return None + + def __enter__(self: _T) -> _T: + # Implement context manager enter function. + return self + + def __exit__(self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType]) -> None: + # Implement context manager exit function. + self.close() + + @classmethod + def parse_address(cls, address: str) -> SocketAddrT: + """ + Parse a string into a QMP address. + + Figure out if the argument is in the port:host form. + If it's not, it's probably a file path. + """ + components = address.split(':') + if len(components) == 2: + try: + port = int(components[1]) + except ValueError: + msg = f"Bad port: '{components[1]}' in '{address}'." + raise QMPBadPortError(msg) from None + return (components[0], port) + + # Treat as filepath. + return address + + def connect(self, negotiate: bool = True) -> Optional[QMPMessage]: + """ + Connect to the QMP Monitor and perform capabilities negotiation. + + :return: QMP greeting dict, or None if negotiate is false + :raise ConnectError: on connection errors + """ + self._qmp.await_greeting = negotiate + self._qmp.negotiate = negotiate + + self._sync( + self._qmp.connect(self._address) + ) + return self._get_greeting() + + def accept(self, timeout: Optional[float] = 15.0) -> QMPMessage: + """ + Await connection from QMP Monitor and perform capabilities negotiation. + + :param timeout: + timeout in seconds (nonnegative float number, or None). + If None, there is no timeout, and this may block forever. + + :return: QMP greeting dict + :raise ConnectError: on connection errors + """ + self._qmp.await_greeting = True + self._qmp.negotiate = True + + self._sync(self._qmp.accept(), timeout) + + ret = self._get_greeting() + assert ret is not None + return ret + + 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 + """ + return dict( + self._sync( + # pylint: disable=protected-access + + # _raw() isn't a public API, because turning off + # automatic ID assignment is discouraged. For + # compatibility with iotests *only*, do it anyway. + self._qmp._raw(qmp_cmd, assign_id=False), + self._timeout + ) + ) + + def cmd(self, name: str, + args: Optional[Dict[str, object]] = None, + cmd_id: Optional[object] = None) -> QMPMessage: + """ + Build a QMP command and send it to the QMP Monitor. + + :param name: command name (string) + :param args: command arguments (dict) + :param cmd_id: command id (dict, list, string or int) + """ + qmp_cmd: QMPMessage = {'execute': name} + if args: + qmp_cmd['arguments'] = args + if cmd_id: + qmp_cmd['id'] = cmd_id + return self.cmd_obj(qmp_cmd) + + def command(self, cmd: str, **kwds: object) -> QMPReturnValue: + """ + Build and send a QMP command to the monitor, report errors if any + """ + return self._sync( + self._qmp.execute(cmd, kwds), + self._timeout + ) + + def pull_event(self, + wait: Union[bool, float] = False) -> Optional[QMPMessage]: + """ + Pulls a single event. + + :param wait: + If False or 0, do not wait. Return None if no events ready. + If True, wait forever until the next event. + Otherwise, wait for the specified number of seconds. + + :raise asyncio.TimeoutError: + When a timeout is requested and the timeout period elapses. + + :return: The first available QMP event, or None. + """ + if not wait: + # wait is False/0: "do not wait, do not except." + if self._qmp.events.empty(): + return None + + # If wait is 'True', wait forever. If wait is False/0, the events + # queue must not be empty; but it still needs some real amount + # of time to complete. + timeout = None + if wait and isinstance(wait, float): + timeout = wait + + return dict( + self._sync( + self._qmp.events.get(), + timeout + ) + ) + + def get_events(self, wait: Union[bool, float] = False) -> List[QMPMessage]: + """ + Get a list of QMP events and clear all pending events. + + :param wait: + If False or 0, do not wait. Return None if no events ready. + If True, wait until we have at least one event. + Otherwise, wait for up to the specified number of seconds for at + least one event. + + :raise asyncio.TimeoutError: + When a timeout is requested and the timeout period elapses. + + :return: A list of QMP events. + """ + events = [dict(x) for x in self._qmp.events.clear()] + if events: + return events + + event = self.pull_event(wait) + return [event] if event is not None else [] + + def clear_events(self) -> None: + """Clear current list of pending events.""" + self._qmp.events.clear() + + def close(self) -> None: + """Close the connection.""" + self._sync( + self._qmp.disconnect() + ) + + def settimeout(self, timeout: Optional[float]) -> None: + """ + Set the timeout for QMP RPC execution. + + This timeout affects the `cmd`, `cmd_obj`, and `command` methods. + The `accept`, `pull_event` and `get_event` methods have their + own configurable timeouts. + + :param timeout: + timeout in seconds, or None. + None will wait indefinitely. + """ + self._timeout = timeout + + def send_fd_scm(self, fd: int) -> None: + """ + Send a file descriptor to the remote via SCM_RIGHTS. + """ + self._qmp.send_fd_scm(fd) + + def __del__(self) -> None: + if self._qmp.runstate == Runstate.IDLE: + return + + if not self._aloop.is_running(): + self.close() + else: + # Garbage collection ran while the event loop was running. + # Nothing we can do about it now, but if we don't raise our + # own error, the user will be treated to a lot of traceback + # they might not understand. + raise QMPError( + "QEMUMonitorProtocol.close()" + " was not called before object was garbage collected" + ) diff --git a/python/qemu/aqmp/message.py b/python/qemu/qmp/message.py index f76ccc9074..f76ccc9074 100644 --- a/python/qemu/aqmp/message.py +++ b/python/qemu/qmp/message.py diff --git a/python/qemu/aqmp/models.py b/python/qemu/qmp/models.py index de87f87804..de87f87804 100644 --- a/python/qemu/aqmp/models.py +++ b/python/qemu/qmp/models.py diff --git a/python/qemu/aqmp/protocol.py b/python/qemu/qmp/protocol.py index 36fae57f27..6ea86650ad 100644 --- a/python/qemu/aqmp/protocol.py +++ b/python/qemu/qmp/protocol.py @@ -196,9 +196,9 @@ class AsyncProtocol(Generic[T]): :param name: Name used for logging messages, if any. By default, messages - will log to 'qemu.aqmp.protocol', but each individual connection + will log to 'qemu.qmp.protocol', but each individual connection can be given its own logger by giving it a name; messages will - then log to 'qemu.aqmp.protocol.${name}'. + then log to 'qemu.qmp.protocol.${name}'. """ # pylint: disable=too-many-instance-attributes diff --git a/python/qemu/aqmp/qmp_client.py b/python/qemu/qmp/qmp_client.py index 90a8737f03..5dcda04a75 100644 --- a/python/qemu/aqmp/qmp_client.py +++ b/python/qemu/qmp/qmp_client.py @@ -192,7 +192,7 @@ class QMPClient(AsyncProtocol[Message], Events): await self.qmp.runstate_changed.wait() await self.disconnect() - See `aqmp.events` for more detail on event handling patterns. + See `qmp.events` for more detail on event handling patterns. """ #: Logger object used for debugging messages. logger = logging.getLogger(__name__) @@ -416,7 +416,7 @@ class QMPClient(AsyncProtocol[Message], Events): @upper_half def _get_exec_id(self) -> str: - exec_id = f"__aqmp#{self._execute_id:05d}" + exec_id = f"__qmp#{self._execute_id:05d}" self._execute_id += 1 return exec_id @@ -476,7 +476,7 @@ class QMPClient(AsyncProtocol[Message], Events): An execution ID will be assigned if assign_id is `True`. It can be disabled, but this requires that an ID is manually assigned instead. For manually assigned IDs, you must not use the string - '__aqmp#' anywhere in the ID. + '__qmp#' anywhere in the ID. :param msg: The QMP `Message` to execute. :param assign_id: If True, assign a new execution ID. @@ -490,7 +490,7 @@ class QMPClient(AsyncProtocol[Message], Events): msg['id'] = self._get_exec_id() elif 'id' in msg: assert isinstance(msg['id'], str) - assert '__aqmp#' not in msg['id'] + assert '__qmp#' not in msg['id'] exec_id = await self._issue(msg) return await self._reply(exec_id) @@ -512,7 +512,7 @@ class QMPClient(AsyncProtocol[Message], Events): Assign an arbitrary execution ID to this message. If `False`, the existing id must either be absent (and no other such pending execution may omit an ID) or a string. If it is - a string, it must not start with '__aqmp#' and no other such + a string, it must not start with '__qmp#' and no other such pending execution may currently be using that ID. :return: Execution reply from the server. @@ -524,7 +524,7 @@ class QMPClient(AsyncProtocol[Message], Events): When assign_id is `False`, an ID is given, and it is not a string. :raise ValueError: When assign_id is `False`, but the ID is not usable; - Either because it starts with '__aqmp#' or it is already in-use. + Either because it starts with '__qmp#' or it is already in-use. """ # 1. convert generic Mapping or bytes to a QMP Message # 2. copy Message objects so that we assign an ID only to the copy. @@ -534,9 +534,9 @@ class QMPClient(AsyncProtocol[Message], Events): if not assign_id and 'id' in msg: if not isinstance(exec_id, str): raise TypeError(f"ID ('{exec_id}') must be a string.") - if exec_id.startswith('__aqmp#'): + if exec_id.startswith('__qmp#'): raise ValueError( - f"ID ('{exec_id}') must not start with '__aqmp#'." + f"ID ('{exec_id}') must not start with '__qmp#'." ) if not assign_id and exec_id in self._pending: diff --git a/python/qemu/aqmp/qmp_shell.py b/python/qemu/qmp/qmp_shell.py index 35691494d0..619ab42ced 100644 --- a/python/qemu/aqmp/qmp_shell.py +++ b/python/qemu/qmp/qmp_shell.py @@ -1,11 +1,12 @@ # -# Copyright (C) 2009, 2010 Red Hat Inc. +# Copyright (C) 2009-2022 Red Hat Inc. # # Authors: # Luiz Capitulino <lcapitulino@redhat.com> +# John Snow <jsnow@redhat.com> # -# This work is licensed under the terms of the GNU GPL, version 2. See -# the COPYING file in the top-level directory. +# This work is licensed under the terms of the GNU LGPL, version 2 or +# later. See the COPYING file in the top-level directory. # """ @@ -97,8 +98,8 @@ from typing import ( Sequence, ) -from qemu.aqmp import ConnectError, QMPError, SocketAddrT -from qemu.aqmp.legacy import ( +from qemu.qmp import ConnectError, QMPError, SocketAddrT +from qemu.qmp.legacy import ( QEMUMonitorProtocol, QMPBadPortError, QMPMessage, diff --git a/python/qemu/aqmp/aqmp_tui.py b/python/qemu/qmp/qmp_tui.py index f1e926dd75..ce239d8979 100644 --- a/python/qemu/aqmp/aqmp_tui.py +++ b/python/qemu/qmp/qmp_tui.py @@ -3,16 +3,16 @@ # Authors: # Niteesh Babu G S <niteesh.gs@gmail.com> # -# This work is licensed under the terms of the GNU GPL, version 2 or +# This work is licensed under the terms of the GNU LGPL, version 2 or # later. See the COPYING file in the top-level directory. """ -AQMP TUI +QMP TUI -AQMP TUI is an asynchronous interface built on top the of the AQMP library. +QMP TUI is an asynchronous interface built on top the of the QMP library. It is the successor of QMP-shell and is bought-in as a replacement for it. -Example Usage: aqmp-tui <SOCKET | TCP IP:PORT> -Full Usage: aqmp-tui --help +Example Usage: qmp-tui <SOCKET | TCP IP:PORT> +Full Usage: qmp-tui --help """ import argparse @@ -35,9 +35,8 @@ from pygments import token as Token import urwid import urwid_readline -from qemu.qmp import QEMUMonitorProtocol, QMPBadPortError - from .error import ProtocolError +from .legacy import QEMUMonitorProtocol, QMPBadPortError from .message import DeserializationError, Message, UnexpectedTypeError from .protocol import ConnectError, Runstate from .qmp_client import ExecInterruptedError, QMPClient @@ -130,7 +129,7 @@ def has_handler_type(logger: logging.Logger, class App(QMPClient): """ - Implements the AQMP TUI. + Implements the QMP TUI. Initializes the widgets and starts the urwid event loop. @@ -613,7 +612,7 @@ def main() -> None: Driver of the whole script, parses arguments, initialize the TUI and the logger. """ - parser = argparse.ArgumentParser(description='AQMP TUI') + parser = argparse.ArgumentParser(description='QMP TUI') parser.add_argument('qmp_server', help='Address of the QMP server. ' 'Format <UNIX socket path | TCP addr:port>') parser.add_argument('--num-retries', type=int, default=10, diff --git a/python/qemu/aqmp/util.py b/python/qemu/qmp/util.py index eaa5fc7d5f..eaa5fc7d5f 100644 --- a/python/qemu/aqmp/util.py +++ b/python/qemu/qmp/util.py diff --git a/python/qemu/utils/qemu_ga_client.py b/python/qemu/utils/qemu_ga_client.py index 15ed430c61..8c38a7ac9c 100644 --- a/python/qemu/utils/qemu_ga_client.py +++ b/python/qemu/utils/qemu_ga_client.py @@ -50,8 +50,8 @@ from typing import ( Sequence, ) -from qemu.aqmp import ConnectError, SocketAddrT -from qemu.aqmp.legacy import QEMUMonitorProtocol +from qemu.qmp import ConnectError, SocketAddrT +from qemu.qmp.legacy import QEMUMonitorProtocol # This script has not seen many patches or careful attention in quite diff --git a/python/qemu/utils/qom.py b/python/qemu/utils/qom.py index bb5d1a78f5..bcf192f477 100644 --- a/python/qemu/utils/qom.py +++ b/python/qemu/utils/qom.py @@ -32,7 +32,7 @@ QOM commands: import argparse -from qemu.aqmp import ExecuteError +from qemu.qmp import ExecuteError from .qom_common import QOMCommand diff --git a/python/qemu/utils/qom_common.py b/python/qemu/utils/qom_common.py index e034a6f247..80da1b2304 100644 --- a/python/qemu/utils/qom_common.py +++ b/python/qemu/utils/qom_common.py @@ -27,8 +27,8 @@ from typing import ( TypeVar, ) -from qemu.aqmp import QMPError -from qemu.aqmp.legacy import QEMUMonitorProtocol +from qemu.qmp import QMPError +from qemu.qmp.legacy import QEMUMonitorProtocol class ObjectPropertyInfo: diff --git a/python/qemu/utils/qom_fuse.py b/python/qemu/utils/qom_fuse.py index 653a76b93b..8dcd59fcde 100644 --- a/python/qemu/utils/qom_fuse.py +++ b/python/qemu/utils/qom_fuse.py @@ -48,7 +48,7 @@ from typing import ( import fuse from fuse import FUSE, FuseOSError, Operations -from qemu.aqmp import ExecuteError +from qemu.qmp import ExecuteError from .qom_common import QOMCommand diff --git a/python/setup.cfg b/python/setup.cfg index 241f243e8b..e877ea5647 100644 --- a/python/setup.cfg +++ b/python/setup.cfg @@ -27,7 +27,6 @@ packages = qemu.qmp qemu.machine qemu.utils - qemu.aqmp [options.package_data] * = py.typed @@ -52,7 +51,7 @@ devel = fuse = fusepy >= 2.0.4 -# AQMP TUI dependencies +# QMP TUI dependencies tui = urwid >= 2.1.2 urwid-readline >= 0.13 @@ -67,9 +66,9 @@ console_scripts = qom-tree = qemu.utils.qom:QOMTree.entry_point qom-fuse = qemu.utils.qom_fuse:QOMFuse.entry_point [fuse] qemu-ga-client = qemu.utils.qemu_ga_client:main - qmp-shell = qemu.aqmp.qmp_shell:main - qmp-shell-wrap = qemu.aqmp.qmp_shell:main_wrap - aqmp-tui = qemu.aqmp.aqmp_tui:main [tui] + qmp-shell = qemu.qmp.qmp_shell:main + qmp-shell-wrap = qemu.qmp.qmp_shell:main_wrap + qmp-tui = qemu.qmp.qmp_tui:main [tui] [flake8] extend-ignore = E722 # Prefer pylint's bare-except checks to flake8's @@ -85,7 +84,7 @@ namespace_packages = True # fusepy has no type stubs: allow_subclassing_any = True -[mypy-qemu.aqmp.aqmp_tui] +[mypy-qemu.qmp.qmp_tui] # urwid and urwid_readline have no type stubs: allow_subclassing_any = True diff --git a/python/tests/protocol.py b/python/tests/protocol.py index d6849ad306..56c4d441f9 100644 --- a/python/tests/protocol.py +++ b/python/tests/protocol.py @@ -6,9 +6,9 @@ from tempfile import TemporaryDirectory import avocado -from qemu.aqmp import ConnectError, Runstate -from qemu.aqmp.protocol import AsyncProtocol, StateError -from qemu.aqmp.util import asyncio_run, create_task +from qemu.qmp import ConnectError, Runstate +from qemu.qmp.protocol import AsyncProtocol, StateError +from qemu.qmp.util import asyncio_run, create_task class NullProtocol(AsyncProtocol[None]): @@ -183,7 +183,7 @@ class Smoke(avocado.Test): def testLogger(self): self.assertEqual( self.proto.logger.name, - 'qemu.aqmp.protocol' + 'qemu.qmp.protocol' ) def testName(self): @@ -196,7 +196,7 @@ class Smoke(avocado.Test): self.assertEqual( self.proto.logger.name, - 'qemu.aqmp.protocol.Steve' + 'qemu.qmp.protocol.Steve' ) self.assertEqual( @@ -431,7 +431,7 @@ class Accept(Connect): await self.proto.start_server_and_accept('/dev/null') async def _hanging_connection(self): - with TemporaryDirectory(suffix='.aqmp') as tmpdir: + with TemporaryDirectory(suffix='.qmp') as tmpdir: sock = os.path.join(tmpdir, type(self.proto).__name__ + ".sock") await self.proto.start_server_and_accept(sock) @@ -587,7 +587,7 @@ class SimpleSession(TestBase): @TestBase.async_test async def testSmoke(self): - with TemporaryDirectory(suffix='.aqmp') as tmpdir: + with TemporaryDirectory(suffix='.qmp') as tmpdir: sock = os.path.join(tmpdir, type(self.proto).__name__ + ".sock") server_task = create_task(self.server.start_server_and_accept(sock)) diff --git a/qapi/migration.json b/qapi/migration.json index 27d7b28158..409eb086a2 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -1619,7 +1619,7 @@ # # Query replication status while the vm is running. # -# Returns: A @ReplicationResult object showing the status. +# Returns: A @ReplicationStatus object showing the status. # # Example: # diff --git a/qapi/misc-target.json b/qapi/misc-target.json index bc9355b595..ed4a468aab 100644 --- a/qapi/misc-target.json +++ b/qapi/misc-target.json @@ -144,7 +144,7 @@ # # @cert-chain: PDH certificate chain (base64 encoded) # -# @cpu0-id: Unique ID of CPU0 (base64 encoded) (since 7.0) +# @cpu0-id: Unique ID of CPU0 (base64 encoded) (since 7.1) # # @cbitpos: C-bit location in page table entry # diff --git a/qapi/sockets.json b/qapi/sockets.json index 5773d9fcc4..fccc38584b 100644 --- a/qapi/sockets.json +++ b/qapi/sockets.json @@ -149,7 +149,7 @@ # # Note: This type is deprecated in favor of SocketAddress. The # difference between SocketAddressLegacy and SocketAddress is that the -# latter is has fewer {} on the wire. +# latter has fewer {} on the wire. # # Since: 1.3 ## diff --git a/scripts/cpu-x86-uarch-abi.py b/scripts/cpu-x86-uarch-abi.py index c262d2f027..82ff07582f 100644 --- a/scripts/cpu-x86-uarch-abi.py +++ b/scripts/cpu-x86-uarch-abi.py @@ -6,7 +6,7 @@ # compatibility levels for each CPU model. # -from qemu.aqmp.legacy import QEMUMonitorProtocol +from qemu.qmp.legacy import QEMUMonitorProtocol import sys if len(sys.argv) != 2: diff --git a/scripts/device-crash-test b/scripts/device-crash-test index 7fbd99158b..4bfc68c008 100755 --- a/scripts/device-crash-test +++ b/scripts/device-crash-test @@ -36,7 +36,7 @@ from itertools import chain sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'python')) from qemu.machine import QEMUMachine -from qemu.aqmp import ConnectError +from qemu.qmp import ConnectError logger = logging.getLogger('device-crash-test') dbg = logger.debug @@ -517,7 +517,7 @@ def main(): # Async QMP, when in use, is chatty about connection failures. # This script knowingly generates a ton of connection errors. # Silence this logger. - logging.getLogger('qemu.aqmp.qmp_client').setLevel(logging.CRITICAL) + logging.getLogger('qemu.qmp.qmp_client').setLevel(logging.CRITICAL) fatal_failures = [] wl_stats = {} diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py index 3cb389e875..48578e1698 100644 --- a/scripts/qapi/expr.py +++ b/scripts/qapi/expr.py @@ -554,7 +554,7 @@ def check_alternate(expr: _JSONObject, info: QAPISourceInfo) -> None: check_name_lower(key, info, source) check_keys(value, info, source, ['type'], ['if']) check_if(value, info, source) - check_type(value['type'], info, source) + check_type(value['type'], info, source, allow_array=True) def check_command(expr: _JSONObject, info: QAPISourceInfo) -> None: diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index b7b3fc0ce4..3728340c37 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -243,6 +243,7 @@ class QAPISchemaType(QAPISchemaEntity): 'number': 'QTYPE_QNUM', 'int': 'QTYPE_QNUM', 'boolean': 'QTYPE_QBOOL', + 'array': 'QTYPE_QLIST', 'object': 'QTYPE_QDICT' } return json2qtype.get(self.json_type()) @@ -1069,6 +1070,9 @@ class QAPISchema: None)) def _make_variant(self, case, typ, ifcond, info): + if isinstance(typ, list): + assert len(typ) == 1 + typ = self._make_array_type(typ[0], info) return QAPISchemaVariant(case, info, typ, ifcond) def _def_union_type(self, expr, info, doc): diff --git a/scripts/qmp/qmp-shell b/scripts/qmp/qmp-shell index 31b19d73e2..4a20f97db7 100755 --- a/scripts/qmp/qmp-shell +++ b/scripts/qmp/qmp-shell @@ -4,7 +4,7 @@ import os import sys sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) -from qemu.aqmp import qmp_shell +from qemu.qmp import qmp_shell if __name__ == '__main__': diff --git a/scripts/qmp/qmp-shell-wrap b/scripts/qmp/qmp-shell-wrap index 66846e36d1..9e94da114f 100755 --- a/scripts/qmp/qmp-shell-wrap +++ b/scripts/qmp/qmp-shell-wrap @@ -4,7 +4,7 @@ import os import sys sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) -from qemu.aqmp import qmp_shell +from qemu.qmp import qmp_shell if __name__ == '__main__': diff --git a/scripts/render_block_graph.py b/scripts/render_block_graph.py index b33fb70d5e..8f731a5cfe 100755 --- a/scripts/render_block_graph.py +++ b/scripts/render_block_graph.py @@ -25,8 +25,8 @@ import json from graphviz import Digraph sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'python')) -from qemu.aqmp import QMPError -from qemu.aqmp.legacy import QEMUMonitorProtocol +from qemu.qmp import QMPError +from qemu.qmp.legacy import QEMUMonitorProtocol def perm(arr): diff --git a/scripts/simplebench/bench_block_job.py b/scripts/simplebench/bench_block_job.py index a403c35b08..56191db44b 100755 --- a/scripts/simplebench/bench_block_job.py +++ b/scripts/simplebench/bench_block_job.py @@ -27,8 +27,7 @@ import json sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) from qemu.machine import QEMUMachine -from qemu.qmp import QMPConnectError -from qemu.aqmp import ConnectError +from qemu.qmp import ConnectError def bench_block_job(cmd, cmd_args, qemu_args): @@ -50,7 +49,7 @@ def bench_block_job(cmd, cmd_args, qemu_args): vm.launch() except OSError as e: return {'error': 'popen failed: ' + str(e)} - except (QMPConnectError, ConnectError, socket.timeout): + except (ConnectError, socket.timeout): return {'error': 'qemu failed: ' + str(vm.get_log())} try: diff --git a/target/rx/cpu.h b/target/rx/cpu.h index 1c267f83bf..5655dffeff 100644 --- a/target/rx/cpu.h +++ b/target/rx/cpu.h @@ -149,6 +149,7 @@ static inline void cpu_get_tb_cpu_state(CPURXState *env, target_ulong *pc, *pc = env->pc; *cs_base = 0; *flags = FIELD_DP32(0, PSW, PM, env->psw_pm); + *flags = FIELD_DP32(*flags, PSW, U, env->psw_u); } static inline int cpu_mmu_index(CPURXState *env, bool ifetch) diff --git a/target/rx/op_helper.c b/target/rx/op_helper.c index 76a746300e..9ca32dcc82 100644 --- a/target/rx/op_helper.c +++ b/target/rx/op_helper.c @@ -450,6 +450,7 @@ G_NORETURN void helper_wait(CPURXState *env) cs->halted = 1; env->in_sleep = 1; + env->psw_i = 1; raise_exception(env, EXCP_HLT, 0); } diff --git a/target/rx/translate.c b/target/rx/translate.c index c8a8991a63..62aee66937 100644 --- a/target/rx/translate.c +++ b/target/rx/translate.c @@ -32,6 +32,7 @@ typedef struct DisasContext { DisasContextBase base; CPURXState *env; uint32_t pc; + uint32_t tb_flags; } DisasContext; typedef struct DisasCompare { @@ -231,7 +232,7 @@ static inline TCGv rx_load_source(DisasContext *ctx, TCGv mem, /* Processor mode check */ static int is_privileged(DisasContext *ctx, int is_exception) { - if (FIELD_EX32(ctx->base.tb->flags, PSW, PM)) { + if (FIELD_EX32(ctx->tb_flags, PSW, PM)) { if (is_exception) { gen_helper_raise_privilege_violation(cpu_env); } @@ -310,9 +311,8 @@ static void psw_cond(DisasCompare *dc, uint32_t cond) } } -static void move_from_cr(TCGv ret, int cr, uint32_t pc) +static void move_from_cr(DisasContext *ctx, TCGv ret, int cr, uint32_t pc) { - TCGv z = tcg_const_i32(0); switch (cr) { case 0: /* PSW */ gen_helper_pack_psw(ret, cpu_env); @@ -321,8 +321,11 @@ static void move_from_cr(TCGv ret, int cr, uint32_t pc) tcg_gen_movi_i32(ret, pc); break; case 2: /* USP */ - tcg_gen_movcond_i32(TCG_COND_NE, ret, - cpu_psw_u, z, cpu_sp, cpu_usp); + if (FIELD_EX32(ctx->tb_flags, PSW, U)) { + tcg_gen_mov_i32(ret, cpu_sp); + } else { + tcg_gen_mov_i32(ret, cpu_usp); + } break; case 3: /* FPSW */ tcg_gen_mov_i32(ret, cpu_fpsw); @@ -334,8 +337,11 @@ static void move_from_cr(TCGv ret, int cr, uint32_t pc) tcg_gen_mov_i32(ret, cpu_bpc); break; case 10: /* ISP */ - tcg_gen_movcond_i32(TCG_COND_EQ, ret, - cpu_psw_u, z, cpu_sp, cpu_isp); + if (FIELD_EX32(ctx->tb_flags, PSW, U)) { + tcg_gen_mov_i32(ret, cpu_isp); + } else { + tcg_gen_mov_i32(ret, cpu_sp); + } break; case 11: /* FINTV */ tcg_gen_mov_i32(ret, cpu_fintv); @@ -349,28 +355,31 @@ static void move_from_cr(TCGv ret, int cr, uint32_t pc) tcg_gen_movi_i32(ret, 0); break; } - tcg_temp_free(z); } static void move_to_cr(DisasContext *ctx, TCGv val, int cr) { - TCGv z; if (cr >= 8 && !is_privileged(ctx, 0)) { /* Some control registers can only be written in privileged mode. */ qemu_log_mask(LOG_GUEST_ERROR, "disallow control register write %s", rx_crname(cr)); return; } - z = tcg_const_i32(0); switch (cr) { case 0: /* PSW */ gen_helper_set_psw(cpu_env, val); + if (is_privileged(ctx, 0)) { + /* PSW.{I,U} may be updated here. exit TB. */ + ctx->base.is_jmp = DISAS_UPDATE; + } break; /* case 1: to PC not supported */ case 2: /* USP */ - tcg_gen_mov_i32(cpu_usp, val); - tcg_gen_movcond_i32(TCG_COND_NE, cpu_sp, - cpu_psw_u, z, cpu_usp, cpu_sp); + if (FIELD_EX32(ctx->tb_flags, PSW, U)) { + tcg_gen_mov_i32(cpu_sp, val); + } else { + tcg_gen_mov_i32(cpu_usp, val); + } break; case 3: /* FPSW */ gen_helper_set_fpsw(cpu_env, val); @@ -382,10 +391,11 @@ static void move_to_cr(DisasContext *ctx, TCGv val, int cr) tcg_gen_mov_i32(cpu_bpc, val); break; case 10: /* ISP */ - tcg_gen_mov_i32(cpu_isp, val); - /* if PSW.U is 0, copy isp to r0 */ - tcg_gen_movcond_i32(TCG_COND_EQ, cpu_sp, - cpu_psw_u, z, cpu_isp, cpu_sp); + if (FIELD_EX32(ctx->tb_flags, PSW, U)) { + tcg_gen_mov_i32(cpu_isp, val); + } else { + tcg_gen_mov_i32(cpu_sp, val); + } break; case 11: /* FINTV */ tcg_gen_mov_i32(cpu_fintv, val); @@ -398,7 +408,6 @@ static void move_to_cr(DisasContext *ctx, TCGv val, int cr) "Unimplement control register %d", cr); break; } - tcg_temp_free(z); } static void push(TCGv val) @@ -626,10 +635,6 @@ static bool trans_POPC(DisasContext *ctx, arg_POPC *a) val = tcg_temp_new(); pop(val); move_to_cr(ctx, val, a->cr); - if (a->cr == 0 && is_privileged(ctx, 0)) { - /* PSW.I may be updated here. exit TB. */ - ctx->base.is_jmp = DISAS_UPDATE; - } tcg_temp_free(val); return true; } @@ -682,7 +687,7 @@ static bool trans_PUSHC(DisasContext *ctx, arg_PUSHC *a) { TCGv val; val = tcg_temp_new(); - move_from_cr(val, a->cr, ctx->pc); + move_from_cr(ctx, val, a->cr, ctx->pc); push(val); tcg_temp_free(val); return true; @@ -2160,7 +2165,12 @@ static inline void clrsetpsw(DisasContext *ctx, int cb, int val) ctx->base.is_jmp = DISAS_UPDATE; break; case PSW_U: - tcg_gen_movi_i32(cpu_psw_u, val); + if (FIELD_EX32(ctx->tb_flags, PSW, U) != val) { + ctx->tb_flags = FIELD_DP32(ctx->tb_flags, PSW, U, val); + tcg_gen_movi_i32(cpu_psw_u, val); + tcg_gen_mov_i32(val ? cpu_isp : cpu_usp, cpu_sp); + tcg_gen_mov_i32(cpu_sp, val ? cpu_usp : cpu_isp); + } break; default: qemu_log_mask(LOG_GUEST_ERROR, "Invalid distination %d", cb); @@ -2200,9 +2210,6 @@ static bool trans_MVTC_i(DisasContext *ctx, arg_MVTC_i *a) imm = tcg_const_i32(a->imm); move_to_cr(ctx, imm, a->cr); - if (a->cr == 0 && is_privileged(ctx, 0)) { - ctx->base.is_jmp = DISAS_UPDATE; - } tcg_temp_free(imm); return true; } @@ -2211,16 +2218,13 @@ static bool trans_MVTC_i(DisasContext *ctx, arg_MVTC_i *a) static bool trans_MVTC_r(DisasContext *ctx, arg_MVTC_r *a) { move_to_cr(ctx, cpu_regs[a->rs], a->cr); - if (a->cr == 0 && is_privileged(ctx, 0)) { - ctx->base.is_jmp = DISAS_UPDATE; - } return true; } /* mvfc rs, rd */ static bool trans_MVFC(DisasContext *ctx, arg_MVFC *a) { - move_from_cr(cpu_regs[a->rd], a->cr, ctx->pc); + move_from_cr(ctx, cpu_regs[a->rd], a->cr, ctx->pc); return true; } @@ -2281,7 +2285,7 @@ static bool trans_INT(DisasContext *ctx, arg_INT *a) static bool trans_WAIT(DisasContext *ctx, arg_WAIT *a) { if (is_privileged(ctx, 1)) { - tcg_gen_addi_i32(cpu_pc, cpu_pc, 2); + tcg_gen_movi_i32(cpu_pc, ctx->base.pc_next); gen_helper_wait(cpu_env); } return true; @@ -2292,6 +2296,7 @@ static void rx_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs) CPURXState *env = cs->env_ptr; DisasContext *ctx = container_of(dcbase, DisasContext, base); ctx->env = env; + ctx->tb_flags = ctx->base.tb->flags; } static void rx_tr_tb_start(DisasContextBase *dcbase, CPUState *cs) diff --git a/tests/qapi-schema/alternate-array.err b/tests/qapi-schema/alternate-array.err index b1aa1f4e8d..e69de29bb2 100644 --- a/tests/qapi-schema/alternate-array.err +++ b/tests/qapi-schema/alternate-array.err @@ -1,2 +0,0 @@ -alternate-array.json: In alternate 'Alt': -alternate-array.json:5: 'data' member 'two' cannot be an array diff --git a/tests/qapi-schema/alternate-array.json b/tests/qapi-schema/alternate-array.json index f241aac122..b878a2db77 100644 --- a/tests/qapi-schema/alternate-array.json +++ b/tests/qapi-schema/alternate-array.json @@ -1,5 +1,3 @@ -# we do not allow array branches in alternates -# TODO: should we support this? { 'struct': 'One', 'data': { 'name': 'str' } } { 'alternate': 'Alt', diff --git a/tests/qapi-schema/alternate-array.out b/tests/qapi-schema/alternate-array.out index e69de29bb2..a657d85738 100644 --- a/tests/qapi-schema/alternate-array.out +++ b/tests/qapi-schema/alternate-array.out @@ -0,0 +1,18 @@ +module ./builtin +object q_empty +enum QType + prefix QTYPE + member none + member qnull + member qnum + member qstring + member qdict + member qlist + member qbool +module alternate-array.json +object One + member name: str optional=False +alternate Alt + tag type + case one: One + case two: intList diff --git a/tests/qapi-schema/alternate-conflict-lists.err b/tests/qapi-schema/alternate-conflict-lists.err new file mode 100644 index 0000000000..f3374ec1e7 --- /dev/null +++ b/tests/qapi-schema/alternate-conflict-lists.err @@ -0,0 +1,2 @@ +alternate-conflict-lists.json: In alternate 'Alt': +alternate-conflict-lists.json:4: branch 'two' can't be distinguished from 'one' diff --git a/tests/qapi-schema/alternate-conflict-lists.json b/tests/qapi-schema/alternate-conflict-lists.json new file mode 100644 index 0000000000..a3efd6c501 --- /dev/null +++ b/tests/qapi-schema/alternate-conflict-lists.json @@ -0,0 +1,6 @@ +# Two lists conflict even if their inner types would be compatible +{ 'struct': 'One', + 'data': { 'name': 'str' } } +{ 'alternate': 'Alt', + 'data': { 'one': [ 'int' ], + 'two': [ 'str' ] } } diff --git a/python/qemu/aqmp/py.typed b/tests/qapi-schema/alternate-conflict-lists.out index e69de29bb2..e69de29bb2 100644 --- a/python/qemu/aqmp/py.typed +++ b/tests/qapi-schema/alternate-conflict-lists.out diff --git a/tests/qapi-schema/meson.build b/tests/qapi-schema/meson.build index caf0791ba8..c18dd7d02f 100644 --- a/tests/qapi-schema/meson.build +++ b/tests/qapi-schema/meson.build @@ -11,6 +11,7 @@ schemas = [ 'alternate-conflict-dict.json', 'alternate-conflict-enum-bool.json', 'alternate-conflict-enum-int.json', + 'alternate-conflict-lists.json', 'alternate-conflict-string.json', 'alternate-conflict-bool-string.json', 'alternate-conflict-num-string.json', diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index 43b8697002..ba7302f42b 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -119,6 +119,7 @@ { 'alternate': 'AltEnumNum', 'data': { 'e': 'EnumOne', 'n': 'number' } } { 'alternate': 'AltNumEnum', 'data': { 'n': 'number', 'e': 'EnumOne' } } { 'alternate': 'AltEnumInt', 'data': { 'e': 'EnumOne', 'i': 'int' } } +{ 'alternate': 'AltListInt', 'data': { 'l': ['int'], 'i': 'int' } } # for testing use of 'str' within alternates { 'alternate': 'AltStrObj', 'data': { 's': 'str', 'o': 'TestStruct' } } diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 1f9585fa9b..043d75c655 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -121,6 +121,10 @@ alternate AltEnumInt tag type case e: EnumOne case i: int +alternate AltListInt + tag type + case l: intList + case i: int alternate AltStrObj tag type case s: str diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index fe10a6cf05..33a44671aa 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -37,9 +37,8 @@ import unittest from contextlib import contextmanager -from qemu.aqmp.legacy import QEMUMonitorProtocol from qemu.machine import qtest -from qemu.qmp import QMPMessage +from qemu.qmp.legacy import QMPMessage, QEMUMonitorProtocol from qemu.utils import VerboseProcessError # Use this logger for logging messages directly from the iotests module diff --git a/tests/qemu-iotests/tests/mirror-top-perms b/tests/qemu-iotests/tests/mirror-top-perms index 6ac8d5efcc..8bca592708 100755 --- a/tests/qemu-iotests/tests/mirror-top-perms +++ b/tests/qemu-iotests/tests/mirror-top-perms @@ -22,7 +22,6 @@ import os from qemu.machine import machine -from qemu.qmp import QMPConnectError import iotests from iotests import change_log_level, qemu_img @@ -98,15 +97,13 @@ class TestMirrorTopPerms(iotests.QMPTestCase): self.vm_b.add_blockdev(f'file,node-name=drive0,filename={source}') self.vm_b.add_device('virtio-blk,drive=drive0,share-rw=on') try: - # Silence AQMP errors temporarily. - # TODO: Remove this and just allow the errors to be logged when - # AQMP fully replaces QMP. - with change_log_level('qemu.aqmp'): + # Silence QMP logging errors temporarily. + with change_log_level('qemu.qmp'): self.vm_b.launch() print('ERROR: VM B launched successfully, ' 'this should not have happened') - except (QMPConnectError, machine.VMLaunchFailure): - assert 'Is another process using the image' in self.vm_b.get_log() + except machine.VMLaunchFailure as exc: + assert 'Is another process using the image' in exc.output result = self.vm.qmp('block-job-cancel', device='mirror') diff --git a/tests/unit/test-qobject-input-visitor.c b/tests/unit/test-qobject-input-visitor.c index aed08eaebc..14329dabcf 100644 --- a/tests/unit/test-qobject-input-visitor.c +++ b/tests/unit/test-qobject-input-visitor.c @@ -775,6 +775,7 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data, AltEnumNum *aen; AltNumEnum *ans; AltEnumInt *asi; + AltListInt *ali; /* Parsing an int */ @@ -801,6 +802,12 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data, g_assert_cmpint(asi->u.i, ==, 42); qapi_free_AltEnumInt(asi); + v = visitor_input_test_init(data, "42"); + visit_type_AltListInt(v, NULL, &ali, &error_abort); + g_assert_cmpint(ali->type, ==, QTYPE_QNUM); + g_assert_cmpint(ali->u.i, ==, 42); + qapi_free_AltListInt(ali); + /* Parsing a double */ v = visitor_input_test_init(data, "42.5"); @@ -826,6 +833,37 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data, qapi_free_AltEnumInt(asi); } +static void test_visitor_in_alternate_list(TestInputVisitorData *data, + const void *unused) +{ + intList *item; + Visitor *v; + AltListInt *ali; + int i; + + v = visitor_input_test_init(data, "[ 42, 43, 44 ]"); + visit_type_AltListInt(v, NULL, &ali, &error_abort); + g_assert(ali != NULL); + + g_assert_cmpint(ali->type, ==, QTYPE_QLIST); + for (i = 0, item = ali->u.l; item; item = item->next, i++) { + g_assert_cmpint(item->value, ==, 42 + i); + } + + qapi_free_AltListInt(ali); + ali = NULL; + + /* An empty list is valid */ + v = visitor_input_test_init(data, "[]"); + visit_type_AltListInt(v, NULL, &ali, &error_abort); + g_assert(ali != NULL); + + g_assert_cmpint(ali->type, ==, QTYPE_QLIST); + g_assert(!ali->u.l); + qapi_free_AltListInt(ali); + ali = NULL; +} + static void input_visitor_test_add(const char *testpath, const void *user_data, void (*test_func)(TestInputVisitorData *data, @@ -1187,6 +1225,8 @@ int main(int argc, char **argv) NULL, test_visitor_in_wrong_type); input_visitor_test_add("/visitor/input/alternate-number", NULL, test_visitor_in_alternate_number); + input_visitor_test_add("/visitor/input/alternate-list", + NULL, test_visitor_in_alternate_list); input_visitor_test_add("/visitor/input/fail/struct", NULL, test_visitor_in_fail_struct); input_visitor_test_add("/visitor/input/fail/struct-nested", |