Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/pr/582'
Browse files Browse the repository at this point in the history
* origin/pr/582:
  ext/audio: split set/unset properties
  ext/audio: few improvements from @DemiMarie's suggestions
  ext/audio: add test for change of audiovm
  ext/audio: handle del of audiovm
  ext/audio: ensure qubes's qdb is ready
  ext/audio: ensure vm.is_running() is available
  ext/audio: update audiovm qubesdb entry
  ext/audio: reformat
  • Loading branch information
marmarek committed Mar 29, 2024
2 parents 50a2336 + 3caf393 commit 8dcabdc
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 45 deletions.
123 changes: 80 additions & 43 deletions qubes/ext/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,69 +20,106 @@
import qubes.config
import qubes.ext


class AUDIO(qubes.ext.Extension):
# pylint: disable=unused-argument
@staticmethod
def attached_vms(vm):
for domain in vm.app.domains:
if getattr(domain, 'audiovm', None) and domain.audiovm == vm:
if getattr(domain, "audiovm", None) == 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 AudioVM: '
'{}'.format(', '.join(vm.name for vm in attached_vms)))

@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 vm.tags:
self.on_property_set(vm, event, name='audiovm',
newvalue=vm.audiovm)
@staticmethod
def set_qubesdb_audiovm(vm):
# Ensure that qube is ready
if not vm.untrusted_qdb:
return

@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)
# Add AudioVM Xen ID for gui-agent
audiovm = getattr(vm, "audiovm", None)
if audiovm is not None:
if audiovm != vm and audiovm.is_running():
vm.untrusted_qdb.write(
"/qubes-audio-domain-xid", str(audiovm.xid)
)
else:
vm.untrusted_qdb.rm("/qubes-audio-domain-xid")

@qubes.ext.handler('property-set:audiovm')
def on_property_set(self, subject, event, name, newvalue, oldvalue=None):
def set_tag_and_qubesdb_entry(self, subject, event, newvalue=None):
# 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 tag.startswith('audiovm-'):
if tag.startswith("audiovm-"):
subject.tags.remove(tag)

if newvalue:
audiovm = 'audiovm-' + newvalue.name
audiovm = "audiovm-" + newvalue.name
subject.tags.add(audiovm)

@qubes.ext.handler('domain-qdb-create')
# It is needed to filter these events because
# vm.is_running() is not yet available.
if event not in ("domain-init", "domain-load"):
self.set_qubesdb_audiovm(subject)

@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 AudioVM: "
"{}".format(", ".join(vm.name for vm in attached_vms)),
)

@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 vm.tags:
self.on_property_set(
vm, event, name="audiovm", newvalue=vm.audiovm
)

@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)

@qubes.ext.handler("property-set:audiovm")
def on_property_set(self, subject, event, name, newvalue, oldvalue=None):
self.set_tag_and_qubesdb_entry(
subject=subject, event=event, newvalue=newvalue
)

@qubes.ext.handler("property-del:audiovm")
def on_property_del(self, subject, event, name, oldvalue=None):
self.set_tag_and_qubesdb_entry(subject=subject, event=event)

@qubes.ext.handler("domain-qdb-create")
def on_domain_qdb_create(self, vm, event):
# Add AudioVM Xen ID for gui-agent
if getattr(vm, 'audiovm', None):
if vm != vm.audiovm and vm.audiovm.is_running():
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):
self.set_qubesdb_audiovm(vm)

@qubes.ext.handler("property-set:default_audiovm", system=True)
def on_property_set_default_audiovm(
self, app, event, name, newvalue, oldvalue=None
):
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)
if hasattr(vm, "audiovm") and vm.property_is_default("audiovm"):
vm.fire_event(
"property-set:audiovm",
name="audiovm",
newvalue=newvalue,
oldvalue=oldvalue,
)

@qubes.ext.handler('domain-start')
@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()]
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))
attached_vm.untrusted_qdb.write(
"/qubes-audio-domain-xid", str(vm.xid)
)
70 changes: 68 additions & 2 deletions qubes/tests/vm/qubesvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -1961,7 +1961,73 @@ def test_623_qdb_audiovm(self, mock_qubesdb, mock_urandom,
@unittest.mock.patch('qubes.utils.get_timezone')
@unittest.mock.patch('qubes.utils.urandom')
@unittest.mock.patch('qubes.vm.qubesvm.QubesVM.untrusted_qdb')
def test_624_qdb_guivm_invalid_keyboard_layout(self, mock_qubesdb,
def test_624_qdb_audiovm_change_to_new_and_none(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)
audiovm_new = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template,
name='sys-audio-new', qid=3, 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.is_running = lambda: True
vm._qubesprop_xid = 2
audiovm.is_running = lambda: True
audiovm._libvirt_domain = unittest.mock.Mock(**{'ID.return_value': 2})
audiovm_new.is_running = lambda: True
audiovm_new._libvirt_domain = unittest.mock.Mock(**{'ID.return_value': 3})
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
expected = {
'/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',
'/connected-ips': '',
'/connected-ips6': '',
}

with self.subTest('default'):
self.assertEqual(test_qubesdb.data, expected)

with self.subTest('value_change'):
vm.audiovm = None
del expected['/qubes-audio-domain-xid']
self.assertEqual(test_qubesdb.data, expected)

with self.subTest('value_change'):
vm.audiovm = audiovm_new
expected['/qubes-audio-domain-xid'] = "3"
self.assertEqual(test_qubesdb.data, expected)

@unittest.mock.patch('qubes.utils.get_timezone')
@unittest.mock.patch('qubes.utils.urandom')
@unittest.mock.patch('qubes.vm.qubesvm.QubesVM.untrusted_qdb')
def test_625_qdb_guivm_invalid_keyboard_layout(self, mock_qubesdb,
mock_urandom, mock_timezone):
mock_urandom.return_value = b'A' * 64
mock_timezone.return_value = 'UTC'
Expand All @@ -1986,7 +2052,7 @@ def test_624_qdb_guivm_invalid_keyboard_layout(self, mock_qubesdb,
@unittest.mock.patch('qubes.utils.get_timezone')
@unittest.mock.patch('qubes.utils.urandom')
@unittest.mock.patch('qubes.vm.qubesvm.QubesVM.untrusted_qdb')
def test_625_qdb_keyboard_layout_change(self, mock_qubesdb, mock_urandom,
def test_626_qdb_keyboard_layout_change(self, mock_qubesdb, mock_urandom,
mock_timezone):
mock_urandom.return_value = b'A' * 64
mock_timezone.return_value = 'UTC'
Expand Down

0 comments on commit 8dcabdc

Please sign in to comment.