From a52cb6bb9179caa5e13d7651019bd181b0b35561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Sun, 20 Oct 2019 12:21:09 +0200 Subject: [PATCH] Make PEP8 happier --- qubes/app.py | 351 ++++++++++++++++----------------- qubes/ext/gui.py | 12 +- qubes/vm/qubesvm.py | 459 ++++++++++++++++++++++++-------------------- 3 files changed, 436 insertions(+), 386 deletions(-) diff --git a/qubes/app.py b/qubes/app.py index b5e650e2..65a70946 100644 --- a/qubes/app.py +++ b/qubes/app.py @@ -66,8 +66,11 @@ import qubes.vm import qubes.vm.adminvm import qubes.vm.qubesvm import qubes.vm.templatevm + + # pylint: enable=wrong-import-position + class VirDomainWrapper: # pylint: disable=too-few-public-methods @@ -103,6 +106,7 @@ class VirDomainWrapper: if self._reconnect_if_dead(): return getattr(self._vm, attrname)(*args, **kwargs) raise + return wrapper @@ -145,24 +149,25 @@ class VirConnectWrapper: return self._wrap_domain( getattr(self._conn, attrname)(*args, **kwargs)) raise + return wrapper class VMMConnection: - '''Connection to Virtual Machine Manager (libvirt)''' + """Connection to Virtual Machine Manager (libvirt)""" def __init__(self, offline_mode=None, libvirt_reconnect_cb=None): - ''' + """ :param offline_mode: enable/disable offline mode; default is to enable when running in chroot as root, otherwise disable :param libvirt_reconnect_cb: callable to be called when connection to libvirt is re-established; the callback is called with old connection as argument - ''' + """ if offline_mode is None: offline_mode = bool(os.getuid() == 0 and - os.stat('/') != os.stat('/proc/1/root/.')) + os.stat('/') != os.stat('/proc/1/root/.')) self._offline_mode = offline_mode self._libvirt_reconnect_cb = libvirt_reconnect_cb @@ -172,16 +177,16 @@ class VMMConnection: @property def offline_mode(self): - '''Check or enable offline mode (do not actually connect to vmm)''' + """Check or enable offline mode (do not actually connect to vmm)""" return self._offline_mode def _libvirt_error_handler(self, ctx, error): pass def init_vmm_connection(self): - '''Initialise connection + """Initialise connection - This method is automatically called when getting''' + This method is automatically called when getting""" if self._libvirt_conn is not None: # Already initialized return @@ -201,16 +206,16 @@ class VMMConnection: @property def libvirt_conn(self): - '''Connection to libvirt''' + """Connection to libvirt""" self.init_vmm_connection() return self._libvirt_conn @property def xs(self): - '''Connection to Xen Store + """Connection to Xen Store This property in available only when running on Xen. - ''' + """ # XXX what about the case when we run under KVM, # but xen modules are importable? @@ -223,10 +228,10 @@ class VMMConnection: @property def xc(self): - '''Connection to Xen + """Connection to Xen This property in available only when running on Xen. - ''' + """ # XXX what about the case when we run under KVM, # but xen modules are importable? @@ -249,11 +254,11 @@ class VMMConnection: class QubesHost: - '''Basic information about host machine + """Basic information about host machine :param qubes.Qubes app: Qubes application context (must have \ :py:attr:`Qubes.vmm` attribute defined) - ''' + """ def __init__(self, app): self.app = app @@ -261,7 +266,6 @@ class QubesHost: self._total_mem = None self._physinfo = None - def _fetch(self): if self._no_cpus is not None: return @@ -280,20 +284,18 @@ class QubesHost: except NotImplementedError: pass - @property def memory_total(self): - '''Total memory, in kbytes''' + """Total memory, in kbytes""" if self.app.vmm.offline_mode: - return 2**64-1 + return 2 ** 64 - 1 self._fetch() return self._total_mem - @property def no_cpus(self): - '''Number of CPUs''' + """Number of CPUs""" if self.app.vmm.offline_mode: return 42 @@ -301,21 +303,19 @@ class QubesHost: self._fetch() return self._no_cpus - def get_free_xen_memory(self): - '''Get free memory from Xen's physinfo. + """Get free memory from Xen's physinfo. :raises NotImplementedError: when not under Xen - ''' + """ try: self._physinfo = self.app.xc.physinfo() except AttributeError: raise NotImplementedError('This function requires Xen hypervisor') return int(self._physinfo['free_memory']) - def get_vm_stats(self, previous_time=None, previous=None, only_vm=None): - '''Measure cpu usage for all domains at once. + """Measure cpu usage for all domains at once. If previous measurements are given, CPU usage will be given in percents of time. Otherwise only absolute value (seconds). @@ -339,7 +339,7 @@ class QubesHost: :param only_vm: get measurements only for this VM :raises NotImplementedError: when not under Xen - ''' + """ if (previous_time is None) != (previous is None): raise ValueError( @@ -382,65 +382,59 @@ class QubesHost: current[domid]['cpu_usage'] = \ int(current[domid]['cpu_usage_raw'] / vcpus) - return (current_time, current) + return current_time, current class VMCollection: - '''A collection of Qubes VMs + """A collection of Qubes VMs VMCollection supports ``in`` operator. You may test for ``qid``, ``name`` and whole VM object's presence. Iterating over VMCollection will yield machine objects. - ''' + """ def __init__(self, app): self.app = app self._dict = dict() - def close(self): del self.app self._dict.clear() del self._dict - def __repr__(self): return '<{} {!r}>'.format( self.__class__.__name__, list(sorted(self.keys()))) - def items(self): - '''Iterate over ``(qid, vm)`` pairs''' + """Iterate over ``(qid, vm)`` pairs""" for qid in self.qids(): yield (qid, self[qid]) - def qids(self): - '''Iterate over all qids + """Iterate over all qids qids are sorted by numerical order. - ''' + """ return iter(sorted(self._dict.keys())) keys = qids - def names(self): - '''Iterate over all names + """Iterate over all names names are sorted by lexical order. - ''' + """ return iter(sorted(vm.name for vm in self._dict.values())) - def vms(self): - '''Iterate over all machines + """Iterate over all machines vms are sorted by qid. - ''' + """ return iter(sorted(self._dict.values())) @@ -448,12 +442,13 @@ class VMCollection: values = vms def add(self, value, _enable_events=True): - '''Add VM to collection + """Add VM to collection :param qubes.vm.BaseVM value: VM to add + :param _enable_events: :raises TypeError: when value is of wrong type :raises ValueError: when there is already VM which has equal ``qid`` - ''' + """ # this violates duck typing, but is needed # for VMProperty to function correctly @@ -463,11 +458,11 @@ class VMCollection: if value.qid in self: raise ValueError('This collection already holds VM that has ' - 'qid={!r} ({!r})'.format(value.qid, self[value.qid])) + 'qid={!r} ({!r})'.format(value.qid, + self[value.qid])) if value.name in self: - raise ValueError('A VM named {!s} already exists' - .format(value.name)) + .format(value.name)) self._dict[value.qid] = value if _enable_events: @@ -518,24 +513,21 @@ class VMCollection: return any((key in (vm, vm.qid, vm.name)) for vm in self) - def __len__(self): return len(self._dict) - def get_vms_based_on(self, template): template = self[template] return set(vm for vm in self - if hasattr(vm, 'template') and vm.template == template) - + if hasattr(vm, 'template') and vm.template == template) def get_vms_connected_to(self, netvm): - new_vms = set([self[netvm]]) + new_vms = {self[netvm]} dependent_vms = set() # Dependency resolving only makes sense on NetVM (or derivative) -# if not self[netvm_qid].is_netvm(): -# return set([]) + # if not self[netvm_qid].is_netvm(): + # return set([]) while new_vms: cur_vm = new_vms.pop() @@ -543,12 +535,11 @@ class VMCollection: if vm in dependent_vms: continue dependent_vms.add(vm) -# if vm.is_netvm(): + # if vm.is_netvm(): new_vms.add(vm) return dependent_vms - # XXX with Qubes Admin Api this will probably lead to race condition # whole process of creating and adding should be synchronised def get_new_unused_qid(self): @@ -558,25 +549,25 @@ class VMCollection: return i raise LookupError("Cannot find unused qid!") - def get_new_unused_dispid(self): for _ in range(int(qubes.config.max_dispid ** 0.5)): dispid = random.SystemRandom().randrange(qubes.config.max_dispid) if not any(getattr(vm, 'dispid', None) == dispid for vm in self): return dispid raise LookupError(( - 'https://xkcd.com/221/', - 'http://dilbert.com/strip/2001-10-25')[random.randint(0, 1)]) + 'https://xkcd.com/221/', + 'http://dilbert.com/strip/2001-10-25')[ + random.randint(0, 1)]) def _default_pool(app): - ''' Default storage pool. + """ Default storage pool. 1. If there is one named 'default', use it. 2. Check if root fs is on LVM thin - use that 3. Look for file(-reflink)-based pool pointing to /var/lib/qubes 4. Fail - ''' + """ if 'default' in app.pools: return app.pools['default'] @@ -595,7 +586,7 @@ def _default_pool(app): if pool.config.get('driver', None) != 'lvm_thin': continue if (pool.config['volume_group'] == root_volume_group and - pool.config['thin_pool'] == root_thin_pool): + pool.config['thin_pool'] == root_thin_pool): return pool # not a thin volume? look for file pools @@ -606,6 +597,7 @@ def _default_pool(app): return pool raise AttributeError('Cannot determine default storage pool') + def _setter_pool(app, prop, value): if isinstance(value, qubes.storage.Pool): return value @@ -613,7 +605,8 @@ def _setter_pool(app, prop, value): return app.pools[value] except KeyError: raise qubes.exc.QubesPropertyValueError(app, prop, value, - 'No such storage pool') + 'No such storage pool') + def _setter_default_netvm(app, prop, value): # skip netvm loop check while loading qubes.xml, to avoid tricky loading @@ -631,13 +624,13 @@ def _setter_default_netvm(app, prop, value): continue if value == vm \ or value in app.domains.get_vms_connected_to(vm): - raise qubes.exc.QubesPropertyValueError(app, prop, value, - 'Network loop on \'{!s}\''.format(vm)) + raise qubes.exc.QubesPropertyValueError( + app, prop, value, 'Network loop on \'{!s}\''.format(vm)) return value class Qubes(qubes.PropertyHolder): - '''Main Qubes application + """Main Qubes application :param str store: path to ``qubes.xml`` @@ -722,88 +715,102 @@ class Qubes(qubes.PropertyHolder): :param pool: Pool object Methods and attributes: - ''' + """ default_netvm = qubes.VMProperty('default_netvm', load_stage=3, - default=None, allow_none=True, - setter=_setter_default_netvm, - doc='''Default NetVM for AppVMs. Initial state is `None`, which means - that AppVMs are not connected to the Internet.''') + default=None, allow_none=True, + setter=_setter_default_netvm, + doc="""Default NetVM for AppVMs. Initial + state is `None`, which means that AppVMs + are not connected to the Internet.""") default_template = qubes.VMProperty('default_template', load_stage=3, - vmclass=qubes.vm.templatevm.TemplateVM, - doc='Default template for new AppVMs', - allow_none=True) + vmclass=qubes.vm.templatevm.TemplateVM, + doc='Default template for new AppVMs', + allow_none=True) updatevm = qubes.VMProperty('updatevm', load_stage=3, - default=None, allow_none=True, - doc='''Which VM to use as `yum` proxy for updating AdminVM and - TemplateVMs''') + default=None, allow_none=True, + doc="""Which VM to use as `yum` proxy for + updating AdminVM and TemplateVMs""") clockvm = qubes.VMProperty('clockvm', load_stage=3, - default=None, allow_none=True, - doc='Which VM to use as NTP proxy for updating AdminVM') + default=None, allow_none=True, + doc='Which VM to use as NTP proxy for updating ' + 'AdminVM') default_kernel = qubes.property('default_kernel', load_stage=3, - doc='Which kernel to use when not overriden in VM') + doc='Which kernel to use when not ' + 'overriden in VM') default_dispvm = qubes.VMProperty('default_dispvm', load_stage=3, - default=None, - doc='Default DispVM base for service calls', allow_none=True) + default=None, + doc='Default DispVM base for service ' + 'calls', + allow_none=True) management_dispvm = qubes.VMProperty('management_dispvm', load_stage=3, - default=None, - doc='Default DispVM base for managing VMs', allow_none=True) + default=None, + doc='Default DispVM base for ' + 'managing VMs', + allow_none=True) default_pool = qubes.property('default_pool', load_stage=3, - default=_default_pool, - setter=_setter_pool, - doc='Default storage pool') + default=_default_pool, + setter=_setter_pool, + doc='Default storage pool') default_pool_private = qubes.property('default_pool_private', load_stage=3, - default=lambda app: app.default_pool, - setter=_setter_pool, - doc='Default storage pool for private volumes') + default=lambda app: app.default_pool, + setter=_setter_pool, + doc='Default storage pool for ' + 'private volumes') default_pool_root = qubes.property('default_pool_root', load_stage=3, - default=lambda app: app.default_pool, - setter=_setter_pool, - doc='Default storage pool for root volumes') + default=lambda app: app.default_pool, + setter=_setter_pool, + doc='Default storage pool for root ' + 'volumes') default_pool_volatile = qubes.property('default_pool_volatile', - load_stage=3, - default=lambda app: app.default_pool, - setter=_setter_pool, - doc='Default storage pool for volatile volumes') + load_stage=3, + default=lambda app: app.default_pool, + setter=_setter_pool, + doc='Default storage pool for ' + 'volatile volumes') default_pool_kernel = qubes.property('default_pool_kernel', load_stage=3, - default=lambda app: app.default_pool, - setter=_setter_pool, - doc='Default storage pool for kernel volumes') + default=lambda app: app.default_pool, + setter=_setter_pool, + doc='Default storage pool for kernel ' + 'volumes') default_qrexec_timeout = qubes.property('default_qrexec_timeout', - load_stage=3, - default=60, - type=int, - doc='''Default time in seconds after which qrexec connection attempt is - deemed failed''') + load_stage=3, + default=60, + type=int, + doc="""Default time in seconds + after which qrexec connection + attempt is deemed failed""") default_shutdown_timeout = qubes.property('default_shutdown_timeout', - load_stage=3, - default=60, - type=int, - doc='''Default time in seconds for VM shutdown to complete''') + load_stage=3, + default=60, + type=int, + doc="""Default time in seconds + for VM shutdown to complete""") stats_interval = qubes.property('stats_interval', - load_stage=3, - default=3, - type=int, - doc='Interval in seconds for VM stats reporting (memory, CPU usage)') + load_stage=3, + default=3, + type=int, + doc='Interval in seconds for VM stats ' + 'reporting (memory, CPU usage)') # TODO #1637 #892 check_updates_vm = qubes.property('check_updates_vm', - type=bool, setter=qubes.property.bool, - load_stage=3, - default=True, - doc='check for updates inside qubes') + type=bool, setter=qubes.property.bool, + load_stage=3, + default=True, + doc='check for updates inside qubes') def __init__(self, store=None, load=True, offline_mode=None, lock=False, - **kwargs): + **kwargs): #: logger instance for logging global messages self.log = logging.getLogger('app') self.log.debug('init() -> %#x', id(self)) @@ -823,7 +830,8 @@ class Qubes(qubes.PropertyHolder): self.pools = {} #: Connection to VMM - self.vmm = VMMConnection(offline_mode=offline_mode, + self.vmm = VMMConnection( + offline_mode=offline_mode, libvirt_reconnect_cb=self.register_event_handlers) #: Information about host system @@ -833,9 +841,10 @@ class Qubes(qubes.PropertyHolder): self._store = store else: self._store = os.environ.get('QUBES_XML_PATH', - os.path.join( - qubes.config.qubes_base_dir, - qubes.config.system_path['qubes_store_filename'])) + os.path.join( + qubes.config.qubes_base_dir, + qubes.config.system_path[ + 'qubes_store_filename'])) super(Qubes, self).__init__(xml=None, **kwargs) @@ -861,7 +870,7 @@ class Qubes(qubes.PropertyHolder): return self._store def _migrate_global_properties(self): - '''Migrate renamed/dropped properties''' + """Migrate renamed/dropped properties""" if self.xml is None: return @@ -893,7 +902,7 @@ class Qubes(qubes.PropertyHolder): # value behind user's back) is worse properties = vm.xml.find('./properties') element = lxml.etree.Element('property', - name='netvm') + name='netvm') element.text = default_fw_netvm.name # manipulate xml directly, before loading netvm # property, to avoid hitting netvm loop detection @@ -905,12 +914,12 @@ class Qubes(qubes.PropertyHolder): node_default_fw_netvm.getparent().remove(node_default_fw_netvm) def load(self, lock=False): - '''Open qubes.xml + """Open qubes.xml :throws EnvironmentError: failure on parsing store :throws xml.parsers.expat.ExpatError: failure on parsing store :raises lxml.etree.XMLSyntaxError: on syntax error in qubes.xml - ''' + """ fh = self._acquire_lock() self.xml = lxml.etree.parse(fh) @@ -970,7 +979,6 @@ class Qubes(qubes.PropertyHolder): if not lock: self._release_lock() - def __xml__(self): element = lxml.etree.Element('qubes') @@ -997,7 +1005,7 @@ class Qubes(qubes.PropertyHolder): return type(self).__name__ def save(self, lock=True): - '''Save all data to qubes.xml + """Save all data to qubes.xml There are several problems with saving :file:`qubes.xml` which must be mitigated: @@ -1009,7 +1017,7 @@ class Qubes(qubes.PropertyHolder): :param bool lock: keep file locked after saving :throws EnvironmentError: failure on saving - ''' + """ if not self.__locked_fh: self._acquire_lock(for_save=True) @@ -1039,11 +1047,10 @@ class Qubes(qubes.PropertyHolder): if not lock: self._release_lock() - def close(self): - '''Deconstruct the object and break circular references + """Deconstruct the object and break circular references - After calling this the object is unusable, not even for saving.''' + After calling this the object is unusable, not even for saving.""" self.log.debug('close() <- %#x', id(self)) for frame in traceback.extract_stack(): @@ -1073,14 +1080,13 @@ class Qubes(qubes.PropertyHolder): if self.__locked_fh: self._release_lock() - def _acquire_lock(self, for_save=False): assert self.__locked_fh is None, 'double lock' while True: try: fd = os.open(self._store, - os.O_RDWR | (os.O_CREAT * int(for_save))) + os.O_RDWR | (os.O_CREAT * int(for_save))) except FileNotFoundError: if not for_save: raise qubes.exc.QubesException( @@ -1116,7 +1122,6 @@ class Qubes(qubes.PropertyHolder): self.__locked_fh = os.fdopen(fd, 'r+b') return self.__locked_fh - def _release_lock(self): assert self.__locked_fh is not None, 'double release' @@ -1125,7 +1130,6 @@ class Qubes(qubes.PropertyHolder): self.__locked_fh.close() self.__locked_fh = None - def load_initial_values(self): self.labels = { 1: qubes.Label(1, '0xcc0000', 'red'), @@ -1181,12 +1185,11 @@ class Qubes(qubes.PropertyHolder): return self - def xml_labels(self): - '''Serialise labels + """Serialise labels :rtype: lxml.etree._Element - ''' + """ labels = lxml.etree.Element('labels') for label in sorted(self.labels.values(), key=lambda labl: labl.index): @@ -1195,14 +1198,14 @@ class Qubes(qubes.PropertyHolder): @staticmethod def get_vm_class(clsname): - '''Find the class for a domain. + """Find the class for a domain. Classes are registered as setuptools' entry points in ``qubes.vm`` group. Any package may supply their own classes. :param str clsname: name of the class :return type: class - ''' + """ try: return qubes.utils.get_entry_point_one( @@ -1213,9 +1216,9 @@ class Qubes(qubes.PropertyHolder): # don't catch TypeError def add_new_vm(self, cls, qid=None, **kwargs): - '''Add new Virtual Machine to collection + """Add new Virtual Machine to collection - ''' + """ if qid is None: qid = self.domains.get_new_unused_qid() @@ -1239,10 +1242,10 @@ class Qubes(qubes.PropertyHolder): return self.domains.add(cls(self, None, qid=qid, **kwargs)) def get_label(self, label): - '''Get label as identified by index or name + """Get label as identified by index or name :throws KeyError: when label is not found - ''' + """ # first search for index, verbatim try: @@ -1290,27 +1293,27 @@ class Qubes(qubes.PropertyHolder): try: pool = self.pools[name] volumes = [(vm, volume) for vm in self.domains - for volume in vm.volumes.values() - if volume.pool is pool] + for volume in vm.volumes.values() + if volume.pool is pool] if volumes: raise qubes.exc.QubesPoolInUseError(pool) prop_suffixes = ['', '_kernel', '_private', '_root', '_volatile'] for suffix in prop_suffixes: if getattr(self, 'default_pool' + suffix, None) is pool: - raise qubes.exc.QubesPoolInUseError(pool, - 'Storage pool is in use: set as {}'.format( - 'default_pool' + suffix)) + raise qubes.exc.QubesPoolInUseError( + pool, + 'Storage pool is in use: ' + 'set as {}'.format('default_pool' + suffix)) yield from self.fire_event_async('pool-pre-delete', - pre_event=True, pool=pool) + pre_event=True, pool=pool) del self.pools[name] yield from qubes.utils.coro_maybe(pool.destroy()) yield from self.fire_event_async('pool-delete', pool=pool) except KeyError: return - def get_pool(self, pool): - ''' Returns a :py:class:`qubes.storage.Pool` instance ''' + """ Returns a :py:class:`qubes.storage.Pool` instance """ if isinstance(pool, qubes.storage.Pool): return pool try: @@ -1341,10 +1344,10 @@ class Qubes(qubes.PropertyHolder): (driver, name)) def register_event_handlers(self, old_connection=None): - '''Register libvirt event handlers, which will translate libvirt + """Register libvirt event handlers, which will translate libvirt events into qubes.events. This function should be called only in 'qubesd' process and only when mainloop has been already set. - ''' + """ if old_connection: try: old_connection.domainEventDeregisterAny( @@ -1361,9 +1364,9 @@ class Qubes(qubes.PropertyHolder): None)) def _domain_event_callback(self, _conn, domain, event, _detail, _opaque): - '''Generic libvirt event handler (virConnectDomainEventCallback), + """Generic libvirt event handler (virConnectDomainEventCallback), translate libvirt event into qubes.events. - ''' + """ if not self.events_enabled: return @@ -1404,9 +1407,12 @@ class Qubes(qubes.PropertyHolder): self.log.error( 'Cannot remove %s, used by %s.%s', vm, obj, prop.__name__) - raise qubes.exc.QubesVMInUseError(vm, 'Domain is in ' - 'use: {!r}; see /var/log/qubes/qubes.log in dom0 for ' - 'details'.format(vm.name)) + raise qubes.exc.QubesVMInUseError( + vm, + 'Domain is in use: {!r};' + 'see /var/log/qubes/qubes.log in dom0 for ' + 'details'.format( + vm.name)) except AttributeError: pass @@ -1419,14 +1425,13 @@ class Qubes(qubes.PropertyHolder): 'clockvm', 'updatevm', 'default_template', - ): + ): try: if getattr(self, propname) == vm: delattr(self, propname) except AttributeError: pass - @qubes.events.handler('property-pre-set:clockvm') def on_property_pre_set_clockvm(self, event, name, newvalue, oldvalue=None): # pylint: disable=unused-argument,no-self-use @@ -1443,19 +1448,20 @@ class Qubes(qubes.PropertyHolder): @qubes.events.handler('property-pre-set:default_netvm') def on_property_pre_set_default_netvm(self, event, name, newvalue, - oldvalue=None): + oldvalue=None): # pylint: disable=unused-argument,invalid-name if newvalue is not None and oldvalue is not None \ and oldvalue.is_running() and not newvalue.is_running() \ and self.domains.get_vms_connected_to(oldvalue): - raise qubes.exc.QubesVMNotRunningError(newvalue, + raise qubes.exc.QubesVMNotRunningError( + newvalue, 'Cannot change {!r} to domain that ' - 'is not running ({!r}).'.format(name, newvalue.name)) - + 'is not running ({!r}).'.format( + name, newvalue.name)) @qubes.events.handler('property-set:default_fw_netvm') def on_property_set_default_fw_netvm(self, event, name, newvalue, - oldvalue=None): + oldvalue=None): # pylint: disable=unused-argument,invalid-name for vm in self.domains: if hasattr(vm, 'provides_network') and vm.provides_network and \ @@ -1463,14 +1469,13 @@ class Qubes(qubes.PropertyHolder): # fire property-del:netvm as it is responsible for resetting # netvm to it's default value vm.fire_event('property-pre-del:netvm', pre_event=True, - name='netvm', oldvalue=oldvalue) + name='netvm', oldvalue=oldvalue) vm.fire_event('property-del:netvm', - name='netvm', oldvalue=oldvalue) - + name='netvm', oldvalue=oldvalue) @qubes.events.handler('property-set:default_netvm') def on_property_set_default_netvm(self, event, name, newvalue, - oldvalue=None): + oldvalue=None): # pylint: disable=unused-argument for vm in self.domains: if hasattr(vm, 'provides_network') and not vm.provides_network and \ @@ -1478,6 +1483,6 @@ class Qubes(qubes.PropertyHolder): # fire property-del:netvm as it is responsible for resetting # netvm to it's default value vm.fire_event('property-pre-del:netvm', pre_event=True, - name='netvm', oldvalue=oldvalue) + name='netvm', oldvalue=oldvalue) vm.fire_event('property-del:netvm', - name='netvm', oldvalue=oldvalue) + name='netvm', oldvalue=oldvalue) diff --git a/qubes/ext/gui.py b/qubes/ext/gui.py index b495c100..32380e78 100644 --- a/qubes/ext/gui.py +++ b/qubes/ext/gui.py @@ -30,16 +30,18 @@ class GUI(qubes.ext.Extension): @staticmethod def send_gui_mode(vm): vm.run_service('qubes.SetGuiMode', - input=('SEAMLESS' - if vm.features.get('gui-seamless', False) - else 'FULLSCREEN')) + input=('SEAMLESS' + if vm.features.get('gui-seamless', False) + else 'FULLSCREEN')) @qubes.ext.handler('domain-qdb-create') def on_domain_qdb_create(self, vm, event): # pylint: disable=unused-argument,no-self-use for feature in ('gui-videoram-overhead', 'gui-videoram-min'): try: - vm.untrusted_qdb.write('/qubes-{}'.format(feature), - vm.features.check_with_template_and_adminvm(feature)) + vm.untrusted_qdb.write( + '/qubes-{}'.format(feature), + vm.features.check_with_template_and_adminvm( + feature)) except KeyError: pass diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index a3e3e4a7..983584e9 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -48,6 +48,7 @@ import qubes.vm.mix.net qmemman_present = False try: import qubes.qmemman.client # pylint: disable=wrong-import-position + qmemman_present = True except ImportError: pass @@ -61,27 +62,29 @@ MEM_OVERHEAD_PER_VCPU = 3 * 1024 * 1024 / 2 def _setter_kernel(self, prop, value): - ''' Helper for setting the domain kernel and running sanity checks on it. - ''' # pylint: disable=unused-argument + """ Helper for setting the domain kernel and running sanity checks on it. + """ # pylint: disable=unused-argument if not value: return '' value = str(value) if '/' in value: - raise qubes.exc.QubesPropertyValueError(self, prop, value, + raise qubes.exc.QubesPropertyValueError( + self, prop, value, 'Kernel name cannot contain \'/\'') return value def _setter_positive_int(self, prop, value): - ''' Helper for setting a positive int. Checks that the int is > 0 ''' + """ Helper for setting a positive int. Checks that the int is > 0 """ # pylint: disable=unused-argument value = int(value) if value <= 0: raise ValueError('Value must be positive') return value + def _setter_non_negative_int(self, prop, value): - ''' Helper for setting a positive int. Checks that the int is >= 0 ''' + """ Helper for setting a positive int. Checks that the int is >= 0 """ # pylint: disable=unused-argument value = int(value) if value < 0: @@ -90,26 +93,31 @@ def _setter_non_negative_int(self, prop, value): def _setter_default_user(self, prop, value): - ''' Helper for setting default user ''' + """ Helper for setting default user """ value = str(value) - # specifically forbid: ':', ' ', ''', '"' + # specifically forbid: ':', ' ', """, '"' allowed_chars = string.ascii_letters + string.digits + '_-+,.' if not all(c in allowed_chars for c in value): - raise qubes.exc.QubesPropertyValueError(self, prop, value, + raise qubes.exc.QubesPropertyValueError( + self, prop, value, 'Username can contain only those characters: ' + allowed_chars) return value + def _setter_virt_mode(self, prop, value): value = str(value) value = value.lower() if value not in ('hvm', 'pv', 'pvh'): - raise qubes.exc.QubesPropertyValueError(self, prop, value, + raise qubes.exc.QubesPropertyValueError( + self, prop, value, 'Invalid virtualization mode, supported values: hvm, pv, pvh') if value == 'pvh' and list(self.devices['pci'].persistent()): - raise qubes.exc.QubesPropertyValueError(self, prop, value, + raise qubes.exc.QubesPropertyValueError( + self, prop, value, "pvh mode can't be set if pci devices are attached") return value + def _default_virt_mode(self): if self.devices['pci'].persistent(): return 'hvm' @@ -118,10 +126,11 @@ def _default_virt_mode(self): except AttributeError: return 'pvh' + def _default_with_template(prop, default): - '''Return a callable for 'default' argument of a property. Use a value + """Return a callable for 'default' argument of a property. Use a value from a template (if any), otherwise *default* - ''' + """ def _func(self): try: @@ -148,18 +157,19 @@ def _default_maxmem(self): # don't use default larger than half of physical ram default_maxmem = min(default_maxmem, - int(self.app.host.memory_total / 1024 / 2)) + int(self.app.host.memory_total / 1024 / 2)) return _default_with_template('maxmem', default_maxmem)(self) + def _default_kernelopts(self): - ''' + """ Return default kernel options for the given kernel. If kernel directory contains 'default-kernelopts-{pci,nopci}.txt' file, use that. Otherwise use built-in defaults. For qubes without PCI devices, kernelopts of qube's template are considered (for template-based qubes). - ''' + """ if not self.kernel: return '' if 'kernel' in self.volumes: @@ -182,11 +192,11 @@ def _default_kernelopts(self): return f_kernelopts.read().strip() else: return (qubes.config.defaults['kernelopts_pcidevs'] if pci else - qubes.config.defaults['kernelopts']) + qubes.config.defaults['kernelopts']) class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): - '''Base functionality of Qubes VM shared between all VMs. + """Base functionality of Qubes VM shared between all VMs. The following events are raised on this class or its subclasses: @@ -496,7 +506,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): performing various post-installation setup. Handler for this event can be asynchronous (a coroutine). - ''' + """ # # per-class properties @@ -509,41 +519,49 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): # properties loaded from XML # - virt_mode = qubes.property('virt_mode', + virt_mode = qubes.property( + 'virt_mode', type=str, setter=_setter_virt_mode, default=_default_virt_mode, - doc='''Virtualisation mode: full virtualisation ("HVM"), - or paravirtualisation ("PV"), or hybrid ("PVH"). TemplateBasedVMs use its ' - 'template\'s value by default.''') + doc="""Virtualisation mode: full virtualisation ("HVM"), + or paravirtualisation ("PV"), or hybrid ("PVH"). + TemplateBasedVMs use its template\'s value by default.""") - installed_by_rpm = qubes.property('installed_by_rpm', + installed_by_rpm = qubes.property( + 'installed_by_rpm', type=bool, setter=qubes.property.bool, default=False, - doc='''If this domain's image was installed from package tracked by - package manager.''') + doc="""If this domain's image was installed from package tracked by + package manager.""") - memory = qubes.property('memory', type=int, + memory = qubes.property( + 'memory', type=int, setter=_setter_positive_int, - default=_default_with_template('memory', lambda self: + default=_default_with_template( + 'memory', + lambda self: qubes.config.defaults[ 'hvm_memory' if self.virt_mode == 'hvm' else 'memory']), doc='Memory currently available for this VM. TemplateBasedVMs use its ' 'template\'s value by default.') - maxmem = qubes.property('maxmem', type=int, + maxmem = qubes.property( + 'maxmem', type=int, setter=_setter_non_negative_int, default=_default_maxmem, - doc='''Maximum amount of memory available for this VM (for the purpose + doc="""Maximum amount of memory available for this VM (for the purpose of the memory balancer). Set to 0 to disable memory balancing for this qube. TemplateBasedVMs use its template\'s value by default - (unless memory balancing not supported for this qube).''') + (unless memory balancing not supported for this qube).""") - stubdom_mem = qubes.property('stubdom_mem', type=int, + stubdom_mem = qubes.property( + 'stubdom_mem', type=int, setter=_setter_positive_int, default=None, doc='Memory amount allocated for the stubdom') - vcpus = qubes.property('vcpus', + vcpus = qubes.property( + 'vcpus', type=int, setter=_setter_positive_int, default=_default_with_template('vcpus', 2), @@ -551,87 +569,104 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): 'template\'s value by default.') # CORE2: swallowed uses_default_kernel - kernel = qubes.property('kernel', type=str, + kernel = qubes.property( + 'kernel', type=str, setter=_setter_kernel, default=_default_with_template('kernel', - lambda self: self.app.default_kernel), + lambda self: self.app.default_kernel), doc='Kernel used by this domain. TemplateBasedVMs use its ' 'template\'s value by default.') # CORE2: swallowed uses_default_kernelopts # pylint: disable=no-member - kernelopts = qubes.property('kernelopts', type=str, load_stage=4, + kernelopts = qubes.property( + 'kernelopts', type=str, load_stage=4, default=_default_kernelopts, doc='Kernel command line passed to domain. TemplateBasedVMs use its ' 'template\'s value by default.') - debug = qubes.property('debug', type=bool, default=False, + debug = qubes.property( + 'debug', type=bool, default=False, setter=qubes.property.bool, doc='Turns on debugging features.') # XXX what this exactly does? # XXX shouldn't this go to standalone VM and TemplateVM, and leave here # only plain property? - default_user = qubes.property('default_user', type=str, + default_user = qubes.property( + 'default_user', type=str, # pylint: disable=no-member - default=_default_with_template('default_user', 'user'), + default=_default_with_template('default_user', + 'user'), setter=_setter_default_user, doc='Default user to start applications as. TemplateBasedVMs use its ' 'template\'s value by default.') # pylint: enable=no-member -# @property -# def default_user(self): -# if self.template is not None: -# return self.template.default_user -# else: -# return self._default_user + # @property + # def default_user(self): + # if self.template is not None: + # return self.template.default_user + # else: + # return self._default_user - qrexec_timeout = qubes.property('qrexec_timeout', type=int, - default=_default_with_template('qrexec_timeout', + qrexec_timeout = qubes.property( + 'qrexec_timeout', type=int, + default=_default_with_template( + 'qrexec_timeout', lambda self: self.app.default_qrexec_timeout), setter=_setter_positive_int, - doc='''Time in seconds after which qrexec connection attempt is deemed + doc="""Time in seconds after which qrexec connection attempt is deemed failed. Operating system inside VM should be able to boot in this - time.''') + time.""") - shutdown_timeout = qubes.property('shutdown_timeout', type=int, - default=_default_with_template('shutdown_timeout', + shutdown_timeout = qubes.property( + 'shutdown_timeout', type=int, + default=_default_with_template( + 'shutdown_timeout', lambda self: self.app.default_shutdown_timeout), setter=_setter_positive_int, - doc='''Time in seconds for shutdown of the VM, after which VM may be + doc="""Time in seconds for shutdown of the VM, after which VM may be forcefully powered off. Operating system inside VM should be - able to fully shutdown in this time.''') + able to fully shutdown in this time.""") - autostart = qubes.property('autostart', default=False, + autostart = qubes.property( + 'autostart', default=False, type=bool, setter=qubes.property.bool, - doc='''Setting this to `True` means that VM should be autostarted on - dom0 boot.''') + doc="""Setting this to `True` means that VM should be autostarted on + dom0 boot.""") - include_in_backups = qubes.property('include_in_backups', + include_in_backups = qubes.property( + 'include_in_backups', default=True, type=bool, setter=qubes.property.bool, doc='If this domain is to be included in default backup.') - backup_timestamp = qubes.property('backup_timestamp', default=None, + backup_timestamp = qubes.property( + 'backup_timestamp', default=None, type=int, doc='Time of last backup of the qube, in seconds since unix epoch') - default_dispvm = qubes.VMProperty('default_dispvm', + default_dispvm = qubes.VMProperty( + 'default_dispvm', load_stage=4, allow_none=True, - default=(lambda self: self.app.default_dispvm), + default=( + lambda self: self.app.default_dispvm), doc='Default VM to be used as Disposable VM for service calls.') - management_dispvm = qubes.VMProperty('management_dispvm', + management_dispvm = qubes.VMProperty( + 'management_dispvm', load_stage=4, allow_none=True, - default=_default_with_template('management_dispvm', + default=_default_with_template( + 'management_dispvm', (lambda self: self.app.management_dispvm)), doc='Default DVM template for Disposable VM for managing this VM.') - updateable = qubes.property('updateable', + updateable = qubes.property( + 'updateable', default=(lambda self: not hasattr(self, 'template')), type=bool, setter=qubes.property.forbidden, @@ -652,10 +687,10 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @qubes.stateless_property def xid(self): - '''Xen ID. + """Xen ID. Or not Xen, but ID. - ''' + """ if self.libvirt_domain is None: return -1 @@ -676,8 +711,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): if self.app.vmm.xs is None: return -1 - stubdom_xid_str = self.app.vmm.xs.read('', - '/local/domain/{}/image/device-model-domid'.format(self.xid)) + stubdom_xid_str = self.app.vmm.xs.read( + '', '/local/domain/{}/image/device-model-domid'.format( + self.xid)) if stubdom_xid_str is None or not stubdom_xid_str.isdigit(): return -1 @@ -702,10 +738,10 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @property def libvirt_domain(self): - '''Libvirt domain object from libvirt. + """Libvirt domain object from libvirt. May be :py:obj:`None`, if libvirt knows nothing about this domain. - ''' + """ if self._libvirt_domain is not None: return self._libvirt_domain @@ -726,9 +762,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @property def block_devices(self): - ''' Return all :py:class:`qubes.storage.BlockDevice` for current domain + """ Return all :py:class:`qubes.storage.BlockDevice` for current domain for serialization in the libvirt XML template as . - ''' + """ for v in self.volumes.values(): block_dev = v.block_device() if block_dev is not None: @@ -736,7 +772,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @property def untrusted_qdb(self): - '''QubesDB handle for this domain.''' + """QubesDB handle for this domain.""" if self._qdb_connection is None: if self.is_running(): import qubesdb # pylint: disable=import-error @@ -745,7 +781,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @property def dir_path(self): - '''Root directory for files related to this domain''' + """Root directory for files related to this domain""" return os.path.join( qubes.config.qubes_base_dir, self.dir_path_prefix, @@ -806,7 +842,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): elif volume_config: raise TypeError( 'volume_config specified, but {} did not expect that.'.format( - self.__class__.__name__)) + self.__class__.__name__)) # Init private attrs @@ -918,19 +954,21 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): qubes.config.system_path['qubes_kernels_base_dir'], newvalue) if not os.path.exists(dirname): - raise qubes.exc.QubesPropertyValueError(self, - self.property_get_def(name), newvalue, - 'Kernel {!r} not installed'.format(newvalue)) + raise qubes.exc.QubesPropertyValueError( + self, self.property_get_def(name), newvalue, + 'Kernel {!r} not installed'.format( + newvalue)) for filename in ('vmlinuz', 'initramfs'): if not os.path.exists(os.path.join(dirname, filename)): - raise qubes.exc.QubesPropertyValueError(self, - self.property_get_def(name), newvalue, + raise qubes.exc.QubesPropertyValueError( + self, self.property_get_def(name), newvalue, 'Kernel {!r} not properly installed: ' - 'missing {!r} file'.format(newvalue, filename)) + 'missing {!r} file'.format( + newvalue, filename)) @qubes.events.handler('property-pre-set:autostart') def on_property_pre_set_autostart(self, event, name, newvalue, - oldvalue=None): + oldvalue=None): # pylint: disable=unused-argument # workaround https://bugzilla.redhat.com/show_bug.cgi?id=1181922 if newvalue: @@ -942,7 +980,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): else: retcode = subprocess.call( ['sudo', 'systemctl', 'disable', - 'qubes-vm@{}.service'.format(self.name)]) + 'qubes-vm@{}.service'.format(self.name)]) if retcode: raise qubes.exc.QubesException( 'Failed to set autostart for VM in systemd') @@ -953,7 +991,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): if oldvalue: retcode = subprocess.call( ['sudo', 'systemctl', 'disable', - 'qubes-vm@{}.service'.format(self.name)]) + 'qubes-vm@{}.service'.format(self.name)]) if retcode: raise qubes.exc.QubesException( 'Failed to reset autostart for VM in systemd') @@ -964,7 +1002,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): if self.autostart: subprocess.call( ['sudo', 'systemctl', 'disable', - 'qubes-vm@{}.service'.format(self.name)]) + 'qubes-vm@{}.service'.format(self.name)]) @qubes.events.handler('domain-create-on-disk') def on_create_on_disk(self, event, **kwargs): @@ -972,7 +1010,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): if self.autostart: subprocess.call( ['sudo', 'systemctl', 'enable', - 'qubes-vm@{}.service'.format(self.name)]) + 'qubes-vm@{}.service'.format(self.name)]) # # methods for changing domain state @@ -980,9 +1018,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @asyncio.coroutine def _ensure_shutdown_handled(self): - '''Make sure previous shutdown is fully handled. + """Make sure previous shutdown is fully handled. MUST NOT be called when domain is running. - ''' + """ with (yield from self._domain_stopped_lock): # Don't accept any new stopped event's till a new VM has been # created. If we didn't received any stopped event or it wasn't @@ -1007,16 +1045,15 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): yield from self.fire_event_async('domain-stopped') yield from self.fire_event_async('domain-shutdown') - @asyncio.coroutine def start(self, start_guid=True, notify_function=None, - mem_required=None): - '''Start domain + mem_required=None): + """Start domain :param bool start_guid: FIXME :param collections.Callable notify_function: FIXME :param int mem_required: FIXME - ''' + """ with (yield from self.startup_lock): # check if domain wasn't removed in the meantime @@ -1033,11 +1070,12 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): try: yield from self.fire_event_async('domain-pre-start', - pre_event=True, - start_guid=start_guid, mem_required=mem_required) + pre_event=True, + start_guid=start_guid, + mem_required=mem_required) except Exception as exc: yield from self.fire_event_async('domain-start-failed', - reason=str(exc)) + reason=str(exc)) raise qmemman_client = None @@ -1058,10 +1096,11 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): # pylint: disable = no-member if self.netvm.qid != 0: if not self.netvm.is_running(): - yield from self.netvm.start(start_guid=start_guid, + yield from self.netvm.start( + start_guid=start_guid, notify_function=notify_function) - qmemman_client = yield from asyncio.get_event_loop().\ + qmemman_client = yield from asyncio.get_event_loop(). \ run_in_executor(None, self.request_memory, mem_required) yield from self.storage.start() @@ -1069,7 +1108,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): except Exception as exc: # let anyone receiving domain-pre-start know that startup failed yield from self.fire_event_async('domain-start-failed', - reason=str(exc)) + reason=str(exc)) if qmemman_client: qmemman_client.close() raise @@ -1084,7 +1123,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): self.log.error('Start failed: %s', str(exc)) # let anyone receiving domain-pre-start know that startup failed yield from self.fire_event_async('domain-start-failed', - reason=str(exc)) + reason=str(exc)) yield from self.storage.stop() raise @@ -1097,7 +1136,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): try: yield from self.fire_event_async('domain-spawn', - start_guid=start_guid) + start_guid=start_guid) self.log.info('Setting Qubes DB info for the VM') yield from self.start_qubesdb() @@ -1110,7 +1149,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): yield from self.start_qrexec_daemon() yield from self.fire_event_async('domain-start', - start_guid=start_guid) + start_guid=start_guid) except Exception as exc: # pylint: disable=bare-except self.log.error('Start failed: %s', str(exc)) @@ -1124,22 +1163,22 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): # let anyone receiving domain-pre-start know that startup failed yield from self.fire_event_async('domain-start-failed', - reason=str(exc)) + reason=str(exc)) raise return self def on_libvirt_domain_stopped(self): - ''' Handle VIR_DOMAIN_EVENT_STOPPED events from libvirt. + """ Handle VIR_DOMAIN_EVENT_STOPPED events from libvirt. This is not a Qubes event handler. Instead we do some sanity checks and synchronization with start() and then emits Qubes events. - ''' + """ state = self.get_power_state() if state not in ['Halted', 'Crashed', 'Dying']: self.log.warning('Stopped event from libvirt received,' - ' but domain is in state {}!'.format(state)) + ' but domain is in state {}!'.format(state)) # ignore this unexpected event return @@ -1169,16 +1208,16 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @qubes.events.handler('domain-stopped') @asyncio.coroutine def on_domain_stopped(self, _event, **_kwargs): - '''Cleanup after domain was stopped''' + """Cleanup after domain was stopped""" try: yield from self.storage.stop() except qubes.storage.StoragePoolException: self.log.exception('Failed to stop storage for domain %s', - self.name) + self.name) @asyncio.coroutine def shutdown(self, force=False, wait=False, timeout=None): - '''Shutdown domain. + """Shutdown domain. :param force: ignored :param wait: wait for shutdown to complete @@ -1186,14 +1225,14 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): :py:attr:`shutdown_timeout` :raises qubes.exc.QubesVMNotStartedError: \ when domain is already shut down. - ''' + """ if self.is_halted(): raise qubes.exc.QubesVMNotStartedError(self) try: yield from self.fire_event_async('domain-pre-shutdown', - pre_event=True, force=force) + pre_event=True, force=force) self.libvirt_domain.shutdown() @@ -1211,18 +1250,18 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): raise qubes.exc.QubesVMShutdownTimeoutError(self) except Exception as ex: yield from self.fire_event_async('domain-shutdown-failed', - reason=str(ex)) + reason=str(ex)) raise return self @asyncio.coroutine def kill(self): - '''Forcefully shutdown (destroy) domain. + """Forcefully shutdown (destroy) domain. :raises qubes.exc.QubesVMNotStartedError: \ when domain is already shut down. - ''' + """ if not self.is_running() and not self.is_paused(): raise qubes.exc.QubesVMNotStartedError(self) @@ -1234,9 +1273,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @asyncio.coroutine def _kill_locked(self): - '''Forcefully shutdown (destroy) domain. + """Forcefully shutdown (destroy) domain. - This function needs to be called with self.startup_lock held.''' + This function needs to be called with self.startup_lock held.""" try: self.libvirt_domain.destroy() except libvirt.libvirtError as e: @@ -1248,7 +1287,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): yield from self._ensure_shutdown_handled() def force_shutdown(self, *args, **kwargs): - '''Deprecated alias for :py:meth:`kill`''' + """Deprecated alias for :py:meth:`kill`""" warnings.warn( 'Call to deprecated function force_shutdown(), use kill() instead', DeprecationWarning, stacklevel=2) @@ -1256,11 +1295,11 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @asyncio.coroutine def suspend(self): - '''Suspend (pause) domain. + """Suspend (pause) domain. :raises qubes.exc.QubesVMNotRunnignError: \ when domain is already shut down. - ''' + """ if not self.is_running() and not self.is_paused(): raise qubes.exc.QubesVMNotRunningError(self) @@ -1268,7 +1307,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): if list(self.devices['pci'].attached()): if self.features.check_with_template('qrexec', False): yield from self.run_service_for_stdio('qubes.SuspendPre', - user='root') + user='root') self.libvirt_domain.pMSuspendForDuration( libvirt.VIR_NODE_SUSPEND_TARGET_MEM, 0, 0) else: @@ -1278,7 +1317,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @asyncio.coroutine def pause(self): - '''Pause (suspend) domain.''' + """Pause (suspend) domain.""" if not self.is_running(): raise qubes.exc.QubesVMNotRunningError(self) @@ -1289,18 +1328,18 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @asyncio.coroutine def resume(self): - '''Resume suspended domain. + """Resume suspended domain. :raises qubes.exc.QubesVMNotSuspendedError: when machine is not paused :raises qubes.exc.QubesVMError: when machine is suspended - ''' + """ # pylint: disable=not-an-iterable if self.get_power_state() == "Suspended": self.libvirt_domain.pMWakeup() if self.features.check_with_template('qrexec', False): yield from self.run_service_for_stdio('qubes.SuspendPost', - user='root') + user='root') else: yield from self.unpause() @@ -1308,7 +1347,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @asyncio.coroutine def unpause(self): - '''Resume (unpause) a domain''' + """Resume (unpause) a domain""" if not self.is_paused(): raise qubes.exc.QubesVMNotPausedError(self) @@ -1318,8 +1357,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @asyncio.coroutine def run_service(self, service, source=None, user=None, - filter_esc=False, autostart=False, gui=False, **kwargs): - '''Run service on this VM + filter_esc=False, autostart=False, gui=False, **kwargs): + """Run service on this VM :param str service: service name :param qubes.vm.qubesvm.QubesVM source: source domain as presented to @@ -1334,7 +1373,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): .. note:: User ``root`` is redefined to ``SYSTEM`` in the Windows agent code - ''' + """ # UNSUPPORTED from previous incarnation: # localcmd, wait, passio*, notify_function, `-e` switch @@ -1364,7 +1403,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): self, 'Domain {!r}: qrexec not connected'.format(self.name)) yield from self.fire_event_async('domain-cmd-pre-run', pre_event=True, - start_guid=gui) + start_guid=gui) return (yield from asyncio.create_subprocess_exec( qubes.config.system_path['qrexec_client_path'], @@ -1375,7 +1414,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @asyncio.coroutine def run_service_for_stdio(self, *args, input=None, **kwargs): - '''Run a service, pass an optional input and return (stdout, stderr). + """Run a service, pass an optional input and return (stdout, stderr). Raises an exception if return code != 0. @@ -1385,7 +1424,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): There are some combinations if stdio-related *kwargs*, which are not filtered for problems originating between the keyboard and the chair. - ''' # pylint: disable=redefined-builtin + """ # pylint: disable=redefined-builtin kwargs.setdefault('stdin', subprocess.PIPE) kwargs.setdefault('stdout', subprocess.PIPE) @@ -1397,23 +1436,23 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): if p.returncode: raise subprocess.CalledProcessError(p.returncode, - args[0], *stdouterr) + args[0], *stdouterr) return stdouterr @staticmethod def _prepare_input_for_vmshell(command, input): - '''Prepare shell input for the given command and optional (real) input - ''' # pylint: disable=redefined-builtin + """Prepare shell input for the given command and optional (real) input + """ # pylint: disable=redefined-builtin if input is None: input = b'' return b''.join((command.rstrip('\n').encode('utf-8'), b'\n', input)) def run(self, command, user=None, **kwargs): - '''Run a shell command inside the domain using qrexec. + """Run a shell command inside the domain using qrexec. This method is a coroutine. - ''' # pylint: disable=redefined-builtin + """ # pylint: disable=redefined-builtin if user is None: user = self.default_user @@ -1426,13 +1465,13 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @asyncio.coroutine def run_for_stdio(self, *args, input=None, **kwargs): - '''Run a shell command inside the domain using qubes.VMShell qrexec. + """Run a shell command inside the domain using qubes.VMShell qrexec. This method is a coroutine. *kwargs* are passed verbatim to :py:meth:`run_service_for_stdio`. See disclaimer there. - ''' # pylint: disable=redefined-builtin + """ # pylint: disable=redefined-builtin kwargs.setdefault('stdin', subprocess.PIPE) kwargs.setdefault('stdout', subprocess.PIPE) @@ -1442,12 +1481,12 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): if p.returncode: raise subprocess.CalledProcessError(p.returncode, - args[0], *stdouterr) + args[0], *stdouterr) return stdouterr def is_memory_balancing_possible(self): - '''Check if memory balancing can be enabled. + """Check if memory balancing can be enabled. Reasons to not enable it: - have PCI devices - balloon driver not present @@ -1456,7 +1495,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): heuristic is HVM virt_mode (PV and PVH require OS support and it does include balloon driver) and lack of qrexec/meminfo-writer service support (no qubes tools installed). - ''' + """ if list(self.devices['pci'].persistent()): return False if self.virt_mode == 'hvm': @@ -1467,10 +1506,11 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): features_set.update(template.features) template = getattr(template, 'template', None) supported_services = any(f.startswith('supported-service.') - for f in features_set) + for f in features_set) if (not self.features.check_with_template('qrexec', False) or - (supported_services and not self.features.check_with_template( - 'supported-service.meminfo-writer', False))): + (supported_services and + not self.features.check_with_template( + 'supported-service.meminfo-writer', False))): return False return True @@ -1484,10 +1524,10 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): stubdom_mem = self.stubdom_mem else: if self.features.check_with_template('linux-stubdom', True): - stubdom_mem = 128 # from libxl_create.c + stubdom_mem = 128 # from libxl_create.c else: - stubdom_mem = 28 # from libxl_create.c - stubdom_mem += 16 # video ram + stubdom_mem = 28 # from libxl_create.c + stubdom_mem += 16 # video ram else: stubdom_mem = 0 @@ -1497,7 +1537,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): qmemman_client = qubes.qmemman.client.QMemmanClient() try: mem_required_with_overhead = mem_required + MEM_OVERHEAD_BASE \ - + self.vcpus * MEM_OVERHEAD_PER_VCPU + + self.vcpus * MEM_OVERHEAD_PER_VCPU got_memory = qmemman_client.request_memory( mem_required_with_overhead) @@ -1513,7 +1553,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @staticmethod @asyncio.coroutine def start_daemon(*command, input=None, **kwargs): - '''Start a daemon for the VM + """Start a daemon for the VM This function take care to run it as appropriate user. @@ -1521,7 +1561,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): :py:meth:`subprocess.check_call`) :param kwargs: args for :py:meth:`subprocess.check_call` :return: None - ''' # pylint: disable=redefined-builtin + """ # pylint: disable=redefined-builtin if os.getuid() == 0: # try to always have VM daemons running as normal user, otherwise @@ -1529,19 +1569,19 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): # permission problems qubes_group = grp.getgrnam('qubes') command = ['runuser', '-u', qubes_group.gr_mem[0], '--'] + \ - list(command) + list(command) p = yield from asyncio.create_subprocess_exec(*command, **kwargs) stdout, stderr = yield from p.communicate(input=input) if p.returncode: raise subprocess.CalledProcessError(p.returncode, command, - output=stdout, stderr=stderr) + output=stdout, stderr=stderr) @asyncio.coroutine def start_qrexec_daemon(self): - '''Start qrexec daemon. + """Start qrexec daemon. :raises OSError: when starting fails. - ''' + """ self.log.debug('Starting the qrexec daemon') qrexec_args = [str(self.xid), self.name, self.default_user] @@ -1562,20 +1602,21 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): env=qrexec_env, stderr=subprocess.PIPE) except subprocess.CalledProcessError as err: if err.returncode == 3: - raise qubes.exc.QubesVMError(self, + raise qubes.exc.QubesVMError( + self, 'Cannot connect to qrexec agent for {} seconds, ' 'see /var/log/xen/console/guest-{}.log for details'.format( self.qrexec_timeout, self.name )) - raise qubes.exc.QubesVMError(self, - 'qrexec-daemon startup failed: ' + err.stderr.decode()) + raise qubes.exc.QubesVMError( + self, 'qrexec-daemon startup failed: ' + err.stderr.decode()) @asyncio.coroutine def start_qubesdb(self): - '''Start QubesDB daemon. + """Start QubesDB daemon. :raises OSError: when starting fails. - ''' + """ # drop old connection to QubesDB, if any self._qdb_connection = None @@ -1591,8 +1632,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @asyncio.coroutine def create_on_disk(self, pool=None, pools=None): - '''Create files needed for VM. - ''' + """Create files needed for VM. + """ self.log.info('Creating directory: {0}'.format(self.dir_path)) os.makedirs(self.dir_path, mode=0o775, exist_ok=True) @@ -1628,7 +1669,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @asyncio.coroutine def remove_from_disk(self): - '''Remove domain remnants from disk.''' + """Remove domain remnants from disk.""" if not self.is_halted(): raise qubes.exc.QubesVMNotHaltedError( "Can't remove VM {!s}, because it's in state {!r}.".format( @@ -1652,10 +1693,10 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @asyncio.coroutine def clone_disk_files(self, src, pool=None, pools=None, ): - '''Clone files from other vm. + """Clone files from other vm. :param qubes.vm.qubesvm.QubesVM src: source VM - ''' + """ # If the current vm name is not a part of `self.app.domains.keys()`, # then the current vm is in creation process. Calling @@ -1706,7 +1747,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): # state of the machine def get_power_state(self): - '''Return power state description string. + """Return power state description string. Return value may be one of those: @@ -1804,7 +1845,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainState Libvirt's enum describing precise state of a domain. - ''' # pylint: disable=too-many-return-statements + """ # pylint: disable=too-many-return-statements # don't try to define libvirt domain, if it isn't there, VM surely # isn't running @@ -1836,7 +1877,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): return "Halting" if libvirt_domain.state()[0] == libvirt.VIR_DOMAIN_SHUTOFF: return "Dying" - if libvirt_domain.state()[0] == libvirt.VIR_DOMAIN_PMSUSPENDED: # nopep8 + if libvirt_domain.state()[ + 0] == libvirt.VIR_DOMAIN_PMSUSPENDED: # nopep8 return "Suspended" if not self.is_fully_usable(): return "Transient" @@ -1851,20 +1893,20 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): assert False def is_halted(self): - ''' Check whether this domain's state is 'Halted' + """ Check whether this domain's state is 'Halted' :returns: :py:obj:`True` if this domain is halted, \ :py:obj:`False` otherwise. :rtype: bool - ''' + """ return self.get_power_state() == 'Halted' def is_running(self): - '''Check whether this domain is running. + """Check whether this domain is running. :returns: :py:obj:`True` if this domain is started, \ :py:obj:`False` otherwise. :rtype: bool - ''' + """ if self.app.vmm.offline_mode: return False @@ -1885,23 +1927,23 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): return bool(self.libvirt_domain.isActive()) def is_paused(self): - '''Check whether this domain is paused. + """Check whether this domain is paused. :returns: :py:obj:`True` if this domain is paused, \ :py:obj:`False` otherwise. :rtype: bool - ''' + """ return self.libvirt_domain \ - and self.libvirt_domain.state()[0] == libvirt.VIR_DOMAIN_PAUSED + and self.libvirt_domain.state()[0] == libvirt.VIR_DOMAIN_PAUSED def is_qrexec_running(self): - '''Check whether qrexec for this domain is available. + """Check whether qrexec for this domain is available. :returns: :py:obj:`True` if qrexec is running, \ :py:obj:`False` otherwise. :rtype: bool - ''' + """ if self.xid < 0: # pylint: disable=comparison-with-callable return False return os.path.exists('/var/run/qubes/qrexec.%s' % self.name) @@ -1911,10 +1953,10 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @qubes.events.handler('domain-is-fully-usable') def on_domain_is_fully_usable(self, event): - '''Check whether domain is running and sane. + """Check whether domain is running and sane. Currently this checks for running qrexec. - ''' # pylint: disable=unused-argument + """ # pylint: disable=unused-argument # Running gui-daemon implies also VM running if not self.is_qrexec_running(): @@ -1923,11 +1965,11 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): # memory and disk def get_mem(self): - '''Get current memory usage from VM. + """Get current memory usage from VM. :returns: Memory usage [FIXME unit]. :rtype: FIXME - ''' + """ if self.libvirt_domain is None: return 0 @@ -1951,11 +1993,11 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): raise def get_mem_static_max(self): - '''Get maximum memory available to VM. + """Get maximum memory available to VM. :returns: Memory limit [FIXME unit]. :rtype: FIXME - ''' + """ if self.libvirt_domain is None: return 0 @@ -1977,11 +2019,11 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): raise def get_cputime(self): - '''Get total CPU time burned by this domain since start. + """Get total CPU time burned by this domain since start. :returns: CPU time usage [FIXME unit]. :rtype: FIXME - ''' + """ if self.libvirt_domain is None: return 0 @@ -1995,9 +2037,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): if not self.libvirt_domain.isActive(): return 0 - # this does not work, because libvirt -# return self.libvirt_domain.getCPUStats( -# libvirt.VIR_NODE_CPU_STATS_ALL_CPUS, 0)[0]['cpu_time']/10**9 + # this does not work, because libvirt + # return self.libvirt_domain.getCPUStats( + # libvirt.VIR_NODE_CPU_STATS_ALL_CPUS, 0)[0]['cpu_time']/10**9 return self.libvirt_domain.info()[4] @@ -2018,16 +2060,16 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @qubes.stateless_property def start_time(self): - '''Tell when machine was started. + """Tell when machine was started. :rtype: float or None - ''' + """ if not self.is_running(): return None # TODO shouldn't this be qubesdb? start_time = self.app.vmm.xs.read('', - '/vm/{}/start_time'.format(self.uuid)) + '/vm/{}/start_time'.format(self.uuid)) if start_time != '': return float(start_time) @@ -2035,17 +2077,17 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): @property def kernelopts_common(self): - '''Kernel options which should be used in addition to *kernelopts* + """Kernel options which should be used in addition to *kernelopts* property. This is specific to kernel (and initrd if any) - ''' + """ if not self.kernel: return '' kernels_dir = self.storage.kernels_dir kernelopts_path = os.path.join(kernels_dir, - 'default-kernelopts-common.txt') + 'default-kernelopts-common.txt') if os.path.exists(kernelopts_path): with open(kernelopts_path) as f_kernelopts: return f_kernelopts.read().rstrip('\n\r') @@ -2057,17 +2099,17 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): # def relative_path(self, path): - '''Return path relative to py:attr:`dir_path`. + """Return path relative to py:attr:`dir_path`. :param str path: Path in question. :returns: Relative path. - ''' + """ return os.path.relpath(path, self.dir_path) def create_qdb_entries(self): - '''Create entries in Qubes DB. - ''' + """Create entries in Qubes DB. + """ # pylint: disable=no-member self.untrusted_qdb.write('/name', self.name) @@ -2075,7 +2117,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): self.untrusted_qdb.write('/default-user', self.default_user) self.untrusted_qdb.write('/qubes-vm-updateable', str(self.updateable)) self.untrusted_qdb.write('/qubes-vm-persistence', - 'full' if self.updateable else 'rw-only') + 'full' if self.updateable else 'rw-only') self.untrusted_qdb.write('/qubes-debug-mode', str(int(self.debug))) try: self.untrusted_qdb.write('/qubes-base-template', self.template.name) @@ -2083,7 +2125,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): self.untrusted_qdb.write('/qubes-base-template', '') self.untrusted_qdb.write('/qubes-random-seed', - base64.b64encode(qubes.utils.urandom(64))) + base64.b64encode(qubes.utils.urandom(64))) if self.provides_network: # '/qubes-netvm-network' value is only checked for being non empty @@ -2091,7 +2133,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): self.untrusted_qdb.write('/qubes-netvm-gateway', str(self.gateway)) if self.gateway6: # pylint: disable=using-constant-test self.untrusted_qdb.write('/qubes-netvm-gateway6', - str(self.gateway6)) + str(self.gateway6)) self.untrusted_qdb.write('/qubes-netvm-netmask', str(self.netmask)) for i, addr in zip(('primary', 'secondary'), self.dns): @@ -2101,9 +2143,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): self.untrusted_qdb.write('/qubes-mac', str(self.mac)) self.untrusted_qdb.write('/qubes-ip', str(self.visible_ip)) self.untrusted_qdb.write('/qubes-netmask', - str(self.visible_netmask)) + str(self.visible_netmask)) self.untrusted_qdb.write('/qubes-gateway', - str(self.visible_gateway)) + str(self.visible_gateway)) for i, addr in zip(('primary', 'secondary'), self.dns): self.untrusted_qdb.write('/qubes-{}-dns'.format(i), str(addr)) @@ -2112,29 +2154,28 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): self.untrusted_qdb.write('/qubes-ip6', str(self.visible_ip6)) if self.visible_gateway6: # pylint: disable=using-constant-test self.untrusted_qdb.write('/qubes-gateway6', - str(self.visible_gateway6)) - + str(self.visible_gateway6)) tzname = qubes.utils.get_timezone() if tzname: self.untrusted_qdb.write('/qubes-timezone', tzname) self.untrusted_qdb.write('/qubes-block-devices', '') - self.untrusted_qdb.write('/qubes-usb-devices', '') # TODO: Currently the whole qmemman is quite Xen-specific, so stay with # xenstore for it until decided otherwise if qmemman_present: self.app.vmm.xs.set_permissions('', - '/local/domain/{}/memory'.format(self.xid), - [{'dom': self.xid}]) + '/local/domain/{}/memory'.format( + self.xid), + [{'dom': self.xid}]) self.fire_event('domain-qdb-create') # TODO async; update this in constructor def _update_libvirt_domain(self): - '''Re-initialise :py:attr:`libvirt_domain`.''' + """Re-initialise :py:attr:`libvirt_domain`.""" domain_config = self.create_config_file() try: self._libvirt_domain = self.app.vmm.libvirt_conn.defineXML( @@ -2142,7 +2183,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_OS_TYPE \ and e.get_str2() == 'hvm': - raise qubes.exc.QubesVMError(self, + raise qubes.exc.QubesVMError( + self, 'HVM qubes are not supported on this machine. ' 'Check BIOS settings for VT-x/AMD-V extensions.') raise @@ -2153,8 +2195,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): def get_prefmem(self): # TODO: qmemman is still xen specific - untrusted_meminfo_key = self.app.vmm.xs.read('', - '/local/domain/{}/memory/meminfo'.format(self.xid)) + untrusted_meminfo_key = self.app.vmm.xs.read( + '', '/local/domain/{}/memory/meminfo'.format(self.xid)) if untrusted_meminfo_key is None or untrusted_meminfo_key == '': return 0 @@ -2199,6 +2241,7 @@ def _patch_pool_config(config, pool=None, pools=None): raise qubes.exc.QubesException(msg) return config + def _patch_volume_config(volume_config, pool=None, pools=None): assert not (pool and pools), \ 'You can not pass pool & pools parameter at same time'