diff --git a/qubes/vm/__init__.py b/qubes/vm/__init__.py index d75d7c03..7ccf4c9f 100644 --- a/qubes/vm/__init__.py +++ b/qubes/vm/__init__.py @@ -28,13 +28,8 @@ ''' -import ast -import collections import datetime -import functools -import itertools import os -import re import subprocess import sys import xml.parsers.expat @@ -125,7 +120,9 @@ class Features(dict): # _NO_DEFAULT = object() + def check_with_template(self, feature, default=_NO_DEFAULT): + ''' Check if the vm's template has the specified feature. ''' if feature in self: return self[feature] @@ -198,14 +195,14 @@ class BaseVM(qubes.PropertyHolder): for node in xml.xpath('./tags/tag'): self.tags[node.get('name')] = node.text - # TODO: firewall, policy + # SEE:1815 firewall, policy. # check if properties are appropriate all_names = set(prop.__name__ for prop in self.property_list()) for node in self.xml.xpath('./properties/property'): name = node.get('name') - if not name in all_names: + if name not in all_names: raise TypeError( 'property {!r} not applicable to {!r}'.format( name, self.__class__.__name__)) @@ -216,12 +213,10 @@ class BaseVM(qubes.PropertyHolder): if hasattr(self, 'name'): self.init_log() - def init_log(self): '''Initialise logger for this domain.''' self.log = qubes.log.get_vm_logger(self.name) - def __xml__(self): element = lxml.etree.Element('domain') element.set('id', 'domain-' + str(self.qid)) @@ -266,54 +261,24 @@ class BaseVM(qubes.PropertyHolder): return '<{} object at {:#x} {}>'.format( self.__class__.__name__, id(self), ' '.join(proprepr)) - # # xml serialising methods # - def create_config_file(self, file_path=None, prepare_dvm=False): + def create_config_file(self, prepare_dvm=False): '''Create libvirt's XML domain config file - If :py:attr:`qubes.vm.qubesvm.QubesVM.uses_custom_config` is true, this - does nothing. - - :param str file_path: Path to file to create \ - (default: :py:attr:`qubes.vm.qubesvm.QubesVM.conf_file`) :param bool prepare_dvm: If we are in the process of preparing \ DisposableVM ''' - - if file_path is None: - file_path = self.conf_file - # TODO - # if self.uses_custom_config: - # conf_appvm = open(file_path, "r") - # domain_config = conf_appvm.read() - # conf_appvm.close() - # return domain_config - domain_config = self.app.env.get_template('libvirt/xen.xml').render( vm=self, prepare_dvm=prepare_dvm) - - # FIXME: This is only for debugging purposes - old_umask = os.umask(002) - try: - conf_appvm = open(file_path, "w") - conf_appvm.write(domain_config) - conf_appvm.close() - except: # pylint: disable=bare-except - # Ignore errors - pass - finally: - os.umask(old_umask) - return domain_config - # # firewall - # TODO rewrite it, have node under - # and possibly integrate with generic policy framework + # SEE:1815 rewrite it, have node under + # and possibly integrate with generic policy framework. # def write_firewall_conf(self, conf): @@ -366,7 +331,7 @@ class BaseVM(qubes.PropertyHolder): tree.write(fd, encoding="UTF-8", pretty_print=True) fd.close() os.umask(old_umask) - except EnvironmentError as err: # pylint: disable=broad-except + except EnvironmentError as err: # pylint: disable=broad-except print >> sys.stderr, "{0}: save error: {1}".format( os.path.basename(sys.argv[0]), err) return False @@ -385,16 +350,18 @@ class BaseVM(qubes.PropertyHolder): subprocess.call(["sudo", "systemctl", "start", "qubes-reload-firewall@%s.timer" % self.name]) - # XXX any better idea? some arguments? + # SEE:1815 any better idea? some arguments? self.fire_event('firewall-changed') return True def has_firewall(self): + ''' Return `True` if there are some vm specific firewall rules set ''' return os.path.exists(os.path.join(self.dir_path, self.firewall_conf)) @staticmethod def get_firewall_defaults(): + ''' Returns the default firewall rules ''' return { 'rules': list(), 'allow': True, @@ -403,6 +370,7 @@ class BaseVM(qubes.PropertyHolder): 'allowYumProxy': False} def get_firewall_conf(self): + ''' Returns the firewall config dictionary ''' conf = self.get_firewall_defaults() try: @@ -459,7 +427,7 @@ class BaseVM(qubes.PropertyHolder): conf["rules"].append(rule) - except EnvironmentError as err: # pylint: disable=broad-except + except EnvironmentError as err: # pylint: disable=broad-except # problem accessing file, like ENOTFOUND, EPERM or sth # return default config return conf @@ -506,7 +474,6 @@ class VMProperty(qubes.property): self.vmclass = vmclass self.allow_none = allow_none - def __set__(self, instance, value): if value is self.__class__.DEFAULT: self.__delete__(instance) diff --git a/qubes/vm/adminvm.py b/qubes/vm/adminvm.py index c4e09e2e..a67e016c 100644 --- a/qubes/vm/adminvm.py +++ b/qubes/vm/adminvm.py @@ -1,6 +1,5 @@ #!/usr/bin/python2 -O # vim: fileencoding=utf-8 - # # The Qubes OS Project, https://www.qubes-os.org/ # @@ -24,6 +23,8 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # +''' This module contains the AdminVM implementation ''' + import qubes import qubes.exc import qubes.vm.qubesvm @@ -41,7 +42,6 @@ class AdminVM(qubes.vm.qubesvm.QubesVM): default=None, doc='There are other ways to set kernel for Dom0.') - @property def xid(self): '''Always ``0``. @@ -51,7 +51,6 @@ class AdminVM(qubes.vm.qubesvm.QubesVM): ''' return 0 - @property def libvirt_domain(self): '''Always :py:obj:`None`. @@ -61,13 +60,6 @@ class AdminVM(qubes.vm.qubesvm.QubesVM): ''' return None - - # XXX probably unneeded, will return None as we don't have netvm -# @property -# def ip(self): -# return "10.137.0.2" - - def is_running(self): '''Always :py:obj:`True`. @@ -76,7 +68,6 @@ class AdminVM(qubes.vm.qubesvm.QubesVM): ''' return True - def get_power_state(self): '''Always ``'Running'``. @@ -85,7 +76,6 @@ class AdminVM(qubes.vm.qubesvm.QubesVM): ''' return 'Running' - def get_mem(self): '''Get current memory usage of Dom0. @@ -101,7 +91,6 @@ class AdminVM(qubes.vm.qubesvm.QubesVM): return int(line.split(':')[1].strip().split()[0]) raise NotImplementedError() - def get_mem_static_max(self): '''Get maximum memory available to Dom0. @@ -118,7 +107,6 @@ class AdminVM(qubes.vm.qubesvm.QubesVM): ''' return True - def start(self, **kwargs): '''Always raises an exception. @@ -127,15 +115,13 @@ class AdminVM(qubes.vm.qubesvm.QubesVM): ''' # pylint: disable=unused-argument raise qubes.exc.QubesVMError(self, 'Cannot start Dom0 fake domain!') - def suspend(self): '''Does nothing. .. seealso: :py:meth:`qubes.vm.qubesvm.QubesVM.suspend` ''' - # XXX shouldn't we spew an exception? - return + raise qubes.exc.QubesVMError(self, 'Cannot suspend Dom0 fake domain!') # def __init__(self, **kwargs): diff --git a/qubes/vm/appvm.py b/qubes/vm/appvm.py index 4e5c4178..6924d3b7 100644 --- a/qubes/vm/appvm.py +++ b/qubes/vm/appvm.py @@ -1,5 +1,28 @@ #!/usr/bin/python2 -O # vim: fileencoding=utf-8 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2014-2016 Wojtek Porczyk +# Copyright (C) 2016 Marek Marczykowski ) +# Copyright (C) 2016 Bahtiar `kalkin-` Gadimov +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +''' This module contains the AppVM implementation ''' import qubes.events import qubes.vm.qubesvm @@ -45,7 +68,6 @@ class AppVM(qubes.vm.qubesvm.QubesVM): @qubes.events.handler('domain-load') def on_domain_loaded(self, event): - # pylint: disable=unused-argument - # Some additional checks for template based VM + ''' When domain is loaded assert that this vm has a template. + ''' # pylint: disable=unused-argument assert self.template - # self.template.appvms.add(self) # XXX diff --git a/qubes/vm/dispvm.py b/qubes/vm/dispvm.py index d98c1120..6d28a705 100644 --- a/qubes/vm/dispvm.py +++ b/qubes/vm/dispvm.py @@ -1,5 +1,27 @@ #!/usr/bin/python2 -O # vim: fileencoding=utf-8 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2014-2016 Wojtek Porczyk +# Copyright (C) 2016 Marek Marczykowski ) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +''' A disposable vm implementation ''' import qubes.vm.qubesvm import qubes.vm.appvm @@ -49,11 +71,9 @@ class DispVM(qubes.vm.qubesvm.QubesVM): @qubes.events.handler('domain-load') def on_domain_loaded(self, event): - # pylint: disable=unused-argument - # Some additional checks for template based VM + ''' When domain is loaded assert that this vm has a template. + ''' # pylint: disable=unused-argument assert self.template - # self.template.appvms.add(self) # XXX - @classmethod def from_appvm(cls, appvm, **kwargs): @@ -85,7 +105,6 @@ class DispVM(qubes.vm.qubesvm.QubesVM): app.save() return dispvm - def cleanup(self): '''Clean up after the DispVM diff --git a/qubes/vm/mix/net.py b/qubes/vm/mix/net.py index c537b464..acb70c58 100644 --- a/qubes/vm/mix/net.py +++ b/qubes/vm/mix/net.py @@ -1,6 +1,5 @@ #!/usr/bin/python2 -O # vim: fileencoding=utf-8 - # # The Qubes OS Project, https://www.qubes-os.org/ # @@ -24,15 +23,18 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # +''' This module contains the NetVMMixin ''' + import re -import libvirt - +import libvirt # pylint: disable=import-error import qubes import qubes.events import qubes.exc + def _setter_mac(self, prop, value): + ''' Helper for setting the MAC address ''' # pylint: disable=unused-argument if not isinstance(value, basestring): raise ValueError('MAC address must be a string') @@ -41,14 +43,16 @@ def _setter_mac(self, prop, value): raise ValueError('Invalid MAC address value') return value + class NetVMMixin(qubes.events.Emitter): + ''' Mixin containing network functionality ''' mac = qubes.property('mac', type=str, default='00:16:3E:5E:6C:00', setter=_setter_mac, ls_width=17, doc='MAC address of the NIC emulated inside VM') - # XXX swallowed uses_default_netvm + # CORE2: swallowed uses_default_netvm netvm = qubes.VMProperty('netvm', load_stage=4, allow_none=True, default=(lambda self: self.app.default_fw_netvm if self.provides_network else self.app.default_netvm), @@ -62,7 +66,6 @@ class NetVMMixin(qubes.events.Emitter): doc='''If this domain can act as network provider (formerly known as NetVM or ProxyVM)''') - # # used in networked appvms or proxyvms (netvm is not None) # @@ -74,11 +77,10 @@ class NetVMMixin(qubes.events.Emitter): if not self.is_networked(): return None if self.netvm is not None: - return self.netvm.get_ip_for_vm(self) + return self.netvm.get_ip_for_vm(self) # pylint: disable=no-member else: return self.get_ip_for_vm(self) - # # used in netvms (provides_network=True) # those properties and methods are most likely accessed as vm.netvm. @@ -88,7 +90,7 @@ class NetVMMixin(qubes.events.Emitter): def get_ip_for_vm(vm): '''Get IP address for (appvm) domain connected to this (netvm) domain. ''' - import qubes.vm.dispvm # pylint: disable=redefined-outer-name + import qubes.vm.dispvm # pylint: disable=redefined-outer-name if isinstance(vm, qubes.vm.dispvm.DispVM): return '10.138.{}.{}'.format((vm.dispid >> 8) & 7, vm.dispid & 7) @@ -108,22 +110,11 @@ class NetVMMixin(qubes.events.Emitter): '''Netmask for gateway address.''' return '255.255.255.255' if self.is_networked() else None - @qubes.tools.qvm_ls.column(width=7) - @property - def vif(self): - '''Name of the network interface backend in netvm that is connected to - NIC inside this domain.''' - if self.xid < 0: - return None - if self.netvm is None: - return None - - # XXX ugly hack ahead - # stubdom_xid is one more than self.xid - return 'vif{0}.+'.format(self.xid + int(self.hvm)) - @property def connected_vms(self): + ''' Return a generator containing all domains connected to the current + NetVM. + ''' for vm in self.app.domains: if vm.netvm is self: yield vm @@ -144,27 +135,25 @@ class NetVMMixin(qubes.events.Emitter): else: return None - def __init__(self, *args, **kwargs): super(NetVMMixin, self).__init__(*args, **kwargs) - @qubes.events.handler('domain-start') def on_domain_started(self, event, **kwargs): '''Connect this domain to its downstream domains. Also reload firewall in its netvm. This is needed when starting netvm *after* its connected domains. - ''' # pylint: disable=unused-argument + ''' # pylint: disable=unused-argument if self.netvm: - self.netvm.reload_firewall_for_vm(self) + self.netvm.reload_firewall_for_vm(self) # pylint: disable=no-member for vm in self.connected_vms: if not vm.is_running(): continue vm.log.info('Attaching network') - # 1426 + # SEE: 1426 vm.cleanup_vifs() try: @@ -179,10 +168,12 @@ class NetVMMixin(qubes.events.Emitter): except qubes.exc.QubesException: vm.log.warning('Cannot attach network', exc_info=1) - @qubes.events.handler('domain-pre-shutdown') def shutdown_net(self, event, force=False): - # pylint: disable=unused-argument + ''' Checks before NetVM shutdown if any connected domains are running. + If `force` is `True` tries to detach network interfaces of connected + vms + ''' # pylint: disable=unused-argument connected_vms = [vm for vm in self.connected_vms if vm.is_running()] if connected_vms and not force: @@ -190,6 +181,7 @@ class NetVMMixin(qubes.events.Emitter): 'There are other VMs connected to this VM: {}'.format( ', '.join(vm.name for vm in connected_vms))) + # SEE: 1426 # detach network interfaces of connected VMs before shutting down, # otherwise libvirt will not notice it and will try to detach them # again (which would fail, obviously). @@ -202,7 +194,6 @@ class NetVMMixin(qubes.events.Emitter): # ignore errors pass - def attach_network(self): '''Attach network in this machine to it's netvm.''' @@ -210,7 +201,8 @@ class NetVMMixin(qubes.events.Emitter): raise qubes.exc.QubesVMNotRunningError(self) assert self.netvm is not None - if not self.netvm.is_running(): + if not self.netvm.is_running(): # pylint: disable=no-member + # pylint: disable=no-member self.log.info('Starting NetVM ({0})'.format(self.netvm.name)) self.netvm.start() @@ -218,7 +210,6 @@ class NetVMMixin(qubes.events.Emitter): self.app.env.get_template('libvirt/devices/net.xml').render( vm=self)) - def detach_network(self): '''Detach machine from it's netvm''' @@ -230,7 +221,6 @@ class NetVMMixin(qubes.events.Emitter): self.app.env.get_template('libvirt/devices/net.xml').render( vm=self)) - def is_networked(self): '''Check whether this VM can reach network (firewall notwithstanding). @@ -244,7 +234,6 @@ class NetVMMixin(qubes.events.Emitter): return self.netvm is not None - def cleanup_vifs(self): '''Remove stale network device backends. @@ -252,10 +241,6 @@ class NetVMMixin(qubes.events.Emitter): it manually. This method is one big hack for #1426. ''' - # FIXME: remove this? - if not self.is_running(): - return - dev_basepath = '/local/domain/%d/device/vif' % self.xid for dev in self.app.vmm.xs.ls('', dev_basepath): # check if backend domain is alive @@ -270,11 +255,13 @@ class NetVMMixin(qubes.events.Emitter): self.app.vmm.xs.rm('', '{}/{}'.format(dev_basepath, dev)) def reload_firewall_for_vm(self, vm): - # TODO QubesOS/qubes-issues#1815 + ''' Reload the firewall rules for the vm ''' + # SEE:1815 pass @qubes.events.handler('property-del:netvm') def on_property_del_netvm(self, event, prop, old_netvm=None): + ''' Sets the the NetVM to default NetVM ''' # pylint: disable=unused-argument # we are changing to default netvm new_netvm = self.netvm @@ -282,9 +269,9 @@ class NetVMMixin(qubes.events.Emitter): return self.fire_event('property-set:netvm', 'netvm', new_netvm, old_netvm) - @qubes.events.handler('property-pre-set:netvm') def on_property_pre_set_netvm(self, event, name, new_netvm, old_netvm=None): + ''' Run sanity checks before setting a new NetVM ''' # pylint: disable=unused-argument if new_netvm is None: return @@ -297,25 +284,24 @@ class NetVMMixin(qubes.events.Emitter): or new_netvm in self.app.domains.get_vms_connected_to(self): raise qubes.exc.QubesValueError('Loops in network are unsupported') - # TODO offline_mode - if self.is_running() and not new_netvm.is_running(): + if not self.app.vmm.offline_mod and self.is_running() \ + and not new_netvm.is_running(): + raise qubes.exc.QubesVMNotStartedError(new_netvm, 'Cannot dynamically attach to stopped NetVM: {!r}'.format( new_netvm)) - @qubes.events.handler('property-set:netvm') def on_property_set_netvm(self, event, name, new_netvm, old_netvm=None): + ''' Replaces the current NetVM with a new one and fires + net-domain-connect event + ''' # pylint: disable=unused-argument if self.netvm is not None: if self.is_running(): self.detach_network() - # TODO change to domain-removed event handler in netvm -# if hasattr(self.netvm, 'post_vm_net_detach'): -# self.netvm.post_vm_net_detach(self) - if new_netvm is None: return @@ -324,19 +310,23 @@ class NetVMMixin(qubes.events.Emitter): self.create_qdb_entries() self.attach_network() - # TODO documentation - new_netvm.fire_event('net-domain-connect', self) - # FIXME handle in the above event? - new_netvm.reload_firewall_for_vm(self) + new_netvm.fire_event('net-domain-connect', self) # SEE: 1811 + + @qubes.events.handler('net-domain-connect') + def on_net_domain_connect(self, event, vm): + ''' Reloads the firewall config for vm ''' + # pylint: disable=unused-argument + self.reload_firewall_for_vm(vm) @qubes.events.handler('domain-qdb-create') def on_domain_qdb_create(self, event): - # TODO: fill firewall QubesDB entries (QubesOS/qubes-issues#1815) + ''' Fills the QubesDB with firewall entries. Not implemented ''' + # SEE: 1815 fill firewall QubesDB entries pass - # FIXME use event after creating Xen domain object, but before "resume" - @qubes.events.handler('firewall-changed') + @qubes.events.handler('firewall-changed', 'domain-spawn') def on_firewall_changed(self, event): + ''' Reloads the firewall if vm is running and has a NetVM assigned ''' # pylint: disable=unused-argument if self.is_running() and self.netvm: - self.netvm.reload_firewall_for_vm(self) + self.netvm.reload_firewall_for_vm(self) # pylint: disable=no-member diff --git a/qubes/vm/templatevm.py b/qubes/vm/templatevm.py index 611b21cb..ca97dec4 100644 --- a/qubes/vm/templatevm.py +++ b/qubes/vm/templatevm.py @@ -1,5 +1,28 @@ #!/usr/bin/python2 -O # vim: fileencoding=utf-8 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2014-2016 Wojtek Porczyk +# Copyright (C) 2016 Marek Marczykowski ) +# Copyright (C) 2016 Bahtiar `kalkin-` Gadimov +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +''' This module contains the TemplateVM implementation ''' import warnings @@ -24,6 +47,9 @@ class TemplateVM(QubesVM): @property def appvms(self): + ''' Returns a generator containing all domains based on the current + TemplateVM. + ''' for vm in self.app.domains: if hasattr(vm, 'template') and vm.template is self: yield vm