features.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. #
  2. # The Qubes OS Project, https://www.qubes-os.org/
  3. #
  4. # Copyright (C) 2010-2015 Joanna Rutkowska <joanna@invisiblethingslab.com>
  5. # Copyright (C) 2011-2015 Marek Marczykowski-Górecki
  6. # <marmarek@invisiblethingslab.com>
  7. # Copyright (C) 2014-2018 Wojtek Porczyk <woju@invisiblethingslab.com>
  8. #
  9. # This library is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU Lesser General Public
  11. # License as published by the Free Software Foundation; either
  12. # version 2.1 of the License, or (at your option) any later version.
  13. #
  14. # This library is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. # Lesser General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU Lesser General Public
  20. # License along with this library; if not, see <https://www.gnu.org/licenses/>.
  21. #
  22. from . import vm as _vm
  23. _NO_DEFAULT = object()
  24. class Features(dict):
  25. '''Manager of the features.
  26. Features can have three distinct values: no value (not present in mapping,
  27. which is closest thing to :py:obj:`None`), empty string (which is
  28. interpreted as :py:obj:`False`) and non-empty string, which is
  29. :py:obj:`True`. Anything assigned to the mapping is coerced to strings,
  30. however if you assign instances of :py:class:`bool`, they are converted as
  31. described above. Be aware that assigning the number `0` (which is considered
  32. false in Python) will result in string `'0'`, which is considered true.
  33. This class inherits from dict, but has most of the methods that manipulate
  34. the item disarmed (they raise NotImplementedError). The ones that are left
  35. fire appropriate events on the qube that owns an instance of this class.
  36. '''
  37. #
  38. # Those are the methods that affect contents. Either disarm them or make
  39. # them report appropriate events. Good approach is to rewrite them carefully
  40. # using official documentation, but use only our (overloaded) methods.
  41. #
  42. def __init__(self, subject, other=None, **kwargs):
  43. super().__init__()
  44. self.subject = subject
  45. self.update(other, **kwargs)
  46. def __delitem__(self, key):
  47. self.subject.fire_event('domain-feature-pre-delete:' + key, feature=key)
  48. super().__delitem__(key)
  49. self.subject.fire_event('domain-feature-delete:' + key, feature=key)
  50. def __setitem__(self, key, value):
  51. if value is None or isinstance(value, bool):
  52. value = '1' if value else ''
  53. else:
  54. value = str(value)
  55. try:
  56. oldvalue = self[key]
  57. has_oldvalue = True
  58. except KeyError:
  59. has_oldvalue = False
  60. if has_oldvalue:
  61. self.subject.fire_event('domain-feature-pre-set:' + key,
  62. pre_event=True,
  63. feature=key, value=value, oldvalue=oldvalue)
  64. else:
  65. self.subject.fire_event('domain-feature-pre-set:' + key,
  66. pre_event=True,
  67. feature=key, value=value)
  68. super().__setitem__(key, value)
  69. if has_oldvalue:
  70. self.subject.fire_event('domain-feature-set:' + key, feature=key,
  71. value=value, oldvalue=oldvalue)
  72. else:
  73. self.subject.fire_event('domain-feature-set:' + key, feature=key,
  74. value=value)
  75. def clear(self):
  76. for key in tuple(self):
  77. del self[key]
  78. def pop(self, _key, _default=None):
  79. '''Not implemented
  80. :raises: NotImplementedError
  81. '''
  82. raise NotImplementedError()
  83. def popitem(self):
  84. '''Not implemented
  85. :raises: NotImplementedError
  86. '''
  87. raise NotImplementedError()
  88. def setdefault(self, _key, _default=None):
  89. '''Not implemented
  90. :raises: NotImplementedError
  91. '''
  92. raise NotImplementedError()
  93. def update(self, other=None, **kwargs):
  94. if other is not None:
  95. if hasattr(other, 'keys'):
  96. for key in other:
  97. self[key] = other[key]
  98. else:
  99. for key, value in other:
  100. self[key] = value
  101. for key in kwargs:
  102. self[key] = kwargs[key]
  103. #
  104. # end of overriding
  105. #
  106. def _recursive_check(self, attr=None, *, feature, default,
  107. check_adminvm=False, check_app=False):
  108. '''Recursive search for a feature.
  109. Traverse domains along one attribute, like
  110. :py:attr:`qubes.vm.qubesvm.QubesVM.netvm` or
  111. :py:attr:`qubes.vm.appvm.AppVM.template`, starting with current domain
  112. (:py:attr:`subject`). Search stops when first `feature` is found. If
  113. a qube has no attribute, or if the attribute is :py:obj:`None`, the
  114. *default* is returned, or if not specified, :py:class:`KeyError` is
  115. raised.
  116. If `check_adminvm` is true, before returning default, also AdminVM is
  117. consulted (the recursion does not restart).
  118. If `check_app` is true, also the app feature is checked. This is not
  119. implemented, as app does not have features yet.
  120. '''
  121. if check_app:
  122. raise NotImplementedError('app does not have features yet')
  123. assert isinstance(self.subject, _vm.BaseVM), (
  124. 'recursive checks do not work for {}'.format(
  125. type(self.subject).__name__))
  126. subject = self.subject
  127. while subject is not None:
  128. try:
  129. return subject.features[feature]
  130. except KeyError:
  131. if attr is None:
  132. break
  133. subject = getattr(subject, attr, None)
  134. if check_adminvm:
  135. adminvm = self.subject.app.domains['dom0']
  136. if adminvm not in (None, self.subject):
  137. try:
  138. return adminvm.features[feature]
  139. except KeyError:
  140. pass
  141. # TODO check_app
  142. if default is not _NO_DEFAULT:
  143. return default
  144. raise KeyError(feature)
  145. def check_with_template(self, feature, default=_NO_DEFAULT):
  146. '''Check if the subject's template has the specified feature.'''
  147. return self._recursive_check('template',
  148. feature=feature, default=default)
  149. def check_with_netvm(self, feature, default=_NO_DEFAULT):
  150. '''Check if the subject's netvm has the specified feature.'''
  151. return self._recursive_check('netvm',
  152. feature=feature, default=default)
  153. def check_with_adminvm(self, feature, default=_NO_DEFAULT):
  154. '''Check if the AdminVM has the specified feature.'''
  155. return self._recursive_check(check_adminvm=True,
  156. feature=feature, default=default)
  157. def check_with_template_and_adminvm(self, feature, default=_NO_DEFAULT):
  158. '''Check if the template and AdminVM has the specified feature.'''
  159. return self._recursive_check('template', check_adminvm=True,
  160. feature=feature, default=default)