diff --git a/qubes/app.py b/qubes/app.py index 3bc8065b..5f5ba3fd 100644 --- a/qubes/app.py +++ b/qubes/app.py @@ -30,9 +30,11 @@ import functools import grp import logging import os +import random import sys import tempfile import time +import uuid import jinja2 import libvirt @@ -485,6 +487,16 @@ class VMCollection(object): raise LookupError("Cannot find unused netid!") + def get_new_unused_dispid(self): + for i in range(qubes.config.max_dispid ** 0.5): + dispid = random.SystemRandom().randrange(qubes.config.max_dispid) + if not any(getattr(vm, 'dispid', None) == dispid for vm in self): + return dispid + raise LookupError(( + 'https://xkcd.com/221/', + 'http://dilbert.com/strip/2001-10-25')[random.randint(0, 1)]) + + class Qubes(qubes.PropertyHolder): '''Main Qubes application diff --git a/qubes/config.py b/qubes/config.py index f1c11394..d76716ac 100644 --- a/qubes/config.py +++ b/qubes/config.py @@ -111,3 +111,4 @@ defaults = { max_qid = 254 max_netid = 254 +max_dispid = 10000 diff --git a/qubes/vm/appvm.py b/qubes/vm/appvm.py index bff48105..4e5c4178 100644 --- a/qubes/vm/appvm.py +++ b/qubes/vm/appvm.py @@ -3,6 +3,7 @@ import qubes.events import qubes.vm.qubesvm + from qubes.config import defaults diff --git a/qubes/vm/dispvm.py b/qubes/vm/dispvm.py index 4a6b6738..2066d5cd 100644 --- a/qubes/vm/dispvm.py +++ b/qubes/vm/dispvm.py @@ -1,6 +1,8 @@ #!/usr/bin/python2 -O # vim: fileencoding=utf-8 +import random + import qubes.vm.qubesvm import qubes.vm.appvm import qubes.config @@ -44,6 +46,7 @@ class DispVM(qubes.vm.qubesvm.QubesVM): 'volume_type': 'read-only', } } + super(DispVM, self).__init__(*args, **kwargs) @qubes.events.handler('domain-load') @@ -52,3 +55,49 @@ class DispVM(qubes.vm.qubesvm.QubesVM): # Some additional checks for template based VM assert self.template # self.template.appvms.add(self) # XXX + + + @classmethod + def from_appvm(cls, appvm, **kwargs): + '''Create a new instance from given AppVM + + :param qubes.vm.appvm.AppVM appvm: template from which the VM should \ + be created (could also be name or qid) + :returns: new disposable vm + + *kwargs* are passed to the newly created VM + + >>> import qubes.vm.dispvm.DispVM + >>> dispvm = qubes.vm.dispvm.DispVM.from_appvm(appvm).start() + >>> dispvm.run_service('qubes.VMShell', input='firefox') + >>> dispvm.cleanup() + + This method modifies :file:`qubes.xml` file. In fact, the newly created + vm belongs to other :py:class:`qubes.Qubes` instance than the *app*. + The qube returned is not started. + ''' + store = appvm.app.store if isinstance(appvm, qubes.vm.BaseVM) else None + app = qubes.Qubes(store) + dispvm = app.add_new_vm( + cls, + dispid=app.domains.get_new_unused_dispid(), + template=app.domains[appvm], + **kwargs) + dispvm.create_on_disk() + app.save() + return dispvm + + + def cleanup(self): + '''Clean up after the DispVM + + This stops the disposable qube and removes it from the store. + + This method modifies :file:`qubes.xml` file. + ''' + app = qubes.Qubes(self.app.store) + self = app.domains[self.uuid] + self.force_shutdown() + self.remove_from_disk() + del app.domains[self] + app.save()