qubes: Allow for explicit closing of objects

This commit is contained in:
Wojtek Porczyk 2017-08-28 14:24:48 +02:00
parent 8547d06cc9
commit de8ff20976
5 changed files with 122 additions and 34 deletions

View File

@ -689,6 +689,21 @@ class PropertyHolder(qubes.events.Emitter):
# pylint: disable=no-member
self.log.fatal(msg)
def close(self):
super().close()
# Remove all properties -- somewhere in them there are cyclic
# references. This just removes all the properties, just in case.
# They are removed directly, bypassing write_once.
for prop in self.property_list():
# pylint: disable=protected-access
try:
delattr(self, prop._attr_name)
except AttributeError:
pass
# pylint: disable=wrong-import-position
from qubes.vm import VMProperty
from qubes.app import Qubes

View File

@ -32,6 +32,7 @@ import subprocess
import sys
import tempfile
import time
import traceback
import uuid
import lxml.etree
@ -138,20 +139,22 @@ class VirConnectWrapper(object):
class VMMConnection(object):
'''Connection to Virtual Machine Manager (libvirt)'''
def __init__(self, offline_mode=None):
def __init__(self, app, offline_mode=None):
'''
:param offline_mode: enable/disable offline mode; default is to
enable when running in chroot as root, otherwise disable
'''
self._libvirt_conn = None
self._xs = None
self._xc = None
self.app = app
if offline_mode is None:
offline_mode = bool(os.getuid() == 0 and
os.stat('/') != os.stat('/proc/1/root/.'))
self._offline_mode = offline_mode
self._libvirt_conn = None
self._xs = None
self._xc = None
@property
def offline_mode(self):
'''Check or enable offline mode (do not actually connect to vmm)'''
@ -218,36 +221,15 @@ class VMMConnection(object):
self.init_vmm_connection()
return self._xc
def register_event_handlers(self, app):
'''Register libvirt event handlers, which will translate libvirt
events into qubes.events. This function should be called only in
'qubesd' process and only when mainloop has been already set.
'''
self.libvirt_conn.domainEventRegisterAny(
None, # any domain
libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
self._domain_event_callback,
app
)
@staticmethod
def _domain_event_callback(_conn, domain, event, _detail, opaque):
'''Generic libvirt event handler (virConnectDomainEventCallback),
translate libvirt event into qubes.events.
'''
app = opaque
try:
vm = app.domains[domain.name()]
except KeyError:
# ignore events for unknown domains
return
if event == libvirt.VIR_DOMAIN_EVENT_STOPPED:
vm.fire_event('domain-shutdown')
def __del__(self):
def close(self):
libvirt.registerErrorHandler(None, None)
if self._xs:
self._xs.close()
self._xs = None
if self._libvirt_conn:
self._libvirt_conn.close()
self._libvirt_conn = None
self._xc = None # and pray it will get garbage-collected
class QubesHost(object):
@ -398,6 +380,12 @@ class VMCollection(object):
self._dict = dict()
def close(self):
del self.app
self._dict.clear()
del self._dict
def __repr__(self):
return '<{} {!r}>'.format(
self.__class__.__name__, list(sorted(self.keys())))
@ -729,6 +717,10 @@ class Qubes(qubes.PropertyHolder):
**kwargs):
#: logger instance for logging global messages
self.log = logging.getLogger('app')
self.log.debug('init() -> %#x', id(self))
self.log.debug('stack:')
for frame in traceback.extract_stack():
self.log.debug('%s', frame)
self._extensions = qubes.ext.get_extensions()
@ -742,7 +734,7 @@ class Qubes(qubes.PropertyHolder):
self.pools = {}
#: Connection to VMM
self.vmm = VMMConnection(offline_mode=offline_mode)
self.vmm = VMMConnection(self, offline_mode=offline_mode)
#: Information about host system
self.host = QubesHost(self)
@ -759,6 +751,7 @@ class Qubes(qubes.PropertyHolder):
self.__load_timestamp = None
self.__locked_fh = None
self._domain_event_callback_id = None
#: jinja2 environment for libvirt XML templates
self.env = jinja2.Environment(
@ -910,6 +903,40 @@ class Qubes(qubes.PropertyHolder):
self._release_lock()
def close(self):
'''Deconstruct the object and break circular references
After calling this the object is unusable, not even for saving.'''
self.log.debug('close() <- %#x', id(self))
for frame in traceback.extract_stack():
self.log.debug('%s', frame)
super().close()
if self._domain_event_callback_id is not None:
self.vmm.libvirt_conn.domainEventDeregisterAny(
self._domain_event_callback_id)
self._domain_event_callback_id = None
# Only our Lord, The God Almighty, knows what references
# are kept in extensions.
del self._extensions
for vm in self.domains:
vm.close()
self.domains.close()
del self.domains
self.vmm.close()
del self.vmm
del self.host
if self.__locked_fh:
self._release_lock()
def _acquire_lock(self, for_save=False):
assert self.__locked_fh is None, 'double lock'
@ -1129,6 +1156,34 @@ class Qubes(qubes.PropertyHolder):
raise qubes.exc.QubesException('No driver %s for pool %s' %
(driver, name))
def register_event_handlers(self):
'''Register libvirt event handlers, which will translate libvirt
events into qubes.events. This function should be called only in
'qubesd' process and only when mainloop has been already set.
'''
self._domain_event_callback_id = (
self.vmm.libvirt_conn.domainEventRegisterAny(
None, # any domain
libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
self._domain_event_callback,
None))
def _domain_event_callback(self, _conn, domain, event, _detail, _opaque):
'''Generic libvirt event handler (virConnectDomainEventCallback),
translate libvirt event into qubes.events.
'''
if not self.events_enabled:
return
try:
vm = self.domains[domain.name()]
except KeyError:
# ignore events for unknown domains
return
if event == libvirt.VIR_DOMAIN_EVENT_STOPPED:
vm.fire_event('domain-shutdown')
@qubes.events.handler('domain-pre-delete')
def on_domain_pre_deleted(self, event, vm):
# pylint: disable=unused-argument

View File

@ -106,6 +106,8 @@ class Emitter(object, metaclass=EmitterMeta):
self.events_enabled = False
self.__handlers__ = collections.defaultdict(set)
def close(self):
self.events_enabled = False
def add_handler(self, event, func):
'''Add event handler to subject's class.

View File

@ -35,7 +35,7 @@ def main(args=None):
loop.close()
raise
args.app.vmm.register_event_handlers(args.app)
args.app.register_event_handlers()
if args.debug:
qubes.log.enable_debug()

View File

@ -297,6 +297,22 @@ class BaseVM(qubes.PropertyHolder):
if hasattr(self, 'name'):
self.init_log()
def close(self):
super().close()
if self._qdb_connection_watch is not None:
asyncio.get_event_loop().remove_reader(
self._qdb_connection_watch.watch_fd())
self._qdb_connection_watch.close()
del self._qdb_connection_watch
del self.app
del self.features
del self.storage
# TODO storage may have circ references, but it doesn't leak fds
del self.devices
del self.tags
def load_extras(self):
# features
for node in self.xml.xpath('./features/feature'):