From 795ff1233a87609211cf3f080725e2b3e71e5129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Thu, 27 Feb 2020 10:31:27 +0100 Subject: [PATCH 01/15] Support for AudioVM --- qubes/app.py | 6 ++++ qubes/ext/audio.py | 64 ++++++++++++++++++++++++++++++++++++++ qubes/tests/app.py | 41 ++++++++++++++++++++++++ qubes/tests/vm/qubesvm.py | 44 +++++++++++++++++++++++++- qubes/vm/qubesvm.py | 4 +++ rpm_spec/core-dom0.spec.in | 1 + setup.py | 1 + 7 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 qubes/ext/audio.py diff --git a/qubes/app.py b/qubes/app.py index c14d1f60..eb5dc0c5 100644 --- a/qubes/app.py +++ b/qubes/app.py @@ -743,6 +743,12 @@ class Qubes(qubes.PropertyHolder): default=lambda app: app.domains['dom0'], allow_none=True, doc='Default GuiVM for VMs.') + default_audiovm = qubes.VMProperty( + 'default_audiovm', + load_stage=3, + default=lambda app: app.domains['dom0'], allow_none=True, + doc='Default AudioVM for VMs.') + default_netvm = qubes.VMProperty( 'default_netvm', load_stage=3, diff --git a/qubes/ext/audio.py b/qubes/ext/audio.py new file mode 100644 index 00000000..55efe913 --- /dev/null +++ b/qubes/ext/audio.py @@ -0,0 +1,64 @@ +# +# The Qubes OS Project, https://www.qubes-os.org/ +# +# Copyright (C) 2019 Frédéric Pierret +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, see . +# + +import qubes.config +import qubes.ext + + +class AUDIO(qubes.ext.Extension): + # pylint: disable=too-few-public-methods + # property-del <=> property-reset-to-default + @qubes.ext.handler('property-del:audiovm') + def on_property_del(self, subject, event, name, oldvalue=None): + newvalue = getattr(subject, 'audiovm', None) + self.on_property_set(subject, event, name, newvalue, oldvalue) + + @qubes.ext.handler('property-set:audiovm') + def on_property_set(self, subject, event, name, newvalue, oldvalue=None): + # pylint: disable=unused-argument,no-self-use + + # Clean other 'audiovm-XXX' tags. + # pulseaudio agent (module-vchan-sink) can connect to only one domain + tags_list = list(subject.tags) + for tag in tags_list: + if 'audiovm-' in tag: + subject.tags.remove(tag) + + if newvalue: + audiovm = 'audiovm-' + newvalue.name + subject.tags.add(audiovm) + + @qubes.ext.handler('domain-qdb-create') + def on_domain_qdb_create(self, vm, event): + # pylint: disable=unused-argument,no-self-use + # Add AudioVM Xen ID for gui-agent + if getattr(vm, 'audiovm', None): + if vm != vm.audiovm: + vm.untrusted_qdb.write('/qubes-audio-domain-xid', + str(vm.audiovm.xid)) + + @qubes.ext.handler('property-set:default_audiovm', system=True) + def on_property_set_default_audiovm(self, app, event, name, newvalue, + oldvalue=None): + # pylint: disable=unused-argument,no-self-use + for vm in app.domains: + if hasattr(vm, 'audiovm') and vm.property_is_default('audiovm'): + vm.fire_event('property-set:audiovm', + name='audiovm', newvalue=newvalue, + oldvalue=oldvalue) diff --git a/qubes/tests/app.py b/qubes/tests/app.py index eceb62ee..19fda806 100644 --- a/qubes/tests/app.py +++ b/qubes/tests/app.py @@ -608,6 +608,47 @@ class TC_90_Qubes(qubes.tests.QubesTestCase): self.assertIn('guivm-sys-gui', appvm.tags) + def test_114_default_audiovm(self): + class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): + default_audiovm = qubes.property('default_audiovm', + default=(lambda self: 'dom0')) + + holder = MyTestHolder(None) + audiovm = self.app.add_new_vm('AppVM', name='sys-audio', audiovm='dom0', + template=self.template, label='red') + appvm = self.app.add_new_vm('AppVM', name='test-vm', + template=self.template, label='red') + holder.default_audiovm = 'sys-audio' + self.assertEqual(holder.default_audiovm, 'sys-audio') + self.assertIsNotNone(self.app.default_audiovm) + self.assertTrue(appvm.property_is_default('audiovm')) + self.app.default_audiovm = audiovm + self.assertEventFired(holder, 'property-set:default_audiovm', + kwargs={'name': 'default_audiovm', + 'newvalue': 'sys-audio'}) + + self.assertIn('audiovm-sys-audio', appvm.tags) + + def test_115_audiovm(self): + class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): + audiovm = qubes.property('audiovm', + default=(lambda self: 'dom0')) + + holder = MyTestHolder(None) + audiovm = self.app.add_new_vm('AppVM', name='sys-audio', audiovm='dom0', + template=self.template, label='red') + appvm = self.app.add_new_vm('AppVM', name='test-vm', audiovm='dom0', + template=self.template, label='red') + holder.audiovm = 'sys-audio' + self.assertEqual(holder.audiovm, 'sys-audio') + self.assertFalse(appvm.property_is_default('audiovm')) + appvm.audiovm = audiovm + self.assertEventFired(holder, 'property-set:audiovm', + kwargs={'name': 'audiovm', + 'newvalue': 'sys-audio'}) + + self.assertIn('audiovm-sys-audio', appvm.tags) + def test_200_remove_template(self): appvm = self.app.add_new_vm('AppVM', name='test-vm', template=self.template, diff --git a/qubes/tests/vm/qubesvm.py b/qubes/tests/vm/qubesvm.py index 9963d84d..4b62c82f 100644 --- a/qubes/tests/vm/qubesvm.py +++ b/qubes/tests/vm/qubesvm.py @@ -1819,7 +1819,7 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase): @unittest.mock.patch('qubes.utils.get_timezone') @unittest.mock.patch('qubes.utils.urandom') @unittest.mock.patch('qubes.vm.qubesvm.QubesVM.untrusted_qdb') - def test_622_qdb_keyboard_layout(self, mock_qubesdb, mock_urandom, + def test_622_qdb_guivm_keyboard_layout(self, mock_qubesdb, mock_urandom, mock_timezone): mock_urandom.return_value = b'A' * 64 mock_timezone.return_value = 'UTC' @@ -1871,6 +1871,48 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase): '/connected-ips6': '', }) + @unittest.mock.patch('qubes.utils.get_timezone') + @unittest.mock.patch('qubes.utils.urandom') + @unittest.mock.patch('qubes.vm.qubesvm.QubesVM.untrusted_qdb') + def test_623_qdb_audiovm(self, mock_qubesdb, mock_urandom, + mock_timezone): + mock_urandom.return_value = b'A' * 64 + mock_timezone.return_value = 'UTC' + template = self.get_vm( + cls=qubes.vm.templatevm.TemplateVM, name='template') + template.netvm = None + audiovm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template, + name='sys-audio', qid=2, provides_network=False) + vm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template, + name='appvm', qid=3) + vm.netvm = None + vm.audiovm = audiovm + vm.events_enabled = True + test_qubesdb = TestQubesDB() + mock_qubesdb.write.side_effect = test_qubesdb.write + mock_qubesdb.rm.side_effect = test_qubesdb.rm + vm.create_qdb_entries() + self.maxDiff = None + self.assertEqual(test_qubesdb.data, { + '/name': 'test-inst-appvm', + '/type': 'AppVM', + '/default-user': 'user', + '/qubes-vm-type': 'AppVM', + '/qubes-audio-domain-xid': '{}'.format(audiovm.xid), + '/qubes-debug-mode': '0', + '/qubes-base-template': 'test-inst-template', + '/qubes-timezone': 'UTC', + '/qubes-random-seed': base64.b64encode(b'A' * 64), + '/qubes-vm-persistence': 'rw-only', + '/qubes-vm-updateable': 'False', + '/qubes-block-devices': '', + '/qubes-usb-devices': '', + '/qubes-iptables': 'reload', + '/qubes-iptables-error': '', + '/qubes-iptables-header': unittest.mock.ANY, + '/qubes-service/qubes-update-check': '0', + '/qubes-service/meminfo-writer': '1', + }) @asyncio.coroutine def coroutine_mock(self, mock, *args, **kwargs): diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index 4f92e3e0..f4cd0c6a 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -519,6 +519,10 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): default=(lambda self: self.app.default_guivm), doc='VM used for Gui') + audiovm = qubes.VMProperty('audiovm', load_stage=4, allow_none=True, + default=(lambda self: self.app.default_audiovm), + doc='VM used for Audio') + virt_mode = qubes.property( 'virt_mode', type=str, setter=_setter_virt_mode, diff --git a/rpm_spec/core-dom0.spec.in b/rpm_spec/core-dom0.spec.in index 98fa6b68..5b962337 100644 --- a/rpm_spec/core-dom0.spec.in +++ b/rpm_spec/core-dom0.spec.in @@ -276,6 +276,7 @@ fi %{python3_sitelib}/qubes/ext/block.py %{python3_sitelib}/qubes/ext/core_features.py %{python3_sitelib}/qubes/ext/gui.py +%{python3_sitelib}/qubes/ext/audio.py %{python3_sitelib}/qubes/ext/pci.py %{python3_sitelib}/qubes/ext/qubesmanager.py %{python3_sitelib}/qubes/ext/r3compatibility.py diff --git a/setup.py b/setup.py index cc3c5c7b..ddbc132e 100644 --- a/setup.py +++ b/setup.py @@ -64,6 +64,7 @@ if __name__ == '__main__': 'qubes.ext.core_features = qubes.ext.core_features:CoreFeatures', 'qubes.ext.qubesmanager = qubes.ext.qubesmanager:QubesManager', 'qubes.ext.gui = qubes.ext.gui:GUI', + 'qubes.ext.audio = qubes.ext.audio:AUDIO', 'qubes.ext.r3compatibility = qubes.ext.r3compatibility:R3Compatibility', 'qubes.ext.pci = qubes.ext.pci:PCIDeviceExtension', 'qubes.ext.block = qubes.ext.block:BlockDeviceExtension', From 8f67334c4fdbfd1b9018a0ccbd2e488684a7904b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Thu, 27 Feb 2020 10:31:28 +0100 Subject: [PATCH 02/15] gui, audio: ensure guivm and audiovm tag are set --- qubes/ext/audio.py | 9 ++++++++- qubes/ext/gui.py | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/qubes/ext/audio.py b/qubes/ext/audio.py index 55efe913..3df00f9c 100644 --- a/qubes/ext/audio.py +++ b/qubes/ext/audio.py @@ -22,7 +22,14 @@ import qubes.ext class AUDIO(qubes.ext.Extension): - # pylint: disable=too-few-public-methods + # pylint: disable=unused-argument,no-self-use + @qubes.ext.handler('domain-init', 'domain-load') + def on_domain_init_load(self, vm, event): + if getattr(vm, 'audiovm', None): + if 'audiovm-' + vm.audiovm not in list(vm.tags): + vm.fire_event('property-set:audiovm', + 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): diff --git a/qubes/ext/gui.py b/qubes/ext/gui.py index 69e97df6..f850ce89 100644 --- a/qubes/ext/gui.py +++ b/qubes/ext/gui.py @@ -35,6 +35,14 @@ class GUI(qubes.ext.Extension): if vm.features.get('gui-seamless', False) else 'FULLSCREEN')) + # pylint: disable=unused-argument,no-self-use + @qubes.ext.handler('domain-init', 'domain-load') + def on_domain_init_load(self, vm, event): + if getattr(vm, 'guivm', None): + if 'guivm-' + vm.guivm not in list(vm.tags): + vm.fire_event('property-set:guivm', + 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): From a1752ef26517b4958cf6b5c8f03344500384cedb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Thu, 27 Feb 2020 10:31:28 +0100 Subject: [PATCH 03/15] gui, audio: better handling of start/stop guivm/audiovm --- qubes/ext/audio.py | 34 ++++++++++++++++++++++++++++------ qubes/ext/gui.py | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/qubes/ext/audio.py b/qubes/ext/audio.py index 3df00f9c..ff0c5f46 100644 --- a/qubes/ext/audio.py +++ b/qubes/ext/audio.py @@ -17,16 +17,42 @@ # License along with this library; if not, see . # +import asyncio + import qubes.config import qubes.ext class AUDIO(qubes.ext.Extension): # pylint: disable=unused-argument,no-self-use + @staticmethod + def attached_vms(vm): + for domain in vm.app.domains: + if getattr(domain, 'audiovm', None) and domain.audiovm == vm: + yield domain + + @qubes.ext.handler('domain-pre-shutdown') + @asyncio.coroutine + def on_domain_pre_shutdown(self, vm, event, **kwargs): + attached_vms = [domain for domain in self.attached_vms(vm) if + domain.is_running()] + if attached_vms and not kwargs.get('force', False): + raise qubes.exc.QubesVMError( + self, 'There are running VMs using this VM as AudioVM: ' + '{}'.format(', '.join(vm.name for vm in attached_vms))) + + @qubes.ext.handler('domain-pre-start') + def on_domain_pre_start(self, vm, event, start_guid, **kwargs): + if getattr(vm, 'audiovm', None): + if vm.audiovm.qid != 0: + if not vm.audiovm.is_running(): + yield from vm.audiovm.start(start_guid=start_guid, + notify_function=None) + @qubes.ext.handler('domain-init', 'domain-load') def on_domain_init_load(self, vm, event): if getattr(vm, 'audiovm', None): - if 'audiovm-' + vm.audiovm not in list(vm.tags): + if 'audiovm-' + vm.audiovm.name not in list(vm.tags): vm.fire_event('property-set:audiovm', name='audiovm', newvalue=vm.audiovm) @@ -38,8 +64,6 @@ class AUDIO(qubes.ext.Extension): @qubes.ext.handler('property-set:audiovm') def on_property_set(self, subject, event, name, newvalue, oldvalue=None): - # pylint: disable=unused-argument,no-self-use - # Clean other 'audiovm-XXX' tags. # pulseaudio agent (module-vchan-sink) can connect to only one domain tags_list = list(subject.tags) @@ -53,7 +77,6 @@ class AUDIO(qubes.ext.Extension): @qubes.ext.handler('domain-qdb-create') def on_domain_qdb_create(self, vm, event): - # pylint: disable=unused-argument,no-self-use # Add AudioVM Xen ID for gui-agent if getattr(vm, 'audiovm', None): if vm != vm.audiovm: @@ -62,8 +85,7 @@ class AUDIO(qubes.ext.Extension): @qubes.ext.handler('property-set:default_audiovm', system=True) def on_property_set_default_audiovm(self, app, event, name, newvalue, - oldvalue=None): - # pylint: disable=unused-argument,no-self-use + oldvalue=None): for vm in app.domains: if hasattr(vm, 'audiovm') and vm.property_is_default('audiovm'): vm.fire_event('property-set:audiovm', diff --git a/qubes/ext/gui.py b/qubes/ext/gui.py index f850ce89..41214c12 100644 --- a/qubes/ext/gui.py +++ b/qubes/ext/gui.py @@ -21,13 +21,38 @@ # License along with this library; if not, see . # +import asyncio + import qubes.config import qubes.ext class GUI(qubes.ext.Extension): - # pylint: disable=too-few-public-methods - # TODO put this somewhere... + # pylint: disable=too-few-public-methods,unused-argument,no-self-use + @staticmethod + def attached_vms(vm): + for domain in vm.app.domains: + if getattr(domain, 'guivm', None) and domain.guivm == vm: + yield domain + + @qubes.ext.handler('domain-pre-shutdown') + def on_domain_pre_shutdown(self, vm, event, **kwargs): + attached_vms = [domain for domain in self.attached_vms(vm) if + domain.is_running()] + if attached_vms and not kwargs.get('force', False): + raise qubes.exc.QubesVMError( + self, 'There are running VMs using this VM as GuiVM: ' + '{}'.format(', '.join(vm.name for vm in attached_vms))) + + @qubes.ext.handler('domain-pre-start') + @asyncio.coroutine + def on_domain_pre_start(self, vm, event, start_guid, **kwargs): + if getattr(vm, 'guivm', None): + if vm.guivm.qid != 0: + if not vm.guivm.is_running(): + yield from vm.guivm.start(start_guid=start_guid, + notify_function=None) + @staticmethod def send_gui_mode(vm): vm.run_service('qubes.SetGuiMode', @@ -35,11 +60,10 @@ class GUI(qubes.ext.Extension): if vm.features.get('gui-seamless', False) else 'FULLSCREEN')) - # pylint: disable=unused-argument,no-self-use @qubes.ext.handler('domain-init', 'domain-load') def on_domain_init_load(self, vm, event): if getattr(vm, 'guivm', None): - if 'guivm-' + vm.guivm not in list(vm.tags): + if 'guivm-' + vm.guivm.name not in list(vm.tags): vm.fire_event('property-set:guivm', name='guivm', newvalue=vm.guivm) @@ -51,8 +75,6 @@ class GUI(qubes.ext.Extension): @qubes.ext.handler('property-set:guivm') def on_property_set(self, subject, event, name, newvalue, oldvalue=None): - # pylint: disable=unused-argument,no-self-use - # Clean other 'guivm-XXX' tags. # gui-daemon can connect to only one domain tags_list = list(subject.tags) @@ -66,7 +88,6 @@ class GUI(qubes.ext.Extension): @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( @@ -106,7 +127,6 @@ class GUI(qubes.ext.Extension): @qubes.ext.handler('property-set:default_guivm', system=True) def on_property_set_default_guivm(self, app, event, name, newvalue, oldvalue=None): - # pylint: disable=unused-argument,no-self-use for vm in app.domains: if hasattr(vm, 'guivm') and vm.property_is_default('guivm'): vm.fire_event('property-set:guivm', From 820500a367541a108d8144a94945f56b7f5fc0f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Thu, 27 Feb 2020 10:31:28 +0100 Subject: [PATCH 04/15] tests: fix missing default audiovm and guivm tags --- qubes/tests/api_admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qubes/tests/api_admin.py b/qubes/tests/api_admin.py index 7364df1f..ab8b7373 100644 --- a/qubes/tests/api_admin.py +++ b/qubes/tests/api_admin.py @@ -1951,7 +1951,7 @@ netvm default=True type=vm \n''' self.vm.tags.add('tag1') self.vm.tags.add('tag2') value = self.call_mgmt_func(b'admin.vm.tag.List', b'test-vm1') - self.assertEqual(value, 'tag1\ntag2\n') + self.assertEqual(value, 'audiovm-dom0\nguivm-dom0\ntag1\ntag2\n') self.assertFalse(self.app.save.called) def test_540_tag_get(self): From 3f4ee9f8d940f2a3cf956b013aba7daa8381aa40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Thu, 27 Feb 2020 10:31:28 +0100 Subject: [PATCH 05/15] xid: ensure vm is not running --- qubes/vm/qubesvm.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index f4cd0c6a..3419a0bd 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -690,7 +690,10 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): if self.libvirt_domain is None: return -1 try: - return self.libvirt_domain.ID() + if self.is_running(): + return self.libvirt_domain.ID() + else: + return -1 except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN: return -1 From edfb25141399e9feb4721ec7f226035c7a3f8f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Thu, 27 Feb 2020 10:31:29 +0100 Subject: [PATCH 06/15] Allow GuiVM to be ran after any attached qubes --- qubes/ext/gui.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/qubes/ext/gui.py b/qubes/ext/gui.py index 41214c12..fee4c29f 100644 --- a/qubes/ext/gui.py +++ b/qubes/ext/gui.py @@ -44,15 +44,6 @@ class GUI(qubes.ext.Extension): self, 'There are running VMs using this VM as GuiVM: ' '{}'.format(', '.join(vm.name for vm in attached_vms))) - @qubes.ext.handler('domain-pre-start') - @asyncio.coroutine - def on_domain_pre_start(self, vm, event, start_guid, **kwargs): - if getattr(vm, 'guivm', None): - if vm.guivm.qid != 0: - if not vm.guivm.is_running(): - yield from vm.guivm.start(start_guid=start_guid, - notify_function=None) - @staticmethod def send_gui_mode(vm): vm.run_service('qubes.SetGuiMode', @@ -99,7 +90,7 @@ class GUI(qubes.ext.Extension): # Add GuiVM Xen ID for gui-daemon if getattr(vm, 'guivm', None): - if vm != vm.guivm: + if vm != vm.guivm and vm.guivm.is_running(): vm.untrusted_qdb.write('/qubes-gui-domain-xid', str(vm.guivm.xid)) @@ -132,3 +123,11 @@ class GUI(qubes.ext.Extension): vm.fire_event('property-set:guivm', name='guivm', newvalue=newvalue, oldvalue=oldvalue) + + @qubes.ext.handler('domain-start') + def on_domain_start(self, vm, event, **kwargs): + attached_vms = [domain for domain in self.attached_vms(vm) if + domain.is_running()] + for attached_vm in attached_vms: + attached_vm.untrusted_qdb.write('/qubes-gui-domain-xid', + str(vm.xid)) From 660c224914c38391ebc0207ef9d81c65b2be01a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Thu, 27 Feb 2020 10:31:29 +0100 Subject: [PATCH 07/15] Allow AudioVM to be ran after any attached qubes --- qubes/ext/audio.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/qubes/ext/audio.py b/qubes/ext/audio.py index ff0c5f46..610fae6e 100644 --- a/qubes/ext/audio.py +++ b/qubes/ext/audio.py @@ -41,14 +41,6 @@ class AUDIO(qubes.ext.Extension): self, 'There are running VMs using this VM as AudioVM: ' '{}'.format(', '.join(vm.name for vm in attached_vms))) - @qubes.ext.handler('domain-pre-start') - def on_domain_pre_start(self, vm, event, start_guid, **kwargs): - if getattr(vm, 'audiovm', None): - if vm.audiovm.qid != 0: - if not vm.audiovm.is_running(): - yield from vm.audiovm.start(start_guid=start_guid, - notify_function=None) - @qubes.ext.handler('domain-init', 'domain-load') def on_domain_init_load(self, vm, event): if getattr(vm, 'audiovm', None): @@ -79,7 +71,7 @@ class AUDIO(qubes.ext.Extension): def on_domain_qdb_create(self, vm, event): # Add AudioVM Xen ID for gui-agent if getattr(vm, 'audiovm', None): - if vm != vm.audiovm: + if vm != vm.audiovm and vm.audiovm.is_running(): vm.untrusted_qdb.write('/qubes-audio-domain-xid', str(vm.audiovm.xid)) @@ -91,3 +83,11 @@ class AUDIO(qubes.ext.Extension): vm.fire_event('property-set:audiovm', name='audiovm', newvalue=newvalue, oldvalue=oldvalue) + + @qubes.ext.handler('domain-start') + def on_domain_start(self, vm, event, **kwargs): + attached_vms = [domain for domain in self.attached_vms(vm) if + domain.is_running()] + for attached_vm in attached_vms: + attached_vm.untrusted_qdb.write('/qubes-audio-domain-xid', + str(vm.xid)) From 7506482d08f25b0ba15d7e436064b366d63b4c38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Thu, 27 Feb 2020 10:31:29 +0100 Subject: [PATCH 08/15] gui/audio: fixes from Marek's comments --- qubes/ext/audio.py | 6 +++--- qubes/ext/gui.py | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/qubes/ext/audio.py b/qubes/ext/audio.py index 610fae6e..c3ece983 100644 --- a/qubes/ext/audio.py +++ b/qubes/ext/audio.py @@ -45,8 +45,8 @@ class AUDIO(qubes.ext.Extension): def on_domain_init_load(self, vm, event): if getattr(vm, 'audiovm', None): if 'audiovm-' + vm.audiovm.name not in list(vm.tags): - vm.fire_event('property-set:audiovm', - name='audiovm', newvalue=vm.audiovm) + self.on_property_set(vm, event, name='audiovm', + newvalue=vm.audiovm) # property-del <=> property-reset-to-default @qubes.ext.handler('property-del:audiovm') @@ -60,7 +60,7 @@ class AUDIO(qubes.ext.Extension): # pulseaudio agent (module-vchan-sink) can connect to only one domain tags_list = list(subject.tags) for tag in tags_list: - if 'audiovm-' in tag: + if tag.startswith('audiovm-'): subject.tags.remove(tag) if newvalue: diff --git a/qubes/ext/gui.py b/qubes/ext/gui.py index fee4c29f..0899fa44 100644 --- a/qubes/ext/gui.py +++ b/qubes/ext/gui.py @@ -55,8 +55,7 @@ class GUI(qubes.ext.Extension): def on_domain_init_load(self, vm, event): if getattr(vm, 'guivm', None): if 'guivm-' + vm.guivm.name not in list(vm.tags): - vm.fire_event('property-set:guivm', - name='guivm', newvalue=vm.guivm) + self.on_property_set(vm, event, name='guivm', newvalue=vm.guivm) # property-del <=> property-reset-to-default @qubes.ext.handler('property-del:guivm') @@ -70,7 +69,7 @@ class GUI(qubes.ext.Extension): # gui-daemon can connect to only one domain tags_list = list(subject.tags) for tag in tags_list: - if 'guivm-' in tag: + if tag.startswith('guivm-'): subject.tags.remove(tag) if newvalue: From 43786e14781dfd0c1476a5f4bb1c0159aae1b7b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Thu, 27 Feb 2020 14:17:42 +0100 Subject: [PATCH 09/15] Make pylint happy - Drop unused 'asyncio' - Drop useless 'else' after return --- qubes/ext/gui.py | 2 -- qubes/vm/qubesvm.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/qubes/ext/gui.py b/qubes/ext/gui.py index 0899fa44..d5292969 100644 --- a/qubes/ext/gui.py +++ b/qubes/ext/gui.py @@ -21,8 +21,6 @@ # License along with this library; if not, see . # -import asyncio - import qubes.config import qubes.ext diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index 3419a0bd..fbe8d6c5 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -692,8 +692,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): try: if self.is_running(): return self.libvirt_domain.ID() - else: - return -1 + + return -1 except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN: return -1 From 4b5ae0833bf9d00b9ca8f2eab2eccf1faad3e576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Thu, 27 Feb 2020 15:01:44 +0100 Subject: [PATCH 10/15] tests: fix tests for gui/audio vm --- qubes/tests/vm/qubesvm.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qubes/tests/vm/qubesvm.py b/qubes/tests/vm/qubesvm.py index 4b62c82f..45b1401f 100644 --- a/qubes/tests/vm/qubesvm.py +++ b/qubes/tests/vm/qubesvm.py @@ -1840,6 +1840,7 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase): '"complete"\x09};\x0a\x09xkb_symbols { include ' \ '"pc+fr+inet(evdev)"\x09};\x0a\x09xkb_geometry ' \ '{ include "pc(pc105)"\x09};\x0a};' + guivm.is_running = lambda: True vm.events_enabled = True test_qubesdb = TestQubesDB() mock_qubesdb.write.side_effect = test_qubesdb.write @@ -1887,6 +1888,7 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase): name='appvm', qid=3) vm.netvm = None vm.audiovm = audiovm + audiovm.is_running = lambda: True vm.events_enabled = True test_qubesdb = TestQubesDB() mock_qubesdb.write.side_effect = test_qubesdb.write @@ -1912,6 +1914,8 @@ class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase): '/qubes-iptables-header': unittest.mock.ANY, '/qubes-service/qubes-update-check': '0', '/qubes-service/meminfo-writer': '1', + '/connected-ips': '', + '/connected-ips6': '', }) @asyncio.coroutine From 9051aff15a9c430a7dbcd28dfc1fe1ae0a1ed50c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Sun, 8 Mar 2020 10:27:25 +0100 Subject: [PATCH 11/15] audio/gui: use simply vm.tags instead of list() --- qubes/ext/audio.py | 2 +- qubes/ext/gui.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qubes/ext/audio.py b/qubes/ext/audio.py index c3ece983..f4b13308 100644 --- a/qubes/ext/audio.py +++ b/qubes/ext/audio.py @@ -44,7 +44,7 @@ class AUDIO(qubes.ext.Extension): @qubes.ext.handler('domain-init', 'domain-load') def on_domain_init_load(self, vm, event): if getattr(vm, 'audiovm', None): - if 'audiovm-' + vm.audiovm.name not in list(vm.tags): + if 'audiovm-' + vm.audiovm.name not in vm.tags: self.on_property_set(vm, event, name='audiovm', newvalue=vm.audiovm) diff --git a/qubes/ext/gui.py b/qubes/ext/gui.py index d5292969..e542a360 100644 --- a/qubes/ext/gui.py +++ b/qubes/ext/gui.py @@ -52,7 +52,7 @@ class GUI(qubes.ext.Extension): @qubes.ext.handler('domain-init', 'domain-load') def on_domain_init_load(self, vm, event): if getattr(vm, 'guivm', None): - if 'guivm-' + vm.guivm.name not in list(vm.tags): + 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 From c36ad38eb3adccb909dcd77d52a7737e575736f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Sun, 8 Mar 2020 10:31:44 +0100 Subject: [PATCH 12/15] audio: add checks for changing/removing audiovm --- qubes/tests/app.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/qubes/tests/app.py b/qubes/tests/app.py index 19fda806..e74727b6 100644 --- a/qubes/tests/app.py +++ b/qubes/tests/app.py @@ -611,11 +611,11 @@ class TC_90_Qubes(qubes.tests.QubesTestCase): def test_114_default_audiovm(self): class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): default_audiovm = qubes.property('default_audiovm', - default=(lambda self: 'dom0')) + default=(lambda self: 'dom0')) holder = MyTestHolder(None) audiovm = self.app.add_new_vm('AppVM', name='sys-audio', audiovm='dom0', - template=self.template, label='red') + template=self.template, label='red') appvm = self.app.add_new_vm('AppVM', name='test-vm', template=self.template, label='red') holder.default_audiovm = 'sys-audio' @@ -632,23 +632,35 @@ class TC_90_Qubes(qubes.tests.QubesTestCase): def test_115_audiovm(self): class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): audiovm = qubes.property('audiovm', - default=(lambda self: 'dom0')) + default=(lambda self: 'dom0')) holder = MyTestHolder(None) audiovm = self.app.add_new_vm('AppVM', name='sys-audio', audiovm='dom0', - template=self.template, label='red') + template=self.template, label='red') + guivm = self.app.add_new_vm('AppVM', name='sys-gui', audiovm='dom0', + template=self.template, label='red') appvm = self.app.add_new_vm('AppVM', name='test-vm', audiovm='dom0', template=self.template, label='red') holder.audiovm = 'sys-audio' self.assertEqual(holder.audiovm, 'sys-audio') - self.assertFalse(appvm.property_is_default('audiovm')) - appvm.audiovm = audiovm + self.assertEventFired(holder, 'property-set:audiovm', kwargs={'name': 'audiovm', 'newvalue': 'sys-audio'}) + # Set AudioVM + self.assertFalse(appvm.property_is_default('audiovm')) + appvm.audiovm = audiovm self.assertIn('audiovm-sys-audio', appvm.tags) + # Change AudioVM + appvm.audiovm = guivm + self.assertIn('audiovm-sys-gui', appvm.tags) + + # Empty AudioVM + del appvm.audiovm + self.assertNotIn('audiovm-', appvm.tags) + def test_200_remove_template(self): appvm = self.app.add_new_vm('AppVM', name='test-vm', template=self.template, From 90584c487a92b19c0a3705bf17e740d0d3df32b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Sun, 8 Mar 2020 11:54:22 +0100 Subject: [PATCH 13/15] gui: add checks for changing/removing guivm --- qubes/tests/app.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/qubes/tests/app.py b/qubes/tests/app.py index e74727b6..a9904d8d 100644 --- a/qubes/tests/app.py +++ b/qubes/tests/app.py @@ -596,18 +596,29 @@ class TC_90_Qubes(qubes.tests.QubesTestCase): holder = MyTestHolder(None) guivm = self.app.add_new_vm('AppVM', name='sys-gui', guivm='dom0', template=self.template, label='red') + vncvm = self.app.add_new_vm('AppVM', name='sys-vnc', guivm='dom0', + template=self.template, label='red') appvm = self.app.add_new_vm('AppVM', name='test-vm', guivm='dom0', template=self.template, label='red') holder.guivm = 'sys-gui' self.assertEqual(holder.guivm, 'sys-gui') - self.assertFalse(appvm.property_is_default('guivm')) - appvm.guivm = guivm self.assertEventFired(holder, 'property-set:guivm', kwargs={'name': 'guivm', 'newvalue': 'sys-gui'}) + # Set GuiVM + self.assertFalse(appvm.property_is_default('guivm')) + appvm.guivm = guivm self.assertIn('guivm-sys-gui', appvm.tags) + # Change GuiVM + appvm.guivm = vncvm + self.assertIn('guivm-sys-vnc', appvm.tags) + + # Empty GuiVM + del appvm.guivm + self.assertNotIn('guivm-', appvm.tags) + def test_114_default_audiovm(self): class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder): default_audiovm = qubes.property('default_audiovm', From 532d9a3a988eecf59a281b121572b64fda4915d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Sun, 8 Mar 2020 22:59:46 +0100 Subject: [PATCH 14/15] tests: ensure notin while setting Audio/Gui VM --- qubes/tests/app.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qubes/tests/app.py b/qubes/tests/app.py index a9904d8d..fae342b8 100644 --- a/qubes/tests/app.py +++ b/qubes/tests/app.py @@ -614,9 +614,12 @@ class TC_90_Qubes(qubes.tests.QubesTestCase): # Change GuiVM appvm.guivm = vncvm self.assertIn('guivm-sys-vnc', appvm.tags) + self.assertNotIn('guivm-sys-vnc', appvm.tags) # Empty GuiVM del appvm.guivm + self.assertNotIn('guivm-sys-vnc', appvm.tags) + self.assertNotIn('guivm-sys-gui', appvm.tags) self.assertNotIn('guivm-', appvm.tags) def test_114_default_audiovm(self): @@ -667,9 +670,12 @@ class TC_90_Qubes(qubes.tests.QubesTestCase): # Change AudioVM appvm.audiovm = guivm self.assertIn('audiovm-sys-gui', appvm.tags) + self.assertNotIn('audiovm-sys-audio', appvm.tags) # Empty AudioVM del appvm.audiovm + self.assertNotIn('audiovm-sys-gui', appvm.tags) + self.assertNotIn('audiovm-sys-audio', appvm.tags) self.assertNotIn('audiovm-', appvm.tags) def test_200_remove_template(self): From 4cfc423d5e5a127803bcd2dc3ba8a073344d0fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Mon, 9 Mar 2020 01:11:58 +0100 Subject: [PATCH 15/15] tests: fix tag name in audiovm test --- qubes/tests/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qubes/tests/app.py b/qubes/tests/app.py index fae342b8..f4c202ea 100644 --- a/qubes/tests/app.py +++ b/qubes/tests/app.py @@ -614,7 +614,7 @@ class TC_90_Qubes(qubes.tests.QubesTestCase): # Change GuiVM appvm.guivm = vncvm self.assertIn('guivm-sys-vnc', appvm.tags) - self.assertNotIn('guivm-sys-vnc', appvm.tags) + self.assertNotIn('guivm-sys-gui', appvm.tags) # Empty GuiVM del appvm.guivm