000QubesVm.py 80 KB

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