__init__.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
  1. #!/usr/bin/python2 -O
  2. # -*- coding: utf-8 -*-
  3. from __future__ import absolute_import
  4. '''
  5. Qubes OS
  6. :copyright: © 2010-2014 Invisible Things Lab
  7. '''
  8. __author__ = 'Invisible Things Lab'
  9. __license__ = 'GPLv2 or later'
  10. __version__ = 'R3'
  11. import ast
  12. import atexit
  13. import collections
  14. import grp
  15. import os
  16. import os.path
  17. import sys
  18. import tempfile
  19. import time
  20. import warnings
  21. import __builtin__
  22. import lxml.etree
  23. import xml.parsers.expat
  24. if os.name == 'posix':
  25. import fcntl
  26. elif os.name == 'nt':
  27. import win32con
  28. import win32file
  29. import pywintypes
  30. else:
  31. raise RuntimeError, "Qubes works only on POSIX or WinNT systems"
  32. import libvirt
  33. try:
  34. import xen.lowlevel.xs
  35. except ImportError:
  36. pass
  37. #: FIXME documentation
  38. MAX_QID = 253
  39. #: FIXME documentation
  40. MAX_NETID = 253
  41. class QubesException(Exception):
  42. '''Exception that can be shown to the user'''
  43. pass
  44. class QubesVMMConnection(object):
  45. '''Connection to Virtual Machine Manager (libvirt)'''
  46. def __init__(self):
  47. self._libvirt_conn = None
  48. self._xs = None
  49. self._xc = None
  50. self._offline_mode = False
  51. @__builtin__.property
  52. def offline_mode(self):
  53. '''Check or enable offline mode (do not actually connect to vmm)'''
  54. return self._offline_mode
  55. @offline_mode.setter
  56. def offline_mode(self, value):
  57. if value and self._libvirt_conn is not None:
  58. raise QubesException("Cannot change offline mode while already connected")
  59. self._offline_mode = value
  60. def _libvirt_error_handler(self, ctx, error):
  61. pass
  62. def init_vmm_connection(self):
  63. '''Initialise connection
  64. This method is automatically called when getting'''
  65. if self._libvirt_conn is not None:
  66. # Already initialized
  67. return
  68. if self._offline_mode:
  69. # Do not initialize in offline mode
  70. raise QubesException("VMM operations disabled in offline mode")
  71. if 'xen.lowlevel.xs' in sys.modules:
  72. self._xs = xen.lowlevel.xs.xs()
  73. self._libvirt_conn = libvirt.open(defaults['libvirt_uri'])
  74. if self._libvirt_conn == None:
  75. raise QubesException("Failed connect to libvirt driver")
  76. libvirt.registerErrorHandler(self._libvirt_error_handler, None)
  77. atexit.register(self._libvirt_conn.close)
  78. @__builtin__.property
  79. def libvirt_conn(self):
  80. '''Connection to libvirt'''
  81. self.init_vmm_connection()
  82. return self._libvirt_conn
  83. @__builtin__.property
  84. def xs(self):
  85. '''Connection to Xen Store
  86. This property in available only when running on Xen.'''
  87. if 'xen.lowlevel.xs' not in sys.modules:
  88. return None
  89. self.init_vmm_connection()
  90. return self._xs
  91. vmm = QubesVMMConnection()
  92. class QubesHost(object):
  93. '''Basic information about host machine'''
  94. def __init__(self):
  95. (model, memory, cpus, mhz, nodes, socket, cores, threads) = vmm.libvirt_conn.getInfo()
  96. self._total_mem = long(memory)*1024
  97. self._no_cpus = cpus
  98. # print "QubesHost: total_mem = {0}B".format (self.xen_total_mem)
  99. # print "QubesHost: free_mem = {0}".format (self.get_free_xen_memory())
  100. # print "QubesHost: total_cpus = {0}".format (self.xen_no_cpus)
  101. @__builtin__.property
  102. def memory_total(self):
  103. '''Total memory, in bytes'''
  104. return self._total_mem
  105. @__builtin__.property
  106. def no_cpus(self):
  107. '''Noumber of CPUs'''
  108. return self._no_cpus
  109. # TODO
  110. def get_free_xen_memory(self):
  111. ret = self.physinfo['free_memory']
  112. return long(ret)
  113. # TODO
  114. def measure_cpu_usage(self, previous=None, previous_time = None,
  115. wait_time=1):
  116. """measure cpu usage for all domains at once"""
  117. if previous is None:
  118. previous_time = time.time()
  119. previous = {}
  120. info = vmm.xc.domain_getinfo(0, qubes_max_qid)
  121. for vm in info:
  122. previous[vm['domid']] = {}
  123. previous[vm['domid']]['cpu_time'] = (
  124. vm['cpu_time'] / vm['online_vcpus'])
  125. previous[vm['domid']]['cpu_usage'] = 0
  126. time.sleep(wait_time)
  127. current_time = time.time()
  128. current = {}
  129. info = vmm.xc.domain_getinfo(0, qubes_max_qid)
  130. for vm in info:
  131. current[vm['domid']] = {}
  132. current[vm['domid']]['cpu_time'] = (
  133. vm['cpu_time'] / max(vm['online_vcpus'], 1))
  134. if vm['domid'] in previous.keys():
  135. current[vm['domid']]['cpu_usage'] = (
  136. float(current[vm['domid']]['cpu_time'] -
  137. previous[vm['domid']]['cpu_time']) /
  138. long(1000**3) / (current_time-previous_time) * 100)
  139. if current[vm['domid']]['cpu_usage'] < 0:
  140. # VM has been rebooted
  141. current[vm['domid']]['cpu_usage'] = 0
  142. else:
  143. current[vm['domid']]['cpu_usage'] = 0
  144. return (current_time, current)
  145. class Label(object):
  146. '''Label definition for virtual machines
  147. Label specifies colour of the padlock displayed next to VM's name.
  148. When this is a :py:class:`qubes.vm.dispvm.DispVM`, padlock is overlayed
  149. with recycling pictogram.
  150. :param int index: numeric identificator of label
  151. :param str color: colour specification as in HTML (``#abcdef``)
  152. :param str name: label's name like "red" or "green"
  153. '''
  154. def __init__(self, index, color, name):
  155. #: numeric identificator of label
  156. self.index = index
  157. #: colour specification as in HTML (``#abcdef``)
  158. self.color = color
  159. #: label's name like "red" or "green"
  160. self.name = name
  161. #: freedesktop icon name, suitable for use in :py:meth:`PyQt4.QtGui.QIcon.fromTheme`
  162. self.icon = 'appvm-' + name
  163. #: freedesktop icon name, suitable for use in :py:meth:`PyQt4.QtGui.QIcon.fromTheme`
  164. #: on DispVMs
  165. self.icon_dispvm = 'dispvm-' + name
  166. @classmethod
  167. def fromxml(cls, xml):
  168. '''Create label definition from XML node
  169. :param lxml.etree._Element xml: XML node reference
  170. :rtype: :py:class:`qubes.Label`
  171. '''
  172. index = int(xml.get('id').split('-', 1)[1])
  173. color = xml.get('color')
  174. name = xml.text
  175. return cls(index, color, name)
  176. def __xml__(self):
  177. element = lxml.etree.Element('label', id='label-' + self.index, color=self.color)
  178. element.text = self.name
  179. return element
  180. def __repr__(self):
  181. return '{}({!r}, {!r}, {!r}, dispvm={!r})'.format(
  182. self.__class__.__name__,
  183. self.index,
  184. self.color,
  185. self.name)
  186. @__builtin__.property
  187. def icon_path(self):
  188. '''Icon path
  189. .. deprecated:: 2.0
  190. use :py:meth:`PyQt4.QtGui.QIcon.fromTheme` and :py:attr:`icon`
  191. '''
  192. return os.path.join(system_path['qubes_icon_dir'], self.icon) + ".png"
  193. @__builtin__.property
  194. def icon_path_dispvm(self):
  195. '''Icon path
  196. .. deprecated:: 2.0
  197. use :py:meth:`PyQt4.QtGui.QIcon.fromTheme` and :py:attr:`icon_dispvm`
  198. '''
  199. return os.path.join(system_path['qubes_icon_dir'], self.icon_dispvm) + ".png"
  200. class VMCollection(object):
  201. '''A collection of Qubes VMs
  202. VMCollection supports ``in`` operator. You may test for ``qid``, ``name``
  203. and whole VM object's presence.
  204. Iterating over VMCollection will yield machine objects.
  205. '''
  206. def __init__(self, app):
  207. self.app = app
  208. self._dict = dict()
  209. def __repr__(self):
  210. return '<{} {!r}>'.format(self.__class__.__name__, list(sorted(self.keys())))
  211. def items(self):
  212. '''Iterate over ``(qid, vm)`` pairs'''
  213. for qid in self.qids():
  214. yield (qid, self[qid])
  215. def qids(self):
  216. '''Iterate over all qids
  217. qids are sorted by numerical order.
  218. '''
  219. return iter(sorted(self._dict.keys()))
  220. keys = qids
  221. def names(self):
  222. '''Iterate over all names
  223. names are sorted by lexical order.
  224. '''
  225. return iter(sorted(vm.name for vm in self._dict.values()))
  226. def vms(self):
  227. '''Iterate over all machines
  228. vms are sorted by qid.
  229. '''
  230. return iter(sorted(self._dict.values()))
  231. __iter__ = vms
  232. values = vms
  233. def add(self, value):
  234. '''Add VM to collection
  235. :param qubes.vm.BaseVM value: VM to add
  236. :raises TypeError: when value is of wrong type
  237. :raises ValueError: when there is already VM which has equal ``qid``
  238. '''
  239. # XXX this violates duck typing, should we do it?
  240. if not isinstance(value, qubes.vm.BaseVM):
  241. raise TypeError('{} holds only BaseVM instances'.format(self.__class__.__name__))
  242. if value.qid in self:
  243. raise ValueError('This collection already holds VM that has qid={!r} (!r)'.format(
  244. value.qid, self[value.qid]))
  245. if value.name in self:
  246. raise ValueError('This collection already holds VM that has name={!r} (!r)'.format(
  247. value.name, self[value.name]))
  248. self._dict[value.qid] = value
  249. def __getitem__(self, key):
  250. if isinstance(key, int):
  251. return self._dict[key]
  252. if isinstance(key, basestring):
  253. for vm in self:
  254. if (vm.name == key):
  255. return vm
  256. raise KeyError(key)
  257. if isinstance(key, qubes.vm.BaseVM):
  258. if key in self:
  259. return key
  260. raise KeyError(key)
  261. raise KeyError(key)
  262. def __delitem__(self, key):
  263. del self._dict[self[key].qid]
  264. def __contains__(self, key):
  265. return any((key == vm or key == vm.qid or key == vm.name) for vm in self)
  266. def __len__(self):
  267. return len(self._dict)
  268. def get_vms_based_on(self, template):
  269. template = self[template]
  270. return set(vm for vm in self if vm.template == template)
  271. def get_vms_connected_to(self, netvm):
  272. new_vms = set([netvm])
  273. dependend_vms = set()
  274. # Dependency resolving only makes sense on NetVM (or derivative)
  275. # if not self[netvm_qid].is_netvm():
  276. # return set([])
  277. while len(new_vms) > 0:
  278. cur_vm = new_vms.pop()
  279. for vm in cur_vm.connected_vms.values():
  280. if vm in dependend_vms:
  281. continue
  282. dependend_vms.add(vm.qid)
  283. # if vm.is_netvm():
  284. new_vms.append(vm.qid)
  285. return dependent_vms
  286. # XXX with Qubes Admin Api this will probably lead to race condition
  287. # whole process of creating and adding should be synchronised
  288. def get_new_unused_qid(self):
  289. used_ids = set(self.qids())
  290. for i in range(1, MAX_QID):
  291. if i not in used_ids:
  292. return i
  293. raise LookupError("Cannot find unused qid!")
  294. def get_new_unused_netid(self):
  295. used_ids = set([vm.netid for vm in self]) # if vm.is_netvm()])
  296. for i in range(1, MAX_NETID):
  297. if i not in used_ids:
  298. return i
  299. raise LookupError("Cannot find unused netid!")
  300. class property(object):
  301. '''Qubes property.
  302. This class holds one property that can be saved to and loaded from
  303. :file:`qubes.xml`. It is used for both global and per-VM properties.
  304. :param str name: name of the property
  305. :param collections.Callable setter: if not :py:obj:`None`, this is used to initialise value; first parameter to the function is holder instance and the second is value; this is called before ``type``
  306. :param type type: if not :py:obj:`None`, value is coerced to this type
  307. :param object default: default value
  308. :param int load_stage: stage when property should be loaded (see :py:class:`Qubes` for description of stages)
  309. :param int order: order of evaluation (bigger order values are later)
  310. :param str doc: docstring; you may use RST markup
  311. '''
  312. def __init__(self, name, setter=None, type=None, default=None,
  313. load_stage=2, order=0, save_via_ref=False, doc=None):
  314. self.__name__ = name
  315. self._setter = setter
  316. self._type = type
  317. self._default = default
  318. self.order = order
  319. self.load_stage = load_stage
  320. self.save_via_ref = save_via_ref
  321. self.__doc__ = doc
  322. self._attr_name = '_qubesprop_' + name
  323. def __get__(self, instance, owner):
  324. # sys.stderr.write('{!r}.__get__({}, {!r})\n'.format(self.__name__, hex(id(instance)), owner))
  325. if instance is None:
  326. return self
  327. # XXX this violates duck typing, shall we keep it?
  328. if not isinstance(instance, PropertyHolder):
  329. raise AttributeError(
  330. 'qubes.property should be used on qubes.PropertyHolder instances only')
  331. # sys.stderr.write(' __get__ try\n')
  332. try:
  333. return getattr(instance, self._attr_name)
  334. except AttributeError:
  335. # sys.stderr.write(' __get__ except\n')
  336. if self._default is None:
  337. raise AttributeError('property {!r} not set'.format(self.__name__))
  338. elif isinstance(self._default, collections.Callable):
  339. return self._default(instance)
  340. else:
  341. return self._default
  342. def __set__(self, instance, value):
  343. if self._setter is not None:
  344. value = self._setter(instance, self, value)
  345. if self._type is not None:
  346. value = self._type(value)
  347. instance._init_property(self, value)
  348. def __repr__(self):
  349. return '<{} object at {:#x} name={!r} default={!r}>'.format(
  350. self.__class__.__name__, id(self), self.__name__, self._default)
  351. def __hash__(self):
  352. return hash(self.__name__)
  353. def __eq__(self, other):
  354. return self.__name__ == other.__name__
  355. #
  356. # some setters provided
  357. #
  358. @staticmethod
  359. def forbidden(self, prop, value):
  360. '''Property setter that forbids loading a property
  361. This is used to effectively disable property in classes which inherit
  362. unwanted property. When someone attempts to load such a property, it
  363. :throws AttributeError: always
  364. '''
  365. raise AttributeError('setting {} property on {} instance is forbidden'.format(
  366. prop.__name__, self.__class__.__name__))
  367. class PropertyHolder(object):
  368. '''Abstract class for holding :py:class:`qubes.property`'''
  369. def __init__(self, xml, *args, **kwargs):
  370. super(PropertyHolder, self).__init__(*args, **kwargs)
  371. self.xml = xml
  372. def get_props_list(self, load_stage=None):
  373. '''List all properties attached to this VM
  374. :param load_stage: Filter by load stage
  375. :type load_stage: :py:func:`int` or :py:obj:`None`
  376. '''
  377. # sys.stderr.write('{!r}.get_props_list(load_stage={})\n'.format('self', load_stage))
  378. props = set()
  379. for class_ in self.__class__.__mro__:
  380. props.update(prop for prop in class_.__dict__.values()
  381. if isinstance(prop, property))
  382. if load_stage is not None:
  383. props = set(prop for prop in props
  384. if prop.load_stage == load_stage)
  385. # sys.stderr.write(' props={!r}\n'.format(props))
  386. return sorted(props, key=lambda prop: (prop.load_stage, prop.order, prop.__name__))
  387. def _init_property(self, prop, value):
  388. '''Initialise property to a given value, without side effects.
  389. :param qubes.property prop: property object of particular interest
  390. :param value: value
  391. '''
  392. setattr(self, prop._attr_name, value)
  393. def load_properties(self, load_stage=None):
  394. '''Load properties from immediate children of XML node.
  395. :param lxml.etree._Element xml: XML node reference
  396. '''
  397. # sys.stderr.write('<{}>.load_properties(load_stage={}) xml={!r}\n'.format(hex(id(self)), load_stage, self.xml))
  398. all_names = set(prop.__name__ for prop in self.get_props_list(load_stage))
  399. # sys.stderr.write(' all_names={!r}\n'.format(all_names))
  400. for node in self.xml.xpath('./properties/property'):
  401. name = node.get('name')
  402. value = node.get('ref') or node.text
  403. # sys.stderr.write(' load_properties name={!r} value={!r}\n'.format(name, value))
  404. if not name in all_names:
  405. raise AttributeError(
  406. 'No property {!r} found in {!r}'.format(
  407. name, self.__class__))
  408. setattr(self, name, value)
  409. # sys.stderr.write(' load_properties return\n')
  410. def save_properties(self, with_defaults=False):
  411. '''Iterator that yields XML nodes representing set properties.
  412. :param bool with_defaults: If :py:obj:`True`, then it also includes properties which were not set explicite, but have default values filled.
  413. '''
  414. # sys.stderr.write('{!r}.save_properties(with_defaults={})\n'.format(self, with_defaults))
  415. properties = lxml.etree.Element('properties')
  416. for prop in self.get_props_list():
  417. try:
  418. value = str(getattr(self, (prop.__name__ if with_defaults else prop._attr_name)))
  419. except AttributeError, e:
  420. # sys.stderr.write('AttributeError: {!s}\n'.format(e))
  421. continue
  422. element = lxml.etree.Element('property', name=prop.__name__)
  423. if prop.save_via_ref:
  424. element.set('ref', value)
  425. else:
  426. element.text = value
  427. properties.append(element)
  428. return properties
  429. import qubes.vm.qubesvm
  430. import qubes.vm.templatevm
  431. class VMProperty(property):
  432. '''Property that is referring to a VM
  433. :param type vmclass: class that returned VM is supposed to be instance of
  434. and all supported by :py:class:`property` with the exception of ``type`` and ``setter``
  435. '''
  436. def __init__(self, name, vmclass=qubes.vm.BaseVM, **kwargs):
  437. if 'type' in kwargs:
  438. raise TypeError("'type' keyword parameter is unsupported in {}".format(
  439. self.__class__.__name__))
  440. if 'setter' in kwargs:
  441. raise TypeError("'setter' keyword parameter is unsupported in {}".format(
  442. self.__class__.__name__))
  443. super(VMProperty, self).__init__(name, **kwargs)
  444. self.vmclass = vmclass
  445. def __set__(self, instance, value):
  446. vm = instance.app.domains[value]
  447. if not isinstance(vm, self.vmclass):
  448. raise TypeError('wrong VM class: domains[{!r}] if of type {!s} and not {!s}'.format(
  449. value, vm.__class__.__name__, self.vmclass.__name__))
  450. super(VMProperty, self).__set__(self, instance, vm)
  451. class Qubes(PropertyHolder):
  452. '''Main Qubes application
  453. :param str store: path to ``qubes.xml``
  454. The store is loaded in stages.
  455. In the first stage there are loaded some basic features from store
  456. (currently labels).
  457. In the second stage stubs for all VMs are loaded. They are filled with
  458. their basic properties, like ``qid`` and ``name``.
  459. In the third stage all global properties are loaded. They often reference
  460. VMs, like default netvm, so they should be filled after loading VMs.
  461. In the fourth stage all remaining VM properties are loaded. They also need
  462. all VMs loaded, because they represent dependencies between VMs like
  463. aforementioned netvm.
  464. In the fifth stage there are some fixups to ensure sane system operation.
  465. '''
  466. default_netvm = VMProperty('default_netvm', load_stage=3,
  467. doc='Default NetVM for new AppVMs')
  468. default_fw_netvm = VMProperty('default_fw_netvm', load_stage=3,
  469. doc='Default NetVM for new ProxyVMs')
  470. default_template = VMProperty('default_template', load_stage=3,
  471. vmclass=qubes.vm.templatevm.TemplateVM,
  472. doc='Default template for new AppVMs')
  473. updatevm = VMProperty('updatevm', load_stage=3,
  474. doc='Which VM to use as ``yum`` proxy for updating AdminVM and TemplateVMs')
  475. clockvm = VMProperty('clockvm', load_stage=3,
  476. doc='Which VM to use as NTP proxy for updating AdminVM')
  477. default_kernel = property('default_kernel', load_stage=3,
  478. doc='Which kernel to use when not overriden in VM')
  479. def __init__(self, store='/var/lib/qubes/qubes.xml'):
  480. #: collection of all VMs managed by this Qubes instance
  481. self.domains = VMCollection()
  482. #: collection of all available labels for VMs
  483. self.labels = {}
  484. self._store = store
  485. try:
  486. self.load()
  487. except IOError:
  488. self._init()
  489. super(PropertyHolder, self).__init__(xml=lxml.etree.parse(self.qubes_store_file))
  490. def _open_store(self):
  491. if hasattr(self, '_storefd'):
  492. return
  493. self._storefd = open(self._store, 'r+')
  494. if os.name == 'posix':
  495. fcntl.lockf (self.qubes_store_file, fcntl.LOCK_EX)
  496. elif os.name == 'nt':
  497. overlapped = pywintypes.OVERLAPPED()
  498. win32file.LockFileEx(win32file._get_osfhandle(self.qubes_store_file.fileno()),
  499. win32con.LOCKFILE_EXCLUSIVE_LOCK, 0, -0x10000, overlapped)
  500. def load(self):
  501. '''
  502. :throws EnvironmentError: failure on parsing store
  503. :throws xml.parsers.expat.ExpatError: failure on parsing store
  504. '''
  505. self._open_store()
  506. # stage 1: load labels
  507. for node in self._xml.xpath('./labels/label'):
  508. label = Label.fromxml(node)
  509. self.labels[label.id] = label
  510. # stage 2: load VMs
  511. for node in self._xml.xpath('./domains/domain'):
  512. cls = qubes.vm.load(node.get("class"))
  513. vm = cls.fromxml(self, node)
  514. self.domains.add(vm)
  515. if not 0 in self.domains:
  516. self.domains.add(qubes.vm.adminvm.AdminVM(self))
  517. # stage 3: load global properties
  518. self.load_properties(self.xml, load_stage=3)
  519. # stage 4: fill all remaining VM properties
  520. for vm in self.domains:
  521. vm.load_properties(None, load_stage=4)
  522. # stage 5: misc fixups
  523. # if we have no default netvm, make first one the default
  524. if not hasattr(self, 'default_netvm'):
  525. for vm in self.domains:
  526. if hasattr(vm, 'provides_network') and hasattr(vm, 'netvm'):
  527. self.default_netvm = vm
  528. break
  529. if not hasattr(self, 'default_fw_netvm'):
  530. for vm in self.domains:
  531. if hasattr(vm, 'provides_network') and not hasattr(vm, 'netvm'):
  532. self.default_netvm = vm
  533. break
  534. # first found template vm is the default
  535. if not hasattr(self, 'default_template'):
  536. for vm in self.domains:
  537. if isinstance(vm, qubes.vm.templatevm.TemplateVM):
  538. self.default_template = vm
  539. break
  540. # if there was no clockvm entry in qubes.xml, try to determine default:
  541. # root of default NetVM chain
  542. if not hasattr(self, 'clockvm') and hasattr(self, 'default_netvm'):
  543. clockvm = self.default_netvm
  544. # Find root of netvm chain
  545. while clockvm.netvm is not None:
  546. clockvm = clockvm.netvm
  547. self.clockvm = clockvm
  548. # Disable ntpd in ClockVM - to not conflict with ntpdate (both are
  549. # using 123/udp port)
  550. if hasattr(self, 'clockvm'):
  551. self.clockvm.services['ntpd'] = False
  552. def _init(self):
  553. self._open_store()
  554. self.labels = {
  555. 1: Label(1, '0xcc0000', 'red'),
  556. 2: Label(2, '0xf57900', 'orange'),
  557. 3: Label(3, '0xedd400', 'yellow'),
  558. 4: Label(4, '0x73d216', 'green'),
  559. 5: Label(5, '0x555753', 'gray'),
  560. 6: Label(6, '0x3465a4', 'blue'),
  561. 7: Label(7, '0x75507b', 'purple'),
  562. 8: Label(8, '0x000000', 'black'),
  563. }
  564. def __del__(self):
  565. # intentionally do not call explicit unlock to not unlock the file
  566. # before all buffers are flushed
  567. self._storefd.close()
  568. del self._storefd
  569. def __xml__(self):
  570. element = lxml.etree.Element('qubes')
  571. element.append(self.save_labels())
  572. element.append(self.save_properties())
  573. domains = lxml.etree.Element('domains')
  574. for vm in self.domains:
  575. domains.append(vm.__xml__())
  576. element.append(domains)
  577. return element
  578. def save(self):
  579. '''Save all data to qubes.xml
  580. '''
  581. self._storefd.seek(0)
  582. self._storefd.truncate()
  583. lxml.etree.ElementTree(self.__xml__()).write(
  584. self._storefd, encoding='utf-8', pretty_print=True)
  585. self._storefd.sync()
  586. os.chmod(self._store, 0660)
  587. os.chown(self._store, -1, grp.getgrnam('qubes').gr_gid)
  588. def save_labels(self):
  589. '''Serialise labels
  590. :rtype: lxml.etree._Element
  591. '''
  592. labels = lxml.etree.Element('labels')
  593. for label in self.labels:
  594. labels.append(label.__xml__())
  595. return labels
  596. def add_new_vm(self, vm):
  597. '''Add new Virtual Machine to colletion
  598. '''
  599. if not hasattr(vm, 'qid'):
  600. vm.qid = self.domains.get_new_unused_qid()
  601. self.domains.add(vm)
  602. #
  603. # XXX
  604. # all this will be moved to an event handler
  605. # and deduplicated with self.load()
  606. #
  607. # make first created NetVM the default one
  608. if not hasattr(self, 'default_fw_netvm') \
  609. and vm.provides_network \
  610. and not hasattr(vm, 'netvm'):
  611. self.default_fw_netvm = vm
  612. if not hasattr(self, 'default_netvm') \
  613. and vm.provides_network \
  614. and hasattr(vm, 'netvm'):
  615. self.default_netvm = vm
  616. # make first created TemplateVM the default one
  617. if not hasattr(self, 'default_template') \
  618. and not hasattr(vm, 'template'):
  619. self.default_template = vm
  620. # make first created ProxyVM the UpdateVM
  621. if not hasattr(self, 'default_netvm') \
  622. and vm.provides_network \
  623. and hasattr(vm, 'netvm'):
  624. self.updatevm = vm
  625. # by default ClockVM is the first NetVM
  626. if not hasattr(self, 'clockvm') \
  627. and vm.provides_network \
  628. and hasattr(vm, 'netvm'):
  629. self.default_clockvm = vm
  630. # XXX don't know if it should return self
  631. return vm
  632. # XXX This was in QubesVmCollection, will be in an event
  633. # def pop(self, qid):
  634. # if self.default_netvm_qid == qid:
  635. # self.default_netvm_qid = None
  636. # if self.default_fw_netvm_qid == qid:
  637. # self.default_fw_netvm_qid = None
  638. # if self.clockvm_qid == qid:
  639. # self.clockvm_qid = None
  640. # if self.updatevm_qid == qid:
  641. # self.updatevm_qid = None
  642. # if self.default_template_qid == qid:
  643. # self.default_template_qid = None
  644. #
  645. # return super(QubesVmCollection, self).pop(qid)
  646. # load plugins
  647. import qubes._pluginloader