From 990cfd8ab9f0559d4918b790acb694a0e0743b3b Mon Sep 17 00:00:00 2001 From: Bahtiar `kalkin-` Gadimov Date: Sat, 8 Apr 2017 04:09:46 +0200 Subject: [PATCH] Migrate DeviceCollection to new API - Use PersistentCollection as _set() - attach/detach expect DeviceAssignment as parater - attached(persistent=True) is now persistent() - attached() returns all attached devices - assigned() returns all attached device assignments `# modified: templates/libvirt/xen.xml Signed-off-by: Bahtiar `kalkin-` Gadimov --- qubes/devices.py | 121 ++++++++++++++++++++++++-------------- qubes/vm/qubesvm.py | 2 +- templates/libvirt/xen.xml | 4 +- 3 files changed, 79 insertions(+), 48 deletions(-) diff --git a/qubes/devices.py b/qubes/devices.py index 5b204b23..f088966b 100644 --- a/qubes/devices.py +++ b/qubes/devices.py @@ -55,6 +55,10 @@ class DeviceAlreadyAttached(qubes.exc.QubesException, KeyError): '''Trying to attach already attached device''' pass +class WrongAssignment(qubes.exc.QubesException, KeyError): + '''Trying to attach non permanent assignment to a halted vm''' + pass + class DeviceAssignment(object): # pylint: disable=too-few-public-methods ''' Maps a device to a frontend_domain. ''' @@ -138,87 +142,113 @@ class DeviceCollection(object): def __init__(self, vm, class_): self._vm = vm self._class = class_ - self._set = set() + self._set = PersistentCollection() self.devclass = qubes.utils.get_entry_point_one( 'qubes.devices', self._class) - def attach(self, device, persistent=True): + def attach(self, device_assignment: DeviceAssignment): '''Attach (add) device to domain. :param DeviceInfo device: device object ''' - if device in self.attached(): + if not device_assignment.frontend_domain: + device_assignment.frontend_domain = self._vm + + if device_assignment.frontend_domain != self._vm: + raise WrongAssignment( + "Trying to attach DeviceAssignment belonging to other domain") + if not device_assignment.persistent and self._vm.is_halted(): + raise WrongAssignment("Devices can only be attached persistent to " + "a halted vm") + device = self._device(device_assignment) + if device in self.assignments(): raise DeviceAlreadyAttached( - 'device {!r} of class {} already attached to {!r}'.format( + 'device {!s} of class {} already attached to {!s}'.format( device, self._class, self._vm)) self._vm.fire_event_pre('device-pre-attach:'+self._class, device=device) - if persistent: - self._set.add(device) + if device_assignment.persistent: + self._set.add(device_assignment) self._vm.fire_event('device-attach:' + self._class, device=device) - - def detach(self, device, persistent=True): + def detach(self, device_assignment: DeviceAssignment): '''Detach (remove) device from domain. :param DeviceInfo device: device object ''' - if device not in self.attached(): + if not device_assignment.frontend_domain: + device_assignment.frontend_domain = self._vm + + if device_assignment in self._set and not self._vm.is_halted(): + raise WrongAssignment( + "Can not remove a persistent attachment from a non halted vm") + if device_assignment not in self.assignments(): raise DeviceNotAttached( 'device {!s} of class {} not attached to {!s}'.format( - device, self._class, self._vm)) + device_assignment.ident, self._class, self._vm)) + + device = self._device(device_assignment) self._vm.fire_event_pre('device-pre-detach:'+self._class, device=device) - if persistent: - self._set.remove(device) + if device in self._set: + device_assignment.persistent = True + self._set.discard(device_assignment) + self._vm.fire_event('device-detach:' + self._class, device=device) - def attached(self, persistent=None): - '''List devices which are (or may be) attached to this vm + def attached(self): + '''List devices which are (or may be) attached to this vm ''' + return self._vm.fire_event('device-list-attached:' + self._class) or [] + + def persistent(self): + ''' Devices persistently attached and safe to access before libvirt + bootstrap. + ''' + return [self._device(a) for a in self._set] + + def assignments(self, persistent=None): + '''List assignments for devices which are (or may be) attached to the + vm. Devices may be attached persistently (so they are included in :file:`qubes.xml`) or not. Device can also be in :file:`qubes.xml`, but be temporarily detached. - :param bool persistent: only include devices which are (or are not) \ - attached persistently - None means both + :param bool persistent: only include devices which are or are not + attached persistently. ''' - seen = self._set.copy() - # ask for really attached devices only when requested not only - # persistent ones - if persistent is not True: - attached = self._vm.fire_event( - 'device-list-attached:' + self._class, - persistent=persistent) - for device in attached: - device_persistent = device in self._set - if persistent is not None and device_persistent != persistent: - continue - assert device.frontend_domain == self._vm, \ - '{!r} != {!r}'.format(device.frontend_domain, self._vm) - - yield device - - try: - seen.remove(device) - except KeyError: - pass - - if persistent is False: - return - - for device in seen: - # get fresh object - may contain updated information - device = device.backend_domain.devices[self._class][device.ident] - yield device + devices = self._vm.fire_event('device-list-attached:' + self._class, + persistent=persistent) + result = [] + for dev in devices: + if dev in self._set and persistent is False: + continue + elif dev in self._set: + result.append(self._set.get(dev)) + elif dev not in self._set and persistent is True: + continue + else: + result.append( + DeviceAssignment(backend_domain=dev.backend_domain, + ident=dev.ident, options=dev.options, + frontend_domain=self._vm)) + if persistent is not False and len(result) == 0: + result.extend(self._set) + return result def available(self): '''List devices exposed by this vm''' devices = self._vm.fire_event('device-list:' + self._class) return devices + def _device(self, assignment: DeviceAssignment): + ''' Helper method for geting a `qubes.devices.DeviceInfo` object from + `qubes.devices.DeviceAssignment`. ''' + + return assignment.backend_domain.devices[self._class][assignment.ident] + def __iter__(self): return iter(self.available()) @@ -259,9 +289,10 @@ class DeviceManager(dict): class DeviceInfo(object): + ''' Holds all information about a device ''' # pylint: disable=too-few-public-methods def __init__(self, backend_domain, ident, description=None, - frontend_domain=None, **kwargs): + frontend_domain=None, options = None, **kwargs): #: domain providing this device self.backend_domain = backend_domain #: device identifier (unique for given domain and device type) diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index 803e0755..2a666f09 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -429,7 +429,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): # CORE2: swallowed uses_default_kernelopts kernelopts = qubes.property('kernelopts', type=str, load_stage=4, default=(lambda self: qubes.config.defaults['kernelopts_pcidevs'] - if list(self.devices['pci'].attached(persistent=True)) + if list(self.devices['pci'].persistent()) else self.template.kernelopts if hasattr(self, 'template') else qubes.config.defaults['kernelopts']), ls_width=30, diff --git a/templates/libvirt/xen.xml b/templates/libvirt/xen.xml index b5d2ae00..c6480cac 100644 --- a/templates/libvirt/xen.xml +++ b/templates/libvirt/xen.xml @@ -32,7 +32,7 @@ {% endif %} - {% if vm.devices['pci'].attached(persistent=True) | list + {% if vm.devices['pci'].persistent() | list and vm.features.get('pci-e820-host', True) %} @@ -106,7 +106,7 @@ {% include 'libvirt/devices/net.xml' with context %} {% endif %} - {% for device in vm.devices.pci.attached(persistent=True) %} + {% for device in vm.devices.pci.persistent() %} {% include 'libvirt/devices/pci.xml' %} {% endfor %}