217 lines
5.4 KiB
Python
217 lines
5.4 KiB
Python
#!/usr/bin/python2 -O
|
|
|
|
'''Qubes Virtual Machines
|
|
|
|
Main public classes
|
|
-------------------
|
|
|
|
.. autoclass:: BaseVM
|
|
:members:
|
|
:show-inheritance:
|
|
.. autoclass:: property
|
|
:members:
|
|
:show-inheritance:
|
|
|
|
Helper classes and functions
|
|
----------------------------
|
|
|
|
.. autoclass:: VMPlugin
|
|
:members:
|
|
:show-inheritance:
|
|
|
|
Particular VM classes
|
|
---------------------
|
|
|
|
Main types:
|
|
|
|
.. toctree::
|
|
:maxdepth: 1
|
|
|
|
qubesvm
|
|
appvm
|
|
templatevm
|
|
|
|
Special VM types:
|
|
|
|
.. toctree::
|
|
:maxdepth: 1
|
|
|
|
netvm
|
|
proxyvm
|
|
dispvm
|
|
adminvm
|
|
|
|
HVMs:
|
|
|
|
.. toctree::
|
|
:maxdepth: 1
|
|
|
|
hvm
|
|
templatehvm
|
|
|
|
'''
|
|
|
|
import ast
|
|
import collections
|
|
import functools
|
|
import sys
|
|
|
|
import dateutil.parser
|
|
|
|
import qubes.plugins
|
|
|
|
class property(object):
|
|
'''Qubes VM property.
|
|
|
|
This class holds one property that can be saved and loaded from qubes.xml
|
|
|
|
:param str name: name of the property
|
|
:param object default: default value
|
|
:param type type: if not :py:obj:`None`, this is used to initialise value
|
|
:param int order: order of evaluation (bigger order values are later)
|
|
:param str doc: docstring
|
|
|
|
'''
|
|
|
|
def __init__(self, name, default=None, type=None, order=0, doc=None):
|
|
self.__name__ = name
|
|
self._default = default
|
|
self._type = type
|
|
self.order = order
|
|
self.__doc__ = doc
|
|
|
|
self._attr_name = '_qubesprop_' + self.__name__
|
|
|
|
def __get__(self, instance, owner):
|
|
if instance is None:
|
|
return self
|
|
|
|
try:
|
|
return getattr(instance, self._attr_name)
|
|
|
|
except AttributeError:
|
|
if self._default is None:
|
|
raise AttributeError('property not set')
|
|
else:
|
|
return self._default
|
|
|
|
def __set__(self, instance, value):
|
|
setattr(instance, self._attr_name,
|
|
(self._type(value) if self._type is not None else value))
|
|
|
|
def __repr__(self):
|
|
return '<{} object at {:#x} name={!r} default={!r}>'.format(
|
|
self.__class__.__name__, id(self), self.__name__, self._default)
|
|
|
|
def __hash__(self):
|
|
return hash(self.__name__)
|
|
|
|
def __eq__(self, other):
|
|
return self.__name__ == other.__name__
|
|
|
|
class VMPlugin(qubes.plugins.Plugin):
|
|
'''Metaclass for :py:class:`.BaseVM`'''
|
|
def __init__(cls, name, bases, dict_):
|
|
super(VMPlugin, cls).__init__(name, bases, dict_)
|
|
cls.__hooks__ = collections.defaultdict(list)
|
|
|
|
class BaseVM(object):
|
|
'''Base class for all VMs
|
|
|
|
:param xml: xml node from which to deserialise
|
|
:type xml: :py:class:`lxml.etree._Element` or :py:obj:`None`
|
|
|
|
This class is responsible for serialising and deserialising machines and
|
|
provides basic framework. It contains no management logic. For that, see
|
|
:py:class:`qubes.vm.qubesvm.QubesVM`.
|
|
'''
|
|
|
|
__metaclass__ = VMPlugin
|
|
|
|
def get_props_list(self):
|
|
'''List all properties attached to this VM'''
|
|
props = set()
|
|
for class_ in self.__class__.__mro__:
|
|
props.update(prop for prop in class_.__dict__.values()
|
|
if isinstance(prop, property))
|
|
return sorted(props, key=lambda prop: (prop.order, prop.__name__))
|
|
|
|
def __init__(self, xml):
|
|
self._xml = xml
|
|
|
|
self.services = {}
|
|
self.devices = collections.defaultdict(list)
|
|
self.tags = {}
|
|
|
|
if self._xml is None:
|
|
return
|
|
|
|
# properties
|
|
all_names = set(prop.__name__ for prop in self.get_props_list())
|
|
for node in self._xml.xpath('.//property'):
|
|
name = node.get('name')
|
|
value = node.get('ref') or node.text
|
|
|
|
if not name in all_names:
|
|
raise AttributeError(
|
|
'No property {!r} found in {!r}'.format(
|
|
name, self.__class__))
|
|
|
|
setattr(self, name, value)
|
|
|
|
# tags
|
|
for node in self._xml.xpath('.//tag'):
|
|
self.tags[node.get('name')] = node.text
|
|
|
|
# services
|
|
for node in self._xml.xpath('.//service'):
|
|
self.services[node.text] = bool(ast.literal_eval(node.get('enabled', 'True')))
|
|
|
|
# devices (pci, usb, ...)
|
|
for parent in self._xml.xpath('.//devices'):
|
|
devclass = parent.get('class')
|
|
for node in parent.xpath('./device'):
|
|
self.devices[devclass].append(node.text)
|
|
|
|
# firewall
|
|
#TODO
|
|
|
|
def __repr__(self):
|
|
return '<{} object at {:#x} {}>'.format(
|
|
self.__class__.__name__, id(self),
|
|
' '.join('{}={}'.format(prop.__name__, getattr(self, prop.__name__))
|
|
for prop in self.get_props_list()))
|
|
|
|
@classmethod
|
|
def add_hook(cls, event, f):
|
|
'''Add hook to entire VM class and all subclasses
|
|
|
|
:param str event: event type
|
|
:param callable f: function to fire on event
|
|
|
|
Prototype of the function depends on the exact type of event. Classes
|
|
which inherit from this class will also inherit the hook.
|
|
'''
|
|
|
|
cls.__hooks__[event].append(f)
|
|
|
|
def fire_hooks(self, event, *args, **kwargs):
|
|
'''Fire hooks associated with an event
|
|
|
|
:param str event: event type
|
|
|
|
*args* and *kwargs* are passed to each function
|
|
'''
|
|
|
|
for cls in self.__class__.__mro__:
|
|
if not hasattr(cls, '__hooks__'): continue
|
|
for hook in cls.__hooks__[event]:
|
|
hook(self, *args, **kwargs)
|
|
|
|
|
|
def load(class_, D):
|
|
cls = BaseVM[class_]
|
|
return cls(D)
|
|
|
|
__all__ = qubes.plugins.load(__file__)
|