000QubesVm.py 70 KB

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