core2migration.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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.devices
  25. import qubes.vm.appvm
  26. import qubes.vm.standalonevm
  27. import qubes.vm.templatevm
  28. import qubes.vm.adminvm
  29. import qubes.ext.r3compatibility
  30. class AppVM(qubes.vm.appvm.AppVM): # pylint: disable=too-many-ancestors
  31. """core2 compatibility AppVM class, with variable dir_path"""
  32. dir_path = qubes.property('dir_path',
  33. # pylint: disable=undefined-variable
  34. default=(lambda self: super(AppVM, self).dir_path),
  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. """ # pylint: disable=too-many-ancestors
  43. dir_path = qubes.property('dir_path',
  44. # pylint: disable=undefined-variable
  45. default=(lambda self: super(StandaloneVM, self).dir_path),
  46. saver=qubes.property.dontsave,
  47. doc="VM storage directory")
  48. def is_running(self):
  49. return False
  50. class Core2Qubes(qubes.Qubes):
  51. def __init__(self, store=None, load=True, **kwargs):
  52. if store is None:
  53. raise ValueError("store path required")
  54. super(Core2Qubes, self).__init__(store, load, **kwargs)
  55. def load_default_template(self, element):
  56. default_template = element.get("default_template")
  57. self.default_template = int(default_template) \
  58. if default_template.lower() != "none" else None
  59. def load_globals(self, element):
  60. default_netvm = element.get("default_netvm")
  61. if default_netvm is not None:
  62. self.default_netvm = int(default_netvm) \
  63. if default_netvm != "None" else None
  64. default_fw_netvm = element.get("default_fw_netvm")
  65. if default_fw_netvm is not None:
  66. self.default_fw_netvm = int(default_fw_netvm) \
  67. if default_fw_netvm != "None" else None
  68. updatevm = element.get("updatevm")
  69. if updatevm is not None:
  70. self.updatevm = int(updatevm) \
  71. if updatevm != "None" else None
  72. clockvm = element.get("clockvm")
  73. if clockvm is not None:
  74. self.clockvm = int(clockvm) \
  75. if clockvm != "None" else None
  76. def set_netvm_dependency(self, element):
  77. kwargs = {}
  78. attr_list = ("qid", "uses_default_netvm", "netvm_qid")
  79. for attribute in attr_list:
  80. kwargs[attribute] = element.get(attribute)
  81. vm = self.domains[int(kwargs["qid"])]
  82. if element.get("uses_default_netvm") is None:
  83. uses_default_netvm = True
  84. else:
  85. uses_default_netvm = (
  86. True if element.get("uses_default_netvm") == "True" else False)
  87. if not uses_default_netvm:
  88. netvm_qid = element.get("netvm_qid")
  89. if netvm_qid is None or netvm_qid == "none":
  90. vm.netvm = None
  91. else:
  92. vm.netvm = int(netvm_qid)
  93. def set_dispvm_netvm_dependency(self, element):
  94. kwargs = {}
  95. attr_list = ("qid", "uses_default_netvm", "netvm_qid")
  96. for attribute in attr_list:
  97. kwargs[attribute] = element.get(attribute)
  98. vm = self.domains[int(kwargs["qid"])]
  99. if element.get("uses_default_dispvm_netvm") is None:
  100. uses_default_dispvm_netvm = True
  101. else:
  102. uses_default_dispvm_netvm = (
  103. True if element.get("uses_default_dispvm_netvm") == "True"
  104. else False)
  105. if not uses_default_dispvm_netvm:
  106. dispvm_netvm_qid = element.get("dispvm_netvm_qid")
  107. if dispvm_netvm_qid is None or dispvm_netvm_qid == "none":
  108. dispvm_netvm = None
  109. else:
  110. dispvm_netvm = self.domains[int(dispvm_netvm_qid)]
  111. else:
  112. dispvm_netvm = vm.netvm
  113. if dispvm_netvm:
  114. dispvm_tpl_name = 'disp-{}'.format(dispvm_netvm.name)
  115. else:
  116. dispvm_tpl_name = 'disp-no-netvm'
  117. if dispvm_tpl_name not in self.domains:
  118. vm = self.add_new_vm(qubes.vm.appvm.AppVM,
  119. name=dispvm_tpl_name)
  120. # TODO: add support for #2075
  121. # TODO: set qrexec policy based on dispvm_netvm value
  122. def import_core2_vm(self, element):
  123. vm_class_name = element.tag
  124. try:
  125. kwargs = {}
  126. if vm_class_name in ["QubesTemplateVm", "QubesTemplateHVm"]:
  127. vm_class = qubes.vm.templatevm.TemplateVM
  128. elif element.get('template_qid').lower() == "none":
  129. kwargs['dir_path'] = element.get('dir_path')
  130. vm_class = StandaloneVM
  131. else:
  132. kwargs['dir_path'] = element.get('dir_path')
  133. kwargs['template'] = self.domains[int(element.get(
  134. 'template_qid'))]
  135. vm_class = AppVM
  136. # simple attributes
  137. for attr, default in {
  138. 'installed_by_rpm': 'False',
  139. 'include_in_backups': 'True',
  140. 'qrexec_timeout': '60',
  141. 'internal': 'False',
  142. 'label': None,
  143. 'name': None,
  144. 'vcpus': '2',
  145. 'memory': '400',
  146. 'maxmem': '4000',
  147. 'default_user': 'user',
  148. 'debug': 'False',
  149. 'pci_strictreset': 'True',
  150. 'mac': None,
  151. 'autostart': 'False'}.items():
  152. value = element.get(attr)
  153. if value and value != default:
  154. kwargs[attr] = value
  155. # attributes with default value
  156. for attr in ["kernel", "kernelopts"]:
  157. value = element.get(attr)
  158. if value and value.lower() == "none":
  159. value = None
  160. value_is_default = element.get(
  161. "uses_default_{}".format(attr))
  162. if value_is_default and value_is_default.lower() != \
  163. "true":
  164. kwargs[attr] = value
  165. kwargs['virt_mode'] = 'hvm' if "HVm" in vm_class_name else 'pv'
  166. kwargs['provides_network'] = \
  167. vm_class_name in ('QubesNetVm', 'QubesProxyVm')
  168. if vm_class_name == 'QubesNetVm':
  169. kwargs['netvm'] = None
  170. vm = self.add_new_vm(vm_class,
  171. qid=int(element.get('qid')), **kwargs)
  172. services = element.get('services')
  173. if services:
  174. services = ast.literal_eval(services)
  175. else:
  176. services = {}
  177. for service, value in services.items():
  178. feature = service
  179. for repl_feature, repl_service in \
  180. qubes.ext.r3compatibility.\
  181. R3Compatibility.features_to_services.\
  182. items():
  183. if repl_service == service:
  184. feature = repl_feature
  185. vm.features[feature] = value
  186. for attr in ['backup_content', 'backup_path',
  187. 'backup_size']:
  188. value = element.get(attr)
  189. vm.features[attr.replace('_', '-')] = value
  190. pcidevs = element.get('pcidevs')
  191. if pcidevs:
  192. pcidevs = ast.literal_eval(pcidevs)
  193. for pcidev in pcidevs:
  194. try:
  195. dev = self.domains[0].devices['pci'][pcidev]
  196. assignment = qubes.devices.DeviceAssignment(
  197. backend_domain=dev.backend_domain, ident=dev.ident)
  198. vm.devices["pci"].attach(assignment)
  199. except qubes.exc.QubesException as e:
  200. self.log.error("VM {}: {}".format(vm.name, str(e)))
  201. except (ValueError, LookupError) as err:
  202. self.log.error("import error ({1}): {2}".format(
  203. vm_class_name, err))
  204. if 'vm' in locals():
  205. del self.domains[vm]
  206. def load(self, lock=False):
  207. fh = self._acquire_lock()
  208. try:
  209. fh.seek(0)
  210. tree = lxml.etree.parse(fh)
  211. except (EnvironmentError, # pylint: disable=broad-except
  212. xml.parsers.expat.ExpatError) as err:
  213. self.log.error(err)
  214. return False
  215. self.load_initial_values()
  216. self.default_kernel = tree.getroot().get("default_kernel")
  217. vm_classes = ["TemplateVm", "TemplateHVm",
  218. "AppVm", "HVm", "NetVm", "ProxyVm"]
  219. # First load templates
  220. for vm_class_name in ["TemplateVm", "TemplateHVm"]:
  221. vms_of_class = tree.findall("Qubes" + vm_class_name)
  222. for element in vms_of_class:
  223. self.import_core2_vm(element)
  224. # Then set default template ...
  225. self.load_default_template(tree.getroot())
  226. # ... and load other VMs
  227. for vm_class_name in ["AppVm", "HVm", "NetVm", "ProxyVm"]:
  228. vms_of_class = tree.findall("Qubes" + vm_class_name)
  229. # first non-template based, then template based
  230. sorted_vms_of_class = sorted(vms_of_class,
  231. key=lambda x: str(x.get('template_qid')).lower() != "none")
  232. for element in sorted_vms_of_class:
  233. self.import_core2_vm(element)
  234. # After importing all VMs, set netvm references, in the same order
  235. for vm_class_name in vm_classes:
  236. for element in tree.findall("Qubes" + vm_class_name):
  237. try:
  238. self.set_netvm_dependency(element)
  239. except (ValueError, LookupError) as err:
  240. self.log.error("VM {}: failed to set netvm dependency: {}".
  241. format(element.get('name'), err))
  242. # and load other defaults (default netvm, updatevm etc)
  243. self.load_globals(tree.getroot())
  244. if not lock:
  245. self._release_lock()
  246. def save(self, lock=False):
  247. raise NotImplementedError("Saving old qubes.xml not supported")