core2migration.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  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 library is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU Lesser General Public
  9. # License as published by the Free Software Foundation; either
  10. # version 2.1 of the License, or (at your option) any later version.
  11. #
  12. # This library 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 GNU
  15. # Lesser General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public
  18. # License along with this library; if not, see <https://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. updatevm = element.get("updatevm")
  65. if updatevm is not None:
  66. self.updatevm = int(updatevm) \
  67. if updatevm != "None" else None
  68. clockvm = element.get("clockvm")
  69. if clockvm is not None:
  70. self.clockvm = int(clockvm) \
  71. if clockvm != "None" else None
  72. def set_netvm_dependency(self, element):
  73. kwargs = {}
  74. attr_list = ("qid", "uses_default_netvm", "netvm_qid")
  75. for attribute in attr_list:
  76. kwargs[attribute] = element.get(attribute)
  77. vm = self.domains[int(kwargs["qid"])]
  78. if element.get("uses_default_netvm") is None:
  79. uses_default_netvm = True
  80. else:
  81. uses_default_netvm = (
  82. True if element.get("uses_default_netvm") == "True" else False)
  83. if not uses_default_netvm:
  84. netvm_qid = element.get("netvm_qid")
  85. if netvm_qid is None or netvm_qid == "none":
  86. vm.netvm = None
  87. else:
  88. vm.netvm = int(netvm_qid)
  89. def set_dispvm_netvm_dependency(self, element):
  90. kwargs = {}
  91. attr_list = ("qid", "uses_default_netvm", "netvm_qid")
  92. for attribute in attr_list:
  93. kwargs[attribute] = element.get(attribute)
  94. vm = self.domains[int(kwargs["qid"])]
  95. if element.get("uses_default_dispvm_netvm") is None:
  96. uses_default_dispvm_netvm = True
  97. else:
  98. uses_default_dispvm_netvm = (
  99. True if element.get("uses_default_dispvm_netvm") == "True"
  100. else False)
  101. if not uses_default_dispvm_netvm:
  102. dispvm_netvm_qid = element.get("dispvm_netvm_qid")
  103. if dispvm_netvm_qid is None or dispvm_netvm_qid == "none":
  104. dispvm_netvm = None
  105. else:
  106. dispvm_netvm = self.domains[int(dispvm_netvm_qid)]
  107. else:
  108. dispvm_netvm = vm.netvm
  109. if dispvm_netvm:
  110. dispvm_tpl_name = 'disp-{}'.format(dispvm_netvm.name)
  111. else:
  112. dispvm_tpl_name = 'disp-no-netvm'
  113. if dispvm_tpl_name not in self.domains:
  114. vm = self.add_new_vm(qubes.vm.appvm.AppVM,
  115. name=dispvm_tpl_name)
  116. # TODO: add support for #2075
  117. # TODO: set qrexec policy based on dispvm_netvm value
  118. def import_core2_vm(self, element):
  119. vm_class_name = element.tag
  120. try:
  121. kwargs = {}
  122. if vm_class_name in ["QubesTemplateVm", "QubesTemplateHVm"]:
  123. vm_class = qubes.vm.templatevm.TemplateVM
  124. elif element.get('template_qid').lower() == "none":
  125. kwargs['dir_path'] = element.get('dir_path')
  126. vm_class = StandaloneVM
  127. else:
  128. kwargs['dir_path'] = element.get('dir_path')
  129. kwargs['template'] = self.domains[int(element.get(
  130. 'template_qid'))]
  131. vm_class = AppVM
  132. # simple attributes
  133. for attr, default in {
  134. 'installed_by_rpm': 'False',
  135. 'include_in_backups': 'True',
  136. 'qrexec_timeout': '60',
  137. 'internal': 'False',
  138. 'label': None,
  139. 'name': None,
  140. 'vcpus': '2',
  141. 'memory': '400',
  142. 'maxmem': '4000',
  143. 'default_user': 'user',
  144. 'debug': 'False',
  145. 'pci_strictreset': 'True',
  146. 'mac': None,
  147. 'autostart': 'False'}.items():
  148. value = element.get(attr)
  149. if value and value != default:
  150. kwargs[attr] = value
  151. # attributes with default value
  152. for attr in ["kernel", "kernelopts"]:
  153. value = element.get(attr)
  154. if value and value.lower() == "none":
  155. value = None
  156. value_is_default = element.get(
  157. "uses_default_{}".format(attr))
  158. if value_is_default and value_is_default.lower() != \
  159. "true":
  160. kwargs[attr] = value
  161. kwargs['virt_mode'] = 'hvm' if "HVm" in vm_class_name else 'pv'
  162. kwargs['provides_network'] = \
  163. vm_class_name in ('QubesNetVm', 'QubesProxyVm')
  164. if vm_class_name == 'QubesNetVm':
  165. kwargs['netvm'] = None
  166. vm = self.add_new_vm(vm_class,
  167. qid=int(element.get('qid')), **kwargs)
  168. services = element.get('services')
  169. if services:
  170. services = ast.literal_eval(services)
  171. else:
  172. services = {}
  173. for service, value in services.items():
  174. feature = service
  175. for repl_feature, repl_service in \
  176. qubes.ext.r3compatibility.\
  177. R3Compatibility.features_to_services.\
  178. items():
  179. if repl_service == service:
  180. feature = repl_feature
  181. vm.features[feature] = value
  182. for attr in ['backup_content', 'backup_path',
  183. 'backup_size']:
  184. value = element.get(attr)
  185. vm.features[attr.replace('_', '-')] = value
  186. pcidevs = element.get('pcidevs')
  187. if pcidevs:
  188. pcidevs = ast.literal_eval(pcidevs)
  189. for pcidev in pcidevs:
  190. try:
  191. dev = self.domains[0].devices['pci'][pcidev]
  192. assignment = qubes.devices.DeviceAssignment(
  193. backend_domain=dev.backend_domain, ident=dev.ident)
  194. vm.devices["pci"].attach(assignment)
  195. except qubes.exc.QubesException as e:
  196. self.log.error("VM {}: {}".format(vm.name, str(e)))
  197. except (ValueError, LookupError) as err:
  198. self.log.error("import error ({1}): {2}".format(
  199. vm_class_name, err))
  200. if 'vm' in locals():
  201. del self.domains[vm]
  202. def load(self, lock=False):
  203. fh = self._acquire_lock()
  204. try:
  205. fh.seek(0)
  206. tree = lxml.etree.parse(fh)
  207. except (EnvironmentError, # pylint: disable=broad-except
  208. xml.parsers.expat.ExpatError) as err:
  209. self.log.error(err)
  210. return False
  211. self.load_initial_values()
  212. self.default_kernel = tree.getroot().get("default_kernel")
  213. vm_classes = ["TemplateVm", "TemplateHVm",
  214. "AppVm", "HVm", "NetVm", "ProxyVm"]
  215. # First load templates
  216. for vm_class_name in ["TemplateVm", "TemplateHVm"]:
  217. vms_of_class = tree.findall("Qubes" + vm_class_name)
  218. for element in vms_of_class:
  219. self.import_core2_vm(element)
  220. # Then set default template ...
  221. self.load_default_template(tree.getroot())
  222. # ... and load other VMs
  223. for vm_class_name in ["AppVm", "HVm", "NetVm", "ProxyVm"]:
  224. vms_of_class = tree.findall("Qubes" + vm_class_name)
  225. # first non-template based, then template based
  226. sorted_vms_of_class = sorted(vms_of_class,
  227. key=lambda x: str(x.get('template_qid')).lower() != "none")
  228. for element in sorted_vms_of_class:
  229. self.import_core2_vm(element)
  230. # After importing all VMs, set netvm references, in the same order
  231. for vm_class_name in vm_classes:
  232. for element in tree.findall("Qubes" + vm_class_name):
  233. try:
  234. self.set_netvm_dependency(element)
  235. except (ValueError, LookupError) as err:
  236. self.log.error("VM {}: failed to set netvm dependency: {}".
  237. format(element.get('name'), err))
  238. # and load other defaults (default netvm, updatevm etc)
  239. self.load_globals(tree.getroot())
  240. if not lock:
  241. self._release_lock()
  242. def save(self, lock=False):
  243. raise NotImplementedError("Saving old qubes.xml not supported")