core2migration.py 9.7 KB

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