000QubesVm.py 64 KB

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