vm: add API for watching changes in QubesDB

Provide an API for use QubesDB.watch() inside of qubesd.

Fixes QubesOS/qubes-issues#2940
This commit is contained in:
Marek Marczykowski-Górecki 2017-07-25 01:00:04 +02:00
parent d5b94d1cbd
commit b7f0cf7d82
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
3 changed files with 72 additions and 0 deletions

View File

@ -24,6 +24,7 @@
'''Qubes Virtual Machines '''Qubes Virtual Machines
''' '''
import asyncio
import re import re
import string import string
@ -265,6 +266,8 @@ class BaseVM(qubes.PropertyHolder):
# self.app must be set before super().__init__, because some property # self.app must be set before super().__init__, because some property
# setters need working .app attribute # setters need working .app attribute
#: mother :py:class:`qubes.Qubes` object #: mother :py:class:`qubes.Qubes` object
self._qdb_watch_paths = set()
self._qdb_connection_watch = None
self.app = app self.app = app
super(BaseVM, self).__init__(xml, **kwargs) super(BaseVM, self).__init__(xml, **kwargs)
@ -387,6 +390,55 @@ class BaseVM(qubes.PropertyHolder):
]).render(vm=self) ]).render(vm=self)
return domain_config return domain_config
def watch_qdb_path(self, path):
'''Add a QubesDB path to be watched.
Each change to the path will cause `domain-qdb-change:path` event to be
fired.
You can call this method for example in response to
`domain-init` and `domain-load` events.
'''
if path not in self._qdb_watch_paths:
self._qdb_watch_paths.add(path)
if self._qdb_connection_watch:
self._qdb_connection_watch.watch(path)
def _qdb_watch_reader(self, loop):
'''Callback when self._qdb_connection_watch.watch_fd() FD is
readable.
Read reported event (watched path change) and fire appropriate event.
'''
import qubesdb # pylint: disable=import-error
try:
path = self._qdb_connection_watch.read_watch()
for watched_path in self._qdb_watch_paths:
if watched_path == path or (
watched_path.endswith('/') and
path.startswith(watched_path)):
self.fire_event('domain-qdb-change:' + watched_path,
path=path)
except qubesdb.DisconnectedError:
loop.remove_reader(self._qdb_connection_watch.watch_fd())
self._qdb_connection_watch.close()
self._qdb_connection_watch = None
def start_qdb_watch(self, name, loop=None):
'''Start watching QubesDB
Calling this method in appropriate time is responsibility of child
class.
'''
import qubesdb # pylint: disable=import-error
self._qdb_connection_watch = qubesdb.QubesDB(name)
if loop is None:
loop = asyncio.get_event_loop()
loop.add_reader(self._qdb_connection_watch.watch_fd(),
self._qdb_watch_reader, loop)
for path in self._qdb_watch_paths:
self._qdb_connection_watch.watch(path)
class VMProperty(qubes.property): class VMProperty(qubes.property):
'''Property that is referring to a VM '''Property that is referring to a VM

View File

@ -55,6 +55,9 @@ class AdminVM(qubes.vm.BaseVM):
self._qdb_connection = None self._qdb_connection = None
self._libvirt_domain = None self._libvirt_domain = None
if not self.app.vmm.offline_mode:
self.start_qdb_watch('dom0')
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -248,6 +248,18 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
This event is a good place to add your custom entries to the qdb. This event is a good place to add your custom entries to the qdb.
.. event:: domain-qdb-change:watched-path (subject, event, path)
Fired when watched QubesDB entry is changed. See
:py:meth:`watch_qdb_path`. *watched-path* part of event name is
what path was registered for watching, *path* in event argument
is what actually have changed (which may be different if watching a
directory, i.e. a path with `/` at the end).
:param subject: Event emitter (the qube object)
:param event: Event name (``'domain-qdb-change'``)
:param path: changed QubesDB path
.. event:: backup-get-files (subject, event) .. event:: backup-get-files (subject, event)
Collects additional file to be included in a backup. Collects additional file to be included in a backup.
@ -726,6 +738,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
if self.storage is None: if self.storage is None:
self.storage = qubes.storage.Storage(self) self.storage = qubes.storage.Storage(self)
if not self.app.vmm.offline_mode and self.is_running():
self.start_qdb_watch(self.name)
@qubes.events.handler('property-set:label') @qubes.events.handler('property-set:label')
def on_property_set_label(self, event, name, newvalue, oldvalue=None): def on_property_set_label(self, event, name, newvalue, oldvalue=None):
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -1770,6 +1785,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self.fire_event('domain-qdb-create') self.fire_event('domain-qdb-create')
self.start_qdb_watch(self.name)
# TODO async; update this in constructor # TODO async; update this in constructor
def _update_libvirt_domain(self): def _update_libvirt_domain(self):
'''Re-initialise :py:attr:`libvirt_domain`.''' '''Re-initialise :py:attr:`libvirt_domain`.'''