fuzz: use qemu_get_exec_dir
[qemu.git] / python / qemu / qmp.py
1 """ QEMU Monitor Protocol Python class """
2 # Copyright (C) 2009, 2010 Red Hat Inc.
3 #
4 # Authors:
5 # Luiz Capitulino <lcapitulino@redhat.com>
6 #
7 # This work is licensed under the terms of the GNU GPL, version 2. See
8 # the COPYING file in the top-level directory.
9
10 import json
11 import errno
12 import socket
13 import logging
14 from typing import (
15 Any,
16 cast,
17 Dict,
18 Optional,
19 TextIO,
20 Type,
21 Tuple,
22 Union,
23 )
24 from types import TracebackType
25
26
27 # QMPMessage is a QMP Message of any kind.
28 # e.g. {'yee': 'haw'}
29 #
30 # QMPReturnValue is the inner value of return values only.
31 # {'return': {}} is the QMPMessage,
32 # {} is the QMPReturnValue.
33 QMPMessage = Dict[str, Any]
34 QMPReturnValue = Dict[str, Any]
35
36 InternetAddrT = Tuple[str, str]
37 UnixAddrT = str
38 SocketAddrT = Union[InternetAddrT, UnixAddrT]
39
40
41 class QMPError(Exception):
42 """
43 QMP base exception
44 """
45
46
47 class QMPConnectError(QMPError):
48 """
49 QMP connection exception
50 """
51
52
53 class QMPCapabilitiesError(QMPError):
54 """
55 QMP negotiate capabilities exception
56 """
57
58
59 class QMPTimeoutError(QMPError):
60 """
61 QMP timeout exception
62 """
63
64
65 class QMPProtocolError(QMPError):
66 """
67 QMP protocol error; unexpected response
68 """
69
70
71 class QMPResponseError(QMPError):
72 """
73 Represents erroneous QMP monitor reply
74 """
75 def __init__(self, reply: QMPMessage):
76 try:
77 desc = reply['error']['desc']
78 except KeyError:
79 desc = reply
80 super().__init__(desc)
81 self.reply = reply
82
83
84 class QEMUMonitorProtocol:
85 """
86 Provide an API to connect to QEMU via QEMU Monitor Protocol (QMP) and then
87 allow to handle commands and events.
88 """
89
90 #: Logger object for debugging messages
91 logger = logging.getLogger('QMP')
92
93 def __init__(self, address, server=False, nickname=None):
94 """
95 Create a QEMUMonitorProtocol class.
96
97 @param address: QEMU address, can be either a unix socket path (string)
98 or a tuple in the form ( address, port ) for a TCP
99 connection
100 @param server: server mode listens on the socket (bool)
101 @raise OSError on socket connection errors
102 @note No connection is established, this is done by the connect() or
103 accept() methods
104 """
105 self.__events = []
106 self.__address = address
107 self.__sock = self.__get_sock()
108 self.__sockfile: Optional[TextIO] = None
109 self._nickname = nickname
110 if self._nickname:
111 self.logger = logging.getLogger('QMP').getChild(self._nickname)
112 if server:
113 self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
114 self.__sock.bind(self.__address)
115 self.__sock.listen(1)
116
117 def __get_sock(self):
118 if isinstance(self.__address, tuple):
119 family = socket.AF_INET
120 else:
121 family = socket.AF_UNIX
122 return socket.socket(family, socket.SOCK_STREAM)
123
124 def __negotiate_capabilities(self):
125 greeting = self.__json_read()
126 if greeting is None or "QMP" not in greeting:
127 raise QMPConnectError
128 # Greeting seems ok, negotiate capabilities
129 resp = self.cmd('qmp_capabilities')
130 if resp and "return" in resp:
131 return greeting
132 raise QMPCapabilitiesError
133
134 def __json_read(self, only_event=False):
135 assert self.__sockfile is not None
136 while True:
137 data = self.__sockfile.readline()
138 if not data:
139 return None
140 # By definition, any JSON received from QMP is a QMPMessage,
141 # and we are asserting only at static analysis time that it
142 # has a particular shape.
143 resp: QMPMessage = json.loads(data)
144 if 'event' in resp:
145 self.logger.debug("<<< %s", resp)
146 self.__events.append(resp)
147 if not only_event:
148 continue
149 return resp
150
151 def __get_events(self, wait=False):
152 """
153 Check for new events in the stream and cache them in __events.
154
155 @param wait (bool): block until an event is available.
156 @param wait (float): If wait is a float, treat it as a timeout value.
157
158 @raise QMPTimeoutError: If a timeout float is provided and the timeout
159 period elapses.
160 @raise QMPConnectError: If wait is True but no events could be
161 retrieved or if some other error occurred.
162 """
163
164 # Check for new events regardless and pull them into the cache:
165 self.__sock.setblocking(False)
166 try:
167 self.__json_read()
168 except OSError as err:
169 if err.errno == errno.EAGAIN:
170 # No data available
171 pass
172 self.__sock.setblocking(True)
173
174 # Wait for new events, if needed.
175 # if wait is 0.0, this means "no wait" and is also implicitly false.
176 if not self.__events and wait:
177 if isinstance(wait, float):
178 self.__sock.settimeout(wait)
179 try:
180 ret = self.__json_read(only_event=True)
181 except socket.timeout:
182 raise QMPTimeoutError("Timeout waiting for event")
183 except:
184 raise QMPConnectError("Error while reading from socket")
185 if ret is None:
186 raise QMPConnectError("Error while reading from socket")
187 self.__sock.settimeout(None)
188
189 def __enter__(self):
190 # Implement context manager enter function.
191 return self
192
193 def __exit__(self,
194 # pylint: disable=duplicate-code
195 # see https://github.com/PyCQA/pylint/issues/3619
196 exc_type: Optional[Type[BaseException]],
197 exc_val: Optional[BaseException],
198 exc_tb: Optional[TracebackType]) -> None:
199 # Implement context manager exit function.
200 self.close()
201
202 def connect(self, negotiate=True):
203 """
204 Connect to the QMP Monitor and perform capabilities negotiation.
205
206 @return QMP greeting dict, or None if negotiate is false
207 @raise OSError on socket connection errors
208 @raise QMPConnectError if the greeting is not received
209 @raise QMPCapabilitiesError if fails to negotiate capabilities
210 """
211 self.__sock.connect(self.__address)
212 self.__sockfile = self.__sock.makefile(mode='r')
213 if negotiate:
214 return self.__negotiate_capabilities()
215 return None
216
217 def accept(self, timeout=15.0):
218 """
219 Await connection from QMP Monitor and perform capabilities negotiation.
220
221 @param timeout: timeout in seconds (nonnegative float number, or
222 None). The value passed will set the behavior of the
223 underneath QMP socket as described in [1].
224 Default value is set to 15.0.
225 @return QMP greeting dict
226 @raise OSError on socket connection errors
227 @raise QMPConnectError if the greeting is not received
228 @raise QMPCapabilitiesError if fails to negotiate capabilities
229
230 [1]
231 https://docs.python.org/3/library/socket.html#socket.socket.settimeout
232 """
233 self.__sock.settimeout(timeout)
234 self.__sock, _ = self.__sock.accept()
235 self.__sockfile = self.__sock.makefile(mode='r')
236 return self.__negotiate_capabilities()
237
238 def cmd_obj(self, qmp_cmd: QMPMessage) -> QMPMessage:
239 """
240 Send a QMP command to the QMP Monitor.
241
242 @param qmp_cmd: QMP command to be sent as a Python dict
243 @return QMP response as a Python dict
244 """
245 self.logger.debug(">>> %s", qmp_cmd)
246 self.__sock.sendall(json.dumps(qmp_cmd).encode('utf-8'))
247 resp = self.__json_read()
248 if resp is None:
249 raise QMPConnectError("Unexpected empty reply from server")
250 self.logger.debug("<<< %s", resp)
251 return resp
252
253 def cmd(self, name, args=None, cmd_id=None):
254 """
255 Build a QMP command and send it to the QMP Monitor.
256
257 @param name: command name (string)
258 @param args: command arguments (dict)
259 @param cmd_id: command id (dict, list, string or int)
260 """
261 qmp_cmd = {'execute': name}
262 if args:
263 qmp_cmd['arguments'] = args
264 if cmd_id:
265 qmp_cmd['id'] = cmd_id
266 return self.cmd_obj(qmp_cmd)
267
268 def command(self, cmd, **kwds):
269 """
270 Build and send a QMP command to the monitor, report errors if any
271 """
272 ret = self.cmd(cmd, kwds)
273 if 'error' in ret:
274 raise QMPResponseError(ret)
275 if 'return' not in ret:
276 raise QMPProtocolError(
277 "'return' key not found in QMP response '{}'".format(str(ret))
278 )
279 return cast(QMPReturnValue, ret['return'])
280
281 def pull_event(self, wait=False):
282 """
283 Pulls a single event.
284
285 @param wait (bool): block until an event is available.
286 @param wait (float): If wait is a float, treat it as a timeout value.
287
288 @raise QMPTimeoutError: If a timeout float is provided and the timeout
289 period elapses.
290 @raise QMPConnectError: If wait is True but no events could be
291 retrieved or if some other error occurred.
292
293 @return The first available QMP event, or None.
294 """
295 self.__get_events(wait)
296
297 if self.__events:
298 return self.__events.pop(0)
299 return None
300
301 def get_events(self, wait=False):
302 """
303 Get a list of available QMP events.
304
305 @param wait (bool): block until an event is available.
306 @param wait (float): If wait is a float, treat it as a timeout value.
307
308 @raise QMPTimeoutError: If a timeout float is provided and the timeout
309 period elapses.
310 @raise QMPConnectError: If wait is True but no events could be
311 retrieved or if some other error occurred.
312
313 @return The list of available QMP events.
314 """
315 self.__get_events(wait)
316 return self.__events
317
318 def clear_events(self):
319 """
320 Clear current list of pending events.
321 """
322 self.__events = []
323
324 def close(self):
325 """
326 Close the socket and socket file.
327 """
328 if self.__sock:
329 self.__sock.close()
330 if self.__sockfile:
331 self.__sockfile.close()
332
333 def settimeout(self, timeout):
334 """
335 Set the socket timeout.
336
337 @param timeout (float): timeout in seconds, or None.
338 @note This is a wrap around socket.settimeout
339 """
340 self.__sock.settimeout(timeout)
341
342 def get_sock_fd(self):
343 """
344 Get the socket file descriptor.
345
346 @return The file descriptor number.
347 """
348 return self.__sock.fileno()
349
350 def is_scm_available(self):
351 """
352 Check if the socket allows for SCM_RIGHTS.
353
354 @return True if SCM_RIGHTS is available, otherwise False.
355 """
356 return self.__sock.family == socket.AF_UNIX