2015-01-19 17:06:30 +01:00
|
|
|
#
|
|
|
|
# The Qubes OS Project, https://www.qubes-os.org/
|
|
|
|
#
|
|
|
|
# Copyright (C) 2010-2015 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
|
|
|
# Copyright (C) 2011-2015 Marek Marczykowski-Górecki
|
|
|
|
# <marmarek@invisiblethingslab.com>
|
|
|
|
# Copyright (C) 2014-2015 Wojtek Porczyk <woju@invisiblethingslab.com>
|
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# This library is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
|
|
# License as published by the Free Software Foundation; either
|
|
|
|
# version 2.1 of the License, or (at your option) any later version.
|
2015-01-19 17:06:30 +01:00
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# This library is distributed in the hope that it will be useful,
|
2015-01-19 17:06:30 +01:00
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2017-10-12 00:11:50 +02:00
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
# Lesser General Public License for more details.
|
2015-01-19 17:06:30 +01:00
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
|
|
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
|
2015-01-19 17:06:30 +01:00
|
|
|
#
|
2014-11-13 14:38:41 +01:00
|
|
|
|
2014-11-13 18:10:27 +01:00
|
|
|
'''Qubes Virtual Machines
|
|
|
|
|
|
|
|
'''
|
2017-07-25 01:00:04 +02:00
|
|
|
import asyncio
|
2017-03-28 20:42:10 +02:00
|
|
|
import re
|
2017-05-13 16:27:34 +02:00
|
|
|
import string
|
2017-09-26 13:24:22 +02:00
|
|
|
import uuid
|
2014-11-13 14:38:41 +01:00
|
|
|
|
2014-12-05 14:58:05 +01:00
|
|
|
import lxml.etree
|
2014-11-13 14:38:41 +01:00
|
|
|
|
2014-12-05 14:58:05 +01:00
|
|
|
import qubes
|
2016-03-02 12:17:29 +01:00
|
|
|
import qubes.devices
|
2014-12-09 14:14:24 +01:00
|
|
|
import qubes.events
|
2018-11-15 16:59:00 +01:00
|
|
|
import qubes.features
|
2016-04-28 16:00:29 +02:00
|
|
|
import qubes.log
|
2014-11-13 14:38:41 +01:00
|
|
|
|
2017-04-03 13:11:34 +02:00
|
|
|
VM_ENTRY_POINT = 'qubes.vm'
|
2014-11-13 14:38:41 +01:00
|
|
|
|
2017-03-28 20:42:10 +02:00
|
|
|
def validate_name(holder, prop, value):
|
|
|
|
''' Check if value is syntactically correct VM name '''
|
|
|
|
if not isinstance(value, str):
|
|
|
|
raise TypeError('VM name must be string, {!r} found'.format(
|
|
|
|
type(value).__name__))
|
|
|
|
if len(value) > 31:
|
|
|
|
if holder is not None and prop is not None:
|
|
|
|
raise qubes.exc.QubesPropertyValueError(holder, prop, value,
|
|
|
|
'{} value must be shorter than 32 characters'.format(
|
|
|
|
prop.__name__))
|
|
|
|
else:
|
|
|
|
raise qubes.exc.QubesValueError(
|
|
|
|
'VM name must be shorter than 32 characters')
|
|
|
|
|
|
|
|
# this regexp does not contain '+'; if it had it, we should specifically
|
|
|
|
# disallow 'lost+found' #1440
|
|
|
|
if re.match(r"^[a-zA-Z][a-zA-Z0-9_-]*$", value) is None:
|
|
|
|
if holder is not None and prop is not None:
|
|
|
|
raise qubes.exc.QubesPropertyValueError(holder, prop, value,
|
|
|
|
'{} value contains illegal characters'.format(prop.__name__))
|
|
|
|
else:
|
|
|
|
raise qubes.exc.QubesValueError(
|
|
|
|
'VM name contains illegal characters')
|
2017-08-13 21:11:45 +02:00
|
|
|
if value in ('none', 'default'):
|
|
|
|
raise qubes.exc.QubesValueError(
|
|
|
|
'VM name cannot be \'none\' nor \'default\'')
|
2017-03-28 20:42:10 +02:00
|
|
|
|
2017-09-26 13:24:22 +02:00
|
|
|
def setter_label(self, prop, value):
|
|
|
|
''' Helper for setting the domain label '''
|
|
|
|
# pylint: disable=unused-argument
|
|
|
|
if isinstance(value, qubes.Label):
|
|
|
|
return value
|
|
|
|
if isinstance(value, str) and value.startswith('label-'):
|
|
|
|
return self.app.labels[int(value.split('-', 1)[1])]
|
|
|
|
|
|
|
|
return self.app.get_label(value)
|
|
|
|
|
|
|
|
|
|
|
|
def _setter_qid(self, prop, value):
|
|
|
|
''' Helper for setting the domain qid '''
|
|
|
|
# pylint: disable=unused-argument
|
|
|
|
value = int(value)
|
|
|
|
if not 0 <= value <= qubes.config.max_qid:
|
|
|
|
raise ValueError(
|
|
|
|
'{} value must be between 0 and qubes.config.max_qid'.format(
|
|
|
|
prop.__name__))
|
|
|
|
return value
|
2017-03-28 20:42:10 +02:00
|
|
|
|
2016-03-03 13:03:27 +01:00
|
|
|
|
2017-05-13 16:27:34 +02:00
|
|
|
class Tags(set):
|
|
|
|
'''Manager of the tags.
|
|
|
|
|
|
|
|
Tags are simple: tag either can be present on qube or not. Tag is a
|
|
|
|
simple string consisting of ASCII alphanumeric characters, plus `_` and
|
|
|
|
`-`.
|
|
|
|
|
|
|
|
This class inherits from set, but has most of the methods that manipulate
|
|
|
|
the item disarmed (they raise NotImplementedError). The ones that are left
|
|
|
|
fire appropriate events on the qube that owns an instance of this class.
|
|
|
|
'''
|
|
|
|
|
|
|
|
#
|
|
|
|
# Those are the methods that affect contents. Either disarm them or make
|
|
|
|
# them report appropriate events. Good approach is to rewrite them carefully
|
|
|
|
# using official documentation, but use only our (overloaded) methods.
|
|
|
|
#
|
|
|
|
def __init__(self, vm, seq=()):
|
|
|
|
super(Tags, self).__init__()
|
|
|
|
self.vm = vm
|
|
|
|
self.update(seq)
|
|
|
|
|
|
|
|
def clear(self):
|
|
|
|
'''Remove all tags'''
|
|
|
|
for item in tuple(self):
|
|
|
|
self.remove(item)
|
|
|
|
|
|
|
|
def symmetric_difference_update(self, *args, **kwargs):
|
|
|
|
'''Not implemented
|
|
|
|
:raises: NotImplementedError
|
|
|
|
'''
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def intersection_update(self, *args, **kwargs):
|
|
|
|
'''Not implemented
|
|
|
|
:raises: NotImplementedError
|
|
|
|
'''
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def pop(self):
|
|
|
|
'''Not implemented
|
|
|
|
:raises: NotImplementedError
|
|
|
|
'''
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def discard(self, elem):
|
|
|
|
'''Remove a tag if present'''
|
|
|
|
if elem in self:
|
|
|
|
self.remove(elem)
|
|
|
|
|
|
|
|
def update(self, *others):
|
|
|
|
'''Add tags from iterable(s)'''
|
|
|
|
for other in others:
|
|
|
|
for elem in other:
|
|
|
|
self.add(elem)
|
|
|
|
|
|
|
|
def add(self, elem):
|
|
|
|
'''Add a tag'''
|
|
|
|
allowed_chars = string.ascii_letters + string.digits + '_-'
|
|
|
|
if any(i not in allowed_chars for i in elem):
|
|
|
|
raise ValueError('Invalid character in tag')
|
|
|
|
if elem in self:
|
|
|
|
return
|
|
|
|
super(Tags, self).add(elem)
|
2018-01-06 15:05:34 +01:00
|
|
|
self.vm.fire_event('domain-tag-add:' + elem, tag=elem)
|
2017-05-13 16:27:34 +02:00
|
|
|
|
|
|
|
def remove(self, elem):
|
|
|
|
'''Remove a tag'''
|
|
|
|
super(Tags, self).remove(elem)
|
2018-01-06 15:05:34 +01:00
|
|
|
self.vm.fire_event('domain-tag-delete:' + elem, tag=elem)
|
2017-05-13 16:27:34 +02:00
|
|
|
|
|
|
|
#
|
|
|
|
# end of overriding
|
|
|
|
#
|
|
|
|
|
2017-06-19 19:57:15 +02:00
|
|
|
@staticmethod
|
|
|
|
def validate_tag(tag):
|
|
|
|
safe_set = string.ascii_letters + string.digits + '-_'
|
2018-06-11 12:32:05 +02:00
|
|
|
if not all((x in safe_set) for x in tag):
|
|
|
|
raise ValueError('disallowed characters')
|
2017-06-19 19:57:15 +02:00
|
|
|
|
2017-05-13 16:27:34 +02:00
|
|
|
|
2017-05-12 14:21:44 +02:00
|
|
|
class BaseVM(qubes.PropertyHolder):
|
2014-11-18 17:35:05 +01:00
|
|
|
'''Base class for all VMs
|
|
|
|
|
2014-12-09 14:14:24 +01:00
|
|
|
:param app: Qubes application context
|
|
|
|
:type app: :py:class:`qubes.Qubes`
|
2014-11-18 17:35:05 +01:00
|
|
|
:param xml: xml node from which to deserialise
|
|
|
|
:type xml: :py:class:`lxml.etree._Element` or :py:obj:`None`
|
|
|
|
|
2014-12-18 14:36:09 +01:00
|
|
|
This class is responsible for serializing and deserialising machines and
|
2014-11-18 17:35:05 +01:00
|
|
|
provides basic framework. It contains no management logic. For that, see
|
|
|
|
:py:class:`qubes.vm.qubesvm.QubesVM`.
|
|
|
|
'''
|
2015-01-19 19:02:28 +01:00
|
|
|
# pylint: disable=no-member
|
2014-11-18 17:35:05 +01:00
|
|
|
|
2017-09-26 13:24:22 +02:00
|
|
|
uuid = qubes.property('uuid', type=uuid.UUID, write_once=True,
|
|
|
|
clone=False,
|
|
|
|
doc='UUID from libvirt.')
|
|
|
|
|
|
|
|
name = qubes.property('name', type=str, write_once=True,
|
|
|
|
clone=False,
|
|
|
|
doc='User-specified name of the domain.')
|
|
|
|
|
|
|
|
qid = qubes.property('qid', type=int, write_once=True,
|
|
|
|
setter=_setter_qid,
|
|
|
|
clone=False,
|
|
|
|
doc='''Internal, persistent identificator of particular domain. Note
|
|
|
|
this is different from Xen domid.''')
|
|
|
|
|
|
|
|
label = qubes.property('label',
|
|
|
|
setter=setter_label,
|
|
|
|
doc='''Colourful label assigned to VM. This is where the colour of the
|
|
|
|
padlock is set.''')
|
|
|
|
|
2016-03-03 13:03:27 +01:00
|
|
|
def __init__(self, app, xml, features=None, devices=None, tags=None,
|
2015-01-21 15:24:29 +01:00
|
|
|
**kwargs):
|
2015-01-19 19:02:28 +01:00
|
|
|
# pylint: disable=redefined-outer-name
|
2015-01-21 15:24:29 +01:00
|
|
|
|
2017-09-28 02:37:43 +02:00
|
|
|
self._qdb_watch_paths = set()
|
|
|
|
self._qdb_connection_watch = None
|
|
|
|
|
2015-07-01 17:10:10 +02:00
|
|
|
# self.app must be set before super().__init__, because some property
|
|
|
|
# setters need working .app attribute
|
2015-01-19 18:03:23 +01:00
|
|
|
#: mother :py:class:`qubes.Qubes` object
|
2014-12-05 14:58:05 +01:00
|
|
|
self.app = app
|
2015-01-19 18:03:23 +01:00
|
|
|
|
2015-07-01 17:10:10 +02:00
|
|
|
super(BaseVM, self).__init__(xml, **kwargs)
|
|
|
|
|
2016-03-03 13:03:27 +01:00
|
|
|
#: dictionary of features of this qube
|
2018-11-15 16:59:00 +01:00
|
|
|
self.features = qubes.features.Features(self, features)
|
2015-01-19 18:03:23 +01:00
|
|
|
|
2015-01-22 19:25:00 +01:00
|
|
|
#: :py:class:`DeviceManager` object keeping devices that are attached to
|
2015-01-19 18:03:23 +01:00
|
|
|
#: this domain
|
2016-03-02 12:17:29 +01:00
|
|
|
self.devices = devices or qubes.devices.DeviceManager(self)
|
2015-01-19 18:03:23 +01:00
|
|
|
|
|
|
|
#: user-specified tags
|
2017-05-13 16:27:34 +02:00
|
|
|
self.tags = Tags(self, tags or ())
|
2014-12-05 14:58:05 +01:00
|
|
|
|
2015-01-21 17:03:17 +01:00
|
|
|
#: logger instance for logging messages related to this VM
|
|
|
|
self.log = None
|
|
|
|
|
2017-06-13 13:17:09 +02:00
|
|
|
#: storage volumes
|
|
|
|
self.volumes = {}
|
|
|
|
|
|
|
|
#: storage manager
|
|
|
|
self.storage = None
|
|
|
|
|
2015-09-23 16:25:53 +02:00
|
|
|
if hasattr(self, 'name'):
|
|
|
|
self.init_log()
|
|
|
|
|
2017-08-28 14:24:48 +02:00
|
|
|
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
|
|
|
|
|
2016-06-26 02:18:13 +02:00
|
|
|
def load_extras(self):
|
2017-12-01 03:03:44 +01:00
|
|
|
if self.xml is None:
|
|
|
|
return
|
|
|
|
|
2016-06-26 02:18:13 +02:00
|
|
|
# features
|
|
|
|
for node in self.xml.xpath('./features/feature'):
|
|
|
|
self.features[node.get('name')] = node.text
|
|
|
|
|
|
|
|
# devices (pci, usb, ...)
|
|
|
|
for parent in self.xml.xpath('./devices'):
|
|
|
|
devclass = parent.get('class')
|
|
|
|
for node in parent.xpath('./device'):
|
2017-04-08 04:09:56 +02:00
|
|
|
options = {}
|
2017-05-22 15:27:14 +02:00
|
|
|
for option in node.xpath('./option'):
|
|
|
|
options[option.get('name')] = option.text
|
2017-04-08 04:09:56 +02:00
|
|
|
|
|
|
|
device_assignment = qubes.devices.DeviceAssignment(
|
2016-06-26 04:07:15 +02:00
|
|
|
self.app.domains[node.get('backend-domain')],
|
2017-04-08 04:09:56 +02:00
|
|
|
node.get('id'),
|
|
|
|
options,
|
|
|
|
persistent=True
|
2016-06-26 04:07:15 +02:00
|
|
|
)
|
2017-06-26 01:52:13 +02:00
|
|
|
self.devices[devclass].load_persistent(device_assignment)
|
2016-06-26 02:18:13 +02:00
|
|
|
|
|
|
|
# tags
|
|
|
|
for node in self.xml.xpath('./tags/tag'):
|
2017-05-13 16:27:34 +02:00
|
|
|
self.tags.add(node.get('name'))
|
2016-06-26 02:18:13 +02:00
|
|
|
|
|
|
|
# SEE:1815 firewall, policy.
|
|
|
|
|
2015-01-21 17:03:17 +01:00
|
|
|
def init_log(self):
|
|
|
|
'''Initialise logger for this domain.'''
|
|
|
|
self.log = qubes.log.get_vm_logger(self.name)
|
|
|
|
|
2014-12-05 14:58:05 +01:00
|
|
|
def __xml__(self):
|
2015-01-12 18:57:37 +01:00
|
|
|
element = lxml.etree.Element('domain')
|
|
|
|
element.set('id', 'domain-' + str(self.qid))
|
|
|
|
element.set('class', self.__class__.__name__)
|
2014-12-05 14:58:05 +01:00
|
|
|
|
2015-01-13 15:56:10 +01:00
|
|
|
element.append(self.xml_properties())
|
2014-12-05 14:58:05 +01:00
|
|
|
|
2016-03-03 13:03:27 +01:00
|
|
|
features = lxml.etree.Element('features')
|
|
|
|
for feature in self.features:
|
2016-04-03 03:43:01 +02:00
|
|
|
node = lxml.etree.Element('feature', name=feature)
|
2016-03-09 16:53:36 +01:00
|
|
|
node.text = self.features[feature]
|
2016-03-03 13:03:27 +01:00
|
|
|
features.append(node)
|
|
|
|
element.append(features)
|
2014-12-05 14:58:05 +01:00
|
|
|
|
|
|
|
for devclass in self.devices:
|
|
|
|
devices = lxml.etree.Element('devices')
|
|
|
|
devices.set('class', devclass)
|
2017-03-17 13:08:37 +01:00
|
|
|
for device in self.devices[devclass].assignments(persistent=True):
|
2014-12-05 14:58:05 +01:00
|
|
|
node = lxml.etree.Element('device')
|
2016-06-26 04:07:15 +02:00
|
|
|
node.set('backend-domain', device.backend_domain.name)
|
|
|
|
node.set('id', device.ident)
|
2017-05-22 15:27:14 +02:00
|
|
|
for key, val in device.options.items():
|
|
|
|
option_node = lxml.etree.Element('option')
|
|
|
|
option_node.set('name', key)
|
|
|
|
option_node.text = val
|
|
|
|
node.append(option_node)
|
2014-12-05 14:58:05 +01:00
|
|
|
devices.append(node)
|
|
|
|
element.append(devices)
|
|
|
|
|
|
|
|
tags = lxml.etree.Element('tags')
|
|
|
|
for tag in self.tags:
|
|
|
|
node = lxml.etree.Element('tag', name=tag)
|
|
|
|
tags.append(node)
|
|
|
|
element.append(tags)
|
|
|
|
|
|
|
|
return element
|
2014-11-13 14:38:41 +01:00
|
|
|
|
|
|
|
def __repr__(self):
|
2014-12-17 13:32:58 +01:00
|
|
|
proprepr = []
|
2015-01-21 12:50:00 +01:00
|
|
|
for prop in self.property_list():
|
2017-09-19 17:02:19 +02:00
|
|
|
if prop.__name__ in ('name', 'qid'):
|
|
|
|
continue
|
2014-12-17 13:32:58 +01:00
|
|
|
try:
|
2016-03-07 01:00:15 +01:00
|
|
|
proprepr.append('{}={!s}'.format(
|
2014-12-17 13:32:58 +01:00
|
|
|
prop.__name__, getattr(self, prop.__name__)))
|
|
|
|
except AttributeError:
|
|
|
|
continue
|
2014-11-13 14:38:41 +01:00
|
|
|
|
2017-09-19 17:02:19 +02:00
|
|
|
return '<{} at {:#x} name={!r} qid={!r} {}>'.format(type(self).__name__,
|
|
|
|
id(self), self.name, self.qid, ' '.join(proprepr))
|
2014-12-05 14:58:05 +01:00
|
|
|
|
2014-12-29 12:46:16 +01:00
|
|
|
#
|
|
|
|
# xml serialising methods
|
|
|
|
#
|
|
|
|
|
2017-06-01 03:49:57 +02:00
|
|
|
def create_config_file(self):
|
2014-12-29 12:46:16 +01:00
|
|
|
'''Create libvirt's XML domain config file
|
|
|
|
|
|
|
|
'''
|
2016-10-04 11:30:29 +02:00
|
|
|
domain_config = self.app.env.select_template([
|
|
|
|
'libvirt/xen/by-name/{}.xml'.format(self.name),
|
|
|
|
'libvirt/xen-user.xml',
|
|
|
|
'libvirt/xen-dist.xml',
|
|
|
|
'libvirt/xen.xml',
|
2017-06-01 03:49:57 +02:00
|
|
|
]).render(vm=self)
|
2014-12-29 12:46:16 +01:00
|
|
|
return domain_config
|
|
|
|
|
2017-07-25 01:00:04 +02:00
|
|
|
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
|
|
|
|
|
2017-09-28 16:12:05 +02:00
|
|
|
def start_qdb_watch(self, loop=None):
|
2017-07-25 01:00:04 +02:00
|
|
|
'''Start watching QubesDB
|
|
|
|
|
|
|
|
Calling this method in appropriate time is responsibility of child
|
|
|
|
class.
|
|
|
|
'''
|
2017-09-28 16:12:05 +02:00
|
|
|
# cleanup old watch connection first, if any
|
|
|
|
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()
|
|
|
|
|
2017-07-25 01:00:04 +02:00
|
|
|
import qubesdb # pylint: disable=import-error
|
2017-09-28 16:12:05 +02:00
|
|
|
self._qdb_connection_watch = qubesdb.QubesDB(self.name)
|
2017-07-25 01:00:04 +02:00
|
|
|
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)
|
|
|
|
|
2017-09-26 13:31:12 +02:00
|
|
|
@qubes.stateless_property
|
|
|
|
def klass(self):
|
|
|
|
'''Domain class name'''
|
|
|
|
return type(self).__name__
|
2016-04-28 16:00:29 +02:00
|
|
|
|
|
|
|
class VMProperty(qubes.property):
|
|
|
|
'''Property that is referring to a VM
|
|
|
|
|
|
|
|
:param type vmclass: class that returned VM is supposed to be instance of
|
|
|
|
|
|
|
|
and all supported by :py:class:`property` with the exception of ``type`` \
|
|
|
|
and ``setter``
|
|
|
|
'''
|
|
|
|
|
|
|
|
_none_value = ''
|
|
|
|
|
|
|
|
def __init__(self, name, vmclass=BaseVM, allow_none=False,
|
|
|
|
**kwargs):
|
|
|
|
if 'type' in kwargs:
|
|
|
|
raise TypeError(
|
|
|
|
"'type' keyword parameter is unsupported in {}".format(
|
|
|
|
self.__class__.__name__))
|
|
|
|
if not issubclass(vmclass, BaseVM):
|
|
|
|
raise TypeError(
|
|
|
|
"'vmclass' should specify a subclass of qubes.vm.BaseVM")
|
|
|
|
|
|
|
|
super(VMProperty, self).__init__(name,
|
|
|
|
saver=(lambda self_, prop, value:
|
|
|
|
self._none_value if value is None else value.name),
|
|
|
|
**kwargs)
|
|
|
|
self.vmclass = vmclass
|
|
|
|
self.allow_none = allow_none
|
|
|
|
|
|
|
|
def __set__(self, instance, value):
|
|
|
|
if value is self.__class__.DEFAULT:
|
|
|
|
self.__delete__(instance)
|
|
|
|
return
|
|
|
|
|
|
|
|
if value == self._none_value:
|
|
|
|
value = None
|
|
|
|
if value is None:
|
|
|
|
if self.allow_none:
|
|
|
|
super(VMProperty, self).__set__(instance, value)
|
|
|
|
return
|
2018-07-15 23:08:23 +02:00
|
|
|
raise ValueError(
|
|
|
|
'Property {!r} does not allow setting to {!r}'.format(
|
|
|
|
self.__name__, value))
|
2016-04-28 16:00:29 +02:00
|
|
|
|
|
|
|
app = instance if isinstance(instance, qubes.Qubes) else instance.app
|
|
|
|
|
|
|
|
try:
|
|
|
|
vm = app.domains[value]
|
|
|
|
except KeyError:
|
|
|
|
raise qubes.exc.QubesVMNotFoundError(value)
|
|
|
|
|
|
|
|
if not isinstance(vm, self.vmclass):
|
2016-08-17 02:13:59 +02:00
|
|
|
raise TypeError('wrong VM class: domains[{!r}] is of type {!s} '
|
2016-04-28 16:00:29 +02:00
|
|
|
'and not {!s}'.format(value,
|
|
|
|
vm.__class__.__name__,
|
|
|
|
self.vmclass.__name__))
|
|
|
|
|
|
|
|
super(VMProperty, self).__set__(instance, vm)
|
2017-03-28 20:43:53 +02:00
|
|
|
|
|
|
|
def sanitize(self, *, untrusted_newvalue):
|
|
|
|
try:
|
|
|
|
untrusted_vmname = untrusted_newvalue.decode('ascii')
|
|
|
|
except UnicodeDecodeError:
|
|
|
|
raise qubes.exc.QubesValueError
|
2017-05-25 12:27:30 +02:00
|
|
|
if untrusted_vmname == '':
|
|
|
|
# allow empty VM name for setting VMProperty value, because it's
|
|
|
|
# string representation of None (see self._none_value)
|
|
|
|
return untrusted_vmname
|
2017-03-28 20:43:53 +02:00
|
|
|
validate_name(None, self, untrusted_vmname)
|
|
|
|
return untrusted_vmname
|