Skip to content

Commit ee26ce6

Browse files
committed
Merge remote-tracking branch 'remotes/jsnow/tags/python-pull-request' into staging
Pull request # gpg: Signature made Tue 12 Oct 2021 02:36:07 PM PDT # gpg: using RSA key F9B7ABDBBCACDF95BE76CBD07DEF8106AAFC390E # gpg: Good signature from "John Snow (John Huston) <[email protected]>" [full] * remotes/jsnow/tags/python-pull-request: python, iotests: remove socket_scm_helper python/qmp: add send_fd_scm directly to QEMUMonitorProtocol python/qmp: clear events on get_events() call python/aqmp: Disable logging messages by default python/aqmp: Reduce severity of EOFError-caused loop terminations python/aqmp: Add dict conversion method to Greeting object python/aqmp: add send_fd_scm python/aqmp: Return cleared events from EventListener.clear() python/aqmp: add .empty() method to EventListener python/aqmp: add greeting property to QMPClient Signed-off-by: Richard Henderson <[email protected]>
2 parents 8be1d4e + c163c72 commit ee26ce6

File tree

15 files changed

+85
-216
lines changed

15 files changed

+85
-216
lines changed

python/qemu/aqmp/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
# This work is licensed under the terms of the GNU GPL, version 2. See
2222
# the COPYING file in the top-level directory.
2323

24+
import logging
2425
import warnings
2526

2627
from .error import AQMPError
@@ -41,6 +42,9 @@
4142

4243
warnings.warn(_WMSG, FutureWarning)
4344

45+
# Suppress logging unless an application engages it.
46+
logging.getLogger('qemu.aqmp').addHandler(logging.NullHandler())
47+
4448

4549
# The order of these fields impact the Sphinx documentation order.
4650
__all__ = (

python/qemu/aqmp/events.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,25 +556,36 @@ async def get(self) -> Message:
556556
"""
557557
return await self._queue.get()
558558

559-
def clear(self) -> None:
559+
def empty(self) -> bool:
560+
"""
561+
Return `True` if there are no pending events.
562+
"""
563+
return self._queue.empty()
564+
565+
def clear(self) -> List[Message]:
560566
"""
561567
Clear this listener of all pending events.
562568
563569
Called when an `EventListener` is being unregistered, this clears the
564570
pending FIFO queue synchronously. It can be also be used to
565571
manually clear any pending events, if desired.
566572
573+
:return: The cleared events, if any.
574+
567575
.. warning::
568576
Take care when discarding events. Cleared events will be
569577
silently tossed on the floor. All events that were ever
570578
accepted by this listener are visible in `history()`.
571579
"""
580+
events = []
572581
while True:
573582
try:
574-
self._queue.get_nowait()
583+
events.append(self._queue.get_nowait())
575584
except asyncio.QueueEmpty:
576585
break
577586

587+
return events
588+
578589
def __aiter__(self) -> AsyncIterator[Message]:
579590
return self
580591

python/qemu/aqmp/models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
# pylint: disable=too-few-public-methods
99

1010
from collections import abc
11+
import copy
1112
from typing import (
1213
Any,
14+
Dict,
1315
Mapping,
1416
Optional,
1517
Sequence,
@@ -66,6 +68,17 @@ def __init__(self, raw: Mapping[str, Any]):
6668
self._check_member('QMP', abc.Mapping, "JSON object")
6769
self.QMP = QMPGreeting(self._raw['QMP'])
6870

71+
def _asdict(self) -> Dict[str, object]:
72+
"""
73+
For compatibility with the iotests sync QMP wrapper.
74+
75+
The legacy QMP interface needs Greetings as a garden-variety Dict.
76+
77+
This interface is private in the hopes that it will be able to
78+
be dropped again in the near-future. Caller beware!
79+
"""
80+
return dict(copy.deepcopy(self._raw))
81+
6982

7083
class QMPGreeting(Model):
7184
"""

python/qemu/aqmp/protocol.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -721,8 +721,11 @@ async def _bh_loop_forever(self, async_fn: _TaskFN, name: str) -> None:
721721
self.logger.debug("Task.%s: cancelled.", name)
722722
return
723723
except BaseException as err:
724-
self.logger.error("Task.%s: %s",
725-
name, exception_summary(err))
724+
self.logger.log(
725+
logging.INFO if isinstance(err, EOFError) else logging.ERROR,
726+
"Task.%s: %s",
727+
name, exception_summary(err)
728+
)
726729
self.logger.debug("Task.%s: failure:\n%s\n",
727730
name, pretty_traceback())
728731
self._schedule_disconnect()

python/qemu/aqmp/qmp_client.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import asyncio
1111
import logging
12+
import socket
13+
import struct
1214
from typing import (
1315
Dict,
1416
List,
@@ -224,6 +226,11 @@ def __init__(self, name: Optional[str] = None) -> None:
224226
'asyncio.Queue[QMPClient._PendingT]'
225227
] = {}
226228

229+
@property
230+
def greeting(self) -> Optional[Greeting]:
231+
"""The `Greeting` from the QMP server, if any."""
232+
return self._greeting
233+
227234
@upper_half
228235
async def _establish_session(self) -> None:
229236
"""
@@ -619,3 +626,23 @@ async def execute(self, cmd: str,
619626
"""
620627
msg = self.make_execute_msg(cmd, arguments, oob=oob)
621628
return await self.execute_msg(msg)
629+
630+
@upper_half
631+
@require(Runstate.RUNNING)
632+
def send_fd_scm(self, fd: int) -> None:
633+
"""
634+
Send a file descriptor to the remote via SCM_RIGHTS.
635+
"""
636+
assert self._writer is not None
637+
sock = self._writer.transport.get_extra_info('socket')
638+
639+
if sock.family != socket.AF_UNIX:
640+
raise AQMPError("Sending file descriptors requires a UNIX socket.")
641+
642+
# Void the warranty sticker.
643+
# Access to sendmsg in asyncio is scheduled for removal in Python 3.11.
644+
sock = sock._sock # pylint: disable=protected-access
645+
sock.sendmsg(
646+
[b' '],
647+
[(socket.SOL_SOCKET, socket.SCM_RIGHTS, struct.pack('@i', fd))]
648+
)

python/qemu/machine/machine.py

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ def __init__(self,
9898
name: Optional[str] = None,
9999
base_temp_dir: str = "/var/tmp",
100100
monitor_address: Optional[SocketAddrT] = None,
101-
socket_scm_helper: Optional[str] = None,
102101
sock_dir: Optional[str] = None,
103102
drain_console: bool = False,
104103
console_log: Optional[str] = None,
@@ -113,7 +112,6 @@ def __init__(self,
113112
@param name: prefix for socket and log file names (default: qemu-PID)
114113
@param base_temp_dir: default location where temp files are created
115114
@param monitor_address: address for QMP monitor
116-
@param socket_scm_helper: helper program, required for send_fd_scm()
117115
@param sock_dir: where to create socket (defaults to base_temp_dir)
118116
@param drain_console: (optional) True to drain console socket to buffer
119117
@param console_log: (optional) path to console log file
@@ -134,7 +132,6 @@ def __init__(self,
134132
self._base_temp_dir = base_temp_dir
135133
self._sock_dir = sock_dir or self._base_temp_dir
136134
self._log_dir = log_dir
137-
self._socket_scm_helper = socket_scm_helper
138135

139136
if monitor_address is not None:
140137
self._monitor_address = monitor_address
@@ -213,48 +210,22 @@ def add_fd(self: _T, fd: int, fdset: int,
213210
def send_fd_scm(self, fd: Optional[int] = None,
214211
file_path: Optional[str] = None) -> int:
215212
"""
216-
Send an fd or file_path to socket_scm_helper.
213+
Send an fd or file_path to the remote via SCM_RIGHTS.
217214
218-
Exactly one of fd and file_path must be given.
219-
If it is file_path, the helper will open that file and pass its own fd.
215+
Exactly one of fd and file_path must be given. If it is
216+
file_path, the file will be opened read-only and the new file
217+
descriptor will be sent to the remote.
220218
"""
221-
# In iotest.py, the qmp should always use unix socket.
222-
assert self._qmp.is_scm_available()
223-
if self._socket_scm_helper is None:
224-
raise QEMUMachineError("No path to socket_scm_helper set")
225-
if not os.path.exists(self._socket_scm_helper):
226-
raise QEMUMachineError("%s does not exist" %
227-
self._socket_scm_helper)
228-
229-
# This did not exist before 3.4, but since then it is
230-
# mandatory for our purpose
231-
if hasattr(os, 'set_inheritable'):
232-
os.set_inheritable(self._qmp.get_sock_fd(), True)
233-
if fd is not None:
234-
os.set_inheritable(fd, True)
235-
236-
fd_param = ["%s" % self._socket_scm_helper,
237-
"%d" % self._qmp.get_sock_fd()]
238-
239219
if file_path is not None:
240220
assert fd is None
241-
fd_param.append(file_path)
221+
with open(file_path, "rb") as passfile:
222+
fd = passfile.fileno()
223+
self._qmp.send_fd_scm(fd)
242224
else:
243225
assert fd is not None
244-
fd_param.append(str(fd))
245-
246-
proc = subprocess.run(
247-
fd_param,
248-
stdin=subprocess.DEVNULL,
249-
stdout=subprocess.PIPE,
250-
stderr=subprocess.STDOUT,
251-
check=False,
252-
close_fds=False,
253-
)
254-
if proc.stdout:
255-
LOG.debug(proc.stdout)
226+
self._qmp.send_fd_scm(fd)
256227

257-
return proc.returncode
228+
return 0
258229

259230
@staticmethod
260231
def _remove_if_exists(path: str) -> None:
@@ -631,7 +602,6 @@ def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
631602
events = self._qmp.get_events(wait=wait)
632603
events.extend(self._events)
633604
del self._events[:]
634-
self._qmp.clear_events()
635605
return events
636606

637607
@staticmethod

python/qemu/machine/qtest.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ def __init__(self,
115115
wrapper: Sequence[str] = (),
116116
name: Optional[str] = None,
117117
base_temp_dir: str = "/var/tmp",
118-
socket_scm_helper: Optional[str] = None,
119118
sock_dir: Optional[str] = None,
120119
qmp_timer: Optional[float] = None):
121120
# pylint: disable=too-many-arguments
@@ -126,7 +125,6 @@ def __init__(self,
126125
sock_dir = base_temp_dir
127126
super().__init__(binary, args, wrapper=wrapper, name=name,
128127
base_temp_dir=base_temp_dir,
129-
socket_scm_helper=socket_scm_helper,
130128
sock_dir=sock_dir, qmp_timer=qmp_timer)
131129
self._qtest: Optional[QEMUQtestProtocol] = None
132130
self._qtest_path = os.path.join(sock_dir, name + "-qtest.sock")

python/qemu/qmp/__init__.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import json
2222
import logging
2323
import socket
24+
import struct
2425
from types import TracebackType
2526
from typing import (
2627
Any,
@@ -361,7 +362,7 @@ def pull_event(self,
361362

362363
def get_events(self, wait: bool = False) -> List[QMPMessage]:
363364
"""
364-
Get a list of available QMP events.
365+
Get a list of available QMP events and clear all pending events.
365366
366367
@param wait (bool): block until an event is available.
367368
@param wait (float): If wait is a float, treat it as a timeout value.
@@ -374,7 +375,9 @@ def get_events(self, wait: bool = False) -> List[QMPMessage]:
374375
@return The list of available QMP events.
375376
"""
376377
self.__get_events(wait)
377-
return self.__events
378+
events = self.__events
379+
self.__events = []
380+
return events
378381

379382
def clear_events(self) -> None:
380383
"""
@@ -406,18 +409,14 @@ def settimeout(self, timeout: Optional[float]) -> None:
406409
raise ValueError(msg)
407410
self.__sock.settimeout(timeout)
408411

409-
def get_sock_fd(self) -> int:
412+
def send_fd_scm(self, fd: int) -> None:
410413
"""
411-
Get the socket file descriptor.
412-
413-
@return The file descriptor number.
414+
Send a file descriptor to the remote via SCM_RIGHTS.
414415
"""
415-
return self.__sock.fileno()
416+
if self.__sock.family != socket.AF_UNIX:
417+
raise RuntimeError("Can't use SCM_RIGHTS on non-AF_UNIX socket.")
416418

417-
def is_scm_available(self) -> bool:
418-
"""
419-
Check if the socket allows for SCM_RIGHTS.
420-
421-
@return True if SCM_RIGHTS is available, otherwise False.
422-
"""
423-
return self.__sock.family == socket.AF_UNIX
419+
self.__sock.sendmsg(
420+
[b' '],
421+
[(socket.SOL_SOCKET, socket.SCM_RIGHTS, struct.pack('@i', fd))]
422+
)

python/qemu/qmp/qmp_shell.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,6 @@ def read_exec_command(self) -> bool:
381381
if cmdline == '':
382382
for event in self.get_events():
383383
print(event)
384-
self.clear_events()
385384
return True
386385

387386
return self._execute_cmd(cmdline)

tests/Makefile.include

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@ check-acceptance: check-venv $(TESTS_RESULTS_DIR) get-vm-images
148148
check:
149149

150150
ifeq ($(CONFIG_TOOLS)$(CONFIG_POSIX),yy)
151-
QEMU_IOTESTS_HELPERS-$(CONFIG_LINUX) = tests/qemu-iotests/socket_scm_helper$(EXESUF)
152151
check: check-block
153152
export PYTHON
154153
check-block: $(SRC_PATH)/tests/check-block.sh qemu-img$(EXESUF) \

0 commit comments

Comments
 (0)