000QubesVm.py 69 KB

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