core2migration.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. #
  2. # The Qubes OS Project, http://www.qubes-os.org
  3. #
  4. # Copyright (C) 2016 Marek Marczykowski-Górecki
  5. # <marmarek@invisiblethingslab.com>
  6. #
  7. # This program is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU General Public License
  9. # as published by the Free Software Foundation; either version 2
  10. # of the License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>
  19. #
  20. import ast
  21. import xml.parsers.expat
  22. import lxml.etree
  23. import qubes
  24. import qubes.vm.appvm
  25. import qubes.vm.standalonevm
  26. import qubes.vm.templatevm
  27. import qubes.vm.adminvm
  28. import qubes.ext.r3compatibility
  29. class AppVM(qubes.vm.appvm.AppVM): # pylint: disable=too-many-ancestors
  30. """core2 compatibility AppVM class, with variable dir_path"""
  31. dir_path = qubes.property('dir_path',
  32. # pylint: disable=undefined-variable
  33. default=(lambda self: super(AppVM, self).dir_path),
  34. saver=qubes.property.dontsave,
  35. doc="VM storage directory",
  36. )
  37. def is_running(self):
  38. return False
  39. class StandaloneVM(qubes.vm.standalonevm.StandaloneVM):
  40. """core2 compatibility StandaloneVM class, with variable dir_path
  41. """ # pylint: disable=too-many-ancestors
  42. dir_path = qubes.property('dir_path',
  43. # pylint: disable=undefined-variable
  44. default=(lambda self: super(StandaloneVM, self).dir_path),
  45. saver=qubes.property.dontsave,
  46. doc="VM storage directory")
  47. def is_running(self):
  48. return False
  49. class Core2Qubes(qubes.Qubes):
  50. def __init__(self, store=None, load=True, **kwargs):
  51. if store is None:
  52. raise ValueError("store path required")
  53. super(Core2Qubes, self).__init__(store, load, **kwargs)
  54. def load_default_template(self, element):
  55. default_template = element.get("default_template")
  56. self.default_template = int(default_template) \
  57. if default_template.lower() != "none" else None
  58. def load_globals(self, element):
  59. default_netvm = element.get("default_netvm")
  60. if default_netvm is not None:
  61. self.default_netvm = int(default_netvm) \
  62. if default_netvm != "None" else None
  63. default_fw_netvm = element.get("default_fw_netvm")
  64. if default_fw_netvm is not None:
  65. self.default_fw_netvm = int(default_fw_netvm) \
  66. if default_fw_netvm != "None" else None
  67. updatevm = element.get("updatevm")
  68. if updatevm is not None:
  69. self.updatevm = int(updatevm) \
  70. if updatevm != "None" else None
  71. clockvm = element.get("clockvm")
  72. if clockvm is not None:
  73. self.clockvm = int(clockvm) \
  74. if clockvm != "None" else None
  75. def set_netvm_dependency(self, element):
  76. kwargs = {}
  77. attr_list = ("qid", "uses_default_netvm", "netvm_qid")
  78. for attribute in attr_list:
  79. kwargs[attribute] = element.get(attribute)
  80. vm = self.domains[int(kwargs["qid"])]
  81. if element.get("uses_default_netvm") is None:
  82. uses_default_netvm = True
  83. else:
  84. uses_default_netvm = (
  85. True if element.get("uses_default_netvm") == "True" else False)
  86. if not uses_default_netvm:
  87. netvm_qid = element.get("netvm_qid")
  88. if netvm_qid is None or netvm_qid == "none":
  89. vm.netvm = None
  90. else:
  91. vm.netvm = int(netvm_qid)
  92. def set_dispvm_netvm_dependency(self, element):
  93. kwargs = {}
  94. attr_list = ("qid", "uses_default_netvm", "netvm_qid")
  95. for attribute in attr_list:
  96. kwargs[attribute] = element.get(attribute)
  97. vm = self.domains[int(kwargs["qid"])]
  98. if element.get("uses_default_dispvm_netvm") is None:
  99. uses_default_dispvm_netvm = True
  100. else:
  101. uses_default_dispvm_netvm = (
  102. True if element.get("uses_default_dispvm_netvm") == "True"
  103. else False)
  104. if not uses_default_dispvm_netvm:
  105. dispvm_netvm_qid = element.get("dispvm_netvm_qid")
  106. if dispvm_netvm_qid is None or dispvm_netvm_qid == "none":
  107. dispvm_netvm = None
  108. else:
  109. dispvm_netvm = self.domains[int(dispvm_netvm_qid)]
  110. else:
  111. dispvm_netvm = vm.netvm
  112. if dispvm_netvm:
  113. dispvm_tpl_name = 'disp-{}'.format(dispvm_netvm.name)
  114. else:
  115. dispvm_tpl_name = 'disp-no-netvm'
  116. if dispvm_tpl_name not in self.domains:
  117. vm = self.add_new_vm(qubes.vm.appvm.AppVM,
  118. name=dispvm_tpl_name)
  119. # TODO: add support for #2075
  120. # TODO: set qrexec policy based on dispvm_netvm value
  121. def import_core2_vm(self, element):
  122. vm_class_name = element.tag
  123. try:
  124. kwargs = {}
  125. if vm_class_name in ["QubesTemplateVm", "QubesTemplateHVm"]:
  126. vm_class = qubes.vm.templatevm.TemplateVM
  127. elif element.get('template_qid').lower() == "none":
  128. kwargs['dir_path'] = element.get('dir_path')
  129. vm_class = StandaloneVM
  130. else:
  131. kwargs['dir_path'] = element.get('dir_path')
  132. kwargs['template'] = self.domains[int(element.get(
  133. 'template_qid'))]
  134. vm_class = AppVM
  135. # simple attributes
  136. for attr, default in {
  137. 'installed_by_rpm': 'False',
  138. 'include_in_backups': 'True',
  139. 'qrexec_timeout': '60',
  140. 'internal': 'False',
  141. 'label': None,
  142. 'name': None,
  143. 'vcpus': '2',
  144. 'memory': '400',
  145. 'maxmem': '4000',
  146. 'default_user': 'user',
  147. 'debug': 'False',
  148. 'pci_strictreset': 'True',
  149. 'mac': None,
  150. 'autostart': 'False'}.items():
  151. value = element.get(attr)
  152. if value and value != default:
  153. kwargs[attr] = value
  154. # attributes with default value
  155. for attr in ["kernel", "kernelopts"]:
  156. value = element.get(attr)
  157. if value and value.lower() == "none":
  158. value = None
  159. value_is_default = element.get(
  160. "uses_default_{}".format(attr))
  161. if value_is_default and value_is_default.lower() != \
  162. "true":
  163. kwargs[attr] = value
  164. kwargs['hvm'] = "HVm" in vm_class_name
  165. kwargs['provides_network'] = \
  166. vm_class_name in ('QubesNetVm', 'QubesProxyVm')
  167. if vm_class_name == 'QubesNetVm':
  168. kwargs['netvm'] = None
  169. vm = self.add_new_vm(vm_class,
  170. qid=int(element.get('qid')), **kwargs)
  171. services = element.get('services')
  172. if services:
  173. services = ast.literal_eval(services)
  174. else:
  175. services = {}
  176. for service, value in services.items():
  177. feature = service
  178. for repl_feature, repl_service in \
  179. qubes.ext.r3compatibility.\
  180. R3Compatibility.features_to_services.\
  181. items():
  182. if repl_service == service:
  183. feature = repl_feature
  184. vm.features[feature] = value
  185. for attr in ['backup_content', 'backup_path',
  186. 'backup_size']:
  187. value = element.get(attr)
  188. vm.features[attr.replace('_', '-')] = value
  189. pcidevs = element.get('pcidevs')
  190. if pcidevs:
  191. pcidevs = ast.literal_eval(pcidevs)
  192. for pcidev in pcidevs:
  193. try:
  194. vm.devices["pci"].attach(
  195. self.domains[0].devices['pci'][pcidev])
  196. except qubes.exc.QubesException as e:
  197. self.log.error("VM {}: {}".format(vm.name, str(e)))
  198. except (ValueError, LookupError) as err:
  199. self.log.error("import error ({1}): {2}".format(
  200. vm_class_name, err))
  201. if 'vm' in locals():
  202. del self.domains[vm]
  203. def load(self, lock=False):
  204. qubes_store_file = open(self._store, 'r')
  205. self._acquire_lock(qubes_store_file)
  206. try:
  207. qubes_store_file.seek(0)
  208. tree = lxml.etree.parse(qubes_store_file)
  209. except (EnvironmentError, # pylint: disable=broad-except
  210. xml.parsers.expat.ExpatError) as err:
  211. self.log.error(err)
  212. return False
  213. self.load_initial_values()
  214. self.default_kernel = tree.getroot().get("default_kernel")
  215. vm_classes = ["TemplateVm", "TemplateHVm",
  216. "AppVm", "HVm", "NetVm", "ProxyVm"]
  217. # First load templates
  218. for vm_class_name in ["TemplateVm", "TemplateHVm"]:
  219. vms_of_class = tree.findall("Qubes" + vm_class_name)
  220. for element in vms_of_class:
  221. self.import_core2_vm(element)
  222. # Then set default template ...
  223. self.load_default_template(tree.getroot())
  224. # ... and load other VMs
  225. for vm_class_name in ["AppVm", "HVm", "NetVm", "ProxyVm"]:
  226. vms_of_class = tree.findall("Qubes" + vm_class_name)
  227. # first non-template based, then template based
  228. sorted_vms_of_class = sorted(vms_of_class,
  229. key=lambda x: str(x.get('template_qid')).lower() != "none")
  230. for element in sorted_vms_of_class:
  231. self.import_core2_vm(element)
  232. # After importing all VMs, set netvm references, in the same order
  233. for vm_class_name in vm_classes:
  234. for element in tree.findall("Qubes" + vm_class_name):
  235. try:
  236. self.set_netvm_dependency(element)
  237. except (ValueError, LookupError) as err:
  238. self.log.error("VM {}: failed to set netvm dependency: {}".
  239. format(element.get('name'), err))
  240. # and load other defaults (default netvm, updatevm etc)
  241. self.load_globals(tree.getroot())
  242. if not lock:
  243. self._release_lock()
  244. def save(self, lock=False):
  245. raise NotImplementedError("Saving old qubes.xml not supported")