base.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. # -*- encoding: utf8 -*-
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2017 Marek Marczykowski-Górecki
  6. # <marmarek@invisiblethingslab.com>
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU Lesser General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU Lesser General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Lesser General Public License along
  19. # with this program; if not, see <http://www.gnu.org/licenses/>.
  20. '''Base classes for managed objects'''
  21. import ast
  22. import qubesmgmt.exc
  23. DEFAULT = object()
  24. class PropertyHolder(object):
  25. '''A base class for object having properties retrievable using mgmt API.
  26. Warning: each (non-private) local attribute needs to be defined at class
  27. level, even if initialized in __init__; otherwise will be treated as
  28. property retrievable using mgmt call.
  29. '''
  30. #: a place for appropriate Qubes() object (QubesLocal or QubesRemote),
  31. # use None for self
  32. app = None
  33. def __init__(self, app, method_prefix, method_dest):
  34. #: appropriate Qubes() object (QubesLocal or QubesRemote), use None
  35. # for self
  36. self.app = app
  37. self._method_prefix = method_prefix
  38. self._method_dest = method_dest
  39. self._properties = None
  40. self._properties_help = None
  41. def qubesd_call(self, dest, method, arg=None, payload=None):
  42. '''
  43. Call into qubesd using appropriate mechanism. This method should be
  44. defined by a subclass.
  45. :param dest: Destination VM name
  46. :param method: Full API method name ('mgmt...')
  47. :param arg: Method argument (if any)
  48. :param payload: Payload send to the method
  49. :return: Data returned by qubesd (string)
  50. '''
  51. # have the actual implementation at Qubes() instance
  52. if self.app:
  53. return self.app.qubesd_call(dest, method, arg, payload)
  54. raise NotImplementedError
  55. @staticmethod
  56. def _parse_qubesd_response(response_data):
  57. '''Parse response from qubesd.
  58. In case of success, return actual data. In case of error,
  59. raise appropriate exception.
  60. '''
  61. if response_data[0:2] == b'\x30\x00':
  62. return response_data[2:]
  63. elif response_data[0:2] == b'\x32\x00':
  64. (_, exc_type, _traceback, format_string, args) = \
  65. response_data.split(b'\x00', 4)
  66. # drop last field because of terminating '\x00'
  67. args = [arg.decode() for arg in args.split(b'\x00')[:-1]]
  68. format_string = format_string.decode('utf-8')
  69. exc_type = exc_type.decode('ascii')
  70. exc_class = getattr(qubesmgmt.exc, exc_type, 'QubesException')
  71. # TODO: handle traceback if given
  72. raise exc_class(format_string, *args)
  73. else:
  74. raise qubesmgmt.exc.QubesException('Invalid response format')
  75. def property_list(self):
  76. '''
  77. List available properties (their names).
  78. :return: list of strings
  79. '''
  80. if self._properties is None:
  81. properties_str = self.qubesd_call(
  82. self._method_dest,
  83. self._method_prefix + 'List',
  84. None,
  85. None)
  86. self._properties = properties_str.decode('ascii').splitlines()
  87. # TODO: make it somehow immutable
  88. return self._properties
  89. def property_is_default(self, item):
  90. '''
  91. Check if given property have default value
  92. :param str item: name of property
  93. :return: bool
  94. '''
  95. if item.startswith('_'):
  96. raise AttributeError(item)
  97. property_str = self.qubesd_call(
  98. self._method_dest,
  99. self._method_prefix + 'Get',
  100. item,
  101. None)
  102. (default, _value) = property_str.split(b' ', 1)
  103. assert default.startswith(b'default=')
  104. is_default_str = default.split(b'=')[1]
  105. is_default = ast.literal_eval(is_default_str.decode('ascii'))
  106. assert isinstance(is_default, bool)
  107. return is_default
  108. def __getattr__(self, item):
  109. if item.startswith('_'):
  110. raise AttributeError(item)
  111. property_str = self.qubesd_call(
  112. self._method_dest,
  113. self._method_prefix + 'Get',
  114. item,
  115. None)
  116. (_default, value) = property_str.split(b' ', 1)
  117. value = value.decode()
  118. if value[0] == '\'':
  119. return ast.literal_eval('u' + value)
  120. else:
  121. return ast.literal_eval(value)
  122. def __setattr__(self, key, value):
  123. if key.startswith('_') or key in dir(self):
  124. return super(PropertyHolder, self).__setattr__(key, value)
  125. if value is qubesmgmt.DEFAULT:
  126. self.qubesd_call(
  127. self._method_dest,
  128. self._method_prefix + 'Reset',
  129. key,
  130. None)
  131. else:
  132. if isinstance(value, qubesmgmt.vm.QubesVM):
  133. value = value.name
  134. self.qubesd_call(
  135. self._method_dest,
  136. self._method_prefix + 'Set',
  137. key,
  138. str(value).encode('utf-8'))
  139. def __delattr__(self, name):
  140. if name.startswith('_') or name in dir(self):
  141. return super(PropertyHolder, self).__delattr__(name)
  142. self.qubesd_call(
  143. self._method_dest,
  144. self._method_prefix + 'Reset',
  145. name
  146. )