diff --git a/doc/qubes-ext.rst b/doc/qubes-ext.rst index 0947efe4..00d3e4b6 100644 --- a/doc/qubes-ext.rst +++ b/doc/qubes-ext.rst @@ -1,8 +1,22 @@ :py:mod:`qubes.ext` -- Qubes extensions ======================================= +Module contents +--------------- + .. automodule:: qubes.ext :members: :show-inheritance: +Extensions defined here +----------------------- + +.. autoclass:: qubes.ext.admin.AdminExtension +.. autoclass:: qubes.ext.block.BlockDeviceExtension +.. autoclass:: qubes.ext.core_features.CoreFeatures +.. autoclass:: qubes.ext.gui.GUI +.. autoclass:: qubes.ext.pci.PCIDeviceExtension +.. autoclass:: qubes.ext.r3compatibility.R3Compatibility +.. autoclass:: qubes.ext.services.ServicesExtension + .. vim: ts=3 sw=3 et diff --git a/qubes/app.py b/qubes/app.py index 8d4bdeb8..be3c104e 100644 --- a/qubes/app.py +++ b/qubes/app.py @@ -680,7 +680,7 @@ class Qubes(qubes.PropertyHolder): doc='''Which VM to use as `yum` proxy for updating AdminVM and TemplateVMs''') clockvm = qubes.VMProperty('clockvm', load_stage=3, - allow_none=True, + 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') @@ -830,17 +830,6 @@ class Qubes(qubes.PropertyHolder): self.property_require('clockvm', allow_none=True) self.property_require('updatevm', allow_none=True) - # Disable ntpd in ClockVM - to not conflict with ntpdate (both are - # using 123/udp port) - if hasattr(self, 'clockvm') and self.clockvm is not None: - if self.clockvm.features.get('service.ntpd', False): - self.log.warning( - 'VM set as clockvm (%r) has enabled \'ntpd\' service! ' - 'Expect failure when syncing time in dom0.', - self.clockvm) - else: - self.clockvm.features['service.ntpd'] = '' - for vm in self.domains: vm.events_enabled = True vm.fire_event('domain-load') @@ -1173,13 +1162,14 @@ class Qubes(qubes.PropertyHolder): # pylint: disable=unused-argument,no-self-use if newvalue is None: return - if newvalue.features.get('service.ntpd', False): - raise qubes.exc.QubesVMError(newvalue, - 'Cannot set {!r} as {!r} since it has ntpd enabled.'.format( - newvalue.name, name)) - else: - newvalue.features['service.ntpd'] = '' + if 'service.clocksync' not in newvalue.features: + newvalue.features['service.clocksync'] = True + @qubes.events.handler('property-set:clockvm') + def on_property_set_clockvm(self, event, name, newvalue, oldvalue=None): + # pylint: disable=unused-argument,no-self-use + if oldvalue and oldvalue.features.get('service.clocksync', False): + del oldvalue.features['service.clocksync'] @qubes.events.handler( 'property-pre-set:default_netvm', diff --git a/qubes/ext/services.py b/qubes/ext/services.py new file mode 100644 index 00000000..85f29dde --- /dev/null +++ b/qubes/ext/services.py @@ -0,0 +1,62 @@ +# -*- encoding: utf-8 -*- +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2017 Marek Marczykowski-Górecki +# +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, see . + +'''Extension responsible for qvm-service framework''' + +import qubes.ext + +class ServicesExtension(qubes.ext.Extension): + '''This extension export features with 'service.' prefix to QubesDB in + /qubes-service/ tree. + ''' + # pylint: disable=no-self-use + @qubes.ext.handler('domain-qdb-create') + def on_domain_qdb_create(self, vm): + '''Actually export features''' + for feature, value in vm.features.items(): + if not feature.startswith('service.'): + continue + service = feature[len('service.'):] + # forcefully convert to '0' or '1' + vm.untrusted_qdb.write('/qubes-service/{}'.format(service), + str(int(bool(value)))) + + @qubes.ext.handler('domain-feature-set') + def on_domain_feature_set(self, vm, feature, value, oldvalue=None): + '''Update /qubes-service/ QubesDB tree in runtime''' + # pylint: disable=unused-argument + if not vm.is_running(): + return + if not feature.startswith('service.'): + return + service = feature[len('service.'):] + # forcefully convert to '0' or '1' + vm.untrusted_qdb.write('/qubes-service/{}'.format(service), + str(int(bool(value)))) + + @qubes.ext.handler('domain-feature-delete') + def on_domain_feature_delete(self, vm, feature): + '''Update /qubes-service/ QubesDB tree in runtime''' + if not vm.is_running(): + return + if not feature.startswith('service.'): + return + service = feature[len('service.'):] + vm.untrusted_qdb.rm('/qubes-service/{}'.format(service)) diff --git a/qubes/tests/app.py b/qubes/tests/app.py index e0fa065f..4acf864c 100644 --- a/qubes/tests/app.py +++ b/qubes/tests/app.py @@ -261,15 +261,41 @@ class TC_30_VMCollection(qubes.tests.QubesTestCase): class TC_90_Qubes(qubes.tests.QubesTestCase): + def tearDown(self): + try: + os.unlink('/tmp/qubestest.xml') + except: + pass + super(TC_90_Qubes, self).tearDown() + @qubes.tests.skipUnlessDom0 def test_000_init_empty(self): # pylint: disable=no-self-use,unused-variable,bare-except try: os.unlink('/tmp/qubestest.xml') - except: + except FileNotFoundError: pass qubes.Qubes.create_empty_store('/tmp/qubestest.xml') + def test_100_clockvm(self): + app = qubes.Qubes('/tmp/qubestest.xml', load=False, offline_mode=True) + app.load_initial_values() + + template = app.add_new_vm('TemplateVM', name='test-template', + label='green') + appvm = app.add_new_vm('AppVM', name='test-vm', template=template, + label='red') + self.assertIsNone(app.clockvm) + self.assertNotIn('service.clocksync', appvm.features) + self.assertNotIn('service.clocksync', template.features) + app.clockvm = appvm + self.assertIn('service.clocksync', appvm.features) + self.assertTrue(appvm.features['service.clocksync']) + app.clockvm = template + self.assertNotIn('service.clocksync', appvm.features) + self.assertIn('service.clocksync', template.features) + self.assertTrue(template.features['service.clocksync']) + @qubes.tests.skipUnlessGit def test_900_example_xml_in_doc(self): self.assertXMLIsValid( diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index 69b8d672..24f9a3ec 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -289,7 +289,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): :param value: new value :param oldvalue: old value, if any - .. event:: domain-feature-delete (subject, event, key) + .. event:: domain-feature-delete (subject, event, feature) A feature was removed. @@ -1746,14 +1746,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): if tzname: self.untrusted_qdb.write('/qubes-timezone', tzname) - for feature, value in self.features.items(): - if not feature.startswith('service.'): - continue - service = feature[len('service.'):] - # forcefully convert to '0' or '1' - self.untrusted_qdb.write('/qubes-service/{}'.format(service), - str(int(bool(value)))) - self.untrusted_qdb.write('/qubes-block-devices', '') self.untrusted_qdb.write('/qubes-usb-devices', '') diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index 14eb6ec5..35b5b8c4 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -294,6 +294,7 @@ fi %{python3_sitelib}/qubes/ext/pci.py %{python3_sitelib}/qubes/ext/qubesmanager.py %{python3_sitelib}/qubes/ext/r3compatibility.py +%{python3_sitelib}/qubes/ext/services.py %dir %{python3_sitelib}/qubes/tests %dir %{python3_sitelib}/qubes/tests/__pycache__