Skip to content

Commit be663a0

Browse files
committed
qubes/ext/utils: provide Port for device_class
1 parent af61341 commit be663a0

File tree

7 files changed

+127
-90
lines changed

7 files changed

+127
-90
lines changed

qubes/device_protocol.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ class Port:
261261
def __init__(
262262
self,
263263
backend_domain: Optional[QubesVM],
264-
port_id: str,
264+
port_id: Optional[str],
265265
devclass: Optional[str],
266266
):
267267
self.__backend_domain = backend_domain
@@ -1302,7 +1302,7 @@ def devices(self) -> List[DeviceInfo]:
13021302
dev = self.backend_domain.devices[self.devclass][self.port_id]
13031303
if (
13041304
isinstance(dev, UnknownDevice)
1305-
or self.device_id in (dev.device_id, "*")
1305+
or (dev and self.device_id in (dev.device_id, "*"))
13061306
):
13071307
return [dev]
13081308
if self.device_id == "0000:0000::?******":

qubes/ext/block.py

+22-14
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from qubes.ext import utils
3535
from qubes.storage import Storage
3636
from qubes.vm.qubesvm import QubesVM
37+
from qubes.devices import Port
3738

3839
name_re = re.compile(r"\A[a-z0-9-]{1,12}\Z")
3940
device_re = re.compile(r"\A[a-z0-9/-]{1,64}\Z")
@@ -47,16 +48,20 @@
4748

4849

4950
class BlockDevice(qubes.device_protocol.DeviceInfo):
50-
def __init__(self, backend_domain, port_id):
51-
port = qubes.device_protocol.Port(
52-
backend_domain=backend_domain, port_id=port_id, devclass="block"
53-
)
51+
52+
def __init__(self, port: qubes.device_protocol.Port):
53+
if port.devclass != "block":
54+
raise qubes.exc.QubesValueError(
55+
f"Incompatible device class for input port: {port.devclass}"
56+
)
57+
58+
# init parent class
5459
super().__init__(port)
5560

5661
# lazy loading
57-
self._mode = None
58-
self._size = None
59-
self._interface_num = None
62+
self._mode: Optional[str] = None
63+
self._size: Optional[int] = None
64+
self._interface_num: Optional[str] = None
6065

6166
@property
6267
def name(self):
@@ -192,8 +197,9 @@ def parent_device(self) -> Optional[qubes.device_protocol.DeviceInfo]:
192197
if not parent_ident:
193198
return None
194199
try:
195-
self._parent = self.backend_domain.devices[
196-
devclass][parent_ident]
200+
self._parent = self.backend_domain.devices[devclass][
201+
parent_ident
202+
]
197203
except KeyError:
198204
self._parent = qubes.device_protocol.UnknownDevice(
199205
qubes.device_protocol.Port(
@@ -250,7 +256,7 @@ def device_id(self) -> str:
250256
# device interface number (not partition)
251257
self_id = self._interface_num
252258
else:
253-
self_id = self._get_possible_partition_number()
259+
self_id = str(self._get_possible_partition_number())
254260
return f"{parent_identity}:{self_id}"
255261

256262
def _get_possible_partition_number(self) -> Optional[int]:
@@ -372,7 +378,9 @@ def device_get(vm, port_id):
372378
)
373379
if not untrusted_qubes_device_attrs:
374380
return None
375-
return BlockDevice(vm, port_id)
381+
return BlockDevice(
382+
Port(backend_domain=vm, port_id=port_id, devclass="block")
383+
)
376384

377385
@qubes.ext.handler("device-list:block")
378386
def on_device_list_block(self, vm, event):
@@ -462,7 +470,7 @@ def on_device_list_attached(self, vm, event, **kwargs):
462470

463471
port_id = port_id.replace("/", "_")
464472

465-
yield (BlockDevice(backend_domain, port_id), options)
473+
yield BlockDevice(Port(backend_domain, port_id, "block")), options
466474

467475
@staticmethod
468476
def find_unused_frontend(vm, devtype="disk"):
@@ -638,13 +646,13 @@ async def on_domain_shutdown(self, vm, event, **_kwargs):
638646
for dev_id, front_vm in self.devices_cache[domain.name].items():
639647
if front_vm is None:
640648
continue
641-
dev = BlockDevice(vm, dev_id)
649+
dev = BlockDevice(Port(vm, dev_id, "block"))
642650
vm.fire_event("device-removed:block", port=dev.port)
643651
await self.detach_and_notify(front_vm, dev.port)
644652
continue
645653
for dev_id, front_vm in self.devices_cache[domain.name].items():
646654
if front_vm == vm:
647-
dev = BlockDevice(vm, dev_id)
655+
dev = BlockDevice(Port(vm, dev_id, "block"))
648656
asyncio.ensure_future(
649657
front_vm.fire_event_async(
650658
"device-detach:block", port=dev.port

qubes/ext/pci.py

+38-24
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import qubes.device_protocol
3535
import qubes.devices
3636
import qubes.ext
37+
from qubes.device_protocol import Port
3738

3839
#: cache of PCI device classes
3940
pci_classes = None
@@ -161,31 +162,35 @@ class PCIDevice(qubes.device_protocol.DeviceInfo):
161162
r"(?P<function>[0-9a-f]+)\Z"
162163
)
163164

164-
def __init__(self, backend_domain, port_id, libvirt_name=None):
165+
def __init__(self, port: Port, libvirt_name=None):
165166
if libvirt_name:
166167
dev_match = self._libvirt_regex.match(libvirt_name)
167168
if not dev_match:
168169
raise UnsupportedDevice(libvirt_name)
169170
port_id = "{bus}_{device}.{function}".format(
170171
**dev_match.groupdict()
171172
)
173+
port = Port(
174+
backend_domain=port.backend_domain,
175+
port_id=port_id,
176+
devclass="pci",
177+
)
172178

173-
port = qubes.device_protocol.Port(
174-
backend_domain=backend_domain, port_id=port_id, devclass="pci"
175-
)
176179
super().__init__(port)
177180

178-
dev_match = self.regex.match(port_id)
181+
dev_match = self.regex.match(port.port_id)
179182
if not dev_match:
180-
raise ValueError("Invalid device identifier: {!r}".format(port_id))
183+
raise ValueError(
184+
"Invalid device identifier: {!r}".format(port.port_id)
185+
)
181186

182187
for group in self.regex.groupindex:
183188
setattr(self, group, dev_match.group(group))
184189

185190
# lazy loading
186-
self._description = None
187-
self._vendor_id = None
188-
self._product_id = None
191+
self._description: Optional[str] = None
192+
self._vendor_id: Optional[str] = None
193+
self._product_id: Optional[str] = None
189194

190195
@property
191196
def vendor(self) -> str:
@@ -247,7 +252,6 @@ def interfaces(self) -> List[qubes.device_protocol.DeviceInterface]:
247252
]
248253
return self._interfaces or []
249254

250-
251255
@property
252256
def parent_device(self) -> Optional[qubes.device_protocol.DeviceInfo]:
253257
"""
@@ -300,12 +304,12 @@ def _load_desc(self) -> Dict[str, str]:
300304
unknown = "unknown"
301305
result = {
302306
"vendor": unknown,
303-
"vendor ID": "0000",
304-
"product": unknown,
305-
"product ID": "0000",
306-
"manufacturer": unknown,
307-
"name": unknown,
308-
"serial": unknown
307+
"vendor ID": "0000",
308+
"product": unknown,
309+
"product ID": "0000",
310+
"manufacturer": unknown,
311+
"name": unknown,
312+
"serial": unknown,
309313
}
310314

311315
if (
@@ -324,11 +328,12 @@ def _load_desc(self) -> Dict[str, str]:
324328
# Data successfully loaded, cache these values
325329
hostdev_xml = lxml.etree.fromstring(hostdev_details.XMLDesc())
326330

327-
328-
self._vendor = result["vendor"] = hostdev_xml.findtext(
329-
"capability/vendor") or unknown
330-
self._product = result["product"] = hostdev_xml.findtext(
331-
"capability/product") or unknown
331+
self._vendor = result["vendor"] = (
332+
hostdev_xml.findtext("capability/vendor") or unknown
333+
)
334+
self._product = result["product"] = (
335+
hostdev_xml.findtext("capability/product") or unknown
336+
)
332337

333338
vendor = hostdev_xml.xpath("//vendor/@id") or []
334339
if vendor and isinstance(vendor, List):
@@ -365,7 +370,10 @@ def on_device_list_pci(self, vm, event):
365370
xml_desc = lxml.etree.fromstring(dev.XMLDesc())
366371
libvirt_name = xml_desc.findtext("name")
367372
try:
368-
yield PCIDevice(vm, None, libvirt_name=libvirt_name)
373+
yield PCIDevice(
374+
Port(backend_domain=vm, port_id=None, devclass="pci"),
375+
libvirt_name=libvirt_name,
376+
)
369377
except UnsupportedDevice:
370378
if libvirt_name not in unsupported_devices_warned:
371379
vm.log.warning("Unsupported device: %s", libvirt_name)
@@ -397,7 +405,13 @@ def on_device_list_attached(self, vm, event, **kwargs):
397405
device=device,
398406
function=function,
399407
)
400-
yield (PCIDevice(vm.app.domains[0], port_id), {})
408+
yield PCIDevice(
409+
Port(
410+
backend_domain=vm.app.domains[0],
411+
port_id=port_id,
412+
devclass="pci",
413+
)
414+
), {}
401415

402416
@qubes.ext.handler("device-pre-attach:pci")
403417
def on_device_pre_attached_pci(self, vm, event, device, options):
@@ -538,4 +552,4 @@ def on_app_close(self, app, event):
538552
@functools.lru_cache(maxsize=None)
539553
def _cache_get(vm, port_id):
540554
"""Caching wrapper around `PCIDevice(vm, port_id)`."""
541-
return PCIDevice(vm, port_id)
555+
return PCIDevice(Port(vm, port_id, "pci"))

qubes/ext/utils.py

+16-8
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import qubes.ext
2626
from qrexec.server import call_socket_service
2727
from qubes import device_protocol
28-
from qubes.device_protocol import VirtualDevice
28+
from qubes.device_protocol import VirtualDevice, Port
2929

3030
SOCKET_PATH = "/var/run/qubes"
3131

@@ -48,25 +48,33 @@ def device_list_change(
4848

4949
# send events about devices detached/attached outside by themselves
5050
for port_id, front_vm in detached.items():
51-
dev = device_class(vm, port_id)
52-
ext.ensure_detach(front_vm, dev.port)
51+
device = device_class(
52+
Port(backend_domain=vm, port_id=port_id, devclass=devclass)
53+
)
54+
ext.ensure_detach(front_vm, device.port)
5355
asyncio.ensure_future(
5456
front_vm.fire_event_async(
55-
f"device-detach:{devclass}", port=dev.port
57+
f"device-detach:{devclass}", port=device.port
5658
)
5759
)
5860
for port_id in removed:
59-
device = device_class(vm, port_id)
61+
device = device_class(
62+
Port(backend_domain=vm, port_id=port_id, devclass=devclass)
63+
)
6064
vm.fire_event(f"device-removed:{devclass}", port=device.port)
6165
for port_id in added:
62-
device = device_class(vm, port_id)
66+
device = device_class(
67+
Port(backend_domain=vm, port_id=port_id, devclass=devclass)
68+
)
6369
vm.fire_event(f"device-added:{devclass}", device=device)
6470
for port_id, front_vm in attached.items():
65-
dev = device_class(vm, port_id)
71+
device = device_class(
72+
Port(backend_domain=vm, port_id=port_id, devclass=devclass)
73+
)
6674
# options are unknown, device already attached
6775
asyncio.ensure_future(
6876
front_vm.fire_event_async(
69-
f"device-attach:{devclass}", device=dev, options={}
77+
f"device-attach:{devclass}", device=device, options={}
7078
)
7179
)
7280

qubes/tests/app.py

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def setUp(self):
7070
self.app = TestApp()
7171
self.app.vmm = mock.Mock()
7272
self.qubes_host = qubes.app.QubesHost(self.app)
73+
self.maxDiff = None
7374

7475
def test_000_get_vm_stats_single(self):
7576
self.app.vmm.configure_mock(**{

0 commit comments

Comments
 (0)