654 řádky
		
	
	
		
			21 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			654 řádky
		
	
	
		
			21 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #
 | |
| # 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>
 | |
| #
 | |
| # This program is free software; you can redistribute it and/or modify
 | |
| # it under the terms of the GNU General Public License as published by
 | |
| # the Free Software Foundation; either version 2 of the License, or
 | |
| # (at your option) any later version.
 | |
| #
 | |
| # This program is distributed in the hope that it will be useful,
 | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| # GNU General Public License for more details.
 | |
| #
 | |
| # You should have received a copy of the GNU General Public License along
 | |
| # with this program; if not, write to the Free Software Foundation, Inc.,
 | |
| # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | |
| #
 | |
| 
 | |
| '''
 | |
| Qubes OS
 | |
| 
 | |
| :copyright: © 2010-2015 Invisible Things Lab
 | |
| '''
 | |
| 
 | |
| import builtins
 | |
| import collections
 | |
| import os
 | |
| import os.path
 | |
| 
 | |
| import lxml.etree
 | |
| import qubes.config
 | |
| import qubes.events
 | |
| import qubes.exc
 | |
| 
 | |
| __author__ = 'Invisible Things Lab'
 | |
| __license__ = 'GPLv2 or later'
 | |
| __version__ = 'R3'
 | |
| 
 | |
| 
 | |
| class Label(object):
 | |
|     '''Label definition for virtual machines
 | |
| 
 | |
|     Label specifies colour of the padlock displayed next to VM's name.
 | |
|     When this is a :py:class:`qubes.vm.dispvm.DispVM`, padlock is overlayed
 | |
|     with recycling pictogram.
 | |
| 
 | |
|     :param int index: numeric identificator of label
 | |
|     :param str color: colour specification as in HTML (``#abcdef``)
 | |
|     :param str name: label's name like "red" or "green"
 | |
|     '''
 | |
| 
 | |
|     def __init__(self, index, color, name):
 | |
|         #: numeric identificator of label
 | |
|         self.index = index
 | |
| 
 | |
|         #: colour specification as in HTML (``#abcdef``)
 | |
|         self.color = color
 | |
| 
 | |
|         #: label's name like "red" or "green"
 | |
|         self.name = name
 | |
| 
 | |
|         #: freedesktop icon name, suitable for use in
 | |
|         #: :py:meth:`PyQt4.QtGui.QIcon.fromTheme`
 | |
|         self.icon = 'appvm-' + name
 | |
| 
 | |
|         #: freedesktop icon name, suitable for use in
 | |
|         #: :py:meth:`PyQt4.QtGui.QIcon.fromTheme` on DispVMs
 | |
|         self.icon_dispvm = 'dispvm-' + name
 | |
| 
 | |
| 
 | |
|     @classmethod
 | |
|     def fromxml(cls, xml):
 | |
|         '''Create label definition from XML node
 | |
| 
 | |
|         :param lxml.etree._Element xml: XML node reference
 | |
|         :rtype: :py:class:`qubes.Label`
 | |
|         '''
 | |
| 
 | |
|         index = int(xml.get('id').split('-', 1)[1])
 | |
|         color = xml.get('color')
 | |
|         name = xml.text
 | |
| 
 | |
|         return cls(index, color, name)
 | |
| 
 | |
| 
 | |
|     def __xml__(self):
 | |
|         element = lxml.etree.Element(
 | |
|             'label', id='label-{}'.format(self.index), color=self.color)
 | |
|         element.text = self.name
 | |
|         return element
 | |
| 
 | |
|     def __str__(self):
 | |
|         return self.name
 | |
| 
 | |
|     def __repr__(self):
 | |
|         return '{}({!r}, {!r}, {!r})'.format(
 | |
|             self.__class__.__name__,
 | |
|             self.index,
 | |
|             self.color,
 | |
|             self.name)
 | |
| 
 | |
|     def __eq__(self, other):
 | |
|         if isinstance(other, Label):
 | |
|             return self.name == other.name
 | |
|         return NotImplemented
 | |
| 
 | |
|     @builtins.property
 | |
|     def icon_path(self):
 | |
|         '''Icon path
 | |
| 
 | |
|         .. deprecated:: 2.0
 | |
|            use :py:meth:`PyQt4.QtGui.QIcon.fromTheme` and :py:attr:`icon`
 | |
|         '''
 | |
|         return os.path.join(qubes.config.system_path['qubes_icon_dir'],
 | |
|             self.icon) + ".png"
 | |
| 
 | |
| 
 | |
|     @builtins.property
 | |
|     def icon_path_dispvm(self):
 | |
|         '''Icon path
 | |
| 
 | |
|         .. deprecated:: 2.0
 | |
|            use :py:meth:`PyQt4.QtGui.QIcon.fromTheme` and :py:attr:`icon_dispvm`
 | |
|         '''
 | |
|         return os.path.join(qubes.config.system_path['qubes_icon_dir'],
 | |
|             self.icon_dispvm) + ".png"
 | |
| 
 | |
| 
 | |
| class property(object): # pylint: disable=redefined-builtin,invalid-name
 | |
|     '''Qubes property.
 | |
| 
 | |
|     This class holds one property that can be saved to and loaded from
 | |
|     :file:`qubes.xml`. It is used for both global and per-VM properties.
 | |
| 
 | |
|     Property can be unset by ordinary ``del`` statement or assigning
 | |
|     :py:attr:`DEFAULT` special value to it. After deletion (or before first
 | |
|     assignment/load) attempting to read a property will get its default value
 | |
|     or, when no default, py:class:`exceptions.AttributeError`.
 | |
| 
 | |
|     :param str name: name of the property
 | |
|     :param collections.Callable setter: if not :py:obj:`None`, this is used to \
 | |
|         initialise value; first parameter to the function is holder instance \
 | |
|         and the second is value; this is called before ``type``
 | |
|     :param collections.Callable saver: function to coerce value to something \
 | |
|         readable by setter
 | |
|     :param type type: if not :py:obj:`None`, value is coerced to this type
 | |
|     :param object default: default value; if callable, will be called with \
 | |
|         holder as first argument
 | |
|     :param int load_stage: stage when property should be loaded (see \
 | |
|         :py:class:`Qubes` for description of stages)
 | |
|     :param int order: order of evaluation (bigger order values are later)
 | |
|     :param bool clone: :py:meth:`PropertyHolder.clone_properties` will not \
 | |
|         include this property by default if :py:obj:`False`
 | |
|     :param str ls_head: column head for :program:`qvm-ls`
 | |
|     :param int ls_width: column width in :program:`qvm-ls`
 | |
|     :param str doc: docstring; this should be one paragraph of plain RST, no \
 | |
|         sphinx-specific features
 | |
| 
 | |
|     Setters and savers have following signatures:
 | |
| 
 | |
|         .. :py:function:: setter(self, prop, value)
 | |
|             :noindex:
 | |
| 
 | |
|             :param self: instance of object that is holding property
 | |
|             :param prop: property object
 | |
|             :param value: value being assigned
 | |
| 
 | |
|         .. :py:function:: saver(self, prop, value)
 | |
|             :noindex:
 | |
| 
 | |
|             :param self: instance of object that is holding property
 | |
|             :param prop: property object
 | |
|             :param value: value being saved
 | |
|             :rtype: str
 | |
|             :raises property.DontSave: when property should not be saved at all
 | |
| 
 | |
|     '''
 | |
| 
 | |
|     #: Assigning this value to property means setting it to its default value.
 | |
|     #: If property has no default value, this will unset it.
 | |
|     DEFAULT = object()
 | |
| 
 | |
|     # internal use only
 | |
|     _NO_DEFAULT = object()
 | |
| 
 | |
|     def __init__(self, name, setter=None, saver=None, type=None,
 | |
|             default=_NO_DEFAULT, write_once=False, load_stage=2, order=0,
 | |
|             save_via_ref=False, clone=True,
 | |
|             ls_head=None, ls_width=None, doc=None):
 | |
|         # pylint: disable=redefined-builtin
 | |
|         self.__name__ = name
 | |
|         self._setter = setter
 | |
|         self._saver = saver if saver is not None else (
 | |
|             lambda self, prop, value: str(value))
 | |
|         self.type = type
 | |
|         self._default = default
 | |
|         self._write_once = write_once
 | |
|         self.order = order
 | |
|         self.load_stage = load_stage
 | |
|         self.save_via_ref = save_via_ref
 | |
|         self.clone = clone
 | |
|         self.__doc__ = doc
 | |
|         self._attr_name = '_qubesprop_' + name
 | |
| 
 | |
|         if ls_head is not None or ls_width is not None:
 | |
|             self.ls_head = ls_head or self.__name__.replace('_', '-').upper()
 | |
|             self.ls_width = max(ls_width or 0, len(self.ls_head) + 1)
 | |
| 
 | |
| 
 | |
|     def __get__(self, instance, owner):
 | |
|         if instance is None:
 | |
|             return self
 | |
| 
 | |
|         # XXX this violates duck typing, shall we keep it?
 | |
|         if not isinstance(instance, PropertyHolder):
 | |
|             raise AttributeError('qubes.property should be used on '
 | |
|                 'qubes.PropertyHolder instances only')
 | |
| 
 | |
|         try:
 | |
|             return getattr(instance, self._attr_name)
 | |
| 
 | |
|         except AttributeError:
 | |
|             if self._default is self._NO_DEFAULT:
 | |
|                 raise AttributeError(
 | |
|                     'property {!r} not set'.format(self.__name__))
 | |
|             elif isinstance(self._default, collections.Callable):
 | |
|                 return self._default(instance)
 | |
|             else:
 | |
|                 return self._default
 | |
| 
 | |
| 
 | |
|     def __set__(self, instance, value):
 | |
|         self._enforce_write_once(instance)
 | |
| 
 | |
|         if value is self.__class__.DEFAULT:
 | |
|             self.__delete__(instance)
 | |
|             return
 | |
| 
 | |
|         try:
 | |
|             oldvalue = getattr(instance, self.__name__)
 | |
|             has_oldvalue = True
 | |
|         except AttributeError:
 | |
|             has_oldvalue = False
 | |
| 
 | |
|         if self._setter is not None:
 | |
|             value = self._setter(instance, self, value)
 | |
|         if self.type not in (None, type(value)):
 | |
|             value = self.type(value)
 | |
| 
 | |
|         if has_oldvalue:
 | |
|             instance.fire_event_pre('property-pre-set:' + self.__name__,
 | |
|                 name=self.__name__, newvalue=value, oldvalue=oldvalue)
 | |
|         else:
 | |
|             instance.fire_event_pre('property-pre-set:' + self.__name__,
 | |
|                 name=self.__name__, newvalue=value)
 | |
| 
 | |
|         instance._property_init(self, value) # pylint: disable=protected-access
 | |
| 
 | |
|         if has_oldvalue:
 | |
|             instance.fire_event('property-set:' + self.__name__,
 | |
|                 name=self.__name__, newvalue=value, oldvalue=oldvalue)
 | |
|         else:
 | |
|             instance.fire_event('property-set:' + self.__name__,
 | |
|                 name=self.__name__, newvalue=value)
 | |
| 
 | |
| 
 | |
|     def __delete__(self, instance):
 | |
|         self._enforce_write_once(instance)
 | |
| 
 | |
|         try:
 | |
|             oldvalue = getattr(instance, self._attr_name)
 | |
|             has_oldvalue = True
 | |
|         except AttributeError:
 | |
|             has_oldvalue = False
 | |
| 
 | |
|         if has_oldvalue:
 | |
|             instance.fire_event_pre('property-pre-del:' + self.__name__,
 | |
|                 name=self.__name__, oldvalue=oldvalue)
 | |
|             delattr(instance, self._attr_name)
 | |
|             instance.fire_event('property-del:' + self.__name__,
 | |
|                 name=self.__name__, oldvalue=oldvalue)
 | |
| 
 | |
|         else:
 | |
|             instance.fire_event_pre('property-pre-del:' + self.__name__,
 | |
|                 name=self.__name__)
 | |
|             instance.fire_event('property-del:' + self.__name__,
 | |
|                 name=self.__name__)
 | |
| 
 | |
| 
 | |
|     def __repr__(self):
 | |
|         default = ' default={!r}'.format(self._default) \
 | |
|             if self._default is not self._NO_DEFAULT \
 | |
|             else ''
 | |
|         return '<{} object at {:#x} name={!r}{}>'.format(
 | |
|             self.__class__.__name__, id(self), self.__name__, default) \
 | |
| 
 | |
| 
 | |
|     def __hash__(self):
 | |
|         return hash(self.__name__)
 | |
| 
 | |
|     def __lt__(self, other):
 | |
|         if isinstance(other, property):
 | |
|             return (self.load_stage, self.order, self.__name__) <\
 | |
|                    (other.load_stage, other.order, other.__name__)
 | |
|         return NotImplemented
 | |
| 
 | |
|     def __eq__(self, other):
 | |
|         if isinstance(other, str):
 | |
|             return self.__name__ == other
 | |
|         return isinstance(other, property) and self.__name__ == other.__name__
 | |
| 
 | |
| 
 | |
|     def _enforce_write_once(self, instance):
 | |
|         if self._write_once and not instance.property_is_default(self):
 | |
|             raise AttributeError(
 | |
|                 'property {!r} is write-once and already set'.format(
 | |
|                     self.__name__))
 | |
| 
 | |
| 
 | |
|     #
 | |
|     # exceptions
 | |
|     #
 | |
| 
 | |
|     class DontSave(Exception):
 | |
|         '''This exception may be raised from saver to sign that property should
 | |
|         not be saved.
 | |
|         '''
 | |
|         pass
 | |
| 
 | |
|     @staticmethod
 | |
|     def dontsave(self, prop, value):
 | |
|         '''Dummy saver that never saves anything.'''
 | |
|         # pylint: disable=bad-staticmethod-argument,unused-argument
 | |
|         raise property.DontSave()
 | |
| 
 | |
|     #
 | |
|     # some setters provided
 | |
|     #
 | |
| 
 | |
|     @staticmethod
 | |
|     def forbidden(self, prop, value):
 | |
|         '''Property setter that forbids loading a property.
 | |
| 
 | |
|         This is used to effectively disable property in classes which inherit
 | |
|         unwanted property. When someone attempts to load such a property, it
 | |
| 
 | |
|         :throws AttributeError: always
 | |
|         ''' # pylint: disable=bad-staticmethod-argument,unused-argument
 | |
| 
 | |
|         raise AttributeError(
 | |
|             'setting {} property on {} instance is forbidden'.format(
 | |
|                 prop.__name__, self.__class__.__name__))
 | |
| 
 | |
| 
 | |
|     @staticmethod
 | |
|     def bool(self, prop, value):
 | |
|         '''Property setter for boolean properties.
 | |
| 
 | |
|         It accepts (case-insensitive) ``'0'``, ``'no'`` and ``false`` as
 | |
|         :py:obj:`False` and ``'1'``, ``'yes'`` and ``'true'`` as
 | |
|         :py:obj:`True`.
 | |
|         ''' # pylint: disable=bad-staticmethod-argument,unused-argument
 | |
| 
 | |
|         if isinstance(value, str):
 | |
|             lcvalue = value.lower()
 | |
|             if lcvalue in ('0', 'no', 'false', 'off'):
 | |
|                 return False
 | |
|             if lcvalue in ('1', 'yes', 'true', 'on'):
 | |
|                 return True
 | |
|             raise qubes.exc.QubesValueError(
 | |
|                 'Invalid literal for boolean property: {!r}'.format(value))
 | |
| 
 | |
|         return bool(value)
 | |
| 
 | |
| 
 | |
| class PropertyHolder(qubes.events.Emitter):
 | |
|     '''Abstract class for holding :py:class:`qubes.property`
 | |
| 
 | |
|     Events fired by instances of this class:
 | |
| 
 | |
|         .. event:: property-load (subject, event)
 | |
| 
 | |
|             Fired once after all properties are loaded from XML. Individual
 | |
|             ``property-set`` events are not fired.
 | |
| 
 | |
|         .. event:: property-set:<propname> \
 | |
|                 (subject, event, name, newvalue[, oldvalue])
 | |
| 
 | |
|             Fired when property changes state. Signature is variable,
 | |
|             *oldvalue* is present only if there was an old value.
 | |
| 
 | |
|             :param name: Property name
 | |
|             :param newvalue: New value of the property
 | |
|             :param oldvalue: Old value of the property
 | |
| 
 | |
|         .. event:: property-pre-set:<propname> \
 | |
|                 (subject, event, name, newvalue[, oldvalue])
 | |
| 
 | |
|             Fired before property changes state. Signature is variable,
 | |
|             *oldvalue* is present only if there was an old value.
 | |
| 
 | |
|             :param name: Property name
 | |
|             :param newvalue: New value of the property
 | |
|             :param oldvalue: Old value of the property
 | |
| 
 | |
|         .. event:: property-del:<propname> \
 | |
|                 (subject, event, name[, oldvalue])
 | |
| 
 | |
|             Fired when property gets deleted (is set to default). Signature is
 | |
|             variable, *oldvalue* is present only if there was an old value.
 | |
| 
 | |
|             :param name: Property name
 | |
|             :param oldvalue: Old value of the property
 | |
| 
 | |
|         .. event:: property-pre-del:<propname> \
 | |
|                 (subject, event, name[, oldvalue])
 | |
| 
 | |
|             Fired before property gets deleted (is set to default). Signature
 | |
|             is variable, *oldvalue* is present only if there was an old value.
 | |
| 
 | |
|             :param name: Property name
 | |
|             :param oldvalue: Old value of the property
 | |
| 
 | |
|         .. event:: clone-properties (subject, event, src, proplist)
 | |
| 
 | |
|             :param src: object, from which we are cloning
 | |
|             :param proplist: list of properties
 | |
| 
 | |
|     Members:
 | |
|     '''
 | |
| 
 | |
|     def __init__(self, xml, **kwargs):
 | |
|         self.xml = xml
 | |
| 
 | |
|         propvalues = {}
 | |
| 
 | |
|         all_names = set(prop.__name__ for prop in self.property_list())
 | |
|         for key in list(kwargs):
 | |
|             if not key in all_names:
 | |
|                 continue
 | |
|             propvalues[key] = kwargs.pop(key)
 | |
| 
 | |
|         super(PropertyHolder, self).__init__(**kwargs)
 | |
| 
 | |
|         for key, value in propvalues.items():
 | |
|             setattr(self, key, value)
 | |
| 
 | |
|         if self.xml is not None:
 | |
|             # check if properties are appropriate
 | |
|             all_names = set(prop.__name__ for prop in self.property_list())
 | |
| 
 | |
|             for node in self.xml.xpath('./properties/property'):
 | |
|                 name = node.get('name')
 | |
|                 if name not in all_names:
 | |
|                     raise TypeError(
 | |
|                         'property {!r} not applicable to {!r}'.format(
 | |
|                             name, self.__class__.__name__))
 | |
| 
 | |
|     @classmethod
 | |
|     def property_list(cls, load_stage=None):
 | |
|         '''List all properties attached to this VM's class
 | |
| 
 | |
|         :param load_stage: Filter by load stage
 | |
|         :type load_stage: :py:func:`int` or :py:obj:`None`
 | |
|         '''
 | |
| 
 | |
|         props = set()
 | |
|         for class_ in cls.__mro__:
 | |
|             props.update(prop for prop in class_.__dict__.values()
 | |
|                 if isinstance(prop, property))
 | |
|         if load_stage is not None:
 | |
|             props = set(prop for prop in props
 | |
|                 if prop.load_stage == load_stage)
 | |
|         return sorted(props)
 | |
| 
 | |
|     def _property_init(self, prop, value):
 | |
|         '''Initialise property to a given value, without side effects.
 | |
| 
 | |
|         :param qubes.property prop: property object of particular interest
 | |
|         :param value: value
 | |
|         '''
 | |
| 
 | |
|         # pylint: disable=protected-access
 | |
|         setattr(self, self.property_get_def(prop)._attr_name, value)
 | |
| 
 | |
| 
 | |
|     def property_is_default(self, prop):
 | |
|         '''Check whether property is in it's default value.
 | |
| 
 | |
|         Properties when unset may return some default value, so
 | |
|         ``hasattr(vm, prop.__name__)`` is wrong in some circumstances. This
 | |
|         method allows for checking if the value returned is in fact it's
 | |
|         default value.
 | |
| 
 | |
|         :param qubes.property prop: property object of particular interest
 | |
|         :rtype: bool
 | |
|         ''' # pylint: disable=protected-access
 | |
| 
 | |
|         # both property_get_def() and ._attr_name may throw AttributeError,
 | |
|         # which we don't want to catch
 | |
|         attrname = self.property_get_def(prop)._attr_name
 | |
|         return not hasattr(self, attrname)
 | |
| 
 | |
| 
 | |
|     @classmethod
 | |
|     def property_get_def(cls, prop):
 | |
|         '''Return property definition object.
 | |
| 
 | |
|         If prop is already :py:class:`qubes.property` instance, return the same
 | |
|         object.
 | |
| 
 | |
|         :param prop: property object or name
 | |
|         :type prop: qubes.property or str
 | |
|         :rtype: qubes.property
 | |
|         '''
 | |
| 
 | |
|         if isinstance(prop, qubes.property):
 | |
|             return prop
 | |
| 
 | |
|         for p in cls.property_list():
 | |
|             if p.__name__ == prop:
 | |
|                 return p
 | |
| 
 | |
|         raise AttributeError('No property {!r} found in {!r}'.format(
 | |
|             prop, cls))
 | |
| 
 | |
| 
 | |
|     def load_properties(self, load_stage=None):
 | |
|         '''Load properties from immediate children of XML node.
 | |
| 
 | |
|         ``property-set`` events are not fired for each individual property.
 | |
| 
 | |
|         :param int load_stage: Stage of loading.
 | |
|         '''
 | |
| 
 | |
|         if self.xml is None:
 | |
|             return
 | |
|         all_names = set(
 | |
|             prop.__name__ for prop in self.property_list(load_stage))
 | |
|         for node in self.xml.xpath('./properties/property'):
 | |
|             name = node.get('name')
 | |
|             value = node.get('ref') or node.text
 | |
| 
 | |
|             if not name in all_names:
 | |
|                 continue
 | |
| 
 | |
|             setattr(self, name, value)
 | |
| 
 | |
| 
 | |
|     def xml_properties(self, with_defaults=False):
 | |
|         '''Iterator that yields XML nodes representing set properties.
 | |
| 
 | |
|         :param bool with_defaults: If :py:obj:`True`, then it also includes \
 | |
|             properties which were not set explicite, but have default values \
 | |
|             filled.
 | |
|         '''
 | |
| 
 | |
| 
 | |
|         properties = lxml.etree.Element('properties')
 | |
| 
 | |
|         for prop in self.property_list():
 | |
|             # pylint: disable=protected-access
 | |
|             try:
 | |
|                 value = getattr(
 | |
|                     self, (prop.__name__ if with_defaults else prop._attr_name))
 | |
|             except AttributeError:
 | |
|                 continue
 | |
| 
 | |
|             try:
 | |
|                 value = prop._saver(self, prop, value)
 | |
|             except property.DontSave:
 | |
|                 continue
 | |
| 
 | |
|             element = lxml.etree.Element('property', name=prop.__name__)
 | |
|             if prop.save_via_ref:
 | |
|                 element.set('ref', value)
 | |
|             else:
 | |
|                 element.text = value
 | |
|             properties.append(element)
 | |
| 
 | |
|         return properties
 | |
| 
 | |
| 
 | |
|     # this was clone_attrs
 | |
|     def clone_properties(self, src, proplist=None):
 | |
|         '''Clone properties from other object.
 | |
| 
 | |
|         :param PropertyHolder src: source object
 | |
|         :param list proplist: list of properties \
 | |
|             (:py:obj:`None` or omit for all properties except those with \
 | |
|             :py:attr:`property.clone` set to :py:obj:`False`)
 | |
|         '''
 | |
| 
 | |
|         if proplist is None:
 | |
|             proplist = [prop for prop in self.property_list()
 | |
|                 if prop.clone]
 | |
|         else:
 | |
|             proplist = [prop for prop in self.property_list()
 | |
|                 if prop.__name__ in proplist or prop in proplist]
 | |
| 
 | |
|         for prop in proplist:
 | |
|             try:
 | |
|                 # pylint: disable=protected-access
 | |
|                 self._property_init(prop, getattr(src, prop._attr_name))
 | |
|             except AttributeError:
 | |
|                 continue
 | |
| 
 | |
|         self.fire_event('clone-properties', src=src, proplist=proplist)
 | |
| 
 | |
| 
 | |
|     def property_require(self, prop, allow_none=False, hard=False):
 | |
|         '''Complain badly when property is not set.
 | |
| 
 | |
|         :param prop: property name or object
 | |
|         :type prop: qubes.property or str
 | |
|         :param bool allow_none: if :py:obj:`True`, don't complain if \
 | |
|             :py:obj:`None` is found
 | |
|         :param bool hard: if :py:obj:`True`, raise :py:class:`AssertionError`; \
 | |
|             if :py:obj:`False`, log warning instead
 | |
|         '''
 | |
| 
 | |
|         if isinstance(prop, qubes.property):
 | |
|             prop = prop.__name__
 | |
| 
 | |
|         try:
 | |
|             value = getattr(self, prop)
 | |
|             if value is None and not allow_none:
 | |
|                 raise AttributeError()
 | |
|         except AttributeError:
 | |
|             # pylint: disable=no-member
 | |
|             msg = 'Required property {!r} not set on {!r}'.format(prop, self)
 | |
|             if hard:
 | |
|                 raise AssertionError(msg)
 | |
|             else:
 | |
|                 # pylint: disable=no-member
 | |
|                 self.log.fatal(msg)
 | |
| 
 | |
| # pylint: disable=wrong-import-position
 | |
| from qubes.vm import VMProperty
 | |
| from qubes.app import Qubes
 | |
| 
 | |
| __all__ = [
 | |
|     'Label',
 | |
|     'PropertyHolder',
 | |
|     'Qubes',
 | |
|     'VMProperty',
 | |
|     'property',
 | |
| ]
 | 
