000QubesVm.py 75 KB

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