000QubesVm.py 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610
  1. #!/usr/bin/python2
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
  6. # Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
  7. #
  8. # This program is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU General Public License
  10. # as published by the Free Software Foundation; either version 2
  11. # of the License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program; if not, write to the Free Software
  20. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21. #
  22. #
  23. import datetime
  24. import fcntl
  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. import xen.lowlevel.xc
  36. from qubes.qubes import xs,dry_run,xc,xl_ctx
  37. from qubes.qubes import register_qubes_vm_class
  38. from qubes.qubes import QubesVmCollection,QubesException,QubesHost,QubesVmLabels
  39. from qubes.qubes import defaults,system_path,vm_files,qubes_max_qid
  40. from qubes.qmemman_client import QMemmanClient
  41. class QubesVm(object):
  42. """
  43. A representation of one Qubes VM
  44. Only persistent information are stored here, while all the runtime
  45. information, e.g. Xen dom id, etc, are to be retrieved via Xen API
  46. Note that qid is not the same as Xen's domid!
  47. """
  48. # In which order load this VM type from qubes.xml
  49. load_order = 100
  50. # hooks for plugins (modules) which want to influence existing classes,
  51. # without introducing new ones
  52. hooks_clone_disk_files = []
  53. hooks_create_on_disk = []
  54. hooks_create_xenstore_entries = []
  55. hooks_get_attrs_config = []
  56. hooks_get_clone_attrs = []
  57. hooks_get_config_params = []
  58. hooks_init = []
  59. hooks_label_setter = []
  60. hooks_netvm_setter = []
  61. hooks_post_rename = []
  62. hooks_pre_rename = []
  63. hooks_remove_from_disk = []
  64. hooks_start = []
  65. hooks_verify_files = []
  66. def get_attrs_config(self):
  67. """ Object attributes for serialization/deserialization
  68. inner dict keys:
  69. - order: initialization order (to keep dependency intact)
  70. attrs without order will be evaluated at the end
  71. - default: default value used when attr not given to object constructor
  72. - attr: set value to this attribute instead of parameter name
  73. - eval: assign result of this expression instead of value directly;
  74. local variable 'value' contains attribute value (or default if it was not given)
  75. - save: use evaluation result as value for XML serialization; only attrs with 'save' key will be saved in XML
  76. - save_skip: if present and evaluates to true, attr will be omitted in XML
  77. - save_attr: save to this XML attribute instead of parameter name
  78. """
  79. attrs = {
  80. # __qid cannot be accessed by setattr, so must be set manually in __init__
  81. "qid": { "attr": "_qid", "order": 0 },
  82. "name": { "order": 1 },
  83. "dir_path": { "default": None, "order": 2 },
  84. "conf_file": { "eval": 'self.absolute_path(value, self.name + ".conf")', 'order': 3 },
  85. ### order >= 10: have base attrs set
  86. "root_img": { "eval": 'self.absolute_path(value, vm_files["root_img"])', 'order': 10 },
  87. "private_img": { "eval": 'self.absolute_path(value, vm_files["private_img"])', 'order': 10 },
  88. "volatile_img": { "eval": 'self.absolute_path(value, vm_files["volatile_img"])', 'order': 10 },
  89. "firewall_conf": { "eval": 'self.absolute_path(value, vm_files["firewall_conf"])', 'order': 10 },
  90. "installed_by_rpm": { "default": False, 'order': 10 },
  91. "template": { "default": None, 'order': 10 },
  92. ### order >= 20: have template set
  93. "uses_default_netvm": { "default": True, 'order': 20 },
  94. "netvm": { "default": None, "attr": "_netvm", 'order': 20 },
  95. "label": { "attr": "_label", "default": defaults["appvm_label"], 'order': 20,
  96. 'xml_deserialize': lambda _x: QubesVmLabels[_x] },
  97. "memory": { "default": defaults["memory"], 'order': 20 },
  98. "maxmem": { "default": None, 'order': 25 },
  99. "pcidevs": { "default": '[]', 'order': 25, "eval": \
  100. '[] if value in ["none", None] else eval(value) if value.find("[") >= 0 else eval("[" + value + "]")' },
  101. # Internal VM (not shown in qubes-manager, doesn't create appmenus entries
  102. "internal": { "default": False },
  103. "vcpus": { "default": None },
  104. "uses_default_kernel": { "default": True, 'order': 30 },
  105. "uses_default_kernelopts": { "default": True, 'order': 30 },
  106. "kernel": { "default": None, 'order': 31,
  107. 'eval': 'collection.get_default_kernel() if self.uses_default_kernel else value' },
  108. "kernelopts": { "default": "", 'order': 31, "eval": \
  109. 'value if not self.uses_default_kernelopts else defaults["kernelopts_pcidevs"] if len(self.pcidevs) > 0 else defaults["kernelopts"]' },
  110. "mac": { "attr": "_mac", "default": None },
  111. "include_in_backups": { "default": True },
  112. "services": { "default": {}, "eval": "eval(str(value))" },
  113. "debug": { "default": False },
  114. "default_user": { "default": "user" },
  115. "qrexec_timeout": { "default": 60 },
  116. ##### Internal attributes - will be overriden in __init__ regardless of args
  117. "config_file_template": { "eval": 'system_path["config_template_pv"]' },
  118. "icon_path": { "eval": 'os.path.join(self.dir_path, "icon.png") if self.dir_path is not None else None' },
  119. # used to suppress side effects of clone_attrs
  120. "_do_not_reset_firewall": { "eval": 'False' },
  121. "kernels_dir": { 'eval': 'os.path.join(system_path["qubes_kernels_base_dir"], self.kernel) if self.kernel is not None else ' + \
  122. # for backward compatibility (or another rare case): kernel=None -> kernel in VM dir
  123. 'os.path.join(self.dir_path, vm_files["kernels_subdir"])' },
  124. "_start_guid_first": { 'eval': 'False' },
  125. }
  126. ### Mark attrs for XML inclusion
  127. # Simple string attrs
  128. for prop in ['qid', 'name', 'dir_path', 'memory', 'maxmem', 'pcidevs', 'vcpus', 'internal',\
  129. 'uses_default_kernel', 'kernel', 'uses_default_kernelopts',\
  130. 'kernelopts', 'services', 'installed_by_rpm',\
  131. 'uses_default_netvm', 'include_in_backups', 'debug',\
  132. 'default_user', 'qrexec_timeout' ]:
  133. attrs[prop]['save'] = 'str(self.%s)' % prop
  134. # Simple paths
  135. for prop in ['conf_file', 'root_img', 'volatile_img', 'private_img']:
  136. attrs[prop]['save'] = 'self.relative_path(self.%s)' % prop
  137. attrs[prop]['save_skip'] = 'self.%s is None' % prop
  138. attrs['mac']['save'] = 'str(self._mac)'
  139. attrs['mac']['save_skip'] = 'self._mac is None'
  140. attrs['netvm']['save'] = 'str(self.netvm.qid) if self.netvm is not None else "none"'
  141. attrs['netvm']['save_attr'] = "netvm_qid"
  142. attrs['template']['save'] = 'str(self.template.qid) if self.template else "none"'
  143. attrs['template']['save_attr'] = "template_qid"
  144. attrs['label']['save'] = 'self.label.name'
  145. # fire hooks
  146. for hook in self.hooks_get_attrs_config:
  147. attrs = hook(self, attrs)
  148. return attrs
  149. def __basic_parse_xml_attr(self, value):
  150. if value is None:
  151. return None
  152. if value.lower() == "none":
  153. return None
  154. if value.lower() == "true":
  155. return True
  156. if value.lower() == "false":
  157. return False
  158. if value.isdigit():
  159. return int(value)
  160. return value
  161. def __init__(self, **kwargs):
  162. collection = None
  163. if 'collection' in kwargs:
  164. collection = kwargs['collection']
  165. else:
  166. raise ValueError("No collection given to QubesVM constructor")
  167. # Special case for template b/c it is given in "template_qid" property
  168. if "xml_element" in kwargs and kwargs["xml_element"].get("template_qid"):
  169. template_qid = kwargs["xml_element"].get("template_qid")
  170. if template_qid.lower() != "none":
  171. if int(template_qid) in collection:
  172. kwargs["template"] = collection[int(template_qid)]
  173. else:
  174. raise ValueError("Unknown template with QID %s" % template_qid)
  175. attrs = self.get_attrs_config()
  176. for attr_name in sorted(attrs, key=lambda _x: attrs[_x]['order'] if 'order' in attrs[_x] else 1000):
  177. attr_config = attrs[attr_name]
  178. attr = attr_name
  179. if 'attr' in attr_config:
  180. attr = attr_config['attr']
  181. value = None
  182. if attr_name in kwargs:
  183. value = kwargs[attr_name]
  184. elif 'xml_element' in kwargs and kwargs['xml_element'].get(attr_name) is not None:
  185. if 'xml_deserialize' in attr_config and callable(attr_config['xml_deserialize']):
  186. value = attr_config['xml_deserialize'](kwargs['xml_element'].get(attr_name))
  187. else:
  188. value = self.__basic_parse_xml_attr(kwargs['xml_element'].get(attr_name))
  189. else:
  190. if 'default' in attr_config:
  191. value = attr_config['default']
  192. if 'eval' in attr_config:
  193. setattr(self, attr, eval(attr_config['eval']))
  194. else:
  195. #print "setting %s to %s" % (attr, value)
  196. setattr(self, attr, value)
  197. #Init private attrs
  198. self.__qid = self._qid
  199. assert self.__qid < qubes_max_qid, "VM id out of bounds!"
  200. assert self.name is not None
  201. if not self.verify_name(self.name):
  202. raise QubesException("Invalid characters in VM name")
  203. if self.netvm is not None:
  204. self.netvm.connected_vms[self.qid] = self
  205. # Not in generic way to not create QubesHost() to frequently
  206. if self.maxmem is None:
  207. qubes_host = QubesHost()
  208. total_mem_mb = qubes_host.memory_total/1024
  209. self.maxmem = total_mem_mb/2
  210. # By default allow use all VCPUs
  211. if self.vcpus is None:
  212. qubes_host = QubesHost()
  213. self.vcpus = qubes_host.no_cpus
  214. # Always set if meminfo-writer should be active or not
  215. if 'meminfo-writer' not in self.services:
  216. self.services['meminfo-writer'] = not (len(self.pcidevs) > 0)
  217. # Additionally force meminfo-writer disabled when VM have PCI devices
  218. if len(self.pcidevs) > 0:
  219. self.services['meminfo-writer'] = False
  220. # Some additional checks for template based VM
  221. if self.template is not None:
  222. if not self.template.is_template():
  223. print >> sys.stderr, "ERROR: template_qid={0} doesn't point to a valid TemplateVM".\
  224. format(self.template.qid)
  225. return False
  226. self.template.appvms[self.qid] = self
  227. else:
  228. assert self.root_img is not None, "Missing root_img for standalone VM!"
  229. self.xid = -1
  230. self.xid = self.get_xid()
  231. # fire hooks
  232. for hook in self.hooks_init:
  233. hook(self)
  234. def absolute_path(self, arg, default):
  235. if arg is not None and os.path.isabs(arg):
  236. return arg
  237. else:
  238. return os.path.join(self.dir_path, (arg if arg is not None else default))
  239. def relative_path(self, arg):
  240. return arg.replace(self.dir_path + '/', '')
  241. @property
  242. def qid(self):
  243. return self.__qid
  244. @property
  245. def label(self):
  246. return self._label
  247. @label.setter
  248. def label(self, new_label):
  249. self._label = new_label
  250. if self.icon_path:
  251. try:
  252. os.remove(self.icon_path)
  253. except:
  254. pass
  255. os.symlink (new_label.icon_path, self.icon_path)
  256. subprocess.call(['sudo', 'xdg-icon-resource', 'forceupdate'])
  257. # fire hooks
  258. for hook in self.hooks_label_setter:
  259. hook(self, new_label)
  260. @property
  261. def netvm(self):
  262. return self._netvm
  263. # Don't know how properly call setter from base class, so workaround it...
  264. @netvm.setter
  265. def netvm(self, new_netvm):
  266. self._set_netvm(new_netvm)
  267. # fire hooks
  268. for hook in self.hooks_netvm_setter:
  269. hook(self, new_netvm)
  270. def _set_netvm(self, new_netvm):
  271. if self.is_running() and new_netvm is not None and not new_netvm.is_running():
  272. raise QubesException("Cannot dynamically attach to stopped NetVM")
  273. if self.netvm is not None:
  274. self.netvm.connected_vms.pop(self.qid)
  275. if self.is_running():
  276. subprocess.call(["xl", "network-detach", self.name, "0"], stderr=subprocess.PIPE)
  277. if hasattr(self.netvm, 'post_vm_net_detach'):
  278. self.netvm.post_vm_net_detach(self)
  279. if new_netvm is None:
  280. if not self._do_not_reset_firewall:
  281. # Set also firewall to block all traffic as discussed in #370
  282. if os.path.exists(self.firewall_conf):
  283. shutil.copy(self.firewall_conf, os.path.join(system_path["qubes_base_dir"],
  284. "backup", "%s-firewall-%s.xml" % (self.name,
  285. time.strftime('%Y-%m-%d-%H:%M:%S'))))
  286. self.write_firewall_conf({'allow': False, 'allowDns': False,
  287. 'allowIcmp': False, 'allowYumProxy': False, 'rules': []})
  288. else:
  289. new_netvm.connected_vms[self.qid]=self
  290. self._netvm = new_netvm
  291. if new_netvm is None:
  292. return
  293. if self.is_running():
  294. # refresh IP, DNS etc
  295. self.create_xenstore_entries()
  296. self.attach_network()
  297. if hasattr(self.netvm, 'post_vm_net_attach'):
  298. self.netvm.post_vm_net_attach(self)
  299. @property
  300. def ip(self):
  301. if self.netvm is not None:
  302. return self.netvm.get_ip_for_vm(self.qid)
  303. else:
  304. return None
  305. @property
  306. def netmask(self):
  307. if self.netvm is not None:
  308. return self.netvm.netmask
  309. else:
  310. return None
  311. @property
  312. def gateway(self):
  313. # This is gateway IP for _other_ VMs, so make sense only in NetVMs
  314. return None
  315. @property
  316. def secondary_dns(self):
  317. if self.netvm is not None:
  318. return self.netvm.secondary_dns
  319. else:
  320. return None
  321. @property
  322. def vif(self):
  323. if self.xid < 0:
  324. return None
  325. if self.netvm is None:
  326. return None
  327. return "vif{0}.+".format(self.xid)
  328. @property
  329. def mac(self):
  330. if self._mac is not None:
  331. return self._mac
  332. else:
  333. return "00:16:3E:5E:6C:{qid:02X}".format(qid=self.qid)
  334. @mac.setter
  335. def mac(self, new_mac):
  336. self._mac = new_mac
  337. @property
  338. def updateable(self):
  339. return self.template is None
  340. # Leaved for compatibility
  341. def is_updateable(self):
  342. return self.updateable
  343. def is_networked(self):
  344. if self.is_netvm():
  345. return True
  346. if self.netvm is not None:
  347. return True
  348. else:
  349. return False
  350. def verify_name(self, name):
  351. return re.match(r"^[a-zA-Z][a-zA-Z0-9_-]*$", name) is not None
  352. def pre_rename(self, new_name):
  353. # fire hooks
  354. for hook in self.hooks_pre_rename:
  355. hook(self, new_name)
  356. def set_name(self, name):
  357. if self.is_running():
  358. raise QubesException("Cannot change name of running VM!")
  359. if not self.verify_name(name):
  360. raise QubesException("Invalid characters in VM name")
  361. self.pre_rename(name)
  362. new_conf = os.path.join(self.dir_path, name + '.conf')
  363. if os.path.exists(self.conf_file):
  364. os.rename(self.conf_file, new_conf)
  365. old_dirpath = self.dir_path
  366. new_dirpath = os.path.join(os.path.dirname(self.dir_path), name)
  367. os.rename(old_dirpath, new_dirpath)
  368. self.dir_path = new_dirpath
  369. old_name = self.name
  370. self.name = name
  371. if self.private_img is not None:
  372. self.private_img = self.private_img.replace(old_dirpath, new_dirpath)
  373. if self.root_img is not None:
  374. self.root_img = self.root_img.replace(old_dirpath, new_dirpath)
  375. if self.volatile_img is not None:
  376. self.volatile_img = self.volatile_img.replace(old_dirpath, new_dirpath)
  377. if self.conf_file is not None:
  378. self.conf_file = new_conf.replace(old_dirpath, new_dirpath)
  379. if self.icon_path is not None:
  380. self.icon_path = self.icon_path.replace(old_dirpath, new_dirpath)
  381. if hasattr(self, 'kernels_dir') and self.kernels_dir is not None:
  382. self.kernels_dir = self.kernels_dir.replace(old_dirpath, new_dirpath)
  383. self.post_rename(old_name)
  384. def post_rename(self, old_name):
  385. # fire hooks
  386. for hook in self.hooks_post_rename:
  387. hook(self, old_name)
  388. def is_template(self):
  389. return False
  390. def is_appvm(self):
  391. return False
  392. def is_netvm(self):
  393. return False
  394. def is_proxyvm(self):
  395. return False
  396. def is_disposablevm(self):
  397. return False
  398. def get_xl_dominfo(self):
  399. if dry_run:
  400. return
  401. domains = xl_ctx.list_domains()
  402. for dominfo in domains:
  403. domname = xl_ctx.domid_to_name(dominfo.domid)
  404. if domname == self.name:
  405. return dominfo
  406. return None
  407. def get_xc_dominfo(self):
  408. if dry_run:
  409. return
  410. start_xid = self.xid
  411. if start_xid < 0:
  412. start_xid = 0
  413. try:
  414. domains = xc.domain_getinfo(start_xid, qubes_max_qid)
  415. except xen.lowlevel.xc.Error:
  416. return None
  417. for dominfo in domains:
  418. domname = xl_ctx.domid_to_name(dominfo['domid'])
  419. if domname == self.name:
  420. return dominfo
  421. return None
  422. def get_xid(self):
  423. if dry_run:
  424. return 666
  425. dominfo = self.get_xc_dominfo()
  426. if dominfo:
  427. self.xid = dominfo['domid']
  428. return self.xid
  429. else:
  430. return -1
  431. def get_uuid(self):
  432. dominfo = self.get_xl_dominfo()
  433. if dominfo:
  434. vmuuid = uuid.UUID(''.join('%02x' % b for b in dominfo.uuid))
  435. return vmuuid
  436. else:
  437. return None
  438. def get_mem(self):
  439. if dry_run:
  440. return 666
  441. dominfo = self.get_xc_dominfo()
  442. if dominfo:
  443. return dominfo['mem_kb']
  444. else:
  445. return 0
  446. def get_mem_static_max(self):
  447. if dry_run:
  448. return 666
  449. dominfo = self.get_xc_dominfo()
  450. if dominfo:
  451. return dominfo['maxmem_kb']
  452. else:
  453. return 0
  454. def get_per_cpu_time(self):
  455. if dry_run:
  456. import random
  457. return random.random() * 100
  458. dominfo = self.get_xc_dominfo()
  459. if dominfo:
  460. return dominfo['cpu_time']/dominfo['online_vcpus']
  461. else:
  462. return 0
  463. def get_disk_utilization_root_img(self):
  464. if not os.path.exists(self.root_img):
  465. return 0
  466. return self.get_disk_usage(self.root_img)
  467. def get_root_img_sz(self):
  468. if not os.path.exists(self.root_img):
  469. return 0
  470. return os.path.getsize(self.root_img)
  471. def get_power_state(self):
  472. if dry_run:
  473. return "NA"
  474. dominfo = self.get_xc_dominfo()
  475. if dominfo:
  476. if dominfo['paused']:
  477. return "Paused"
  478. elif dominfo['crashed']:
  479. return "Crashed"
  480. elif dominfo['shutdown']:
  481. if dominfo['shutdown_reason'] == 2:
  482. return "Suspended"
  483. else:
  484. return "Halting"
  485. elif dominfo['dying']:
  486. return "Dying"
  487. else:
  488. if not self.is_fully_usable():
  489. return "Transient"
  490. else:
  491. return "Running"
  492. else:
  493. return 'Halted'
  494. return "NA"
  495. def is_guid_running(self):
  496. xid = self.get_xid()
  497. if xid < 0:
  498. return False
  499. if not os.path.exists('/var/run/qubes/guid-running.%d' % xid):
  500. return False
  501. return True
  502. def is_fully_usable(self):
  503. # Running gui-daemon implies also VM running
  504. if not self.is_guid_running():
  505. return False
  506. # currently qrexec daemon doesn't cleanup socket in /var/run/qubes, so
  507. # it can be left from some other VM
  508. return True
  509. def is_running(self):
  510. # in terms of Xen and internal logic - starting VM is running
  511. if self.get_power_state() in ["Running", "Transient", "Halting"]:
  512. return True
  513. else:
  514. return False
  515. def is_paused(self):
  516. if self.get_power_state() == "Paused":
  517. return True
  518. else:
  519. return False
  520. def get_start_time(self):
  521. if not self.is_running():
  522. return None
  523. dominfo = self.get_xl_dominfo()
  524. uuid = self.get_uuid()
  525. start_time = xs.read('', "/vm/%s/start_time" % str(uuid))
  526. if start_time != '':
  527. return datetime.datetime.fromtimestamp(float(start_time))
  528. else:
  529. return None
  530. def is_outdated(self):
  531. # Makes sense only on VM based on template
  532. if self.template is None:
  533. return False
  534. if not self.is_running():
  535. return False
  536. rootimg_inode = os.stat(self.template.root_img)
  537. try:
  538. rootcow_inode = os.stat(self.template.rootcow_img)
  539. except OSError:
  540. # The only case when rootcow_img doesn't exists is in the middle of
  541. # commit_changes, so VM is outdated right now
  542. return True
  543. current_dmdev = "/dev/mapper/snapshot-{0:x}:{1}-{2:x}:{3}".format(
  544. rootimg_inode[2], rootimg_inode[1],
  545. rootcow_inode[2], rootcow_inode[1])
  546. # 51712 (0xCA00) is xvda
  547. # backend node name not available through xenapi :(
  548. used_dmdev = xs.read('', "/local/domain/0/backend/vbd/{0}/51712/node".format(self.get_xid()))
  549. return used_dmdev != current_dmdev
  550. def get_disk_usage(self, file_or_dir):
  551. if not os.path.exists(file_or_dir):
  552. return 0
  553. p = subprocess.Popen (["du", "-s", "--block-size=1", file_or_dir],
  554. stdout=subprocess.PIPE)
  555. result = p.communicate()
  556. m = re.match(r"^(\d+)\s.*", result[0])
  557. sz = int(m.group(1)) if m is not None else 0
  558. return sz
  559. def get_disk_utilization(self):
  560. return self.get_disk_usage(self.dir_path)
  561. def get_disk_utilization_private_img(self):
  562. return self.get_disk_usage(self.private_img)
  563. def get_private_img_sz(self):
  564. if not os.path.exists(self.private_img):
  565. return 0
  566. return os.path.getsize(self.private_img)
  567. def resize_private_img(self, size):
  568. assert size >= self.get_private_img_sz(), "Cannot shrink private.img"
  569. f_private = open (self.private_img, "a+b")
  570. f_private.truncate (size)
  571. f_private.close ()
  572. retcode = 0
  573. if self.is_running():
  574. # find loop device
  575. p = subprocess.Popen (["sudo", "losetup", "--associated", self.private_img],
  576. stdout=subprocess.PIPE)
  577. result = p.communicate()
  578. m = re.match(r"^(/dev/loop\d+):\s", result[0])
  579. if m is None:
  580. raise QubesException("ERROR: Cannot find loop device!")
  581. loop_dev = m.group(1)
  582. # resize loop device
  583. subprocess.check_call(["sudo", "losetup", "--set-capacity", loop_dev])
  584. retcode = self.run("while [ \"`blockdev --getsize64 /dev/xvdb`\" -lt {0} ]; do ".format(size) +
  585. "head /dev/xvdb > /dev/null; sleep 0.2; done; resize2fs /dev/xvdb", user="root", wait=True)
  586. else:
  587. retcode = subprocess.check_call(["sudo", "resize2fs", "-f", self.private_img])
  588. if retcode != 0:
  589. raise QubesException("resize2fs failed")
  590. # FIXME: should be outside of QubesVM?
  591. def get_timezone(self):
  592. # fc18
  593. if os.path.islink('/etc/localtime'):
  594. return '/'.join(os.readlink('/etc/localtime').split('/')[-2:])
  595. # <=fc17
  596. elif os.path.exists('/etc/sysconfig/clock'):
  597. clock_config = open('/etc/sysconfig/clock', "r")
  598. clock_config_lines = clock_config.readlines()
  599. clock_config.close()
  600. zone_re = re.compile(r'^ZONE="(.*)"')
  601. for line in clock_config_lines:
  602. line_match = zone_re.match(line)
  603. if line_match:
  604. return line_match.group(1)
  605. return None
  606. def cleanup_vifs(self):
  607. """
  608. Xend does not remove vif when backend domain is down, so we must do it
  609. manually
  610. """
  611. if not self.is_running():
  612. return
  613. dev_basepath = '/local/domain/%d/device/vif' % self.xid
  614. for dev in xs.ls('', dev_basepath):
  615. # check if backend domain is alive
  616. backend_xid = int(xs.read('', '%s/%s/backend-id' % (dev_basepath, dev)))
  617. if xl_ctx.domid_to_name(backend_xid) is not None:
  618. # check if device is still active
  619. if xs.read('', '%s/%s/state' % (dev_basepath, dev)) == '4':
  620. continue
  621. # remove dead device
  622. xs.rm('', '%s/%s' % (dev_basepath, dev))
  623. def create_xenstore_entries(self, xid = None):
  624. if dry_run:
  625. return
  626. if xid is None:
  627. xid = self.xid
  628. domain_path = xs.get_domain_path(xid)
  629. # Set Xen Store entires with VM networking info:
  630. xs.write('', "{0}/qubes-vm-type".format(domain_path),
  631. self.type)
  632. xs.write('', "{0}/qubes-vm-updateable".format(domain_path),
  633. str(self.updateable))
  634. if self.is_netvm():
  635. xs.write('',
  636. "{0}/qubes-netvm-gateway".format(domain_path),
  637. self.gateway)
  638. xs.write('',
  639. "{0}/qubes-netvm-secondary-dns".format(domain_path),
  640. self.secondary_dns)
  641. xs.write('',
  642. "{0}/qubes-netvm-netmask".format(domain_path),
  643. self.netmask)
  644. xs.write('',
  645. "{0}/qubes-netvm-network".format(domain_path),
  646. self.network)
  647. if self.netvm is not None:
  648. xs.write('', "{0}/qubes-ip".format(domain_path), self.ip)
  649. xs.write('', "{0}/qubes-netmask".format(domain_path),
  650. self.netvm.netmask)
  651. xs.write('', "{0}/qubes-gateway".format(domain_path),
  652. self.netvm.gateway)
  653. xs.write('',
  654. "{0}/qubes-secondary-dns".format(domain_path),
  655. self.netvm.secondary_dns)
  656. tzname = self.get_timezone()
  657. if tzname:
  658. xs.write('',
  659. "{0}/qubes-timezone".format(domain_path),
  660. tzname)
  661. for srv in self.services.keys():
  662. # convert True/False to "1"/"0"
  663. xs.write('', "{0}/qubes-service/{1}".format(domain_path, srv),
  664. str(int(self.services[srv])))
  665. xs.write('',
  666. "{0}/qubes-block-devices".format(domain_path),
  667. '')
  668. xs.write('',
  669. "{0}/qubes-usb-devices".format(domain_path),
  670. '')
  671. xs.write('', "{0}/qubes-debug-mode".format(domain_path),
  672. str(int(self.debug)))
  673. # Fix permissions
  674. xs.set_permissions('', '{0}/device'.format(domain_path),
  675. [{ 'dom': xid }])
  676. xs.set_permissions('', '{0}/memory'.format(domain_path),
  677. [{ 'dom': xid }])
  678. xs.set_permissions('', '{0}/qubes-block-devices'.format(domain_path),
  679. [{ 'dom': xid }])
  680. xs.set_permissions('', '{0}/qubes-usb-devices'.format(domain_path),
  681. [{ 'dom': xid }])
  682. # fire hooks
  683. for hook in self.hooks_create_xenstore_entries:
  684. hook(self, xid=xid)
  685. def get_rootdev(self, source_template=None):
  686. if self.template:
  687. return "'script:snapshot:{dir}/root.img:{dir}/root-cow.img,xvda,r',".format(dir=self.template.dir_path)
  688. else:
  689. return "'script:file:{dir}/root.img,xvda,w',".format(dir=self.dir_path)
  690. def get_config_params(self, source_template=None):
  691. args = {}
  692. args['name'] = self.name
  693. if hasattr(self, 'kernels_dir'):
  694. args['kerneldir'] = self.kernels_dir
  695. args['vmdir'] = self.dir_path
  696. args['pcidev'] = str(self.pcidevs).strip('[]')
  697. args['mem'] = str(self.memory)
  698. if self.maxmem < self.memory:
  699. args['mem'] = str(self.maxmem)
  700. args['maxmem'] = str(self.maxmem)
  701. if 'meminfo-writer' in self.services and not self.services['meminfo-writer']:
  702. # If dynamic memory management disabled, set maxmem=mem
  703. args['maxmem'] = args['mem']
  704. args['vcpus'] = str(self.vcpus)
  705. if self.netvm is not None:
  706. args['ip'] = self.ip
  707. args['mac'] = self.mac
  708. args['gateway'] = self.netvm.gateway
  709. args['dns1'] = self.netvm.gateway
  710. args['dns2'] = self.secondary_dns
  711. args['netmask'] = self.netmask
  712. args['netdev'] = "'mac={mac},script=/etc/xen/scripts/vif-route-qubes,ip={ip}".format(ip=self.ip, mac=self.mac)
  713. if self.netvm.qid != 0:
  714. args['netdev'] += ",backend={0}".format(self.netvm.name)
  715. args['netdev'] += "'"
  716. args['disable_network'] = '';
  717. else:
  718. args['ip'] = ''
  719. args['mac'] = ''
  720. args['gateway'] = ''
  721. args['dns1'] = ''
  722. args['dns2'] = ''
  723. args['netmask'] = ''
  724. args['netdev'] = ''
  725. args['disable_network'] = '#';
  726. args['rootdev'] = self.get_rootdev(source_template=source_template)
  727. args['privatedev'] = "'script:file:{dir}/private.img,xvdb,w',".format(dir=self.dir_path)
  728. args['volatiledev'] = "'script:file:{dir}/volatile.img,xvdc,w',".format(dir=self.dir_path)
  729. if hasattr(self, 'kernel'):
  730. modulesmode='r'
  731. if self.kernel is None:
  732. modulesmode='w'
  733. args['otherdevs'] = "'script:file:{dir}/modules.img,xvdd,{mode}',".format(dir=self.kernels_dir, mode=modulesmode)
  734. if hasattr(self, 'kernelopts'):
  735. args['kernelopts'] = self.kernelopts
  736. if self.debug:
  737. print >> sys.stderr, "--> Debug mode: adding 'earlyprintk=xen' to kernel opts"
  738. args['kernelopts'] += ' earlyprintk=xen'
  739. # fire hooks
  740. for hook in self.hooks_get_config_params:
  741. args = hook(self, args)
  742. return args
  743. @property
  744. def uses_custom_config(self):
  745. return self.conf_file != self.absolute_path(self.name + ".conf", None)
  746. def create_config_file(self, file_path = None, source_template = None, prepare_dvm = False):
  747. if file_path is None:
  748. file_path = self.conf_file
  749. if self.uses_custom_config:
  750. return
  751. if source_template is None:
  752. source_template = self.template
  753. f_conf_template = open(self.config_file_template, 'r')
  754. conf_template = f_conf_template.read()
  755. f_conf_template.close()
  756. template_params = self.get_config_params(source_template)
  757. if prepare_dvm:
  758. template_params['name'] = '%NAME%'
  759. template_params['privatedev'] = ''
  760. template_params['netdev'] = re.sub(r"ip=[0-9.]*", "ip=%IP%", template_params['netdev'])
  761. conf_appvm = open(file_path, "w")
  762. conf_appvm.write(conf_template.format(**template_params))
  763. conf_appvm.close()
  764. def create_on_disk(self, verbose, source_template = None):
  765. if source_template is None:
  766. source_template = self.template
  767. assert source_template is not None
  768. if dry_run:
  769. return
  770. if verbose:
  771. print >> sys.stderr, "--> Creating directory: {0}".format(self.dir_path)
  772. os.mkdir (self.dir_path)
  773. if verbose:
  774. print >> sys.stderr, "--> Creating the VM config file: {0}".format(self.conf_file)
  775. self.create_config_file(source_template = source_template)
  776. template_priv = source_template.private_img
  777. if verbose:
  778. print >> sys.stderr, "--> Copying the template's private image: {0}".\
  779. format(template_priv)
  780. # We prefer to use Linux's cp, because it nicely handles sparse files
  781. retcode = subprocess.call (["cp", template_priv, self.private_img])
  782. if retcode != 0:
  783. raise IOError ("Error while copying {0} to {1}".\
  784. format(template_priv, self.private_img))
  785. if self.updateable:
  786. template_root = source_template.root_img
  787. if verbose:
  788. print >> sys.stderr, "--> Copying the template's root image: {0}".\
  789. format(template_root)
  790. # We prefer to use Linux's cp, because it nicely handles sparse files
  791. retcode = subprocess.call (["cp", template_root, self.root_img])
  792. if retcode != 0:
  793. raise IOError ("Error while copying {0} to {1}".\
  794. format(template_root, self.root_img))
  795. kernels_dir = source_template.kernels_dir
  796. if verbose:
  797. print >> sys.stderr, "--> Copying the kernel (set kernel \"none\" to use it): {0}".\
  798. format(kernels_dir)
  799. os.mkdir (self.dir_path + '/kernels')
  800. for f in ("vmlinuz", "initramfs", "modules.img"):
  801. shutil.copy(os.path.join(kernels_dir, f),
  802. os.path.join(self.dir_path, vm_files["kernels_subdir"], f))
  803. # Create volatile.img
  804. self.reset_volatile_storage(source_template = source_template, verbose=verbose)
  805. if verbose:
  806. print >> sys.stderr, "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path)
  807. os.symlink (self.label.icon_path, self.icon_path)
  808. # fire hooks
  809. for hook in self.hooks_create_on_disk:
  810. hook(self, verbose, source_template=source_template)
  811. def get_clone_attrs(self):
  812. attrs = ['kernel', 'uses_default_kernel', 'netvm', 'uses_default_netvm', \
  813. 'memory', 'maxmem', 'kernelopts', 'uses_default_kernelopts', 'services', 'vcpus', \
  814. '_mac', 'pcidevs', 'include_in_backups', '_label']
  815. # fire hooks
  816. for hook in self.hooks_get_clone_attrs:
  817. attrs = hook(self, attrs)
  818. return attrs
  819. def clone_attrs(self, src_vm):
  820. self._do_not_reset_firewall = True
  821. for prop in self.get_clone_attrs():
  822. setattr(self, prop, getattr(src_vm, prop))
  823. self._do_not_reset_firewall = False
  824. def clone_disk_files(self, src_vm, verbose):
  825. if dry_run:
  826. return
  827. if src_vm.is_running():
  828. raise QubesException("Attempt to clone a running VM!")
  829. if verbose:
  830. print >> sys.stderr, "--> Creating directory: {0}".format(self.dir_path)
  831. os.mkdir (self.dir_path)
  832. if src_vm.private_img is not None and self.private_img is not None:
  833. if verbose:
  834. print >> sys.stderr, "--> Copying the private image:\n{0} ==>\n{1}".\
  835. format(src_vm.private_img, self.private_img)
  836. # We prefer to use Linux's cp, because it nicely handles sparse files
  837. retcode = subprocess.call (["cp", src_vm.private_img, self.private_img])
  838. if retcode != 0:
  839. raise IOError ("Error while copying {0} to {1}".\
  840. format(src_vm.private_img, self.private_img))
  841. if src_vm.updateable and src_vm.root_img is not None and self.root_img is not None:
  842. if verbose:
  843. print >> sys.stderr, "--> Copying the root image:\n{0} ==>\n{1}".\
  844. format(src_vm.root_img, self.root_img)
  845. # We prefer to use Linux's cp, because it nicely handles sparse files
  846. retcode = subprocess.call (["cp", src_vm.root_img, self.root_img])
  847. if retcode != 0:
  848. raise IOError ("Error while copying {0} to {1}".\
  849. format(src_vm.root_img, self.root_img))
  850. if src_vm.icon_path is not None and self.icon_path is not None:
  851. if os.path.exists (src_vm.dir_path):
  852. if os.path.islink(src_vm.icon_path):
  853. icon_path = os.readlink(src_vm.icon_path)
  854. if verbose:
  855. print >> sys.stderr, "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, icon_path)
  856. os.symlink (icon_path, self.icon_path)
  857. else:
  858. if verbose:
  859. print >> sys.stderr, "--> Copying icon: {0} -> {1}".format(src_vm.icon_path, self.icon_path)
  860. shutil.copy(src_vm.icon_path, self.icon_path)
  861. # fire hooks
  862. for hook in self.hooks_clone_disk_files:
  863. hook(self, src_vm, verbose)
  864. def verify_files(self):
  865. if dry_run:
  866. return
  867. if not os.path.exists (self.dir_path):
  868. raise QubesException (
  869. "VM directory doesn't exist: {0}".\
  870. format(self.dir_path))
  871. if self.updateable and not os.path.exists (self.root_img):
  872. raise QubesException (
  873. "VM root image file doesn't exist: {0}".\
  874. format(self.root_img))
  875. if not os.path.exists (self.private_img):
  876. raise QubesException (
  877. "VM private image file doesn't exist: {0}".\
  878. format(self.private_img))
  879. if not os.path.exists (os.path.join(self.kernels_dir, 'vmlinuz')):
  880. raise QubesException (
  881. "VM kernel does not exists: {0}".\
  882. format(os.path.join(self.kernels_dir, 'vmlinuz')))
  883. if not os.path.exists (os.path.join(self.kernels_dir, 'initramfs')):
  884. raise QubesException (
  885. "VM initramfs does not exists: {0}".\
  886. format(os.path.join(self.kernels_dir, 'initramfs')))
  887. if not os.path.exists (os.path.join(self.kernels_dir, 'modules.img')):
  888. raise QubesException (
  889. "VM kernel modules image does not exists: {0}".\
  890. format(os.path.join(self.kernels_dir, 'modules.img')))
  891. # fire hooks
  892. for hook in self.hooks_verify_files:
  893. hook(self)
  894. return True
  895. def reset_volatile_storage(self, source_template = None, verbose = False):
  896. assert not self.is_running(), "Attempt to clean volatile image of running VM!"
  897. if source_template is None:
  898. source_template = self.template
  899. # Only makes sense on template based VM
  900. if source_template is None:
  901. # For StandaloneVM create it only if not already exists (eg after backup-restore)
  902. if not os.path.exists(self.volatile_img):
  903. if verbose:
  904. print >> sys.stderr, "--> Creating volatile image: {0}...".format (self.volatile_img)
  905. f_root = open (self.root_img, "r")
  906. f_root.seek(0, os.SEEK_END)
  907. root_size = f_root.tell()
  908. f_root.close()
  909. subprocess.check_call([system_path["prepare_volatile_img_cmd"], self.volatile_img, str(root_size / 1024 / 1024)])
  910. return
  911. if verbose:
  912. print >> sys.stderr, "--> Cleaning volatile image: {0}...".format (self.volatile_img)
  913. if dry_run:
  914. return
  915. if os.path.exists (self.volatile_img):
  916. os.remove (self.volatile_img)
  917. retcode = subprocess.call (["tar", "xf", source_template.clean_volatile_img, "-C", self.dir_path])
  918. if retcode != 0:
  919. raise IOError ("Error while unpacking {0} to {1}".\
  920. format(source_template.clean_volatile_img, self.volatile_img))
  921. def remove_from_disk(self):
  922. if dry_run:
  923. return
  924. # fire hooks
  925. for hook in self.hooks_remove_from_disk:
  926. hook(self)
  927. shutil.rmtree (self.dir_path)
  928. def write_firewall_conf(self, conf):
  929. defaults = self.get_firewall_conf()
  930. for item in defaults.keys():
  931. if item not in conf:
  932. conf[item] = defaults[item]
  933. root = lxml.etree.Element(
  934. "QubesFirwallRules",
  935. policy = "allow" if conf["allow"] else "deny",
  936. dns = "allow" if conf["allowDns"] else "deny",
  937. icmp = "allow" if conf["allowIcmp"] else "deny",
  938. yumProxy = "allow" if conf["allowYumProxy"] else "deny"
  939. )
  940. for rule in conf["rules"]:
  941. # For backward compatibility
  942. if "proto" not in rule:
  943. if rule["portBegin"] is not None and rule["portBegin"] > 0:
  944. rule["proto"] = "tcp"
  945. else:
  946. rule["proto"] = "any"
  947. element = lxml.etree.Element(
  948. "rule",
  949. address=rule["address"],
  950. proto=str(rule["proto"]),
  951. )
  952. if rule["netmask"] is not None and rule["netmask"] != 32:
  953. element.set("netmask", str(rule["netmask"]))
  954. if rule["portBegin"] is not None and rule["portBegin"] > 0:
  955. element.set("port", str(rule["portBegin"]))
  956. if rule["portEnd"] is not None and rule["portEnd"] > 0:
  957. element.set("toport", str(rule["portEnd"]))
  958. root.append(element)
  959. tree = lxml.etree.ElementTree(root)
  960. try:
  961. f = open(self.firewall_conf, 'a') # create the file if not exist
  962. f.close()
  963. with open(self.firewall_conf, 'w') as f:
  964. fcntl.lockf(f, fcntl.LOCK_EX)
  965. tree.write(f, encoding="UTF-8", pretty_print=True)
  966. fcntl.lockf(f, fcntl.LOCK_UN)
  967. f.close()
  968. except EnvironmentError as err:
  969. print >> sys.stderr, "{0}: save error: {1}".format(
  970. os.path.basename(sys.argv[0]), err)
  971. return False
  972. # Automatically enable/disable 'yum-proxy-setup' service based on allowYumProxy
  973. if conf['allowYumProxy']:
  974. self.services['yum-proxy-setup'] = True
  975. else:
  976. if self.services.has_key('yum-proxy-setup'):
  977. self.services.pop('yum-proxy-setup')
  978. return True
  979. def has_firewall(self):
  980. return os.path.exists (self.firewall_conf)
  981. def get_firewall_defaults(self):
  982. return { "rules": list(), "allow": True, "allowDns": True, "allowIcmp": True, "allowYumProxy": False }
  983. def get_firewall_conf(self):
  984. conf = self.get_firewall_defaults()
  985. try:
  986. tree = lxml.etree.parse(self.firewall_conf)
  987. root = tree.getroot()
  988. conf["allow"] = (root.get("policy") == "allow")
  989. conf["allowDns"] = (root.get("dns") == "allow")
  990. conf["allowIcmp"] = (root.get("icmp") == "allow")
  991. conf["allowYumProxy"] = (root.get("yumProxy") == "allow")
  992. for element in root:
  993. rule = {}
  994. attr_list = ("address", "netmask", "proto", "port", "toport")
  995. for attribute in attr_list:
  996. rule[attribute] = element.get(attribute)
  997. if rule["netmask"] is not None:
  998. rule["netmask"] = int(rule["netmask"])
  999. else:
  1000. rule["netmask"] = 32
  1001. if rule["port"] is not None:
  1002. rule["portBegin"] = int(rule["port"])
  1003. else:
  1004. # backward compatibility
  1005. rule["portBegin"] = 0
  1006. # For backward compatibility
  1007. if rule["proto"] is None:
  1008. if rule["portBegin"] > 0:
  1009. rule["proto"] = "tcp"
  1010. else:
  1011. rule["proto"] = "any"
  1012. if rule["toport"] is not None:
  1013. rule["portEnd"] = int(rule["toport"])
  1014. else:
  1015. rule["portEnd"] = None
  1016. del(rule["port"])
  1017. del(rule["toport"])
  1018. conf["rules"].append(rule)
  1019. except EnvironmentError as err:
  1020. return conf
  1021. except (xml.parsers.expat.ExpatError,
  1022. ValueError, LookupError) as err:
  1023. print("{0}: load error: {1}".format(
  1024. os.path.basename(sys.argv[0]), err))
  1025. return None
  1026. return conf
  1027. def pci_add(self, pci):
  1028. if not os.path.exists('/sys/bus/pci/devices/0000:%s' % pci):
  1029. raise QubesException("Invalid PCI device: %s" % pci)
  1030. if self.pcidevs.count(pci):
  1031. # already added
  1032. return
  1033. self.pcidevs.append(pci)
  1034. if self.is_running():
  1035. try:
  1036. subprocess.check_call(['sudo', system_path["qubes_pciback_cmd"], pci])
  1037. subprocess.check_call(['sudo', 'xl', 'pci-attach', str(self.xid), pci])
  1038. except Exception as e:
  1039. print >>sys.stderr, "Failed to attach PCI device on the fly " \
  1040. "(%s), changes will be seen after VM restart" % str(e)
  1041. def pci_remove(self, pci):
  1042. if not self.pcidevs.count(pci):
  1043. # not attached
  1044. return
  1045. self.pcidevs.remove(pci)
  1046. if self.is_running():
  1047. p = subprocess.Popen(['xl', 'pci-list', str(self.xid)],
  1048. stdout=subprocess.PIPE)
  1049. result = p.communicate()
  1050. m = re.search(r"^(\d+.\d+)\s+0000:%s$" % pci, result[0], flags=re.MULTILINE)
  1051. if not m:
  1052. print >>sys.stderr, "Device %s already detached" % pci
  1053. return
  1054. vmdev = m.group(1)
  1055. try:
  1056. self.run("QUBESRPC qubes.DetachPciDevice dom0", user="root",
  1057. localcmd="echo 00:%s" % vmdev, wait=True)
  1058. subprocess.check_call(['sudo', 'xl', 'pci-detach', str(self.xid), pci])
  1059. except Exception as e:
  1060. print >>sys.stderr, "Failed to detach PCI device on the fly " \
  1061. "(%s), changes will be seen after VM restart" % str(e)
  1062. def run(self, command, user = None, verbose = True, autostart = False, notify_function = None, passio = False, passio_popen = False, passio_stderr=False, ignore_stderr=False, localcmd = None, wait = False, gui = True):
  1063. """command should be in form 'cmdline'
  1064. When passio_popen=True, popen object with stdout connected to pipe.
  1065. When additionally passio_stderr=True, stderr also is connected to pipe.
  1066. When ignore_stderr=True, stderr is connected to /dev/null.
  1067. """
  1068. if user is None:
  1069. user = self.default_user
  1070. null = None
  1071. if not self.is_running() and not self.is_paused():
  1072. if not autostart:
  1073. raise QubesException("VM not running")
  1074. try:
  1075. if notify_function is not None:
  1076. notify_function ("info", "Starting the '{0}' VM...".format(self.name))
  1077. elif verbose:
  1078. print >> sys.stderr, "Starting the VM '{0}'...".format(self.name)
  1079. xid = self.start(verbose=verbose, start_guid = gui, notify_function=notify_function)
  1080. except (IOError, OSError, QubesException) as err:
  1081. raise QubesException("Error while starting the '{0}' VM: {1}".format(self.name, err))
  1082. except (MemoryError) as err:
  1083. raise QubesException("Not enough memory to start '{0}' VM! Close one or more running VMs and try again.".format(self.name))
  1084. xid = self.get_xid()
  1085. if gui and os.getenv("DISPLAY") is not None and not self.is_guid_running():
  1086. self.start_guid(verbose = verbose, notify_function = notify_function)
  1087. args = [system_path["qrexec_client_path"], "-d", str(xid), "%s:%s" % (user, command)]
  1088. if localcmd is not None:
  1089. args += [ "-l", localcmd]
  1090. if passio:
  1091. os.execv(system_path["qrexec_client_path"], args)
  1092. exit(1)
  1093. call_kwargs = {}
  1094. if ignore_stderr:
  1095. null = open("/dev/null", "w")
  1096. call_kwargs['stderr'] = null
  1097. if passio_popen:
  1098. popen_kwargs={'stdout': subprocess.PIPE}
  1099. popen_kwargs['stdin'] = subprocess.PIPE
  1100. if passio_stderr:
  1101. popen_kwargs['stderr'] = subprocess.PIPE
  1102. else:
  1103. popen_kwargs['stderr'] = call_kwargs.get('stderr', None)
  1104. p = subprocess.Popen (args, **popen_kwargs)
  1105. if null:
  1106. null.close()
  1107. return p
  1108. if not wait:
  1109. args += ["-e"]
  1110. retcode = subprocess.call(args, **call_kwargs)
  1111. if null:
  1112. null.close()
  1113. return retcode
  1114. def attach_network(self, verbose = False, wait = True, netvm = None):
  1115. if dry_run:
  1116. return
  1117. if not self.is_running():
  1118. raise QubesException ("VM not running!")
  1119. if netvm is None:
  1120. netvm = self.netvm
  1121. if netvm is None:
  1122. raise QubesException ("NetVM not set!")
  1123. if netvm.qid != 0:
  1124. if not netvm.is_running():
  1125. if verbose:
  1126. print >> sys.stderr, "--> Starting NetVM {0}...".format(netvm.name)
  1127. netvm.start()
  1128. xs_path = '/local/domain/%d/device/vif/0/state' % (self.xid)
  1129. if xs.read('', xs_path) is not None:
  1130. # TODO: check its state and backend state (this can be stale vif after NetVM restart)
  1131. if verbose:
  1132. print >> sys.stderr, "NOTICE: Network already attached"
  1133. return
  1134. xm_cmdline = ["/usr/sbin/xl", "network-attach", str(self.xid), "script=/etc/xen/scripts/vif-route-qubes", "ip="+self.ip, "backend="+netvm.name ]
  1135. retcode = subprocess.call (xm_cmdline)
  1136. if retcode != 0:
  1137. print >> sys.stderr, ("WARNING: Cannot attach to network to '{0}'!".format(self.name))
  1138. if wait:
  1139. tries = 0
  1140. while xs.read('', xs_path) != '4':
  1141. tries += 1
  1142. if tries > 50:
  1143. raise QubesException ("Network attach timed out!")
  1144. time.sleep(0.2)
  1145. def wait_for_session(self, notify_function = None):
  1146. #self.run('echo $$ >> /tmp/qubes-session-waiter; [ ! -f /tmp/qubes-session-env ] && exec sleep 365d', ignore_stderr=True, gui=False, wait=True)
  1147. # Note : User root is redefined to SYSTEM in the Windows agent code
  1148. p = self.run('QUBESRPC qubes.WaitForSession none', user="root", passio_popen=True, gui=False, wait=True)
  1149. p.communicate(input=self.default_user)
  1150. def start_guid(self, verbose = True, notify_function = None):
  1151. if verbose:
  1152. print >> sys.stderr, "--> Starting Qubes GUId..."
  1153. xid = self.get_xid()
  1154. guid_cmd = [system_path["qubes_guid_path"], "-d", str(xid), "-c", self.label.color, "-i", self.label.icon_path, "-l", str(self.label.index)]
  1155. if self.debug:
  1156. guid_cmd += ['-v', '-v']
  1157. retcode = subprocess.call (guid_cmd)
  1158. if (retcode != 0) :
  1159. raise QubesException("Cannot start qubes-guid!")
  1160. if verbose:
  1161. print >> sys.stderr, "--> Sending monitor layout..."
  1162. try:
  1163. subprocess.call([system_path["monitor_layout_notify_cmd"], self.name])
  1164. except Exception as e:
  1165. print >>sys.stderr, "ERROR: %s" % e
  1166. if verbose:
  1167. print >> sys.stderr, "--> Waiting for qubes-session..."
  1168. self.wait_for_session(notify_function)
  1169. def start_qrexec_daemon(self, verbose = False, notify_function = None):
  1170. if verbose:
  1171. print >> sys.stderr, "--> Starting the qrexec daemon..."
  1172. xid = self.get_xid()
  1173. qrexec_env = os.environ
  1174. qrexec_env['QREXEC_STARTUP_TIMEOUT'] = str(self.qrexec_timeout)
  1175. retcode = subprocess.call ([system_path["qrexec_daemon_path"], str(xid), self.name, self.default_user], env=qrexec_env)
  1176. if (retcode != 0) :
  1177. self.force_shutdown(xid=xid)
  1178. raise OSError ("ERROR: Cannot execute qrexec-daemon!")
  1179. def start(self, debug_console = False, verbose = False, preparing_dvm = False, start_guid = True, notify_function = None):
  1180. if dry_run:
  1181. return
  1182. # Intentionally not used is_running(): eliminate also "Paused", "Crashed", "Halting"
  1183. if self.get_power_state() != "Halted":
  1184. raise QubesException ("VM is already running!")
  1185. self.verify_files()
  1186. if self.netvm is not None:
  1187. if self.netvm.qid != 0:
  1188. if not self.netvm.is_running():
  1189. if verbose:
  1190. print >> sys.stderr, "--> Starting NetVM {0}...".format(self.netvm.name)
  1191. self.netvm.start(verbose = verbose, start_guid = start_guid, notify_function = notify_function)
  1192. self.reset_volatile_storage(verbose=verbose)
  1193. if verbose:
  1194. print >> sys.stderr, "--> Loading the VM (type = {0})...".format(self.type)
  1195. # refresh config file
  1196. self.create_config_file()
  1197. mem_required = int(self.memory) * 1024 * 1024
  1198. qmemman_client = QMemmanClient()
  1199. try:
  1200. got_memory = qmemman_client.request_memory(mem_required)
  1201. except IOError as e:
  1202. raise IOError("ERROR: Failed to connect to qmemman: %s" % str(e))
  1203. if not got_memory:
  1204. qmemman_client.close()
  1205. raise MemoryError ("ERROR: insufficient memory to start VM '%s'" % self.name)
  1206. # Bind pci devices to pciback driver
  1207. for pci in self.pcidevs:
  1208. try:
  1209. subprocess.check_call(['sudo', system_path["qubes_pciback_cmd"], pci])
  1210. except subprocess.CalledProcessError:
  1211. raise QubesException("Failed to prepare PCI device %s" % pci)
  1212. xl_cmdline = ['sudo', '/usr/sbin/xl', 'create', self.conf_file, '-q', '-p']
  1213. try:
  1214. subprocess.check_call(xl_cmdline)
  1215. except:
  1216. raise QubesException("Failed to load VM config")
  1217. xid = self.get_xid()
  1218. self.xid = xid
  1219. if preparing_dvm:
  1220. self.services['qubes-dvm'] = True
  1221. if verbose:
  1222. print >> sys.stderr, "--> Setting Xen Store info for the VM..."
  1223. self.create_xenstore_entries(xid)
  1224. qvm_collection = QubesVmCollection()
  1225. qvm_collection.lock_db_for_reading()
  1226. qvm_collection.load()
  1227. qvm_collection.unlock_db()
  1228. if verbose:
  1229. print >> sys.stderr, "--> Updating firewall rules..."
  1230. for vm in qvm_collection.values():
  1231. if vm.is_proxyvm() and vm.is_running():
  1232. vm.write_iptables_xenstore_entry()
  1233. # fire hooks
  1234. for hook in self.hooks_start:
  1235. hook(self, verbose = verbose, preparing_dvm = preparing_dvm,
  1236. start_guid = start_guid, notify_function = notify_function)
  1237. if verbose:
  1238. print >> sys.stderr, "--> Starting the VM..."
  1239. xc.domain_unpause(xid)
  1240. # close() is not really needed, because the descriptor is close-on-exec
  1241. # anyway, the reason to postpone close() is that possibly xl is not done
  1242. # constructing the domain after its main process exits
  1243. # so we close() when we know the domain is up
  1244. # the successful unpause is some indicator of it
  1245. qmemman_client.close()
  1246. if self._start_guid_first and start_guid and not preparing_dvm and os.path.exists('/var/run/shm.id'):
  1247. self.start_guid(verbose=verbose,notify_function=notify_function)
  1248. if not preparing_dvm:
  1249. self.start_qrexec_daemon(verbose=verbose,notify_function=notify_function)
  1250. if not self._start_guid_first and start_guid and not preparing_dvm and os.path.exists('/var/run/shm.id'):
  1251. self.start_guid(verbose=verbose,notify_function=notify_function)
  1252. if preparing_dvm:
  1253. if verbose:
  1254. print >> sys.stderr, "--> Preparing config template for DispVM"
  1255. self.create_config_file(file_path = self.dir_path + '/dvm.conf', prepare_dvm = True)
  1256. # perhaps we should move it before unpause and fork?
  1257. # FIXME: this uses obsolete xm api
  1258. if debug_console:
  1259. from xen.xm import console
  1260. if verbose:
  1261. print >> sys.stderr, "--> Starting debug console..."
  1262. console.execConsole (xid)
  1263. return xid
  1264. def shutdown(self, force=False, xid = None):
  1265. if dry_run:
  1266. return
  1267. if not self.is_running():
  1268. raise QubesException ("VM already stopped!")
  1269. subprocess.call (['/usr/sbin/xl', 'shutdown', str(xid) if xid is not None else self.name])
  1270. #xc.domain_destroy(self.get_xid())
  1271. def force_shutdown(self, xid = None):
  1272. if dry_run:
  1273. return
  1274. if not self.is_running() and not self.is_paused():
  1275. raise QubesException ("VM already stopped!")
  1276. subprocess.call (['/usr/sbin/xl', 'destroy', str(xid) if xid is not None else self.name])
  1277. def suspend(self):
  1278. if dry_run:
  1279. return
  1280. if not self.is_running() and not self.is_paused():
  1281. raise QubesException ("VM already stopped!")
  1282. if len (self.pcidevs) > 0:
  1283. xs_path = '/local/domain/%d/control/shutdown' % self.get_xid()
  1284. xs.write('', xs_path, 'suspend')
  1285. tries = 0
  1286. while self.get_power_state() != "Suspended":
  1287. tries += 1
  1288. if tries > 15:
  1289. # fallback to pause
  1290. print >>sys.stderr, "Failed to suspend domain %s, falling back to pause method" % self.name
  1291. self.pause()
  1292. break
  1293. time.sleep(0.2)
  1294. else:
  1295. self.pause()
  1296. def resume(self):
  1297. if dry_run:
  1298. return
  1299. xc_info = self.get_xc_dominfo()
  1300. if not xc_info:
  1301. raise QubesException ("VM isn't started (cannot get xc_dominfo)!")
  1302. if xc_info['shutdown_reason'] == 2:
  1303. # suspended
  1304. xc.domain_resume(xc_info['domid'], 1)
  1305. else:
  1306. self.unpause()
  1307. def pause(self):
  1308. if dry_run:
  1309. return
  1310. xc.domain_pause(self.get_xid())
  1311. def unpause(self):
  1312. if dry_run:
  1313. return
  1314. xc.domain_unpause(self.get_xid())
  1315. def get_xml_attrs(self):
  1316. attrs = {}
  1317. attrs_config = self.get_attrs_config()
  1318. for attr in attrs_config:
  1319. attr_config = attrs_config[attr]
  1320. if 'save' in attr_config:
  1321. if 'save_skip' in attr_config and eval(attr_config['save_skip']):
  1322. continue
  1323. if 'save_attr' in attr_config:
  1324. attrs[attr_config['save_attr']] = eval(attr_config['save'])
  1325. else:
  1326. attrs[attr] = eval(attr_config['save'])
  1327. return attrs
  1328. def create_xml_element(self):
  1329. # Compatibility hack (Qubes*VM in type vs Qubes*Vm in XML)...
  1330. rx_type = re.compile (r"VM")
  1331. attrs = self.get_xml_attrs()
  1332. element = lxml.etree.Element(
  1333. "Qubes" + rx_type.sub("Vm", self.type),
  1334. **attrs)
  1335. return element
  1336. register_qubes_vm_class(QubesVm)