__init__.py 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610
  1. #!/usr/bin/python2 -O
  2. # vim: fileencoding=utf-8
  3. #
  4. # The Qubes OS Project, https://www.qubes-os.org/
  5. #
  6. # Copyright (C) 2010-2015 Joanna Rutkowska <joanna@invisiblethingslab.com>
  7. # Copyright (C) 2011-2015 Marek Marczykowski-Górecki
  8. # <marmarek@invisiblethingslab.com>
  9. # Copyright (C) 2014-2015 Wojtek Porczyk <woju@invisiblethingslab.com>
  10. #
  11. # This program is free software; you can redistribute it and/or modify
  12. # it under the terms of the GNU General Public License as published by
  13. # the Free Software Foundation; either version 2 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # This program is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. # GNU General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU General Public License along
  22. # with this program; if not, write to the Free Software Foundation, Inc.,
  23. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  24. #
  25. '''
  26. Qubes OS
  27. :copyright: © 2010-2015 Invisible Things Lab
  28. '''
  29. from __future__ import absolute_import
  30. __author__ = 'Invisible Things Lab'
  31. __license__ = 'GPLv2 or later'
  32. __version__ = 'R3'
  33. import collections
  34. import errno
  35. import grp
  36. import logging
  37. import os
  38. import os.path
  39. import sys
  40. import tempfile
  41. import time
  42. import __builtin__
  43. import jinja2
  44. import lxml.etree
  45. import pkg_resources
  46. import qubes.config
  47. import qubes.events
  48. import qubes.exc
  49. import qubes.ext
  50. if os.name == 'posix':
  51. import fcntl
  52. elif os.name == 'nt':
  53. # pylint: disable=import-error
  54. import win32con
  55. import win32file
  56. import pywintypes
  57. else:
  58. raise RuntimeError("Qubes works only on POSIX or WinNT systems")
  59. import libvirt
  60. try:
  61. import xen.lowlevel.xs
  62. import xen.lowlevel.xc
  63. except ImportError:
  64. pass
  65. def get_entry_point_one(group, name):
  66. epoints = tuple(pkg_resources.iter_entry_points(group, name))
  67. if not epoints:
  68. raise KeyError(name)
  69. elif len(epoints) > 1:
  70. raise TypeError(
  71. 'more than 1 implementation of {!r} found: {}'.format(name,
  72. ', '.join('{}.{}'.format(ep.module_name, '.'.join(ep.attrs))
  73. for ep in epoints)))
  74. return epoints[0].load()
  75. class VMMConnection(object):
  76. '''Connection to Virtual Machine Manager (libvirt)'''
  77. def __init__(self):
  78. self._libvirt_conn = None
  79. self._xs = None
  80. self._xc = None
  81. self._offline_mode = False
  82. @__builtin__.property
  83. def offline_mode(self):
  84. '''Check or enable offline mode (do not actually connect to vmm)'''
  85. return self._offline_mode
  86. @offline_mode.setter
  87. def offline_mode(self, value):
  88. if value and self._libvirt_conn is not None:
  89. raise qubes.exc.QubesException(
  90. 'Cannot change offline mode while already connected')
  91. self._offline_mode = value
  92. def _libvirt_error_handler(self, ctx, error):
  93. pass
  94. def init_vmm_connection(self):
  95. '''Initialise connection
  96. This method is automatically called when getting'''
  97. if self._libvirt_conn is not None:
  98. # Already initialized
  99. return
  100. if self._offline_mode:
  101. # Do not initialize in offline mode
  102. raise qubes.exc.QubesException(
  103. 'VMM operations disabled in offline mode')
  104. if 'xen.lowlevel.xs' in sys.modules:
  105. self._xs = xen.lowlevel.xs.xs()
  106. if 'xen.lowlevel.cs' in sys.modules:
  107. self._xc = xen.lowlevel.xc.xc()
  108. self._libvirt_conn = libvirt.open(qubes.config.defaults['libvirt_uri'])
  109. if self._libvirt_conn is None:
  110. raise qubes.exc.QubesException('Failed connect to libvirt driver')
  111. libvirt.registerErrorHandler(self._libvirt_error_handler, None)
  112. @__builtin__.property
  113. def libvirt_conn(self):
  114. '''Connection to libvirt'''
  115. self.init_vmm_connection()
  116. return self._libvirt_conn
  117. @__builtin__.property
  118. def xs(self):
  119. '''Connection to Xen Store
  120. This property in available only when running on Xen.
  121. '''
  122. # XXX what about the case when we run under KVM,
  123. # but xen modules are importable?
  124. if 'xen.lowlevel.xs' not in sys.modules:
  125. raise AttributeError(
  126. 'xs object is available under Xen hypervisor only')
  127. self.init_vmm_connection()
  128. return self._xs
  129. @__builtin__.property
  130. def xc(self):
  131. '''Connection to Xen
  132. This property in available only when running on Xen.
  133. '''
  134. # XXX what about the case when we run under KVM,
  135. # but xen modules are importable?
  136. if 'xen.lowlevel.xc' not in sys.modules:
  137. raise AttributeError(
  138. 'xc object is available under Xen hypervisor only')
  139. self.init_vmm_connection()
  140. return self._xs
  141. def __del__(self):
  142. if self._libvirt_conn:
  143. self._libvirt_conn.close()
  144. class QubesHost(object):
  145. '''Basic information about host machine
  146. :param qubes.Qubes app: Qubes application context (must have \
  147. :py:attr:`Qubes.vmm` attribute defined)
  148. '''
  149. def __init__(self, app):
  150. self.app = app
  151. self._no_cpus = None
  152. self._total_mem = None
  153. self._physinfo = None
  154. def _fetch(self):
  155. if self._no_cpus is not None:
  156. return
  157. # pylint: disable=unused-variable
  158. (model, memory, cpus, mhz, nodes, socket, cores, threads) = \
  159. self.app.vmm.libvirt_conn.getInfo()
  160. self._total_mem = long(memory) * 1024
  161. self._no_cpus = cpus
  162. self.app.log.debug('QubesHost: no_cpus={} memory_total={}'.format(
  163. self.no_cpus, self.memory_total))
  164. try:
  165. self.app.log.debug('QubesHost: xen_free_memory={}'.format(
  166. self.get_free_xen_memory()))
  167. except NotImplementedError:
  168. pass
  169. @__builtin__.property
  170. def memory_total(self):
  171. '''Total memory, in kbytes'''
  172. self._fetch()
  173. return self._total_mem
  174. @__builtin__.property
  175. def no_cpus(self):
  176. '''Number of CPUs'''
  177. self._fetch()
  178. return self._no_cpus
  179. def get_free_xen_memory(self):
  180. '''Get free memory from Xen's physinfo.
  181. :raises NotImplementedError: when not under Xen
  182. '''
  183. try:
  184. self._physinfo = self.app.xc.physinfo()
  185. except AttributeError:
  186. raise NotImplementedError('This function requires Xen hypervisor')
  187. return long(self._physinfo['free_memory'])
  188. def measure_cpu_usage(self, previous_time=None, previous=None,
  189. wait_time=1):
  190. '''Measure cpu usage for all domains at once.
  191. This function requires Xen hypervisor.
  192. .. versionchanged:: 3.0
  193. argument order to match return tuple
  194. :raises NotImplementedError: when not under Xen
  195. '''
  196. if previous is None:
  197. previous_time = time.time()
  198. previous = {}
  199. try:
  200. info = self.app.vmm.xc.domain_getinfo(0, qubes.config.max_qid)
  201. except AttributeError:
  202. raise NotImplementedError(
  203. 'This function requires Xen hypervisor')
  204. for vm in info:
  205. previous[vm['domid']] = {}
  206. previous[vm['domid']]['cpu_time'] = (
  207. vm['cpu_time'] / vm['online_vcpus'])
  208. previous[vm['domid']]['cpu_usage'] = 0
  209. time.sleep(wait_time)
  210. current_time = time.time()
  211. current = {}
  212. try:
  213. info = self.app.vmm.xc.domain_getinfo(0, qubes.config.max_qid)
  214. except AttributeError:
  215. raise NotImplementedError(
  216. 'This function requires Xen hypervisor')
  217. for vm in info:
  218. current[vm['domid']] = {}
  219. current[vm['domid']]['cpu_time'] = (
  220. vm['cpu_time'] / max(vm['online_vcpus'], 1))
  221. if vm['domid'] in previous.keys():
  222. current[vm['domid']]['cpu_usage'] = (
  223. float(current[vm['domid']]['cpu_time'] -
  224. previous[vm['domid']]['cpu_time']) /
  225. long(1000 ** 3) / (current_time - previous_time) * 100)
  226. if current[vm['domid']]['cpu_usage'] < 0:
  227. # VM has been rebooted
  228. current[vm['domid']]['cpu_usage'] = 0
  229. else:
  230. current[vm['domid']]['cpu_usage'] = 0
  231. return (current_time, current)
  232. class Label(object):
  233. '''Label definition for virtual machines
  234. Label specifies colour of the padlock displayed next to VM's name.
  235. When this is a :py:class:`qubes.vm.dispvm.DispVM`, padlock is overlayed
  236. with recycling pictogram.
  237. :param int index: numeric identificator of label
  238. :param str color: colour specification as in HTML (``#abcdef``)
  239. :param str name: label's name like "red" or "green"
  240. '''
  241. def __init__(self, index, color, name):
  242. #: numeric identificator of label
  243. self.index = index
  244. #: colour specification as in HTML (``#abcdef``)
  245. self.color = color
  246. #: label's name like "red" or "green"
  247. self.name = name
  248. #: freedesktop icon name, suitable for use in
  249. #: :py:meth:`PyQt4.QtGui.QIcon.fromTheme`
  250. self.icon = 'appvm-' + name
  251. #: freedesktop icon name, suitable for use in
  252. #: :py:meth:`PyQt4.QtGui.QIcon.fromTheme` on DispVMs
  253. self.icon_dispvm = 'dispvm-' + name
  254. @classmethod
  255. def fromxml(cls, xml):
  256. '''Create label definition from XML node
  257. :param lxml.etree._Element xml: XML node reference
  258. :rtype: :py:class:`qubes.Label`
  259. '''
  260. index = int(xml.get('id').split('-', 1)[1])
  261. color = xml.get('color')
  262. name = xml.text
  263. return cls(index, color, name)
  264. def __xml__(self):
  265. element = lxml.etree.Element(
  266. 'label', id='label-{}'.format(self.index), color=self.color)
  267. element.text = self.name
  268. return element
  269. def __str__(self):
  270. return self.name
  271. def __repr__(self):
  272. return '{}({!r}, {!r}, {!r})'.format(
  273. self.__class__.__name__,
  274. self.index,
  275. self.color,
  276. self.name)
  277. @__builtin__.property
  278. def icon_path(self):
  279. '''Icon path
  280. .. deprecated:: 2.0
  281. use :py:meth:`PyQt4.QtGui.QIcon.fromTheme` and :py:attr:`icon`
  282. '''
  283. return os.path.join(qubes.config.system_path['qubes_icon_dir'],
  284. self.icon) + ".png"
  285. @__builtin__.property
  286. def icon_path_dispvm(self):
  287. '''Icon path
  288. .. deprecated:: 2.0
  289. use :py:meth:`PyQt4.QtGui.QIcon.fromTheme` and :py:attr:`icon_dispvm`
  290. '''
  291. return os.path.join(qubes.config.system_path['qubes_icon_dir'],
  292. self.icon_dispvm) + ".png"
  293. class VMCollection(object):
  294. '''A collection of Qubes VMs
  295. VMCollection supports ``in`` operator. You may test for ``qid``, ``name``
  296. and whole VM object's presence.
  297. Iterating over VMCollection will yield machine objects.
  298. '''
  299. def __init__(self, app):
  300. self.app = app
  301. self._dict = dict()
  302. def __repr__(self):
  303. return '<{} {!r}>'.format(
  304. self.__class__.__name__, list(sorted(self.keys())))
  305. def items(self):
  306. '''Iterate over ``(qid, vm)`` pairs'''
  307. for qid in self.qids():
  308. yield (qid, self[qid])
  309. def qids(self):
  310. '''Iterate over all qids
  311. qids are sorted by numerical order.
  312. '''
  313. return iter(sorted(self._dict.keys()))
  314. keys = qids
  315. def names(self):
  316. '''Iterate over all names
  317. names are sorted by lexical order.
  318. '''
  319. return iter(sorted(vm.name for vm in self._dict.values()))
  320. def vms(self):
  321. '''Iterate over all machines
  322. vms are sorted by qid.
  323. '''
  324. return iter(sorted(self._dict.values()))
  325. __iter__ = vms
  326. values = vms
  327. def add(self, value):
  328. '''Add VM to collection
  329. :param qubes.vm.BaseVM value: VM to add
  330. :raises TypeError: when value is of wrong type
  331. :raises ValueError: when there is already VM which has equal ``qid``
  332. '''
  333. # this violates duck typing, but is needed
  334. # for VMProperty to function correctly
  335. if not isinstance(value, qubes.vm.BaseVM):
  336. raise TypeError('{} holds only BaseVM instances'.format(
  337. self.__class__.__name__))
  338. if value.qid in self:
  339. raise ValueError('This collection already holds VM that has '
  340. 'qid={!r} ({!r})'.format(value.qid, self[value.qid]))
  341. if value.name in self:
  342. raise ValueError('This collection already holds VM that has '
  343. 'name={!r} ({!r})'.format(value.name, self[value.name]))
  344. self._dict[value.qid] = value
  345. value.events_enabled = True
  346. self.app.fire_event('domain-add', value)
  347. return value
  348. def __getitem__(self, key):
  349. if isinstance(key, int):
  350. return self._dict[key]
  351. if isinstance(key, basestring):
  352. for vm in self:
  353. if vm.name == key:
  354. return vm
  355. raise KeyError(key)
  356. if isinstance(key, qubes.vm.BaseVM):
  357. if key in self:
  358. return key
  359. raise KeyError(key)
  360. raise KeyError(key)
  361. def __delitem__(self, key):
  362. vm = self[key]
  363. self.app.fire_event_pre('domain-pre-delete', vm)
  364. del self._dict[vm.qid]
  365. self.app.fire_event('domain-delete', vm)
  366. def __contains__(self, key):
  367. return any((key == vm or key == vm.qid or key == vm.name)
  368. for vm in self)
  369. def __len__(self):
  370. return len(self._dict)
  371. def get_vms_based_on(self, template):
  372. template = self[template]
  373. return set(vm for vm in self
  374. if hasattr(vm, 'template') and vm.template == template)
  375. def get_vms_connected_to(self, netvm):
  376. new_vms = set([self[netvm]])
  377. dependent_vms = set()
  378. # Dependency resolving only makes sense on NetVM (or derivative)
  379. # if not self[netvm_qid].is_netvm():
  380. # return set([])
  381. while len(new_vms) > 0:
  382. cur_vm = new_vms.pop()
  383. for vm in cur_vm.connected_vms.values():
  384. if vm in dependent_vms:
  385. continue
  386. dependent_vms.add(vm.qid)
  387. # if vm.is_netvm():
  388. new_vms.add(vm.qid)
  389. return dependent_vms
  390. # XXX with Qubes Admin Api this will probably lead to race condition
  391. # whole process of creating and adding should be synchronised
  392. def get_new_unused_qid(self):
  393. used_ids = set(self.qids())
  394. for i in range(1, qubes.config.max_qid):
  395. if i not in used_ids:
  396. return i
  397. raise LookupError("Cannot find unused qid!")
  398. def get_new_unused_netid(self):
  399. used_ids = set([vm.netid for vm in self]) # if vm.is_netvm()])
  400. for i in range(1, qubes.config.max_netid):
  401. if i not in used_ids:
  402. return i
  403. raise LookupError("Cannot find unused netid!")
  404. class property(object): # pylint: disable=redefined-builtin,invalid-name
  405. '''Qubes property.
  406. This class holds one property that can be saved to and loaded from
  407. :file:`qubes.xml`. It is used for both global and per-VM properties.
  408. Property can be unset by ordinary ``del`` statement or assigning
  409. :py:attr:`DEFAULT` special value to it. After deletion (or before first
  410. assignment/load) attempting to read a property will get its default value
  411. or, when no default, py:class:`exceptions.AttributeError`.
  412. :param str name: name of the property
  413. :param collections.Callable setter: if not :py:obj:`None`, this is used to \
  414. initialise value; first parameter to the function is holder instance \
  415. and the second is value; this is called before ``type``
  416. :param collections.Callable saver: function to coerce value to something \
  417. readable by setter
  418. :param type type: if not :py:obj:`None`, value is coerced to this type
  419. :param object default: default value; if callable, will be called with \
  420. holder as first argument
  421. :param int load_stage: stage when property should be loaded (see \
  422. :py:class:`Qubes` for description of stages)
  423. :param int order: order of evaluation (bigger order values are later)
  424. :param bool clone: :py:meth:`PropertyHolder.clone_properties` will not \
  425. include this property by default if :py:obj:`False`
  426. :param str ls_head: column head for :program:`qvm-ls`
  427. :param int ls_width: column width in :program:`qvm-ls`
  428. :param str doc: docstring; this should be one paragraph of plain RST, no \
  429. sphinx-specific features
  430. Setters and savers have following signatures:
  431. .. :py:function:: setter(self, prop, value)
  432. :noindex:
  433. :param self: instance of object that is holding property
  434. :param prop: property object
  435. :param value: value being assigned
  436. .. :py:function:: saver(self, prop, value)
  437. :noindex:
  438. :param self: instance of object that is holding property
  439. :param prop: property object
  440. :param value: value being saved
  441. :rtype: str
  442. :raises property.DontSave: when property should not be saved at all
  443. '''
  444. #: Assigning this value to property means setting it to its default value.
  445. #: If property has no default value, this will unset it.
  446. DEFAULT = object()
  447. # internal use only
  448. _NO_DEFAULT = object()
  449. def __init__(self, name, setter=None, saver=None, type=None,
  450. default=_NO_DEFAULT, write_once=False, load_stage=2, order=0,
  451. save_via_ref=False, clone=True,
  452. ls_head=None, ls_width=None, doc=None):
  453. # pylint: disable=redefined-builtin
  454. self.__name__ = name
  455. self._setter = setter
  456. self._saver = saver if saver is not None else (
  457. lambda self, prop, value: str(value))
  458. self._type = type
  459. self._default = default
  460. self._write_once = write_once
  461. self.order = order
  462. self.load_stage = load_stage
  463. self.save_via_ref = save_via_ref
  464. self.clone = clone
  465. self.__doc__ = doc
  466. self._attr_name = '_qubesprop_' + name
  467. if ls_head is not None or ls_width is not None:
  468. self.ls_head = ls_head or self.__name__.replace('_', '-').upper()
  469. self.ls_width = max(ls_width or 0, len(self.ls_head) + 1)
  470. def __get__(self, instance, owner):
  471. if instance is None:
  472. return self
  473. # XXX this violates duck typing, shall we keep it?
  474. if not isinstance(instance, PropertyHolder):
  475. raise AttributeError('qubes.property should be used on '
  476. 'qubes.PropertyHolder instances only')
  477. try:
  478. return getattr(instance, self._attr_name)
  479. except AttributeError:
  480. if self._default is self._NO_DEFAULT:
  481. raise AttributeError(
  482. 'property {!r} not set'.format(self.__name__))
  483. elif isinstance(self._default, collections.Callable):
  484. return self._default(instance)
  485. else:
  486. return self._default
  487. def __set__(self, instance, value):
  488. self._enforce_write_once(instance)
  489. if value is self.__class__.DEFAULT:
  490. self.__delete__(instance)
  491. return
  492. try:
  493. oldvalue = getattr(instance, self.__name__)
  494. has_oldvalue = True
  495. except AttributeError:
  496. has_oldvalue = False
  497. if self._setter is not None:
  498. value = self._setter(instance, self, value)
  499. if self._type not in (None, type(value)):
  500. value = self._type(value)
  501. if has_oldvalue:
  502. instance.fire_event_pre('property-pre-set:' + self.__name__,
  503. self.__name__, value, oldvalue)
  504. else:
  505. instance.fire_event_pre('property-pre-set:' + self.__name__,
  506. self.__name__, value)
  507. instance._property_init(self, value) # pylint: disable=protected-access
  508. if has_oldvalue:
  509. instance.fire_event('property-set:' + self.__name__, self.__name__,
  510. value, oldvalue)
  511. else:
  512. instance.fire_event('property-set:' + self.__name__, self.__name__,
  513. value)
  514. def __delete__(self, instance):
  515. self._enforce_write_once(instance)
  516. try:
  517. oldvalue = getattr(instance, self.__name__)
  518. has_oldvalue = True
  519. except AttributeError:
  520. has_oldvalue = False
  521. if has_oldvalue:
  522. instance.fire_event_pre('property-pre-del:' + self.__name__,
  523. self.__name__, oldvalue)
  524. delattr(instance, self._attr_name)
  525. instance.fire_event('property-del:' + self.__name__,
  526. self.__name__, oldvalue)
  527. else:
  528. instance.fire_event_pre('property-pre-del:' + self.__name__,
  529. self.__name__)
  530. instance.fire_event('property-del:' + self.__name__,
  531. self.__name__)
  532. def __repr__(self):
  533. default = ' default={!r}'.format(self._default) \
  534. if self._default is not self._NO_DEFAULT \
  535. else ''
  536. return '<{} object at {:#x} name={!r}{}>'.format(
  537. self.__class__.__name__, id(self), self.__name__, default) \
  538. def __hash__(self):
  539. return hash(self.__name__)
  540. def __eq__(self, other):
  541. return isinstance(other, property) and self.__name__ == other.__name__
  542. def _enforce_write_once(self, instance):
  543. if self._write_once and not instance.property_is_default(self):
  544. raise AttributeError(
  545. 'property {!r} is write-once and already set'.format(
  546. self.__name__))
  547. #
  548. # exceptions
  549. #
  550. class DontSave(Exception):
  551. '''This exception may be raised from saver to sign that property should
  552. not be saved.
  553. '''
  554. pass
  555. @staticmethod
  556. def dontsave(self, prop, value):
  557. '''Dummy saver that never saves anything.'''
  558. # pylint: disable=bad-staticmethod-argument,unused-argument
  559. raise property.DontSave()
  560. #
  561. # some setters provided
  562. #
  563. @staticmethod
  564. def forbidden(self, prop, value):
  565. '''Property setter that forbids loading a property.
  566. This is used to effectively disable property in classes which inherit
  567. unwanted property. When someone attempts to load such a property, it
  568. :throws AttributeError: always
  569. ''' # pylint: disable=bad-staticmethod-argument,unused-argument
  570. raise AttributeError(
  571. 'setting {} property on {} instance is forbidden'.format(
  572. prop.__name__, self.__class__.__name__))
  573. @staticmethod
  574. def bool(self, prop, value):
  575. '''Property setter for boolean properties.
  576. It accepts (case-insensitive) ``'0'``, ``'no'`` and ``false`` as
  577. :py:obj:`False` and ``'1'``, ``'yes'`` and ``'true'`` as
  578. :py:obj:`True`.
  579. ''' # pylint: disable=bad-staticmethod-argument,unused-argument
  580. if isinstance(value, basestring):
  581. lcvalue = value.lower()
  582. if lcvalue in ('0', 'no', 'false', 'off'):
  583. return False
  584. if lcvalue in ('1', 'yes', 'true', 'on'):
  585. return True
  586. raise ValueError(
  587. 'Invalid literal for boolean property: {!r}'.format(value))
  588. return bool(value)
  589. class PropertyHolder(qubes.events.Emitter):
  590. '''Abstract class for holding :py:class:`qubes.property`
  591. Events fired by instances of this class:
  592. .. event:: property-load (subject, event)
  593. Fired once after all properties are loaded from XML. Individual
  594. ``property-set`` events are not fired.
  595. .. event:: property-set:<propname> \
  596. (subject, event, name, newvalue[, oldvalue])
  597. Fired when property changes state. Signature is variable,
  598. *oldvalue* is present only if there was an old value.
  599. :param name: Property name
  600. :param newvalue: New value of the property
  601. :param oldvalue: Old value of the property
  602. .. event:: property-pre-set:<propname> \
  603. (subject, event, name, newvalue[, oldvalue])
  604. Fired before property changes state. Signature is variable,
  605. *oldvalue* is present only if there was an old value.
  606. :param name: Property name
  607. :param newvalue: New value of the property
  608. :param oldvalue: Old value of the property
  609. .. event:: property-del:<propname> \
  610. (subject, event, name[, oldvalue])
  611. Fired when property gets deleted (is set to default). Signature is
  612. variable, *oldvalue* is present only if there was an old value.
  613. :param name: Property name
  614. :param oldvalue: Old value of the property
  615. .. event:: property-pre-del:<propname> \
  616. (subject, event, name[, oldvalue])
  617. Fired before property gets deleted (is set to default). Signature
  618. is variable, *oldvalue* is present only if there was an old value.
  619. :param name: Property name
  620. :param oldvalue: Old value of the property
  621. Members:
  622. '''
  623. def __init__(self, xml, **kwargs):
  624. self.xml = xml
  625. propvalues = {}
  626. all_names = set(prop.__name__ for prop in self.property_list())
  627. for key in list(kwargs.keys()):
  628. if not key in all_names:
  629. continue
  630. propvalues[key] = kwargs.pop(key)
  631. super(PropertyHolder, self).__init__(**kwargs)
  632. for key, value in propvalues.items():
  633. setattr(self, key, value)
  634. @classmethod
  635. def property_list(cls, load_stage=None):
  636. '''List all properties attached to this VM's class
  637. :param load_stage: Filter by load stage
  638. :type load_stage: :py:func:`int` or :py:obj:`None`
  639. '''
  640. props = set()
  641. for class_ in cls.__mro__:
  642. props.update(prop for prop in class_.__dict__.values()
  643. if isinstance(prop, property))
  644. if load_stage is not None:
  645. props = set(prop for prop in props
  646. if prop.load_stage == load_stage)
  647. return sorted(props,
  648. key=lambda prop: (prop.load_stage, prop.order, prop.__name__))
  649. def _property_init(self, prop, value):
  650. '''Initialise property to a given value, without side effects.
  651. :param qubes.property prop: property object of particular interest
  652. :param value: value
  653. '''
  654. # pylint: disable=protected-access
  655. setattr(self, self.property_get_def(prop)._attr_name, value)
  656. def property_is_default(self, prop):
  657. '''Check whether property is in it's default value.
  658. Properties when unset may return some default value, so
  659. ``hasattr(vm, prop.__name__)`` is wrong in some circumstances. This
  660. method allows for checking if the value returned is in fact it's
  661. default value.
  662. :param qubes.property prop: property object of particular interest
  663. :rtype: bool
  664. ''' # pylint: disable=protected-access
  665. # both property_get_def() and ._attr_name may throw AttributeError,
  666. # which we don't want to catch
  667. attrname = self.property_get_def(prop)._attr_name
  668. return not hasattr(self, attrname)
  669. @classmethod
  670. def property_get_def(cls, prop):
  671. '''Return property definition object.
  672. If prop is already :py:class:`qubes.property` instance, return the same
  673. object.
  674. :param prop: property object or name
  675. :type prop: qubes.property or str
  676. :rtype: qubes.property
  677. '''
  678. if isinstance(prop, qubes.property):
  679. return prop
  680. for p in cls.property_list():
  681. if p.__name__ == prop:
  682. return p
  683. raise AttributeError('No property {!r} found in {!r}'.format(
  684. prop, cls))
  685. def load_properties(self, load_stage=None):
  686. '''Load properties from immediate children of XML node.
  687. ``property-set`` events are not fired for each individual property.
  688. :param int load_stage: Stage of loading.
  689. '''
  690. if self.xml is None:
  691. return
  692. all_names = set(
  693. prop.__name__ for prop in self.property_list(load_stage))
  694. for node in self.xml.xpath('./properties/property'):
  695. name = node.get('name')
  696. value = node.get('ref') or node.text
  697. if not name in all_names:
  698. continue
  699. setattr(self, name, value)
  700. def xml_properties(self, with_defaults=False):
  701. '''Iterator that yields XML nodes representing set properties.
  702. :param bool with_defaults: If :py:obj:`True`, then it also includes \
  703. properties which were not set explicite, but have default values \
  704. filled.
  705. '''
  706. properties = lxml.etree.Element('properties')
  707. for prop in self.property_list():
  708. # pylint: disable=protected-access
  709. try:
  710. value = getattr(
  711. self, (prop.__name__ if with_defaults else prop._attr_name))
  712. except AttributeError:
  713. continue
  714. try:
  715. value = prop._saver(self, prop, value)
  716. except property.DontSave:
  717. continue
  718. element = lxml.etree.Element('property', name=prop.__name__)
  719. if prop.save_via_ref:
  720. element.set('ref', value)
  721. else:
  722. element.text = value
  723. properties.append(element)
  724. return properties
  725. # this was clone_attrs
  726. def clone_properties(self, src, proplist=None):
  727. '''Clone properties from other object.
  728. :param PropertyHolder src: source object
  729. :param list proplist: list of properties \
  730. (:py:obj:`None` or omit for all properties except those with \
  731. :py:attr:`property.clone` set to :py:obj:`False`)
  732. '''
  733. if proplist is None:
  734. proplist = [prop for prop in self.property_list()
  735. if prop.clone]
  736. else:
  737. proplist = [prop for prop in self.property_list()
  738. if prop.__name__ in proplist or prop in proplist]
  739. for prop in proplist:
  740. try:
  741. # pylint: disable=protected-access
  742. self._property_init(prop, getattr(src, prop._attr_name))
  743. except AttributeError:
  744. continue
  745. self.fire_event('clone-properties', src, proplist)
  746. def property_require(self, prop, allow_none=False, hard=False):
  747. '''Complain badly when property is not set.
  748. :param prop: property name or object
  749. :type prop: qubes.property or str
  750. :param bool allow_none: if :py:obj:`True`, don't complain if \
  751. :py:obj:`None` is found
  752. :param bool hard: if :py:obj:`True`, raise :py:class:`AssertionError`; \
  753. if :py:obj:`False`, log warning instead
  754. '''
  755. if isinstance(prop, qubes.property):
  756. prop = prop.__name__
  757. try:
  758. value = getattr(self, prop)
  759. if value is None and not allow_none:
  760. raise AttributeError()
  761. except AttributeError:
  762. # pylint: disable=no-member
  763. msg = 'Required property {!r} not set on {!r}'.format(prop, self)
  764. if hard:
  765. raise AssertionError(msg)
  766. else:
  767. # pylint: disable=no-member
  768. self.log.fatal(msg)
  769. import qubes.vm
  770. class VMProperty(property):
  771. '''Property that is referring to a VM
  772. :param type vmclass: class that returned VM is supposed to be instance of
  773. and all supported by :py:class:`property` with the exception of ``type`` \
  774. and ``setter``
  775. '''
  776. _none_value = ''
  777. def __init__(self, name, vmclass=qubes.vm.BaseVM, allow_none=False,
  778. **kwargs):
  779. if 'type' in kwargs:
  780. raise TypeError(
  781. "'type' keyword parameter is unsupported in {}".format(
  782. self.__class__.__name__))
  783. if 'setter' in kwargs:
  784. raise TypeError(
  785. "'setter' keyword parameter is unsupported in {}".format(
  786. self.__class__.__name__))
  787. if not issubclass(vmclass, qubes.vm.BaseVM):
  788. raise TypeError(
  789. "'vmclass' should specify a subclass of qubes.vm.BaseVM")
  790. super(VMProperty, self).__init__(name,
  791. saver=(lambda self_, prop, value:
  792. self._none_value if value is None else value.name),
  793. **kwargs)
  794. self.vmclass = vmclass
  795. self.allow_none = allow_none
  796. def __set__(self, instance, value):
  797. if value is self.__class__.DEFAULT:
  798. self.__delete__(instance)
  799. return
  800. if value == self._none_value:
  801. value = None
  802. if value is None:
  803. if self.allow_none:
  804. super(VMProperty, self).__set__(instance, value)
  805. return
  806. else:
  807. raise ValueError(
  808. 'Property {!r} does not allow setting to {!r}'.format(
  809. self.__name__, value))
  810. app = instance if isinstance(instance, Qubes) else instance.app
  811. try:
  812. vm = app.domains[value]
  813. except KeyError:
  814. raise qubes.exc.QubesVMNotFoundError(value)
  815. if not isinstance(vm, self.vmclass):
  816. raise TypeError('wrong VM class: domains[{!r}] if of type {!s} '
  817. 'and not {!s}'.format(value,
  818. vm.__class__.__name__,
  819. self.vmclass.__name__))
  820. super(VMProperty, self).__set__(instance, vm)
  821. import qubes.vm.qubesvm
  822. import qubes.vm.templatevm
  823. import qubes.vm.adminvm
  824. class Qubes(PropertyHolder):
  825. '''Main Qubes application
  826. :param str store: path to ``qubes.xml``
  827. The store is loaded in stages:
  828. 1. In the first stage there are loaded some basic features from store
  829. (currently labels).
  830. 2. In the second stage stubs for all VMs are loaded. They are filled
  831. with their basic properties, like ``qid`` and ``name``.
  832. 3. In the third stage all global properties are loaded. They often
  833. reference VMs, like default netvm, so they should be filled after
  834. loading VMs.
  835. 4. In the fourth stage all remaining VM properties are loaded. They
  836. also need all VMs loaded, because they represent dependencies
  837. between VMs like aforementioned netvm.
  838. 5. In the fifth stage there are some fixups to ensure sane system
  839. operation.
  840. This class emits following events:
  841. .. event:: domain-add (subject, event, vm)
  842. When domain is added.
  843. :param subject: Event emitter
  844. :param event: Event name (``'domain-add'``)
  845. :param vm: Domain object
  846. .. event:: domain-delete (subject, event, vm)
  847. When domain is deleted. VM still has reference to ``app`` object,
  848. but is not contained within VMCollection.
  849. :param subject: Event emitter
  850. :param event: Event name (``'domain-delete'``)
  851. :param vm: Domain object
  852. Methods and attributes:
  853. '''
  854. default_netvm = VMProperty('default_netvm', load_stage=3,
  855. default=None, allow_none=True,
  856. doc='''Default NetVM for AppVMs. Initial state is `None`, which means
  857. that AppVMs are not connected to the Internet.''')
  858. default_fw_netvm = VMProperty('default_fw_netvm', load_stage=3,
  859. default=None, allow_none=True,
  860. doc='''Default NetVM for ProxyVMs. Initial state is `None`, which means
  861. that ProxyVMs (including FirewallVM) are not connected to the
  862. Internet.''')
  863. default_template = VMProperty('default_template', load_stage=3,
  864. vmclass=qubes.vm.templatevm.TemplateVM,
  865. doc='Default template for new AppVMs')
  866. updatevm = VMProperty('updatevm', load_stage=3,
  867. allow_none=True,
  868. doc='''Which VM to use as `yum` proxy for updating AdminVM and
  869. TemplateVMs''')
  870. clockvm = VMProperty('clockvm', load_stage=3,
  871. allow_none=True,
  872. doc='Which VM to use as NTP proxy for updating AdminVM')
  873. default_kernel = property('default_kernel', load_stage=3,
  874. doc='Which kernel to use when not overriden in VM')
  875. # TODO #1637 #892
  876. check_updates_vm = property('check_updates_vm',
  877. type=bool, setter=property.bool,
  878. default=True,
  879. doc='check for updates inside qubes')
  880. def __init__(self, store=None, load=True, **kwargs):
  881. #: logger instance for logging global messages
  882. self.log = logging.getLogger('app')
  883. self._extensions = qubes.ext.get_extensions()
  884. #: collection of all VMs managed by this Qubes instance
  885. self.domains = VMCollection(self)
  886. #: collection of all available labels for VMs
  887. self.labels = {}
  888. #: collection of all pools
  889. self.pools = {}
  890. #: Connection to VMM
  891. self.vmm = VMMConnection()
  892. #: Information about host system
  893. self.host = QubesHost(self)
  894. if store is not None:
  895. self._store = store
  896. else:
  897. self._store = os.environ.get('QUBES_XML_PATH',
  898. os.path.join(
  899. qubes.config.system_path['qubes_base_dir'],
  900. qubes.config.system_path['qubes_store_filename']))
  901. super(Qubes, self).__init__(xml=None, **kwargs)
  902. self.__load_timestamp = None
  903. #: jinja2 environment for libvirt XML templates
  904. self.env = jinja2.Environment(
  905. loader=jinja2.FileSystemLoader('/usr/share/qubes/templates'),
  906. undefined=jinja2.StrictUndefined)
  907. if load:
  908. self.load()
  909. self.events_enabled = True
  910. @__builtin__.property
  911. def store(self):
  912. return self._store
  913. def load(self):
  914. '''Open qubes.xml
  915. :throws EnvironmentError: failure on parsing store
  916. :throws xml.parsers.expat.ExpatError: failure on parsing store
  917. :raises lxml.etree.XMLSyntaxError: on syntax error in qubes.xml
  918. '''
  919. try:
  920. fd = os.open(self._store, os.O_RDWR) # no O_CREAT
  921. except OSError as e:
  922. if e.errno != errno.ENOENT:
  923. raise
  924. raise qubes.exc.QubesException(
  925. 'Qubes XML store {!r} is missing; use qubes-create tool'.format(
  926. self._store))
  927. fh = os.fdopen(fd, 'rb')
  928. if os.name == 'posix':
  929. fcntl.lockf(fh, fcntl.LOCK_EX)
  930. elif os.name == 'nt':
  931. # pylint: disable=protected-access
  932. win32file.LockFileEx(
  933. win32file._get_osfhandle(fh.fileno()),
  934. win32con.LOCKFILE_EXCLUSIVE_LOCK,
  935. 0, -0x10000,
  936. pywintypes.OVERLAPPED())
  937. self.xml = lxml.etree.parse(fh)
  938. # stage 1: load labels and pools
  939. for node in self.xml.xpath('./labels/label'):
  940. label = Label.fromxml(node)
  941. self.labels[label.index] = label
  942. for node in self.xml.xpath('./pools/pool'):
  943. name = node.get('name')
  944. assert name, "Pool name '%s' is invalid " % name
  945. try:
  946. self.pools[name] = self._get_pool(**node.attrib)
  947. except qubes.exc.QubesException as e:
  948. self.log.error(e.message)
  949. # stage 2: load VMs
  950. for node in self.xml.xpath('./domains/domain'):
  951. # pylint: disable=no-member
  952. cls = self.get_vm_class(node.get('class'))
  953. vm = cls(self, node)
  954. vm.load_properties(load_stage=2)
  955. vm.init_log()
  956. self.domains.add(vm)
  957. if 0 not in self.domains:
  958. self.domains.add(qubes.vm.adminvm.AdminVM(
  959. self, None, qid=0, name='dom0'))
  960. # stage 3: load global properties
  961. self.load_properties(load_stage=3)
  962. # stage 4: fill all remaining VM properties
  963. for vm in self.domains:
  964. vm.load_properties(load_stage=4)
  965. # stage 5: misc fixups
  966. self.property_require('default_fw_netvm', allow_none=True)
  967. self.property_require('default_netvm', allow_none=True)
  968. self.property_require('default_template')
  969. self.property_require('clockvm', allow_none=True)
  970. self.property_require('updatevm', allow_none=True)
  971. # Disable ntpd in ClockVM - to not conflict with ntpdate (both are
  972. # using 123/udp port)
  973. if hasattr(self, 'clockvm') and self.clockvm is not None:
  974. if self.clockvm.features.get('services/ntpd', False):
  975. self.log.warning("VM set as clockvm ({!r}) has enabled 'ntpd' "
  976. "service! Expect failure when syncing time in dom0.".format(
  977. self.clockvm))
  978. else:
  979. self.clockvm.features['services/ntpd'] = ''
  980. for vm in self.domains:
  981. vm.events_enabled = True
  982. vm.fire_event('domain-load')
  983. # get a file timestamp (before closing it - still holding the lock!),
  984. # to detect whether anyone else have modified it in the meantime
  985. self.__load_timestamp = os.path.getmtime(self._store)
  986. # intentionally do not call explicit unlock
  987. fh.close()
  988. del fh
  989. def __xml__(self):
  990. element = lxml.etree.Element('qubes')
  991. element.append(self.xml_labels())
  992. element.append(self.xml_pool_configs())
  993. element.append(self.xml_properties())
  994. domains = lxml.etree.Element('domains')
  995. for vm in self.domains:
  996. domains.append(vm.__xml__())
  997. element.append(domains)
  998. return element
  999. def save(self):
  1000. '''Save all data to qubes.xml
  1001. There are several problems with saving :file:`qubes.xml` which must be
  1002. mitigated:
  1003. - Running out of disk space. No space left should not result in empty
  1004. file. This is done by writing to temporary file and then renaming.
  1005. - Attempts to write two or more files concurrently. This is done by
  1006. sophisticated locking.
  1007. :throws EnvironmentError: failure on saving
  1008. '''
  1009. while True:
  1010. fd_old = os.open(self._store, os.O_RDWR | os.O_CREAT)
  1011. if os.name == 'posix':
  1012. fcntl.lockf(fd_old, fcntl.LOCK_EX)
  1013. elif os.name == 'nt':
  1014. # pylint: disable=protected-access
  1015. overlapped = pywintypes.OVERLAPPED()
  1016. win32file.LockFileEx(
  1017. win32file._get_osfhandle(fd_old),
  1018. win32con.LOCKFILE_EXCLUSIVE_LOCK, 0, -0x10000, overlapped)
  1019. # While we were waiting for lock, someone could have unlink()ed (or
  1020. # rename()d) our file out of the filesystem. We have to ensure we
  1021. # got lock on something linked to filesystem. If not, try again.
  1022. if os.fstat(fd_old) == os.stat(self._store):
  1023. break
  1024. else:
  1025. os.close(fd_old)
  1026. if self.__load_timestamp:
  1027. current_file_timestamp = os.path.getmtime(self._store)
  1028. if current_file_timestamp != self.__load_timestamp:
  1029. os.close(fd_old)
  1030. raise qubes.exc.QubesException(
  1031. "Someone else modified qubes.xml in the meantime")
  1032. fh_new = tempfile.NamedTemporaryFile(prefix=self._store, delete=False)
  1033. lxml.etree.ElementTree(self.__xml__()).write(
  1034. fh_new, encoding='utf-8', pretty_print=True)
  1035. fh_new.flush()
  1036. os.chmod(fh_new.name, 0660)
  1037. os.chown(fh_new.name, -1, grp.getgrnam('qubes').gr_gid)
  1038. os.rename(fh_new.name, self._store)
  1039. # intentionally do not call explicit unlock to not unlock the file
  1040. # before all buffers are flushed
  1041. fh_new.close()
  1042. # update stored mtime, in case of multiple save() calls without
  1043. # loading qubes.xml again
  1044. self.__load_timestamp = os.path.getmtime(self._store)
  1045. os.close(fd_old)
  1046. @classmethod
  1047. def create_empty_store(cls, *args, **kwargs):
  1048. self = cls(*args, load=False, **kwargs)
  1049. self.labels = {
  1050. 1: Label(1, '0xcc0000', 'red'),
  1051. 2: Label(2, '0xf57900', 'orange'),
  1052. 3: Label(3, '0xedd400', 'yellow'),
  1053. 4: Label(4, '0x73d216', 'green'),
  1054. 5: Label(5, '0x555753', 'gray'),
  1055. 6: Label(6, '0x3465a4', 'blue'),
  1056. 7: Label(7, '0x75507b', 'purple'),
  1057. 8: Label(8, '0x000000', 'black'),
  1058. }
  1059. for name, config in qubes.config.defaults['pool_configs'].items():
  1060. self.pools[name] = self._get_pool(**config)
  1061. self.domains.add(
  1062. qubes.vm.adminvm.AdminVM(self, None, qid=0, name='dom0'))
  1063. self.save()
  1064. return self
  1065. def xml_labels(self):
  1066. '''Serialise labels
  1067. :rtype: lxml.etree._Element
  1068. '''
  1069. labels = lxml.etree.Element('labels')
  1070. for label in sorted(self.labels.values(), key=lambda labl: labl.index):
  1071. labels.append(label.__xml__())
  1072. return labels
  1073. def xml_pool_configs(self):
  1074. """ Helper for converting pools config to xml """
  1075. pools_xml = lxml.etree.Element('pools')
  1076. for pool in self.pools.values():
  1077. p = lxml.etree.Element('pool', **pool.config)
  1078. pools_xml.append(p)
  1079. return pools_xml
  1080. def get_vm_class(self, clsname):
  1081. '''Find the class for a domain.
  1082. Classess are registered as setuptools' entry points in ``qubes.vm``
  1083. group. Any package may supply their own classess.
  1084. :param str clsname: name of the class
  1085. :return type: class
  1086. '''
  1087. try:
  1088. return get_entry_point_one('qubes.vm', clsname)
  1089. except KeyError:
  1090. raise qubes.exc.QubesException(
  1091. 'no such VM class: {!r}'.format(clsname))
  1092. # don't catch TypeError
  1093. def add_new_vm(self, cls, qid=None, **kwargs):
  1094. '''Add new Virtual Machine to colletion
  1095. '''
  1096. if qid is None:
  1097. qid = self.domains.get_new_unused_qid()
  1098. # handle default template; specifically allow template=None (do not
  1099. # override it with default template)
  1100. if 'template' not in kwargs and hasattr(cls, 'template'):
  1101. kwargs['template'] = self.default_template
  1102. return self.domains.add(cls(self, None, qid=qid, **kwargs))
  1103. def get_label(self, label):
  1104. '''Get label as identified by index or name
  1105. :throws KeyError: when label is not found
  1106. '''
  1107. # first search for index, verbatim
  1108. try:
  1109. return self.labels[label]
  1110. except KeyError:
  1111. pass
  1112. # then search for name
  1113. for i in self.labels.values():
  1114. if i.name == label:
  1115. return i
  1116. # last call, if label is a number represented as str, search in indices
  1117. try:
  1118. return self.labels[int(label)]
  1119. except (KeyError, ValueError):
  1120. pass
  1121. raise KeyError(label)
  1122. def add_pool(self, **kwargs):
  1123. """ Add a storage pool to config."""
  1124. name = kwargs['name']
  1125. self.pools[name] = self._get_pool(**kwargs)
  1126. self.save()
  1127. def remove_pool(self, name):
  1128. """ Remove a storage pool from config file. """
  1129. try:
  1130. del self.pools[name]
  1131. except KeyError:
  1132. return
  1133. self.save()
  1134. def get_pool(self, name):
  1135. ''' Returns a :py:class:`qubes.storage.Pool` instance '''
  1136. try:
  1137. return self.pools[name]
  1138. except KeyError:
  1139. raise qubes.exc.QubesException('Unknown storage pool ' + name)
  1140. def _get_pool(self, **kwargs):
  1141. try:
  1142. name = kwargs['name']
  1143. assert name, 'Name needs to be an non empty string'
  1144. except KeyError:
  1145. raise qubes.exc.QubesException('No pool name for pool')
  1146. try:
  1147. driver = kwargs['driver']
  1148. except KeyError:
  1149. raise qubes.exc.QubesException('No driver specified for pool ' +
  1150. name)
  1151. try:
  1152. klass = qubes.get_entry_point_one(
  1153. qubes.storage.STORAGE_ENTRY_POINT, driver)
  1154. del kwargs['driver']
  1155. return klass(**kwargs)
  1156. except KeyError:
  1157. raise qubes.exc.QubesException('Driver %s for pool %s' %
  1158. (driver, name))
  1159. @qubes.events.handler('domain-pre-delete')
  1160. def on_domain_pre_deleted(self, event, vm):
  1161. # pylint: disable=unused-argument
  1162. if isinstance(vm, qubes.vm.templatevm.TemplateVM):
  1163. appvms = self.domains.get_vms_based_on(vm)
  1164. if appvms:
  1165. raise qubes.exc.QubesException(
  1166. 'Cannot remove template that has dependent AppVMs. '
  1167. 'Affected are: {}'.format(', '.join(
  1168. vm.name for name in sorted(appvms))))
  1169. @qubes.events.handler('domain-delete')
  1170. def on_domain_deleted(self, event, vm):
  1171. # pylint: disable=unused-argument
  1172. for propname in (
  1173. 'default_netvm',
  1174. 'default_fw_netvm',
  1175. 'clockvm',
  1176. 'updatevm',
  1177. 'default_template',
  1178. ):
  1179. try:
  1180. if getattr(self, propname) == vm:
  1181. delattr(self, propname)
  1182. except AttributeError:
  1183. pass
  1184. @qubes.events.handler('property-pre-set:clockvm')
  1185. def on_property_pre_set_clockvm(self, event, name, newvalue, oldvalue=None):
  1186. # pylint: disable=unused-argument,no-self-use
  1187. if newvalue is None:
  1188. return
  1189. if newvalue.features.get('services/ntpd', False):
  1190. raise qubes.exc.QubesVMError(newvalue,
  1191. 'Cannot set {!r} as {!r} since it has ntpd enabled.'.format(
  1192. newvalue.name, name))
  1193. else:
  1194. newvalue.features['services/ntpd'] = ''
  1195. @qubes.events.handler(
  1196. 'property-pre-set:default_netvm',
  1197. 'property-pre-set:default_fw_netvm')
  1198. def on_property_pre_set_default_netvm(self, event, name, newvalue,
  1199. oldvalue=None):
  1200. # pylint: disable=unused-argument,invalid-name
  1201. if newvalue is not None and oldvalue is not None \
  1202. and oldvalue.is_running() and not newvalue.is_running() \
  1203. and self.domains.get_vms_connected_to(oldvalue):
  1204. raise qubes.exc.QubesVMNotRunningError(newvalue,
  1205. 'Cannot change {!r} to domain that '
  1206. 'is not running ({!r}).'.format(name, newvalue.name))
  1207. @qubes.events.handler('property-set:default_fw_netvm')
  1208. def on_property_set_default_fw_netvm(self, event, name, newvalue,
  1209. oldvalue=None):
  1210. # pylint: disable=unused-argument,invalid-name
  1211. for vm in self.domains:
  1212. if not vm.provides_network and vm.property_is_default('netvm'):
  1213. # fire property-del:netvm as it is responsible for resetting
  1214. # netvm to it's default value
  1215. vm.fire_event('property-del:netvm', 'netvm', newvalue, oldvalue)
  1216. @qubes.events.handler('property-set:default_netvm')
  1217. def on_property_set_default_netvm(self, event, name, newvalue,
  1218. oldvalue=None):
  1219. # pylint: disable=unused-argument
  1220. for vm in self.domains:
  1221. if vm.provides_network and vm.property_is_default('netvm'):
  1222. # fire property-del:netvm as it is responsible for resetting
  1223. # netvm to it's default value
  1224. vm.fire_event('property-del:netvm', 'netvm', newvalue, oldvalue)