diff --git a/qubes/__init__.py b/qubes/__init__.py index 74f51092..f8668ddf 100644 --- a/qubes/__init__.py +++ b/qubes/__init__.py @@ -284,6 +284,10 @@ class property: # pylint: disable=redefined-builtin,invalid-name has_oldvalue = False if has_oldvalue: + instance.fire_event('property-pre-reset:' + self.__name__, + pre_event=True, + name=self.__name__, oldvalue=oldvalue) + # deprecated, to be removed in Qubes 5.0 instance.fire_event('property-pre-del:' + self.__name__, pre_event=True, name=self.__name__, oldvalue=oldvalue) @@ -291,13 +295,23 @@ class property: # pylint: disable=redefined-builtin,invalid-name delattr(instance, self._attr_name) except AttributeError: pass + instance.fire_event('property-reset:' + self.__name__, + name=self.__name__, oldvalue=oldvalue) + # deprecated, to be removed in Qubes 5.0 instance.fire_event('property-del:' + self.__name__, name=self.__name__, oldvalue=oldvalue) else: + instance.fire_event('property-pre-reset:' + self.__name__, + pre_event=True, + name=self.__name__) + # deprecated, to be removed in Qubes 5.0 instance.fire_event('property-pre-del:' + self.__name__, pre_event=True, name=self.__name__) + instance.fire_event('property-reset:' + self.__name__, + name=self.__name__) + # deprecated, to be removed in Qubes 5.0 instance.fire_event('property-del:' + self.__name__, name=self.__name__) @@ -470,6 +484,9 @@ class PropertyHolder(qubes.events.Emitter): Fired when property gets deleted (is set to default). Signature is variable, *oldvalue* is present only if there was an old value. + This event is deprecated and will be removed in Qubes 5.0. + Use property-reset instead. + :param name: Property name :param oldvalue: Old value of the property @@ -479,6 +496,31 @@ class PropertyHolder(qubes.events.Emitter): Fired before property gets deleted (is set to default). Signature is variable, *oldvalue* is present only if there was an old value. + This event is deprecated and will be removed in Qubes 5.0. + Use property-pre-reset instead. + + :param name: Property name + :param oldvalue: Old value of the property + + .. event:: property-reset: \ + (subject, event, name[, oldvalue]) + + Fired when property gets reset to the (possibly dynamic) default. + This even may be also fired when the property is already in + "default" state, but the calculated default value changes. + Signature is variable, *oldvalue* is present only if there was an + old value. + + :param name: Property name + :param oldvalue: Old value of the property + + .. event:: property-pre-reset: \ + (subject, event, name[, oldvalue]) + + Fired before property gets reset to the (possibly dynamic) default. + Signature is variable, *oldvalue* is present only if there was an + old value. + :param name: Property name :param oldvalue: Old value of the property diff --git a/qubes/app.py b/qubes/app.py index afff320d..91a9a97f 100644 --- a/qubes/app.py +++ b/qubes/app.py @@ -1523,20 +1523,6 @@ class Qubes(qubes.PropertyHolder): '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): - # pylint: disable=unused-argument,invalid-name - for vm in self.domains: - if hasattr(vm, 'provides_network') and vm.provides_network and \ - hasattr(vm, 'netvm') and vm.property_is_default('netvm'): - # 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) - vm.fire_event('property-del:netvm', - name='netvm', oldvalue=oldvalue) - @qubes.events.handler('property-set:default_netvm') def on_property_set_default_netvm(self, event, name, newvalue, oldvalue=None): @@ -1544,9 +1530,7 @@ class Qubes(qubes.PropertyHolder): for vm in self.domains: if hasattr(vm, 'provides_network') and not vm.provides_network and \ hasattr(vm, 'netvm') and vm.property_is_default('netvm'): - # fire property-del:netvm as it is responsible for resetting + # fire property-reset: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) - vm.fire_event('property-del:netvm', + vm.fire_event('property-reset:netvm', name='netvm', oldvalue=oldvalue) diff --git a/qubes/ext/audio.py b/qubes/ext/audio.py index f4b13308..a8108b0b 100644 --- a/qubes/ext/audio.py +++ b/qubes/ext/audio.py @@ -48,9 +48,8 @@ class AUDIO(qubes.ext.Extension): self.on_property_set(vm, event, name='audiovm', newvalue=vm.audiovm) - # property-del <=> property-reset-to-default - @qubes.ext.handler('property-del:audiovm') - def on_property_del(self, subject, event, name, oldvalue=None): + @qubes.ext.handler('property-reset:audiovm') + def on_property_reset(self, subject, event, name, oldvalue=None): newvalue = getattr(subject, 'audiovm', None) self.on_property_set(subject, event, name, newvalue, oldvalue) diff --git a/qubes/ext/core_features.py b/qubes/ext/core_features.py index 4df3da9f..f541e642 100644 --- a/qubes/ext/core_features.py +++ b/qubes/ext/core_features.py @@ -67,16 +67,20 @@ class CoreFeatures(qubes.ext.Extension): def set_servicevm_feature(self, subject): if getattr(subject, 'provides_network', False): subject.features['servicevm'] = 1 + # icon is calculated based on this feature + subject.fire_event('property-reset:icon', name='icon') elif 'servicevm' in subject.features: del subject.features['servicevm'] + # icon is calculated based on this feature + subject.fire_event('property-reset:icon', name='icon') @qubes.ext.handler('property-set:provides_network') def on_property_set(self, subject, event, name, newvalue, oldvalue=None): # pylint: disable=unused-argument self.set_servicevm_feature(subject) - @qubes.ext.handler('property-del:provides_network') - def on_property_del(self, subject, event, name): + @qubes.ext.handler('property-reset:provides_network') + def on_property_reset(self, subject, event, name): # pylint: disable=unused-argument self.set_servicevm_feature(subject) diff --git a/qubes/ext/gui.py b/qubes/ext/gui.py index dc61e888..9ff81651 100644 --- a/qubes/ext/gui.py +++ b/qubes/ext/gui.py @@ -58,9 +58,8 @@ class GUI(qubes.ext.Extension): if 'guivm-' + vm.guivm.name not in vm.tags: self.on_property_set(vm, event, name='guivm', newvalue=vm.guivm) - # property-del <=> property-reset-to-default - @qubes.ext.handler('property-del:guivm') - def on_property_del(self, subject, event, name, oldvalue=None): + @qubes.ext.handler('property-reset:guivm') + def on_property_reset(self, subject, event, name, oldvalue=None): newvalue = getattr(subject, 'guivm', None) self.on_property_set(subject, event, name, newvalue, oldvalue) diff --git a/qubes/firewall.py b/qubes/firewall.py index 37b6fc99..f16ccdd6 100644 --- a/qubes/firewall.py +++ b/qubes/firewall.py @@ -329,8 +329,8 @@ class Rule(qubes.PropertyHolder): if newvalue not in ('icmp',): self.icmptype = qubes.property.DEFAULT - @qubes.events.handler('property-del:proto') - def on_del_proto(self, event, name, oldvalue): + @qubes.events.handler('property-reset:proto') + def on_reset_proto(self, event, name, oldvalue): # pylint: disable=unused-argument self.dstports = qubes.property.DEFAULT self.icmptype = qubes.property.DEFAULT diff --git a/qubes/tests/init.py b/qubes/tests/init.py index a2d41de9..fa4cc66c 100644 --- a/qubes/tests/init.py +++ b/qubes/tests/init.py @@ -170,8 +170,12 @@ class TC_10_property(qubes.tests.QubesTestCase): self.assertEventFired(self.holder, 'property-pre-del:testprop1', kwargs={'name': 'testprop1', 'oldvalue': 'testvalue'}) + self.assertEventFired(self.holder, 'property-pre-reset:testprop1', + kwargs={'name': 'testprop1', 'oldvalue': 'testvalue'}) self.assertEventFired(self.holder, 'property-del:testprop1', kwargs={'name': 'testprop1', 'oldvalue': 'testvalue'}) + self.assertEventFired(self.holder, 'property-reset:testprop1', + kwargs={'name': 'testprop1', 'oldvalue': 'testvalue'}) def test_081_delete_by_assign(self): self.holder.testprop1 = 'testvalue' @@ -203,8 +207,12 @@ class TC_10_property(qubes.tests.QubesTestCase): self.assertEqual(holder.testprop1, 'defaultvalue') self.assertEventFired(holder, 'property-pre-del:testprop1', kwargs={ 'name': 'testprop1', 'oldvalue': 'testvalue'}) + self.assertEventFired(holder, 'property-pre-reset:testprop1', kwargs={ + 'name': 'testprop1', 'oldvalue': 'testvalue'}) self.assertEventFired(holder, 'property-del:testprop1', kwargs={ 'name': 'testprop1', 'oldvalue': 'testvalue'}) + self.assertEventFired(holder, 'property-reset:testprop1', kwargs={ + 'name': 'testprop1', 'oldvalue': 'testvalue'}) def test_090_write_once_set(self): class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): diff --git a/qubes/vm/appvm.py b/qubes/vm/appvm.py index df69569f..fb7ee016 100644 --- a/qubes/vm/appvm.py +++ b/qubes/vm/appvm.py @@ -112,8 +112,8 @@ class AppVM(qubes.vm.qubesvm.QubesVM): ''' # pylint: disable=unused-argument assert self.template - @qubes.events.handler('property-pre-del:template') - def on_property_pre_del_template(self, event, name, oldvalue=None): + @qubes.events.handler('property-pre-reset:template') + def on_property_pre_reset_template(self, event, name, oldvalue=None): '''Forbid deleting template of running VM ''' # pylint: disable=unused-argument,no-self-use raise qubes.exc.QubesValueError('Cannot unset template') diff --git a/qubes/vm/dispvm.py b/qubes/vm/dispvm.py index da0214c0..a11b14f6 100644 --- a/qubes/vm/dispvm.py +++ b/qubes/vm/dispvm.py @@ -134,7 +134,7 @@ class DispVM(qubes.vm.qubesvm.QubesVM): assert self.template @qubes.events.handler('property-pre-set:template', - 'property-pre-del:template') + 'property-pre-reset:template') def on_property_pre_set_template(self, event, name, newvalue=None, oldvalue=None): ''' Disposable VM cannot have template changed ''' diff --git a/qubes/vm/mix/net.py b/qubes/vm/mix/net.py index a5e3f0e5..77277d76 100644 --- a/qubes/vm/mix/net.py +++ b/qubes/vm/mix/net.py @@ -406,8 +406,8 @@ class NetVMMixin(qubes.events.Emitter): '/connected-ips6', ' '.join(connected_ips6)) - @qubes.events.handler('property-pre-del:netvm') - def on_property_pre_del_netvm(self, event, name, oldvalue=None): + @qubes.events.handler('property-pre-reset:netvm') + def on_property_pre_reset_netvm(self, event, name, oldvalue=None): ''' Sets the the NetVM to default NetVM ''' # pylint: disable=unused-argument # we are changing to default netvm @@ -419,8 +419,8 @@ class NetVMMixin(qubes.events.Emitter): self.fire_event('property-pre-set:netvm', pre_event=True, name='netvm', newvalue=newvalue, oldvalue=oldvalue) - @qubes.events.handler('property-del:netvm') - def on_property_del_netvm(self, event, name, oldvalue=None): + @qubes.events.handler('property-reset:netvm') + def on_property_reset_netvm(self, event, name, oldvalue=None): ''' Sets the the NetVM to default NetVM ''' # pylint: disable=unused-argument # we are changing to default netvm @@ -475,6 +475,59 @@ class NetVMMixin(qubes.events.Emitter): # pylint: disable=unused-argument self.reload_firewall_for_vm(vm) + @qubes.events.handler('property-set:ip', 'property-reset:ip') + def on_property_set_ip(self, _event, name, newvalue=None, oldvalue=None): + # pylint: disable=unused-argument + if newvalue == oldvalue: + return + if self.provides_network: + self.fire_event('property-reset:gateway', name='gateway') + self.fire_event('property-reset:visible_ip', name='visible_ip') + for vm in self.connected_vms: + vm.fire_event( + 'property-reset:visible_gateway', name='visible_gateway') + + @qubes.events.handler('property-set:ip6', 'property-reset:ipv6') + def on_property_set_ip6(self, _event, name, newvalue=None, oldvalue=None): + # pylint: disable=unused-argument + if newvalue == oldvalue: + return + if self.provides_network: + self.fire_event('property-reset:gateway6', name='gateway6') + self.fire_event('property-reset:visible_ip6', name='visible_ip6') + for vm in self.connected_vms: + vm.fire_event( + 'property-reset:visible_gateway6', name='visible_gateway6') + + @qubes.events.handler('feature-set:net.fake-ip') + def on_feature_set_net_fake_ip(self, event, name, newvalue, oldvalue=None): + # pylint: disable=unused-argument + if oldvalue == newvalue: + return + self.fire_event('property-reset:visible_ip', name='visible_ip') + for vm in self.connected_vms: + vm.fire_event( + 'property-reset:visible_gateway', name='visible_gateway') + + @qubes.events.handler('feature-set:ipv6') + def on_feature_set_ipv6(self, event, name, newvalue, oldvalue=None): + # pylint: disable=unused-argument + if oldvalue == newvalue: + return + self.fire_event('property-reset:visible_ip6', name='visible_ip6') + for vm in self.connected_vms: + vm.fire_event( + 'property-reset:visible_gateway6', name='visible_gateway6') + + @qubes.events.handler('property-set:provides_network') + def on_property_set_provides( + self, _event, name, newvalue, oldvalue=None): + # pylint: disable=unused-argument + if newvalue == oldvalue: + return + self.fire_event('property-reset:gateway', name='gateway') + self.fire_event('property-reset:gateway6', name='gateway6') + @qubes.events.handler('domain-qdb-create') def on_domain_qdb_create(self, event): ''' Fills the QubesDB with firewall entries. ''' diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index e0b42da9..aca16905 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -965,6 +965,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): else: shutil.copy(newvalue.icon_path, self.icon_path) + # icon is calculated based on label + self.fire_event('property-reset:icon', name='icon') + @qubes.events.handler('property-pre-set:kernel') def on_property_pre_set_kernel(self, event, name, newvalue, oldvalue=None): # pylint: disable=unused-argument @@ -1006,8 +1009,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): raise qubes.exc.QubesException( 'Failed to set autostart for VM in systemd') - @qubes.events.handler('property-pre-del:autostart') - def on_property_pre_del_autostart(self, event, name, oldvalue=None): + @qubes.events.handler('property-pre-reset:autostart') + def on_property_pre_reset_autostart(self, event, name, oldvalue=None): # pylint: disable=unused-argument if oldvalue: retcode = subprocess.call( @@ -1142,6 +1145,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): self.libvirt_domain.createWithFlags( libvirt.VIR_DOMAIN_START_PAUSED) + # the above allocates xid, lets announce that + self.fire_event('property-reset:xid', name='xid') + self.fire_event('property-reset:start_time', name='start_time') except libvirt.libvirtError as exc: # missing IOMMU? if self.virt_mode == 'hvm' and \ @@ -1250,6 +1256,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): except qubes.storage.StoragePoolException: self.log.exception('Failed to stop storage for domain %s', self.name) + self.fire_event('property-reset:xid', name='xid') + self.fire_event('property-reset:start_time', name='start_time') @asyncio.coroutine def shutdown(self, force=False, wait=False, timeout=None): diff --git a/qubes/vm/templatevm.py b/qubes/vm/templatevm.py index f63e2172..01ab5278 100644 --- a/qubes/vm/templatevm.py +++ b/qubes/vm/templatevm.py @@ -85,3 +85,27 @@ class TemplateVM(QubesVM): } } super(TemplateVM, self).__init__(*args, **kwargs) + + @qubes.events.handler('property-set:default_user', + 'property-set:kernel', + 'property-set:kernelopts', + 'property-set:vcpus', + 'property-set:memory', + 'property-set:maxmem', + 'property-set:qrexec_timeout', + 'property-set:shutdown_timeout', + 'property-set:management_dispvm') + def on_property_set_child(self, _event, name, newvalue, oldvalue=None): + """Send event about default value change to child VMs + (which use default inherited from the template). + + This handler is supposed to be set for properties using + `_default_with_template()` function for the default value. + """ + if newvalue == oldvalue: + return + + for vm in self.appvms: + if not vm.property_is_default(name): + continue + vm.fire_event('property-reset:' + name, name=name)