000QubesVm.py 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872
  1. #!/usr/bin/python2
  2. # -*- coding: utf-8 -*-
  3. #
  4. # The Qubes OS Project, http://www.qubes-os.org
  5. #
  6. # Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
  7. # Copyright (C) 2013 Marek Marczykowski <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, write to the Free Software
  21. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. #
  23. #
  24. import datetime
  25. import fcntl
  26. import lxml.etree
  27. import os
  28. import os.path
  29. import re
  30. import shutil
  31. import subprocess
  32. import sys
  33. import time
  34. import uuid
  35. import xml.parsers.expat
  36. import xen.lowlevel.xc
  37. from qubes import qmemman
  38. from qubes import qmemman_algo
  39. from qubes.qubes import xs,dry_run,xc,xl_ctx
  40. from qubes.qubes import register_qubes_vm_class
  41. from qubes.qubes import QubesVmCollection,QubesException,QubesHost,QubesVmLabels
  42. from qubes.qubes import defaults,system_path,vm_files,qubes_max_qid
  43. from qubes.qmemman_client import QMemmanClient
  44. import qubes.qubesutils
  45. xid_to_name_cache = {}
  46. class QubesVm(object):
  47. """
  48. A representation of one Qubes VM
  49. Only persistent information are stored here, while all the runtime
  50. information, e.g. Xen dom id, etc, are to be retrieved via Xen API
  51. Note that qid is not the same as Xen's domid!
  52. """
  53. # In which order load this VM type from qubes.xml
  54. load_order = 100
  55. # hooks for plugins (modules) which want to influence existing classes,
  56. # without introducing new ones
  57. hooks_clone_disk_files = []
  58. hooks_create_on_disk = []
  59. hooks_create_xenstore_entries = []
  60. hooks_get_attrs_config = []
  61. hooks_get_clone_attrs = []
  62. hooks_get_config_params = []
  63. hooks_init = []
  64. hooks_label_setter = []
  65. hooks_netvm_setter = []
  66. hooks_post_rename = []
  67. hooks_pre_rename = []
  68. hooks_remove_from_disk = []
  69. hooks_start = []
  70. hooks_verify_files = []
  71. hooks_set_attr = []
  72. def get_attrs_config(self):
  73. """ Object attributes for serialization/deserialization
  74. inner dict keys:
  75. - order: initialization order (to keep dependency intact)
  76. attrs without order will be evaluated at the end
  77. - default: default value used when attr not given to object constructor
  78. - attr: set value to this attribute instead of parameter name
  79. - eval: (DEPRECATED) assign result of this expression instead of
  80. value directly; local variable 'value' contains
  81. attribute value (or default if it was not given)
  82. - func: callable used to parse the value retrieved from XML
  83. - save: use evaluation result as value for XML serialization; only attrs with 'save' key will be saved in XML
  84. - save_skip: if present and evaluates to true, attr will be omitted in XML
  85. - save_attr: save to this XML attribute instead of parameter name
  86. """
  87. attrs = {
  88. # __qid cannot be accessed by setattr, so must be set manually in __init__
  89. "qid": { "attr": "_qid", "order": 0 },
  90. "name": { "order": 1 },
  91. "dir_path": { "default": None, "order": 2 },
  92. "conf_file": {
  93. "func": lambda value: self.absolute_path(value, self.name +
  94. ".conf"),
  95. "order": 3 },
  96. ### order >= 10: have base attrs set
  97. "root_img": {
  98. "func": self._absolute_path_gen(vm_files["root_img"]),
  99. "order": 10 },
  100. "private_img": {
  101. "func": self._absolute_path_gen(vm_files["private_img"]),
  102. "order": 10 },
  103. "volatile_img": {
  104. "func": self._absolute_path_gen(vm_files["volatile_img"]),
  105. "order": 10 },
  106. "firewall_conf": {
  107. "func": self._absolute_path_gen(vm_files["firewall_conf"]),
  108. "order": 10 },
  109. "installed_by_rpm": { "default": False, 'order': 10 },
  110. "template": { "default": None, "attr": '_template', 'order': 10 },
  111. ### order >= 20: have template set
  112. "uses_default_netvm": { "default": True, 'order': 20 },
  113. "netvm": { "default": None, "attr": "_netvm", 'order': 20 },
  114. "label": { "attr": "_label", "default": defaults["appvm_label"], 'order': 20,
  115. 'xml_deserialize': lambda _x: QubesVmLabels[_x] },
  116. "memory": { "default": defaults["memory"], 'order': 20 },
  117. "maxmem": { "default": None, 'order': 25 },
  118. "pcidevs": {
  119. "default": '[]',
  120. "order": 25,
  121. "func": lambda value: [] if value in ["none", None] else
  122. eval(value) if value.find("[") >= 0 else
  123. eval("[" + value + "]") },
  124. # Internal VM (not shown in qubes-manager, doesn't create appmenus entries
  125. "internal": { "default": False, 'attr': '_internal' },
  126. "vcpus": { "default": None },
  127. "uses_default_kernel": { "default": True, 'order': 30 },
  128. "uses_default_kernelopts": { "default": True, 'order': 30 },
  129. "kernel": {
  130. "attr": "_kernel",
  131. "default": None,
  132. "order": 31,
  133. "func": lambda value: self._collection.get_default_kernel() if
  134. self.uses_default_kernel else value },
  135. "kernelopts": {
  136. "default": "",
  137. "order": 31,
  138. "func": lambda value: value if not self.uses_default_kernelopts\
  139. else defaults["kernelopts_pcidevs"] if len(self.pcidevs)>0 \
  140. else defaults["kernelopts"] },
  141. "mac": { "attr": "_mac", "default": None },
  142. "include_in_backups": { "default": True },
  143. "services": {
  144. "default": {},
  145. "func": lambda value: eval(str(value)) },
  146. "debug": { "default": False },
  147. "default_user": { "default": "user" },
  148. "qrexec_timeout": { "default": 60 },
  149. "autostart": { "default": False, "attr": "_autostart" },
  150. "backup_content" : { 'default': False },
  151. "backup_size" : {
  152. "default": 0,
  153. "func": int },
  154. "backup_path" : { 'default': "" },
  155. "backup_timestamp": {
  156. "func": lambda value:
  157. datetime.datetime.fromtimestamp(int(value)) if value
  158. else None },
  159. ##### Internal attributes - will be overriden in __init__ regardless of args
  160. "config_file_template": {
  161. "func": lambda x: system_path["config_template_pv"] },
  162. "icon_path": {
  163. "func": lambda x: os.path.join(self.dir_path, "icon.png") if
  164. self.dir_path is not None else None },
  165. # used to suppress side effects of clone_attrs
  166. "_do_not_reset_firewall": { "func": lambda x: False },
  167. "kernels_dir": {
  168. # for backward compatibility (or another rare case): kernel=None -> kernel in VM dir
  169. "func": lambda x: \
  170. os.path.join(system_path["qubes_kernels_base_dir"],
  171. self.kernel) if self.kernel is not None \
  172. else os.path.join(self.dir_path,
  173. vm_files["kernels_subdir"]) },
  174. "_start_guid_first": { "func": lambda x: False },
  175. }
  176. ### Mark attrs for XML inclusion
  177. # Simple string attrs
  178. for prop in ['qid', 'name', 'dir_path', 'memory', 'maxmem', 'pcidevs', 'vcpus', 'internal',\
  179. 'uses_default_kernel', 'kernel', 'uses_default_kernelopts',\
  180. 'kernelopts', 'services', 'installed_by_rpm',\
  181. 'uses_default_netvm', 'include_in_backups', 'debug',\
  182. 'default_user', 'qrexec_timeout', 'autostart',
  183. 'default_user', 'qrexec_timeout',
  184. 'backup_content', 'backup_size', 'backup_path' ]:
  185. attrs[prop]['save'] = lambda prop=prop: str(getattr(self, prop))
  186. # Simple paths
  187. for prop in ['conf_file', 'root_img', 'volatile_img', 'private_img']:
  188. attrs[prop]['save'] = \
  189. lambda prop=prop: self.relative_path(getattr(self, prop))
  190. attrs[prop]['save_skip'] = \
  191. lambda prop=prop: getattr(self, prop) is None
  192. attrs['mac']['save'] = lambda: str(self._mac)
  193. attrs['mac']['save_skip'] = lambda: self._mac is None
  194. attrs['backup_timestamp']['save'] = \
  195. lambda: self.backup_timestamp.strftime("%s")
  196. attrs['backup_timestamp']['save_skip'] = \
  197. lambda: self.backup_timestamp is None
  198. attrs['netvm']['save'] = \
  199. lambda: str(self.netvm.qid) if self.netvm is not None else "none"
  200. attrs['netvm']['save_attr'] = "netvm_qid"
  201. attrs['template']['save'] = \
  202. lambda: str(self.template.qid) if self.template else "none"
  203. attrs['template']['save_attr'] = "template_qid"
  204. attrs['label']['save'] = lambda: self.label.name
  205. # fire hooks
  206. for hook in self.hooks_get_attrs_config:
  207. attrs = hook(self, attrs)
  208. return attrs
  209. def post_set_attr(self, attr, newvalue, oldvalue):
  210. for hook in self.hooks_set_attr:
  211. hook(self, attr, newvalue, oldvalue)
  212. def __basic_parse_xml_attr(self, value):
  213. if value is None:
  214. return None
  215. if value.lower() == "none":
  216. return None
  217. if value.lower() == "true":
  218. return True
  219. if value.lower() == "false":
  220. return False
  221. if value.isdigit():
  222. return int(value)
  223. return value
  224. def __init__(self, **kwargs):
  225. self._collection = None
  226. if 'collection' in kwargs:
  227. self._collection = kwargs['collection']
  228. else:
  229. raise ValueError("No collection given to QubesVM constructor")
  230. # Special case for template b/c it is given in "template_qid" property
  231. if "xml_element" in kwargs and kwargs["xml_element"].get("template_qid"):
  232. template_qid = kwargs["xml_element"].get("template_qid")
  233. if template_qid.lower() != "none":
  234. if int(template_qid) in self._collection:
  235. kwargs["template"] = self._collection[int(template_qid)]
  236. else:
  237. raise ValueError("Unknown template with QID %s" % template_qid)
  238. attrs = self.get_attrs_config()
  239. for attr_name in sorted(attrs, key=lambda _x: attrs[_x]['order'] if 'order' in attrs[_x] else 1000):
  240. attr_config = attrs[attr_name]
  241. attr = attr_name
  242. if 'attr' in attr_config:
  243. attr = attr_config['attr']
  244. value = None
  245. if attr_name in kwargs:
  246. value = kwargs[attr_name]
  247. elif 'xml_element' in kwargs and kwargs['xml_element'].get(attr_name) is not None:
  248. if 'xml_deserialize' in attr_config and callable(attr_config['xml_deserialize']):
  249. value = attr_config['xml_deserialize'](kwargs['xml_element'].get(attr_name))
  250. else:
  251. value = self.__basic_parse_xml_attr(kwargs['xml_element'].get(attr_name))
  252. else:
  253. if 'default' in attr_config:
  254. value = attr_config['default']
  255. if 'func' in attr_config:
  256. setattr(self, attr, attr_config['func'](value))
  257. elif 'eval' in attr_config:
  258. setattr(self, attr, eval(attr_config['eval']))
  259. else:
  260. #print "setting %s to %s" % (attr, value)
  261. setattr(self, attr, value)
  262. #Init private attrs
  263. self.__qid = self._qid
  264. assert self.__qid < qubes_max_qid, "VM id out of bounds!"
  265. assert self.name is not None
  266. if not self.verify_name(self.name):
  267. raise QubesException("Invalid VM name (invalid characters, "
  268. "or one of 'none', 'true', 'false')")
  269. if self.netvm is not None:
  270. self.netvm.connected_vms[self.qid] = self
  271. # Not in generic way to not create QubesHost() to frequently
  272. if self.maxmem is None:
  273. qubes_host = QubesHost()
  274. total_mem_mb = qubes_host.memory_total/1024
  275. self.maxmem = total_mem_mb/2
  276. # By default allow use all VCPUs
  277. if self.vcpus is None:
  278. qubes_host = QubesHost()
  279. self.vcpus = qubes_host.no_cpus
  280. # Always set if meminfo-writer should be active or not
  281. if 'meminfo-writer' not in self.services:
  282. self.services['meminfo-writer'] = not (len(self.pcidevs) > 0)
  283. # Additionally force meminfo-writer disabled when VM have PCI devices
  284. if len(self.pcidevs) > 0:
  285. self.services['meminfo-writer'] = False
  286. # Some additional checks for template based VM
  287. if self.template is not None:
  288. if not self.template.is_template():
  289. print >> sys.stderr, "ERROR: template_qid={0} doesn't point to a valid TemplateVM".\
  290. format(self.template.qid)
  291. return False
  292. self.template.appvms[self.qid] = self
  293. else:
  294. assert self.root_img is not None, "Missing root_img for standalone VM!"
  295. self.xid = -1
  296. self.xid = self.get_xid()
  297. # fire hooks
  298. for hook in self.hooks_init:
  299. hook(self)
  300. def absolute_path(self, arg, default):
  301. if arg is not None and os.path.isabs(arg):
  302. return arg
  303. else:
  304. return os.path.join(self.dir_path, (arg if arg is not None else default))
  305. def _absolute_path_gen(self, default):
  306. return lambda value: self.absolute_path(value, default)
  307. def relative_path(self, arg):
  308. return arg.replace(self.dir_path + '/', '')
  309. @property
  310. def qid(self):
  311. return self.__qid
  312. @property
  313. def label(self):
  314. return self._label
  315. @label.setter
  316. def label(self, new_label):
  317. self._label = new_label
  318. if self.icon_path:
  319. try:
  320. os.remove(self.icon_path)
  321. except:
  322. pass
  323. os.symlink (new_label.icon_path, self.icon_path)
  324. subprocess.call(['sudo', 'xdg-icon-resource', 'forceupdate'])
  325. # fire hooks
  326. for hook in self.hooks_label_setter:
  327. hook(self, new_label)
  328. @property
  329. def netvm(self):
  330. return self._netvm
  331. # Don't know how properly call setter from base class, so workaround it...
  332. @netvm.setter
  333. def netvm(self, new_netvm):
  334. self._set_netvm(new_netvm)
  335. # fire hooks
  336. for hook in self.hooks_netvm_setter:
  337. hook(self, new_netvm)
  338. def _set_netvm(self, new_netvm):
  339. if self.is_running() and new_netvm is not None and not new_netvm.is_running():
  340. raise QubesException("Cannot dynamically attach to stopped NetVM")
  341. if self.netvm is not None:
  342. self.netvm.connected_vms.pop(self.qid)
  343. if self.is_running():
  344. subprocess.call(["xl", "network-detach", self.name, "0"], stderr=subprocess.PIPE)
  345. if hasattr(self.netvm, 'post_vm_net_detach'):
  346. self.netvm.post_vm_net_detach(self)
  347. if new_netvm is None:
  348. if not self._do_not_reset_firewall:
  349. # Set also firewall to block all traffic as discussed in #370
  350. if os.path.exists(self.firewall_conf):
  351. shutil.copy(self.firewall_conf, os.path.join(system_path["qubes_base_dir"],
  352. "backup", "%s-firewall-%s.xml" % (self.name,
  353. time.strftime('%Y-%m-%d-%H:%M:%S'))))
  354. self.write_firewall_conf({'allow': False, 'allowDns': False,
  355. 'allowIcmp': False, 'allowYumProxy': False, 'rules': []})
  356. else:
  357. new_netvm.connected_vms[self.qid]=self
  358. self._netvm = new_netvm
  359. if new_netvm is None:
  360. return
  361. if self.is_running():
  362. # refresh IP, DNS etc
  363. self.create_xenstore_entries()
  364. self.attach_network()
  365. if hasattr(self.netvm, 'post_vm_net_attach'):
  366. self.netvm.post_vm_net_attach(self)
  367. @property
  368. def ip(self):
  369. if self.netvm is not None:
  370. return self.netvm.get_ip_for_vm(self.qid)
  371. else:
  372. return None
  373. @property
  374. def netmask(self):
  375. if self.netvm is not None:
  376. return self.netvm.netmask
  377. else:
  378. return None
  379. @property
  380. def gateway(self):
  381. # This is gateway IP for _other_ VMs, so make sense only in NetVMs
  382. return None
  383. @property
  384. def secondary_dns(self):
  385. if self.netvm is not None:
  386. return self.netvm.secondary_dns
  387. else:
  388. return None
  389. @property
  390. def vif(self):
  391. if self.xid < 0:
  392. return None
  393. if self.netvm is None:
  394. return None
  395. return "vif{0}.+".format(self.xid)
  396. @property
  397. def mac(self):
  398. if self._mac is not None:
  399. return self._mac
  400. else:
  401. return "00:16:3E:5E:6C:{qid:02X}".format(qid=self.qid)
  402. @mac.setter
  403. def mac(self, new_mac):
  404. self._mac = new_mac
  405. @property
  406. def kernel(self):
  407. return self._kernel
  408. @kernel.setter
  409. def kernel(self, new_value):
  410. if new_value is not None:
  411. if not os.path.exists(os.path.join(system_path[
  412. 'qubes_kernels_base_dir'], new_value)):
  413. raise QubesException("Kernel '%s' not installed" % new_value)
  414. for f in ('vmlinuz', 'modules.img'):
  415. if not os.path.exists(os.path.join(
  416. system_path['qubes_kernels_base_dir'], new_value, f)):
  417. raise QubesException(
  418. "Kernel '%s' not properly installed: missing %s "
  419. "file" % (new_value, f))
  420. self._kernel = new_value
  421. @property
  422. def updateable(self):
  423. return self.template is None
  424. # Leaved for compatibility
  425. def is_updateable(self):
  426. return self.updateable
  427. def is_networked(self):
  428. if self.is_netvm():
  429. return True
  430. if self.netvm is not None:
  431. return True
  432. else:
  433. return False
  434. def verify_name(self, name):
  435. if not isinstance(self.__basic_parse_xml_attr(name), str):
  436. return False
  437. return re.match(r"^[a-zA-Z][a-zA-Z0-9_-]*$", name) is not None
  438. def pre_rename(self, new_name):
  439. # fire hooks
  440. for hook in self.hooks_pre_rename:
  441. hook(self, new_name)
  442. def set_name(self, name):
  443. if self.is_running():
  444. raise QubesException("Cannot change name of running VM!")
  445. if not self.verify_name(name):
  446. raise QubesException("Invalid characters in VM name")
  447. self.pre_rename(name)
  448. new_conf = os.path.join(self.dir_path, name + '.conf')
  449. if os.path.exists(self.conf_file):
  450. os.rename(self.conf_file, new_conf)
  451. old_dirpath = self.dir_path
  452. new_dirpath = os.path.join(os.path.dirname(self.dir_path), name)
  453. os.rename(old_dirpath, new_dirpath)
  454. self.dir_path = new_dirpath
  455. old_name = self.name
  456. self.name = name
  457. if self.private_img is not None:
  458. self.private_img = self.private_img.replace(old_dirpath, new_dirpath)
  459. if self.root_img is not None:
  460. self.root_img = self.root_img.replace(old_dirpath, new_dirpath)
  461. if self.volatile_img is not None:
  462. self.volatile_img = self.volatile_img.replace(old_dirpath, new_dirpath)
  463. if self.conf_file is not None:
  464. self.conf_file = new_conf.replace(old_dirpath, new_dirpath)
  465. if self.icon_path is not None:
  466. self.icon_path = self.icon_path.replace(old_dirpath, new_dirpath)
  467. if hasattr(self, 'kernels_dir') and self.kernels_dir is not None:
  468. self.kernels_dir = self.kernels_dir.replace(old_dirpath, new_dirpath)
  469. self.post_rename(old_name)
  470. def post_rename(self, old_name):
  471. # fire hooks
  472. for hook in self.hooks_post_rename:
  473. hook(self, old_name)
  474. @property
  475. def internal(self):
  476. return self._internal
  477. @internal.setter
  478. def internal(self, value):
  479. oldvalue = self._internal
  480. self._internal = value
  481. self.post_set_attr('internal', value, oldvalue)
  482. @property
  483. def autostart(self):
  484. return self._autostart
  485. @autostart.setter
  486. def autostart(self, value):
  487. if value:
  488. retcode = subprocess.call(["sudo", "systemctl", "enable", "qubes-vm@%s.service" % self.name])
  489. else:
  490. retcode = subprocess.call(["sudo", "systemctl", "disable", "qubes-vm@%s.service" % self.name])
  491. if retcode != 0:
  492. raise QubesException("Failed to set autostart for VM via systemctl")
  493. self._autostart = bool(value)
  494. @classmethod
  495. def is_template_compatible(cls, template):
  496. """Check if given VM can be a template for this VM"""
  497. # FIXME: check if the value is instance of QubesTemplateVM, not the VM
  498. # type. The problem is while this file is loaded, QubesTemplateVM is
  499. # not defined yet.
  500. if template and (not template.is_template() or template.type != "TemplateVM"):
  501. return False
  502. return True
  503. @property
  504. def template(self):
  505. return self._template
  506. @template.setter
  507. def template(self, value):
  508. if self._template is None and value is not None:
  509. raise QubesException("Cannot set template for standalone VM")
  510. if value and not self.is_template_compatible(value):
  511. raise QubesException("Incompatible template type %s with VM of type %s" % (value.type, self.type))
  512. self._template = value
  513. def is_template(self):
  514. return False
  515. def is_appvm(self):
  516. return False
  517. def is_netvm(self):
  518. return False
  519. def is_proxyvm(self):
  520. return False
  521. def is_disposablevm(self):
  522. return False
  523. def _xid_to_name(self, xid):
  524. if xid in xid_to_name_cache:
  525. return xid_to_name_cache[xid]
  526. else:
  527. domname = xl_ctx.domid_to_name(xid)
  528. if domname:
  529. xid_to_name_cache[xid] = domname
  530. return domname
  531. def get_xl_dominfo(self):
  532. if dry_run:
  533. return
  534. start_xid = self.xid
  535. domains = xl_ctx.list_domains()
  536. for dominfo in domains:
  537. if dominfo.domid == start_xid:
  538. return dominfo
  539. elif dominfo.domid < start_xid:
  540. # the current XID can't lower than one noticed earlier, if VM
  541. # was restarted in the meantime, the next XID will greater
  542. continue
  543. domname = self._xid_to_name(dominfo.domid)
  544. if domname == self.name:
  545. self.xid = dominfo.domid
  546. return dominfo
  547. return None
  548. def get_xc_dominfo(self, name = None):
  549. if dry_run:
  550. return
  551. if name is None:
  552. name = self.name
  553. start_xid = self.xid
  554. if start_xid < 0:
  555. start_xid = 0
  556. try:
  557. domains = xc.domain_getinfo(start_xid, qubes_max_qid)
  558. except xen.lowlevel.xc.Error:
  559. return None
  560. # If previous XID is still valid, this is the right domain - XID can't
  561. # be reused for another domain until system reboot
  562. if start_xid > 0 and domains[0]['domid'] == start_xid:
  563. return domains[0]
  564. for dominfo in domains:
  565. domname = self._xid_to_name(dominfo['domid'])
  566. if domname == name:
  567. return dominfo
  568. return None
  569. def get_xid(self):
  570. if dry_run:
  571. return 666
  572. dominfo = self.get_xc_dominfo()
  573. if dominfo:
  574. self.xid = dominfo['domid']
  575. return self.xid
  576. else:
  577. return -1
  578. def get_uuid(self):
  579. dominfo = self.get_xl_dominfo()
  580. if dominfo:
  581. vmuuid = uuid.UUID(''.join('%02x' % b for b in dominfo.uuid))
  582. return vmuuid
  583. else:
  584. return None
  585. def get_mem(self):
  586. if dry_run:
  587. return 666
  588. dominfo = self.get_xc_dominfo()
  589. if dominfo:
  590. return dominfo['mem_kb']
  591. else:
  592. return 0
  593. def get_mem_static_max(self):
  594. if dry_run:
  595. return 666
  596. dominfo = self.get_xc_dominfo()
  597. if dominfo:
  598. return dominfo['maxmem_kb']
  599. else:
  600. return 0
  601. def get_prefmem(self):
  602. untrusted_meminfo_key = xs.read('', '/local/domain/%s/memory/meminfo'
  603. % self.xid)
  604. if untrusted_meminfo_key is None or untrusted_meminfo_key == '':
  605. return 0
  606. domain = qmemman.DomainState(self.xid)
  607. qmemman_algo.refresh_meminfo_for_domain(domain, untrusted_meminfo_key)
  608. domain.memory_maximum = self.get_mem_static_max()*1024
  609. return qmemman_algo.prefmem(domain)/1024
  610. def get_per_cpu_time(self):
  611. if dry_run:
  612. import random
  613. return random.random() * 100
  614. dominfo = self.get_xc_dominfo()
  615. if dominfo:
  616. return dominfo['cpu_time']/dominfo['online_vcpus']
  617. else:
  618. return 0
  619. def get_disk_utilization_root_img(self):
  620. return qubes.qubesutils.get_disk_usage(self.root_img)
  621. def get_root_img_sz(self):
  622. if not os.path.exists(self.root_img):
  623. return 0
  624. return os.path.getsize(self.root_img)
  625. def get_power_state(self):
  626. if dry_run:
  627. return "NA"
  628. dominfo = self.get_xc_dominfo()
  629. if dominfo:
  630. if dominfo['paused']:
  631. return "Paused"
  632. elif dominfo['crashed']:
  633. return "Crashed"
  634. elif dominfo['shutdown']:
  635. if dominfo['shutdown_reason'] == 2:
  636. return "Suspended"
  637. else:
  638. return "Halting"
  639. elif dominfo['dying']:
  640. return "Dying"
  641. else:
  642. if not self.is_fully_usable():
  643. return "Transient"
  644. else:
  645. return "Running"
  646. else:
  647. return 'Halted'
  648. return "NA"
  649. def is_guid_running(self):
  650. xid = self.get_xid()
  651. if xid < 0:
  652. return False
  653. if not os.path.exists('/var/run/qubes/guid-running.%d' % xid):
  654. return False
  655. return True
  656. def is_qrexec_running(self):
  657. if self.xid < 0:
  658. return False
  659. return os.path.exists('/var/run/qubes/qrexec.%s' % self.name)
  660. def is_fully_usable(self):
  661. # Running gui-daemon implies also VM running
  662. if not self.is_guid_running():
  663. return False
  664. if not self.is_qrexec_running():
  665. return False
  666. return True
  667. def is_running(self):
  668. # in terms of Xen and internal logic - starting VM is running
  669. if self.get_power_state() in ["Running", "Transient", "Halting"]:
  670. return True
  671. else:
  672. return False
  673. def is_paused(self):
  674. if self.get_power_state() == "Paused":
  675. return True
  676. else:
  677. return False
  678. def get_start_time(self):
  679. if not self.is_running():
  680. return None
  681. dominfo = self.get_xl_dominfo()
  682. uuid = self.get_uuid()
  683. start_time = xs.read('', "/vm/%s/start_time" % str(uuid))
  684. if start_time != '':
  685. return datetime.datetime.fromtimestamp(float(start_time))
  686. else:
  687. return None
  688. def is_outdated(self):
  689. # Makes sense only on VM based on template
  690. if self.template is None:
  691. return False
  692. if not self.is_running():
  693. return False
  694. if not hasattr(self.template, 'rootcow_img'):
  695. return False
  696. rootimg_inode = os.stat(self.template.root_img)
  697. try:
  698. rootcow_inode = os.stat(self.template.rootcow_img)
  699. except OSError:
  700. # The only case when rootcow_img doesn't exists is in the middle of
  701. # commit_changes, so VM is outdated right now
  702. return True
  703. current_dmdev = "/dev/mapper/snapshot-{0:x}:{1}-{2:x}:{3}".format(
  704. rootimg_inode[2], rootimg_inode[1],
  705. rootcow_inode[2], rootcow_inode[1])
  706. # 51712 (0xCA00) is xvda
  707. # backend node name not available through xenapi :(
  708. used_dmdev = xs.read('', "/local/domain/0/backend/vbd/{0}/51712/node".format(self.get_xid()))
  709. return used_dmdev != current_dmdev
  710. def get_disk_utilization(self):
  711. return qubes.qubesutils.get_disk_usage(self.dir_path)
  712. def get_disk_utilization_private_img(self):
  713. return qubes.qubesutils.get_disk_usage(self.private_img)
  714. def get_private_img_sz(self):
  715. if not os.path.exists(self.private_img):
  716. return 0
  717. return os.path.getsize(self.private_img)
  718. def resize_private_img(self, size):
  719. assert size >= self.get_private_img_sz(), "Cannot shrink private.img"
  720. f_private = open (self.private_img, "a+b")
  721. f_private.truncate (size)
  722. f_private.close ()
  723. retcode = 0
  724. if self.is_running():
  725. # find loop device
  726. p = subprocess.Popen (["sudo", "losetup", "--associated", self.private_img],
  727. stdout=subprocess.PIPE)
  728. result = p.communicate()
  729. m = re.match(r"^(/dev/loop\d+):\s", result[0])
  730. if m is None:
  731. raise QubesException("ERROR: Cannot find loop device!")
  732. loop_dev = m.group(1)
  733. # resize loop device
  734. subprocess.check_call(["sudo", "losetup", "--set-capacity", loop_dev])
  735. retcode = self.run("while [ \"`blockdev --getsize64 /dev/xvdb`\" -lt {0} ]; do ".format(size) +
  736. "head /dev/xvdb > /dev/null; sleep 0.2; done; resize2fs /dev/xvdb", user="root", wait=True)
  737. if retcode != 0:
  738. raise QubesException("resize2fs failed")
  739. # FIXME: should be outside of QubesVM?
  740. def get_timezone(self):
  741. # fc18
  742. if os.path.islink('/etc/localtime'):
  743. return '/'.join(os.readlink('/etc/localtime').split('/')[-2:])
  744. # <=fc17
  745. elif os.path.exists('/etc/sysconfig/clock'):
  746. clock_config = open('/etc/sysconfig/clock', "r")
  747. clock_config_lines = clock_config.readlines()
  748. clock_config.close()
  749. zone_re = re.compile(r'^ZONE="(.*)"')
  750. for line in clock_config_lines:
  751. line_match = zone_re.match(line)
  752. if line_match:
  753. return line_match.group(1)
  754. else:
  755. # last resort way, some applications makes /etc/localtime
  756. # hardlink instead of symlink...
  757. tz_info = os.stat('/etc/localtime')
  758. if not tz_info:
  759. return None
  760. if tz_info.st_nlink > 1:
  761. p = subprocess.Popen(['find', '/usr/share/zoneinfo',
  762. '-inum', str(tz_info.st_ino)],
  763. stdout=subprocess.PIPE)
  764. tz_path = p.communicate()[0].strip()
  765. return tz_path.replace('/usr/share/zoneinfo/', '')
  766. return None
  767. def cleanup_vifs(self):
  768. """
  769. Xend does not remove vif when backend domain is down, so we must do it
  770. manually
  771. """
  772. if not self.is_running():
  773. return
  774. dev_basepath = '/local/domain/%d/device/vif' % self.xid
  775. for dev in xs.ls('', dev_basepath):
  776. # check if backend domain is alive
  777. backend_xid = int(xs.read('', '%s/%s/backend-id' % (dev_basepath, dev)))
  778. if xl_ctx.domid_to_name(backend_xid) is not None:
  779. # check if device is still active
  780. if xs.read('', '%s/%s/state' % (dev_basepath, dev)) == '4':
  781. continue
  782. # remove dead device
  783. xs.rm('', '%s/%s' % (dev_basepath, dev))
  784. def create_xenstore_entries(self, xid = None):
  785. if dry_run:
  786. return
  787. if xid is None:
  788. xid = self.xid
  789. domain_path = xs.get_domain_path(xid)
  790. # Set Xen Store entires with VM networking info:
  791. xs.write('', "{0}/qubes-vm-type".format(domain_path),
  792. self.type)
  793. xs.write('', "{0}/qubes-vm-updateable".format(domain_path),
  794. str(self.updateable))
  795. if self.is_netvm():
  796. xs.write('',
  797. "{0}/qubes-netvm-gateway".format(domain_path),
  798. self.gateway)
  799. xs.write('',
  800. "{0}/qubes-netvm-secondary-dns".format(domain_path),
  801. self.secondary_dns)
  802. xs.write('',
  803. "{0}/qubes-netvm-netmask".format(domain_path),
  804. self.netmask)
  805. xs.write('',
  806. "{0}/qubes-netvm-network".format(domain_path),
  807. self.network)
  808. if self.netvm is not None:
  809. xs.write('', "{0}/qubes-ip".format(domain_path), self.ip)
  810. xs.write('', "{0}/qubes-netmask".format(domain_path),
  811. self.netvm.netmask)
  812. xs.write('', "{0}/qubes-gateway".format(domain_path),
  813. self.netvm.gateway)
  814. xs.write('',
  815. "{0}/qubes-secondary-dns".format(domain_path),
  816. self.netvm.secondary_dns)
  817. tzname = self.get_timezone()
  818. if tzname:
  819. xs.write('',
  820. "{0}/qubes-timezone".format(domain_path),
  821. tzname)
  822. for srv in self.services.keys():
  823. # convert True/False to "1"/"0"
  824. xs.write('', "{0}/qubes-service/{1}".format(domain_path, srv),
  825. str(int(self.services[srv])))
  826. xs.write('',
  827. "{0}/qubes-block-devices".format(domain_path),
  828. '')
  829. xs.write('',
  830. "{0}/qubes-usb-devices".format(domain_path),
  831. '')
  832. xs.write('', "{0}/qubes-debug-mode".format(domain_path),
  833. str(int(self.debug)))
  834. # Fix permissions
  835. xs.set_permissions('', '{0}/device'.format(domain_path),
  836. [{ 'dom': xid }])
  837. xs.set_permissions('', '{0}/memory'.format(domain_path),
  838. [{ 'dom': xid }])
  839. xs.set_permissions('', '{0}/qubes-block-devices'.format(domain_path),
  840. [{ 'dom': xid }])
  841. xs.set_permissions('', '{0}/qubes-usb-devices'.format(domain_path),
  842. [{ 'dom': xid }])
  843. # fire hooks
  844. for hook in self.hooks_create_xenstore_entries:
  845. hook(self, xid=xid)
  846. def get_rootdev(self, source_template=None):
  847. if self.template:
  848. return "'script:snapshot:{dir}/root.img:{dir}/root-cow.img,xvda,r',".format(dir=self.template.dir_path)
  849. else:
  850. return "'script:file:{dir}/root.img,xvda,w',".format(dir=self.dir_path)
  851. def get_config_params(self, source_template=None):
  852. args = {}
  853. args['name'] = self.name
  854. if hasattr(self, 'kernels_dir'):
  855. args['kerneldir'] = self.kernels_dir
  856. args['vmdir'] = self.dir_path
  857. args['pcidev'] = str(self.pcidevs).strip('[]')
  858. args['mem'] = str(self.memory)
  859. if self.maxmem < self.memory:
  860. args['mem'] = str(self.maxmem)
  861. args['maxmem'] = str(self.maxmem)
  862. if 'meminfo-writer' in self.services and not self.services['meminfo-writer']:
  863. # If dynamic memory management disabled, set maxmem=mem
  864. args['maxmem'] = args['mem']
  865. args['vcpus'] = str(self.vcpus)
  866. if self.netvm is not None:
  867. args['ip'] = self.ip
  868. args['mac'] = self.mac
  869. args['gateway'] = self.netvm.gateway
  870. args['dns1'] = self.netvm.gateway
  871. args['dns2'] = self.secondary_dns
  872. args['netmask'] = self.netmask
  873. args['netdev'] = "'mac={mac},script=/etc/xen/scripts/vif-route-qubes,ip={ip}".format(ip=self.ip, mac=self.mac)
  874. if self.netvm.qid != 0:
  875. args['netdev'] += ",backend={0}".format(self.netvm.name)
  876. args['netdev'] += "'"
  877. args['disable_network'] = '';
  878. else:
  879. args['ip'] = ''
  880. args['mac'] = ''
  881. args['gateway'] = ''
  882. args['dns1'] = ''
  883. args['dns2'] = ''
  884. args['netmask'] = ''
  885. args['netdev'] = ''
  886. args['disable_network'] = '#';
  887. args['rootdev'] = self.get_rootdev(source_template=source_template)
  888. args['privatedev'] = "'script:file:{dir}/private.img,xvdb,w',".format(dir=self.dir_path)
  889. args['volatiledev'] = "'script:file:{dir}/volatile.img,xvdc,w',".format(dir=self.dir_path)
  890. if hasattr(self, 'kernel'):
  891. modulesmode='r'
  892. if self.kernel is None:
  893. modulesmode='w'
  894. args['otherdevs'] = "'script:file:{dir}/modules.img,xvdd,{mode}',".format(dir=self.kernels_dir, mode=modulesmode)
  895. if hasattr(self, 'kernelopts'):
  896. args['kernelopts'] = self.kernelopts
  897. if self.debug:
  898. print >> sys.stderr, "--> Debug mode: adding 'earlyprintk=xen' to kernel opts"
  899. args['kernelopts'] += ' earlyprintk=xen'
  900. # fire hooks
  901. for hook in self.hooks_get_config_params:
  902. args = hook(self, args)
  903. return args
  904. @property
  905. def uses_custom_config(self):
  906. return self.conf_file != self.absolute_path(self.name + ".conf", None)
  907. def create_config_file(self, file_path = None, source_template = None, prepare_dvm = False):
  908. if file_path is None:
  909. file_path = self.conf_file
  910. if self.uses_custom_config:
  911. return
  912. if source_template is None:
  913. source_template = self.template
  914. f_conf_template = open(self.config_file_template, 'r')
  915. conf_template = f_conf_template.read()
  916. f_conf_template.close()
  917. template_params = self.get_config_params(source_template)
  918. if prepare_dvm:
  919. template_params['name'] = '%NAME%'
  920. template_params['privatedev'] = ''
  921. template_params['netdev'] = re.sub(r"ip=[0-9.]*", "ip=%IP%", template_params['netdev'])
  922. conf_appvm = open(file_path, "w")
  923. conf_appvm.write(conf_template.format(**template_params))
  924. conf_appvm.close()
  925. def create_on_disk(self, verbose, source_template = None):
  926. if source_template is None:
  927. source_template = self.template
  928. assert source_template is not None
  929. if dry_run:
  930. return
  931. if verbose:
  932. print >> sys.stderr, "--> Creating directory: {0}".format(self.dir_path)
  933. os.mkdir (self.dir_path)
  934. if verbose:
  935. print >> sys.stderr, "--> Creating the VM config file: {0}".format(self.conf_file)
  936. self.create_config_file(source_template = source_template)
  937. template_priv = source_template.private_img
  938. if verbose:
  939. print >> sys.stderr, "--> Copying the template's private image: {0}".\
  940. format(template_priv)
  941. # We prefer to use Linux's cp, because it nicely handles sparse files
  942. retcode = subprocess.call (["cp", template_priv, self.private_img])
  943. if retcode != 0:
  944. raise IOError ("Error while copying {0} to {1}".\
  945. format(template_priv, self.private_img))
  946. if self.updateable:
  947. template_root = source_template.root_img
  948. if verbose:
  949. print >> sys.stderr, "--> Copying the template's root image: {0}".\
  950. format(template_root)
  951. # We prefer to use Linux's cp, because it nicely handles sparse files
  952. retcode = subprocess.call (["cp", template_root, self.root_img])
  953. if retcode != 0:
  954. raise IOError ("Error while copying {0} to {1}".\
  955. format(template_root, self.root_img))
  956. kernels_dir = source_template.kernels_dir
  957. if verbose:
  958. print >> sys.stderr, "--> Copying the kernel (set kernel \"none\" to use it): {0}".\
  959. format(kernels_dir)
  960. os.mkdir (self.dir_path + '/kernels')
  961. for f in ("vmlinuz", "initramfs", "modules.img"):
  962. shutil.copy(os.path.join(kernels_dir, f),
  963. os.path.join(self.dir_path, vm_files["kernels_subdir"], f))
  964. # Create volatile.img
  965. self.reset_volatile_storage(source_template = source_template, verbose=verbose)
  966. if verbose:
  967. print >> sys.stderr, "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path)
  968. os.symlink (self.label.icon_path, self.icon_path)
  969. # fire hooks
  970. for hook in self.hooks_create_on_disk:
  971. hook(self, verbose, source_template=source_template)
  972. def get_clone_attrs(self):
  973. attrs = ['kernel', 'uses_default_kernel', 'netvm', 'uses_default_netvm', \
  974. 'memory', 'maxmem', 'kernelopts', 'uses_default_kernelopts', 'services', 'vcpus', \
  975. '_mac', 'pcidevs', 'include_in_backups', '_label', 'default_user']
  976. # fire hooks
  977. for hook in self.hooks_get_clone_attrs:
  978. attrs = hook(self, attrs)
  979. return attrs
  980. def clone_attrs(self, src_vm, fail_on_error=True):
  981. self._do_not_reset_firewall = True
  982. for prop in self.get_clone_attrs():
  983. try:
  984. setattr(self, prop, getattr(src_vm, prop))
  985. except Exception as e:
  986. if fail_on_error:
  987. self._do_not_reset_firewall = False
  988. raise
  989. else:
  990. print >>sys.stderr, "WARNING: %s" % str(e)
  991. self._do_not_reset_firewall = False
  992. def clone_disk_files(self, src_vm, verbose):
  993. if dry_run:
  994. return
  995. if src_vm.is_running():
  996. raise QubesException("Attempt to clone a running VM!")
  997. if verbose:
  998. print >> sys.stderr, "--> Creating directory: {0}".format(self.dir_path)
  999. os.mkdir (self.dir_path)
  1000. if src_vm.private_img is not None and self.private_img is not None:
  1001. if verbose:
  1002. print >> sys.stderr, "--> Copying the private image:\n{0} ==>\n{1}".\
  1003. format(src_vm.private_img, self.private_img)
  1004. # We prefer to use Linux's cp, because it nicely handles sparse files
  1005. retcode = subprocess.call (["cp", src_vm.private_img, self.private_img])
  1006. if retcode != 0:
  1007. raise IOError ("Error while copying {0} to {1}".\
  1008. format(src_vm.private_img, self.private_img))
  1009. if src_vm.updateable and src_vm.root_img is not None and self.root_img is not None:
  1010. if verbose:
  1011. print >> sys.stderr, "--> Copying the root image:\n{0} ==>\n{1}".\
  1012. format(src_vm.root_img, self.root_img)
  1013. # We prefer to use Linux's cp, because it nicely handles sparse files
  1014. retcode = subprocess.call (["cp", src_vm.root_img, self.root_img])
  1015. if retcode != 0:
  1016. raise IOError ("Error while copying {0} to {1}".\
  1017. format(src_vm.root_img, self.root_img))
  1018. if src_vm.icon_path is not None and self.icon_path is not None:
  1019. if os.path.exists (src_vm.dir_path):
  1020. if os.path.islink(src_vm.icon_path):
  1021. icon_path = os.readlink(src_vm.icon_path)
  1022. if verbose:
  1023. print >> sys.stderr, "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, icon_path)
  1024. os.symlink (icon_path, self.icon_path)
  1025. else:
  1026. if verbose:
  1027. print >> sys.stderr, "--> Copying icon: {0} -> {1}".format(src_vm.icon_path, self.icon_path)
  1028. shutil.copy(src_vm.icon_path, self.icon_path)
  1029. # fire hooks
  1030. for hook in self.hooks_clone_disk_files:
  1031. hook(self, src_vm, verbose)
  1032. def verify_files(self):
  1033. if dry_run:
  1034. return
  1035. if not os.path.exists (self.dir_path):
  1036. raise QubesException (
  1037. "VM directory doesn't exist: {0}".\
  1038. format(self.dir_path))
  1039. if self.updateable and not os.path.exists (self.root_img):
  1040. raise QubesException (
  1041. "VM root image file doesn't exist: {0}".\
  1042. format(self.root_img))
  1043. if not os.path.exists (self.private_img):
  1044. raise QubesException (
  1045. "VM private image file doesn't exist: {0}".\
  1046. format(self.private_img))
  1047. if not os.path.exists (os.path.join(self.kernels_dir, 'vmlinuz')):
  1048. raise QubesException (
  1049. "VM kernel does not exists: {0}".\
  1050. format(os.path.join(self.kernels_dir, 'vmlinuz')))
  1051. if not os.path.exists (os.path.join(self.kernels_dir, 'initramfs')):
  1052. raise QubesException (
  1053. "VM initramfs does not exists: {0}".\
  1054. format(os.path.join(self.kernels_dir, 'initramfs')))
  1055. if not os.path.exists (os.path.join(self.kernels_dir, 'modules.img')):
  1056. raise QubesException (
  1057. "VM kernel modules image does not exists: {0}".\
  1058. format(os.path.join(self.kernels_dir, 'modules.img')))
  1059. # fire hooks
  1060. for hook in self.hooks_verify_files:
  1061. hook(self)
  1062. return True
  1063. def reset_volatile_storage(self, source_template = None, verbose = False):
  1064. assert not self.is_running(), "Attempt to clean volatile image of running VM!"
  1065. if source_template is None:
  1066. source_template = self.template
  1067. # Only makes sense on template based VM
  1068. if source_template is None:
  1069. # For StandaloneVM create it only if not already exists (eg after backup-restore)
  1070. if not os.path.exists(self.volatile_img):
  1071. if verbose:
  1072. print >> sys.stderr, "--> Creating volatile image: {0}...".format (self.volatile_img)
  1073. f_root = open (self.root_img, "r")
  1074. f_root.seek(0, os.SEEK_END)
  1075. root_size = f_root.tell()
  1076. f_root.close()
  1077. subprocess.check_call([system_path["prepare_volatile_img_cmd"], self.volatile_img, str(root_size / 1024 / 1024)])
  1078. return
  1079. if verbose:
  1080. print >> sys.stderr, "--> Cleaning volatile image: {0}...".format (self.volatile_img)
  1081. if dry_run:
  1082. return
  1083. if os.path.exists (self.volatile_img):
  1084. os.remove (self.volatile_img)
  1085. if hasattr(source_template, 'clean_volatile_img'):
  1086. retcode = subprocess.call (["tar", "xf", source_template.clean_volatile_img, "-C", self.dir_path])
  1087. if retcode != 0:
  1088. raise IOError ("Error while unpacking {0} to {1}".\
  1089. format(source_template.clean_volatile_img, self.volatile_img))
  1090. def remove_from_disk(self):
  1091. if dry_run:
  1092. return
  1093. # fire hooks
  1094. for hook in self.hooks_remove_from_disk:
  1095. hook(self)
  1096. shutil.rmtree (self.dir_path)
  1097. def write_firewall_conf(self, conf):
  1098. defaults = self.get_firewall_conf()
  1099. expiring_rules_present = False
  1100. for item in defaults.keys():
  1101. if item not in conf:
  1102. conf[item] = defaults[item]
  1103. root = lxml.etree.Element(
  1104. "QubesFirewallRules",
  1105. policy = "allow" if conf["allow"] else "deny",
  1106. dns = "allow" if conf["allowDns"] else "deny",
  1107. icmp = "allow" if conf["allowIcmp"] else "deny",
  1108. yumProxy = "allow" if conf["allowYumProxy"] else "deny"
  1109. )
  1110. for rule in conf["rules"]:
  1111. # For backward compatibility
  1112. if "proto" not in rule:
  1113. if rule["portBegin"] is not None and rule["portBegin"] > 0:
  1114. rule["proto"] = "tcp"
  1115. else:
  1116. rule["proto"] = "any"
  1117. element = lxml.etree.Element(
  1118. "rule",
  1119. address=rule["address"],
  1120. proto=str(rule["proto"]),
  1121. )
  1122. if rule["netmask"] is not None and rule["netmask"] != 32:
  1123. element.set("netmask", str(rule["netmask"]))
  1124. if rule.get("portBegin", None) is not None and \
  1125. rule["portBegin"] > 0:
  1126. element.set("port", str(rule["portBegin"]))
  1127. if rule.get("portEnd", None) is not None and rule["portEnd"] > 0:
  1128. element.set("toport", str(rule["portEnd"]))
  1129. if "expire" in rule:
  1130. element.set("expire", str(rule["expire"]))
  1131. expiring_rules_present = True
  1132. root.append(element)
  1133. tree = lxml.etree.ElementTree(root)
  1134. try:
  1135. f = open(self.firewall_conf, 'a') # create the file if not exist
  1136. f.close()
  1137. with open(self.firewall_conf, 'w') as f:
  1138. fcntl.lockf(f, fcntl.LOCK_EX)
  1139. tree.write(f, encoding="UTF-8", pretty_print=True)
  1140. fcntl.lockf(f, fcntl.LOCK_UN)
  1141. f.close()
  1142. except EnvironmentError as err:
  1143. print >> sys.stderr, "{0}: save error: {1}".format(
  1144. os.path.basename(sys.argv[0]), err)
  1145. return False
  1146. # Automatically enable/disable 'yum-proxy-setup' service based on allowYumProxy
  1147. if conf['allowYumProxy']:
  1148. self.services['yum-proxy-setup'] = True
  1149. else:
  1150. if self.services.has_key('yum-proxy-setup'):
  1151. self.services.pop('yum-proxy-setup')
  1152. if expiring_rules_present:
  1153. subprocess.call(["sudo", "systemctl", "start",
  1154. "qubes-reload-firewall@%s.timer" % self.name])
  1155. return True
  1156. def has_firewall(self):
  1157. return os.path.exists (self.firewall_conf)
  1158. def get_firewall_defaults(self):
  1159. return { "rules": list(), "allow": True, "allowDns": True, "allowIcmp": True, "allowYumProxy": False }
  1160. def get_firewall_conf(self):
  1161. conf = self.get_firewall_defaults()
  1162. try:
  1163. tree = lxml.etree.parse(self.firewall_conf)
  1164. root = tree.getroot()
  1165. conf["allow"] = (root.get("policy") == "allow")
  1166. conf["allowDns"] = (root.get("dns") == "allow")
  1167. conf["allowIcmp"] = (root.get("icmp") == "allow")
  1168. conf["allowYumProxy"] = (root.get("yumProxy") == "allow")
  1169. for element in root:
  1170. rule = {}
  1171. attr_list = ("address", "netmask", "proto", "port", "toport",
  1172. "expire")
  1173. for attribute in attr_list:
  1174. rule[attribute] = element.get(attribute)
  1175. if rule["netmask"] is not None:
  1176. rule["netmask"] = int(rule["netmask"])
  1177. else:
  1178. rule["netmask"] = 32
  1179. if rule["port"] is not None:
  1180. rule["portBegin"] = int(rule["port"])
  1181. else:
  1182. # backward compatibility
  1183. rule["portBegin"] = 0
  1184. # For backward compatibility
  1185. if rule["proto"] is None:
  1186. if rule["portBegin"] > 0:
  1187. rule["proto"] = "tcp"
  1188. else:
  1189. rule["proto"] = "any"
  1190. if rule["toport"] is not None:
  1191. rule["portEnd"] = int(rule["toport"])
  1192. else:
  1193. rule["portEnd"] = None
  1194. if rule["expire"] is not None:
  1195. rule["expire"] = int(rule["expire"])
  1196. if rule["expire"] <= int(datetime.datetime.now().strftime(
  1197. "%s")):
  1198. continue
  1199. else:
  1200. del(rule["expire"])
  1201. del(rule["port"])
  1202. del(rule["toport"])
  1203. conf["rules"].append(rule)
  1204. except EnvironmentError as err:
  1205. return conf
  1206. except (xml.parsers.expat.ExpatError,
  1207. ValueError, LookupError) as err:
  1208. print("{0}: load error: {1}".format(
  1209. os.path.basename(sys.argv[0]), err))
  1210. return None
  1211. return conf
  1212. def pci_add(self, pci):
  1213. if not os.path.exists('/sys/bus/pci/devices/0000:%s' % pci):
  1214. raise QubesException("Invalid PCI device: %s" % pci)
  1215. if self.pcidevs.count(pci):
  1216. # already added
  1217. return
  1218. self.pcidevs.append(pci)
  1219. if self.is_running():
  1220. try:
  1221. subprocess.check_call(['sudo', system_path["qubes_pciback_cmd"], pci])
  1222. subprocess.check_call(['sudo', 'xl', 'pci-attach', str(self.xid), pci])
  1223. except Exception as e:
  1224. print >>sys.stderr, "Failed to attach PCI device on the fly " \
  1225. "(%s), changes will be seen after VM restart" % str(e)
  1226. def pci_remove(self, pci):
  1227. if not self.pcidevs.count(pci):
  1228. # not attached
  1229. return
  1230. self.pcidevs.remove(pci)
  1231. if self.is_running():
  1232. p = subprocess.Popen(['xl', 'pci-list', str(self.xid)],
  1233. stdout=subprocess.PIPE)
  1234. result = p.communicate()
  1235. m = re.search(r"^(\d+.\d+)\s+0000:%s$" % pci, result[0], flags=re.MULTILINE)
  1236. if not m:
  1237. print >>sys.stderr, "Device %s already detached" % pci
  1238. return
  1239. vmdev = m.group(1)
  1240. try:
  1241. self.run_service("qubes.DetachPciDevice",
  1242. user="root", input="00:%s" % vmdev)
  1243. subprocess.check_call(['sudo', 'xl', 'pci-detach', str(self.xid), pci])
  1244. except Exception as e:
  1245. print >>sys.stderr, "Failed to detach PCI device on the fly " \
  1246. "(%s), changes will be seen after VM restart" % str(e)
  1247. def run(self, command, user = None, verbose = True, autostart = False,
  1248. notify_function = None,
  1249. passio = False, passio_popen = False, passio_stderr=False,
  1250. ignore_stderr=False, localcmd = None, wait = False, gui = True,
  1251. filter_esc = False):
  1252. """command should be in form 'cmdline'
  1253. When passio_popen=True, popen object with stdout connected to pipe.
  1254. When additionally passio_stderr=True, stderr also is connected to pipe.
  1255. When ignore_stderr=True, stderr is connected to /dev/null.
  1256. """
  1257. if user is None:
  1258. user = self.default_user
  1259. null = None
  1260. if not self.is_running() and not self.is_paused():
  1261. if not autostart:
  1262. raise QubesException("VM not running")
  1263. try:
  1264. if notify_function is not None:
  1265. notify_function ("info", "Starting the '{0}' VM...".format(self.name))
  1266. elif verbose:
  1267. print >> sys.stderr, "Starting the VM '{0}'...".format(self.name)
  1268. xid = self.start(verbose=verbose, start_guid = gui, notify_function=notify_function)
  1269. except (IOError, OSError, QubesException) as err:
  1270. raise QubesException("Error while starting the '{0}' VM: {1}".format(self.name, err))
  1271. except (MemoryError) as err:
  1272. raise QubesException("Not enough memory to start '{0}' VM! "
  1273. "Close one or more running VMs and try "
  1274. "again.".format(self.name))
  1275. if self.is_paused():
  1276. raise QubesException("VM is paused")
  1277. if not self.is_qrexec_running():
  1278. raise QubesException(
  1279. "Domain '{}': qrexec not connected.".format(self.name))
  1280. xid = self.get_xid()
  1281. if gui and os.getenv("DISPLAY") is not None and not self.is_guid_running():
  1282. self.start_guid(verbose = verbose, notify_function = notify_function)
  1283. args = [system_path["qrexec_client_path"], "-d", str(xid), "%s:%s" % (user, command)]
  1284. if localcmd is not None:
  1285. args += [ "-l", localcmd]
  1286. if filter_esc:
  1287. args += ["-t"]
  1288. if os.isatty(sys.stderr.fileno()):
  1289. args += ["-T"]
  1290. if passio:
  1291. os.execv(system_path["qrexec_client_path"], args)
  1292. exit(1)
  1293. call_kwargs = {}
  1294. if ignore_stderr:
  1295. null = open("/dev/null", "w")
  1296. call_kwargs['stderr'] = null
  1297. if passio_popen:
  1298. popen_kwargs={'stdout': subprocess.PIPE}
  1299. popen_kwargs['stdin'] = subprocess.PIPE
  1300. if passio_stderr:
  1301. popen_kwargs['stderr'] = subprocess.PIPE
  1302. else:
  1303. popen_kwargs['stderr'] = call_kwargs.get('stderr', None)
  1304. p = subprocess.Popen (args, **popen_kwargs)
  1305. if null:
  1306. null.close()
  1307. return p
  1308. if not wait:
  1309. args += ["-e"]
  1310. retcode = subprocess.call(args, **call_kwargs)
  1311. if null:
  1312. null.close()
  1313. return retcode
  1314. def run_service(self, service, source="dom0", user=None,
  1315. passio_popen = False, input=None):
  1316. if input and passio_popen:
  1317. raise ValueError("'input' and 'passio_popen' cannot be used "
  1318. "together")
  1319. if input:
  1320. return self.run("QUBESRPC %s %s" % (service, source),
  1321. localcmd="echo %s" % input, user=user, wait=True)
  1322. else:
  1323. return self.run("QUBESRPC %s %s" % (service, source),
  1324. passio_popen=passio_popen, user=user, wait=True)
  1325. def attach_network(self, verbose = False, wait = True, netvm = None):
  1326. if dry_run:
  1327. return
  1328. if not self.is_running():
  1329. raise QubesException ("VM not running!")
  1330. if netvm is None:
  1331. netvm = self.netvm
  1332. if netvm is None:
  1333. raise QubesException ("NetVM not set!")
  1334. if netvm.qid != 0:
  1335. if not netvm.is_running():
  1336. if verbose:
  1337. print >> sys.stderr, "--> Starting NetVM {0}...".format(netvm.name)
  1338. netvm.start()
  1339. xs_path = '/local/domain/%d/device/vif/0/state' % (self.xid)
  1340. if xs.read('', xs_path) is not None:
  1341. # TODO: check its state and backend state (this can be stale vif after NetVM restart)
  1342. if verbose:
  1343. print >> sys.stderr, "NOTICE: Network already attached"
  1344. return
  1345. xm_cmdline = ["/usr/sbin/xl", "network-attach", str(self.xid), "script=/etc/xen/scripts/vif-route-qubes", "ip="+self.ip, "backend="+netvm.name ]
  1346. retcode = subprocess.call (xm_cmdline)
  1347. if retcode != 0:
  1348. print >> sys.stderr, ("WARNING: Cannot attach to network to '{0}'!".format(self.name))
  1349. if wait:
  1350. tries = 0
  1351. while xs.read('', xs_path) != '4':
  1352. tries += 1
  1353. if tries > 50:
  1354. raise QubesException ("Network attach timed out!")
  1355. time.sleep(0.2)
  1356. def wait_for_session(self, notify_function = None):
  1357. #self.run('echo $$ >> /tmp/qubes-session-waiter; [ ! -f /tmp/qubes-session-env ] && exec sleep 365d', ignore_stderr=True, gui=False, wait=True)
  1358. # Note : User root is redefined to SYSTEM in the Windows agent code
  1359. p = self.run('QUBESRPC qubes.WaitForSession none', user="root", passio_popen=True, gui=False, wait=True)
  1360. p.communicate(input=self.default_user)
  1361. def start_guid(self, verbose = True, notify_function = None,
  1362. extra_guid_args=[], before_qrexec=False):
  1363. if verbose:
  1364. print >> sys.stderr, "--> Starting Qubes GUId..."
  1365. xid = self.get_xid()
  1366. guid_cmd = [system_path["qubes_guid_path"],
  1367. "-d", str(xid),
  1368. "-c", self.label.color,
  1369. "-i", self.label.icon_path,
  1370. "-l", str(self.label.index)]
  1371. guid_cmd += extra_guid_args
  1372. if self.debug:
  1373. guid_cmd += ['-v', '-v']
  1374. retcode = subprocess.call (guid_cmd)
  1375. if (retcode != 0) :
  1376. raise QubesException("Cannot start qubes-guid!")
  1377. if verbose:
  1378. print >> sys.stderr, "--> Sending monitor layout..."
  1379. try:
  1380. subprocess.call([system_path["monitor_layout_notify_cmd"], self.name])
  1381. except Exception as e:
  1382. print >>sys.stderr, "ERROR: %s" % e
  1383. if verbose:
  1384. print >> sys.stderr, "--> Waiting for qubes-session..."
  1385. self.wait_for_session(notify_function)
  1386. def start_qrexec_daemon(self, verbose = False, notify_function = None):
  1387. if verbose:
  1388. print >> sys.stderr, "--> Starting the qrexec daemon..."
  1389. xid = self.get_xid()
  1390. qrexec_env = os.environ
  1391. qrexec_env['QREXEC_STARTUP_TIMEOUT'] = str(self.qrexec_timeout)
  1392. retcode = subprocess.call ([system_path["qrexec_daemon_path"], str(xid), self.name, self.default_user], env=qrexec_env)
  1393. if (retcode != 0) :
  1394. raise OSError ("Cannot execute qrexec-daemon!")
  1395. def start(self, debug_console = False, verbose = False, preparing_dvm = False, start_guid = True, notify_function = None):
  1396. if dry_run:
  1397. return
  1398. # Intentionally not used is_running(): eliminate also "Paused", "Crashed", "Halting"
  1399. if self.get_power_state() != "Halted":
  1400. raise QubesException ("VM is already running!")
  1401. self.verify_files()
  1402. if self.netvm is not None:
  1403. if self.netvm.qid != 0:
  1404. if not self.netvm.is_running():
  1405. if verbose:
  1406. print >> sys.stderr, "--> Starting NetVM {0}...".format(self.netvm.name)
  1407. self.netvm.start(verbose = verbose, start_guid = start_guid, notify_function = notify_function)
  1408. self.reset_volatile_storage(verbose=verbose)
  1409. if verbose:
  1410. print >> sys.stderr, "--> Loading the VM (type = {0})...".format(self.type)
  1411. # refresh config file
  1412. self.create_config_file()
  1413. mem_required = int(self.memory) * 1024 * 1024
  1414. qmemman_client = QMemmanClient()
  1415. try:
  1416. got_memory = qmemman_client.request_memory(mem_required)
  1417. except IOError as e:
  1418. raise IOError("ERROR: Failed to connect to qmemman: %s" % str(e))
  1419. if not got_memory:
  1420. qmemman_client.close()
  1421. raise MemoryError ("ERROR: insufficient memory to start VM '%s'" % self.name)
  1422. # Bind pci devices to pciback driver
  1423. for pci in self.pcidevs:
  1424. try:
  1425. subprocess.check_call(['sudo', system_path["qubes_pciback_cmd"], pci])
  1426. except subprocess.CalledProcessError:
  1427. raise QubesException("Failed to prepare PCI device %s" % pci)
  1428. xl_cmdline = ['sudo', '/usr/sbin/xl', 'create', self.conf_file, '-q', '-p']
  1429. try:
  1430. subprocess.check_call(xl_cmdline)
  1431. except:
  1432. try:
  1433. self._cleanup_zombie_domains()
  1434. except:
  1435. pass
  1436. raise QubesException("Failed to load VM config")
  1437. xid = self.get_xid()
  1438. self.xid = xid
  1439. if preparing_dvm:
  1440. self.services['qubes-dvm'] = True
  1441. if verbose:
  1442. print >> sys.stderr, "--> Setting Xen Store info for the VM..."
  1443. self.create_xenstore_entries(xid)
  1444. qvm_collection = QubesVmCollection()
  1445. qvm_collection.lock_db_for_reading()
  1446. qvm_collection.load()
  1447. qvm_collection.unlock_db()
  1448. if verbose:
  1449. print >> sys.stderr, "--> Updating firewall rules..."
  1450. for vm in qvm_collection.values():
  1451. if vm.is_proxyvm() and vm.is_running():
  1452. vm.write_iptables_xenstore_entry()
  1453. # fire hooks
  1454. for hook in self.hooks_start:
  1455. hook(self, verbose = verbose, preparing_dvm = preparing_dvm,
  1456. start_guid = start_guid, notify_function = notify_function)
  1457. if verbose:
  1458. print >> sys.stderr, "--> Starting the VM..."
  1459. xc.domain_unpause(xid)
  1460. # close() is not really needed, because the descriptor is close-on-exec
  1461. # anyway, the reason to postpone close() is that possibly xl is not done
  1462. # constructing the domain after its main process exits
  1463. # so we close() when we know the domain is up
  1464. # the successful unpause is some indicator of it
  1465. qmemman_client.close()
  1466. if self._start_guid_first and start_guid and not preparing_dvm and os.path.exists('/var/run/shm.id'):
  1467. self.start_guid(verbose=verbose, notify_function=notify_function, before_qrexec=True)
  1468. if not preparing_dvm:
  1469. self.start_qrexec_daemon(verbose=verbose,notify_function=notify_function)
  1470. if start_guid and not preparing_dvm and os.path.exists('/var/run/shm.id'):
  1471. self.start_guid(verbose=verbose, notify_function=notify_function)
  1472. if preparing_dvm:
  1473. if verbose:
  1474. print >> sys.stderr, "--> Preparing config template for DispVM"
  1475. self.create_config_file(file_path = self.dir_path + '/dvm.conf', prepare_dvm = True)
  1476. return xid
  1477. def _cleanup_zombie_domains(self):
  1478. """
  1479. This function is workaround broken libxl (which leaves not fully
  1480. created domain on failure) and vchan on domain crash behaviour
  1481. @return: None
  1482. """
  1483. xc = self.get_xc_dominfo()
  1484. if xc and xc['dying'] == 1:
  1485. # GUID still running?
  1486. guid_pidfile = '/var/run/qubes/guid-running.%d' % xc['domid']
  1487. if os.path.exists(guid_pidfile):
  1488. guid_pid = open(guid_pidfile).read().strip()
  1489. os.kill(int(guid_pid), 15)
  1490. # qrexec still running?
  1491. if self.is_qrexec_running():
  1492. #TODO: kill qrexec daemon
  1493. pass
  1494. def shutdown(self, force=False, xid = None):
  1495. if dry_run:
  1496. return
  1497. if not self.is_running():
  1498. raise QubesException ("VM already stopped!")
  1499. subprocess.call (['/usr/sbin/xl', 'shutdown', str(xid) if xid is not None else self.name])
  1500. #xc.domain_destroy(self.get_xid())
  1501. def force_shutdown(self, xid = None):
  1502. if dry_run:
  1503. return
  1504. if not self.is_running() and not self.is_paused():
  1505. raise QubesException ("VM already stopped!")
  1506. subprocess.call(['sudo', '/usr/sbin/xl', 'destroy',
  1507. str(xid) if xid is not None else self.name])
  1508. def suspend(self):
  1509. if dry_run:
  1510. return
  1511. if not self.is_running() and not self.is_paused():
  1512. raise QubesException ("VM already stopped!")
  1513. if len (self.pcidevs) > 0:
  1514. xs_path = '/local/domain/%d/control/shutdown' % self.get_xid()
  1515. xs.write('', xs_path, 'suspend')
  1516. tries = 0
  1517. while self.get_power_state() != "Suspended":
  1518. tries += 1
  1519. if tries > 15:
  1520. # fallback to pause
  1521. print >>sys.stderr, "Failed to suspend domain %s, falling back to pause method" % self.name
  1522. self.pause()
  1523. break
  1524. time.sleep(0.2)
  1525. else:
  1526. self.pause()
  1527. def resume(self):
  1528. if dry_run:
  1529. return
  1530. xc_info = self.get_xc_dominfo()
  1531. if not xc_info:
  1532. raise QubesException ("VM isn't started (cannot get xc_dominfo)!")
  1533. if xc_info['shutdown_reason'] == 2:
  1534. # suspended
  1535. xc.domain_resume(xc_info['domid'], 1)
  1536. xs.resume_domain(xc_info['domid'])
  1537. else:
  1538. self.unpause()
  1539. def pause(self):
  1540. if dry_run:
  1541. return
  1542. xc.domain_pause(self.get_xid())
  1543. def unpause(self):
  1544. if dry_run:
  1545. return
  1546. xc.domain_unpause(self.get_xid())
  1547. def get_xml_attrs(self):
  1548. attrs = {}
  1549. attrs_config = self.get_attrs_config()
  1550. for attr in attrs_config:
  1551. attr_config = attrs_config[attr]
  1552. if 'save' in attr_config:
  1553. if 'save_skip' in attr_config:
  1554. if callable(attr_config['save_skip']):
  1555. if attr_config['save_skip']():
  1556. continue
  1557. elif eval(attr_config['save_skip']):
  1558. continue
  1559. if callable(attr_config['save']):
  1560. value = attr_config['save']()
  1561. else:
  1562. value = eval(attr_config['save'])
  1563. if 'save_attr' in attr_config:
  1564. attrs[attr_config['save_attr']] = value
  1565. else:
  1566. attrs[attr] = value
  1567. return attrs
  1568. def create_xml_element(self):
  1569. # Compatibility hack (Qubes*VM in type vs Qubes*Vm in XML)...
  1570. rx_type = re.compile (r"VM")
  1571. attrs = self.get_xml_attrs()
  1572. element = lxml.etree.Element(
  1573. "Qubes" + rx_type.sub("Vm", self.type),
  1574. **attrs)
  1575. return element
  1576. register_qubes_vm_class(QubesVm)