diff --git a/qubes/device_protocol.py b/qubes/device_protocol.py index 0b5f4b56a..232848ea2 100644 --- a/qubes/device_protocol.py +++ b/qubes/device_protocol.py @@ -858,18 +858,23 @@ def _load_classes(cls, bus: str): subclass_id = None for line in pciids.readlines(): line = line.rstrip() + if not line: + continue + first_char = line[0] if ( - line.startswith("\t\t") + first_char == "\t" + and len(line) >= 2 + and line[1] == "\t" and class_id is not None and subclass_id is not None ): progif_id, _, progif_name = line[2:].split(" ", 2) result[class_id + subclass_id + progif_id] = progif_name - elif line.startswith("\t") and class_id: + elif first_char == "\t" and class_id: subclass_id, _, subclass_name = line[1:].split(" ", 2) # store both prog-if specific entry and generic one result[class_id + subclass_id + "**"] = subclass_name - elif line.startswith("C "): + elif first_char == "C" and len(line) >= 2 and line[1] == " ": _, class_id, _, class_name = line.split(" ", 3) result[class_id + "****"] = class_name subclass_id = None diff --git a/qubes/events.py b/qubes/events.py index a676652fb..5783ff6e3 100644 --- a/qubes/events.py +++ b/qubes/events.py @@ -160,7 +160,7 @@ def _fire_event(self, event, kwargs, pre_event=False): h_func for h_name, h_func_set in handlers_dict.items() for h_func in h_func_set - if fnmatch.fnmatch(event, h_name) + if fnmatch.fnmatchcase(event, h_name) ] for func in sorted( handlers, diff --git a/qubes/ext/block.py b/qubes/ext/block.py index 3181ee281..0b185f295 100644 --- a/qubes/ext/block.py +++ b/qubes/ext/block.py @@ -230,11 +230,8 @@ def attachment(self) -> Optional[QubesVM]: def _is_attached_to(self, vm): xml_desc = lxml.etree.fromstring(vm.libvirt_domain.XMLDesc()) - for disk in xml_desc.findall("devices/disk"): - info = _try_get_block_device_info(vm.app, disk) - if not info: - continue - backend_domain, port_id = info + for disk in xml_desc.findall("devices/disk[source][@type='block']"): + backend_domain, port_id = _try_get_block_device_info(vm.app, disk) if backend_domain.name != self.backend_domain.name: continue @@ -288,18 +285,13 @@ def _sanitize( def _try_get_block_device_info(app, disk): - if disk.get("type") != "block": - return None - dev_path_node = disk.find("source") - if dev_path_node is None: - return None - backend_domain_node = disk.find("backenddomain") if backend_domain_node is not None: backend_domain = app.domains[backend_domain_node.get("name")] else: backend_domain = app.domains[0] + dev_path_node = disk.find("source") dev_path = dev_path_node.get("dev") if dev_path.startswith("/dev/"): @@ -357,11 +349,10 @@ def get_device_attachments(vm_): xml_desc = lxml.etree.fromstring(vm.libvirt_domain.XMLDesc()) - for disk in xml_desc.findall("devices/disk"): - info = _try_get_block_device_info(vm.app, disk) - if not info: - continue - backend_domain, port_id = info + for disk in xml_desc.findall("devices/disk[source][@type='block']"): + backend_domain, port_id = _try_get_block_device_info( + vm.app, disk + ) if backend_domain != vm_: continue @@ -434,29 +425,16 @@ def on_device_list_attached(self, vm, event, **kwargs): system_disks = SYSTEM_DISKS_DOM0_KERNEL xml_desc = lxml.etree.fromstring(vm.libvirt_domain.XMLDesc()) - for disk in xml_desc.findall("devices/disk"): - if disk.get("type") != "block": - continue - dev_path_node = disk.find("source") - if dev_path_node is None: - continue - dev_path = dev_path_node.get("dev") - + for disk in xml_desc.findall( + "devices/disk[source][target][@type='block']" + ): + backend_domain, port_id = _try_get_block_device_info(vm.app, disk) target_node = disk.find("target") - if target_node is not None: - frontend_dev = target_node.get("dev") - if not frontend_dev: - continue - if frontend_dev in system_disks: - continue - else: + frontend_dev = target_node.get("dev") + if not frontend_dev: + continue + if frontend_dev in system_disks: continue - - backend_domain_node = disk.find("backenddomain") - if backend_domain_node is not None: - backend_domain = vm.app.domains[backend_domain_node.get("name")] - else: - backend_domain = vm.app.domains[0] options = {} read_only_node = disk.find("readonly") @@ -465,15 +443,9 @@ def on_device_list_attached(self, vm, event, **kwargs): else: options["read-only"] = "no" options["frontend-dev"] = frontend_dev - if disk.get("device") != "disk": - options["devtype"] = disk.get("device") - - if dev_path.startswith("/dev/"): - port_id = dev_path[len("/dev/") :] - else: - port_id = dev_path - - port_id = port_id.replace("/", "_") + devtype = disk.get("device") + if devtype != "disk": + options["devtype"] = devtype yield BlockDevice(Port(backend_domain, port_id, "block")), options diff --git a/qubes/ext/pci.py b/qubes/ext/pci.py index bc06d44f9..5a6c03b2b 100644 --- a/qubes/ext/pci.py +++ b/qubes/ext/pci.py @@ -64,15 +64,24 @@ def load_pci_classes(): subclass_id = None for line in pciids.readlines(): line = line.rstrip() - if line.startswith("\t\t") and class_id and subclass_id: + if not line: + continue + first_char = line[0] + if ( + first_char == "\t" + and len(line) >= 2 + and line[1] == "\t" + and class_id is not None + and subclass_id is not None + ): progif_id, _, class_name = line[2:].split(" ", 2) result[class_id + subclass_id + progif_id] = class_name - elif line.startswith("\t") and class_id: + elif first_char == "\t" and class_id: subclass_id, _, class_name = line[1:].split(" ", 2) # store both prog-if specific entry and generic one result[class_id + subclass_id + "00"] = class_name result[class_id + subclass_id] = class_name - elif line.startswith("C "): + elif first_char == "C" and len(line) >= 2 and line[1] == " ": _, class_id, _, class_name = line.split(" ", 3) result[class_id + "0000"] = class_name result[class_id + "00"] = class_name @@ -355,16 +364,11 @@ def __init__(self): @qubes.ext.handler("device-list:pci") def on_device_list_pci(self, vm, event): # pylint: disable=unused-argument - # only dom0 expose PCI devices + # only dom0 exposes PCI devices if vm.qid != 0: return - for dev in vm.app.vmm.libvirt_conn.listAllDevices(): - if "pci" not in dev.listCaps(): - continue - - xml_desc = lxml.etree.fromstring(dev.XMLDesc()) - libvirt_name = xml_desc.findtext("name") + for libvirt_name in vm.app.vmm.libvirt_conn.listDevices("pci"): try: yield PCIDevice( Port(backend_domain=vm, port_id=None, devclass="pci"), @@ -388,14 +392,13 @@ def on_device_list_attached(self, vm, event, **kwargs): return xml_desc = lxml.etree.fromstring(vm.libvirt_domain.XMLDesc()) - for hostdev in xml_desc.findall("devices/hostdev"): - if hostdev.get("type") != "pci": - continue - address = hostdev.find("source/address") - segment = address.get("domain")[2:] - bus = address.get("bus")[2:] - device = address.get("slot")[2:] - function = address.get("function")[2:] + for hostdev in xml_desc.findall( + "devices/hostdev[@type='pci']/source/address" + ): + segment = hostdev.get("domain")[2:] + bus = hostdev.get("bus")[2:] + device = hostdev.get("slot")[2:] + function = hostdev.get("function")[2:] libvirt_name = "pci_{segment}_{bus}_{device}_{function}".format( segment=segment, diff --git a/qubes/storage/__init__.py b/qubes/storage/__init__.py index 3bbe41701..7fedcf686 100644 --- a/qubes/storage/__init__.py +++ b/qubes/storage/__init__.py @@ -657,24 +657,28 @@ def attach(self, volume, rw=False): def _is_already_attached(self, volume): """Checks if the given volume is already attached""" + # TODO: ben: cache parsed_xml = lxml.etree.fromstring(self.vm.libvirt_domain.XMLDesc()) - disk_sources = parsed_xml.xpath("//domain/devices/disk/source") - for source in disk_sources: - if source.get("dev") == "/dev/%s" % volume.vid: - return True + disks = parsed_xml.xpath( + "//domain/devices/disk/source[@dev='/dev/$vid']", vid=volume.vid + ) + if disks: + return True return False def detach(self, volume): """Detach a volume from domain""" + # TODO: ben: cache parsed_xml = lxml.etree.fromstring(self.vm.libvirt_domain.XMLDesc()) - disks = parsed_xml.xpath("//domain/devices/disk") - for disk in disks: - source = disk.xpath("source")[0] - if source.get("dev") == "/dev/%s" % volume.vid: - disk_xml = lxml.etree.tostring(disk, encoding="utf-8") - self.vm.libvirt_domain.detachDevice(disk_xml) - return - raise StoragePoolException("Volume {!r} is not attached".format(volume)) + disks = parsed_xml.xpath( + "//domain/devices/disk/source[@dev='/dev/$vid']", vid=volume.vid + ) + if not disks: + raise StoragePoolException( + "Volume {!r} is not attached".format(volume) + ) + disk_xml = lxml.etree.tostring(disks[0], encoding="utf-8") + self.vm.libvirt_domain.detachDevice(disk_xml) @property def kernels_dir(self): @@ -878,11 +882,12 @@ def unused_frontend(self): @property def used_frontends(self): """Used device names""" + # TODO: ben: cache xml = self.vm.libvirt_domain.XMLDesc() parsed_xml = lxml.etree.fromstring(xml) return { target.get("dev", None) - for target in parsed_xml.xpath("//domain/devices/disk/target") + for target in parsed_xml.xpath("//domain/devices/disk/target[@dev]") } async def export(self, volume): diff --git a/qubes/tests/devices_pci.py b/qubes/tests/devices_pci.py index 5fe480355..8fe36d7d9 100644 --- a/qubes/tests/devices_pci.py +++ b/qubes/tests/devices_pci.py @@ -52,8 +52,8 @@ def __hash__(self): PCI_XML = """ - pci_{}_00_14_0 - /sys/devices/pci{}:00/{}:00:14.0 + pci_{address}_00_14_0 + /sys/devices/pci{address}:00/{address}:00:14.0 computer pciback @@ -196,25 +196,11 @@ def test_000_unsupported_device(self): **{ "vmm.offline_mode": False, "vmm.libvirt_conn.nodeDeviceLookupByName.return_value": mock.Mock( - **{"XMLDesc.return_value": PCI_XML.format(*["0000"] * 3)} + **{"XMLDesc.return_value": PCI_XML.format(address="0000")} ), - "vmm.libvirt_conn.listAllDevices.return_value": [ - mock.Mock( - **{ - "XMLDesc.return_value": PCI_XML.format( - *["0000"] * 3 - ), - "listCaps.return_value": ["pci"], - } - ), - mock.Mock( - **{ - "XMLDesc.return_value": PCI_XML.format( - *["10000"] * 3 - ), - "listCaps.return_value": ["pci"], - } - ), + "vmm.libvirt_conn.listDevices.return_value": [ + "pci_0000_00_14_0", + "pci_10000_00_14_0", ], } ) diff --git a/qubes/utils.py b/qubes/utils.py index 73618f464..5a25a6a92 100644 --- a/qubes/utils.py +++ b/qubes/utils.py @@ -21,6 +21,7 @@ # import asyncio +import functools import hashlib import logging import re @@ -177,6 +178,7 @@ def urandom(size): return hashlib.sha512(rand).digest() +@functools.cache def get_entry_point_one(group, name): epoints = tuple(importlib.metadata.entry_points(group=group, name=name)) if not epoints: diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index b1f1b3007..3f7208b0b 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -1030,15 +1030,14 @@ def attached_volumes(self): result = [] xml_desc = self.libvirt_domain.XMLDesc() xml = lxml.etree.fromstring(xml_desc) - for disk in xml.xpath("//domain/devices/disk"): - if disk.find("backenddomain") is not None: - pool_name = "p_%s" % disk.find("backenddomain").get("name") - pool = self.app.pools[pool_name] - vid = disk.find("source").get("dev").split("/dev/")[1] - for volume in pool.volumes: - if volume.vid == vid: - result += [volume] - break + for disk in xml.xpath("//domain/devices/disk[source][backenddomain]"): + pool_name = "p_%s" % disk.find("backenddomain").get("name") + pool = self.app.pools[pool_name] + vid = disk.find("source").get("dev").split("/dev/")[1] + for volume in pool.volumes: + if volume.vid == vid: + result += [volume] + break return result + list(self.volumes.values())